8000 [Messenger][Profiler] Trace middleware execution · symfony/symfony@e974f67 · GitHub
[go: up one dir, main page]

Skip to content

Commit e974f67

Browse files
committed
[Messenger][Profiler] Trace middleware execution
1 parent 55def78 commit e974f67

File tree

6 files changed

+230
-2
lines changed

6 files changed

+230
-2
lines changed

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.html.twig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
'event_listener': '#00B8F5',
1010
'template': '#66CC00',
1111
'doctrine': '#FF6633',
12+
'messenger.middleware': '#BDB81E',
1213
} %}
1314
{% endif %}
1415

src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\Messenger\Handler\ChainHandler;
2323
use Symfony\Component\Messenger\Handler\Locator\ContainerHandlerLocator;
2424
use Symfony\Component\Messenger\Handler\MessageSubscriberInterface;
25+
use Symfony\Component\Messenger\Middleware\Enhancers\TraceableMiddleware;
2526
use Symfony\Component\Messenger\TraceableMessageBus;
2627
use Symfony\Component\Messenger\Transport\ReceiverInterface;
2728
use Symfony\Component\Messenger\Transport\SenderInterface;
@@ -37,13 +38,15 @@ class MessengerPass implements CompilerPassInterface
3738
private $busTag;
3839
private $senderTag;
3940
private $receiverTag;
41+
private $debugStopwatchId;
4042

41-
public function __construct(string $handlerTag = 'messenger.message_handler', string $busTag = 'messenger.bus', string $senderTag = 'messenger.sender', string $receiverTag = 'messenger.receiver')
43+
public function __construct(string $handlerTag = 'messenger.message_handler', string $busTag = 'messenger.bus', string $senderTag = 'messenger.sender', string $receiverTag = 'messenger.receiver', string $debugStopwatchId = 'debug.stopwatch')
4244
{
4345
$this->handlerTag = $handlerTag;
4446
$this->busTag = $busTag;
4547
$this->senderTag = $senderTag;
4648
$this->receiverTag = $receiverTag;
49+
$this->debugStopwatchId = $debugStopwatchId;
4750
}
4851

4952
/**
@@ -303,6 +306,7 @@ private function registerBusToCollector(ContainerBuilder $container, string $bus
303306

304307
private function registerBusMiddleware(ContainerBuilder $container, string $busId, array $middlewareCollection)
305308
{
309+
$debug = $container->getParameter('kernel.debug') && $container->has($this->debugStopwatchId);
306310
$middlewareReferences = array();
307311
foreach ($middlewareCollection as $middlewareItem) {
308312
$id = $middlewareItem['id'];
@@ -315,7 +319,7 @@ private function registerBusMiddleware(ContainerBuilder $container, string $busI
315319
throw new RuntimeException(sprintf('Invalid middleware "%s": define such service to be able to use it.', $id));
316320
}
317321

318-
if (($definition = $container->findDefinition($messengerMiddlewareId))->isAbstract()) {
322+
if ($isDefinitionAbstract = ($definition = $container->findDefinition($messengerMiddlewareId))->isAbstract()) {
319323
$childDefinition = new ChildDefinition($messengerMiddlewareId);
320324
$count = \count($definition->getArguments());
321325
foreach (array_values($arguments ?? array()) as $key => $argument) {
@@ -329,6 +333,21 @@ private function registerBusMiddleware(ContainerBuilder $container, string $busI
329333
throw new RuntimeException(sprintf('Invalid middleware factory "%s": a middleware factory must be an abstract definition.', $id));
330334
}
331335

336+
if ($debug) {
337+
$container->register($debugMiddlewareId = '.messenger.debug.traced.'.$messengerMiddlewareId, TraceableMiddleware::class)
338+
// Decorates with a high priority so it's applied the earliest:
339+
->setDecoratedService($messengerMiddlewareId, null, 100)
340+
->setArguments(array(
341+
new Reference($debugMiddlewareId.'.inner'),
342+
new Reference($this->debugStopwatchId),
343+
// In case the definition isn't abstract,
344+
// we cannot be sure the service instance is used by one bus only.
345+
// So we only inject the bus name when the original definition is abstract.
346+
$isDefinitionAbstract ? $busId : null,
347+
))
348+
;
349+
}
350+
332351
$middlewareReferences[] = new Reference($messengerMiddlewareId);
333352
}
334353

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Messenger\Middleware\Enhancers;
13+
14+
use Symfony\Component\Messenger\Envelope;
15+
use Symfony\Component\Messenger\EnvelopeAwareInterface;
16+
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
17+
use Symfony\Component\Stopwatch\Stopwatch;
18+
19+
/**
20+
* Collects some data about a middleware.
21+
*
22+
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
23+
*/
24+
class TraceableMiddleware implements MiddlewareInterface, EnvelopeAwareInterface
25+
{
26+
private $inner;
27+
private $stopwatch;
28+
private $busName;
29+
private $eventCategory;
30+
31+
public function __construct(MiddlewareInterface $inner, Stopwatch $stopwatch, string $busName = null, string $eventCategory = 'messenger.middleware')
32+
{
33+
$this->inner = $inner;
34+
$this->stopwatch = $stopwatch;
35+
$this->busName = $busName;
36+
$this->eventCategory = $eventCategory;
37+
}
38+
39+
/**
40+
* @param Envelope $envelope
41+
*/
42+
public function handle($envelope, callable $next)
43+
{
44+
$class = \get_class($this->inner);
45+
$eventName = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
46+
47+
if ($this->busName) {
48+
$eventName .= " (bus: {$this->busName})";
49+
}
50+
51+
$this->stopwatch->start($eventName, $this->eventCategory);
52+
53+
try {
54+
$result = $this->inner->handle($envelope->getMessageFor($this->inner), function ($message) use ($next, $eventName) {
55+
$this->stopwatch->stop($eventName);
56+
$result = $next($message);
57+
$this->stopwatch->start($eventName, $this->eventCategory);
58+
59+
return $result;
60+
});
61+
} finally {
62+
if ($this->stopwatch->isStarted($eventName)) {
63+
$this->stopwatch->stop($eventName);
64+
}
65+
}
66+
67+
return $result;
68+
}
69+
}

src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
use Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver;
4242
use Symfony\Component\Messenger\Transport\AmqpExt\AmqpSender;
4343
use Symfony\Component\Messenger\Transport\ReceiverInterface;
44+
use Symfony\Component\Stopwatch\Stopwatch;
4445

4546
class MessengerPassTest extends TestCase
4647
{
@@ -561,6 +562,39 @@ public function testMiddlewareFactoryDefinitionMustBeAbstract()
561562
(new MessengerPass())->process($container);
562563
}
563564

565+
public function testDecoratesWithTraceableMiddlewareOnDebug()
566+
{
567+
$container = $this->getContainerBuilder();
568+
569+
$container->register($busId = 'message_bus', MessageBusInterface::class)->setArgument(0, array())->addTag('messenger.bus');
570+
$container->register('abstract_middleware', UselessMiddleware::class)->setAbstract(true);
571+
$container->register('concrete_middleware', UselessMiddleware::class);
572+
573+
$container->setParameter($middlewareParameter = $busId.'.middleware', array(
574+
array('id' => 'abstract_middleware'),
575+
array('id' => 'concrete_middleware'),
576+
));
577+
578+
$container->setParameter('kernel.debug', true);
579+
$container->register('debug.stopwatch', Stopwatch::class);
580+
581+
(new MessengerPass())->process($container);
582+
583+
$this->assertNotNull($concreteDef = $container->getDefinition('.messenger.debug.traced.concrete_middleware'));
584+
$this->assertEquals(array(
585+
new Reference('.messenger.debug.traced.concrete_middleware.inner'),
586+
new Reference('debug.stopwatch'),
587+
null,
588+
), $concreteDef->getArguments());
589+
590+
$this->assertNotNull($abstractDef = $container->getDefinition(".messenger.debug.traced.$busId.middleware.abstract_middleware"));
591+
$this->assertEquals(array(
592+
new Reference(".messenger.debug.traced.$busId.middleware.abstract_middleware.inner"),
593+
new Reference('debug.stopwatch'),
594+
$busId,
595+
), $abstractDef->getArguments());
596+
}
597+
564598
public function testItRegistersTheDebugCommand()
565599
{
566600
$container = $this->getContainerBuilder($commandBusId = 'command_bus');
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Messenger\Tests\Middleware\Enhancers;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Messenger\Envelope;
16+
use Symfony\Component\Messenger\Middleware\Enhancers\TraceableMiddleware;
17+
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
18+
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
19+
use Symfony\Component\Stopwatch\Stopwatch;
20+
21+
/**
22+
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
23+
*/
24+
class TraceableMiddlewareTest extends TestCase
25+
{
26+
public function testHandle()
27+
{
28+
$busId = 'command_bus';
29+
$envelope = Envelope::wrap($message = new DummyMessage('Hello'));
30+
31+
$middleware = $this->getMockBuilder(MiddlewareInterface::class)->getMock();
32+
$middleware->expects($this->once())
33+
->method('handle')
34+
->with($message, $this->anything())
35+
->will($this->returnCallback(function ($message, callable $next) {
36+
return $next($message);
37+
}))
38+
;
39+
40+
$next = $this->createPartialMock(\stdClass::class, array('__invoke'));
41+
$next
42+
->expects($this->once())
43+
->method('__invoke')
44+
->with($message)
45+
->willReturn($expectedReturnedValue = 'Hello')
46+
;
47+
48+
$stopwatch = $this->createMock(Stopwatch::class);
49+
$stopwatch->expects($this->once())->method('isStarted')->willReturn(true);
50+
$stopwatch->expects($this->exactly(2))
51+
->method('start')
52+
->with($this->matches('%sMiddlewareInterface%s (bus: command_bus)'), 'messenger.middleware')
53+
;
54+
$stopwatch->expects($this->exactly(2))
55+
->method('stop')
56+
->with($this->matches('%sMiddlewareInterface%s (bus: command_bus)'))
57+
;
58+
59+
$traced = new TraceableMiddleware($middleware, $stopwatch, $busId);
60+
61+
$this->assertSame($expectedReturnedValue, $traced->handle($envelope, $next));
62+
}
63+
64+
/**
65+
* @expectedException \RuntimeException
66+
* @expectedExceptionMessage Foo exception from next callable
67+
*/
68+
public function testHandleWithException()
69+
{
70+
$busId = 'command_bus';
71+
$envelope = Envelope::wrap($message = new DummyMessage('Hello'));
72+
73+
$middleware = $this->getMockBuilder(MiddlewareInterface::class)->getMock();
74+
$middleware->expects($this->once())
75+
->method('handle')
76+
->with($message, $this->anything())
77+
->will($this->returnCallback(function ($message, callable $next) {
78+
return $next($message);
79+
}))
80+
;
81+
82+
$next = $this->createPartialMock(\stdClass::class, array('__invoke'));
83+
$next
84+
->expects($this->once())
85+
->method('__invoke')
86+
->willThrowException(new \RuntimeException('Foo exception from next callable'))
87+
;
88+
89+
$stopwatch = $this->createMock(Stopwatch::class);
90+
$stopwatch->expects($this->once())->method('isStarted')->willReturn(true);
91+
// Start is only expected to be called once, as an exception is thrown by the next callable:
92+
$stopwatch->expects($this->exactly(1))
93+
->method('start')
94+
->with($this->matches('%sMiddlewareInterface%s (bus: command_bus)'), 'messenger.middleware')
95+
;
96+
$stopwatch->expects($this->exactly(2))
97+
->method('stop')
98+
->with($this->matches('%sMiddlewareInterface%s (bus: command_bus)'))
99+
;
100+
101+
$traced = new TraceableMiddleware($middleware, $stopwatch, $busId);
102+
$traced->handle($envelope, $next);
103+
}
104+
}

src/Symfony/Component/Messenger/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"symfony/process": "~3.4|~4.0",
2727
"symfony/property-access": "~3.4|~4.0",
2828
"symfony/serializer": "~3.4|~4.0",
29+
"symfony/stopwatch": "~3.4|~4.0",
2930
"symfony/validator": "~3.4|~4.0",
3031
"symfony/var-dumper": "~3.4|~4.0"
3132
},

0 commit comments

Comments
 (0)
0