From d73c793989104bb305d7440541350dd86aab731e Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Fri, 28 Nov 2025 20:59:46 +0530 Subject: [PATCH] format chore --- .../container-monitor/container_monitor.py | 27 +++-- .../{data_collector.py => data_collection.py} | 28 ++--- BCC-Examples/container-monitor/tui.py | 107 ++++++++++++------ 3 files changed, 98 insertions(+), 64 deletions(-) rename BCC-Examples/container-monitor/{data_collector.py => data_collection.py} (90%) diff --git a/BCC-Examples/container-monitor/container_monitor.py b/BCC-Examples/container-monitor/container_monitor.py index 07a29c1..7904060 100644 --- a/BCC-Examples/container-monitor/container_monitor.py +++ b/BCC-Examples/container-monitor/container_monitor.py @@ -1,20 +1,18 @@ """Container Monitor - TUI-based cgroup monitoring combining syscall, file I/O, and network tracking.""" -import time -import os -from pathlib import Path from pythonbpf import bpf, map, section, bpfglobal, struct, BPF from pythonbpf.maps import HashMap from pythonbpf.helper import get_current_cgroup_id from ctypes import c_int32, c_uint64, c_void_p from vmlinux import struct_pt_regs, struct_sk_buff -from data_collector import ContainerDataCollector +from data_collection import ContainerDataCollector from tui import ContainerMonitorTUI # ==================== BPF Structs ==================== + @bpf @struct class read_stats: @@ -40,6 +38,7 @@ class net_stats: # ==================== BPF Maps ==================== + @bpf @map def read_map() -> HashMap: @@ -66,6 +65,7 @@ def syscall_count() -> HashMap: # ==================== File I/O Tracing ==================== + @bpf @section("kprobe/vfs_read") def trace_read(ctx: struct_pt_regs) -> c_int32: @@ -109,6 +109,7 @@ def trace_write(ctx1: struct_pt_regs) -> c_int32: # ==================== Network I/O Tracing ==================== + @bpf @section("kprobe/__netif_receive_skb") def trace_netif_rx(ctx2: struct_pt_regs) -> c_int32: @@ -165,6 +166,7 @@ def trace_dev_xmit(ctx3: struct_pt_regs) -> c_int32: # ==================== Syscall Tracing ==================== + @bpf @section("tracepoint/raw_syscalls/sys_enter") def count_syscalls(ctx: c_void_p) -> c_int32: @@ -190,32 +192,29 @@ def LICENSE() -> str: if __name__ == "__main__": print("🔥 Loading BPF programs...") - + # Load and attach BPF program b = BPF() b.load() b.attach_all() - + # Get map references and enable struct deserialization read_map_ref = b["read_map"] write_map_ref = b["write_map"] net_stats_map_ref = b["net_stats_map"] syscall_count_ref = b["syscall_count"] - + read_map_ref.set_value_struct("read_stats") write_map_ref.set_value_struct("write_stats") net_stats_map_ref.set_value_struct("net_stats") - + print("✅ BPF programs loaded and attached") - + # Setup data collector collector = ContainerDataCollector( - read_map_ref, - write_map_ref, - net_stats_map_ref, - syscall_count_ref + read_map_ref, write_map_ref, net_stats_map_ref, syscall_count_ref ) - + # Create and run TUI tui = ContainerMonitorTUI(collector) tui.run() diff --git a/BCC-Examples/container-monitor/data_collector.py b/BCC-Examples/container-monitor/data_collection.py similarity index 90% rename from BCC-Examples/container-monitor/data_collector.py rename to BCC-Examples/container-monitor/data_collection.py index 47c2b49..9854055 100644 --- a/BCC-Examples/container-monitor/data_collector.py +++ b/BCC-Examples/container-monitor/data_collection.py @@ -3,7 +3,7 @@ import os import time from pathlib import Path -from typing import Dict, List, Set, Optional, Tuple +from typing import Dict, List, Set, Optional from dataclasses import dataclass from collections import deque, defaultdict @@ -11,6 +11,7 @@ from collections import deque, defaultdict @dataclass class CgroupInfo: """Information about a cgroup.""" + id: int name: str path: str @@ -19,6 +20,7 @@ class CgroupInfo: @dataclass class ContainerStats: """Statistics for a container/cgroup.""" + cgroup_id: int cgroup_name: str @@ -44,7 +46,9 @@ class ContainerStats: class ContainerDataCollector: """Collects and manages container monitoring data from BPF.""" - def __init__(self, read_map, write_map, net_stats_map, syscall_map, history_size: int = 100): + def __init__( + self, read_map, write_map, net_stats_map, syscall_map, history_size: int = 100 + ): self.read_map = read_map self.write_map = write_map self.net_stats_map = net_stats_map @@ -53,12 +57,14 @@ class ContainerDataCollector: # Caching self._cgroup_cache: Dict[int, CgroupInfo] = {} self._cgroup_cache_time = 0 - self._cache_ttl = 5. + self._cache_ttl = 5.0 0 # Refresh cache every 5 seconds # Historical data for graphing self._history_size = history_size - self._history: Dict[int, deque] = defaultdict(lambda: deque(maxlen=history_size)) + self._history: Dict[int, deque] = defaultdict( + lambda: deque(maxlen=history_size) + ) def get_all_cgroups(self) -> List[CgroupInfo]: """Get all cgroups with caching.""" @@ -105,11 +111,7 @@ class ContainerDataCollector: best_path = self._get_best_cgroup_path(paths) name = self._get_cgroup_name(best_path) - new_cache[cgroup_id] = CgroupInfo( - id=cgroup_id, - name=name, - path=best_path - ) + new_cache[cgroup_id] = CgroupInfo(id=cgroup_id, name=name, path=best_path) self._cgroup_cache = new_cache self._cgroup_cache_time = time.time() @@ -120,13 +122,13 @@ class ContainerDataCollector: # Prefer paths with more components (more specific) # Prefer paths containing docker, podman, etc. - for keyword in ['docker', 'podman', 'kubernetes', 'k8s', 'systemd']: + for keyword in ["docker", "podman", "kubernetes", "k8s", "systemd"]: for path in path_list: if keyword in path.lower(): return path # Return longest path (most specific) - return max(path_list, key=lambda p: (len(p.split('/')), len(p))) + return max(path_list, key=lambda p: (len(p.split("/")), len(p))) def _get_cgroup_name(self, path: str) -> str: """Extract a friendly name from cgroup path.""" @@ -165,9 +167,7 @@ class ContainerDataCollector: cgroup_name = cgroup_info.name if cgroup_info else f"cgroup-{cgroup_id}" stats = ContainerStats( - cgroup_id=cgroup_id, - cgroup_name=cgroup_name, - timestamp=time.time() + cgroup_id=cgroup_id, cgroup_name=cgroup_name, timestamp=time.time() ) # Get file I/O stats diff --git a/BCC-Examples/container-monitor/tui.py b/BCC-Examples/container-monitor/tui.py index a01f0ca..8f688fd 100644 --- a/BCC-Examples/container-monitor/tui.py +++ b/BCC-Examples/container-monitor/tui.py @@ -1,10 +1,9 @@ """Terminal User Interface for container monitoring.""" -import sys import time import curses from typing import Optional, List -from data_collector import ContainerDataCollector, CgroupInfo, ContainerStats +from data_collection import ContainerDataCollector class ContainerMonitorTUI: @@ -122,11 +121,11 @@ class ContainerMonitorTUI: # Highlight selected stdscr.attron(curses.color_pair(2) | curses.A_BOLD | curses.A_REVERSE) line = f"► {cgroup.name:<40} ID: {cgroup.id}" - stdscr.addstr(y, 2, line[:width - 4]) + stdscr.addstr(y, 2, line[: width - 4]) stdscr.attroff(curses.color_pair(2) | curses.A_BOLD | curses.A_REVERSE) else: line = f" {cgroup.name:<40} ID: {cgroup.id}" - stdscr.addstr(y, 2, line[:width - 4]) + stdscr.addstr(y, 2, line[: width - 4]) # Footer with count footer = f"Total cgroups: {len(cgroups)}" @@ -174,21 +173,37 @@ class ContainerMonitorTUI: # RX graph y += 2 - rx_label = f"RX: {self._format_bytes(stats.rx_bytes)} ({stats.rx_packets:,} packets)" + rx_label = ( + f"RX: {self._format_bytes(stats.rx_bytes)} ({stats.rx_packets:,} packets)" + ) stdscr.addstr(y, 2, rx_label) if len(history) > 1: - self._draw_bar_graph(stdscr, y + 1, 2, width - 4, 3, - [s.rx_bytes for s in history], - curses.color_pair(2)) + self._draw_bar_graph( + stdscr, + y + 1, + 2, + width - 4, + 3, + [s.rx_bytes for s in history], + curses.color_pair(2), + ) # TX graph y += 5 - tx_label = f"TX: {self._format_bytes(stats.tx_bytes)} ({stats.tx_packets:,} packets)" + tx_label = ( + f"TX: {self._format_bytes(stats.tx_bytes)} ({stats.tx_packets:,} packets)" + ) stdscr.addstr(y, 2, tx_label) if len(history) > 1: - self._draw_bar_graph(stdscr, y + 1, 2, width - 4, 3, - [s.tx_bytes for s in history], - curses.color_pair(3)) + self._draw_bar_graph( + stdscr, + y + 1, + 2, + width - 4, + 3, + [s.tx_bytes for s in history], + curses.color_pair(3), + ) # File I/O graphs y += 5 @@ -196,21 +211,37 @@ class ContainerMonitorTUI: # Read graph y += 2 - read_label = f"READ: {self._format_bytes(stats.read_bytes)} ({stats.read_ops:,} ops)" + read_label = ( + f"READ: {self._format_bytes(stats.read_bytes)} ({stats.read_ops:,} ops)" + ) stdscr.addstr(y, 2, read_label) if len(history) > 1: - self._draw_bar_graph(stdscr, y + 1, 2, width - 4, 3, - [s.read_bytes for s in history], - curses.color_pair(4)) + self._draw_bar_graph( + stdscr, + y + 1, + 2, + width - 4, + 3, + [s.read_bytes for s in history], + curses.color_pair(4), + ) # Write graph y += 5 - write_label = f"WRITE: {self._format_bytes(stats.write_bytes)} ({stats.write_ops:,} ops)" + write_label = ( + f"WRITE: {self._format_bytes(stats.write_bytes)} ({stats.write_ops:,} ops)" + ) stdscr.addstr(y, 2, write_label) if len(history) > 1: - self._draw_bar_graph(stdscr, y + 1, 2, width - 4, 3, - [s.write_bytes for s in history], - curses.color_pair(5)) + self._draw_bar_graph( + stdscr, + y + 1, + 2, + width - 4, + 3, + [s.write_bytes for s in history], + curses.color_pair(5), + ) def _draw_section_header(self, stdscr, y: int, title: str, color_pair: int): """Draw a section header.""" @@ -220,8 +251,16 @@ class ContainerMonitorTUI: stdscr.addstr(y, len(title) + 3, "─" * (width - len(title) - 5)) stdscr.attroff(curses.color_pair(color_pair) | curses.A_BOLD) - def _draw_bar_graph(self, stdscr, y: int, x: int, width: int, height: int, - data: List[float], color_pair: int): + def _draw_bar_graph( + self, + stdscr, + y: int, + x: int, + width: int, + height: int, + data: List[float], + color_pair: int, + ): """Draw a simple bar graph.""" if not data or width < 2: return @@ -250,25 +289,21 @@ class ContainerMonitorTUI: else: bar_line += " " - try: - stdscr.attron(color_pair) - stdscr.addstr(y + row, x, bar_line[:width]) - stdscr.attroff(color_pair) - except: - pass # Ignore errors at screen edges + stdscr.attron(color_pair) + stdscr.addstr(y + row, x, bar_line[:width]) + stdscr.attroff(color_pair) - def _format_bytes(self, bytes_val: int) -> str: + def _format_bytes(self, bytes_val: float) -> str: """Format bytes into human-readable string.""" - for unit in ['B', 'KB', 'MB', 'GB', 'TB']: + for unit in ["B", "KB", "MB", "GB", "TB"]: if bytes_val < 1024.0: return f"{bytes_val:.2f} {unit}" - bytes_val /= 1024. - 0 + bytes_val /= 1024.0 return f"{bytes_val:.2f} PB" def _handle_input(self, key: int) -> bool: """Handle keyboard input. Returns False to exit.""" - if key == ord('q') or key == ord('Q'): + if key == ord("q") or key == ord("Q"): return False # Exit if self.current_screen == "selection": @@ -277,19 +312,19 @@ class ContainerMonitorTUI: elif key == curses.KEY_DOWN: cgroups = self.collector.get_all_cgroups() self.selected_index = min(len(cgroups) - 1, self.selected_index + 1) - elif key == ord('\n') or key == curses.KEY_ENTER or key == 10: + elif key == ord("\n") or key == curses.KEY_ENTER or key == 10: # Select cgroup cgroups = self.collector.get_all_cgroups() if cgroups and 0 <= self.selected_index < len(cgroups): cgroups.sort(key=lambda c: c.name) self.selected_cgroup = cgroups[self.selected_index].id self.current_screen = "monitoring" - elif key == ord('r') or key == ord('R'): + elif key == ord("r") or key == ord("R"): # Force refresh cache self.collector._cgroup_cache_time = 0 elif self.current_screen == "monitoring": - if key == 27 or key == ord('b') or key == ord('B'): # ESC or 'b' + if key == 27 or key == ord("b") or key == ord("B"): # ESC or 'b' self.current_screen = "selection" self.selected_cgroup = None