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.
Reading system stats from PHP usually means one of three bad options:
- Shell out to
top,vmstat,df,psand parse the output. Fragile on every OS update, andfork/execoverhead adds up if you poll on a schedule. - Read
/procby hand. Linux-only. Forces hand-rolled regex for every statistic, and the format drifts between kernel releases. - Pull in a heavy monitoring framework. Overkill if all you need is a CPU number for a health endpoint.
libstatgrab is the right primitive: cross-platform, well-tested, in the package manager of every modern Unix. But the 2006 PECL binding has not shipped a PHP 8 build, and its 2006 BC quirks (stringified counters, swapped page-stat keys, a flat name_list for users) made the old extension awkward even when you could compile it.
| 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 a leak-fix patch; 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, with the 2.0 BC notes documented below |
The case for a native extension is the failure modes of the alternatives:
exec("top ...")and friends fork a process per call. The overhead is real if you poll every few seconds, and the output format drifts between OS releases.- Hand-parsing
/procties you to Linux and to whatever the kernel decided to print this year. Each file (/proc/meminfo,/proc/loadavg,/proc/diskstats,/proc/net/dev) has its own format and edge cases. - Calling out to a stats daemon adds a network hop and a daemon to deploy.
statgrab calls libstatgrab in-process. libstatgrab handles the per-OS path (Linux /proc, FreeBSD kvm, macOS host_* APIs) and exposes a single typed surface. The extension wraps that surface with no allocation per call beyond the result array.
PIE is the PHP Foundation's PECL successor.
It installs from Packagist, builds against the active php-config, and
produces a loadable .so. Make sure libstatgrab is installed first
(see the From-source section below for the system package names), then:
pie install iliaal/statgrabThen add extension=statgrab to your php.ini.
The package remains in the PECL channel for legacy installers:
pecl install statgrabsudo apt install libstatgrab-dev # Debian/Ubuntu
# OR: brew install libstatgrab # macOS
# OR: pkg install libstatgrab # FreeBSD
phpize
./configure --with-statgrab
make
sudo make installThen add extension=statgrab to your php.ini.
config.m4 resolves libstatgrab through pkg-config first; if that
fails it falls back to a path probe (/usr and /usr/local). Pass
--with-statgrab=<prefix> to point at a custom install.
The repo carries a vendored copy of libstatgrab 0.92.1 under
vendor/libstatgrab/ with one local patch (see
vendor/libstatgrab/LOCAL_PATCHES.md) that fixes a process-exit leak
upstream hasn't released yet. To use it:
(cd vendor/libstatgrab && ./configure --enable-static --disable-shared --without-ncurses --with-pic && make)
phpize
./configure --with-statgrab=bundled
makeThe resulting .so has no libstatgrab.so runtime dependency.
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.
The 2006 function names are preserved.
sg_cpu_percent_usage(): array|false // user/kernel/idle/iowait/swap/nice (%)
sg_cpu_totals(): array|false // cumulative jiffies + ctx switches/syscalls/IRQs
sg_cpu_diff(): array|false // jiffies since last call
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 = null, int $limit = 0): array|false
sg_user_stats(): array|false // [{login_name, device, pid, login_time, ...}]
sg_network_iface_stats(): array|false // [ifname => {speed, duplex, active}]$sg = new Statgrab();
$sg->cpu();
$sg->host();
$sg->memory();
$sg->processes(Statgrab::SORT_CPU, 10);
$sg->disks(diff: true);Class constants:
Statgrab::DUPLEX_FULL | DUPLEX_HALF | DUPLEX_UNKNOWNStatgrab::SORT_NAME | PID | UID | GID | SIZE | RES | CPU | TIMEStatgrab::STATE_RUNNING | SLEEPING | STOPPED | ZOMBIE | UNKNOWN
The SG_* global constants from 2006 are still defined for BC.
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 per modern PHP convention.
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. 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 for the kinds of work pure-PHP libraries handle slowly:
- php_excel: Excel I/O via LibXL.
- mdparser: CommonMark + GitHub Flavored Markdown via cmark-gfm.
- php_clickhouse: native ClickHouse client via clickhouse-cpp.
If this saved you from parsing /proc by hand, ⭐ star it!
