Proc-Sentry is a specialized, lightweight Prometheus exporter designed to monitor the Top N resource-consuming processes on Linux systems. It is built for performance, efficiency, and container awareness.
Standard exporters like node_exporter provide great system-level metrics but lack granular process details. Conversely, exporters that track every process often suffer from "cardinality explosions" in Prometheus, consuming massive amounts of storage and memory.
Proc-Sentry bridges this gap. It intelligently scans all processes but only exports metrics for the top consumers of CPU, Memory, and Disk I/O. This gives you deep visibility into what's actually slowing down your node, without the overhead of monitoring thousands of idle processes.
- Ultra Lightweight: Written in Go, deployed as a static binary in a scratch-based Docker image (~12MB).
- Low Overhead: Uses ~8MB RAM and negligible CPU. Zero Disk I/O impact.
- Container Aware: Automatically detects
container_idfor processes running in Docker, Containerd, or Kubernetes (Cgroups v1 & v2). - User Context: Resolves numeric UIDs to human-readable usernames (e.g.,
root,nginx,postgres). - Secure: Capabilities-aware (
SYS_PTRACE), read-only root filesystem, and AppArmor compatible. - Cardinality Controlled: Uses a "Sort-Then-Select" union strategy across 4 dimensions (CPU, Memory, Disk Read, Disk Write), capping at
4 * TOP_Nunique time series.
| Metric | Description | Labels |
|---|---|---|
proc_process_top_cpu_percent |
CPU usage percentage (per-core scale, 100% = 1 core) | pid, user, command, runtime, rank, hostname, container_id, ports, full_path |
proc_process_top_memory_bytes |
RSS memory in bytes | (same) |
proc_process_top_disk_read_bytes |
Cumulative disk read bytes | (same) |
proc_process_top_disk_write_bytes |
Cumulative disk write bytes | (same) |
proc_scrape_duration_seconds |
Histogram of collection duration | |
proc_scrape_errors_total |
Counter of collection errors | |
proc_scrape_panics_total |
Counter of recovered panics | |
proc_processes_scraped_total |
Total processes seen per scrape | runtime |
+--------------+
| /metrics | <-- Prometheus scrapes here
| (HTTP) |
+------+-------+
|
prometheus.Collector interface
(builds metrics on-demand from snapshot)
|
+------+-------+
| Collector | <-- Background goroutine (every SCRAPE_INTERVAL)
| Loop |
+------+-------+
|
+----------------+----------------+
| | |
+-----------+ +-----------+ +-----------+
| ReadProc | | Top-N | | Port |
| /proc/pid | | Selection | | Resolve |
| stat,io, | | Union of | | (winners |
| cgroup | | 4 dims | | only) |
+-----------+ +-----------+ +-----------+
- Background Collection: A goroutine scans
/procat a configurable interval (default: 5s). - Process Reading: Reads
stat(CPU ticks),status(UID/memory),io(disk),cgroup(container), andexe(full path) for all processes. - CPU Delta Accounting: Computes per-process CPU% using tick deltas against system-wide CPU time, scaled by core count to match
topoutput. - Top-N Selection: Sorts 4 independent lists (CPU, Memory, DiskRead, DiskWrite) and takes the union of top N from each. At most
4 * TOP_Nunique processes. - Port Resolution: Scans
/proc/[pid]/net/tcp[6]and matches socket inodes to file descriptors, only for winner processes to avoid expensive full-system scans. - Metric Emission: Implements the
prometheus.Collectorinterface. Metrics are built on-demand during Prometheus scrapes from a read-locked snapshot, avoiding race conditions.
proc-sentry/
├── main.go # Entry point, HTTP server, graceful shutdown
├── config/
│ └── config.go # Environment variable parsing and validation
├── collector/
│ ├── collector.go # Background loop, prometheus.Collector interface
│ ├── process.go # Process struct, /proc reading
│ ├── topn.go # Top-N union selection algorithm
│ └── ports.go # TCP port resolution via inode matching
├── container/
│ └── detect.go # Cgroup parsing, runtime detection (docker/k8s/containerd)
├── internal/
│ └── usermap.go # /etc/passwd cache and UID resolution
├── cmd/
│ └── test-accuracy/
│ └── main.go # CPU accuracy benchmark test
├── Dockerfile # Multi-stage build (scratch runtime, ~12MB)
├── docker-compose.yml # Docker Compose configuration
├── k8s-manifest.yaml # Kubernetes DaemonSet + Service
└── grafana_dashboard.json # Grafana dashboard
The exporter is configured entirely via environment variables. Invalid values cause a startup failure with a clear error message.
| Variable | Default | Description |
|---|---|---|
TOP_N |
40 |
Top processes per metric dimension. Valid range: 1-500. |
SCRAPE_INTERVAL |
5s |
Collection interval (Go duration format). Valid range: 1s-5m. |
ENABLE_DISK_IO |
true |
Enable/disable disk read/write metrics. |
ENABLE_PORTS |
true |
Enable/disable listening port resolution. |
PROCFS_PATH |
/proc |
Path to /proc filesystem. Use /host/proc in containers. |
PROC_HOSTNAME |
os.Hostname() |
Override the hostname label value. |
METRICS_PORT |
9105 |
Port to serve metrics on. |
Add the following to your prometheus.yml:
scrape_configs:
- job_name: "proc-sentry"
scrape_interval: 15s
static_configs:
- targets: ["<YOUR_NODE_IP>:9105"]For Kubernetes, use a ServiceMonitor if using the Prometheus Operator:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: proc-sentry
labels:
release: prometheus
spec:
selector:
matchLabels:
app: proc-sentry
endpoints:
- port: metricsA professional dashboard covering CPU, Memory, Disk I/O, and Container linkage is included.
- Download
grafana_dashboard.jsonfrom this repository. - Open Grafana > Dashboards > New > Import.
- Upload the JSON file.
- Select your Prometheus datasource.
go build -o proc-sentry .go test ./... -vsudo ./proc-sentry
# or with custom config:
TOP_N=20 SCRAPE_INTERVAL=10s ./proc-sentrygo build -o proc-sentry . && sudo ./proc-sentry &
sleep 2
go run ./cmd/test-accuracy/curl -s localhost:9105/metrics | grep "proc_process_top_cpu_percent" | sort -nr -k 2 | head -n 5ps -eo pid,user,comm,%cpu --sort=-%cpu | head -n 6ps -eo pid,user,comm,rss,%mem --sort=-%mem | head -n 6(Note: ps reports RSS in KB, while proc-sentry reports bytes)
docker logs proc-sentryExpected: Starting proc-sentry on :9105 (TOP_N=40, ...)
If you see apparmor="DENIED" operation="ptrace" in dmesg or journalctl, add security_opt: - apparmor=unconfined to your docker-compose.yml, or --security-opt apparmor=unconfined to docker run.
curl -s localhost:9105/health
# Expected: OKSee DEPLOYMENT.md for detailed installation instructions:
MIT License