101 lines
3.2 KiB
Python
Executable File
101 lines
3.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Monitor per-second interrupt rates for one CPU (or TOTAL), sorted by type."""
|
|
|
|
import sys
|
|
import time
|
|
|
|
|
|
def parse_interrupts():
|
|
"""Parse /proc/interrupts.
|
|
|
|
Returns (cpu_names, rows) where rows maps label -> (counts, description).
|
|
counts is a list of per-CPU integers; description is the trailing text
|
|
(e.g. "IO-APIC 2-edge timer" or "Local timer interrupts").
|
|
"""
|
|
rows = {}
|
|
with open("/proc/interrupts") as f:
|
|
cpu_names = f.readline().split()
|
|
num_cpus = len(cpu_names)
|
|
for line in f:
|
|
parts = line.split()
|
|
if not parts:
|
|
continue
|
|
label = parts[0].rstrip(":")
|
|
counts = []
|
|
i = 1
|
|
while i < len(parts) and len(counts) < num_cpus:
|
|
try:
|
|
counts.append(int(parts[i]))
|
|
except ValueError:
|
|
break
|
|
i += 1
|
|
if not counts:
|
|
continue
|
|
description = " ".join(parts[i:])
|
|
rows[label] = (counts, description)
|
|
return cpu_names, rows
|
|
|
|
|
|
def resolve_cpu(arg, cpu_names):
|
|
"""Resolve a CPU argument to an index, or None for TOTAL."""
|
|
if arg.upper() == "TOTAL":
|
|
return None
|
|
name = arg if arg.upper().startswith("CPU") else f"CPU{arg}"
|
|
name = name.upper()
|
|
for idx, cpu in enumerate(cpu_names):
|
|
if cpu.upper() == name:
|
|
return idx
|
|
print(f"Unknown CPU '{arg}'. Available: {', '.join(cpu_names)}, TOTAL", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def value_for(counts, cpu_idx):
|
|
"""Return the count for the selected CPU, or the sum for TOTAL."""
|
|
if cpu_idx is None:
|
|
return sum(counts)
|
|
if cpu_idx < len(counts):
|
|
return counts[cpu_idx]
|
|
return 0
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print(f"Usage: {sys.argv[0]} <cpu-index|CPU0|TOTAL> [interval-secs]", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
cpu_names, prev_rows = parse_interrupts()
|
|
cpu_idx = resolve_cpu(sys.argv[1], cpu_names)
|
|
interval = float(sys.argv[2]) if len(sys.argv) > 2 else 1.0
|
|
|
|
target = "TOTAL (all CPUs)" if cpu_idx is None else cpu_names[cpu_idx]
|
|
print(f"Monitoring interrupts on {target} every {interval:g}s, sorted by rate (Ctrl-C to stop)\n")
|
|
|
|
prev = {label: value_for(counts, cpu_idx) for label, (counts, _) in prev_rows.items()}
|
|
|
|
try:
|
|
while True:
|
|
time.sleep(interval)
|
|
_, curr_rows = parse_interrupts()
|
|
deltas = []
|
|
for label, (counts, description) in curr_rows.items():
|
|
curr = value_for(counts, cpu_idx)
|
|
delta = curr - prev.get(label, curr)
|
|
if delta > 0:
|
|
deltas.append((delta, label, description))
|
|
prev[label] = curr
|
|
|
|
deltas.sort(reverse=True)
|
|
total = sum(d for d, _, _ in deltas)
|
|
print(f"===== {total} interrupts/{interval:g}s on {target} =====")
|
|
print(f"{'RATE':>10} {'IRQ':<8} {'TYPE'}")
|
|
print("-" * 60)
|
|
for delta, label, description in deltas:
|
|
print(f"{delta:>10} {label:<8} {description}")
|
|
print()
|
|
except KeyboardInterrupt:
|
|
print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|