Skip to content

Commit fdeecd4

Browse files
mamazustofdmaicherostrolucky
authored
Add loaded entities profiler section (#1872)
--------- Co-authored-by: Christophe Coevoet <[email protected]> Co-authored-by: David Maicher <[email protected]> Co-authored-by: Gabriel Ostrolucký <[email protected]>
1 parent d9ea81d commit fdeecd4

File tree

4 files changed

+97
-15
lines changed

4 files changed

+97
-15
lines changed

src/DataCollector/DoctrineDataCollector.php

+41-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
use function array_map;
1919
use function array_sum;
20+
use function arsort;
2021
use function assert;
2122
use function count;
2223
use function usort;
@@ -42,13 +43,16 @@
4243
* errors: array<string, array<class-string, list<string>>>,
4344
* managers: list<string>,
4445
* queries: array<string, list<QueryType>>,
46+
* entityCounts: array<string, array<class-string, int>>
4547
* }
4648
* @psalm-property DataType $data
4749
*/
4850
class DoctrineDataCollector extends BaseCollector
4951
{
5052
private int|null $invalidEntityCount = null;
5153

54+
private int|null $managedEntityCount = null;
55+
5256
/**
5357
* @var mixed[][]|null
5458
* @phpstan-var ?array<string, list<QueryType&array{count: int, index: int, executionPercent?: float}>>
@@ -68,9 +72,10 @@ public function collect(Request $request, Response $response, Throwable|null $ex
6872
{
6973
parent::collect($request, $response, $exception);
7074

71-
$errors = [];
72-
$entities = [];
73-
$caches = [
75+
$errors = [];
76+
$entities = [];
77+
$entityCounts = [];
78+
$caches = [
7479
'enabled' => false,
7580
'log_enabled' => false,
7681
'counts' => [
@@ -114,6 +119,14 @@ public function collect(Request $request, Response $response, Throwable|null $ex
114119
}
115120
}
116121

122+
$entityCounts[$name] = [];
123+
foreach ($em->getUnitOfWork()->getIdentityMap() as $className => $entityList) {
124+
$entityCounts[$name][$className] = count($entityList);
125+
}
126+
127+
// Sort entities by count (in descending order)
128+
arsort($entityCounts[$name]);
129+
117130
$emConfig = $em->getConfiguration();
118131
$slcEnabled = $emConfig->isSecondLevelCacheEnabled();
119132

@@ -165,10 +178,11 @@ public function collect(Request $request, Response $response, Throwable|null $ex
165178
}
166179
}
167180

168-
$this->data['entities'] = $entities;
169-
$this->data['errors'] = $errors;
170-
$this->data['caches'] = $caches;
171-
$this->groupedQueries = null;
181+
$this->data['entities'] = $entities;
182+
$this->data['errors'] = $errors;
183+
$this->data['caches'] = $caches;
184+
$this->data['entityCounts'] = $entityCounts;
185+
$this->groupedQueries = null;
172186
}
173187

174188
/** @return array<string, array<class-string, array{class: class-string, file: false|string, line: false|int}>> */
@@ -228,6 +242,26 @@ public function getInvalidEntityCount()
228242
return $this->invalidEntityCount ??= array_sum(array_map('count', $this->data['errors']));
229243
}
230244

245+
public function getManagedEntityCount(): int
246+
{
247+
if ($this->managedEntityCount === null) {
248+
$total = 0;
249+
foreach ($this->data['entityCounts'] as $entities) {
250+
$total += array_sum($entities);
251+
}
252+
253+
$this->managedEntityCount = $total;
254+
}
255+
256+
return $this->managedEntityCount;
257+
}
258+
259+
/** @return array<string, array<class-string, int>> */
260+
public function getManagedEntityCountByClass(): array
261+
{
262+
return $this->data['entityCounts'];
263+
}
264+
231265
/**
232266
* @return string[][]
233267
* @phpstan-return array<string, list<QueryType&array{count: int, index: int, executionPercent?: float}>>

templates/Collector/db.html.twig

+26-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
<b>Invalid entities</b>
4545
<span class="sf-toolbar-status {{ collector.invalidEntityCount > 0 ? 'sf-toolbar-status-red' : '' }}">{{ collector.invalidEntityCount }}</span>
4646
</div>
47+
<div class="sf-toolbar-info-piece">
48+
<b>Managed entities</b>
49+
<span class="sf-toolbar-status">{{ collector.managedEntityCount }}</span>
50+
</div>
4751
{% if collector.cacheEnabled %}
4852
<div class="sf-toolbar-info-piece">
4953
<b>Cache hits</b>
@@ -137,6 +141,11 @@
137141
<span class="value">{{ collector.invalidEntityCount }}</span>
138142
<span class="label">Invalid entities</span>
139143
</div>
144+
145+
<div class="metric">
146+
<span class="value">{{ collector.managedEntityCount }}</span>
147+
<span class="label">Managed entities</span>
148+
</div>
140149
</div>
141150

142151
{% if collector.cacheEnabled %}
@@ -384,6 +393,22 @@
384393
</div>
385394
</div>
386395

396+
<div class="tab">
397+
<h3 class="tab-title">Managed Entities</h3>
398+
<div class="tab-content">
399+
{% if not collector.managedEntityCountByClass %}
400+
<div class="empty">
401+
<p>No managed entities.</p>
402+
</div>
403+
{% else %}
404+
{% for manager, entityCounts in collector.managedEntityCountByClass %}
405+
<h4>{{ manager }} <small>entity manager</small></h4>
406+
{{ helper.render_simple_table('Class', 'Amount of managed objects', entityCounts) }}
407+
{% endfor %}
408+
{% endif %}
409+
</div>
410+
</div>
411+
387412
<div class="tab {{ not collector.entities ? 'disabled' }}">
388413
<h3 class="tab-title">Entities Mapping</h3>
389414
<div class="tab-content">
@@ -395,7 +420,7 @@
395420
{% else %}
396421
{% for manager, classes in collector.entities %}
397422
{% if collector.managers|length > 1 %}
398-
<h3>{{ manager }} <small>entity manager</small></h3>
423+
<h4>{{ manager }} <small>entity manager</small></h4>
399424
{% endif %}
400425

401426
{% if classes is empty %}

tests/DataCollector/DoctrineDataCollectorTest.php

+27-7
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
use Doctrine\ORM\EntityManagerInterface;
99
use Doctrine\ORM\Mapping\ClassMetadata;
1010
use Doctrine\ORM\Mapping\ClassMetadataFactory;
11+
use Doctrine\ORM\UnitOfWork;
1112
use Doctrine\Persistence\ManagerRegistry;
1213
use PHPUnit\Framework\TestCase;
1314
use ReflectionClass;
15+
use stdClass;
1416
use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder;
1517
use Symfony\Component\HttpFoundation\Request;
1618
use Symfony\Component\HttpFoundation\Response;
@@ -28,17 +30,27 @@ public function testCollectEntities(): void
2830
self::markTestSkipped('This test requires ORM');
2931
}
3032

31-
$manager = $this->createMock(EntityManagerInterface::class);
32-
$config = $this->createMock(Configuration::class);
33-
$factory = $this->createMock(ClassMetadataFactory::class);
34-
$collector = $this->createCollector(['default' => $manager], true, $this->createMock(DebugDataHolder::class));
33+
$manager = $this->createMock(EntityManagerInterface::class);
34+
$config = $this->createMock(Configuration::class);
35+
$factory = $this->createMock(ClassMetadataFactory::class);
36+
$collector = $this->createCollector(['default' => $manager], true, $this->createMock(DebugDataHolder::class));
37+
$unitOfWork = $this->createMock(UnitOfWork::class);
3538

3639
$manager->expects($this->any())
3740
->method('getMetadataFactory')
3841
->will($this->returnValue($factory));
3942
$manager->expects($this->any())
4043
->method('getConfiguration')
4144
->will($this->returnValue($config));
45+
$manager->expects($this->any())
46+
->method('getUnitOfWork')
47+
->will($this->returnValue($unitOfWork));
48+
$unitOfWork->expects($this->any())
49+
->method('getIdentityMap')
50+
->will($this->returnValue([
51+
self::FIRST_ENTITY => [new stdClass()],
52+
self::SECOND_ENTITY => [new stdClass(), new stdClass()],
53+
]));
4254

4355
$config->expects($this->once())
4456
->method('isSecondLevelCacheEnabled')
@@ -58,6 +70,7 @@ public function testCollectEntities(): void
5870
$entities = $collector->getEntities();
5971
$this->assertArrayHasKey('default', $entities);
6072
$this->assertCount(2, $entities['default']);
73+
$this->assertSame(3, $collector->getManagedEntityCount());
6174
}
6275

6376
public function testDoesNotCollectEntities(): void
@@ -66,14 +79,21 @@ public function testDoesNotCollectEntities(): void
6679
self::markTestSkipped('This test requires ORM');
6780
}
6881

69-
$manager = $this->createMock(EntityManager::class);
70-
$config = $this->createMock(Configuration::class);
71-
$collector = $this->createCollector(['default' => $manager], false, $this->createMock(DebugDataHolder::class));
82+
$manager = $this->createMock(EntityManager::class);
83+
$config = $this->createMock(Configuration::class);
84+
$collector = $this->createCollector(['default' => $manager], false, $this->createMock(DebugDataHolder::class));
85+
$unitOfWork = $this->createMock(UnitOfWork::class);
7286

7387
$manager->expects($this->never())
7488
->method('getMetadataFactory');
7589
$manager->method('getConfiguration')
7690
->will($this->returnValue($config));
91+
$manager->expects($this->any())
92+
->method('getUnitOfWork')
93+
->will($this->returnValue($unitOfWork));
94+
$unitOfWork->expects($this->any())
95+
->method('getIdentityMap')
96+
->will($this->returnValue([]));
7797

7898
$collector->collect(new Request(), new Response());
7999

tests/ProfilerTest.php

+3
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,8 @@ public function testRender(): void
120120
'.*',
121121
preg_quote('SELECT * FROM foo WHERE bar IN ( ? , ? )'),
122122
) . '/', $output));
123+
124+
$this->assertStringContainsString('Managed entities', $output);
125+
$this->assertStringContainsString('No managed entities.', $output);
123126
}
124127
}

0 commit comments

Comments
 (0)