Skip to content

Commit e197a22

Browse files
committed
Add diagnostics & tracy panel
1 parent 4c415dc commit e197a22

File tree

8 files changed

+462
-21
lines changed

8 files changed

+462
-21
lines changed

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
"symfony/event-dispatcher": "^6.4.3 || ^7.0.3"
2424
},
2525
"require-dev": {
26+
"psr/log": "^2.0.0 || ^3.0.0",
27+
"tracy/tracy": "^2.10.5",
2628
"contributte/qa": "^0.4",
2729
"contributte/tester": "^0.3",
2830
"contributte/phpstan": "^0.1",

src/Diagnostics/DebugDispatcher.php

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Contributte\EventDispatcher\Diagnostics;
4+
5+
use Psr\Log\LoggerInterface;
6+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
7+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
8+
9+
class DebugDispatcher implements EventDispatcherInterface
10+
{
11+
12+
private EventDispatcherInterface $original;
13+
14+
/** @var LoggerInterface[] */
15+
private array $loggers = [];
16+
17+
public function __construct(EventDispatcherInterface $original)
18+
{
19+
$this->original = $original;
20+
}
21+
22+
public function addLogger(LoggerInterface $logger): void
23+
{
24+
$this->loggers[] = $logger;
25+
}
26+
27+
/**
28+
* @param LoggerInterface[] $loggers
29+
*/
30+
public function setLoggers(array $loggers = []): void
31+
{
32+
$this->loggers = $loggers;
33+
}
34+
35+
/**
36+
* @return LoggerInterface[]
37+
*/
38+
public function getLoggers(): array
39+
{
40+
return $this->loggers;
41+
}
42+
43+
/**
44+
* {@inheritdoc}
45+
*/
46+
public function addListener(string $eventName, callable $listener, int $priority = 0): void
47+
{
48+
$this->original->addListener($eventName, $listener, $priority);
49+
}
50+
51+
/**
52+
* {@inheritdoc}
53+
*/
54+
public function addSubscriber(EventSubscriberInterface $subscriber): void
55+
{
56+
$this->original->addSubscriber($subscriber);
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*/
62+
public function removeListener(string $eventName, callable $listener): void
63+
{
64+
$this->original->removeListener($eventName, $listener);
65+
}
66+
67+
/**
68+
* {@inheritdoc}
69+
*/
70+
public function removeSubscriber(EventSubscriberInterface $subscriber): void
71+
{
72+
$this->original->removeSubscriber($subscriber);
73+
}
74+
75+
/**
76+
* {@inheritdoc}
77+
*/
78+
public function getListeners(?string $eventName = null): array
79+
{
80+
return $this->original->getListeners($eventName);
81+
}
82+
83+
/**
84+
* {@inheritdoc}
85+
*/
86+
public function getListenerPriority(string $eventName, callable $listener): ?int
87+
{
88+
return $this->original->getListenerPriority($eventName, $listener);
89+
}
90+
91+
/**
92+
* {@inheritdoc}
93+
*/
94+
public function hasListeners(?string $eventName = null): bool
95+
{
96+
return $this->original->hasListeners($eventName);
97+
}
98+
99+
/**
100+
* {@inheritdoc}
101+
*/
102+
public function dispatch(object $event, ?string $eventName = null): object
103+
{
104+
$trace = new EventTrace($event, $eventName);
105+
106+
// Iterate over all loggers
107+
foreach ($this->loggers as $logger) {
108+
$logger->debug(sprintf('EventDispatcher@%s: event started', $trace->name), ['event' => $trace]);
109+
}
110+
111+
// Start timer
112+
$start = microtime(true);
113+
114+
// Dispatch event
115+
$return = $this->original->dispatch($event, $eventName);
116+
117+
// If event was handled, mark it
118+
if ($this->original->hasListeners($trace->name)) {
119+
$trace->handled = true;
120+
}
121+
122+
// Calculate duration
123+
$trace->duration = microtime(true) - $start;
124+
125+
foreach ($this->loggers as $logger) {
126+
$logger->debug(sprintf('EventDispatcher@%s: event dispatched', $trace->name), ['event' => $trace]);
127+
}
128+
129+
return $return;
130+
}
131+
132+
}

src/Diagnostics/EventTrace.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Contributte\EventDispatcher\Diagnostics;
4+
5+
class EventTrace
6+
{
7+
8+
public object $event;
9+
10+
public string $name;
11+
12+
public bool $handled = false;
13+
14+
public float $duration = 0.0;
15+
16+
public function __construct(object $event, ?string $eventName = null)
17+
{
18+
$this->event = $event;
19+
$this->name = $eventName ?? $event::class;
20+
}
21+
22+
}

src/Diagnostics/TracyDispatcher.php

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Contributte\EventDispatcher\Diagnostics;
4+
5+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
6+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
7+
8+
class TracyDispatcher implements EventDispatcherInterface
9+
{
10+
11+
private EventDispatcherInterface $original;
12+
13+
/** @var EventTrace[] */
14+
private array $events = [];
15+
16+
public function __construct(EventDispatcherInterface $original)
17+
{
18+
$this->original = $original;
19+
}
20+
21+
/**
22+
* @return EventTrace[]
23+
*/
24+
public function getEvents(): array
25+
{
26+
return $this->events;
27+
}
28+
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
public function addListener(string $eventName, callable $listener, int $priority = 0): void
33+
{
34+
$this->original->addListener($eventName, $listener, $priority);
35+
}
36+
37+
/**
38+
* {@inheritdoc}
39+
*/
40+
public function addSubscriber(EventSubscriberInterface $subscriber): void
41+
{
42+
$this->original->addSubscriber($subscriber);
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*/
48+
public function removeListener(string $eventName, callable $listener): void
49+
{
50+
$this->original->removeListener($eventName, $listener);
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*/
56+
public function removeSubscriber(EventSubscriberInterface $subscriber): void
57+
{
58+
$this->original->removeSubscriber($subscriber);
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
public function getListeners(?string $eventName = null): array
65+
{
66+
return $this->original->getListeners($eventName);
67+
}
68+
69+
/**
70+
* {@inheritdoc}
71+
*/
72+
public function getListenerPriority(string $eventName, callable $listener): ?int
73+
{
74+
return $this->original->getListenerPriority($eventName, $listener);
75+
}
76+
77+
/**
78+
* {@inheritdoc}
79+
*/
80+
public function hasListeners(?string $eventName = null): bool
81+
{
82+
return $this->original->hasListeners($eventName);
83+
}
84+
85+
/**
86+
* {@inheritdoc}
87+
*/
88+
public function dispatch(object $event, ?string $eventName = null): object
89+
{
90+
$trace = new EventTrace($event, $eventName);
91+
92+
// Store trace
93+
$this->events[] = $trace;
94+
95+
// Start timer
96+
$start = microtime(true);
97+
98+
// Dispatch event
99+
$return = $this->original->dispatch($event, $eventName);
100+
101+
// If event was handled, mark it
102+
if ($this->original->hasListeners($trace->name)) {
103+
$trace->handled = true;
104+
}
105+
106+
// Calculate duration
107+
$trace->duration = microtime(true) - $start;
108+
109+
return $return;
110+
}
111+
112+
}

src/Tracy/EventPanel.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Contributte\EventDispatcher\Tracy;
4+
5+
use Contributte\EventDispatcher\Diagnostics\TracyDispatcher;
6+
use Tracy\IBarPanel;
7+
8+
class EventPanel implements IBarPanel
9+
{
10+
11+
private TracyDispatcher $dispatcher;
12+
13+
public function __construct(TracyDispatcher $dispatcher)
14+
{
15+
$this->dispatcher = $dispatcher;
16+
}
17+
18+
/**
19+
* {@inheritdoc}
20+
*/
21+
public function getTab(): string
22+
{
23+
$totalCount = count($this->dispatcher->getEvents()); // @phpcs:ignore
24+
$handledCount = $this->handledCount(); // @phpcs:ignore
25+
$totalTime = $this->countTotalTime(); // @phpcs:ignore
26+
$totalTime = ($totalTime > 0 ? ' / ' . number_format($totalTime * 1000, 1, '.', '') . ' ms' : ''); // @phpcs:ignore
27+
28+
ob_start();
29+
require __DIR__ . '/templates/tab.phtml';
30+
31+
return (string) ob_get_clean();
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function getPanel(): string
38+
{
39+
$handledCount = $this->handledCount(); // @phpcs:ignore
40+
$totalTime = $this->countTotalTime(); // @phpcs:ignore
41+
$events = $this->dispatcher->getEvents(); // @phpcs:ignore
42+
$listeners = $this->dispatcher->getListeners();
43+
ksort($listeners);
44+
ob_start();
45+
require __DIR__ . '/templates/panel.phtml';
46+
47+
return (string) ob_get_clean();
48+
}
49+
50+
private function countTotalTime(): float
51+
{
52+
$totalTime = 0;
53+
foreach ($this->dispatcher->getEvents() as $event) {
54+
$totalTime += $event->duration;
55+
}
56+
57+
return $totalTime;
58+
}
59+
60+
private function handledCount(): int
61+
{
62+
$handled = 0;
63+
foreach ($this->dispatcher->getEvents() as $event) {
64+
$handled += $event->handled ? 1 : 0;
65+
}
66+
67+
return $handled;
68+
}
69+
70+
}

0 commit comments

Comments
 (0)