Skip to content

display children requests as nested in the profiler #181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 46 additions & 9 deletions Collector/Collector.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
*/
class Collector extends DataCollector
{
/**
* @var Stack|null
*/
private $activeStack;

public function __construct()
{
$this->data['stacks'] = [];
Expand All @@ -41,6 +46,38 @@ public function getName()
return 'httplug';
}

/**
* Mark the stack as active. If a stack was already active, use it as parent for our stack.
*
* @param Stack $stack
*/
public function activateStack(Stack $stack)
{
if ($this->activeStack !== null) {
$stack->setParent($this->activeStack);
}

$this->activeStack = $stack;
}

/**
* Mark the stack as inactive.
*
* @param Stack $stack
*/
public function deactivateStack(Stack $stack)
{
$this->activeStack = $stack->getParent();
}

/**
* @return Stack|null
*/
public function getActiveStack()
{
return $this->activeStack;
}

/**
* @param Stack $stack
*/
Expand All @@ -50,23 +87,23 @@ public function addStack(Stack $stack)
}

/**
* @param Stack $parent
*
* @return Stack[]
*/
public function getStacks()
public function getChildrenStacks(Stack $parent)
{
return $this->data['stacks'];
return array_filter($this->data['stacks'], function (Stack $stack) use ($parent) {
return $stack->getParent() === $parent;
});
}

/**
* @return Stack|null Return null there is no current stack.
* @return Stack[]
*/
public function getCurrentStack()
public function getStacks()
{
if (false === $stack = end($this->data['stacks'])) {
return null;
}

return $stack;
return $this->data['stacks'];
}

/**
Expand Down
68 changes: 34 additions & 34 deletions Collector/ProfileClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,59 +75,67 @@ public function __construct($client, Collector $collector, Formatter $formatter,
*/
public function sendAsyncRequest(RequestInterface $request)
{
$stack = $this->collector->getCurrentStack();
$stack = $this->collector->getActiveStack();

$this->collectRequestInformations($request, $stack);
$event = $this->stopwatch->start($this->getStopwatchEventName($request));

return $this->client->sendAsyncRequest($request)->then(
function (ResponseInterface $response) use ($event, $stack) {
$event->stop();
$this->collectResponseInformations($response, $event, $stack);
$onFulfilled = function (ResponseInterface $response) use ($event, $stack) {
$this->collectResponseInformations($response, $event, $stack);

return $response;
};

$onRejected = function (\Exception $exception) use ($event, $stack) {
$this->collectExceptionInformations($exception, $event, $stack);

throw $exception;
};

return $response;
}, function (\Exception $exception) use ($event, $stack) {
$event->stop();
$this->collectExceptionInformations($exception, $event, $stack);
$this->collector->deactivateStack($stack);

throw $exception;
}
);
try {
return $this->client->sendAsyncRequest($request)->then($onFulfilled, $onRejected);
} finally {
$event->stop();
$this->collector->activateStack($stack);
}
}

/**
* {@inheritdoc}
*/
public function sendRequest(RequestInterface $request)
{
$stack = $this->collector->getCurrentStack();
$stack = $this->collector->getActiveStack();

$this->collectRequestInformations($request, $stack);
$event = $this->stopwatch->start($this->getStopwatchEventName($request));

try {
$response = $this->client->sendRequest($request);
$event->stop();

$this->collectResponseInformations($response, $event, $stack);

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

throw $e;
} catch (\Throwable $e) {
$this->collectExceptionInformations($e, $event, $stack);

throw $e;
} finally {
$event->stop();
}
}

/**
* @param RequestInterface $request
* @param Stack|null $stack
* @param Stack $stack
*/
private function collectRequestInformations(RequestInterface $request, Stack $stack = null)
private function collectRequestInformations(RequestInterface $request, Stack $stack)
{
if (null === $stack) {
return;
}

$stack->setRequestTarget($request->getRequestTarget());
$stack->setRequestMethod($request->getMethod());
$stack->setRequestScheme($request->getUri()->getScheme());
Expand All @@ -139,14 +147,10 @@ private function collectRequestInformations(RequestInterface $request, Stack $st
/**
* @param ResponseInterface $response
* @param StopwatchEvent $event
* @param Stack|null $stack
* @param Stack $stack
*/
private function collectResponseInformations(ResponseInterface $response, StopwatchEvent $event, Stack $stack = null)
private function collectResponseInformations(ResponseInterface $response, StopwatchEvent $event, Stack $stack)
{
if (null === $stack) {
return;
}

$stack->setDuration($event->getDuration());
$stack->setResponseCode($response->getStatusCode());
$stack->setClientResponse($this->formatter->formatResponse($response));
Expand All @@ -155,18 +159,14 @@ private function collectResponseInformations(ResponseInterface $response, Stopwa
/**
* @param \Exception $exception
* @param StopwatchEvent $event
* @param Stack|null $stack
* @param Stack $stack
*/
private function collectExceptionInformations(\Exception $exception, StopwatchEvent $event, Stack $stack = null)
private function collectExceptionInformations(\Exception $exception, StopwatchEvent $event, Stack $stack)
{
if ($exception instanceof HttpException) {
$this->collectResponseInformations($exception->getResponse(), $event, $stack);
}

if (null === $stack) {
return;
}

$stack->setDuration($event->getDuration());
$stack->setClientException($this->formatter->formatException($exception));
}
Expand Down
10 changes: 2 additions & 8 deletions Collector/ProfilePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,8 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
{
$profile = new Profile(get_class($this->plugin));

$stack = $this->collector->getCurrentStack();
if (null !== $stack) {
$stack->addProfile($profile);
}
$stack = $this->collector->getActiveStack();
$stack->addProfile($profile);

// wrap the next callback to profile the plugin request changes
$wrappedNext = function (RequestInterface $request) use ($next, $profile) {
Expand Down Expand Up @@ -136,10 +134,6 @@ private function onOutgoingResponse(ResponseInterface $response, Profile $profil
*/
private function collectRequestInformation(RequestInterface $request, Stack $stack = null)
{
if (null === $stack) {
return;
}

if (empty($stack->getRequestTarget())) {
$stack->setRequestTarget($request->getRequestTarget());
}
Expand Down
21 changes: 21 additions & 0 deletions Collector/Stack.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ final class Stack
*/
private $client;

/**
* @var Stack
*/
private $parent;

/**
* @var Profile[]
*/
Expand Down Expand Up @@ -104,6 +109,22 @@ public function getClient()
return $this->client;
}

/**
* @return Stack
*/
public function getParent()
{
return $this->parent;
}

/**
* @param Stack $parent
*/
public function setParent(Stack $parent)
{
$this->parent = $parent;
}

/**
* @param Profile $profile
*/
Expand Down
15 changes: 12 additions & 3 deletions Collector/StackPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,25 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
$stack = new Stack($this->client, $this->formatter->formatRequest($request));

$this->collector->addStack($stack);
$this->collector->activateStack($stack);

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

return $response;
}, function (Exception $exception) use ($stack) {
};

$onRejected = function (Exception $exception) use ($stack) {
$stack->setResponse($this->formatter->formatException($exception));
$stack->setFailed(true);

throw $exception;
});
};

try {
return $next($request)->then($onFulfilled, $onRejected);
} finally {
$this->collector->deactivateStack($stack);
}
}
}
7 changes: 7 additions & 0 deletions Resources/public/style/httplug.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@
display: flex;
}

/**
* Stack
*/
.httplug-stack>.httplug-stack {
margin-left: 2.5em;
}

/**
* Stack header
*/
Expand Down
75 changes: 75 additions & 0 deletions Resources/views/stack.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<div class="httplug-stack-header httplug-toggle" data-toggle="#httplug-{{ client }}-{{ id }}-details">
<div>
{% if stack.failed %}
<span class="httplug-stack-failed">✘</span>
{% else %}
<span class="httplug-stack-success">✔</span>
{% endif %}
<span class="label httplug-method httplug-method-{{ stack.requestMethod|lower }}">{{ stack.requestMethod }}</span>
</div>
<div class="label httplug-stack-header-target">
<span class="httplug-scheme">{{ stack.requestScheme }}://</span>
<span class="httplug-host">{{ stack.requestHost }}</span>
<span class="httplug-target">{{ stack.requestTarget }}</span>
</div>
<div>
<span class="label httplug-duration">{{ stack.duration|number_format }} ms</span>
{% if stack.responseCode >= 400 and stack.responseCode <= 599 %}
<span class="label status-error">{{ stack.responseCode }}</span>
{% elseif stack.responseCode >= 300 and stack.responseCode <= 399 %}
<span class="label status-warning">{{ stack.responseCode }}</span>
{% else %}
<span class="label status-success">{{ stack.responseCode }}</span>
{% endif %}
</div>
</div>
<div id="httplug-{{ client }}-{{ id }}-details" class="httplug-hidden">
<div class="httplug-toolbar">
<div class="httplug-copy-as-curl">
<input readonly="readonly" type="text" value="{{ stack.curlCommand }}" />
<button class="btn tooltip-toggle" aria-label="Copy to clipboard">Copy to clipboard</button>
</div>
<button data-toggle="#httplug-{{ client }}-{{ id }}-stack" class="httplug-toggle btn" >Toggle plugin stack</button>
<button data-toggle="#httplug-{{ client }}-{{ id }}-details .httplug-http-body" class="httplug-toggle btn">Toggle body</button>
</div>
<div class="httplug-messages">
<div class="httplug-message card">
<h4>Request</h4>
{{ stack.clientRequest|httplug_markup|nl2br }}
</div>
<div class="httplug-message card">
<h4>Response</h4>
{{ stack.clientResponse|httplug_markup|nl2br }}
</div>
</div>
{% if stack.profiles %}
<div id="httplug-{{ client }}-{{ id }}-stack" class="httplug-hidden card">
{% for profile in stack.profiles %}
<h3 class="httplug-plugin-name">{{ profile.plugin }}</h3>
<div class="httplug-messages">
<div class="httplug-message">
<h4>Request</h4>
{{ profile.request|httplug_markup|nl2br }}
</div>
<div class="httplug-message">
<h4>Response</h4>
{{ profile.response|httplug_markup|nl2br }}
</div>
</div>
{% if not loop.last %}
<hr />
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
{% for child in collector.childrenStacks(stack) %}
<div class="httplug-stack">
{% include 'HttplugBundle::stack.html.twig' with {
'collector': collector,
'client': client,
'stack': child,
'id': id ~ '-' ~ loop.index
} only %}
</div>
{% endfor %}
Loading