Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Overview

License: LGPL v3 Ada

CPU Load is an Ada library that reports the CPU load of the system, of a specific process by its ID, or of a specific application by its name (meaning every one of its processes running when a sample is taken). It gives one simple interface: take a sample, wait, take another, and compare the two.

CPU Load is part of the Joular project.

It compiles to native code with minimal overhead, and also provides a C interface, so it can be used from any language with a C FFI (C, C++, Java, Python, Rust, etc.).

CPU Load is the library PowerJoular uses to measure the CPU load, alongside Joular Core for the hardware.

Key Features

  • Measure the CPU load of the whole system, of one process by its ID, or of an application by its name (every process of it)
  • Measure on Linux, Windows, macOS (Apple Silicon and Intel Macs) and FreeBSD
  • Match an application with the program its processes actually run, so firefox finds every process of Firefox
  • Give every load as a share of the whole machine, from 0.0 to 1.0
  • Tell apart a process that could not be read (a negative load) from one that used no CPU time (0.0)
  • Keep no state: a sample is a plain record, so it can be taken in one thread and compared in another
  • Read the counters with no particular rights (except for the processes of other users on macOS and Windows)
  • Static library for Ada programs, and a shared library with a C interface for other languages (one self-contained file on Linux, Windows and FreeBSD)

License

CPU Load is licensed under the GNU LGPL 3 license only (LGPL-3.0-only).

Copyright © 2026, Adel Noureddine.
All rights reserved. This program and the accompanying materials are made available under the terms of the GNU Lesser General Public License v3.0 only (LGPL-3.0-only).

Author: Adel Noureddine

Supported Platforms

CPU Load runs on Linux, Windows, macOS and FreeBSD, on PCs, servers, Macs and single-board computers such as the Raspberry Pi. It reads what the operating system counts, not the hardware, so it works the same whatever the processor.

What is measuredOSMethod
The whole systemLinuxThe cpu line of /proc/stat
The whole systemmacOShost_statistics, macOS’ CPU counters
The whole systemWindowsGetSystemTimes
The whole systemFreeBSDkern.cp_time through sysctl
One process, by its numberLinuxutime + stime of /proc/<pid>/stat
One process, by its numbermacOSproc_pidinfo, the user and system time of the process
One process, by its numberWindowsOpenProcess + GetProcessTimes
One process, by its numberFreeBSDkern.proc through sysctl, the CPU time of the process
An application, every process of itLinux/proc scanned, each process named by /proc/<pid>/exe
An application, every process of itmacOSproc_listpids, each process named by proc_pidpath
An application, every process of itWindowsEnumProcesses + QueryFullProcessImageNameW
An application, every process of itFreeBSDkern.proc listed, each process named by kern.proc.pathname

FreeBSD is supported on 64-bit systems only.

Matching an Application

An application is given by the name of its program, without its folder. The name must match exactly, but upper or lower case does not matter. Every OS matches the program with the process that actually runs, so firefox finds every process of Firefox, its content processes included.

Linux
A process is named by the program /proc/<pid>/exe points to. A process whose /proc/<pid>/exe cannot be read (such as a kernel thread, which runs no program directly, or another user’s process) falls back on /proc/<pid>/comm: the name the process was given, cut to 15 characters.

macOS
A process is named by the program inside the bundle, so firefox finds the application inside Firefox.app, and also every program inside Firefox.app: for example, its content processes run plugin-container, from a helper bundle inside it.

Windows
A trailing .exe is ignored, so firefox also finds firefox.exe.

FreeBSD
A process is named by the program kern.proc.pathname gives. A process whose program cannot be named (a kernel process, which runs none, or a process whose program was replaced while it runs) falls back on its command name, cut to 19 characters.

Macs

macOS is supported on Apple Silicon and Intel Macs, with the library built for the chip it runs on. An x86_64 build running under Rosetta on an Apple Silicon Mac reads process times about 40 times too low, so process and application loads come out close to 0%.

Installation

CPU Load is added to the program that uses it rather than installed on its own. It can be taken from Alire, or compiled from source.

With Alire

In an Ada project managed by Alire, add the library with:

alr with cpuload

Alire fetches the library and builds it along with your program. Then add with CPU_Load; to your code (see Quick Usage).

Alire offers CPU Load on Linux, Windows, macOS and FreeBSD.

From Source

Clone the GitHub repository and build it with GNAT and GPRBuild, or with Alire. The build gives:

  • a static library (libcpuload.a), to link into an Ada program, which is the default
  • a shared library (libcpuload.so on Linux and FreeBSD, libcpuload.dll on Windows, libcpuload.dylib on macOS), which carries the C interface for programs in C, C++, Java, Python, Rust, etc.

On Linux, Windows and FreeBSD, the shared library carries the Ada runtime too, so it is the only file to ship with your program (on Linux and FreeBSD, libcpuload.so.0, the name programs look for). On macOS, the Ada runtime stays a file of its own, which the library loads from the folder of the compiler that built it.

See Compilation for the build commands and options.

Platform-specific Requirements

CPU Load is a library, so the privileges below are needed for the program using it.

The counters need no particular rights on Linux, Windows, macOS or FreeBSD: any user can read the load of the whole machine, and of their own processes.

  • Linux: the load of other users’ processes can be read too, but not the program they run, so they are matched by the name in /proc/<pid>/comm instead (see Supported Platforms).
  • macOS: the processes of other users cannot be read unless the program runs as root (with sudo), so their load is negative.
  • Windows: the processes of other users cannot be read. One of them followed by its ID reads a negative load, and in an application they are left out, as their program cannot be found either (an application made only of them reads 0.0).
  • FreeBSD: the processes of other users can be read too, with the program they run, unless the system hides them (security.bsd.see_other_uids=0): one of them followed by its ID then reads a negative load, and in an application they are left out.

Quick Usage

CPU Load has one simple interface: take a sample, wait, take another, then compare the two.

  • Take reads the CPU counters of the whole machine, and of one process or of one application when one is given.
  • System_Usage gives the load of the whole machine between two samples.
  • Process_Usage gives the load of the process or of the application between two samples.

Every load is a share of the whole machine, from 0.0 to 1.0: a process using all of one core of an eight core machine reads 0.125, not 1.0. A negative load means the process or the application could not be read at all. See Reading the Numbers for the details.

Using from Ada

with Ada.Text_IO; use Ada.Text_IO;
with CPU_Load; use CPU_Load;

procedure Measure is
    --  The first sample, which the first reading below is measured against
    Before : Sample := Take ("firefox");
    After : Sample;
begin
    for I in 1 .. 5 loop
        delay 1.0;
        After := Take ("firefox");

        --  The same pair of samples gives both figures
        Put_Line ("machine:" & Long_Float'Image (100.0 * System_Usage (Before, After)) & " %");
        Put_Line ("firefox:" & Long_Float'Image (100.0 * Process_Usage (Before, After)) & " %");

        --  This reading becomes the one the next is measured against
        Before := After;
    end loop;
end Measure;

To follow several things at once, read the machine once and measure each of them against that one reading. Each is then measured over exactly the same period of time, and the machine’s counters are read once instead of once per thing:

Machine := Take;
Ours := Take (Our_PID, Machine);
Theirs := Take ("firefox", Machine);

With Alire, add the library to your project with alr with cpuload.

A full example program is in example/src/example_cpu_load.adb. It follows the machine, itself, and an application named on the command line, once per second until stopped with Ctrl+C. To build and run it:

gprbuild -P example/example.gpr -p
./example/example_cpu_load firefox

A number after the name stops it after that many readings ("" follows no application):

./example/example_cpu_load "" 3

Using from C

The C declarations are in include/cpuload.h. Build the relocatable (shared) library (see Compilation), then:

#include <stdio.h>
#include <unistd.h>
#include "cpuload.h"

cpuload_sample before, after;

cpuload_take_app("firefox", &before);
sleep(1);
cpuload_take_app("firefox", &after);

printf("machine: %.2f%%\n", 100.0 * cpuload_system_usage(&before, &after));
printf("firefox: %.2f%%\n", 100.0 * cpuload_process_usage(&before, &after));

cpuload_take_pid_with and cpuload_take_app_with are the same as cpuload_take_pid and cpuload_take_app, but measure against a machine sample already taken instead of taking another one:

cpuload_sample machine, mine, theirs;

cpuload_take_system(&machine);
cpuload_take_pid_with(getpid(), &machine, &mine);
cpuload_take_app_with("firefox", &machine, &theirs);

A full example program is in example/c/main.c. Like the Ada one, it follows the machine, itself, and an application named on the command line, once per second until stopped with Ctrl+C. It comes with a Makefile that builds the shared library and the program:

make -C example/c run APP=firefox

Run from the root of the repository with no application, for two readings:

./example/c/example_c "" 2

It prints:

CPU Load 0.0.4
Following the machine and this program. Name an application to follow it as well: ./example/c/example_c firefox
machine 8.29% | this program 0.00%
machine 7.89% | this program 0.00%
Stopping

Using from Python

From Python, the same interface through ctypes:

import ctypes, time

class Sample(ctypes.Structure):
    _fields_ = [("busy", ctypes.c_int64), ("total", ctypes.c_int64), ("used", ctypes.c_int64)]

lib = ctypes.CDLL("lib/relocatable/libcpuload.so")  # libcpuload.dylib on macOS, libcpuload.dll on Windows
lib.cpuload_system_usage.restype = ctypes.c_double
lib.cpuload_process_usage.restype = ctypes.c_double
lib.cpuload_version.restype = ctypes.c_char_p

before, after = Sample(), Sample()

lib.cpuload_take_app(b"firefox", ctypes.byref(before))
time.sleep(1)
lib.cpuload_take_app(b"firefox", ctypes.byref(after))

print("machine:", 100.0 * lib.cpuload_system_usage(ctypes.byref(before), ctypes.byref(after)), "%")
print("firefox:", 100.0 * lib.cpuload_process_usage(ctypes.byref(before), ctypes.byref(after)), "%")

cpuload_take_pid_with and cpuload_take_app_with are there as well, taking the machine sample as their middle argument.

A full example program is in example/python/main.py, with a Makefile that builds the shared library:

make -C example/python run APP=firefox

Other languages (C++, Java, Rust, etc.) use the same C interface. See Integration with Systems and Tools.

Compilation

CPU Load is written in Ada and is built with GPRBuild, or with Alire. A modern GNAT compiler is the only requirement.

On FreeBSD, pkg install gprbuild brings GPRBuild and GNAT, whose folder /usr/local/gnat12/bin has to be added to PATH.

Default Build

With Alire:

alr build

Or directly with GNAT:

gprbuild -P cpuload.gpr

The build produces a static library by default, libcpuload.a in lib/static/, for Ada programs.

Choosing the OS

The build detects the OS on its own: Linux, macOS, Windows and FreeBSD are each recognised from the target GPRBuild reports, so nothing has to be passed.

-XPJ_OS still says which OS to build for (linux, macos, windows or freebsd) when it is not the one of the machine building it:

gprbuild -P cpuload.gpr -XPJ_OS=windows

A library built for another OS reads no counters at all: the machine reads 0%, and processes and applications read negative.

Library Types

For other library types, set -XCPULOAD_LIBRARY_TYPE (when it is not set, the generic LIBRARY_TYPE is used if set, and static otherwise):

gprbuild -P cpuload.gpr -XCPULOAD_LIBRARY_TYPE=relocatable
Library typeDefaultDescription
staticonlibcpuload.a, linked into an Ada program.
relocatableoffThe shared library (libcpuload.so / .dll / .dylib) that carries the C interface, for programs in other languages.
static-picoffA static library built position independent, to go inside someone else’s shared library.

Each type is built in its own folder: lib/static/, lib/relocatable/ or lib/static-pic/.

The relocatable library is stand-alone (it starts itself up when loaded) and, on Linux, Windows and FreeBSD, encapsulated: it carries the Ada runtime too, so it is one self-contained file. On Linux and FreeBSD, it is versioned as libcpuload.so.0.

On macOS it cannot be encapsulated, so the Ada runtime stays a file of its own: the library records the folder of the runtime of the compiler that built it, and loads it from there with nothing to set (no DYLD_LIBRARY_PATH, so it also works under sudo and from the system’s Python).

Building the Examples

The Ada example uses the static library:

gprbuild -P example/example.gpr -p
./example/example_cpu_load firefox

The C example comes with a Makefile that builds the shared library and the program:

make -C example/c run APP=firefox

To build it by hand instead, from the root of the repository, first compile the library:

gprbuild -P cpuload.gpr -XCPULOAD_LIBRARY_TYPE=relocatable

Then compile the C program:

gcc example/c/main.c -Iinclude -Llib/relocatable -lcpuload -Wl,-rpath,"$PWD/lib/relocatable" -o example/c/example_c

-I is the folder holding cpuload.h, -L and -l the library to link with, and -rpath the folder where the program looks for the library when it runs. Without -rpath, the program still compiles but stops on start because it cannot find the library, unless you set LD_LIBRARY_PATH (Linux and FreeBSD) or DYLD_LIBRARY_PATH (macOS) yourself. Windows has no -rpath: put a copy of the DLL next to the program instead (which is what the Makefile does).

The Python example only needs the shared library, which its Makefile builds:

make -C example/python run APP=firefox

Library Interface

CPU Load has the same interface in Ada and in C: take a sample, compare two samples, and the version of the library.

Ada

The whole interface is the CPU_Load package.

Types

TypeDescription
Process_IDThe ID of a process (a Natural).
SampleOne reading of the CPU counters, in microseconds: Busy (machine time not spent idle, summed over every core), Total (machine time there was to spend: the elapsed time times the number of cores, 0 if the sample could not be taken) and Used (CPU time of the process or application sampled, 0 for the system alone, negative if it could not be read), all three Integer_64.

Subprograms

SubprogramDescription
function Take return SampleSample the system alone.
function Take (PID : in Process_ID) return SampleSample the system and one process. 0 samples the system alone.
function Take (App : in String) return SampleSample the system and every process of an application. App is the program’s name without its folder: the name must match exactly, but upper or lower case does not matter (see Supported Platforms). "" samples the system alone.
function Take (PID : in Process_ID; Machine : in Sample) return SampleThe same as Take (PID), measured against a machine sample already taken.
function Take (App : in String; Machine : in Sample) return SampleThe same as Take (App), measured against a machine sample already taken.
function System_Usage (Before, After : in Sample) return Long_FloatCPU load of the whole machine between two samples, from 0.0 to 1.0 (samples of any kind: system, process or application).
function Process_Usage (Before, After : in Sample) return Long_FloatCPU load of the process or application between two samples, from 0.0 to 1.0, and negative if it could not be read at all.
function Version return StringThe version of the library.

The two Take functions with a Machine sample measure everything over exactly the same period of time, and read the machine’s counters once:

Machine := Take;
Ours := Take (Our_PID, Machine);
Theirs := Take ("firefox", Machine);

C

The C declarations are in include/cpuload.h. Use them with the relocatable (shared) library, which starts itself up when loaded: no other initialization call is needed.

Types

typedef struct cpuload_sample {
    int64_t busy;   /* machine time not spent idle, added up over every core */
    int64_t total;  /* machine time altogether, idle included, added up over every core */
    int64_t used;   /* CPU time of what was sampled, 0 for a sample of the system, -1 if it could not be read */
} cpuload_sample;

A total of 0 means the sample could not be taken at all.

Functions

FunctionDescription
void cpuload_take_system(cpuload_sample *out)Same as Take: a sample of the whole system.
void cpuload_take_pid(unsigned int pid, cpuload_sample *out)Same as Take (PID). 0 samples the system alone, and a pid too large to be any process (e.g. a negative pid_t turned unsigned) gives a used of -1.
void cpuload_take_app(const char *app, cpuload_sample *out)Same as Take (App). NULL or "" samples the system alone.
void cpuload_take_pid_with(unsigned int pid, const cpuload_sample *machine, cpuload_sample *out)Same as Take (PID, Machine).
void cpuload_take_app_with(const char *app, const cpuload_sample *machine, cpuload_sample *out)Same as Take (App, Machine).
double cpuload_system_usage(const cpuload_sample *before, const cpuload_sample *after)Same as System_Usage.
double cpuload_process_usage(const cpuload_sample *before, const cpuload_sample *after)Same as Process_Usage.
const char *cpuload_version(void)Same as Version. The string is owned by the library, so do not free it.

A NULL machine counts as a sample with a total of 0: cpuload_system_usage then gives 0.0, and so does cpuload_process_usage unless the process could not be read. A NULL before or after gives 0.0, and a NULL out does nothing.

Constraints

  • The library keeps no state. Linked statically into an Ada program, it can be called from any number of tasks.
  • The shared library must be called from one thread at a time, whatever the language: it runs on an Ada runtime without tasking, which has a single working stack for the whole process. A sample is a plain record either way, so it can be taken in one thread and compared in another.
  • Sample about a second apart (see Reading the Numbers).

Reading the Numbers

A sample holds the CPU counters of one moment, and the load is what changed between two samples.

Units

Every counter in a Sample is in microseconds, on every OS.

Both loads, System_Usage and Process_Usage, run from 0.0 to 1.0 and are a share of the whole machine, not of one core: a process using all of one core of an eight core machine reads 0.125, not 1.0. A core here is a CPU as the OS counts them, so a hardware thread on a processor with Hyper-Threading or SMT. To get a load as a share of one of them, multiply it by their number.

Negative and Zero

A negative load means the process could not be read at all: not running (a process that has ended but is not yet cleaned up by the system counts as not running), or not allowed to get the information needed. That is not the same as 0.0, which means no CPU time used at all.

A process is read in both samples, and the load is negative when it could not be read in one of them.

Applications

For an application, a process that could not be read is left out of the sum, so the figure is short by what it used. The answer is negative only when some of the application’s processes are running and none of them would say anything at all, or when the processes could not be listed. An application that is not running reads 0.0.

On Windows, the processes of other users cannot even be named, so they are never part of an application: an application made only of them reads 0.0.

An application is every process running its program when the sample is taken. A process of the application that ends between two samples takes all of its time out of the second one, so that stretch reads low, or 0.0.

Samples That Cannot Be Compared

  • A sample with a total of 0 could not be taken at all (the counters of the machine could not be read). The loads computed with it are 0.0, or negative when the process could not be read.
  • Two samples given the wrong way round give 0.0.

A total of 0 on the very first sample is worth checking: it is what a library built for another OS gives (see Compilation), and without the check a program would print 0% forever and look idle. The example programs do this check.

How Often to Sample

Sample about a second apart: Linux counts a process in 10 ms units, Windows in about 15 ms, and FreeBSD the machine in ticks of about 8 ms, too coarse for shorter waits. macOS counts a process in nanoseconds, and reads it well below a second, though the machine itself is still counted in 10 ms ticks.

On macOS, the counters of the machine are 32 bits, counting 100 times per second for each busy core: on a 10 core machine kept busy, they can wrap after about 50 days. Only the load measured across the wrap shows 0%.

Integration with Systems and Tools

CPU Load is designed to be embedded in other programs and tools. It only measures the CPU load, and leaves to the program using it what to do with the numbers: show them, write them to files, share power out to processes, etc.

For example, PowerJoular uses CPU Load together with our Joular Core library: Joular Core gives the power of the CPU, and CPU Load the load of the machine and of the monitored process or application. The power of the process is then the CPU power times its load, divided by the load of the machine.

See Library Interface for the details of the interface. This page focuses on using it from different languages.

Ada Programs

Ada programs use the CPU_Load package directly, and link the static library. With Alire, add the library to your project with:

alr with cpuload

Without Alire, add the folder of the library to the project search path of GPRBuild, and add with "cpuload.gpr"; to your project file:

gprbuild -P your_project.gpr -aP../cpuload

C, C++ and Other Languages

Any language with a C FFI can use the shared library (libcpuload.so on Linux and FreeBSD, libcpuload.dll on Windows, libcpuload.dylib on macOS) through the C interface in include/cpuload.h: C and C++ directly, Python through ctypes, Java through FFM or JNA, Rust through libloading or FFI declarations, etc.

A sample is three 64-bit integers, busy, total and used, in this order, with the same layout on every target.

On Linux, Windows and FreeBSD, the shared library is one self-contained file, carrying the Ada runtime too, so it is the only file to ship with your program. On Windows, the program looks for the DLL next to it, so put a copy there.

On macOS, the library loads the Ada runtime from the folder of the compiler that built it (see Compilation), so it runs on a Mac where that same compiler is installed in the same folder.

Python

The Quick Usage page shows how to call the library from Python with ctypes, and example/python/main.py is a full example.

ctypes takes every result as an int unless told otherwise, so declare ctypes.c_double as the result of cpuload_system_usage and cpuload_process_usage, or the loads come back wrong. The full example declares the types of every function it calls.

The library leaves the signal handlers of the program loading it in place, so Ctrl+C raises KeyboardInterrupt in Python as usual. The example still puts Python’s Ctrl+C handler back after loading the library, as a safeguard.

Java

From Java, the library can be called through the Foreign Function and Memory API (FFM) or JNA. The shared library does not install any handler for the signals the JVM relies on (such as SIGSEGV and SIGBUS), so it does not get in the way of the JVM.

Threads

The shared library must be called from one thread at a time. A sample is a plain record, so it can be taken in one thread and compared in another.

Linked statically into an Ada program, the library can be called from any number of tasks, as it keeps no state.

How CPU Load Works

This page describes the internal architecture of CPU Load: what a sample holds, how a load is calculated, how each OS is read, and how to add a new OS.

Samples and Loads

A sample holds three counters, read at one moment, in microseconds:

  • Busy: the machine time not spent idle, summed over every core.
  • Total: the machine time there was to spend: the elapsed time times the number of cores.
  • Used: the CPU time of the process or of the application sampled, and 0 for the system alone.

These counters count up, so a load is what changed between two samples:

system load  = (Busy after − Busy before) / (Total after − Total before)
process load = (Used after − Used before) / (Total after − Total before)

As Total covers every core, a load is a share of the whole machine.

When a process or an application is sampled, the counters of the machine are read first, and the process right after. The small lag between the two is the same in both samples, so it cancels out.

An application is every process running its program at the moment of the sample: CPU Load lists the processes, keeps the ones running the program named, and adds up their CPU time. A process that could not be read is left out of the sum.

Linux

The machine. The first line of /proc/stat gives the time of all the cores together, in clock ticks (usually 100 per second, as sysconf says). Busy is user, nice, system, irq, softirq and steal. The idle time is idle and iowait. The guest times are already inside user and nice, so they are not added again.

A process. utime and stime of /proc/<pid>/stat give the time the process spent running its own code and in the kernel. The name of the process, in brackets, may hold spaces or brackets, so the fields are counted from the last ). A process in state Z (zombie), X or x (dead) has ended, and is not read.

An application. /proc is scanned, and each process is named by the program /proc/<pid>/exe points to, without the (deleted) the kernel adds when the program’s file was replaced while it runs. When it cannot be read, the name in /proc/<pid>/comm is used instead.

The files are read through file descriptors rather than Ada.Text_IO, whose Open fails when several tasks call it at once.

macOS

The machine. host_statistics gives the user, system and nice ticks of all the cores (100 per second), which make Busy. Total comes from the monotonic clock (mach_absolute_time) times the number of cores, rather than from the idle ticks, which macOS updates only every 90 ms or so.

A process. proc_pidinfo gives the user and system time of the process, in the units of mach_absolute_time, turned into microseconds with mach_timebase_info (125/3 on Apple Silicon, 1/1 on Intel). It fails for the processes of other users unless the program runs as root.

An application. proc_listpids lists the processes, and proc_pidpath gives the full path of each one’s program. A process is part of the application when its program has the name given, or when it runs from inside the bundle of that name (<name>.app), as Firefox runs its content processes from a helper bundle inside Firefox.app.

Under Rosetta, a process is still counted in the units of Apple Silicon (24 MHz), which an x86_64 build reads with the units of an Intel Mac: process times come out about 40 times too low.

Windows

The machine. GetSystemTimes gives the idle, kernel and user times, in 100 ns units. The kernel time includes the idle time, so Total is the kernel and user times, and Busy is Total without the idle time.

A process. OpenProcess (asking for limited information only), then GetProcessTimes, give the kernel and user times of the process. The processes of other users cannot be opened, so they are not read. A process that has ended can still be queried while a handle to it is held. Its exit time, which stays zero until it ends, tells it apart, and such a process is not read.

An application. EnumProcesses lists the processes (with room for twice as many each time the list fills up, up to 65536), and QueryFullProcessImageNameW gives the path of each one’s program, through the same OpenProcess: the processes of other users cannot be named, so they are left out. A trailing .exe is ignored on both sides of the comparison.

FreeBSD

The machine. kern.cp_time, read through sysctl, gives the user, nice, system, interrupt and idle ticks of all the cores together, counted by the statistics clock (stathz of kern.clockrate, around 128 per second). Busy is all but the idle ticks.

A process. kern.proc.pid gives the kinfo_proc of the process, whose ki_runtime is its CPU time, already in microseconds. A process in state SZOMB (zombie) has ended, and is not read. Above the highest process number, FreeBSD takes the number for a thread and answers with its process, so the answer is checked to be about the process asked for.

An application. kern.proc.proc lists every process once (kern.proc.all lists every thread), and kern.proc.pathname gives the full path of each one’s program. When there is none (a kernel process runs none, and a program replaced while it runs has no path), the command name in the kinfo_proc is used instead.

Code Layout

PathPurpose
src/cpu_load.ads, src/cpu_load.adbThe interface (Sample, Take, System_Usage, Process_Usage, Version), and what an application is, the same on every OS
src/cpu_load-platform.adsThe four functions each OS provides
src/cpu_load-c_api.ads, src/cpu_load-c_api.adbThe C interface, matching include/cpuload.h
src/linux/, src/macos/, src/windows/, src/freebsd/One body of CPU_Load.Platform per OS (src/macos also holds the specs file recording where the shared library finds the Ada runtime)
include/cpuload.hThe C declarations
example/Example programs in Ada, C and Python

Adding a New OS

The package spec src/cpu_load.ads and its body src/cpu_load.adb are shared by every OS. They hold the Take and usage functions, and what an application is: every process running its program, their times added up.

Each OS has its own body of src/cpu_load-platform.ads, which has four functions about the machine:

  • Measure_System: the machine’s own CPU counters
  • Used_By_PID: the CPU time one process has used
  • Runs: whether a process runs the program an application is named by
  • For_Each_Process: every process running

One body per OS lives in src/linux, src/macos, src/windows and src/freebsd, and cpuload.gpr picks the folder for the OS being built from PJ_OS.

To support a new OS, write a body of CPU_Load.Platform for it, then add the OS and its folder to PJ_OS in cpuload.gpr, and to alire.toml.