process_analyzer is a lightweight Linux command-line tool written in C that periodically samples process information from /proc and provides aggregated statistics at the end of execution.
The tool is designed for low-overhead monitoring, accurate time-based calculations, and post-run analysis of CPU, memory (RSS), disk I/O, and file descriptor usage per process. It focuses on low overhead and long-term observation rather than deep profiling.
Repository Mirrors
-
Main: GitHub repo
-
Mirror: GitLab repo
Updates are pushed to both locations when possible.
- Periodic process sampling with configurable interval
- Accurate CPU usage calculation using monotonic time
- Memory (RSS) tracking:
- Average RSS
- RSS increase since startup
- RSS delta
- Disk I/O metrics per process:
- Total bytes read / written
- Average disk read/write rate (KB/s)
- File descriptor metrics per process:
- Currently opened file descriptors
- File descriptors opened since startup
- File descriptor delta since startup
- Optional PID and process-name filtering to monitor only selected processes
- Snapshot logging with log rotation
- Graceful shutdown on
CTRL+CorSIGTERM - Supports infinite runtime mode (
-n infinity) - Minimal runtime dependencies (glibc, procfs)
- Optional asynchronous gzip compression for rotated logs
- Prebuilt binaries for Linux x86_64 and ARM64
Full tutorial, including:
- configuration file
- CLI options
- Dashboard usage
Demo with a thorough presentation of the new features introduced in version 1.3:
A short walkthrough of process_analyzer running from the command line,
including sampling, metrics generation, and output files.
A quick presentation of the HTML dashboard that visualizes metrics.json
and allows interactive exploration of collected data.
- Investigating long-running background processes
- Identifying disk-heavy applications over time
- Post-mortem analysis after performance degradation
- Not a real-time interactive monitor (like
toporhtop) - Not a system-wide resource accounting tool
- Not intended for per-thread profiling
At each sampling interval, the tool:
- Reads process data from
/proc/[pid]/stat,/proc/[pid]/status, and/proc/[pid]/io - Reads file descriptor counts from
/proc/[pid]/fd - Stores a snapshot of all running processes (or only selected PIDs/process names when filtering is enabled)
- Accumulates statistics over time using monotonic timestamps
- Designed to minimize per-sample overhead even at small intervals (tens of milliseconds)
At the end of execution (or when interrupted), the requested metrics are calculated and displayed on the console and in a metrics.json file.
Starting with version 1.1, the project includes a lightweight HTML dashboard
for visualizing collected metrics.
The dashboard is fully client-side and requires no backend.
If a metrics.json file is present in the dashboard/data directory, it will be loaded automatically when the dashboard starts.
Prebuilt binaries are available in the GitHub Releases section
Supported architectures:
- Linux x86_64 (amd64)
- Linux ARM64 (aarch64)
Download the appropriate binary for your system and make it executable:
chmod +x process_analyzerThen run:
./process_analyzer -h./process_analyzer \
-i 100ms \
-n 100 \
-c 10 -r 10 -s 10 -d 10 \
-e 10 -f 10 \
-g 10 -a 10 \
-p 10 -m 10 -o 10Runtime requirements are minimal:
- Linux with
/procfilesystem - glibc
- No additional libraries required
If you prefer to build manually:
./make.shOr build with tests:
./makeAll.sh -includeUnitTests -includeIntegrationTestsSee Build Instructions section below for more details.
The tool supports an external configuration file that controls output behavior and log rotation.
The configuration file path is provided via an environment variable:
export PROCESS_ANALYZER_CONFIG=/path/to/configuration.configIf the variable is not set, default values are used.
If a configuration file is present but invalid, the program exits with an error before starting sampling.
# Output directory (default: /tmp/ptime)
output_dir=/home/user/ptime
# Raw snapshot output
raw_log_enabled=true
raw_jsonl_enabled=true
raw_console_enabled=false
#.log / .jsonl data compression
compression_enabled=false
# Metrics output
metrics_on_console=true
metrics_on_json=true
# Log rotation
max_file_size=5m
max_number_of_files=3
# General options
include_self=false| Option | Type | Default | Description |
|---|---|---|---|
output_dir |
string | /tmp/ptime |
Directory for all output files |
raw_log_enabled |
bool | true |
Enable .log snapshot files |
raw_jsonl_enabled |
bool | true |
Enable .jsonl snapshot files |
raw_console_enabled |
bool | false |
Print raw snapshots to console |
compression_enabled |
bool | false |
Enable gzip compression for rotated raw logs |
metrics_on_console |
bool | true |
Print aggregated metrics |
metrics_on_json |
bool | true |
Generate metrics.json |
max_file_size |
size | 5m |
Max size per rotated file |
max_number_of_files |
int | 3 |
Number of rotated files |
include_self |
bool | false |
Include process_analyzer in analysis |
Supported size suffixes: k, m, g.
./process_analyzer [options]The following options are required:
-i,--interval <dur>— sampling interval-n,--count <N>— number of snapshots (infinityis allowed)
-i, --interval <dur> Interval (e.g. 500ms, 1s, 2m)
-n, --count <N> Number of snapshots
-c, --cpu_usage <N> Top N by average CPU usage
-r, --rss_usage <N> Top N by average RSS usage
-s, --rss_increase <N> Top N by RSS increase
-d, --rss_delta <N> Top N by RSS delta
-e, --bytes_read <N> Top N by disk read (KB)
-f, --bytes_write <N> Top N by disk write (KB)
-g, --read_rate <N> Top N by read rate (KB/s)
-a, --write_rate <N> Top N by write rate (KB/s)
-p, --opened_fds <N> Top N by currently opened file descriptors
-m, --fds_increase <N> Top N by file descriptors opened since startup
-o, --fds_delta <N> Top N by file descriptor delta since startup
-k, --filter_by_pid <pid> Comma-separated list of PIDs to include in the analysis
-l, --filter_by_name <name> Comma-separated list of process names to include in the analysis
-j, --delete_old_files Delete old log files
-v, --version Show version
-h, --help Show help
./process_analyzer \
-i 50ms \
-n infinity \
-c 10 -r 10 -s 10 -d 10 \
-e 10 -f 10 \
-g 15 -a 15 \
-p 10 -m 10 -o 10 \
-jStop the program using CTRL+C.
Monitor only specific processes:
./process_analyzer \
-i 1s \
-n 100 \
-k 1234,5678Monitor only processes matching one or more comm names:
./process_analyzer \
-i 1s \
-n 100 \
-l systemd,bashIf both -k and -l are provided, both filters are applied. OR rule is used between them.
./process_analyzer \
-i 1s \
-n 100 \
-c 10 \
-l systemd,bash \
-k 1234,5678The example from above will include in the analysis:
- all processes named "systemd" and "bash"
- processes with PID 1234 and 5678
Each sampling iteration is logged as a snapshot.
[2026-01-11 19:19:49.611] SNAPSHOT START #################
PID=1548 COMM=systemd STATE=S PPID=1 UTIME=71 STIME=21 RSS(KB)=12592 IOR(KB)=0 IOW(KB)=0 THREADS=1 FD=4
PID=1558 COMM=pipewire STATE=S PPID=1548 UTIME=2514 STIME=2346 RSS(KB)=16776 IOR(KB)=1180 IOW(KB)=0 THREADS=3 FD=18
...
SNAPSHOT END #################
{"timestamp":"2026-01-19 21:47:41.677","pid":2603,"comm":"eclipse","state":"S","ppid":1800,"utime":7,"stime":1,"rss_kb":23376,"io_read_kb":640,"io_write_kb":0,"threads":5,"fds":112}
{"timestamp":"2026-01-19 21:47:41.677","pid":2618,"comm":"java","state":"S","ppid":2603,"utime":19907,"stime":1161,"rss_kb":1129624,"io_read_kb":212404,"io_write_kb":16744,"threads":70,"fds":196}
{"timestamp":"2026-01-19 21:47:41.677","pid":2677,"comm":"nautilus","state":"S","ppid":1515,"utime":204,"stime":32,"rss_kb":178828,"io_read_kb":6132,"io_write_kb":80,"threads":19,"fds":31}
All output files are stored in output_dir.
By default:
/tmp/ptime/
Rotation settings are configurable via the config file.
Starting with version 1.2, the tool supports optional gzip compression for rotated snapshot logs.
Compression is designed to run asynchronously and does not block the sampling loop.
When log rotation occurs:
- The active log file is renamed to a rotated filename.
- A compression job is queued.
- A background worker thread compresses the file using gzip.
- The compressed file replaces the rotated file.
- The original uncompressed file is removed.
This design guarantees:
- Minimal impact on sampling performance
- No blocking I/O in the main thread
- Safe operation even under frequent rotations
During rotation with compression enabled:
ptime.jsonl → active file
ptime.jsonl.1.gz → most recent rotated file
ptime.jsonl.2.gz → older files
...
Temporary filenames may briefly appear during compression but are automatically cleaned up afterward.
Compression is controlled via:
compression_enabled=true
If compression is disabled:
- Rotated files remain uncompressed
- No worker thread is created
- No additional CPU overhead occurs
Compression runs in a dedicated worker thread.
Typical overhead:
- ~1% additional CPU at very high sampling rates (≈15 ms interval, 100 MB file size)
- Negligible impact at ≥100 ms intervals
The compression subsystem is implemented in:
code/compression/
compression_worker.c
compression_worker.h
Key characteristics:
- Thread-safe job queue
- Condition-variable based worker wakeup
- Graceful shutdown support
- Failure-safe file handling
- Uses zlib (gzopen, gzwrite)
CLOCK_MONOTONICis used internally for all delta and rate calculations
(CPU usage, disk I/O rates, averages)CLOCK_REALTIMEis used only for human-readable timestamps in logs
This guarantees stable measurements even if the system clock changes.
metrics.jsonfollows a versioned schema- Schema documentation: SCHEMA.md
- Snapshot data uses JSON Lines format (
.jsonl)
Tests were performed on:
- CPU: Intel Core i7 (8th generation, 8 cores)
- Laptop: HP ProBook 450 G5
- OS: Ubuntu Linux
| Sampling Interval | CPU Usage (process_analyzer) |
|---|---|
| 15 ms | ~12% (single core saturated) |
| 30 ms | ~7.5% |
| 1 s | ~0.17–0.2% |
Notes:
- At very small intervals (<20ms), one CPU core may reach near 100%
- For long-running monitoring, intervals ≥100ms are recommended
- With compression enabled, CPU usage increases by approximately ~1% at very small sampling intervals (≈15 ms).
- At typical monitoring intervals (≥100 ms), the compression impact is negligible.
The recommended way to build the project is using the helper scripts from the project root directory:
./make.shOr:
./makeAll.shBoth scripts support the following optional parameters:
-includeUnitTests-includeIntegrationTests
Examples:
# Build only
./make.sh
# Build + unit tests
./make.sh -includeUnitTests
# Build + unit + integration tests
./make.sh -includeUnitTests -includeIntegrationTestsIf no parameters are provided, only the build is executed.
cd code
./make.shOr using Ninja:
mkdir build
cd build
cmake ..
ninjaNote: Manual builds do not run tests automatically. For full validation, use the root build scripts.
├── code/ # Core source code
│ ├── process_analyzer.c # Main entry point
│ ├── args_parser/
│ │ ├── args_parser.c
│ │ └── args_parser.h
│ ├── config/
│ │ ├── config.c
│ │ └── config.h
│ ├── compression/
│ │ ├── compression_worker.c
│ │ └── compression_worker.h
│ ├── process_snapshot/
│ │ ├── process_snapshot.c
│ │ └── process_snapshot.h
│ ├── process_stats/
│ │ ├── process_stats.c
│ │ └── process_stats.h
│ ├── third_party/
│ │ └── uthash/
│ │ └── uthash.h
│ └── CMakeLists.txt
│
├── dashboard/ # client side HTML dashboard
├── tests/ # Test suites
│ ├── unit/ # Unit tests
│ └── integration/ # Integration tests
│
├── make.sh # Main build script
├── makeAll.sh # Clean + full rebuild script
- Interactive charts for all aggregated metrics
- Per-process info box (PID, threads, records)
- Click on any bar to jump to detailed timeline view
- Records page for
.jsonlexploration by PID and metric - Remembers last loaded file in browser storage
- Run
process_analyzernormally - Open
dashboard/index.htmlin your browser - Upload the generated
metrics.json - Optionally open
records.htmlfor timeline exploration
No installation required — just open the HTML file locally.
The project includes both unit tests and integration tests.
- Unit tests validate individual modules
- Integration tests validate real execution scenarios
Tests can be enabled using:
./make.sh -includeUnitTests -includeIntegrationTests
# or
./makeAll.sh -includeUnitTests -includeIntegrationTestsThe project uses cppcheck for static code analysis both locally and in GitHub Actions CI.
Run it locally from the repository root with:
cppcheck \
--enable=warning,style,performance \
--inconclusive \
--error-exitcode=1 \
--std=c11 \
--quiet \
--inline-suppr \
-i code/build \
code/Notes:
-i code/buildexcludes generated build files and reduces unnecessary warnings--inline-supprenables the small number of source-level suppressions used for known false positives--error-exitcode=1makes the command fail when unsuppressed findings are reported
In GitHub Actions, the same cppcheck command runs before the build and test steps.
Each workflow run also uploads a cppcheck-report artifact that contains the XML report generated by CI.
- Requires Linux with
/proc - Disk I/O depends on kernel support
- Short-lived processes may have fewer samples
- Tested on modern systemd-based distributions
RSS and I/O values are reported as -1 when unavailable.
Common causes:
- Kernel threads (
kworker/*) - Process exits during sampling
Invalid samples are excluded from calculations.
Only dependent metrics are skipped.
- Extended configuration support
- Plugin system
- Built-in visualization
- Python analysis tools
Developed as a learning and analysis tool for Linux process monitoring.
MIT License




