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/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: