diff --git a/README.md b/README.md index 9cea2d4103..2ab4bfc51d 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # influxdb-php ## InfluxDB client library for PHP -###Overview +### Overview This 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. -###Usage +### Getting started Initialize a new client object: @@ -18,3 +18,118 @@ $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: + +```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(); +```php + +### Reading + +To fetch records from InfluxDB you can do a query directly on a database: + +```php + + + // 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 + $result = $database->getQueryBuilder() + ->select('cpucount') + ->from('test_metric') + ->limit(2) + ->getResultSet()->getPoints(); +``` + +### Writing data + +Writing data is done by providing an array of points to the writePoints method on a database: + +```php + + $newPoints = $database->writePoints( + array( + new Point( + 'test_metric', + 0.64, + array('host' => 'server01', 'region' => 'us-west'), + array('cpucount' => 10) + ), + new Point( + 'test_metric', + 0.84, + array('host' => 'server01', 'region' => 'us-west'), + array('cpucount' => 10) + ) + ) + ); + +``` + +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. + +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. + +### 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, '', ''); + + // create the database with a retention policy + $result = $database->create(new RetentionPolicy('test', '5d', 1, true)); + +``` + +You can also alter retention policies: + +```php + $database->alterRetentionPolicy(new RetentionPolicy('test', '2d', 5, true)); +``` + +and list them: + +```php + $result = $database->listRetentionPolicies(); +``` + +### 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(); +``` + +## Changelog + +0.1 +------ +Initial release \ No newline at end of file diff --git a/composer.json b/composer.json index b969c7b3d9..73f3b4f755 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ ], "require": { "php": ">=5.3", - "guzzlehttp/guzzle": "~6.0" + "guzzlehttp/guzzle": "~5.0" }, "scripts": { "post-install-cmd": [ diff --git a/composer.lock b/composer.lock index 3040b508de..0a9d8afcdd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,42 +4,32 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "ef84333e4b0fdb96255d8f56a4d42e3b", + "hash": "10abd66a90e1473453cbda87799ed079", "packages": [ { "name": "guzzlehttp/guzzle", - "version": "dev-master", + "version": "5.3.x-dev", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "0da18ca6654b98a1d57cc9278331a7f7491d0d04" + "reference": "fd861570a9c3c28d98f418feea1f43f6268bdfa0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0da18ca6654b98a1d57cc9278331a7f7491d0d04", - "reference": "0da18ca6654b98a1d57cc9278331a7f7491d0d04", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fd861570a9c3c28d98f418feea1f43f6268bdfa0", + "reference": "fd861570a9c3c28d98f418feea1f43f6268bdfa0", "shasum": "" }, "require": { - "guzzlehttp/promises": "~1.0", - "guzzlehttp/psr7": "~1.0", - "php": ">=5.5.0" + "guzzlehttp/ringphp": "^1.1", + "php": ">=5.4.0" }, "require-dev": { "ext-curl": "*", - "phpunit/phpunit": "~4.0", - "psr/log": "~1.0" + "phpunit/phpunit": "^4.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.0-dev" - } - }, "autoload": { - "files": [ - "src/functions.php" - ], "psr-4": { "GuzzleHttp\\": "src/" } @@ -55,7 +45,7 @@ "homepage": "https://github.com/mtdowling" } ], - "description": "Guzzle is a PHP HTTP client library", + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", "homepage": "http://guzzlephp.org/", "keywords": [ "client", @@ -66,96 +56,44 @@ "rest", "web service" ], - "time": "2015-06-14 02:43:56" + "time": "2015-06-03 05:11:42" }, { - "name": "guzzlehttp/promises", + "name": "guzzlehttp/ringphp", "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "156f6915a89ca2cc9a3b36abef65f336ce68c754" + "url": "https://github.com/guzzle/RingPHP.git", + "reference": "9465032ac5d6beaa55f10923403e6e1c36018d9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/156f6915a89ca2cc9a3b36abef65f336ce68c754", - "reference": "156f6915a89ca2cc9a3b36abef65f336ce68c754", - "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.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-16 01:16:05" - }, - { - "name": "guzzlehttp/psr7", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "644db73bfa1571af785172887836d6ec01d787ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/644db73bfa1571af785172887836d6ec01d787ed", - "reference": "644db73bfa1571af785172887836d6ec01d787ed", + "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/9465032ac5d6beaa55f10923403e6e1c36018d9c", + "reference": "9465032ac5d6beaa55f10923403e6e1c36018d9c", "shasum": "" }, "require": { + "guzzlehttp/streams": "~3.0", "php": ">=5.4.0", - "psr/http-message": "~1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" + "react/promise": "~2.0" }, "require-dev": { + "ext-curl": "*", "phpunit/phpunit": "~4.0" }, + "suggest": { + "ext-curl": "Guzzle will use specific adapters if cURL is present" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" } }, "autoload": { "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions.php" - ] + "GuzzleHttp\\Ring\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -168,41 +106,38 @@ "homepage": "https://github.com/mtdowling" } ], - "description": "PSR-7 message implementation", - "keywords": [ - "http", - "message", - "stream", - "uri" - ], - "time": "2015-06-16 01:05:58" + "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", + "time": "2015-05-21 17:23:02" }, { - "name": "psr/http-message", + "name": "guzzlehttp/streams", "version": "dev-master", "source": { "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298" + "url": "https://github.com/guzzle/streams.git", + "reference": "d1f8a6c55f0f753cfd6f6755856473eb02cedb19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", - "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", + "url": "https://api.github.com/repos/guzzle/streams/zipball/d1f8a6c55f0f753cfd6f6755856473eb02cedb19", + "reference": "d1f8a6c55f0f753cfd6f6755856473eb02cedb19", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" + "GuzzleHttp\\Stream\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -211,20 +146,62 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" } ], - "description": "Common interface for HTTP messages", + "description": "Provides a simple abstraction over streams of data", + "homepage": "http://guzzlephp.org/", "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" + "Guzzle", + "stream" ], - "time": "2015-05-04 20:22:00" + "time": "2015-01-22 00:01:34" + }, + { + "name": "react/promise", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/365fcee430dfa4ace1fbc75737ca60ceea7eeeef", + "reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@googlemail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "time": "2014-12-30 13:32:42" } ], "packages-dev": [], diff --git a/src/InfluxDB/Client.php b/src/InfluxDB/Client.php index 9540a2a84b..4306ffe7e6 100644 --- a/src/InfluxDB/Client.php +++ b/src/InfluxDB/Client.php @@ -116,10 +116,11 @@ class Client $this->setBaseURI(sprintf('%s://%s:%d', $this->scheme, $this->host, $this->port)); $this->httpClient = new httpClient(array( - 'base_uri' => $this->getBaseURI(), + 'base_url' => $this->getBaseURI(), 'timeout' => $this->getTimeout() ) ); + } /** @@ -156,11 +157,12 @@ class Client $params += array('db' => $database); } - $params = array_merge(array('q' => $query), $params); - $options = array_merge($this->options, array('query' => $params, 'http_errors' => false)); + $params = '?'.http_build_query(array_merge(array('q' => $query), $params)); + + $options = array_merge($this->options, array('exceptions' => false)); try { - $response = $this->httpClient->get('query', $options); + $response = $this->httpClient->get('query'.$params, $options); $raw = (string) $response->getBody(); @@ -180,6 +182,7 @@ class Client public function write($database, $data) { try { + $this->httpClient->post( $this->getBaseURI() . '/write?db=' . $database, array('body' => $data) diff --git a/src/InfluxDB/Database.php b/src/InfluxDB/Database.php index a2a6eb8484..470df190b2 100644 --- a/src/InfluxDB/Database.php +++ b/src/InfluxDB/Database.php @@ -100,16 +100,21 @@ class Database } } + /** + * @param RetentionPolicy $retentionPolicy + */ public function createRetentionPolicy(RetentionPolicy $retentionPolicy) { $this->query($this->getRetentionPolicyQuery('CREATE', $retentionPolicy)); } /** - * Writes points into INfluxdb + * Writes points into InfluxDB * - * @param Point [] - * @return ResultSet + * @param array $points + * + * @return bool + * @throws Exception */ public function writePoints(array $points) { @@ -118,7 +123,7 @@ class Database foreach ($points as $point) { if (!$point instanceof Point) { - throw new \InvalidArgumentException('Array of Point should be passed'); + throw new \InvalidArgumentException('An array of Point[] should be passed'); } $payload[] = (string) $point; diff --git a/src/InfluxDB/Point.php b/src/InfluxDB/Point.php index 3d8f97009f..6567721de5 100644 --- a/src/InfluxDB/Point.php +++ b/src/InfluxDB/Point.php @@ -2,6 +2,8 @@ namespace InfluxDB; +use InfluxDB\Database\Exception; + /** * Class Point * @@ -13,30 +15,45 @@ class Point /** * @var array */ - private $tags; + private $tags = array(); /** * @var array */ - private $fields; + private $fields = array(); /** * @var string */ - private $timestamp; + private $timestamp = null; /** * The timestamp is optional. * If you do not specify a timestamp the server’s local timestamp will be used * - * @param $measurement - * @param array $tags - * @param array $fields - * @param string $timestamp + * @param string $measurement Name of the measurement + * @param float $value Value of the measurement + * @param array $tags Array of tags + * @param array $additionalFields Array of optional fields + * @param int $timestamp Optional timestamp + * + * @throws Exception */ - public function __construct($measurement, array $tags, array $fields, $timestamp = '') + public function __construct($measurement, $value, array $tags = array(), array $additionalFields = array(), $timestamp = null) { - $this->measurement = $measurement; + + if (empty($measurement)) { + throw new Exception('Invalid measurement name provided'); + } + + $this->measurement = (string) $measurement; $this->tags = $tags; - $this->fields = $fields; + $this->fields = $additionalFields; + + $this->fields += array('value' => (float) $value); + + if ($timestamp && !$this->isValidTimeStamp($timestamp)) { + throw new Exception(sprintf('%s is not a valid timestamp', $timestamp)); + } + $this->timestamp = $timestamp; } @@ -48,13 +65,20 @@ class Point */ public function __toString() { - return sprintf( - '%s,%s %s %s', - $this->measurement, - $this->arrayToString($this->tags), - $this->arrayToString($this->fields), - $this->timestamp - ); + + $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; } private function arrayToString(array $arr) @@ -68,4 +92,15 @@ class Point return implode(",", $strParts); } + /** + * @param $timestamp + * + * @return bool + */ + private function isValidTimeStamp($timestamp) + { + return ((int) $timestamp === $timestamp) + && ($timestamp <= PHP_INT_MAX) + && ($timestamp >= ~PHP_INT_MAX); + } } \ No newline at end of file diff --git a/src/InfluxDB/Query/Builder.php b/src/InfluxDB/Query/Builder.php index 96865025d4..4792f299e8 100644 --- a/src/InfluxDB/Query/Builder.php +++ b/src/InfluxDB/Query/Builder.php @@ -20,6 +20,9 @@ use InfluxDB\ResultSet; * * $series->select('*')->from('*')->getResult(); * + * @todo add inner join + * @todo add merge + * * @package InfluxDB\Query */ class Builder @@ -31,6 +34,7 @@ class Builder protected $startTime = null; protected $endTime = null; protected $metric = null; + protected $limitClause = ''; /** * @param Database $db @@ -190,25 +194,34 @@ class Builder } /** - * Gets the result from the database (builds the query) + * Limit the ResultSet to n records * - * @param bool $raw always return the ResultSeriesObjects, even when using an aggregation function + * @param int $count + * + * @return $this + */ + public function limit($count) + { + $this->limitClause = sprintf(' LIMIT %s', (int) $count); + + return $this; + } + +// public function orderBy + + /** + * Gets the result from the database (builds the query) * * @return ResultSet */ - public function getResultSet($raw = false) + public function getResultSet() { $query = sprintf("SELECT %s FROM %s", $this->selection, $this->metric); - $aggregateKey = null; if (!$this->metric) { throw new \InvalidArgumentException('No metric provided to from()'); } - if (preg_match("/([a-z]+)\(/i", $this->selection, $matches)) { - $aggregateKey = $matches[1]; - } - for ($i=0; $i < count($this->where); $i++) { $selection = 'WHERE'; if ($i > 0) { @@ -220,6 +233,10 @@ class Builder } + if ($this->limitClause) { + $query .= $this->limitClause; + } + return $this->db->query($query); } } \ No newline at end of file diff --git a/src/InfluxDB/ResultSet.php b/src/InfluxDB/ResultSet.php index d8ca020da0..39516a978c 100644 --- a/src/InfluxDB/ResultSet.php +++ b/src/InfluxDB/ResultSet.php @@ -92,7 +92,7 @@ class ResultSet throw new ClientException($object['error']); } - return $object['series']; + return (isset($object['series']) ? $object['series'] : array()); }; // Foreach object, pick series key diff --git a/tests/unit/DatabaseTest.php b/tests/unit/DatabaseTest.php index a351e8031d..ab328dae1d 100644 --- a/tests/unit/DatabaseTest.php +++ b/tests/unit/DatabaseTest.php @@ -38,15 +38,15 @@ class DatabaseTest extends \PHPUnit_Framework_TestCase { $point1 = new Point( 'cpu_load_short', - array('host' =>'server01', 'region'=>'us-west'), - array('value' => 0.64), - 'myTime' + 0.64, + array('host' => 'server01', 'region' => 'us-west'), + array('cpucount' => 10), + 1435222310 ); + $point2 = new Point( 'cpu_load_short', - array('host' =>'server01', 'region'=>'us-west'), - array('value' => 0.84), - 'myTime' + 0.84 ); $payloadExpected ="$point1\n$point2"; diff --git a/tests/unit/PointTest.php b/tests/unit/PointTest.php index d57713b37a..eaee63fe75 100644 --- a/tests/unit/PointTest.php +++ b/tests/unit/PointTest.php @@ -15,13 +15,14 @@ class PointTest extends \PHPUnit_Framework_TestCase { public function testPointStringRepresentation() { - $expected = 'cpu_load_short,host=server01,region=us-west value=0.64 myTime'; + $expected = 'cpu_load_short,host=server01,region=us-west cpucount=10,value=0.64 1435222310'; - $point = new Point( + $point = new Point( 'cpu_load_short', - array('host' =>'server01', 'region'=>'us-west'), - array('value' => 0.64), - 'myTime' + 0.64, + array('host' => 'server01', 'region' => 'us-west'), + array('cpucount' => 10), + 1435222310 ); $this->assertEquals($expected, (string) $point);