Add template events data collector

This commit is contained in:
Kamil Kokot 2019-12-27 20:17:49 +01:00
parent 5d020ae84f
commit d917f2d088
No known key found for this signature in database
GPG key ID: 7BD76F7054D93C89
8 changed files with 372 additions and 0 deletions

View file

@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\UiBundle\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* @internal
*/
final class TemplateBlockDataCollector extends DataCollector
{
/** @var TemplateBlockRenderingHistory */
private $templateBlockRenderingHistory;
public function __construct(TemplateBlockRenderingHistory $templateBlockRenderingHistory)
{
$this->templateBlockRenderingHistory = $templateBlockRenderingHistory;
$this->reset();
}
public function collect(Request $request, Response $response): void
{
$this->data['renderedEvents'] = $this->templateBlockRenderingHistory->getRenderedEvents();
}
public function getRenderedEvents(): array
{
return $this->data['renderedEvents'];
}
public function getNumberOfRenderedEvents(): int
{
return count($this->data['renderedEvents']);
}
public function getNumberOfRenderedBlocks(): int
{
return array_reduce($this->data['renderedEvents'], static function (int $accumulator, array $event): int {
return $accumulator + count($event['blocks']);
}, 0);
}
public function getTotalDuration(): float
{
return array_reduce($this->data['renderedEvents'], static function (float $accumulator, array $event): float {
return $accumulator + $event['time'];
}, 0.0);
}
public function getName(): string
{
return 'sylius_ui.template_block';
}
public function reset(): void
{
$this->data['renderedEvents'] = [];
}
}

View file

@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\UiBundle\DataCollector;
use Sylius\Bundle\UiBundle\Registry\TemplateBlock;
/**
* @internal
*/
final class TemplateBlockRenderingHistory
{
/** @psalm-var list<array{name: string, start: float, stop: float, time: float, blocks: list<array{definition: TemplateBlock, start: float, stop: float, time: float}>}> */
private $renderedEvents = [];
/** @psalm-var array{name: string, start: float, stop?: float, time?: float, blocks: list<array{definition: TemplateBlock, start: float, stop: float, time: float}>} */
private $currentlyRenderedEvent = [];
/** @psalm-var array{definition: TemplateBlock, start: float, stop?: float, time?: float} */
private $currentlyRenderedBlock = [];
public function startRenderingEvent(string $eventName, array $context): void
{
$this->currentlyRenderedEvent = ['name' => $eventName, 'start' => microtime(true), 'blocks' => []];
}
public function startRenderingBlock(string $eventName, TemplateBlock $templateBlock, array $context): void
{
$this->currentlyRenderedBlock = ['definition' => $templateBlock, 'start' => microtime(true)];
}
public function stopRenderingBlock(string $eventName, TemplateBlock $templateBlock, array $context): void
{
$this->currentlyRenderedBlock['stop'] = microtime(true);
$this->currentlyRenderedBlock['time'] = $this->currentlyRenderedBlock['stop'] - $this->currentlyRenderedBlock['start'];
$this->currentlyRenderedEvent['blocks'][] = $this->currentlyRenderedBlock;
$this->currentlyRenderedBlock = [];
}
public function stopRenderingEvent(string $eventName, array $context): void
{
$this->currentlyRenderedEvent['stop'] = microtime(true);
$this->currentlyRenderedEvent['time'] = $this->currentlyRenderedEvent['stop'] - $this->currentlyRenderedEvent['start'];
$this->renderedEvents[] = $this->currentlyRenderedEvent;
$this->currentlyRenderedEvent = [];
}
public function getRenderedEvents(): array
{
return $this->renderedEvents;
}
public function reset(): void
{
$this->renderedEvents = $this->currentlyRenderedEvent = $this->currentlyRenderedBlock = [];
}
}

View file

@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\UiBundle\DataCollector;
use Sylius\Bundle\UiBundle\Registry\TemplateBlock;
use Sylius\Bundle\UiBundle\Renderer\TemplateBlockRendererInterface;
/**
* @internal
*/
final class TraceableTemplateBlockRenderer implements TemplateBlockRendererInterface
{
/** @var TemplateBlockRendererInterface */
private $templateBlockRenderer;
/** @var TemplateBlockRenderingHistory */
private $templateBlockRenderingHistory;
public function __construct(TemplateBlockRendererInterface $templateBlockRenderer, TemplateBlockRenderingHistory $templateBlockRenderingHistory)
{
$this->templateBlockRenderer = $templateBlockRenderer;
$this->templateBlockRenderingHistory = $templateBlockRenderingHistory;
}
public function render(string $eventName, TemplateBlock $templateBlock, array $context = []): string
{
$this->templateBlockRenderingHistory->startRenderingBlock($eventName, $templateBlock, $context);
$renderedBlock = $this->templateBlockRenderer->render($eventName, $templateBlock, $context);
$this->templateBlockRenderingHistory->stopRenderingBlock($eventName, $templateBlock, $context);
return $renderedBlock;
}
}

View file

@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\UiBundle\DataCollector;
use Sylius\Bundle\UiBundle\Renderer\TemplateEventRendererInterface;
/**
* @internal
*/
final class TraceableTemplateEventRenderer implements TemplateEventRendererInterface
{
/** @var TemplateEventRendererInterface */
private $templateEventRenderer;
/** @var TemplateBlockRenderingHistory */
private $templateBlockRenderingHistory;
public function __construct(TemplateEventRendererInterface $templateEventRenderer, TemplateBlockRenderingHistory $templateBlockRenderingHistory)
{
$this->templateEventRenderer = $templateEventRenderer;
$this->templateBlockRenderingHistory = $templateBlockRenderingHistory;
}
public function render(string $eventName, array $context = []): string
{
$this->templateBlockRenderingHistory->startRenderingEvent($eventName, $context);
$renderedEvent = $this->templateEventRenderer->render($eventName, $context);
$this->templateBlockRenderingHistory->stopRenderingEvent($eventName, $context);
return $renderedEvent;
}
}

View file

@ -31,6 +31,10 @@ final class SyliusUiExtension extends Extension
$loader->load('services.xml');
if ($container->getParameter('kernel.debug')) {
$loader->load('services/debug/template_event.xml');
}
$this->loadEvents($config['events'], $container);
}

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file is part of the Sylius package.
(c) Paweł Jędrzejewski
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="Sylius\Bundle\UiBundle\DataCollector\TemplateBlockRenderingHistory" />
<service id="Sylius\Bundle\UiBundle\DataCollector\TemplateBlockDataCollector">
<argument type="service" id="Sylius\Bundle\UiBundle\DataCollector\TemplateBlockRenderingHistory" />
<tag name="data_collector" template="@SyliusUi/DataCollector/templateBlock.html.twig" id="sylius_ui.template_block" />
</service>
<service id="Sylius\Bundle\UiBundle\DataCollector\TraceableTemplateBlockRenderer" decorates="Sylius\Bundle\UiBundle\Renderer\TemplateBlockRendererInterface">
<argument type="service" id="Sylius\Bundle\UiBundle\DataCollector\TraceableTemplateBlockRenderer.inner" />
<argument type="service" id="Sylius\Bundle\UiBundle\DataCollector\TemplateBlockRenderingHistory" />
</service>
<service id="Sylius\Bundle\UiBundle\DataCollector\TraceableTemplateEventRenderer" decorates="Sylius\Bundle\UiBundle\Renderer\TemplateEventRendererInterface">
<argument type="service" id="Sylius\Bundle\UiBundle\DataCollector\TraceableTemplateEventRenderer.inner" />
<argument type="service" id="Sylius\Bundle\UiBundle\DataCollector\TemplateBlockRenderingHistory" />
</service>
</services>
</container>

View file

@ -0,0 +1,101 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block toolbar %}
<div class="sf-toolbar-block">
<a href="{{ path('_profiler', { 'token': token, 'panel': name }) }}">
<div class="sf-toolbar-icon">
{{ include('@SonataBlock/Profiler/icon.svg') }}
<span class="sf-toolbar-value">{{ collector.numberOfRenderedEvents }}</span>
</div>
</a>
<div class="sf-toolbar-info">
<div class="sf-toolbar-info-piece">
<b>Rendered events</b>
<span class="sf-toolbar-status">{{ collector.numberOfRenderedEvents }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Rendered blocks</b>
<span class="sf-toolbar-status">{{ collector.numberOfRenderedBlocks }}</span>
</div>
</div>
</div>
{% endblock %}
{% block menu %}
<span class="label">
<span class="icon">
{{ include('@SonataBlock/Profiler/icon.svg') }}
</span>
<strong>Template events</strong>
<span class="count">
<span>{{ collector.numberOfRenderedEvents }}</span>
</span>
</span>
{% endblock %}
{% block panel %}
<h2>Template events metrics</h2>
<div class="metrics">
<div class="metric">
<span class="value">{{ '%.0f'|format(collector.totalDuration * 1000) }} <span class="unit">ms</span></span>
<span class="label">Total execution time</span>
</div>
<div class="metric">
<span class="value">{{ '%.0f'|format(collector.numberOfRenderedEvents) }}</span>
<span class="label">Rendered events</span>
</div>
<div class="metric">
<span class="value">{{ '%.0f'|format(collector.numberOfRenderedBlocks) }}</span>
<span class="label">Rendered blocks</span>
</div>
</div>
<h2>Rendered template events</h2>
{% if collector.renderedEvents|length > 0 %}
<table>
<tr>
<th>Event</th>
<th>Duration</th>
<th>Blocks</th>
</tr>
{% for event in collector.renderedEvents %}
<tr>
<td>{{ event.name }}</td>
<td>{{ '%.0f'|format(event.time * 1000) }}ms</td>
<td>
{% if event.blocks|length > 0 %}
<table>
<tr>
<th>Name</th>
<th>Template</th>
<th>Duration</th>
<th>Priority</th>
</tr>
{% for block in event.blocks %}
<tr>
<td>{{ block.definition.name }}</td>
<td>{{ block.definition.template }}</td>
<td>{{ '%.0f'|format(block.time * 1000) }}ms</td>
<td>{{ block.definition.priority }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<i>no blocks rendered</i>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="empty">
No template events have been rendered.
</div>
{% endif %}
{% endblock %}

View file

@ -24,6 +24,8 @@ final class SyliusUiExtensionTest extends AbstractExtensionTestCase
/** @test */
public function it_configures_the_multiple_event_block_listener_service_with_events_and_blocks_data(): void
{
$this->container->setParameter('kernel.debug', true);
$this->load(['events' => [
'first_event' => ['blocks' => [
'first_block' => ['template' => 'first.html.twig', 'enabled' => true, 'priority' => 0],
@ -52,6 +54,8 @@ final class SyliusUiExtensionTest extends AbstractExtensionTestCase
/** @test */
public function it_sorts_blocks_by_their_priority_and_uses_fifo_ordering(): void
{
$this->container->setParameter('kernel.debug', true);
$this->load(['events' => [
'event_name' => ['blocks' => [
'fourth_block' => ['template' => 'fourth.html.twig', 'enabled' => true, 'priority' => -5],