Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 64cd933

Browse files
committed
Add session profiling
1 parent 1fc7b86 commit 64cd933

File tree

8 files changed

+177
-5
lines changed

8 files changed

+177
-5
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
<argument key="session" type="service" id="session" on-invalid="ignore" />
7878
<argument key="initialized_session" type="service" id="session" on-invalid="ignore_uninitialized" />
7979
<argument key="logger" type="service" id="logger" on-invalid="ignore" />
80+
<argument key="data_collector.request" type="service" id="data_collector.request" on-invalid="ignore" />
8081
</argument>
8182
<argument>%kernel.debug%</argument>
8283
</service>

src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.1.0
5+
-----
6+
7+
* added session usage profiling
8+
49
5.0.0
510
-----
611

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/request.html.twig

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@
5959
<b>Has session</b>
6060
<span>{% if collector.sessionmetadata|length %}yes{% else %}no{% endif %}</span>
6161
</div>
62+
63+
<div class="sf-toolbar-info-piece">
64+
<b>Stateless check</b>
65+
<span>{% if collector.statelesscheck %}yes{% else %}no{% endif %}</span>
66+
</div>
6267
</div>
6368

6469
{% if redirect_handler is defined -%}
@@ -228,7 +233,7 @@
228233
</div>
229234

230235
<div class="tab {{ collector.sessionmetadata is empty ? 'disabled' }}">
231-
<h3 class="tab-title">Session</h3>
236+
<h3 class="tab-title">Session{% if collector.sessionusages is not empty %} <span class="badge">{{ collector.sessionusages|length }}</span>{% endif %}</h3>
232237

233238
<div class="tab-content">
234239
<h3>Session Metadata</h3>
@@ -250,6 +255,59 @@
250255
{% else %}
251256
{{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.sessionattributes, labels: ['Attribute', 'Value'] }, with_context = false) }}
252257
{% endif %}
258+
259+
<h3>Session Usage</h3>
260+
261+
<div class="metrics">
262+
<div class="metric">
263+
<span class="value">{{ collector.sessionusages|length }}</span>
264+
<span class="label">Usages</span>
265+
</div>
266+
267+
<div class="metric">
268+
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.statelesscheck ? 'yes' : 'no') ~ '.svg') }}</span>
269+
<span class="label">Stateless check enabled</span>
270+
</div>
271+
</div>
272+
273+
{% if collector.sessionusages is empty %}
274+
<div class="empty">
275+
<p>No session usage were made.</p>
276+
</div>
277+
{% else %}
278+
<table class="session_usages">
279+
<thead>
280+
<tr>
281+
<th>Time</th>
282+
<th class="full-width">Usage</th>
283+
</tr>
284+
</thead>
285+
286+
<tbody>
287+
{% for key, usage in collector.sessionusages %}
288+
<tr>
289+
<td class="font-normal text-small" nowrap>
290+
<time class="text-muted newline" title="{{ usage.timestamp|date('r') }}" datetime="{{ usage.timestamp|date('c') }}">{{ usage.timestamp|date('H:i:s') }}</time>
291+
</td>
292+
293+
<td class="font-normal">
294+
{%- set link = usage.file|file_link(usage.line) %}
295+
{%- if link %}<a href="{{ link }}" title="{{ usage.name }}">{% else %}<span title="{{ usage.name }}">{% endif %}
296+
{{ usage.name }}
297+
{%- if link %}</a>{% else %}</span>{% endif %}
298+
<div class="text-small font-normal">
299+
{% set usage_id = 'session-usage-trace-' ~ key %}
300+
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ usage_id }}" data-toggle-alt-content="Hide trace">Show trace</a>
301+
</div>
302+
<div id="{{ usage_id }}" class="context sf-toggle-content sf-toggle-hidden">
303+
{{ profiler_dump(usage.trace, maxDepth=2) }}
304+
</div>
305+
</td>
306+
</tr>
307+
{% endfor %}
308+
</tbody>
309+
</table>
310+
{% endif %}
253311
</div>
254312
</div>
255313

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* allowed using public aliases to reference controllers
99
* added session usage reporting when the `_stateless` attribute of the request is set to `true`
1010
* added `AbstractSessionListener::onSessionUsage()` to report when the session is used while a request is stateless
11+
* added session usage profiling
1112

1213
5.0.0
1314
-----

src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use Symfony\Component\HttpFoundation\ParameterBag;
1717
use Symfony\Component\HttpFoundation\Request;
1818
use Symfony\Component\HttpFoundation\Response;
19+
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
20+
use Symfony\Component\HttpFoundation\Session\SessionInterface;
1921
use Symfony\Component\HttpKernel\Event\ControllerEvent;
2022
use Symfony\Component\HttpKernel\Event\ResponseEvent;
2123
use Symfony\Component\HttpKernel\KernelEvents;
@@ -28,6 +30,7 @@
2830
class RequestDataCollector extends DataCollector implements EventSubscriberInterface, LateDataCollectorInterface
2931
{
3032
protected $controllers;
33+
protected $sessionUsages = [];
3134

3235
public function __construct()
3336
{
@@ -105,6 +108,8 @@ public function collect(Request $request, Response $response, \Throwable $except
105108
'response_cookies' => $responseCookies,
106109
'session_metadata' => $sessionMetadata,
107110
'session_attributes' => $sessionAttributes,
111+
'session_usages' => $this->sessionUsages,
112+
'stateless_check' => $request->attributes->get('_stateless'),
108113
'flashes' => $flashes,
109114
'path_info' => $request->getPathInfo(),
110115
'controller' => 'n/a',
@@ -175,6 +180,7 @@ public function reset()
175180
{
176181
$this->data = [];
177182
$this->controllers = new \SplObjectStorage();
183+
$this->sessionUsages = [];
178184
}
179185

180186
public function getMethod()
@@ -242,6 +248,16 @@ public function getSessionAttributes()
242248
return $this->data['session_attributes']->getValue();
243249
}
244250

251+
public function getStatelessCheck()
252+
{
253+
return $this->data['stateless_check'];
254+
}
255+
256+
public function getSessionUsages()
257+
{
258+
return $this->data['session_usages'];
259+
}
260+
245261
public function getFlashes()
246262
{
247263
return $this->data['flashes']->getValue();
@@ -382,6 +398,34 @@ public function getName()
382398
return 'request';
383399
}
384400

401+
public function collectSessionUsage(): void
402+
{
403+
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
404+
405+
$traceEndIndex = \count($trace) - 1;
406+
for ($i = $traceEndIndex; $i > 0; --$i) {
407+
if (null !== ($class = $trace[$i]['class'] ?? null) && (is_subclass_of($class, SessionInterface::class) || is_subclass_of($class, SessionBagInterface::class))) {
408+
$traceEndIndex = $i;
409+
break;
410+
}
411+
}
412+
413+
if ((\count($trace) - 1) === $traceEndIndex) {
414+
return;
415+
}
416+
417+
// Remove part of the backtrace that belongs to session only
418+
array_splice($trace, 0, $traceEndIndex);
419+
420+
$this->sessionUsages[] = [
421+
'name' => sprintf('%s:%s', $trace[1]['class'] ?? $trace[0]['file'], $trace[0]['line']),
422+
'file' => $trace[0]['file'],
423+
'line' => $trace[0]['line'],
424+
'trace' => $trace,
425+
'timestamp' => time(),
426+
];
427+
}
428+
385429
/**
386430
* Parse a controller.
387431
*

src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@ public function onSessionUsage(): void
156156
return;
157157
}
158158

159+
if (!$session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : $requestStack->getCurrentRequest()->getSession()) {
160+
return;
161+
}
162+
163+
if ($dataCollector = $this->container && $this->container->has('data_collector.request') ? $this->container->get('data_collector.request') : null) {
164+
$dataCollector->collectSessionUsage();
165+
}
166+
159167
$stateless = false;
160168
$clonedRequestStack = clone $requestStack;
161169
while (null !== ($request = $clonedRequestStack->pop()) && !$stateless) {
@@ -166,10 +174,6 @@ public function onSessionUsage(): void
166174
return;
167175
}
168176

169-
if (!$session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : $requestStack->getCurrentRequest()->getSession()) {
170-
return;
171-
}
172-
173177
if ($session->isStarted()) {
174178
$session->save();
175179
}

src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
use Symfony\Component\HttpFoundation\Request;
2020
use Symfony\Component\HttpFoundation\Response;
2121
use Symfony\Component\HttpFoundation\Session\Session;
22+
use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
23+
use Symfony\Component\HttpFoundation\Session\SessionInterface;
2224
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
2325
use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
2426
use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector;
@@ -248,6 +250,54 @@ public function testItCollectsTheRedirectionAndClearTheCookie()
248250
$this->assertNull($cookie->getValue());
249251
}
250252

253+
public function testItCollectsTheSessionTraceProperly(): void
254+
{
255+
$collector = new RequestDataCollector();
256+
$request = $this->createRequest();
257+
258+
// RequestDataCollectorTest doesn't implement SessionInterface or SessionBagInterface, therefore should do nothing.
259+
$collector->collectSessionUsage();
260+
261+
$collector->collect($request, $this->createResponse());
262+
$this->assertSame([], $collector->getSessionUsages());
263+
264+
$collector->reset();
265+
266+
$session = $this->createMock(SessionInterface::class);
267+
$session->method('getMetadataBag')->willReturnCallback(static function () use ($collector) {
268+
$collector->collectSessionUsage();
269+
});
270+
$session->getMetadataBag();
271+
272+
$collector->collect($request, $this->createResponse());
273+
$collector->lateCollect();
274+
275+
$usages = $collector->getSessionUsages();
276+
277+
$this->assertCount(1, $usages);
278+
$this->assertSame(__FILE__, $usages[0]['file']);
279+
$this->assertSame(__LINE__ - 9, $line = $usages[0]['line']);
280+
281+
$trace = $usages[0]['trace'];
282+
$this->assertSame('getMetadataBag', $trace[0]['function']);
283+
$this->assertSame(self::class, $class = $trace[1]['class']);
284+
285+
$this->assertSame(sprintf('%s:%s', $class, $line), $usages[0]['name']);
286+
}
287+
288+
public function testStatelessCheck(): void
289+
{
290+
$collector = new RequestDataCollector();
291+
292+
$request = $this->createRequest();
293+
$request->attributes->set('_stateless', true);
294+
295+
$collector->collect($request, $response = $this->createResponse());
296+
$collector->lateCollect();
297+
298+
$this->assertTrue($collector->getStatelessCheck());
299+
}
300+
251301
protected function createRequest($routeParams = ['name' => 'foo'])
252302
{
253303
$request = Request::create('http://test.com/foo?bar=baz');

src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\HttpFoundation\Response;
2121
use Symfony\Component\HttpFoundation\Session\Session;
2222
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
23+
use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector;
2324
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
2425
use Symfony\Component\HttpKernel\Event\RequestEvent;
2526
use Symfony\Component\HttpKernel\Event\ResponseEvent;
@@ -260,9 +261,13 @@ public function testSessionUsageCallbackWhenDebugAndStateless()
260261
$requestStack->push($request);
261262
$requestStack->push(new Request());
262263

264+
$collector = $this->createMock(RequestDataCollector::class);
265+
$collector->expects($this->once())->method('collectSessionUsage');
266+
263267
$container = new Container();
264268
$container->set('initialized_session', $session);
265269
$container->set('request_stack', $requestStack);
270+
$container->set('data_collector.request', $collector);
266271

267272
$this->expectException(UnexpectedSessionUsageException::class);
268273
(new SessionListener($container, true))->onSessionUsage();
@@ -280,9 +285,13 @@ public function testSessionUsageCallbackWhenNoDebug()
280285
$requestStack = $this->getMockBuilder(RequestStack::class)->getMock();
281286
$requestStack->expects($this->never())->method('getMasterRequest')->willReturn($request);
282287

288+
$collector = $this->createMock(RequestDataCollector::class);
289+
$collector->expects($this->never())->method('collectSessionUsage');
290+
283291
$container = new Container();
284292
$container->set('initialized_session', $session);
285293
$container->set('request_stack', $requestStack);
294+
$container->set('data_collector.request', $collector);
286295

287296
(new SessionListener($container))->onSessionUsage();
288297
}

0 commit comments

Comments
 (0)