Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 357b552

Browse filesBrowse files
committed
Added cache data collector and profiler page
1 parent 64e1da0 commit 357b552
Copy full SHA for 357b552

File tree

Expand file treeCollapse file tree

7 files changed

+362
-0
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+362
-0
lines changed
+52Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
$serviceIds = $container->findTaggedServiceIds('cache.pool');
37+
38+
foreach (array_keys($serviceIds) as $id) {
39+
if ($container->getDefinition($id)->isAbstract()) {
40+
continue;
41+
}
42+
43+
$container->register($id.'.recorder', TraceableAdapter::class)
44+
->setDecoratedService($id)
45+
->addArgument(new Reference($id.'.recorder.inner'))
46+
->setPublic(false);
47+
48+
// Tell the collector to add the new instance
49+
$collectorDefinition->addMethodCall('addInstance', array($id, new Reference($id)));
50+
}
51+
}
52+
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $
349349

350350
$loader->load('profiling.xml');
351351
$loader->load('collectors.xml');
352+
$loader->load('cache_debug.xml');
352353

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

‎src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProcessorPass;
1616
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddValidatorInitializersPass;
1717
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass;
18+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CacheCollectorPass;
1819
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPass;
1920
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolClearerPass;
2021
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ControllerArgumentValueResolverPass;
@@ -103,6 +104,7 @@ public function build(ContainerBuilder $container)
103104
$container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING);
104105
$container->addCompilerPass(new CompilerDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING);
105106
$container->addCompilerPass(new ConfigCachePass());
107+
$container->addCompilerPass(new CacheCollectorPass());
106108
}
107109
}
108110
}
+13Lines 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>
+111Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
{% for name, calls in collector.calls %}
52+
<h3>Statistics for '{{ name }}'</h3>
53+
<div class="metrics">
54+
{% for key, value in collector.statistics[name] %}
55+
<div class="metric">
56+
<span class="value">
57+
{% if key == 'time' %}
58+
<td>{{ '%0.2f'|format(1000*value) }} ms</td>
59+
{% else %}
60+
<td>{{ value }}</td>
61+
{% endif %}
62+
</span>
63+
<span class="label">{{ key|capitalize }}</span>
64+
</div>
65+
{% endfor %}
66+
</div>
67+
<h4>Calls for '{{ name }}'</h4>
68+
69+
{% if not collector.totals.calls %}
70+
<p>
71+
<em>No calls.</em>
72+
</p>
73+
{% else %}
74+
<table>
75+
<thead>
76+
<tr>
77+
<th style="width: 5rem;">Key</th>
78+
<th>Value</th>
79+
</tr>
80+
</thead>
81+
<tbody>
82+
{% for i, call in calls %}
83+
<tr>
84+
<th style="padding-top:2rem">#{{ i }}</th>
85+
<th style="padding-top:2rem">Pool::{{ call.name }}</th>
86+
</tr>
87+
<tr>
88+
<th>Argument</th>
89+
<td>{{ profiler_dump(call.argument, maxDepth=2) }}</td>
90+
</tr>
91+
<tr>
92+
<th>Results</th>
93+
<td>
94+
{% if call.result != false %}
95+
{{ profiler_dump(call.result, maxDepth=1) }}
96+
{% endif %}
97+
</td>
98+
</tr>
99+
<tr>
100+
<th>Time</th>
101+
<td>{{ '%0.2f'|format((call.end - call.start) * 1000) }} ms</td>
102+
</tr>
103+
104+
{% endfor %}
105+
</tbody>
106+
107+
</table>
108+
{% endif %}
109+
{% endfor %}
110+
111+
{% endblock %}
+8Lines changed: 8 additions & 0 deletions
Loading
+175Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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\Component\Cache\DataCollector;
13+
14+
use Symfony\Component\Cache\Adapter\TraceableAdapter;
15+
use Symfony\Component\Cache\Adapter\TraceableAdapterEvent;
16+
use Symfony\Component\HttpFoundation\Request;
17+
use Symfony\Component\HttpFoundation\Response;
18+
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
19+
20+
/**
21+
* @author Aaron Scherer <aequasi@gmail.com>
22+
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
23+
*/
24+
class CacheDataCollector extends DataCollector
25+
{
26+
/**
27+
* @var TraceableAdapter[]
28+
*/
29+
private $instances = array();
30+
31+
/**
32+
* @param string $name
33+
* @param TraceableAdapter $instance
34+
*/
35+
public function addInstance($name, TraceableAdapter $instance)
36+
{
37+
$this->instances[$name] = $instance;
38+
}
39+
40+
/**
41+
* {@inheritdoc}
42+
*/
43+
public function collect(Request $request, Response $response, \Exception $exception = null)
44+
{
45+
$empty = array('calls' => array(), 'config' => array(), 'options' => array(), 'statistics' => array());
46+
$this->data = array('instances' => $empty, 'total' => $empty);
47+
foreach ($this->instances as $name => $instance) {
48+
$calls = $instance->getCalls();
49+
foreach ($calls as $call) {
50+
if (isset($call->result)) {
51+
$call->result = $this->cloneVar($call->result);
52+
}
53+
if (isset($call->argument)) {
54+
$call->argument = $this->cloneVar($call->argument);
55+
}
56+
}
57+
$this->data['instances']['calls'][$name] = $calls;
58+
}
59+
60+
$this->data['instances']['statistics'] = $this->calculateStatistics();
61+
$this->data['total']['statistics'] = $this->calculateTotalStatistics();
62+
}
63+
64+
/**
65+
* {@inheritdoc}
66+
*/
67+
public function getName()
68+
{
69+
return 'cache';
70+
}
71+
72+
/**
73+
* Method returns amount of logged Cache reads: "get" calls.
74+
*
75+
* @return array
76+
*/
77+
public function getStatistics()
78+
{
79+
return $this->data['instances']['statistics'];
80+
}
81+
82+
/**
83+
* Method returns the statistic totals.
84+
*
85+
* @return array
86+
*/
87+
public function getTotals()
88+
{
89+
return $this->data['total']['statistics'];
90+
}
91+
92+
/**
93+
* Method returns all logged Cache call objects.
94+
*
95+
* @return mixed
96+
*/
97+
public function getCalls()
98+
{
99+
return $this->data['instances']['calls'];
100+
}
101+
102+
/**
103+
* @return array
104+
*/
105+
private function calculateStatistics()
106+
{
107+
$statistics = array();
108+
foreach ($this->data['instances']['calls'] as $name => $calls) {
109+
$statistics[$name] = array(
110+
'calls' => 0,
111+
'time' => 0,
112+
'reads' => 0,
113+
'hits' => 0,
114+
'misses' => 0,
115+
'writes' => 0,
116+
'deletes' => 0,
117+
);
118+
/** @var TraceableAdapterEvent $call */
119+
foreach ($calls as $call) {
120+
$statistics[$name]['calls'] += 1;
121+
$statistics[$name]['time'] += $call->end - $call->start;
122+
if ($call->name === 'getItem') {
123+
$statistics[$name]['reads'] += 1;
124+
if ($call->hits) {
125+
$statistics[$name]['hits'] += 1;
126+
} else {
127+
$statistics[$name]['misses'] += 1;
128+
}
129+
} elseif ($call->name === 'getItems') {
130+
$count = $call->hits + $call->misses;
131+
$statistics[$name]['reads'] += $count;
132+
$statistics[$name]['hits'] += $call->hits;
133+
$statistics[$name]['misses'] += $count - $call->misses;
134+
} elseif ($call->name === 'hasItem') {
135+
$statistics[$name]['reads'] += 1;
136+
if ($call->result === false) {
137+
$statistics[$name]['misses'] += 1;
138+
}
139+
} elseif ($call->name === 'save') {
140+
$statistics[$name]['writes'] += 1;
141+
} elseif ($call->name === 'deleteItem') {
142+
$statistics[$name]['deletes'] += 1;
143+
}
144+
}
145+
if ($statistics[$name]['reads']) {
146+
$statistics[$name]['hits/reads'] = round(100 * $statistics[$name]['hits'] / $statistics[$name]['reads'], 2).'%';
147+
} else {
148+
$statistics[$name]['hits/reads'] = 'N/A';
149+
}
150+
}
151+
152+
return $statistics;
153+
}
154+
155+
/**
156+
* @return array
157+
*/
158+
private function calculateTotalStatistics()
159+
{
160+
$statistics = $this->getStatistics();
161+
$totals = array('calls' => 0, 'time' => 0, 'reads' => 0, 'hits' => 0, 'misses' => 0, 'writes' => 0);
162+
foreach ($statistics as $name => $values) {
163+
foreach ($totals as $key => $value) {
164+
$totals[$key] += $statistics[$name][$key];
165+
}
166+
}
167+
if ($totals['reads']) {
168+
$totals['hits/reads'] = round(100 * $totals['hits'] / $totals['reads'], 2).'%';
169+
} else {
170+
$totals['hits/reads'] = 'N/A';
171+
}
172+
173+
return $totals;
174+
}
175+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.