From c4378a4ddec25055d478b7f9a38c6acbaeed4133 Mon Sep 17 00:00:00 2001 From: Petr Knap <8299754+petrknap@users.noreply.github.com> Date: Tue, 15 Oct 2024 20:24:05 +0200 Subject: [PATCH] feat: implemented support for touching profiling --- README.md | 62 ++++++++++++------- .../ProfilingHasBeenAlreadyFinished.php | 1 - src/Profiling.php | 30 +++++++-- tests/ProfilingTest.php | 21 ++++++- tests/ReadmeTest.php | 7 ++- 5 files changed, 89 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 1407d26..83ed8b3 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ This tool allows you to monitor performance and detect memory leaks as well as inconsistent performance behavior of your application over time. -## Short-term profiling +## Basic profiling -For **short-term profiling** you can use a [profiling helper](./src/Profiling.php). +For basic profiling you can use a [profiling helper](./src/Profiling.php). The [`Profiling`](./src/Profiling.php) will allow you to profile between `start` and `finish` methods calls. ```php @@ -17,10 +17,10 @@ $profile = $profiling->finish(); printf('It took %.1f s to do something.', $profile->getDuration()); ``` -The [`Profiling`](./src/Profiling.php) is simple - **cannot be turned on and off** easily. -So a [profiler](./src/ProfilerInterface.php) was created for the purpose of hard-coded long-term profiling. +The [`Profiling`](./src/Profiling.php) is simple and **cannot be turned on and off** easily. +So a [profiler](./src/ProfilerInterface.php) was created for the purpose of hard-coded more complex profiling. -## Long-term profiling +## Complex profiling Request a [profiler](./src/ProfilerInterface.php) as a dependency and call a `profile` method on it. @@ -28,9 +28,7 @@ Request a [profiler](./src/ProfilerInterface.php) as a dependency and call a `pr namespace PetrKnap\Profiler; function doSomething(ProfilerInterface $profiler): string { - // do something without profiling return $profiler->profile(function (): string { - // do something return 'something'; })->process(fn (ProfileInterface $profile) => printf( 'It took %.1f s to do something.', @@ -50,40 +48,62 @@ echo doSomething(new Profiler()); echo doSomething(new NullProfiler()); ``` -### Cascade profiling +## Useful features -The `profile` method provides you a nested [profiler](./src/ProfilerInterface.php) that you can use for more detailed cascade profiling. +### Touching a profile + +If you need to **measure the current values**, just call the `touch` method on the [`Profiling`](./src/Profiling.php). ```php namespace PetrKnap\Profiler; -echo (new Profiler())->profile(function (ProfilerInterface $profiler): string { - // do something before something - return doSomething($profiler); -})->process(fn (ProfileInterface $profile) => printf( - 'It took %.1f s to do something before something and something, there are %d children profiles.', - $profile->getDuration(), - count($profile->getChildren()), -)); +$profiling = Profiling::start(); +// do something +$profiling->touch(); +// do something more +$profile = $profiling->finish(); + +printf('There are %d memory usages.', count($profile->getMemoryUsages())); ``` +If you want to automate it then use a [tick listening](#tick-listening). +Or you can use a more practical [cascade profiling](#cascade-profiling). + ### Tick listening -For greater precision, you can use measurements at each `N` tick. -This will result in **very detailed code tracking**, which can degrade the performance of the monitored application. +For greater precision, you can use **measurements at each `N` tick**. ```php -declare(ticks=3); // this declaration is important (N=3) +declare(ticks=2); // this declaration is important (N=2) namespace PetrKnap\Profiler; $profiling = Profiling::start(listenToTicks: true); -doSomething(new NullProfiler()); +(fn () => 'something')(); $profile = $profiling->finish(); printf('There are %d memory usage records.', count($profile->getMemoryUsages())); ``` +This will result in **very detailed code tracking**, which can degrade the performance of the monitored application. + +### Cascade profiling + +The `profile` method provides you a nested [profiler](./src/ProfilerInterface.php) that you can use for more detailed cascade profiling. + +```php +namespace PetrKnap\Profiler; + +$profile = (new Profiler())->profile(function (ProfilerInterface $profiler): void { + // do something + $profiler->profile(function (): void { + // do something more + }); +}); + +printf('There are %d memory usage records.', count($profile->getMemoryUsages())); +``` + --- Run `composer require petrknap/profiler` to install it. diff --git a/src/Exception/ProfilingHasBeenAlreadyFinished.php b/src/Exception/ProfilingHasBeenAlreadyFinished.php index 4bc940e..084e3ad 100644 --- a/src/Exception/ProfilingHasBeenAlreadyFinished.php +++ b/src/Exception/ProfilingHasBeenAlreadyFinished.php @@ -7,7 +7,6 @@ use LogicException; /** - * @todo rename to `ProfilingCouldNotBeFinished` * @todo remove implementation of {@see ProfilerException} */ final class ProfilingHasBeenAlreadyFinished extends LogicException implements ProfilerException, ProfilingException diff --git a/src/Profiling.php b/src/Profiling.php index 3a55e63..b6fa3bc 100644 --- a/src/Profiling.php +++ b/src/Profiling.php @@ -24,18 +24,26 @@ public static function start( return new self($profile, $listenToTicks); } + /** + * @throws Exception\ProfilingHasBeenAlreadyFinished + */ + public function touch(): void + { + $this->checkProfileState(); + + $this->profile->tickHandler(); + } + /** * @throws Exception\ProfilingHasBeenAlreadyFinished */ public function finish(): ProfileInterface { - try { - $this->profile->finish(); + $this->checkProfileState(); - return $this->profile; - } catch (Exception\ProfileException $profileException) { - throw new Exception\ProfilingHasBeenAlreadyFinished(previous: $profileException); - } + $this->profile->finish(); + + return $this->profile; } /** @@ -74,4 +82,14 @@ public function profile(callable $callable): ProcessableProfileInterface & Profi } }; } + + /** + * @throws Exception\ProfilingHasBeenAlreadyFinished + */ + private function checkProfileState(): void + { + if ($this->profile->getState() === ProfileState::Finished) { + throw new Exception\ProfilingHasBeenAlreadyFinished(); + } + } } diff --git a/tests/ProfilingTest.php b/tests/ProfilingTest.php index 1c5b2e0..6374c1b 100644 --- a/tests/ProfilingTest.php +++ b/tests/ProfilingTest.php @@ -17,7 +17,26 @@ public function testProfiles(): void self::assertEquals(1, round($profile->getDuration())); } - public function testThrowsOnSecondFinishCall(): void + public function testTouchesProfile(): void + { + $profiling = Profiling::start(); + $profiling->touch(); + $profile = $profiling->finish(); + + self::assertCount(2 + 1, $profile->getMemoryUsages()); + } + + public function testTouchThrowsOnFinishedProfile(): void + { + $profiling = Profiling::start(); + $profiling->finish(); + + self::expectException(Exception\ProfilingHasBeenAlreadyFinished::class); + + $profiling->touch(); + } + + public function testFinishThrowsOnFinishedProfile(): void { $profiling = Profiling::start(); $profiling->finish(); diff --git a/tests/ReadmeTest.php b/tests/ReadmeTest.php index d26f0f1..33fd250 100644 --- a/tests/ReadmeTest.php +++ b/tests/ReadmeTest.php @@ -20,11 +20,12 @@ public static function getPathToMarkdownFile(): string public static function getExpectedOutputsOfPhpExamples(): iterable { return [ - 'short-term-profiling' => 'It took 0.0 s to do something.', - 'long-term-profiling' => '', + 'basic-profiling' => 'It took 0.0 s to do something.', + 'complex-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', + 'touching-profile' => 'There are 3 memory usages.', 'tick-listening' => 'There are 3 memory usage records.', + 'cascade-profiling' => 'There are 4 memory usage records.', ]; } }