generate perf event data structure in Python automatically (#2198)
* generate perf event data structure in Python automatically
When ring buffers are opened to receive custom perf event, we have
to define the event data structure twice: once in BPF C and once
in Python. It is tedious and error-prone.
This patch implements the automatic generation of Python data structure
from the C declaration, thus making the redundant definition in Python
unnecessary. Example:
// define output data structure in C
struct data_t {
u32 pid;
u64 ts;
char comm[TASK_COMM_LEN];
};
BPF_PERF_OUTPUT(events);
...
Old way:
# define output data structure in Python
TASK_COMM_LEN = 16 # linux/sched.h
class Data(ct.Structure):
_fields_ = [("pid", ct.c_ulonglong),
("ts", ct.c_ulonglong),
("comm", ct.c_char * TASK_COMM_LEN)]
def print_event(cpu, data, size):
event = ct.cast(data, ct.POINTER(Data)).contents
...
New way:
def print_event(cpu, data, size):
event = b["events"].event(data)
...
* tools/tcpconnect.py: deduce perf event data structure automatically
Take tcpconnect.py as an example to show the new, simpler way
of outputing perf event.
Other tools/examples can be simplified in a similar way.
diff --git a/src/cc/bpf_common.cc b/src/cc/bpf_common.cc
index fa42d19..e65ef9d 100644
--- a/src/cc/bpf_common.cc
+++ b/src/cc/bpf_common.cc
@@ -230,6 +230,7 @@
if (!mod) return -1;
return mod->table_key_scanf(id, buf, key);
}
+
int bpf_table_leaf_sscanf(void *program, size_t id, const char *buf, void *leaf) {
auto mod = static_cast<ebpf::BPFModule *>(program);
if (!mod) return -1;
@@ -248,4 +249,18 @@
}
+size_t bpf_perf_event_fields(void *program, const char *event) {
+ auto mod = static_cast<ebpf::BPFModule *>(program);
+ if (!mod)
+ return 0;
+ return mod->perf_event_fields(event);
+}
+
+const char * bpf_perf_event_field(void *program, const char *event, size_t i) {
+ auto mod = static_cast<ebpf::BPFModule *>(program);
+ if (!mod)
+ return nullptr;
+ return mod->perf_event_field(event, i);
+}
+
}
diff --git a/src/cc/bpf_common.h b/src/cc/bpf_common.h
index 56c30f6..9ad4142 100644
--- a/src/cc/bpf_common.h
+++ b/src/cc/bpf_common.h
@@ -62,6 +62,8 @@
int bpf_table_leaf_snprintf(void *program, size_t id, char *buf, size_t buflen, const void *leaf);
int bpf_table_key_sscanf(void *program, size_t id, const char *buf, void *key);
int bpf_table_leaf_sscanf(void *program, size_t id, const char *buf, void *leaf);
+size_t bpf_perf_event_fields(void *program, const char *event);
+const char * bpf_perf_event_field(void *program, const char *event, size_t i);
struct bpf_insn;
int bcc_func_load(void *program, int prog_type, const char *name,
diff --git a/src/cc/bpf_module.cc b/src/cc/bpf_module.cc
index cfb6ff6..de7e9a5 100644
--- a/src/cc/bpf_module.cc
+++ b/src/cc/bpf_module.cc
@@ -159,7 +159,7 @@
int BPFModule::load_cfile(const string &file, bool in_memory, const char *cflags[], int ncflags) {
ClangLoader clang_loader(&*ctx_, flags_);
if (clang_loader.parse(&mod_, *ts_, file, in_memory, cflags, ncflags, id_,
- *func_src_, mod_src_, maps_ns_, fake_fd_map_))
+ *func_src_, mod_src_, maps_ns_, fake_fd_map_, perf_events_))
return -1;
return 0;
}
@@ -172,7 +172,7 @@
int BPFModule::load_includes(const string &text) {
ClangLoader clang_loader(&*ctx_, flags_);
if (clang_loader.parse(&mod_, *ts_, text, true, nullptr, 0, "", *func_src_,
- mod_src_, "", fake_fd_map_))
+ mod_src_, "", fake_fd_map_, perf_events_))
return -1;
return 0;
}
@@ -595,6 +595,20 @@
size_t BPFModule::num_tables() const { return tables_.size(); }
+size_t BPFModule::perf_event_fields(const char *event) const {
+ auto it = perf_events_.find(event);
+ if (it == perf_events_.end())
+ return 0;
+ return it->second.size();
+}
+
+const char * BPFModule::perf_event_field(const char *event, size_t i) const {
+ auto it = perf_events_.find(event);
+ if (it == perf_events_.end() || i >= it->second.size())
+ return nullptr;
+ return it->second[i].c_str();
+}
+
size_t BPFModule::table_id(const string &name) const {
auto it = table_names_.find(name);
if (it == table_names_.end()) return ~0ull;
diff --git a/src/cc/bpf_module.h b/src/cc/bpf_module.h
index 547972e..b372a95 100644
--- a/src/cc/bpf_module.h
+++ b/src/cc/bpf_module.h
@@ -133,6 +133,8 @@
const struct bpf_insn *insns, int prog_len,
const char *license, unsigned kern_version,
int log_level, char *log_buf, unsigned log_buf_size);
+ size_t perf_event_fields(const char *) const;
+ const char * perf_event_field(const char *, size_t i) const;
private:
unsigned flags_; // 0x1 for printing
@@ -160,6 +162,9 @@
std::unique_ptr<TableStorage> local_ts_;
BTF *btf_;
fake_fd_map_def fake_fd_map_;
+
+ // map of events -- key: event name, value: event fields
+ std::map<std::string, std::vector<std::string>> perf_events_;
};
} // namespace ebpf
diff --git a/src/cc/frontends/clang/b_frontend_action.cc b/src/cc/frontends/clang/b_frontend_action.cc
index 8f734db..4fc643c 100644
--- a/src/cc/frontends/clang/b_frontend_action.cc
+++ b/src/cc/frontends/clang/b_frontend_action.cc
@@ -813,6 +813,23 @@
GET_ENDLOC(Call->getArg(2)))));
txt = "bpf_perf_event_output(" + arg0 + ", bpf_pseudo_fd(1, " + fd + ")";
txt += ", CUR_CPU_IDENTIFIER, " + args_other + ")";
+
+ // e.g.
+ // struct data_t { u32 pid; }; data_t data;
+ // events.perf_submit(ctx, &data, sizeof(data));
+ // ...
+ // &data -> data -> typeof(data) -> data_t
+ auto type_arg1 = Call->getArg(1)->IgnoreCasts()->getType().getTypePtr()->getPointeeType().getTypePtr();
+ if (type_arg1->isStructureType()) {
+ auto event_type = type_arg1->getAsTagDecl();
+ const auto *r = dyn_cast<RecordDecl>(event_type);
+ std::vector<std::string> perf_event;
+
+ for (auto it = r->field_begin(); it != r->field_end(); ++it) {
+ perf_event.push_back(it->getNameAsString() + "#" + it->getType().getAsString()); //"pid#u32"
+ }
+ fe_.perf_events_[name] = perf_event;
+ }
} else if (memb_name == "perf_submit_skb") {
string skb = rewriter_.getRewrittenText(expansionRange(Call->getArg(0)->getSourceRange()));
string skb_len = rewriter_.getRewrittenText(expansionRange(Call->getArg(1)->getSourceRange()));
@@ -1348,7 +1365,8 @@
const std::string &main_path,
FuncSource &func_src, std::string &mod_src,
const std::string &maps_ns,
- fake_fd_map_def &fake_fd_map)
+ fake_fd_map_def &fake_fd_map,
+ std::map<std::string, std::vector<std::string>> &perf_events)
: os_(os),
flags_(flags),
ts_(ts),
@@ -1359,7 +1377,8 @@
func_src_(func_src),
mod_src_(mod_src),
next_fake_fd_(-1),
- fake_fd_map_(fake_fd_map) {}
+ fake_fd_map_(fake_fd_map),
+ perf_events_(perf_events) {}
bool BFrontendAction::is_rewritable_ext_func(FunctionDecl *D) {
StringRef file_name = rewriter_->getSourceMgr().getFilename(GET_BEGINLOC(D));
diff --git a/src/cc/frontends/clang/b_frontend_action.h b/src/cc/frontends/clang/b_frontend_action.h
index bc6690c..a8ac858 100644
--- a/src/cc/frontends/clang/b_frontend_action.h
+++ b/src/cc/frontends/clang/b_frontend_action.h
@@ -156,7 +156,8 @@
const std::string &id, const std::string &main_path,
FuncSource &func_src, std::string &mod_src,
const std::string &maps_ns,
- fake_fd_map_def &fake_fd_map);
+ fake_fd_map_def &fake_fd_map,
+ std::map<std::string, std::vector<std::string>> &perf_events);
// Called by clang when the AST has been completed, here the output stream
// will be flushed.
@@ -192,6 +193,7 @@
std::set<clang::Decl *> m_;
int next_fake_fd_;
fake_fd_map_def &fake_fd_map_;
+ std::map<std::string, std::vector<std::string>> &perf_events_;
};
} // namespace visitor
diff --git a/src/cc/frontends/clang/loader.cc b/src/cc/frontends/clang/loader.cc
index 3e579ba..a3e09e6 100644
--- a/src/cc/frontends/clang/loader.cc
+++ b/src/cc/frontends/clang/loader.cc
@@ -109,7 +109,8 @@
int ncflags, const std::string &id, FuncSource &func_src,
std::string &mod_src,
const std::string &maps_ns,
- fake_fd_map_def &fake_fd_map) {
+ fake_fd_map_def &fake_fd_map,
+ std::map<std::string, std::vector<std::string>> &perf_events) {
string main_path = "/virtual/main.c";
unique_ptr<llvm::MemoryBuffer> main_buf;
struct utsname un;
@@ -206,7 +207,7 @@
#endif
if (do_compile(mod, ts, in_memory, flags_cstr, flags_cstr_rem, main_path,
- main_buf, id, func_src, mod_src, true, maps_ns, fake_fd_map)) {
+ main_buf, id, func_src, mod_src, true, maps_ns, fake_fd_map, perf_events)) {
#if BCC_BACKUP_COMPILE != 1
return -1;
#else
@@ -218,7 +219,7 @@
mod_src.clear();
fake_fd_map.clear();
if (do_compile(mod, ts, in_memory, flags_cstr, flags_cstr_rem, main_path,
- main_buf, id, func_src, mod_src, false, maps_ns, fake_fd_map))
+ main_buf, id, func_src, mod_src, false, maps_ns, fake_fd_map, perf_events))
return -1;
#endif
}
@@ -266,7 +267,8 @@
const std::string &id, FuncSource &func_src,
std::string &mod_src, bool use_internal_bpfh,
const std::string &maps_ns,
- fake_fd_map_def &fake_fd_map) {
+ fake_fd_map_def &fake_fd_map,
+ std::map<std::string, std::vector<std::string>> &perf_events) {
using namespace clang;
vector<const char *> flags_cstr = flags_cstr_in;
@@ -380,7 +382,8 @@
// capture the rewritten c file
string out_str1;
llvm::raw_string_ostream os1(out_str1);
- BFrontendAction bact(os1, flags_, ts, id, main_path, func_src, mod_src, maps_ns, fake_fd_map);
+ BFrontendAction bact(os1, flags_, ts, id, main_path, func_src, mod_src,
+ maps_ns, fake_fd_map, perf_events);
if (!compiler1.ExecuteAction(bact))
return -1;
unique_ptr<llvm::MemoryBuffer> out_buf1 = llvm::MemoryBuffer::getMemBuffer(out_str1);
diff --git a/src/cc/frontends/clang/loader.h b/src/cc/frontends/clang/loader.h
index 984ca2f..176fc7e 100644
--- a/src/cc/frontends/clang/loader.h
+++ b/src/cc/frontends/clang/loader.h
@@ -55,7 +55,8 @@
const std::string &file, bool in_memory, const char *cflags[],
int ncflags, const std::string &id, FuncSource &func_src,
std::string &mod_src, const std::string &maps_ns,
- fake_fd_map_def &fake_fd_map);
+ fake_fd_map_def &fake_fd_map,
+ std::map<std::string, std::vector<std::string>> &perf_events);
private:
int do_compile(std::unique_ptr<llvm::Module> *mod, TableStorage &ts,
@@ -66,7 +67,8 @@
const std::string &id, FuncSource &func_src,
std::string &mod_src, bool use_internal_bpfh,
const std::string &maps_ns,
- fake_fd_map_def &fake_fd_map);
+ fake_fd_map_def &fake_fd_map,
+ std::map<std::string, std::vector<std::string>> &perf_events);
private:
std::map<std::string, std::unique_ptr<llvm::MemoryBuffer>> remapped_headers_;
diff --git a/src/python/bcc/__init__.py b/src/python/bcc/__init__.py
index a0f7002..534bc66 100644
--- a/src/python/bcc/__init__.py
+++ b/src/python/bcc/__init__.py
@@ -477,7 +477,7 @@
if not leaf_desc:
raise Exception("Failed to load BPF Table %s leaf desc" % name)
leaftype = BPF._decode_table_type(json.loads(leaf_desc))
- return Table(self, map_id, map_fd, keytype, leaftype, reducer=reducer)
+ return Table(self, map_id, map_fd, keytype, leaftype, name, reducer=reducer)
def __getitem__(self, key):
if key not in self.tables:
diff --git a/src/python/bcc/libbcc.py b/src/python/bcc/libbcc.py
index 2aa35b2..23d0b11 100644
--- a/src/python/bcc/libbcc.py
+++ b/src/python/bcc/libbcc.py
@@ -65,6 +65,10 @@
lib.bpf_table_leaf_sscanf.restype = ct.c_int
lib.bpf_table_leaf_sscanf.argtypes = [ct.c_void_p, ct.c_ulonglong,
ct.c_char_p, ct.c_void_p]
+lib.bpf_perf_event_fields.restype = ct.c_ulonglong
+lib.bpf_perf_event_fields.argtypes = [ct.c_void_p, ct.c_char_p]
+lib.bpf_perf_event_field.restype = ct.c_char_p
+lib.bpf_perf_event_field.argtypes = [ct.c_void_p, ct.c_char_p, ct.c_ulonglong]
# keep in sync with libbpf.h
lib.bpf_get_next_key.restype = ct.c_int
diff --git a/src/python/bcc/table.py b/src/python/bcc/table.py
index f6449de..78eddf3 100644
--- a/src/python/bcc/table.py
+++ b/src/python/bcc/table.py
@@ -18,6 +18,7 @@
import multiprocessing
import os
import errno
+import re
from .libbcc import lib, _RAW_CB_TYPE, _LOST_CB_TYPE
from .perf import Perf
@@ -122,7 +123,7 @@
_stars(val, val_max, stars)))
-def Table(bpf, map_id, map_fd, keytype, leaftype, **kwargs):
+def Table(bpf, map_id, map_fd, keytype, leaftype, name, **kwargs):
"""Table(bpf, map_id, map_fd, keytype, leaftype, **kwargs)
Create a python object out of a reference to a bpf table handle"""
@@ -136,7 +137,7 @@
elif ttype == BPF_MAP_TYPE_PROG_ARRAY:
t = ProgArray(bpf, map_id, map_fd, keytype, leaftype)
elif ttype == BPF_MAP_TYPE_PERF_EVENT_ARRAY:
- t = PerfEventArray(bpf, map_id, map_fd, keytype, leaftype)
+ t = PerfEventArray(bpf, map_id, map_fd, keytype, leaftype, name)
elif ttype == BPF_MAP_TYPE_PERCPU_HASH:
t = PerCpuHash(bpf, map_id, map_fd, keytype, leaftype, **kwargs)
elif ttype == BPF_MAP_TYPE_PERCPU_ARRAY:
@@ -162,7 +163,7 @@
class TableBase(MutableMapping):
- def __init__(self, bpf, map_id, map_fd, keytype, leaftype):
+ def __init__(self, bpf, map_id, map_fd, keytype, leaftype, name=None):
self.bpf = bpf
self.map_id = map_id
self.map_fd = map_fd
@@ -171,6 +172,7 @@
self.ttype = lib.bpf_table_type_id(self.bpf.module, self.map_id)
self.flags = lib.bpf_table_flags_id(self.bpf.module, self.map_id)
self._cbs = {}
+ self._name = name
def key_sprintf(self, key):
buf = ct.create_string_buffer(ct.sizeof(self.Key) * 8)
@@ -537,6 +539,7 @@
def __init__(self, *args, **kwargs):
super(PerfEventArray, self).__init__(*args, **kwargs)
self._open_key_fds = {}
+ self._event_class = None
def __del__(self):
keys = list(self._open_key_fds.keys())
@@ -559,6 +562,68 @@
lib.bpf_close_perf_event_fd(self._open_key_fds[key])
del self._open_key_fds[key]
+ def _get_event_class(self):
+ ct_mapping = { 'char' : ct.c_char,
+ 's8' : ct.c_char,
+ 'unsigned char' : ct.c_ubyte,
+ 'u8' : ct.c_ubyte,
+ 'u8 *' : ct.c_char_p,
+ 'char *' : ct.c_char_p,
+ 'short' : ct.c_short,
+ 's16' : ct.c_short,
+ 'unsigned short' : ct.c_ushort,
+ 'u16' : ct.c_ushort,
+ 'int' : ct.c_int,
+ 's32' : ct.c_int,
+ 'unsigned int' : ct.c_uint,
+ 'u32' : ct.c_uint,
+ 'long' : ct.c_long,
+ 'unsigned long' : ct.c_ulong,
+ 'long long' : ct.c_longlong,
+ 's64' : ct.c_longlong,
+ 'unsigned long long': ct.c_ulonglong,
+ 'u64' : ct.c_ulonglong,
+ '__int128' : (ct.c_longlong * 2),
+ 'unsigned __int128' : (ct.c_ulonglong * 2),
+ 'void *' : ct.c_void_p }
+
+ # handle array types e.g. "int [16] foo"
+ array_type = re.compile(r"(.+) \[([0-9]+)\]$")
+
+ fields = []
+ num_fields = lib.bpf_perf_event_fields(self.bpf.module, self._name)
+ i = 0
+ while i < num_fields:
+ field = lib.bpf_perf_event_field(self.bpf.module, self._name, i)
+ m = re.match(r"(.*)#(.*)", field)
+ field_name = m.group(1)
+ field_type = m.group(2)
+
+ m = array_type.match(field_type)
+ try:
+ if m:
+ fields.append((field_name, ct_mapping[m.group(1)] * int(m.group(2))))
+ else:
+ fields.append((field_name, ct_mapping[field_type]))
+ except KeyError:
+ print("Type: '%s' not recognized. Please define the data with ctypes manually."
+ % field_type)
+ exit()
+ i += 1
+ return type('', (ct.Structure,), {'_fields_': fields})
+
+ def event(self, data):
+ """event(data)
+
+ When ring buffers are opened to receive custom perf event,
+ the underlying event data struct which is defined in C in
+ the BPF program can be deduced via this function. This avoids
+ redundant definitions in Python.
+ """
+ if self._event_class == None:
+ self._event_class = self._get_event_class()
+ return ct.cast(data, ct.POINTER(self._event_class)).contents
+
def open_perf_buffer(self, callback, page_cnt=8, lost_cb=None):
"""open_perf_buffers(callback)
diff --git a/tools/tcpconnect.py b/tools/tcpconnect.py
index 54364c9..e230f65 100755
--- a/tools/tcpconnect.py
+++ b/tools/tcpconnect.py
@@ -24,7 +24,6 @@
import argparse
from socket import inet_ntop, ntohs, AF_INET, AF_INET6
from struct import pack
-import ctypes as ct
# arguments
examples = """examples:
@@ -187,36 +186,9 @@
if args.ebpf:
exit()
-# event data
-TASK_COMM_LEN = 16 # linux/sched.h
-
-class Data_ipv4(ct.Structure):
- _fields_ = [
- ("ts_us", ct.c_ulonglong),
- ("pid", ct.c_uint),
- ("uid", ct.c_uint),
- ("saddr", ct.c_uint),
- ("daddr", ct.c_uint),
- ("ip", ct.c_ulonglong),
- ("dport", ct.c_ushort),
- ("task", ct.c_char * TASK_COMM_LEN)
- ]
-
-class Data_ipv6(ct.Structure):
- _fields_ = [
- ("ts_us", ct.c_ulonglong),
- ("pid", ct.c_uint),
- ("uid", ct.c_uint),
- ("saddr", (ct.c_ulonglong * 2)),
- ("daddr", (ct.c_ulonglong * 2)),
- ("ip", ct.c_ulonglong),
- ("dport", ct.c_ushort),
- ("task", ct.c_char * TASK_COMM_LEN)
- ]
-
# process event
def print_ipv4_event(cpu, data, size):
- event = ct.cast(data, ct.POINTER(Data_ipv4)).contents
+ event = b["ipv4_events"].event(data)
global start_ts
if args.timestamp:
if start_ts == 0:
@@ -230,7 +202,7 @@
inet_ntop(AF_INET, pack("I", event.daddr)).encode(), event.dport))
def print_ipv6_event(cpu, data, size):
- event = ct.cast(data, ct.POINTER(Data_ipv6)).contents
+ event = b["ipv6_events"].event(data)
global start_ts
if args.timestamp:
if start_ts == 0: