From f0fe84ffbab1cb7b96df7709a047510cb7aa287b Mon Sep 17 00:00:00 2001 From: butschster Date: Sun, 14 Jul 2024 11:27:42 +0400 Subject: [PATCH] Add export digraph for profile --- .env.sample | 5 +- .../Application/Handlers/StoreProfile.php | 1 - .../Application/ProfilerBootloader.php | 20 ++ .../Application/Query/FindDigraphByUuid.php | 17 ++ app/modules/Profiler/Domain/Edge.php | 2 +- app/modules/Profiler/Domain/Profile.php | 3 +- .../Domain/ProfileEdgeRepositoryInterface.php | 20 ++ .../Domain/ProfileRepositoryInterface.php | 21 ++ .../CycleOrm/ProfileEdgeRepository.php | 20 ++ .../CycleOrm/ProfileRepository.php | 28 ++ .../Http/Controllers/ShowDigraphAction.php | 34 +++ .../Interfaces/Jobs/StoreProfileHandler.php | 15 +- .../Queries/FindCallGraphByUuidHandler.php | 8 +- .../Queries/FindDigraphByUuidHandler.php | 257 ++++++++++++++++++ .../Queries/FindFlameChartByUuidHandler.php | 8 +- .../Queries/FindTopFunctionsByUuidHandler.php | 10 +- .../Http/Profiler/ProfilerActionTest.php | 30 +- 17 files changed, 465 insertions(+), 34 deletions(-) create mode 100644 app/modules/Profiler/Application/Query/FindDigraphByUuid.php create mode 100644 app/modules/Profiler/Domain/ProfileEdgeRepositoryInterface.php create mode 100644 app/modules/Profiler/Domain/ProfileRepositoryInterface.php create mode 100644 app/modules/Profiler/Integration/CycleOrm/ProfileEdgeRepository.php create mode 100644 app/modules/Profiler/Integration/CycleOrm/ProfileRepository.php create mode 100644 app/modules/Profiler/Interfaces/Http/Controllers/ShowDigraphAction.php create mode 100644 app/modules/Profiler/Interfaces/Queries/FindDigraphByUuidHandler.php diff --git a/.env.sample b/.env.sample index aa52baa7..e016d98c 100644 --- a/.env.sample +++ b/.env.sample @@ -11,7 +11,10 @@ ENCRYPTER_KEY={encrypt-key} SAFE_MIGRATIONS=true # Queue -QUEUE_CONNECTION=sync +QUEUE_CONNECTION=roadrunner + +# Broadcast +BROADCAST_CONNECTION=centrifugo # Monolog MONOLOG_DEFAULT_CHANNEL=default diff --git a/app/modules/Profiler/Application/Handlers/StoreProfile.php b/app/modules/Profiler/Application/Handlers/StoreProfile.php index d9372551..eee19e6d 100644 --- a/app/modules/Profiler/Application/Handlers/StoreProfile.php +++ b/app/modules/Profiler/Application/Handlers/StoreProfile.php @@ -11,7 +11,6 @@ use Modules\Profiler\Interfaces\Jobs\StoreProfileHandler; use Spiral\Queue\QueueInterface; -// TODO: refactor this, use repository final readonly class StoreProfile implements EventHandlerInterface { public function __construct( diff --git a/app/modules/Profiler/Application/ProfilerBootloader.php b/app/modules/Profiler/Application/ProfilerBootloader.php index 1687599f..583a1476 100644 --- a/app/modules/Profiler/Application/ProfilerBootloader.php +++ b/app/modules/Profiler/Application/ProfilerBootloader.php @@ -5,15 +5,23 @@ namespace Modules\Profiler\Application; use App\Application\Event\EventTypeRegistryInterface; +use Cycle\ORM\ORMInterface; +use Cycle\ORM\Select; use Modules\Profiler\Application\Handlers\CalculateDiffsBetweenEdges; use Modules\Profiler\Application\Handlers\CleanupEvent; use Modules\Profiler\Application\Handlers\PrepareEdges; use Modules\Profiler\Application\Handlers\PreparePeaks; use Modules\Profiler\Application\Handlers\StoreProfile; +use Modules\Profiler\Domain\Edge; use Modules\Profiler\Domain\EdgeFactoryInterface; +use Modules\Profiler\Domain\Profile; +use Modules\Profiler\Domain\ProfileEdgeRepositoryInterface; use Modules\Profiler\Domain\ProfileFactoryInterface; +use Modules\Profiler\Domain\ProfileRepositoryInterface; use Modules\Profiler\Integration\CycleOrm\EdgeFactory; +use Modules\Profiler\Integration\CycleOrm\ProfileEdgeRepository; use Modules\Profiler\Integration\CycleOrm\ProfileFactory; +use Modules\Profiler\Integration\CycleOrm\ProfileRepository; use Modules\Profiler\Interfaces\Queries\FindCallGraphByUuidHandler; use Modules\Profiler\Interfaces\Queries\FindFlameChartByUuidHandler; use Psr\Container\ContainerInterface; @@ -55,6 +63,18 @@ public function defineSingletons(): array ): FindFlameChartByUuidHandler => $factory->make(FindFlameChartByUuidHandler::class, [ 'bucket' => $storage->bucket('profiles'), ]), + + ProfileRepositoryInterface::class => static fn( + ORMInterface $orm, + ): ProfileRepositoryInterface => new ProfileRepository( + select: new Select(orm: $orm, role: Profile::class), + ), + + ProfileEdgeRepositoryInterface::class => static fn( + ORMInterface $orm, + ): ProfileEdgeRepositoryInterface => new ProfileEdgeRepository( + select: new Select(orm: $orm, role: Edge::class), + ), ]; } diff --git a/app/modules/Profiler/Application/Query/FindDigraphByUuid.php b/app/modules/Profiler/Application/Query/FindDigraphByUuid.php new file mode 100644 index 00000000..988d0bb5 --- /dev/null +++ b/app/modules/Profiler/Application/Query/FindDigraphByUuid.php @@ -0,0 +1,17 @@ + 'ASC'], fkOnDelete: 'CASCADE', + load: 'eager', )] public ArrayCollection $edges; diff --git a/app/modules/Profiler/Domain/ProfileEdgeRepositoryInterface.php b/app/modules/Profiler/Domain/ProfileEdgeRepositoryInterface.php new file mode 100644 index 00000000..a701240d --- /dev/null +++ b/app/modules/Profiler/Domain/ProfileEdgeRepositoryInterface.php @@ -0,0 +1,20 @@ + + */ +interface ProfileEdgeRepositoryInterface extends RepositoryInterface +{ + /** + * @return iterable + */ + public function getByProfileUuid(Uuid $profileUuid): iterable; +} diff --git a/app/modules/Profiler/Domain/ProfileRepositoryInterface.php b/app/modules/Profiler/Domain/ProfileRepositoryInterface.php new file mode 100644 index 00000000..290e4c5a --- /dev/null +++ b/app/modules/Profiler/Domain/ProfileRepositoryInterface.php @@ -0,0 +1,21 @@ + + */ +interface ProfileRepositoryInterface extends RepositoryInterface +{ + /** + * @throws EntityNotFoundException + */ + public function getByUuid(Uuid $uuid): Profile; +} diff --git a/app/modules/Profiler/Integration/CycleOrm/ProfileEdgeRepository.php b/app/modules/Profiler/Integration/CycleOrm/ProfileEdgeRepository.php new file mode 100644 index 00000000..b35071d4 --- /dev/null +++ b/app/modules/Profiler/Integration/CycleOrm/ProfileEdgeRepository.php @@ -0,0 +1,20 @@ +select() + ->where('profile_uuid', $profileUuid) + ->orderBy('order', 'ASC') + ->fetchAll(); + } +} diff --git a/app/modules/Profiler/Integration/CycleOrm/ProfileRepository.php b/app/modules/Profiler/Integration/CycleOrm/ProfileRepository.php new file mode 100644 index 00000000..c8164aca --- /dev/null +++ b/app/modules/Profiler/Integration/CycleOrm/ProfileRepository.php @@ -0,0 +1,28 @@ + + */ +final class ProfileRepository extends Repository implements ProfileRepositoryInterface +{ + public function getByUuid(Uuid $uuid): Profile + { + $profile = $this->findByPK($uuid); + + if ($profile === null) { + throw new EntityNotFoundException('Profile not found'); + } + + return $profile; + } +} diff --git a/app/modules/Profiler/Interfaces/Http/Controllers/ShowDigraphAction.php b/app/modules/Profiler/Interfaces/Http/Controllers/ShowDigraphAction.php new file mode 100644 index 00000000..172ad42d --- /dev/null +++ b/app/modules/Profiler/Interfaces/Http/Controllers/ShowDigraphAction.php @@ -0,0 +1,34 @@ +/digraph', name: 'profiler.show.digraph', methods: ['GET'], group: 'api')] + public function __invoke( + QueryBusInterface $bus, + Uuid $uuid, + ): string { + try { + /** @var string $digraph */ + $digraph = $bus->ask( + new FindDigraphByUuid( + profileUuid: $uuid, + ), + ); + } catch (EntityNotFoundException $e) { + throw new NotFoundException($e->getMessage()); + } + + return $digraph; + } +} diff --git a/app/modules/Profiler/Interfaces/Jobs/StoreProfileHandler.php b/app/modules/Profiler/Interfaces/Jobs/StoreProfileHandler.php index e2a2ac0c..37c2168a 100644 --- a/app/modules/Profiler/Interfaces/Jobs/StoreProfileHandler.php +++ b/app/modules/Profiler/Interfaces/Jobs/StoreProfileHandler.php @@ -6,16 +6,14 @@ use App\Application\Domain\ValueObjects\Uuid; use Cycle\ORM\EntityManagerInterface; -use Cycle\ORM\ORMInterface; use Modules\Profiler\Application\Query\FindTopFunctionsByUuid; use Modules\Profiler\Domain\EdgeFactoryInterface; use Modules\Profiler\Domain\Edge; -use Modules\Profiler\Domain\Profile; +use Modules\Profiler\Domain\ProfileRepositoryInterface; use Spiral\Core\InvokerInterface; use Spiral\Cqrs\QueryBusInterface; use Spiral\Queue\JobHandler; -// TODO: refactor this, use repository final class StoreProfileHandler extends JobHandler { private const BATCH_SIZE = 100; @@ -24,7 +22,7 @@ public function __construct( private readonly EdgeFactoryInterface $edgeFactory, private readonly EntityManagerInterface $em, private readonly QueryBusInterface $bus, - private readonly ORMInterface $orm, + private readonly ProfileRepositoryInterface $profiles, InvokerInterface $invoker, ) { parent::__construct($invoker); @@ -39,6 +37,11 @@ public function invoke(array $payload): void $batchSize = 0; $i = 0; foreach ($event['edges'] as $id => $edge) { + $parentUuid = null; + if ($edge['parent'] !== null && isset($parents[$edge['parent']])) { + $parentUuid = $parents[$edge['parent']]; + } + $this->em->persist( $edge = $this->edgeFactory->create( profileUuid: $profileUuid, @@ -66,7 +69,7 @@ public function invoke(array $payload): void ), callee: $edge['callee'], caller: $edge['caller'], - parentUuid: $edge['parent'] ? $parents[$edge['parent']] ?? null : null, + parentUuid: $parentUuid, ), ); @@ -80,7 +83,7 @@ public function invoke(array $payload): void $batchSize++; } - $profile = $this->orm->getRepository(Profile::class)->findByPK($profileUuid); + $profile = $this->profiles->getByUuid($profileUuid); $functions = $this->bus->ask(new FindTopFunctionsByUuid(profileUuid: $profileUuid)); foreach ($functions['overall_totals'] as $metric => $value) { diff --git a/app/modules/Profiler/Interfaces/Queries/FindCallGraphByUuidHandler.php b/app/modules/Profiler/Interfaces/Queries/FindCallGraphByUuidHandler.php index 47371bbf..bdb0f08b 100644 --- a/app/modules/Profiler/Interfaces/Queries/FindCallGraphByUuidHandler.php +++ b/app/modules/Profiler/Interfaces/Queries/FindCallGraphByUuidHandler.php @@ -4,24 +4,22 @@ namespace Modules\Profiler\Interfaces\Queries; -use Cycle\ORM\ORMInterface; use Modules\Profiler\Application\CallGraph\Node; use Modules\Profiler\Application\Query\FindCallGraphByUuid; use Modules\Profiler\Domain\Edge; -use Modules\Profiler\Domain\Profile; +use Modules\Profiler\Domain\ProfileRepositoryInterface; use Spiral\Cqrs\Attribute\QueryHandler; -// TODO: refactor this, use repository final readonly class FindCallGraphByUuidHandler { public function __construct( - private ORMInterface $orm, + private ProfileRepositoryInterface $profiles, ) {} #[QueryHandler] public function __invoke(FindCallGraphByUuid $query): array { - $profile = $this->orm->getRepository(Profile::class)->findByPK($query->profileUuid); + $profile = $this->profiles->getByUuid($query->profileUuid); $edges = $profile->edges; $registered = []; diff --git a/app/modules/Profiler/Interfaces/Queries/FindDigraphByUuidHandler.php b/app/modules/Profiler/Interfaces/Queries/FindDigraphByUuidHandler.php new file mode 100644 index 00000000..9f42a16f --- /dev/null +++ b/app/modules/Profiler/Interfaces/Queries/FindDigraphByUuidHandler.php @@ -0,0 +1,257 @@ +profiles->getByUuid($query->profileUuid); + $topFn = $this->bus->ask(new FindTopFunctionsByUuid($query->profileUuid))['functions']; + $sym_table = []; + + foreach ($topFn as $fn) { + $sym_table[$fn['function']] = $fn; + } + + unset($topFn); + + $totalWt = $profile->getPeaks()->wt; + + $right = null; + $left = null; + + $maxWidth = 5; + $maxHeight = 3.5; + $maxFontSize = 35; + $maxSizingRatio = 20; + $source = 'xhprof'; + $threshold = $query->threshold; + + /** @var array $edges */ + $edges = []; + + foreach ($profile->edges as $edge) { + $edges[$this->buildKey($edge->getCaller(), $edge->getCallee())] = $edge; + } + + if ($query->criticalPath) { + $tree = $this->buildTree($edges); + + $node = "main()"; + $path = []; + $pathEdges = []; + $visited = []; + while ($node) { + $visited[$node] = true; + if (isset($tree[$node])) { + $maxChild = null; + foreach ($tree[$node] as $child) { + if (isset($visited[$child])) { + continue; + } + if ($maxChild === null || + \abs($edges[$this->buildKey($node, $child)]->getCost()->wt) > + \abs($edges[$this->buildKey($node, $maxChild)]->getCost()->wt) + ) { + $maxChild = $child; + } + } + if ($maxChild !== null) { + $path[$maxChild] = true; + $pathEdges[$this->buildKey($node, $maxChild)] = true; + } + $node = $maxChild; + } else { + $node = null; + } + } + } + + $result = "digraph call_graph {\n"; + + $cur_id = 0; + $max_wt = 0; + + foreach ($sym_table as $symbol => $info) { + $fn = $info["function"]; + if (\abs($info["wt"] / $totalWt) < $threshold) { + unset($sym_table[$symbol]); + continue; + } + if ($max_wt == 0 || $max_wt < \abs($info["excl_wt"])) { + $max_wt = \abs($info["excl_wt"]); + } + $sym_table[$symbol]["id"] = $cur_id; + $cur_id++; + } + + // Generate all nodes' information. + foreach ($sym_table as $symbol => $info) { + $fn = $info["function"]; + if ($info["excl_wt"] == 0) { + $sizingFactor = $maxSizingRatio; + } else { + $sizingFactor = $max_wt / abs($info["excl_wt"]); + if ($sizingFactor > $maxSizingRatio) { + $sizingFactor = $maxSizingRatio; + } + } + $fillcolor = (($sizingFactor < 1.5) ? + ", style=filled, fillcolor=red" : ""); + + if ($query->criticalPath) { + // highlight nodes along critical path. + if (!$fillcolor && array_key_exists($symbol, $path)) { + $fillcolor = ", style=filled, fillcolor=yellow"; + } + } + + $fontsize = ", fontsize=" + . (int) ($maxFontSize / (($sizingFactor - 1) / 10 + 1)); + + $width = ", width=" . sprintf("%.1f", $maxWidth / $sizingFactor); + $height = ", height=" . sprintf("%.1f", $maxHeight / $sizingFactor); + + if ($symbol == "main()") { + $shape = "octagon"; + $name = "Total: " . ($totalWt / 1000.0) . " ms\\n"; + $name .= addslashes($fn); + } else { + $shape = "box"; + $name = addslashes($fn) . "\\nInc: " . sprintf("%.3f", $info["wt"] / 1000) . + " ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totalWt) . ")"; + } + if ($left === null) { + $label = ", label=\"" . $name . "\\nExcl: " + . (sprintf("%.3f", $info["excl_wt"] / 1000.0)) . " ms (" + . sprintf("%.1f%%", 100 * $info["excl_wt"] / $totalWt) + . ")\\n" . $info["ct"] . " total calls\""; + } else { + if (isset($left[$symbol]) && isset($right[$symbol])) { + $label = ", label=\"" . addslashes($fn) . + "\\nInc: " . (sprintf("%.3f", $left[$symbol]["wt"] / 1000.0)) + . " ms - " + . (sprintf("%.3f", $right[$symbol]["wt"] / 1000.0)) . " ms = " + . (sprintf("%.3f", $info["wt"] / 1000.0)) . " ms" . + "\\nExcl: " + . (sprintf("%.3f", $left[$symbol]["excl_wt"] / 1000.0)) + . " ms - " . (sprintf("%.3f", $right[$symbol]["excl_wt"] / 1000.0)) + . " ms = " . (sprintf("%.3f", $info["excl_wt"] / 1000.0)) . " ms" . + "\\nCalls: " . (sprintf("%.3f", $left[$symbol]["ct"])) . " - " + . (sprintf("%.3f", $right[$symbol]["ct"])) . " = " + . (sprintf("%.3f", $info["ct"])) . "\""; + } else { + if (isset($left[$symbol])) { + $label = ", label=\"" . addslashes($fn) . + "\\nInc: " . (sprintf("%.3f", $left[$symbol]["wt"] / 1000.0)) + . " ms - 0 ms = " . (sprintf("%.3f", $info["wt"] / 1000.0)) + . " ms" . "\\nExcl: " + . (sprintf("%.3f", $left[$symbol]["excl_wt"] / 1000.0)) + . " ms - 0 ms = " + . (sprintf("%.3f", $info["excl_wt"] / 1000.0)) . " ms" . + "\\nCalls: " . (sprintf("%.3f", $left[$symbol]["ct"])) . " - 0 = " + . (sprintf("%.3f", $info["ct"])) . "\""; + } else { + $label = ", label=\"" . addslashes($fn) . + "\\nInc: 0 ms - " + . (sprintf("%.3f", $right[$symbol]["wt"] / 1000.0)) + . " ms = " . (sprintf("%.3f", $info["wt"] / 1000.0)) . " ms" . + "\\nExcl: 0 ms - " + . (sprintf("%.3f", $right[$symbol]["excl_wt"] / 1000.0)) + . " ms = " . (sprintf("%.3f", $info["excl_wt"] / 1000.0)) . " ms" . + "\\nCalls: 0 - " . (sprintf("%.3f", $right[$symbol]["ct"])) + . " = " . (sprintf("%.3f", $info["ct"])) . "\""; + } + } + } + $result .= "N" . $sym_table[$symbol]["id"]; + $result .= "[shape=$shape " . $label . $width + . $height . $fontsize . $fillcolor . "];\n"; + } + + + // Generate all the edges' information. + foreach ($edges as $edge) { + [$parent, $child] = [$edge->getCaller(), $edge->getCallee()]; + + if (isset($sym_table[$parent]) && isset($sym_table[$child])) { + $label = $edge->getCost()->ct == 1 ? $edge->getCost()->ct . " call" : $edge->getCost()->ct . " calls"; + + $headlabel = $sym_table[$child]["wt"] > 0 ? + sprintf( + "%.1f%%", + 100 * $info["wt"] + / $sym_table[$child]["wt"], + ) + : "0.0%"; + + $taillabel = ($sym_table[$parent]["wt"] > 0) ? + sprintf( + "%.1f%%", + 100 * $info["wt"] / + ($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"]), + ) + : "0.0%"; + + $linewidth = 1; + $arrow_size = 1; + + if ($query->criticalPath && isset($tree[$this->buildKey($parent, $child)])) { + $linewidth = 10; + $arrow_size = 2; + } + + $result .= "N" . $sym_table[$parent]["id"] . " -> N" + . $sym_table[$child]["id"]; + $result .= "[arrowsize=$arrow_size, color=grey, style=\"setlinewidth($linewidth)\"," + . " label=\"" + . $label . "\", headlabel=\"" . $headlabel + . "\", taillabel=\"" . $taillabel . "\" ]"; + $result .= ";\n"; + } + } + + return $result . "\n}"; + } + + /** + * @param array $edges + */ + private function buildTree(array $edges): array + { + $tree = []; + + foreach ($edges as $edge) { + $parent = $edge->getCaller(); + $child = $edge->getCallee(); + if (!isset($tree[$parent])) { + $tree[$parent] = [$child]; + } else { + $tree[$parent][] = $child; + } + } + + return $tree; + } + + private function buildKey(?string $parent, ?string $child): string + { + return $parent . '==>' . $child; + } +} diff --git a/app/modules/Profiler/Interfaces/Queries/FindFlameChartByUuidHandler.php b/app/modules/Profiler/Interfaces/Queries/FindFlameChartByUuidHandler.php index 8900e0fd..947d6ef4 100644 --- a/app/modules/Profiler/Interfaces/Queries/FindFlameChartByUuidHandler.php +++ b/app/modules/Profiler/Interfaces/Queries/FindFlameChartByUuidHandler.php @@ -4,18 +4,16 @@ namespace Modules\Profiler\Interfaces\Queries; -use Cycle\ORM\ORMInterface; use Modules\Profiler\Application\Query\FindFlameChartByUuid; use Modules\Profiler\Domain\Edge; -use Modules\Profiler\Domain\Profile; +use Modules\Profiler\Domain\ProfileRepositoryInterface; use Spiral\Cqrs\Attribute\QueryHandler; use Spiral\Storage\BucketInterface; -// TODO: refactor this, use repository final readonly class FindFlameChartByUuidHandler { public function __construct( - private ORMInterface $orm, + private ProfileRepositoryInterface $profiles, private BucketInterface $bucket, ) {} @@ -27,7 +25,7 @@ public function __invoke(FindFlameChartByUuid $query): array return \json_decode($this->bucket->getContents($file), true); } - $profile = $this->orm->getRepository(Profile::class)->findByPK($query->profileUuid); + $profile = $this->profiles->getByUuid($query->profileUuid); /** @var Edge[] $edges */ $edges = $profile->edges; diff --git a/app/modules/Profiler/Interfaces/Queries/FindTopFunctionsByUuidHandler.php b/app/modules/Profiler/Interfaces/Queries/FindTopFunctionsByUuidHandler.php index 26dc04eb..d7cc23a8 100644 --- a/app/modules/Profiler/Interfaces/Queries/FindTopFunctionsByUuidHandler.php +++ b/app/modules/Profiler/Interfaces/Queries/FindTopFunctionsByUuidHandler.php @@ -4,23 +4,21 @@ namespace Modules\Profiler\Interfaces\Queries; -use Cycle\ORM\ORMInterface; use Modules\Profiler\Application\Query\FindTopFunctionsByUuid; use Modules\Profiler\Domain\Edge; -use Modules\Profiler\Domain\Profile; +use Modules\Profiler\Domain\ProfileRepositoryInterface; use Spiral\Cqrs\Attribute\QueryHandler; -// TODO: refactor this, use repository -final class FindTopFunctionsByUuidHandler +final readonly class FindTopFunctionsByUuidHandler { public function __construct( - private ORMInterface $orm, + private ProfileRepositoryInterface $profiles, ) {} #[QueryHandler] public function __invoke(FindTopFunctionsByUuid $query): array { - $profile = $this->orm->getRepository(Profile::class)->findByPK($query->profileUuid); + $profile = $this->profiles->getByUuid($query->profileUuid); $overallTotals = []; diff --git a/tests/Feature/Interfaces/Http/Profiler/ProfilerActionTest.php b/tests/Feature/Interfaces/Http/Profiler/ProfilerActionTest.php index 73cb59ad..12bd74b7 100644 --- a/tests/Feature/Interfaces/Http/Profiler/ProfilerActionTest.php +++ b/tests/Feature/Interfaces/Http/Profiler/ProfilerActionTest.php @@ -5,6 +5,9 @@ namespace Tests\Feature\Interfaces\Http\Profiler; use App\Application\Broadcasting\Channel\EventsChannel; +use App\Application\Domain\ValueObjects\Uuid; +use Modules\Profiler\Domain\ProfileEdgeRepositoryInterface; +use Modules\Profiler\Domain\ProfileRepositoryInterface; use Nyholm\Psr7\Stream; use Tests\Feature\Interfaces\Http\ControllerTestCase; @@ -47,15 +50,26 @@ public function testSendDataWithProject(): void public function assertEvent(?string $project = null): void { - $this->broadcastig->assertPushed((string)new EventsChannel($project), function (array $data) use ($project) { - $this->assertSame('event.received', $data['event']); - $this->assertSame('profiler', $data['data']['type']); - $this->assertSame($project, $data['data']['project']); + $profiles = $this->get(ProfileRepositoryInterface::class); + $edges = $this->get(ProfileEdgeRepositoryInterface::class); - $this->assertNotEmpty($data['data']['uuid']); - $this->assertNotEmpty($data['data']['timestamp']); + $this->broadcastig->assertPushed( + (string) new EventsChannel($project), + function (array $data) use ($profiles, $edges, $project) { + $this->assertSame('event.received', $data['event']); + $this->assertSame('profiler', $data['data']['type']); + $this->assertSame($project, $data['data']['project']); - return true; - }); + $this->assertNotEmpty($data['data']['uuid']); + $this->assertNotEmpty($data['data']['timestamp']); + + // Check if the event was saved to the profiles table + $profileUuid = Uuid::fromString($data['data']['uuid']); + $this->assertNotNull($profiles->findByPK($profileUuid)); + $this->assertCount(19, $edges->getByProfileUuid($profileUuid)); + + return true; + }, + ); } }