Skip to content

Commit e823e85

Browse files
committed
feature symfony#21065 Added cache data collector and profiler page (Nyholm)
This PR was squashed before being merged into the 3.3-dev branch (closes symfony#21065). Discussion ---------- Added cache data collector and profiler page | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | symfony#19297 | License | MIT | Doc PR | n/a Adding a first version of cache profiler page. Most things are taken from PHP-cache. FYI: @aequasi ### What is included? A collector, recording adapter and a profiler page. ![screen shot 2016-12-27 at 16 07 35](https://cloud.githubusercontent.com/assets/1275206/21502325/4bee2ed4-cc4f-11e6-89fc-37ed16aca864.png) ![screen shot 2016-12-27 at 16 07 45](https://cloud.githubusercontent.com/assets/1275206/21502326/4bee9450-cc4f-11e6-904d-527b7b0ce85b.png) ### What is not included? * A good logo * Nice design on the profiler page This PR aims to pass as the minimum requirement for a cache page. Im happy to do fancy extra features but those should be a different PR. Commits ------- 7497f1c Added cache data collector and profiler page
2 parents 71f8f15 + 7497f1c commit e823e85

File tree

7 files changed

+400
-0
lines changed

7 files changed

+400
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\Cache\Adapter\TraceableAdapter;
15+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
19+
/**
20+
* Inject a data collector to all the cache services to be able to get detailed statistics.
21+
*
22+
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
23+
*/
24+
class CacheCollectorPass implements CompilerPassInterface
25+
{
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function process(ContainerBuilder $container)
30+
{
31+
if (!$container->hasDefinition('data_collector.cache')) {
32+
return;
33+
}
34+
35+
$collectorDefinition = $container->getDefinition('data_collector.cache');
36+
foreach ($container->findTaggedServiceIds('cache.pool') as $id => $attributes) {
37+
if ($container->getDefinition($id)->isAbstract()) {
38+
continue;
39+
}
40+
41+
$container->register($id.'.recorder', TraceableAdapter::class)
42+
->setDecoratedService($id)
43+
->addArgument(new Reference($id.'.recorder.inner'))
44+
->setPublic(false);
45+
46+
// Tell the collector to add the new instance
47+
$collectorDefinition->addMethodCall('addInstance', array($id, new Reference($id)));
48+
}
49+
}
50+
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $
356356

357357
$loader->load('profiling.xml');
358358
$loader->load('collectors.xml');
359+
$loader->load('cache_debug.xml');
359360

360361
if ($this->formConfigEnabled) {
361362
$loader->load('form_debug.xml');

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass;
1515
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProcessorPass;
1616
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddValidatorInitializersPass;
17+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CacheCollectorPass;
1718
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPass;
1819
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolClearerPass;
1920
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ControllerArgumentValueResolverPass;
@@ -105,6 +106,7 @@ public function build(ContainerBuilder $container)
105106
$container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING);
106107
$container->addCompilerPass(new CompilerDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING);
107108
$container->addCompilerPass(new ConfigCachePass());
109+
$container->addCompilerPass(new CacheCollectorPass());
108110
}
109111
}
110112
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<!-- DataCollector -->
9+
<service id="data_collector.cache" class="Symfony\Component\Cache\DataCollector\CacheDataCollector">
10+
<tag name="data_collector" template="@WebProfiler/Collector/cache.html.twig" id="cache" priority="275" />
11+
</service>
12+
</services>
13+
</container>
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}
2+
3+
{% block toolbar %}
4+
{% if collector.totals.calls > 0 %}
5+
{% set icon %}
6+
{{ include('@WebProfiler/Icon/cache.svg') }}
7+
<span class="sf-toolbar-value">{{ collector.totals.calls }}</span>
8+
<span class="sf-toolbar-info-piece-additional-detail">
9+
<span class="sf-toolbar-label">in</span>
10+
<span class="sf-toolbar-value">{{ '%0.2f'|format(collector.totals.time * 1000) }}</span>
11+
<span class="sf-toolbar-label">ms</span>
12+
</span>
13+
{% endset %}
14+
{% set text %}
15+
<div class="sf-toolbar-info-piece">
16+
<b>Cache Calls</b>
17+
<span>{{ collector.totals.calls }}</span>
18+
</div>
19+
<div class="sf-toolbar-info-piece">
20+
<b>Total time</b>
21+
<span>{{ '%0.2f'|format(collector.totals.time * 1000) }} ms</span>
22+
</div>
23+
<div class="sf-toolbar-info-piece">
24+
<b>Cache hits</b>
25+
<span>{{ collector.totals.hits }}/{{ collector.totals.reads }} ({{ collector.totals['hits/reads'] }})</span>
26+
</div>
27+
<div class="sf-toolbar-info-piece">
28+
<b>Cache writes</b>
29+
<span>{{ collector.totals.writes }}</span>
30+
</div>
31+
{% endset %}
32+
{% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %}
33+
{% endif %}
34+
{% endblock %}
35+
36+
{% block menu %}
37+
<span class="label {{ collector.totals.calls == 0 ? 'disabled' }}">
38+
<span class="icon">
39+
{{ include('@WebProfiler/Icon/cache.svg') }}
40+
</span>
41+
<strong>Cache</strong>
42+
<span class="count">
43+
<span>{{ collector.totals.calls }}</span>
44+
<span>{{ '%0.2f'|format(collector.totals.time * 1000) }} ms</span>
45+
</span>
46+
</span>
47+
{% endblock %}
48+
49+
{% block panel %}
50+
<h2>Cache</h2>
51+
<div class="metrics">
52+
<div class="metric">
53+
<span class="value">{{ collector.totals.calls }}</span>
54+
<span class="label">Total calls</span>
55+
</div>
56+
<div class="metric">
57+
<span class="value">{{ '%0.2f'|format(collector.totals.time * 1000) }} ms</span>
58+
<span class="label">Total time</span>
59+
</div>
60+
<div class="metric">
61+
<span class="value">{{ collector.totals.reads }}</span>
62+
<span class="label">Total reads</span>
63+
</div>
64+
<div class="metric">
65+
<span class="value">{{ collector.totals.hits }}</span>
66+
<span class="label">Total hits</span>
67+
</div>
68+
<div class="metric">
69+
<span class="value">{{ collector.totals.misses }}</span>
70+
<span class="label">Total misses</span>
71+
</div>
72+
<div class="metric">
73+
<span class="value">{{ collector.totals.writes }}</span>
74+
<span class="label">Total writes</span>
75+
</div>
76+
<div class="metric">
77+
<span class="value">{{ collector.totals.deletes }}</span>
78+
<span class="label">Total deletes</span>
79+
</div>
80+
<div class="metric">
81+
<span class="value">{{ collector.totals['hits/reads'] }}</span>
82+
<span class="label">Hits/reads</span>
83+
</div>
84+
</div>
85+
86+
{% for name, calls in collector.calls %}
87+
<h3>Statistics for '{{ name }}'</h3>
88+
<div class="metrics">
89+
{% for key, value in collector.statistics[name] %}
90+
<div class="metric">
91+
<span class="value">
92+
{% if key == 'time' %}
93+
{{ '%0.2f'|format(1000*value) }} ms
94+
{% else %}
95+
{{ value }}
96+
{% endif %}
97+
</span>
98+
<span class="label">{{ key|capitalize }}</span>
99+
</div>
100+
{% endfor %}
101+
</div>
102+
<h4>Calls for '{{ name }}'</h4>
103+
104+
{% if not collector.totals.calls %}
105+
<p>
106+
<em>No calls.</em>
107+
</p>
108+
{% else %}
109+
<table>
110+
<thead>
111+
<tr>
112+
<th style="width: 5rem;">Key</th>
113+
<th>Value</th>
114+
</tr>
115+
</thead>
116+
<tbody>
117+
{% for i, call in calls %}
118+
<tr>
119+
<th style="padding-top:2rem">#{{ i }}</th>
120+
<th style="padding-top:2rem">Pool::{{ call.name }}</th>
121+
</tr>
122+
<tr>
123+
<th>Argument</th>
124+
<td>{{ profiler_dump(call.argument, maxDepth=2) }}</td>
125+
</tr>
126+
<tr>
127+
<th>Results</th>
128+
<td>
129+
{% if call.result != false %}
130+
{{ profiler_dump(call.result, maxDepth=1) }}
131+
{% endif %}
132+
</td>
133+
</tr>
134+
<tr>
135+
<th>Time</th>
136+
<td>{{ '%0.2f'|format((call.end - call.start) * 1000) }} ms</td>
137+
</tr>
138+
139+
{% endfor %}
140+
</tbody>
141+
142+
</table>
143+
{% endif %}
144+
{% endfor %}
145+
146+
{% endblock %}
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)