Skip to content

Instantly share code, notes, and snippets.

@microlinux
Created July 8, 2026 00:27
Show Gist options
  • Select an option

  • Save microlinux/db22cf903771da79540cc958efafed89 to your computer and use it in GitHub Desktop.

Select an option

Save microlinux/db22cf903771da79540cc958efafed89 to your computer and use it in GitHub Desktop.
Program to snapshot system resource usage and process information. Originally written for analysis of ODX (https://github.com/WebODM/ODX) performance.
#!/bin/env python3
"""
peeper.py
Program to snapshot system resource usage and process information. Originally
written for analysis of ODX (https://github.com/WebODM/ODX) performance.
Output format (JSON):
{
"time": 1783055343.2371664,
"cpu_total_used": 64.7,
"cpu_cores": {
"0": {
"used": 30.6,
"freq": 4547.17
},
"1": {
"used": 11.1,
"freq": 4547.17
},
"2": {
"used": 58.0,
"freq": 4547.18
},
"3": {
"used": 26.0,
"freq": 4547.16
},
"4": {
"used": 46.5,
"freq": 550.0
},
"5": {
"used": 76.0,
"freq": 550.0
},
"6": {
"used": 89.9,
"freq": 550.0
},
"7": {
"used": 100.0,
"freq": 4547.18
},
"8": {
"used": 67.7,
"freq": 550.0
},
"9": {
"used": 96.0,
"freq": 4547.18
},
"10": {
"used": 21.0,
"freq": 4547.17
},
"11": {
"used": 2.0,
"freq": 550.0
},
"12": {
"used": 100.0,
"freq": 4547.17
},
"13": {
"used": 73.0,
"freq": 550.0
},
"14": {
"used": 46.5,
"freq": 4547.17
},
"15": {
"used": 81.2,
"freq": 550.0
},
"16": {
"used": 69.3,
"freq": 550.0
},
"17": {
"used": 88.0,
"freq": 550.0
},
"18": {
"used": 39.0,
"freq": 550.0
},
"19": {
"used": 72.3,
"freq": 550.0
},
"20": {
"used": 98.0,
"freq": 4547.16
},
"21": {
"used": 100.0,
"freq": 4547.17
},
"22": {
"used": 100.0,
"freq": 4547.17
},
"23": {
"used": 100.0,
"freq": 4547.16
},
"24": {
"used": 100.0,
"freq": 4547.17
},
"25": {
"used": 21.0,
"freq": 4547.16
},
"26": {
"used": 78.0,
"freq": 550.0
},
"27": {
"used": 100.0,
"freq": 4547.18
},
"28": {
"used": 0.0,
"freq": 550.0
},
"29": {
"used": 26.0,
"freq": 4547.16
},
"30": {
"used": 54.0,
"freq": 550.0
},
"31": {
"used": 100.0,
"freq": 4547.17
}
},
"ram_total": 134987984896,
"ram_used": 23532359680,
"ram_avail": 111455625216,
"swap_total": 255997239296,
"swap_used": 0,
"swap_avail": 255997239296,
"disk_total": 3934595964928,
"disk_used": 97899986944,
"disk_avail": 3636752879616,
"gpu_cpu_used": 0.0,
"gpu_ram_total": 6291456000,
"gpu_ram_used": 1024000,
"gpu_ram_avail": 5881856000,
"procs": {
"184507": {
"cmd": "renderdem /var/www/data/10e824de-3d92-4cb2-95f9-a6af17a1b6e5/odm_georeferencing/odm_georeferenced_model.laz --outdir /var/www/data/10e824de- 3d92-4cb2-95f9-a6af17a1b6e5/odm_dem --output-type max --radiuses 0.02,0.02828427 1247461905,0.04000000000000001 --resolution 0.02 --max-tiles 0 --decimation 1 -- classification -1 --tile-size 4096 --force",
"ram_used": 15243636736,
"cpu_used": 47.47
},
"184592": {
"cmd": "/home/trm/peeper/bin/python3 /home/trm/peeper/peeper.py -d /home/trm/peeper/data.log -e /home/trm/peeper/error.log",
"ram_used": 16252928,
"cpu_used": 0.03
}
}
}
"""
from argparse import ArgumentParser
from datetime import datetime
from json import dumps, loads
from pathlib import Path
from psutil import AccessDenied, NoSuchProcess, ZombieProcess, cpu_count, cpu_freq, cpu_percent, process_iter, swap_memory, virtual_memory
from shutil import disk_usage
from subprocess import run
from sys import stderr
from time import sleep, time
from traceback import format_exc
def parse_args():
"""Parse command line arguments.
Returns:
argparse.Namespace
"""
parser = ArgumentParser(description='Prints system resource usage and process info as a JSON encoded dictionary to stdout. Processes with zero CPU or RAM usage are not captured.')
parser.add_argument('-f', action='store_true', help='Format stdout for a human')
parser.add_argument('-d', metavar='FILEPATH', type=str, help='Also write data to a file')
parser.add_argument('-e', metavar='FILEPATH', type=str, help='Also write errors to a file')
parser.add_argument('-m', metavar='MOUNTPOINT', default='/', help='Mount point of file system to check (default: /)')
parser.add_argument('-s', metavar='SECONDS', type=int, default=1, help='Time period for CPU measurement in seconds (default: 1)')
return parser.parse_args()
def get_cpu_info(sleep_time=1):
"""Get usage (%) and frequency (MHz) of each CPU core.
Args:
sleep_time (float|int): Time period for CPU measurement in seconds.
Returns:
Dictionary containing total CPU usage and another dictionary where CPU core numbers are indexes. Each index is a dictionary containing usage and frequency.
"""
used = cpu_percent(interval=sleep_time, percpu=True)
freq = cpu_freq(percpu=True)
cores = {}
total = 0
for i in range(len(used)):
cores[i] = {'used': round(used[i], 2), 'freq': round(freq[i].current, 2)}
total = total + cores[i]['used']
total = round(total / len(cores), 1)
return {'cpu_total_used': total, 'cpu_cores': cores}
def get_disk_info(mount_point='/'):
"""Get total, used and available disk space (bytes) for a file system.
Args:
mount_point (string): Mount point of the file system to check.
Returns:
Dictionary
"""
total, used, avail = disk_usage(mount_point)
return {'disk_total': total, 'disk_used': used, 'disk_avail': avail}
def get_gpu_info():
"""Get total, used and available GPU RAM (bytes) and CPU usage (%). Values will be None if the underlying shell command fails.
Returns:
Dictionary
"""
try:
result = run(['nvidia-smi', '--query-gpu', 'memory.used,memory.free,memory.total,clocks.gr,clocks.sm,utilization.gpu'], capture_output=True, text=True)
gpu_ram_used, _, gpu_ram_avail, _, gpu_ram_total, _, _, _, _, _, gpu_cpu_used, _ = result.stdout.strip().splitlines()[1].split()
return {'gpu_cpu_used': float(gpu_cpu_used), 'gpu_ram_total': int(gpu_ram_total) * 1024000, 'gpu_ram_used': int(gpu_ram_used) * 1024000, 'gpu_ram_avail': int(gpu_ram_avail) * 1024000}
except:
return {'gpu_cpu_used': None, 'gpu_ram_total': None, 'gpu_ram_used': None, 'gpu_ram_avail': None}
def get_mem_info():
"""Get total, used and available system RAM and swap (bytes).
Returns:
Dictionary
"""
ram = virtual_memory()
swap = swap_memory()
return {'ram_total': ram.total, 'ram_used': ram.used, 'ram_avail': ram.available, 'swap_total': swap.total, 'swap_used': swap.used, 'swap_avail': swap.free}
def get_procs(sleep_time=1):
"""Get the PID, command, RAM (bytes) and CPU used (%) for processes. Processes using zero RAM, zero CPU or those that die during data collection are not captured.
Args:
sleep_time (float|int): Time period for CPU measurement in seconds.
Returns:
Dictionary where PIDs are indexes.
"""
procs = {}
dead_procs = []
for proc in process_iter(attrs=['pid', 'name', 'memory_info']):
try:
cmd = ' '.join(proc.cmdline()).strip()
ram = proc.info['memory_info'].rss
proc.cpu_percent(interval=None)
except (AccessDenied, NoSuchProcess, ZombieProcess):
continue
procs[proc.info['pid']] = {'cmd': cmd, 'ram_used': ram, 'obj': proc}
sleep(sleep_time)
for pid, info in procs.items():
try:
procs[pid]['cpu_used'] = round(procs[pid]['obj'].cpu_percent(interval=None) / cpu_count(), 2)
except (AccessDenied, NoSuchProcess, ZombieProcess):
dead_procs.append(pid)
for pid in dead_procs:
del procs[pid]
for pid in procs:
del procs[pid]['obj']
return procs
"""Begin main program"""
try:
args = parse_args()
cpu_info = get_cpu_info(args.s)
mem_info = get_mem_info()
disk_info = get_disk_info(args.m)
gpu_info = get_gpu_info()
procs = get_procs()
discard_procs = []
for pid in procs:
if procs[pid]['cpu_used'] == 0.0 or procs[pid]['ram_used'] == 0:
discard_procs.append(pid)
for pid in discard_procs:
del procs[pid]
json_string = dumps({'time': time()} | cpu_info | mem_info | disk_info | gpu_info | {'procs': procs})
if args.f:
print(dumps(loads(json_string), indent=4))
else:
print(json_string)
if args.d:
with open(args.d, 'a+') as fh:
fh.write(f'{json_string}\n')
except Exception as e:
stderr.write('The execution of this program resulted in abject failure, for the vague reason stated below.\n\n')
stderr.write(format_exc())
if args.e:
with open(args.e, 'a+') as fh:
fh.write(f'{datetime.fromtimestamp(time()).strftime("%Y-%m-%d %H:%M:%S")} {format_exc()}\n')
exit(1)
exit(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment