From 26f8f769c5bd2befaee7fba19ab98d834babfd34 Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Mon, 29 Sep 2025 23:44:49 +0530 Subject: [PATCH] remove demos and add examples Signed-off-by: varun-r-mallya --- examples/IO-run.ipynb | 430 ++++++++++++++++++++++++++++++++ examples/clone-matplotlib.ipynb | 420 +++++++++++++++++++++++++++++++ examples/pybpf0.py | 35 +++ examples/pybpf1.py | 41 +++ examples/pybpf2.py | 43 ++++ examples/pybpf3.py | 49 ++++ examples/pybpf4.py | 62 +++++ 7 files changed, 1080 insertions(+) create mode 100644 examples/IO-run.ipynb create mode 100644 examples/clone-matplotlib.ipynb create mode 100644 examples/pybpf0.py create mode 100644 examples/pybpf1.py create mode 100644 examples/pybpf2.py create mode 100644 examples/pybpf3.py create mode 100644 examples/pybpf4.py diff --git a/examples/IO-run.ipynb b/examples/IO-run.ipynb new file mode 100644 index 0000000..d6e8238 --- /dev/null +++ b/examples/IO-run.ipynb @@ -0,0 +1,430 @@ +{ + "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 \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": "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w4f1/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": [ + "
" + ] + }, + "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 +} diff --git a/examples/clone-matplotlib.ipynb b/examples/clone-matplotlib.ipynb new file mode 100644 index 0000000..c452065 --- /dev/null +++ b/examples/clone-matplotlib.ipynb @@ -0,0 +1,420 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "22dd4e7b-2ea2-49cb-a8d5-1da108c10034", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "\n", + "from pythonbpf import *\n", + "from pylibbpf import *\n", + "\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": "ac7a07bf-440f-41e2-bec8-95f520f9cd53", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Module(\n", + " body=[\n", + " FunctionDef(\n", + " name='hist',\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='hello',\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='process_id', 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='hist', ctx=Load()),\n", + " args=[],\n", + " keywords=[]),\n", + " attr='lookup',\n", + " ctx=Load()),\n", + " args=[\n", + " Name(id='process_id', ctx=Load())],\n", + " keywords=[])),\n", + " If(\n", + " test=Name(id='prev', ctx=Load()),\n", + " body=[\n", + " Assign(\n", + " targets=[\n", + " Name(id='previous_value', 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=Name(id='print', ctx=Load()),\n", + " args=[\n", + " JoinedStr(\n", + " values=[\n", + " Constant(value='count: '),\n", + " FormattedValue(\n", + " value=Name(id='previous_value', ctx=Load()),\n", + " conversion=-1),\n", + " Constant(value=' with '),\n", + " FormattedValue(\n", + " value=Name(id='process_id', ctx=Load()),\n", + " conversion=-1)])],\n", + " keywords=[])),\n", + " Expr(\n", + " value=Call(\n", + " func=Attribute(\n", + " value=Call(\n", + " func=Name(id='hist', ctx=Load()),\n", + " args=[],\n", + " keywords=[]),\n", + " attr='update',\n", + " ctx=Load()),\n", + " args=[\n", + " Name(id='process_id', ctx=Load()),\n", + " Name(id='previous_value', 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", + " orelse=[\n", + " Expr(\n", + " value=Call(\n", + " func=Attribute(\n", + " value=Call(\n", + " func=Name(id='hist', ctx=Load()),\n", + " args=[],\n", + " keywords=[]),\n", + " attr='update',\n", + " ctx=Load()),\n", + " args=[\n", + " Name(id='process_id', 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_clone')],\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: hist\n", + "Found BPF function/struct: hello\n", + "Found BPF function/struct: LICENSE\n", + "Found BPF map: hist\n", + "Processing BPF map: hist\n", + "Creating HashMap map: hist\n", + "Map parameters: {'type': 'HASH', 'key': 'c_int32', 'value': 'c_uint64', 'max_entries': 4096}\n", + "Created BPF map: hist\n", + "Found probe_string of hello: tracepoint/syscalls/sys_enter_clone\n", + "Pre-allocated variable process_id for helper\n", + "Pre-allocated variable one of type c_int64\n", + "Pre-allocated variable prev for map\n", + "Pre-allocated variable previous_value of type c_int64\n", + "Local symbol table: dict_keys(['process_id', 'one', 'prev', 'previous_value'])\n", + "Processing statement: Assign(targets=[Name(id='process_id', ctx=Store())], value=Call(func=Name(id='pid', ctx=Load()), args=[], keywords=[]))\n", + "Handling assignment to Name(id='process_id', ctx=Store())\n", + "Assignment call type: pid\n", + "{}\n", + "Assigned constant pid to process_id\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='hist', ctx=Load()), args=[], keywords=[]), attr='lookup', ctx=Load()), args=[Name(id='process_id', ctx=Load())], keywords=[]))\n", + "Handling assignment to Name(id='prev', ctx=Store())\n", + "Assignment call attribute: Attribute(value=Call(func=Name(id='hist', 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='previous_value', ctx=Store())], value=BinOp(left=Name(id='prev', ctx=Load()), op=Add(), right=Constant(value=1))), Expr(value=Call(func=Name(id='print', ctx=Load()), args=[JoinedStr(values=[Constant(value='count: '), FormattedValue(value=Name(id='previous_value', ctx=Load()), conversion=-1), Constant(value=' with '), FormattedValue(value=Name(id='process_id', ctx=Load()), conversion=-1)])], keywords=[])), Expr(value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='previous_value', ctx=Load())], keywords=[])), Return(value=Call(func=Name(id='c_int64', ctx=Load()), args=[Constant(value=0)], keywords=[]))], orelse=[Expr(value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='one', ctx=Load())], keywords=[]))])\n", + "Handling if statement\n", + "Processing statement: Assign(targets=[Name(id='previous_value', ctx=Store())], value=BinOp(left=Name(id='prev', ctx=Load()), op=Add(), right=Constant(value=1)))\n", + "Handling assignment to Name(id='previous_value', ctx=Store())\n", + "; ModuleID = \"/tmp/tmpf9jfb5h5.py\"\n", + "target triple = \"bpf\"\n", + "target datalayout = \"e-m:e-p:64:64-i64:64-i128:128-n32:64-S128\"\n", + "\n", + "@\"hist\" = dso_local global {ptr, ptr, ptr, ptr} zeroinitializer, section \".maps\", align 8, !dbg !22\n", + "define dso_local i64 @\"hello\"(ptr nocapture %\".1\") noinline nounwind optnone section \"tracepoint/syscalls/sys_enter_clone\"\n", + "{\n", + "entry:\n", + " %\"process_id\" = alloca i64, align 8\n", + " %\"one\" = alloca i64, align 8\n", + " %\"prev\" = alloca i64*\n", + " %\"previous_value\" = 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* %\"process_id\"\n", + " store i64 1, i64* %\"one\"\n", + " %\".8\" = inttoptr i64 1 to ptr (ptr, ptr)*\n", + " %\".9\" = call ptr %\".8\"({ptr, ptr, ptr, ptr}* @\"hist\", i64* %\"process_id\")\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/tmpf9jfb5h5.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: \"hist\", 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 \n", + "Processing statement: Expr(value=Call(func=Name(id='print', ctx=Load()), args=[JoinedStr(values=[Constant(value='count: '), FormattedValue(value=Name(id='previous_value', ctx=Load()), conversion=-1), Constant(value=' with '), FormattedValue(value=Name(id='process_id', ctx=Load()), conversion=-1)])], keywords=[]))\n", + "{}\n", + "Handling expression: Expr(value=Call(func=Name(id='print', ctx=Load()), args=[JoinedStr(values=[Constant(value='count: '), FormattedValue(value=Name(id='previous_value', ctx=Load()), conversion=-1), Constant(value=' with '), FormattedValue(value=Name(id='process_id', ctx=Load()), conversion=-1)])], keywords=[]))\n", + "{}\n", + "Evaluating expression: Call(func=Name(id='print', ctx=Load()), args=[JoinedStr(values=[Constant(value='count: '), FormattedValue(value=Name(id='previous_value', ctx=Load()), conversion=-1), Constant(value=' with '), FormattedValue(value=Name(id='process_id', ctx=Load()), conversion=-1)])], keywords=[])\n", + "{}\n", + "{}\n", + "Value in f-string: Constant(value='count: ')\n", + "Value in f-string: FormattedValue(value=Name(id='previous_value', ctx=Load()), conversion=-1)\n", + "Formatted value: FormattedValue(value=Name(id='previous_value', ctx=Load()), conversion=-1)\n", + "Value in f-string: Constant(value=' with ')\n", + "Value in f-string: FormattedValue(value=Name(id='process_id', ctx=Load()), conversion=-1)\n", + "Formatted value: FormattedValue(value=Name(id='process_id', ctx=Load()), conversion=-1)\n", + "Name(id='previous_value', ctx=Load())\n", + "Evaluating expression: Name(id='previous_value', ctx=Load())\n", + "{}\n", + "Name(id='process_id', ctx=Load())\n", + "Evaluating expression: Name(id='process_id', ctx=Load())\n", + "{}\n", + "Processing statement: Expr(value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='previous_value', ctx=Load())], keywords=[]))\n", + "{}\n", + "Handling expression: Expr(value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='previous_value', ctx=Load())], keywords=[]))\n", + "{}\n", + "Evaluating expression: Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='previous_value', ctx=Load())], keywords=[])\n", + "{}\n", + "Handling method call: Attribute(value=Call(func=Name(id='hist', 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", + "Processing statement: Expr(value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='one', ctx=Load())], keywords=[]))\n", + "{}\n", + "Handling expression: Expr(value=Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='one', ctx=Load())], keywords=[]))\n", + "{}\n", + "Evaluating expression: Call(func=Attribute(value=Call(func=Name(id='hist', ctx=Load()), args=[], keywords=[]), attr='update', ctx=Load()), args=[Name(id='process_id', ctx=Load()), Name(id='one', ctx=Load())], keywords=[])\n", + "{}\n", + "Handling method call: Attribute(value=Call(func=Name(id='hist', 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/tmpowf1hvxf.ll\n" + ] + } + ], + "source": [ + "@bpf\n", + "@map\n", + "def hist() -> HashMap:\n", + " return HashMap(key=c_int32, value=c_uint64, max_entries=4096)\n", + "\n", + "@bpf\n", + "@section(\"tracepoint/syscalls/sys_enter_clone\")\n", + "def hello(ctx: c_void_p) -> c_int64:\n", + " process_id = pid()\n", + " one = 1\n", + " prev = hist().lookup(process_id)\n", + " if prev:\n", + " previous_value = prev + 1\n", + " print(f\"count: {previous_value} with {process_id}\")\n", + " hist().update(process_id, previous_value)\n", + " return c_int64(0)\n", + " else:\n", + " hist().update(process_id, one)\n", + " return c_int64(0)\n", + "\n", + "\n", + "@bpf\n", + "@bpfglobal\n", + "def LICENSE() -> str:\n", + " return \"GPL\"\n", + "\n", + "b = BPF()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "93fae9f8-464e-48d6-b61e-57b9f93e508a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Recording\n", + "PID 116823 called clone() >40 times\n", + "PID 117099 called clone() >40 times\n", + "PID 116696 called clone() >40 times\n", + "Total PIDs with clone() >40 times: 3\n" + ] + }, + { + "data": { + "image/png": "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r149tG7dGn5+flw5nIiIiKodrXp2nj59ioULF8LZ2RkBAQE4ePAg8vLyYGhoiPT0dMyZMweNGjVCQEAAzpw5o+/MRERERFrTqmenSZMmUCgU2LBhA95//30YGRlV2OfOnTuIjIxE//79MWPGDIwYMULysERERERiaVXsHD58GM2aNfvbfVxcXBAWFobJkycjMzNTknBEREREutLqNtY/FTp/ZWRkhMaNG1c6EBEREZGUtCp2xPbU3Lt3r1JhiIiIiKSmVbHTrl07jBo1CufPn3/pPvn5+diwYQNatGiBXbt2SRaQiIiISBdajdm5du0avvzyS7z//vswNTWFt7c3nJycYGpqij///BPXrl3D1atX0aZNG0RERCAgIEDfuYmIiIi0olXPjq2tLZYvX44HDx7gP//5D9zc3PDw4UPcuHEDADBw4EAkJCQgPj6ehQ4RERFVK6ImFTQzM8OHH36IDz/8UF95iIiIiCRV6eUi0tPTER0djadPnwIABEGQLBQRERGRVEQXO48ePYKvry+aNGmCgIAAPHjwAAAwfPhwTJo0SfKARERERLoQXeyEhITAyMgImZmZMDc3V7f369cPhw4dkjQcERERka5EjdkBns2mHB0djfr162u0u7m54c6dO5IFIyIiIpKC6J6doqIijR6d53Jzc2FiYiJJKCIiIiKpiC523n77bWzbtk39XiaTQaVSISIiAu+++66k4YiIiIh0Jfo2VkREBHx9fXHhwgWUlpZi6tSpuHr1KnJzc3Hq1Cl9ZCQiIiKqNNE9Oy1atEBaWho6deqEXr16oaioCH369MGlS5e4ACgRERFVO6J7dgBALpdjxowZUmchIiIiklylip28vDycO3cOOTk5UKlUGtsGDx4sSTAiIiIiKYgudvbt24eBAweisLAQ1tbWkMlk6m0ymYzFDhEREVUrosfsTJo0CZ999hkKCwuRl5eHP//8U/3Kzc3VR0YiIiKiShNd7Ny7dw/jx49/4Vw7RERERNWN6GLH398fFy5c0EcWIiIiIsmJHrMTGBiIKVOm4Nq1a/D09ISRkZHG9p49e0oWjoiIiEhXooudESNGAADmz59fYZtMJoNSqdQ9FREREZFERBc7//uoOREREVF1JnrMDhEREdGrpFLFTlxcHHr06AFXV1e4urqiZ8+e+O2336TORkRERKQz0cXO999/Dz8/P5ib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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "b.load_and_attach()\n", + "hist = BpfMap(b, hist)\n", + "print(\"Recording\")\n", + "time.sleep(10)\n", + "\n", + "counts = list(hist.values())\n", + "x = 0\n", + "for key in hist.keys():\n", + " if hist[key] > 40:\n", + " x += 1\n", + " print(f\"PID {key} called clone() >40 times\")\n", + "print(f\"Total PIDs with clone() >40 times: {x}\")\n", + "plt.hist(counts, bins=20)\n", + "plt.xlabel(\"Clone calls per PID\")\n", + "plt.ylabel(\"Number of processes that called clone() x times in last 10 seconds\")\n", + "plt.title(\"x\")\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 +} diff --git a/examples/pybpf0.py b/examples/pybpf0.py new file mode 100644 index 0000000..eb29a2f --- /dev/null +++ b/examples/pybpf0.py @@ -0,0 +1,35 @@ +from pythonbpf import bpf, section, bpfglobal, BPF +import sys +from ctypes import c_void_p, c_int64 + +# Instructions to how to run this program +# 1. Install PythonBPF: pip install pythonbpf +# 2. `sudo /path/to/venv/bin/python ./python-bpf/demo/pybpf0.py` + +@bpf +@section("tracepoint/syscalls/sys_enter_execve") +def hello_world(ctx: c_void_p) -> c_int64: + print("Hello, World!") + return c_int64(0) + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + +b = BPF() +b.load_and_attach() + +def main(): + try: + with open("/sys/kernel/debug/tracing/trace_pipe", "r") as f: + for line in f: + sys.stdout.write(line) + sys.stdout.flush() + except KeyboardInterrupt: + pass + except PermissionError: + sys.stderr.write("Need root privileges to read trace_pipe\n") + +if __name__ == "__main__": + main() diff --git a/examples/pybpf1.py b/examples/pybpf1.py new file mode 100644 index 0000000..409e553 --- /dev/null +++ b/examples/pybpf1.py @@ -0,0 +1,41 @@ +from pythonbpf import bpf, map, section, bpfglobal, compile +from pythonbpf.helpers import XDP_PASS +from pythonbpf.maps import HashMap + +from ctypes import c_void_p, c_int64 + +# Instructions to how to run this program +# 1. Install PythonBPF: pip install pythonbpf +# 2. Run the program: python demo/pybpf1.py +# 3. Run the program with sudo: sudo examples/check.sh run demo/pybpf1.o +# 4. Attach object file to any network device with something like ./check.sh xdp ../demo/pybpf1.o tailscale0 +# 5. send traffic through the device and observe effects + +@bpf +@map +def count() -> HashMap: + return HashMap(key=c_int64, value=c_int64, max_entries=1) + + +@bpf +@section("xdp") +def hello_world(ctx: c_void_p) -> c_int64: + key = 0 + one = 1 + prev = count().lookup(key) + if prev: + prevval = prev + 1 + print(f"count: {prevval}") + count().update(key, prevval) + return XDP_PASS + else: + count().update(key, one) + + return XDP_PASS + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + +compile() diff --git a/examples/pybpf2.py b/examples/pybpf2.py new file mode 100644 index 0000000..94e0d6a --- /dev/null +++ b/examples/pybpf2.py @@ -0,0 +1,43 @@ +from pythonbpf import bpf, map, section, bpfglobal, compile +from pythonbpf.helpers import ktime +from pythonbpf.maps import HashMap + +from ctypes import c_void_p, c_int64, c_uint64 + +# Instructions to how to run this program +# 1. Install PythonBPF: pip install pythonbpf +# 2. Run the program: python demo/pybpf2.py +# 3. Run the program with sudo: sudo examples/check.sh run demo/pybpf2.o +# 4. Start a Python repl and `import os` and then keep entering `os.sync()` to see reponses. + +@bpf +@map +def last() -> HashMap: + return HashMap(key=c_uint64, value=c_uint64, max_entries=3) + + +@bpf +@section("tracepoint/syscalls/sys_enter_sync") +def do_trace(ctx: c_void_p) -> c_int64: + key = 0 + tsp = last().lookup(key) + if tsp: + kt = ktime() + delta = (kt - tsp) + if delta < 1000000000: + time_ms = (delta // 1000000) + print(f"sync called within last second, last {time_ms} ms ago") + last().delete(key) + else: + kt = ktime() + last().update(key, kt) + return c_int64(0) + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + + +compile() diff --git a/examples/pybpf3.py b/examples/pybpf3.py new file mode 100644 index 0000000..ca60172 --- /dev/null +++ b/examples/pybpf3.py @@ -0,0 +1,49 @@ +from pythonbpf import * +from pylibbpf import * +import sys +from ctypes import c_void_p, c_int64, c_uint64 + +@bpf +@map +def last() -> HashMap: + return HashMap(key=c_uint64, value=c_uint64, max_entries=3) + +@bpf +@section("tracepoint/syscalls/sys_enter_clone") +def do_trace(ctx: c_void_p) -> c_int64: + key = 0 + tsp = last().lookup(key) + if tsp: + kt = ktime() + delta = (kt - tsp) + if delta < 1000000000: + time_ms = (delta // 1000000) + print(f"Clone syscall entered within last second, last {time_ms} ms ago") + last().delete(key) + else: + kt = ktime() + last().update(key, kt) + return c_int64(0) + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + +b = BPF() +# autoattaches tracepoints +b.load_and_attach() + +def main(): + try: + with open("/sys/kernel/debug/tracing/trace_pipe", "r") as f: + for line in f: + sys.stdout.write(line) + sys.stdout.flush() + except KeyboardInterrupt: + pass + except PermissionError: + sys.stderr.write("Need root privileges to read trace_pipe\n") + +if __name__ == "__main__": + main() diff --git a/examples/pybpf4.py b/examples/pybpf4.py new file mode 100644 index 0000000..049b9d1 --- /dev/null +++ b/examples/pybpf4.py @@ -0,0 +1,62 @@ +import time + +from pythonbpf import bpf, map, section, bpfglobal, BPF +from pythonbpf.helpers import pid +from pythonbpf.maps import HashMap +from pylibbpf import BpfMap + +from ctypes import c_void_p, c_int64, c_uint64, c_int32 +import matplotlib.pyplot as plt + +# This program attaches an eBPF tracepoint to sys_enter_clone, +# counts per-PID clone syscalls, stores them in a hash map, +# and then plots the distribution as a histogram using matplotlib. +# It provides a quick view of process creation activity over 10 seconds. +# Everything is done with Python only code and with the new pylibbpf library. +# Run `sudo /path/to/python/binary/ pybpf4.py` + +@bpf +@map +def hist() -> HashMap: + return HashMap(key=c_int32, value=c_uint64, max_entries=4096) + +@bpf +@section("tracepoint/syscalls/sys_enter_clone") +def hello(ctx: c_void_p) -> c_int64: + process_id = pid() + one = 1 + prev = hist().lookup(process_id) + if prev: + previous_value = prev + 1 + print(f"count: {previous_value} with {process_id}") + hist().update(process_id, previous_value) + return c_int64(0) + else: + hist().update(process_id, one) + return c_int64(0) + + +@bpf +@bpfglobal +def LICENSE() -> str: + return "GPL" + +b = BPF() +b.load_and_attach() +hist = BpfMap(b, hist) +print("Recording") +time.sleep(10) + +counts = list(hist.values()) +x = 0 +for key in hist.keys(): + if hist[key] > 40: + x += 1 + print(f"PID {key} called clone() >40 times") + +print(f"Total PIDs with clone() >40 times: {x}") +plt.hist(counts, bins=20) +plt.xlabel("Clone calls per PID") +plt.ylabel("Number of processes that called clone() x times in last 10 seconds") +plt.title("x") +plt.show()