Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,42 @@

use Modules\Profiler\Application\EventHandlerInterface;

// TODO: fix diff calculation
final class CalculateDiffsBetweenEdges implements EventHandlerInterface
{
public function handle(array $event): array
{
$data = \array_reverse($event['profile'] ?? []);
$parents = [];

foreach ($data as $name => $values) {
[$parent, $func] = $this->splitName($name);

if ($parent) {
$parentValues = $parents[$parent] ?? ['cpu' => 0, 'wt' => 0, 'mu' => 0, 'pmu' => 0];
$event['profile'][$name] = \array_merge([
'd_cpu' => $parentValues['cpu'] - $values['cpu'],
'd_wt' => $parentValues['wt'] - $values['wt'],
'd_mu' => $parentValues['mu'] - $values['mu'],
'd_pmu' => $parentValues['pmu'] - $values['pmu'],
], $values);
}
$profile = $event['profile'] ?? [];

$parents[$func] = $values;
}
// Aggregate children's inclusive metrics per parent function
$childrenSum = [];
foreach ($profile as $name => $values) {
[$parent] = EdgeNameSplitter::split($name);

return $event;
}
if ($parent !== null) {
if (!isset($childrenSum[$parent])) {
$childrenSum[$parent] = ['cpu' => 0, 'wt' => 0, 'mu' => 0, 'pmu' => 0, 'ct' => 0];
}

/**
* @return array{0: string|null, 1: string}
*/
private function splitName(string $name): array
{
$a = \explode('==>', $name);
if (isset($a[1])) {
return $a;
foreach (['cpu', 'wt', 'mu', 'pmu', 'ct'] as $metric) {
$childrenSum[$parent][$metric] += $values[$metric] ?? 0;
}
}
}

// Calculate diff: parent's inclusive minus sum of all its children (exclusive time of parent)
foreach ($profile as $name => $values) {
[, $func] = EdgeNameSplitter::split($name);
$children = $childrenSum[$func] ?? ['cpu' => 0, 'wt' => 0, 'mu' => 0, 'pmu' => 0, 'ct' => 0];

$event['profile'][$name] = \array_merge($values, [
'd_cpu' => \max(0, ($values['cpu'] ?? 0) - $children['cpu']),
'd_wt' => \max(0, ($values['wt'] ?? 0) - $children['wt']),
'd_mu' => \max(0, ($values['mu'] ?? 0) - $children['mu']),
'd_pmu' => \max(0, ($values['pmu'] ?? 0) - $children['pmu']),
'd_ct' => \max(0, ($values['ct'] ?? 0) - $children['ct']),
]);
}

return [null, $a[0]];
return $event;
}
}
25 changes: 25 additions & 0 deletions app/modules/Profiler/Application/Handlers/EdgeNameSplitter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Modules\Profiler\Application\Handlers;

final class EdgeNameSplitter
{
/**
* Split XHProf edge name "parent==>child" into [parent, child].
* For root entries like "main()", returns [null, "main()"].
*
* @return array{0: string|null, 1: string}
*/
public static function split(string $name): array
{
$parts = \explode('==>', $name);

if (isset($parts[1])) {
return [$parts[0], $parts[1]];
}

return [null, $parts[0]];
}
}
14 changes: 1 addition & 13 deletions app/modules/Profiler/Application/Handlers/PrepareEdges.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function handle(array $event): array

$id = 1;
foreach ($profile as $name => $values) {
[$caller, $callee] = $this->splitName($name);
[$caller, $callee] = EdgeNameSplitter::split($name);

foreach (['cpu', 'mu', 'pmu', 'wt', 'ct'] as $key) {
$peakValue = $event['peaks'][$key] ?? 0;
Expand Down Expand Up @@ -85,16 +85,4 @@ public function handle(array $event): array
return $event;
}

/**
* @return array{0: string|null, 1: string}
*/
private function splitName(string $name): array
{
$a = \explode('==>', $name);
if (isset($a[1])) {
return $a;
}

return [null, $a[0]];
}
}
8 changes: 8 additions & 0 deletions app/modules/Profiler/Application/ProfilerBootloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Modules\Profiler\Domain\ProfileFactoryInterface;
use Modules\Profiler\Integration\CycleOrm\EdgeFactory;
use Modules\Profiler\Integration\CycleOrm\ProfileFactory;
use Modules\Profiler\Interfaces\Events\DeleteEventListener;
use Modules\Profiler\Interfaces\Queries\FindCallGraphByUuidHandler;
use Modules\Profiler\Interfaces\Queries\FindFlameChartByUuidHandler;
use Psr\Container\ContainerInterface;
Expand Down Expand Up @@ -56,6 +57,13 @@ public function defineSingletons(): array
): FindFlameChartByUuidHandler => $factory->make(FindFlameChartByUuidHandler::class, [
'bucket' => $storage->bucket('profiles'),
]),

DeleteEventListener::class => static fn(
FactoryInterface $factory,
StorageInterface $storage,
): DeleteEventListener => $factory->make(DeleteEventListener::class, [
'bucket' => $storage->bucket('profiles'),
]),
];
}

Expand Down
11 changes: 11 additions & 0 deletions app/modules/Profiler/Interfaces/Events/DeleteEventListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\ORMInterface;
use Modules\Events\Domain\Events\EventWasDeleted;
use Modules\Profiler\Application\CallGraph\Metric;
use Modules\Profiler\Domain\Profile;
use Spiral\Events\Attribute\Listener;
use Spiral\Storage\BucketInterface;

final readonly class DeleteEventListener
{
public function __construct(
private ORMInterface $orm,
private EntityManagerInterface $em,
private BucketInterface $bucket,
) {}

#[Listener]
Expand All @@ -25,6 +28,14 @@ public function __invoke(EventWasDeleted $event): void
return;
}

// Clean up cached flame chart files for all metrics
foreach (Metric::cases() as $metric) {
$file = $event->uuid . '.' . $metric->value . '.flamechart.json';
if ($this->bucket->exists($file)) {
$this->bucket->delete($file);
}
}

$this->em->delete($profile)->run();
}
}
6 changes: 3 additions & 3 deletions app/modules/Profiler/Interfaces/Jobs/StoreProfileHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,12 @@ public function invoke(array $payload): void

$parents[$id] = $edge->getUuid();

if (self::BATCH_SIZE === $batchSize) {
$batchSize++;

if ($batchSize >= self::BATCH_SIZE) {
$this->em->run();
$batchSize = 0;
}

$batchSize++;
}

$profile = $this->orm->getRepository(Profile::class)->findByPK($profileUuid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use Modules\Profiler\Domain\Profile;
use Spiral\Cqrs\Attribute\QueryHandler;

// TODO: refactor this, use repository
final class FindTopFunctionsByUuidHandler
{
public function __construct(
Expand All @@ -22,44 +21,48 @@ public function __invoke(FindTopFunctionsByUuid $query): array
{
$profile = $this->orm->getRepository(Profile::class)->findByPK($query->profileUuid);

$overallTotals = [];

$functions = [];

/** @var Edge[] $edges */
$edges = $profile->edges;

$metrics = ['cpu', 'ct', 'wt', 'mu', 'pmu'];

foreach ($metrics as $metric) {
$overallTotals[$metric] = 0;
}

// Aggregate inclusive metrics per function (same callee may appear from different callers)
foreach ($edges as $edge) {
if (!isset($functions[$edge->getCallee()])) {
$functions[$edge->getCallee()] = [
'function' => $edge->getCallee(),
];
$callee = $edge->getCallee();

if (!isset($functions[$callee])) {
$functions[$callee] = ['function' => $callee];
foreach ($metrics as $metric) {
$functions[$edge->getCallee()][$metric] = $edge->getCost()->{$metric};
$functions[$callee][$metric] = 0;
}
continue;
}

foreach ($metrics as $metric) {
$overallTotals[$metric] = $functions['main()'][$metric];
$functions[$callee][$metric] += $edge->getCost()->{$metric};
}
}

foreach ($functions as $function => $m) {
// Overall totals from main() entry (inclusive metrics for the entire request)
$overallTotals = [];
foreach ($metrics as $metric) {
$overallTotals[$metric] = $functions['main()'][$metric] ?? 0;
}
// ct total is sum of all calls, not just main()
$overallTotals['ct'] = 0;
foreach ($functions as $m) {
$overallTotals['ct'] += $m['ct'];
}

// Initialize exclusive metrics as copy of inclusive
foreach (array_keys($functions) as $function) {
foreach ($metrics as $metric) {
$functions[$function]['excl_' . $metric] = $functions[$function][$metric];
}

$overallTotals['ct'] += $m['ct'];
}

// Subtract children's inclusive metrics from parent's exclusive metrics
foreach ($edges as $edge) {
if (!$edge->getCaller()) {
continue;
Expand All @@ -69,7 +72,7 @@ public function __invoke(FindTopFunctionsByUuid $query): array
$field = 'excl_' . $metric;

if (!isset($functions[$edge->getCaller()][$field])) {
$functions[$edge->getCaller()][$field] = 0;
continue;
}

$functions[$edge->getCaller()][$field] -= $edge->getCost()->{$metric};
Expand Down
Loading