added Stopwatch support in debug mode, added a timeline representing the stopwatch events in the web profiler
Enjoy!
This commit is contained in:
parent
106ebdbe18
commit
842ac36f33
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Bridge\Doctrine\Logger;
|
namespace Symfony\Bridge\Doctrine\Logger;
|
||||||
|
|
||||||
use Symfony\Component\HttpKernel\Log\LoggerInterface;
|
use Symfony\Component\HttpKernel\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Debug\Stopwatch;
|
||||||
use Doctrine\DBAL\Logging\DebugStack;
|
use Doctrine\DBAL\Logging\DebugStack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,15 +23,18 @@ use Doctrine\DBAL\Logging\DebugStack;
|
|||||||
class DbalLogger extends DebugStack
|
class DbalLogger extends DebugStack
|
||||||
{
|
{
|
||||||
protected $logger;
|
protected $logger;
|
||||||
|
protected $stopwatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param LoggerInterface $logger A LoggerInterface instance
|
* @param LoggerInterface $logger A LoggerInterface instance
|
||||||
|
* @param Stopwatch $stopwatch A Stopwatch instance
|
||||||
*/
|
*/
|
||||||
public function __construct(LoggerInterface $logger = null)
|
public function __construct(LoggerInterface $logger = null, Stopwatch $stopwatch = null)
|
||||||
{
|
{
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
|
$this->stopwatch = $stopwatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,11 +44,27 @@ class DbalLogger extends DebugStack
|
|||||||
{
|
{
|
||||||
parent::startQuery($sql, $params, $types);
|
parent::startQuery($sql, $params, $types);
|
||||||
|
|
||||||
|
if (null !== $this->stopwatch) {
|
||||||
|
$this->stopwatch->start('doctrine', 'doctrine');
|
||||||
|
}
|
||||||
|
|
||||||
if (null !== $this->logger) {
|
if (null !== $this->logger) {
|
||||||
$this->log($sql.' ('.json_encode($params).')');
|
$this->log($sql.' ('.json_encode($params).')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function stopQuery()
|
||||||
|
{
|
||||||
|
parent::stopQuery();
|
||||||
|
|
||||||
|
if (null !== $this->stopwatch) {
|
||||||
|
$this->stopwatch->stop('doctrine');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a message.
|
* Logs a message.
|
||||||
*
|
*
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
<service id="doctrine.dbal.logger" class="%doctrine.dbal.logger.class%" public="false">
|
<service id="doctrine.dbal.logger" class="%doctrine.dbal.logger.class%" public="false">
|
||||||
<tag name="monolog.logger" channel="doctrine" />
|
<tag name="monolog.logger" channel="doctrine" />
|
||||||
<argument type="service" id="logger" on-invalid="null" />
|
<argument type="service" id="logger" on-invalid="null" />
|
||||||
|
<argument type="service" id="debug.stopwatch" on-invalid="null" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="data_collector.doctrine" class="%doctrine.data_collector.class%" public="false">
|
<service id="data_collector.doctrine" class="%doctrine.data_collector.class%" public="false">
|
||||||
|
@ -140,6 +140,11 @@ class ContainerAwareEventDispatcher extends EventDispatcher
|
|||||||
parent::dispatch($eventName, $event);
|
parent::dispatch($eventName, $event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getContainer()
|
||||||
|
{
|
||||||
|
return $this->container;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lazily loads listeners for this event from the dependency injection
|
* Lazily loads listeners for this event from the dependency injection
|
||||||
* container.
|
* container.
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Bundle\FrameworkBundle\Controller;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Debug\Stopwatch;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TraceableControllerResolver.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class TraceableControllerResolver extends ControllerResolver
|
||||||
|
{
|
||||||
|
private $stopwatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param ContainerInterface $container A ContainerInterface instance
|
||||||
|
* @param ControllerNameParser $parser A ControllerNameParser instance
|
||||||
|
* @param Stopwatch $stopwatch A Stopwatch instance
|
||||||
|
* @param LoggerInterface $logger A LoggerInterface instance
|
||||||
|
*/
|
||||||
|
public function __construct(ContainerInterface $container, ControllerNameParser $parser, Stopwatch $stopwatch, LoggerInterface $logger = null)
|
||||||
|
{
|
||||||
|
parent::__construct($container, $parser, $logger);
|
||||||
|
|
||||||
|
$this->stopwatch = $stopwatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @{inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getController(Request $request)
|
||||||
|
{
|
||||||
|
$e = $this->stopwatch->start('controller.get_callable');
|
||||||
|
|
||||||
|
$ret = parent::getController($request);
|
||||||
|
|
||||||
|
$e->stop();
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @{inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getArguments(Request $request, $controller)
|
||||||
|
{
|
||||||
|
$e = $this->stopwatch->start('controller.get_arguments');
|
||||||
|
|
||||||
|
$ret = parent::getArguments($request, $controller);
|
||||||
|
|
||||||
|
$e->stop();
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
}
|
@ -12,10 +12,13 @@
|
|||||||
namespace Symfony\Bundle\FrameworkBundle\Debug;
|
namespace Symfony\Bundle\FrameworkBundle\Debug;
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\ContainerAwareEventDispatcher;
|
use Symfony\Bundle\FrameworkBundle\ContainerAwareEventDispatcher;
|
||||||
|
use Symfony\Component\HttpKernel\Debug\Stopwatch;
|
||||||
use Symfony\Component\HttpKernel\Log\LoggerInterface;
|
use Symfony\Component\HttpKernel\Log\LoggerInterface;
|
||||||
use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcherInterface;
|
use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcherInterface;
|
||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
use Symfony\Component\EventDispatcher\Event;
|
use Symfony\Component\EventDispatcher\Event;
|
||||||
|
use Symfony\Component\HttpKernel\Profiler\Profiler;
|
||||||
|
use Symfony\Component\HttpKernel\Profiler\Profile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extends the ContainerAwareEventDispatcher to add some debugging tools.
|
* Extends the ContainerAwareEventDispatcher to add some debugging tools.
|
||||||
@ -26,21 +29,53 @@ class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements
|
|||||||
{
|
{
|
||||||
private $logger;
|
private $logger;
|
||||||
private $called;
|
private $called;
|
||||||
|
private $stopwatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param ContainerInterface $container A ContainerInterface instance
|
* @param ContainerInterface $container A ContainerInterface instance
|
||||||
|
* @param Stopwatch $stopwatch A Stopwatch instance
|
||||||
* @param LoggerInterface $logger A LoggerInterface instance
|
* @param LoggerInterface $logger A LoggerInterface instance
|
||||||
*/
|
*/
|
||||||
public function __construct(ContainerInterface $container, LoggerInterface $logger = null)
|
public function __construct(ContainerInterface $container, Stopwatch $stopwatch, LoggerInterface $logger = null)
|
||||||
{
|
{
|
||||||
parent::__construct($container);
|
parent::__construct($container);
|
||||||
|
|
||||||
|
$this->stopwatch = $stopwatch;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->called = array();
|
$this->called = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function dispatch($eventName, Event $event = null)
|
||||||
|
{
|
||||||
|
if ('kernel.request' === $eventName) {
|
||||||
|
$this->stopwatch->startSection();
|
||||||
|
} elseif ('kernel.view' === $eventName || 'kernel.response' === $eventName) {
|
||||||
|
// stop only if a controller has been executed
|
||||||
|
try {
|
||||||
|
$this->stopwatch->stop('controller');
|
||||||
|
} catch (\LogicException $e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$e1 = $this->stopwatch->start($eventName, 'section');
|
||||||
|
|
||||||
|
parent::dispatch($eventName, $event);
|
||||||
|
|
||||||
|
$e1->stop();
|
||||||
|
|
||||||
|
if ('kernel.controller' === $eventName) {
|
||||||
|
$this->stopwatch->start('controller', 'section');
|
||||||
|
} elseif ('kernel.response' === $eventName) {
|
||||||
|
$token = $event->getResponse()->headers->get('X-Debug-Token');
|
||||||
|
|
||||||
|
$this->stopwatch->stopSection($token);
|
||||||
|
|
||||||
|
$this->updateProfile($token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
@ -79,8 +114,12 @@ class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements
|
|||||||
|
|
||||||
$this->called[$eventName.'.'.$info['pretty']] = $info;
|
$this->called[$eventName.'.'.$info['pretty']] = $info;
|
||||||
|
|
||||||
|
$e2 = $this->stopwatch->start(substr($info['class'], strrpos($info['class'], '\\') + 1), 'event_listener');
|
||||||
|
|
||||||
call_user_func($listener, $event);
|
call_user_func($listener, $event);
|
||||||
|
|
||||||
|
$e2->stop();
|
||||||
|
|
||||||
if ($event->isPropagationStopped()) {
|
if ($event->isPropagationStopped()) {
|
||||||
if (null !== $this->logger) {
|
if (null !== $this->logger) {
|
||||||
$this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName));
|
$this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName));
|
||||||
@ -115,6 +154,18 @@ class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
protected function lazyLoad($eventName)
|
||||||
|
{
|
||||||
|
$e = $this->stopwatch->start($eventName.'.loading', 'event_listener_loading');
|
||||||
|
|
||||||
|
parent::lazyLoad($eventName);
|
||||||
|
|
||||||
|
$e->stop();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@ -199,4 +250,25 @@ class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements
|
|||||||
|
|
||||||
return $info;
|
return $info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function updateProfile($token)
|
||||||
|
{
|
||||||
|
if (!$this->getContainer()->has('profiler')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$profiler = $this->getContainer()->get('profiler');
|
||||||
|
if (!$profile = $profiler->loadProfile($token)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile->getCollector('time')->setEvents($this->stopwatch->getSectionEvents($profile->getToken()));
|
||||||
|
$profiler->saveProfile($profile);
|
||||||
|
|
||||||
|
// children
|
||||||
|
foreach ($profile->getChildren() as $child) {
|
||||||
|
$child->getCollector('time')->setEvents($this->stopwatch->getSectionEvents($child->getToken()));
|
||||||
|
$profiler->saveProfile($child);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,9 @@ class FrameworkExtension extends Extension
|
|||||||
$loader->load('debug.xml');
|
$loader->load('debug.xml');
|
||||||
$container->setDefinition('event_dispatcher', $container->findDefinition('debug.event_dispatcher'));
|
$container->setDefinition('event_dispatcher', $container->findDefinition('debug.event_dispatcher'));
|
||||||
$container->setAlias('debug.event_dispatcher', 'event_dispatcher');
|
$container->setAlias('debug.event_dispatcher', 'event_dispatcher');
|
||||||
|
|
||||||
|
$container->setDefinition('controller_resolver', $container->findDefinition('debug.controller_resolver'));
|
||||||
|
$container->setAlias('debug.controller_resolver', 'controller_resolver');
|
||||||
}
|
}
|
||||||
|
|
||||||
$configuration = new Configuration($container->getParameter('kernel.debug'));
|
$configuration = new Configuration($container->getParameter('kernel.debug'));
|
||||||
|
@ -21,6 +21,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
|||||||
/**
|
/**
|
||||||
* This HttpKernel is used to manage scope changes of the DI container.
|
* This HttpKernel is used to manage scope changes of the DI container.
|
||||||
*
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||||
*/
|
*/
|
||||||
class HttpKernel extends BaseHttpKernel
|
class HttpKernel extends BaseHttpKernel
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<parameter key="data_collector.exception.class">Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector</parameter>
|
<parameter key="data_collector.exception.class">Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector</parameter>
|
||||||
<parameter key="data_collector.events.class">Symfony\Component\HttpKernel\DataCollector\EventDataCollector</parameter>
|
<parameter key="data_collector.events.class">Symfony\Component\HttpKernel\DataCollector\EventDataCollector</parameter>
|
||||||
<parameter key="data_collector.logger.class">Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector</parameter>
|
<parameter key="data_collector.logger.class">Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector</parameter>
|
||||||
<parameter key="data_collector.timer.class">Symfony\Component\HttpKernel\DataCollector\TimerDataCollector</parameter>
|
<parameter key="data_collector.time.class">Symfony\Component\HttpKernel\DataCollector\TimeDataCollector</parameter>
|
||||||
<parameter key="data_collector.memory.class">Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector</parameter>
|
<parameter key="data_collector.memory.class">Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
|
|
||||||
@ -21,7 +21,6 @@
|
|||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="data_collector.request" class="%data_collector.request.class%">
|
<service id="data_collector.request" class="%data_collector.request.class%">
|
||||||
<tag name="kernel.event_listener" event="kernel.controller" method="onKernelController"/>
|
|
||||||
<tag name="data_collector" template="WebProfilerBundle:Collector:request" id="request" priority="255" />
|
<tag name="data_collector" template="WebProfilerBundle:Collector:request" id="request" priority="255" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
@ -42,8 +41,8 @@
|
|||||||
<argument type="service" id="logger" on-invalid="ignore" />
|
<argument type="service" id="logger" on-invalid="ignore" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="data_collector.timer" class="%data_collector.timer.class%" public="false">
|
<service id="data_collector.time" class="%data_collector.time.class%" public="false">
|
||||||
<tag name="data_collector" template="WebProfilerBundle:Collector:timer" id="timer" priority="255" />
|
<tag name="data_collector" template="WebProfilerBundle:Collector:time" id="time" priority="255" />
|
||||||
<argument type="service" id="kernel" />
|
<argument type="service" id="kernel" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
@ -6,14 +6,27 @@
|
|||||||
|
|
||||||
<parameters>
|
<parameters>
|
||||||
<parameter key="debug.event_dispatcher.class">Symfony\Bundle\FrameworkBundle\Debug\TraceableEventDispatcher</parameter>
|
<parameter key="debug.event_dispatcher.class">Symfony\Bundle\FrameworkBundle\Debug\TraceableEventDispatcher</parameter>
|
||||||
|
<parameter key="debug.stopwatch.class">Symfony\Component\HttpKernel\Debug\Stopwatch</parameter>
|
||||||
<parameter key="debug.container.dump">%kernel.cache_dir%/%kernel.container_class%.xml</parameter>
|
<parameter key="debug.container.dump">%kernel.cache_dir%/%kernel.container_class%.xml</parameter>
|
||||||
|
<parameter key="debug.controller_resolver.class">Symfony\Bundle\FrameworkBundle\Controller\TraceableControllerResolver</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
|
|
||||||
<services>
|
<services>
|
||||||
|
<service id="debug.stopwatch" class="%debug.stopwatch.class%" />
|
||||||
|
|
||||||
<service id="debug.event_dispatcher" class="%debug.event_dispatcher.class%">
|
<service id="debug.event_dispatcher" class="%debug.event_dispatcher.class%">
|
||||||
<tag name="monolog.logger" channel="event" />
|
<tag name="monolog.logger" channel="event" />
|
||||||
<argument type="service" id="service_container" />
|
<argument type="service" id="service_container" />
|
||||||
|
<argument type="service" id="debug.stopwatch" />
|
||||||
<argument type="service" id="logger" on-invalid="null" />
|
<argument type="service" id="logger" on-invalid="null" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="debug.controller_resolver" class="%debug.controller_resolver.class%">
|
||||||
|
<tag name="monolog.logger" channel="request" />
|
||||||
|
<argument type="service" id="service_container" />
|
||||||
|
<argument type="service" id="controller_name_converter" />
|
||||||
|
<argument type="service" id="debug.stopwatch" />
|
||||||
|
<argument type="service" id="logger" on-invalid="ignore" />
|
||||||
|
</service>
|
||||||
</services>
|
</services>
|
||||||
</container>
|
</container>
|
||||||
|
@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Debug;
|
|||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
|
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
|
||||||
use Symfony\Bundle\FrameworkBundle\Debug\TraceableEventDispatcher;
|
use Symfony\Bundle\FrameworkBundle\Debug\TraceableEventDispatcher;
|
||||||
|
use Symfony\Component\HttpKernel\Debug\Stopwatch;
|
||||||
|
|
||||||
class TraceableEventDispatcherTest extends TestCase
|
class TraceableEventDispatcherTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -23,7 +24,7 @@ class TraceableEventDispatcherTest extends TestCase
|
|||||||
public function testThrowsAnExceptionWhenAListenerMethodIsNotCallable()
|
public function testThrowsAnExceptionWhenAListenerMethodIsNotCallable()
|
||||||
{
|
{
|
||||||
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
|
||||||
$dispatcher = new TraceableEventDispatcher($container);
|
$dispatcher = new TraceableEventDispatcher($container, new Stopwatch());
|
||||||
$dispatcher->addListener('onFooEvent', new \stdClass());
|
$dispatcher->addListener('onFooEvent', new \stdClass());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
56
src/Symfony/Bundle/TwigBundle/Debug/TimedTwigEngine.php
Normal file
56
src/Symfony/Bundle/TwigBundle/Debug/TimedTwigEngine.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Bundle\TwigBundle\Debug;
|
||||||
|
|
||||||
|
use Symfony\Bundle\TwigBundle\TwigEngine;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables;
|
||||||
|
use Symfony\Component\Templating\TemplateNameParserInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Debug\Stopwatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Times the time spent to render a template.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class TimedTwigEngine extends TwigEngine
|
||||||
|
{
|
||||||
|
protected $stopwatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param \Twig_Environment $environment A \Twig_Environment instance
|
||||||
|
* @param TemplateNameParserInterface $parser A TemplateNameParserInterface instance
|
||||||
|
* @param GlobalVariables|null $globals A GlobalVariables instance or null
|
||||||
|
* @param Stopwatch $stopwatch A Stopwatch instance
|
||||||
|
*/
|
||||||
|
public function __construct(\Twig_Environment $environment, TemplateNameParserInterface $parser, Stopwatch $stopwatch, GlobalVariables $globals = null)
|
||||||
|
{
|
||||||
|
parent::__construct($environment, $parser, $globals);
|
||||||
|
|
||||||
|
$this->stopwatch = $stopwatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function render($name, array $parameters = array())
|
||||||
|
{
|
||||||
|
$e = $this->stopwatch->start(sprintf('template.twig (%s)', $name), 'template');
|
||||||
|
|
||||||
|
$ret = $this->load($name)->render($parameters);
|
||||||
|
|
||||||
|
$e->stop();
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
}
|
@ -63,6 +63,13 @@ class TwigExtension extends Extension
|
|||||||
|
|
||||||
$container->setParameter('twig.options', $config);
|
$container->setParameter('twig.options', $config);
|
||||||
|
|
||||||
|
if ($container->getParameter('kernel.debug')) {
|
||||||
|
$loader->load('debug.xml');
|
||||||
|
|
||||||
|
$container->setDefinition('templating.engine.twig', $container->findDefinition('debug.templating.engine.twig'));
|
||||||
|
$container->setAlias('debug.templating.engine.twig', 'templating.engine.twig');
|
||||||
|
}
|
||||||
|
|
||||||
$this->addClassesToCompile(array(
|
$this->addClassesToCompile(array(
|
||||||
'Twig_Environment',
|
'Twig_Environment',
|
||||||
'Twig_ExtensionInterface',
|
'Twig_ExtensionInterface',
|
||||||
|
19
src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml
Normal file
19
src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<container xmlns="http://symfony.com/schema/dic/services"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||||
|
|
||||||
|
<parameters>
|
||||||
|
<parameter key="debug.templating.engine.twig.class">Symfony\Bundle\TwigBundle\Debug\TimedTwigEngine</parameter>
|
||||||
|
</parameters>
|
||||||
|
|
||||||
|
<services>
|
||||||
|
<service id="debug.templating.engine.twig" class="%debug.templating.engine.twig.class%" public="false">
|
||||||
|
<argument type="service" id="twig" />
|
||||||
|
<argument type="service" id="templating.name_parser" />
|
||||||
|
<argument type="service" id="debug.stopwatch" />
|
||||||
|
<argument type="service" id="templating.globals" />
|
||||||
|
</service>
|
||||||
|
</services>
|
||||||
|
</container>
|
@ -15,7 +15,6 @@ use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
|
|||||||
use Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables;
|
use Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables;
|
||||||
use Symfony\Component\Templating\TemplateNameParserInterface;
|
use Symfony\Component\Templating\TemplateNameParserInterface;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This engine knows how to render Twig templates.
|
* This engine knows how to render Twig templates.
|
||||||
|
@ -427,3 +427,9 @@ td.main, td.menu {
|
|||||||
#navigation .import input {
|
#navigation .import input {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeline {
|
||||||
|
background-color: #fbfbfb;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 641 B After Width: | Height: | Size: 641 B |
@ -0,0 +1,323 @@
|
|||||||
|
{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}
|
||||||
|
|
||||||
|
{% if colors is not defined %}
|
||||||
|
{% set colors = {
|
||||||
|
'default': '#aacd4e',
|
||||||
|
'section': '#666',
|
||||||
|
'event_listener': '#3dd',
|
||||||
|
'event_listener_loading': '#add',
|
||||||
|
'template': '#dd3',
|
||||||
|
'doctrine': '#d3d',
|
||||||
|
'child_sections': '#eed',
|
||||||
|
} %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block toolbar %}
|
||||||
|
{% set icon %}
|
||||||
|
<img width="16" height="28" alt="Time" style="vertical-align: middle; margin-right: 5px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAcCAYAAABoMT8aAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAiNJREFUeNpi/P//PwMlgImBQjDwBrCcO3cOq0RRUdF3ZH5fXx8nTVzAePbsWcq8gMwxMjJiSUlJcXv9+nXm169fbf78+SMAVsTC8paXl3ePmJjYjJkzZx4GevsviheAGhmBguL+/v4779y5s/Xjx48+MM0gAGQLv3//PvzmzZv7AwMD19y+fVsEpAfsBWBCYly8eLHcsmXLjnz//l2GGGcDXXM1IyPD2dvb+xXIBTwbN25chU3zgQMHwBgdfP78WXvp0qVzgUwuprq6utg3b96YkRp4z549854wYYI7071791LJjYFLly7lM7148UKHXAOALtdnAYYwCyGFyOHg4OAAZ3/69ImfopTIzMz8j4WVlfXf79+/sRqEbBs2wMfH94tJXV39DbkuUFFReclkb29/jlwDPD09jzGFhoZu0NTU/EKqZktLyzdOTk7bQX4/U1tbu1pcXPwvsZoVFBR+lZeXLwUyz4MMuCMlJbWmv79/o56e3k9Cms3MzL5PmjRphYCAwCYg9wE4MwEZwkBsDsReO3fudN+zZ4/shQsX2ICxA9bEzs7OYGBg8NPHx+eBra3tdqDQVpDLgfgjuEABZk2QS3hBAQvExkBsAHIpMAsLAOP6PzC63gP590FOBmJQCXQPiL8Ai4D/KCUS0CBWIAUqB8SAWAiIQeUgqOIAlY/vgPgVEH8AavyDtUQCSoDc/BqEoQUGLIH9A9mGtUwc8JoJIMAAS9XemfR7crQAAAAASUVORK5CYII="/>
|
||||||
|
{% endset %}
|
||||||
|
{% set text %}
|
||||||
|
{{ '%.0f'|format(collector.totaltime) }} ms
|
||||||
|
{% endset %}
|
||||||
|
{% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block menu %}
|
||||||
|
<span class="label">
|
||||||
|
<span class="icon"><img src="{{ asset('bundles/webprofiler/images/profiler/time.png') }}" alt="Timeline" /></span>
|
||||||
|
<strong>Timeline</strong>
|
||||||
|
</span>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block panel %}
|
||||||
|
<h2>Timeline</h2>
|
||||||
|
|
||||||
|
{% set threshold = app.request.query.get('threshold', 1) %}
|
||||||
|
{% set width = app.request.query.get('width', 990) %}
|
||||||
|
|
||||||
|
<form action="" method="get" style="display: inline">
|
||||||
|
<input type="hidden" name="panel" value="time" />
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 20%">Total time</th>
|
||||||
|
<td>{{ '%.0f'|format(collector.totaltime) }} ms</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Initialization time</th>
|
||||||
|
<td>{{ '%.0f'|format(collector.inittime) }} ms</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Threshold</th>
|
||||||
|
<td><input type="number" size="3" name="threshold" value="{{ threshold }}" /> ms</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Width</th>
|
||||||
|
<td><input type="range" name="width" min="0" max="1200" step="10" value="{{ width }}" onChange="drawCanvases(this.value);" /> px</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th> </th>
|
||||||
|
<td><input type="submit" value="refresh" /></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
Main Request
|
||||||
|
<small>
|
||||||
|
- {{ collector.events.section.totaltime }} ms
|
||||||
|
{% if profile.parent %}
|
||||||
|
- <a href="{{ path('_profiler', { 'token': profile.parent.token }) }}?panel=time&threshold={{ threshold }}&width={{ width }}">parent</a>
|
||||||
|
{% endif %}
|
||||||
|
</small>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{% set max = collector.events.section.endtime %}
|
||||||
|
|
||||||
|
{{ _self.display_timeline('timeline_' ~ token, collector.events, threshold, colors, width) }}
|
||||||
|
|
||||||
|
{% if profile.children|length %}
|
||||||
|
{% for child in profile.children %}
|
||||||
|
{% set events = child.getcollector('time').events %}
|
||||||
|
<h3>
|
||||||
|
Sub-request "<a href="{{ path('_profiler', { 'token': child.token }) }}?panel=time&threshold={{ threshold }}&width={{ width }}">{{ child.getcollector('request').requestattributes.get('_controller') }}</a>"
|
||||||
|
<small> - {{ events.section.totaltime }} ms</small>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{{ _self.display_timeline('timeline_' ~ child.token, events, threshold, colors, width) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function drawCanvas(request, max, threshold, width) {
|
||||||
|
var colors = {
|
||||||
|
{% for name, color in colors %}
|
||||||
|
"{{ name }}": "{{ color }}"{{ loop.last ? '' : ', ' }}
|
||||||
|
{% endfor %}
|
||||||
|
};
|
||||||
|
var space = 10.5;
|
||||||
|
var ratio = (width - space * 2) / max;
|
||||||
|
var height = 0;
|
||||||
|
for (i = 0; i < request.events.length; i++) {
|
||||||
|
if (request.events[i].totaltime < threshold) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
height = height + 38;
|
||||||
|
}
|
||||||
|
var h = space;
|
||||||
|
var x = request.left * ratio + space;
|
||||||
|
|
||||||
|
var canvas = document.getElementById('timeline_' + request.id);
|
||||||
|
canvas.width = width;
|
||||||
|
|
||||||
|
var context = canvas.getContext("2d");
|
||||||
|
context.textBaseline = "middle";
|
||||||
|
context.lineWidth = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < request.events.length; i++) {
|
||||||
|
var event = request.events[i];
|
||||||
|
|
||||||
|
if (event.totaltime < threshold) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = 0; j < event.periods.length; j++) {
|
||||||
|
var period = event.periods[j];
|
||||||
|
|
||||||
|
if ('section.child' == event.name) {
|
||||||
|
context.fillStyle = colors.child_sections;
|
||||||
|
context.fillRect(x + period.begin * ratio, 0, (period.end - period.begin) * ratio, height);
|
||||||
|
} else if ('section' == event.category) {
|
||||||
|
context.beginPath();
|
||||||
|
context.strokeStyle = "#dfdfdf";
|
||||||
|
context.moveTo(x + period.begin * ratio, 0);
|
||||||
|
context.lineTo(x + period.begin * ratio, height);
|
||||||
|
context.moveTo(x + period.end * ratio, 0);
|
||||||
|
context.lineTo(x + period.end * ratio, height);
|
||||||
|
context.fill();
|
||||||
|
context.closePath();
|
||||||
|
context.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < request.events.length; i++) {
|
||||||
|
var event = request.events[i];
|
||||||
|
|
||||||
|
if (event.totaltime < threshold || 'section.child' == event.name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
h += 8;
|
||||||
|
|
||||||
|
for (j = 0; j < event.periods.length; j++) {
|
||||||
|
var period = event.periods[j];
|
||||||
|
|
||||||
|
if (colors[event.name]) {
|
||||||
|
context.fillStyle = colors[event.name];
|
||||||
|
context.strokeStyle = colors[event.name];
|
||||||
|
} else if (colors[event.category]) {
|
||||||
|
context.fillStyle = colors[event.category];
|
||||||
|
context.strokeStyle = colors[event.category];
|
||||||
|
} else {
|
||||||
|
context.fillStyle = colors.default;
|
||||||
|
context.strokeStyle = colors.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('section' != event.category) {
|
||||||
|
context.fillRect(x + period.begin * ratio, h + 3, 2, 6);
|
||||||
|
context.fillRect(x + period.begin * ratio, h, (period.end - period.begin) * ratio ? (period.end - period.begin) * ratio : 2, 6);
|
||||||
|
} else {
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x + period.begin * ratio, h);
|
||||||
|
context.lineTo(x + period.begin * ratio, h + 11);
|
||||||
|
context.lineTo(x + period.begin * ratio + 8, h);
|
||||||
|
context.lineTo(x + period.begin * ratio, h);
|
||||||
|
context.fill();
|
||||||
|
context.closePath();
|
||||||
|
context.stroke();
|
||||||
|
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x + period.end * ratio, h);
|
||||||
|
context.lineTo(x + period.end * ratio, h + 11);
|
||||||
|
context.lineTo(x + period.end * ratio - 8, h);
|
||||||
|
context.lineTo(x + period.end * ratio, h);
|
||||||
|
context.fill();
|
||||||
|
context.closePath();
|
||||||
|
context.stroke();
|
||||||
|
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x + period.begin * ratio, h);
|
||||||
|
context.lineTo(x + period.end * ratio, h);
|
||||||
|
context.lineTo(x + period.end * ratio, h + 2);
|
||||||
|
context.lineTo(x + period.begin * ratio, h + 2);
|
||||||
|
context.lineTo(x + period.begin * ratio, h);
|
||||||
|
context.fill();
|
||||||
|
context.closePath();
|
||||||
|
context.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h += 30;
|
||||||
|
|
||||||
|
context.beginPath();
|
||||||
|
context.strokeStyle = "#dfdfdf";
|
||||||
|
context.moveTo(0, h - 10);
|
||||||
|
context.lineTo(width, h - 10);
|
||||||
|
context.closePath();
|
||||||
|
context.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
h = space;
|
||||||
|
|
||||||
|
for (i = 0; i < request.events.length; i++) {
|
||||||
|
var event = request.events[i];
|
||||||
|
|
||||||
|
if (event.totaltime < threshold || 'section.child' == event.name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.fillStyle = "#444";
|
||||||
|
context.font = "12px sans-serif";
|
||||||
|
var text = event.name;
|
||||||
|
var ms;
|
||||||
|
if (event.totaltime == 0) {
|
||||||
|
ms = " < 1 ms";
|
||||||
|
} else {
|
||||||
|
ms = " ~ " + event.totaltime + " ms";
|
||||||
|
}
|
||||||
|
if (x + event.starttime * ratio + context.measureText(text + ms).width > width) {
|
||||||
|
context.textAlign = "end";
|
||||||
|
context.font = "10px sans-serif";
|
||||||
|
xc = x + event.endtime * ratio - 1;
|
||||||
|
context.fillText(ms, xc, h);
|
||||||
|
|
||||||
|
xc = xc - context.measureText(ms).width
|
||||||
|
context.font = "12px sans-serif";
|
||||||
|
context.fillText(text, xc, h);
|
||||||
|
} else {
|
||||||
|
context.textAlign = "start";
|
||||||
|
context.font = "12px sans-serif";
|
||||||
|
xc = x + event.starttime * ratio + 1;
|
||||||
|
context.fillText(text, xc, h);
|
||||||
|
|
||||||
|
xc = xc + context.measureText(text).width;
|
||||||
|
context.font = "10px sans-serif";
|
||||||
|
context.fillText(ms, xc, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
h += 38;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawCanvases(width)
|
||||||
|
{
|
||||||
|
for (k = 0; k < requests_data.requests.length; k++) {
|
||||||
|
drawCanvas(requests_data.requests[k], requests_data.max, {{ threshold }}, width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var requests_data = {
|
||||||
|
"max": {{ collector.events.section.endtime }},
|
||||||
|
"requests": [
|
||||||
|
{{ _self.dump_request_data(token, profile, collector.events, collector.events.section.origin) }}
|
||||||
|
|
||||||
|
{% if profile.children|length %}
|
||||||
|
,
|
||||||
|
{% for child in profile.children %}
|
||||||
|
{{ _self.dump_request_data(child.token, child, child.getcollector('time').events, collector.events.section.origin) }}{{ loop.last ? '' : ',' }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
drawCanvases({{ width }});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% macro dump_request_data(token, profile, events, origin) %}
|
||||||
|
{
|
||||||
|
"id": "{{ token }}",
|
||||||
|
"left": {{ events.section.origin - origin }},
|
||||||
|
"events": [
|
||||||
|
{{ _self.dump_events(events) }}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro dump_events(events) %}
|
||||||
|
{% for name, event in events %}
|
||||||
|
{% if 'section' != name %}
|
||||||
|
{
|
||||||
|
"name": "{{ name }}",
|
||||||
|
"category": "{{ event.category }}",
|
||||||
|
"origin": {{ event.origin }},
|
||||||
|
"starttime": {{ event.starttime }},
|
||||||
|
"endtime": {{ event.endtime }},
|
||||||
|
"totaltime": {{ event.totaltime }},
|
||||||
|
"periods": [
|
||||||
|
{%- for period in event.periods -%}
|
||||||
|
{"begin": {{ period.0 }}, "end": {{ period.1 }}}{{ loop.last ? '' : ', ' }}
|
||||||
|
{%- endfor -%}
|
||||||
|
]
|
||||||
|
}{{ loop.last ? '' : ',' }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro display_timeline(id, events, threshold, colors, width) %}
|
||||||
|
{% set height = 0 %}
|
||||||
|
{% for name, event in events if 'section' != name and event.totaltime >= threshold %}
|
||||||
|
{% set height = height + 38 %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<small>
|
||||||
|
{% for category, color in colors %}
|
||||||
|
<span style="background-color: {{ color }}"> </span> {{ category }}
|
||||||
|
{% endfor %}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<canvas width="{{ width }}" height="{{ height }}" id="{{ id }}" class="timeline"></canvas>
|
||||||
|
{% endmacro %}
|
@ -1,11 +0,0 @@
|
|||||||
{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}
|
|
||||||
|
|
||||||
{% block toolbar %}
|
|
||||||
{% set icon %}
|
|
||||||
<img width="16" height="28" alt="Timers" style="vertical-align: middle; margin-right: 5px;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAcCAYAAABoMT8aAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAiNJREFUeNpi/P//PwMlgImBQjDwBrCcO3cOq0RRUdF3ZH5fXx8nTVzAePbsWcq8gMwxMjJiSUlJcXv9+nXm169fbf78+SMAVsTC8paXl3ePmJjYjJkzZx4GevsviheAGhmBguL+/v4779y5s/Xjx48+MM0gAGQLv3//PvzmzZv7AwMD19y+fVsEpAfsBWBCYly8eLHcsmXLjnz//l2GGGcDXXM1IyPD2dvb+xXIBTwbN25chU3zgQMHwBgdfP78WXvp0qVzgUwuprq6utg3b96YkRp4z549854wYYI7071791LJjYFLly7lM7148UKHXAOALtdnAYYwCyGFyOHg4OAAZ3/69ImfopTIzMz8j4WVlfXf79+/sRqEbBs2wMfH94tJXV39DbkuUFFReclkb29/jlwDPD09jzGFhoZu0NTU/EKqZktLyzdOTk7bQX4/U1tbu1pcXPwvsZoVFBR+lZeXLwUyz4MMuCMlJbWmv79/o56e3k9Cms3MzL5PmjRphYCAwCYg9wE4MwEZwkBsDsReO3fudN+zZ4/shQsX2ICxA9bEzs7OYGBg8NPHx+eBra3tdqDQVpDLgfgjuEABZk2QS3hBAQvExkBsAHIpMAsLAOP6PzC63gP590FOBmJQCXQPiL8Ai4D/KCUS0CBWIAUqB8SAWAiIQeUgqOIAlY/vgPgVEH8AavyDtUQCSoDc/BqEoQUGLIH9A9mGtUwc8JoJIMAAS9XemfR7crQAAAAASUVORK5CYII="/>
|
|
||||||
{% endset %}
|
|
||||||
{% set text %}
|
|
||||||
{{ '%.0f'|format(collector.time * 1000) }} ms
|
|
||||||
{% endset %}
|
|
||||||
{% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': false } %}
|
|
||||||
{% endblock %}
|
|
@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\HttpKernel\DataCollector;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||||
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TimeDataCollector.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class TimeDataCollector extends DataCollector
|
||||||
|
{
|
||||||
|
protected $kernel;
|
||||||
|
|
||||||
|
public function __construct(KernelInterface $kernel = null)
|
||||||
|
{
|
||||||
|
$this->kernel = $kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function collect(Request $request, Response $response, \Exception $exception = null)
|
||||||
|
{
|
||||||
|
$this->data = array(
|
||||||
|
'start_time' => (null !== $this->kernel ? $this->kernel->getStartTime() : $_SERVER['REQUEST_TIME']) * 1000,
|
||||||
|
'events' => array(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the request events.
|
||||||
|
*
|
||||||
|
* @param array $event The request events
|
||||||
|
*/
|
||||||
|
public function setEvents(array $events)
|
||||||
|
{
|
||||||
|
foreach ($events as $event) {
|
||||||
|
$event->ensureStopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->data['events'] = $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the request events.
|
||||||
|
*
|
||||||
|
* @return array The request events
|
||||||
|
*/
|
||||||
|
public function getEvents()
|
||||||
|
{
|
||||||
|
return $this->data['events'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the request elapsed time.
|
||||||
|
*
|
||||||
|
* @return integer The elapsed time
|
||||||
|
*/
|
||||||
|
public function getTotalTime()
|
||||||
|
{
|
||||||
|
$values = array_values($this->data['events']);
|
||||||
|
$lastEvent = $values[count($values) - 1];
|
||||||
|
|
||||||
|
return $lastEvent->getOrigin() + $lastEvent->getEndTime() - $this->data['start_time'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the initialization time.
|
||||||
|
*
|
||||||
|
* This is the time spent until the beginning of the request handling.
|
||||||
|
*
|
||||||
|
* @return integer The elapsed time
|
||||||
|
*/
|
||||||
|
public function getInitTime()
|
||||||
|
{
|
||||||
|
return $this->data['events']['section']->getOrigin() - $this->getStartTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the request time.
|
||||||
|
*
|
||||||
|
* @return integer The time
|
||||||
|
*/
|
||||||
|
public function getStartTime()
|
||||||
|
{
|
||||||
|
return $this->data['start_time'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return 'time';
|
||||||
|
}
|
||||||
|
}
|
@ -1,60 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Symfony package.
|
|
||||||
*
|
|
||||||
* (c) Fabien Potencier <fabien@symfony.com>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Symfony\Component\HttpKernel\DataCollector;
|
|
||||||
|
|
||||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
|
||||||
use Symfony\Component\HttpKernel\KernelInterface;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TimerDataCollector.
|
|
||||||
*
|
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
|
||||||
*/
|
|
||||||
class TimerDataCollector extends DataCollector
|
|
||||||
{
|
|
||||||
protected $kernel;
|
|
||||||
|
|
||||||
public function __construct(KernelInterface $kernel)
|
|
||||||
{
|
|
||||||
$this->kernel = $kernel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function collect(Request $request, Response $response, \Exception $exception = null)
|
|
||||||
{
|
|
||||||
$this->data = array(
|
|
||||||
'time' => microtime(true) - $this->kernel->getStartTime(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the request time.
|
|
||||||
*
|
|
||||||
* @return integer The time
|
|
||||||
*/
|
|
||||||
public function getTime()
|
|
||||||
{
|
|
||||||
return $this->data['time'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getName()
|
|
||||||
{
|
|
||||||
return 'timer';
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user