Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature for Timer to measure callable performance #6321

Merged
merged 15 commits into from
Aug 5, 2022

Conversation

rumpfc
Copy link
Contributor

@rumpfc rumpfc commented Jul 31, 2022

Description
This is a convinience feature enhancement for Timer to measure short code blocks without adding its start/stop methods before and after the block. The new method record() accepts a string and a callable as parameters.

// Example
$benchmark->record('slow_function', function () { slow_function('...'); });

Which is the equivalent to

$benchmark->start('slow_function');
slow_function('...');
$benchmark->stop('slow_function');

It can also return the callables return value

$length = $benchmark->record('string length', fn () => strlen('CI4'));
// $length = 3

Alternatively the common function timer() also accepts an optional callable for the same mechanics. Leaving out the 2nd parameter behaves like before (returning a Timer instance).

$length = timer('string length', fn () => strlen('CI4'));

Checklist:

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

@kenjis kenjis added 4.3 enhancement PRs that improve existing functionalities labels Jul 31, 2022
Copy link
Member

@MGatner MGatner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice PR! I like this new feature, especially the inclusion of callable which (in my opinion) allows the very valuable array notation benchmarking, not just closures. Our timer() function is starting to get a bit abstruse, but I'm okay with this expansion unless someone else objects.

Thanks for the contribution @rumpfc! A few notes inline, feel free to discuss.

system/Common.php Outdated Show resolved Hide resolved
system/Debug/Timer.php Outdated Show resolved Hide resolved
user_guide_src/source/changelogs/v4.3.0.rst Outdated Show resolved Hide resolved
@kenjis
Copy link
Member

kenjis commented Aug 1, 2022

@rumpfc rumpfc requested a review from MGatner August 1, 2022 17:32
@rumpfc rumpfc requested a review from kenjis August 2, 2022 07:55
$this->expectException(RuntimeException::class);

$timer = new Timer();
$timer->record('ex', static function () { throw new RuntimeException(); });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a question. If by mistake I made the callable throw an exception, then how can I get the elapsed time since $this->stop($name) will not be called? Should the invocation of the callable be wrapped in a try-finally ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an exception is thrown and you don't catch it, then I think you might have other problems than worrying about the time stoppage.

But you also have a point. It does not handle the case of purposely catching exceptions for error handling.

try {
    $benchmark->record('test', 'func_that_throws');
} catch (Throwable $e) {
    // stop() isn't called yet
}

@kenjis shall we cover this case as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if an exception is thrown after $benchmark->start() or in $iterator->run()?

Copy link
Contributor Author

@rumpfc rumpfc Aug 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For $iterator->run(), nothing is caught internally. It would not record anything in the report and would go out of the method, ignoring all follow up Closures.

For $benchmark->start(), you would need to call stop() inside the catch or finally block.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need try-finally.

An exception should be thrown in an exceptional case, and not recoverable.
Normally, a developer would modify the code to prevent the exception from occurring.

If a developer really wants to know the elapsed time when an exception is thrown, they can catch and measure it by themselves.

@kenjis
Copy link
Member

kenjis commented Aug 3, 2022

Please fix coding style and failed tests.

@rumpfc
Copy link
Contributor Author

rumpfc commented Aug 4, 2022

Calling strlen without any parameters has 2 different behaviours between PHP7 and PHP8. That's why the unit test keep failing for 7.4, but not for 8.0 and 8.1:

// PHP 7.4 and before
strlen();   // throws ErrorException (extends Exception)

// PHP 8.0 and after
strlen();   // throws ArgumentCountError (extends TypeError)

@kenjis I can offer 2 solutions:

  1. Unit test expects a Throwable to be thrown
  2. I catch ErrorException inside record() and throw an ArgumentCountError

@kenjis
Copy link
Member

kenjis commented Aug 4, 2022

@rumpfc Not specifically catch Exception in the method. So it's weird to catch only ErrorException.
I don't know all ErrorExceptions in PHP7 will be ArgumentCountError in PHP8.

So it's okay to expect Throwable in the test.
Also, please comment why we need to use Throwable.

Copy link
Member

@kenjis kenjis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@MGatner MGatner merged commit b6840cd into codeigniter4:4.3 Aug 5, 2022
@MGatner
Copy link
Member

MGatner commented Aug 5, 2022

Thanks all!

@rumpfc rumpfc deleted the feature-timer-record-callable branch August 11, 2022 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4.3 enhancement PRs that improve existing functionalities
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants