From b0e403882430c95dae44bde2a72e89ec175ef3e7 Mon Sep 17 00:00:00 2001 From: Zaafar Ahmed Date: Fri, 25 Mar 2016 15:03:24 +0500 Subject: [PATCH] added percpu support in bcc. 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 | 6 ++ src/python/bcc/__init__.py | 4 +- src/python/bcc/table.py | 141 +++++++++++++++++++++++++--- 3 files changed, 138 insertions(+), 13 deletions(-) diff --git a/src/cc/frontends/clang/b_frontend_action.cc b/src/cc/frontends/clang/b_frontend_action.cc index bc5ede2..1ba30cb 100644 --- a/src/cc/frontends/clang/b_frontend_action.cc +++ b/src/cc/frontends/clang/b_frontend_action.cc @@ -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; diff --git a/src/python/bcc/__init__.py b/src/python/bcc/__init__.py index 52a7a33..a286f26 100644 --- a/src/python/bcc/__init__.py +++ b/src/python/bcc/__init__.py @@ -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: diff --git a/src/python/bcc/table.py b/src/python/bcc/table.py index c33cb39..f1c317d 100644 --- a/src/python/bcc/table.py +++ b/src/python/bcc/table.py @@ -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): -- 2.7.4