[PATCH v1 40/58] perf net_dropmonitor: Port net_dropmonitor to use python module

Ian Rogers
2026-04-20T00:00:55Z

Ported from tools/perf/scripts/python/.
- Refactored the script to use a class structure (DropMonitor) to
encapsulate state.
- Used perf.session for event processing instead of legacy global
handlers.
- Maintained the manual /proc/kallsyms reading and binary search for
symbol resolution as in the original script.
- Cleaned up Python 2 compatibility artifacts.

Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/python/net_dropmonitor.py | 103 +++++++++++++++++++++++++++
1 file changed, 103 insertions(+)
create mode 100755 tools/perf/python/net_dropmonitor.py

diff --git a/tools/perf/python/net_dropmonitor.py b/tools/perf/python/net_dropmonitor.py
new file mode 100755
index 000000000000..474b616725b8
--- /dev/null
+++ b/tools/perf/python/net_dropmonitor.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Monitor the system for dropped packets and produce a report of drop locations and counts.
+Ported from tools/perf/scripts/python/net_dropmonitor.py
+"""
+
+import argparse
+from collections import defaultdict
+from typing import Optional
+import perf
+
+
+class DropMonitor:
+ """Monitors dropped packets and aggregates counts by location."""
+
+ def __init__(self):
+ self.drop_log: dict[str, int] = defaultdict(int)
+ self.kallsyms: list[tuple[int, str]] = []
+
+ def get_kallsyms_table(self) -> None:
+ """Read /proc/kallsyms to resolve addresses."""
+ try:
+ with open("/proc/kallsyms", "r", encoding='utf-8') as f:
+ for line in f:
+ parts = line.split()
+ if len(parts) >= 3:
+ loc = int(parts[0], 16)
+ name = parts[2]
+ self.kallsyms.append((loc, name))
+ except OSError as e:
+ print(f"Failed to read /proc/kallsyms: {e}")
+ return
+ self.kallsyms.sort()
+
+ def get_sym(self, sloc: str) -> tuple[Optional[str], int]:
+ """Binary search in kallsyms table."""
+ loc = int(sloc)
+
+ # Invariant: kallsyms[i][0] <= loc for all 0 <= i <= start
+ # kallsyms[i][0] > loc for all end <= i < len(kallsyms)
+ start, end = -1, len(self.kallsyms)
+ while end != start + 1:
+ pivot = (start + end) // 2
+ if loc < self.kallsyms[pivot][0]:
+ end = pivot
+ else:
+ start = pivot
+
+ # Now (start == -1 or kallsyms[start][0] <= loc)
+ # and (start == len(kallsyms) - 1 or loc < kallsyms[start + 1][0])
+ if start >= 0:
+ symloc, name = self.kallsyms[start]
+ return name, loc - symloc
+ return None, 0
+
+ def print_drop_table(self) -> None:
+ """Print aggregated results."""
+ print(f"{'LOCATION':>25} {'OFFSET':>25} {'COUNT':>25}")
+ for i in sorted(self.drop_log.keys()):
+ sym, off = self.get_sym(i)
+ if sym is None:
+ sym = i
+ print(f"{sym:>25} {off:>25} {self.drop_log[i]:>25}")
+
+ def process_event(self, sample: perf.sample_event) -> None:
+ """Process a single sample event."""
+ if str(sample.evsel) != "evsel(skb:kfree_skb)":
+ return
+
+ location = getattr(sample, "location", None)
+ if location is None:
+ # Try to get it from raw_buf if not exposed directly
+ # But let's assume it is exposed as in failed-syscalls
+ return
+
+ slocation = str(location)
+ self.drop_log[slocation] += 1
+
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser(
+ description="Monitor the system for dropped packets and produce a "
+ "report of drop locations and counts.")
+ ap.add_argument("-i", "--input", default="perf.data", help="Input file name")
+ args = ap.parse_args()
+
+ monitor = DropMonitor()
+
+ try:
+ print("Starting trace (Ctrl-C to dump results)")
+ session = perf.session(perf.data(args.input), sample=monitor.process_event)
+ session.process_events()
+
+ print("Gathering kallsyms data")
+ monitor.get_kallsyms_table()
+ monitor.print_drop_table()
+ except KeyboardInterrupt:
+ print("\nGathering kallsyms data")
+ monitor.get_kallsyms_table()
+ monitor.print_drop_table()
+ except Exception as e:
+ print(f"Error processing events: {e}")
--
2.54.0.rc1.513.gad8abe7a5a-goog