BCC是一個Python庫,簡化了eBPF應用的開發過程,並收集了大量性能分析相關的eBPF應用。BCC爲BPF開發提供了不一樣的前端支持,包括Python和Lua,實現了map建立、代碼編譯、解析、注入等操做,使開發人員只需聚焦於用C語言開發要注入的內核代碼。
BCC工具集大部分工具須要Linux Kernel 4.1以上版本支持,完整工具支持須要Linux Kernel 4.15以上版本支持。
GitHub:https://github.com/iovisor/bcc前端
yum install bcc-tools export PATH=$PATH:/usr/share/bcc/tools
opensnoop經過追蹤open()系統調用顯示企圖打開文件的進程,能夠用於定位配置文件或日誌文件,或排除啓動失敗的故障應用。
opensnoop經過動態追蹤sys_open()內核函數並更新函數的任何變化,opensnoop須要Linux Kernel 4.5版本支持,因爲使用BPF,所以須要root權限。opensnoop [-h] [-T] [-U] [-x] [-p PID] [-t TID] [-u UID] [-d DURATION] [-n NAME] [-e] [-f FLAG_FILTER]
-h, --help:幫助信息查看
-T, --timestamp:輸出結果打印時間戳
-U, --print-uid:打印UID
-x, --failed:只顯示失敗open系統調用
-p PID, --pid PID:只追蹤PID進程
-t TID, --tid TID:只追蹤TID線程
-u UID, --uid UID:只追蹤UID
-d DURATION, --duration DURATION:追蹤時間,單位爲秒
-n NAME, --name NAME:只打印包含name的進程
-e, --extended_fields:顯示擴展字段
-f FLAG_FILTER, --flag_filter FLAG_FILTER:指定過濾字段,如O_WRONLYpython
execsnoop經過追蹤exec系統調用追蹤新進程,對於使用fork而不是exec產生的進程不會包括在顯示結果中。
execsnoop須要BPF支持,所以須要root權限。execsnoop [-h] [-T] [-t] [-x] [-q] [-n NAME] [-l LINE] [--max-args MAX_ARGS]
-h:查看幫助信息
-T:打印時間戳,格式HH:MM:SS
-t:打印時間戳
-x:包括失敗exec
-n NAME:只打印正則表達式匹配name的命令行
-l LINE:只打印參數中匹配LINE的命令行
--max-args MAXARGS:解析和顯示最大參數數量,默認爲20個linux
biolatency經過追蹤塊設備IO,記錄IO延遲分佈,並以直方圖顯示。biolatency經過動態追蹤blk_
族函數並記錄函數的變化。
biolatency須要BPF支持,所以須要root權限。biolatency [-h] [-F] [-T] [-Q] [-m] [-D] [interval [count]]
-h Print usage message.
-T:輸出包含時間戳
-m:輸出ms級直方圖
-D:打印每一個磁盤設備的直方圖
-F:打印每一個IO集的直方圖
interval:輸出間隔
count:輸出數量
ios
ext4slower經過跟蹤ext4文件系統的read、write、open、sync操做,並測量相應操做所耗時間,打印超過閾值的詳細信息。默認閾值最小值是10ms,若是閾值爲0,則打印全部事件。
ext4slower須要BPF支持,所以須要root權限。
ext4slower能夠經過文件系統識別獨立較慢的磁盤IO。ext4slower [-h] [-j] [-p PID] [min_ms]
-h, --help:查看幫助信息
-j, --csv:使用csv格式打印字段
-p PID, --pid PID:只追蹤PID進程
min_ms:追蹤IO的閾值,默認爲10。
git
biosnoop能夠追蹤設備IO併爲每一個IO設備打印一行彙總信息。
biosnoop經過動態追蹤blk_
族函數並記錄函數的變化。
biosnoop須要BPF支持,所以須要root權限。
biosnoop [-hQ]
-h:查看幫助信息
-Q:顯示在OS隊列的耗時
github
cachestat用於統計Linux Page的命中率和缺失率,經過動態追蹤內核頁的cache函數,並更新cache函數的任何變化。
cachestat須要BPF支持,所以須要root權限。cachestat [-h] [-T] [interval] [count]
-h:查看幫助信息
-T, --timestamp:輸出時間戳
interval:輸出間隔,單位爲秒
count:輸出數量
正則表達式
cachetop用於統計每一個進程的Linux Page緩存的命中率和缺失率,經過動態追蹤內核頁的cache函數,並更新cache函數的任何變化。
cachestat須要BPF支持,所以須要root權限。
cachetop [-h] [interval]
-h:查看幫助信息
interval:輸出間隔
PID:進程ID
UID:進程用戶ID
HITS:頁緩存命中數量
MISSES:頁緩存缺失數量
DIRTIES:增長到頁緩存的髒頁數量
READ_HIT%:頁緩存的讀命中率
WRITE_HIT%:頁緩存的寫命中率
BUFFERS_MB:Buffer大小,數據源/proc/meminfo
CACHED_MB:當前頁的Cache大小,數據源/proc/meminfo編程
tcpconnect用於追蹤TCP活躍鏈接數量,經過動態追蹤內核tcp_v4_connect和tcp_v6_connect函數,並記錄函數內的任何變化。
tcpconnect須要BPF支持,所以須要root權限。tcpconnect [-h] [-c] [-t] [-x] [-p PID] [-P PORT]
-h:查看幫助信息
-t:打印時間戳
-c:統計每一個源IP和目的IP/端口的鏈接數
-p PID:只追蹤PID進程
-P PORT:要追蹤的目的端口列表,使用逗號分隔
api
trace用於追蹤某個函數調用並打印函數參數或返回值,須要BPF支持,所以須要root權限。trace [-h] [-b BUFFER_PAGES] [-p PID] [-L TID] [-v] [-Z STRING_SIZE] [-S] [-s SYM_FILE_LIST] [-M MAX_EVENTS] [-t] [-u] [-T] [-C] [-K] [-U] [-a] [-I header] probe [probe ...]
-h:查看幫助信息
-p PID:只追蹤PID進程
-L TID:只追蹤TID線程
-v:顯示生成的BPF程序,調試使用
-z STRING_SIZE:收集字符串參數的長度
-s SYM_FILE_LIST:收集棧大小
-M MAX_EVENTS:打印追蹤消息的最大數量
-t:打印時間,單位爲秒。
-u:打印時間戳
-T:打印時間列
-C:打印CPU ID
-K:打印每一個事件的內核棧
-U:打印每一個事件的用戶棧
-a:打印序內核棧和用戶棧的虛擬地址
-I header:增長header文件到BPF程序
probe [probe ...]:附加到函數的探針
trace '::do_sys_open "%s", arg2'
追蹤open系統調用的全部調用方式trace ':c:malloc "size = %d", arg1'
追蹤malloc調用並打印申請分配內存的大小trace 'u:pthread:pthread_create "start addr = %llx", arg3'
追蹤pthread_create函數調用並打印線程啓動函數地址
緩存
deadlock用於查找正在運行進程潛在的死鎖。deadlock經過附加uprobe事件,須要BPF支持,所以須要root權限。deadlock [-h] [--binary BINARY] [--dump-graph DUMP_GRAPH] [--verbose] [--lock-symbols LOCK_SYMBOLS] [--unlock-symbols UNLOCK_SYMBOLS] pid
-h, --help:查看幫助信息
--binary BINARY:指定線程庫,對於動態連接程序必須指定。
--dump-graph DUMP_GRAPH:導出mutex圖到指定文件
--verbose:打印mutex統計信息
--lock-symbols LOCK_SYMBOLS:要追蹤的鎖的列表,使用逗號分隔,默認爲pthread_mutex_lock。
--unlock-symbols UNLOCK_SYMBOLS:要追蹤的解鎖的列表,使用逗號分隔,默認爲pthread_mutex_unlock。
pid:要追蹤的進程IDdeadlock 181 --binary /lib/x86_64-linux-gnu/libpthread.so.0
查找進程181中的潛在死鎖,若是進程被動態連接程序建立,須要使用--binary指定使用的線程庫。
memleak用於追蹤和查找內存分配和釋放配對,須要Linux Kernel 4.7以上版本支持。memleak [-h] [-p PID] [-t] [-a] [-o OLDER] [-c COMMAND] [--combined-only] [-s SAMPLE_RATE] [-T TOP] [-z MIN_SIZE] [-Z MAX_SIZE] [-O OBJ] [INTERVAL] [COUNT]
-h:查看幫助信息
-p PID:指定進程PID
-t:追蹤全部內存分配和釋放請求和結果
-a:輸出未釋放內存的列表
-z MIN_SIZE:捕獲分配內存的最小值
-Z MAX_SIZE:捕獲分配內存的最大值memleak -z 16 -Z 32
只捕獲分析分配大小未16字節至32字節間的內存分配
BCC是eBPF的一個工具集,是對eBPF提取數據的上層封裝,BCC工具編程形式是Python中嵌套BPF程序。Python代碼能夠爲用戶提供友好使用eBPF的上層接口,也能夠用於數據處理。BPF程序會注入內核,提取數據。當BPF程序運行時,經過LLVM將BPF程序編譯獲得BPF指令集的elf文件,從elf文件中解析出能夠注入內核的部分,使用bpf_load_program方法完成注入。
bpf_load_program注入程序方法加入了複雜的verifier機制,在運行注入程序前,先進行一系列的安全檢查,最大限度的保證系統的安全。通過安全檢查的BPF字節碼使用內核JIT進行編譯,生成本機彙編指令,附加到內核特定掛鉤的程序。最終內核態與用戶態經過高效的map機制進行通訊,BCC工具在用戶態使用Python進行數據處理。
Python部分編碼須要引入使用的模塊和包。
BCC工具的Python部分代碼中經過以下方式使用BPF C語言程序代碼:
hello_world.py:
#!/usr/bin/python3 from bcc import BPF bpf_program = ''' int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World!\\n"); return 0; }''' if __name__ == "__main__": BPF(text=bpf_program).trace_print()
kprobe__sys_clone
是經過kprobes進行內核動態跟蹤的快捷方式,若是C函數以開頭kprobe__
,則其他部分被視爲要檢測的內核函數名稱。
bpf_trace_printk: 輸出python3 hello_world.py
#!/usr/bin/python from bcc import BPF import pyroute2 import time import sys flags = 0 def usage(): print("Usage: {0} [-S] <ifdev>".format(sys.argv[0])) print(" -S: use skb mode\n") print("e.g.: {0} eth0\n".format(sys.argv[0])) exit(1) if len(sys.argv) < 2 or len(sys.argv) > 3: usage() if len(sys.argv) == 2: device = sys.argv[1] if len(sys.argv) == 3: if "-S" in sys.argv: # XDP_FLAGS_SKB_MODE flags |= 2 << 0 if "-S" == sys.argv[1]: device = sys.argv[2] else: device = sys.argv[1] mode = BPF.XDP ctxtype = "xdp_md" # load BPF program b = BPF(text = """ #define KBUILD_MODNAME "foo" #include <uapi/linux/bpf.h> #include <linux/in.h> #include <linux/if_ether.h> #include <linux/if_packet.h> #include <linux/if_vlan.h> #include <linux/ip.h> #include <linux/ipv6.h> // how to determin ddos #define MAX_NB_PACKETS 1000 #define LEGAL_DIFF_TIMESTAMP_PACKETS 1000000 // store data, data can be accessd in kernel and user namespace BPF_HASH(rcv_packets); BPF_TABLE("percpu_array", uint32_t, long, dropcnt, 256); static inline int parse_ipv4(void *data, u64 nh_off, void *data_end) { struct iphdr *iph = data + nh_off; if ((void*)&iph[1] > data_end) return 0; return iph->protocol; } static inline int parse_ipv6(void *data, u64 nh_off, void *data_end) { struct ipv6hdr *ip6h = data + nh_off; if ((void*)&ip6h[1] > data_end) return 0; return ip6h->nexthdr; } // determine ddos static inline int detect_ddos(){ // Used to count number of received packets u64 rcv_packets_nb_index = 0, rcv_packets_nb_inter=1, *rcv_packets_nb_ptr; // Used to measure elapsed time between 2 successive received packets u64 rcv_packets_ts_index = 1, rcv_packets_ts_inter=0, *rcv_packets_ts_ptr; int ret = 0; rcv_packets_nb_ptr = rcv_packets.lookup(&rcv_packets_nb_index); rcv_packets_ts_ptr = rcv_packets.lookup(&rcv_packets_ts_index); if(rcv_packets_nb_ptr != 0 && rcv_packets_ts_ptr != 0){ rcv_packets_nb_inter = *rcv_packets_nb_ptr; rcv_packets_ts_inter = bpf_ktime_get_ns() - *rcv_packets_ts_ptr; if(rcv_packets_ts_inter < LEGAL_DIFF_TIMESTAMP_PACKETS){ rcv_packets_nb_inter++; } else { rcv_packets_nb_inter = 0; } if(rcv_packets_nb_inter > MAX_NB_PACKETS){ ret = 1; } } rcv_packets_ts_inter = bpf_ktime_get_ns(); rcv_packets.update(&rcv_packets_nb_index, &rcv_packets_nb_inter); rcv_packets.update(&rcv_packets_ts_index, &rcv_packets_ts_inter); return ret; } // determine and recode by proto int xdp_prog1(struct CTXTYPE *ctx) { void* data_end = (void*)(long)ctx->data_end; void* data = (void*)(long)ctx->data; struct ethhdr *eth = data; // drop packets int rc = XDP_PASS; // let pass XDP_PASS or redirect to tx via XDP_TX long *value; uint16_t h_proto; uint64_t nh_off = 0; uint32_t index; nh_off = sizeof(*eth); if (data + nh_off > data_end) return rc; h_proto = eth->h_proto; // parse double vlans if (detect_ddos() == 0){ return rc; } rc = XDP_DROP; #pragma unroll for (int i=0; i<2; i++) { if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) { struct vlan_hdr *vhdr; vhdr = data + nh_off; nh_off += sizeof(struct vlan_hdr); if (data + nh_off > data_end) return rc; h_proto = vhdr->h_vlan_encapsulated_proto; } } if (h_proto == htons(ETH_P_IP)) index = parse_ipv4(data, nh_off, data_end); else if (h_proto == htons(ETH_P_IPV6)) index = parse_ipv6(data, nh_off, data_end); else index = 0; value = dropcnt.lookup(&index); if (value) *value += 1; return rc; } """, cflags=["-w", "-DCTXTYPE=%s" % ctxtype]) fn = b.load_func("xdp_prog1", mode) b.attach_xdp(device, fn, flags) dropcnt = b.get_table("dropcnt") prev = [0] * 256 print("Printing drops per IP protocol-number, hit CTRL+C to stop") while 1: try: for k in dropcnt.keys(): val = dropcnt.sum(k).value i = k.value if val: delta = val - prev[i] prev[i] = val print("{}: {} pkt/s".format(i, delta)) time.sleep(1) except KeyboardInterrupt: print("Removing filter from device") break; b.remove_xdp(device, flags)