Skip to content

Instantly share code, notes, and snippets.

@marshallmassengill
Created October 10, 2025 20:31
Show Gist options
  • Select an option

  • Save marshallmassengill/2cf5434cbd2acdbd2f252676aa4402cf to your computer and use it in GitHub Desktop.

Select an option

Save marshallmassengill/2cf5434cbd2acdbd2f252676aa4402cf to your computer and use it in GitHub Desktop.
snmpd /proc/pressure stats config
[root@meridian ~]# snmpwalk -v2c -c public localhost .1.3.6.1.4.1.99999.2
SNMPv2-SMI::enterprises.99999.2.1.1.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.1.2.0 = Gauge32: 4
SNMPv2-SMI::enterprises.99999.2.1.3.0 = Gauge32: 14
SNMPv2-SMI::enterprises.99999.2.1.4.0 = Counter64: 31034160
SNMPv2-SMI::enterprises.99999.2.2.1.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.2.2.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.2.3.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.2.4.0 = Counter64: 0
SNMPv2-SMI::enterprises.99999.2.3.1.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.3.2.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.3.3.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.3.4.0 = Counter64: 488871
SNMPv2-SMI::enterprises.99999.2.4.1.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.4.2.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.4.3.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.4.4.0 = Counter64: 466626
SNMPv2-SMI::enterprises.99999.2.5.1.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.5.2.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.5.3.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.5.4.0 = Counter64: 37
SNMPv2-SMI::enterprises.99999.2.6.1.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.6.2.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.6.3.0 = Gauge32: 0
SNMPv2-SMI::enterprises.99999.2.6.4.0 = Counter64: 37
#!/usr/bin/python3
"""
SNMP pass-persist script for /proc/pressure statistics.
Exposes CPU, I/O, and Memory pressure stall information via SNMP.
OID base: .1.3.6.1.4.1.99999.2
OID Structure:
.2.1.x = CPU some (some tasks stalled)
.2.2.x = CPU full (all tasks stalled)
.2.3.x = I/O some
.2.4.x = I/O full
.2.5.x = Memory some
.2.6.x = Memory full
Where x is:
.1 = avg10 (gauge, scaled by 100: 123 = 1.23%)
.2 = avg60 (gauge, scaled by 100)
.3 = avg300 (gauge, scaled by 100)
.4 = total stall time (counter64, microseconds)
"""
import sys
import time
import os
import re
# Enable unbuffered I/O
sys.stdin.reconfigure(line_buffering=True)
sys.stdout.reconfigure(line_buffering=True)
# Configuration
LOG_FILE = "/var/log/snmpd-pressure.log"
BASE_OID = ".1.3.6.1.4.1.99999.2"
# Pressure file paths
PRESSURE_FILES = {
'cpu': '/proc/pressure/cpu',
'io': '/proc/pressure/io',
'memory': '/proc/pressure/memory'
}
# OID mapping: (resource_type, stall_type, metric) -> OID suffix
OID_MAP = {
('cpu', 'some', 'avg10'): '1.1',
('cpu', 'some', 'avg60'): '1.2',
('cpu', 'some', 'avg300'): '1.3',
('cpu', 'some', 'total'): '1.4',
('cpu', 'full', 'avg10'): '2.1',
('cpu', 'full', 'avg60'): '2.2',
('cpu', 'full', 'avg300'): '2.3',
('cpu', 'full', 'total'): '2.4',
('io', 'some', 'avg10'): '3.1',
('io', 'some', 'avg60'): '3.2',
('io', 'some', 'avg300'): '3.3',
('io', 'some', 'total'): '3.4',
('io', 'full', 'avg10'): '4.1',
('io', 'full', 'avg60'): '4.2',
('io', 'full', 'avg300'): '4.3',
('io', 'full', 'total'): '4.4',
('memory', 'some', 'avg10'): '5.1',
('memory', 'some', 'avg60'): '5.2',
('memory', 'some', 'avg300'): '5.3',
('memory', 'some', 'total'): '5.4',
('memory', 'full', 'avg10'): '6.1',
('memory', 'full', 'avg60'): '6.2',
('memory', 'full', 'avg300'): '6.3',
('memory', 'full', 'total'): '6.4',
}
def log(msg):
"""Log to file"""
try:
with open(LOG_FILE, 'a') as f:
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
f.write(f"[{timestamp}] {msg}\n")
except Exception:
pass
def send_response(lines):
"""Send response to snmpd"""
try:
for line in lines:
print(line)
sys.stdout.flush()
return True
except (BrokenPipeError, IOError):
log("ERROR: Broken pipe")
return False
def parse_pressure_line(line):
"""
Parse a line like: some avg10=0.00 avg60=0.05 avg300=0.12 total=1234567
Returns dict: {'avg10': 0.00, 'avg60': 0.05, 'avg300': 0.12, 'total': 1234567}
"""
result = {}
# Match patterns like avg10=0.00 or total=1234567
matches = re.findall(r'(\w+)=([\d.]+)', line)
for key, value in matches:
if key == 'total':
result[key] = int(value)
else:
result[key] = float(value)
return result
def read_pressure_stats():
"""
Read all pressure stats and return as OID -> (type, value) dict
"""
data = {}
for resource, filepath in PRESSURE_FILES.items():
try:
with open(filepath, 'r') as f:
lines = f.readlines()
for line in lines:
line = line.strip()
if not line:
continue
# Determine stall type (some or full)
if line.startswith('some '):
stall_type = 'some'
elif line.startswith('full '):
stall_type = 'full'
else:
continue
# Parse metrics
metrics = parse_pressure_line(line)
# Create OIDs for each metric
for metric, value in metrics.items():
key = (resource, stall_type, metric)
if key not in OID_MAP:
continue
oid_suffix = OID_MAP[key]
full_oid = f"{BASE_OID}.{oid_suffix}.0"
# Determine SNMP type and format value
if metric == 'total':
# Cumulative counter in microseconds
snmp_type = 'counter64'
snmp_value = str(value)
else:
# Average percentages - scale by 100 and use gauge
# e.g., 1.23% becomes 123
snmp_type = 'gauge'
snmp_value = str(int(value * 100))
data[full_oid] = (snmp_type, snmp_value)
except FileNotFoundError:
log(f"Warning: {filepath} not found")
except Exception as e:
log(f"Error reading {filepath}: {e}")
return data
def get_next_oid(oid, data):
"""Find the next OID in sequence"""
oids = sorted(data.keys())
for next_oid in oids:
if next_oid > oid:
return next_oid
return None
def handle_get(oid):
"""Handle a GET request"""
data = read_pressure_stats()
if oid in data:
dtype, value = data[oid]
log(f"GET {oid} -> {dtype}: {value}")
return send_response([oid, dtype, value])
else:
log(f"GET {oid} -> NONE")
return send_response(["NONE"])
def handle_getnext(oid):
"""Handle a GETNEXT request"""
data = read_pressure_stats()
next_oid = get_next_oid(oid, data)
if next_oid:
dtype, value = data[next_oid]
log(f"GETNEXT {oid} -> {next_oid}: {dtype}: {value}")
return send_response([next_oid, dtype, value])
else:
log(f"GETNEXT {oid} -> NONE")
return send_response(["NONE"])
def main():
log(f"Pressure stats script starting (PID: {os.getpid()})")
try:
# Read PING
line = sys.stdin.readline().strip()
log(f"Received: {line}")
if line != "PING":
log(f"ERROR: Expected PING, got: {line}")
sys.exit(1)
# Respond with PONG
if not send_response(["PONG"]):
sys.exit(1)
log("Sent: PONG")
# Main loop
while True:
line = sys.stdin.readline()
if not line:
log("EOF received, exiting")
break
command = line.strip()
if command == "PING":
if not send_response(["PONG"]):
break
continue
# Read OID
oid = sys.stdin.readline().strip()
if command == "get":
if not handle_get(oid):
break
elif command == "getnext":
if not handle_getnext(oid):
break
else:
log(f"Unknown command: {command}")
if not send_response(["NONE"]):
break
except Exception as e:
log(f"FATAL ERROR: {e}")
sys.exit(1)
log("Script exiting")
if __name__ == "__main__":
main()
#I have something screwy so I added this explicitly to get it working
view all included .1.3.6.1.4.1.99999.2
#Should be able to adjust the oid but whatever... this works.
#you mostly just need this line
pass_persist .1.3.6.1.4.1.99999.2 /usr/local/bin/snmpd-pressure.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment