Skip to content

Commit fbfbfa1

Browse files
committed
Init profiler
1 parent 74668e0 commit fbfbfa1

File tree

7 files changed

+388
-2
lines changed

7 files changed

+388
-2
lines changed

config/services.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
2222

2323
use MongoDB\Bundle\Command\DebugCommand;
24-
use MongoDB\Client;
24+
use MongoDB\Bundle\DataCollector\MongoDBDataCollector;
25+
use MongoDB\Bundle\TraceableClient;
2526

2627
return static function (ContainerConfigurator $container): void {
2728
// default configuration for services in *this* file
@@ -38,9 +39,17 @@
3839
->tag('console.command');
3940

4041
$services
41-
->set('mongodb.prototype.client', Client::class)
42+
->set('mongodb.prototype.client', TraceableClient::class)
4243
->arg('$uri', abstract_arg('Should be defined by pass'))
4344
->arg('$uriOptions', abstract_arg('Should be defined by pass'))
4445
->arg('$driverOptions', abstract_arg('Should be defined by pass'))
4546
->tag('mongodb.client');
47+
48+
$services
49+
->set('data_collector.mongodb', MongoDBDataCollector::class)
50+
->tag('data_collector', [
51+
'template' => '@MongoDB/Collector/mongodb.html.twig',
52+
'id' => 'mongodb',
53+
'priority' => 250,
54+
]);
4655
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Copyright 2023-present MongoDB, Inc.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* https://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
namespace MongoDB\Bundle\DataCollector;
22+
23+
use MongoDB\Client;
24+
use MongoDB\Driver\Monitoring\CommandFailedEvent;
25+
use MongoDB\Driver\Monitoring\CommandStartedEvent;
26+
use MongoDB\Driver\Monitoring\CommandSubscriber;
27+
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
28+
29+
final class DriverEventSubscriber implements CommandSubscriber
30+
{
31+
/**
32+
* @var list<CommandFailedEvent|CommandStartedEvent|CommandSucceededEvent>
33+
*/
34+
private array $events = [];
35+
36+
public function subscribe(Client $client): void
37+
{
38+
$client->getManager()->addSubscriber($this);
39+
}
40+
41+
/**
42+
* @return list<CommandFailedEvent|CommandStartedEvent|CommandSucceededEvent>
43+
*/
44+
public function getEvents(): array
45+
{
46+
return $this->events;
47+
}
48+
49+
public function commandFailed(CommandFailedEvent $event): void
50+
{
51+
$this->events[] = $event;
52+
}
53+
54+
public function commandStarted(CommandStartedEvent $event): void
55+
{
56+
$this->events[] = $event;
57+
}
58+
59+
public function commandSucceeded(CommandSucceededEvent $event): void
60+
{
61+
$this->events[] = $event;
62+
}
63+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Copyright 2023-present MongoDB, Inc.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* https://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
namespace MongoDB\Bundle\DataCollector;
22+
23+
use MongoDB\BSON\Document;
24+
use MongoDB\Bundle\TraceableClient;
25+
use MongoDB\Client;
26+
use MongoDB\Driver\Monitoring\CommandFailedEvent;
27+
use MongoDB\Driver\Monitoring\CommandStartedEvent;
28+
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
29+
use Symfony\Component\HttpFoundation\Request;
30+
use Symfony\Component\HttpFoundation\Response;
31+
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
32+
33+
final class MongoDBDataCollector extends DataCollector
34+
{
35+
/**
36+
* @var list<Client>
37+
*/
38+
private array $clients = [];
39+
40+
public function addClient(string $name, Client $client): void
41+
{
42+
$this->clients[$name] = $client;
43+
}
44+
45+
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
46+
{
47+
foreach ($this->clients as $name => $client) {
48+
$totalTime = 0;
49+
$requestCount = 0;
50+
$errorCount = 0;
51+
$requests = [];
52+
53+
if ($client instanceof TraceableClient) {
54+
foreach ($client->getEvents() as $event) {
55+
$requestId = $event->getRequestId();
56+
57+
$eventData = [
58+
'class' => $event::class,
59+
'commandName' => $event->getCommandName(),
60+
'server' => $event->getServer()->getInfo(),
61+
'client' => $name,
62+
];
63+
64+
if ($event instanceof CommandStartedEvent) {
65+
$command = (array) $event->getCommand();
66+
unset($command['lsid'], $command['$clusterTime']);
67+
68+
$requests[$requestId] = [
69+
'client' => $name,
70+
'startedAt' => hrtime(true),
71+
'commandName' => $event->getCommandName(),
72+
'command' => $command,
73+
// 'server' => $event->getServer()->getInfo(),
74+
'operationId' => $event->getOperationId(),
75+
'database' => $event->getDatabaseName(),
76+
'serviceId' => $event->getServiceId(),
77+
];
78+
++$requestCount;
79+
} elseif ($event instanceof CommandSucceededEvent) {
80+
$requests[$requestId] += [
81+
// 'reply' => Document::fromPHP($event->getReply()),
82+
'duration' => $event->getDurationMicros(),
83+
'endedAt' => hrtime(true),
84+
'success' => true,
85+
];
86+
$totalTime += $event->getDurationMicros();
87+
} elseif ($event instanceof CommandFailedEvent) {
88+
$requests[$requestId] += [
89+
// 'reply' => Document::fromPHP($event->getReply()),
90+
'duration' => $event->getDurationMicros(),
91+
'error' => $event->getError(),
92+
'success' => false,
93+
];
94+
$totalTime += $event->getDurationMicros();
95+
++$errorCount;
96+
}
97+
}
98+
}
99+
100+
$this->data['clients'][$name] = [
101+
'name' => $name,
102+
'uri' => (string) $client,
103+
'totalTime' => $totalTime,
104+
'requestCount' => $requestCount,
105+
'errorCount' => $errorCount,
106+
'requests' => $requests,
107+
];
108+
}
109+
}
110+
111+
public function getRequestCount(): int
112+
{
113+
return array_sum(array_column($this->data['clients'], 'requestCount'));
114+
}
115+
116+
public function getErrorCount(): int
117+
{
118+
return array_sum(array_column($this->data['clients'], 'errorCount'));
119+
}
120+
121+
public function getTime(): float
122+
{
123+
return array_sum(array_column($this->data['clients'], 'totalTime'));
124+
}
125+
126+
public function getClients(): array
127+
{
128+
return $this->data['clients'];
129+
}
130+
131+
public function getName(): string
132+
{
133+
return 'mongodb';
134+
}
135+
136+
public function reset(): void
137+
{
138+
// TODO: Implement reset() method.
139+
}
140+
}

src/DependencyInjection/MongoDBExtension.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
use MongoDB\Client;
2525
use Symfony\Component\Config\FileLocator;
2626
use Symfony\Component\DependencyInjection\ContainerBuilder;
27+
use Symfony\Component\DependencyInjection\Definition;
2728
use Symfony\Component\DependencyInjection\Extension\Extension;
2829
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
30+
use Symfony\Component\DependencyInjection\Reference;
2931

3032
use function dirname;
3133
use function sprintf;
@@ -62,6 +64,7 @@ public static function createClientServiceId(string $clientId): string
6264
private function createClients(string $defaultClient, array $clients, ContainerBuilder $container): void
6365
{
6466
$clientPrototype = $container->getDefinition('mongodb.prototype.client');
67+
$dataCollector = $container->getDefinition('mongodb.data_collector');
6568

6669
foreach ($clients as $client => $configuration) {
6770
$serviceId = self::createClientServiceId($client);
@@ -73,6 +76,8 @@ private function createClients(string $defaultClient, array $clients, ContainerB
7376

7477
$container->setDefinition($serviceId, $clientDefinition);
7578

79+
$dataCollector->addMethodCall('addClient', [$client, new Reference($serviceId)]);
80+
7681
if (isset($configuration['default_database'])) {
7782
$container->setParameter(sprintf('%s.default_database', $serviceId), $configuration['default_database']);
7883
}

src/TraceableClient.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Copyright 2023-present MongoDB, Inc.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* https://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
namespace MongoDB\Bundle;
22+
23+
use MongoDB\Bundle\DataCollector\DriverEventSubscriber;
24+
use MongoDB\Client;
25+
use MongoDB\Driver\Monitoring\CommandFailedEvent;
26+
use MongoDB\Driver\Monitoring\CommandStartedEvent;
27+
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
28+
29+
final class TraceableClient extends Client
30+
{
31+
private DriverEventSubscriber $subscriber;
32+
33+
public function __construct(?string $uri = null, array $uriOptions = [], array $driverOptions = [])
34+
{
35+
parent::__construct($uri, $uriOptions, $driverOptions);
36+
37+
$this->subscriber = new DriverEventSubscriber();
38+
$this->getManager()->addSubscriber($this->subscriber);
39+
}
40+
41+
/**
42+
* @return list<CommandFailedEvent|CommandStartedEvent|CommandSucceededEvent>
43+
*/
44+
public function getEvents(): array
45+
{
46+
return $this->subscriber->getEvents();
47+
}
48+
}

0 commit comments

Comments
 (0)