diff --git a/.travis.yml b/.travis.yml index 72fa03f..a3a838f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ matrix: before_script: - COMPOSER_ROOT_VERSION=dev-master composer install --dev --prefer-source - (php --version | grep -i php && pecl install -f xhprof) - - (php --version | grep -i php && yes | pecl install -f mongo) + - (php --version | grep -i php && yes | pecl install -f mongodb) notifications: email: false diff --git a/README.md b/README.md index ab4c8e2..d43863d 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,12 @@ All ideas are welcome, and contributors as well. Requirements ============ -* PHP 5.4 is required but using the latest version of PHP is highly recommended -* [XHProf](http://pecl.php.net/package/xhprof) or [Uprofiler](https://github.com/FriendsOfPHP/uprofiler) is required to do actual profiling +PHP 5.4 is required but using the latest version of PHP is highly recommended + +One of the following profilers to do actual profiling +* [XHProf](http://pecl.php.net/package/xhprof) +* [Uprofiler](https://github.com/FriendsOfPHP/uprofiler) +* [Tideways](https://github.com/tideways/php-profiler-extension) Installation ============ diff --git a/composer.json b/composer.json index 0fab46b..e1f79b4 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=5.3.3", + "php": ">=5.4", "ramsey/uuid": "^2.7.3" }, "require-dev": { diff --git a/composer.lock b/composer.lock index a42da4c..341c1b2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "2d8b25a20381c933b1ce654a51e48a59", + "content-hash": "9c46d24f64b6723af19762ecbd26ba77", "packages": [ { "name": "ramsey/uuid", @@ -70,7 +70,7 @@ "identifier", "uuid" ], - "time": "2015-07-23 19:00:41" + "time": "2015-07-23T19:00:41+00:00" } ], "packages-dev": [ @@ -126,7 +126,7 @@ "constructor", "instantiate" ], - "time": "2015-06-14 21:17:01" + "time": "2015-06-14T21:17:01+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -171,7 +171,7 @@ "keywords": [ "test" ], - "time": "2015-05-11 14:41:42" + "time": "2015-05-11T14:41:42+00:00" }, { "name": "league/flysystem", @@ -252,19 +252,19 @@ "sftp", "storage" ], - "time": "2015-07-28 20:41:58" + "time": "2015-07-28T20:41:58+00:00" }, { "name": "mockery/mockery", "version": "0.9.4", "source": { "type": "git", - "url": "https://github.com/padraic/mockery.git", + "url": "https://github.com/mockery/mockery.git", "reference": "70bba85e4aabc9449626651f48b9018ede04f86b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/padraic/mockery/zipball/70bba85e4aabc9449626651f48b9018ede04f86b", + "url": "https://api.github.com/repos/mockery/mockery/zipball/70bba85e4aabc9449626651f48b9018ede04f86b", "reference": "70bba85e4aabc9449626651f48b9018ede04f86b", "shasum": "" }, @@ -317,7 +317,7 @@ "test double", "testing" ], - "time": "2015-04-02 19:54:00" + "time": "2015-04-02T19:54:00+00:00" }, { "name": "phpdocumentor/reflection-docblock", @@ -366,7 +366,7 @@ "email": "mike.vanriel@naenius.com" } ], - "time": "2015-02-03 12:10:50" + "time": "2015-02-03T12:10:50+00:00" }, { "name": "phpspec/prophecy", @@ -426,7 +426,7 @@ "spy", "stub" ], - "time": "2015-04-27 22:15:08" + "time": "2015-04-27T22:15:08+00:00" }, { "name": "phpunit/php-code-coverage", @@ -488,7 +488,7 @@ "testing", "xunit" ], - "time": "2015-08-04 03:42:39" + "time": "2015-08-04T03:42:39+00:00" }, { "name": "phpunit/php-file-iterator", @@ -535,7 +535,7 @@ "filesystem", "iterator" ], - "time": "2015-06-21 13:08:43" + "time": "2015-06-21T13:08:43+00:00" }, { "name": "phpunit/php-text-template", @@ -576,7 +576,7 @@ "keywords": [ "template" ], - "time": "2015-06-21 13:50:34" + "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", @@ -617,7 +617,7 @@ "keywords": [ "timer" ], - "time": "2015-06-21 08:01:12" + "time": "2015-06-21T08:01:12+00:00" }, { "name": "phpunit/php-token-stream", @@ -666,20 +666,20 @@ "keywords": [ "tokenizer" ], - "time": "2015-06-19 03:43:16" + "time": "2015-06-19T03:43:16+00:00" }, { "name": "phpunit/phpunit", - "version": "4.7.7", + "version": "4.8.28", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9b97f9d807b862c2de2a36e86690000801c85724" + "reference": "558a3a0d28b4cb7e4a593a4fbd2220e787076225" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9b97f9d807b862c2de2a36e86690000801c85724", - "reference": "9b97f9d807b862c2de2a36e86690000801c85724", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/558a3a0d28b4cb7e4a593a4fbd2220e787076225", + "reference": "558a3a0d28b4cb7e4a593a4fbd2220e787076225", "shasum": "" }, "require": { @@ -689,15 +689,15 @@ "ext-reflection": "*", "ext-spl": "*", "php": ">=5.3.3", - "phpspec/prophecy": "~1.3,>=1.3.1", + "phpspec/prophecy": "^1.3.1", "phpunit/php-code-coverage": "~2.1", "phpunit/php-file-iterator": "~1.4", "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": ">=1.0.6", + "phpunit/php-timer": "^1.0.6", "phpunit/phpunit-mock-objects": "~2.3", "sebastian/comparator": "~1.1", "sebastian/diff": "~1.2", - "sebastian/environment": "~1.2", + "sebastian/environment": "~1.3", "sebastian/exporter": "~1.2", "sebastian/global-state": "~1.0", "sebastian/version": "~1.0", @@ -712,7 +712,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.7.x-dev" + "dev-master": "4.8.x-dev" } }, "autoload": { @@ -738,7 +738,7 @@ "testing", "xunit" ], - "time": "2015-07-13 11:28:34" + "time": "2016-11-14T06:25:28+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -794,7 +794,7 @@ "mock", "xunit" ], - "time": "2015-07-10 06:54:24" + "time": "2015-07-10T06:54:24+00:00" }, { "name": "predis/predis", @@ -844,7 +844,7 @@ "predis", "redis" ], - "time": "2015-07-30 18:34:15" + "time": "2015-07-30T18:34:15+00:00" }, { "name": "sebastian/comparator", @@ -908,7 +908,7 @@ "compare", "equality" ], - "time": "2015-07-26 15:48:44" + "time": "2015-07-26T15:48:44+00:00" }, { "name": "sebastian/diff", @@ -960,7 +960,7 @@ "keywords": [ "diff" ], - "time": "2015-02-22 15:13:53" + "time": "2015-02-22T15:13:53+00:00" }, { "name": "sebastian/environment", @@ -1010,7 +1010,7 @@ "environment", "hhvm" ], - "time": "2015-08-03 06:14:51" + "time": "2015-08-03T06:14:51+00:00" }, { "name": "sebastian/exporter", @@ -1076,7 +1076,7 @@ "export", "exporter" ], - "time": "2015-06-21 07:55:53" + "time": "2015-06-21T07:55:53+00:00" }, { "name": "sebastian/global-state", @@ -1127,7 +1127,7 @@ "keywords": [ "global state" ], - "time": "2014-10-06 09:23:50" + "time": "2014-10-06T09:23:50+00:00" }, { "name": "sebastian/recursion-context", @@ -1180,7 +1180,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-06-21 08:04:50" + "time": "2015-06-21T08:04:50+00:00" }, { "name": "sebastian/version", @@ -1215,7 +1215,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-06-21 13:59:46" + "time": "2015-06-21T13:59:46+00:00" }, { "name": "symfony/yaml", @@ -1264,7 +1264,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-07-28 14:07:07" + "time": "2015-07-28T14:07:07+00:00" }, { "name": "zendframework/zend-db", @@ -1317,7 +1317,7 @@ "db", "zf2" ], - "time": "2015-02-18 19:15:09" + "time": "2015-02-18T19:15:09+00:00" }, { "name": "zendframework/zend-stdlib", @@ -1372,7 +1372,7 @@ "stdlib", "zf2" ], - "time": "2015-02-10 14:55:30" + "time": "2015-02-10T14:55:30+00:00" } ], "aliases": [], diff --git a/src/Link0/Profiler/PersistenceHandler/MongoDbHandler.php b/src/Link0/Profiler/PersistenceHandler/MongoDbHandler.php index 638c24e..7a50f3d 100644 --- a/src/Link0/Profiler/PersistenceHandler/MongoDbHandler.php +++ b/src/Link0/Profiler/PersistenceHandler/MongoDbHandler.php @@ -5,15 +5,13 @@ * * @author Dennis de Greef */ + namespace Link0\Profiler\PersistenceHandler; use Link0\Profiler\PersistenceHandler; use Link0\Profiler\PersistenceHandler\MongoDbHandler\MongoClientInterface; use Link0\Profiler\PersistenceHandlerInterface; use Link0\Profiler\ProfileInterface; -use MongoCollection; -use MongoDate; -use MongoDB; /** * MongoDb implementation for Persistence @@ -28,14 +26,9 @@ final class MongoDbHandler extends PersistenceHandler implements PersistenceHand private $client; /** - * @var MongoDB + * @var string */ - private $database; - - /** - * @var MongoCollection - */ - private $collection; + private $namespace; /** * @param MongoClientInterface $client @@ -47,8 +40,7 @@ public function __construct(MongoClientInterface $client, $databaseName = 'xhpro parent::__construct(); $this->client = $client; - $this->database = $this->client->$databaseName; - $this->collection = $this->database->$collection; + $this->namespace = $databaseName . '.' . $collection; } /** @@ -61,21 +53,32 @@ public function getList() { // Awaiting https://github.com/link0/profiler/issues/60 for refactoring // The getList interface (or renamed method) should return an Iterator of some kind - return iterator_to_array($this->collection->find()); + return $this->client->executeQuery($this->namespace, array()); } /** - * @param string $identifier + * @param string $identifier * * @return ProfileInterface|null $profile */ public function retrieve($identifier) { - $profileData = $this->collection->findOne([ - 'identifier' => $identifier, - ]); + $profiles = $this->client->executeQuery( + $this->namespace, + array( + 'identifier' => $identifier, + ), + array( + 'limit' => 1, + )); + + $profileData = reset($profiles); + + if ($profileData === false) { + return null; + } - if($profileData !== null) { + if ($profileData !== null) { return $this->createProfileFromProfileData($profileData['profile']); } @@ -83,7 +86,7 @@ public function retrieve($identifier) } /** - * @param ProfileInterface $profile + * @param ProfileInterface $profile * * @return PersistenceHandlerInterface $this */ @@ -91,36 +94,71 @@ public function persist(ProfileInterface $profile) { // This is messed up, but this is finally compatible with XHGui, which is more important to me now. // Find a way to abstract this nicely! BUT FIRST! Release time! YEAH! (I am _SO_ gonna regret this...) - $profileArray = $profile->toArray(); - $serverData = $profileArray['serverData']; - - $requestTime = isset($serverData['REQUEST_TIME']) ? $serverData['REQUEST_TIME'] : time(); - $requestTimeFloat = isset($serverData['REQUEST_TIME_FLOAT']) ? $serverData['REQUEST_TIME_FLOAT'] : microtime(true); - $timeParts = explode('.', $requestTimeFloat); - if(!isset($timeParts[1])) { - $timeParts[1] = 0; - } + $mongoRequestDateTime = $this->getMongoRequestDateTime($profile); + $uri = $this->getMongoUri($profile); - $scriptName = isset($serverData['SCRIPT_NAME']) ? $serverData['SCRIPT_NAME'] : '__unknown__'; - $uri = isset($serverData['REQUEST_URI']) ? $serverData['REQUEST_URI'] : $scriptName; - - $mongoData = array( + $mongoDocument = array( 'identifier' => $profile->getIdentifier(), - 'profile' => $profileArray['profileData'], + 'profile' => $profile->getProfileData(), 'meta' => array( 'url' => $uri, - 'SERVER' => $profileArray['serverData'], + 'SERVER' => $profile->getServerData(), 'get' => array(), 'env' => array(), 'simple_url' => $uri, - 'request_ts' => new MongoDate($requestTime), - 'request_ts_micro' => new MongoDate($timeParts[0], $timeParts[1]), - 'request_date' => date('Y-m-d', $requestTime), + 'request_ts' => $mongoRequestDateTime, + 'request_ts_micro' => $this->getMongoRequestTimestamp($profile), + 'request_date' => $mongoRequestDateTime->toDateTime()->format('Y-m-d'), ) ); - $this->collection->insert($mongoData); + $this->client->insert($this->namespace, $mongoDocument); return $this; } + + /** + * @param ProfileInterface $profile + * @return \MongoDB\BSON\UTCDateTime + */ + private function getMongoRequestDateTime(ProfileInterface $profile) + { + $serverData = $profile->getServerData(); + + $requestTimeStamp = isset($serverData['REQUEST_TIME']) ? $serverData['REQUEST_TIME'] : time(); + $requestTime = new \DateTime(); + $requestTime->setTimestamp($requestTimeStamp); + + // NOTE: Even though my local documentation for this class says you cannot pass a DateTimeInterface, + // this is actually possible according to php.net. Actually testing it verifies this. + return new \MongoDB\BSON\UTCDateTime($requestTime); + } + + /** + * @param ProfileInterface $profile + * @return \MongoDB\BSON\Timestamp + */ + private function getMongoRequestTimestamp(ProfileInterface $profile) + { + $requestTimeFloat = isset($serverData['REQUEST_TIME_FLOAT']) ? $serverData['REQUEST_TIME_FLOAT'] : microtime(true); + $timeParts = explode('.', $requestTimeFloat); + if (!isset($timeParts[1])) { + $timeParts[1] = 0; + } + + return new \MongoDB\BSON\Timestamp($timeParts[1], $timeParts[0]); + } + + /** + * @param ProfileInterface $profile + * @return string + */ + private function getMongoUri(ProfileInterface $profile) + { + $serverData = $profile->getServerData(); + $scriptName = isset($serverData['SCRIPT_NAME']) ? $serverData['SCRIPT_NAME'] : '__unknown__'; + $uri = isset($serverData['REQUEST_URI']) ? $serverData['REQUEST_URI'] : $scriptName; + + return $uri; + } } diff --git a/src/Link0/Profiler/PersistenceHandler/MongoDbHandler/MongoClient.php b/src/Link0/Profiler/PersistenceHandler/MongoDbHandler/MongoClient.php index a97d77b..f04755d 100644 --- a/src/Link0/Profiler/PersistenceHandler/MongoDbHandler/MongoClient.php +++ b/src/Link0/Profiler/PersistenceHandler/MongoDbHandler/MongoClient.php @@ -12,6 +12,74 @@ * * @package Link0\Profiler\PersistenceHandler\MongoDbHandler */ -final class MongoClient extends \MongoClient implements MongoClientInterface +final class MongoClient implements MongoClientInterface { + /** + * @var \MongoDB\Driver\Manager + */ + private $driverManager; + + public function __construct($uri = 'mongodb://127.0.0.1', $uriOptions = [], $driverOptions = []) + { + $this->driverManager = new \MongoDB\Driver\Manager($uri, $uriOptions, $driverOptions); + } + + + /** + * @param string $namespace + * @param array|object $filter + * @param array $queryOptions + * @return string[] + * + * @throws \MongoDB\Driver\Exception\Exception + * @throws \MongoDB\Driver\Exception\AuthenticationException if authentication is needed and fails + * @throws \MongoDB\Driver\Exception\ConnectionException if connection to the server fails for other then authentication reasons + * @throws \MongoDB\Driver\Exception\RuntimeException on other errors (invalid command, command arguments, ...) + */ + public function executeQuery($namespace, $filter, $queryOptions = array()) + { + $query = new \MongoDB\Driver\Query($filter, $queryOptions); + + return iterator_to_array($this->driverManager->executeQuery($namespace, $query)); + } + + /** + * @param string $namespace + * @param array|object $document + * @param array|null $options + * @return bool Whether or not the write was acknowledged + * + * TODO: Any sort of feedback from insert was never actually processed before. + * It would be prettier if it was somehow. + * + * TODO: The driver docs I have locally state a different set of possible exceptions than + * the documentation on php.net - figuring out what is actually going to happen + * would be preferred. + * + * @throws \MongoDB\Driver\Exception\Exception + * @throws \MongoDB\Driver\Exception\AuthenticationException if authentication is needed and fails + * @throws \MongoDB\Driver\Exception\ConnectionException if connection to the server fails for other then authentication reasons + * @throws \MongoDB\Driver\Exception\InvalidArgumentException on argument parsing errors + * @throws \MongoDB\Driver\Exception\BulkWriteException on any write failure (e.g. write error, failure to apply a write concern) + * @throws \MongoDB\Driver\Exception\RuntimeException on other errors + */ + public function insert($namespace, $document, $options = null) + { + if ($options === null) { + $options = array( + 'ordered' => false, + ); + } + + $bulkWrite = new \MongoDB\Driver\BulkWrite($options); + + // NOTE: This function returns a \MongoDB\BSON\ObjectID if the document contained no + // ID. This is currently not used anywhere, but the interface will need refactoring + // if it is necessary in any place. + $bulkWrite->insert($document); + + $writeResult = $this->driverManager->executeBulkWrite($namespace, $bulkWrite); + + return $writeResult->isAcknowledged(); + } } diff --git a/src/Link0/Profiler/PersistenceHandler/MongoDbHandler/MongoClientInterface.php b/src/Link0/Profiler/PersistenceHandler/MongoDbHandler/MongoClientInterface.php index c92d817..e681636 100644 --- a/src/Link0/Profiler/PersistenceHandler/MongoDbHandler/MongoClientInterface.php +++ b/src/Link0/Profiler/PersistenceHandler/MongoDbHandler/MongoClientInterface.php @@ -14,4 +14,22 @@ */ interface MongoClientInterface { + /** + * @param string $namespace + * @param array|object $filter + * @param array $queryOptions + * @return string[] + */ + public function executeQuery($namespace, $filter, $queryOptions = array()); + + /** + * @param string $namespace + * @param array|object $document + * @param array|null $options + * @return bool Whether or not the write was acknowledged + * + * TODO: Any sort of feedback from insert was never actually processed before. + * It would be prettier if it was somehow. + */ + public function insert($namespace, $document, $options = null); } diff --git a/src/Link0/Profiler/Profiler.php b/src/Link0/Profiler/Profiler.php index feb5810..b4fd48a 100644 --- a/src/Link0/Profiler/Profiler.php +++ b/src/Link0/Profiler/Profiler.php @@ -5,6 +5,7 @@ * * @author Dennis de Greef */ + namespace Link0\Profiler; /** @@ -41,40 +42,33 @@ final class Profiler /** * @param null|PersistenceHandlerInterface $persistenceHandler - * @param null|int $flags - * @param array $options - */ - public function __construct(PersistenceHandlerInterface $persistenceHandler = null, $flags = null, $options = array()) - { + * @param null|int $flags + * @param array $options + * @param ProfilerAdapterFactory|null $profilerAdapterFactory + */ + public function __construct( + PersistenceHandlerInterface $persistenceHandler = null, + $flags = null, + $options = array(), + ProfilerAdapterFactory $profilerAdapterFactory = null + ) { if ($flags === null) { // Flags for XHProf and Uprofiler adding up to consume memory and cpu statistics - // Hardcoded to value 6, because if you have either extension, the constants of the other don't exist + // Hardcoded to value 6, because if you have one extension, the constants of the other(s) don't exist $flags = 6; } $options = $this->addInternalIgnoreFunctions($options); - $this->setDefaultPreferredProfileAdapters($flags, $options); - $this->profilerAdapter = $this->getPreferredProfilerAdapter(); + if ($profilerAdapterFactory === null) { + $profilerAdapterFactory = new ProfilerAdapterFactory($flags, $options); + } + + $this->profilerAdapter = $profilerAdapterFactory->create(); $this->persistenceService = new PersistenceService($persistenceHandler); $this->profileFactory = new ProfileFactory(); } - /** - * Sets default preferred profile adapters - * - * @param int $flags - * @param array $options - */ - private function setDefaultPreferredProfileAdapters($flags, $options) - { - $this->preferredProfilerAdapters = array( - new ProfilerAdapter\UprofilerAdapter($flags, $options), - new ProfilerAdapter\XhprofAdapter($flags, $options), - new ProfilerAdapter\NullAdapter($flags, $options), - ); - } - /** * Adds internal methods for ignored_functions * @@ -83,15 +77,12 @@ private function setDefaultPreferredProfileAdapters($flags, $options) */ private function addInternalIgnoreFunctions($options) { - if(isset($options['ignored_functions']) === false) { + if (isset($options['ignored_functions']) === false) { $options['ignored_functions'] = array(); } $options['ignored_functions'] = array_merge($options['ignored_functions'], array( 'xhprof_disable', - 'Link0\Profiler\ProfilerAdapter\XhprofAdapter::stop', - 'Link0\Profiler\ProfilerAdapter\UprofilerAdapter::stop', - 'Link0\Profiler\ProfilerAdapter\NullAdapter::stop', 'Link0\Profiler\ProfilerAdapter::stop', 'Link0\Profiler\ProfilerAdapter::isRunning', 'Link0\Profiler\Profiler::getProfilerAdapter', @@ -130,47 +121,6 @@ public function getPersistenceService() { return $this->persistenceService; } - - /** - * @param ProfilerAdapterInterface[] $preferredProfilerAdapters - * @return Profiler $this - */ - public function setPreferredProfilerAdapters($preferredProfilerAdapters) - { - $this->preferredProfilerAdapters = array(); - foreach ($preferredProfilerAdapters as $preferredProfilerAdapter) { - if (in_array('Link0\Profiler\ProfilerAdapterInterface', class_implements($preferredProfilerAdapter)) === true) { - $this->preferredProfilerAdapters[] = $preferredProfilerAdapter; - } - } - - return $this; - } - - /** - * @return ProfilerAdapterInterface[] $preferredProfilerAdapters - */ - public function getPreferredProfilerAdapters() - { - return $this->preferredProfilerAdapters; - } - - /** - * @throws Exception - * @return ProfilerAdapterInterface $profilerAdapter - */ - public function getPreferredProfilerAdapter() - { - /** @var ProfilerAdapterInterface $adapter */ - foreach ($this->getPreferredProfilerAdapters() as $adapter) { - if ($adapter->isExtensionLoaded() === true) { - return $adapter; - } - } - - throw new Exception('No valid profilerAdapter found. Did you forget to install an extension?'); - } - /** * @param ProfileFactoryInterface $profileFactory * @return Profiler $this @@ -249,7 +199,7 @@ public function isRunning() /** * Stops profiling and persists and returns the Profile object * - * @return Profile + * @return ProfileInterface */ public function stop() { @@ -273,10 +223,10 @@ public function stop() public function __destruct() { try { - if($this->isRunning() === true) { + if ($this->isRunning() === true) { $this->stop(); } - } catch(Exception $e) { + } catch (Exception $e) { // Exceptions can't be thrown in destructors } } diff --git a/src/Link0/Profiler/ProfilerAdapter.php b/src/Link0/Profiler/ProfilerAdapter.php index a5f3b84..2b452e2 100644 --- a/src/Link0/Profiler/ProfilerAdapter.php +++ b/src/Link0/Profiler/ProfilerAdapter.php @@ -37,6 +37,15 @@ public function __construct($flags = 0, $options = array()) { $this->flags = $flags; $this->options = $options; + + $ignoredFunctions = []; + if (isset($options['ignored_functions'])) { + $ignoredFunctions = $options['ignored_functions']; + } + + $options['ignored_functions'] = array_merge($ignoredFunctions, array( + get_called_class() . '::stop', + )); } /** diff --git a/src/Link0/Profiler/ProfilerAdapter/TidewaysAdapter.php b/src/Link0/Profiler/ProfilerAdapter/TidewaysAdapter.php new file mode 100644 index 0000000..3623494 --- /dev/null +++ b/src/Link0/Profiler/ProfilerAdapter/TidewaysAdapter.php @@ -0,0 +1,55 @@ +getFlags(), $this->getOptions()); + + return $this; + } + + /** + * Stops the profiling and triggers an event with the result data + * + * @return array $data + */ + public function stop() + { + parent::stop(); + + return tideways_disable(); + } + + /** + * @return string + */ + public function getExtensionName() + { + return 'tideways'; + } + + /** + * @return string The output directory specified in the php.ini configuration + */ + public function getFileOutputDirectory() + { + return ini_get('tideways.output_dir'); + } +} diff --git a/src/Link0/Profiler/ProfilerAdapterFactory.php b/src/Link0/Profiler/ProfilerAdapterFactory.php new file mode 100644 index 0000000..f4afc2c --- /dev/null +++ b/src/Link0/Profiler/ProfilerAdapterFactory.php @@ -0,0 +1,73 @@ +possibleProfilerAdapters = array( + new ProfilerAdapter\NullAdapter($flags, $options), + new ProfilerAdapter\XhprofAdapter($flags, $options), + new ProfilerAdapter\UprofilerAdapter($flags, $options), + new ProfilerAdapter\TidewaysAdapter($flags, $options), + ); + } + + /** + * Will reset $this->preferredProfilerAdapters to an empty array. Then adds every item in + * $preferredProfilerAdapters to it if it is an instance of ProfilerAdapterInterface. + * + * @param array $possibleProfilerAdapters + */ + public function setPossibleProfilerAdapters($possibleProfilerAdapters) + { + $this->possibleProfilerAdapters = array(); + + for(end($possibleProfilerAdapters); key($possibleProfilerAdapters) !== null; prev($possibleProfilerAdapters)) { + $adapter = current($possibleProfilerAdapters); + if ($adapter instanceof ProfilerAdapterInterface) { + $this->possibleProfilerAdapters[] = $adapter; + } + } + } + + /** + * Iterates through $this->preferredProfilerAdapters in reverse order, returning the first adapter that reports + * it's extension as being loaded. + * + * @return ProfilerAdapter + * @throws Exception + */ + public function create() + { + for(end($this->possibleProfilerAdapters); key($this->possibleProfilerAdapters) !== null; prev($this->possibleProfilerAdapters)) { + /** @var ProfilerAdapter $adapter */ + $adapter = current($this->possibleProfilerAdapters); + + if ($adapter->isExtensionLoaded() === true) { + return $adapter; + } + } + + throw new Exception('No valid profilerAdapter found. Did you forget to install an extension?'); + } + +} diff --git a/tests/Link0/Profiler/PersistenceHandler/MongoDbHandlerTest.php b/tests/Link0/Profiler/PersistenceHandler/MongoDbHandlerTest.php index 47f142c..c1a7c78 100644 --- a/tests/Link0/Profiler/PersistenceHandler/MongoDbHandlerTest.php +++ b/tests/Link0/Profiler/PersistenceHandler/MongoDbHandlerTest.php @@ -5,6 +5,7 @@ * * @author Dennis de Greef */ + namespace Link0\Profiler\PersistenceHandler; use Link0\Profiler\Profile; @@ -23,46 +24,40 @@ class MongoDbHandlerTest extends \PHPUnit_Framework_TestCase private $handler; /** - * @var MongoCollection $collection + * @var \Mockery\MockInterface */ - private $mongoCollection; + private $client; public function setUp() { - $traversable = new \ArrayObject(); - - $mongoCollection = Mockery::mock('\Link0\Profiler\PersistenceHandler\MongoDbHandler\MongoCollectionInterface'); - $mongoCollection->shouldReceive('find')->once()->andReturn($traversable); - $mongoCollection->shouldReceive('insert')->once()->andReturn(true); - $this->mongoCollection = $mongoCollection; - - $mongoDb = Mockery::mock('\Link0\Profiler\PersistenceHandler\MongoDbHandler\MongoDbInterface'); - $mongoDb->bar = $mongoCollection; - $client = Mockery::mock('\Link0\Profiler\PersistenceHandler\MongoDbHandler\MongoClientInterface'); - $client->foo = $mongoDb; + $this->client = $client; $this->handler = new MongoDbHandler($client, 'foo', 'bar'); } public function testEmptyList() { + $this->client->shouldReceive('executeQuery')->once()->andReturn(new \ArrayObject()); $this->assertEmpty($this->handler->getList()); } public function testRetrieveReturnsNullWhenNotFound() { - $this->mongoCollection->shouldReceive('findOne'); + $this->client->shouldReceive('executeQuery')->andReturn(array()); $this->assertNull($this->handler->retrieve('FooBarNonExistent')); } public function testRetrieveReturnsProfile() { $profile = Profile::create(); - $this->mongoCollection->shouldReceive('findOne')->once()->andReturn(array( - 'identifier' => $profile->getIdentifier(), - 'profile' => serialize($profile->toArray()), + $this->client->shouldReceive('executeQuery')->once()->andReturn(array( + array( + 'identifier' => $profile->getIdentifier(), + 'profile' => serialize($profile->toArray()), + ) )); + $this->assertInstanceOf('\Link0\Profiler\Profile', $this->handler->retrieve('Foo')); } @@ -70,12 +65,18 @@ public function testPersistReturnsSelf() { $profile = Profile::create(); $profile->setServerData(array()); + + $this->client->shouldReceive('insert')->once(); + $this->assertSame($this->handler, $this->handler->persist($profile)); } public function testPersistProfile() { $profile = Profile::create(); + + $this->client->shouldReceive('insert')->once(); + $this->handler->persist($profile); } @@ -86,6 +87,8 @@ public function testPersistWithRoundMicrotime() 'REQUEST_TIME_FLOAT' => 1234, )); + $this->client->shouldReceive('insert')->once(); + $this->handler->persist($profile); } -} \ No newline at end of file +} diff --git a/tests/Link0/Profiler/ProfilerAdapter/TidewaysAdapterTest.php b/tests/Link0/Profiler/ProfilerAdapter/TidewaysAdapterTest.php new file mode 100644 index 0000000..4a39aad --- /dev/null +++ b/tests/Link0/Profiler/ProfilerAdapter/TidewaysAdapterTest.php @@ -0,0 +1,66 @@ +profilerAdapter = new TidewaysAdapter(); + $this->assertInstanceOf('Link0\Profiler\ProfilerAdapter\TidewaysAdapter', $this->profilerAdapter); + } + + /** + * Asserts the extensionName + */ + public function testExtensionName() + { + $this->assertEquals('tideways', $this->profilerAdapter->getExtensionName()); + } + + /** + * Tests the outputDirectory property + */ + public function testGetFileOutputDirectory() + { + $this->assertEquals(ini_get('tideways.output_dir'), $this->profilerAdapter->getFileOutputDirectory()); + } + + /** + * Tests the complete implementation including assertion on running for Tideways + */ + public function testTidewaysImplementationIfExtensionLoaded() + { + $this->assertFalse($this->profilerAdapter->isRunning()); + $this->profilerAdapter->start(); + $this->assertTrue($this->profilerAdapter->isRunning()); + $this->profilerAdapter->start(); + $this->assertTrue($this->profilerAdapter->isRunning()); + + $this->profilerAdapter->stop(); + $this->assertFalse($this->profilerAdapter->isRunning()); + $this->profilerAdapter->stop(); + $this->assertFalse($this->profilerAdapter->isRunning()); + } +} diff --git a/tests/Link0/Profiler/ProfilerAdapterFactoryTest.php b/tests/Link0/Profiler/ProfilerAdapterFactoryTest.php new file mode 100644 index 0000000..325ff2c --- /dev/null +++ b/tests/Link0/Profiler/ProfilerAdapterFactoryTest.php @@ -0,0 +1,28 @@ +setPossibleProfilerAdapters(array()); + $factory->create(); + } + + public function testSetPossibleAdapters() + { + $nullAdapter = new ProfilerAdapter\NullAdapter(); + $factory = new ProfilerAdapterFactory(); + $factory->setPossibleProfilerAdapters(array($nullAdapter)); + $this->assertSame($nullAdapter, $factory->create()); + } + +} diff --git a/tests/Link0/Profiler/ProfilerTest.php b/tests/Link0/Profiler/ProfilerTest.php index b733ec5..7590c17 100644 --- a/tests/Link0/Profiler/ProfilerTest.php +++ b/tests/Link0/Profiler/ProfilerTest.php @@ -104,25 +104,6 @@ public function testSetProfilerAdapter() $this->assertSame($profilerAdapter, $profiler->getProfilerAdapter()); } - /** - * @expectedException Exception - * @expectedExceptionMessage No valid profilerAdapter found. Did you forget to install an extension? - */ - public function testNoPreferredProfilerAdapterCanBeFoundWhenNoneSet() - { - $profiler = new Profiler(); - $profiler->setPreferredProfilerAdapters(array()); - $profiler->getPreferredProfilerAdapter(); - } - - public function testSetPreferredAdapters() - { - $nullAdapter = new ProfilerAdapter\NullAdapter(); - $profiler = new Profiler(); - $profiler->setPreferredProfilerAdapters(array($nullAdapter)); - $this->assertSame($nullAdapter, $profiler->getPreferredProfilerAdapters()[0]); - } - public function testSetCustomProfilerFactory() { $profileFactory = new ProfileFactory(); @@ -132,4 +113,4 @@ public function testSetCustomProfilerFactory() $profiler->setProfileFactory($profileFactory); $this->assertSame($profileFactory, $profiler->getProfileFactory()); } -} \ No newline at end of file +}