Skip to content

Commit

Permalink
Split bcc.table.BPFTable into multiple type-specific classes
Browse files Browse the repository at this point in the history
BPFTable contained all of the logic for multiple table types, which is
incorrect since a bpf table has either hash or array behavior.
Additionally, some methods on the classes aren't valid for some table
types. Create HashTable, Array, ProgArray, and PerfEventArray classes to
contain this behavior.

In future, the new Array class and its children should behave more like
an array than a dict as it currently does.

Signed-off-by: Brenden Blanco <[email protected]>
  • Loading branch information
Brenden Blanco committed Feb 16, 2016
1 parent 134f653 commit 1217f01
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 131 deletions.
8 changes: 5 additions & 3 deletions src/python/bcc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
basestring = (unicode if sys.version_info[0] < 3 else str)

from .libbcc import lib, _CB_TYPE
from .table import BPFTable
from .table import Table

open_kprobes = {}
open_uprobes = {}
Expand Down Expand Up @@ -64,7 +64,9 @@ class BPF(object):
_libsearch_cache = {}
_lib_load_address_cache = {}
_lib_symbol_cache = {}
Table = BPFTable

# defined for compatibility reasons, to be removed
Table = Table

class Function(object):
def __init__(self, bpf, name, fd):
Expand Down Expand Up @@ -234,7 +236,7 @@ def get_table(self, name, keytype=None, leaftype=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.decode()))
return BPF.Table(self, map_id, map_fd, keytype, leaftype)
return Table(self, map_id, map_fd, keytype, leaftype)

def __getitem__(self, key):
if key not in self.tables:
Expand Down
305 changes: 177 additions & 128 deletions src/python/bcc/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,77 @@

from .libbcc import lib, _RAW_CB_TYPE

BPF_MAP_TYPE_HASH = 1
BPF_MAP_TYPE_ARRAY = 2
BPF_MAP_TYPE_PROG_ARRAY = 3
BPF_MAP_TYPE_PERF_EVENT_ARRAY = 4

stars_max = 40

class BPFTable(MutableMapping):
HASH = 1
ARRAY = 2
PROG_ARRAY = 3
PERF_EVENT_ARRAY = 4
# helper functions, consider moving these to a utils module
def _stars(val, val_max, width):
i = 0
text = ""
while (1):
if (i > (width * val / val_max) - 1) or (i > width - 1):
break
text += "*"
i += 1
if val > val_max:
text = text[:-1] + "+"
return text

def _print_log2_hist(vals, val_type):
global stars_max
log2_dist_max = 64
idx_max = -1
val_max = 0

for i, v in enumerate(vals):
if v > 0: idx_max = i
if v > val_max: val_max = v

if idx_max <= 32:
header = " %-19s : count distribution"
body = "%10d -> %-10d : %-8d |%-*s|"
stars = stars_max
else:
header = " %-29s : count distribution"
body = "%20d -> %-20d : %-8d |%-*s|"
stars = int(stars_max / 2)

if idx_max > 0:
print(header % val_type);
for i in range(1, idx_max + 1):
low = (1 << i) >> 1
high = (1 << i) - 1
if (low == high):
low -= 1
val = vals[i]
print(body % (low, high, val, stars,
_stars(val, val_max, stars)))


def Table(bpf, map_id, map_fd, keytype, leaftype):
"""Table(bpf, map_id, map_fd, keytype, leaftype)
Create a python object out of a reference to a bpf table handle"""

ttype = lib.bpf_table_type_id(bpf.module, map_id)
t = None
if ttype == BPF_MAP_TYPE_HASH:
t = HashTable(bpf, map_id, map_fd, keytype, leaftype)
elif ttype == BPF_MAP_TYPE_ARRAY:
t = Array(bpf, map_id, map_fd, keytype, leaftype)
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)
if t == None:
raise Exception("Unknown table type %d" % ttype)
return t

class TableBase(MutableMapping):

def __init__(self, bpf, map_id, map_fd, keytype, leaftype):
self.bpf = bpf
Expand Down Expand Up @@ -70,35 +134,6 @@ def leaf_scanf(self, leaf_str):
raise Exception("Could not scanf leaf")
return leaf

def open_perf_buffer(self, callback):
"""open_perf_buffers(callback)
Opens a set of per-cpu ring buffer to receive custom perf event
data from the bpf program. The callback will be invoked for each
event submitted from the kernel, up to millions per second.
"""

for i in range(0, multiprocessing.cpu_count()):
self._open_perf_buffer(i, callback)

def _open_perf_buffer(self, cpu, callback):
fn = _RAW_CB_TYPE(lambda _, data, size: callback(cpu, data, size))
reader = lib.bpf_open_perf_buffer(fn, None, -1, cpu)
if not reader:
raise Exception("Could not open perf buffer")
fd = lib.perf_reader_fd(reader)
self[self.Key(cpu)] = self.Leaf(fd)
open_kprobes[(id(self), cpu)] = reader
# keep a refcnt
self._cbs[cpu] = fn

def close_perf_buffer(self, key):
reader = open_kprobes.get((id(self), key))
if reader:
lib.perf_reader_free(reader)
del(open_kprobes[(id(self), key)])
del self._cbs[key]

def __getitem__(self, key):
key_p = ct.pointer(key)
leaf = self.Leaf()
Expand All @@ -125,25 +160,7 @@ def __len__(self):
return i

def __delitem__(self, key):
key_p = ct.pointer(key)
ttype = lib.bpf_table_type_id(self.bpf.module, self.map_id)
# Deleting from array type maps does not have an effect, so
# zero out the entry instead.
if ttype in (self.ARRAY, self.PROG_ARRAY, self.PERF_EVENT_ARRAY):
leaf = self.Leaf()
leaf_p = ct.pointer(leaf)
res = lib.bpf_update_elem(self.map_fd,
ct.cast(key_p, ct.c_void_p),
ct.cast(leaf_p, ct.c_void_p), 0)
if res < 0:
raise Exception("Could not clear item")
if ttype == self.PERF_EVENT_ARRAY:
self.close_perf_buffer(key)
else:
res = lib.bpf_delete_elem(self.map_fd,
ct.cast(key_p, ct.c_void_p))
if res < 0:
raise KeyError
raise Exception("__delitem__ not implemented, abstract base class")

# override the MutableMapping's implementation of these since they
# don't handle KeyError nicely
Expand Down Expand Up @@ -174,18 +191,45 @@ def clear(self):
for k in self.keys():
self.__delitem__(k)

@staticmethod
def _stars(val, val_max, width):
i = 0
text = ""
while (1):
if (i > (width * val / val_max) - 1) or (i > width - 1):
break
text += "*"
i += 1
if val > val_max:
text = text[:-1] + "+"
return text

def __iter__(self):
return TableBase.Iter(self, self.Key)

def iter(self): return self.__iter__()
def keys(self): return self.__iter__()

class Iter(object):
def __init__(self, table, keytype):
self.Key = keytype
self.table = table
k = self.Key()
kp = ct.pointer(k)
# if 0 is a valid key, try a few alternatives
if k in table:
ct.memset(kp, 0xff, ct.sizeof(k))
if k in table:
ct.memset(kp, 0x55, ct.sizeof(k))
if k in table:
raise Exception("Unable to allocate iterator")
self.key = k
def __iter__(self):
return self
def __next__(self):
return self.next()
def next(self):
self.key = self.table.next(self.key)
return self.key

def next(self, key):
next_key = self.Key()
next_key_p = ct.pointer(next_key)
key_p = ct.pointer(key)
res = lib.bpf_get_next_key(self.map_fd,
ct.cast(key_p, ct.c_void_p),
ct.cast(next_key_p, ct.c_void_p))
if res < 0:
raise StopIteration()
return next_key

def print_log2_hist(self, val_type="value", section_header="Bucket ptr",
section_print_fn=None):
Expand Down Expand Up @@ -214,78 +258,83 @@ def print_log2_hist(self, val_type="value", section_header="Bucket ptr",
section_print_fn(bucket)))
else:
print("\n%s = %r" % (section_header, bucket))
self._print_log2_hist(vals, val_type, 0)
_print_log2_hist(vals, val_type)
else:
vals = [0] * 65
for k, v in self.items():
vals[k.value] = v.value
self._print_log2_hist(vals, val_type, 0)
_print_log2_hist(vals, val_type)

def _print_log2_hist(self, vals, val_type, val_max):
global stars_max
log2_dist_max = 64
idx_max = -1

for i, v in enumerate(vals):
if v > 0: idx_max = i
if v > val_max: val_max = v
class HashTable(TableBase):
def __init__(self, *args, **kwargs):
super(HashTable, self).__init__(*args, **kwargs)

if idx_max <= 32:
header = " %-19s : count distribution"
body = "%10d -> %-10d : %-8d |%-*s|"
stars = stars_max
else:
header = " %-29s : count distribution"
body = "%20d -> %-20d : %-8d |%-*s|"
stars = int(stars_max / 2)

if idx_max > 0:
print(header % val_type);
for i in range(1, idx_max + 1):
low = (1 << i) >> 1
high = (1 << i) - 1
if (low == high):
low -= 1
val = vals[i]
print(body % (low, high, val, stars,
self._stars(val, val_max, stars)))
def __delitem__(self, key):
key_p = ct.pointer(key)
res = lib.bpf_delete_elem(self.map_fd, ct.cast(key_p, ct.c_void_p))
if res < 0:
raise KeyError

class ArrayBase(TableBase):
def __init__(self, *args, **kwargs):
super(ArrayBase, self).__init__(*args, **kwargs)

def __iter__(self):
return BPFTable.Iter(self, self.Key)
def __delitem__(self, key):
key_p = ct.pointer(key)

def iter(self): return self.__iter__()
def keys(self): return self.__iter__()
# Deleting from array type maps does not have an effect, so
# zero out the entry instead.
leaf = self.Leaf()
leaf_p = ct.pointer(leaf)
res = lib.bpf_update_elem(self.map_fd, ct.cast(key_p, ct.c_void_p),
ct.cast(leaf_p, ct.c_void_p), 0)
if res < 0:
raise Exception("Could not clear item")

class Iter(object):
def __init__(self, table, keytype):
self.Key = keytype
self.table = table
k = self.Key()
kp = ct.pointer(k)
# if 0 is a valid key, try a few alternatives
if k in table:
ct.memset(kp, 0xff, ct.sizeof(k))
if k in table:
ct.memset(kp, 0x55, ct.sizeof(k))
if k in table:
raise Exception("Unable to allocate iterator")
self.key = k
def __iter__(self):
return self
def __next__(self):
return self.next()
def next(self):
self.key = self.table.next(self.key)
return self.key
class Array(ArrayBase):
def __init__(self, *args, **kwargs):
super(Array, self).__init__(*args, **kwargs)

def next(self, key):
next_key = self.Key()
next_key_p = ct.pointer(next_key)
key_p = ct.pointer(key)
res = lib.bpf_get_next_key(self.map_fd,
ct.cast(key_p, ct.c_void_p),
ct.cast(next_key_p, ct.c_void_p))
if res < 0:
raise StopIteration()
return next_key


class ProgArray(ArrayBase):
def __init__(self, *args, **kwargs):
super(ProgArray, self).__init__(*args, **kwargs)

class PerfEventArray(ArrayBase):
def __init__(self, *args, **kwargs):
super(PerfEventArray, self).__init__(*args, **kwargs)

def __delitem__(self, key):
super(PerfEventArray, self).__init__(key)
self.close_perf_buffer(key)

def open_perf_buffer(self, callback):
"""open_perf_buffers(callback)
Opens a set of per-cpu ring buffer to receive custom perf event
data from the bpf program. The callback will be invoked for each
event submitted from the kernel, up to millions per second.
"""

for i in range(0, multiprocessing.cpu_count()):
self._open_perf_buffer(i, callback)

def _open_perf_buffer(self, cpu, callback):
fn = _RAW_CB_TYPE(lambda _, data, size: callback(cpu, data, size))
reader = lib.bpf_open_perf_buffer(fn, None, -1, cpu)
if not reader:
raise Exception("Could not open perf buffer")
fd = lib.perf_reader_fd(reader)
self[self.Key(cpu)] = self.Leaf(fd)
open_kprobes[(id(self), cpu)] = reader
# keep a refcnt
self._cbs[cpu] = fn

def close_perf_buffer(self, key):
reader = open_kprobes.get((id(self), key))
if reader:
lib.perf_reader_free(reader)
del(open_kprobes[(id(self), key)])
del self._cbs[key]

0 comments on commit 1217f01

Please sign in to comment.