-
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
- Loading branch information
There are no files selected for viewing
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); | ||
} | ||
} |
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 GitHub Actions / psalm (ubuntu-latest, 8.2, locked)InvalidParamDefault
Check failure on line 37 in src/Module/Profiler/Struct/Profile.php GitHub Actions / psalm (ubuntu-latest, 8.2, locked)InvalidParamDefault
|
||
*/ | ||
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 GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)
Check failure on line 43 in src/Module/Profiler/Struct/Profile.php GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)
|
||
?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 GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)
|
||
{ | ||
$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 GitHub Actions / psalm (ubuntu-latest, 8.2, locked)PossiblyInvalidArgument
Check failure on line 71 in src/Module/Profiler/Struct/Profile.php GitHub Actions / psalm (ubuntu-latest, 8.2, locked)PossiblyInvalidArgument
|
||
); | ||
|
||
return $self; | ||
} | ||
|
||
/** | ||
* @return ProfileData | ||
Check failure on line 78 in src/Module/Profiler/Struct/Profile.php GitHub Actions / psalm (ubuntu-latest, 8.2, locked)MixedReturnTypeCoercion
Check failure on line 78 in src/Module/Profiler/Struct/Profile.php GitHub Actions / psalm (ubuntu-latest, 8.2, locked)MixedReturnTypeCoercion
|
||
*/ | ||
public function jsonSerialize(): array | ||
Check failure on line 80 in src/Module/Profiler/Struct/Profile.php GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)
|
||
{ | ||
return [ | ||
Check failure on line 82 in src/Module/Profiler/Struct/Profile.php GitHub Actions / psalm (ubuntu-latest, 8.2, locked)MixedReturnTypeCoercion
Check failure on line 82 in src/Module/Profiler/Struct/Profile.php GitHub Actions / psalm (ubuntu-latest, 8.2, locked)MixedReturnTypeCoercion
|
||
'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 |
---|---|---|
@@ -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 GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)
|
||
\assert($callee !== ''); | ||
Check failure on line 67 in src/Module/Profiler/XHProf/ProfileBuilder.php GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)
Check failure on line 67 in src/Module/Profiler/XHProf/ProfileBuilder.php GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)
Check failure on line 67 in src/Module/Profiler/XHProf/ProfileBuilder.php GitHub Actions / phpstan (ubuntu-latest, 8.2, locked)
|
||
|
||
$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, | ||
// ]; | ||
} | ||
} |