diff --git a/lib/influxdb-php/.gitignore b/lib/influxdb-php/.gitignore new file mode 100644 index 0000000000..2826a22d79 --- /dev/null +++ b/lib/influxdb-php/.gitignore @@ -0,0 +1,6 @@ +# Created by .gitignore support plugin (hsz.mobi) + +vendor/ +.idea +build/ +test.php \ No newline at end of file diff --git a/lib/influxdb-php/.travis.yml b/lib/influxdb-php/.travis.yml new file mode 100644 index 0000000000..c71c55677b --- /dev/null +++ b/lib/influxdb-php/.travis.yml @@ -0,0 +1,14 @@ +language: php +php: + - 5.5 + - 5.6 + +install: + - composer install + +addons: + code_climate: + repo_token: 5371d86d298d66eb9007cc8de462d7063e58f6dd85e430928834736edee479a9 + +after_script: + - vendor/bin/test-reporter diff --git a/lib/influxdb-php/CONTRIBUTE.md b/lib/influxdb-php/CONTRIBUTE.md new file mode 100644 index 0000000000..d9348f142d --- /dev/null +++ b/lib/influxdb-php/CONTRIBUTE.md @@ -0,0 +1,50 @@ +/* CONTRIBUTE */ + +This is the contribute.md of influxdb-php. Great to have you here. + +Here are a few ways you can help make this project better. + +# Contribute.md + +## Team members + +* Stephen "TheCodeAssassin" Hoogendijk +* Daniel "danibrutal" Martinez + +## Helping out + +We appreciate any efforts to help us writing this library. You can contribute in any of the following ways: + +* Documentation +* Unit tests +* New features +* Bug fixed +* Reviewing pull requests + +## Guidelines + +In order for your pull requests to get accepted we hold all the code to the following criteria: + +* PSR-1/PSR-2 compliant +* Do not use left hand conditions such as false == $something +* New features need to be documented +* Breaking changes should be well highlighted and explained in the PR +* Only short-array syntax should be used for arrays +* Use regular string concatenation for single-variable strings, and sprintf for multi-variable strings +* Do not align variable assignments + +The following is optional but encouraged: + +* Code should be documented +* Code should be unit tested +* Do not write conditions like false === $something, rather $something === false. + +## Special thanks + +We would like to thank the following people for helping to make this library possible: + +* InfluxDB Community +* LeaseWeb Technologies +* Paul Dix +* Sean Beckett +* CentaurWarchief \ No newline at end of file diff --git a/lib/influxdb-php/LICENSE b/lib/influxdb-php/LICENSE new file mode 100644 index 0000000000..38ee24919e --- /dev/null +++ b/lib/influxdb-php/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 InfluxDB + +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/README.md b/lib/influxdb-php/README.md new file mode 100644 index 0000000000..06f60943af --- /dev/null +++ b/lib/influxdb-php/README.md @@ -0,0 +1,341 @@ +# influxdb-php +## InfluxDB client library for PHP +[![Build Status](https://travis-ci.org/influxdb/influxdb-php.svg?branch=master)](https://travis-ci.org/influxdb/influxdb-php) +[![Code Climate](https://codeclimate.com/github/influxdb/influxdb-php/badges/gpa.svg)](https://codeclimate.com/github/influxdb/influxdb-php) +[![Test Coverage](https://codeclimate.com/github/influxdb/influxdb-php/badges/coverage.svg)](https://codeclimate.com/github/influxdb/influxdb-php/coverage) + +### Overview + +A easy to use library for using InfluxDB with PHP. + +The influxdb-php library was created to have php port of the python influxdb client. +This way there will be a common abstraction library between different programming languages. + +### Installation + +Installation can be done with composer: + +composer require influxdb/influxdb-php:dev-master + +### NOTE for PHP 5.3 and PHP 5.4 users + +If you use either PHP 5.3 and PHP 5.4, the 0.1.x release is still supported (bug fixes and new release fixes). +The 0.1.x branch will work on PHP 5.3 and PHP 5.4 but doesn't contain all the features that the 1.0.0 release has such as UDP support. + +### Getting started + +Initialize a new client object: + +```php + +$client = new InfluxDB\Client($host, $port); + + +``` + +This will create a new client object which you can use to read and write points to InfluxDB. + +It's also possible to create a client from a DSN (Data Source Name): + +```php + + // directly get the database object + $database = InfluxDB\Client::fromDSN(sprintf('influxdb://user:pass@%s:%s/%s', $host, $port, $dbname)); + + // get the client to retrieve other databases + $client = $database->getClient(); + +``` + +### Reading + +To fetch records from InfluxDB you can do a query directly on a database: + +```php + + // fetch the database + $database = $client->selectDB('influx_test_db'); + + // executing a query will yield a resultset object + $result = $database->query('select * from test_metric LIMIT 5'); + + // get the points from the resultset yields an array + $points = $result->getPoints(); + +``` + +It's also possible to use the QueryBuilder object. This is a class that simplifies the process of building queries. + +```php + + // retrieve points with the query builder + $result = $database->getQueryBuilder() + ->select('cpucount') + ->from('test_metric') + ->limit(2) + ->getResultSet() + ->getPoints(); + + + // get the query from the QueryBuilder + $query = $database->getQueryBuilder() + ->select('cpucount') + ->from('test_metric') + ->getQuery(); + +``` + +### Writing data + +Writing data is done by providing an array of points to the writePoints method on a database: + +```php + + // create an array of points + $points = array( + new Point( + 'test_metric', // name of the measurement + 0.64, // the measurement value + ['host' => 'server01', 'region' => 'us-west'], // optional tags + ['cpucount' => 10], // optional additional fields + 1435255849 // Time precision has to be set to seconds! + ), + new Point( + 'test_metric', // name of the measurement + 0.84, // the measurement value + ['host' => 'server01', 'region' => 'us-west'], // optional tags + ['cpucount' => 10], // optional additional fields + 1435255849 // Time precision has to be set to seconds! + ) + ); + + // we are writing unix timestamps, which have a second precision + $result = $database->writePoints($points, Database::PRECISION_SECONDS); + +``` + +It's possible to add multiple [fields](https://influxdb.com/docs/v0.9/concepts/key_concepts.html) when writing +measurements to InfluxDB. The point class allows one to easily write data in batches to influxDB. + +The name of a measurement and the value are mandatory. Additional fields, tags and a timestamp are optional. +InfluxDB takes the current time as the default timestamp. + +You can also write multiple fields to a measurement without specifying a value: + +```php + $points = [ + new Point( + 'instance', // the name of the measurement + null, // measurement value + ['host' => 'server01', 'region' => 'us-west'], // measurement tags + ['cpucount' => 10, 'free' => 1], // measurement fields + exec('date +%s%N') // timestamp in nanoseconds + ), + new Point( + 'instance', // the name of the measurement + null, // measurement value + ['host' => 'server01', 'region' => 'us-west'], // measurement tags + ['cpucount' => 10, 'free' => 2], // measurement fields + exec('date +%s%N') // timestamp in nanoseconds + ) + ]; + +``` + +#### Writing data using udp + +First, set your InfluxDB host to support incoming UDP sockets: + +```ini +[udp] + enabled = true + bind-address = ":4444" + database = "test_db" +``` + +Then, configure the UDP driver in the client: + +```php + + // set the UDP driver in the client + $client->setDriver(new \InfluxDB\Driver\UDP($client->getHost(), 4444)); + + $points = [ + new Point( + 'test_metric', + 0.84, + ['host' => 'server01', 'region' => 'us-west'], + ['cpucount' => 10], + exec('date +%s%N') // this will produce a nanosecond timestamp in Linux operating systems + ) + ]; + + // now just write your points like you normally would + $result = $database->writePoints($points); +``` + +Or simply use a DSN (Data Source Name) to send metrics using UDP: + +```php + + // get a database object using a DSN (Data Source Name) + $database = \InfluxDB\Client::fromDSN('udp+influxdb://username:pass@localhost:4444/test123'); + + // write your points + $result = $database->writePoints($points); +``` + +*Note:* It is import to note that precision will be *ignored* when you use UDP. You should always use nanosecond +precision when writing data to InfluxDB using UDP. + +#### Timestamp precision + +It's important to provide the correct precision when adding a timestamp to a Point object. This is because +if you specify a timestamp in seconds and the default (nanosecond) precision is set; the entered timestamp will be invalid. + +```php + // Points will require a nanosecond precision (this is default as per influxdb standard) + $newPoints = $database->writePoints($points); + + // Points will require second precision + $newPoints = $database->writePoints($points, Database::PRECISION_SECONDS); + + // Points will require microsecond precision + $newPoints = $database->writePoints($points, Database::PRECISION_MICROSECONDS); +``` + +### Creating databases + +When creating a database a default retention policy is added. This retention policy does not have a duration +so the data will be flushed with the memory. + +This library makes it easy to provide a retention policy when creating a database: + +```php + + // create the client + $client = new \InfluxDB\Client($host, $port, '', ''); + + // select the database + $database = $client->selectDB('influx_test_db'); + + // create the database with a retention policy + $result = $database->create(new RetentionPolicy('test', '5d', 1, true)); + + // check if a database exists then create it if it doesn't + $database = $client->selectDB('test_db'); + + if (!$database->exists()) { + $database->create(new RetentionPolicy('test', '1d', 2, true)); + } + +``` + +You can also alter retention policies: + +```php + $database->alterRetentionPolicy(new RetentionPolicy('test', '2d', 5, true)); +``` + +and list them: + +```php + $result = $database->listRetentionPolicies(); +``` + +You can add more retention policies to a database: + +```php + $result = $database->createRetentionPolicy(new RetentionPolicy('test2', '30d', 1, true)); +``` + +### Client functions + +Some functions are too general for a database. So these are available in the client: + +```php + + // list users + $result = $client->listUsers(); + + // list databases + $result = $client->listDatabases(); +``` + +### Admin functionality + +You can use the client's $client->admin functionality to administer InfluxDB via the API. + +```php + // add a new user without privileges + $client->admin->createUser('testuser123', 'testpassword'); + + // add a new user with ALL cluster-wide privileges + $client->admin->createUser('admin_user', 'password', \InfluxDB\Client\Admin::PRIVILEGE_ALL); + + // drop user testuser123 + $client->admin->dropUser('testuser123'); +``` + +List all the users: + +```php + // show a list of all users + $results = $client->admin->showUsers(); + + // show users returns a ResultSet object + $users = $results->getPoints(); +``` + +#### Granting and revoking privileges + +Granting permissions can be done on both the database level and cluster-wide. +To grant a user specific privileges on a database, provide a database object or a database name. + +```php + + // grant permissions using a database object + $database = $client->selectDB('test_db'); + $client->admin->grant(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123', $database); + + // give user testuser123 read privileges on database test_db + $client->admin->grant(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123', 'test_db'); + + // revoke user testuser123's read privileges on database test_db + $client->admin->revoke(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123', 'test_db'); + + // grant a user cluster-wide privileges + $client->admin->grant(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123'); + + // Revoke an admin's cluster-wide privileges + $client->admin->revoke(\InfluxDB\Client\Admin::PRIVILEGE_ALL, 'admin_user'); + +``` + +## Todo + +* More unit tests +* Increase documentation (wiki?) +* Add more features to the query builder +* Add validation to RetentionPolicy + + +## Changelog + +####1.0.1 +* Added support for authentication in the guzzle driver +* Added admin functionality + +####1.0.0 +* -BREAKING CHANGE- Dropped support for PHP 5.3 and PHP 5.4 +* Allowing for custom drivers +* UDP support + +####0.1.2 +* Added exists method to Database class +* Added time precision to database class + +####0.1.1 +* Merged repository to influxdb/influxdb-php +* Added unit test for createRetentionPolicy +* -BREAKING CHANGE- changed $client->db to $client->selectDB diff --git a/lib/influxdb-php/composer.json b/lib/influxdb-php/composer.json new file mode 100644 index 0000000000..7af0fafe8f --- /dev/null +++ b/lib/influxdb-php/composer.json @@ -0,0 +1,47 @@ +{ + "name": "influxdb/influxdb-php", + "description": "InfluxDB client library for PHP", + "license": "MIT", + "keywords": [ + "influxdb", + "client", + "time series", + "influxdb client", + "influxdb class", + "influxdb library" + ], + "minimum-stability": "dev", + "authors": [ + { + "name": "Stephen Hoogendijk", + "email": "stephen@tca0.nl" + }, + { + "name": "Daniel Martinez", + "email": "danimartcas@hotmail.com" + } + ], + "require": { + "php": ">=5.5", + "guzzlehttp/guzzle": "6.*", + "symfony/event-dispatcher": "2.*" + }, + "require-dev": { + "codeclimate/php-test-reporter": "0.*", + "symfony/config": "~2.8", + "symfony/console": "~2.8", + "symfony/filesystem": "~2.8", + "symfony/stopwatch": "~2.8", + "symfony/yaml": "~2.8" + }, + "autoload": { + "psr-4": { + "InfluxDB\\": "src/InfluxDB" + } + }, + "autoload-dev": { + "psr-4": { + "InfluxDB\\Test\\": "tests" + } + } +} diff --git a/lib/influxdb-php/composer.lock b/lib/influxdb-php/composer.lock new file mode 100644 index 0000000000..a2fb5ba284 --- /dev/null +++ b/lib/influxdb-php/composer.lock @@ -0,0 +1,812 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "ee3f2e2a4eb50f8de36c6efbff37fe71", + "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "1879fbe853b0c64d109e369c7aeff09849e62d1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1879fbe853b0c64d109e369c7aeff09849e62d1e", + "reference": "1879fbe853b0c64d109e369c7aeff09849e62d1e", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "~1.0", + "guzzlehttp/psr7": "~1.1", + "php": ">=5.5.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0", + "psr/log": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2015-07-10 20:04:21" + }, + { + "name": "guzzlehttp/promises", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "f596be052ef429a16b2f640812fcf84392dd38f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f596be052ef429a16b2f640812fcf84392dd38f7", + "reference": "f596be052ef429a16b2f640812fcf84392dd38f7", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2015-06-30 16:39:54" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "af0e1758de355eb113917ad79c3c0e3604bce4bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/af0e1758de355eb113917ad79c3c0e3604bce4bd", + "reference": "af0e1758de355eb113917ad79c3c0e3604bce4bd", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "PSR-7 message implementation", + "keywords": [ + "http", + "message", + "stream", + "uri" + ], + "time": "2015-06-24 19:55:15" + }, + { + "name": "psr/http-message", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", + "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2015-05-04 20:22:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/EventDispatcher.git", + "reference": "d7246885b7fe4cb5a2786bda34362d2f0e40b730" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/d7246885b7fe4cb5a2786bda34362d2f0e40b730", + "reference": "d7246885b7fe4cb5a2786bda34362d2f0e40b730", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.0,>=2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/phpunit-bridge": "~2.7|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2015-06-24 15:32:32" + } + ], + "packages-dev": [ + { + "name": "codeclimate/php-test-reporter", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/codeclimate/php-test-reporter.git", + "reference": "418ae782307841ac50fe26daa4cfe04520b0de9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codeclimate/php-test-reporter/zipball/418ae782307841ac50fe26daa4cfe04520b0de9c", + "reference": "418ae782307841ac50fe26daa4cfe04520b0de9c", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3", + "satooshi/php-coveralls": "0.6.*", + "symfony/console": ">=2.0" + }, + "require-dev": { + "ext-xdebug": "*", + "phpunit/phpunit": "3.7.*@stable" + }, + "bin": [ + "composer/bin/test-reporter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "CodeClimate\\Component": "src/", + "CodeClimate\\Bundle": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Code Climate", + "email": "hello@codeclimate.com", + "homepage": "https://codeclimate.com" + } + ], + "description": "PHP client for reporting test coverage to Code Climate", + "homepage": "https://github.com/codeclimate/php-test-reporter", + "keywords": [ + "codeclimate", + "coverage" + ], + "time": "2015-04-18 14:43:54" + }, + { + "name": "guzzle/guzzle", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "b3f5050cb6270c7a728a0b74ac2de50a262b3e02" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/b3f5050cb6270c7a728a0b74ac2de50a262b3e02", + "reference": "b3f5050cb6270c7a728a0b74ac2de50a262b3e02", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2015-04-29 17:06:53" + }, + { + "name": "psr/log", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2012-12-21 11:40:51" + }, + { + "name": "satooshi/php-coveralls", + "version": "v0.6.1", + "source": { + "type": "git", + "url": "https://github.com/satooshi/php-coveralls.git", + "reference": "dd0df95bd37a7cf5c5c50304dfe260ffe4b50760" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/satooshi/php-coveralls/zipball/dd0df95bd37a7cf5c5c50304dfe260ffe4b50760", + "reference": "dd0df95bd37a7cf5c5c50304dfe260ffe4b50760", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": ">=3.0", + "php": ">=5.3", + "psr/log": "1.0.0", + "symfony/config": ">=2.0", + "symfony/console": ">=2.0", + "symfony/stopwatch": ">=2.2", + "symfony/yaml": ">=2.0" + }, + "require-dev": { + "apigen/apigen": "2.8.*@stable", + "pdepend/pdepend": "dev-master", + "phpmd/phpmd": "dev-master", + "phpunit/php-invoker": ">=1.1.0,<1.2.0", + "phpunit/phpunit": "3.7.*@stable", + "sebastian/finder-facade": "dev-master", + "sebastian/phpcpd": "1.4.*@stable", + "squizlabs/php_codesniffer": "1.4.*@stable", + "theseer/fdomdocument": "dev-master" + }, + "bin": [ + "composer/bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-0": { + "Contrib\\Component": "src/", + "Contrib\\Bundle": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/satooshi/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2013-05-04 08:07:33" + }, + { + "name": "symfony/config", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/Config.git", + "reference": "358ec929e494b6f12d8508d88357cbd7383a10ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Config/zipball/358ec929e494b6f12d8508d88357cbd7383a10ca", + "reference": "358ec929e494b6f12d8508d88357cbd7383a10ca", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/filesystem": "~2.3|~3.0.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2015-07-09 16:11:14" + }, + { + "name": "symfony/console", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/Console.git", + "reference": "fd85e7517e79a2bceafcee8f7e8b7bbd0919a90a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Console/zipball/fd85e7517e79a2bceafcee8f7e8b7bbd0919a90a", + "reference": "fd85e7517e79a2bceafcee8f7e8b7bbd0919a90a", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/phpunit-bridge": "~2.7|~3.0.0", + "symfony/process": "~2.1|~3.0.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2015-07-16 12:22:14" + }, + { + "name": "symfony/filesystem", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/Filesystem.git", + "reference": "9f70c5625a32b2f1e6fc37222f52b4e0eb437b0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Filesystem/zipball/9f70c5625a32b2f1e6fc37222f52b4e0eb437b0e", + "reference": "9f70c5625a32b2f1e6fc37222f52b4e0eb437b0e", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2015-07-09 16:11:14" + }, + { + "name": "symfony/stopwatch", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/Stopwatch.git", + "reference": "cd5f0dc1d3d0e2c83461dad77e20a9186beb6146" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/cd5f0dc1d3d0e2c83461dad77e20a9186beb6146", + "reference": "cd5f0dc1d3d0e2c83461dad77e20a9186beb6146", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "time": "2015-07-01 18:24:26" + }, + { + "name": "symfony/yaml", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/Yaml.git", + "reference": "000e7fc2653335cd42c6d21405dac1c74224a387" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/000e7fc2653335cd42c6d21405dac1c74224a387", + "reference": "000e7fc2653335cd42c6d21405dac1c74224a387", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2015-07-01 14:16:54" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.5" + }, + "platform-dev": [] +} diff --git a/lib/influxdb-php/phpunit.xml b/lib/influxdb-php/phpunit.xml new file mode 100644 index 0000000000..9913a0eda8 --- /dev/null +++ b/lib/influxdb-php/phpunit.xml @@ -0,0 +1,29 @@ + + + + + tests + + + + + src/ + + + + + + + + + + diff --git a/lib/influxdb-php/src/InfluxDB/Client.php b/lib/influxdb-php/src/InfluxDB/Client.php new file mode 100644 index 0000000000..b724c63f04 --- /dev/null +++ b/lib/influxdb-php/src/InfluxDB/Client.php @@ -0,0 +1,324 @@ +host = (string) $host; + $this->port = (int) $port; + $this->username = (string) $username; + $this->password = (string) $password; + $this->timeout = (int) $timeout; + $this->verifySSL = (bool) $verifySSL; + + if ($ssl) { + $this->scheme = 'https'; + $this->options['verify'] = $verifySSL; + } + + // the the base URI + $this->baseURI = sprintf('%s://%s:%d', $this->scheme, $this->host, $this->port); + + // set the default driver to guzzle + $this->driver = new Guzzle( + new \GuzzleHttp\Client( + [ + 'timeout' => $this->timeout, + 'base_uri' => $this->baseURI, + 'verify' => $this->verifySSL + ] + ) + ); + + $this->admin = new Admin($this); + } + + /** + * Use the given database + * + * @param string $name + * @return Database + */ + public function selectDB($name) + { + return new Database($name, $this); + } + + /** + * Query influxDB + * + * @param string $database + * @param string $query + * @param array $parameters + * + * @return ResultSet + * @throws Exception + */ + public function query($database, $query, $parameters = []) + { + + if (!$this->driver instanceof QueryDriverInterface) { + throw new Exception('The currently configured driver does not support query operations'); + } + + if ($database) { + $parameters['db'] = $database; + } + + $driver = $this->getDriver(); + + $parameters = [ + 'url' => 'query?' . http_build_query(array_merge(['q' => $query], $parameters)), + 'database' => $database, + 'method' => 'get' + ]; + + // add authentication to the driver if needed + if (!empty($this->username) && !empty($this->password)) { + $parameters += ['auth' => [$this->username, $this->password]]; + } + + $driver->setParameters($parameters); + + try { + // perform the query and return the resultset + return $driver->query(); + + } catch (DriverException $e) { + throw new Exception('Query has failed', $e->getCode(), $e); + } + } + + /** + * List all the databases + */ + public function listDatabases() + { + $result = $this->query(null, 'SHOW DATABASES')->getPoints(); + + return $this->pointsToArray($result); + } + + /** + * List all the users + * + * @return array + * @throws Exception + */ + public function listUsers() + { + $result = $this->query(null, 'SHOW USERS')->getPoints(); + + return $this->pointsToArray($result); + } + + /** + * Build the client from a dsn + * Examples: + * + * https+influxdb://username:pass@localhost:8086/databasename + * udp+influxdb://username:pass@localhost:4444/databasename + * + * @param string $dsn + * @param int $timeout + * @param bool $verifySSL + * +*@return Client|Database + * @throws ClientException + */ + public static function fromDSN($dsn, $timeout = 0, $verifySSL = false) + { + $connParams = parse_url($dsn); + $schemeInfo = explode('+', $connParams['scheme']); + $dbName = null; + $modifier = null; + $scheme = $schemeInfo[0]; + + if (isset($schemeInfo[1])) { + $modifier = strtolower($schemeInfo[0]); + $scheme = $schemeInfo[1]; + } + + if ($scheme != 'influxdb') { + throw new ClientException($scheme . ' is not a valid scheme'); + } + + $ssl = $modifier === 'https' ? true : false; + $dbName = $connParams['path'] ? substr($connParams['path'], 1) : null; + + $client = new self( + $connParams['host'], + $connParams['port'], + $connParams['user'], + $connParams['pass'], + $ssl, + $verifySSL, + $timeout + ); + + // set the UDP driver when the DSN specifies UDP + if ($modifier == 'udp') { + $client->setDriver(new UDP($connParams['host'], $connParams['port'])); + } + + return ($dbName ? $client->selectDB($dbName) : $client); + } + + /** + * @return mixed + */ + public function getBaseURI() + { + return $this->baseURI; + } + + /** + * @return int + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * @param Driver\DriverInterface $driver + */ + public function setDriver(DriverInterface $driver) + { + $this->driver = $driver; + } + + /** + * @return DriverInterface|QueryDriverInterface + */ + public function getDriver() + { + return $this->driver; + } + + /** + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * @param Point[] $points + * @return array + */ + protected function pointsToArray(array $points) + { + $names = []; + + foreach ($points as $item) { + $names[] = $item['name']; + } + + return $names; + } + +} diff --git a/lib/influxdb-php/src/InfluxDB/Client/Admin.php b/lib/influxdb-php/src/InfluxDB/Client/Admin.php new file mode 100644 index 0000000000..b62782a38c --- /dev/null +++ b/lib/influxdb-php/src/InfluxDB/Client/Admin.php @@ -0,0 +1,158 @@ +client = $client; + } + + /** + * Create a user + * + * @param string $username + * @param string $password + * + * @param string $privilege + * + * @throws \InfluxDB\Exception + * @return \InfluxDB\ResultSet + */ + public function createUser($username, $password, $privilege = null) + { + $query = sprintf('CREATE USER %s WITH PASSWORD \'%s\'', $username, $password); + + if ($privilege) { + $query .= " WITH $privilege PRIVILEGES"; + } + + return $this->client->query(null, $query); + } + + /** + * @param string $username + * + * @return \InfluxDB\ResultSet + * @throws \InfluxDB\Exception + */ + public function dropUser($username) + { + return $this->client->query(null, 'DROP USER ' . $username); + } + + /** + * Change a users password + * + * @param string $username + * @param string $newPassword + * + * @return \InfluxDB\ResultSet + * @throws \InfluxDB\Exception + */ + public function changeUserPassword($username, $newPassword) + { + return $this->client->query(null, "SET PASSWORD FOR $username = '$newPassword'"); + } + + /** + * Shows a list of all the users + * + * @return \InfluxDB\ResultSet + * @throws \InfluxDB\Exception + */ + public function showUsers() + { + return $this->client->query(null, "SHOW USERS"); + } + + /** + * Grants permissions + * + * @param string $privilege + * @param string $username + * @param Database|string $database + * + * @return \InfluxDB\ResultSet + */ + public function grant($privilege, $username, $database = null) + { + return $this->executePrivilege('GRANT', $privilege, $username, $database); + } + + /** + * Revokes permissions + * + * @param string $privilege + * @param string $username + * @param Database|string $database + * + * @throws \InfluxDB\Exception + * @return \InfluxDB\ResultSet + */ + public function revoke($privilege, $username, $database = null) + { + return $this->executePrivilege('REVOKE', $privilege, $username, $database); + } + + /** + * @param string $type + * @param string $privilege + * @param string $username + * @param Database|string $database + * + * @throws \InfluxDB\Exception + * @return \InfluxDB\ResultSet + */ + private function executePrivilege($type, $privilege, $username, $database = null) + { + + if (!in_array($privilege, [self::PRIVILEGE_READ, self::PRIVILEGE_WRITE, self::PRIVILEGE_ALL])) { + throw new Exception($privilege . ' is not a valid privileges, allowed privileges: READ, WRITE, ALL'); + } + + if ($privilege != self::PRIVILEGE_ALL && !$database) { + throw new Exception('Only grant ALL cluster-wide privileges are allowed'); + } + + $database = ($database instanceof Database ? $database->getName() : (string) $database); + + $query = "$type $privilege"; + + if ($database) { + $query .= sprintf(' ON %s ', $database); + } else { + $query .= " PRIVILEGES "; + } + + if ($username && $type == 'GRANT') { + $query .= "TO $username"; + } elseif ($username && $type == 'REVOKE') { + $query .= "FROM $username"; + } + + return $this->client->query(null, $query); + } +} \ No newline at end of file diff --git a/lib/influxdb-php/src/InfluxDB/Client/Exception.php b/lib/influxdb-php/src/InfluxDB/Client/Exception.php new file mode 100644 index 0000000000..0674e86868 --- /dev/null +++ b/lib/influxdb-php/src/InfluxDB/Client/Exception.php @@ -0,0 +1,12 @@ +name = (string) $name; + $this->client = $client; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Query influxDB + * + * @param string $query + * @param array $params + * @return ResultSet + * @throws Exception + */ + public function query($query, $params = []) + { + return $this->client->query($this->name, $query, $params); + } + + /** + * Create this database + * + * @param RetentionPolicy $retentionPolicy + * @return ResultSet + * @throws DatabaseException + * @throws Exception + */ + public function create(RetentionPolicy $retentionPolicy = null) + { + try { + $this->query(sprintf('CREATE DATABASE %s', $this->name)); + + if ($retentionPolicy) { + $this->createRetentionPolicy($retentionPolicy); + } + } catch (\Exception $e) { + throw new DatabaseException( + sprintf('Failed to created database %s', $this->name), + $e->getCode(), + $e + ); + } + } + + /** + * @param RetentionPolicy $retentionPolicy + * @return ResultSet + */ + public function createRetentionPolicy(RetentionPolicy $retentionPolicy) + { + return $this->query($this->getRetentionPolicyQuery('CREATE', $retentionPolicy)); + } + + /** + * Writes points into InfluxDB + * + * @param Point[] $points Array of points + * @param string $precision The timestamp precision (defaults to nanoseconds) + * @return bool + * @throws Exception + */ + public function writePoints(array $points, $precision = self::PRECISION_NANOSECONDS) + { + $payload = array_map( + function (Point $point) { + return (string) $point; + }, + $points + ); + + try { + $driver = $this->client->getDriver(); + + $parameters = [ + 'url' => sprintf('write?db=%s&precision=%s', $this->name, $precision), + 'database' => $this->name, + 'method' => 'post' + ]; + + // add authentication to the driver if needed + if (!empty($this->username) && !empty($this->password)) { + $parameters += ['auth' => [$this->username, $this->password]]; + } + + $driver->setParameters($parameters); + + // send the points to influxDB + $driver->write(implode(PHP_EOL, $payload)); + + return $driver->isSuccess(); + + } catch (\Exception $e) { + throw new Exception($e->getMessage(), $e->getCode()); + } + } + + /** + * @return bool + */ + public function exists() + { + $databases = $this->client->listDatabases(); + + return in_array($this->name, $databases); + } + + /** + * @param RetentionPolicy $retentionPolicy + */ + public function alterRetentionPolicy(RetentionPolicy $retentionPolicy) + { + $this->query($this->getRetentionPolicyQuery('ALTER', $retentionPolicy)); + } + + /** + * @return array + * @throws Exception + */ + public function listRetentionPolicies() + { + return $this->query(sprintf('SHOW RETENTION POLICIES %s', $this->name))->getPoints(); + } + + /** + * Drop this database + */ + public function drop() + { + $this->query(sprintf('DROP DATABASE %s', $this->name)); + } + + /** + * Retrieve the query builder + * + * @return QueryBuilder + */ + public function getQueryBuilder() + { + return new QueryBuilder($this); + } + + /** + * @return Client + */ + public function getClient() + { + return $this->client; + } + + /** + * @param string $method + * @param RetentionPolicy $retentionPolicy + * @return string + */ + protected function getRetentionPolicyQuery($method, RetentionPolicy $retentionPolicy) + { + if (!in_array($method, ['CREATE', 'ALTER'])) { + throw new \InvalidArgumentException(sprintf('%s is not a valid method')); + } + + $query = sprintf( + '%s RETENTION POLICY %s ON %s DURATION %s REPLICATION %s', + $method, + $retentionPolicy->name, + $this->name, + $retentionPolicy->duration, + $retentionPolicy->replication + ); + + if ($retentionPolicy->default) { + $query .= " DEFAULT"; + } + + return $query; + } +} diff --git a/lib/influxdb-php/src/InfluxDB/Database/Exception.php b/lib/influxdb-php/src/InfluxDB/Database/Exception.php new file mode 100644 index 0000000000..f5142b49ea --- /dev/null +++ b/lib/influxdb-php/src/InfluxDB/Database/Exception.php @@ -0,0 +1,10 @@ +name = (string) $name; + $this->duration = $duration; + $this->replication = (int) $replication; + $this->default = (bool) $default; + } +} diff --git a/lib/influxdb-php/src/InfluxDB/Driver/DriverInterface.php b/lib/influxdb-php/src/InfluxDB/Driver/DriverInterface.php new file mode 100644 index 0000000000..fc3a2c2f96 --- /dev/null +++ b/lib/influxdb-php/src/InfluxDB/Driver/DriverInterface.php @@ -0,0 +1,49 @@ + 'name of the database', + * 'url' => 'URL to the resource', + * 'method' => 'HTTP method used' + * ] + * + * @param array $parameters + * + * @return mixed + */ + public function setParameters(array $parameters); + + /** + * Send the data + * + * @param $data + * + * @return mixed + */ + public function write($data = null); + + /** + * Should return if sending the data was successful + * + * @return bool + */ + public function isSuccess(); + +} diff --git a/lib/influxdb-php/src/InfluxDB/Driver/Exception.php b/lib/influxdb-php/src/InfluxDB/Driver/Exception.php new file mode 100644 index 0000000000..237766eeef --- /dev/null +++ b/lib/influxdb-php/src/InfluxDB/Driver/Exception.php @@ -0,0 +1,16 @@ +httpClient = $client; + } + + /** + * Called by the client write() method, will pass an array of required parameters such as db name + * + * will contain the following parameters: + * + * [ + * 'database' => 'name of the database', + * 'url' => 'URL to the resource', + * 'method' => 'HTTP method used' + * ] + * + * @param array $parameters + * + * @return mixed + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + } + + /** + * Send the data + * + * @param $data + * + * @throws Exception + * @return mixed + */ + public function write($data = null) + { + $this->response = $this->httpClient->post($this->parameters['url'], $this->getRequestParameters($data)); + } + + /** + * @throws Exception + * @return ResultSet + */ + public function query() + { + + $response = $this->httpClient->get($this->parameters['url'], $this->getRequestParameters()); + + $raw = (string) $response->getBody(); + + $responseJson = json_encode($raw); + + if (isset($responseJson->error)) { + throw new Exception($responseJson->error); + } + + return new ResultSet($raw); + + } + + /** + * Should return if sending the data was successful + * + * @return bool + */ + public function isSuccess() + { + return in_array($this->response->getStatusCode(), ['200', '204']); + } + + /** + * @param null $data + * + * @return array + */ + protected function getRequestParameters($data = null) + { + $requestParameters = ['http_errors' => false]; + + if ($data) { + $requestParameters += ['body' => $data]; + } + + if (isset($this->parameters['auth'])) { + $requestParameters += ['auth' => $this->parameters['auth']]; + } + + return $requestParameters; + } +} diff --git a/lib/influxdb-php/src/InfluxDB/Driver/QueryDriverInterface.php b/lib/influxdb-php/src/InfluxDB/Driver/QueryDriverInterface.php new file mode 100644 index 0000000000..0966cecc3d --- /dev/null +++ b/lib/influxdb-php/src/InfluxDB/Driver/QueryDriverInterface.php @@ -0,0 +1,22 @@ +config['host'] = $host; + $this->config['port'] = $port; + + + } + + /** + * Called by the client write() method, will pass an array of required parameters such as db name + * + * will contain the following parameters: + * + * [ + * 'database' => 'name of the database', + * 'url' => 'URL to the resource', + * 'method' => 'HTTP method used' + * ] + * + * @param array $parameters + * + * @return mixed + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + } + + /** + * Send the data + * + * @param $data + * + * @return mixed + */ + public function write($data = null) + { + + $host = sprintf('udp://%s:%d', $this->config['host'], $this->config['port']); + + // stream the data using UDP and suppress any errors + $stream = @stream_socket_client($host); + @stream_socket_sendto($stream, $data); + @fclose($stream); + + return true; + } + + /** + * Should return if sending the data was successful + * + * @return bool + */ + public function isSuccess() + { + return true; + } +} \ No newline at end of file diff --git a/lib/influxdb-php/src/InfluxDB/Exception.php b/lib/influxdb-php/src/InfluxDB/Exception.php new file mode 100644 index 0000000000..868dd74c48 --- /dev/null +++ b/lib/influxdb-php/src/InfluxDB/Exception.php @@ -0,0 +1,10 @@ +measurement = (string) $measurement; + $this->tags = $tags; + $this->fields = $additionalFields; + + if ($value) { + $this->fields['value'] = $value; + } + + if ($timestamp && !$this->isValidTimeStamp($timestamp)) { + throw new DatabaseException(sprintf('%s is not a valid timestamp', $timestamp)); + } + + $this->timestamp = $timestamp; + } + + /** + * @see: https://influxdb.com/docs/v0.9/concepts/reading_and_writing_data.html + * + * Should return this format + * 'cpu_load_short,host=server01,region=us-west value=0.64 1434055562000000000' + */ + public function __toString() + { + + $string = $this->measurement; + + if (count($this->tags) > 0) { + $string .= ',' . $this->arrayToString($this->tags); + } + + $string .= ' ' . $this->arrayToString($this->fields); + + if ($this->timestamp) { + $string .= ' '.$this->timestamp; + } + + return $string; + } + + /** + * @param array $arr + * @return string + */ + private function arrayToString(array $arr) + { + $strParts = []; + + foreach ($arr as $key => $value) { + $strParts[] = sprintf('%s=%s', $key, $value); + } + + return implode(',', $strParts); + } + + /** + * @param int $timestamp + * @return bool + */ + private function isValidTimeStamp($timestamp) + { + if ((int) $timestamp === $timestamp) { + return true; + } + + if ($timestamp <= PHP_INT_MAX && $timestamp >= ~PHP_INT_MAX) { + return true; + } + + return false; + } +} diff --git a/lib/influxdb-php/src/InfluxDB/Query/Builder.php b/lib/influxdb-php/src/InfluxDB/Query/Builder.php new file mode 100644 index 0000000000..1060bc39d4 --- /dev/null +++ b/lib/influxdb-php/src/InfluxDB/Query/Builder.php @@ -0,0 +1,271 @@ +percentile(95)->setTimeRange($timeFrom, $timeTo)->getResult(); + * + * $series->select('*')->from('*')->getResult(); + * + * @todo add inner join + * @todo add merge + * + * @package InfluxDB\Query + * @author Stephen "TheCodeAssassin" Hoogendijk + */ +class Builder +{ + /** + * @var Database + */ + protected $db; + + /** + * @var string + */ + protected $selection = '*'; + + /** + * @var string[] + */ + protected $where = array(); + + /** + * @var string + */ + protected $startTime; + + /** + * @var string + */ + protected $endTime; + + /** + * @var string + */ + protected $metric; + + /** + * @var string + */ + protected $limitClause = ''; + + /** + * @param Database $db + */ + public function __construct(Database $db) + { + $this->db = $db; + } + + /** + * @param string $metric The metric to select (required) + * @return $this + */ + public function from($metric) + { + $this->metric = $metric; + + return $this; + } + + /** + * Custom select method + * + * example: + * + * $series->select('sum(value)', + * + * @param string $customSelect + * @return $this + */ + public function select($customSelect) + { + $this->selection = $customSelect; + + return $this; + } + + /** + * @param array $conditions + * + * Example: array('time > now()', 'time < now() -1d'); + * + * @return $this + */ + public function where(array $conditions) + { + foreach ($conditions as $condition) { + $this->where[] = $condition; + } + + return $this; + } + + /** + * @param string $field + * @return $this + */ + public function count($field = 'type') + { + $this->selection = sprintf('count(%s)', $field); + + return $this; + } + + /** + * @param string $field + * @return $this + */ + public function median($field = 'type') + { + $this->selection = sprintf('median(%s)', $field); + + return $this; + } + + /** + * @param string $field + * @return $this + */ + public function mean($field = 'type') + { + $this->selection = sprintf('mean(%s)', $field); + + return $this; + } + + /** + * @param string $field + * @return $this + */ + public function sum($field = 'type') + { + $this->selection = sprintf('sum(%s)', $field); + + return $this; + } + + /** + * @param string $field + * @return $this + */ + public function first($field = 'type') + { + $this->selection = sprintf('first(%s)', $field); + + return $this; + } + + /** + * @param string $field + * @return $this + */ + public function last($field = 'type') + { + $this->selection = sprintf('last(%s)', $field); + + return $this; + } + + /** + * Set's the time range to select data from + * + * @param int $from + * @param int $to + * @return $this + */ + public function setTimeRange($from, $to) + { + $fromDate = date('Y-m-d H:i:s', (int) $from); + $toDate = date('Y-m-d H:i:s', (int) $to); + + $this->where(array("time > '$fromDate'", "time < '$toDate'")); + + return $this; + } + + /** + * @param int $percentile Percentage to select (for example 95 for 95th percentile billing) + * + * @return $this + */ + public function percentile($percentile = 95) + { + $this->selection = sprintf('percentile(value, %d)', (int) $percentile); + + return $this; + } + + /** + * Limit the ResultSet to n records + * + * @param int $count + * + * @return $this + */ + public function limit($count) + { + $this->limitClause = sprintf(' LIMIT %s', (int) $count); + + return $this; + } + + /** + * @return string + */ + public function getQuery() + { + return $this->parseQuery(); + } + + /** + * Gets the result from the database (builds the query) + * + * @return ResultSet + */ + public function getResultSet() + { + return $this->db->query($this->parseQuery()); + } + + /** + * @return string + */ + protected function parseQuery() + { + $query = sprintf("SELECT %s FROM %s", $this->selection, $this->metric); + + if (! $this->metric) { + throw new \InvalidArgumentException('No metric provided to from()'); + } + + for ($i = 0; $i < count($this->where); $i++) { + $selection = 'WHERE'; + + if ($i > 0) { + $selection = 'AND'; + } + + $clause = $this->where[$i]; + $query .= ' ' . $selection . ' ' . $clause; + + } + + if ($this->limitClause) { + $query .= $this->limitClause; + } + + return $query; + } +} diff --git a/lib/influxdb-php/src/InfluxDB/Query/Exception.php b/lib/influxdb-php/src/InfluxDB/Query/Exception.php new file mode 100644 index 0000000000..448a41e979 --- /dev/null +++ b/lib/influxdb-php/src/InfluxDB/Query/Exception.php @@ -0,0 +1,10 @@ +parsedResults = json_decode((string) $raw, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \InvalidArgumentException('Invalid JSON'); + } + + // There was an error in the query thrown by influxdb + if (isset($this->parsedResults['error'])) { + throw new ClientException($this->parsedResults['error']); + } + + // Check if there are errors in the first serie + if (isset($this->parsedResults['results'][0]['error'])) { + throw new ClientException($this->parsedResults['results'][0]['error']); + } + } + + /** + * @param $metricName + * @param array $tags + * @return array $points + */ + public function getPoints($metricName = '', array $tags = array()) + { + $points = []; + $series = $this->getSeries(); + + foreach ($series as $serie) { + if ((empty($metricName) && empty($tags) + || $serie['name'] == $metricName + || (isset($serie['tags']) && array_intersect($tags, $serie['tags']))) + && isset($serie['values']) + ) { + $points = array_merge($points, $this->getPointsFromSerie($serie)); + } + } + + return $points; + } + + /** + * @see: https://influxdb.com/docs/v0.9/concepts/reading_and_writing_data.html + * + * results is an array of objects, one for each query, + * each containing the keys for a series + * + * @throws Exception + * @return array $series + */ + public function getSeries() + { + $series = array_map( + function ($object) { + if (isset($object['error'])) { + throw new ClientException($object['error']); + } + + return isset($object['series']) ? $object['series'] : []; + }, + $this->parsedResults['results'] + ); + + return array_shift($series); + } + + /** + * @param array $serie + * @return array + */ + private function getPointsFromSerie(array $serie) + { + $points = []; + + foreach ($serie['values'] as $point) { + $points[] = array_combine($serie['columns'], $point); + } + + return $points; + } +} diff --git a/lib/influxdb-php/tests/bootstrap.php b/lib/influxdb-php/tests/bootstrap.php new file mode 100644 index 0000000000..f94d45bf44 --- /dev/null +++ b/lib/influxdb-php/tests/bootstrap.php @@ -0,0 +1,7 @@ +mockClient = $this->getMockBuilder('\InfluxDB\Client') + ->disableOriginalConstructor() + ->getMock(); + + $this->resultData = file_get_contents(dirname(__FILE__) . '/result.example.json'); + + $this->mockClient->expects($this->any()) + ->method('getBaseURI') + ->will($this->returnValue($this->equalTo('http://localhost:8086'))); + + $this->mockClient->expects($this->any()) + ->method('query') + ->will($this->returnValue(new ResultSet($this->resultData))); + + $httpMockClient = new Guzzle($this->buildHttpMockClient('')); + + // make sure the client has a valid driver + $this->mockClient->expects($this->any()) + ->method('getDriver') + ->will($this->returnValue($httpMockClient)); + + $this->database = new Database('influx_test_db', $this->mockClient); + + } + + /** + * @return mixed + */ + public function getMockResultSet() + { + return $this->mockResultSet; + } + + /** + * @param mixed $mockResultSet + */ + public function setMockResultSet($mockResultSet) + { + $this->mockResultSet = $mockResultSet; + } + + + /** + * @return GuzzleClient + */ + public function buildHttpMockClient($body) + { + // Create a mock and queue two responses. + $mock = new MockHandler([new Response(200, array(), $body)]); + + $handler = HandlerStack::create($mock); + return new GuzzleClient(['handler' => $handler]); + } + + /** + * @return string + */ + public function getEmptyResult() + { + return $this->emptyResult; + } + + /** + * @param bool $emptyResult + * + * @return PHPUnit_Framework_MockObject_MockObject|Client + */ + public function getClientMock($emptyResult = false) + { + $mockClient = $this->getMockBuilder('\InfluxDB\Client') + ->disableOriginalConstructor() + ->getMock(); + + if ($emptyResult) { + $mockClient->expects($this->once()) + ->method('query') + ->will($this->returnValue(new ResultSet($this->getEmptyResult()))); + } + + return $mockClient; + } +} \ No newline at end of file diff --git a/lib/influxdb-php/tests/unit/AdminTest.php b/lib/influxdb-php/tests/unit/AdminTest.php new file mode 100644 index 0000000000..889874f4c6 --- /dev/null +++ b/lib/influxdb-php/tests/unit/AdminTest.php @@ -0,0 +1,72 @@ +getAdminObject(true); + + $this->assertEquals( + new ResultSet($this->emptyResult), + $adminObject->createUser('test', 'test', Client\Admin::PRIVILEGE_ALL) + ); + } + + public function testChangeUserPassword() + { + $adminObject = $this->getAdminObject(true); + + $this->assertEquals( + new ResultSet($this->emptyResult), + $adminObject->changeUserPassword('test', 'test') + ); + } + + public function testShowUsers() + { + $testJson = file_get_contents(dirname(__FILE__) . '/result-test-users.example.json'); + + $clientMock = $this->getClientMock(); + $testResult = new ResultSet($testJson); + + $clientMock->expects($this->once()) + ->method('query') + ->will($this->returnValue($testResult)); + + $adminMock = new Client\Admin($clientMock); + + $this->assertEquals($testResult, $adminMock->showUsers()); + } + + /** + * @return Client\Admin + */ + private function getAdminObject() + { + return new Client\Admin($this->getClientMock(true)); + } + +} \ No newline at end of file diff --git a/lib/influxdb-php/tests/unit/ClientTest.php b/lib/influxdb-php/tests/unit/ClientTest.php new file mode 100644 index 0000000000..09f420d1bf --- /dev/null +++ b/lib/influxdb-php/tests/unit/ClientTest.php @@ -0,0 +1,60 @@ +assertEquals($client->getBaseURI(), 'http://localhost:8086'); + } + + public function testSelectDbShouldReturnDatabaseInstance() + { + $client = new Client('localhost', 8086); + + $dbName = 'test-database'; + $database = $client->selectDB($dbName); + + $this->assertInstanceOf('\InfluxDB\Database', $database); + + $this->assertEquals($dbName, $database->getName()); + } + + + /** + */ + public function testGuzzleQuery() + { + $client = new Client('localhost', 8086); + $query = "some-bad-query"; + + $bodyResponse = file_get_contents(dirname(__FILE__) . '/result.example.json'); + $httpMockClient = $this->buildHttpMockClient($bodyResponse); + + $client->setDriver(new Guzzle($httpMockClient)); + + /** @var \InfluxDB\ResultSet $result */ + $result = $client->query(null, $query); + + $this->assertInstanceOf('\InfluxDB\ResultSet', $result); + } + +} \ No newline at end of file diff --git a/lib/influxdb-php/tests/unit/DatabaseTest.php b/lib/influxdb-php/tests/unit/DatabaseTest.php new file mode 100644 index 0000000000..e410385476 --- /dev/null +++ b/lib/influxdb-php/tests/unit/DatabaseTest.php @@ -0,0 +1,100 @@ +resultData = file_get_contents(dirname(__FILE__) . '/result.example.json'); + + $this->mockClient->expects($this->any()) + ->method('listDatabases') + ->will($this->returnValue(array('test123', 'test'))); + + $this->dataToInsert = file_get_contents(dirname(__FILE__) . '/input.example.json'); + + } + + /** + * + */ + public function testQuery() + { + $testResultSet = new ResultSet($this->resultData); + $this->assertEquals($this->database->query('SELECT * FROM test_metric'), $testResultSet); + } + + public function testCreateRetentionPolicy() + { + $retentionPolicy = new Database\RetentionPolicy('test', '1d', 1, true); + + $mockClient = $this->getClientMock(true); + + $database = new Database('test', $mockClient); + + $this->assertEquals($database->createRetentionPolicy($retentionPolicy), new ResultSet($this->getEmptyResult())); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testEmptyDatabaseName() + { + new Database(null, $this->mockClient); + } + + public function testExists() + { + $database = new Database('test', $this->mockClient); + + $this->assertEquals($database->exists(), true); + } + + + public function testNotExists() + { + $database = new Database('test_not_exists', $this->mockClient); + + $this->assertEquals($database->exists(), false); + } + + public function testWritePointsInASingleCall() + { + $point1 = new Point( + 'cpu_load_short', + 0.64, + array('host' => 'server01', 'region' => 'us-west'), + array('cpucount' => 10), + 1435222310 + ); + + $point2 = new Point( + 'cpu_load_short', + 0.84 + ); + + $this->assertEquals(true, $this->database->writePoints(array($point1, $point2))); + } +} \ No newline at end of file diff --git a/lib/influxdb-php/tests/unit/PointTest.php b/lib/influxdb-php/tests/unit/PointTest.php new file mode 100644 index 0000000000..eaee63fe75 --- /dev/null +++ b/lib/influxdb-php/tests/unit/PointTest.php @@ -0,0 +1,30 @@ + 'server01', 'region' => 'us-west'), + array('cpucount' => 10), + 1435222310 + ); + + $this->assertEquals($expected, (string) $point); + } +} \ No newline at end of file diff --git a/lib/influxdb-php/tests/unit/ResultSetTest.php b/lib/influxdb-php/tests/unit/ResultSetTest.php new file mode 100644 index 0000000000..4c04bf1e9b --- /dev/null +++ b/lib/influxdb-php/tests/unit/ResultSetTest.php @@ -0,0 +1,141 @@ +resultSet = new ResultSet($resultJsonExample); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testThrowsExceptionIfJSONisNotValid() + { + $invalidJSON = 'foo'; + + new ResultSet($invalidJSON); + } + + /** + * Throws Exception if something went wrong with influxDB + * @expectedException \InfluxDB\Exception + */ + public function testThrowsInfluxDBException() + { + + $errorResult = <<resultSet = new ResultSet($resultJsonExample); + + $measurementName = 'cpu_load_short'; + $expectedNumberOfPoints = 2; + + $points = $this->resultSet->getPoints($measurementName); + + $this->assertTrue(is_array($points)); + + $this->assertCount($expectedNumberOfPoints, $points); + } + + /** + * We can get points from measurement + */ + public function testGetPoints() + { + $expectedNumberOfPoints = 3; + + $points = $this->resultSet->getPoints(); + + $this->assertTrue( + is_array($points) + ); + + $this->assertCount($expectedNumberOfPoints, $points); + + } + + /** + * We can get points from measurement + */ + public function testGetPointsFromMeasurementName() + { + $measurementName = 'cpu_load_short'; + $expectedNumberOfPoints = 2; + $expectedValueFromFirstPoint = 0.64; + + $points = $this->resultSet->getPoints($measurementName); + + $this->assertTrue( + is_array($points) + ); + + $this->assertCount($expectedNumberOfPoints, $points); + + $somePoint = array_shift($points); + + $this->assertEquals($expectedValueFromFirstPoint, $somePoint['value']); + } + + public function testGetPointsFromTags() + { + $tags = array("host" => "server01"); + $expectedNumberOfPoints = 2; + + $points = $this->resultSet->getPoints('', $tags); + + $this->assertTrue(is_array($points)); + $this->assertCount($expectedNumberOfPoints, $points); + } + + public function testGetPointsFromNameAndTags() + { + $tags = array("host" => "server01"); + $expectedNumberOfPoints = 2; + + $points = $this->resultSet->getPoints('', $tags); + + $this->assertTrue(is_array($points)); + $this->assertCount($expectedNumberOfPoints, $points); + } +} \ No newline at end of file diff --git a/lib/influxdb-php/tests/unit/input.example.json b/lib/influxdb-php/tests/unit/input.example.json new file mode 100644 index 0000000000..f47806261d --- /dev/null +++ b/lib/influxdb-php/tests/unit/input.example.json @@ -0,0 +1,17 @@ +{ + "database": "mydb", + "retentionPolicy": "mypolicy", + "points": [ + { + "measurement": "cpu_load_short", + "tags": { + "host": "server01", + "region": "us-west" + }, + "time": "2009-11-10T23:00:00Z", + "fields": { + "value": 0.64 + } + } + ] +} \ No newline at end of file diff --git a/lib/influxdb-php/tests/unit/result-no-tags.example.json b/lib/influxdb-php/tests/unit/result-no-tags.example.json new file mode 100644 index 0000000000..feba13f256 --- /dev/null +++ b/lib/influxdb-php/tests/unit/result-no-tags.example.json @@ -0,0 +1,47 @@ +{ + "results": [ + { + "series": [ + { + "name": "cpu_load_short", + "columns": [ + "time", + "value" + ], + "values": [ + [ + "2015-01-29T21:51:28.968422294Z", + 0.64 + ] + ] + }, + { + "name": "cpu_load_short", + "columns": [ + "time", + "value" + ], + "values": [ + [ + "2015-01-29T21:51:28.968422294Z", + 0.65 + ] + ] + }, + { + "name": "other_serie", + "columns": [ + "time", + "value" + ], + "values": [ + [ + "2015-01-29T21:51:28.968422294Z", + 0.66 + ] + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/lib/influxdb-php/tests/unit/result-test-users.example.json b/lib/influxdb-php/tests/unit/result-test-users.example.json new file mode 100644 index 0000000000..34f5f999b6 --- /dev/null +++ b/lib/influxdb-php/tests/unit/result-test-users.example.json @@ -0,0 +1,24 @@ +{ + "results": [ + { + "series": [ + { + "columns": [ + "user", + "admin" + ], + "values": [ + [ + "test1", + true + ], + [ + "test2", + false + ] + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/lib/influxdb-php/tests/unit/result.example.json b/lib/influxdb-php/tests/unit/result.example.json new file mode 100644 index 0000000000..8603cae10f --- /dev/null +++ b/lib/influxdb-php/tests/unit/result.example.json @@ -0,0 +1,59 @@ +{ + "results": [ + { + "series": [ + { + "name": "cpu_load_short", + "tags": { + "host": "server01", + "region": "us-west" + }, + "columns": [ + "time", + "value" + ], + "values": [ + [ + "2015-01-29T21:51:28.968422294Z", + 0.64 + ] + ] + }, + { + "name": "cpu_load_short", + "tags": { + "host": "server02", + "region": "us-west" + }, + "columns": [ + "time", + "value" + ], + "values": [ + [ + "2015-01-29T21:51:28.968422294Z", + 0.65 + ] + ] + }, + { + "name": "other_serie", + "tags": { + "host": "server01", + "region": "us-west" + }, + "columns": [ + "time", + "value" + ], + "values": [ + [ + "2015-01-29T21:51:28.968422294Z", + 0.66 + ] + ] + } + ] + } + ] +} \ No newline at end of file