package.xml 0000664 0001750 0001750 00000016142 14715550562 014013 0 ustar tstarling tstarling
excimerpecl.php.netInterrupting timer and low-overhead sampling profilerCalls a user-defined callback when a one-shot or periodic timer expires. Also a sampling profiler backend, which aggregates backtraces collected by a periodic timer.Tim Starlingtstarlingtstarling@wikimedia.orgyesKunal Mehtalegoktmlegoktm@debian.orgyesTimo Tijhofkrinklekrinkle@fastmail.comyes2024-11-151.2.31.2.3stablestableApache 2.0
- Fix start time stagger, broken by previous release
7.1.01.8.0unixexcimer2024-07-311.2.21.2.2
- Fix PHP 8.4 compatibility (patch by Remi Collet)
2024-02-291.2.11.2.1
- Fix compiler warning in excimer_log
- Fix invalid OS requirement in package.xml, allow all "unix"
2024-02-281.2.01.2.0
- Add support for BSD and macOS (only real/wall-clock, no CPU timer).
- Add excimer.default_max_depth and default to 1000 (previously unlimited).
2023-03-131.1.11.1.1
- Restore support for PHP 7.1-7.3
2023-03-011.1.01.1.0
- Fix leading semi-colon in ExcimerLog::formatCollapsed output
- Change ExcimerLog::formatCollapsed to mark truncated frames
- Add support for PHP 8.2
- Add ExcimerLog::getSpeedscopeData for Speedscope support
2022-05-071.0.41.0.4
- Fix arginfo error for PHP 7.1
2022-05-041.0.31.0.3
- Set return type on ExcimerLog::aggregateByFunction
- Set return type on ExcimerProfiler::getLog
2021-10-161.0.21.0.2
- Fix Iterator prototypes for PHP 8.1
- Add extension version in phpinfo()
2021-09-291.0.11.0.1
- Filter null bytes out of the collapsed output
- Fix segfault in ZTS mode
- Fix [-Wincompatible-pointer-types] with PHP 8
2021-02-261.0.01.0.0
- Initial PECL release
excimer-1.2.3/stubs/ExcimerLog.php 0000664 0001750 0001750 00000010315 14715550562 017674 0 ustar tstarling tstarling getLog() as $entry ) {
* var_dump( $entry->getTrace() );
* }
*/
class ExcimerLog implements ArrayAccess, Iterator {
/**
* ExcimerLog is not constructible by user code. Objects of this type
* are available via:
* - ExcimerProfiler::getLog()
* - ExcimerProfiler::flush()
* - The callback to ExcimerProfiler::setFlushCallback()
*/
private final function __construct() {
}
/**
* Aggregate the stack traces and convert them to a line-based format
* understood by Brendan Gregg's FlameGraph utility. Each stack trace is
* represented as a series of function names, separated by semicolons.
* After this identifier, there is a single space character, then a number
* giving the number of times the stack appeared. Then there is a line
* break. This is repeated for each unique stack trace.
*
* @return string
*/
function formatCollapsed() {
}
/**
* Produce an array with an element for every function which appears in
* the log. The key is a human-readable unique identifier for the function,
* method or closure. The value is an associative array with the following
* elements:
*
* - self: The number of events in which the function itself was running,
* no other userspace function was being called. This includes time
* spent in internal functions that this function called.
* - inclusive: The number of events in which this function appeared
* somewhere in the stack.
*
* And optionally the following elements, if they are relevant:
*
* - file: The filename in which the function appears
* - line: The exact line number at which the first relevant event
* occurred.
* - class: The class name in which the method is defined
* - function: The name of the function or method
* - closure_line: The line number at which the closure was defined
*
* The event counts in the "self" and "inclusive" fields are adjusted for
* overruns. They represent an estimate of the number of profiling periods
* in which those functions were present.
*
* @return array
*/
function aggregateByFunction() {
}
/**
* Get an array which can be JSON encoded for import into speedscope
*
* @return array
*/
function getSpeedscopeData() {
}
/**
* Get the total number of profiling periods represented by this log.
*
* @return int
*/
function getEventCount() {
}
/**
* Get the current ExcimerLogEntry object. Part of the Iterator interface.
*
* @return ExcimerLogEntry|null
*/
function current() {
}
/**
* Get the current integer key or null. Part of the Iterator interface.
*
* @return int|null
*/
function key() {
}
/**
* Advance to the next log entry. Part of the Iterator interface.
*/
function next() {
}
/**
* Rewind back to the first log entry. Part of the Iterator interface.
*/
function rewind() {
}
/**
* Check if the current position is valid. Part of the Iterator interface.
*
* @return bool
*/
function valid() {
}
/**
* Get the number of log entries contained in this log. This is always less
* than or equal to the number returned by getEventCount(), which includes
* overruns.
*
* @return int
*/
function count() {
}
/**
* Determine whether a log entry exists at the specified array offset.
* Part of the ArrayAccess interface.
*
* @param int $offset
* @return bool
*/
function offsetExists( $offset ) {
}
/**
* Get the ExcimerLogEntry object at the specified array offset.
*
* @param int $offset
* @return ExcimerLogEntry|null
*/
function offsetGet( $offset ) {
}
/**
* This function is included for compliance with the ArrayAccess interface.
* It raises a warning and does nothing.
*
* @param int $offset
* @param mixed $value
*/
function offsetSet( $offset, $value ) {
}
/**
* This function is included for compliance with the ArrayAccess interface.
* It raises a warning and does nothing.
*
* @param int $offset
*/
function offsetUnset( $offset ) {
}
}
excimer-1.2.3/stubs/ExcimerLogEntry.php 0000664 0001750 0001750 00000002372 14715550562 020722 0 ustar tstarling tstarling setCallback( $callback );
* $timer->setInterval( $interval );
* $timer->start();
* return $timer;
*
* Note that you must keep a copy of the return value. If it goes out of scope,
* the object will be destroyed and the timer will stop.
*
* If the callback is not callable, a warning is raised and null is returned.
*
* @param callable $callback
* @param float $interval
* @return ExcimerTimer|null
*/
function excimer_set_timeout( $callback, $interval ) {
}
excimer-1.2.3/tests/cpu.phpt 0000664 0001750 0001750 00000014316 14715550562 016620 0 ustar tstarling tstarling --TEST--
ExcimerProfiler CPU profile
--SKIPIF--
--INI--
zend.assertions=1
--FILE--
getStartLine();
$c2line = (new ReflectionFunction($closure1))->getStartLine();
class C {
function member() {
return md5(str_repeat('x', 100000));
}
}
$obj = new C;
function foo() {
bar();
}
function bar() {
global $closure1, $closure2, $obj;
$closure1();
$closure2();
$obj->member();
baz();
}
function baz() {
return md5(str_repeat('x', 100000));
}
$profiler = new ExcimerProfiler;
$profiler->setEventType(EXCIMER_CPU);
$profiler->setPeriod(0.1);
$profiler->start();
while (count($profiler->getLog()) < 30) {
for ($i = 0; $i < 100; $i++) {
foo();
}
}
$profiler->stop();
$log = $profiler->flush();
function sort_lines($text) {
$lines = explode("\n", trim($text));
sort($lines);
return implode("\n", $lines);
}
// It takes 3 seconds to populate the profiler log, which is quite expensive,
// so we do as many tests as possible on the result while we have it.
// Test formatCollapsed
$collapsed = $log->formatCollapsed();
// Normalize variable file path, and sample count
$collapsed = str_replace(__FILE__, '/data/cpu.php', $collapsed);
$collapsed = preg_replace('/ \d+$/m', ' 00', $collapsed);
$collapsed = sort_lines($collapsed);
echo "formatCollapsed:\n$collapsed\n\n";
// Test getSpeedscopeData
function check_speedscope($speedscope, $name, $expected) {
$ok = false;
foreach ($speedscope['profiles'][0]['samples'] as $sample) {
if (count($sample) !== count($expected)) {
break;
}
$matches = 0;
foreach ($expected as $i => $expectedFrame) {
if (!isset($sample[$i])) {
break;
}
$frame = $speedscope['shared']['frames'][$sample[$i]];
if ($frame === $expectedFrame) {
$matches++;
} else {
break;
}
}
if ($matches === count($expected)) {
$ok = true;
break;
}
}
echo "getSpeedscopeData $name: " . ($ok ? "OK\n" : "FAILED\n");
if (!$ok) {
echo "Expected: ";
var_dump($expected);
echo "Actual: ";
var_dump($speedscope);
}
}
$speedscope = $log->getSpeedscopeData();
check_speedscope(
$speedscope,
'baz',
[
['name' => __FILE__, 'file' => __FILE__],
['name' => 'foo', 'file' => __FILE__],
['name' => 'bar', 'file' => __FILE__],
['name' => 'baz', 'file' => __FILE__]
]
);
// Test foreach (get_iterator handler)
$found = [];
$count = 0;
$eventCount = 0;
$traces = [];
$firstTimestamp = false;
$lastTimestamp = false;
foreach ($log as $entry) {
$trace = $entry->getTrace();
$traces[] = $trace;
if (count($trace) == 4
&& ($trace[0]['function'] ?? '') === 'baz'
&& ($trace[1]['function'] ?? '') === 'bar'
&& ($trace[2]['function'] ?? '') === 'foo'
&& ($trace[3]['function'] ?? '') === '')
{
$found['baz'] = true;
}
if (count($trace) == 4
&& ($trace[0]['closure_line'] ?? '') === $c1line
&& ($trace[1]['function'] ?? '') === 'bar'
&& ($trace[2]['function'] ?? '') === 'foo'
&& ($trace[3]['function'] ?? '') === '')
{
$found['closure1'] = true;
}
if (count($trace) == 4
&& ($trace[0]['closure_line'] ?? '') === $c2line
&& ($trace[1]['function'] ?? '') === 'bar'
&& ($trace[2]['function'] ?? '') === 'foo'
&& ($trace[3]['function'] ?? '') === '')
{
$found['closure2'] = true;
}
if (count($trace) == 4
&& ($trace[0]['class'] ?? '') === 'C'
&& ($trace[0]['function'] ?? '') === 'member'
&& ($trace[1]['function'] ?? '') === 'bar'
&& ($trace[2]['function'] ?? '') === 'foo'
&& ($trace[3]['function'] ?? '') === '')
{
$found['member'] = true;
}
if ($firstTimestamp === false) {
$firstTimestamp = $entry->getTimestamp();
}
if ($lastTimestamp !== false) {
assert(is_float($entry->getTimestamp()));
assert($entry->getTimestamp() >= 0);
assert($entry->getTimestamp() > $lastTimestamp);
}
$lastTimestamp = $entry->getTimestamp();
assert( $entry->getEventCount() > 0 );
$eventCount += $entry->getEventCount();
$count++;
}
assert( $log->getEventCount() === $eventCount );
function check_found($found, $func) {
if (isset($found[$func])) {
echo "foreach found $func: OK\n";
} else {
echo "foreach found $func: FAILED\n";
}
}
check_found($found, 'baz');
check_found($found, 'closure1');
check_found($found, 'closure2');
check_found($found, 'member');
$timestampDiff = $lastTimestamp - $firstTimestamp;
if ($timestampDiff > 1 && $timestampDiff < 1000) {
echo "getTimestamp: OK\n";
} else {
echo "getTimestamp: FAILED $timestampDiff\n";
}
echo 'ExcimerLog::count ' . ($count == $log->count() ? "OK\n" : "FAILED\n");
echo 'count(ExcimerLog) ' . ($count == count($log) ? "OK\n" : "FAILED\n");
// Test Iterator interface
$count = 0;
for ($log->rewind(); $log->valid(); $log->next(), $count++) {
$entry = $log->current();
assert(serialize($entry->getTrace()) == serialize($traces[$count]));
}
assert($count == $log->count());
// Test aggregateByFunction
// Typically the parent functions foo() and bar() will have self=0 and
// inclusive ~= 30. The other 4 functions will have a count of about 30/4 = 7.5.
// The probability of C::member() or baz() having a count of zero is about 1 in 5600.
$stats = $log->aggregateByFunction();
assert($stats['foo']['inclusive'] > 10);
assert($stats['foo']['self'] < 10);
assert($stats['bar']['inclusive'] > 10);
assert($stats['bar']['self'] < 10);
assert($stats['C::member']['inclusive'] > 0);
assert($stats['C::member']['self'] > 0);
assert($stats['baz']['inclusive'] > 0);
assert($stats['baz']['self'] > 0);
// Ensure $stats is sorted
$counts = array_column( $stats, 'inclusive' );
$sortedCounts = $counts;
rsort( $sortedCounts );
assert( $sortedCounts === $counts );
--EXPECT--
formatCollapsed:
/data/cpu.php;foo;bar;C::member 00
/data/cpu.php;foo;bar;baz 00
/data/cpu.php;foo;bar;{closure:/data/cpu.php(5)} 00
/data/cpu.php;foo;bar;{closure:/data/cpu.php(8)} 00
getSpeedscopeData baz: OK
foreach found baz: OK
foreach found closure1: OK
foreach found closure2: OK
foreach found member: OK
getTimestamp: OK
ExcimerLog::count OK
count(ExcimerLog) OK
excimer-1.2.3/tests/delayedPeriodic.phpt 0000664 0001750 0001750 00000002370 14715550562 021114 0 ustar tstarling tstarling --TEST--
ExcimerTimer periodic mode with initial delay
--SKIPIF--
--FILE--
setInterval(0.25);
$timer->setPeriod(0.5);
$expCount = 0;
$events = [];
$timer->setCallback(function($n) use (&$expCount, &$events) {
$expCount += $n;
$events[] = microtime(true);
});
$start = microtime(true);
$timer->start();
$elapsed = 0;
$interval = 10000;
while ($elapsed < 1400000) {
usleep($interval);
$elapsed += $interval;
}
$firstDelay = $events[0] - $start;
$secondDelay = $events[1] - $events[0];
$count = count($events);
if ($count === 3) {
echo "periodic event count: OK\n";
} else {
echo "periodic event count: FAIL - got $count\n";
}
if ($expCount === 3) {
echo "periodic expiration count: OK\n";
} else {
echo "periodic expiration count: FAIL - got $expCount\n";
}
if ($firstDelay >= 0.15 && $firstDelay <= 0.35) {
echo "first delay: OK\n";
} else {
echo "first delay: FAIL - got $firstDelay\n";
}
if ($secondDelay >= 0.4 && $secondDelay <= 0.6) {
echo "second delay: OK\n";
} else {
echo "second delay: FAIL - got $secondDelay\n";
}
--EXPECT--
periodic event count: OK
periodic expiration count: OK
first delay: OK
second delay: OK
excimer-1.2.3/tests/getTime.phpt 0000664 0001750 0001750 00000001562 14715550562 017426 0 ustar tstarling tstarling --TEST--
ExcimerProfiler::getTime
--SKIPIF--
--FILE--
setEventType(EXCIMER_REAL);
$timer->setPeriod(5);
$time = $timer->getTime();
if ($time == 0.0) {
echo "remaining time is zero when not started: OK\n";
} else {
echo "unexpected remaining time before starting: $time\n";
}
$timer->start();
sleep(1);
$time = $timer->getTime();
if ($time <= 4.0 && $time >= 3.0) {
echo "remaining time: OK\n";
} else {
echo "unexpected remaining time: $time\n";
}
$timer->stop();
$time = $timer->getTime();
if ($time == 0.0) {
echo "remaining time is zero after stopping: OK\n";
} else {
echo "unexpected remaining time after stopping: $time\n";
}
--EXPECTF--
remaining time is zero when not started: OK
remaining time: OK
remaining time is zero after stopping: OK
excimer-1.2.3/tests/maxDepth.phpt 0000664 0001750 0001750 00000001114 14715550562 017573 0 ustar tstarling tstarling --TEST--
ExcimerProfiler max depth
--SKIPIF--
--FILE--
setEventType(EXCIMER_REAL);
$profiler->setPeriod(0.1);
$profiler->setMaxDepth(5);
function foo( $depth ) {
global $profiler;
if ( $depth > 0 ) {
foo( $depth - 1 );
} else {
$profiler->start();
while (!count($profiler->getLog())) {
usleep(10000);
}
$profiler->stop();
}
}
foo( 20 );
$log = $profiler->flush();
echo $log->formatCollapsed() . "\n";
--EXPECTF--
excimer_truncated;foo;foo;foo;foo;foo;foo %d
excimer-1.2.3/tests/oneshot.phpt 0000664 0001750 0001750 00000001247 14715550562 017507 0 ustar tstarling tstarling --TEST--
ExcimerTimer one-shot mode
--SKIPIF--
--FILE--
setInterval(0.5);
$count = 0;
$timer->setCallback(function() use (&$count) {
$count++;
});
$timer->start();
$elapsed = 0;
$fired_at = 0;
$interval = 10000;
while ($elapsed < 600000) {
if ($count === 1 && $fired_at === 0) {
$fired_at = $elapsed;
}
usleep($interval);
$elapsed += $interval;
}
if ($count === 1 && $fired_at >= 400000 && $fired_at <= 550000) {
echo "oneshot: OK\n";
} else {
echo "oneshot: FAIL - count: $count, fired_at: $fired_at, elapsed: $elapsed\n";
}
--EXPECT--
oneshot: OK
excimer-1.2.3/tests/periodic.phpt 0000664 0001750 0001750 00000001252 14715550562 017622 0 ustar tstarling tstarling --TEST--
ExcimerTimer periodic mode
--SKIPIF--
--FILE--
setPeriod(0.1);
$callCount = 0;
$eventCount = 0;
$timer->setCallback(function($n) use (&$callCount, &$eventCount) {
$callCount ++;
$eventCount += $n;
});
$t = microtime(true);
$timer->start();
while (microtime(true) - $t < 1) {
usleep(10000);
}
if ($callCount > 5 && $callCount < 15) {
echo "call count: OK\n";
} else {
echo "call count: FAILED\n";
}
if ($eventCount > 5 && $eventCount < 15) {
echo "event count: OK\n";
} else {
echo "event count: FAILED - got $eventCount\n";
}
--EXPECT--
call count: OK
event count: OK
excimer-1.2.3/tests/real.phpt 0000664 0001750 0001750 00000001250 14715550562 016745 0 ustar tstarling tstarling --TEST--
ExcimerProfiler real time profile
--SKIPIF--
--FILE--
setEventType(EXCIMER_REAL);
$profiler->setPeriod(0.1);
for ($j = 0; $j < 3; $j++) {
$profiler->start();
for ($i = 0; $i < 10; $i++) {
foo();
}
$profiler->stop();
}
$log = $profiler->flush();
$found = 0;
foreach ($log as $entry) {
$trace = $entry->getTrace();
if (isset($trace[0]['function'])
&& $trace[0]['function'] === 'foo'
&& !isset($trace[1]['function']))
{
$found++;
}
}
if ($found > 11) {
echo "OK\n";
} else {
echo "FAILED\n";
}
--EXPECT--
OK
excimer-1.2.3/tests/stagger.phpt 0000664 0001750 0001750 00000001020 14715550562 017451 0 ustar tstarling tstarling --TEST--
ExcimerProfiler start time stagger
--SKIPIF--
--FILE--
setEventType(EXCIMER_REAL);
$profiler->setPeriod(0.999);
$profiler->start();
f1();
f2();
$log = $profiler->flush();
if (isset($log[0]) && $log[0]->getTrace()[0]['function'] === 'f2') {
echo "OK\n";
return;
}
}
echo "FAILED\n";
--EXPECT--
OK
excimer-1.2.3/tests/subprocess.phpt 0000664 0001750 0001750 00000000672 14715550562 020221 0 ustar tstarling tstarling --TEST--
Long-running subprocess does not interfere with ExcimerTimer
--SKIPIF--
--FILE--
setEventType(EXCIMER_REAL);
$timer->setInterval(5);
$timer->start();
shell_exec('sleep 10 >/dev/null 2>&1 &');
$before = microtime(true);
$timer->stop();
$after = microtime(true);
if ( ( $after - $before ) < 10 ) {
echo 'OK';
}
--EXPECTF--
OK
excimer-1.2.3/tests/timeout.phpt 0000664 0001750 0001750 00000000451 14715550562 017512 0 ustar tstarling tstarling --TEST--
excimer_set_timeout
--SKIPIF--
--FILE--
--FILE--
setInterval(1);
$timer->setCallback(function() {
throw new Exception('timeout');
});
$timer->start();
try {
while (true) {
usleep(100000);
}
} catch (Exception $e) {
echo "OK\n";
}
--EXPECT--
OK
excimer-1.2.3/LICENSE 0000664 0001750 0001750 00000026136 14715550562 015002 0 ustar tstarling tstarling
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
excimer-1.2.3/README.md 0000664 0001750 0001750 00000001125 14715550562 015243 0 ustar tstarling tstarling # Excimer
Excimer is an extension for PHP 7.1+ that provides a low-overhead interrupting timer and sampling profiler.
## Installation
Excimer is available from [packages.debian.org](https://packages.debian.org/stable/php-excimer), and can be installed via `apt-get install php-excimer`.
For other installation methods, see .
## Documentation
For the API, see [PHPDoc stubs](./stubs).
For usage examples, see .
## See also
* [wikimedia/arc-lamp](https://gerrit.wikimedia.org/g/performance/arc-lamp/)
excimer-1.2.3/config.m4 0000664 0001750 0001750 00000001774 14715550562 015505 0 ustar tstarling tstarling dnl config.m4 for extension excimer
PHP_ARG_ENABLE(excimer, whether to enable excimer support,
[ --enable-excimer Enable excimer support])
if test "$PHP_EXCIMER" != "no"; then
dnl Timers require real-time and pthread library on Linux and not
dnl supported on other platforms
AC_SEARCH_LIBS([timer_create], [rt], [
AC_DEFINE(HAVE_TIMER_CREATE, 1, [Whether timer_create is available on the current platform])
PHP_EVAL_LIBLINE($LIBS, EXCIMER_SHARED_LIBADD)
excimer_os_sources=excimer_os_timer_posix.c
], [
AC_SEARCH_LIBS([kevent], [kqueue], [
PHP_EVAL_LIBLINE($LIBS, EXCIMER_SHARED_LIBADD)
excimer_os_sources=excimer_os_timer_kqueue.c
], [
AC_MSG_ERROR([excimer requires timer_create or kevent])
])
])
AC_SEARCH_LIBS([sem_init], [pthread], [
PHP_EVAL_LIBLINE($LIBS, EXCIMER_SHARED_LIBADD)
])
PHP_SUBST(EXCIMER_SHARED_LIBADD)
PHP_NEW_EXTENSION(excimer, excimer.c excimer_mutex.c excimer_timer.c excimer_log.c $excimer_os_sources, $ext_shared)
fi
excimer-1.2.3/excimer.c 0000664 0001750 0001750 00000130077 14715550562 015575 0 ustar tstarling tstarling /* Copyright 2018 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include "php.h"
#include "zend_exceptions.h"
#include "zend_interfaces.h"
#include "ext/spl/spl_exceptions.h"
#if PHP_VERSION_ID < 80400
#include "ext/standard/php_mt_rand.h"
#else
#include "ext/random/php_random.h"
#endif
#include "ext/standard/info.h"
#if PHP_VERSION_ID < 70200
/* For spl_ce_Countable */
#include "ext/spl/spl_iterators.h"
#endif
#include "php_excimer.h"
#include "excimer_timer.h"
#include "excimer_log.h"
#define EXCIMER_OBJ(type, object) \
((type ## _obj*)excimer_check_object(object, XtOffsetOf(type ## _obj, std), &type ## _handlers))
#define EXCIMER_OBJ_Z(type, zval) (Z_TYPE(zval) == IS_OBJECT ? EXCIMER_OBJ(type, Z_OBJ(zval)) : NULL)
#define EXCIMER_OBJ_ZP(type, zval_ptr) EXCIMER_OBJ(type, Z_OBJ_P(zval_ptr))
#define EXCIMER_NEW_OBJECT(type, ce) \
excimer_object_alloc_init(sizeof(type ## _obj), &type ## _handlers, ce)
#define EXCIMER_DEFAULT_PERIOD 0.1
#define EXCIMER_BILLION 1000000000LL
/* {{{ types */
/**
* ExcimerProfiler_obj: underlying storage for ExcimerProfiler
*/
typedef struct {
/** The period which will be used when the timer is next started */
struct timespec period;
/** The initial interval */
struct timespec initial;
/** The event type, either EXCIMER_CPU or EXCIMER_REAL */
zend_long event_type;
/** The currently-attached log */
zval z_log;
/** The flush callback. If this is set, max_samples will also be set. */
zval z_callback;
/** The maximum number of samples in z_log before z_callback is called. */
zend_long max_samples;
/** The timer backend object */
excimer_timer timer;
zend_object std;
} ExcimerProfiler_obj;
/**
* ExcimerLog_iterator: Iterator object returned by get_iterator handler, used
* by foreach.
*/
typedef struct {
/**
* FIXME Iterators use PHP 5 style inheritance. This is actually a
* zend_object header. Maybe it is harmless but it seems dodgy to me,
* should be fixed upstream.
*/
zend_user_iterator intern;
/** Cached (lazy-initialised) value to use for current() */
zval z_current;
/** Current log index */
zend_long index;
} ExcimerLog_iterator;
/**
* ExcimerLog_obj: underlying storage for ExcimerLog
*/
typedef struct {
/** The log backend object */
excimer_log log;
/** The cached value to use for current() */
zval z_current;
/** The current index, for key() etc. */
zend_long iter_entry_index;
zend_object std;
} ExcimerLog_obj;
/**
* ExcimerLogEntry_obj: underlying storage for ExcimerLogEntry
*/
typedef struct {
/**
* The ExcimerLog. Note that this can be a circular reference if
* ExcimerLog_obj.z_current points here.
*/
zval z_log;
/** The index of this entry in the ExcimerLog */
zend_long index;
zend_object std;
} ExcimerLogEntry_obj;
/**
* ExcimerTimer_obj: underlying storage for ExcimerTimer
*/
typedef struct {
/** The timer backend object */
excimer_timer timer;
/** The timer period */
struct timespec period;
/** The initial expiry, or zero to use the period */
struct timespec initial;
/** The event type, EXCIMER_REAL or EXCIMER_CPU */
zend_long event_type;
/** The event function, or null for no callback */
zval z_callback;
zend_object std;
} ExcimerTimer_obj;
/* }}} */
/* {{{ static function declarations */
static void ExcimerProfiler_start(ExcimerProfiler_obj *profiler);
static void ExcimerProfiler_stop(ExcimerProfiler_obj *profiler);
static void ExcimerProfiler_event(zend_long event_count, void *user_data);
static void ExcimerProfiler_flush(ExcimerProfiler_obj *profiler, zval *zp_old_log);
static zend_object *ExcimerProfiler_new(zend_class_entry *ce);
static void ExcimerProfiler_free_object(zend_object *object);
static void ExcimerProfiler_dtor(zend_object *object);
static PHP_METHOD(ExcimerProfiler, setPeriod);
static PHP_METHOD(ExcimerProfiler, setEventType);
static PHP_METHOD(ExcimerProfiler, setMaxDepth);
static PHP_METHOD(ExcimerProfiler, setFlushCallback);
static PHP_METHOD(ExcimerProfiler, clearFlushCallback);
static PHP_METHOD(ExcimerProfiler, start);
static PHP_METHOD(ExcimerProfiler, stop);
static PHP_METHOD(ExcimerProfiler, getLog);
static PHP_METHOD(ExcimerProfiler, flush);
static zend_object *ExcimerLog_new(zend_class_entry *ce);
static void ExcimerLog_free_object(zend_object *object);
static zend_object_iterator *ExcimerLog_get_iterator(zend_class_entry *ce, zval *object, int by_ref);
#if PHP_VERSION_ID < 80000
static int ExcimerLog_count_elements(zval *zp_log, zend_long *lp_count);
#else
static int ExcimerLog_count_elements(zend_object *object, zend_long *lp_count);
#endif
static void ExcimerLog_init_entry(zval *zp_dest, zval *zp_log, zend_long index);
static void ExcimerLog_iterator_dtor(zend_object_iterator *iter);
static int ExcimerLog_iterator_valid(zend_object_iterator *iter);
static zval *ExcimerLog_iterator_get_current_data(zend_object_iterator *iter);
static void ExcimerLog_iterator_get_current_key(zend_object_iterator *iter, zval *key);
static void ExcimerLog_iterator_move_forward(zend_object_iterator *iter);
static void ExcimerLog_iterator_rewind(zend_object_iterator *iter);
static void ExcimerLog_iterator_invalidate_current(zend_object_iterator *iter);
static PHP_METHOD(ExcimerLog, __construct);
static PHP_METHOD(ExcimerLog, formatCollapsed);
static PHP_METHOD(ExcimerLog, getSpeedscopeData);
static PHP_METHOD(ExcimerLog, aggregateByFunction);
static PHP_METHOD(ExcimerLog, getEventCount);
static PHP_METHOD(ExcimerLog, current);
static PHP_METHOD(ExcimerLog, key);
static PHP_METHOD(ExcimerLog, next);
static PHP_METHOD(ExcimerLog, rewind);
static PHP_METHOD(ExcimerLog, valid);
static PHP_METHOD(ExcimerLog, count);
static PHP_METHOD(ExcimerLog, offsetExists);
static PHP_METHOD(ExcimerLog, offsetGet);
static PHP_METHOD(ExcimerLog, offsetSet);
static PHP_METHOD(ExcimerLog, offsetUnset);
static zend_object *ExcimerLogEntry_new(zend_class_entry *ce);
static void ExcimerLogEntry_free_object(zend_object *object);
static PHP_METHOD(ExcimerLogEntry, __construct);
static PHP_METHOD(ExcimerLogEntry, getTimestamp);
static PHP_METHOD(ExcimerLogEntry, getEventCount);
static PHP_METHOD(ExcimerLogEntry, getTrace);
static zend_object *ExcimerTimer_new(zend_class_entry *ce);
static void ExcimerTimer_free_object(zend_object *object);
static PHP_METHOD(ExcimerTimer, setEventType);
static PHP_METHOD(ExcimerTimer, setInterval);
static PHP_METHOD(ExcimerTimer, setPeriod);
static PHP_METHOD(ExcimerTimer, setCallback);
static PHP_METHOD(ExcimerTimer, start);
static PHP_METHOD(ExcimerTimer, stop);
static PHP_METHOD(ExcimerTimer, getTime);
static void ExcimerTimer_start(ExcimerTimer_obj *timer_obj);
static void ExcimerTimer_stop(ExcimerTimer_obj *timer_obj);
static void ExcimerTimer_event(zend_long event_count, void *user_data);
static int ExcimerTimer_set_callback(ExcimerTimer_obj *timer_obj, zval *zp_callback);
static PHP_FUNCTION(excimer_set_timeout);
/* }}} */
static zend_class_entry *ExcimerProfiler_ce;
static zend_class_entry *ExcimerLog_ce;
static zend_class_entry *ExcimerLogEntry_ce;
static zend_class_entry *ExcimerTimer_ce;
static zend_object_handlers ExcimerProfiler_handlers;
static zend_object_handlers ExcimerLog_handlers;
static zend_object_handlers ExcimerLogEntry_handlers;
static zend_object_handlers ExcimerTimer_handlers;
/** {{{ arginfo */
#ifndef ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX
#define ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, allow_null) \
ZEND_BEGIN_ARG_INFO_EX(name, 0, return_reference, required_num_args)
#endif
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerProfiler_setPeriod, 0)
ZEND_ARG_INFO(0, period)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerProfiler_setEventType, 0)
ZEND_ARG_INFO(0, event_type)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerProfiler_setMaxDepth, 0)
ZEND_ARG_INFO(0, max_depth)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerProfiler_setFlushCallback, 0)
ZEND_ARG_INFO(0, callback)
ZEND_ARG_INFO(0, max_samples)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerProfiler_clearFlushCallback, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerProfiler_start, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerProfiler_stop, 0)
ZEND_END_ARG_INFO()
#if PHP_VERSION_ID >= 70200
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_ExcimerProfiler_getLog, 0, 0, ExcimerLog, 0)
#else
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerProfiler_getLog, 0)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerProfiler_flush, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerLog___construct, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerLog_formatCollapsed, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerLog_getSpeedscopeData, 0)
ZEND_END_ARG_INFO()
#if PHP_VERSION_ID < 70200
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(arginfo_ExcimerLog_aggregateByFunction, IS_ARRAY, NULL, 0)
#else
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(arginfo_ExcimerLog_aggregateByFunction, IS_ARRAY, 0)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerLog_getEventCount, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_ExcimerLog_current, 0, 0, IS_MIXED, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_ExcimerLog_key, 0, 0, IS_MIXED, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_ExcimerLog_next, 0, 0, IS_VOID, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_ExcimerLog_rewind, 0, 0, IS_VOID, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_ExcimerLog_valid, 0, 0, _IS_BOOL, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_ExcimerLog_count, 0, 0, IS_LONG, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_ExcimerLog_offsetExists, 0, 1, _IS_BOOL, 0)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_ExcimerLog_offsetGet, 0, 1, IS_MIXED, 0)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_ExcimerLog_offsetSet, 0, 2, IS_VOID, 0)
ZEND_ARG_INFO(0, offset)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_ExcimerLog_offsetUnset, 0, 1, IS_VOID, 0)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerLogEntry___construct, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerLogEntry_getTimestamp, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerLogEntry_getEventCount, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerLogEntry_getTrace, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerTimer_setEventType, 0)
ZEND_ARG_INFO(0, event_type)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerTimer_setInterval, 0)
ZEND_ARG_INFO(0, interval)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerTimer_setPeriod, 0)
ZEND_ARG_INFO(0, period)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerTimer_setCallback, 0)
ZEND_ARG_INFO(0, callback)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerTimer_start, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerTimer_stop, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_ExcimerTimer_getTime, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO(arginfo_excimer_set_timeout, 0)
ZEND_ARG_INFO(0, callback)
ZEND_ARG_INFO(0, interval)
ZEND_END_ARG_INFO()
/* }}} */
/** {{{ function entries */
static const zend_function_entry ExcimerProfiler_methods[] = {
PHP_ME(ExcimerProfiler, setPeriod, arginfo_ExcimerProfiler_setPeriod, 0)
PHP_ME(ExcimerProfiler, setEventType, arginfo_ExcimerProfiler_setEventType, 0)
PHP_ME(ExcimerProfiler, setMaxDepth, arginfo_ExcimerProfiler_setMaxDepth, 0)
PHP_ME(ExcimerProfiler, setFlushCallback, arginfo_ExcimerProfiler_setFlushCallback, 0)
PHP_ME(ExcimerProfiler, clearFlushCallback, arginfo_ExcimerProfiler_clearFlushCallback, 0)
PHP_ME(ExcimerProfiler, start, arginfo_ExcimerProfiler_start, 0)
PHP_ME(ExcimerProfiler, stop, arginfo_ExcimerProfiler_stop, 0)
PHP_ME(ExcimerProfiler, getLog, arginfo_ExcimerProfiler_getLog, 0)
PHP_ME(ExcimerProfiler, flush, arginfo_ExcimerProfiler_flush, 0)
PHP_FE_END
};
static const zend_function_entry ExcimerLog_methods[] = {
PHP_ME(ExcimerLog, __construct, arginfo_ExcimerLog___construct,
ZEND_ACC_PRIVATE | ZEND_ACC_FINAL)
PHP_ME(ExcimerLog, formatCollapsed, arginfo_ExcimerLog_formatCollapsed, 0)
PHP_ME(ExcimerLog, getSpeedscopeData, arginfo_ExcimerLog_getSpeedscopeData, 0)
PHP_ME(ExcimerLog, aggregateByFunction, arginfo_ExcimerLog_aggregateByFunction, 0)
PHP_ME(ExcimerLog, getEventCount, arginfo_ExcimerLog_getEventCount, 0)
PHP_ME(ExcimerLog, current, arginfo_ExcimerLog_current, 0)
PHP_ME(ExcimerLog, key, arginfo_ExcimerLog_key, 0)
PHP_ME(ExcimerLog, next, arginfo_ExcimerLog_next, 0)
PHP_ME(ExcimerLog, rewind, arginfo_ExcimerLog_rewind, 0)
PHP_ME(ExcimerLog, valid, arginfo_ExcimerLog_valid, 0)
PHP_ME(ExcimerLog, count, arginfo_ExcimerLog_count, 0)
PHP_ME(ExcimerLog, offsetExists, arginfo_ExcimerLog_offsetExists, 0)
PHP_ME(ExcimerLog, offsetGet, arginfo_ExcimerLog_offsetGet, 0)
PHP_ME(ExcimerLog, offsetSet, arginfo_ExcimerLog_offsetSet, 0)
PHP_ME(ExcimerLog, offsetUnset, arginfo_ExcimerLog_offsetUnset, 0)
PHP_FE_END
};
static zend_object_iterator_funcs ExcimerLog_iterator_funcs = {
ExcimerLog_iterator_dtor,
ExcimerLog_iterator_valid,
ExcimerLog_iterator_get_current_data,
ExcimerLog_iterator_get_current_key,
ExcimerLog_iterator_move_forward,
ExcimerLog_iterator_rewind,
ExcimerLog_iterator_invalidate_current
};
static const zend_function_entry ExcimerLogEntry_methods[] = {
PHP_ME(ExcimerLogEntry, __construct, arginfo_ExcimerLogEntry___construct,
ZEND_ACC_PRIVATE | ZEND_ACC_FINAL)
PHP_ME(ExcimerLogEntry, getTimestamp, arginfo_ExcimerLogEntry_getTimestamp, 0)
PHP_ME(ExcimerLogEntry, getEventCount, arginfo_ExcimerLogEntry_getEventCount, 0)
PHP_ME(ExcimerLogEntry, getTrace, arginfo_ExcimerLogEntry_getTrace, 0)
PHP_FE_END
};
static const zend_function_entry ExcimerTimer_methods[] = {
PHP_ME(ExcimerTimer, setEventType, arginfo_ExcimerTimer_setEventType, 0)
PHP_ME(ExcimerTimer, setInterval, arginfo_ExcimerTimer_setInterval, 0)
PHP_ME(ExcimerTimer, setPeriod, arginfo_ExcimerTimer_setPeriod, 0)
PHP_ME(ExcimerTimer, setCallback, arginfo_ExcimerTimer_setCallback, 0)
PHP_ME(ExcimerTimer, start, arginfo_ExcimerTimer_start, 0)
PHP_ME(ExcimerTimer, stop, arginfo_ExcimerTimer_stop, 0)
PHP_ME(ExcimerTimer, getTime, arginfo_ExcimerTimer_getTime, 0)
PHP_FE_END
};
static const zend_function_entry excimer_functions[] = {
PHP_FE(excimer_set_timeout, arginfo_excimer_set_timeout)
PHP_FE_END
};
/* }}} */
/* {{{ INI Settings */
PHP_INI_BEGIN()
PHP_INI_ENTRY("excimer.default_max_depth", "1000", PHP_INI_ALL, NULL)
PHP_INI_END()
/* }}} */
static void *excimer_object_alloc_init(size_t object_size, zend_object_handlers *handlers, zend_class_entry *ce) /* {{{ */
{
#if PHP_VERSION_ID < 70300
char *intern = ecalloc(1, object_size + zend_object_properties_size(ce));
#else
char *intern = zend_object_alloc(object_size, ce);
#endif
const size_t header_size = object_size - sizeof(zend_object);
zend_object *object = (zend_object*)(intern + header_size);
zend_object_std_init(object, ce);
object_properties_init(object, ce);
object->handlers = handlers;
return intern;
}
/* }}} */
static void excimer_set_timespec(struct timespec *dest, double source) /* {{{ */
{
double fractional, integral;
if (source < 0) {
dest->tv_sec = dest->tv_nsec = 0;
return;
}
fractional = modf(source, &integral);
dest->tv_sec = (time_t)integral;
dest->tv_nsec = (long)(fractional * 1000000000.0);
if (dest->tv_nsec >= EXCIMER_BILLION) {
dest->tv_nsec -= EXCIMER_BILLION;
dest->tv_sec ++;
}
}
/* }}} */
static inline uint64_t excimer_timespec_to_ns(struct timespec *ts)
{
return (uint64_t)ts->tv_nsec + (uint64_t)ts->tv_sec * EXCIMER_BILLION;
}
static inline double excimer_timespec_to_double(struct timespec *ts)
{
return excimer_timespec_to_ns(ts) * 1e-9;
}
static inline void* excimer_check_object(zend_object *object, size_t offset, const zend_object_handlers *handlers)
{
if (object->handlers != handlers) {
return NULL;
} else {
return (void*)((char*)object - offset);
}
}
/* {{{ PHP_MINIT_FUNCTION
*/
static PHP_MINIT_FUNCTION(excimer)
{
zend_class_entry ce;
REGISTER_INI_ENTRIES();
REGISTER_LONG_CONSTANT("EXCIMER_REAL", EXCIMER_REAL, CONST_CS | CONST_PERSISTENT);
// Only define EXCIMER_CPU if the current platform supports POSIX timers,
// which are necessary for CPU profiling.
// This allows application code to detect and gracefully handle a lack of CPU profiling support.
#ifdef HAVE_TIMER_CREATE
REGISTER_LONG_CONSTANT("EXCIMER_CPU", EXCIMER_CPU, CONST_CS | CONST_PERSISTENT);
#endif
#define REGISTER_EXCIMER_CLASS(class_name) \
INIT_CLASS_ENTRY(ce, #class_name, class_name ## _methods); \
class_name ## _ce = zend_register_internal_class(&ce); \
class_name ## _ce->create_object = class_name ## _new; \
memcpy(&class_name ## _handlers, zend_get_std_object_handlers(), \
sizeof(zend_object_handlers)); \
class_name ## _handlers.offset = XtOffsetOf(class_name ## _obj, std); \
class_name ## _handlers.free_obj = class_name ## _free_object;
REGISTER_EXCIMER_CLASS(ExcimerProfiler);
ExcimerProfiler_handlers.dtor_obj = ExcimerProfiler_dtor;
REGISTER_EXCIMER_CLASS(ExcimerLog);
ExcimerLog_ce->get_iterator = ExcimerLog_get_iterator;
ExcimerLog_handlers.count_elements = ExcimerLog_count_elements;
zend_class_implements(ExcimerLog_ce, 1, zend_ce_iterator);
#if PHP_VERSION_ID >= 70200
zend_class_implements(ExcimerLog_ce, 1, zend_ce_countable);
zend_class_implements(ExcimerLog_ce, 1, zend_ce_arrayaccess);
#elif defined(HAVE_SPL)
zend_class_implements(ExcimerLog_ce, 1, spl_ce_Countable);
zend_class_implements(ExcimerLog_ce, 1, spl_ce_ArrayAccess);
#endif
REGISTER_EXCIMER_CLASS(ExcimerLogEntry);
REGISTER_EXCIMER_CLASS(ExcimerTimer);
#undef REGISTER_EXCIMER_CLASS
excimer_timer_module_init();
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MSHUTDOWN_FUNCTION
*/
static PHP_MSHUTDOWN_FUNCTION(excimer)
{
UNREGISTER_INI_ENTRIES();
excimer_timer_module_shutdown();
return SUCCESS;
}
/* }}} */
/* {{{ PHP_RINIT_FUNCTION
*/
static PHP_RINIT_FUNCTION(excimer)
{
excimer_timer_thread_init();
return SUCCESS;
}
/* }}} */
/* {{{ ZEND_MODULE_POST_ZEND_DEACTIVATE_D */
static ZEND_MODULE_POST_ZEND_DEACTIVATE_D(excimer)
{
excimer_timer_thread_shutdown();
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MINFO_FUNCTION
*/
static PHP_MINFO_FUNCTION(excimer)
{
php_info_print_table_start();
php_info_print_table_header(2, "excimer support", "enabled");
php_info_print_table_row(2, "excimer version", PHP_EXCIMER_VERSION);
php_info_print_table_end();
DISPLAY_INI_ENTRIES();
}
/* }}} */
static zend_object *ExcimerProfiler_new(zend_class_entry *ce) /* {{{ */
{
ExcimerProfiler_obj *profiler = EXCIMER_NEW_OBJECT(ExcimerProfiler, ce);
ExcimerLog_obj *log_obj;
struct timespec now_ts;
double initial;
clock_gettime(CLOCK_MONOTONIC, &now_ts);
object_init_ex(&profiler->z_log, ExcimerLog_ce);
log_obj = EXCIMER_OBJ_Z(ExcimerLog, profiler->z_log);
log_obj->log.max_depth = INI_INT("excimer.default_max_depth");
log_obj->log.epoch = excimer_timespec_to_ns(&now_ts);
ZVAL_NULL(&profiler->z_callback);
profiler->event_type = EXCIMER_REAL;
// Stagger start time
initial = php_mt_rand() * EXCIMER_DEFAULT_PERIOD / UINT32_MAX;
excimer_set_timespec(&profiler->initial, initial);
excimer_set_timespec(&profiler->period, EXCIMER_DEFAULT_PERIOD);
log_obj->log.period = EXCIMER_DEFAULT_PERIOD * EXCIMER_BILLION;
return &profiler->std;
}
/* }}} */
static void ExcimerProfiler_free_object(zend_object *object) /* {{{ */
{
ExcimerProfiler_obj *profiler = EXCIMER_OBJ(ExcimerProfiler, object);
if (profiler->timer.is_valid) {
excimer_timer_destroy(&profiler->timer);
}
zval_ptr_dtor(&profiler->z_log);
ZVAL_UNDEF(&profiler->z_log);
zval_ptr_dtor(&profiler->z_callback);
ZVAL_UNDEF(&profiler->z_callback);
zend_object_std_dtor(object);
}
/* }}} */
static void ExcimerProfiler_dtor(zend_object *object) /* {{{ */
{
ExcimerProfiler_obj *profiler = EXCIMER_OBJ(ExcimerProfiler, object);
ExcimerLog_obj *log_obj = EXCIMER_OBJ_Z(ExcimerLog, profiler->z_log);
zval z_old_log;
if (log_obj->log.entries_size) {
ExcimerProfiler_flush(profiler, &z_old_log);
zval_ptr_dtor(&z_old_log);
}
}
/* }}} */
/* {{{ proto void ExcimerProfiler::setPeriod(float period)
*/
static PHP_METHOD(ExcimerProfiler, setPeriod)
{
double period, initial;
ExcimerProfiler_obj *profiler = EXCIMER_OBJ_ZP(ExcimerProfiler, getThis());
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_DOUBLE(period)
ZEND_PARSE_PARAMETERS_END();
// Stagger start time
initial = php_mt_rand() * period / UINT32_MAX;
excimer_set_timespec(&profiler->period, period);
excimer_set_timespec(&profiler->initial, initial);
ExcimerLog_obj *log = EXCIMER_OBJ_ZP(ExcimerLog, &profiler->z_log);
log->log.period = period * EXCIMER_BILLION;
}
/* }}} */
/* {{{ proto void ExcimerProfiler::setEventType(int event_type)
*/
static PHP_METHOD(ExcimerProfiler, setEventType)
{
zend_long event_type;
ExcimerProfiler_obj *profiler = EXCIMER_OBJ_ZP(ExcimerProfiler, getThis());
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(event_type)
ZEND_PARSE_PARAMETERS_END();
if (event_type != EXCIMER_CPU && event_type != EXCIMER_REAL) {
php_error_docref(NULL, E_WARNING, "Invalid event type");
return;
}
profiler->event_type = event_type;
}
/* }}} */
/* {{{ proto void ExcimerProfiler::setMaxDepth(int max_depth)
*/
static PHP_METHOD(ExcimerProfiler, setMaxDepth)
{
zend_long max_depth;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(max_depth)
ZEND_PARSE_PARAMETERS_END();
ExcimerProfiler_obj *profiler = EXCIMER_OBJ_ZP(ExcimerProfiler, getThis());
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, &profiler->z_log);
excimer_log_set_max_depth(&log_obj->log, max_depth);
}
/* }}} */
/* {{{ proto void ExcimerProfiler::setFlushCallback(callable callback, mixed max_samples)
*/
static PHP_METHOD(ExcimerProfiler, setFlushCallback)
{
zval *z_callback;
zend_long max_samples;
ExcimerProfiler_obj *profiler = EXCIMER_OBJ_ZP(ExcimerProfiler, getThis());
char *is_callable_error;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_ZVAL(z_callback)
Z_PARAM_LONG(max_samples)
ZEND_PARSE_PARAMETERS_END();
if (!zend_is_callable_ex(z_callback, NULL, 0, NULL, NULL, &is_callable_error)) {
php_error_docref(NULL, E_WARNING, "flush callback is not callable: %s",
is_callable_error);
return;
}
ZVAL_COPY(&profiler->z_callback, z_callback);
profiler->max_samples = max_samples;
}
/* }}} */
/* {{{ proto void ExcimerProfiler::clearFlushCallback()
*/
static PHP_METHOD(ExcimerProfiler, clearFlushCallback)
{
ExcimerProfiler_obj *profiler = EXCIMER_OBJ_ZP(ExcimerProfiler, getThis());
zval_ptr_dtor(&profiler->z_callback);
ZVAL_NULL(&profiler->z_callback);
profiler->max_samples = 0;
}
/* }}} */
/* {{{ proto void ExcimerProfiler::start()
*/
static PHP_METHOD(ExcimerProfiler, start)
{
ExcimerProfiler_obj *profiler = EXCIMER_OBJ_ZP(ExcimerProfiler, getThis());
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
if (profiler->timer.is_running) {
ExcimerProfiler_stop(profiler);
}
ExcimerProfiler_start(profiler);
}
/* }}} */
/* {{{ proto void ExcimerProfiler::stop()
*/
static PHP_METHOD(ExcimerProfiler, stop)
{
ExcimerProfiler_obj *profiler = EXCIMER_OBJ_ZP(ExcimerProfiler, getThis());
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
ExcimerProfiler_stop(profiler);
}
/* }}} */
/* {{{ proto ExcimerLog ExcimerProfiler::getLog()
*/
static PHP_METHOD(ExcimerProfiler, getLog)
{
ExcimerProfiler_obj * profiler = EXCIMER_OBJ_ZP(ExcimerProfiler, getThis());
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
RETURN_ZVAL(&profiler->z_log, 1, 0);
}
/* }}} */
/* {{{ proto ExcimerLog ExcimerProfiler::flush() */
static PHP_METHOD(ExcimerProfiler, flush)
{
ExcimerProfiler_obj *profiler = EXCIMER_OBJ_ZP(ExcimerProfiler, getThis());
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
ExcimerProfiler_flush(profiler, return_value);
}
/* }}} */
static void ExcimerProfiler_start(ExcimerProfiler_obj *profiler) /* {{{ */
{
if (profiler->timer.is_valid) {
excimer_timer_destroy(&profiler->timer);
}
if (excimer_timer_init(&profiler->timer,
profiler->event_type,
ExcimerProfiler_event,
(void*)profiler) == FAILURE)
{
/* Error message already sent */
return;
}
excimer_timer_start(&profiler->timer,
&profiler->period,
&profiler->initial);
}
/* }}} */
static void ExcimerProfiler_stop(ExcimerProfiler_obj *profiler) /* {{{ */
{
if (profiler->timer.is_valid) {
excimer_timer_destroy(&profiler->timer);
}
}
/* }}} */
static void ExcimerProfiler_event(zend_long event_count, void *user_data) /* {{{ */
{
uint64_t now_ns;
struct timespec now_ts;
ExcimerProfiler_obj *profiler = (ExcimerProfiler_obj*)user_data;
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, &profiler->z_log);
excimer_log *log;
log = &log_obj->log;
clock_gettime(CLOCK_MONOTONIC, &now_ts);
now_ns = excimer_timespec_to_ns(&now_ts);
excimer_log_add(log, EG(current_execute_data), event_count, now_ns);
if (profiler->max_samples && log->entries_size >= profiler->max_samples) {
zval z_old_log;
ExcimerProfiler_flush(profiler, &z_old_log);
zval_ptr_dtor(&z_old_log);
}
}
/* }}} */
static void ExcimerProfiler_flush(ExcimerProfiler_obj *profiler, zval *zp_old_log) /* {{{ */
{
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, &profiler->z_log);
excimer_log *log = &log_obj->log;
zend_fcall_info fci;
zend_fcall_info_cache fcc;
char *is_callable_error = NULL;
zval retval;
int status;
/* Rotate the log */
ZVAL_COPY(zp_old_log, &profiler->z_log);
Z_DELREF(profiler->z_log);
object_init_ex(&profiler->z_log, ExcimerLog_ce);
excimer_log_copy_options(&EXCIMER_OBJ_ZP(ExcimerLog, &profiler->z_log)->log, log);
if (Z_ISNULL(profiler->z_callback)) {
return;
}
/* Prepare to call the flush callback */
if (zend_fcall_info_init(&profiler->z_callback, 0, &fci, &fcc, NULL,
&is_callable_error) != SUCCESS)
{
php_error(E_WARNING, "ExcimerProfiler callback is not callable (during event): %s",
is_callable_error);
ExcimerProfiler_stop(profiler);
return;
}
fci.retval = &retval;
/* Call it */
zend_fcall_info_argn(&fci, 1, zp_old_log);
status = zend_call_function(&fci, &fcc);
if (status == SUCCESS) {
zval_ptr_dtor(&retval);
}
zend_fcall_info_args_clear(&fci, 1);
}
/* }}} */
static zend_object *ExcimerLog_new(zend_class_entry *ce) /* {{{ */
{
ExcimerLog_obj *log_obj = EXCIMER_NEW_OBJECT(ExcimerLog, ce);
excimer_log_init(&log_obj->log);
/* Lazy-initialise z_current to minimise circular references */
ZVAL_NULL(&log_obj->z_current);
log_obj->iter_entry_index = 0;
return &log_obj->std;
}
/* }}} */
static void ExcimerLog_free_object(zend_object *object) /* {{{ */
{
ExcimerLog_obj *log_obj = EXCIMER_OBJ(ExcimerLog, object);
excimer_log_destroy(&log_obj->log);
zval_ptr_dtor(&log_obj->z_current);
zend_object_std_dtor(object);
}
/* }}} */
/* {{{ ExcimerLog_get_iterator */
static zend_object_iterator *ExcimerLog_get_iterator(
zend_class_entry *ce, zval *zp_log, int by_ref)
{
ExcimerLog_iterator *iterator;
if (by_ref) {
zend_throw_exception(spl_ce_RuntimeException, "An iterator cannot be used with foreach by reference", 0);
return NULL;
}
iterator = emalloc(sizeof(ExcimerLog_iterator));
zend_iterator_init((zend_object_iterator*)iterator);
ZVAL_COPY(&iterator->intern.it.data, zp_log);
iterator->intern.it.funcs = &ExcimerLog_iterator_funcs;
iterator->intern.ce = ce;
iterator->index = 0;
ZVAL_NULL(&iterator->z_current);
return &iterator->intern.it;
}
/* }}} */
static void ExcimerLog_iterator_dtor(zend_object_iterator *iter) /* {{{ */
{
ExcimerLog_iterator *iterator = (ExcimerLog_iterator*)iter;
zval_ptr_dtor(&iterator->z_current);
ZVAL_UNDEF(&iterator->z_current);
zval_ptr_dtor(&iterator->intern.it.data);
ZVAL_UNDEF(&iterator->intern.it.data);
}
/* }}} */
static int ExcimerLog_iterator_valid(zend_object_iterator *iter) /* {{{ */
{
ExcimerLog_iterator *iterator = (ExcimerLog_iterator*)iter;
ExcimerLog_obj *log_obj = EXCIMER_OBJ_Z(ExcimerLog, iterator->intern.it.data);
if (iterator->index < log_obj->log.entries_size) {
return SUCCESS;
} else {
return FAILURE;
}
}
/* }}} */
static zval *ExcimerLog_iterator_get_current_data(zend_object_iterator *iter) /* {{{ */
{
ExcimerLog_iterator *iterator = (ExcimerLog_iterator*)iter;
ExcimerLog_obj *log_obj = EXCIMER_OBJ_Z(ExcimerLog, iterator->intern.it.data);
if (Z_ISNULL(iterator->z_current)) {
if (iterator->index < log_obj->log.entries_size) {
ExcimerLog_init_entry(&iterator->z_current, &iterator->intern.it.data, iterator->index);
} else {
return NULL;
}
}
return &iterator->z_current;
}
/* }}} */
static void ExcimerLog_iterator_get_current_key(zend_object_iterator *iter, zval *key) /* {{{ */
{
ExcimerLog_iterator *iterator = (ExcimerLog_iterator*)iter;
ExcimerLog_obj *log_obj = EXCIMER_OBJ_Z(ExcimerLog, iterator->intern.it.data);
if (iterator->index < log_obj->log.entries_size) {
ZVAL_LONG(key, iterator->index);
} else {
ZVAL_NULL(key);
}
}
/* }}} */
static void ExcimerLog_iterator_move_forward(zend_object_iterator *iter) /* {{{ */
{
ExcimerLog_iterator *iterator = (ExcimerLog_iterator*)iter;
ExcimerLog_obj *log_obj = EXCIMER_OBJ_Z(ExcimerLog, iterator->intern.it.data);
zval_ptr_dtor(&iterator->z_current);
ZVAL_NULL(&iterator->z_current);
if (iterator->index < log_obj->log.entries_size) {
iterator->index++;
}
}
/* }}} */
static void ExcimerLog_iterator_rewind(zend_object_iterator *iter) /* {{{ */
{
ExcimerLog_iterator *iterator = (ExcimerLog_iterator*)iter;
zval_ptr_dtor(&iterator->z_current);
ZVAL_NULL(&iterator->z_current);
iterator->index = 0;
}
/* }}} */
static void ExcimerLog_iterator_invalidate_current(zend_object_iterator *iter) /* {{{ */
{
ExcimerLog_iterator *iterator = (ExcimerLog_iterator*)iter;
zval_ptr_dtor(&iterator->z_current);
ZVAL_NULL(&iterator->z_current);
}
/* }}} */
static void ExcimerLog_init_entry(zval *zp_dest, zval *zp_log, zend_long index) /* {{{ */
{
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, zp_log);
excimer_log_entry *entry = excimer_log_get_entry(&log_obj->log, index);
ExcimerLogEntry_obj *entry_obj;
if (entry) {
object_init_ex(zp_dest, ExcimerLogEntry_ce);
entry_obj = EXCIMER_OBJ_ZP(ExcimerLogEntry, zp_dest);
ZVAL_COPY(&entry_obj->z_log, zp_log);
entry_obj->index = index;
} else {
ZVAL_NULL(zp_dest);
}
}
/* }}} */
#if PHP_VERSION_ID < 80000
static int ExcimerLog_count_elements(zval *zp_log, zend_long *lp_count) /* {{{ */
{
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, zp_log);
*lp_count = log_obj->log.entries_size;
return SUCCESS;
}
/* }}} */
#else
static int ExcimerLog_count_elements(zend_object *object, zend_long *lp_count) /* {{{ */
{
ExcimerLog_obj *log_obj = EXCIMER_OBJ(ExcimerLog, object);
*lp_count = log_obj->log.entries_size;
return SUCCESS;
}
/* }}} */
#endif
/* {{{ proto void ExcimerLog::__construct()
*/
static PHP_METHOD(ExcimerLog, __construct)
{
php_error_docref(NULL, E_ERROR, "ExcimerLog cannot be constructed directly");
}
/* }}} */
/* {{{ proto string ExcimerLog::formatCollapsed()
*/
static PHP_METHOD(ExcimerLog, formatCollapsed)
{
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, getThis());
RETURN_STR(excimer_log_format_collapsed(&log_obj->log));
}
/* }}} */
/* {{{ proto string ExcimerLog::getSpeedscopeData()
*/
static PHP_METHOD(ExcimerLog, getSpeedscopeData)
{
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, getThis());
excimer_log_get_speedscope_data(&log_obj->log, return_value);
}
/* }}} */
/* {{{ proto string ExcimerLog::aggregateByFunction()
*/
static PHP_METHOD(ExcimerLog, aggregateByFunction)
{
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, getThis());
RETURN_ARR(excimer_log_aggr_by_func(&log_obj->log));
}
/* }}} */
/* {{{ proto string ExcimerLog::getEventCount()
*/
static PHP_METHOD(ExcimerLog, getEventCount)
{
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, getThis());
RETURN_LONG(log_obj->log.event_count);
}
/* }}} */
/* {{{ proto array ExcimerLog::current()
*/
static PHP_METHOD(ExcimerLog, current)
{
ExcimerLog_obj * log_obj = EXCIMER_OBJ_ZP(ExcimerLog, getThis());
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
if (Z_ISNULL(log_obj->z_current) && log_obj->iter_entry_index < log_obj->log.entries_size) {
ExcimerLog_init_entry(&log_obj->z_current, getThis(), log_obj->iter_entry_index);
}
RETURN_ZVAL(&log_obj->z_current, 1, 0);
}
/* }}} */
/* {{{ proto int ExcimerLog::key()
*/
static PHP_METHOD(ExcimerLog, key)
{
ExcimerLog_obj * log_obj = EXCIMER_OBJ_ZP(ExcimerLog, getThis());
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
if (log_obj->iter_entry_index < log_obj->log.entries_size) {
RETURN_LONG(log_obj->iter_entry_index);
} else {
RETURN_NULL();
}
}
/* }}} */
/* {{{ proto void ExcimerLog::next()
*/
static PHP_METHOD(ExcimerLog, next)
{
ExcimerLog_obj * log_obj = EXCIMER_OBJ_ZP(ExcimerLog, getThis());
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
zval_ptr_dtor(&log_obj->z_current);
ZVAL_NULL(&log_obj->z_current);
if (log_obj->iter_entry_index < log_obj->log.entries_size) {
log_obj->iter_entry_index++;
}
}
/* }}} */
/* {{{ proto void ExcimerLog::rewind()
*/
static PHP_METHOD(ExcimerLog, rewind)
{
ExcimerLog_obj * log_obj = EXCIMER_OBJ_ZP(ExcimerLog, getThis());
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
log_obj->iter_entry_index = 0;
zval_ptr_dtor(&log_obj->z_current);
ZVAL_NULL(&log_obj->z_current);
}
/* }}} */
/* {{{ proto bool ExcimerLog::valid()
*/
static PHP_METHOD(ExcimerLog, valid)
{
ExcimerLog_obj * log_obj = EXCIMER_OBJ_ZP(ExcimerLog, getThis());
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
if (log_obj->iter_entry_index < log_obj->log.entries_size) {
RETURN_TRUE;
} else {
RETURN_FALSE;
}
}
/* }}} */
/* {{{ proto int ExcimerLog::count()
*/
static PHP_METHOD(ExcimerLog, count)
{
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, getThis());
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
RETURN_LONG(log_obj->log.entries_size);
}
/* }}} */
/* {{{ proto bool ExcimerLog::offsetExists(mixed offset) */
static PHP_METHOD(ExcimerLog, offsetExists)
{
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, getThis());
zend_long offset;
ZEND_PARSE_PARAMETERS_START(1, 1);
Z_PARAM_LONG(offset)
ZEND_PARSE_PARAMETERS_END();
if (offset >= 0 && offset < log_obj->log.entries_size) {
RETURN_TRUE;
} else {
RETURN_FALSE;
}
}
/* }}} */
/* {{{ proto mixed ExcimerLog::offsetGet(mixed offset) */
static PHP_METHOD(ExcimerLog, offsetGet)
{
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, getThis());
zend_long offset;
ZEND_PARSE_PARAMETERS_START(1, 1);
Z_PARAM_LONG(offset)
ZEND_PARSE_PARAMETERS_END();
if (offset < 0 || offset >= log_obj->log.entries_size) {
RETURN_NULL();
}
ExcimerLog_init_entry(return_value, getThis(), offset);
}
/* }}} */
/* {{{ proto void ExcimerLog::offsetSet(mixed offset, mixed value) */
static PHP_METHOD(ExcimerLog, offsetSet)
{
php_error_docref(NULL, E_WARNING, "ExcimerLog cannot be modified");
}
/* }}} */
/* {{{ proto void ExcimerLog::offsetUnset(mixed offset) */
static PHP_METHOD(ExcimerLog, offsetUnset)
{
php_error_docref(NULL, E_WARNING, "ExcimerLog cannot be modified");
}
/* }}} */
static zend_object *ExcimerLogEntry_new(zend_class_entry *ce) /* {{{ */
{
ExcimerLogEntry_obj *entry_obj = EXCIMER_NEW_OBJECT(ExcimerLogEntry, ce);
ZVAL_NULL(&entry_obj->z_log);
entry_obj->index = 0;
return &entry_obj->std;
}
/* }}} */
static void ExcimerLogEntry_free_object(zend_object *object) /* {{{ */
{
ExcimerLogEntry_obj *entry_obj = EXCIMER_OBJ(ExcimerLogEntry, object);
zval_ptr_dtor(&entry_obj->z_log);
ZVAL_UNDEF(&entry_obj->z_log);
zend_object_std_dtor(object);
}
/* }}} */
/* {{{ proto void ExcimerLogEntry::__construct()
*/
static PHP_METHOD(ExcimerLogEntry, __construct)
{
php_error_docref(NULL, E_ERROR, "ExcimerLogEntry cannot be constructed directly");
}
/* }}} */
/* {{{ proto float ExcimerLogEntry::getTimestamp()
*/
static PHP_METHOD(ExcimerLogEntry, getTimestamp)
{
ExcimerLogEntry_obj *entry_obj = EXCIMER_OBJ_ZP(ExcimerLogEntry, getThis());
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, &entry_obj->z_log);
excimer_log_entry *entry = excimer_log_get_entry(&log_obj->log, entry_obj->index);
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
RETURN_DOUBLE((entry->timestamp - log_obj->log.epoch) / 1e9);
}
/* }}} */
/* {{{ proto float ExcimerLogEntry::getEventCount()
*/
static PHP_METHOD(ExcimerLogEntry, getEventCount)
{
ExcimerLogEntry_obj *entry_obj = EXCIMER_OBJ_ZP(ExcimerLogEntry, getThis());
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, &entry_obj->z_log);
excimer_log_entry *entry = excimer_log_get_entry(&log_obj->log, entry_obj->index);
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
RETURN_LONG(entry->event_count);
}
/* }}} */
/* {{{ proto array ExcimerLogEntry::getTrace()
*/
static PHP_METHOD(ExcimerLogEntry, getTrace)
{
ExcimerLogEntry_obj *entry_obj = EXCIMER_OBJ_ZP(ExcimerLogEntry, getThis());
ExcimerLog_obj *log_obj = EXCIMER_OBJ_ZP(ExcimerLog, &entry_obj->z_log);
excimer_log_entry *entry = excimer_log_get_entry(&log_obj->log, entry_obj->index);
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
RETURN_ARR(excimer_log_trace_to_array(&log_obj->log, entry->frame_index));
}
/* }}} */
static zend_object *ExcimerTimer_new(zend_class_entry *ce) /* {{{ */
{
ExcimerTimer_obj *timer_obj = EXCIMER_NEW_OBJECT(ExcimerTimer, ce);
ZVAL_UNDEF(&timer_obj->z_callback);
timer_obj->event_type = EXCIMER_REAL;
return &timer_obj->std;
}
/* }}} */
static void ExcimerTimer_free_object(zend_object *object) /* {{{ */
{
ExcimerTimer_obj *timer_obj = EXCIMER_OBJ(ExcimerTimer, object);
if (timer_obj->timer.is_valid) {
excimer_timer_destroy(&timer_obj->timer);
}
zval_ptr_dtor(&timer_obj->z_callback);
ZVAL_UNDEF(&timer_obj->z_callback);
}
/* }}} */
/* {{{ proto void ExcimerTimer::setEventType(int event_type)
*/
static PHP_METHOD(ExcimerTimer, setEventType)
{
ExcimerTimer_obj *timer_obj = EXCIMER_OBJ_ZP(ExcimerTimer, getThis());
zend_long event_type;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(event_type)
ZEND_PARSE_PARAMETERS_END();
if (event_type != EXCIMER_CPU && event_type != EXCIMER_REAL) {
php_error_docref(NULL, E_WARNING, "Invalid event type");
return;
}
timer_obj->event_type = event_type;
}
/* }}} */
/* {{{ proto void ExcimerTimer::setInterval(float interval)
*/
static PHP_METHOD(ExcimerTimer, setInterval)
{
ExcimerTimer_obj *timer_obj = EXCIMER_OBJ_ZP(ExcimerTimer, getThis());
double initial;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_DOUBLE(initial)
ZEND_PARSE_PARAMETERS_END();
excimer_set_timespec(&timer_obj->initial, initial);
}
/* }}} */
/* {{{ proto void ExcimerTimer::setPeriod(float period)
*/
static PHP_METHOD(ExcimerTimer, setPeriod)
{
ExcimerTimer_obj *timer_obj = EXCIMER_OBJ_ZP(ExcimerTimer, getThis());
double period;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_DOUBLE(period)
ZEND_PARSE_PARAMETERS_END();
excimer_set_timespec(&timer_obj->period, period);
}
/* }}} */
/* {{{ proto void ExcimerTimer::setCallback(callback callback)
*/
static PHP_METHOD(ExcimerTimer, setCallback)
{
ExcimerTimer_obj *timer_obj = EXCIMER_OBJ_ZP(ExcimerTimer, getThis());
zval *zp_callback;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ZVAL(zp_callback)
ZEND_PARSE_PARAMETERS_END();
if (Z_TYPE_P(zp_callback) == IS_NULL) {
zval_ptr_dtor(&timer_obj->z_callback);
ZVAL_NULL(&timer_obj->z_callback);
} else {
ExcimerTimer_set_callback(timer_obj, zp_callback);
}
}
/* }}} */
static int ExcimerTimer_set_callback(ExcimerTimer_obj *timer_obj, zval *zp_callback) /* {{{ */
{
char *is_callable_error;
if (!zend_is_callable_ex(zp_callback, NULL, 0, NULL, NULL, &is_callable_error)) {
php_error_docref(NULL, E_WARNING, "timer callback is not callable: %s",
is_callable_error);
return FAILURE;
}
zval_ptr_dtor(&timer_obj->z_callback);
ZVAL_COPY(&timer_obj->z_callback, zp_callback);
return SUCCESS;
}
/* }}} */
/* {{{ proto void ExcimerTimer::start()
*/
static PHP_METHOD(ExcimerTimer, start)
{
ExcimerTimer_obj *timer_obj = EXCIMER_OBJ_ZP(ExcimerTimer, getThis());
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
if (timer_obj->timer.is_running) {
ExcimerTimer_stop(timer_obj);
}
ExcimerTimer_start(timer_obj);
}
/* }}} */
/* {{{ proto void ExcimerTimer::stop()
*/
static PHP_METHOD(ExcimerTimer, stop)
{
ExcimerTimer_obj *timer_obj = EXCIMER_OBJ_ZP(ExcimerTimer, getThis());
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
ExcimerTimer_stop(timer_obj);
}
/* }}} */
/* {{{ proto float ExcimerTimer::getTime()
*/
static PHP_METHOD(ExcimerTimer, getTime)
{
ExcimerTimer_obj *timer_obj = EXCIMER_OBJ_ZP(ExcimerTimer, getThis());
struct timespec ts;
ZEND_PARSE_PARAMETERS_START(0, 0);
ZEND_PARSE_PARAMETERS_END();
excimer_timer_get_time(&timer_obj->timer, &ts);
RETURN_DOUBLE(excimer_timespec_to_double(&ts));
}
/* }}} */
static void ExcimerTimer_start(ExcimerTimer_obj *timer_obj) /* {{{ */
{
if (timer_obj->timer.is_valid) {
excimer_timer_destroy(&timer_obj->timer);
}
if (excimer_timer_init(&timer_obj->timer,
timer_obj->event_type,
ExcimerTimer_event,
(void*)timer_obj) == FAILURE)
{
/* Error message already sent */
return;
}
excimer_timer_start(&timer_obj->timer,
&timer_obj->period,
&timer_obj->initial);
}
/* }}} */
static void ExcimerTimer_stop(ExcimerTimer_obj *timer_obj) /* {{{ */
{
if (timer_obj->timer.is_valid) {
excimer_timer_destroy(&timer_obj->timer);
}
}
/* }}} */
static void ExcimerTimer_event(zend_long event_count, void *user_data) /* {{{ */
{
ExcimerTimer_obj *timer_obj = (ExcimerTimer_obj*)user_data;
zend_fcall_info fci;
zend_fcall_info_cache fcc;
zval retval;
zval z_event_count;
char *is_callable_error;
if (Z_ISNULL(timer_obj->z_callback) || Z_ISUNDEF(timer_obj->z_callback)) {
return;
}
if (zend_fcall_info_init(&timer_obj->z_callback, 0, &fci, &fcc, NULL,
&is_callable_error) != SUCCESS)
{
php_error(E_WARNING, "ExcimerTimer callback is not callable (during event): %s",
is_callable_error);
ExcimerTimer_stop(timer_obj);
return;
}
fci.retval = &retval;
ZVAL_LONG(&z_event_count, event_count);
zend_fcall_info_argn(&fci, 1, &z_event_count);
if (zend_call_function(&fci, &fcc) == SUCCESS) {
zval_ptr_dtor(&retval);
}
zend_fcall_info_args_clear(&fci, 1);
}
/* }}} */
/* {{{ proto ExcimerTimer excimer_set_timeout(callable callback, float interval)
*/
PHP_FUNCTION(excimer_set_timeout)
{
ExcimerTimer_obj *timer_obj;
zval * zp_callback;
double initial;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_ZVAL(zp_callback)
Z_PARAM_DOUBLE(initial)
ZEND_PARSE_PARAMETERS_END();
object_init_ex(return_value, ExcimerTimer_ce);
timer_obj = EXCIMER_OBJ_ZP(ExcimerTimer, return_value);
if (ExcimerTimer_set_callback(timer_obj, zp_callback) == FAILURE) {
zval_ptr_dtor(return_value);
ZVAL_NULL(return_value);
}
excimer_set_timespec(&timer_obj->initial, initial);
ExcimerTimer_start(timer_obj);
}
/* }}} */
static const zend_module_dep excimer_deps[] = {
#if PHP_VERSION_ID < 70200
ZEND_MOD_REQUIRED("spl")
#endif
ZEND_MOD_END
};
/* {{{ excimer_module_entry */
zend_module_entry excimer_module_entry = {
STANDARD_MODULE_HEADER_EX,
NULL,
excimer_deps,
"excimer",
excimer_functions,
PHP_MINIT(excimer),
PHP_MSHUTDOWN(excimer),
PHP_RINIT(excimer),
NULL, /* RSHUTDOWN */
PHP_MINFO(excimer),
PHP_EXCIMER_VERSION,
NO_MODULE_GLOBALS,
ZEND_MODULE_POST_ZEND_DEACTIVATE_N(excimer),
STANDARD_MODULE_PROPERTIES_EX
};
/* }}} */
#ifdef COMPILE_DL_EXCIMER
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(excimer)
#endif
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/
excimer-1.2.3/excimer_events.h 0000664 0001750 0001750 00000001400 14715550562 017151 0 ustar tstarling tstarling /* Copyright 2024 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef EXCIMER_EVENTS_H
#define EXCIMER_EVENTS_H
enum {
/** Event type: real, wall-clock time */
EXCIMER_REAL,
/** Event type: CPU time */
EXCIMER_CPU
};
#endif excimer-1.2.3/excimer_log.c 0000664 0001750 0001750 00000052627 14715550562 016442 0 ustar tstarling tstarling /* Copyright 2018 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "php.h"
#include "Zend/zend_smart_str.h"
#include "php_excimer.h"
#include "excimer_log.h"
static const char excimer_log_truncated_name[] = "excimer_truncated";
static const char excimer_log_fake_filename[] = "excimer fake file";
static uint32_t excimer_log_find_or_add_frame(excimer_log *log,
zend_execute_data *execute_data, zend_long depth);
/* {{{ Compatibility functions and macros */
#if PHP_VERSION_ID >= 70300
#define excimer_log_new_array zend_new_array
#else
static inline HashTable *excimer_log_new_array(uint32_t nSize)
{
HashTable *ht = emalloc(sizeof(HashTable));
zend_hash_init(ht, nSize, NULL, ZVAL_PTR_DTOR, 0);
return ht;
}
#endif
#if PHP_VERSION_ID >= 70200
#define excimer_log_smart_str_get_len smart_str_get_len
#define excimer_log_smart_str_extract smart_str_extract
#define excimer_log_smart_str_append_printf smart_str_append_printf
#define excimer_log_known_string ZSTR_KNOWN
#else
static inline size_t excimer_log_smart_str_get_len(smart_str *str)
{
return str->s ? ZSTR_LEN(str->s) : 0;
}
static inline zend_string *excimer_log_smart_str_extract(smart_str *str)
{
if (str->s) {
zend_string *res;
smart_str_0(str);
res = str->s;
str->s = NULL;
return res;
} else {
return ZSTR_EMPTY_ALLOC();
}
}
static void excimer_log_smart_str_append_printf(smart_str *dest, const char *format, ...)
{
va_list arg;
size_t len;
char *buf;
va_start(arg, format);
len = vspprintf(&buf, 0, format, arg);
va_end(arg);
smart_str_appendl(dest, buf, len);
efree(buf);
}
#define excimer_log_known_string(index) CG(known_strings)[index]
#endif
#if PHP_VERSION_ID >= 80100
#define excimer_log_add_assoc_array add_assoc_array
#else
static inline void excimer_log_add_assoc_array(zval *dest, const char *key, HashTable *arr)
{
zval z_tmp;
ZVAL_ARR(&z_tmp, arr);
add_assoc_zval(dest, key, &z_tmp);
}
#endif
/* }}} */
void excimer_log_init(excimer_log *log)
{
log->entries_size = 0;
log->entries = NULL;
log->frames = ecalloc(1, sizeof(excimer_log_frame));
log->frames_size = 1;
log->reverse_frames = excimer_log_new_array(0);
log->epoch = 0;
log->event_count = 0;
}
void excimer_log_destroy(excimer_log *log)
{
if (log->entries) {
efree(log->entries);
}
if (log->frames) {
int i;
for (i = 0; i < log->frames_size; i++) {
if (log->frames[i].filename) {
zend_string_delref(log->frames[i].filename);
}
if (log->frames[i].class_name) {
zend_string_delref(log->frames[i].class_name);
}
if (log->frames[i].function_name) {
zend_string_delref(log->frames[i].function_name);
}
}
efree(log->frames);
}
zend_hash_destroy(log->reverse_frames);
efree(log->reverse_frames);
}
void excimer_log_set_max_depth(excimer_log *log, zend_long depth)
{
log->max_depth = depth;
}
void excimer_log_copy_options(excimer_log *dest, excimer_log *src)
{
dest->max_depth = src->max_depth;
dest->epoch = src->epoch;
dest->period = src->period;
}
void excimer_log_add(excimer_log *log, zend_execute_data *execute_data,
zend_long event_count, uint64_t timestamp)
{
uint32_t frame_index = excimer_log_find_or_add_frame(log, execute_data, 0);
excimer_log_entry *entry;
log->entries = safe_erealloc(log->entries, log->entries_size + 1,
sizeof(excimer_log_entry), 0);
entry = &log->entries[log->entries_size++];
entry->frame_index = frame_index;
entry->event_count = event_count;
log->event_count += event_count;
entry->timestamp = timestamp;
}
static uint32_t excimer_log_get_truncation_marker(excimer_log *log) {
zval* zp_index;
zval z_new_index;
excimer_log_frame *p_frame;
zp_index = zend_hash_str_find(log->reverse_frames,
excimer_log_truncated_name, sizeof(excimer_log_truncated_name) - 1);
if (zp_index) {
return excimer_safe_uint32(Z_LVAL_P(zp_index));
}
ZVAL_LONG(&z_new_index, log->frames_size);
zend_hash_str_add(log->reverse_frames,
excimer_log_truncated_name, sizeof(excimer_log_truncated_name) - 1,
&z_new_index);
log->frames = safe_erealloc(log->frames, log->frames_size + 1,
sizeof(excimer_log_frame), 0);
p_frame = &log->frames[log->frames_size++];
p_frame->filename = zend_string_init(excimer_log_fake_filename,
sizeof(excimer_log_fake_filename) - 1, 0);
p_frame->lineno = 1;
p_frame->closure_line = 0;
p_frame->class_name = NULL;
p_frame->function_name = zend_string_init(excimer_log_truncated_name,
sizeof(excimer_log_truncated_name) - 1, 0);
p_frame->prev_index = 0;
return excimer_safe_uint32(Z_LVAL(z_new_index));
}
static uint32_t excimer_log_find_or_add_frame(excimer_log *log,
zend_execute_data *execute_data, zend_long depth)
{
uint32_t prev_index;
if (!execute_data) {
return 0;
} else if (!execute_data->prev_execute_data) {
prev_index = 0;
} else if (log->max_depth && depth >= log->max_depth) {
prev_index = excimer_log_get_truncation_marker(log);
} else {
prev_index = excimer_log_find_or_add_frame(log,
execute_data->prev_execute_data, depth + 1);
}
if (!execute_data->func
|| !ZEND_USER_CODE(execute_data->func->common.type))
{
return prev_index;
} else {
zend_function *func = execute_data->func;
excimer_log_frame frame = {NULL};
smart_str ss_key = {NULL};
zend_string *str_key;
zval* zp_index;
frame.filename = func->op_array.filename;
zend_string_addref(frame.filename);
if (func->common.scope && func->common.scope->name) {
frame.class_name = func->common.scope->name;
zend_string_addref(frame.class_name);
}
if (func->common.function_name) {
frame.function_name = func->common.function_name;
zend_string_addref(frame.function_name);
}
if (func->op_array.fn_flags & ZEND_ACC_CLOSURE) {
frame.closure_line = func->op_array.line_start;
}
frame.lineno = execute_data->opline->lineno;
frame.prev_index = prev_index;
/* Make a key for reverse lookup */
smart_str_append(&ss_key, frame.filename);
smart_str_appendc(&ss_key, '\0');
excimer_log_smart_str_append_printf(&ss_key, "%d", frame.lineno);
smart_str_appendc(&ss_key, '\0');
excimer_log_smart_str_append_printf(&ss_key, "%d", frame.prev_index);
str_key = excimer_log_smart_str_extract(&ss_key);
/* Look for a matching frame in the reverse hashtable */
zp_index = zend_hash_find(log->reverse_frames, str_key);
if (zp_index) {
zend_string_free(str_key);
zend_string_delref(frame.filename);
if (frame.class_name) {
zend_string_delref(frame.class_name);
}
if (frame.function_name) {
zend_string_delref(frame.function_name);
}
return excimer_safe_uint32(Z_LVAL_P(zp_index));
} else {
zval z_new_index;
/* Create a new entry in the array and reverse hashtable */
ZVAL_LONG(&z_new_index, log->frames_size);
zend_hash_add(log->reverse_frames, str_key, &z_new_index);
log->frames = safe_erealloc(log->frames, log->frames_size + 1,
sizeof(excimer_log_frame), 0);
memcpy(&log->frames[log->frames_size++], &frame, sizeof(excimer_log_frame));
zend_string_delref(str_key);
return excimer_safe_uint32(Z_LVAL(z_new_index));
}
}
}
zend_long excimer_log_get_size(excimer_log *log)
{
return log->entries_size;
}
excimer_log_entry *excimer_log_get_entry(excimer_log *log, zend_long i)
{
if (i >= 0 && i < log->entries_size) {
return &log->entries[i];
} else {
return NULL;
}
}
excimer_log_frame *excimer_log_get_frame(excimer_log *log, zend_long i)
{
if (i > 0 && i < log->frames_size) {
return &log->frames[i];
} else {
return NULL;
}
}
static void excimer_log_append_no_spaces(smart_str *dest, zend_string *src)
{
size_t new_len = smart_str_alloc(dest, ZSTR_LEN(src), 0);
size_t prev_len = ZSTR_LEN(dest->s);
size_t i;
for (i = 0; i < ZSTR_LEN(src); i++) {
char c = ZSTR_VAL(src)[i];
if (c == ' ' || c == '\0') {
c = '_';
}
ZSTR_VAL(dest->s)[prev_len + i] = c;
}
ZSTR_LEN(dest->s) = new_len;
}
static void excimer_log_append_frame_name(smart_str *ss, excimer_log_frame *frame) {
if (frame->closure_line != 0) {
/* Annotate anonymous functions with their source location.
* Example: {closure:/path/to/file.php(123)}
*/
smart_str_appends(ss, "{closure:");
excimer_log_append_no_spaces(ss, frame->filename);
excimer_log_smart_str_append_printf(ss, "(%d)}", frame->closure_line);
} else if (frame->function_name == NULL) {
/* For file-scope code, use the file name */
excimer_log_append_no_spaces(ss, frame->filename);
} else {
if (frame->class_name) {
excimer_log_append_no_spaces(ss, frame->class_name);
smart_str_appends(ss, "::");
}
excimer_log_append_no_spaces(ss, frame->function_name);
}
}
zend_string *excimer_log_format_collapsed(excimer_log *log)
{
zend_long entry_index;
zend_long frame_index;
zval *zp_count;
zval z_count;
smart_str ss_out = {NULL};
HashTable frame_counts_storage, lines_storage;
HashTable *ht_frame_counts, *ht_lines;
ht_frame_counts = &frame_counts_storage;
memset(ht_frame_counts, 0, sizeof(HashTable));
zend_hash_init(ht_frame_counts, 0, NULL, NULL, 0);
ht_lines = &lines_storage;
memset(ht_lines, 0, sizeof(HashTable));
zend_hash_init(ht_lines, 0, NULL, NULL, 0);
excimer_log_frame ** frame_ptrs = NULL;
size_t frames_capacity = 0;
zend_string *str_line;
/* Collate frame counts */
for (entry_index = 0; entry_index < log->entries_size; entry_index++) {
excimer_log_entry *entry = excimer_log_get_entry(log, entry_index);
zp_count = zend_hash_index_find(ht_frame_counts, entry->frame_index);
if (!zp_count) {
ZVAL_LONG(&z_count, 0);
zp_count = zend_hash_index_add(ht_frame_counts, entry->frame_index, &z_count);
}
Z_LVAL_P(zp_count) += entry->event_count;
}
/* Format traces, and deduplicate frames that differ only in hidden line numbers */
ZEND_HASH_FOREACH_NUM_KEY_VAL(ht_frame_counts, frame_index, zp_count) {
zend_long current_frame_index = frame_index;
zend_long num_frames = 0;
excimer_log_frame *frame;
zend_long i;
int line_start = 1; /* TODO use bool when PHP 7.4 support is dropped */
smart_str ss_line = {NULL};
/* Build the array of frame pointers */
while (current_frame_index) {
frame = excimer_log_get_frame(log, current_frame_index);
if (num_frames >= frames_capacity) {
if (frames_capacity >= ZEND_LONG_MAX - 1) {
/* Probably unreachable */
zend_error_noreturn(E_ERROR, "Too many Excimer frames");
}
frames_capacity++;
frame_ptrs = safe_erealloc(frame_ptrs, frames_capacity, sizeof(*frame_ptrs), 0);
}
frame_ptrs[num_frames++] = frame;
current_frame_index = frame->prev_index;
}
/* Run through the array in reverse */
for (i = num_frames - 1; i >= 0; i--) {
frame = frame_ptrs[i];
if (line_start) {
line_start = 0;
} else {
smart_str_appends(&ss_line, ";");
}
excimer_log_append_frame_name(&ss_line, frame);
}
/* ht_lines[ss_line] += zp_count */
str_line = excimer_log_smart_str_extract(&ss_line);
zval *zp_line_count = zend_hash_find(ht_lines, str_line);
if (!zp_line_count) {
ZVAL_LONG(&z_count, 0);
zp_line_count = zend_hash_add(ht_lines, str_line, &z_count);
}
Z_LVAL_P(zp_line_count) += Z_LVAL_P(zp_count);
}
ZEND_HASH_FOREACH_END();
/* Concatenate lines */
ZEND_HASH_FOREACH_STR_KEY_VAL(ht_lines, str_line, zp_count) {
smart_str_append(&ss_out, str_line);
excimer_log_smart_str_append_printf(&ss_out, " " ZEND_LONG_FMT "\n", Z_LVAL_P(zp_count));
}
ZEND_HASH_FOREACH_END();
zend_hash_destroy(ht_frame_counts);
zend_hash_destroy(ht_lines);
efree(frame_ptrs);
return excimer_log_smart_str_extract(&ss_out);
}
static HashTable *excimer_log_frame_to_speedscope_array(excimer_log_frame *frame) {
HashTable *ht_func = excimer_log_new_array(0);
zval tmp;
smart_str ss_name = {NULL};
excimer_log_append_frame_name(&ss_name, frame);
ZVAL_STR(&tmp, excimer_log_smart_str_extract(&ss_name));
zend_hash_str_add(ht_func, "name", sizeof("name")-1, &tmp);
if (frame->filename) {
ZVAL_STR_COPY(&tmp, frame->filename);
zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_FILE), &tmp);
/* Don't include the line number since it causes speedscope to split functions */
}
return ht_func;
}
static zend_string *excimer_log_get_speedscope_frame_key(excimer_log_frame *frame) {
smart_str ss = {NULL};
excimer_log_append_frame_name(&ss, frame);
smart_str_appendc(&ss, '\0');
smart_str_append(&ss, frame->filename);
return excimer_log_smart_str_extract(&ss);
}
static uint32_t excimer_log_count_frames(excimer_log *log, uint32_t frame_index) {
uint32_t n = 0;
while (frame_index) {
n++;
frame_index = log->frames[frame_index].prev_index;
}
return n;
}
void excimer_log_get_speedscope_data(excimer_log *log, zval *zp_data) {
array_init(zp_data);
add_assoc_string(zp_data, "$schema", "https://www.speedscope.app/file-format-schema.json");
add_assoc_string(zp_data, "exporter", "Excimer");
HashTable *ht_frames = excimer_log_new_array(0);
HashTable *ht_indexes_by_key = excimer_log_new_array(0);
zend_long *lp_frame_indexes = ecalloc(log->frames_size, sizeof(zend_long));
zend_long i;
zval *zp_frame_index;
zend_string *str_key;
zval z_tmp, *zp_tmp;
/* Build the frames array */
for (i = 1; i < log->frames_size; i++) {
zend_long index;
excimer_log_frame *frame = &log->frames[i];
str_key = excimer_log_get_speedscope_frame_key(frame);
zp_frame_index = zend_hash_find(ht_indexes_by_key, str_key);
if (!zp_frame_index) {
/* Add the frame to ht_frames */
index = zend_hash_num_elements(ht_frames);
ZVAL_ARR(&z_tmp, excimer_log_frame_to_speedscope_array(frame));
zend_hash_next_index_insert_new(ht_frames, &z_tmp);
/* Add the frame index to ht_indexes_by_key */
ZVAL_LONG(&z_tmp, index);
zp_frame_index = zend_hash_add_new(ht_indexes_by_key, str_key, &z_tmp);
}
lp_frame_indexes[i] = Z_LVAL_P(zp_frame_index);
}
/* zp_data["shared"] = ["frames" => ht_frames] */
zval z_shared;
array_init(&z_shared);
excimer_log_add_assoc_array(&z_shared, "frames", ht_frames);
add_assoc_zval(zp_data, "shared", &z_shared);
/* Build the samples and weights arrays */
HashTable *ht_samples = excimer_log_new_array(log->entries_size);
HashTable *ht_weights = excimer_log_new_array(log->entries_size);
uint64_t first_timestamp = 0;
uint64_t last_timestamp = 0;
for (i = 0; i < log->entries_size; i++) {
excimer_log_entry *entry = &log->entries[i];
uint32_t frame_index = entry->frame_index;
if (i == 0) {
first_timestamp = entry->timestamp;
}
last_timestamp = entry->timestamp;
uint32_t num_frames = excimer_log_count_frames(log, frame_index);
uint32_t j;
/* Create the array with ZEND_HASH_FILL_PACKED. This is just a fast way
* to get it into the right state, with num_frames elements. */
HashTable *ht_stack = excimer_log_new_array(num_frames);
zend_hash_extend(ht_stack, num_frames, 1);
ZEND_HASH_FILL_PACKED(ht_stack) {
#if PHP_VERSION_ID < 70400
zval new_val;
ZVAL_LONG(&new_val, 0);
for (j = 0; j < num_frames; j++) {
ZEND_HASH_FILL_ADD(&new_val);
}
#else
for (j = 0; j < num_frames; j++) {
ZEND_HASH_FILL_SET_LONG(0);
ZEND_HASH_FILL_NEXT();
}
#endif
} ZEND_HASH_FILL_END();
/* Write the values in reverse order */
ZEND_HASH_REVERSE_FOREACH_VAL(ht_stack, zp_tmp) {
ZVAL_LONG(zp_tmp, lp_frame_indexes[frame_index]);
frame_index = log->frames[frame_index].prev_index;
}
ZEND_HASH_FOREACH_END();
ZVAL_ARR(&z_tmp, ht_stack);
zend_hash_next_index_insert_new(ht_samples, &z_tmp);
ZVAL_LONG(&z_tmp, entry->event_count * log->period);
zend_hash_next_index_insert_new(ht_weights, &z_tmp);
}
/* Build the profile array */
zval z_profile;
array_init(&z_profile);
add_assoc_string(&z_profile, "type", "sampled");
add_assoc_string(&z_profile, "name", "");
add_assoc_string(&z_profile, "unit", "nanoseconds");
add_assoc_long(&z_profile, "startValue", 0);
add_assoc_long(&z_profile, "endValue", last_timestamp - first_timestamp);
excimer_log_add_assoc_array(&z_profile, "samples", ht_samples);
excimer_log_add_assoc_array(&z_profile, "weights", ht_weights);
/* zp_data["profiles"] = [profile] */
zval z_profiles;
array_init(&z_profiles);
add_next_index_zval(&z_profiles, &z_profile);
add_assoc_zval(zp_data, "profiles", &z_profiles);
efree(lp_frame_indexes);
}
HashTable *excimer_log_frame_to_array(excimer_log_frame *frame) {
HashTable *ht_func = excimer_log_new_array(0);
zval tmp;
if (frame->filename) {
ZVAL_STR_COPY(&tmp, frame->filename);
zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_FILE), &tmp);
ZVAL_LONG(&tmp, frame->lineno);
zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_LINE), &tmp);
}
if (frame->class_name) {
ZVAL_STR_COPY(&tmp, frame->class_name);
zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_CLASS), &tmp);
}
if (frame->function_name) {
ZVAL_STR_COPY(&tmp, frame->function_name);
zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_FUNCTION), &tmp);
}
if (frame->closure_line) {
zend_string *s = zend_string_init("closure_line", sizeof("closure_line") - 1, 0);
ZVAL_LONG(&tmp, frame->closure_line);
zend_hash_add_new(ht_func, s, &tmp);
zend_string_delref(s);
}
return ht_func;
}
HashTable *excimer_log_trace_to_array(excimer_log *log, zend_long l_frame_index)
{
HashTable *ht_trace = excimer_log_new_array(0);
uint32_t frame_index = excimer_safe_uint32(l_frame_index);
while (frame_index) {
excimer_log_frame *frame = excimer_log_get_frame(log, frame_index);
HashTable *ht_func = excimer_log_frame_to_array(frame);
zval tmp;
ZVAL_ARR(&tmp, ht_func);
zend_hash_next_index_insert(ht_trace, &tmp);
frame_index = frame->prev_index;
}
return ht_trace;
}
/**
* ht[key] += term;
*/
static void excimer_log_array_incr(HashTable *ht, zend_string *sp_key, zend_long term)
{
zval *zp_value = zend_hash_find(ht, sp_key);
if (!zp_value) {
zval z_tmp;
ZVAL_LONG(&z_tmp, term);
zend_hash_add_new(ht, sp_key, &z_tmp);
} else {
Z_LVAL_P(zp_value) += term;
}
}
#if PHP_VERSION_ID < 80000
static int excimer_log_aggr_compare(const void *a, const void *b)
{
zval *zp_a = &((Bucket*)a)->val;
zval *zp_b = &((Bucket*)b)->val;
#else
static int excimer_log_aggr_compare(Bucket *a, Bucket *b)
{
zval *zp_a = &a->val;
zval *zp_b = &b->val;
#endif
zval *zp_a_incl = zend_hash_str_find(Z_ARRVAL_P(zp_a), "inclusive", sizeof("inclusive")-1);
zval *zp_b_incl = zend_hash_str_find(Z_ARRVAL_P(zp_b), "inclusive", sizeof("inclusive")-1);
return ZEND_NORMALIZE_BOOL(Z_LVAL_P(zp_b_incl) - Z_LVAL_P(zp_a_incl));
}
HashTable *excimer_log_aggr_by_func(excimer_log *log)
{
HashTable *ht_result = excimer_log_new_array(0);
zend_string *sp_inclusive = zend_string_init("inclusive", sizeof("inclusive")-1, 0);
zend_string *sp_self = zend_string_init("self", sizeof("self")-1, 0);
HashTable *ht_unique_names = excimer_log_new_array(0);
size_t entry_index;
zval z_zero;
ZVAL_LONG(&z_zero, 0);
for (entry_index = 0; entry_index < log->entries_size; entry_index++) {
excimer_log_entry *entry = excimer_log_get_entry(log, entry_index);
uint32_t frame_index = entry->frame_index;
int is_top = 1;
while (frame_index) {
excimer_log_frame *frame = excimer_log_get_frame(log, frame_index);
smart_str ss_name = {NULL};
zend_string *sp_name;
zval *zp_info;
zval z_tmp;
/* Make a human-readable name */
if (frame->closure_line != 0) {
/* Annotate anonymous functions with their source location.
* Example: {closure:/path/to/file.php(123)}
*/
smart_str_appends(&ss_name, "{closure:");
smart_str_append(&ss_name, frame->filename);
excimer_log_smart_str_append_printf(&ss_name, "(%d)}", frame->closure_line);
} else if (frame->function_name == NULL) {
/* For file-scope code, use the file name */
smart_str_append(&ss_name, frame->filename);
} else {
if (frame->class_name) {
smart_str_append(&ss_name, frame->class_name);
smart_str_appends(&ss_name, "::");
}
smart_str_append(&ss_name, frame->function_name);
}
sp_name = excimer_log_smart_str_extract(&ss_name);
/* If it is not in ht_result, add it, along with frame info */
zp_info = zend_hash_find(ht_result, sp_name);
if (!zp_info) {
ZVAL_ARR(&z_tmp, excimer_log_frame_to_array(frame));
zend_hash_add_new(Z_ARRVAL(z_tmp), sp_self, &z_zero);
zend_hash_add_new(Z_ARRVAL(z_tmp), sp_inclusive, &z_zero);
zp_info = zend_hash_add(ht_result, sp_name, &z_tmp);
}
/* If this is the top frame of a log entry, increment the "self" key */
if (is_top) {
excimer_log_array_incr(Z_ARRVAL_P(zp_info), sp_self, entry->event_count);
}
/* If this is the first instance of a function in an entry, i.e.
* counting recursive functions only once, increment the "inclusive" key */
if (zend_hash_find(ht_unique_names, sp_name) == NULL) {
excimer_log_array_incr(Z_ARRVAL_P(zp_info), sp_inclusive, entry->event_count);
/* Add the function to the unique_names array */
zend_hash_add_new(ht_unique_names, sp_name, &z_zero);
}
is_top = 0;
frame_index = frame->prev_index;
zend_string_delref(sp_name);
}
zend_hash_clean(ht_unique_names);
}
zend_hash_destroy(ht_unique_names);
zend_string_delref(sp_self);
zend_string_delref(sp_inclusive);
/* Sort the result in descending order by inclusive */
zend_hash_sort(ht_result, excimer_log_aggr_compare, 0);
return ht_result;
}
excimer-1.2.3/excimer_log.h 0000664 0001750 0001750 00000012442 14715550562 016436 0 ustar tstarling tstarling /* Copyright 2018 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef EXCIMER_LOG_H
#define EXCIMER_LOG_H
/**
* Structure representing a unique location in the code and its backtrace
*/
typedef struct _excimer_log_frame {
/** The filename, or may be fake e.g. "php shell code" */
zend_string *filename;
/** The executing line number within the filename */
uint32_t lineno;
/**
* If the function was a closure, the "start line" of its definition.
* Zero if the function was not a closure.
*/
uint32_t closure_line;
/** The class name, or NULL if there was no class name. */
zend_string *class_name;
/**
* The function name, or NULL if there was no function name, or a
* fake thing like "eval()'d code".
*/
zend_string *function_name;
/**
* The index within excimer_log.frames of the calling frame.
*/
uint32_t prev_index;
} excimer_log_frame;
/**
* Structure representing a log entry
*/
typedef struct _excimer_log_entry {
/**
* The index within excimer_log.frames of the frame associated with this event.
*/
uint32_t frame_index;
/**
* The number of times the timer elapsed before the log entry was finally registered.
*/
zend_long event_count;
/**
* The wall clock time at which the event occurred. The interpretation is
* caller-defined, but in Excimer it is the number of nanoseconds since boot.
*/
uint64_t timestamp;
} excimer_log_entry;
/**
* Structure representing the entire log
*/
typedef struct _excimer_log {
/** Array of log entries */
excimer_log_entry *entries;
/** Size of the "entries" array */
size_t entries_size;
/** Array of frames */
excimer_log_frame *frames;
/* Size of the "frames" array */
size_t frames_size;
/**
* A hashtable where the key is a unique frame identifier combining some
* elements of the frame object, and the value is the frame index. Used
* for deduplication of frames.
*/
HashTable *reverse_frames;
/**
* The maximum stack depth of collected frames. If this is exceeded, the
* backtrace is truncated.
*/
zend_long max_depth;
/**
* This is used by ExcimerProfiler to store the creation time of the
* ExcimerProfiler object.
*/
uint64_t epoch;
/**
* The nominal period in nanoseconds
*/
uint64_t period;
/**
* The sum of the event counts of all contained log entries
*/
zend_long event_count;
} excimer_log;
/**
* Initialise the log object.
*
* @param log Valid memory location at which to place the object
*/
void excimer_log_init(excimer_log *log);
/**
* Destroy the log object. This frees internal objects but does not free the
* excimer_log itself.
*
* @param log The log object to destroy
*/
void excimer_log_destroy(excimer_log *log);
/**
* Set the max depth
*
* @param log The log object
* @param depth The new depth
*/
void excimer_log_set_max_depth(excimer_log *log, zend_long depth);
/**
* Copy persistent options to another log. This is used during log rotation.
*
* @param dest The destination log object
* @param src The source log object
*/
void excimer_log_copy_options(excimer_log *dest, excimer_log *src);
/**
* Add a log entry
*
* @param log The log object
* @param execute_data The VM state
* @param event_count The number of times the timer expired
* @param timestamp The timestamp to store in the log entry
*/
void excimer_log_add(excimer_log *log, zend_execute_data *execute_data,
zend_long event_count, uint64_t timestamp);
/**
* Get the number of entries in the log
*
* @param log The log object
* @return The number of entries in the log
*/
zend_long excimer_log_get_size(excimer_log *log);
/**
* Get a log entry
*
* @param log The log object
* @param i The index of the entry
* @return The log entry, or NULL if the index is out of range
*/
excimer_log_entry *excimer_log_get_entry(excimer_log *log, zend_long i);
/**
* Get a frame by index
*
* @param log The log object
* @param i The index
* @return The frame, or NULL if the index is out of range
*/
excimer_log_frame *excimer_log_get_frame(excimer_log *log, zend_long i);
/**
* Format the log in flamegraph.pl collapsed format
*
* @param log The log object
* @return A new zend_string owned by the caller
*/
zend_string *excimer_log_format_collapsed(excimer_log *log);
/**
* Get an array in speedscope format
*
* @param log The log object
* @param zp_data The destination
*/
void excimer_log_get_speedscope_data(excimer_log *log, zval *zp_data);
/**
* Aggregate the log producing self/inclusive statistics as an array
*/
HashTable *excimer_log_aggr_by_func(excimer_log *log);
/**
* Convert a frame to a backtrace array for returning to the user
*
* @param log The log object
* @param l_frame_index The frame index
* @return A new hashtable, owned by the caller
*/
HashTable *excimer_log_trace_to_array(excimer_log *log, zend_long l_frame_index);
#endif
excimer-1.2.3/excimer_mutex.c 0000664 0001750 0001750 00000002643 14715550562 017014 0 ustar tstarling tstarling /* Copyright 2024 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "excimer_mutex.h"
#include "php.h"
void excimer_mutex_init(pthread_mutex_t *mutex)
{
int result = pthread_mutex_init(mutex, NULL);
if (result != 0) {
zend_error_noreturn(E_ERROR, "pthread_mutex_init(): %s", strerror(result));
}
}
void excimer_mutex_lock(pthread_mutex_t *mutex)
{
int result = pthread_mutex_lock(mutex);
if (result != 0) {
fprintf(stderr, "pthread_mutex_lock(): %s", strerror(result));
abort();
}
}
void excimer_mutex_unlock(pthread_mutex_t *mutex)
{
int result = pthread_mutex_unlock(mutex);
if (result != 0) {
fprintf(stderr, "pthread_mutex_unlock(): %s", strerror(result));
abort();
}
}
void excimer_mutex_destroy(pthread_mutex_t *mutex)
{
int result = pthread_mutex_destroy(mutex);
if (result != 0) {
zend_error_noreturn(E_ERROR, "pthread_mutex_destroy(): %s", strerror(result));
}
}
excimer-1.2.3/excimer_mutex.h 0000664 0001750 0001750 00000002474 14715550562 017023 0 ustar tstarling tstarling /* Copyright 2024 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef EXCIMER_MUTEX_H
#define EXCIMER_MUTEX_H
#include
/**
* Initialize the given mutex, raising a PHP Error in case of failure.
* @param mutex The mutex to initialize.
*/
void excimer_mutex_init(pthread_mutex_t *mutex);
/**
* Lock the given mutex, aborting the process in case of failure.
* @param mutex The mutex to lock.
*/
void excimer_mutex_lock(pthread_mutex_t *mutex);
/**
* Unlock the given mutex, aborting the process in case of failure.
* @param mutex The mutex to unlock.
*/
void excimer_mutex_unlock(pthread_mutex_t *mutex);
/**
* Destroy the given mutex, raising a PHP Error in case of failure.
* @param mutex The mutex to destroy.
*/
void excimer_mutex_destroy(pthread_mutex_t *mutex);
#endif
excimer-1.2.3/excimer_os_timer.h 0000664 0001750 0001750 00000005315 14715550562 017477 0 ustar tstarling tstarling /* Copyright 2024 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef EXCIMER_OS_TIMER_H
#define EXCIMER_OS_TIMER_H
#ifdef HAVE_CONFIG_H
#include
#endif
#ifdef HAVE_TIMER_CREATE
#include "excimer_os_timer_posix.h"
#else
#include "excimer_os_timer_kqueue.h"
#endif
/**
* Initialize a new timer.
* @param event_type May be EXCIMER_REAL or EXCIMER_CPU
* @param timer_id ID of the Excimer timer that owns this timer
* @param os_timer Pointer to the timer object to be populated
* @return SUCCESS if the timer was successfully initialized, FAILURE otherwise
*/
int excimer_os_timer_create(int event_type, intptr_t timer_id, excimer_os_timer_t* os_timer, excimer_os_timer_notify_function_t* notify_function);
/**
* Start a timer.
* @param os_timer Pointer to the timer to be started
* @param period The interval at which the timer should fire
* @param initial The initial delay of the timer
* @return SUCCESS if the timer was successfully started, FAILURE otherwise
*/
int excimer_os_timer_start(excimer_os_timer_t* os_timer, struct timespec *period, struct timespec *initial);
/**
* Stop a timer.
* @param os_timer Pointer to the timer to be stopped.
* @return SUCCESS if the timer was successfully stopped, FAILURE otherwise
*/
int excimer_os_timer_stop(excimer_os_timer_t* os_timer);
/**
* Clean up resources associated with a timer.
* @param os_timer Pointer to the timer to be cleaned up.
*/
int excimer_os_timer_delete(excimer_os_timer_t* os_timer);
/**
* Get the overrun count of a timer.
* @param os_timer Pointer to the timer whose overrun count should be fetched
* @return The number of times the timer has fired more than once in a single period
*/
zend_long excimer_os_timer_get_overrun_count(excimer_os_timer_t* os_timer);
/**
* Get the remaining time until the next scheduled expiratioh of a timer.
* This is an estimate based on the last reported firing time of the timer and the configured period.
*
* @param os_timer Pointer to the timer whose remaining time should be fetched
* @param remaining Pointer to the timespec struct to be populated with the remaining time
*/
void excimer_os_timer_get_time(excimer_os_timer_t *timer, struct timespec *remaining);
#endif
excimer-1.2.3/excimer_os_timer_kqueue.c 0000664 0001750 0001750 00000017162 14715550562 021054 0 ustar tstarling tstarling /* Copyright 2024 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "excimer_os_timer.h"
#include "excimer_mutex.h"
#include
#include
#include
#include
#include "php.h"
#include "zend_types.h"
#include
/**
* Get the current time using a monotonic clock, raising a PHP Warning on failure.
* @param time Pointer to a timespec struct to store the current time in.
*/
static void os_timer_get_current_time(struct timespec* time) {
if (clock_gettime(CLOCK_MONOTONIC, time) == -1) {
php_error_docref(NULL, E_WARNING, "clock_gettime(): %s", strerror(errno));
}
}
/**
* Check if a timespec is zero.
* @param ts Pointer to the timespec to check.
* @return 1 if the timespec is zero, 0 otherwise.
*/
static inline zend_bool os_timer_is_timespec_zero(struct timespec *ts) {
return ts->tv_sec == 0 && ts->tv_nsec == 0;
}
/**
* Handle a single timer event from a kqueue-based timer.
* @param timer The os_timer instance holding the kqueue-based timer.
* @param event Pointer to a kevent struct to store the event in.
* @return SUCCESS if event processing may continue, FAILURE otherwise.
*/
static int os_timer_handle_timer_event(excimer_os_timer_t *timer, struct kevent* event) {
int ret = kevent(timer->kq, NULL, 0, event, 1, NULL);
if (ret == -1) {
// EINTR merely implies that a signal was delivered before the timeout expired, so ignore it if it shows up.
if (errno != EINTR) {
// EBADF implies that the kqueue was closed, so we should exit the thread.
if (errno != EBADF) {
php_error_docref(NULL, E_WARNING, "kevent(): %s", strerror(errno));
}
return FAILURE;
}
} else if (ret > 0) {
// Help emulate POSIX timer_gettime by keeping track of each moment the timer fires.
excimer_mutex_lock(&timer->last_fired_at_mutex);
os_timer_get_current_time(&timer->last_fired_at);
excimer_mutex_unlock(&timer->last_fired_at_mutex);
// Match the behavior of POSIX's timer_getoverrun, which only counts additional timer expirations.
timer->overrun_count = event->data - 1;
// Forward the timer ID received in the event to the notify function.
// This uses a sigval so that excimer_timer_handle can be used as the notify function
// irrespective of the platform.
union sigval sv;
sv.sival_ptr = event->udata;
timer->notify_function(sv);
}
return SUCCESS;
}
/**
* Configure a kqueue-based timer using the given flags and period.
* @param os_timer Pointer to the os_timer this timer belongs to.
* @param flags Flags to use when configuring the kqueue timer.
* @param period Period to use for the timer.
* @return SUCCESS if the timer was successfully setup, FAILURE otherwise.
*/
static int os_timer_setup_kqueue_timer(excimer_os_timer_t *os_timer, int flags, struct timespec* period) {
zend_long timer_period_nanos = period->tv_sec * 1000000000 + period->tv_nsec;
EV_SET(&os_timer->kev, os_timer->id, EVFILT_TIMER, flags, NOTE_NSECONDS, timer_period_nanos, (void*)os_timer->id);
int ret = kevent(os_timer->kq, &os_timer->kev, 1, NULL, 0, NULL);
if (ret == -1) {
php_error_docref(NULL, E_WARNING, "kevent(): %s", strerror(errno));
return FAILURE;
}
return SUCCESS;
}
/**
* Main loop for a kqueue-based timer handler thread.
* @param arg Pointer to the os_timer this handler belongs to.
*/
static void* os_timer_kqueue_handle(void *arg) {
struct kevent event;
excimer_os_timer_t *timer = (excimer_os_timer_t*)arg;
// kqueue supports either periodic or one-shot timers, but not periodic timers with a delayed initial expiration.
// So, if the initial delay is non-zero, wait for the one-shot initial timer to expire,
// then - if needed - reconfigure the underlying kqueue as a periodic timer with the proper period going forward.
if (!os_timer_is_timespec_zero(&timer->initial)) {
if (os_timer_handle_timer_event(timer, &event) == FAILURE) {
return NULL;
}
if (os_timer_is_timespec_zero(&timer->period)) {
return NULL;
}
int ret = os_timer_setup_kqueue_timer(timer, EV_ADD | EV_ENABLE, &timer->period);
if (ret == FAILURE) {
return NULL;
}
}
while (os_timer_handle_timer_event(timer, &event) == SUCCESS) {}
return NULL;
}
int excimer_os_timer_create(int event_type, intptr_t timer_id, excimer_os_timer_t* os_timer, excimer_os_timer_notify_function_t* notify_function) {
if (event_type == EXCIMER_CPU) {
php_error_docref(NULL, E_WARNING, "CPU timers are not supported on this platform");
}
os_timer->kq = -1;
os_timer->overrun_count = 0;
os_timer->period.tv_nsec = 0;
os_timer->period.tv_sec = 0;
os_timer->last_fired_at.tv_sec = 0;
os_timer->last_fired_at.tv_nsec = 0;
os_timer->id = timer_id;
os_timer->notify_function = notify_function;
excimer_mutex_init(&os_timer->last_fired_at_mutex);
return SUCCESS;
}
int excimer_os_timer_start(excimer_os_timer_t* os_timer, struct timespec *period, struct timespec *initial) {
os_timer->period = *period;
os_timer->initial = *initial;
int kq = kqueue();
if (kq == -1) {
php_error_docref(NULL, E_WARNING, "kqueue(): %s", strerror(errno));
return FAILURE;
}
os_timer->kq = kq;
os_timer_get_current_time(&os_timer->last_fired_at);
int flags = EV_ADD | EV_ENABLE;
int ret;
// Use a non-periodic timer if an initial expiration was provided
if (!os_timer_is_timespec_zero(initial)) {
flags |= EV_ONESHOT;
ret = os_timer_setup_kqueue_timer(os_timer, flags, initial);
} else {
ret = os_timer_setup_kqueue_timer(os_timer, flags, period);
}
if (ret == FAILURE) {
return FAILURE;
}
ret = pthread_create(&os_timer->handler_thread_id, NULL, os_timer_kqueue_handle, os_timer);
if (ret != 0) {
php_error_docref(NULL, E_WARNING, "pthread_create(): %s", strerror(ret));
return FAILURE;
}
return SUCCESS;
}
int excimer_os_timer_stop(excimer_os_timer_t* os_timer) {
if (os_timer->kq != -1) {
os_timer->period.tv_sec = 0;
os_timer->period.tv_nsec = 0;
if (close(os_timer->kq) == -1) {
php_error_docref(NULL, E_WARNING, "close(): %s", strerror(errno));
return FAILURE;
}
// Wait for the signal handler thread to finish.
int ret = pthread_join(os_timer->handler_thread_id, NULL);
if (ret != 0) {
php_error_docref(NULL, E_WARNING, "pthread_join(): %s", strerror(ret));
return FAILURE;
}
}
return SUCCESS;
}
int excimer_os_timer_delete(excimer_os_timer_t *os_timer) {
excimer_mutex_destroy(&os_timer->last_fired_at_mutex);
return SUCCESS;
}
zend_long excimer_os_timer_get_overrun_count(excimer_os_timer_t* os_timer) {
return os_timer->overrun_count;
}
void excimer_os_timer_get_time(excimer_os_timer_t *timer, struct timespec *remaining) {
struct timespec will_fire_at, now;
excimer_mutex_lock(&timer->last_fired_at_mutex);
will_fire_at.tv_sec = timer->last_fired_at.tv_sec + timer->period.tv_sec;
will_fire_at.tv_nsec = timer->last_fired_at.tv_nsec + timer->period.tv_nsec;
excimer_mutex_unlock(&timer->last_fired_at_mutex);
os_timer_get_current_time(&now);
remaining->tv_sec = will_fire_at.tv_sec - now.tv_sec;
remaining->tv_nsec = will_fire_at.tv_nsec - now.tv_nsec;
if (remaining->tv_nsec < 0) {
remaining->tv_sec--;
remaining->tv_nsec += 1000000000;
}
if (remaining->tv_sec < 0) {
remaining->tv_sec = 0;
remaining->tv_nsec = 0;
}
}
excimer-1.2.3/excimer_os_timer_kqueue.h 0000664 0001750 0001750 00000003557 14715550562 021064 0 ustar tstarling tstarling /* Copyright 2024 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef EXCIMER_OS_TIMER_KQUEUE_H
#define EXCIMER_OS_TIMER_KQUEUE_H
#include
#include
#include
#include
#include
#include
#include "excimer_mutex.h"
#include "excimer_events.h"
#include "php.h"
/** Signature for a callback to be invoked when a timer fires. */
typedef void (excimer_os_timer_notify_function_t)(union sigval sv);
/** Represents a timer backed by kqueue. */
typedef struct {
/** File descriptor of the kqueue backing this timer */
int kq;
/** ID of the Excimer timer that owns this timer */
intptr_t id;
/** The overrun count for this timer */
volatile int overrun_count;
/** The kevent structure controlling this timer */
struct kevent kev;
/** The period of this timer */
struct timespec period;
/** The initial expiration time of this timer */
struct timespec initial;
/** Pointer to a callback to be invoked when this timer fires. */
excimer_os_timer_notify_function_t* notify_function;
/** Thread ID of the kqueue signal handler thread for this timer. */
pthread_t handler_thread_id;
/** The time at which this timer last fired. */
struct timespec last_fired_at;
/** Mutex to protect last_fired_at from concurrent access */
pthread_mutex_t last_fired_at_mutex;
} excimer_os_timer_t;
#endif
excimer-1.2.3/excimer_os_timer_posix.c 0000664 0001750 0001750 00000005520 14715550562 020712 0 ustar tstarling tstarling /* Copyright 2024 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "excimer_os_timer.h"
#include
#include
#include
#include
#include "php.h"
#include
int excimer_os_timer_create(int event_type, intptr_t timer_id, excimer_os_timer_t* os_timer, excimer_os_timer_notify_function_t* notify_function) {
struct sigevent ev;
memset(&ev, 0, sizeof(ev));
ev.sigev_notify = SIGEV_THREAD;
ev.sigev_notify_function = notify_function;
ev.sigev_value.sival_ptr = (void*)timer_id;
clockid_t clock_id;
if (event_type == EXCIMER_CPU) {
if (pthread_getcpuclockid(pthread_self(), &clock_id) != 0) {
php_error_docref(NULL, E_WARNING, "Unable to get thread clock ID: %s",
strerror(errno));
return FAILURE;
}
} else {
clock_id = CLOCK_MONOTONIC;
}
if (timer_create(clock_id, &ev, &os_timer->os_timer_id) != 0) {
php_error_docref(NULL, E_WARNING, "Unable to create timer: %s",
strerror(errno));
return FAILURE;
}
os_timer->id = timer_id;
return SUCCESS;
}
int excimer_os_timer_start(excimer_os_timer_t* os_timer, struct timespec *period, struct timespec *initial) {
struct itimerspec its;
its.it_interval = *period;
its.it_value = *initial;
if (timer_settime(os_timer->os_timer_id, 0, &its, NULL) != 0) {
php_error_docref(NULL, E_WARNING, "timer_settime(): %s", strerror(errno));
return FAILURE;
}
return SUCCESS;
}
int excimer_os_timer_stop(excimer_os_timer_t* os_timer) {
struct itimerspec its;
struct timespec zero = {0, 0};
its.it_interval = zero;
its.it_value = zero;
if (timer_settime(os_timer->os_timer_id, 0, &its, NULL) != 0) {
php_error_docref(NULL, E_WARNING, "timer_settime(): %s", strerror(errno));
return FAILURE;
}
return SUCCESS;
}
int excimer_os_timer_delete(excimer_os_timer_t *os_timer) {
if (timer_delete(os_timer->os_timer_id) != 0) {
php_error_docref(NULL, E_WARNING, "timer_delete(): %s", strerror(errno));
return FAILURE;
}
return SUCCESS;
}
zend_long excimer_os_timer_get_overrun_count(excimer_os_timer_t* os_timer) {
return timer_getoverrun(os_timer->os_timer_id);
}
void excimer_os_timer_get_time(excimer_os_timer_t *timer, struct timespec *remaining) {
struct itimerspec its;
timer_gettime(timer->os_timer_id, &its);
remaining->tv_sec = its.it_value.tv_sec;
remaining->tv_nsec = its.it_value.tv_nsec;
}
excimer-1.2.3/excimer_os_timer_posix.h 0000664 0001750 0001750 00000002361 14715550562 020717 0 ustar tstarling tstarling /* Copyright 2024 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef EXCIMER_OS_TIMER_POSIX_H
#define EXCIMER_OS_TIMER_POSIX_H
#include
#include
#include
#include "excimer_events.h"
#include "php.h"
/** Signature for a callback to be invoked when a timer fires. */
typedef void (excimer_os_timer_notify_function_t)(union sigval sv);
/** Represents a timer backed by a POSIX timer. */
typedef struct {
/** ID of the Excimer timer that owns this timer */
intptr_t id;
/** ID of the POSIX timer backing this timer */
timer_t os_timer_id;
/** Pointer to a callback to be invoked when this timer fires. */
excimer_os_timer_notify_function_t* notify_function;
} excimer_os_timer_t;
#endif
excimer-1.2.3/excimer_timer.c 0000664 0001750 0001750 00000021124 14715550562 016765 0 ustar tstarling tstarling /* Copyright 2018 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include
#include
#include
#include
#include
#include
#include "php.h"
#include "excimer_mutex.h"
#include "excimer_timer.h"
#include "zend_types.h"
#if PHP_VERSION_ID >= 80200
#define excimer_timer_atomic_bool_store(dest, value) zend_atomic_bool_store(dest, value)
#else
#define excimer_timer_atomic_bool_store(dest, value) *dest = value
#endif
excimer_timer_globals_t excimer_timer_globals;
ZEND_TLS excimer_timer_tls_t excimer_timer_tls;
static void excimer_timer_handle(union sigval sv);
static void excimer_timer_interrupt(zend_execute_data *execute_data);
static inline int excimer_timer_is_zero(struct timespec *ts)
{
return ts->tv_sec == 0 && ts->tv_nsec == 0;
}
void excimer_timer_module_init()
{
excimer_timer_globals.timers_by_id = malloc(sizeof(HashTable));
zend_hash_init(excimer_timer_globals.timers_by_id, 0, NULL, NULL, 1);
excimer_timer_globals.next_id = 1;
excimer_mutex_init(&excimer_timer_globals.mutex);
excimer_timer_globals.old_zend_interrupt_function = zend_interrupt_function;
zend_interrupt_function = excimer_timer_interrupt;
}
void excimer_timer_module_shutdown()
{
/* Wait for handler to finish, hopefully no more events are queued */
excimer_mutex_lock(&excimer_timer_globals.mutex);
zend_hash_destroy(excimer_timer_globals.timers_by_id);
free(excimer_timer_globals.timers_by_id);
/* "Attempting to destroy a locked mutex results in undefined behaviour" */
excimer_mutex_unlock(&excimer_timer_globals.mutex);
excimer_mutex_destroy(&excimer_timer_globals.mutex);
}
void excimer_timer_thread_init()
{
excimer_timer_tls.event_counts = malloc(sizeof(HashTable));
zend_hash_init(excimer_timer_tls.event_counts, 0, NULL, NULL, 1);
excimer_mutex_init(&excimer_timer_tls.mutex);
excimer_timer_tls.timers_by_id = malloc(sizeof(HashTable));
zend_hash_init(excimer_timer_tls.timers_by_id, 0, NULL, NULL, 1);
}
void excimer_timer_thread_shutdown()
{
zval *zp_thread;
/* Destroy any timers still active in this thread */
ZEND_HASH_FOREACH_VAL(excimer_timer_tls.timers_by_id, zp_thread) {
excimer_timer *timer = (excimer_timer*)Z_PTR_P(zp_thread);
excimer_timer_destroy(timer);
}
ZEND_HASH_FOREACH_END();
zend_hash_destroy(excimer_timer_tls.timers_by_id);
free(excimer_timer_tls.timers_by_id);
excimer_timer_tls.timers_by_id = NULL;
/* Acquire the thread so that we can write to event_counts.
* This will wait for the handler to finish. */
excimer_mutex_lock(&excimer_timer_tls.mutex);
zend_hash_destroy(excimer_timer_tls.event_counts);
free(excimer_timer_tls.event_counts);
excimer_timer_tls.event_counts = NULL;
excimer_mutex_unlock(&excimer_timer_tls.mutex);
excimer_mutex_destroy(&excimer_timer_tls.mutex);
}
int excimer_timer_init(excimer_timer *timer, int event_type,
excimer_timer_callback callback, void *user_data)
{
zval z_timer;
memset(timer, 0, sizeof(excimer_timer));
ZVAL_PTR(&z_timer, timer);
timer->vm_interrupt_ptr = &EG(vm_interrupt);
timer->callback = callback;
timer->user_data = user_data;
timer->event_counts_ptr = &excimer_timer_tls.event_counts;
timer->thread_mutex_ptr = &excimer_timer_tls.mutex;
excimer_mutex_lock(&excimer_timer_globals.mutex);
timer->id = excimer_timer_globals.next_id++;
if (timer->id == 0) {
excimer_mutex_unlock(&excimer_timer_globals.mutex);
php_error_docref(NULL, E_WARNING, "Timer ID counter has overflowed");
return FAILURE;
}
zend_hash_index_add(excimer_timer_globals.timers_by_id, timer->id, &z_timer);
excimer_mutex_unlock(&excimer_timer_globals.mutex);
zend_hash_index_add(excimer_timer_tls.timers_by_id, timer->id, &z_timer);
if (excimer_os_timer_create(event_type, timer->id, &timer->os_timer, &excimer_timer_handle) == FAILURE) {
return FAILURE;
}
timer->is_valid = 1;
timer->is_running = 0;
return SUCCESS;
}
void excimer_timer_start(excimer_timer *timer,
struct timespec *period, struct timespec *initial)
{
if (!timer->is_valid) {
php_error_docref(NULL, E_WARNING, "Unable to start uninitialised timer" );
return;
}
/* If a periodic timer has an initial value of 0, use the period instead,
* since it_value=0 means disarmed */
if (excimer_timer_is_zero(initial)) {
initial = period;
}
/* If the value is still zero, flag an error */
if (excimer_timer_is_zero(initial)) {
php_error_docref(NULL, E_WARNING, "Unable to start timer with a value of zero "
"duration and period");
return;
}
if (excimer_os_timer_start(&timer->os_timer, period, initial) == SUCCESS) {
timer->is_running = 1;
}
}
void excimer_timer_destroy(excimer_timer *timer)
{
if (!timer->is_valid) {
/* This could happen if the timer is manually destroyed after
* excimer_timer_thread_shutdown() is called */
return;
}
if (timer->event_counts_ptr != &excimer_timer_tls.event_counts) {
php_error_docref(NULL, E_WARNING,
"Cannot delete a timer belonging to a different thread");
return;
}
/* Stop the timer. This does not take effect immediately. */
if (timer->is_running) {
timer->is_running = 0;
excimer_os_timer_stop(&timer->os_timer);
}
/* Wait for the handler to finish if it is running */
excimer_mutex_lock(&excimer_timer_globals.mutex);
/* Remove the ID from the global hashtable */
zend_hash_index_del(excimer_timer_globals.timers_by_id, timer->id);
excimer_mutex_unlock(&excimer_timer_globals.mutex);
timer->is_valid = 0;
timer->event_counts_ptr = NULL;
/* Get the thread-local mutex */
excimer_mutex_lock(&excimer_timer_tls.mutex);
/* Remove the timer from the thread-local tables */
zend_hash_index_del(excimer_timer_tls.event_counts, timer->id);
zend_hash_index_del(excimer_timer_tls.timers_by_id, timer->id);
excimer_mutex_unlock(&excimer_timer_tls.mutex);
excimer_os_timer_delete(&timer->os_timer);
}
static void excimer_timer_handle(union sigval sv)
{
excimer_timer *timer;
zval *zp_event_count;
zend_long event_count;
intptr_t id = (intptr_t)sv.sival_ptr;
/* Acquire the global mutex, which protects timers_by_id */
excimer_mutex_lock(&excimer_timer_globals.mutex);
timer = (excimer_timer*)zend_hash_index_find_ptr(excimer_timer_globals.timers_by_id, id);
if (!timer || !timer->is_running) {
/* Timer has been deleted, ignore event */
excimer_mutex_unlock(&excimer_timer_globals.mutex);
return;
}
/* Acquire the thread-specific mutex */
excimer_mutex_lock(timer->thread_mutex_ptr);
/* Add the event count to the thread-local hashtable */
event_count = excimer_os_timer_get_overrun_count(&timer->os_timer) + 1;
zp_event_count = zend_hash_index_find(*timer->event_counts_ptr, id);
if (!zp_event_count) {
zval tmp;
ZVAL_LONG(&tmp, event_count);
zend_hash_index_add_new(*timer->event_counts_ptr, id, &tmp);
} else {
Z_LVAL_P(zp_event_count) += event_count;
}
excimer_timer_atomic_bool_store(timer->vm_interrupt_ptr, 1);
/* Release the mutexes */
excimer_mutex_unlock(timer->thread_mutex_ptr);
excimer_mutex_unlock(&excimer_timer_globals.mutex);
}
static void excimer_timer_interrupt(zend_execute_data *execute_data)
{
zend_long id;
zval *zp_count;
HashTable *event_counts;
excimer_mutex_lock(&excimer_timer_tls.mutex);
event_counts = excimer_timer_tls.event_counts;
excimer_timer_tls.event_counts = malloc(sizeof(HashTable));
zend_hash_init(excimer_timer_tls.event_counts, 0, NULL, NULL, 1);
excimer_mutex_unlock(&excimer_timer_tls.mutex);
ZEND_HASH_FOREACH_NUM_KEY_VAL(event_counts, id, zp_count) {
excimer_timer *timer = zend_hash_index_find_ptr(excimer_timer_tls.timers_by_id, id);
/* If a previous callback destroyed this timer, then it would be
* missing from the timers_by_id hashtable. */
if (timer) {
timer->callback(Z_LVAL_P(zp_count), timer->user_data);
}
}
ZEND_HASH_FOREACH_END();
zend_hash_destroy(event_counts);
free(event_counts);
if (excimer_timer_globals.old_zend_interrupt_function) {
excimer_timer_globals.old_zend_interrupt_function(execute_data);
}
}
void excimer_timer_get_time(excimer_timer *timer, struct timespec *remaining)
{
if (!timer->is_valid || !timer->is_running) {
remaining->tv_sec = 0;
remaining->tv_nsec = 0;
return;
}
excimer_os_timer_get_time(&timer->os_timer, remaining);
}
excimer-1.2.3/excimer_timer.h 0000664 0001750 0001750 00000011051 14715550562 016770 0 ustar tstarling tstarling /* Copyright 2018 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef EXCIMER_TIMER_H
#define EXCIMER_TIMER_H
#include "excimer_events.h"
#include "excimer_os_timer.h"
typedef void (*excimer_timer_callback)(zend_long, void *);
typedef struct _excimer_timer {
/** True if the object has been initialised and not destroyed */
int is_valid;
/** True if the timer has started */
int is_running;
/** &EG(vm_interrupt) in the relevant thread */
#if PHP_VERSION_ID >= 80200
zend_atomic_bool *vm_interrupt_ptr;
#else
zend_bool *vm_interrupt_ptr;
#endif
/** A unique ID identifying this object. These IDs are never reused, so that
* the ID can be used to identify events received for deleted objects. The
* type is intptr_t because it is 64 bits on a 64-bit platform, making
* an overflow less likely. Using a signed type means it can be converted
* to zend_long without changing the interpretation. We can't use int64_t
* or zend_long directly because the maximum width is sizeof(union sigval),
* which is too small on a 32-bit platform.
*/
intptr_t id;
/** The timer returned by excimer_os_timer_create() */
excimer_os_timer_t os_timer;
/** The event callback. */
excimer_timer_callback callback;
/** The event callback user data */
void *user_data;
/** A pointer to excimer_timer_tls.event_counts */
HashTable ** event_counts_ptr;
/** A pointer to excimer_timer_tls.mutex */
pthread_mutex_t *thread_mutex_ptr;
} excimer_timer;
typedef struct _excimer_timer_globals_t {
/**
* A hashtable mapping unique ID (excimer_timer.id) to the excimer_timer
* pointer. Use Z_PTR() to extract the pointer.
*/
HashTable *timers_by_id;
/**
* The mutex protecting timers_by_id and next_id from concurrent modification.
*/
pthread_mutex_t mutex;
/**
* The next ID to be used for excimer_timer.id
*/
intptr_t next_id;
/**
* The old value of the zend_interrupt_function hook. If set, this must be
* called to allow pcntl_signal() etc. to work.
*/
void (*old_zend_interrupt_function)(zend_execute_data *execute_data);
} excimer_timer_globals_t;
typedef struct _excimer_timer_tls_t {
/** A map of ID => event_count, protected by a mutex */
HashTable *event_counts;
/** The mutex protecting event_counts */
pthread_mutex_t mutex;
/** A map of ID => *timer, which is not protected, it is only accessed by
* the same thread */
HashTable *timers_by_id;
} excimer_timer_tls_t;
/**
* Global initialisation of the timer module
*/
void excimer_timer_module_init();
/**
* Global shutdown of the timer module
*/
void excimer_timer_module_shutdown();
/**
* Thread-local initialisation of the timer module. This must be called before
* any timer objects are created.
*/
void excimer_timer_thread_init();
/**
* Thread-local shutdown of the timer module. After calling this,
* excimer_timer_thread_init() may be called again to reinitialise the module.
*/
void excimer_timer_thread_shutdown();
/**
* Initialise a timer object allocated by the caller
*
* @param timer The timer object pointer
* @param event_type May be EXCIMER_REAL or EXCIMER_CPU
* @param callback The callback to call during VM interrupt
* @param user_data An arbitrary pointer passed to the callback
* @return SUCCESS or FAILURE
*/
int excimer_timer_init(excimer_timer *timer, int event_type,
excimer_timer_callback callback, void *user_data);
/**
* Start a timer. If there is no error, timer->is_running will be set to 1.
*
* @param timer The timer object
* @param period The period (it_interval) of the timer
* @param initial The initial timer value (it_value)
*/
void excimer_timer_start(excimer_timer *timer,
struct timespec *period, struct timespec *initial);
/**
* Destroy the contents of a timer object
*
* @param timer The timer object pointer, memory owned by the caller
*/
void excimer_timer_destroy(excimer_timer *timer);
/**
* Get remaining time
*
* @param timer The timer object
* @param remaining This struct will be filled with the time remaining
*/
void excimer_timer_get_time(excimer_timer *timer, struct timespec *remaining);
#endif
excimer-1.2.3/php_excimer.h 0000664 0001750 0001750 00000002650 14715550562 016444 0 ustar tstarling tstarling /* Copyright 2018 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef PHP_EXCIMER_H
#define PHP_EXCIMER_H
extern zend_module_entry excimer_module_entry;
#define phpext_excimer_ptr &excimer_module_entry
#define PHP_EXCIMER_VERSION "1.2.3"
#ifdef PHP_WIN32
# define PHP_EXCIMER_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
# define PHP_EXCIMER_API __attribute__ ((visibility("default")))
#else
# define PHP_EXCIMER_API
#endif
#ifdef ZTS
#include "TSRM.h"
#endif
#if defined(ZTS) && defined(COMPILE_DL_EXCIMER)
ZEND_TSRMLS_CACHE_EXTERN()
#endif
static inline uint32_t excimer_safe_uint32(zend_long i) {
if (i < 0 || i > UINT32_MAX) {
zend_error_noreturn(E_ERROR, "Integer out of range");
}
return (uint32_t)i;
}
#endif /* PHP_EXCIMER_H */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/