Skip to content

Commit d8a85ac

Browse files
authored
Merge pull request #135 from chr-hertel/extend_profiler
Extend profiler toolbar item
2 parents 8c98056 + 52da00b commit d8a85ac

File tree

11 files changed

+155
-31
lines changed

11 files changed

+155
-31
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66

77
- The real request method and target url are now displayed in the profiler.
88
- Support the cache plugin configuration for `respect_response_cache_directives`.
9+
- Extended WebProfilerToolbar item to list request with details.
910

1011
### Changed
1112

1213
- The profiler design has been updated.
14+
- Removed stopwatch-plugin in favor of `ProfileClient`.
1315

1416
### Deprecated
1517

Collector/Collector.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,14 @@ public function getClientStacks($client)
108108
return $stack->getClient() == $client;
109109
});
110110
}
111+
112+
/**
113+
* @return int
114+
*/
115+
public function getTotalDuration()
116+
{
117+
return array_reduce($this->data['stacks'], function ($carry, Stack $stack) {
118+
return $carry + $stack->getDuration();
119+
}, 0);
120+
}
111121
}

Collector/ProfileClient.php

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use Http\Client\HttpClient;
99
use Psr\Http\Message\RequestInterface;
1010
use Psr\Http\Message\ResponseInterface;
11+
use Symfony\Component\Stopwatch\Stopwatch;
12+
use Symfony\Component\Stopwatch\StopwatchEvent;
1113

1214
/**
1315
* The ProfileClient decorates any client that implement both HttpClient and HttpAsyncClient interfaces to gather target
@@ -34,13 +36,24 @@ class ProfileClient implements HttpClient, HttpAsyncClient
3436
*/
3537
private $formatter;
3638

39+
/**
40+
* @var Stopwatch
41+
*/
42+
private $stopwatch;
43+
44+
/**
45+
* @var array
46+
*/
47+
private $eventNames = [];
48+
3749
/**
3850
* @param HttpClient|HttpAsyncClient $client The client to profile. Client must implement both HttpClient and
3951
* HttpAsyncClient interfaces.
4052
* @param Collector $collector
4153
* @param Formatter $formatter
54+
* @param Stopwatch $stopwatch
4255
*/
43-
public function __construct($client, Collector $collector, Formatter $formatter)
56+
public function __construct($client, Collector $collector, Formatter $formatter, Stopwatch $stopwatch)
4457
{
4558
if (!($client instanceof HttpClient && $client instanceof HttpAsyncClient)) {
4659
throw new \RuntimeException(sprintf(
@@ -54,6 +67,7 @@ public function __construct($client, Collector $collector, Formatter $formatter)
5467
$this->client = $client;
5568
$this->collector = $collector;
5669
$this->formatter = $formatter;
70+
$this->stopwatch = $stopwatch;
5771
}
5872

5973
/**
@@ -63,16 +77,21 @@ public function sendAsyncRequest(RequestInterface $request)
6377
{
6478
$stack = $this->collector->getCurrentStack();
6579
$this->collectRequestInformations($request, $stack);
80+
$event = $this->stopwatch->start($this->getStopwatchEventName($request));
6681

67-
return $this->client->sendAsyncRequest($request)->then(function (ResponseInterface $response) use ($stack) {
68-
$this->collectResponseInformations($response, $stack);
82+
return $this->client->sendAsyncRequest($request)->then(
83+
function (ResponseInterface $response) use ($event, $stack) {
84+
$event->stop();
85+
$this->collectResponseInformations($response, $event, $stack);
6986

70-
return $response;
71-
}, function (\Exception $exception) use ($stack) {
72-
$this->collectExceptionInformations($exception, $stack);
87+
return $response;
88+
}, function (\Exception $exception) use ($event, $stack) {
89+
$event->stop();
90+
$this->collectExceptionInformations($exception, $event, $stack);
7391

74-
throw $exception;
75-
});
92+
throw $exception;
93+
}
94+
);
7695
}
7796

7897
/**
@@ -82,15 +101,18 @@ public function sendRequest(RequestInterface $request)
82101
{
83102
$stack = $this->collector->getCurrentStack();
84103
$this->collectRequestInformations($request, $stack);
104+
$event = $this->stopwatch->start($this->getStopwatchEventName($request));
85105

86106
try {
87107
$response = $this->client->sendRequest($request);
108+
$event->stop();
88109

89-
$this->collectResponseInformations($response, $stack);
110+
$this->collectResponseInformations($response, $event, $stack);
90111

91112
return $response;
92113
} catch (\Exception $e) {
93-
$this->collectExceptionInformations($e, $stack);
114+
$event->stop();
115+
$this->collectExceptionInformations($e, $event, $stack);
94116

95117
throw $e;
96118
}
@@ -115,32 +137,56 @@ private function collectRequestInformations(RequestInterface $request, Stack $st
115137

116138
/**
117139
* @param ResponseInterface $response
140+
* @param StopwatchEvent $event
118141
* @param Stack|null $stack
119142
*/
120-
private function collectResponseInformations(ResponseInterface $response, Stack $stack = null)
143+
private function collectResponseInformations(ResponseInterface $response, StopwatchEvent $event, Stack $stack = null)
121144
{
122145
if (!$stack) {
123146
return;
124147
}
125148

149+
$stack->setDuration($event->getDuration());
126150
$stack->setResponseCode($response->getStatusCode());
127151
$stack->setClientResponse($this->formatter->formatResponse($response));
128152
}
129153

130154
/**
131-
* @param \Exception $exception
132-
* @param Stack|null $stack
155+
* @param \Exception $exception
156+
* @param StopwatchEvent $event
157+
* @param Stack|null $stack
133158
*/
134-
private function collectExceptionInformations(\Exception $exception, Stack $stack = null)
159+
private function collectExceptionInformations(\Exception $exception, StopwatchEvent $event, Stack $stack = null)
135160
{
136161
if ($exception instanceof HttpException) {
137-
$this->collectResponseInformations($exception->getResponse(), $stack);
162+
$this->collectResponseInformations($exception->getResponse(), $event, $stack);
138163
}
139164

140165
if (!$stack) {
141166
return;
142167
}
143168

169+
$stack->setDuration($event->getDuration());
144170
$stack->setClientException($this->formatter->formatException($exception));
145171
}
172+
173+
/**
174+
* Generates the event name.
175+
*
176+
* @param RequestInterface $request
177+
*
178+
* @return string
179+
*/
180+
private function getStopwatchEventName(RequestInterface $request)
181+
{
182+
$name = sprintf('%s %s', $request->getMethod(), $request->getUri());
183+
184+
if (isset($this->eventNames[$name])) {
185+
$name .= sprintf(' [#%d]', ++$this->eventNames[$name]);
186+
} else {
187+
$this->eventNames[$name] = 1;
188+
}
189+
190+
return $name;
191+
}
146192
}

Collector/ProfileClientFactory.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Http\Client\HttpAsyncClient;
77
use Http\Client\HttpClient;
88
use Http\HttplugBundle\ClientFactory\ClientFactory;
9+
use Symfony\Component\Stopwatch\Stopwatch;
910

1011
/**
1112
* The ProfileClientFactory decorates any ClientFactory and returns the created client decorated by a ProfileClient.
@@ -31,19 +32,26 @@ class ProfileClientFactory implements ClientFactory
3132
*/
3233
private $formatter;
3334

35+
/**
36+
* @var Stopwatch
37+
*/
38+
private $stopwatch;
39+
3440
/**
3541
* @param ClientFactory|callable $factory
3642
* @param Collector $collector
3743
* @param Formatter $formatter
44+
* @param Stopwatch $stopwatch
3845
*/
39-
public function __construct($factory, Collector $collector, Formatter $formatter)
46+
public function __construct($factory, Collector $collector, Formatter $formatter, Stopwatch $stopwatch)
4047
{
4148
if (!$factory instanceof ClientFactory && !is_callable($factory)) {
4249
throw new \RuntimeException(sprintf('First argument to ProfileClientFactory::__construct must be a "%s" or a callable.', ClientFactory::class));
4350
}
4451
$this->factory = $factory;
4552
$this->collector = $collector;
4653
$this->formatter = $formatter;
54+
$this->stopwatch = $stopwatch;
4755
}
4856

4957
/**
@@ -57,6 +65,6 @@ public function createClient(array $config = [])
5765
$client = new FlexibleHttpClient($client);
5866
}
5967

60-
return new ProfileClient($client, $this->collector, $this->formatter);
68+
return new ProfileClient($client, $this->collector, $this->formatter, $this->stopwatch);
6169
}
6270
}

Collector/Stack.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ final class Stack
7676
*/
7777
private $responseCode;
7878

79+
/**
80+
* @var int
81+
*/
82+
private $duration = 0;
83+
7984
/**
8085
* @param string $client
8186
* @param string $request
@@ -277,4 +282,20 @@ public function setRequestScheme($requestScheme)
277282
{
278283
$this->requestScheme = $requestScheme;
279284
}
285+
286+
/**
287+
* @return int
288+
*/
289+
public function getDuration()
290+
{
291+
return $this->duration;
292+
}
293+
294+
/**
295+
* @param int $duration
296+
*/
297+
public function setDuration($duration)
298+
{
299+
$this->duration = $duration;
300+
}
280301
}

DependencyInjection/HttplugExtension.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,6 @@ private function configureClient(ContainerBuilder $container, $clientName, array
279279

280280
$pluginClientOptions = [];
281281
if ($profiling) {
282-
// Add the stopwatch plugin
283-
if (!in_array('httplug.plugin.stopwatch', $arguments['plugins'])) {
284-
array_unshift($plugins, 'httplug.plugin.stopwatch');
285-
}
286-
287282
//Decorate each plugin with a ProfilePlugin instance.
288283
foreach ($plugins as $pluginServiceId) {
289284
$this->decoratePluginWithProfilePlugin($container, $pluginServiceId);
@@ -561,6 +556,7 @@ private function configureAutoDiscoveryFactory(ContainerBuilder $container, $dis
561556
$factory,
562557
new Reference('httplug.collector.collector'),
563558
new Reference('httplug.collector.formatter'),
559+
new Reference('debug.stopwatch'),
564560
]);
565561
$factory = new Reference($factoryServiceId);
566562
}

Resources/config/data-collector.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,31 +30,37 @@
3030
<argument type="service" id="httplug.collector.factory.buzz.inner"/>
3131
<argument type="service" id="httplug.collector.collector"/>
3232
<argument type="service" id="httplug.collector.formatter"/>
33+
<argument type="service" id="debug.stopwatch"/>
3334
</service>
3435
<service id="httplug.collector.factory.curl" class="Http\HttplugBundle\Collector\ProfileClientFactory" decorates="httplug.factory.curl" public="false">
3536
<argument type="service" id="httplug.collector.factory.curl.inner"/>
3637
<argument type="service" id="httplug.collector.collector"/>
3738
<argument type="service" id="httplug.collector.formatter"/>
39+
<argument type="service" id="debug.stopwatch"/>
3840
</service>
3941
<service id="httplug.collector.factory.guzzle5" class="Http\HttplugBundle\Collector\ProfileClientFactory" decorates="httplug.factory.guzzle5" public="false">
4042
<argument type="service" id="httplug.collector.factory.guzzle5.inner"/>
4143
<argument type="service" id="httplug.collector.collector"/>
4244
<argument type="service" id="httplug.collector.formatter"/>
45+
<argument type="service" id="debug.stopwatch"/>
4346
</service>
4447
<service id="httplug.collector.factory.guzzle6" class="Http\HttplugBundle\Collector\ProfileClientFactory" decorates="httplug.factory.guzzle6" public="false">
4548
<argument type="service" id="httplug.collector.factory.guzzle6.inner"/>
4649
<argument type="service" id="httplug.collector.collector"/>
4750
<argument type="service" id="httplug.collector.formatter"/>
51+
<argument type="service" id="debug.stopwatch"/>
4852
</service>
4953
<service id="httplug.collector.factory.react" class="Http\HttplugBundle\Collector\ProfileClientFactory" decorates="httplug.factory.react" public="false">
5054
<argument type="service" id="httplug.collector.factory.react.inner"/>
5155
<argument type="service" id="httplug.collector.collector"/>
5256
<argument type="service" id="httplug.collector.formatter"/>
57+
<argument type="service" id="debug.stopwatch"/>
5358
</service>
5459
<service id="httplug.collector.factory.socket" class="Http\HttplugBundle\Collector\ProfileClientFactory" decorates="httplug.factory.socket" public="false">
5560
<argument type="service" id="httplug.collector.factory.socket.inner"/>
5661
<argument type="service" id="httplug.collector.collector"/>
5762
<argument type="service" id="httplug.collector.formatter"/>
63+
<argument type="service" id="debug.stopwatch"/>
5864
</service>
5965
</services>
6066
</container>

Resources/views/webprofiler.html.twig

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,39 @@
55
{% set icon %}
66
{{ include('@Httplug/Icon/httplug.svg') }}
77
<span class="sf-toolbar-value">{{ collector.stacks|length }}</span>
8-
<span class="sf-toolbar-label">req.</span>
8+
<span class="sf-toolbar-label">in</span>
9+
<span class="sf-toolbar-value">{{ collector.totalDuration|number_format }}</span>
10+
<span class="sf-toolbar-label">ms</span>
911
{% endset %}
1012

1113
{% set text %}
1214
<div class="sf-toolbar-info-piece">
13-
<b>Successful requests</b>
14-
<span class="sf-toolbar-status">{{ collector.successfulStacks|length }}</span>
15+
<b>{{ collector.stacks|length }} requests</b>
1516
</div>
1617
<div class="sf-toolbar-info-piece">
17-
<b>Failed requests</b>
18-
<span class="sf-toolbar-status {{ collector.failedStacks|length ? 'sf-toolbar-status-red' }}">{{ collector.failedStacks|length }}</span>
18+
<table class="sf-toolbar-ajax-requests">
19+
<thead>
20+
<tr>
21+
<th>Client</th>
22+
<th>Method</th>
23+
<th>Target</th>
24+
<th>Time</th>
25+
<th>Status</th>
26+
</tr>
27+
</thead>
28+
<tbody class="sf-toolbar-ajax-request-list">
29+
{% for stack in collector.stacks %}
30+
<tr>
31+
<td>{{ stack.client }}</td>
32+
<td>{{ stack.requestMethod }}</td>
33+
{% set target = stack.requestTarget %}
34+
<td title="{{ target }}">{{ target|length > 30 ? target[:30] ~ '...' : target }}</td>
35+
<td>{{ stack.duration == 0 ? 'n/a' : stack.duration|number_format ~ ' ms'}}</td>
36+
<td>{{ stack.failed ? 'FAILED' : stack.responseCode|default('n/a') }}</td>
37+
</tr>
38+
{% endfor %}
39+
</tbody>
40+
</table>
1941
</div>
2042
{% endset %}
2143

0 commit comments

Comments
 (0)