Skip to content

Commit

Permalink
chore(profiler): create Profiler module and move some things to there
Browse files Browse the repository at this point in the history
  • Loading branch information
roxblnfk committed Jun 13, 2024
1 parent 9924477 commit 53b2ecc
Show file tree
Hide file tree
Showing 10 changed files with 358 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Buggregator\Trap\Service\FilesObserver\Converter;
namespace Buggregator\Trap\Module\Profiler\Struct;

/**
* @template-covariant T of object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Buggregator\Trap\Service\FilesObserver\Converter;
namespace Buggregator\Trap\Module\Profiler\Struct;

/**
* @internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Buggregator\Trap\Service\FilesObserver\Converter;
namespace Buggregator\Trap\Module\Profiler\Struct;

/**
* @internal
Expand Down
85 changes: 85 additions & 0 deletions src/Module/Profiler/Struct/Peaks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Module\Profiler\Struct;

/**
* @psalm-type PeaksData = array{
* ct: int<0, max>,
* wt: int<0, max>,
* cpu: int<0, max>,
* mu: int<0, max>,
* pmu: int<0, max>
* }
*
* @internal
*/
final class Peaks implements \JsonSerializable
{
/**
* @param int<0, max> $ct
* @param int<0, max> $wt
* @param int<0, max> $cpu
* @param int<0, max> $mu
* @param int<0, max> $pmu
*/
public function __construct(
public int $ct = 0,
public int $wt = 0,
public int $cpu = 0,
public int $mu = 0,
public int $pmu = 0,
) {}

/**
* @param array{
* ct: int<0, max>,
* wt: int<0, max>,
* cpu: int<0, max>,
* mu: int<0, max>,
* pmu: int<0, max>
* } $data
*/
public static function fromArray(array $data): self
{
$self = new self(
$data['ct'],
$data['wt'],
$data['cpu'],
$data['mu'],
$data['pmu'],
);

return $self;
}

/**
* @return array{
* ct: int<0, max>,
* wt: int<0, max>,
* cpu: int<0, max>,
* mu: int<0, max>,
* pmu: int<0, max>
* }
*/
public function jsonSerialize(): array
{
return [
'ct' => $this->ct,
'wt' => $this->wt,
'cpu' => $this->cpu,
'mu' => $this->mu,
'pmu' => $this->pmu,
];
}

public function update(Cost $item): void
{
$this->ct = \max($this->ct, $item->ct);
$this->wt = \max($this->wt, $item->wt);
$this->cpu = \max($this->cpu, $item->cpu);
$this->mu = \max($this->mu, $item->mu);
$this->pmu = \max($this->pmu, $item->pmu);
}
}
95 changes: 95 additions & 0 deletions src/Module/Profiler/Struct/Profile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Module\Profiler\Struct;

/**
* @psalm-type Metadata = array{
* app_name?: string,
* hostname?: string,
* filename?: string,
* ...
* }
*
* @psalm-type ProfileData = array{
* date: int,
* app_name?: string,
* hostname?: string,
* filename?: string,
* tags: array<non-empty-string, non-empty-string>,
* peaks: PeaksData|Peaks,
* edges: array,
* total_edges: int<0, max>,
* }
*
* @psalm-import-type PeaksData from Peaks
*
* @internal
*/
final class Profile implements \JsonSerializable
{
public Peaks $peaks;

/**
* @param Metadata $metadata
* @param array<non-empty-string, non-empty-string> $tags
* @param Tree<Edge> $calls

Check failure on line 37 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

InvalidParamDefault

src/Module/Profiler/Struct/Profile.php:37:15: InvalidParamDefault: Default value type Buggregator\Trap\Module\Profiler\Struct\Tree<object> for argument 4 of method Buggregator\Trap\Module\Profiler\Struct\Profile::__construct does not match the given type Buggregator\Trap\Module\Profiler\Struct\Tree<Buggregator\Trap\Module\Profiler\Struct\Edge> (see https://psalm.dev/062)

Check failure on line 37 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

InvalidParamDefault

src/Module/Profiler/Struct/Profile.php:37:15: InvalidParamDefault: Default value type Buggregator\Trap\Module\Profiler\Struct\Tree<object> for argument 4 of method Buggregator\Trap\Module\Profiler\Struct\Profile::__construct does not match the given type Buggregator\Trap\Module\Profiler\Struct\Tree<Buggregator\Trap\Module\Profiler\Struct\Edge> (see https://psalm.dev/062)
*/
public function __construct(
public \DateTimeInterface $date = new \DateTimeImmutable(),
public array $metadata = [],
public array $tags = [],
public Tree $calls = new Tree(),

Check failure on line 43 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Default value of the parameter #4 $calls (Buggregator\Trap\Module\Profiler\Struct\Tree<object>) of method Buggregator\Trap\Module\Profiler\Struct\Profile::__construct() is incompatible with type Buggregator\Trap\Module\Profiler\Struct\Tree<Buggregator\Trap\Module\Profiler\Struct\Edge>.

Check failure on line 43 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Default value of the parameter #4 $calls (Buggregator\Trap\Module\Profiler\Struct\Tree<object>) of method Buggregator\Trap\Module\Profiler\Struct\Profile::__construct() is incompatible with type Buggregator\Trap\Module\Profiler\Struct\Tree<Buggregator\Trap\Module\Profiler\Struct\Edge>.
?Peaks $peaks = null,
) {
if ($peaks === null) {
$this->peaks = new Peaks();

/** @var Edge $edge */
foreach ($this->calls as $edge) {
$this->peaks->update($edge->cost);
}
} else {
$this->peaks = $peaks;
}
}

/**
* @param ProfileData $data
*/
public static function fromArray(array $data): self

Check failure on line 61 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Method Buggregator\Trap\Module\Profiler\Struct\Profile::fromArray() has parameter $data with no value type specified in iterable type array.

Check failure on line 61 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Method Buggregator\Trap\Module\Profiler\Struct\Profile::fromArray() has parameter $data with no value type specified in iterable type array.
{
$metadata = $data;
unset($metadata['tags'], $metadata['peaks'], $metadata['total_edges'], $metadata['date']);

$self = new self(
date: new \DateTimeImmutable('@' . $data['date']),
metadata: $metadata,
tags: $data['tags'],
// todo calls from edges
peaks: Peaks::fromArray($data['peaks']),

Check failure on line 71 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

PossiblyInvalidArgument

src/Module/Profiler/Struct/Profile.php:71:37: PossiblyInvalidArgument: Argument 1 of Buggregator\Trap\Module\Profiler\Struct\Peaks::fromArray expects array{cpu: int<0, max>, ct: int<0, max>, mu: int<0, max>, pmu: int<0, max>, wt: int<0, max>}, but possibly different type Buggregator\Trap\Module\Profiler\Struct\Peaks|array{cpu: int<0, max>, ct: int<0, max>, mu: int<0, max>, pmu: int<0, max>, wt: int<0, max>} provided (see https://psalm.dev/092)

Check failure on line 71 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

PossiblyInvalidArgument

src/Module/Profiler/Struct/Profile.php:71:37: PossiblyInvalidArgument: Argument 1 of Buggregator\Trap\Module\Profiler\Struct\Peaks::fromArray expects array{cpu: int<0, max>, ct: int<0, max>, mu: int<0, max>, pmu: int<0, max>, wt: int<0, max>}, but possibly different type Buggregator\Trap\Module\Profiler\Struct\Peaks|array{cpu: int<0, max>, ct: int<0, max>, mu: int<0, max>, pmu: int<0, max>, wt: int<0, max>} provided (see https://psalm.dev/092)
);

return $self;
}

/**
* @return ProfileData

Check failure on line 78 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

MixedReturnTypeCoercion

src/Module/Profiler/Struct/Profile.php:78:16: MixedReturnTypeCoercion: The declared return type 'array{app_name?: string, date: int, edges: array<array-key, mixed>, filename?: string, hostname?: string, peaks: Buggregator\Trap\Module\Profiler\Struct\Peaks|array{cpu: int<0, max>, ct: int<0, max>, mu: int<0, max>, pmu: int<0, max>, wt: int<0, max>}, tags: array<non-empty-string, non-empty-string>, total_edges: int<0, max>}' for Buggregator\Trap\Module\Profiler\Struct\Profile::jsonSerialize is more specific than the inferred return type 'array{app_name: string, date: int, edges: array<array-key, Buggregator\Trap\Module\Profiler\Struct\Edge>, filename: mixed|string, hostname: mixed|string, peaks: Buggregator\Trap\Module\Profiler\Struct\Peaks, tags: array<non-empty-string, non-empty-string>, total_edges: int<0, max>}' (see https://psalm.dev/197)

Check failure on line 78 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

MixedReturnTypeCoercion

src/Module/Profiler/Struct/Profile.php:78:16: MixedReturnTypeCoercion: The declared return type 'array{app_name?: string, date: int, edges: array<array-key, mixed>, filename?: string, hostname?: string, peaks: Buggregator\Trap\Module\Profiler\Struct\Peaks|array{cpu: int<0, max>, ct: int<0, max>, mu: int<0, max>, pmu: int<0, max>, wt: int<0, max>}, tags: array<non-empty-string, non-empty-string>, total_edges: int<0, max>}' for Buggregator\Trap\Module\Profiler\Struct\Profile::jsonSerialize is more specific than the inferred return type 'array{app_name: string, date: int, edges: array<array-key, Buggregator\Trap\Module\Profiler\Struct\Edge>, filename: mixed|string, hostname: mixed|string, peaks: Buggregator\Trap\Module\Profiler\Struct\Peaks, tags: array<non-empty-string, non-empty-string>, total_edges: int<0, max>}' (see https://psalm.dev/197)
*/
public function jsonSerialize(): array

Check failure on line 80 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Method Buggregator\Trap\Module\Profiler\Struct\Profile::jsonSerialize() return type has no value type specified in iterable type array.

Check failure on line 80 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Method Buggregator\Trap\Module\Profiler\Struct\Profile::jsonSerialize() return type has no value type specified in iterable type array.
{
return [

Check failure on line 82 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

MixedReturnTypeCoercion

src/Module/Profiler/Struct/Profile.php:82:16: MixedReturnTypeCoercion: The type 'array{app_name: string, date: int, edges: array<array-key, Buggregator\Trap\Module\Profiler\Struct\Edge>, filename: mixed|string, hostname: mixed|string, peaks: Buggregator\Trap\Module\Profiler\Struct\Peaks, tags: array<non-empty-string, non-empty-string>, total_edges: int<0, max>}' is more general than the declared return type 'array{app_name?: string, date: int, edges: array<array-key, mixed>, filename?: string, hostname?: string, peaks: Buggregator\Trap\Module\Profiler\Struct\Peaks|array{cpu: int<0, max>, ct: int<0, max>, mu: int<0, max>, pmu: int<0, max>, wt: int<0, max>}, tags: array<non-empty-string, non-empty-string>, total_edges: int<0, max>}' for Buggregator\Trap\Module\Profiler\Struct\Profile::jsonSerialize (see https://psalm.dev/197)

Check failure on line 82 in src/Module/Profiler/Struct/Profile.php

View workflow job for this annotation

GitHub Actions / psalm (ubuntu-latest, 8.2, locked)

MixedReturnTypeCoercion

src/Module/Profiler/Struct/Profile.php:82:16: MixedReturnTypeCoercion: The type 'array{app_name: string, date: int, edges: array<array-key, Buggregator\Trap\Module\Profiler\Struct\Edge>, filename: mixed|string, hostname: mixed|string, peaks: Buggregator\Trap\Module\Profiler\Struct\Peaks, tags: array<non-empty-string, non-empty-string>, total_edges: int<0, max>}' is more general than the declared return type 'array{app_name?: string, date: int, edges: array<array-key, mixed>, filename?: string, hostname?: string, peaks: Buggregator\Trap\Module\Profiler\Struct\Peaks|array{cpu: int<0, max>, ct: int<0, max>, mu: int<0, max>, pmu: int<0, max>, wt: int<0, max>}, tags: array<non-empty-string, non-empty-string>, total_edges: int<0, max>}' for Buggregator\Trap\Module\Profiler\Struct\Profile::jsonSerialize (see https://psalm.dev/197)
'date' => $this->date->getTimestamp(),
'app_name' => $this->metadata['app_name'] ?? '',
'hostname' => $this->metadata['hostname'] ?? '',
'filename' => $this->metadata['filename'] ?? '',
'tags' => $this->tags,
'peaks' => $this->peaks,
'edges' => \iterator_to_array($this->calls->getItemsSortedV1(
static fn(Branch $a, Branch $b): int => $b->item->cost->wt <=> $a->item->cost->wt,
)),
'total_edges' => $this->calls->count(),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Buggregator\Trap\Service\FilesObserver\Converter;
namespace Buggregator\Trap\Module\Profiler\Struct;

/**
* @template-covariant TItem of object
Expand All @@ -11,7 +11,7 @@
*
* @internal
*/
final class Tree implements \IteratorAggregate
final class Tree implements \IteratorAggregate, \Countable
{
/** @var array<non-empty-string, Branch<TItem>> */
private array $root = [];
Expand Down Expand Up @@ -84,6 +84,16 @@ public function getIterator(): \Traversable
yield from $this->all;
}

/**
* @return \Traversable<TItem>
*/
public function iterateAll(): \Traversable
{
foreach ($this->all as $branch) {
yield $branch->item;
}
}

/**
* Yield items by the level in the hierarchy with custom sorting in level scope
*
Expand Down Expand Up @@ -142,4 +152,12 @@ public function __destruct()

unset($this->all, $this->root, $this->lostChildren);
}

/**
* @return int<0, max>
*/
public function count(): int
{
return \count($this->all);
}
}
122 changes: 122 additions & 0 deletions src/Module/Profiler/XHProf/ProfileBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

declare(strict_types=1);

namespace Buggregator\Trap\Module\Profiler\XHProf;

use Buggregator\Trap\Module\Profiler\Struct\Branch;
use Buggregator\Trap\Module\Profiler\Struct\Cost;
use Buggregator\Trap\Module\Profiler\Struct\Edge;
use Buggregator\Trap\Module\Profiler\Struct\Peaks;
use Buggregator\Trap\Module\Profiler\Struct\Profile;
use Buggregator\Trap\Module\Profiler\Struct\Tree;

/**
* @psalm-type RawData = array<non-empty-string, array{
* ct: int<0, max>,
* wt: int<0, max>,
* cpu: int<0, max>,
* mu: int<0, max>,
* pmu: int<0, max>
* }>
*
* @psalm-import-type Metadata from Profile
*
* @internal
*/
final class ProfileBuilder
{
/**
* @param Metadata $metadata
* @param RawData $calls
* @param array<non-empty-string, non-empty-string> $tags
*/
public function createProfile(
\DateTimeInterface $date = new \DateTimeImmutable(),
array $metadata = [],
array $tags = [],
array $calls = [],
): Profile {
[$tree, $peaks] = $this->dataToPayload($calls);
return new Profile(
date: $date,
metadata: $metadata,
tags: $tags,
calls: $tree,
peaks: $peaks,
);
}

/**
* @param RawData $data
* @return array{Tree<Edge>, Peaks}
*/
private function dataToPayload(array $data): array
{
$peaks = new Peaks();

/** @var Tree<Edge> $tree */
$tree = new Tree();

foreach (\array_reverse($data, true) as $key => $value) {
[$caller, $callee] = \explode('==>', $key, 2) + [1 => ''];
if ($callee === '') {
[$caller, $callee] = [null, $caller];
}
$caller === '' and $caller = null;

Check failure on line 66 in src/Module/Profiler/XHProf/ProfileBuilder.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Only booleans are allowed in &&, null given on the right side.

Check failure on line 66 in src/Module/Profiler/XHProf/ProfileBuilder.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Only booleans are allowed in &&, null given on the right side.
\assert($callee !== '');

Check failure on line 67 in src/Module/Profiler/XHProf/ProfileBuilder.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Call to function assert() with true will always evaluate to true.

Check failure on line 67 in src/Module/Profiler/XHProf/ProfileBuilder.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Strict comparison using !== between non-empty-string|null and '' will always evaluate to true.

Check failure on line 67 in src/Module/Profiler/XHProf/ProfileBuilder.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Call to function assert() with true will always evaluate to true.

Check failure on line 67 in src/Module/Profiler/XHProf/ProfileBuilder.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)

Strict comparison using !== between non-empty-string|null and '' will always evaluate to true.

$edge = new Edge(
caller: $caller,
callee: $callee,
cost: Cost::fromArray($value),
);

$peaks->update($edge->cost);
$tree->addItem($edge, $edge->callee, $edge->caller);
}

/**
* Calc percentages and delta
* @var Branch<Edge> $branch Needed for IDE
*/
foreach ($tree->getIterator() as $branch) {
$cost = $branch->item->cost;
$cost->p_cpu = $peaks->cpu > 0 ? \round($cost->cpu / $peaks->cpu * 100, 3) : 0;
$cost->p_ct = $peaks->ct > 0 ? \round($cost->ct / $peaks->ct * 100, 3) : 0;
$cost->p_mu = $peaks->mu > 0 ? \round($cost->mu / $peaks->mu * 100, 3) : 0;
$cost->p_pmu = $peaks->pmu > 0 ? \round($cost->pmu / $peaks->pmu * 100, 3) : 0;
$cost->p_wt = $peaks->wt > 0 ? \round($cost->wt / $peaks->wt * 100, 3) : 0;

if ($branch->parent !== null) {
$parentCost = $branch->parent->item->cost;
$cost->d_cpu = $cost->cpu - $parentCost->cpu;
$cost->d_ct = $cost->ct - $parentCost->ct;
$cost->d_mu = $cost->mu - $parentCost->mu;
$cost->d_pmu = $cost->pmu - $parentCost->pmu;
$cost->d_wt = $cost->wt - $parentCost->wt;
}
}

return [$tree, $peaks];

// return [
// 'edges' => \iterator_to_array(match ($this->config->algorithm) {
// // Deep-first
// 0 => $tree->getItemsSortedV0(null),
// // Deep-first with sorting by WT
// 1 => $tree->getItemsSortedV0(
// static fn(Branch $a, Branch $b): int => $b->item->cost->wt <=> $a->item->cost->wt,
// ),
// // Level-by-level
// 2 => $tree->getItemsSortedV1(null),
// // Level-by-level with sorting by WT
// 3 => $tree->getItemsSortedV1(
// static fn(Branch $a, Branch $b): int => $b->item->cost->wt <=> $a->item->cost->wt,
// ),
// default => throw new \LogicException('Unknown XHProf sorting algorithm.'),
// }),
// 'peaks' => $peaks,
// ];
}
}
Loading

0 comments on commit 53b2ecc

Please sign in to comment.