Skip to content

Commit 69e90d2

Browse files
committed
feature #46571 [Messenger] Add new messenger:count command that return a list of transports with their "to be processed" message count. (ktherage, ogizanagi, EXT - THERAGE Kevin)
This PR was squashed before being merged into the 6.2 branch. Discussion ---------- [Messenger] Add new `messenger:count` command that return a list of transports with their "to be processed" message count. | Q | A | ------------- | --- | Branch? | 6.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | | License | MIT | Doc PR | Todo ### Why ? I worked on a project that didn't had insights on how many messages were present in "queues" (because of some hosting quirks) and to have this information we've developed a command that aims to make it possible via the Symfony's console. After a visio call with an ex-colleague on another Symfony project, it turns out that he was verry interested to have this command in Symfony. I've also discused with @Jean-Beru that literraly pushed me too open that PR. So here I am. ### What does it does ? This quite simple, like for the `setup-transport` command it takes in input all configured transports' receivers and **Only if they implement `Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface`** it gets the message count and prints it out in a pretty little table. ### Limitation This command won't work if the configured transport's receiver does not implement `Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface` ### A small demo ? https://user-images.githubusercontent.com/35264408/171853603-6807f0d1-a251-4180-b34b-80d3a3c49b92.mp4 Commits ------- e5433cd6da [Messenger] Add new `messenger:count` command that return a list of transports with their "to be processed" message count.
2 parents c4be1ca + 623cb84 commit 69e90d2

File tree

4 files changed

+229
-0
lines changed

4 files changed

+229
-0
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.2
5+
---
6+
7+
* Add new `messenger:stats` command that return a list of transports with their "to be processed" message count.
8+
49
6.1
510
---
611

Command/StatsCommand.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Command;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\Console\Attribute\AsCommand;
16+
use Symfony\Component\Console\Command\Command;
17+
use Symfony\Component\Console\Input\InputArgument;
18+
use Symfony\Component\Console\Input\InputInterface;
19+
use Symfony\Component\Console\Output\ConsoleOutputInterface;
20+
use Symfony\Component\Console\Output\OutputInterface;
21+
use Symfony\Component\Console\Style\SymfonyStyle;
22+
use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
23+
24+
/**
25+
* @author Kévin Thérage <[email protected]>
26+
*/
27+
#[AsCommand(name: 'messenger:stats', description: 'Show the message count for one or more transports')]
28+
class StatsCommand extends Command
29+
{
30+
private ContainerInterface $transportLocator;
31+
private array $transportNames;
32+
33+
public function __construct(ContainerInterface $transportLocator, array $transportNames = [])
34+
{
35+
$this->transportLocator = $transportLocator;
36+
$this->transportNames = $transportNames;
37+
38+
parent::__construct();
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
protected function configure()
45+
{
46+
$this
47+
->addArgument('transport_names', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'List of transports\' names')
48+
->setHelp(<<<EOF
49+
The <info>%command.name%</info> command counts the messages for all the transports:
50+
51+
<info>php %command.full_name%</info>
52+
53+
Or specific transports only:
54+
55+
<info>php %command.full_name% <transportNames></info>
56+
EOF
57+
)
58+
;
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
protected function execute(InputInterface $input, OutputInterface $output): int
65+
{
66+
$io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
67+
68+
$transportNames = $this->transportNames;
69+
if ($input->getArgument('transport_names')) {
70+
$transportNames = $input->getArgument('transport_names');
71+
}
72+
73+
$outputTable = [];
74+
$uncountableTransports = [];
75+
foreach ($transportNames as $transportName) {
76+
if (!$this->transportLocator->has($transportName)) {
77+
$io->warning(sprintf('The "%s" transport does not exist.', $transportName));
78+
79+
continue;
80+
}
81+
$transport = $this->transportLocator->get($transportName);
82+
if (!$transport instanceof MessageCountAwareInterface) {
83+
$uncountableTransports[] = $transportName;
84+
85+
continue;
86+
}
87+
$outputTable[] = [$transportName, $transport->getMessageCount()];
88+
}
89+
90+
$io->table(['Transport', 'Count'], $outputTable);
91+
92+
if ($uncountableTransports) {
93+
$io->note(sprintf('Unable to get message count for the following transports: "%s".', implode('", "', $uncountableTransports)));
94+
}
95+
96+
return 0;
97+
}
98+
}

DependencyInjection/MessengerPass.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,11 @@ private function registerReceivers(ContainerBuilder $container, array $busIds)
311311
->replaceArgument(1, array_values($receiverNames));
312312
}
313313

314+
if ($container->hasDefinition('console.command.messenger_stats')) {
315+
$container->getDefinition('console.command.messenger_stats')
316+
->replaceArgument(1, array_values($receiverNames));
317+
}
318+
314319
$container->getDefinition('messenger.receiver_locator')->replaceArgument(0, $receiverMapping);
315320

316321
$failureTransportsLocator = ServiceLocatorTagPass::register($container, $failureTransportsMap);

Tests/Command/StatsCommandTest.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Command;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Console\Tester\CommandTester;
16+
use Symfony\Component\DependencyInjection\ServiceLocator;
17+
use Symfony\Component\Messenger\Command\StatsCommand;
18+
use Symfony\Component\Messenger\Transport\Receiver\MessageCountAwareInterface;
19+
use Symfony\Component\Messenger\Transport\TransportInterface;
20+
21+
/**
22+
* @author Kévin Thérage <[email protected]>
23+
*/
24+
class StatsCommandTest extends TestCase
25+
{
26+
private StatsCommand $command;
27+
28+
public function setUp(): void
29+
{
30+
$messageCountableTransport = $this->createMock(MessageCountAwareInterface::class);
31+
$messageCountableTransport->method('getMessageCount')->willReturn(6);
32+
33+
$simpleTransport = $this->createMock(TransportInterface::class);
34+
35+
// mock a service locator
36+
/** @var MockObject&ServiceLocator $serviceLocator */
37+
$serviceLocator = $this->createMock(ServiceLocator::class);
38+
$serviceLocator
39+
->method('get')
40+
->willReturnCallback(function (string $transportName) use ($messageCountableTransport, $simpleTransport) {
41+
if (\in_array($transportName, ['message_countable', 'another_message_countable'], true)) {
42+
return $messageCountableTransport;
43+
}
44+
45+
return $simpleTransport;
46+
});
47+
$serviceLocator
48+
->method('has')
49+
->willReturnCallback(function (string $transportName) {
50+
return \in_array($transportName, ['message_countable', 'simple', 'another_message_countable'], true);
51+
})
52+
;
53+
54+
$this->command = new StatsCommand($serviceLocator, [
55+
'message_countable',
56+
'simple',
57+
'another_message_countable',
58+
'unexisting'
59+
]);
60+
}
61+
62+
public function testWithoutArgument(): void
63+
{
64+
$tester = new CommandTester($this->command);
65+
$tester->execute([]);
66+
$display = $tester->getDisplay();
67+
68+
$this->assertStringContainsString('[WARNING] The "unexisting" transport does not exist.', $display);
69+
$this->assertStringContainsString('message_countable 6', $display);
70+
$this->assertStringContainsString('another_message_countable 6', $display);
71+
$this->assertStringContainsString('! [NOTE] Unable to get message count for the following transports: "simple".', $display);
72+
}
73+
74+
public function testWithOneExistingMessageCountableTransport(): void
75+
{
76+
$tester = new CommandTester($this->command);
77+
$tester->execute(['transport_names' => ['message_countable']]);
78+
$display = $tester->getDisplay();
79+
80+
$this->assertStringNotContainsString('[WARNING] The "unexisting" transport does not exist.', $display);
81+
$this->assertStringContainsString('message_countable 6', $display);
82+
$this->assertStringNotContainsString('another_message_countable', $display);
83+
$this->assertStringNotContainsString(' ! [NOTE] Unable to get message count for the following transports: "simple".', $display);
84+
}
85+
86+
public function testWithMultipleExistingMessageCountableTransport(): void
87+
{
88+
$tester = new CommandTester($this->command);
89+
$tester->execute(['transport_names' => ['message_countable', 'another_message_countable']]);
90+
$display = $tester->getDisplay();
91+
92+
$this->assertStringNotContainsString('[WARNING] The "unexisting" transport does not exist.', $display);
93+
$this->assertStringContainsString('message_countable 6', $display);
94+
$this->assertStringContainsString('another_message_countable 6', $display);
95+
$this->assertStringNotContainsString('! [NOTE] Unable to get message count for the following transports: "simple".', $display);
96+
}
97+
98+
public function testWithNotMessageCountableTransport(): void
99+
{
100+
$tester = new CommandTester($this->command);
101+
$tester->execute(['transport_names' => ['simple']]);
102+
$display = $tester->getDisplay();
103+
104+
$this->assertStringNotContainsString('[WARNING] The "unexisting" transport does not exist.', $display);
105+
$this->assertStringNotContainsString('message_countable', $display);
106+
$this->assertStringNotContainsString('another_message_countable', $display);
107+
$this->assertStringContainsString('! [NOTE] Unable to get message count for the following transports: "simple".', $display);
108+
}
109+
110+
public function testWithNotExistingTransport(): void
111+
{
112+
$tester = new CommandTester($this->command);
113+
$tester->execute(['transport_names' => ['unexisting']]);
114+
$display = $tester->getDisplay();
115+
116+
$this->assertStringContainsString('[WARNING] The "unexisting" transport does not exist.', $display);
117+
$this->assertStringNotContainsString('message_countable', $display);
118+
$this->assertStringNotContainsString('another_message_countable', $display);
119+
$this->assertStringNotContainsString('! [NOTE] Unable to get message count for the following transports: "simple".', $display);
120+
}
121+
}

0 commit comments

Comments
 (0)