Skip to content

Commit 6d14164

Browse files
committed
extend profiler toolbar item
1 parent 8c98056 commit 6d14164

File tree

7 files changed

+128
-23
lines changed

7 files changed

+128
-23
lines changed

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 getDurationSum()
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: 47 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,19 @@ class ProfileClient implements HttpClient, HttpAsyncClient
3436
*/
3537
private $formatter;
3638

39+
/**
40+
* @var Stopwatch
41+
*/
42+
private $stopwatch;
43+
3744
/**
3845
* @param HttpClient|HttpAsyncClient $client The client to profile. Client must implement both HttpClient and
3946
* HttpAsyncClient interfaces.
4047
* @param Collector $collector
4148
* @param Formatter $formatter
49+
* @param Stopwatch $stopwatch
4250
*/
43-
public function __construct($client, Collector $collector, Formatter $formatter)
51+
public function __construct($client, Collector $collector, Formatter $formatter, Stopwatch $stopwatch)
4452
{
4553
if (!($client instanceof HttpClient && $client instanceof HttpAsyncClient)) {
4654
throw new \RuntimeException(sprintf(
@@ -54,6 +62,7 @@ public function __construct($client, Collector $collector, Formatter $formatter)
5462
$this->client = $client;
5563
$this->collector = $collector;
5664
$this->formatter = $formatter;
65+
$this->stopwatch = $stopwatch;
5766
}
5867

5968
/**
@@ -63,16 +72,20 @@ public function sendAsyncRequest(RequestInterface $request)
6372
{
6473
$stack = $this->collector->getCurrentStack();
6574
$this->collectRequestInformations($request, $stack);
75+
$event = $this->stopwatch->start($this->getStopwatchEventName($request));
6676

67-
return $this->client->sendAsyncRequest($request)->then(function (ResponseInterface $response) use ($stack) {
68-
$this->collectResponseInformations($response, $stack);
77+
return $this->client->sendAsyncRequest($request)->then(
78+
function (ResponseInterface $response) use ($event, $stack) {
79+
$event->stop();
80+
$this->collectResponseInformations($response, $event, $stack);
6981

70-
return $response;
71-
}, function (\Exception $exception) use ($stack) {
72-
$this->collectExceptionInformations($exception, $stack);
82+
return $response;
83+
}, function (\Exception $exception) use ($event, $stack) {
84+
$this->collectExceptionInformations($exception, $event, $stack);
7385

74-
throw $exception;
75-
});
86+
throw $exception;
87+
}
88+
);
7689
}
7790

7891
/**
@@ -82,15 +95,18 @@ public function sendRequest(RequestInterface $request)
8295
{
8396
$stack = $this->collector->getCurrentStack();
8497
$this->collectRequestInformations($request, $stack);
98+
$event = $this->stopwatch->start($this->getStopwatchEventName($request));
8599

86100
try {
87101
$response = $this->client->sendRequest($request);
102+
$event->stop();
88103

89-
$this->collectResponseInformations($response, $stack);
104+
$this->collectResponseInformations($response, $event, $stack);
90105

91106
return $response;
92107
} catch (\Exception $e) {
93-
$this->collectExceptionInformations($e, $stack);
108+
$event->stop();
109+
$this->collectExceptionInformations($e, $event, $stack);
94110

95111
throw $e;
96112
}
@@ -115,32 +131,48 @@ private function collectRequestInformations(RequestInterface $request, Stack $st
115131

116132
/**
117133
* @param ResponseInterface $response
134+
* @param StopwatchEvent $event
118135
* @param Stack|null $stack
119136
*/
120-
private function collectResponseInformations(ResponseInterface $response, Stack $stack = null)
137+
private function collectResponseInformations(ResponseInterface $response, StopwatchEvent $event, Stack $stack = null)
121138
{
122139
if (!$stack) {
123140
return;
124141
}
125142

143+
$stack->setDuration($event->getDuration());
126144
$stack->setResponseCode($response->getStatusCode());
127145
$stack->setClientResponse($this->formatter->formatResponse($response));
128146
}
129147

130148
/**
131-
* @param \Exception $exception
132-
* @param Stack|null $stack
149+
* @param \Exception $exception
150+
* @param StopwatchEvent $event
151+
* @param Stack|null $stack
133152
*/
134-
private function collectExceptionInformations(\Exception $exception, Stack $stack = null)
153+
private function collectExceptionInformations(\Exception $exception, StopwatchEvent $event, Stack $stack = null)
135154
{
136155
if ($exception instanceof HttpException) {
137-
$this->collectResponseInformations($exception->getResponse(), $stack);
156+
$this->collectResponseInformations($exception->getResponse(), $event, $stack);
138157
}
139158

140159
if (!$stack) {
141160
return;
142161
}
143162

163+
$stack->setDuration($event->getDuration());
144164
$stack->setClientException($this->formatter->formatException($exception));
145165
}
166+
167+
/**
168+
* Generates the event name.
169+
*
170+
* @param RequestInterface $request
171+
*
172+
* @return string
173+
*/
174+
private function getStopwatchEventName(RequestInterface $request)
175+
{
176+
return sprintf('%s %s', $request->getMethod(), $request->getUri()->__toString());
177+
}
146178
}

Collector/ProfileClientFactory.php

Lines changed: 9 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,25 @@ 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
3844
*/
39-
public function __construct($factory, Collector $collector, Formatter $formatter)
45+
public function __construct($factory, Collector $collector, Formatter $formatter, Stopwatch $stopwatch)
4046
{
4147
if (!$factory instanceof ClientFactory && !is_callable($factory)) {
4248
throw new \RuntimeException(sprintf('First argument to ProfileClientFactory::__construct must be a "%s" or a callable.', ClientFactory::class));
4349
}
4450
$this->factory = $factory;
4551
$this->collector = $collector;
4652
$this->formatter = $formatter;
53+
$this->stopwatch = $stopwatch;
4754
}
4855

4956
/**
@@ -57,6 +64,6 @@ public function createClient(array $config = [])
5764
$client = new FlexibleHttpClient($client);
5865
}
5966

60-
return new ProfileClient($client, $this->collector, $this->formatter);
67+
return new ProfileClient($client, $this->collector, $this->formatter, $this->stopwatch);
6168
}
6269
}

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
}

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.durationSum|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|number_format ~ ' ms'}}</td>
36+
<td>{{ stack.failed ? 'FAILED' : stack.responseCode }}</td>
37+
</tr>
38+
{% endfor %}
39+
</tbody>
40+
</table>
1941
</div>
2042
{% endset %}
2143

Tests/Unit/Collector/ProfileClientTest.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Psr\Http\Message\RequestInterface;
1313
use Psr\Http\Message\ResponseInterface;
1414
use Psr\Http\Message\UriInterface;
15+
use Symfony\Component\Stopwatch\Stopwatch;
1516

1617
class ProfileClientTest extends \PHPUnit_Framework_TestCase
1718
{
@@ -40,6 +41,11 @@ class ProfileClientTest extends \PHPUnit_Framework_TestCase
4041
*/
4142
private $formatter;
4243

44+
/**
45+
* @var Stopwatch
46+
*/
47+
private $stopwatch;
48+
4349
/**
4450
* @var ProfileClient
4551
*/
@@ -67,7 +73,8 @@ public function setUp()
6773
$this->client = $this->getMockBuilder(ClientInterface::class)->getMock();
6874
$this->request = $this->getMockBuilder(RequestInterface::class)->getMock();
6975
$this->formatter = $this->getMockBuilder(Formatter::class)->disableOriginalConstructor()->getMock();
70-
$this->subject = new ProfileClient($this->client, $this->collector, $this->formatter);
76+
$this->stopwatch = new Stopwatch();
77+
$this->subject = new ProfileClient($this->client, $this->collector, $this->formatter, $this->stopwatch);
7178
$this->response = $this->getMockBuilder(ResponseInterface::class)->getMock();
7279
$this->promise = $this->getMockBuilder(Promise::class)->getMock();
7380
$this->uri = $this->getMockBuilder(UriInterface::class)->getMock();

0 commit comments

Comments
 (0)