Created
October 10, 2025 20:31
-
-
Save marshallmassengill/2cf5434cbd2acdbd2f252676aa4402cf to your computer and use it in GitHub Desktop.
snmpd /proc/pressure stats config
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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