Skip to content

Commit

Permalink
Add diagnostics & tracy panel
Browse files Browse the repository at this point in the history
  • Loading branch information
f3l1x committed Feb 5, 2024
1 parent 4c415dc commit e197a22
Show file tree
Hide file tree
Showing 8 changed files with 462 additions and 21 deletions.
2 changes: 2 additions & 0 deletions composer.json
Expand Up @@ -23,6 +23,8 @@
"symfony/event-dispatcher": "^6.4.3 || ^7.0.3"
},
"require-dev": {
"psr/log": "^2.0.0 || ^3.0.0",
"tracy/tracy": "^2.10.5",
"contributte/qa": "^0.4",
"contributte/tester": "^0.3",
"contributte/phpstan": "^0.1",
Expand Down
132 changes: 132 additions & 0 deletions src/Diagnostics/DebugDispatcher.php
@@ -0,0 +1,132 @@
<?php declare(strict_types = 1);

namespace Contributte\EventDispatcher\Diagnostics;

use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class DebugDispatcher implements EventDispatcherInterface
{

private EventDispatcherInterface $original;

/** @var LoggerInterface[] */
private array $loggers = [];

public function __construct(EventDispatcherInterface $original)
{
$this->original = $original;
}

public function addLogger(LoggerInterface $logger): void
{
$this->loggers[] = $logger;
}

/**
* @param LoggerInterface[] $loggers
*/
public function setLoggers(array $loggers = []): void
{
$this->loggers = $loggers;
}

/**
* @return LoggerInterface[]
*/
public function getLoggers(): array
{
return $this->loggers;
}

/**
* {@inheritdoc}
*/
public function addListener(string $eventName, callable $listener, int $priority = 0): void
{
$this->original->addListener($eventName, $listener, $priority);
}

/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber): void
{
$this->original->addSubscriber($subscriber);
}

/**
* {@inheritdoc}
*/
public function removeListener(string $eventName, callable $listener): void
{
$this->original->removeListener($eventName, $listener);
}

/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber): void
{
$this->original->removeSubscriber($subscriber);
}

/**
* {@inheritdoc}
*/
public function getListeners(?string $eventName = null): array
{
return $this->original->getListeners($eventName);
}

/**
* {@inheritdoc}
*/
public function getListenerPriority(string $eventName, callable $listener): ?int
{
return $this->original->getListenerPriority($eventName, $listener);
}

/**
* {@inheritdoc}
*/
public function hasListeners(?string $eventName = null): bool
{
return $this->original->hasListeners($eventName);
}

/**
* {@inheritdoc}
*/
public function dispatch(object $event, ?string $eventName = null): object
{
$trace = new EventTrace($event, $eventName);

// Iterate over all loggers
foreach ($this->loggers as $logger) {
$logger->debug(sprintf('EventDispatcher@%s: event started', $trace->name), ['event' => $trace]);
}

// Start timer
$start = microtime(true);

// Dispatch event
$return = $this->original->dispatch($event, $eventName);

// If event was handled, mark it
if ($this->original->hasListeners($trace->name)) {
$trace->handled = true;
}

// Calculate duration
$trace->duration = microtime(true) - $start;

foreach ($this->loggers as $logger) {
$logger->debug(sprintf('EventDispatcher@%s: event dispatched', $trace->name), ['event' => $trace]);
}

return $return;
}

}
22 changes: 22 additions & 0 deletions src/Diagnostics/EventTrace.php
@@ -0,0 +1,22 @@
<?php declare(strict_types = 1);

namespace Contributte\EventDispatcher\Diagnostics;

class EventTrace
{

public object $event;

public string $name;

public bool $handled = false;

public float $duration = 0.0;

public function __construct(object $event, ?string $eventName = null)
{
$this->event = $event;
$this->name = $eventName ?? $event::class;
}

}
112 changes: 112 additions & 0 deletions src/Diagnostics/TracyDispatcher.php
@@ -0,0 +1,112 @@
<?php declare(strict_types = 1);

namespace Contributte\EventDispatcher\Diagnostics;

use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class TracyDispatcher implements EventDispatcherInterface
{

private EventDispatcherInterface $original;

/** @var EventTrace[] */
private array $events = [];

public function __construct(EventDispatcherInterface $original)
{
$this->original = $original;
}

/**
* @return EventTrace[]
*/
public function getEvents(): array
{
return $this->events;
}

/**
* {@inheritdoc}
*/
public function addListener(string $eventName, callable $listener, int $priority = 0): void
{
$this->original->addListener($eventName, $listener, $priority);
}

/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber): void
{
$this->original->addSubscriber($subscriber);
}

/**
* {@inheritdoc}
*/
public function removeListener(string $eventName, callable $listener): void
{
$this->original->removeListener($eventName, $listener);
}

/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber): void
{
$this->original->removeSubscriber($subscriber);
}

/**
* {@inheritdoc}
*/
public function getListeners(?string $eventName = null): array
{
return $this->original->getListeners($eventName);
}

/**
* {@inheritdoc}
*/
public function getListenerPriority(string $eventName, callable $listener): ?int
{
return $this->original->getListenerPriority($eventName, $listener);
}

/**
* {@inheritdoc}
*/
public function hasListeners(?string $eventName = null): bool
{
return $this->original->hasListeners($eventName);
}

/**
* {@inheritdoc}
*/
public function dispatch(object $event, ?string $eventName = null): object
{
$trace = new EventTrace($event, $eventName);

// Store trace
$this->events[] = $trace;

// Start timer
$start = microtime(true);

// Dispatch event
$return = $this->original->dispatch($event, $eventName);

// If event was handled, mark it
if ($this->original->hasListeners($trace->name)) {
$trace->handled = true;
}

// Calculate duration
$trace->duration = microtime(true) - $start;

return $return;
}

}
70 changes: 70 additions & 0 deletions src/Tracy/EventPanel.php
@@ -0,0 +1,70 @@
<?php declare(strict_types = 1);

namespace Contributte\EventDispatcher\Tracy;

use Contributte\EventDispatcher\Diagnostics\TracyDispatcher;
use Tracy\IBarPanel;

class EventPanel implements IBarPanel
{

private TracyDispatcher $dispatcher;

public function __construct(TracyDispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
}

/**
* {@inheritdoc}
*/
public function getTab(): string
{
$totalCount = count($this->dispatcher->getEvents()); // @phpcs:ignore
$handledCount = $this->handledCount(); // @phpcs:ignore
$totalTime = $this->countTotalTime(); // @phpcs:ignore
$totalTime = ($totalTime > 0 ? ' / ' . number_format($totalTime * 1000, 1, '.', ' ') . ' ms' : ''); // @phpcs:ignore

ob_start();
require __DIR__ . '/templates/tab.phtml';

return (string) ob_get_clean();
}

/**
* {@inheritdoc}
*/
public function getPanel(): string
{
$handledCount = $this->handledCount(); // @phpcs:ignore
$totalTime = $this->countTotalTime(); // @phpcs:ignore
$events = $this->dispatcher->getEvents(); // @phpcs:ignore
$listeners = $this->dispatcher->getListeners();
ksort($listeners);
ob_start();
require __DIR__ . '/templates/panel.phtml';

return (string) ob_get_clean();
}

private function countTotalTime(): float
{
$totalTime = 0;
foreach ($this->dispatcher->getEvents() as $event) {
$totalTime += $event->duration;
}

return $totalTime;
}

private function handledCount(): int
{
$handled = 0;
foreach ($this->dispatcher->getEvents() as $event) {
$handled += $event->handled ? 1 : 0;
}

return $handled;
}

}

0 comments on commit e197a22

Please sign in to comment.