+ */
+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;
+ }
+}
diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php
index f7a65debd4..a27e378f3f 100644
--- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php
+++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php
@@ -63,6 +63,13 @@ class TwigExtension extends Extension
$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(
'Twig_Environment',
'Twig_ExtensionInterface',
diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml
new file mode 100644
index 0000000000..b83eb8b0dc
--- /dev/null
+++ b/src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Symfony\Bundle\TwigBundle\Debug\TimedTwigEngine
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/TwigBundle/TwigEngine.php b/src/Symfony/Bundle/TwigBundle/TwigEngine.php
index 7c9a6b22f5..639212acc3 100644
--- a/src/Symfony/Bundle/TwigBundle/TwigEngine.php
+++ b/src/Symfony/Bundle/TwigBundle/TwigEngine.php
@@ -15,7 +15,6 @@ use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables;
use Symfony\Component\Templating\TemplateNameParserInterface;
use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* This engine knows how to render Twig templates.
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/public/css/profiler.css b/src/Symfony/Bundle/WebProfilerBundle/Resources/public/css/profiler.css
index 01a6aa669d..15196a313e 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/public/css/profiler.css
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/public/css/profiler.css
@@ -427,3 +427,9 @@ td.main, td.menu {
#navigation .import input {
width: 100px;
}
+
+.timeline {
+ background-color: #fbfbfb;
+ margin-bottom: 15px;
+ margin-top: 5px;
+}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/public/images/profiler/time.png b/src/Symfony/Bundle/WebProfilerBundle/Resources/public/images/profiler/time.png
new file mode 100644
index 0000000000..95ec7e5528
Binary files /dev/null and b/src/Symfony/Bundle/WebProfilerBundle/Resources/public/images/profiler/time.png differ
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/public/images/timer.png b/src/Symfony/Bundle/WebProfilerBundle/Resources/public/images/time.png
similarity index 100%
rename from src/Symfony/Bundle/WebProfilerBundle/Resources/public/images/timer.png
rename to src/Symfony/Bundle/WebProfilerBundle/Resources/public/images/time.png
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig
new file mode 100644
index 0000000000..962550b0e0
--- /dev/null
+++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig
@@ -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 %}
+
+ {% endset %}
+ {% set text %}
+ {{ '%.0f'|format(collector.totaltime) }} ms
+ {% endset %}
+ {% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %}
+{% endblock %}
+
+{% block menu %}
+
+
+ Timeline
+
+{% endblock %}
+
+{% block panel %}
+ Timeline
+
+ {% set threshold = app.request.query.get('threshold', 1) %}
+ {% set width = app.request.query.get('width', 990) %}
+
+
+
+
+ Main Request
+
+ - {{ collector.events.section.totaltime }} ms
+ {% if profile.parent %}
+ - parent
+ {% endif %}
+
+
+
+ {% 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 %}
+
+
+ {{ _self.display_timeline('timeline_' ~ child.token, events, threshold, colors, width) }}
+ {% endfor %}
+ {% endif %}
+
+
+{% 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 %}
+
+
+
+ {% for category, color in colors %}
+ {{ category }}
+ {% endfor %}
+
+
+
+
+{% endmacro %}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/timer.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/timer.html.twig
deleted file mode 100644
index bc734ad3d3..0000000000
--- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/timer.html.twig
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}
-
-{% block toolbar %}
- {% set icon %}
-
- {% endset %}
- {% set text %}
- {{ '%.0f'|format(collector.time * 1000) }} ms
- {% endset %}
- {% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': false } %}
-{% endblock %}
diff --git a/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php
new file mode 100644
index 0000000000..5896bff2cd
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php
@@ -0,0 +1,110 @@
+
+ *
+ * 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
+ */
+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';
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/DataCollector/TimerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/TimerDataCollector.php
deleted file mode 100644
index 3c8839e503..0000000000
--- a/src/Symfony/Component/HttpKernel/DataCollector/TimerDataCollector.php
+++ /dev/null
@@ -1,60 +0,0 @@
-
- *
- * 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
- */
-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';
- }
-}