Skip to content

Commit

Permalink
generate perf event data structure in Python automatically (iovisor#2198
Browse files Browse the repository at this point in the history
)

* 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.
  • Loading branch information
boat0 authored and yonghong-song committed Feb 14, 2019
1 parent 9dcde27 commit 3156303
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 46 deletions.
15 changes: 15 additions & 0 deletions src/cc/bpf_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ int bpf_table_key_sscanf(void *program, size_t id, const char *buf, void *key) {
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;
Expand All @@ -248,4 +249,18 @@ int bcc_func_load(void *program, int prog_type, const char *name,

}

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);
}

}
2 changes: 2 additions & 0 deletions src/cc/bpf_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ int bpf_table_key_snprintf(void *program, size_t id, char *buf, size_t buflen, c
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,
Expand Down
18 changes: 16 additions & 2 deletions src/cc/bpf_module.cc
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ int BPFModule::free_bcc_memory() {
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;
}
Expand All @@ -172,7 +172,7 @@ int BPFModule::load_cfile(const string &file, bool in_memory, const char *cflags
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;
}
Expand Down Expand Up @@ -595,6 +595,20 @@ unsigned BPFModule::kern_version() const {

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;
Expand Down
5 changes: 5 additions & 0 deletions src/cc/bpf_module.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ class BPFModule {
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
Expand Down Expand Up @@ -160,6 +162,9 @@ class BPFModule {
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
23 changes: 21 additions & 2 deletions src/cc/frontends/clang/b_frontend_action.cc
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,23 @@ bool BTypeVisitor::VisitCallExpr(CallExpr *Call) {
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()));
Expand Down Expand Up @@ -1348,7 +1365,8 @@ BFrontendAction::BFrontendAction(llvm::raw_ostream &os, unsigned flags,
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),
Expand All @@ -1359,7 +1377,8 @@ BFrontendAction::BFrontendAction(llvm::raw_ostream &os, unsigned flags,
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));
Expand Down
4 changes: 3 additions & 1 deletion src/cc/frontends/clang/b_frontend_action.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ class BFrontendAction : public clang::ASTFrontendAction {
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.
Expand Down Expand Up @@ -192,6 +193,7 @@ class BFrontendAction : public clang::ASTFrontendAction {
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
13 changes: 8 additions & 5 deletions src/cc/frontends/clang/loader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ int ClangLoader::parse(unique_ptr<llvm::Module> *mod, TableStorage &ts,
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;
Expand Down Expand Up @@ -206,7 +207,7 @@ int ClangLoader::parse(unique_ptr<llvm::Module> *mod, TableStorage &ts,
#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
Expand All @@ -218,7 +219,7 @@ int ClangLoader::parse(unique_ptr<llvm::Module> *mod, TableStorage &ts,
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
}
Expand Down Expand Up @@ -266,7 +267,8 @@ int ClangLoader::do_compile(unique_ptr<llvm::Module> *mod, TableStorage &ts,
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;
Expand Down Expand Up @@ -380,7 +382,8 @@ int ClangLoader::do_compile(unique_ptr<llvm::Module> *mod, TableStorage &ts,
// 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);
Expand Down
6 changes: 4 additions & 2 deletions src/cc/frontends/clang/loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ class ClangLoader {
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,
Expand All @@ -66,7 +67,8 @@ class ClangLoader {
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_;
Expand Down
2 changes: 1 addition & 1 deletion src/python/bcc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ def get_table(self, name, keytype=None, leaftype=None, reducer=None):
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:
Expand Down
4 changes: 4 additions & 0 deletions src/python/bcc/libbcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
71 changes: 68 additions & 3 deletions src/python/bcc/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -122,7 +123,7 @@ def _print_linear_hist(vals, val_type):
_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"""
Expand All @@ -136,7 +137,7 @@ def Table(bpf, map_id, map_fd, keytype, leaftype, **kwargs):
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:
Expand All @@ -162,7 +163,7 @@ def Table(bpf, map_id, map_fd, keytype, leaftype, **kwargs):

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
Expand All @@ -171,6 +172,7 @@ def __init__(self, bpf, map_id, map_fd, keytype, leaftype):
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)
Expand Down Expand Up @@ -537,6 +539,7 @@ class PerfEventArray(ArrayBase):
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())
Expand All @@ -559,6 +562,68 @@ def __delitem__(self, key):
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)
Expand Down
Loading

0 comments on commit 3156303

Please sign in to comment.