diff --git a/lib/influxdb-php-sdk/.gitignore b/lib/influxdb-php-sdk/.gitignore new file mode 100644 index 0000000000..b7f1a2785f --- /dev/null +++ b/lib/influxdb-php-sdk/.gitignore @@ -0,0 +1,3 @@ +vendor +tags +composer.lock diff --git a/lib/influxdb-php-sdk/.scrutinizer.yml b/lib/influxdb-php-sdk/.scrutinizer.yml new file mode 100644 index 0000000000..963a7af45c --- /dev/null +++ b/lib/influxdb-php-sdk/.scrutinizer.yml @@ -0,0 +1,3 @@ +tools: + external_code_coverage: + timeout: 600 diff --git a/lib/influxdb-php-sdk/CONTRIBUTING.md b/lib/influxdb-php-sdk/CONTRIBUTING.md new file mode 100644 index 0000000000..778ffc64c9 --- /dev/null +++ b/lib/influxdb-php-sdk/CONTRIBUTING.md @@ -0,0 +1,13 @@ +# Contributing to InfluxDB PHP SDK + +## Pull requests are always welcome + +Not sure if that typo is worth a pull request? Found a bug and know how to fix +it? Do it! We will appreciate it. Any significant improvement should be +documented as a GitHub issue before anybody starts working on it. + +We are always thrilled to receive pull requests. We do our best to process them +quickly. If your pull request is not accepted on the first try, don't get +discouraged! + +Just branch on master and prepare your PR. diff --git a/lib/influxdb-php-sdk/LICENSE b/lib/influxdb-php-sdk/LICENSE new file mode 100644 index 0000000000..db1e02eebe --- /dev/null +++ b/lib/influxdb-php-sdk/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Corley S.r.l. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lib/influxdb-php-sdk/README.md b/lib/influxdb-php-sdk/README.md new file mode 100644 index 0000000000..09662befed --- /dev/null +++ b/lib/influxdb-php-sdk/README.md @@ -0,0 +1,347 @@ +# InfluxDB PHP SDK + +[![Circle CI](https://circleci.com/gh/corley/influxdb-php-sdk/tree/master.svg?style=svg)](https://circleci.com/gh/corley/influxdb-php-sdk/tree/master) +[![Code Coverage](https://scrutinizer-ci.com/g/corley/influxdb-php-sdk/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/corley/influxdb-php-sdk/?branch=master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/corley/influxdb-php-sdk/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/corley/influxdb-php-sdk/?branch=master) +[![Latest Stable Version](https://poser.pugx.org/corley/influxdb-sdk/v/stable)](https://packagist.org/packages/corley/influxdb-sdk) +[![License](https://poser.pugx.org/corley/influxdb-sdk/license)](https://packagist.org/packages/corley/influxdb-sdk) + +Send metrics to InfluxDB and query for any data. + +This project support InfluxDB API `>= 0.9` - **For InfluxDB v0.8 checkout branch 0.3** + +Supported adapters: + + * HTTP + * UDP/IP + +## Install it + +Just use composer + +```json +{ + "require": { + // ... + "corley/influxdb-sdk": ">=0.4" + } +} +``` + +## Use it + +Add new points: + +```php +$client->mark("app-search", [ + "key" => "this is my search" +]); +``` + +Or use InfluxDB direct messages + +```php +$client->mark([ + "tags" => [ + "dc" => "eu-west-1", + ], + "points" => [ + [ + "measurement" => "instance", + "fields" => [ + "cpu" => 18.12, + "free" => 712423, + ], + ], + ] +]); +``` + +Retrieve existing points: + +```php +$results = $client->query('select * from "app-search"'); +``` + +## InfluxDB client adapters + +Actually we supports two network adapters + + * UDP/IP - in order to send data via UDP/IP (datagram) + * HTTP JSON - in order to send/retrieve using HTTP messages (connection oriented) + +### Using UDP/IP Adapter + +In order to use the UDP/IP adapter your must have PHP compiled with the `sockets` extension. + +**Usage** + +```php +$options = new Options(); +$adapter = new UdpAdapter($options); + +$client = new Client($adapter); +``` + +### Using HTTP Adapters + +Actually Guzzle is used as HTTP client library + +```php + [ + "name" => "InfluxDB\\Adapter\\GuzzleAdapter", + "options" => [ + // guzzle options + ], + ], + "options" => [ + "host" => "my.influx.domain.tld", + "db" => "mydb", + "retention_policy" => "myPolicy", + "tags" => [ + "env" => "prod", + "app" => "myApp", + ], + ] +]; +$client = \InfluxDB\ClientFactory::create($options); +``` + +Of course you can always use a DiC (eg `symfony/dependency-injection`) or your service manager in order to create +a valid client instance. + +### Query InfluxDB + +You can query the time series database using the query method. + +```php +$influx->query('select * from "mine"'); +``` + +You can query the database only if the adapter is queryable (implements +`QueryableInterface`), actually `GuzzleAdapter`. + +The adapter returns the json decoded body of the InfluxDB response, something +like: + +``` +array(1) { + 'results' => + array(1) { + [0] => + array(1) { + 'series' => + array(1) { + ... + } + } + } +} +``` + +## UDP/IP support + +As you know InfluxDB support UDP/IP with a "line protocol", that is a string +line, like: + +``` +cpu,region=us-west,env=prod,zone=1c cpu=18.12,free=712432 1257894000 +``` + +In order to simplify the SDK usage, you will use a single method signature +for both adapters, UDP/IP and HTTP: + +**Concise Format** + +```php +$client->mark("serie-name", [ + "power" => 124.21, + "voltage" => 12.4, +]); +``` + +**Extended Format** + +```php +$client->mark([ + "tags" => [ + "region" => "us-west", + "host" => "serverA", + "env" => "prod", + "target" => "servers", + "zone" => "1c", + ], + "time" => "2009-11-10T23:00:00Z", + "points" => [ + [ + "measurement" => "cpu", + "fields" => [ + "cpu" => 18.12, + "free" => 712432, + ], + ], + ], +]); +``` + +If you want to use the inline protocol directly you have to use the UDP/IP adapter directly + +``` +$udp = new UdpAdapter($options); +$udp->write("cpu,region=us-west,host=serverA,env=prod,target=servers,zone=1c cpu=18.12,free=712432 1257894000"); +``` + +## Database operations + +You can create, list or destroy databases using dedicated methods + +```php +$client->getDatabases(); // list all databases +$client->createDatabase("my.name"); // create a new database with name "my.name" +$client->deleteDatabase("my.name"); // delete an existing database with name "my.name" +``` + +Actually only queryable adapters can handle databases (implements the +`QueryableInterface`) + +## Global tags and retention policy + +You can set a set of default tags, that the SDK will add to your metrics: + +```php +$options = new Options(); +$options->setTags([ + "env" => "prod", + "region" => "eu-west-1", +]); +``` + +The SDK mark all point adding those tags. + +You can set a default retentionPolicy using + +``` +$options->setRetentionPolicy("myPolicy"); +``` + +In that way the SDK use that policy instead of `default` policy. + +## Proxies and InfluxDB + +If you proxy your InfluxDB typically you have a prefix in your endpoints. + +``` +$option->setHost("proxy.influxdb.tld"); +$option->setPort(80); +$option->setPrefix("/influxdb"); // your prefix is: /influxdb + +// final url will be: http://proxy.influxdb.tld:80/influxdb/write + +$client->mark("serie", ["data" => "my-data"]); +``` + +## Benchmarks + +Simple benchmarks executed on a Sony Vaio T13 (SVT1311C5E) + +### Adapters + +The impact using UDP/IP or HTTP adapters + +``` +Corley\Benchmarks\InfluxDB\AdapterEvent + Method Name Iterations Average Time Ops/second + ------------------------ ------------ -------------- ------------- + sendDataUsingHttpAdapter: [1,000 ] [0.0167509446144] [59.69813] + sendDataUsingUdpAdapter : [1,000 ] [0.0000905156136] [11,047.81773] +``` + +### Message to inline protocol conversion + +As you know the SDK will provide a single interface in order to send data to +InfluxDB (concise or expanded). + +The impact of message to inline protocol conversion is: + +``` +Corley\Benchmarks\InfluxDB\MessageToInlineProtocolEvent + Method Name Iterations Average Time Ops/second + ---------------------------------------------------- ------------ -------------- ------------- + convertMessageToInlineProtocolWithNoTags : [10,000 ] [0.0000343696594] [29,095.42942] + convertMessageToInlineProtocolWithGlobalTags : [10,000 ] [0.0000437165260] [22,874.64469] + convertMessageToInlineProtocolWithDifferentTagLevels: [10,000 ] [0.0000493728638] [20,254.04086] +``` + +### Query Builder + +Interested in a Query Builder? + +https://github.com/corley/dbal-influxdb + +Thanks to Doctrine DBAL (Abstract Layer) you can use the query builder + +``` +$qb = $conn->createQueryBuilder(); + +$qb->select("*") + ->from("cpu_load_short") + ->where("time = ?") + ->setParameter(0, 1434055562000000000); + +$data = $qb->execute(); +foreach ($data->fetchAll() as $element) { + // Use your element +} +``` + +```php +$config = new \Doctrine\DBAL\Configuration(); +//.. +$connectionParams = array( + 'dbname' => 'mydb', + 'user' => 'root', + 'password' => 'root', + 'host' => 'localhost', + 'port' => 8086, + "driverClass" => "Corley\\DBAL\\Driver\\InfluxDB", +); +$conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config); +``` + + +## FAQ + +### Add sockets support to your PHP + +To verify if you have the `sockets` extension just issue a: + +```bash +php -m | grep sockets +``` + +If you don't have the `sockets` extension, you can proceed in two ways: + + - Recompile your PHP whith the `--enable-sockets` flag + - Or just compile the `sockets` extension extracting it from the PHP source. + + 1. Download the source relative to the PHP version that you on from [here](https://github.com/php/php-src/releases) + 2. Enter in the `ext/sockets` directory + 3. Issue a `phpize && ./configure && make -j && sudo make install` + 4. Add `extension=sockets.so` to your php.ini + diff --git a/lib/influxdb-php-sdk/VERSION b/lib/influxdb-php-sdk/VERSION new file mode 100644 index 0000000000..8f0916f768 --- /dev/null +++ b/lib/influxdb-php-sdk/VERSION @@ -0,0 +1 @@ +0.5.0 diff --git a/lib/influxdb-php-sdk/benchmarks/Benchmarks/InfluxDB/AdapterEvent.php b/lib/influxdb-php-sdk/benchmarks/Benchmarks/InfluxDB/AdapterEvent.php new file mode 100644 index 0000000000..d3d37f6e5e --- /dev/null +++ b/lib/influxdb-php-sdk/benchmarks/Benchmarks/InfluxDB/AdapterEvent.php @@ -0,0 +1,54 @@ +setHost("localhost"); + $options->setPort(8086); + $options->setUsername("root"); + $options->setPassword("root"); + $options->setDatabase("tcp.test"); + + $client = new Client(new GuzzleAdapter(new HttpClient(), $options)); + $client->createDatabase("tcp.test"); + $client->createDatabase("udp.test"); + + $this->httpClient = $client; + + $opts = new Options(); + $opts->setPort(4444); + + $client = new Client(new UdpAdapter($opts)); + + $this->udpClient = $client; + } + + /** + * @iterations 1000 + */ + public function sendDataUsingHttpAdapter() + { + $this->httpClient->mark("metric.name", ["key" => "value"]); + } + + /** + * @iterations 1000 + */ + public function sendDataUsingUdpAdapter() + { + $this->udpClient->mark("metric.name", ["key" => "value"]); + } +} diff --git a/lib/influxdb-php-sdk/benchmarks/Benchmarks/InfluxDB/MessageToInlineProtocolEvent.php b/lib/influxdb-php-sdk/benchmarks/Benchmarks/InfluxDB/MessageToInlineProtocolEvent.php new file mode 100644 index 0000000000..964fdc34e6 --- /dev/null +++ b/lib/influxdb-php-sdk/benchmarks/Benchmarks/InfluxDB/MessageToInlineProtocolEvent.php @@ -0,0 +1,76 @@ + [ + [ + "measurement" => "vm-serie", + "fields" => [ + "cpu" => 18.12, + "free" => 712423, + ], + ], + ] + ] + ); + } + + /** + * @iterations 10000 + */ + public function convertMessageToInlineProtocolWithGlobalTags() + { + \InfluxDB\Adapter\message_to_inline_protocol( + [ + "tags" => [ + "dc" => "eu-west-1", + ], + "points" => [ + [ + "measurement" => "vm-serie", + "fields" => [ + "cpu" => 18.12, + "free" => 712423, + ], + ], + ] + ] + ); + } + + /** + * @iterations 10000 + */ + public function convertMessageToInlineProtocolWithDifferentTagLevels() + { + \InfluxDB\Adapter\message_to_inline_protocol( + [ + "tags" => [ + "dc" => "eu-west-1", + ], + "points" => [ + [ + "measurement" => "vm-serie", + "tags" => [ + "server" => "tc12", + ], + "fields" => [ + "cpu" => 18.12, + "free" => 712423, + ], + ], + ] + ] + ); + } +} diff --git a/lib/influxdb-php-sdk/circle.yml b/lib/influxdb-php-sdk/circle.yml new file mode 100644 index 0000000000..4ccd1fe06a --- /dev/null +++ b/lib/influxdb-php-sdk/circle.yml @@ -0,0 +1,51 @@ +machine: + timezone: + Europe/Rome + php: + version: 5.6.5 + +dependencies: + pre: + - sudo apt-get update + - sudo apt-get install nginx + - wget https://s3.amazonaws.com/influxdb/influxdb_latest_amd64.deb + - sudo useradd influxdb + - sudo dpkg -i influxdb_latest_amd64.deb + - sudo cp ./scripts/influxdb_conf.toml /etc/opt/influxdb/influxdb.conf + - sudo /opt/influxdb/influxd -config /etc/opt/influxdb/influxdb.conf: {background: true} + - sudo cp scripts/nginx_proxy.conf /etc/nginx/conf.d/proxy.conf + - sudo service nginx stop + - sleep 2 + - sudo service nginx start + - sed -i 's/^;//' ~/.phpenv/versions/$(phpenv global)/etc/conf.d/xdebug.ini + override: + - phpenv global 5.6.5 + - composer install --prefer-source --no-interaction + - phpenv global 5.5.21 + - composer install --prefer-source --no-interaction + +test: + override: + - phpenv global 5.6.5 + - composer require guzzlehttp/guzzle:~6 --prefer-source --no-interaction --update-with-dependencies + - composer show -i + - vendor/bin/phpunit --coverage-clover clover.xml + - composer require guzzlehttp/guzzle:~5 --prefer-source --no-interaction --update-with-dependencies + - composer show -i + - vendor/bin/phpunit + - composer require guzzlehttp/guzzle:~4 --prefer-source --no-interaction --update-with-dependencies + - composer show -i + - vendor/bin/phpunit + - phpenv global 5.5.21 + - composer require guzzlehttp/guzzle:~6 --prefer-source --no-interaction --update-with-dependencies + - composer show -i + - vendor/bin/phpunit + - composer require guzzlehttp/guzzle:~5 --prefer-source --no-interaction --update-with-dependencies + - composer show -i + - vendor/bin/phpunit + - composer require guzzlehttp/guzzle:~4 --prefer-source --no-interaction --update-with-dependencies + - composer show -i + - vendor/bin/phpunit + post: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover clover.xml diff --git a/lib/influxdb-php-sdk/composer.json b/lib/influxdb-php-sdk/composer.json new file mode 100644 index 0000000000..6abce12da7 --- /dev/null +++ b/lib/influxdb-php-sdk/composer.json @@ -0,0 +1,53 @@ +{ + "name": "corley/influxdb-sdk", + "license": "MIT", + "description": "Send your app metrics to InfluxDB", + "require": { + "php": ">=5.4", + "guzzlehttp/guzzle": "~4|~5|~6", + "zendframework/zend-stdlib": "~2", + "zendframework/zend-filter": "~2", + "zendframework/zend-servicemanager": "~2" + }, + "require-dev": { + "codeclimate/php-test-reporter": "dev-master", + "phpunit/phpunit": "4.*", + "athletic/athletic": "~0.1", + "ext-sockets": "*" + }, + "homepage": "http://www.corley.it/", + "keywords": ["influxdb", "udp", "sdk"], + "authors": [ + { + "name": "Gianluca Arbezzano", + "email": "gianluca.arbezzano@corley.it" + }, + { + "name": "Walter Dal Mut", + "email": "walter.dalmut@corley.it" + }, + { + "name": "Gabriele Mittica", + "email": "gabriele.mittica@corley.it" + } + ], + "autoload": { + "psr-4": { + "InfluxDB\\": ["./src/"], + "Corley\\": ["./benchmarks/"] + }, + "files": ["src/Adapter/helpers.php"] + }, + "autoload-dev": { + "psr-4": { + "InfluxDB\\": ["./tests/unit"], + "InfluxDB\\Integration\\": ["./tests/integration"] + } + }, + "suggest": { + "corley/dbal-influxdb": "This library help you to build query", + "fabpot/pimple": "Allows to prepare the client dependencies in order to require an initialized instance", + "zendframework/zend-servicemanager": "Use a service locator in order to prepare a valid instance", + "symfony/dependency-injection": "Prepare a valid instance via the symfony DiC" + } +} diff --git a/lib/influxdb-php-sdk/phpunit.xml.dist b/lib/influxdb-php-sdk/phpunit.xml.dist new file mode 100644 index 0000000000..36a9debbef --- /dev/null +++ b/lib/influxdb-php-sdk/phpunit.xml.dist @@ -0,0 +1,22 @@ + + + + tests/integration + + + tests/unit + + + + + src + + + diff --git a/lib/influxdb-php-sdk/scripts/influxdb_conf.toml b/lib/influxdb-php-sdk/scripts/influxdb_conf.toml new file mode 100644 index 0000000000..9c2568114b --- /dev/null +++ b/lib/influxdb-php-sdk/scripts/influxdb_conf.toml @@ -0,0 +1,74 @@ +[meta] + dir = "/var/opt/influxdb/meta" + hostname = "localhost" + bind-address = ":8088" + retention-autocreate = true + election-timeout = "1s" + heartbeat-timeout = "1s" + leader-lease-timeout = "500ms" + commit-timeout = "50ms" + +[data] + dir = "/var/opt/influxdb/data" + retention-auto-create = true + retention-check-enabled = true + retention-check-period = "10m0s" + retention-create-period = "45m0s" + +[cluster] + shard-writer-timeout = "5s" + +[retention] + enabled = true + check-interval = "10m0s" + +[admin] + enabled = true + bind-address = ":8083" + +[http] + enabled = true + bind-address = ":8086" + auth-enabled = false + log-enabled = true + write-tracing = false + pprof-enabled = false + +[collectd] + enabled = false + bind-address = "" + database = "" + typesdb = "" + +[opentsdb] + enabled = false + bind-address = "" + database = "" + retention-policy = "" + +[udp] + enabled = true + bind-address = ":4444" + database = "udp.test" + batch-size = 0 + batch-timeout = "10ns" + +[monitoring] + enabled = false + write-interval = "1m0s" + +[continuous_queries] + enabled = true + recompute-previous-n = 2 + recompute-no-older-than = "10m0s" + compute-runs-per-interval = 10 + compute-no-more-than = "2m0s" + +[hinted-handoff] + enabled = true + dir = "/var/opt/influxdb/hh" + max-size = 1073741824 + max-age = "168h0m0s" + retry-rate-limit = 0 + retry-interval = "1s" + diff --git a/lib/influxdb-php-sdk/scripts/nginx_proxy.conf b/lib/influxdb-php-sdk/scripts/nginx_proxy.conf new file mode 100644 index 0000000000..f92d10a30a --- /dev/null +++ b/lib/influxdb-php-sdk/scripts/nginx_proxy.conf @@ -0,0 +1,25 @@ +server { + listen 9000 default_server; + listen [::]:9000 default_server ipv6only=on; + + root /usr/share/nginx/html; + index index.html index.htm; + + server_name localhost; + + location / { + try_files $uri $uri/ =404; + } + + location /influxdb { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + + rewrite ^/influxdb/?(.*) /$1 break; + + proxy_pass http://localhost:8086; + proxy_redirect off; + } +} diff --git a/lib/influxdb-php-sdk/src/Adapter/AdapterAbstract.php b/lib/influxdb-php-sdk/src/Adapter/AdapterAbstract.php new file mode 100644 index 0000000000..342b40bda7 --- /dev/null +++ b/lib/influxdb-php-sdk/src/Adapter/AdapterAbstract.php @@ -0,0 +1,31 @@ +options = $options; + } + + public function getOptions() + { + return $this->options; + } + + protected function getMessageDefaults() + { + return [ + "database" => $this->getOptions()->getDatabase(), + "retentionPolicy" => $this->getOptions()->getRetentionPolicy(), + "tags" => $this->getOptions()->getTags(), + ]; + } + + abstract public function send(array $message); +} diff --git a/lib/influxdb-php-sdk/src/Adapter/GuzzleAdapter.php b/lib/influxdb-php-sdk/src/Adapter/GuzzleAdapter.php new file mode 100644 index 0000000000..a7f4ca7a93 --- /dev/null +++ b/lib/influxdb-php-sdk/src/Adapter/GuzzleAdapter.php @@ -0,0 +1,81 @@ +httpClient = $httpClient; + } + + public function send(array $message) + { + $message = array_replace_recursive($this->getMessageDefaults(), $message); + + if (!count($message["tags"])) { + unset($message["tags"]); + } + + $httpMessage = [ + "auth" => [$this->getOptions()->getUsername(), $this->getOptions()->getPassword()], + 'query' => [ + "db" => $message["database"], + "retentionPolicy" => $message["retentionPolicy"], + ], + "body" => message_to_inline_protocol($message) + ]; + + $endpoint = $this->getHttpSeriesEndpoint(); + return $this->httpClient->post($endpoint, $httpMessage); + } + + public function query($query) + { + $options = [ + "auth" => [$this->getOptions()->getUsername(), $this->getOptions()->getPassword()], + 'query' => [ + "q" => $query, + "db" => $this->getOptions()->getDatabase(), + ] + ]; + + return $this->get($options); + } + + private function get(array $httpMessage) + { + $endpoint = $this->getHttpQueryEndpoint(); + return json_decode($this->httpClient->get($endpoint, $httpMessage)->getBody(), true); + } + + protected function getHttpSeriesEndpoint() + { + return $this->getHttpEndpoint("write"); + } + + protected function getHttpQueryEndpoint() + { + return $this->getHttpEndpoint("query"); + } + + private function getHttpEndpoint($operation) + { + $url = sprintf( + "%s://%s:%d%s/%s", + $this->getOptions()->getProtocol(), + $this->getOptions()->getHost(), + $this->getOptions()->getPort(), + $this->getOptions()->getPrefix(), + $operation + ); + + return $url; + } +} diff --git a/lib/influxdb-php-sdk/src/Adapter/QueryableInterface.php b/lib/influxdb-php-sdk/src/Adapter/QueryableInterface.php new file mode 100644 index 0000000000..9ce4440dc3 --- /dev/null +++ b/lib/influxdb-php-sdk/src/Adapter/QueryableInterface.php @@ -0,0 +1,7 @@ +getMessageDefaults(), $message); + + if (array_key_exists("tags", $message)) { + $message["tags"] = array_replace_recursive($this->getOptions()->getTags(), $message["tags"]); + } + + $message = message_to_inline_protocol($message); + + $this->write($message); + } + + public function write($message) + { + // Create a handler in order to handle the 'Host is down' message + set_error_handler(function() { + // Suppress the error, this is the UDP adapter and if we can't send + // it then we shouldn't inturrupt their application. + }); + + $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + socket_sendto($socket, $message, strlen($message), 0, $this->getOptions()->getHost(), $this->getOptions()->getPort()); + socket_close($socket); + + // Remove our error handler. + restore_error_handler(); + } +} diff --git a/lib/influxdb-php-sdk/src/Adapter/WritableInterface.php b/lib/influxdb-php-sdk/src/Adapter/WritableInterface.php new file mode 100644 index 0000000000..8d5d7291c9 --- /dev/null +++ b/lib/influxdb-php-sdk/src/Adapter/WritableInterface.php @@ -0,0 +1,7 @@ +format("U") * 1e9); + } + + $lines = []; + foreach ($message["points"] as $point) { + $tags = array_key_exists("tags", $message) ? $message["tags"] : []; + if (array_key_exists("tags", $point)) { + $tags = array_replace_recursive($tags, $point["tags"]); + } + + if (!$tags) { + $lines[] = trim( + sprintf( + "%s %s %d", + $point["measurement"], list_to_string($point["fields"], true), $unixepoch + ) + ); + } else { + $lines[] = trim( + sprintf( + "%s,%s %s %d", + $point["measurement"], list_to_string($tags), list_to_string($point["fields"], true), $unixepoch + ) + ); + } + } + + return implode("\n", $lines); +} + +function list_to_string(array $elements, $escape = false) +{ + array_walk($elements, function(&$value, $key) use ($escape) { + if ($escape && is_string($value)) { + $value = "\"{$value}\""; + } + + $value = "{$key}={$value}"; + }); + + return implode(",", $elements); +} diff --git a/lib/influxdb-php-sdk/src/Client.php b/lib/influxdb-php-sdk/src/Client.php new file mode 100644 index 0000000000..121d398914 --- /dev/null +++ b/lib/influxdb-php-sdk/src/Client.php @@ -0,0 +1,67 @@ +adapter = $adapter; + } + + public function getAdapter() + { + return $this->adapter; + } + + public function mark($name, array $values = []) + { + if (!($this->getAdapter() instanceOf WritableInterface)) { + throw new \BadMethodCallException("You can write data to database only if the adapter supports it!"); + } + + $data = $name; + if (!is_array($name)) { + $data =[]; + + $data['points'][0]['measurement'] = $name; + $data['points'][0]['fields'] = $values; + } + + return $this->getAdapter()->send($data); + } + + public function query($query) + { + if (!($this->getAdapter() instanceOf QueryableInterface)) { + throw new \BadMethodCallException("You can query the database only if the adapter supports it!"); + } + + $return = $this->getAdapter()->query($query); + + return $return; + } + + public function getDatabases() + { + return $this->getAdapter()->query("show databases"); + } + + public function createDatabase($name) + { + return $this->getAdapter()->query("create database \"{$name}\""); + } + + public function deleteDatabase($name) + { + return $this->getAdapter()->query("drop database \"{$name}\""); + } +} diff --git a/lib/influxdb-php-sdk/src/ClientFactory.php b/lib/influxdb-php-sdk/src/ClientFactory.php new file mode 100644 index 0000000000..690dbbd329 --- /dev/null +++ b/lib/influxdb-php-sdk/src/ClientFactory.php @@ -0,0 +1,51 @@ + [ + "name" => false, + "options" => [], + ], + "options" => [], + ]; + $options = array_replace_recursive($defaultOptions, $options); + + $adapterOptions = new Options(); + + $hydrator = new ClassMethods(); + $hydrator->hydrate($options["options"], $adapterOptions); + + $adapter = null; + $adapterName = $options["adapter"]["name"]; + switch ($adapterName) { + case 'InfluxDB\\Adapter\\UdpAdapter': + $adapter = new $adapterName($adapterOptions); + break; + case 'InfluxDB\\Adapter\\GuzzleAdapter': + $adapter = new $adapterName(new GuzzleClient($options["adapter"]["options"]), $adapterOptions); + break; + default: + throw new \InvalidArgumentException("Missing adapter {$adapter}"); + } + + $client = new Client($adapter); + + return $client; + } +} diff --git a/lib/influxdb-php-sdk/src/Options.php b/lib/influxdb-php-sdk/src/Options.php new file mode 100644 index 0000000000..15b1581f24 --- /dev/null +++ b/lib/influxdb-php-sdk/src/Options.php @@ -0,0 +1,142 @@ +setHost("localhost"); + $this->setPort(8086); + $this->setUsername("root"); + $this->setPassword("root"); + $this->setProtocol("http"); + $this->setPrefix(""); + + $this->setRetentionPolicy("default"); + $this->setTags([]); + } + + public function getPrefix() + { + return $this->prefix; + } + + public function setPrefix($prefix) + { + $this->prefix = $prefix; + return $this; + } + + public function getTags() + { + return $this->tags; + } + + public function setTags($tags) + { + $this->tags = $tags; + return $this; + } + + public function getRetentionPolicy() + { + return $this->retentionPolicy; + } + + public function setRetentionPolicy($retentionPolicy) + { + $this->retentionPolicy = $retentionPolicy; + return $this; + } + + /** + * @return string + */ + public function getProtocol() + { + return $this->protocol; + } + + public function setProtocol($protocol) + { + $this->protocol = $protocol; + return $this; + } + + public function getHost() + { + return $this->host; + } + + public function setHost($host) + { + $this->host = $host; + return $this; + } + + public function getPort() + { + return $this->port; + } + + public function setPort($port) + { + $this->port = $port; + return $this; + } + + public function getUsername() + { + return $this->username; + } + + public function setUsername($username) + { + $this->username = $username; + return $this; + } + + public function getPassword() + { + return $this->password; + } + + public function setPassword($password) + { + $this->password = $password; + return $this; + } + + public function getDatabase() + { + return $this->database; + } + + public function setDatabase($database) + { + $this->database = $database; + return $this; + } +} diff --git a/lib/influxdb-php-sdk/tests/integration/Adapter/GuzzleAdapterTest.php b/lib/influxdb-php-sdk/tests/integration/Adapter/GuzzleAdapterTest.php new file mode 100644 index 0000000000..eed5132c1d --- /dev/null +++ b/lib/influxdb-php-sdk/tests/integration/Adapter/GuzzleAdapterTest.php @@ -0,0 +1,72 @@ +getClient()->createDatabase("tcp.test"); + + $options = new Options(); + $options->setPort(8086); + $options->setDatabase("tcp.test"); + + $http = new GuzzleHttpClient(); + $adapter = new GuzzleAdapter($http, $options); + + $adapter->send([ + "points" => [ + [ + "measurement" => "vm-serie", + "fields" => [ + "cpu" => 18.12, + "free" => 712423, + ], + ], + ] + ]); + + $this->assertSerieExists("tcp.test", "vm-serie"); + $this->assertSerieCount("tcp.test", "vm-serie", 1); + $this->assertValueExistsInSerie("tcp.test", "vm-serie", "cpu", 18.12); + $this->assertValueExistsInSerie("tcp.test", "vm-serie", "free", 712423); + } + + public function testWorksWithProxies() + { + $this->getClient()->createDatabase("proxy.test"); + + $options = new Options(); + $options->setPort(9000); + $options->setDatabase("proxy.test"); + $options->setPrefix("/influxdb"); + + $http = new GuzzleHttpClient(); + $adapter = new GuzzleAdapter($http, $options); + + $adapter->send([ + "points" => [ + [ + "measurement" => "vm-serie", + "fields" => [ + "cpu" => 18.12, + "free" => 712423, + ], + ], + ] + ]); + + $this->assertSerieExists("proxy.test", "vm-serie"); + $this->assertSerieCount("proxy.test", "vm-serie", 1); + $this->assertValueExistsInSerie("proxy.test", "vm-serie", "cpu", 18.12); + $this->assertValueExistsInSerie("proxy.test", "vm-serie", "free", 712423); + } +} diff --git a/lib/influxdb-php-sdk/tests/integration/Adapter/UdpAdapterTest.php b/lib/influxdb-php-sdk/tests/integration/Adapter/UdpAdapterTest.php new file mode 100644 index 0000000000..33c1c89044 --- /dev/null +++ b/lib/influxdb-php-sdk/tests/integration/Adapter/UdpAdapterTest.php @@ -0,0 +1,55 @@ +setPort(4444); + $adapter = new UdpAdapter($options); + + $this->getClient()->createDatabase("udp.test"); + + $adapter->write("cpu value=12.33 " . (int)(microtime(true)*1e9)); + + sleep(2); + + $this->assertSerieExists("udp.test", "cpu"); + $this->assertSerieCount("udp.test", "cpu", 1); + $this->assertValueExistsInSerie("udp.test", "cpu", "value", 12.33); + } + + public function testWriteSimplePointsUsingSendMethod() + { + $options = (new Options()) + ->setPort(4444); + $adapter = new UdpAdapter($options); + + $this->getClient()->createDatabase("udp.test"); + + $adapter->send([ + "retentionPolicy" => "default", + "points" => [ + [ + "measurement" => "mem", + "fields" => [ + "value" => 1233, + "with_string" => "this is a string", + ], + ], + ], + ]); + + sleep(2); + + $this->assertSerieExists("udp.test", "mem"); + $this->assertSerieCount("udp.test", "mem", 1); + $this->assertValueExistsInSerie("udp.test", "mem", "value", 1233); + $this->assertValueExistsInSerie("udp.test", "mem", "with_string", "this is a string"); + } +} diff --git a/lib/influxdb-php-sdk/tests/integration/ClientTest.php b/lib/influxdb-php-sdk/tests/integration/ClientTest.php new file mode 100644 index 0000000000..1b56232e6b --- /dev/null +++ b/lib/influxdb-php-sdk/tests/integration/ClientTest.php @@ -0,0 +1,166 @@ +getClient()->createDatabase("tcp.test"); + $this->getClient()->createDatabase("udp.test"); + } + + public function testSimpleMarkPublicSignature() + { + $options = new Options(); + $options->setDatabase("tcp.test"); + + $guzzleHttp = new GuzzleHttpClient(); + $adapter = new InfluxHttpAdapter($guzzleHttp, $options); + $client = new Client($adapter); + + $client->mark("vm", ["mark" => "element"]); + + $this->assertSerieExists("tcp.test", "vm"); + $this->assertSerieCount("tcp.test", "vm", 1); + $this->assertValueExistsInSerie("tcp.test", "vm", "mark", "element"); + } + + public function testDirectMessagesMarkPublicSignature() + { + $options = new Options(); + $options->setDatabase("tcp.test"); + + $guzzleHttp = new GuzzleHttpClient(); + $adapter = new InfluxHttpAdapter($guzzleHttp, $options); + $client = new Client($adapter); + + $client->mark([ + "database" => "tcp.test", + "retentionPolicy" => "default", + "points" => [ + [ + "measurement" => "tt", + "fields" => [ + "cpu" => 1, + "mem" => 2, + ], + ] + ], + ]); + + $this->assertSerieExists("tcp.test", "tt"); + $this->assertSerieCount("tcp.test", "tt", 1); + $this->assertValueExistsInSerie("tcp.test", "tt", "cpu", 1); + $this->assertValueExistsInSerie("tcp.test", "tt", "mem", 2); + } + + public function testListActiveDatabases() + { + $options = new Options(); + $guzzleHttp = new GuzzleHttpClient(); + $adapter = new InfluxHttpAdapter($guzzleHttp, $options); + $client = new Client($adapter); + + $databases = $client->getDatabases(); + + $this->assertCount(2, $databases["results"][0]["series"][0]["values"]); + } + + public function testCreateANewDatabase() + { + $options = new Options(); + $guzzleHttp = new GuzzleHttpClient(); + $adapter = new InfluxHttpAdapter($guzzleHttp, $options); + + $client = new Client($adapter); + + $client->createDatabase("walter"); + + $databases = $client->getDatabases(); + + $this->assertCount(3, $databases["results"][0]["series"][0]["values"]); + } + + public function testDropExistingDatabase() + { + $options = new Options(); + $guzzleHttp = new GuzzleHttpClient(); + $adapter = new InfluxHttpAdapter($guzzleHttp, $options); + + $client = new Client($adapter); + + $client->createDatabase("walter"); + $this->assertDatabasesCount(3); + + $client->deleteDatabase("walter"); + $this->assertDatabasesCount(2); + } + + /** + * Test that we handle socket problems correctly in the UDP + * adapter, and that they don't inturrupt the user's application. + * + * @group udp + */ + public function testReplicateIssue27() + { + $options = new \InfluxDB\Options(); + + // Configure options + $options->setHost('172.16.1.182'); + $options->setPort(4444); + $options->setDatabase('...'); + $options->setUsername('root'); + $options->setPassword('root'); + + $httpAdapter = new \InfluxDB\Adapter\UdpAdapter($options); + + $client = new \InfluxDB\Client($httpAdapter); + $client->mark("udp.test", ["mark" => "element"]); + } + + /** + * @group udp + */ + public function testWriteUDPPackagesToNoOne() + { + $options = new Options(); + $options->setHost("127.0.0.1"); + $options->setUsername("nothing"); + $options->setPassword("nothing"); + $options->setPort(64071); //This is a wrong port + + $adapter = new UdpAdapter($options); + $object = new Client($adapter); + + $object->mark("udp.test", ["mark" => "element"]); + } + + /** + * @group udp + */ + public function testWriteUDPPackagesToInvalidHostname() + { + $options = new Options(); + $options->setHost("www.test-invalid.this-is-not-a-tld"); + $options->setUsername("nothing"); + $options->setPassword("nothing"); + $options->setPort(15984); + + $adapter = new UdpAdapter($options); + $object = new Client($adapter); + + $object->mark("udp.test", ["mark" => "element"]); + } +} diff --git a/lib/influxdb-php-sdk/tests/integration/Framework/TestCase.php b/lib/influxdb-php-sdk/tests/integration/Framework/TestCase.php new file mode 100644 index 0000000000..6f6d151070 --- /dev/null +++ b/lib/influxdb-php-sdk/tests/integration/Framework/TestCase.php @@ -0,0 +1,91 @@ +options = new Options(); + $guzzleHttp = new GuzzleHttpClient(); + $adapter = new InfluxHttpAdapter($guzzleHttp, $options); + + $client = $this->client = new Client($adapter); + + $this->dropAll(); + } + + public function tearDown() + { + $this->dropAll(); + } + + private function dropAll() + { + $databases = $this->getClient()->getDatabases(); + if (array_key_exists("values", $databases["results"][0]["series"][0])) { + foreach ($databases["results"][0]["series"][0]["values"] as $database) { + $this->getClient()->deleteDatabase($database[0]); + } + } + } + + public function assertValueExistsInSerie($database, $serieName, $column, $value) + { + $this->getOptions()->setDatabase($database); + $body = $this->getClient()->query("select {$column} from \"{$serieName}\""); + + foreach ($body["results"][0]["series"][0]["values"] as $result) { + if ($result[1] == $value) { + return $this->assertTrue(true); + } + } + + return $this->fail("Missing value '{$value}'"); + } + + public function assertSerieCount($database, $serieName, $count) + { + $this->getOptions()->setDatabase($database); + $body = $this->getClient()->query("select * from \"{$serieName}\""); + + $this->assertCount(1, $body["results"][0]["series"][0]["values"]); + } + + public function assertSerieExists($database, $serieName) + { + $this->getOptions()->setDatabase($database); + $body = $this->getClient()->query("show measurements"); + + foreach ($body["results"][0]["series"][0]["values"] as $result) { + if ($result[0] == $serieName) { + return $this->assertTrue(true); + } + } + + return $this->fail("Missing serie with name '{$serieName}' in database '{$database}'"); + } + + public function assertDatabasesCount($count) + { + $databases = $this->client->getDatabases(); + $this->assertCount($count, $databases["results"][0]["series"][0]["values"]); + } + + public function getOptions() + { + return $this->options; + } + + public function getClient() + { + return $this->client; + } +} diff --git a/lib/influxdb-php-sdk/tests/unit/Adapter/GuzzleAdapterTest.php b/lib/influxdb-php-sdk/tests/unit/Adapter/GuzzleAdapterTest.php new file mode 100644 index 0000000000..57aaf5b724 --- /dev/null +++ b/lib/influxdb-php-sdk/tests/unit/Adapter/GuzzleAdapterTest.php @@ -0,0 +1,225 @@ +getMethod("getHttpSeriesEndpoint"); + $method->setAccessible(true); + + $endpoint = $method->invokeArgs($adapter, []); + $this->assertEquals($final, $endpoint); + } + + public function getWriteEndpoints() + { + return [ + ["http://localhost:9000/write", (new Options())->setHost("localhost")->setPort(9000)], + ["https://localhost:9000/write", (new Options())->setHost("localhost")->setPort(9000)->setProtocol("https")], + ["http://localhost:9000/influxdb/write", (new Options())->setHost("localhost")->setPort(9000)->setPrefix("/influxdb")], + ]; + } + + /** + * @group tcp + * @group proxy + * @dataProvider getQueryEndpoints + */ + public function testQueryEndpointGeneration($final, $options) + { + $guzzleHttp = new GuzzleHttpClient(); + $adapter = new InfluxHttpAdapter($guzzleHttp, $options); + + $reflection = new \ReflectionClass(get_class($adapter)); + $method = $reflection->getMethod("getHttpQueryEndpoint"); + $method->setAccessible(true); + + $endpoint = $method->invokeArgs($adapter, []); + $this->assertEquals($final, $endpoint); + } + + public function getQueryEndpoints() + { + return [ + ["http://localhost:9000/query", (new Options())->setHost("localhost")->setPort(9000)], + ["https://localhost:9000/query", (new Options())->setHost("localhost")->setPort(9000)->setProtocol("https")], + ["http://localhost:9000/influxdb/query", (new Options())->setHost("localhost")->setPort(9000)->setPrefix("/influxdb")], + ]; + } + + public function testMergeWithDefaultOptions() + { + $options = new Options(); + $options->setDatabase("db"); + $httpClient = $this->prophesize("GuzzleHttp\\Client"); + $httpClient->post(Argument::Any(), [ + "auth" => ["root", "root"], + "query" => [ + "db" => "db", + "retentionPolicy" => "default", + ], + "body" => null, + ])->shouldBeCalledTimes(1); + + $adapter = new InfluxHttpAdapter($httpClient->reveal(), $options); + $adapter->send([]); + } + + public function testAdapterPrepareJsonDataCorrectly() + { + $guzzleHttp = $this->prophesize("GuzzleHttp\Client"); + $guzzleHttp->post("http://localhost:8086/write", [ + "auth" => ["root", "root"], + "query" => [ + "db" => "db", + "retentionPolicy" => "default", + ], + "body" => 'tcp.test mark="element" 1257894000000000000', + ])->shouldBeCalledTimes(1); + $options = (new Options())->setDatabase("db"); + $adapter = new InfluxHttpAdapter($guzzleHttp->reveal(), $options); + + $adapter->send([ + "time" => "2009-11-10T23:00:00Z", + "points" => [ + [ + "measurement" => "tcp.test", + "fields" => [ + "mark" => "element" + ] + ] + ] + ]); + } + + public function testDefaultOptionOverwrite() + { + $options = new Options(); + $options->setDatabase("db"); + $httpClient = $this->prophesize("GuzzleHttp\\Client"); + $httpClient->post(Argument::Any(), [ + "auth" => ["root", "root"], + "query" => [ + "db" => "mydb", + "retentionPolicy" => "myPolicy", + ], + "body" => null, + ])->shouldBeCalledTimes(1); + + $adapter = new InfluxHttpAdapter($httpClient->reveal(), $options); + $adapter->send([ + "database" => "mydb", + "retentionPolicy" => "myPolicy" + ]); + } + + public function testEmptyTagsFieldIsRemoved() + { + $options = new Options(); + $options->setDatabase("db"); + $httpClient = $this->prophesize("GuzzleHttp\\Client"); + $httpClient->post(Argument::Any(), [ + "auth" => ["root", "root"], + "query" => [ + "db" => "db", + "retentionPolicy" => "default", + ], + "body" => 'tcp.test mark="element" 1257894000000000000', + ])->shouldBeCalledTimes(1); + + $adapter = new InfluxHttpAdapter($httpClient->reveal(), $options); + $adapter->send([ + "time" => "2009-11-10T23:00:00Z", + "points" => [ + [ + "measurement" => "tcp.test", + "fields" => [ + "mark" => "element" + ] + ] + ] + ]); + } + + public function testGlobalTagsAreInPlace() + { + $options = new Options(); + $options->setDatabase("db"); + $options->setTags([ + "dc" => "us-west", + ]); + $httpClient = $this->prophesize("GuzzleHttp\\Client"); + $httpClient->post(Argument::Any(), [ + "auth" => ["root", "root"], + "query" => [ + "db" => "db", + "retentionPolicy" => "default", + ], + "body" => 'tcp.test,dc=us-west mark="element" 1257894000000000000', + ])->shouldBeCalledTimes(1); + + $adapter = new InfluxHttpAdapter($httpClient->reveal(), $options); + $adapter->send([ + "time" => "2009-11-10T23:00:00Z", + "points" => [ + [ + "measurement" => "tcp.test", + "fields" => [ + "mark" => "element" + ] + ] + ] + ]); + } + + public function testTagsFieldIsMergedWithGlobalTags() + { + $options = new Options(); + $options->setDatabase("db"); + $options->setTags([ + "dc" => "us-west", + ]); + $httpClient = $this->prophesize("GuzzleHttp\\Client"); + $httpClient->post(Argument::Any(), [ + "auth" => ["root", "root"], + "query" => [ + "db" => "db", + "retentionPolicy" => "default", + ], + "body" => 'tcp.test,dc=us-west,region=us mark="element" 1257894000000000000', + ])->shouldBeCalledTimes(1); + + $adapter = new InfluxHttpAdapter($httpClient->reveal(), $options); + $adapter->send([ + "time" => "2009-11-10T23:00:00Z", + "tags" => ["region" => "us"], + "points" => [ + [ + "measurement" => "tcp.test", + "fields" => [ + "mark" => "element" + ] + ] + ] + ]); + } +} diff --git a/lib/influxdb-php-sdk/tests/unit/Adapter/HelpersTest.php b/lib/influxdb-php-sdk/tests/unit/Adapter/HelpersTest.php new file mode 100644 index 0000000000..cc3776413f --- /dev/null +++ b/lib/influxdb-php-sdk/tests/unit/Adapter/HelpersTest.php @@ -0,0 +1,23 @@ +assertEquals($result, list_to_string($message, $escape)); + } + + public function getElements() + { + return [ + [["one" => "two"], "one=two", false], + [["one" => "two"], "one=\"two\"", true], + [["one" => "two", "three" => "four"], "one=two,three=four", false], + [["one" => "two", "three" => "four"], "one=\"two\",three=\"four\"", true], + ]; + } +} diff --git a/lib/influxdb-php-sdk/tests/unit/Adapter/UdpAdapterTest.php b/lib/influxdb-php-sdk/tests/unit/Adapter/UdpAdapterTest.php new file mode 100644 index 0000000000..68271bac49 --- /dev/null +++ b/lib/influxdb-php-sdk/tests/unit/Adapter/UdpAdapterTest.php @@ -0,0 +1,299 @@ +getMockBuilder("InfluxDB\Adapter\UdpAdapter") + ->setConstructorArgs([new Options()]) + ->setMethods(["write"]) + ->getMock(); + $object->expects($this->once()) + ->method("write") + ->with($response); + + $object->send($input); + } + + public function getMessages() + { + return [ + [ + [ + "time" => "2009-11-10T23:00:00Z", + "points" => [ + [ + "measurement" => "cpu", + "fields" => [ + "value" => 1, + ], + ], + ], + ], + "cpu value=1 1257894000000000000" + ], + [ + [ + "time" => "2009-11-10T23:00:00Z", + "points" => [ + [ + "measurement" => "cpu", + "fields" => [ + "value" => 1, + "string" => "escape", + ], + ], + ], + ], + "cpu value=1,string=\"escape\" 1257894000000000000" + ], + [ + [ + "tags" => [ + "region" => "us-west", + "host" => "serverA", + "env" => "prod", + "target" => "servers", + "zone" => "1c", + ], + "time" => "2009-11-10T23:00:00Z", + "points" => [ + [ + "measurement" => "cpu", + "fields" => [ + "cpu" => 18.12, + "free" => 712432, + ], + ], + ], + ], + "cpu,region=us-west,host=serverA,env=prod,target=servers,zone=1c cpu=18.12,free=712432 1257894000000000000" + ], + [ + [ + "tags" => [ + "region" => "us-west", + "host" => "serverA", + "env" => "prod", + "target" => "servers", + "zone" => "1c", + ], + "time" => "2009-11-10T23:00:00Z", + "points" => [ + [ + "measurement" => "cpu", + "fields" => [ + "cpu" => 18.12, + ], + ], + [ + "measurement" => "mem", + "fields" => [ + "free" => 712432, + ], + ], + ], + ], + <<setDatabase("test"); + $adapter = $this->getMockBuilder("InfluxDB\\Adapter\\UdpAdapter") + ->setConstructorArgs([$options]) + ->setMethods(["write", "generateTimeInNanoSeconds"]) + ->getMock(); + + $adapter->expects($this->any()) + ->method("generateTimeInNanoSeconds") + ->will($this->returnValue(1245)); + + $adapter->expects($this->once()) + ->method("write") + ->with($this->matchesRegularExpression("/udp.test mark=\"element\" \d+/i")); + + $adapter->send([ + "points" => [ + [ + "measurement" => "udp.test", + "fields" => [ + "mark" => "element" + ] + ] + ] + ]); + } + + /** + * @group udp + */ + public function testSendMultipleMeasurementWithUdpIp() + { + $options = (new Options())->setDatabase("test"); + $adapter = $this->getMockBuilder("InfluxDB\\Adapter\\UdpAdapter") + ->setConstructorArgs([$options]) + ->setMethods(["write", "generateTimeInNanoSeconds"]) + ->getMock(); + + $adapter->expects($this->any()) + ->method("generateTimeInNanoSeconds") + ->will($this->onConsecutiveCalls(1245, 1246)); + + $adapter->expects($this->once()) + ->method("write") + ->with($this->matchesRegularExpression(<<send([ + "points" => [ + [ + "measurement" => "mem", + "fields" => [ + "free" => 712423, + ], + ], + [ + "measurement" => "cpu", + "fields" => [ + "cpu" => 18.12, + ], + ], + ] + ]); + } + + /** + * @group udp + */ + public function testFillWithGlobalTags() + { + $options = (new Options()) + ->setDatabase("test") + ->setTags(["dc" => "eu-west"]); + $adapter = $this->getMockBuilder("InfluxDB\\Adapter\\UdpAdapter") + ->setConstructorArgs([$options]) + ->setMethods(["write"]) + ->getMock(); + + $adapter->expects($this->once()) + ->method("write") + ->with($this->matchesRegularExpression("/mem,dc=eu-west free=712423 \d+/i")); + + $adapter->send([ + "points" => [ + [ + "measurement" => "mem", + "fields" => [ + "free" => 712423, + ], + ], + ] + ]); + } + + /** + * @group udp + */ + public function testMergeGlobalTags() + { + $options = (new Options()) + ->setDatabase("test") + ->setTags(["dc" => "eu-west"]); + $adapter = $this->getMockBuilder("InfluxDB\\Adapter\\UdpAdapter") + ->setConstructorArgs([$options]) + ->setMethods(["write", "generateTimeInNanoSeconds"]) + ->getMock(); + + $adapter->expects($this->any()) + ->method("generateTimeInNanoSeconds") + ->will($this->returnValue(1245)); + + $adapter->expects($this->once()) + ->method("write") + ->with($this->matchesRegularExpression(<<send([ + "tags" => [ + "region" => "eu-west-1", + ], + "points" => [ + [ + "measurement" => "mem", + "fields" => [ + "free" => 712423, + ], + ], + ] + ]); + } + + /** + * @group udp + */ + public function testMergeFullTagsPositions() + { + $options = (new Options()) + ->setDatabase("test") + ->setTags(["dc" => "eu-west"]); + $adapter = $this->getMockBuilder("InfluxDB\\Adapter\\UdpAdapter") + ->setConstructorArgs([$options]) + ->setMethods(["write", "generateTimeInNanoSeconds"]) + ->getMock(); + + $adapter->expects($this->any()) + ->method("generateTimeInNanoSeconds") + ->will($this->returnValue(1245)); + + $adapter->expects($this->once()) + ->method("write") + ->with($this->matchesRegularExpression(<<send([ + "tags" => [ + "region" => "eu-west-1", + ], + "points" => [ + [ + "measurement" => "mem", + "tags" => [ + "location" => "ireland", + ], + "fields" => [ + "free" => 712423, + ], + ], + ] + ]); + } +} diff --git a/lib/influxdb-php-sdk/tests/unit/ClientFactoryTest.php b/lib/influxdb-php-sdk/tests/unit/ClientFactoryTest.php new file mode 100644 index 0000000000..8143b00bf0 --- /dev/null +++ b/lib/influxdb-php-sdk/tests/unit/ClientFactoryTest.php @@ -0,0 +1,135 @@ + ["name" => "UdpAdapter"]]); + } + + /** + * @group factory + * @group udp + */ + public function testCreateUdpClient() + { + $options = [ + "adapter" => [ + "name" => "InfluxDB\\Adapter\\UdpAdapter", + ], + "options" => [ + "host" => "127.0.0.1", + "username" => "user", + "password" => "pass", + ], + ]; + + $client = ClientFactory::create($options); + $this->assertInstanceOf("InfluxDB\\Client", $client); + + $this->assertInstanceOf("InfluxDB\\Adapter\\UdpAdapter", $client->getAdapter()); + $this->assertEquals("127.0.0.1", $client->getAdapter()->getOptions()->getHost()); + $this->assertEquals("user", $client->getAdapter()->getOptions()->getUsername()); + $this->assertEquals("pass", $client->getAdapter()->getOptions()->getPassword()); + } + + /** + * @group factory + * @group tcp + * @dataProvider getHttpAdapters + */ + public function testCreateTcpClient($adapter) + { + $options = [ + "adapter" => [ + "name" => $adapter, + ], + "options" => [ + "host" => "127.0.0.1", + "username" => "user", + "password" => "pass", + ], + ]; + + $client = ClientFactory::create($options); + $this->assertInstanceOf("InfluxDB\\Client", $client); + + $this->assertInstanceOf($adapter, $client->getAdapter()); + $this->assertEquals("127.0.0.1", $client->getAdapter()->getOptions()->getHost()); + $this->assertEquals("user", $client->getAdapter()->getOptions()->getUsername()); + $this->assertEquals("pass", $client->getAdapter()->getOptions()->getPassword()); + } + + /** + * @group factory + * @dataProvider getHttpAdapters + */ + public function testCreateTcpClientWithAllOptions($adapter) + { + $options = [ + "adapter" => [ + "name" => $adapter, + ], + "options" => [ + "host" => "127.0.0.1", + "username" => "user", + "password" => "pass", + "retention_policy" => "too_many_data", + "tags" => [ + "region" => "eu", + "env" => "prod", + ], + ], + ]; + + $client = ClientFactory::create($options); + $this->assertInstanceOf("InfluxDB\\Client", $client); + + $this->assertInstanceOf($adapter, $client->getAdapter()); + $this->assertEquals("127.0.0.1", $client->getAdapter()->getOptions()->getHost()); + $this->assertEquals("user", $client->getAdapter()->getOptions()->getUsername()); + $this->assertEquals("pass", $client->getAdapter()->getOptions()->getPassword()); + $this->assertEquals(["region" => "eu", "env" => "prod"], $client->getAdapter()->getOptions()->getTags()); + $this->assertEquals("too_many_data", $client->getAdapter()->getOptions()->getRetentionPolicy()); + } + + public function getHttpAdapters() + { + return [ + ["InfluxDB\\Adapter\\GuzzleAdapter"], + ]; + } + + /** + * @expectedException InvalidArgumentException + * @dataProvider getInvalidClasses + */ + public function testInvalidProviderThrowsException($class) + { + $client = ClientFactory::create([ + "adapter" => [ + "name" => $class, + ], + ]); + } + + public function getInvalidClasses() + { + return [["InvalidClass"],["stdClass"]]; + } +} diff --git a/lib/influxdb-php-sdk/tests/unit/ClientTest.php b/lib/influxdb-php-sdk/tests/unit/ClientTest.php new file mode 100644 index 0000000000..da6586ec52 --- /dev/null +++ b/lib/influxdb-php-sdk/tests/unit/ClientTest.php @@ -0,0 +1,83 @@ +prophesize("InfluxDB\\Adapter\\WritableInterface"); + $mock->send([ + "points" => [ + [ + "measurement" => "tcp.test", + "fields" => [ + "mark" => "element" + ] + ] + ] + ])->shouldBeCalledTimes(1); + + $object = new Client($mock->reveal()); + $object->mark("tcp.test", ["mark" => "element"]); + } + + public function testWriteDirectMessages() + { + $mock = $this->prophesize("InfluxDB\\Adapter\\WritableInterface"); + $mock->send([ + "tags" => [ + "dc" => "eu-west-1", + ], + "points" => [ + [ + "measurement" => "vm-serie", + "fields" => [ + "cpu" => 18.12, + "free" => 712423, + ] + ] + ] + ])->shouldBeCalledTimes(1); + $object = new Client($mock->reveal()); + + $object->mark([ + "tags" => [ + "dc" => "eu-west-1", + ], + "points" => [ + [ + "measurement" => "vm-serie", + "fields" => [ + "cpu" => 18.12, + "free" => 712423, + ], + ], + ] + ]); + } + + /** + * @expectedException BadMethodCallException + */ + public function testNeedWritableInterfaceDuringMark() + { + $client = new Client(new \stdClass()); + $client->mark("OK", []); + } + + /** + * @expectedException BadMethodCallException + */ + public function testNeedQueryableInterfaceDuringQuery() + { + $client = new Client(new \stdClass()); + $client->query("OK", []); + } +}