package.xml 0000664 0001750 0001750 00000020550 15012753166 014005 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.comyes2025-05-201.2.51.2.5stablestableApache 2.0
- Fix build error with libtool 1.5
7.1.01.8.0unixexcimer2025-05-191.2.41.2.4
- Rewrite the backend to work around a glibc timer aliasing bug (T391426).
Timer creation and deletion are now more expensive, so applications should
try to avoid unnecessary creation and deletion. It's cheaper to stop and
start an existing timer. Handling events is cheaper, so profiling
performance is improved.
- Compile with -fvisibility=hidden
2024-11-151.2.31.2.3
- Fix start time stagger, broken by previous release
2024-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.5/stubs/ExcimerLog.php 0000664 0001750 0001750 00000010315 15012753166 017672 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.5/stubs/ExcimerLogEntry.php 0000664 0001750 0001750 00000002372 15012753166 020720 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.5/tests/aliasing.phpt 0000664 0001750 0001750 00000001076 15012753166 017615 0 ustar tstarling tstarling --TEST--
glibc timer aliasing (T389734)
--SKIPIF--
--FILE--
setPeriod( 0.001 );
$timer1->setCallback( $noop );
$timer1->start();
usleep( 1000 );
$timer1 = null;
$timer2 = new \ExcimerTimer;
$timer2->setInterval( 60 );
$timer2->setCallback( $throw );
$timer2->start();
usleep( 1000 );
$timer2 = null;
}
echo "OK\n";
--EXPECT--
OK
excimer-1.2.5/tests/concurrentTimers.phpt 0000664 0001750 0001750 00000001564 15012753166 021376 0 ustar tstarling tstarling --TEST--
Concurrent timers
--SKIPIF--
--FILE--
setInterval($i * $interval);
$timer->setCallback(function () use (&$fired, $i) {
$fired[$i] = microtime(true);
} );
$timers[] = $timer;
}
$start = microtime(true);
foreach ($timers as $timer) {
$timer->start();
}
while (count($fired) < 10 && (microtime(true) - $start) < 10) {
usleep(100000);
}
for ($i = 1; $i <= 10; $i++) {
$min = ($i - 1) * $interval;
$max = ($i + 2) * $interval;
$t = $fired[$i] - $start;
if ($t >= $min && $t <= $max) {
print "$i: OK\n";
} else {
print "$i: FAILED: $min <= $t <= $max\n";
}
}
--EXPECT--
1: OK
2: OK
3: OK
4: OK
5: OK
6: OK
7: OK
8: OK
9: OK
10: OK
excimer-1.2.5/tests/cpu.phpt 0000664 0001750 0001750 00000014370 15012753166 016616 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();
$j = 0;
while (count($profiler->getLog()) < 30) {
for ($i = 0; $i < 100; $i++) {
foo();
}
if (++$j > 100000) {
break;
}
}
$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.5/tests/delayedPeriodic.phpt 0000664 0001750 0001750 00000002370 15012753166 021112 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 = 50000;
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.5/tests/getTime.phpt 0000664 0001750 0001750 00000001562 15012753166 017424 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.5/tests/maxDepth.phpt 0000664 0001750 0001750 00000001114 15012753166 017571 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.5/tests/oneshot.phpt 0000664 0001750 0001750 00000001352 15012753166 017502 0 ustar tstarling tstarling --TEST--
ExcimerTimer one-shot mode
--SKIPIF--
--FILE--
setInterval(0.5);
$count = 0;
$timer->setCallback(function() use (&$count) {
$count++;
});
$timer->start();
$start = microtime(true);
$elapsed = 0;
$fired_at = 0;
$interval = 20000;
while ($elapsed < 600000) {
$t = microtime(true);
$elapsed = ($t - $start) * 1e6;
if ($count === 1 && $fired_at === 0) {
$fired_at = $elapsed;
}
usleep($interval);
}
if ($count === 1 && $fired_at >= 400000 && $fired_at <= 600000) {
echo "oneshot: OK\n";
} else {
echo "oneshot: FAIL - count: $count, fired_at: $fired_at, elapsed: $elapsed\n";
}
--EXPECT--
oneshot: OK
excimer-1.2.5/tests/periodic.phpt 0000664 0001750 0001750 00000001252 15012753166 017620 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.5/tests/real.phpt 0000664 0001750 0001750 00000001250 15012753166 016743 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.5/tests/stagger.phpt 0000664 0001750 0001750 00000001020 15012753166 017447 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.5/tests/subprocess.phpt 0000664 0001750 0001750 00000000672 15012753166 020217 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.5/tests/timeout.phpt 0000664 0001750 0001750 00000000451 15012753166 017510 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.5/timerlib/README.md 0000664 0001750 0001750 00000001215 15012753166 017050 0 ustar tstarling tstarling This is a prototype for a platform-independent timer library to be shared
between Excimer and LuaSandbox.
The library doesn't have its own build system or packaging.
`timerlib_config.h` belongs to the application. It allows the application to
configure the library.
Linux is the fully tested production platform. The kqueue implementation should
support BSDs and Mac OS. We try to be generic enough to allow for future Windows
support.
In most cases, errors are handled by calling an application-defined function and
returning `TIMERLIB_FAILURE`. The return value should be ignorable if the app's
error function handled the error sufficiently well.
excimer-1.2.5/timerlib/timerlib.h 0000664 0001750 0001750 00000014253 15012753166 017557 0 ustar tstarling tstarling /* Copyright 2025 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 TIMERLIB_TIMERLIB_H
#define TIMERLIB_TIMERLIB_H
#include "timerlib_config.h"
/**
* The function pointer type used for notifying the caller of timer events
*/
typedef void (timerlib_notify_function_t)(void *data, int overrun_count);
#if defined(TIMERLIB_USE_POSIX)
#include "timerlib_posix.h"
#elif defined(TIMERLIB_USE_KQUEUE)
#include "timerlib_kqueue.h"
#else
#error "No timer implementation available"
#endif
#define timerlib_abort(libfunc, error_number) timerlib_abort_func(__func__, (libfunc), (error_number))
//--------------------------------------------------------------------------------
// Timer functions
//--------------------------------------------------------------------------------
/**
* Initialize a new timer.
*
* Regardless of the return value, use timerlib_timer_destroy() to destroy
* the structure.
*
* @param[out] timer Pointer to the timer object to be populated
* @param clock May be TIMERLIB_REAL for wall-clock time, or TIMERLIB_CPU for CPU time
* @param notify_function Function to be called when the timer expires
* @param notify_data The first parameter sent to notify_function
* @return TIMERLIB_SUCCESS if the timer was successfully initialized, TIMERLIB_FAILURE otherwise
*/
int timerlib_timer_init(timerlib_timer_t *timer, int clock,
timerlib_notify_function_t *notify_function, void *notify_data);
/**
* Start a one-shot timer
*
* @param[in,out] timer
* @param[in] duration How long before the timer expires
* @return TIMERLIB_SUCCESS if the timer was successfully started, TIMERLIB_FAILURE otherwise
*/
int timerlib_timer_start_oneshot(timerlib_timer_t *timer, timerlib_timespec_t *duration);
/**
* Start a periodic timer
*
* @param[in,out] timer
* @param[in] period The interval at which the timer should fire
* @return TIMERLIB_SUCCESS if the timer was successfully started, TIMERLIB_FAILURE otherwise
*/
int timerlib_timer_start_periodic(timerlib_timer_t *timer, timerlib_timespec_t *period);
/**
* Start a generic timer
*
* @param[in,out] timer
* @param[in] period The period at which the timer should fire, or zero for a one-shot timer
* @param[in] initial The initial delay of the timer
* @return TIMERLIB_SUCCESS if the timer was successfully started, TIMERLIB_FAILURE otherwise
*/
int timerlib_timer_start(timerlib_timer_t *timer, timerlib_timespec_t *period, timerlib_timespec_t *initial);
/**
* Stop a timer.
*
* @param[in,out] timer
* @return TIMERLIB_SUCCESS if the timer was successfully stopped, TIMERLIB_FAILURE otherwise
*/
int timerlib_timer_stop(timerlib_timer_t *timer);
/**
* Clean up resources associated with a timer.
*
* If a timer callback is executing, wait until it returns.
*
* It is guaranteed that the callback will not be called again after this
* function returns.
*
* @param[in,out] timer
*/
void timerlib_timer_destroy(timerlib_timer_t *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[in] timer
* @param[out] remaining Pointer to the timespec struct to be populated with the remaining time
* @return TIMERLIB_SUCCESS or TIMERLIB_FAILURE
*/
int timerlib_timer_get_time(timerlib_timer_t *timer, timerlib_timespec_t *remaining);
//--------------------------------------------------------------------------------
// Clock functions
//--------------------------------------------------------------------------------
/**
* Get the current time relative to some implementation-dependent epoch
* @param clock Either TIMERLIB_REAL or TIMERLIB_CPU
* @param[out] time Pointer to the timespec to be populated with the current time
* @return TIMERLIB_SUCCESS or TIMERLIB_FAILURE
*/
int timerlib_clock_get_time(int clock, timerlib_timespec_t * time);
//--------------------------------------------------------------------------------
// Timespec functions
//--------------------------------------------------------------------------------
/**
* A long billion
*/
#define TIMERLIB_BILLION_L 1000000000L
/**
* A long long billion
*/
#define TIMERLIB_BILLION_LL 1000000000LL
/**
* Determine if a timespec is zero
* @param[in] ts
* @return 1 if the seconds and nanoseconds parts are both zero, 0 otherwise
*/
static inline int timerlib_timespec_is_zero(timerlib_timespec_t *ts)
{
return ts->tv_sec == 0 && ts->tv_nsec == 0;
}
/**
* Convert a timespec to a number of nanoseconds
*
* Overflow will silently wrap around after 585 years.
*
* @param[in] ts
* @return The number of nanoseconds
*/
static inline uint64_t timerlib_timespec_to_ns(timerlib_timespec_t *ts)
{
return (uint64_t)ts->tv_nsec + (uint64_t)ts->tv_sec * TIMERLIB_BILLION_LL;
}
/**
* Convert a timespec to a floating-point number of seconds
*
* Some precision will be lost if the timespec is larger than about 104 days.
*
* @param[in] ts
* @return The number of seconds
*/
static inline double timerlib_timespec_to_double(timerlib_timespec_t *ts)
{
return timerlib_timespec_to_ns(ts) * 1e-9;
}
/**
* Add two timespecs like a += b
*
* @param[in,out] a The destination and left operand
* @param[in] b The right operand
*/
void timerlib_timespec_add(timerlib_timespec_t * a, const timerlib_timespec_t * b);
/**
* Subtract timespecs like a -= b
*
* @param[in,out] a The destination and left operand
* @param[in] b The right operand
*/
void timerlib_timespec_subtract(timerlib_timespec_t * a, const timerlib_timespec_t * b);
/**
* Populate a timespec from a floating-point number of seconds
*
* @param[out] dest
* @param[in] source
*/
void timerlib_timespec_from_double(timerlib_timespec_t * dest, double source);
#endif
excimer-1.2.5/timerlib/timerlib_common.c 0000664 0001750 0001750 00000002360 15012753166 021116 0 ustar tstarling tstarling #include "timerlib.h"
#include
void timerlib_timespec_add(timerlib_timespec_t * a, const timerlib_timespec_t * b)
{
a->tv_sec += b->tv_sec;
a->tv_nsec += b->tv_nsec;
if (a->tv_nsec > TIMERLIB_BILLION_L) {
a->tv_nsec -= TIMERLIB_BILLION_L;
a->tv_sec++;
}
}
void timerlib_timespec_subtract(timerlib_timespec_t * a, const timerlib_timespec_t * b)
{
a->tv_sec -= b->tv_sec;
if (a->tv_nsec < b->tv_nsec) {
a->tv_sec--;
a->tv_nsec += TIMERLIB_BILLION_L - b->tv_nsec;
} else {
a->tv_nsec -= b->tv_nsec;
}
}
void timerlib_timespec_from_double(timerlib_timespec_t * 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 * 1e9);
if (dest->tv_nsec >= TIMERLIB_BILLION_L) {
dest->tv_nsec -= TIMERLIB_BILLION_L;
dest->tv_sec ++;
}
}
int timerlib_timer_start_oneshot(timerlib_timer_t *timer, timerlib_timespec_t *duration)
{
timerlib_timespec_t period = {0};
return timerlib_timer_start(timer, &period, duration);
}
int timerlib_timer_start_periodic(timerlib_timer_t *timer, timerlib_timespec_t *period)
{
return timerlib_timer_start(timer, period, period);
}
excimer-1.2.5/timerlib/timerlib_kqueue.c 0000664 0001750 0001750 00000014772 15012753166 021137 0 ustar tstarling tstarling /* Copyright 2025 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 "timerlib.h"
#include
#include
#include
#include
#include
#include
#include "timerlib_pthread_mutex.h"
/**
* Handle a single timer event from a kqueue-based timer.
* @param timer The timer instance holding the kqueue-based timer.
* @param event Pointer to a kevent struct to store the event in.
* @return TIMERLIB_SUCCESS if event processing may continue, TIMERLIB_FAILURE otherwise.
*/
static int timerlib_handle_timer_event(timerlib_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) {
timerlib_abort("kevent", errno);
}
return TIMERLIB_FAILURE;
}
} else if (ret > 0) {
// Help emulate POSIX timer_gettime by keeping track of each moment the timer fires.
timerlib_mutex_lock(&timer->last_fired_at_mutex);
timerlib_clock_get_time(0, &timer->last_fired_at);
timerlib_mutex_unlock(&timer->last_fired_at_mutex);
// Match the behavior of POSIX's timer_getoverrun, which only counts additional timer expirations.
int overrun_count = (int)event->data - 1;
timer->notify_function(timer->notify_data, overrun_count);
}
return TIMERLIB_SUCCESS;
}
/**
* Configure a kqueue-based timer using the given flags and period.
* @param timer Pointer to the timer this timer belongs to.
* @param flags Flags to use when configuring the kqueue timer.
* @param period Period to use for the timer.
* @return TIMERLIB_SUCCESS if the timer was successfully setup, TIMERLIB_FAILURE otherwise.
*/
static int timerlib_setup_kqueue_timer(timerlib_timer_t *timer, int flags, timerlib_timespec_t* period) {
struct kevent kev;
EV_SET(&kev, 1, EVFILT_TIMER, flags,
NOTE_NSECONDS, timerlib_timespec_to_ns(period), (void*)timer);
int ret = kevent(timer->kq, &kev, 1, NULL, 0, NULL);
if (ret == -1) {
timerlib_report_errno("kevent", errno);
return TIMERLIB_FAILURE;
}
return TIMERLIB_SUCCESS;
}
/**
* Main loop for a kqueue-based timer handler thread.
* @param arg Pointer to the timer this handler belongs to.
*/
static void* timerlib_kqueue_handle(void *arg) {
struct kevent event;
timerlib_timer_t *timer = (timerlib_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 (!timerlib_timespec_is_zero(&timer->initial)) {
if (timerlib_handle_timer_event(timer, &event) == TIMERLIB_FAILURE) {
return NULL;
}
if (timerlib_timespec_is_zero(&timer->period)) {
return NULL;
}
int ret = timerlib_setup_kqueue_timer(timer, EV_ADD | EV_ENABLE, &timer->period);
if (ret == TIMERLIB_FAILURE) {
return NULL;
}
}
while (timerlib_handle_timer_event(timer, &event) == TIMERLIB_SUCCESS) {}
return NULL;
}
int timerlib_timer_init(timerlib_timer_t *timer, int clock,
timerlib_notify_function_t *notify_function, void *notify_data)
{
*timer = (timerlib_timer_t){
.kq = -1,
.notify_function = notify_function,
.notify_data = notify_data,
.last_fired_at_mutex = PTHREAD_MUTEX_INITIALIZER
};
return TIMERLIB_SUCCESS;
}
int timerlib_timer_start(timerlib_timer_t* timer, timerlib_timespec_t *period, timerlib_timespec_t *initial) {
timer->period = *period;
timer->initial = *initial;
int kq = kqueue();
if (kq == -1) {
timerlib_report_errno("kqueue", errno);
return TIMERLIB_FAILURE;
}
timer->kq = kq;
timerlib_clock_get_time(0, &timer->last_fired_at);
int flags = EV_ADD | EV_ENABLE;
int ret;
// Use a non-periodic timer if an initial expiration was provided
if (!timerlib_timespec_is_zero(initial)) {
flags |= EV_ONESHOT;
ret = timerlib_setup_kqueue_timer(timer, flags, initial);
} else {
ret = timerlib_setup_kqueue_timer(timer, flags, period);
}
if (ret == TIMERLIB_FAILURE) {
return TIMERLIB_FAILURE;
}
ret = pthread_create(&timer->handler_thread_id, NULL, timerlib_kqueue_handle, timer);
if (ret != 0) {
timerlib_report_errno("pthread_create", ret);
return TIMERLIB_FAILURE;
}
return TIMERLIB_SUCCESS;
}
int timerlib_timer_stop(timerlib_timer_t* timer) {
if (timer->kq != -1) {
timer->period.tv_sec = 0;
timer->period.tv_nsec = 0;
if (close(timer->kq) == -1) {
timerlib_report_errno("close", errno);
return TIMERLIB_FAILURE;
}
// Wait for the signal handler thread to finish.
int ret = pthread_join(timer->handler_thread_id, NULL);
if (ret != 0) {
timerlib_report_errno("pthread_join", ret);
return TIMERLIB_FAILURE;
}
}
return TIMERLIB_SUCCESS;
}
void timerlib_timer_destroy(timerlib_timer_t *timer) {
int ret = pthread_mutex_destroy(&timer->last_fired_at_mutex);
if (ret != 0) {
timerlib_report_errno("pthread_mutex_destroy", ret);
}
}
int timerlib_timer_get_time(timerlib_timer_t *timer, timerlib_timespec_t *remaining) {
// Get the time at which the timer last fired
timerlib_mutex_lock(&timer->last_fired_at_mutex);
timerlib_timespec_t last_fired_at = timer->last_fired_at;
timerlib_mutex_unlock(&timer->last_fired_at_mutex);
// Add the period to get the next expiry time
timerlib_timespec_t will_fire_at = timer->period;
timerlib_timespec_add(&will_fire_at, &last_fired_at);
// Subtract the current time to get the remaining time
timerlib_timespec_t now;
timerlib_clock_get_time(0, &now);
*remaining = will_fire_at;
timerlib_timespec_subtract(remaining, &now);
return TIMERLIB_SUCCESS;
}
int timerlib_clock_get_time(int clock, timerlib_timespec_t* time) {
if (clock_gettime(CLOCK_MONOTONIC, time) == -1) {
timerlib_report_errno("clock_gettime", errno);
return TIMERLIB_FAILURE;
}
return TIMERLIB_SUCCESS;
}
excimer-1.2.5/timerlib/timerlib_kqueue.h 0000664 0001750 0001750 00000001701 15012753166 021130 0 ustar tstarling tstarling #ifndef TIMERLIB_KQUEUE_H
#define TIMERLIB_KQUEUE_H
#include
#include
typedef struct timespec timerlib_timespec_t;
/** Represents a timer backed by kqueue. */
typedef struct {
/** File descriptor of the kqueue backing this timer */
int kq;
/** The overrun count for this timer */
volatile int overrun_count;
/** 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. */
timerlib_notify_function_t *notify_function;
/** Data to be passed to the callback */
void *notify_data;
/** 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;
} timerlib_timer_t;
#endif
excimer-1.2.5/timerlib/timerlib_posix.c 0000664 0001750 0001750 00000020166 15012753166 020774 0 ustar tstarling tstarling /* Copyright 2025 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.
*/
// For gettid, pthread_attr_setsigmask_np
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include "timerlib.h"
#include
#include
// https://sourceware.org/bugzilla/show_bug.cgi?id=27417
# ifndef sigev_notify_thread_id
# define sigev_notify_thread_id _sigev_un._tid
# endif
#ifdef HAVE_PTHREAD_ATTR_SETSIGMASK_NP
// glibc 2.32+: set the new thread's sigmask using an attribute
static inline void timerlib_block_signals(pthread_attr_t *attr, sigset_t *old_sigmask)
{
sigset_t sigmask;
sigfillset(&sigmask);
pthread_attr_setsigmask_np(attr, &sigmask);
}
static inline void timerlib_unblock_signals(sigset_t *old_sigmask)
{
}
#else
// glibc before 2.32: save and restore the main thread's sigmask so that the new
// thread will inherit a sigmask with all signals blocked
static inline void timerlib_block_signals(pthread_attr_t *attr, sigset_t *old_sigmask)
{
sigset_t sigmask;
sigfillset(&sigmask);
pthread_sigmask(SIG_BLOCK, &sigmask, old_sigmask);
}
static inline void timerlib_unblock_signals(sigset_t *old_sigmask)
{
pthread_sigmask(SIG_SETMASK, old_sigmask, NULL);
}
#endif
#ifndef HAVE_GETTID
#include
#define gettid() ((pid_t)syscall(SYS_gettid))
#endif
#include "timerlib_pthread_mutex.h"
/**
* This is called by the handler thread to notify the main thread that
* timer->tid is valid.
*/
static void timerlib_notify_ready(timerlib_timer_t *timer)
{
timerlib_mutex_lock(&timer->ready_mutex);
timer->ready = 1;
int error = pthread_cond_broadcast(&timer->ready_cond);
if (error) {
timerlib_abort("pthread_cond_broadcast", error);
}
timerlib_mutex_unlock(&timer->ready_mutex);
}
/**
* Stop the handler thread (called from the main thread)
*/
static int timerlib_graceful_exit(timerlib_timer_t *timer)
{
// We share TIMERLIB_SIGNAL between timer expiration and shutdown, mostly
// to be less intrusive on the application. But if an expiration signal
// is already pending, sending another will be ignored. We set a variable
// so that the thread will terminate anyway in that case.
timer->killed = 1;
int error = pthread_kill(timer->thread, TIMERLIB_SIGNAL);
if (error) {
timerlib_report_errno("pthread_kill", error);
return TIMERLIB_FAILURE;
}
return TIMERLIB_SUCCESS;
}
/**
* Join the handler thread, wait for it to exit.
*/
static int timerlib_join(timerlib_timer_t *timer)
{
int error = pthread_join(timer->thread, NULL);
if (error) {
timerlib_report_errno("pthread_join", error);
return TIMERLIB_FAILURE;
}
return TIMERLIB_SUCCESS;
}
/**
* Convert a timerlib clock constant to a POSIX clock
* @param clock May be either TIMERLIB_REAL or TIMERLIB_CPU
*/
static clockid_t timerlib_map_clock(int clock)
{
if (clock == TIMERLIB_REAL) {
return CLOCK_MONOTONIC;
} else {
clockid_t c = CLOCK_MONOTONIC;
int error = pthread_getcpuclockid(pthread_self(), &c);
if (error) {
timerlib_report_errno("pthread_getcpuclockid", error);
}
return c;
}
}
/**
* The start routine of the handler thread
*/
static void* timerlib_timer_thread_main(void *data)
{
timerlib_timer_t *timer = data;
timer->tid = gettid();
// Tell the main thread we are ready to start
timerlib_notify_ready(timer);
// Receive only our signal
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, TIMERLIB_SIGNAL);
while (1) {
siginfo_t si;
// There is an identical empty loop in the glibc SIGEV_THREAD
// implementation. The documentation indicates that EINTR is the only
// possible error.
while (sigwaitinfo(&sigset, &si) < 0);
// If timer_stop() has been called, exit the thread
if (timer->killed) {
return NULL;
}
// If signal occurred due to a timer expiration, call the callback.
if (si.si_code == SI_TIMER) {
timer->notify_function(timer->notify_data, si.si_overrun);
}
}
}
int timerlib_timer_init(timerlib_timer_t *timer, int clock,
timerlib_notify_function_t *notify_function, void *notify_data)
{
// Initialise the data. Fields that are not named are automatically zeroed.
*timer = (timerlib_timer_t){
.clock = clock,
.notify_function = notify_function,
.notify_data = notify_data,
.ready_cond = PTHREAD_COND_INITIALIZER,
.ready_mutex = PTHREAD_MUTEX_INITIALIZER,
};
// Block all signals. This prevents the thread from receiving process-directed
// signals which are normally handled by the main thread.
pthread_attr_t attr;
sigset_t old_sigset;
pthread_attr_init(&attr);
timerlib_block_signals(&attr, &old_sigset);
// Create the thread
int error = pthread_create(&timer->thread, &attr, timerlib_timer_thread_main, timer);
timerlib_unblock_signals(&old_sigset);
pthread_attr_destroy(&attr);
if (error) {
timerlib_report_errno("pthread_create", error);
return TIMERLIB_FAILURE;
}
timer->thread_valid = 1;
// Wait for timer->tid to become valid
timerlib_mutex_lock(&timer->ready_mutex);
while (!timer->ready) {
error = pthread_cond_wait(&timer->ready_cond, &timer->ready_mutex);
if (error) {
timerlib_report_errno("pthread_cond_wait", error);
return TIMERLIB_FAILURE;
}
}
timerlib_mutex_unlock(&timer->ready_mutex);
// Create the timer
// This needs to be done in the main thread, otherwise it silently fails
// to deliver any events in CPU mode.
struct sigevent sev = {
.sigev_signo = TIMERLIB_SIGNAL,
.sigev_notify = SIGEV_THREAD_ID,
.sigev_notify_thread_id = timer->tid
};
if (timer_create(timerlib_map_clock(timer->clock), &sev, &timer->timer)) {
timerlib_report_errno("timer_create", errno);
return TIMERLIB_FAILURE;
}
// Remember that timer->timer is valid and needs to be deleted
timer->timer_valid = 1;
return TIMERLIB_SUCCESS;
}
int timerlib_timer_start(timerlib_timer_t *timer, timerlib_timespec_t *period, timerlib_timespec_t *initial)
{
struct itimerspec its = {
.it_interval = *period,
.it_value = *initial
};
if (!timer->timer_valid) {
// No point reporting another error, since we presumably already reported
// an error in timerlib_timer_init
return TIMERLIB_FAILURE;
}
if (timerlib_timespec_is_zero(initial)) {
// Make sure the timer is armed
its.it_value.tv_nsec = 1;
}
if (timer_settime(timer->timer, 0, &its, NULL) != 0) {
timerlib_report_errno("timer_settime", errno);
return TIMERLIB_FAILURE;
}
return TIMERLIB_SUCCESS;
}
int timerlib_timer_stop(timerlib_timer_t * timer)
{
struct itimerspec its = {0};
if (!timer->timer_valid) {
return TIMERLIB_FAILURE;
}
if (timer_settime(timer->timer, 0, &its, NULL) != 0) {
timerlib_report_errno("timer_settime", errno);
return TIMERLIB_FAILURE;
}
return TIMERLIB_SUCCESS;
}
void timerlib_timer_destroy(timerlib_timer_t * timer)
{
if (timer->thread_valid) {
timer->thread_valid = 0;
if (timerlib_graceful_exit(timer) == TIMERLIB_SUCCESS) {
timerlib_join(timer);
}
}
if (timer->timer_valid) {
timer->timer_valid = 0;
if (timer_delete(timer->timer)) {
timerlib_report_errno("timer_delete", errno);
}
}
}
int timerlib_timer_get_time(timerlib_timer_t *timer, timerlib_timespec_t *remaining)
{
int ret = TIMERLIB_FAILURE;
struct itimerspec its = {0};
// Write to *remaining even on error, so that an unchecked error value will
// not lead to the caller using uninitialised memory.
if (timer->timer_valid) {
if (timer_gettime(timer->timer, &its)) {
timerlib_report_errno("timer_gettime", errno);
} else {
ret = TIMERLIB_SUCCESS;
}
}
*remaining = its.it_value;
return ret;
}
int timerlib_clock_get_time(int clock, timerlib_timespec_t * time)
{
if (clock_gettime(timerlib_map_clock(clock), time)) {
*time = (timerlib_timespec_t){0};
timerlib_report_errno("timer_gettime", errno);
return TIMERLIB_FAILURE;
} else {
return TIMERLIB_SUCCESS;
}
}
excimer-1.2.5/timerlib/timerlib_posix.h 0000664 0001750 0001750 00000003064 15012753166 020777 0 ustar tstarling tstarling /* Copyright 2025 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
#define TIMERLIB_HAVE_CPU_CLOCK
typedef struct timespec timerlib_timespec_t;
typedef struct {
// ID of the POSIX timer backing this timer
timer_t timer;
// True if timer is valid will need to be deleted
int timer_valid;
// The handler thread
pthread_t thread;
// True if the thread is valid
int thread_valid;
// The handler thread ID
pid_t tid;
// The clock type, TIMERLIB_REAL or TIMERLIB_CPU
int clock;
// Pointer to a callback to be invoked when this timer fires.
timerlib_notify_function_t *notify_function;
// Data to be passed to notify_function as the first argument
void *notify_data;
// The handler thread sets this when it is ready to receive events
int ready;
// A condition variable associated with "ready"
pthread_cond_t ready_cond;
// A mutex associated with "ready"
pthread_mutex_t ready_mutex;
// The main thread sets this to notify the handler thread that it should exit
int killed;
} timerlib_timer_t;
excimer-1.2.5/timerlib/timerlib_pthread_mutex.h 0000664 0001750 0001750 00000002332 15012753166 022503 0 ustar tstarling tstarling /* Copyright 2025 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.
*/
/**
* Helper functions for dealing with pthread mutexes
*/
#define timerlib_mutex_lock(mutex) timerlib_mutex_lock_func(mutex, __func__)
#define timerlib_mutex_unlock(mutex) timerlib_mutex_unlock_func(mutex, __func__)
static void timerlib_mutex_lock_func(pthread_mutex_t *mutex, const char *func)
{
int result = pthread_mutex_lock(mutex);
if (result != 0) {
timerlib_abort_func(func, "pthread_mutex_lock", result);
}
}
static void timerlib_mutex_unlock_func(pthread_mutex_t *mutex, const char *func)
{
int result = pthread_mutex_unlock(mutex);
if (result != 0) {
timerlib_abort_func(func, "pthread_mutex_unlock", result);
}
}
excimer-1.2.5/LICENSE 0000664 0001750 0001750 00000026136 15012753166 015000 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.5/README.md 0000664 0001750 0001750 00000001125 15012753166 015241 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.5/config.m4 0000664 0001750 0001750 00000003476 15012753166 015504 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_CHECK_DECL(SIGEV_THREAD_ID, [
AC_CHECK_LIB(rt, timer_create)
AC_CHECK_DECL(pthread_attr_setsigmask_np,[
AC_DEFINE(HAVE_PTHREAD_ATTR_SETSIGMASK_NP, 1, [Whether pthread_attr_setsigmask_np is available])
],,[[
#define _GNU_SOURCE 1
#include
]])
AC_CHECK_DECL(gettid,[
AC_DEFINE(HAVE_GETTID, 1, [Whether gettid is available])
],,[[
#define _GNU_SOURCE 1
#include
]])
AC_DEFINE(HAVE_SIGEV_THREAD_ID, 1, [Whether SIGEV_THREAD_ID is available on the current platform])
PHP_EVAL_LIBLINE($LIBS, EXCIMER_SHARED_LIBADD)
excimer_os_sources=timerlib/timerlib_posix.c
], [
AC_SEARCH_LIBS([kevent], [kqueue], [
AC_DEFINE(HAVE_KQUEUE, 1, [Whether kqueue is available on the current platform])
PHP_EVAL_LIBLINE($LIBS, EXCIMER_SHARED_LIBADD)
excimer_os_sources=timerlib/timerlib_kqueue.c
], [
AC_MSG_ERROR([excimer requires timer_create or kevent])
])
], [[
#include
]])
AC_SEARCH_LIBS([pthread_mutex_lock], [pthread], [
PHP_EVAL_LIBLINE($LIBS, EXCIMER_SHARED_LIBADD)
])
dnl Avoid exporting symbols unnecessarily
AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],
[CFLAGS="$CFLAGS -fvisibility=hidden"])
PHP_SUBST(EXCIMER_SHARED_LIBADD)
PHP_NEW_EXTENSION(excimer, excimer.c \
excimer_mutex.c \
excimer_timer.c \
excimer_log.c \
timerlib/timerlib_common.c \
$excimer_os_sources, $ext_shared)
dnl Create build directory (T394738)
PHP_ADD_BUILD_DIR([$ext_builddir/timerlib])
fi
excimer-1.2.5/excimer.c 0000664 0001750 0001750 00000127705 15012753166 015577 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;
/** Whether a parameter has changed that requires reinitialisation of the timer. */
int need_reinit;
/** 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;
/** Whether a parameter has changed that requires reinitialisation of the timer. */
int need_reinit;
/** 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 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 it.
// This allows application code to detect and gracefully handle a lack of CPU profiling support.
#ifdef TIMERLIB_HAVE_CPU_CLOCK
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;
timerlib_clock_get_time(TIMERLIB_REAL, &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 = timerlib_timespec_to_ns(&now_ts);
ZVAL_NULL(&profiler->z_callback);
profiler->event_type = EXCIMER_REAL;
profiler->need_reinit = 1;
// Stagger start time
initial = php_mt_rand() * EXCIMER_DEFAULT_PERIOD / UINT32_MAX;
timerlib_timespec_from_double(&profiler->initial, initial);
timerlib_timespec_from_double(&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;
timerlib_timespec_from_double(&profiler->period, period);
timerlib_timespec_from_double(&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;
profiler->need_reinit = 1;
}
/* }}} */
/* {{{ 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->need_reinit || !profiler->timer.is_valid) {
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;
}
profiler->need_reinit = 0;
}
excimer_timer_start(&profiler->timer,
&profiler->period,
&profiler->initial);
}
/* }}} */
static void ExcimerProfiler_stop(ExcimerProfiler_obj *profiler) /* {{{ */
{
if (profiler->timer.is_valid) {
excimer_timer_stop(&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;
timerlib_clock_get_time(TIMERLIB_REAL, &now_ts);
now_ns = timerlib_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;
timer_obj->need_reinit = 1;
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;
timer_obj->need_reinit = 1;
}
/* }}} */
/* {{{ 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();
timerlib_timespec_from_double(&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();
timerlib_timespec_from_double(&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(timerlib_timespec_to_double(&ts));
}
/* }}} */
static void ExcimerTimer_start(ExcimerTimer_obj *timer_obj) /* {{{ */
{
if (timer_obj->need_reinit || !timer_obj->timer.is_valid) {
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;
}
timer_obj->need_reinit = 0;
}
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_stop(&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);
}
timerlib_timespec_from_double(&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.5/excimer_events.h 0000664 0001750 0001750 00000001400 15012753166 017147 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.5/excimer_log.c 0000664 0001750 0001750 00000052627 15012753166 016440 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.5/excimer_log.h 0000664 0001750 0001750 00000012442 15012753166 016434 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.5/excimer_mutex.c 0000664 0001750 0001750 00000002643 15012753166 017012 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.5/excimer_mutex.h 0000664 0001750 0001750 00000002474 15012753166 017021 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.5/excimer_timer.c 0000664 0001750 0001750 00000020262 15012753166 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(void * data, int overrun_count);
static void excimer_timer_interrupt(zend_execute_data *execute_data);
/**
* Add a timer to the pending list. Unsynchronised, i.e. the caller is
* responsible for locking the mutex if required.
*/
static void excimer_timer_list_enqueue(excimer_timer *timer)
{
excimer_timer **head_pp = &excimer_timer_tls.pending_head;
if (!timer->pending_next) {
if (*head_pp) {
timer->pending_next = *head_pp;
timer->pending_prev = (*head_pp)->pending_prev;
(*head_pp)->pending_prev->pending_next = timer;
(*head_pp)->pending_prev = timer;
} else {
*head_pp = timer;
timer->pending_next = timer;
timer->pending_prev = timer;
}
}
}
/**
* Remove the first (FIFO) timer from the pending list and provide a pointer
* to it. (unsynchronised)
*
* @param[out] timer_pp
* @return True if a timer was returned, false if the list was empty
*/
static int excimer_timer_list_dequeue(excimer_timer **timer_pp)
{
excimer_timer **head_pp = &excimer_timer_tls.pending_head;
if (*head_pp) {
// Get the pending timer
excimer_timer *timer = *timer_pp = *head_pp;
if (timer->pending_next == timer) {
// List is now empty
*head_pp = NULL;
} else {
// Relink the head and neighbours
timer->pending_next->pending_prev = timer->pending_prev;
*head_pp = timer->pending_prev->pending_next = timer->pending_next;
}
// Unlink the timer being returned
timer->pending_next = NULL;
timer->pending_prev = NULL;
return 1;
} else {
return 0;
}
}
/**
* Remove the specified timer from the pending list, if it is in there. If it
* is not in the list, do nothing. (unsynchronised)
*/
static void excimer_timer_list_remove(excimer_timer *timer)
{
excimer_timer **head_pp = &excimer_timer_tls.pending_head;
if (timer->pending_next) {
if (timer->pending_next == timer) {
*head_pp = NULL;
} else {
timer->pending_next->pending_prev = timer->pending_prev;
timer->pending_prev->pending_next = timer->pending_next;
if (*head_pp == timer) {
*head_pp = timer->pending_next;
}
}
timer->pending_next = NULL;
timer->pending_prev = NULL;
}
}
/**
* Atomically dequeue a timer and get its event count at the time of removal
* from the queue. The timer may be immediately re-added to the queue by the
* event handler.
*
* @param[out] timer_pp Where to put the pointer to the timer
* @param[out] event_count_p Where to put the event count
* @return True if a timer was removed, false if the list was empty.
*/
static int excimer_timer_pending_dequeue(excimer_timer **timer_pp, zend_long *event_count_p)
{
excimer_mutex_lock(&excimer_timer_tls.mutex);
int ret = excimer_timer_list_dequeue(timer_pp);
if (ret) {
*event_count_p = (*timer_pp)->event_count;
(*timer_pp)->event_count = 0;
}
excimer_mutex_unlock(&excimer_timer_tls.mutex);
return ret;
}
// Note: functions with external linkage are documented in the header
void excimer_timer_module_init()
{
excimer_timer_globals.old_zend_interrupt_function = zend_interrupt_function;
zend_interrupt_function = excimer_timer_interrupt;
}
void excimer_timer_module_shutdown()
{
}
void excimer_timer_thread_init()
{
excimer_timer_tls = (excimer_timer_tls_t){
.mutex = PTHREAD_MUTEX_INITIALIZER
};
}
void excimer_timer_thread_shutdown()
{
if (excimer_timer_tls.timers_active) {
// If this ever happens, it means we've got the logic wrong and we need
// to rethink. It's very bad for timers to keep existing after thread
// termination, because the mutex will be a dangling pointer. It's not
// much help to avoid excimer_mutex_destroy() here because the whole TLS
// segment will be destroyed and reused.
php_error_docref(NULL, E_WARNING, "Timer still active at thread termination");
} else {
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->tls = &excimer_timer_tls;
if (timerlib_timer_init(&timer->tl_timer, event_type, &excimer_timer_handle, timer) == FAILURE) {
timerlib_timer_destroy(&timer->tl_timer);
return FAILURE;
}
excimer_timer_tls.timers_active++;
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 (timerlib_timespec_is_zero(initial)) {
initial = period;
}
/* If the value is still zero, flag an error */
if (timerlib_timespec_is_zero(initial)) {
php_error_docref(NULL, E_WARNING, "Unable to start timer with a value of zero "
"duration and period");
return;
}
if (timerlib_timer_start(&timer->tl_timer, period, initial) == SUCCESS) {
timer->is_running = 1;
}
}
void excimer_timer_stop(excimer_timer *timer)
{
if (!timer->is_valid) {
php_error_docref(NULL, E_WARNING, "Unable to start uninitialised timer" );
return;
}
if (timer->is_running) {
if (timerlib_timer_stop(&timer->tl_timer) == SUCCESS) {
timer->is_running = 0;
}
}
}
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->tls != &excimer_timer_tls) {
php_error_docref(NULL, E_WARNING,
"Cannot delete a timer belonging to a different thread");
return;
}
/* Stop the timer */
if (timer->is_running) {
timer->is_running = 0;
timerlib_timer_stop(&timer->tl_timer);
}
/* Destroy the timer. This will wait until any events are done. */
timerlib_timer_destroy(&timer->tl_timer);
excimer_timer_tls.timers_active--;
/* Remove the timer from the pending list */
excimer_mutex_lock(&excimer_timer_tls.mutex);
excimer_timer_list_remove(timer);
excimer_mutex_unlock(&excimer_timer_tls.mutex);
timer->is_valid = 0;
timer->tls = NULL;
}
static void excimer_timer_handle(void * data, int overrun_count)
{
excimer_timer *timer = (excimer_timer*)data;
excimer_mutex_lock(&excimer_timer_tls.mutex);
timer->event_count += overrun_count + 1;
excimer_timer_list_enqueue(timer);
excimer_mutex_unlock(&excimer_timer_tls.mutex);
excimer_timer_atomic_bool_store(timer->vm_interrupt_ptr, 1);
}
static void excimer_timer_interrupt(zend_execute_data *execute_data)
{
excimer_timer *timer = NULL;
zend_long count = 0;
while (excimer_timer_pending_dequeue(&timer, &count)) {
timer->callback(count, timer->user_data);
}
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;
}
timerlib_timer_get_time(&timer->tl_timer, remaining);
}
excimer-1.2.5/excimer_timer.h 0000664 0001750 0001750 00000010713 15012753166 016772 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 "timerlib/timerlib.h"
typedef void (*excimer_timer_callback)(zend_long, void *);
/* Forward declaration */
typedef struct _excimer_timer_tls_t excimer_timer_tls_t;
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
/** The underlying timerlib timer */
timerlib_timer_t tl_timer;
/** The event callback. */
excimer_timer_callback callback;
/** The event callback user data */
void *user_data;
/**
* The next pending timer, in a circular doubly-linked list of pending
* timers, or NULL if the timer is not in the list.
*/
struct _excimer_timer *pending_next;
/** The previous pending timer */
struct _excimer_timer *pending_prev;
zend_long event_count;
/** The thread-local data associated with the thread that created the timer */
excimer_timer_tls_t *tls;
} excimer_timer;
typedef struct _excimer_timer_globals_t {
/**
* 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 {
/** The mutex protecting the pending list */
pthread_mutex_t mutex;
/**
* The head of the list of pending timers. This is a doubly-linked list
* because we need to randomly delete members when timers are destroyed.
* It's circular, with the last element pointing back to the first element,
* because that makes it a bit easier to check whether an element is in the
* list. A circular list means that objects have a non-NULL prev/next if and
* only if they are in the list.
*/
excimer_timer *pending_head;
/** The number of active timers in this thread */
unsigned long timers_active;
} 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);
/**
* Stop a timer. If there is no error, timer->is_running will be set to 0.
*
* @param timer The timer object
*/
void excimer_timer_stop(excimer_timer *timer);
/**
* 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.5/php_excimer.h 0000664 0001750 0001750 00000002650 15012753166 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.
*/
#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.5"
#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
*/
excimer-1.2.5/timerlib_config.h 0000664 0001750 0001750 00000002441 15012753166 017271 0 ustar tstarling tstarling #ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include "excimer_events.h"
#if defined(HAVE_SIGEV_THREAD_ID)
#define TIMERLIB_USE_POSIX
#elif defined(HAVE_KQUEUE)
#define TIMERLIB_USE_KQUEUE
#else
#error "No timer implementation available"
#endif
#define TIMERLIB_REAL EXCIMER_REAL
#define TIMERLIB_CPU EXCIMER_CPU
#define TIMERLIB_FAILURE FAILURE
#define TIMERLIB_SUCCESS SUCCESS
// PHP uses SIGRTMIN for request timeouts
#define TIMERLIB_SIGNAL (SIGRTMIN + 1)
/**
* Report an error from a C library function in the main thread
* @param func The function which encountered the error
* @param error_number The error number, e.g. EINVAL
*/
inline static void timerlib_report_errno(const char *func, int error_number)
{
php_error_docref(NULL, E_WARNING, "Error in %s(): %s", func, strerror(error_number));
}
/**
* Report an error from a C library function and abort the program
* @param tlfunc The timerlib function which caused the error
* @param libfunc The C library function which caused the error
* @param error_number The error number, e.g. EINVAL
*/
inline static void timerlib_abort_func(const char *tlfunc, const char *libfunc, int error_number) {
fprintf(stderr, "Fatal error in %s/%s(): %s\n", tlfunc, libfunc, strerror(error_number));
abort();
}