Skip to content

Commit 6c55d56

Browse files
add file io and network stats in container monitor example
1 parent 704b0d8 commit 6c55d56

File tree

2 files changed

+304
-13
lines changed

2 files changed

+304
-13
lines changed

BCC-Examples/container-monitor/file_io.bpf.py

Lines changed: 112 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import logging
2-
3-
from pythonbpf import bpf, map, section, bpfglobal, struct, compile
2+
import time
3+
import os
4+
from pathlib import Path
5+
from pythonbpf import bpf, map, section, bpfglobal, struct, compile, BPF
46
from pythonbpf.maps import HashMap
57
from pythonbpf.helper import get_current_cgroup_id
68
from ctypes import c_int32, c_uint64
@@ -33,23 +35,18 @@ def write_map() -> HashMap:
3335
return HashMap(key=c_uint64, value=write_stats, max_entries=1024)
3436

3537

36-
#
37-
# READ PROBE
38-
#
3938
@bpf
4039
@section("kprobe/vfs_read")
4140
def trace_read(ctx: struct_pt_regs) -> c_int32:
4241
cg = get_current_cgroup_id()
4342
count = c_uint64(ctx.dx)
4443
ptr = read_map.lookup(cg)
45-
4644
if ptr:
4745
s = read_stats()
4846
s.bytes = ptr.bytes + count
4947
s.ops = ptr.ops + 1
50-
read_map.update(cg, ptr)
48+
read_map.update(cg, s)
5149
else:
52-
print("read init")
5350
s = read_stats()
5451
s.bytes = count
5552
s.ops = c_uint64(1)
@@ -58,9 +55,6 @@ def trace_read(ctx: struct_pt_regs) -> c_int32:
5855
return c_int32(0)
5956

6057

61-
#
62-
# WRITE PROBE
63-
#
6458
@bpf
6559
@section("kprobe/vfs_write")
6660
def trace_write(ctx1: struct_pt_regs) -> c_int32:
@@ -74,7 +68,6 @@ def trace_write(ctx1: struct_pt_regs) -> c_int32:
7468
s.ops = ptr.ops + 1
7569
write_map.update(cg, s)
7670
else:
77-
print("write init")
7871
s = write_stats()
7972
s.bytes = count
8073
s.ops = c_uint64(1)
@@ -89,4 +82,110 @@ def LICENSE() -> str:
8982
return "GPL"
9083

9184

92-
compile(loglevel=logging.INFO)
85+
# Load and attach BPF program
86+
b = BPF()
87+
b.load()
88+
b.attach_all()
89+
90+
# Get map references and enable struct deserialization
91+
read_map_ref = b["read_map"]
92+
write_map_ref = b["write_map"]
93+
read_map_ref.set_value_struct("read_stats")
94+
write_map_ref.set_value_struct("write_stats")
95+
96+
97+
def get_cgroup_ids():
98+
"""Get all cgroup IDs from the system"""
99+
cgroup_ids = set()
100+
101+
# Get cgroup IDs from running processes
102+
for proc_dir in Path("/proc").glob("[0-9]*"):
103+
try:
104+
cgroup_file = proc_dir / "cgroup"
105+
if cgroup_file.exists():
106+
with open(cgroup_file) as f:
107+
for line in f:
108+
# Parse cgroup path and get inode
109+
parts = line.strip().split(":")
110+
if len(parts) >= 3:
111+
cgroup_path = parts[2]
112+
# Try to get the cgroup inode which is used as ID
113+
cgroup_mount = f"/sys/fs/cgroup{cgroup_path}"
114+
if os.path.exists(cgroup_mount):
115+
stat_info = os.stat(cgroup_mount)
116+
cgroup_ids.add(stat_info.st_ino)
117+
except (PermissionError, FileNotFoundError, OSError):
118+
continue
119+
120+
return cgroup_ids
121+
122+
123+
# Display function
124+
def display_stats():
125+
"""Read and display file I/O statistics from BPF maps"""
126+
print("\n" + "=" * 80)
127+
print(f"{'CGROUP ID':<20} {'READ OPS':<15} {'READ BYTES':<20} {'WRITE OPS':<15} {'WRITE BYTES':<20}")
128+
print("=" * 80)
129+
130+
# Get cgroup IDs from the system
131+
cgroup_ids = get_cgroup_ids()
132+
133+
if not cgroup_ids:
134+
print("No cgroups found...")
135+
print("=" * 80)
136+
return
137+
138+
# Initialize totals
139+
total_read_ops = 0
140+
total_read_bytes = 0
141+
total_write_ops = 0
142+
total_write_bytes = 0
143+
144+
# Track which cgroups have data
145+
cgroups_with_data = []
146+
147+
# Display stats for each cgroup
148+
for cgroup_id in sorted(cgroup_ids):
149+
# Get read stats using lookup
150+
read_ops = 0
151+
read_bytes = 0
152+
read_stat = read_map_ref.lookup(cgroup_id)
153+
if read_stat:
154+
read_ops = int(read_stat.ops)
155+
read_bytes = int(read_stat.bytes)
156+
total_read_ops += read_ops
157+
total_read_bytes += read_bytes
158+
159+
# Get write stats using lookup
160+
write_ops = 0
161+
write_bytes = 0
162+
write_stat = write_map_ref.lookup(cgroup_id)
163+
if write_stat:
164+
write_ops = int(write_stat.ops)
165+
write_bytes = int(write_stat.bytes)
166+
total_write_ops += write_ops
167+
total_write_bytes += write_bytes
168+
169+
# Only print if there's data for this cgroup
170+
if read_stat or write_stat:
171+
print(f"{cgroup_id:<20} {read_ops:<15} {read_bytes:<20} {write_ops:<15} {write_bytes:<20}")
172+
cgroups_with_data.append(cgroup_id)
173+
174+
if not cgroups_with_data:
175+
print("No data collected yet...")
176+
177+
print("=" * 80)
178+
print(f"{'TOTAL':<20} {total_read_ops:<15} {total_read_bytes:<20} {total_write_ops:<15} {total_write_bytes:<20}")
179+
print()
180+
181+
182+
# Main loop
183+
if __name__ == "__main__":
184+
print("Tracing file I/O operations... Press Ctrl+C to exit\n")
185+
186+
try:
187+
while True:
188+
time.sleep(5) # Update every 5 seconds
189+
display_stats()
190+
except KeyboardInterrupt:
191+
print("\nStopped")
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import logging
2+
import time
3+
import os
4+
from pathlib import Path
5+
from pythonbpf import bpf, map, section, bpfglobal, struct, compile, BPF
6+
from pythonbpf.maps import HashMap
7+
from pythonbpf.helper import get_current_cgroup_id
8+
from ctypes import c_int32, c_uint64, c_void_p, c_uint32
9+
from vmlinux import struct_sk_buff, struct_pt_regs
10+
11+
12+
@bpf
13+
@struct
14+
class net_stats:
15+
rx_packets: c_uint64
16+
tx_packets: c_uint64
17+
rx_bytes: c_uint64
18+
tx_bytes: c_uint64
19+
20+
21+
@bpf
22+
@map
23+
def net_stats_map() -> HashMap:
24+
return HashMap(key=c_uint64, value=net_stats, max_entries=1024)
25+
26+
27+
@bpf
28+
@section("kprobe/__netif_receive_skb")
29+
def trace_netif_rx(ctx: struct_pt_regs) -> c_int32:
30+
cgroup_id = get_current_cgroup_id()
31+
32+
# Read skb pointer from first argument (PT_REGS_PARM1)
33+
skb = struct_sk_buff(ctx.di) # x86_64: first arg in rdi
34+
35+
# Read skb->len
36+
pkt_len = c_uint64(skb.len)
37+
38+
39+
stats_ptr = net_stats_map.lookup(cgroup_id)
40+
41+
if stats_ptr:
42+
stats = net_stats()
43+
stats.rx_packets = stats_ptr.rx_packets + 1
44+
stats.tx_packets = stats_ptr.tx_packets
45+
stats.rx_bytes = stats_ptr.rx_bytes + pkt_len
46+
stats.tx_bytes = stats_ptr.tx_bytes
47+
net_stats_map.update(cgroup_id, stats)
48+
else:
49+
stats = net_stats()
50+
stats.rx_packets = c_uint64(1)
51+
stats.tx_packets = c_uint64(0)
52+
stats.rx_bytes = pkt_len
53+
stats.tx_bytes = c_uint64(0)
54+
net_stats_map.update(cgroup_id, stats)
55+
56+
return c_int32(0)
57+
58+
@bpf
59+
@section("kprobe/__dev_queue_xmit")
60+
def trace_dev_xmit(ctx1: struct_pt_regs) -> c_int32:
61+
cgroup_id = get_current_cgroup_id()
62+
63+
# Read skb pointer from first argument
64+
skb = struct_sk_buff(ctx1.di)
65+
pkt_len = c_uint64(skb.len)
66+
67+
stats_ptr = net_stats_map.lookup(cgroup_id)
68+
69+
if stats_ptr:
70+
stats = net_stats()
71+
stats.rx_packets = stats_ptr.rx_packets
72+
stats.tx_packets = stats_ptr.tx_packets + 1
73+
stats.rx_bytes = stats_ptr.rx_bytes
74+
stats.tx_bytes = stats_ptr.tx_bytes + pkt_len
75+
net_stats_map.update(cgroup_id, stats)
76+
else:
77+
stats = net_stats()
78+
stats.rx_packets = c_uint64(0)
79+
stats.tx_packets = c_uint64(1)
80+
stats.rx_bytes = c_uint64(0)
81+
stats.tx_bytes = pkt_len
82+
net_stats_map.update(cgroup_id, stats)
83+
84+
return c_int32(0)
85+
86+
87+
@bpf
88+
@bpfglobal
89+
def LICENSE() -> str:
90+
return "GPL"
91+
92+
# Load and attach BPF program
93+
b = BPF()
94+
b.load()
95+
b.attach_all()
96+
97+
# Get map reference and enable struct deserialization
98+
net_stats_map_ref = b["net_stats_map"]
99+
net_stats_map_ref.set_value_struct("net_stats")
100+
101+
102+
def get_cgroup_ids():
103+
"""Get all cgroup IDs from the system"""
104+
cgroup_ids = set()
105+
106+
# Get cgroup IDs from running processes
107+
for proc_dir in Path("/proc").glob("[0-9]*"):
108+
try:
109+
cgroup_file = proc_dir / "cgroup"
110+
if cgroup_file.exists():
111+
with open(cgroup_file) as f:
112+
for line in f:
113+
# Parse cgroup path and get inode
114+
parts = line.strip().split(":")
115+
if len(parts) >= 3:
116+
cgroup_path = parts[2]
117+
# Try to get the cgroup inode which is used as ID
118+
cgroup_mount = f"/sys/fs/cgroup{cgroup_path}"
119+
if os.path.exists(cgroup_mount):
120+
stat_info = os.stat(cgroup_mount)
121+
cgroup_ids.add(stat_info.st_ino)
122+
except (PermissionError, FileNotFoundError, OSError):
123+
continue
124+
125+
return cgroup_ids
126+
127+
128+
# Display function
129+
def display_stats():
130+
"""Read and display network I/O statistics from BPF maps"""
131+
print("\n" + "=" * 100)
132+
print(f"{'CGROUP ID':<20} {'RX PACKETS':<15} {'RX BYTES':<20} {'TX PACKETS':<15} {'TX BYTES':<20}")
133+
print("=" * 100)
134+
135+
# Get cgroup IDs from the system
136+
cgroup_ids = get_cgroup_ids()
137+
138+
if not cgroup_ids:
139+
print("No cgroups found...")
140+
print("=" * 100)
141+
return
142+
143+
# Initialize totals
144+
total_rx_packets = 0
145+
total_rx_bytes = 0
146+
total_tx_packets = 0
147+
total_tx_bytes = 0
148+
149+
# Track which cgroups have data
150+
cgroups_with_data = []
151+
152+
# Display stats for each cgroup
153+
for cgroup_id in sorted(cgroup_ids):
154+
# Get network stats using lookup
155+
rx_packets = 0
156+
rx_bytes = 0
157+
tx_packets = 0
158+
tx_bytes = 0
159+
160+
net_stat = net_stats_map_ref.lookup(cgroup_id)
161+
if net_stat:
162+
rx_packets = int(net_stat.rx_packets)
163+
rx_bytes = int(net_stat.rx_bytes)
164+
tx_packets = int(net_stat.tx_packets)
165+
tx_bytes = int(net_stat.tx_bytes)
166+
167+
total_rx_packets += rx_packets
168+
total_rx_bytes += rx_bytes
169+
total_tx_packets += tx_packets
170+
total_tx_bytes += tx_bytes
171+
172+
print(f"{cgroup_id:<20} {rx_packets:<15} {rx_bytes:<20} {tx_packets:<15} {tx_bytes:<20}")
173+
cgroups_with_data.append(cgroup_id)
174+
175+
if not cgroups_with_data:
176+
print("No data collected yet...")
177+
178+
print("=" * 100)
179+
print(f"{'TOTAL':<20} {total_rx_packets:<15} {total_rx_bytes:<20} {total_tx_packets:<15} {total_tx_bytes:<20}")
180+
print()
181+
182+
183+
# Main loop
184+
if __name__ == "__main__":
185+
print("Tracing network I/O operations... Press Ctrl+C to exit\n")
186+
187+
try:
188+
while True:
189+
time.sleep(5) # Update every 5 seconds
190+
display_stats()
191+
except KeyboardInterrupt:
192+
print("\nStopped")

0 commit comments

Comments
 (0)