[ProfilerBundle] refactored the profiler bundle

This commit is contained in:
Fabien Potencier 2010-06-16 10:38:41 +02:00
parent fad8bd768c
commit b9ae18db39
16 changed files with 342 additions and 163 deletions

View File

@ -22,16 +22,24 @@ use Symfony\Framework\ProfilerBundle\DataCollector\DataCollector;
*/
class DoctrineDataCollector extends DataCollector
{
protected function collect()
public function collect()
{
$data = array();
$this->data = array();
if ($this->container->hasService('doctrine.dbal.logger')) {
$data = array(
$this->data = array(
'queries' => $this->container->getDoctrine_Dbal_LoggerService()->queries,
);
}
}
return $data;
public function getQueryCount()
{
return count($this->data['queries']);
}
public function getQueries()
{
return $this->data['queries'];
}
public function getSummary()

View File

@ -20,18 +20,28 @@ namespace Symfony\Framework\ProfilerBundle\DataCollector;
*/
class AppDataCollector extends DataCollector
{
protected function collect()
public function collect()
{
$request = $this->container->getRequestService();
return array(
$this->data = array(
'route' => $request->path->get('_route') ? $request->path->get('_route') : '<span style="color: #a33">NONE</span>',
'format' => $request->getRequestFormat(),
'content_type' => $this->manager->getResponse()->headers->get('Content-Type') ? $this->manager->getResponse()->headers->get('Content-Type') : 'text/html',
'code' => $this->manager->getResponse()->getStatusCode(),
'content_type' => $this->profiler->getResponse()->headers->get('Content-Type') ? $this->profiler->getResponse()->headers->get('Content-Type') : 'text/html',
'code' => $this->profiler->getResponse()->getStatusCode(),
);
}
public function getRoute()
{
return $this->data['route'];
}
public function getFormat()
{
return $this->data['format'];
}
public function getSummary()
{
return sprintf('<img style="margin-left: 10px; vertical-align: middle" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAABKElEQVR42sSTPa6CQBSFv3lRrCRYACvQxMLCBhsTK5fAGtiAPaGfDbgGNmBCS0PFAqhsoSGSWE1yrTDvxd+E4t1qkrnn3HPPnFEiwpD6YWCNPjUcj0fquhYAz/MUQBRFzwniOH6cMBrJfD4HoKoq6UleKkiS5H7WWstut+N0OmHbNrZt92rURw+01rJerzmfzyyXSy6XC77vf2ei1lpWqxVN02CMwRhDEARUVcXHFfrJbdsyHo8BcByHsiwxxqjfBj4omE6nstlsuF6vWJaFZVl4nkdZlhwOB/U2B1mWSRiGFEWB67pMJhNc16Uoipfgpx6EYUie58xmM/I8fwsGUH2UsywTgMViAUCapnRdp9498x+COI5lu93eL/b7vfomyurfP9NggtsAfaVzbTWryOIAAAAASUVORK5CYII=" />

View File

@ -22,12 +22,12 @@ use Symfony\Foundation\Kernel;
*/
class ConfigDataCollector extends DataCollector
{
protected function collect()
public function collect()
{
$kernel = $this->container->getKernelService();
return array(
'token' => $this->manager->getProfilerStorage()->getToken(),
$this->data = array(
'token' => $this->profiler->getProfilerStorage()->getToken(),
'symfony_version' => Kernel::VERSION,
'name' => $kernel->getName(),
'env' => $kernel->getEnvironment(),

View File

@ -3,6 +3,7 @@
namespace Symfony\Framework\ProfilerBundle\DataCollector;
use Symfony\Components\DependencyInjection\ContainerInterface;
use Symfony\Framework\ProfilerBundle\Profiler;
/*
* This file is part of the Symfony framework.
@ -22,7 +23,7 @@ use Symfony\Components\DependencyInjection\ContainerInterface;
*/
abstract class DataCollector implements DataCollectorInterface
{
protected $manager;
protected $profiler;
protected $container;
protected $data;
@ -33,17 +34,18 @@ abstract class DataCollector implements DataCollectorInterface
public function getData()
{
if (null === $this->data) {
$this->data = $this->collect();
}
return $this->data;
}
abstract protected function collect();
public function setCollectorManager(DataCollectorManager $manager)
public function setData($data)
{
$this->manager = $manager;
$this->data = $data;
}
abstract public function collect();
public function setProfiler(Profiler $profiler)
{
$this->profiler = $profiler;
}
}

View File

@ -2,6 +2,8 @@
namespace Symfony\Framework\ProfilerBundle\DataCollector;
use Symfony\Framework\ProfilerBundle\Profiler;
/*
* This file is part of the Symfony framework.
*
@ -20,7 +22,7 @@ namespace Symfony\Framework\ProfilerBundle\DataCollector;
*/
interface DataCollectorInterface
{
public function setCollectorManager(DataCollectorManager $manager);
public function setProfiler(Profiler $profiler);
public function getData();

View File

@ -1,115 +0,0 @@
<?php
namespace Symfony\Framework\ProfilerBundle\DataCollector;
use Symfony\Components\DependencyInjection\ContainerInterface;
use Symfony\Components\EventDispatcher\EventDispatcher;
use Symfony\Components\EventDispatcher\Event;
use Symfony\Components\HttpKernel\Response;
use Symfony\Components\HttpKernel\HttpKernelInterface;
use Symfony\Framework\ProfilerBundle\ProfilerStorage;
use Symfony\Foundation\LoggerInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* DataCollectorManager.
*
* @package Symfony
* @subpackage Framework_ProfilerBundle
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class DataCollectorManager
{
protected $container;
protected $profilerStorage;
protected $collectors;
protected $response;
protected $lifetime;
protected $logger;
public function __construct(ContainerInterface $container, LoggerInterface $logger, ProfilerStorage $profilerStorage, $lifetime = 86400)
{
$this->container = $container;
$this->logger = $logger;
$this->lifetime = $lifetime;
$this->profilerStorage = $profilerStorage;
$this->collectors = $this->initCollectors();
}
/**
* Registers a core.response listener.
*
* @param Symfony\Components\EventDispatcher\EventDispatcher $dispatcher An EventDispatcher instance
*/
public function register(EventDispatcher $dispatcher)
{
$dispatcher->connect('core.response', array($this, 'handle'));
}
public function handle(Event $event, Response $response)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getParameter('request_type')) {
return $response;
}
$this->response = $response;
$this->response->headers->set('X-Debug-Token', $this->profilerStorage->getToken());
$data = array();
foreach ($this->collectors as $name => $collector) {
$data[$name] = $collector->getData();
}
try {
$this->profilerStorage->write($data);
$this->profilerStorage->purge($this->lifetime);
} catch (\Exception $e) {
$this->logger->err('Unable to store the profiler information.');
}
return $response;
}
public function getProfilerStorage()
{
return $this->profilerStorage;
}
public function getResponse()
{
return $this->response;
}
public function getCollectors()
{
return $this->collectors;
}
public function initCollectors()
{
$config = $this->container->findAnnotatedServiceIds('data_collector');
$ids = array();
$coreCollectors = array();
$userCollectors = array();
foreach ($config as $id => $attributes) {
$collector = $this->container->getService($id);
$collector->setCollectorManager($this);
if (isset($attributes[0]['core']) && $attributes[0]['core']) {
$coreCollectors[$collector->getName()] = $collector;
} else {
$userCollectors[$collector->getName()] = $collector;
}
}
return $this->collectors = array_merge($coreCollectors, $userCollectors);
}
}

View File

@ -20,13 +20,18 @@ namespace Symfony\Framework\ProfilerBundle\DataCollector;
*/
class MemoryDataCollector extends DataCollector
{
protected function collect()
public function collect()
{
return array(
$this->data = array(
'memory' => memory_get_peak_usage(true),
);
}
public function getMemory()
{
return $this->data['memory'];
}
public function getSummary()
{
return sprintf('<img style="margin-left: 10px; vertical-align: middle" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAAmElEQVR42sSTuwoEIQxFr7JtOgf0n+z9Rhsr/0nB0jKiW00xs7Myj4VNFQj3cAKJGGPgSUk8rNfaeO8vqTjnxAYAAMuynAqXUj4NAKDWen8FKSWstadCIYRjg9YaUkrTsDHm2GAFtNamgP18A2BmKKWmAGaeG+ScpwCt9XdA7x299ylgP//dJQIAEYGI7gNijJcNxN+/8T0A1+E5NmcLfJkAAAAASUVORK5CYII=" />

View File

@ -20,13 +20,18 @@ namespace Symfony\Framework\ProfilerBundle\DataCollector;
*/
class TimerDataCollector extends DataCollector
{
protected function collect()
public function collect()
{
return array(
$this->data = array(
'time' => microtime(true) - $this->container->getKernelService()->getStartTime(),
);
}
public function getTime()
{
return $this->data['time'];
}
public function getSummary()
{
return sprintf('<img style="margin-left: 10px; vertical-align: middle" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAABSElEQVR42pyTwZHCMAxFXxxKUAXuIA3gFnJxERxohAt9QAM5EhpwB6rAJdjJXmKThIWdWc1oJvbo/y8p383tdmMfMcYEtKurLCKHfZ33nsMOmAFjraXrunofQmhVdQYmEVkTvwhijPMaOI5jLXLO0XUdIQSjqrOINBuCGGMu4AL03leC+/1eiQBUNZdOzFJjCth7X8GXy6WSee8Zx7F0WHCYGGOy1la1aZpqrs/zPNcaa21ZNAZoi3rf9+ScawL1O6VE3/frLtrNEgFSSm+/9Hq9AnA+n/ktvhKcTqev5GWEHELAOccwDKSUPuYwDDjnCCEAZAAjIgdV3Sh9yhKqSnFmGWEKIRjnHI/HA4Dj8VgBz+ez+mBRnzY7EJFi1WqWvRMXSxf19m2JItKoalZVY62toDXw61sonSwzJlX98zUCNGuH/Sd+BgBGROvHb4RJ6gAAAABJRU5ErkJggg==" />

View File

@ -26,7 +26,7 @@ class ProfilerExtension extends LoaderExtension
{
public function configLoad($config, BuilderConfiguration $configuration)
{
if (!$configuration->hasDefinition('data_collector_manager')) {
if (!$configuration->hasDefinition('profiler')) {
$loader = new XmlFileLoader(__DIR__.'/../Resources/config');
$configuration->merge($loader->load('collectors.xml'));
}

View File

@ -0,0 +1,56 @@
<?php
namespace Symfony\Framework\ProfilerBundle\Listener;
use Symfony\Components\EventDispatcher\EventDispatcher;
use Symfony\Components\EventDispatcher\Event;
use Symfony\Components\HttpKernel\Response;
use Symfony\Components\HttpKernel\HttpKernelInterface;
use Symfony\Framework\ProfilerBundle\Profiler;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* DataCollector collects data for the current request by listening to the core.response event.
*
* @package Symfony
* @subpackage Framework_ProfilerBundle
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class DataCollector
{
protected $profiler;
public function __construct(Profiler $profiler)
{
$this->profiler = $profiler;
}
/**
* Registers a core.response listener.
*
* @param Symfony\Components\EventDispatcher\EventDispatcher $dispatcher An EventDispatcher instance
*/
public function register(EventDispatcher $dispatcher)
{
$dispatcher->connect('core.response', array($this, 'handle'));
}
public function handle(Event $event, Response $response)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getParameter('request_type')) {
return $response;
}
$this->profiler->collect($response);
return $response;
}
}

View File

@ -7,7 +7,7 @@ use Symfony\Components\EventDispatcher\EventDispatcher;
use Symfony\Components\EventDispatcher\Event;
use Symfony\Components\HttpKernel\Response;
use Symfony\Components\HttpKernel\HttpKernelInterface;
use Symfony\Framework\ProfilerBundle\DataCollector\DataCollectorManager;
use Symfony\Framework\ProfilerBundle\Profiler;
/*
* This file is part of the Symfony framework.
@ -28,12 +28,12 @@ use Symfony\Framework\ProfilerBundle\DataCollector\DataCollectorManager;
class WebDebugToolbar
{
protected $container;
protected $collectorManager;
protected $profiler;
public function __construct(ContainerInterface $container, DataCollectorManager $collectorManager)
public function __construct(ContainerInterface $container, Profiler $profiler)
{
$this->container = $container;
$this->collectorManager = $collectorManager;
$this->profiler = $profiler;
}
/**
@ -77,7 +77,7 @@ class WebDebugToolbar
protected function injectToolbar(Response $response)
{
$data = '';
foreach ($this->collectorManager->getCollectors() as $name => $collector) {
foreach ($this->profiler->getCollectors() as $name => $collector) {
$data .= $collector->getSummary();
}

View File

@ -0,0 +1,192 @@
<?php
namespace Symfony\Framework\ProfilerBundle;
use Symfony\Components\DependencyInjection\ContainerInterface;
use Symfony\Components\HttpKernel\Response;
use Symfony\Framework\ProfilerBundle\ProfilerStorage;
use Symfony\Foundation\LoggerInterface;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* Profiler.
*
* @package Symfony
* @subpackage Framework_ProfilerBundle
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Profiler implements \ArrayAccess
{
protected $container;
protected $profilerStorage;
protected $collectors;
protected $response;
protected $logger;
public function __construct(ContainerInterface $container, ProfilerStorage $profilerStorage, LoggerInterface $logger = null)
{
$this->container = $container;
$this->profilerStorage = $profilerStorage;
$this->logger = $logger;
$this->initCollectors();
$this->loadCollectorData();
}
public function __clone()
{
$this->profilerStorage = clone $this->profilerStorage;
}
public function load(Response $response)
{
return $this->getProfilerForToken($response->headers->get('X-Debug-Token'));
}
public function getProfilerForToken($token)
{
$profiler = clone $this;
$profiler->profilerStorage->setToken($token);
$profiler->loadCollectorData();
return $profiler;
}
public function collect(Response $response)
{
$this->response = $response;
$this->response->headers->set('X-Debug-Token', $this->profilerStorage->getToken());
$data = array();
foreach ($this->collectors as $name => $collector) {
$collector->collect();
$data[$name] = $collector->getData();
}
try {
$this->profilerStorage->write($data);
$this->profilerStorage->purge();
} catch (\Exception $e) {
if (null !== $this->logger) {
$this->logger->err('Unable to store the profiler information.');
}
}
}
public function getProfilerStorage()
{
return $this->profilerStorage;
}
public function getResponse()
{
return $this->response;
}
public function loadCollectorData()
{
try {
foreach ($this->collectors as $name => $collector) {
$collector->setData($this->profilerStorage->getData($name));
}
} catch (\Exception $e) {
if (null !== $this->logger) {
$this->logger->err('Unable to read the profiler information.');
}
}
}
public function getCollectors()
{
return $this->collectors;
}
public function hasCollector($name)
{
return isset($this->collectors[$name]);
}
public function getCollector($name)
{
if (!isset($this->collectors[$name])) {
throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name));
}
return $this->collectors[$name];
}
/**
* Returns true if the named field exists.
*
* @param string $name The field name
*
* @param Boolean true if the field exists, false otherwise
*/
public function offsetExists($name)
{
return $this->hasCollector($name);
}
/**
* Gets the value of a field.
*
* @param string $name The field name
*
* @throws \InvalidArgumentException if the field does not exist
*/
public function offsetGet($name)
{
return $this->getCollector($name);
}
/**
* Sets the value of a field.
*
* @param string $name The field name
* @param string|array $value The value of the field
*
* @throws \InvalidArgumentException if the field does not exist
*/
public function offsetSet($name, $value)
{
throw new \LogicException('The Collectors cannot be set.');
}
/**
* Unimplemented.
*
* @param string $name The field name
*/
public function offsetUnset($name)
{
throw new \LogicException('The Collectors cannot be removed.');
}
protected function initCollectors()
{
$config = $this->container->findAnnotatedServiceIds('data_collector');
$ids = array();
$coreCollectors = array();
$userCollectors = array();
foreach ($config as $id => $attributes) {
$collector = $this->container->getService($id);
$collector->setProfiler($this);
if (isset($attributes[0]['core']) && $attributes[0]['core']) {
$coreCollectors[$collector->getName()] = $collector;
} else {
$userCollectors[$collector->getName()] = $collector;
}
}
$this->collectors = array_merge($coreCollectors, $userCollectors);
}
}

View File

@ -23,12 +23,14 @@ class ProfilerStorage
protected $token;
protected $data;
protected $store;
protected $lifetime;
public function __construct($store, $token = null)
public function __construct($store, $token = null, $lifetime = 86400)
{
$this->store = $store;
$this->token = null === $token ? uniqid() : $token;
$this->data = null;
$this->lifetime = (int) $lifetime;
}
public function hasData()
@ -46,7 +48,13 @@ class ProfilerStorage
return $this->data;
}
return isset($this->data[$name]) ? $this->data[$name] : null;
return isset($this->data[$name]) ? $this->data[$name] : array();
}
public function setToken($token)
{
$this->token = $token;
$this->data = null;
}
public function getToken()
@ -134,10 +142,10 @@ class ProfilerStorage
}
}
public function purge($lifetime)
public function purge()
{
$db = $this->initDb(false);
$args = array(':time' => time() - (int) $lifetime);
$args = array(':time' => time() - $this->lifetime);
$this->exec($db, 'DELETE FROM data WHERE created_at < :time', $args);
$this->close($db);
}

View File

@ -5,10 +5,11 @@
xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="data_collector_manager.class">Symfony\Framework\ProfilerBundle\DataCollector\DataCollectorManager</parameter>
<parameter key="data_collector_manager.storage.class">Symfony\Framework\ProfilerBundle\ProfilerStorage</parameter>
<parameter key="data_collector_manager.storage.file">%kernel.cache_dir%/profiler.db</parameter>
<parameter key="data_collector_manager.lifetime">86400</parameter>
<parameter key="profiler.class">Symfony\Framework\ProfilerBundle\Profiler</parameter>
<parameter key="profiler.storage.class">Symfony\Framework\ProfilerBundle\ProfilerStorage</parameter>
<parameter key="profiler.storage.file">%kernel.cache_dir%/profiler.db</parameter>
<parameter key="profiler.storage.lifetime">86400</parameter>
<parameter key="data_collector.class">Symfony\Framework\ProfilerBundle\Listener\DataCollector</parameter>
<parameter key="data_collector.config.class">Symfony\Framework\ProfilerBundle\DataCollector\ConfigDataCollector</parameter>
<parameter key="data_collector.app.class">Symfony\Framework\ProfilerBundle\DataCollector\AppDataCollector</parameter>
<parameter key="data_collector.timer.class">Symfony\Framework\ProfilerBundle\DataCollector\TimerDataCollector</parameter>
@ -16,16 +17,21 @@
</parameters>
<services>
<service id="data_collector_manager" class="%data_collector_manager.class%">
<annotation name="kernel.listener" />
<service id="profiler" class="%profiler.class%">
<argument type="service" id="service_container" />
<argument type="service" id="logger" />
<argument type="service" id="data_collector_manager.storage" />
<argument>%data_collector_manager.lifetime%</argument>
<argument type="service" id="profiler.storage" />
<argument type="service" id="logger" on-invalid="null" />
</service>
<service id="data_collector_manager.storage" class="%data_collector_manager.storage.class%">
<argument>%data_collector_manager.storage.file%</argument>
<service id="profiler.storage" class="%profiler.storage.class%">
<argument>%profiler.storage.file%</argument>
<argument>null</argument>
<argument>%profiler.storage.lifetime%</argument>
</service>
<service id="data_collector" class="%data_collector.class%">
<annotation name="kernel.listener" />
<argument type="service" id="profiler" />
</service>
<service id="data_collector.config" class="%data_collector.config.class%">

View File

@ -12,7 +12,7 @@
<service id="debug.toolbar" class="%debug.toolbar.class%">
<annotation name="kernel.listener" />
<argument type="service" id="service_container" />
<argument type="service" id="data_collector_manager" />
<argument type="service" id="profiler" />
</service>
</services>
</container>

View File

@ -23,7 +23,7 @@ class ProfilerExtensionTest extends TestCase
$loader = new ProfilerExtension();
$configuration = $loader->configLoad(array(), $configuration);
$this->assertEquals('Symfony\\Framework\\ProfilerBundle\\DataCollector\\DataCollectorManager', $configuration->getParameter('data_collector_manager.class'), '->configLoad() loads the collectors.xml file if not already loaded');
$this->assertEquals('Symfony\\Framework\\ProfilerBundle\\Profiler', $configuration->getParameter('profiler.class'), '->configLoad() loads the collectors.xml file if not already loaded');
$this->assertFalse($configuration->hasParameter('debug.toolbar.class'), '->configLoad() does not load the toolbar.xml file');
$configuration = $loader->configLoad(array('toolbar' => true), $configuration);