Skip to content
joularPublic

About

CPU Load is a library to monitor processes, applications and system CPU usage on Windows, Linux and macOS

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

ย 

History

20 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Joular Project CPU Load

License: LGPL v3 Ada

CPU Load is a library that reports CPU load of the system, of a specific process by its ID, or a specific application by its name (meaning every one of its processes running when a sample is taken).

The library is written in Ada, and also provides a C interface so it can be used from any language with a C FFI (C, C++, Java, Python, Rust, etc.).

Detailed documentation (including user and reference guides) is available at: https://joular.github.io/cpuload/.

๐Ÿ“ก Supported platforms

What is measured OS Method
The whole system Linux The cpu line of /proc/stat
The whole system macOS host_statistics, macOS' CPU counters
The whole system Windows GetSystemTimes
The whole system FreeBSD kern.cp_time through sysctl
One process, by its number Linux utime + stime of /proc/<pid>/stat
One process, by its number macOS proc_pidinfo, the user and system time of the process
One process, by its number Windows OpenProcess + GetProcessTimes
One process, by its number FreeBSD kern.proc through sysctl, the CPU time of the process
An application, every process of it Linux /proc scanned, each process named by /proc/<pid>/exe
An application, every process of it macOS proc_listpids, each process named by proc_pidpath
An application, every process of it Windows EnumProcesses + QueryFullProcessImageNameW
An application, every process of it FreeBSD kern.proc listed, each process named by kern.proc.pathname

Every OS matches the program with the process that actually runs, so firefox finds every process of Firefox, its content processes included. On macOS that is 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. On Windows a trailing .exe is ignored, so firefox also finds firefox.exe.

On Linux, 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. This file have the given name of the process (with a max size of 15 character).

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%.

FreeBSD is supported on 64-bit systems only. 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, which the kernel cuts to 19 characters.

๐Ÿ“ Reading the numbers

Every counter in a Sample is in microseconds, on every system. Both loads 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 negative load means it 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.

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. An application that is not running reads 0.0. 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.

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 or task at a time, whatever the language: it carries its own 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.

Building

With Alire:

alr build

Or directly with GNAT:

gprbuild -P cpuload.gpr

On FreeBSD, build with GNAT 15 or newer: pkg install gprbuild gnat15, then add /usr/local/gnat15/bin to PATH. GNAT 12, which pkg install gprbuild uses, ignores the pragma that keeps the shared library away from the signal handlers of the program loading it. Alire takes the GNAT in PATH there too, and refuses an older one.

The build produces a static library by default, and detects the system automatically: Linux, macOS, Windows and FreeBSD are each recognised from the target gprbuild reports, so nothing has to be passed. -XPJ_OS still says which system to build for (linux, macos, windows or freebsd) when it is not the one of the machine building it. A library built for another system reads no counters at all and reports 0% for everything.

For other library types (shared, etc.), set -XCPULOAD_LIBRARY_TYPE:

gprbuild -P cpuload.gpr -XCPULOAD_LIBRARY_TYPE=relocatable

relocatable builds the shared library (libcpuload.so / .dll / .dylib) that carries the C interface and is standalone: it starts itself up when loaded. On Linux, FreeBSD and Windows it is encapsulated as well, carrying the Ada runtime with it, so it is one self-contained file. On macOS it cannot be encapsulated, so the Ada runtime is a separate file: 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).

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;

The whole interface is Sample, the three Take functions (the system alone, one Process_ID, or an application by name).

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);

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:

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), which is what the CI runs: ./example/example_cpu_load "" 3.

The counters need no particular rights on any system. When the machine reads nothing at all, the example says what to look into on the system it was built for.

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

Using from C (and any other language)

The C declarations are in include/cpuload.h. Build the relocatable library, then:

#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

On FreeBSD, the Makefile needs GNU make: run gmake instead of make.

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 with a "library not loaded" error, unless you set LD_LIBRARY_PATH yourself. macOS works the same way. Windows has no -rpath: put a copy of the DLL next to the program instead (which is what the Makefile does).

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. example/python/main.py uses them, and declares the types of every function it calls, which ctypes needs to keep the double values whole on the way back.

Note that Python puts its own Ctrl+C handler back after loading the library if you want to stop a reading loop that way: the Ada runtime installs its own while it starts up.

Java (through FFM or JNA), Rust (through libloading or FFI declarations), and every other language with a C FFI work the same way.

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 the PJ_OS symbol.

To support a new OS, write a body of CPU_Load.Platform for it and add its folder there.

๐Ÿ“œ License

CPU Load is licensed under the GNU Lesser General Public License 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 (LGPL-3.0-only) which accompanies this distribution.

Author: Prof. Adel Noureddine

About

CPU Load is a library to monitor processes, applications and system CPU usage on Windows, Linux and macOS

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages