[PATCH v7 40/59] perf net_dropmonitor: Port net_dropmonitor to use python module

Ian Rogers
2026-04-25T22:52:42Z

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>
---
v2:

1. Fixed Sorting of Locations: Kept location as an integer in the
drop_log dictionary keys so that they are sorted numerically rather
than lexicographically when generating the report.

2. Fixed Interrupt Handling: Moved the call to get_kallsyms_table()
and print_drop_table() outside the try-except block. This ensures
that the reporting phase happens exactly once, even if the user
interrupts the trace with Ctrl-C.
---
tools/perf/python/net_dropmonitor.py | 58 ++++++++++++++++++++++++++++
1 file changed, 58 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..25ea2a66ed3c
--- /dev/null
+++ b/tools/perf/python/net_dropmonitor.py
@@ -0,0 +1,58 @@
+#!/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
+import sys
+import perf
+
+
+class DropMonitor:
+ """Monitors dropped packets and aggregates counts by location."""
+
+ def __init__(self):
+ self.drop_log: dict[tuple[str, int], int] = defaultdict(int)
+ self.unhandled: dict[str, int] = defaultdict(int)
+
+ def print_drop_table(self) -> None:
+ """Print aggregated results."""
+ print(f"{'LOCATION':>25} {'OFFSET':>25} {'COUNT':>25}")
+ for (sym, off) in sorted(self.drop_log.keys()):
+ print(f"{sym:>25} {off:>25d} {self.drop_log[(sym, off)]:>25d}")
+
+ def process_event(self, sample: perf.sample_event) -> None:
+ """Process a single sample event."""
+ if str(sample.evsel) != "evsel(skb:kfree_skb)":
+ return
+
+ try:
+ symbol = getattr(sample, "symbol", "[unknown]")
+ symoff = getattr(sample, "symoff", 0)
+ self.drop_log[(symbol, symoff)] += 1
+ except AttributeError:
+ self.unhandled[str(sample.evsel)] += 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:
+ session = perf.session(perf.data(args.input), sample=monitor.process_event)
+ session.process_events()
+ except KeyboardInterrupt:
+ print("\nStopping trace...")
+ except Exception as e:
+ print(f"Error processing events: {e}")
+ sys.exit(1)
+
+ monitor.print_drop_table()
--
2.54.0.545.g6539524ca2-goog