iliaal / statgrab
PHP extension wrapping libstatgrab, the cross-platform system-statistics library. CPU, memory, disk I/O, filesystems, network interfaces, processes, users.
Package info
Language:C
Type:php-ext
Ext name:ext-statgrab
pkg:composer/iliaal/statgrab
Requires
- php: >=8.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-15 00:26:14 UTC
README
A native PHP extension wrapping libstatgrab, the cross-platform system-statistics library. Originally released to PECL in 2006 against libstatgrab 0.6; the 2.0.0 line is a full modernization for the PHP 8.0+ era against libstatgrab 0.92+.
Supports PHP 8.0 through 8.5 on glibc Linux, musl, macOS, and *BSD.
⚖️ Why native
Without an extension, reading system stats from PHP means one of these:
- Shell out to
top,vmstat,df, orpsand parse the output. Each call forks a process, which adds up if you poll every few seconds, and the output format drifts between OS releases. - Parse
/procby hand. That's Linux-only, and each file (/proc/meminfo,/proc/loadavg,/proc/diskstats,/proc/net/dev) has its own format and edge cases. - Query a stats daemon or monitoring framework. That adds a network hop and a daemon to deploy, which is overkill if all you need is a CPU number for a health endpoint.
statgrab calls libstatgrab in-process. libstatgrab handles the per-OS path (Linux /proc, FreeBSD kvm, macOS host_* APIs) and is packaged for Debian/Ubuntu, Homebrew, and FreeBSD. The 2006 PECL binding never shipped a PHP 8 build, and its quirks (stringified counters, swapped page-stat keys, a flat name_list for users) made it awkward even when you could compile it.
✨ Key features
| Feature | Notes |
|---|---|
| Cross-platform | glibc Linux, musl, macOS, FreeBSD |
| Procedural + OO API | 2006 sg_* function names preserved; modern Statgrab class on top |
| Bundled libstatgrab option | Vendored 0.92.1 with local patches (see vendor/libstatgrab/LOCAL_PATCHES.md); resulting .so has no runtime dependency on libstatgrab.so |
| Modern types | Counters returned as 64-bit int, not stringified numbers |
| Modern PHP errors | E_WARNING on library failure, ArgumentCountError for arg-count violations |
| BC-preserved 2006 names | Drop-in for callers of the original PECL extension, except the 2.0 BC breaks listed below |
🚀 Quick start
PIE (recommended on PHP 8.x)
PIE is the PHP Foundation's PECL successor.
It installs from Packagist and builds against the active php-config.
Install libstatgrab first (package names are under From source), then:
pie install iliaal/statgrab
Then add extension=statgrab to your php.ini.
PECL
statgrab is also on PECL:
pecl install statgrab
From source
sudo apt install libstatgrab-dev # Debian/Ubuntu # OR: brew install libstatgrab # macOS # OR: pkg install libstatgrab # FreeBSD phpize ./configure --with-statgrab make sudo make install
Then add extension=statgrab to your php.ini.
config.m4 finds libstatgrab through pkg-config first, then falls back
to a path probe (/usr and /usr/local). Pass
--with-statgrab=<prefix> to point at a custom install.
Bundled libstatgrab (statically linked, leak-fixed)
The repo carries a vendored copy of libstatgrab 0.92.1 under
vendor/libstatgrab/ with local patches (see
vendor/libstatgrab/LOCAL_PATCHES.md) for a process-exit leak, a stack
buffer overflow in the diff getters, uninitialised process session IDs,
and a heap buffer overflow when parsing Linux process command lines,
which upstream has not released yet, plus a local fix for two undefined
shifts. A Git checkout does not include the generated
libstatgrab configure script, so generate it with autoreconf first.
This requires Autoconf, Automake, and Libtool in addition to the usual
C compiler, Make, and PHP development tools (on Debian/Ubuntu, install
autoconf automake libtool php-dev build-essential pkg-config). To use it:
(cd vendor/libstatgrab && autoreconf -fiv && \ ./configure --enable-static --disable-shared --without-ncurses --with-pic && make) phpize ./configure --with-statgrab=bundled make
The resulting .so has no libstatgrab.so runtime dependency.
PHP's make clean also deletes the bundled libstatgrab.a; after it,
run make -C vendor/libstatgrab before make, or the build stops.
The vendored libstatgrab tree stays LGPL 2.1+ (see LICENSE.libstatgrab);
the extension code stays PHP-3.01 (see LICENSE). Dynamic-link or
static-link, neither license infects the other.
API
Procedural
The 2006 function names are preserved.
sg_cpu_percent_usage(int $source = Statgrab::CPU_PERCENT_ENTIRE): array|false // $source: CPU_PERCENT_ENTIRE (cumulative since boot, default), // CPU_PERCENT_LAST_DIFF (reuse the latest diff; collect one if absent), // CPU_PERCENT_NEW_DIFF (collect a new diff from the previous CPU sample) sg_cpu_totals(): array|false // cumulative jiffies + ctx switches/syscalls/IRQs sg_cpu_diff(): array|false // jiffies since the previous CPU sample; see below sg_diskio_stats(): array|false // [diskname => [read, written, time_frame]] sg_diskio_stats_diff(): array|false sg_fs_stats(): array|false // mounted filesystems with size/used/inodes/... sg_general_stats(): array|false // os_name, hostname, uptime, ncpus, ... sg_load_stats(): array|false // min1, min5, min15 sg_memory_stats(): array|false // total, free, used, cache (bytes) sg_swap_stats(): array|false // total, free, used (bytes) sg_network_stats(): array|false // [ifname => sent/received/packets/...] sg_network_stats_diff(): array|false sg_page_stats(): array|false // pages_in, pages_out (cumulative) sg_page_stats_diff(): array|false sg_process_count(): array|false // total, running, sleeping, stopped, zombie sg_process_stats(?int $sort_order = null, int $num_entries = 0): array|false // $num_entries 0 or > count returns all entries; negative throws ValueError sg_user_stats(): array|false // [{login_name, device, pid, login_time, ...}] sg_network_iface_stats(): array|false // [ifname => {speed, duplex, active}] // 2.1 additions: sg_valid_filesystems(): array|false // list of fs-type strings libstatgrab treats as "real" sg_set_valid_filesystems(array $filesystems): bool // subsequent sg_fs_stats() only returns these fs_type values sg_snapshot(): bool // backend snapshot hook; see CPU sampling below sg_error_details(): array|false // [code, errno, message, arg] for the pending libstatgrab error, or false if none
CPU sampling and timing
With bundled libstatgrab 0.92.1 on the supported Unix platforms, CPU
results carry the legacy previous_run key with these meanings (in whole
seconds). The corresponding Statgrab methods use the same behavior:
sg_cpu_totals()/cpuStats()andsg_cpu_percent_usage(CPU_PERCENT_ENTIRE)/cpu(CPU_PERCENT_ENTIRE): the Unix timestamp when the current cumulative sample was collected.sg_cpu_diff()/cpuDiff()andsg_cpu_percent_usage(CPU_PERCENT_NEW_DIFF)/cpu(CPU_PERCENT_NEW_DIFF): collect a new CPU sample and report elapsed wall-clock seconds since the previous CPU sample. If no baseline exists yet, the first call returns cumulative data and its collection timestamp instead.sg_cpu_percent_usage(CPU_PERCENT_LAST_DIFF)/cpu(CPU_PERCENT_LAST_DIFF): reuse the most recently computed diff and its elapsed seconds without collecting another sample. If no diff exists, collect one using the same first-call behavior asCPU_PERCENT_NEW_DIFF.
The CPU_PERCENT_* names above are Statgrab class constants. CPU
sampling state is shared by both APIs within the calling thread:
cumulative CPU calls also replace its baseline. Whole-second wall-clock differences may be zero for rapid
samples and can be affected by system clock adjustments.
To measure a fresh interval, call sg_cpu_totals(), wait for the desired
interval, then call sg_cpu_percent_usage(Statgrab::CPU_PERCENT_NEW_DIFF).
Avoid other CPU sampling calls between them. sg_snapshot() is a no-op
in bundled libstatgrab on Unix; it does not seed or reset these counters.
System-linked builds use the installed libstatgrab's backend behavior.
Rapid consecutive CPU diff samples can contain no elapsed CPU ticks. In
that case CPU percentages are 0.0 rather than NaN, so samples remain
JSON-encodable; wait between samples to measure meaningful utilization.
sg_diskio_stats() / sg_diskio_stats_diff(), sg_network_stats() /
sg_network_stats_diff(), and sg_network_iface_stats() key rows by
device/interface name; a duplicate name (multipath/LVM can repeat one)
falls back to a numeric key instead of overwriting the earlier row.
proc_title from sg_process_stats() / processes() holds at most 4096
bytes of each process's command line. Any local user controls their own
argv, so the cap keeps a few huge command lines from exhausting
memory_limit. libstatgrab still reads each full command line into the
C heap while it collects the list, so that memory is used transiently
outside memory_limit. When the cut lands inside a UTF-8 sequence, the
title ends before that sequence, so a truncated title can be up to 3 bytes
shorter.
The valid-filesystems filter is process-wide in libstatgrab, not
per-request. In NTS builds it persists across requests in long-lived
workers such as FPM. Under a threaded (ZTS) SAPI,
sg_set_valid_filesystems() in one request also changes the
sg_fs_stats() filter for every other thread. The extension serializes
sg_fs_stats(), sg_valid_filesystems(), and sg_set_valid_filesystems()
(and their Statgrab methods) across threads so concurrent calls are
memory-safe. A hung mount that blocks sg_fs_stats() therefore stalls all
of these calls in every thread.
Object-oriented
$sg = new Statgrab(); $sg->cpu(); // cpu(int $source = Statgrab::CPU_PERCENT_ENTIRE): array|false $sg->host(); $sg->memory(); $sg->processes(Statgrab::SORT_CPU, 10); // processes(?int $sort_order = null, int $num_entries = 0): array|false $sg->disks(diff: true); $sg->validFilesystems(); // array|false: list of fs-type strings $sg->setValidFilesystems(['ext4']); // bool: subsequent filesystems() only returns these fs_type values $sg->snapshot(); // bool: backend snapshot hook; see CPU sampling above $sg->errorDetails(); // array|false: [code, errno, message, arg], or false if none pending
Class constants:
Statgrab::DUPLEX_FULL | DUPLEX_HALF | DUPLEX_UNKNOWNStatgrab::SORT_NAME | PID | UID | GID | SIZE | RES | CPU | TIMEStatgrab::STATE_RUNNING | SLEEPING | STOPPED | ZOMBIE | UNKNOWNStatgrab::CPU_PERCENT_ENTIRE | LAST_DIFF | NEW_DIFF:$sourcemodes forcpu()/sg_cpu_percent_usage()Statgrab::HOST_STATE_UNKNOWN | PHYSICAL | VIRTUAL_MACHINE | PARAVIRTUAL_MACHINE | HARDWARE_VIRTUALIZED: interprets thehost_statefield onhost()/sg_general_stats()Statgrab::FS_UNKNOWN | REGULAR | SPECIAL | LOOPBACK | REMOTE | LOCAL | ALLTYPES: bitmask values for thedevice_typefield onfilesystems()/sg_fs_stats()Statgrab::ERROR_NONE | INVALID_ARGUMENT | OPEN | OPENDIR | PERMISSION | UNSUPPORTED: common libstatgrab error codes to match on
Errors
Library-side errors emit E_WARNING with the libstatgrab error string
and code, and the function returns false. The OO surface follows the
same convention. Argument-count violations on no-arg functions throw
ArgumentCountError.
sg_set_valid_filesystems() and Statgrab::setValidFilesystems() throw
instead of warning on bad input: ValueError on an empty array or an
entry containing NUL bytes, TypeError on a non-string entry. A
libstatgrab runtime failure from the setter (or any other stat call)
takes the usual warning and false path. Call sg_error_details() /
Statgrab::errorDetails() after a false return for the code, message,
errno, and arg.
Notable 2.0 BC breaks
sg_user_stats()returns per-user records, not a flat array of usernames. The underlyingname_listfield was removed from libstatgrab 0.91+. Migrate callers to readlogin_namefrom each record.- Numeric counters (memory totals, fs sizes, jiffies) are returned as
intinstead of stringified numbers. The 2006 release stringified viasnprintf("%lld")because 32-bit PHP couldn't hold them; modern 64-bitzend_longdoes. On 32-bit PHP builds these fields still truncate above 2^31, so use a 64-bit build. sg_page_stats()/sg_page_stats_diff()were swapped in 2006 and are now correct.sg_process_stats()fieldsgidandegidare now distinct fromuidandeuid(2006 had a copy-paste bug returning uid/euid for both).
See CHANGELOG.md for the full list.
🔗 Native PHP extensions
Companion native PHP extensions:
- php_excel: native Excel I/O via LibXL. 7-10× faster than PhpSpreadsheet, full XLS/XLSX with formulas, formatting, and styling.
- mdparser: native CommonMark + GFM markdown parser via md4c. 15-30× faster than pure-PHP libraries.
- php_clickhouse: native ClickHouse client speaking the wire protocol directly. Picks up where SeasClick left off.
- pdo_duckdb: PDO driver for DuckDB, analytical SQL in your PHP stack.
- fastjson: drop-in faster
ext/json, backed by yyjson. 6× encode, 2.7× decode, 5× validate. - phpser: decoder-optimized binary serializer for cache workloads. Faster than igbinary on packed numerics and DTO batches.
- fast_uuid: high-throughput UUID generation (v1/v4/v7), batched CSPRNG and SIMD hex formatter, ramsey-compatible API.
- fastchart: native chart-rendering extension. 38 chart types behind one fluent OO API, SVG-canonical with PNG/JPG/WebP and optional PDF output.
- phonetic: native phonetic name matching (Double Metaphone, Beider-Morse, Daitch-Mokotoff, NYSIIS, Match Rating), the encoders PHP core lacks.
License
If this saved you from parsing /proc by hand, ⭐ star it!
