Files
python-bpf/examples/IO-run.ipynb
2025-09-29 23:44:49 +05:30

431 lines
47 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "84c8e052-bd25-4ca8-9687-28dda98c271d",
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"from pythonbpf import BPF, bpf, section, map, section, bpfglobal\n",
"from pythonbpf.maps import HashMap\n",
"from pylibbpf import BpfMap\n",
"from ctypes import c_void_p, c_int64, c_uint64, c_int32\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "9fffe5cd-54b9-45ae-9b12-abd021deaf81",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Module(\n",
" body=[\n",
" FunctionDef(\n",
" name='opencounts',\n",
" args=arguments(\n",
" posonlyargs=[],\n",
" args=[],\n",
" kwonlyargs=[],\n",
" kw_defaults=[],\n",
" defaults=[]),\n",
" body=[\n",
" Return(\n",
" value=Call(\n",
" func=Name(id='HashMap', ctx=Load()),\n",
" args=[],\n",
" keywords=[\n",
" keyword(\n",
" arg='key',\n",
" value=Name(id='c_int32', ctx=Load())),\n",
" keyword(\n",
" arg='value',\n",
" value=Name(id='c_uint64', ctx=Load())),\n",
" keyword(\n",
" arg='max_entries',\n",
" value=Constant(value=4096))]))],\n",
" decorator_list=[\n",
" Name(id='bpf', ctx=Load()),\n",
" Name(id='map', ctx=Load())],\n",
" returns=Name(id='HashMap', ctx=Load()),\n",
" type_params=[]),\n",
" FunctionDef(\n",
" name='trace_open',\n",
" args=arguments(\n",
" posonlyargs=[],\n",
" args=[\n",
" arg(\n",
" arg='ctx',\n",
" annotation=Name(id='c_void_p', ctx=Load()))],\n",
" kwonlyargs=[],\n",
" kw_defaults=[],\n",
" defaults=[]),\n",
" body=[\n",
" Assign(\n",
" targets=[\n",
" Name(id='pidval', ctx=Store())],\n",
" value=Call(\n",
" func=Name(id='pid', ctx=Load()),\n",
" args=[],\n",
" keywords=[])),\n",
" Assign(\n",
" targets=[\n",
" Name(id='one', ctx=Store())],\n",
" value=Constant(value=1)),\n",
" Assign(\n",
" targets=[\n",
" Name(id='prev', ctx=Store())],\n",
" value=Call(\n",
" func=Attribute(\n",
" value=Call(\n",
" func=Name(id='opencounts', ctx=Load()),\n",
" args=[],\n",
" keywords=[]),\n",
" attr='lookup',\n",
" ctx=Load()),\n",
" args=[\n",
" Name(id='pidval', ctx=Load())],\n",
" keywords=[])),\n",
" If(\n",
" test=Name(id='prev', ctx=Load()),\n",
" body=[\n",
" Assign(\n",
" targets=[\n",
" Name(id='prev2', ctx=Store())],\n",
" value=BinOp(\n",
" left=Name(id='prev', ctx=Load()),\n",
" op=Add(),\n",
" right=Constant(value=1))),\n",
" Expr(\n",
" value=Call(\n",
" func=Attribute(\n",
" value=Call(\n",
" func=Name(id='opencounts', ctx=Load()),\n",
" args=[],\n",
" keywords=[]),\n",
" attr='update',\n",
" ctx=Load()),\n",
" args=[\n",
" Name(id='pidval', ctx=Load()),\n",
" Name(id='prev2', ctx=Load())],\n",
" keywords=[]))],\n",
" orelse=[\n",
" Expr(\n",
" value=Call(\n",
" func=Attribute(\n",
" value=Call(\n",
" func=Name(id='opencounts', ctx=Load()),\n",
" args=[],\n",
" keywords=[]),\n",
" attr='update',\n",
" ctx=Load()),\n",
" args=[\n",
" Name(id='pidval', ctx=Load()),\n",
" Name(id='one', ctx=Load())],\n",
" keywords=[]))]),\n",
" Return(\n",
" value=Call(\n",
" func=Name(id='c_int64', ctx=Load()),\n",
" args=[\n",
" Constant(value=0)],\n",
" keywords=[]))],\n",
" decorator_list=[\n",
" Name(id='bpf', ctx=Load()),\n",
" Call(\n",
" func=Name(id='section', ctx=Load()),\n",
" args=[\n",
" Constant(value='tracepoint/syscalls/sys_enter_openat')],\n",
" keywords=[])],\n",
" returns=Name(id='c_int64', ctx=Load()),\n",
" type_params=[]),\n",
" FunctionDef(\n",
" name='LICENSE',\n",
" args=arguments(\n",
" posonlyargs=[],\n",
" args=[],\n",
" kwonlyargs=[],\n",
" kw_defaults=[],\n",
" defaults=[]),\n",
" body=[\n",
" Return(\n",
" value=Constant(value='GPL'))],\n",
" decorator_list=[\n",
" Name(id='bpf', ctx=Load()),\n",
" Name(id='bpfglobal', ctx=Load())],\n",
" returns=Name(id='str', ctx=Load()),\n",
" type_params=[]),\n",
" Assign(\n",
" targets=[\n",
" Name(id='b', ctx=Store())],\n",
" value=Call(\n",
" func=Name(id='BPF', ctx=Load()),\n",
" args=[],\n",
" keywords=[]))],\n",
" type_ignores=[])\n",
"Found BPF function/struct: opencounts\n",
"Found BPF function/struct: trace_open\n",
"Found BPF function/struct: LICENSE\n",
"Found BPF map: opencounts\n",
"Processing BPF map: opencounts\n",
"Creating HashMap map: opencounts\n",
"Map parameters: {'type': 'HASH', 'key': 'c_int32', 'value': 'c_uint64', 'max_entries': 4096}\n",
"Created BPF map: opencounts\n",
"Found probe_string of trace_open: tracepoint/syscalls/sys_enter_openat\n",
"Pre-allocated variable pidval for helper\n",
"Pre-allocated variable one of type c_int64\n",
"Pre-allocated variable prev for map\n",
"Pre-allocated variable prev2 of type c_int64\n",
"Local symbol table: dict_keys(['pidval', 'one', 'prev', 'prev2'])\n",
"Processing statement: Assign(targets=[Name(id='pidval', ctx=Store())], value=Call(func=Name(id='pid', ctx=Load()), args=[], keywords=[]))\n",
"Handling assignment to Name(id='pidval', ctx=Store())\n",
"Assignment call type: pid\n",
"{}\n",
"Assigned constant pid to pidval\n",
"Processing statement: Assign(targets=[Name(id='one', ctx=Store())], value=Constant(value=1))\n",
"Handling assignment to Name(id='one', ctx=Store())\n",
"Assigned constant 1 to one\n",
"Processing statement: Assign(targets=[Name(id='prev', ctx=Store())], value=Call(func=Attribute(value=Call(func=Name(id='opencounts', ctx=Load()), args=[], keywords=[]), attr='lookup', ctx=Load()), args=[Name(id='pidval', ctx=Load())], keywords=[]))\n",
"Handling assignment to Name(id='prev', ctx=Store())\n",
"Assignment call attribute: Attribute(value=Call(func=Name(id='opencounts', ctx=Load()), args=[], keywords=[]), attr='lookup', ctx=Load())\n",
"{}\n",
"{}\n",
"Processing statement: If(test=Name(id='prev', ctx=Load()), body=[Assign(targets=[Name(id='prev2', ctx=Store())], value=BinOp(left=Name(id='prev', ctx=Load()), op=Add(), right=Constant(value=1))), Expr(value=Call(func=Attribute(value=Call(func=Name(id='opencounts', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='pidval', ctx=Load()), Name(id='prev2', ctx=Load())], keywords=[]))], orelse=[Expr(value=Call(func=Attribute(value=Call(func=Name(id='opencounts', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='pidval', ctx=Load()), Name(id='one', ctx=Load())], keywords=[]))])\n",
"Handling if statement\n",
"Processing statement: Assign(targets=[Name(id='prev2', ctx=Store())], value=BinOp(left=Name(id='prev', ctx=Load()), op=Add(), right=Constant(value=1)))\n",
"Handling assignment to Name(id='prev2', ctx=Store())\n",
"; ModuleID = \"/tmp/tmp4t_neck5.py\"\n",
"target triple = \"bpf\"\n",
"target datalayout = \"e-m:e-p:64:64-i64:64-i128:128-n32:64-S128\"\n",
"\n",
"@\"opencounts\" = dso_local global {ptr, ptr, ptr, ptr} zeroinitializer, section \".maps\", align 8, !dbg !22\n",
"define dso_local i64 @\"trace_open\"(ptr nocapture %\".1\") noinline nounwind optnone section \"tracepoint/syscalls/sys_enter_openat\"\n",
"{\n",
"entry:\n",
" %\"pidval\" = alloca i64, align 8\n",
" %\"one\" = alloca i64, align 8\n",
" %\"prev\" = alloca i64*\n",
" %\"prev2\" = alloca i64, align 8\n",
" %\".3\" = inttoptr i64 14 to i64 ()*\n",
" %\".4\" = call i64 %\".3\"()\n",
" %\".5\" = and i64 %\".4\", 4294967295\n",
" store i64 %\".5\", i64* %\"pidval\"\n",
" store i64 1, i64* %\"one\"\n",
" %\".8\" = inttoptr i64 1 to ptr (ptr, ptr)*\n",
" %\".9\" = call ptr %\".8\"({ptr, ptr, ptr, ptr}* @\"opencounts\", i64* %\"pidval\")\n",
" store ptr %\".9\", i64** %\"prev\"\n",
" %\".11\" = load i64*, i64** %\"prev\"\n",
" %\".12\" = icmp ne i64* %\".11\", null\n",
" br i1 %\".12\", label %\"if.then\", label %\"if.else\"\n",
"if.then:\n",
"if.end:\n",
"if.else:\n",
"}\n",
"\n",
"!llvm.dbg.cu = !{ !1 }\n",
"!0 = !DIFile(directory: \"/tmp\", filename: \"/tmp/tmp4t_neck5.py\")\n",
"!1 = distinct !DICompileUnit(emissionKind: 1, file: !0, isOptimized: true, language: 29, nameTableKind: 0, producer: \"PythonBPF DSL Compiler\", runtimeVersion: 0, splitDebugInlining: false)\n",
"!2 = !DIBasicType(encoding: 7, name: \"unsigned int\", size: 32)\n",
"!3 = !DIBasicType(encoding: 7, name: \"unsigned long long\", size: 64)\n",
"!4 = !DISubrange(count: 1)\n",
"!5 = !{ !4 }\n",
"!6 = !DICompositeType(baseType: !2, elements: !5, size: 32, tag: 1)\n",
"!7 = !DIDerivedType(baseType: !6, size: 64, tag: 15)\n",
"!8 = !DIDerivedType(baseType: !2, size: 64, tag: 15)\n",
"!9 = !DIDerivedType(baseType: !3, size: 64, tag: 15)\n",
"!10 = !DIDerivedType(baseType: !7, file: !0, name: \"type\", offset: 0, size: 64, tag: 13)\n",
"!11 = !DIDerivedType(baseType: !8, file: !0, name: \"key\", offset: 64, size: 64, tag: 13)\n",
"!12 = !DIDerivedType(baseType: !9, file: !0, name: \"value\", offset: 128, size: 64, tag: 13)\n",
"!13 = !DISubrange(count: 4096)\n",
"!14 = !{ !13 }\n",
"!15 = !DICompositeType(baseType: !2, elements: !14, size: 32, tag: 1)\n",
"!16 = !DIDerivedType(baseType: !15, size: 64, tag: 15)\n",
"!17 = !DIDerivedType(baseType: !16, file: !0, name: \"max_entries\", offset: 192, size: 64, tag: 13)\n",
"!18 = !{ !10, !11, !12, !17 }\n",
"!19 = distinct !DICompositeType(elements: !18, file: !0, size: 256, tag: 19)\n",
"!20 = distinct !DIGlobalVariable(file: !0, isDefinition: true, isLocal: false, name: \"opencounts\", scope: !1, type: !19)\n",
"!21 = !DIExpression()\n",
"!22 = !DIGlobalVariableExpression(expr: !21, var: !20)\n",
"left is %\".15\" = load i64, i64* %\".14\", right is i64 1, op is <ast.Add object at 0x7e74d78c1490>\n",
"Processing statement: Expr(value=Call(func=Attribute(value=Call(func=Name(id='opencounts', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='pidval', ctx=Load()), Name(id='prev2', ctx=Load())], keywords=[]))\n",
"{}\n",
"Handling expression: Expr(value=Call(func=Attribute(value=Call(func=Name(id='opencounts', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='pidval', ctx=Load()), Name(id='prev2', ctx=Load())], keywords=[]))\n",
"{}\n",
"Evaluating expression: Call(func=Attribute(value=Call(func=Name(id='opencounts', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='pidval', ctx=Load()), Name(id='prev2', ctx=Load())], keywords=[])\n",
"{}\n",
"Handling method call: Attribute(value=Call(func=Name(id='opencounts', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load())\n",
"{}\n",
"{}\n",
"Processing statement: Expr(value=Call(func=Attribute(value=Call(func=Name(id='opencounts', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='pidval', ctx=Load()), Name(id='one', ctx=Load())], keywords=[]))\n",
"{}\n",
"Handling expression: Expr(value=Call(func=Attribute(value=Call(func=Name(id='opencounts', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='pidval', ctx=Load()), Name(id='one', ctx=Load())], keywords=[]))\n",
"{}\n",
"Evaluating expression: Call(func=Attribute(value=Call(func=Name(id='opencounts', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='pidval', ctx=Load()), Name(id='one', ctx=Load())], keywords=[])\n",
"{}\n",
"Handling method call: Attribute(value=Call(func=Name(id='opencounts', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load())\n",
"{}\n",
"{}\n",
"Processing statement: Return(value=Call(func=Name(id='c_int64', ctx=Load()), args=[Constant(value=0)], keywords=[]))\n",
"IR written to /tmp/tmp0xl39_a9.ll\n"
]
}
],
"source": [
"# BPF map: PID -> count of openat calls\n",
"@bpf\n",
"@map\n",
"def opencounts() -> HashMap:\n",
" return HashMap(key=c_int32, value=c_uint64, max_entries=4096)\n",
"\n",
"@bpf\n",
"@section(\"tracepoint/syscalls/sys_enter_openat\")\n",
"def trace_open(ctx: c_void_p) -> c_int64:\n",
" pidval = pid()\n",
" one = 1\n",
" prev = opencounts().lookup(pidval)\n",
" if prev:\n",
" prev2 = prev + 1\n",
" opencounts().update(pidval, prev2)\n",
" else:\n",
" opencounts().update(pidval, one)\n",
" return c_int64(0)\n",
"\n",
"@bpf\n",
"@bpfglobal\n",
"def LICENSE() -> str:\n",
" return \"GPL\"\n",
"\n",
"# Load\n",
"b = BPF()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "56318641-5547-41c7-8d97-271c89a72c8d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tracking openat() calls for 15s...\n",
"\n",
"Top 10 PIDs by openat() calls:\n",
"PID 45983 -> 28648 calls\n",
"PID 45985 -> 27865 calls\n",
"PID 45984 -> 27849 calls\n",
"PID 45992 -> 27468 calls\n",
"PID 45979 -> 27428 calls\n",
"PID 45977 -> 27227 calls\n",
"PID 45993 -> 26994 calls\n",
"PID 45989 -> 26953 calls\n",
"PID 45981 -> 26942 calls\n",
"PID 45988 -> 26743 calls\n",
"\n",
"Anomalous PIDs (suspiciously high openat calls):\n",
"PID 45983 -> 28648 calls (outlier)\n",
"PID 45985 -> 27865 calls (outlier)\n",
"PID 45984 -> 27849 calls (outlier)\n",
"PID 45992 -> 27468 calls (outlier)\n",
"PID 45979 -> 27428 calls (outlier)\n",
"PID 45977 -> 27227 calls (outlier)\n",
"PID 45993 -> 26994 calls (outlier)\n",
"PID 45989 -> 26953 calls (outlier)\n",
"PID 45981 -> 26942 calls (outlier)\n",
"PID 45988 -> 26743 calls (outlier)\n",
"PID 45987 -> 26721 calls (outlier)\n",
"PID 45986 -> 26672 calls (outlier)\n",
"PID 45975 -> 26373 calls (outlier)\n",
"PID 45991 -> 26305 calls (outlier)\n",
"PID 45990 -> 26176 calls (outlier)\n",
"PID 45974 -> 26086 calls (outlier)\n",
"PID 45980 -> 25662 calls (outlier)\n",
"PID 45978 -> 25655 calls (outlier)\n",
"PID 45976 -> 25627 calls (outlier)\n",
"PID 45982 -> 24832 calls (outlier)\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAk0AAAHHCAYAAACiOWx7AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAUKZJREFUeJzt3XlYVPX////HALKpoKSApOGe4p6W8s7cE83MrXKpNLX6ZFAppUaZW/U2rVwql7LSLDS1xdzeKuGWipkoueWaawlaKrgFAq/fH32ZnyOoZwxk1Pvtus51Oec853WeM4z48JzXOWMzxhgBAADgitwKuwEAAIAbAaEJAADAAkITAACABYQmAAAACwhNAAAAFhCaAAAALCA0AQAAWEBoAgAAsIDQBAAAYAGhCcAty2azKSoqqsD3s2fPHrVu3Vr+/v6y2WyaN2+epk+fLpvNpgMHDtjrmjVrpmbNmhV4Pze6AwcOyGazafr06U4/98knn1T58uXzvSfcGghNwCVsNpulZeXKlYXdKm4QvXr10tatW/XWW2/piy++UIMGDa7Lfp988kkVK1Ysz20LFiyQm5ubkpOT7ev++usvDRw4UHfeeae8vb0VEBCgiIgILVy48Lr0e6mZM2dq/PjxBbqPc+fOafjw4fx9hiUehd0A4Gq++OILh8czZsxQXFxcrvXVq1e/nm3hBnX+/HklJCTotddecziq9cQTT6hbt27y8vIqlL4WLVqk+vXrKzg4WJK0a9cutWzZUsePH1fv3r3VoEEDnTp1SrGxsWrfvr1efvllvfPOO9e1x5kzZ2rbtm3q37+/w/rQ0FCdP39eRYoUcXrMqVOnKjs72/743LlzGjFihCRxlA9XRWgCLvH44487PF6/fr3i4uJyrXd1586dk6+vb2G3ccs7fvy4JKlEiRIO693d3eXu7l4IHf1j8eLF6tOnjyTpwoULevjhh3Xy5EmtXr1aDRs2tNcNGDBAjz32mN599101aNBAXbt2LayW7Ww2m7y9va/pudcStIAcnJ4DrsHZs2f10ksvqVy5cvLy8tKdd96pd999V8YYh7qcOTOxsbH2Ux7169fX6tWrr7qPlStXymazafbs2Xr11VcVHBysokWL6qGHHtLhw4cdaps1a6aaNWsqMTFRTZo0ka+vr1599VVJ0rFjx9S3b18FBQXJ29tbderU0eeff55rf9nZ2ZowYYJq1aolb29vlS5dWm3atNHGjRsd6r788kvVr19fPj4+CggIULdu3XL1s2fPHnXp0kXBwcHy9vZW2bJl1a1bN6Wmptpr4uLi1LhxY5UoUULFihXTnXfeae85R3p6uoYNG6bKlSvLy8tL5cqV06BBg5Senu5QZ2WsK7nSz2fFihWy2Wz67rvvcj1v5syZstlsSkhIyHPc4cOHKzQ0VJI0cOBA2Ww2+3yavOY05cXqe+CMrVu36vDhw2rXrp0k6ZtvvtG2bdv0yiuvOAQm6Z9w99FHH6lEiRIaPnz4VceeNm2aWrRoocDAQHl5eSksLEyTJ0/Os/Z///ufmjZtquLFi8vPz0933323Zs6cKemfz/SiRYt08OBB+ynxnPfu0jlN7777rmw2mw4ePJhrHzExMfL09NTJkyclOc5pOnDggEqXLi1JGjFihH0/w4cP17Rp02Sz2bR58+ZcY/73v/+Vu7u7fv/996u+H7i5cKQJcJIxRg899JBWrFihvn37qm7dulq6dKkGDhyo33//XePGjXOoX7VqlWbPnq0XXnhBXl5emjRpktq0aaMNGzaoZs2aV93fW2+9JZvNpsGDB+vYsWMaP368WrVqpaSkJPn4+Njr/vrrL7Vt21bdunXT448/rqCgIJ0/f17NmjXT3r17FRUVpQoVKmju3Ll68sknderUKb344ov25/ft21fTp09X27Zt9dRTTykzM1M//vij1q9fb5+D89Zbb+n111/Xo48+qqeeekrHjx/XBx98oCZNmmjz5s0qUaKEMjIyFBERofT0dD3//PMKDg7W77//roULF+rUqVPy9/fX9u3b9eCDD6p27doaOXKkvLy8tHfvXq1du9beT3Z2th566CGtWbNGzzzzjKpXr66tW7dq3Lhx2r17t+bNmydJlsa6kqv9fJo1a6Zy5copNjZWnTp1cnhubGysKlWqpPDw8DzH7ty5s0qUKKEBAwaoe/fueuCBBy47xygvVt8DZy1evFiBgYH2n+uCBQskST179syz3t/fXx06dNDnn3+uvXv3qnLlypcde/LkyapRo4YeeugheXh4aMGCBXruueeUnZ2tyMhIe9306dPVp08f1ahRQzExMSpRooQ2b96sJUuWqEePHnrttdeUmpqqI0eO2P9OXe69e/TRRzVo0CDNmTNHAwcOdNg2Z84ctW7dWiVLlsz1vNKlS2vy5Mnq16+fOnXqpM6dO0uSateurQoVKigyMlKxsbGqV6+ew/NiY2PVrFkz3X777Zd9H3CTMgCuKDIy0lz8V2XevHlGknnzzTcd6h5++GFjs9nM3r177eskGUlm48aN9nUHDx403t7eplOnTlfc74oVK4wkc/vtt5u0tDT7+jlz5hhJZsKECfZ1TZs2NZLMlClTHMYYP368kWS+/PJL+7qMjAwTHh5uihUrZh93+fLlRpJ54YUXcvWRnZ1tjDHmwIEDxt3d3bz11lsO27du3Wo8PDzs6zdv3mwkmblz5172tY0bN85IMsePH79szRdffGHc3NzMjz/+6LB+ypQpRpJZu3at5bEux+rPJyYmxnh5eZlTp07Z1x07dsx4eHiYYcOGXXEf+/fvN5LMO++847B+2rRpRpLZv3+/fV3Tpk1N06ZN7Y+tvgeX06tXL1O0aNFc6++77z7Tq1cv++O6desaf3//K441duxYI8nMnz//inXnzp3LtS4iIsJUrFjR/vjUqVOmePHipmHDhub8+fMOtTmfN2OMadeunQkNDc01Xs57Om3aNPu68PBwU79+fYe6DRs2GElmxowZ9nW9evVyGPP48eNGUp4/x+7du5uQkBCTlZVlX7dp06Zc+8atg9NzgJMWL14sd3d3vfDCCw7rX3rpJRlj9L///c9hfXh4uOrXr29/fMcdd6hDhw5aunSpsrKyrrq/nj17qnjx4vbHDz/8sMqUKaPFixc71Hl5eal37965eg0ODlb37t3t64oUKaIXXnhBZ86c0apVqyT9c3rGZrNp2LBhufZvs9kkSd9++62ys7P16KOP6s8//7QvwcHBqlKlilasWCHpn6MSkrR06VKdO3cuz9eUM7/n+++/d5iUe7G5c+eqevXqqlatmsP+WrRoIUn2/VkZ60qs/Hx69uyp9PR0ff311/a62bNnKzMzs0Dnull9D5xx6tQpJSQk2E/NSdLp06cdPmN5ydmelpZ2xbqLj36mpqbqzz//VNOmTfXbb7/ZT8/GxcXp9OnTeuWVV3LNTcr5vDmra9euSkxM1L59++zrZs+eLS8vL3Xo0OGaxuzZs6f++OMPh/c5NjZWPj4+6tKlyzWNiRsboQlw0sGDBxUSEpLrH5mcq+kunVdRpUqVXGNUrVpV586ds08SvpJLn2+z2VS5cuVcc2Fuv/12eXp65uq1SpUqcnNz/Kt+aa/79u1TSEiIAgICLtvHnj17ZIxRlSpVVLp0aYfl119/1bFjxyRJFSpUUHR0tD755BOVKlVKERERmjhxosN8pq5du+ree+/VU089paCgIHXr1k1z5sxxCD179uzR9u3bc+2ratWqkmTfn5WxrsTKz6datWq6++67FRsba6+JjY1Vo0aNrniq6t+y+h44Y+nSpZKk1q1b29cVL15cp0+fvuLzcrZfLVytXbtWrVq1UtGiRVWiRAmVLl3aPr8s5zOQE2ysnJ626pFHHpGbm5tmz54t6Z/T6HPnzlXbtm3l5+d3TWPef//9KlOmjP3nnp2drVmzZqlDhw5XfR9wc2JOE3CTuPh/+AUhOztbNptN//vf//K86uvi+SbvvfeennzySX3//fdatmyZXnjhBY0aNUrr169X2bJl5ePjo9WrV2vFihVatGiRlixZotmzZ6tFixZatmyZ3N3dlZ2drVq1amns2LF59lOuXDlJsjRWfujZs6defPFFHTlyROnp6Vq/fr0+/PDDfBn7cqy+B85YvHix7r33XvsRQemfEJ2UlKRDhw7pjjvuyPN5W7ZskSSFhYVddux9+/apZcuWqlatmsaOHaty5crJ09NTixcv1rhx467pSKBVISEhuu+++zRnzhy9+uqrWr9+vQ4dOqTRo0df85ju7u7q0aOHpk6dqkmTJmnt2rX6448/brgraZF/CE2Ak0JDQ/XDDz/kOqWxc+dO+/aL7dmzJ9cYu3fvlq+vr/3KnSu59PnGGO3du1e1a9e21OuWLVuUnZ3tcLTp0l4rVaqkpUuX6sSJE5c92lSpUiUZY1ShQgX7kY4rqVWrlmrVqqUhQ4Zo3bp1uvfeezVlyhS9+eabkiQ3Nze1bNlSLVu21NixY/Xf//5Xr732mlasWKFWrVqpUqVK+uWXX9SyZcurnrK52lhXYvXn061bN0VHR2vWrFn2ewQV9OX3zrwHVhhjtGTJEr388ssO6x988EHNmjVLM2bM0JAhQ3I9Ly0tTd9//72qVat2xSNrCxYsUHp6uubPn+8Qvi49jVipUiVJ0rZt2644nrOvuWvXrnruuee0a9cuzZ49W76+vmrfvv0Vn3O1ffTs2VPvvfeeFixYoP/9738qXbq0IiIinOoLNw9OzwFOeuCBB5SVlZXrKMO4ceNks9nUtm1bh/UJCQnatGmT/fHhw4f1/fffq3Xr1paOgsyYMcPh1MnXX3+to0eP5trP5XpNTk62n7KQpMzMTH3wwQcqVqyYmjZtKknq0qWLjDH2m/xdzPy/2yh07txZ7u7uGjFiRK5bKxhj9Ndff0n65x/YzMxMh+21atWSm5ub/TL5EydO5NpP3bp1Jcle8+ijj+r333/X1KlTc9WeP39eZ8+etTzWlVj9+ZQqVUpt27bVl19+qdjYWLVp00alSpW66vj/htX3wKqff/5Zx44dc5jPJP0zTy4sLExvv/12rltMZGdnq1+/fjp58mSec94ulvN+Xfz5SE1N1bRp0xzqWrdureLFi2vUqFH6+++/HbZd/NyiRYs6nNa9mi5dusjd3V2zZs3S3Llz9eCDD6po0aJXfE7OvcxOnTqV5/batWurdu3a+uSTT/TNN9+oW7du8vDgeMOtip884KT27durefPmeu2113TgwAHVqVNHy5Yt0/fff6/+/fvb/xedo2bNmoqIiHC4pF1SngElLwEBAWrcuLF69+6tlJQUjR8/XpUrV9bTTz991ec+88wz+uijj/Tkk08qMTFR5cuX19dff621a9dq/Pjx9iNlzZs31xNPPKH3339fe/bsUZs2bZSdna0ff/xRzZs3V1RUlCpVqqQ333xTMTExOnDggDp27KjixYtr//79+u677/TMM8/o5Zdf1vLlyxUVFaVHHnlEVatWVWZmpr744gu5u7vbJ8+OHDlSq1evVrt27RQaGqpjx45p0qRJKlu2rBo3bizpnztmz5kzR88++6xWrFihe++9V1lZWdq5c6fmzJmjpUuXqkGDBpbGuhJnfj49e/bUww8/LEl64403LP38/g2r74FVixYtUvny5XOdYvP09NTXX3+tli1b2j9rOXcEnzlzpjZt2qSXXnpJ3bp1u+L4rVu3lqenp9q3b6//+7//05kzZzR16lQFBgbq6NGj9jo/Pz+NGzdOTz31lO6++2716NFDJUuW1C+//KJz587Z7yNWv359zZ49W9HR0br77rtVrFixKx45CgwMVPPmzTV27FidPn3a0pFAHx8fhYWFafbs2apataoCAgJUs2ZNh/lWPXv2tB+d49TcLa5wLtoDbhyX3nLAGGNOnz5tBgwYYEJCQkyRIkVMlSpVzDvvvONwubQx/1zSHhkZab788ktTpUoV4+XlZerVq2dWrFhx1f3m3HJg1qxZ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ueeeM97e3i7d98WeeeYZU6tWrctud8XPyY32++TcuXP2/3i5u7ubkJAQM2jQICMp18/Glfq+2NV6cKW+t2/fbsqUKWPGjBljfvnlF7NkyRJTq1Yt06dPn8v2UtAITS7su+++M5KMu7u7fZFkbDabcXd3N5mZmbmec+bMGSPJLFmyxGF9dna2+f333825c+fMjh07jCSzYcMGY4wxQ4YMyXX++fDhw0aSSUhIuGx/b7zxhqlateoN1/fLL79sGjVq5LJ9nzx5MlcfNpvNvi4+Pr5Q+s6R8z/H9PR0s3jxYiPJHDt2zKFm1qxZxsfHxyxcuDDXvgurb2OMadGihenYseNlezp//rw5cuSI/R/+sLCwG6LvM2fOGD8/PzN+/PjL1rji5+RiN9Lvk4yMDHP48GGTmZlpnxx+6cUlrti3MVcPQK7U9+OPP55rftyPP/542SN71wNf2OvCWrZsqa1btzqs6927t6pVq6bBgwfL3d0913OSkpIkSWXKlHFYb7PZFBISIkmaNWuWypUrp7vuukuSdO7cObm5OU5vyxk7Ozv7sv1lZ2crPT39hus7KSkp135cqW8/P79cfUyaNEnLly/X119/rQoVKhRK3xf3evvtt9trwsPDVbp0afv2WbNmqU+fPvrqq6+ueOn79e57//79WrFihebPn3/Znry9vXX77bfrwoUL+uabb/Too4/eEH3PnTtX6enpevzxxy9b42qfk0vdSL9PihQporJly0qSvvrqKz344IO5nuuKfVvhSn2fO3dOHh4eedaYwvra3EKJarhmF/8vYe/evWbkyJFm48aNZv/+/eb77783FStWNE2aNHF4zpgxY8yWLVvMtm3bzMiRI02RIkXMd999Z98eHx9vbDabGTFihNm9e7dJTEw0ERERJjQ01Jw7d84YY8yHH35o5s+fb3bv3m12795tPvnkE1O8eHHz2muvuXTf48aNM/PmzTN79uwxW7duNS+++KJxc3MzP/zwg0v3fSkrV88VdN/Hjx83kydPNr/++qvZvHmzeeGFF4y3t7fDHJrY2Fjj4eFhJk6c6HA58qlTpwqt7xxDhgwxISEhef5Pef369eabb74x+/btM6tXrzYtWrQwFSpUsHzVZWH1naNx48ama9eulnot6L6tfE5u1N8nu3btMl988YXZvXu3+emnn0zXrl1NQECA2b9/v0v3bYwxmzdvNps3bzb169c3PXr0MJs3bzbbt2936b6nTZtmPDw8zKRJk8y+ffvMmjVrTIMGDcw999xjqe+CQGi6wVz84T106JBp0qSJCQgIMF5eXqZy5cpm4MCBDvfLMMaY5s2bG39/f+Pt7W0aNmxoFi9enGvcWbNmmXr16pmiRYua0qVLm4ceesj8+uuv9u3vv/++qVGjhvH19TV+fn6mXr16ZtKkSVe931Fh9z169GhTqVIl4+3tbQICAkyzZs3M8uXLLfVcmH1f6t+Epvzq+/jx46ZRo0amaNGixtfX17Rs2TLXHJWmTZsaSbmWi+8tdL37NsaYrKwsU7ZsWfPqq6/mud+VK1ea6tWrGy8vL3PbbbeZJ554wvz++++Wei7Mvo3552IHSWbZsmWW+y3Ivq18Tm7U3yc7duwwdevWNT4+PsbPz8906NDB7Ny501LPhdm3MSbPv5ehoaEu3/f7779vwsLCjI+PjylTpox57LHHzJEjRyz1XRBsxhTWMS4AAIAbB/dpAgAAsIDQBAAAYAGhCQAAwAJCEwAAgAWEJgAAAAsITQAAABYQmgAAACwgNAEAAFhAaAJwS3vyySdls9lks9nk6empypUra+TIkcrMzNTKlStls9l06tQpSbI/ttlscnNzk7+/v+rVq6dBgwbp6NGjhftCABQ4vrAXwC2vTZs2mjZtmtLT07V48WJFRkaqSJEiCg8Pz7N+165d8vPzU1pamjZt2qQxY8bo008/1cqVK1WrVq3r3D2A64UjTQBueV5eXgoODlZoaKj69eunVq1aaf78+ZetDwwMVHBwsKpWrapu3bpp7dq1Kl26tPr163cduwZwvRGaAOASPj4+ysjIcKr+2Wef1dq1a3Xs2LEC7AxAYSI0AcD/Y4zRDz/8oKVLl6pFixZOPbdatWqSpAMHDhRAZwBcAXOaANzyFi5cqGLFiunChQvKzs5Wjx49NHz4cP3888+WxzDGSJJsNltBtQmgkBGaANzymjdvrsmTJ8vT01MhISHy8HD+V+Ovv/4qSSpfvnw+dwfAVRCaANzyihYtqsqVK1/z88+fP6+PP/5YTZo0UenSpfOxMwCuhNAEAE46duyY/v77b50+fVqJiYkaM2aM/vzzT3377beF3RqAAkRoAgAn3XnnnbLZbCpWrJgqVqyo1q1bKzo6WsHBwYXdGoACZDM5sxcBAABwWdxyAAAAwAJCEwAAgAWEJgAAAAsITQAAABYQmgAAACwgNAEAAFhAaAIAALCA0AQAAGABoQkAAMACQhMAAIAFhCYAAAALCE0AAAAW/H+xoim9Q3hJUgAAAABJRU5ErkJggg==",
"text/plain": [
"<Figure size 640x480 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Expose Python-side map\n",
"b.load_and_attach()\n",
"open_map = BpfMap(b, opencounts)\n",
"\n",
"print(\"Tracking openat() calls for 15s...\")\n",
"time.sleep(15)\n",
"\n",
"# Fetch results\n",
"counts = [(pid, open_map[pid]) for pid in open_map.keys()]\n",
"counts.sort(key=lambda x: x[1], reverse=True)\n",
"\n",
"# Top 10 processes\n",
"top = counts[:10]\n",
"print(\"\\nTop 10 PIDs by openat() calls:\")\n",
"for pid, cnt in top:\n",
" print(f\"PID {pid:<6} -> {cnt} calls\")\n",
"\n",
"# Detect anomaly: outliers beyond mean+3σ\n",
"vals = [cnt for _, cnt in counts]\n",
"if vals:\n",
" mean = sum(vals) / len(vals)\n",
" var = sum((x - mean) ** 2 for x in vals) / len(vals)\n",
" std = var ** 0.5\n",
" threshold = mean + 3 * std\n",
" anomalies = [(pid, cnt) for pid, cnt in counts if cnt > threshold]\n",
"\n",
" if anomalies:\n",
" print(\"\\nAnomalous PIDs (suspiciously high openat calls):\")\n",
" for pid, cnt in anomalies:\n",
" print(f\"PID {pid:<6} -> {cnt} calls (outlier)\")\n",
"\n",
"# Visualization\n",
"pids = [pid for pid, _ in top]\n",
"vals = [cnt for _, cnt in top]\n",
"\n",
"plt.bar(range(len(top)), vals, tick_label=pids)\n",
"plt.xlabel(\"PID\")\n",
"plt.ylabel(\"openat() calls in 15s\")\n",
"plt.title(\"Top processes by file I/O activity\")\n",
"plt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}