From bb691fb8164721ac3a85b29d62883c19a55d4d48 Mon Sep 17 00:00:00 2001 From: Dave Marchevsky Date: Fri, 13 Aug 2021 20:17:27 -0700 Subject: [PATCH] bcc/python: extend perf_event_attr ctype This commit brings the Perf.perf_event_attr ctype in line with version 6 of struct perf_event_attr (see uapi/linux/perf_event.h kernel header). Specifically: * All named fields are added, including field names within anonymous unions and bitfields * Perf.perf_event_attr now complains when a field which isn't part of the ctype struct is set. * Goal here is to prevent users from setting a recently-added field - which we haven't updated the ctype _fields_ to include - and getting confused when it doesn't propagate to the perf_event_open syscall. This bit me in #3571 and I am pretty familiar with bcc internals so I'd like to prevent this from confusing others down the line. * Perf.perf_event_attr's 'flags' field is removed as it was a standin for the bitfields. The _old_ profile.py was the only script in bcc tools that I could find using this. The last bullet is a breaking change. Although `tools/old/profile.py` has been migrated to use the bitfield it was flipping using `flags`, there could be some scripts out in the wild which break. I don't think this is likely: this stuff hasn't been significantly touched since 2016 and I suspect if users of the python interface were writing lots of perf_event programs we would've seen more python tools or activity here. Regardless, there is probably a way to keep `flags` field working while also exposing named bitfields, but I suspect it'll be ugly and wanted to see if anyone thought it was necessary. --- src/python/bcc/perf.py | 119 ++++++++++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 20 deletions(-) diff --git a/src/python/bcc/perf.py b/src/python/bcc/perf.py index b1c13f72..51327437 100644 --- a/src/python/bcc/perf.py +++ b/src/python/bcc/perf.py @@ -16,32 +16,114 @@ import ctypes as ct import os from .utils import get_online_cpus +class _sample_period_union(ct.Union): + _fields_ = [ + ('sample_period', ct.c_ulong), + ('sample_freq', ct.c_ulong), + ] + +class _wakeup_events_union(ct.Union): + _fields_ = [ + ('wakeup_events', ct.c_uint), + ('wakeup_watermark', ct.c_uint), + ] + +class _bp_addr_union(ct.Union): + _fields_ = [ + ('bp_addr', ct.c_ulong), + ('kprobe_func', ct.c_ulong), + ('uprobe_path', ct.c_ulong), + ('config1', ct.c_ulong), + ] + +class _bp_len_union(ct.Union): + _fields_ = [ + ('bp_len', ct.c_ulong), + ('kprobe_addr', ct.c_ulong), + ('probe_offset', ct.c_ulong), + ('config2', ct.c_ulong), + ] + class Perf(object): + class perf_event_attr(ct.Structure): + _anonymous_ = [ + "_sample_period_union", + "_wakeup_events_union", + "_bp_addr_union", + "_bp_len_union" + ] + _fields_ = [ ('type', ct.c_uint), ('size', ct.c_uint), ('config', ct.c_ulong), - ('sample_period', ct.c_ulong), + ('_sample_period_union', _sample_period_union), # ct.c_ulong ('sample_type', ct.c_ulong), ('read_format', ct.c_ulong), - ('flags', ct.c_ulong), - ('wakeup_events', ct.c_uint), - ('IGNORE3', ct.c_uint), # bp_type - ('IGNORE4', ct.c_ulong), # bp_addr - ('IGNORE5', ct.c_ulong), # bp_len - ('IGNORE6', ct.c_ulong), # branch_sample_type - ('IGNORE7', ct.c_ulong), # sample_regs_user - ('IGNORE8', ct.c_uint), # sample_stack_user - ('IGNORE9', ct.c_int), # clockid - ('IGNORE10', ct.c_ulong), # sample_regs_intr - ('IGNORE11', ct.c_uint), # aux_watermark - ('IGNORE12', ct.c_uint16), # sample_max_stack - ('IGNORE13', ct.c_uint16), # __reserved_2 - ('IGNORE14', ct.c_uint), # aux_sample_size - ('IGNORE15', ct.c_uint), # __reserved_3 + ('disabled', ct.c_uint, 1), + ('inherit', ct.c_uint, 1), + ('pinned', ct.c_uint, 1), + ('exclusive', ct.c_uint, 1), + ('exclude_user', ct.c_uint, 1), + ('exclude_kernel', ct.c_uint, 1), + ('exclude_hv', ct.c_uint, 1), + ('exclude_idle', ct.c_uint, 1), + ('mmap', ct.c_uint, 1), + ('comm', ct.c_uint, 1), + ('freq', ct.c_uint, 1), + ('inherit_stat', ct.c_uint, 1), + ('enable_on_exec', ct.c_uint, 1), + ('task', ct.c_uint, 1), + ('watermark', ct.c_uint, 1), + ('precise_ip', ct.c_uint, 2), + ('mmap_data', ct.c_uint, 1), + ('sample_id_all', ct.c_uint, 1), + ('exclude_host', ct.c_uint, 1), + ('exclude_guest', ct.c_uint, 1), + ('exclude_callchain_kernel', ct.c_uint, 1), + ('exclude_callchain_user', ct.c_uint, 1), + ('mmap2', ct.c_uint, 1), + ('comm_exec', ct.c_uint, 1), + ('use_clockid', ct.c_uint, 1), + ('context_switch', ct.c_uint, 1), + ('write_backward', ct.c_uint, 1), + ('namespaces', ct.c_uint, 1), + ('ksymbol', ct.c_uint, 1), + ('bpf_event', ct.c_uint, 1), + ('aux_output', ct.c_uint, 1), + ('cgroup', ct.c_uint, 1), + ('text_poke', ct.c_uint, 1), + ('__reserved_1', ct.c_uint, 30), + ('_wakeup_events_union', _wakeup_events_union), # ct.c_uint + ('bp_type', ct.c_uint), + ('_bp_addr_union', _bp_addr_union), # ct.c_ulong + ('_bp_len_union', _bp_len_union), # ct.c_ulong + ('branch_sample_type', ct.c_ulong), + ('sample_regs_user', ct.c_ulong), + ('sample_stack_user', ct.c_uint), + ('clockid', ct.c_int), + ('sample_regs_intr', ct.c_ulong), + ('aux_watermark', ct.c_uint), + ('sample_max_stack', ct.c_uint16), + ('__reserved_2', ct.c_uint16), + ('aux_sample_size', ct.c_uint), + ('__reserved_3', ct.c_uint), ] + def __init__(self): + self.size = 120 # PERF_ATTR_SIZE_VER6 + self.ctype_fields = [item[0] for item in self._fields_] + self.ctype_fields.extend([item[0] for item in _sample_period_union._fields_]) + self.ctype_fields.extend([item[0] for item in _wakeup_events_union._fields_]) + self.ctype_fields.extend([item[0] for item in _bp_addr_union._fields_]) + self.ctype_fields.extend([item[0] for item in _bp_len_union._fields_]) + + def __setattr__(self, key, value): + if hasattr(self, 'ctype_fields') and key not in self.ctype_fields: + print("Warning: Setting field {} on perf_event_attr that isn't part of the ctype - {} won't make it to perf_event_open".format(key, key)) + super(Perf.perf_event_attr, self).__setattr__(key, value) + # x86 specific, from arch/x86/include/generated/uapi/asm/unistd_64.h NR_PERF_EVENT_OPEN = 298 @@ -59,9 +141,6 @@ class Perf(object): # perf_event_sample_format PERF_SAMPLE_RAW = 1024 # it's a u32; could also try zero args - # perf_event_attr - PERF_ATTR_FLAG_FREQ = 1024 - # perf_event.h PERF_FLAG_FD_CLOEXEC = 8 PERF_EVENT_IOC_SET_FILTER = 1074275334 @@ -103,7 +182,7 @@ class Perf(object): attr.sample_type = Perf.PERF_SAMPLE_RAW if freq > 0: # setup sampling - attr.flags = Perf.PERF_ATTR_FLAG_FREQ # no mmap or comm + attr.freq = 1 # no mmap or comm attr.sample_period = freq else: attr.sample_period = 1 -- 2.34.1