Skip to content

Commit e24a8b7

Browse files
committed
feat: Add profiler integration
1 parent 5e5f531 commit e24a8b7

File tree

10 files changed

+152
-41
lines changed

10 files changed

+152
-41
lines changed

config/services.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use Laudis\Neo4j\Contracts\SessionInterface;
88
use Laudis\Neo4j\Contracts\TransactionInterface;
99
use Neo4j\Neo4jBundle\ClientFactory;
10-
use Neo4j\Neo4jBundle\EventHandler;
1110
use Neo4j\Neo4jBundle\EventListener\Neo4jProfileListener;
1211
use Neo4j\Neo4jBundle\SymfonyClient;
1312
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

src/Collector/Neo4jDataCollector.php

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,61 @@
44

55
namespace Neo4j\Neo4jBundle\Collector;
66

7-
use Laudis\Neo4j\Databags\ResultSummary;
87
use Neo4j\Neo4jBundle\EventListener\Neo4jProfileListener;
98
use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector;
109
use Symfony\Component\HttpFoundation\Request;
1110
use Symfony\Component\HttpFoundation\Response;
1211

1312
/**
1413
* @var array{
15-
* successful_statements: array<array-key, array<string, mixed>>,
16-
* failed_statements: list<array{
17-
* statement: mixed,
18-
* exception: mixed,
19-
* alias: string|null
20-
* }>
14+
* successful_statements_count: int,
15+
* failed_statements_count: int,
16+
* statements: array<array-key, array<string, mixed>> | list<array{
17+
* statement: mixed,
18+
* exception: mixed,
19+
* alias: string|null
20+
* }>,
2121
* } $data
2222
*/
2323
final class Neo4jDataCollector extends AbstractDataCollector
2424
{
2525
public function __construct(
26-
private Neo4jProfileListener $subscriber
26+
private readonly Neo4jProfileListener $subscriber
2727
) {
2828
}
2929

3030
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
3131
{
32-
$this->data['successful_statements'] = array_map(
33-
static fn (ResultSummary $summary) => $summary->toArray(),
34-
$this->subscriber->getProfiledSummaries()
32+
$profiledSummaries = $this->subscriber->getProfiledSummaries();
33+
$successfulStatements = array_map(
34+
static function (string $key, mixed $value) {
35+
if ('result' !== $key) {
36+
return [...$value, 'status' => 'success'];
37+
}
38+
39+
return array_map(
40+
static function (string $key, mixed $obj) {
41+
if (is_object($obj) && method_exists($obj, 'toArray')) {
42+
return $obj->toArray();
43+
}
44+
45+
return $obj;
46+
},
47+
$value['result']->toArray()
48+
);
49+
},
50+
array_keys($profiledSummaries),
51+
array_values($profiledSummaries)
3552
);
3653

37-
$this->data['failed_statements'] = array_map(
54+
$failedStatements = array_map(
3855
static fn (array $x) => [
39-
'statement' => $x['statement']->toArray(),
56+
'status' => 'failure',
57+
'time' => $x['time'],
58+
'timestamp' => $x['timestamp'],
59+
'result' => [
60+
'statement' => $x['statement']->toArray(),
61+
],
4062
'exception' => [
4163
'code' => $x['exception']->getErrors()[0]->getCode(),
4264
'message' => $x['exception']->getErrors()[0]->getMessage(),
@@ -48,6 +70,15 @@ public function collect(Request $request, Response $response, ?\Throwable $excep
4870
],
4971
$this->subscriber->getProfiledFailures()
5072
);
73+
74+
$this->data['successful_statements_count'] = count($successfulStatements);
75+
$this->data['failed_statements_count'] = count($failedStatements);
76+
$mergedArray = array_merge($successfulStatements, $failedStatements);
77+
uasort(
78+
$mergedArray,
79+
static fn (array $a, array $b) => $a['start_time'] <=> $b['timestamp']
80+
);
81+
$this->data['statements'] = $mergedArray;
5182
}
5283

5384
public function reset(): void
@@ -61,19 +92,40 @@ public function getName(): string
6192
return 'neo4j';
6293
}
6394

64-
public function getFailedStatements(): array
95+
public function getStatements(): array
6596
{
66-
return $this->data['failed_statements'];
97+
return $this->data['statements'];
6798
}
6899

69100
public function getSuccessfulStatements(): array
70101
{
71-
return $this->data['successful_statements'];
102+
return array_filter(
103+
$this->data['statements'],
104+
static fn (array $x) => 'success' === $x['status']
105+
);
106+
}
107+
108+
public function getFailedStatements(): array
109+
{
110+
return array_filter(
111+
$this->data['statements'],
112+
static fn (array $x) => 'failure' === $x['status']
113+
);
114+
}
115+
116+
public function getFailedStatementsCount(): array
117+
{
118+
return $this->data['failed_statements_count'];
119+
}
120+
121+
public function getSuccessfulStatementsCount(): array
122+
{
123+
return $this->data['successful_statements_count'];
72124
}
73125

74126
public function getQueryCount(): int
75127
{
76-
return count($this->data['successful_statements']) + count($this->data['failed_statements']);
128+
return count($this->data['statements']);
77129
}
78130

79131
public static function getTemplate(): ?string

src/DependencyInjection/Neo4jExtension.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function load(array $configs, ContainerBuilder $container): ContainerBuil
2828
$configuration = new Configuration();
2929
$mergedConfig = $this->processConfiguration($configuration, $configs);
3030

31-
$loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/../../config'));
31+
$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../../config'));
3232
$loader->load('services.php');
3333

3434
$defaultAlias = $mergedConfig['default_driver'] ?? $mergedConfig['drivers'][0]['alias'] ?? 'default';
@@ -61,8 +61,8 @@ public function load(array $configs, ContainerBuilder $container): ContainerBuil
6161
$enabledProfiles = [];
6262
foreach ($mergedConfig['drivers'] as $driver) {
6363
if (true === $driver['profiling'] || (null === $driver['profiling'] && $container->getParameter(
64-
'kernel.debug'
65-
))) {
64+
'kernel.debug'
65+
))) {
6666
$enabledProfiles[] = $driver['alias'];
6767
}
6868
}

src/Event/FailureEvent.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class FailureEvent extends Event
1414

1515
protected bool $shouldThrowException = true;
1616

17-
public function __construct(private ?string $alias, private Statement $statement, private Neo4jException $exception)
17+
public function __construct(private ?string $alias, private Statement $statement, private Neo4jException $exception, private \DateTimeInterface $time)
1818
{
1919
}
2020

@@ -33,6 +33,11 @@ public function shouldThrowException(): bool
3333
return $this->shouldThrowException;
3434
}
3535

36+
public function getTime(): \DateTimeInterface
37+
{
38+
return $this->time;
39+
}
40+
3641
public function getAlias(): ?string
3742
{
3843
return $this->alias;

src/Event/PostRunEvent.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class PostRunEvent extends Event
1313

1414
public function __construct(
1515
private ?string $alias,
16-
private ResultSummary $result
16+
private ResultSummary $result,
17+
private \DateTimeInterface $time
1718
) {
1819
}
1920

@@ -22,6 +23,11 @@ public function getResult(): ResultSummary
2223
return $this->result;
2324
}
2425

26+
public function getTime(): \DateTimeInterface
27+
{
28+
return $this->time;
29+
}
30+
2531
public function getAlias(): ?string
2632
{
2733
return $this->alias;

src/Event/PreRunEvent.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class PreRunEvent extends Event
1111
{
1212
public const EVENT_ID = 'neo4j.pre_run';
1313

14-
public function __construct(private ?string $alias, private Statement $statement)
14+
public function __construct(private ?string $alias, private Statement $statement, private \DateTimeInterface $time)
1515
{
1616
}
1717

@@ -20,6 +20,11 @@ public function getStatement(): Statement
2020
return $this->statement;
2121
}
2222

23+
public function getTime(): \DateTimeInterface
24+
{
25+
return $this->time;
26+
}
27+
2328
public function getAlias(): ?string
2429
{
2530
return $this->alias;

src/EventHandler.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,20 @@ public function handle(callable $runHandler, Statement $statement, ?string $alia
4242
return $runHandler($statement);
4343
}
4444

45-
$this->dispatcher->dispatch(new PreRunEvent($alias, $statement), PreRunEvent::EVENT_ID);
45+
/** @noinspection PhpUnhandledExceptionInspection */
46+
$time = new \DateTimeImmutable('now', new \DateTimeZone(date_default_timezone_get()));
47+
$this->dispatcher->dispatch(new PreRunEvent($alias, $statement, $time), PreRunEvent::EVENT_ID);
4648

4749
try {
4850
$tbr = $runHandler($statement);
49-
$this->dispatcher->dispatch(new PostRunEvent($alias ?? $this->alias, $tbr->getSummary()), PostRunEvent::EVENT_ID);
51+
$this->dispatcher->dispatch(
52+
new PostRunEvent($alias ?? $this->alias, $tbr->getSummary(), $time),
53+
PostRunEvent::EVENT_ID
54+
);
5055
} catch (Neo4jException $e) {
51-
$event = new FailureEvent($alias ?? $this->alias, $statement, $e);
56+
/** @noinspection PhpUnhandledExceptionInspection */
57+
$time = new \DateTimeImmutable('now', new \DateTimeZone(date_default_timezone_get()));
58+
$event = new FailureEvent($alias ?? $this->alias, $statement, $e, $time);
5259
$event = $this->dispatcher->dispatch($event, FailureEvent::EVENT_ID);
5360

5461
if ($event->shouldThrowException()) {

src/EventListener/Neo4jProfileListener.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Neo4j\Neo4jBundle\EventListener;
66

7+
use DateTimeInterface;
78
use Laudis\Neo4j\Databags\ResultSummary;
89
use Laudis\Neo4j\Databags\Statement;
910
use Laudis\Neo4j\Exception\Neo4jException;
@@ -15,7 +16,11 @@
1516
final class Neo4jProfileListener implements EventSubscriberInterface, ResetInterface
1617
{
1718
/**
18-
* @var list<ResultSummary>
19+
* @var list<array{
20+
* result: ResultSummary,
21+
* alias: string|null,
22+
* time: DateTimeInterface
23+
* }>
1924
*/
2025
private array $profiledSummaries = [];
2126

@@ -27,7 +32,7 @@ final class Neo4jProfileListener implements EventSubscriberInterface, ResetInter
2732
/**
2833
* @param list<string> $enabledProfiles
2934
*/
30-
public function __construct(private array $enabledProfiles = [])
35+
public function __construct(private readonly array $enabledProfiles = [])
3136
{
3237
}
3338

@@ -42,17 +47,29 @@ public static function getSubscribedEvents(): array
4247
public function onPostRun(PostRunEvent $event): void
4348
{
4449
if (in_array($event->getAlias(), $this->enabledProfiles)) {
45-
$this->profiledSummaries[] = $event->getResult();
50+
$time = $event->getTime();
51+
$result = $event->getResult();
52+
$end_time = $time->getTimestamp() + $result->getResultAvailableAfter() + $result->getResultConsumedAfter();
53+
$this->profiledSummaries[] = [
54+
'result' => $event->getResult(),
55+
'alias' => $event->getAlias(),
56+
'time' => $time->format('Y-m-d H:i:s'),
57+
'start_time' => $time->getTimestamp(),
58+
'end_time' => $end_time,
59+
];
4660
}
4761
}
4862

4963
public function onFailure(FailureEvent $event): void
5064
{
5165
if (in_array($event->getAlias(), $this->enabledProfiles)) {
66+
$time = $event->getTime();
5267
$this->profiledFailures[] = [
5368
'exception' => $event->getException(),
5469
'statement' => $event->getStatement(),
5570
'alias' => $event->getAlias(),
71+
'time' => $time->format('Y-m-d H:i:s'),
72+
'timestamp' => $time->getTimestamp(),
5673
];
5774
}
5875
}

src/Resources/views/web_profiler.html.twig

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,51 +60,71 @@
6060
<th class="nowrap">#</th>
6161
<th class="nowrap">Status</th>
6262
<th style="width: 100%;">Query</th>
63+
<th class="nowrap">Query Time</th>
64+
<th class="nowrap">Query Type</th>
65+
<th class="nowrap">Database</th>
66+
<th class="nowrap">Executed At</th>
67+
<th class="nowrap">Exception Title</th>
6368
</tr>
6469
</thead>
6570
<tbody>
66-
{% for idx, statement in collector.successfulStatements %}
71+
{% for idx, statement in collector.statements %}
6772
<tr>
73+
{% set status = statement.status|default('unknown') %}
6874
{% set start_time = statement.start_time|default(null) %}
6975
{% set end_time = statement.end_time|default(null) %}
7076

7177
<td class="nowrap">{{ idx + 1 }}</td>
72-
<td class="nowrap">{% if start_time is not null and end_time is not null %}{{ '%0.2f'|format(end_time - start_time) }}ms{% endif %}</td>
78+
<td class="nowrap">{{ statement.status }}</td>
7379
<td>
7480
<div>
75-
{{ statement.query|default('') }}
81+
{{ statement.result.statement.text|default('') }}
7682
</div>
7783
<div class="text-small font-normal">
7884
<a href="#" class="sf-toggle link-inverse" data-toggle-selector="#neo4j-details-{{ idx }}" data-toggle-alt-content="Hide details" data-toggle-original-content="View details">View details</a>
7985
</div>
8086

8187
<div id="neo4j-details-{{ idx }}">
8288
<div>
83-
<strong class="font-normal text-small">Parameters</strong>: {{ statement.parameters|default([])|yaml_encode }}
89+
<strong class="font-normal text-small">Parameters</strong>: {{ statement.result.parameters|default([])|yaml_encode }}
8490
</div>
8591
<div>
86-
<strong class="font-normal text-small">Tag</strong>: {{ statement.tag|default('N/A')|yaml_encode }}
92+
<strong class="font-normal text-small">Tag</strong>: {{ statement.result.tag|default('N/A')|yaml_encode }}
8793
</div>
88-
{# {% if statement.success %}#}
94+
{# {% if statement.result.success %}#}
8995
<div>
90-
<strong class="font-normal text-small">Number of results</strong>: {{ statement.nb_results|default('N/A') }}
96+
<strong class="font-normal text-small">Number of results</strong>: {{ statement.result.nb_results|default('N/A') }}
9197
</div>
9298
<div>
93-
<strong class="font-normal text-small">Scheme</strong>: {{ statement.scheme|default('N/A') }}
99+
<strong class="font-normal text-small">Scheme</strong>: {{ statement.result.scheme|default('N/A') }}
94100
</div>
95101
<div>
96102
<strong class="font-normal text-small">Statistics</strong>: {{ statement|yaml_encode }}
97103
</div>
98104
{# {% else %}#}
99105
{# <div>#}
100-
{# <strong class="font-normal text-small">Type</strong>: {{ statement.exceptionCode }}#}
106+
{# <strong class="font-normal text-small">Type</strong>: {{ statement.result.exceptionCode }}#}
101107
{# </div>#}
102108
{# <div>#}
103-
{# <strong class="font-normal text-small">message</strong>: {{ statement.exceptionMessage }}#}
109+
{# <strong class="font-normal text-small">message</strong>: {{ statement.result.exceptionMessage }}#}
104110
{# </div>.suc#}
105111
{# {% endif %}#}
106112
</div>
107113
</td>
114+
<td class="nowrap">{% if status is same as('success') %}{% if start_time is not null and end_time is not null %}{{ '%0.2f'|format(end_time - start_time) }}ms{% endif %}{% else %}N/A{% endif %}</td>
115+
{% if status is same as('success') %}
116+
<td class="nowrap">{{ statement.result.queryType }}</td>
117+
<td class="nowrap">{{ statement.result.databaseInfo.name }}</td>
118+
{% else %}
119+
<td class="nowrap">N/A</td>
120+
<td class="nowrap">N/A</td>
121+
{% endif %}
122+
<td class="nowrap">{{ statement.time }}</td>
123+
{% if status is same as('success') %}
124+
<td class="nowrap">N/A</td>
125+
{% else %}
126+
<td class="nowrap">{{ statement.exception.title }}</td>
127+
{% endif %}
108128
</tr>
109129
{% endfor %}
110130
</tbody>

0 commit comments

Comments
 (0)