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: