feature #13428 Added a Twig profiler (fabpot)
This PR was merged into the 2.7 branch. Discussion ---------- Added a Twig profiler | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | #6914 | License | MIT | Doc PR | n/a This PR integrates the new Twig 1.18 Profiler (see twigphp/Twig#1597) into Symfony (replace the current TimedTwigEngine) and adds a new Twig panel. The timers are now available for all rendered templates (TimedTwigEngine was only able to get information from a few of them -- mainly the first template only). The Twig panel gives you a lot of information about the execution of the templates, including a call graph. ![image](https://cloud.githubusercontent.com/assets/47313/5773885/fdf6756e-9d67-11e4-8dce-5ec20b07eca9.png) ![image](https://cloud.githubusercontent.com/assets/47313/5773892/0ae24d5c-9d68-11e4-9cbe-767bc31c9152.png) ![image](https://cloud.githubusercontent.com/assets/47313/5773897/13c0b6b6-9d68-11e4-95a1-b9188aca9651.png) ![image](https://cloud.githubusercontent.com/assets/47313/5773902/1c5498d8-9d68-11e4-975e-9822385fb836.png) ![image](https://cloud.githubusercontent.com/assets/47313/5773917/4eba00ba-9d68-11e4-8114-0a2d05eae5ea.png) Commits -------daad64f
added a Twig panel to the WebProfileref0c967
integrated the Twig profiler
This commit is contained in:
commit
9e8cb01fde
@ -18,7 +18,7 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.3.9",
|
"php": ">=5.3.9",
|
||||||
"doctrine/common": "~2.3",
|
"doctrine/common": "~2.3",
|
||||||
"twig/twig": "~1.17",
|
"twig/twig": "~1.18",
|
||||||
"psr/log": "~1.0"
|
"psr/log": "~1.0"
|
||||||
},
|
},
|
||||||
"replace": {
|
"replace": {
|
||||||
|
143
src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php
Normal file
143
src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
<?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\Bridge\Twig\DataCollector;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||||
|
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TwigDataCollector.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class TwigDataCollector extends DataCollector implements LateDataCollectorInterface
|
||||||
|
{
|
||||||
|
private $profile;
|
||||||
|
private $computed;
|
||||||
|
|
||||||
|
public function __construct(\Twig_Profiler_Profile $profile)
|
||||||
|
{
|
||||||
|
$this->profile = $profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function collect(Request $request, Response $response, \Exception $exception = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function lateCollect()
|
||||||
|
{
|
||||||
|
$this->data['profile'] = serialize($this->profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTime()
|
||||||
|
{
|
||||||
|
return $this->getProfile()->getDuration() * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTemplateCount()
|
||||||
|
{
|
||||||
|
return $this->getComputedData('template_count');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTemplates()
|
||||||
|
{
|
||||||
|
return $this->getComputedData('templates');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBlockCount()
|
||||||
|
{
|
||||||
|
return $this->getComputedData('block_count');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMacroCount()
|
||||||
|
{
|
||||||
|
return $this->getComputedData('macro_count');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHtmlCallGraph()
|
||||||
|
{
|
||||||
|
$dumper = new \Twig_Profiler_Dumper_Html();
|
||||||
|
|
||||||
|
return new \Twig_Markup($dumper->dump($this->getProfile()), 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProfile()
|
||||||
|
{
|
||||||
|
if (null === $this->profile) {
|
||||||
|
$this->profile = unserialize($this->data['profile']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getComputedData($index)
|
||||||
|
{
|
||||||
|
if (null === $this->computed) {
|
||||||
|
$this->computed = $this->computeData($this->getProfile());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->computed['index'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function computeData(\Twig_Profiler_Profile $profile)
|
||||||
|
{
|
||||||
|
$data = array(
|
||||||
|
'template_count' => 0,
|
||||||
|
'block_count' => 0,
|
||||||
|
'macro_count' => 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
$templates = array();
|
||||||
|
foreach ($profile as $p) {
|
||||||
|
$d = $this->computeData($p);
|
||||||
|
|
||||||
|
$data['template_count'] += ($p->isTemplate() ? 1 : 0) + $d['template_count'];
|
||||||
|
$data['block_count'] += ($p->isBlock() ? 1 : 0) + $d['block_count'];
|
||||||
|
$data['macro_count'] += ($p->isMacro() ? 1 : 0) + $d['macro_count'];
|
||||||
|
|
||||||
|
if ($p->isTemplate()) {
|
||||||
|
if (!isset($templates[$p->getTemplate()])) {
|
||||||
|
$templates[$p->getTemplate()] = 1;
|
||||||
|
} else {
|
||||||
|
$templates[$p->getTemplate()]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($d['templates'] as $template => $count) {
|
||||||
|
if (!isset($templates[$template])) {
|
||||||
|
$templates[$template] = $count;
|
||||||
|
} else {
|
||||||
|
$templates[$template] += $count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$data['templates'] = $templates;
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return 'twig';
|
||||||
|
}
|
||||||
|
}
|
58
src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php
Normal file
58
src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?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\Bridge\Twig\Extension;
|
||||||
|
|
||||||
|
use Symfony\Component\Stopwatch\Stopwatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*/
|
||||||
|
class ProfilerExtension extends \Twig_Extension_Profiler
|
||||||
|
{
|
||||||
|
private $stopwatch;
|
||||||
|
private $events;
|
||||||
|
|
||||||
|
public function __construct(\Twig_Profiler_Profile $profile, Stopwatch $stopwatch = null)
|
||||||
|
{
|
||||||
|
parent::__construct($profile);
|
||||||
|
|
||||||
|
$this->stopwatch = $stopwatch;
|
||||||
|
$this->events = new \SplObjectStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function enter(\Twig_Profiler_Profile $profile)
|
||||||
|
{
|
||||||
|
if ($this->stopwatch && $profile->isTemplate()) {
|
||||||
|
$this->events[$profile] = $this->stopwatch->start($profile->getName(), 'template');
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::enter($profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function leave(\Twig_Profiler_Profile $profile)
|
||||||
|
{
|
||||||
|
parent::leave($profile);
|
||||||
|
|
||||||
|
if ($this->stopwatch && $profile->isTemplate()) {
|
||||||
|
$this->events[$profile]->stop();
|
||||||
|
unset($this->events[$profile]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return 'native_profiler';
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.3.9",
|
"php": ">=5.3.9",
|
||||||
"symfony/security-csrf": "~2.6|~3.0.0",
|
"symfony/security-csrf": "~2.6|~3.0.0",
|
||||||
"twig/twig": "~1.17"
|
"twig/twig": "~1.18"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/finder": "~2.3|~3.0.0",
|
"symfony/finder": "~2.3|~3.0.0",
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
namespace Symfony\Bundle\TwigBundle\Debug;
|
namespace Symfony\Bundle\TwigBundle\Debug;
|
||||||
|
|
||||||
|
trigger_error('The '.__NAMESPACE__.'\TimedTwigEngine class is deprecated since version 2.7 and will be removed in 3.0. Use the Twig native profiler instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
use Symfony\Bundle\TwigBundle\TwigEngine;
|
use Symfony\Bundle\TwigBundle\TwigEngine;
|
||||||
use Symfony\Component\Templating\TemplateNameParserInterface;
|
use Symfony\Component\Templating\TemplateNameParserInterface;
|
||||||
use Symfony\Component\Stopwatch\Stopwatch;
|
use Symfony\Component\Stopwatch\Stopwatch;
|
||||||
@ -20,6 +22,8 @@ use Symfony\Component\Config\FileLocatorInterface;
|
|||||||
* Times the time spent to render a template.
|
* Times the time spent to render a template.
|
||||||
*
|
*
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* @deprecated since version 2.7, to be removed in 3.0. Use the Twig native profiler instead.
|
||||||
*/
|
*/
|
||||||
class TimedTwigEngine extends TwigEngine
|
class TimedTwigEngine extends TwigEngine
|
||||||
{
|
{
|
||||||
|
@ -63,13 +63,13 @@ class ExtensionPass implements CompilerPassInterface
|
|||||||
$container->getDefinition('twig.extension.code')->replaceArgument(0, $container->getParameter('templating.helper.code.file_link_format'));
|
$container->getDefinition('twig.extension.code')->replaceArgument(0, $container->getParameter('templating.helper.code.file_link_format'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($container->getParameter('kernel.debug')) {
|
||||||
|
$container->getDefinition('twig.extension.profiler')->addTag('twig.extension');
|
||||||
|
$container->getDefinition('twig.extension.debug')->addTag('twig.extension');
|
||||||
|
}
|
||||||
|
|
||||||
if ($container->has('templating')) {
|
if ($container->has('templating')) {
|
||||||
$container->getDefinition('twig.cache_warmer')->addTag('kernel.cache_warmer');
|
$container->getDefinition('twig.cache_warmer')->addTag('kernel.cache_warmer');
|
||||||
|
|
||||||
if ($container->getParameter('kernel.debug')) {
|
|
||||||
$container->setDefinition('templating.engine.twig', $container->findDefinition('debug.templating.engine.twig'));
|
|
||||||
$container->setAlias('debug.templating.engine.twig', 'templating.engine.twig');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$loader = $container->getDefinition('twig.loader.native_filesystem');
|
$loader = $container->getDefinition('twig.loader.native_filesystem');
|
||||||
$loader->addTag('twig.loader');
|
$loader->addTag('twig.loader');
|
||||||
|
@ -101,10 +101,6 @@ class TwigExtension extends Extension
|
|||||||
$config['extensions']
|
$config['extensions']
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($container->getParameter('kernel.debug')) {
|
|
||||||
$loader->load('debug.xml');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($config['autoescape_service']) && isset($config['autoescape_service_method'])) {
|
if (isset($config['autoescape_service']) && isset($config['autoescape_service_method'])) {
|
||||||
$config['autoescape'] = array(new Reference($config['autoescape_service']), $config['autoescape_service_method']);
|
$config['autoescape'] = array(new Reference($config['autoescape_service']), $config['autoescape_service_method']);
|
||||||
}
|
}
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
<?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="templating.locator" />
|
|
||||||
<argument type="service" id="debug.stopwatch" />
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<service id="twig.extension.debug" class="Twig_Extension_Debug" public="false">
|
|
||||||
<tag name="twig.extension" />
|
|
||||||
</service>
|
|
||||||
</services>
|
|
||||||
</container>
|
|
@ -70,6 +70,18 @@
|
|||||||
<argument type="service" id="templating.locator" />
|
<argument type="service" id="templating.locator" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="twig.extension.profiler" class="Symfony\Bridge\Twig\Extension\ProfilerExtension" public="false">
|
||||||
|
<argument type="service" id="twig.profile" />
|
||||||
|
<argument type="service" id="debug.stopwatch" on-invalid="null" />
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="twig.profile" class="Twig_Profiler_Profile" />
|
||||||
|
|
||||||
|
<service id="data_collector.twig" class="Symfony\Bridge\Twig\DataCollector\TwigDataCollector" public="false">
|
||||||
|
<tag name="data_collector" template="@WebProfiler/Collector/twig.html.twig" id="twig" priority="255" />
|
||||||
|
<argument type="service" id="twig.profile" />
|
||||||
|
</service>
|
||||||
|
|
||||||
<service id="twig.extension.trans" class="%twig.extension.trans.class%" public="false">
|
<service id="twig.extension.trans" class="%twig.extension.trans.class%" public="false">
|
||||||
<argument type="service" id="translator" />
|
<argument type="service" id="translator" />
|
||||||
</service>
|
</service>
|
||||||
@ -121,6 +133,8 @@
|
|||||||
<argument type="service" id="twig.form.renderer" />
|
<argument type="service" id="twig.form.renderer" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="twig.extension.debug" class="Twig_Extension_Debug" public="false" />
|
||||||
|
|
||||||
<service id="twig.form.engine" class="%twig.form.engine.class%" public="false">
|
<service id="twig.form.engine" class="%twig.form.engine.class%" public="false">
|
||||||
<argument>%twig.form.resources%</argument>
|
<argument>%twig.form.resources%</argument>
|
||||||
</service>
|
</service>
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block toolbar %}
|
||||||
|
{% set time = collector.templatecount ? '%0.0f ms'|format(collector.time) : 'n/a' %}
|
||||||
|
{% set icon %}
|
||||||
|
<img height="28" alt="Twig" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAcCAYAAACOGPReAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAbElEQVRIx2NgGAXUBowMDAwMaWlp/6ll4KxZsxhZYJy0tDRqGMjAwMDAwEQL77OgCxSXlJBsSG9PDwqfJi6lj/fRvTJ4XYocUTBXE4q8oRtRRBnKwsw8RFw6fA0lKkd1dnYOIpfCCthRMIIAAI0IFu9Hxh7ZAAAAAElFTkSuQmCC" />
|
||||||
|
<span class="sf-toolbar-status">{{ time }}</span>
|
||||||
|
{% endset %}
|
||||||
|
{% set text %}
|
||||||
|
<div class="sf-toolbar-info-piece">
|
||||||
|
<b>Render Time</b>
|
||||||
|
<span>{{ time }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sf-toolbar-info-piece">
|
||||||
|
<b>Template Calls</b>
|
||||||
|
<span>{{ collector.templatecount }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sf-toolbar-info-piece">
|
||||||
|
<b>Block Calls</b>
|
||||||
|
<span>{{ collector.blockcount }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sf-toolbar-info-piece">
|
||||||
|
<b>Macro Calls</b>
|
||||||
|
<span>{{ collector.macrocount }}</span>
|
||||||
|
</div>
|
||||||
|
{% endset %}
|
||||||
|
{% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block menu %}
|
||||||
|
<span class="label">
|
||||||
|
<span class="icon"><img alt="Twig" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAgCAYAAAABtRhCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAbklEQVRIx2NgGAVDHTDCGGlpaf9pZcmsWbPg9rAgS6SlpdHCMhQ+E72DlAWbYHFJCcUG9/b0YBWnuw9HLaRPosEV4cPHh9iyBczXxGaZ0WxBfBwwM4/mw1ELRy0c4MK7s7NzCPsQvYU1CkYBNgAAV5UW+fU+ZL4AAAAASUVORK5CYII="></span>
|
||||||
|
<strong>Twig</strong>
|
||||||
|
<span class="count">
|
||||||
|
<span>{{ collector.templatecount }}</span>
|
||||||
|
<span>{{ '%0.0f ms'|format(collector.time) }}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block panel %}
|
||||||
|
{% if collector.templatecount %}
|
||||||
|
<h2>Twig Stats</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Total Render Time<br /><small>including sub-requests rendering time</small></th>
|
||||||
|
<td><pre>{{ '%0.0f ms'|format(collector.time) }}</pre></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" style="width: 30%">Template Calls</th>
|
||||||
|
<td scope="col" style="width: 60%"><pre>{{ collector.templatecount }}</pre></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Block Calls</th>
|
||||||
|
<td><pre>{{ collector.blockcount }}</pre></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Macro Calls</th>
|
||||||
|
<td><pre>{{ collector.macrocount }}</pre></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Rendered Templates</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Template Name</th>
|
||||||
|
<th scope="col">Render Count</th>
|
||||||
|
</tr>
|
||||||
|
{% for template, count in collector.templates %}
|
||||||
|
<tr>
|
||||||
|
<td><code>{{ template }}</code></td>
|
||||||
|
<td><pre>{{ count }}</pre></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Rendering Call Graph</h2>
|
||||||
|
|
||||||
|
{{ collector.htmlcallgraph }}
|
||||||
|
{% else %}
|
||||||
|
<p><em>No Twig templates were rendered for this request.</em></p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
@ -19,7 +19,7 @@
|
|||||||
"php": ">=5.3.9",
|
"php": ">=5.3.9",
|
||||||
"symfony/http-kernel": "~2.4|~3.0.0",
|
"symfony/http-kernel": "~2.4|~3.0.0",
|
||||||
"symfony/routing": "~2.2|~3.0.0",
|
"symfony/routing": "~2.2|~3.0.0",
|
||||||
"symfony/twig-bridge": "~2.2|~3.0.0"
|
"symfony/twig-bridge": "~2.7|~3.0.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/config": "~2.2|~3.0.0",
|
"symfony/config": "~2.2|~3.0.0",
|
||||||
|
Reference in New Issue
Block a user