Skip to content

Commit 3a03832

Browse files
fbourigaultNyholm
authored andcommitted
display children requests as nested in the profiler (#181)
1 parent f507c1a commit 3a03832

File tree

13 files changed

+420
-144
lines changed

13 files changed

+420
-144
lines changed

Collector/Collector.php

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
*/
2121
class Collector extends DataCollector
2222
{
23+
/**
24+
* @var Stack|null
25+
*/
26+
private $activeStack;
27+
2328
public function __construct()
2429
{
2530
$this->data['stacks'] = [];
@@ -41,6 +46,38 @@ public function getName()
4146
return 'httplug';
4247
}
4348

49+
/**
50+
* Mark the stack as active. If a stack was already active, use it as parent for our stack.
51+
*
52+
* @param Stack $stack
53+
*/
54+
public function activateStack(Stack $stack)
55+
{
56+
if ($this->activeStack !== null) {
57+
$stack->setParent($this->activeStack);
58+
}
59+
60+
$this->activeStack = $stack;
61+
}
62+
63+
/**
64+
* Mark the stack as inactive.
65+
*
66+
* @param Stack $stack
67+
*/
68+
public function deactivateStack(Stack $stack)
69+
{
70+
$this->activeStack = $stack->getParent();
71+
}
72+
73+
/**
74+
* @return Stack|null
75+
*/
76+
public function getActiveStack()
77+
{
78+
return $this->activeStack;
79+
}
80+
4481
/**
4582
* @param Stack $stack
4683
*/
@@ -50,23 +87,23 @@ public function addStack(Stack $stack)
5087
}
5188

5289
/**
90+
* @param Stack $parent
91+
*
5392
* @return Stack[]
5493
*/
55-
public function getStacks()
94+
public function getChildrenStacks(Stack $parent)
5695
{
57-
return $this->data['stacks'];
96+
return array_filter($this->data['stacks'], function (Stack $stack) use ($parent) {
97+
return $stack->getParent() === $parent;
98+
});
5899
}
59100

60101
/**
61-
* @return Stack|null Return null there is no current stack.
102+
* @return Stack[]
62103
*/
63-
public function getCurrentStack()
104+
public function getStacks()
64105
{
65-
if (false === $stack = end($this->data['stacks'])) {
66-
return null;
67-
}
68-
69-
return $stack;
106+
return $this->data['stacks'];
70107
}
71108

72109
/**

Collector/ProfileClient.php

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -75,59 +75,67 @@ public function __construct($client, Collector $collector, Formatter $formatter,
7575
*/
7676
public function sendAsyncRequest(RequestInterface $request)
7777
{
78-
$stack = $this->collector->getCurrentStack();
78+
$stack = $this->collector->getActiveStack();
79+
7980
$this->collectRequestInformations($request, $stack);
8081
$event = $this->stopwatch->start($this->getStopwatchEventName($request));
8182

82-
return $this->client->sendAsyncRequest($request)->then(
83-
function (ResponseInterface $response) use ($event, $stack) {
84-
$event->stop();
85-
$this->collectResponseInformations($response, $event, $stack);
83+
$onFulfilled = function (ResponseInterface $response) use ($event, $stack) {
84+
$this->collectResponseInformations($response, $event, $stack);
85+
86+
return $response;
87+
};
88+
89+
$onRejected = function (\Exception $exception) use ($event, $stack) {
90+
$this->collectExceptionInformations($exception, $event, $stack);
91+
92+
throw $exception;
93+
};
8694

87-
return $response;
88-
}, function (\Exception $exception) use ($event, $stack) {
89-
$event->stop();
90-
$this->collectExceptionInformations($exception, $event, $stack);
95+
$this->collector->deactivateStack($stack);
9196

92-
throw $exception;
93-
}
94-
);
97+
try {
98+
return $this->client->sendAsyncRequest($request)->then($onFulfilled, $onRejected);
99+
} finally {
100+
$event->stop();
101+
$this->collector->activateStack($stack);
102+
}
95103
}
96104

97105
/**
98106
* {@inheritdoc}
99107
*/
100108
public function sendRequest(RequestInterface $request)
101109
{
102-
$stack = $this->collector->getCurrentStack();
110+
$stack = $this->collector->getActiveStack();
111+
103112
$this->collectRequestInformations($request, $stack);
104113
$event = $this->stopwatch->start($this->getStopwatchEventName($request));
105114

106115
try {
107116
$response = $this->client->sendRequest($request);
108-
$event->stop();
109-
110117
$this->collectResponseInformations($response, $event, $stack);
111118

112119
return $response;
113120
} catch (\Exception $e) {
114-
$event->stop();
115121
$this->collectExceptionInformations($e, $event, $stack);
116122

117123
throw $e;
124+
} catch (\Throwable $e) {
125+
$this->collectExceptionInformations($e, $event, $stack);
126+
127+
throw $e;
128+
} finally {
129+
$event->stop();
118130
}
119131
}
120132

121133
/**
122134
* @param RequestInterface $request
123-
* @param Stack|null $stack
135+
* @param Stack $stack
124136
*/
125-
private function collectRequestInformations(RequestInterface $request, Stack $stack = null)
137+
private function collectRequestInformations(RequestInterface $request, Stack $stack)
126138
{
127-
if (null === $stack) {
128-
return;
129-
}
130-
131139
$stack->setRequestTarget($request->getRequestTarget());
132140
$stack->setRequestMethod($request->getMethod());
133141
$stack->setRequestScheme($request->getUri()->getScheme());
@@ -139,14 +147,10 @@ private function collectRequestInformations(RequestInterface $request, Stack $st
139147
/**
140148
* @param ResponseInterface $response
141149
* @param StopwatchEvent $event
142-
* @param Stack|null $stack
150+
* @param Stack $stack
143151
*/
144-
private function collectResponseInformations(ResponseInterface $response, StopwatchEvent $event, Stack $stack = null)
152+
private function collectResponseInformations(ResponseInterface $response, StopwatchEvent $event, Stack $stack)
145153
{
146-
if (null === $stack) {
147-
return;
148-
}
149-
150154
$stack->setDuration($event->getDuration());
151155
$stack->setResponseCode($response->getStatusCode());
152156
$stack->setClientResponse($this->formatter->formatResponse($response));
@@ -155,18 +159,14 @@ private function collectResponseInformations(ResponseInterface $response, Stopwa
155159
/**
156160
* @param \Exception $exception
157161
* @param StopwatchEvent $event
158-
* @param Stack|null $stack
162+
* @param Stack $stack
159163
*/
160-
private function collectExceptionInformations(\Exception $exception, StopwatchEvent $event, Stack $stack = null)
164+
private function collectExceptionInformations(\Exception $exception, StopwatchEvent $event, Stack $stack)
161165
{
162166
if ($exception instanceof HttpException) {
163167
$this->collectResponseInformations($exception->getResponse(), $event, $stack);
164168
}
165169

166-
if (null === $stack) {
167-
return;
168-
}
169-
170170
$stack->setDuration($event->getDuration());
171171
$stack->setClientException($this->formatter->formatException($exception));
172172
}

Collector/ProfilePlugin.php

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,8 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
5151
{
5252
$profile = new Profile(get_class($this->plugin));
5353

54-
$stack = $this->collector->getCurrentStack();
55-
if (null !== $stack) {
56-
$stack->addProfile($profile);
57-
}
54+
$stack = $this->collector->getActiveStack();
55+
$stack->addProfile($profile);
5856

5957
// wrap the next callback to profile the plugin request changes
6058
$wrappedNext = function (RequestInterface $request) use ($next, $profile) {
@@ -136,10 +134,6 @@ private function onOutgoingResponse(ResponseInterface $response, Profile $profil
136134
*/
137135
private function collectRequestInformation(RequestInterface $request, Stack $stack = null)
138136
{
139-
if (null === $stack) {
140-
return;
141-
}
142-
143137
if (empty($stack->getRequestTarget())) {
144138
$stack->setRequestTarget($request->getRequestTarget());
145139
}

Collector/Stack.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ final class Stack
1616
*/
1717
private $client;
1818

19+
/**
20+
* @var Stack
21+
*/
22+
private $parent;
23+
1924
/**
2025
* @var Profile[]
2126
*/
@@ -104,6 +109,22 @@ public function getClient()
104109
return $this->client;
105110
}
106111

112+
/**
113+
* @return Stack
114+
*/
115+
public function getParent()
116+
{
117+
return $this->parent;
118+
}
119+
120+
/**
121+
* @param Stack $parent
122+
*/
123+
public function setParent(Stack $parent)
124+
{
125+
$this->parent = $parent;
126+
}
127+
107128
/**
108129
* @param Profile $profile
109130
*/

Collector/StackPlugin.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,25 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
5252
$stack = new Stack($this->client, $this->formatter->formatRequest($request));
5353

5454
$this->collector->addStack($stack);
55+
$this->collector->activateStack($stack);
5556

56-
return $next($request)->then(function (ResponseInterface $response) use ($stack) {
57+
$onFulfilled = function (ResponseInterface $response) use ($stack) {
5758
$stack->setResponse($this->formatter->formatResponse($response));
5859

5960
return $response;
60-
}, function (Exception $exception) use ($stack) {
61+
};
62+
63+
$onRejected = function (Exception $exception) use ($stack) {
6164
$stack->setResponse($this->formatter->formatException($exception));
6265
$stack->setFailed(true);
6366

6467
throw $exception;
65-
});
68+
};
69+
70+
try {
71+
return $next($request)->then($onFulfilled, $onRejected);
72+
} finally {
73+
$this->collector->deactivateStack($stack);
74+
}
6675
}
6776
}

Resources/public/style/httplug.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@
7474
display: flex;
7575
}
7676

77+
/**
78+
* Stack
79+
*/
80+
.httplug-stack>.httplug-stack {
81+
margin-left: 2.5em;
82+
}
83+
7784
/**
7885
* Stack header
7986
*/

Resources/views/stack.html.twig

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<div class="httplug-stack-header httplug-toggle" data-toggle="#httplug-{{ client }}-{{ id }}-details">
2+
<div>
3+
{% if stack.failed %}
4+
<span class="httplug-stack-failed">✘</span>
5+
{% else %}
6+
<span class="httplug-stack-success">✔</span>
7+
{% endif %}
8+
<span class="label httplug-method httplug-method-{{ stack.requestMethod|lower }}">{{ stack.requestMethod }}</span>
9+
</div>
10+
<div class="label httplug-stack-header-target">
11+
<span class="httplug-scheme">{{ stack.requestScheme }}://</span>
12+
<span class="httplug-host">{{ stack.requestHost }}</span>
13+
<span class="httplug-target">{{ stack.requestTarget }}</span>
14+
</div>
15+
<div>
16+
<span class="label httplug-duration">{{ stack.duration|number_format }} ms</span>
17+
{% if stack.responseCode >= 400 and stack.responseCode <= 599 %}
18+
<span class="label status-error">{{ stack.responseCode }}</span>
19+
{% elseif stack.responseCode >= 300 and stack.responseCode <= 399 %}
20+
<span class="label status-warning">{{ stack.responseCode }}</span>
21+
{% else %}
22+
<span class="label status-success">{{ stack.responseCode }}</span>
23+
{% endif %}
24+
</div>
25+
</div>
26+
<div id="httplug-{{ client }}-{{ id }}-details" class="httplug-hidden">
27+
<div class="httplug-toolbar">
28+
<div class="httplug-copy-as-curl">
29+
<input readonly="readonly" type="text" value="{{ stack.curlCommand }}" />
30+
<button class="btn tooltip-toggle" aria-label="Copy to clipboard">Copy to clipboard</button>
31+
</div>
32+
<button data-toggle="#httplug-{{ client }}-{{ id }}-stack" class="httplug-toggle btn" >Toggle plugin stack</button>
33+
<button data-toggle="#httplug-{{ client }}-{{ id }}-details .httplug-http-body" class="httplug-toggle btn">Toggle body</button>
34+
</div>
35+
<div class="httplug-messages">
36+
<div class="httplug-message card">
37+
<h4>Request</h4>
38+
{{ stack.clientRequest|httplug_markup|nl2br }}
39+
</div>
40+
<div class="httplug-message card">
41+
<h4>Response</h4>
42+
{{ stack.clientResponse|httplug_markup|nl2br }}
43+
</div>
44+
</div>
45+
{% if stack.profiles %}
46+
<div id="httplug-{{ client }}-{{ id }}-stack" class="httplug-hidden card">
47+
{% for profile in stack.profiles %}
48+
<h3 class="httplug-plugin-name">{{ profile.plugin }}</h3>
49+
<div class="httplug-messages">
50+
<div class="httplug-message">
51+
<h4>Request</h4>
52+
{{ profile.request|httplug_markup|nl2br }}
53+
</div>
54+
<div class="httplug-message">
55+
<h4>Response</h4>
56+
{{ profile.response|httplug_markup|nl2br }}
57+
</div>
58+
</div>
59+
{% if not loop.last %}
60+
<hr />
61+
{% endif %}
62+
{% endfor %}
63+
</div>
64+
{% endif %}
65+
</div>
66+
{% for child in collector.childrenStacks(stack) %}
67+
<div class="httplug-stack">
68+
{% include 'HttplugBundle::stack.html.twig' with {
69+
'collector': collector,
70+
'client': client,
71+
'stack': child,
72+
'id': id ~ '-' ~ loop.index
73+
} only %}
74+
</div>
75+
{% endfor %}

0 commit comments

Comments
 (0)