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:
Fabien Potencier 2011-09-20 07:19:49 +02:00
commit e2463caacd
2 changed files with 174 additions and 20 deletions

View File

@ -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;
}
}

View File

@ -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();
}
}