diff --git a/src/Proto/Frame/Profiler.php b/src/Proto/Frame/Profiler.php index d58c0599..a1b72e42 100644 --- a/src/Proto/Frame/Profiler.php +++ b/src/Proto/Frame/Profiler.php @@ -5,8 +5,7 @@ namespace Buggregator\Trap\Proto\Frame; use Buggregator\Trap\Proto\Frame; -use Buggregator\Trap\Proto\Frame\Profiler\File; -use Buggregator\Trap\Proto\Frame\Profiler\Payload; +use Buggregator\Trap\ProtoType; use Buggregator\Trap\Support\Json; use DateTimeImmutable; @@ -14,15 +13,27 @@ * @internal * @psalm-internal Buggregator */ -abstract class Profiler extends Frame +final class Profiler extends Frame { + public function __construct( + public readonly Frame\Profiler\Payload $payload, + DateTimeImmutable $time = new DateTimeImmutable(), + ) { + parent::__construct(ProtoType::Profiler, $time); + } + public static function fromString(string $payload, DateTimeImmutable $time): static { $data = Json::decode($payload); - return match (true) { - $data['type'] === File::PROFILE_FRAME_TYPE => File::fromArray($data, $time), - $data['type'] === Payload::PROFILE_FRAME_TYPE => Payload::fromArray($data, $time), - default => throw new \InvalidArgumentException('Unknown Profile frame type.'), - }; + + return new self(Frame\Profiler\Payload::fromArray($data), $time); + } + + /** + * @throws \JsonException + */ + public function __toString(): string + { + return Json::encode($this->payload->jsonSerialize() + ['']); } } diff --git a/src/Proto/Frame/Profiler/File.php b/src/Proto/Frame/Profiler/File.php deleted file mode 100644 index c99e81b0..00000000 --- a/src/Proto/Frame/Profiler/File.php +++ /dev/null @@ -1,40 +0,0 @@ -fileInfo->toArray()); - } - - public static function fromArray(array $data, DateTimeImmutable $time): static - { - return new self(FileInfo::fromArray($data), $time); - } -} diff --git a/src/Proto/Frame/Profiler/Payload.php b/src/Proto/Frame/Profiler/Payload.php index d83c5ff5..c65cc398 100644 --- a/src/Proto/Frame/Profiler/Payload.php +++ b/src/Proto/Frame/Profiler/Payload.php @@ -4,36 +4,84 @@ namespace Buggregator\Trap\Proto\Frame\Profiler; -use Buggregator\Trap\Proto\Frame; -use Buggregator\Trap\ProtoType; -use Buggregator\Trap\Support\Json; -use DateTimeImmutable; +use Buggregator\Trap\Proto\Frame\Profiler\Type as PayloadType; +use Buggregator\Trap\Service\FilesObserver\FileInfo; /** + * @psalm-type Metadata = array{ + * date: int, + * hostname: non-empty-string, + * filename?: non-empty-string, + * ... + * } + * @psalm-type Calls = array{ + * edges: array, + * peaks: array + * } + * * @internal * @psalm-internal Buggregator */ -final class Payload extends Frame\Profiler +class Payload implements \JsonSerializable { - public const PROFILE_FRAME_TYPE = 'payload'; - - public function __construct( - public array $payload, - DateTimeImmutable $time = new DateTimeImmutable(), + /** + * @param PayloadType $type + * @param Metadata $metadata + * @param \Closure(): Calls $callsProvider + */ + private function __construct( + public readonly PayloadType $type, + private array $metadata, + private \Closure $callsProvider, ) { - parent::__construct(ProtoType::Profiler, $time); + $this->metadata['type'] = $type->value; + } + + public static function fromArray(array $data, ?Type $type = null): static + { + $metadata = $data; + unset($metadata['edges'], $metadata['peaks']); + return new static( + $type ?? PayloadType::from($data['type']), + $metadata, + static fn(): array => $data, + ); + } + + public static function fromFile(FileInfo $fileInfo): static + { + // todo + // $metadata = $data; + // unset($metadata['edges'], $metadata['peaks']); + // return new static(PayloadType::from($data['type']), $metadata, fn(): array => $data); } /** - * @throws \JsonException + * @return Calls */ - public function __toString(): string + public function getCalls(): array { - return Json::encode($this->payload); + return ($this->callsProvider)(); } - public static function fromArray(array $data, DateTimeImmutable $time): static + /** + * @return Metadata + */ + public function getMetadata(): array + { + return $this->metadata; + } + + public function toArray(): array + { + return ['type' => $this->type->value] + $this->getCalls() + $this->getMetadata(); + } + + /** + * @return array{type: non-empty-string}&Calls&Metadata + */ + public function jsonSerialize(): array { - return new self($data, $time); + return $this->toArray(); } } diff --git a/src/Proto/Frame/Profiler/Type.php b/src/Proto/Frame/Profiler/Type.php new file mode 100644 index 00000000..3b445d2e --- /dev/null +++ b/src/Proto/Frame/Profiler/Type.php @@ -0,0 +1,15 @@ + + * + * @internal + */ +final class Profiler implements Renderer +{ + public function isSupport(Frame $frame): bool + { + return $frame->type === ProtoType::Profiler; + } + + public function render(OutputInterface $output, Frame $frame): void + { + \assert($frame instanceof Frame\Profiler); + + $subtitle = $frame->payload->type->value; + Common::renderHeader1($output, 'PROFILER', $subtitle); + + $metadata = $frame->payload->getMetadata(); + $data = []; + isset($metadata['date']) && \is_numeric($metadata['date']) + and $data['Time'] = new DateTimeImmutable('@' . $metadata['date']); + isset($metadata['hostname']) and $data['Hostname'] = $metadata['hostname']; + isset($metadata['filename']) and $data['File name'] = $metadata['filename']; + + Common::renderMetadata($output, $data); + } +} diff --git a/src/Sender/ConsoleSender.php b/src/Sender/ConsoleSender.php index 1d626a1a..adf846f9 100644 --- a/src/Sender/ConsoleSender.php +++ b/src/Sender/ConsoleSender.php @@ -35,6 +35,7 @@ public static function create(OutputInterface $output): self $renderer->register(new Renderer\Monolog($templateRenderer)); $renderer->register(new Renderer\Smtp()); $renderer->register(new Renderer\Http()); + $renderer->register(new Renderer\Profiler()); $renderer->register(new Renderer\Binary()); $renderer->register(new Renderer\Plain($templateRenderer)); diff --git a/src/Sender/Frontend/FrameMapper.php b/src/Sender/Frontend/FrameMapper.php index 91a6f5d7..da83fe1c 100644 --- a/src/Sender/Frontend/FrameMapper.php +++ b/src/Sender/Frontend/FrameMapper.php @@ -20,7 +20,7 @@ public function map(Frame $frame): Event Frame\Sentry\SentryStore::class => (new Mapper\SentryStore())->map($frame), Frame\Sentry\SentryEnvelope::class => (new Mapper\SentryEnvelope())->map($frame), Frame\Monolog::class => (new Mapper\Monolog())->map($frame), - Frame\Profiler\Payload::class => (new Mapper\Profiler())->map($frame), + Frame\Profiler::class => (new Mapper\Profiler())->map($frame), default => throw new \InvalidArgumentException('Unknown frame type ' . $frame::class), }; } diff --git a/src/Sender/Frontend/Mapper/Profiler.php b/src/Sender/Frontend/Mapper/Profiler.php index 3d4776dd..7c7accd2 100644 --- a/src/Sender/Frontend/Mapper/Profiler.php +++ b/src/Sender/Frontend/Mapper/Profiler.php @@ -12,12 +12,12 @@ */ final class Profiler { - public function map(\Buggregator\Trap\Proto\Frame\Profiler\Payload $frame): Event + public function map(\Buggregator\Trap\Proto\Frame\Profiler $frame): Event { return new Event( uuid: Uuid::generate(), type: 'profiler', - payload: $frame->payload, + payload: $frame->payload->toArray(), timestamp: (float)$frame->time->format('U.u'), ); } diff --git a/src/Service/FilesObserver/Filter/XHProf.php b/src/Service/FilesObserver/Filter/XHProf.php index 50e8bf69..b01b072a 100644 --- a/src/Service/FilesObserver/Filter/XHProf.php +++ b/src/Service/FilesObserver/Filter/XHProf.php @@ -29,10 +29,14 @@ public function convert(FileInfo $file): \Traversable $payload = $this->dataToPayload($data); $payload['date'] = $file->mtime; $payload['hostname'] = \explode('.', $file->getName(), 2)[0]; + $payload['filename'] = $file->getName(); - yield new ProfilerFrame\Payload( - $payload, + yield new ProfilerFrame( + ProfilerFrame\Payload::fromArray($payload, ProfilerFrame\Type::XHProf), ); + // yield new ProfilerFrame( + // ProfilerFrame\Payload::fromFile($file), + // ); } catch (\Throwable $e) { // todo log var_dump($e->getMessage());