added percpu support in bcc.
authorZaafar Ahmed <zaafar.tahir@gmail.com>
Fri, 25 Mar 2016 10:03:24 +0000 (15:03 +0500)
committerZaafar Ahmed <zaafar.tahir@gmail.com>
Fri, 25 Mar 2016 10:03:24 +0000 (15:03 +0500)
Due to alignment issue ( alignment of array
must be same as cache alignment of cache ),
complex data structures involved in percpu array/hash tables
must be padded to match cache alignment or else, bcc will generate a error.
non complex data structures which are not aligned to cache size such as int,
uint32 is allowed but a bit slow due to casting/reverting process.

src/cc/frontends/clang/b_frontend_action.cc
src/python/bcc/__init__.py
src/python/bcc/table.py

index bc5ede2..1ba30cb 100644 (file)
@@ -570,6 +570,12 @@ bool BTypeVisitor::VisitVarDecl(VarDecl *Decl) {
       map_type = BPF_MAP_TYPE_HASH;
     } else if (A->getName() == "maps/array") {
       map_type = BPF_MAP_TYPE_ARRAY;
+    } else if (A->getName() == "maps/pc_hash") {
+      if (KERNEL_VERSION(major,minor,0) >= KERNEL_VERSION(4,5,0))
+        map_type = BPF_MAP_TYPE_PERCPU_HASH;
+    } else if (A->getName() == "maps/pc_array") {
+      if (KERNEL_VERSION(major,minor,0) >= KERNEL_VERSION(4,5,0))
+        map_type = BPF_MAP_TYPE_PERCPU_ARRAY;
     } else if (A->getName() == "maps/histogram") {
       if (table.key_desc == "\"int\"")
         map_type = BPF_MAP_TYPE_ARRAY;
index 52a7a33..a286f26 100644 (file)
@@ -228,7 +228,7 @@ class BPF(object):
         cls = type(str(desc[0]), (base,), dict(_fields_=fields))
         return cls
 
-    def get_table(self, name, keytype=None, leaftype=None):
+    def get_table(self, name, keytype=None, leaftype=None, func_reducer=None):
         map_id = lib.bpf_table_id(self.module, name.encode("ascii"))
         map_fd = lib.bpf_table_fd(self.module, name.encode("ascii"))
         if map_fd < 0:
@@ -243,7 +243,7 @@ class BPF(object):
             if not leaf_desc:
                 raise Exception("Failed to load BPF Table %s leaf desc" % name)
             leaftype = BPF._decode_table_type(json.loads(leaf_desc.decode()))
-        return Table(self, map_id, map_fd, keytype, leaftype)
+        return Table(self, map_id, map_fd, keytype, leaftype, func_reducer)
 
     def __getitem__(self, key):
         if key not in self.tables:
index c33cb39..f1c317d 100644 (file)
@@ -17,6 +17,7 @@ import ctypes as ct
 import multiprocessing
 
 from .libbcc import lib, _RAW_CB_TYPE
+from subprocess import check_output
 
 BPF_MAP_TYPE_HASH = 1
 BPF_MAP_TYPE_ARRAY = 2
@@ -73,8 +74,8 @@ def _print_log2_hist(vals, val_type):
                       _stars(val, val_max, stars)))
 
 
-def Table(bpf, map_id, map_fd, keytype, leaftype):
-    """Table(bpf, map_id, map_fd, keytype, leaftype)
+def Table(bpf, map_id, map_fd, keytype, leaftype, func_reducer):
+    """Table(bpf, map_id, map_fd, keytype, leaftype, func_reducer)
 
     Create a python object out of a reference to a bpf table handle"""
 
@@ -89,9 +90,11 @@ def Table(bpf, map_id, map_fd, keytype, leaftype):
     elif ttype == BPF_MAP_TYPE_PERF_EVENT_ARRAY:
         t = PerfEventArray(bpf, map_id, map_fd, keytype, leaftype)
     elif ttype == BPF_MAP_TYPE_PERCPU_HASH:
-        t = PerCpuHashTable(bpf, map_id, map_fd, keytype, leaftype)
+        t = PerCpuHash(bpf, map_id, map_fd, keytype,
+                            leaftype, func=func_reducer)
     elif ttype == BPF_MAP_TYPE_PERCPU_ARRAY:
-        t = PerCpuArray(bpf, map_id, map_fd, keytype, leaftype)
+        t = PerCpuArray(bpf, map_id, map_fd, keytype,
+                            leaftype, func=func_reducer)
     elif ttype == BPF_MAP_TYPE_STACK_TRACE:
         t = StackTrace(bpf, map_id, map_fd, keytype, leaftype)
     if t == None:
@@ -146,9 +149,9 @@ class TableBase(MutableMapping):
             raise Exception("Could not scanf leaf")
         return leaf
 
-    def __getitem__(self, key):
+    def __getitem__(self, key, leaf=None):
         key_p = ct.pointer(key)
-        leaf = self.Leaf()
+        if not leaf: leaf = self.Leaf()
         leaf_p = ct.pointer(leaf)
         res = lib.bpf_lookup_elem(self.map_fd,
                 ct.cast(key_p, ct.c_void_p),
@@ -309,9 +312,9 @@ class ArrayBase(TableBase):
     def __len__(self):
         return self.max_entries
 
-    def __getitem__(self, key):
+    def __getitem__(self, key, leaf=None):
         key = self._normalize_key(key)
-        return super(ArrayBase, self).__getitem__(key)
+        return super(ArrayBase, self).__getitem__(key, leaf)
 
     def __setitem__(self, key, leaf):
         key = self._normalize_key(key)
@@ -403,13 +406,129 @@ class PerfEventArray(ArrayBase):
             del(self.bpf.open_kprobes()[(id(self), key)])
         del self._cbs[key]
 
-class PerCpuHashTable(TableBase):
+class PerCpuHash(HashTable):
     def __init__(self, *args, **kwargs):
-        raise Exception("Unsupported")
+        self.func_reducer = kwargs['func']
+        del kwargs['func']
+        self.total_cpu = multiprocessing.cpu_count()
+        self.alignment = int(check_output(["grep", "-m", "1", "cache_alignment", "/proc/cpuinfo"]).split(' ', 2)[1])/8
+        super(PerCpuHash, self).__init__(*args, **kwargs)
+        self.leafsize = ct.sizeof(self.Leaf)
+        if isinstance(self.Leaf(), ct.Structure) and self.leafsize % self.alignment is not 0:
+            # Struct that are not aligned to cache.
+            raise IndexError("Struct must be aligned to %s, please add some padding" % self.alignment)
+
+    def __getitem__(self, key):
+        if self.leafsize % self.alignment is 0:
+            # Struct/DataTypes that are aligned to cache
+            leaf_arr = (self.Leaf * self.total_cpu)()
+        else:
+            # DataTypes that are not aligned to cache
+            leaf_arr = (ct.c_uint64 * self.total_cpu)()
+
+        if (self.func_reducer):
+            super(PerCpuHash, self).__getitem__(key, leaf_arr)
+            leaf_ret = (self.Leaf * self.total_cpu)()
+            for i in range(0, self.total_cpu):
+                leaf_ret[i] = leaf_arr[i]
+            return reduce(self.func_reducer, leaf_ret)
+        else:
+            super(PerCpuHash,self).__getitem__(key,leaf_arr)
+            leaf_ret = (self.Leaf * self.total_cpu)()
+            for i in range(0, self.total_cpu):
+                leaf_ret[i] = leaf_arr[i]
+            return leaf_ret
+
+    def __setitem__(self, key, leaf):
+        if self.leafsize % self.alignment is 0:
+            leaf_arr = (self.Leaf * self.total_cpu)()
+            for i in range(0, self.total_cpu):
+                leaf_arr[i] = leaf
+        else:
+            leaf_arr = (ct.c_uint64 * self.total_cpu)()
+            for i in range(0, self.total_cpu):
+                leaf_arr[i] = leaf.value
+        super(PerCpuHash, self).__setitem__(key, leaf_arr)
+
+    def sum(self, key):
+        if isinstance(self.Leaf(), ct.Structure):
+            raise IndexError("Leaf must be an integer type for default sum functions")
+        leaf_arr = (ct.c_uint64 * self.total_cpu)()
+        return self.Leaf(reduce(lambda x,y: x+y, super(PerCpuHash, self).__getitem__(key, leaf_arr)))
+
+    def max(self, key):
+        if isinstance(self.Leaf(), ct.Structure):
+            raise IndexError("Leaf must be an integer type for default sum functions")
+        leaf_arr = (ct.c_uint64 * self.total_cpu)()
+        return self.Leaf(max(super(PerCpuHash, self).__getitem__(key, leaf_arr)))
+
+    def average(self, key):
+        if isinstance(self.Leaf(), ct.Structure):
+            raise IndexError("Leaf must be an integer type for default sum functions")
+        leaf_arr = (ct.c_uint64 * self.total_cpu)()
+        return self.Leaf(reduce(lambda x,y: x+y, super(PerCpuHash, self).__getitem__(key, leaf_arr))/self.total_cpu)
 
 class PerCpuArray(ArrayBase):
     def __init__(self, *args, **kwargs):
-        raise Exception("Unsupported")
+        self.func_reducer = kwargs['func']
+        del kwargs['func']
+        self.total_cpu = multiprocessing.cpu_count()
+        self.alignment = int(check_output(["grep", "-m", "1", "cache_alignment", "/proc/cpuinfo"]).split(' ', 2)[1])/8
+        super(PerCpuArray, self).__init__(*args, **kwargs)
+        self.leafsize = ct.sizeof(self.Leaf)
+        if isinstance(self.Leaf(), ct.Structure) and self.leafsize % self.alignment is not 0:
+            # Struct that are not aligned to cache.
+            raise IndexError("Struct must be aligned to %s, please add some padding" % self.alignment)
+
+    def __getitem__(self, key):
+        if self.leafsize % self.alignment is 0:
+            # Struct/DataTypes that are aligned to cache
+            leaf_arr = (self.Leaf * self.total_cpu)()
+        else:
+            # DataTypes that are not aligned to cache
+            leaf_arr = (ct.c_uint64 * self.total_cpu)()
+
+        if (self.func_reducer):
+            super(PerCpuArray, self).__getitem__(key, leaf_arr)
+            leaf_ret = (self.Leaf * self.total_cpu)()
+            for i in range(0, self.total_cpu):
+                leaf_ret[i] = leaf_arr[i]
+            return reduce(self.func_reducer, leaf_ret)
+        else:
+            super(PerCpuArray,self).__getitem__(key,leaf_arr)
+            leaf_ret = (self.Leaf * self.total_cpu)()
+            for i in range(0, self.total_cpu):
+                leaf_ret[i] = leaf_arr[i]
+            return leaf_ret
+
+    def __setitem__(self, key, leaf):
+        if self.leafsize % self.alignment is 0:
+            leaf_arr = (self.Leaf * self.total_cpu)()
+            for i in range(0, self.total_cpu):
+                leaf_arr[i] = leaf
+        else:
+            leaf_arr = (ct.c_uint64 * self.total_cpu)()
+            for i in range(0, self.total_cpu):
+                leaf_arr[i] = leaf.value
+        super(PerCpuArray, self).__setitem__(key, leaf_arr)
+
+    def sum(self, key):
+        if isinstance(self.Leaf(), ct.Structure):
+            raise IndexError("Leaf must be an integer type for default sum functions")
+        leaf_arr = (ct.c_uint64 * self.total_cpu)()
+        return self.Leaf(reduce(lambda x,y: x+y, super(PerCpuArray, self).__getitem__(key, leaf_arr)))
+
+    def max(self, key):
+        if isinstance(self.Leaf(), ct.Structure):
+            raise IndexError("Leaf must be an integer type for default sum functions")
+        leaf_arr = (ct.c_uint64 * self.total_cpu)()
+        return self.Leaf(max(super(PerCpuArray, self).__getitem__(key, leaf_arr)))
+
+    def average(self, key):
+        if isinstance(self.Leaf(), ct.Structure):
+            raise IndexError("Leaf must be an integer type for default sum functions")
+        leaf_arr = (ct.c_uint64 * self.total_cpu)()
+        return self.Leaf(reduce(lambda x,y: x+y, super(PerCpuArray, self).__getitem__(key, leaf_arr))/self.total_cpu)
 
 class StackTrace(TableBase):
     def __init__(self, *args, **kwargs):