From 8fd524e57598facd7a900ce34565329fbff87996 Mon Sep 17 00:00:00 2001 From: Petr Knap <8299754+petrknap@users.noreply.github.com> Date: Sun, 13 Oct 2024 10:20:29 +0200 Subject: [PATCH] feat: added support for recording custom data --- README.md | 17 ++++++++++++++ src/Exception/ProfilerCouldNotRecordData.php | 11 +++++++++ src/NullProfiler.php | 9 ++++++++ src/Profile.php | 24 ++++++++++++++++++++ src/ProfileInterface.php | 5 ++++ src/Profiler.php | 5 ++++ src/ProfilerInterface.php | 5 ++++ src/Profiling.php | 9 ++++++++ tests/ProfileInterfaceTestTrait.php | 17 ++++++++++++++ tests/ReadmeTest.php | 1 + 10 files changed, 103 insertions(+) create mode 100644 src/Exception/ProfilerCouldNotRecordData.php diff --git a/README.md b/README.md index 47d113d..e7ba0e3 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,23 @@ echo (new Profiler())->profile(function (ProfilerInterface $profiler): string { )); ``` +### Recording custom data + +A [profiler](./src/ProfilerInterface.php) provides the `record` method to record custom data in a [profile](./src/ProfileInterface.php). + +```php +namespace PetrKnap\Profiler; + +$profile = (new Profiler())->profile(function (ProfilerInterface $profiler): void { + for ($i = 0; $i < 5; $i++) { + $profiler->record('main_loop', $i); + } + $profiler->record('main_loop', 'done'); +}); + +echo implode(' -> ', $profile->getRecords('main_loop')); +``` + --- Run `composer require petrknap/profiler` to install it. diff --git a/src/Exception/ProfilerCouldNotRecordData.php b/src/Exception/ProfilerCouldNotRecordData.php new file mode 100644 index 0000000..f632b5f --- /dev/null +++ b/src/Exception/ProfilerCouldNotRecordData.php @@ -0,0 +1,11 @@ + */ @@ -35,6 +37,11 @@ final class Profile implements ProcessableProfileInterface, ProfileWithOutputInt */ private Optional $outputOption; + /** + * @var array> + */ + private array $records = []; + public function __construct() { $this->state = ProfileState::Created; @@ -140,6 +147,23 @@ public function getMemoryUsages(bool $sortedByTime = self::SORTED_BY_TIME): arra ); } + public function addRecord(string $type, mixed $data): void + { + $records = $this->records[$type] ?? []; + $records[sprintf(self::MICROTIME_FORMAT, microtime(as_float: true))] = $data; + $this->records[$type] = $records; + } + + public function getRecords(string $type, bool $sortedByTime = self::SORTED_BY_TIME): array + { + return self::expandRecords( + $this->records[$type] ?? [], + $this->children, + __FUNCTION__, + sortedByKey: $sortedByTime, + ); + } + /** * @template TRecord of mixed * diff --git a/src/ProfileInterface.php b/src/ProfileInterface.php index e55b25c..0dc3961 100644 --- a/src/ProfileInterface.php +++ b/src/ProfileInterface.php @@ -25,4 +25,9 @@ public function getMemoryUsageChange(): int; * @return array bytes at {@see microtime} */ public function getMemoryUsages(): array; + + /** + * @return array data at {@see microtime} + */ + public function getRecords(string $type): array; } diff --git a/src/Profiler.php b/src/Profiler.php index ad05ac8..648f8b9 100644 --- a/src/Profiler.php +++ b/src/Profiler.php @@ -16,4 +16,9 @@ public function profile(callable $callable): ProcessableProfileInterface & Profi return $profile; // @phpstan-ignore return.type } + + public function record(string $type, mixed $data): void + { + throw new Exception\ProfilerCouldNotRecordData(); + } } diff --git a/src/ProfilerInterface.php b/src/ProfilerInterface.php index 3a73c08..b23a60d 100644 --- a/src/ProfilerInterface.php +++ b/src/ProfilerInterface.php @@ -14,4 +14,9 @@ interface ProfilerInterface * @return ProcessableProfileInterface & ProfileWithOutputInterface */ public function profile(callable $callable): ProcessableProfileInterface & ProfileWithOutputInterface; + + /** + * @throws Exception\ProfilerCouldNotRecordData + */ + public function record(string $type, mixed $data): void; } diff --git a/src/Profiling.php b/src/Profiling.php index 6664ab0..391cde3 100644 --- a/src/Profiling.php +++ b/src/Profiling.php @@ -61,6 +61,15 @@ public function profile(callable $callable): ProcessableProfileInterface & Profi return $profile; } + + public function record(string $type, mixed $data): void + { + try { + $this->parentProfile->addRecord($type, $data); + } catch (Exception\ProfileException $profileException) { + throw new Exception\ProfilerCouldNotRecordData(previous: $profileException); + } + } }; } } diff --git a/tests/ProfileInterfaceTestTrait.php b/tests/ProfileInterfaceTestTrait.php index 104452c..08476f4 100644 --- a/tests/ProfileInterfaceTestTrait.php +++ b/tests/ProfileInterfaceTestTrait.php @@ -56,4 +56,21 @@ public function testProfilesMemoryUsages(): void self::assertEquals(2, array_shift($memoryUsages)); self::assertEquals(3, array_shift($memoryUsages)); } + + public function testRecordsDataOfCustomType(): void + { + $profile = new Profile(); + $profile->addRecord('a', 'a1'); + $profile->addRecord('b', 'b1'); + $profile->addRecord('a', 'a2'); + + self::assertSame( + ['a1', 'a2'], + array_values($profile->getRecords('a')), + ); + self::assertSame( + ['b1'], + array_values($profile->getRecords('b')), + ); + } } diff --git a/tests/ReadmeTest.php b/tests/ReadmeTest.php index 87897ba..4f0e98f 100644 --- a/tests/ReadmeTest.php +++ b/tests/ReadmeTest.php @@ -24,6 +24,7 @@ public static function getExpectedOutputsOfPhpExamples(): iterable 'long-term-profiling' => '', 'how-to-enable-disable-it' => 'It took 0.0 s to do something.' . 'something' . 'something', 'cascade-profiling' => 'It took 0.0 s to do something.' . 'It took 0.0 s to do something before something and something, there are 1 children profiles.' . 'something', + 'recording-custom-data' => '0 -> 1 -> 2 -> 3 -> 4 -> done', ]; } }