From a123cd63ba2e5f4280ef6640c63e464aea10c504 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Sat, 6 Jul 2024 21:51:31 +0400 Subject: [PATCH] feat(frontend): support sorting in profiler "Top functions" table --- .../Frontend/Module/Profiler/Mapper.php | 15 +++- src/Module/Profiler/Struct/Tree.php | 38 ++++++++++ .../Unit/Module/Profiler/Struct/TreeTest.php | 70 +++++++++++++++++++ 3 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/Module/Profiler/Struct/TreeTest.php diff --git a/src/Module/Frontend/Module/Profiler/Mapper.php b/src/Module/Frontend/Module/Profiler/Mapper.php index bc7331c2..68f49b54 100644 --- a/src/Module/Frontend/Module/Profiler/Mapper.php +++ b/src/Module/Frontend/Module/Profiler/Mapper.php @@ -8,6 +8,7 @@ use Buggregator\Trap\Module\Frontend\Module\Profiler\Message\CallGraph; use Buggregator\Trap\Module\Frontend\Module\Profiler\Message\FlameChart; use Buggregator\Trap\Module\Frontend\Module\Profiler\Message\TopFunctions; +use Buggregator\Trap\Module\Profiler\Struct\Branch; use Buggregator\Trap\Proto\Frame\Profiler\Payload as ProfilerPayload; /** @@ -38,8 +39,18 @@ public function topFunctions(Event $event, string $metric): TopFunctions // Get top $top = []; - foreach ($profile->calls->all as $branch) { - // todo: limit with 100 and sort + $topBranches = $profile->calls->top( + 100, + match ($metric) { + 'ct' => static fn(Branch $a, Branch $b): int => $b->item->cost->ct <=> $a->item->cost->ct, + 'cpu' => static fn(Branch $a, Branch $b): int => $b->item->cost->cpu <=> $a->item->cost->cpu, + 'mu' => static fn(Branch $a, Branch $b): int => $b->item->cost->mu <=> $a->item->cost->mu, + 'pmu' => static fn(Branch $a, Branch $b): int => $b->item->cost->pmu <=> $a->item->cost->pmu, + default => static fn(Branch $a, Branch $b): int => $b->item->cost->wt <=> $a->item->cost->wt, + } + ); + + foreach ($topBranches as $branch) { $top[] = TopFunctions\Func::fromEdge($branch->item); } diff --git a/src/Module/Profiler/Struct/Tree.php b/src/Module/Profiler/Struct/Tree.php index 8c5e0230..3f2976d2 100644 --- a/src/Module/Profiler/Struct/Tree.php +++ b/src/Module/Profiler/Struct/Tree.php @@ -46,6 +46,44 @@ public static function fromEdgesList(array $edges, callable $getCurrent, callabl return $tree; } + /** + * Get the top N branches by a custom sorting. + * + * @param int<1, max> $limit + * @param callable(Branch, Branch): int $sorter + * + * @return list + */ + public function top(int $limit, callable $sorter): array + { + // Get N branches and sort it + $all = \array_values($this->all); + $result = \array_slice($all, 0, $limit); + \usort($result, $sorter); + + // Compare next item with the last of the top + // and resort the top one by one + $end = \array_key_last($result); + $next = $limit - 1; + + while (++$next < \count($all)) { + if ($sorter($all[$next], $result[$end]) > 0) { + continue; + } + + // Add the next item to the top + $result[$end] = $all[$next]; + + // Sort + $current = $end; + while (--$current >= 0 && $sorter($result[$current], $result[$current + 1]) > 0) { + [$result[$current], $result[$current + 1]] = [$result[$current + 1], $result[$current]]; + } + } + + return $result; + } + /** * @param non-empty-string $id * @param non-empty-string|null $parentId diff --git a/tests/Unit/Module/Profiler/Struct/TreeTest.php b/tests/Unit/Module/Profiler/Struct/TreeTest.php new file mode 100644 index 00000000..592961a6 --- /dev/null +++ b/tests/Unit/Module/Profiler/Struct/TreeTest.php @@ -0,0 +1,70 @@ +generateEdges(); + $tree = Tree::fromEdgesList( + $edges, + static fn(Edge $edge) => $edge->callee, + static fn(Edge $edge) => $edge->caller, + ); + + $result = $tree->top($top, static fn(Branch $a,Branch $b) => $b->item->cost->wt <=> $a->item->cost->wt); + + self::assertCount(\min($top, \count($edges)), $result); + + $topUnoptimized = \array_map(static fn (Edge $e) => $e->cost->wt, $edges); + \rsort($topUnoptimized); + $topUnoptimized = \array_slice($topUnoptimized, 0, $top); + $topVals = \array_map(static fn (Branch $b) => $b->item->cost->wt, $result); + self::assertSame($topUnoptimized, $topVals); + } + + /** + * @return array + */ + private function generateEdges(int $multiplier = 20): array + { + $result = []; + + for ($i = 0; $i < $multiplier; $i++) { + for ($j = 0; $j <= $i; $j++) { + $result[] = new Edge( + $i === 0 ? null : 'item-' . ($i - 1) . "-$j", "item-$i-$j", + new Cost( + ct: (int) $i**$i, + wt: ($multiplier - $i) * 1000 + $j, + cpu: ($multiplier - $i) * 10, + mu: (int) $i * 1000, + pmu: (int) $i * 1000, + ), + ); + } + } + + return $result; + } +}