merged branch snc/mongodb-profiler (PR #2129)
Commits -------a0329c3
Added lifetime/cleanup support.365e73a
Fixed the find() method and changed the way the profile data is stored.beeec5e
Allow socket dsn (for example mongodb:///tmp/mongodb-27017.sock).218eaba
Fixed storage of time value.85c3806
Added support for sorting by time like other profiler storage implementations.73692c6
Fixed MongoDbProfilerStorage::find() when passing empty parameters.4cd2dec
Use token as identifier to make usage of the automatically created index. Discussion ---------- [2.1] [HttpKernel] MongoDB profiler updates I fixed one issue within the MongoDbProfilerStorage::find() function and made some more changes. --------------------------------------------------------------------------- by snc at 2011/09/11 02:28:35 -0700 Please don't merge this in yet. There are some more commits pending... --------------------------------------------------------------------------- by fabpot at 2011/09/14 01:07:39 -0700 @snc: is it ready for a merge? --------------------------------------------------------------------------- by snc at 2011/09/14 01:20:32 -0700 Unfortunately not... while testing I found out that the currently merged in implementation does not work completely. The web profiler search function errors because the find function returns the wrong data. I fixed this already but now I have some strange "maximum function nesting level reached" errors when viewing profiles with children. I will work on it later today. --------------------------------------------------------------------------- by snc at 2011/09/14 13:27:50 -0700 Now only one thing is missing... the generated container code looks like this: `$this->services['profiler'] = $instance = new \Symfony\Component\HttpKernel\Profiler\Profiler(new \Symfony\Component\HttpKernel\Profiler\MongoDbProfilerStorage('mongodb://localhost/sf2-mongo-profiler/profiler', '', '', 86400), $a);` The current constructor only uses the first parameter (dsn). What about the username, password and lifetime? Username and passwort can already be passed via the dsn... but the lifetime feature is not part of the interface... should I implement it? --------------------------------------------------------------------------- by fabpot at 2011/09/15 11:03:02 -0700 The `lifetime` is used to cleanup the database (see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/Profiler/PdoProfilerStorage.php#L136). So, it should probably be implemented for MongoDB as well (but it can probably be done in another PR). --------------------------------------------------------------------------- by snc at 2011/09/19 13:42:52 -0700 Sorry for the delay, lifetime support is now implemented. What do you think about an AbstractProfilerStorageTest class to share some testing code between the different implementations (of cause in a separate PR)?
This commit is contained in:
commit
e2463caacd
@ -14,6 +14,7 @@ namespace Symfony\Component\HttpKernel\Profiler;
|
||||
class MongoDbProfilerStorage implements ProfilerStorageInterface
|
||||
{
|
||||
protected $dsn;
|
||||
protected $lifetime;
|
||||
private $mongo;
|
||||
|
||||
/**
|
||||
@ -21,9 +22,10 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
|
||||
*
|
||||
* @param string $dsn A data source name
|
||||
*/
|
||||
public function __construct($dsn)
|
||||
public function __construct($dsn, $username = '', $password = '', $lifetime = 86400)
|
||||
{
|
||||
$this->dsn = $dsn;
|
||||
$this->lifetime = (int) $lifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,13 +39,14 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
|
||||
*/
|
||||
public function find($ip, $url, $limit)
|
||||
{
|
||||
$cursor = $this->getMongo()->find(array('ip' => $ip, 'url' => $url))->limit($limit);
|
||||
$return = array();
|
||||
$cursor = $this->getMongo()->find($this->buildQuery($ip, $url), array('_id', 'parent', 'ip', 'url', 'time'))->sort(array('time' => -1))->limit($limit);
|
||||
|
||||
$tokens = array();
|
||||
foreach ($cursor as $profile) {
|
||||
$return[] = $profile['token'];
|
||||
$tokens[] = $this->getData($profile);
|
||||
}
|
||||
|
||||
return $return;
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,9 +68,13 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
|
||||
*/
|
||||
public function read($token)
|
||||
{
|
||||
$profile = $this->getMongo()->findOne(array('token' => $token));
|
||||
$profile = $this->getMongo()->findOne(array('_id' => $token, 'data' => array('$exists' => true)));
|
||||
|
||||
return $profile !== null ? unserialize($profile['profile']) : null;
|
||||
if (null !== $profile) {
|
||||
$profile = $this->createProfileFromData($this->getData($profile));
|
||||
}
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,12 +86,18 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
|
||||
*/
|
||||
public function write(Profile $profile)
|
||||
{
|
||||
return $this->getMongo()->insert(array(
|
||||
'token' => $profile->getToken(),
|
||||
$this->cleanup();
|
||||
|
||||
$record = array(
|
||||
'_id' => $profile->getToken(),
|
||||
'parent' => $profile->getParent() ? $profile->getParent()->getToken() : null,
|
||||
'data' => serialize($profile->getCollectors()),
|
||||
'ip' => $profile->getIp(),
|
||||
'url' => $profile->getUrl() === null ? '' : $profile->getUrl(),
|
||||
'profile' => serialize($profile)
|
||||
));
|
||||
'url' => $profile->getUrl(),
|
||||
'time' => $profile->getTime()
|
||||
);
|
||||
|
||||
return $this->getMongo()->insert(array_filter($record, function ($v) { return !empty($v); }));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,11 +108,108 @@ class MongoDbProfilerStorage implements ProfilerStorageInterface
|
||||
protected function getMongo()
|
||||
{
|
||||
if ($this->mongo === null) {
|
||||
$mongo = new \Mongo($this->dsn);
|
||||
list($database, $collection,) = explode('/', substr(parse_url($this->dsn, PHP_URL_PATH), 1));
|
||||
$this->mongo = $mongo->selectCollection($database, $collection);
|
||||
if (preg_match('#^(mongodb://.*)/(.*)/(.*)$#', $this->dsn, $matches)) {
|
||||
$mongo = new \Mongo($matches[1]);
|
||||
$database = $matches[2];
|
||||
$collection = $matches[3];
|
||||
$this->mongo = $mongo->selectCollection($database, $collection);
|
||||
} else {
|
||||
throw new \RuntimeException('Please check your configuration. You are trying to use MongoDB with an invalid dsn. "'.$this->dsn.'"');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $this->mongo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return Profile
|
||||
*/
|
||||
protected function createProfileFromData(array $data)
|
||||
{
|
||||
$profile = $this->getProfile($data);
|
||||
|
||||
if ($data['parent']) {
|
||||
$parent = $this->getMongo()->findOne(array('_id' => $data['parent'], 'data' => array('$exists' => true)));
|
||||
if ($parent) {
|
||||
$profile->setParent($this->getProfile($this->getData($parent)));
|
||||
}
|
||||
}
|
||||
|
||||
$profile->setChildren($this->readChildren($data['token']));
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $token
|
||||
* @return array
|
||||
*/
|
||||
protected function readChildren($token)
|
||||
{
|
||||
$profiles = array();
|
||||
|
||||
$cursor = $this->getMongo()->find(array('parent' => $token, 'data' => array('$exists' => true)));
|
||||
foreach ($cursor as $d) {
|
||||
$profiles[] = $this->getProfile($this->getData($d));
|
||||
}
|
||||
|
||||
return $profiles;
|
||||
}
|
||||
|
||||
protected function cleanup()
|
||||
{
|
||||
$this->getMongo()->remove(array('time' => array('$lt' => time() - $this->lifetime)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $ip
|
||||
* @param string $url
|
||||
* @return array
|
||||
*/
|
||||
private function buildQuery($ip, $url)
|
||||
{
|
||||
$query = array();
|
||||
|
||||
if (!empty($ip)) {
|
||||
$query['ip'] = $ip;
|
||||
}
|
||||
|
||||
if (!empty($url)) {
|
||||
$query['url'] = $url;
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
private function getData(array $data)
|
||||
{
|
||||
return array(
|
||||
'token' => $data['_id'],
|
||||
'parent' => isset($data['parent']) ? $data['parent'] : null,
|
||||
'ip' => isset($data['ip']) ? $data['ip'] : null,
|
||||
'url' => isset($data['url']) ? $data['url'] : null,
|
||||
'time' => isset($data['time']) ? $data['time'] : null,
|
||||
'data' => isset($data['data']) ? $data['data'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return Profile
|
||||
*/
|
||||
private function getProfile(array $data)
|
||||
{
|
||||
$profile = new Profile($data['token']);
|
||||
$profile->setIp($data['ip']);
|
||||
$profile->setUrl($data['url']);
|
||||
$profile->setTime($data['time']);
|
||||
$profile->setCollectors(unserialize($data['data']));
|
||||
|
||||
return $profile;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class MongoDbProfilerStorageTest extends \PHPUnit_Framework_TestCase
|
||||
protected function setUp()
|
||||
{
|
||||
if (extension_loaded('mongo')) {
|
||||
self::$storage = new DummyMongoDbProfilerStorage('mongodb://localhost/symfony_tests/profiler_data');
|
||||
self::$storage = new DummyMongoDbProfilerStorage('mongodb://localhost/symfony_tests/profiler_data', '', '', 86400);
|
||||
try {
|
||||
self::$storage->getMongo();
|
||||
} catch(\MongoConnectionException $e) {
|
||||
@ -40,7 +40,7 @@ class MongoDbProfilerStorageTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
public function testStore()
|
||||
{
|
||||
for ($i = 0; $i < 10; $i ++) {
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$profile = new Profile('token_'.$i);
|
||||
$profile->setIp('127.0.0.1');
|
||||
$profile->setUrl('http://foo.bar');
|
||||
@ -71,7 +71,26 @@ class MongoDbProfilerStorageTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
self::$storage->purge();
|
||||
}
|
||||
|
||||
|
||||
public function testStoreTime()
|
||||
{
|
||||
$dt = new \DateTime('now');
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$dt->modify('+1 minute');
|
||||
$profile = new Profile('time_'.$i);
|
||||
$profile->setIp('127.0.0.1');
|
||||
$profile->setUrl('http://foo.bar');
|
||||
$profile->setTime($dt->getTimestamp());
|
||||
self::$storage->write($profile);
|
||||
}
|
||||
$records = self::$storage->find('', '', 3);
|
||||
$this->assertEquals(count($records), 3, '->find() returns all previously added records');
|
||||
$this->assertEquals($records[0]['token'], 'time_2', '->find() returns records ordered by time in descendant order');
|
||||
$this->assertEquals($records[1]['token'], 'time_1', '->find() returns records ordered by time in descendant order');
|
||||
$this->assertEquals($records[2]['token'], 'time_0', '->find() returns records ordered by time in descendant order');
|
||||
self::$storage->purge();
|
||||
}
|
||||
|
||||
public function testRetrieveByIp()
|
||||
{
|
||||
$profile = new Profile('token');
|
||||
@ -121,4 +140,29 @@ class MongoDbProfilerStorageTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
self::$storage->purge();
|
||||
}
|
||||
|
||||
public function testRetrieveByEmptyUrlAndIp()
|
||||
{
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$profile = new Profile('token_'.$i);
|
||||
self::$storage->write($profile);
|
||||
}
|
||||
$this->assertEquals(count(self::$storage->find('', '', 10)), 5, '->find() returns all previously added records');
|
||||
self::$storage->purge();
|
||||
}
|
||||
|
||||
public function testCleanup()
|
||||
{
|
||||
$dt = new \DateTime('-2 day');
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$dt->modify('-1 day');
|
||||
$profile = new Profile('time_'.$i);
|
||||
$profile->setTime($dt->getTimestamp());
|
||||
self::$storage->write($profile);
|
||||
}
|
||||
$records = self::$storage->find('', '', 3);
|
||||
$this->assertEquals(count($records), 1, '->find() returns only one record');
|
||||
$this->assertEquals($records[0]['token'], 'time_2', '->find() returns the latest added record');
|
||||
self::$storage->purge();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user