Skip to content

Commit b6840cd

Browse files
authored
Merge pull request #6321 from rumpfc/feature-timer-record-callable
2 parents 6d81280 + 4a122f3 commit b6840cd

File tree

8 files changed

+173
-3
lines changed

8 files changed

+173
-3
lines changed

system/Common.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,19 +1076,25 @@ function stringify_attributes($attributes, bool $js = false): string
10761076
if (! function_exists('timer')) {
10771077
/**
10781078
* A convenience method for working with the timer.
1079-
* If no parameter is passed, it will return the timer instance,
1080-
* otherwise will start or stop the timer intelligently.
1079+
* If no parameter is passed, it will return the timer instance.
1080+
* If callable is passed, it measures time of callable and
1081+
* returns its return value if any.
1082+
* Otherwise will start or stop the timer intelligently.
10811083
*
10821084
* @return mixed|Timer
10831085
*/
1084-
function timer(?string $name = null)
1086+
function timer(?string $name = null, ?callable $callable = null)
10851087
{
10861088
$timer = Services::timer();
10871089

10881090
if (empty($name)) {
10891091
return $timer;
10901092
}
10911093

1094+
if ($callable !== null) {
1095+
return $timer->record($name, $callable);
1096+
}
1097+
10921098
if ($timer->has($name)) {
10931099
return $timer->stop($name);
10941100
}

system/Debug/Timer.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,22 @@ public function has(string $name): bool
126126
{
127127
return array_key_exists(strtolower($name), $this->timers);
128128
}
129+
130+
/**
131+
* Executes callable and measures its time.
132+
* Returns its return value if any.
133+
*
134+
* @param string $name The name of the timer
135+
* @param callable $callable callable to be executed
136+
*
137+
* @return array|bool|float|int|object|resource|string|null
138+
*/
139+
public function record(string $name, callable $callable)
140+
{
141+
$this->start($name);
142+
$returnValue = $callable();
143+
$this->stop($name);
144+
145+
return $returnValue;
146+
}
129147
}

tests/system/Debug/TimerTest.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111

1212
namespace CodeIgniter\Debug;
1313

14+
use ArgumentCountError;
1415
use CodeIgniter\Test\CIUnitTestCase;
16+
use ErrorException;
17+
use RuntimeException;
1518

1619
/**
1720
* @internal
@@ -121,4 +124,95 @@ public function testReturnsNullGettingElapsedTimeOfNonTimer()
121124

122125
$this->assertNull($timer->getElapsedTime('test1'));
123126
}
127+
128+
public function testRecordFunctionNoReturn()
129+
{
130+
$timer = new Timer();
131+
$returnValue = $timer->record('longjohn', static function () { usleep(100000); });
132+
133+
$this->assertGreaterThanOrEqual(0.1, $timer->getElapsedTime('longjohn'));
134+
$this->assertNull($returnValue);
135+
}
136+
137+
public function testRecordFunctionWithReturn()
138+
{
139+
$timer = new Timer();
140+
$returnValue = $timer->record('longjohn', static function () {
141+
usleep(100000);
142+
143+
return 'test';
144+
});
145+
146+
$this->assertGreaterThanOrEqual(0.1, $timer->getElapsedTime('longjohn'));
147+
$this->assertSame('test', $returnValue);
148+
}
149+
150+
public function testRecordArrowFunction()
151+
{
152+
$timer = new Timer();
153+
$returnValue = $timer->record('longjohn', static fn () => strlen('CI4'));
154+
155+
$this->assertLessThan(0.1, $timer->getElapsedTime('longjohn'));
156+
$this->assertSame(3, $returnValue);
157+
}
158+
159+
public function testRecordThrowsException()
160+
{
161+
$this->expectException(RuntimeException::class);
162+
163+
$timer = new Timer();
164+
$timer->record('ex', static function () { throw new RuntimeException(); });
165+
}
166+
167+
public function testRecordThrowsErrorOnCallableWithParams()
168+
{
169+
if (version_compare(PHP_VERSION, '8.0.0') >= 0) {
170+
$this->expectException(ArgumentCountError::class);
171+
} else {
172+
$this->expectException(ErrorException::class);
173+
}
174+
175+
$timer = new Timer();
176+
$timer->record('error', 'strlen');
177+
}
178+
179+
public function testCommonNoNameExpectTimer()
180+
{
181+
$returnValue = timer();
182+
183+
$this->assertInstanceOf(Timer::class, $returnValue);
184+
}
185+
186+
public function testCommonWithNameExpectTimer()
187+
{
188+
$returnValue = timer('test');
189+
190+
$this->assertInstanceOf(Timer::class, $returnValue);
191+
$this->assertTrue($returnValue->has('test'));
192+
}
193+
194+
public function testCommonNoNameCallableExpectTimer()
195+
{
196+
$returnValue = timer(null, static fn () => strlen('CI4'));
197+
198+
$this->assertInstanceOf(Timer::class, $returnValue);
199+
}
200+
201+
public function testCommonCallableExpectNoReturn()
202+
{
203+
$returnValue = timer('common', static function () { usleep(100000); });
204+
205+
$this->assertNotInstanceOf(Timer::class, $returnValue);
206+
$this->assertNull($returnValue);
207+
$this->assertGreaterThanOrEqual(0.1, timer()->getElapsedTime('common'));
208+
}
209+
210+
public function testCommonCallableExpectWithReturn()
211+
{
212+
$returnValue = timer('common', static fn () => strlen('CI4'));
213+
214+
$this->assertNotInstanceOf(Timer::class, $returnValue);
215+
$this->assertSame(3, $returnValue);
216+
$this->assertLessThanOrEqual(0.1, timer()->getElapsedTime('common'));
217+
}
124218
}

user_guide_src/source/changelogs/v4.3.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Enhancements
4343
- SQLite ``BaseConnection::getIndexData()`` now can return pseudo index named ``PRIMARY`` for `AUTOINCREMENT` column, and each returned index data has ``type`` property.
4444
- Added ``spark filter:check`` command to check the filters for a route. See :ref:`Controller Filters <spark-filter-check>` for the details.
4545
- Now **Encryption** can decrypt data encrypted with CI3's Encryption. See :ref:`encryption-compatible-with-ci3`.
46+
- Added method ``Timer::record()`` to measure performance in a callable. Also enhanced common function ``timer()`` to accept optional callable.
4647
- Now ``spark routes`` command shows route names. See :ref:`URI Routing <routing-spark-routes>`.
4748

4849
Changes

user_guide_src/source/testing/benchmark.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ and stop timers:
3838

3939
.. literalinclude:: benchmark/003.php
4040

41+
Since v4.3.0, if you use very small code blocks to benchmark, you can also use the ``record()`` method. It accepts
42+
a no-parameter callable and measures its execution time. Methods ``start()`` and ``stop()`` will be called
43+
automatically around the function call.
44+
45+
.. literalinclude:: benchmark/010.php
46+
47+
You can also return the callable's return value for further processing.
48+
49+
.. literalinclude:: benchmark/011.php
50+
51+
The same functionality is also available when passing callable to ``timer()`` as second parameter.
52+
53+
.. literalinclude:: benchmark/012.php
54+
4155
Viewing Your Benchmark Points
4256
=============================
4357

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
$benchmark->record('slow_function', static function () { slow_function('...'); });
4+
5+
/*
6+
* Same as:
7+
*
8+
* $benchmark->start('slow_function');
9+
* slow_function('...');
10+
* $benchmark->stop('slow_function');
11+
*/
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
$length = $benchmark->record('string length', static fn () => strlen('CI4'));
4+
5+
/*
6+
* $length = 3
7+
*
8+
* Same as:
9+
*
10+
* $benchmark->start('string length');
11+
* $length = strlen('CI4');
12+
* $benchmark->stop('string length');
13+
*/
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
$length = timer('string length', static fn () => strlen('CI4'));
4+
5+
/*
6+
* $length = 3
7+
*
8+
* Same as:
9+
*
10+
* timer('string length');
11+
* $length = strlen('CI4');
12+
* timer('string length');
13+
*/

0 commit comments

Comments
 (0)