From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pg1-f197.google.com (mail-pg1-f197.google.com [209.85.215.197]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D7B6A3546C6 for ; Mon, 21 Sep 2026 05:07:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.197 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789967257; cv=none; b=BfuDnRtM5rcWmIcwKaT2SBZQqnFuPYORaa9RwgiEqGHwEPeVhZV0QS13aUvjNEiTRxmXHnf8uCwYuxktyceRtkDJCcRj4DvKAtYw3jaIzjp78asDnQ/NhhczYFHlTZ/1+rcZfe+CoqN6oAIxzaa0IW8AJr0QR7Nl8+9hXyZ5qps= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789967257; c=relaxed/simple; bh=RsfbNX1q8VR474P15vkzO2zpl+30AidBKmscqxm2T9Q=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=H8XZsoFjez3g4kpD7vn/m+L8hMTYx0qM6qu5mXvClp+iKqfCOVHO4dz2awOUCwHryHaoAWkkFjwpJtZ6ix9QiDD2DFYAtOzzvPZEkfTVGFs9LWpt0qPZgOTHrV1MG3imxw9NvH7Pr3kXPiUpOaz4+SAGYBv21VlmtXGvAJ+VNFQ= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=ep1Wy/1i; arc=none smtp.client-ip=209.85.215.197 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="ep1Wy/1i" Received: by mail-pg1-f197.google.com with SMTP id 41be03b00d2f7-cbb20f82a0eso1942612a12.0 for ; Sun, 20 Sep 2026 22:07:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1789967255; x=1790572055; darn=vger.kernel.org; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:from:to:cc:subject:date:message-id:reply-to :content-type; bh=RLFgidr120htM5oKPYXLVzJEWN9PgK5GeqQdNIklz6E=; b=ep1Wy/1ijzn/TM5elCVnvfqV9ysElsfDLPQFmo1I1uZf+T9kXBOZ+FfWwOVkb7y/kS rE+7i7vFqRoIqj+Xb9kFlbd/BdcdqDWVd9vcmxgfIdqnhTWtQ0ioSHBcG5N0+AUz5k0Q C6ojkTbM+EyuBghfP4gEEo76WpANrSwmfSFYWhsc9HwGSeZhFpXenZ2eFrz+h6z3GfEm QpBlljTKMIRRjoeISHofrSIQF9l8AKzZ5Cy3sAVjejEOfESsS+p9uF4F/mSlsHQwkeoH udAk5zfKEcAdU433/lVmOugXT4GFRENN7/EvSJo6pk47SscxDJMC3x4vBS/mAxqv6CXZ SGxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789967255; x=1790572055; h=content-type:cc:to:from:subject:message-id:references:mime-version :in-reply-to:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=RLFgidr120htM5oKPYXLVzJEWN9PgK5GeqQdNIklz6E=; b=Zq02E+9lsDjBVFw/k8Dr2XQIrCfTwPc9djHe9B9dcvRnxbPSmA093wpQ2p+Y/z4yxS vEE3YCo/HDERFluM+h+lWuB75DA6TlEIOOvwR965c8WY2hcXWDDEPszu/7E5tmvGxkee Vf8dYnSjAP3RjMHqfL7lqWHeiBr2vaGPVox7dyeM8X8SsGYnBGPPEDzYrme2zuIENYfK yT3yZOR3jViYesq9Y8npESZzdLlOCkrC/6WN7SzlkJtob4/SumlhUN4CKm4bU72az/rA 0sT79HFbKuRnKMRcN6QUkJFoCWcEyH6hXKQ97HarxX0fGGIeSvf51183a4PcGg0NguFM 3mFA== X-Forwarded-Encrypted: i=1; AKwUvBxKUzH6r5bN/LIqYvQEtFYALfl7zlhqK9iCPF2hhorQLVY2yDFYG60DnAFmGJJ4Ar8E5GTRJcJqxbq9Tbc=@vger.kernel.org X-Gm-Message-State: AFuF++n6Q7q5g6sfCQrvDEmnDeoqrOV3f32LfLjUbPh1RSY5x8RAzeQx iO4KTHxaYp65bE8XauJbdm3rr4H51utBZZQ/KxBcfEUqnrAaDktW+3ylmgjeUKUjKxp82YPd5wB JSAB7Ojh5BQ== X-Received: from dybmx14.prod.google.com ([2002:a05:7300:d40e:b0:33b:f8f7:a552]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6a20:4312:b0:3dd:a196:308f with SMTP id adf61e73a8af0-3dda1963408mr8175453637.63.1789967254902; Sun, 20 Sep 2026 22:07:34 -0700 (PDT) Date: Sun, 20 Sep 2026 22:06:26 -0700 In-Reply-To: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: X-Mailer: git-send-email 2.55.0.1082.g2b9226bbc0-goog Message-ID: <5116186e1258404cbbc7e15d145d463ab71e2491.1789966896.git.irogers@google.com> Subject: [PATCH v2 08/49] perf python: Expose addr location, transaction, and context_switch From: Ian Rogers To: irogers@google.com, acme@kernel.org, alice.mei.rogers@gmail.com, linux-perf-users@vger.kernel.org, namhyung@kernel.org Cc: adrian.hunter@intel.com, dapeng1.mi@linux.intel.com, james.clark@linaro.org, leo.yan@linux.dev, linux-kernel@vger.kernel.org, mingo@redhat.com, peterz@infradead.org, tmricht@linux.ibm.com Content-Type: text/plain; charset="UTF-8" Expose destination address location components (addr_dso, addr_symbol, addr_sym_offset), branch/transaction metrics (branch_type, in_tx, flags, transaction), machine_pid, vcpu, sym_offset, and context_switch callbacks directly to the Python extension. Also include supporting fixes and infrastructure updates in util/python.c and perf.pyi: - Support fetching sym_offset on resolved sample symbols. - Populate sample machine_pid and vcpu from evlist__id2sid() when perf_guest is enabled, and resolve samples against the matching guest or host machine. - Expose next_prev_pid and next_prev_tid on switch_event only for PERF_RECORD_SWITCH_CPU_WIDE records (returning None otherwise). - Wire up tracing_data in pyrf_session__new(), use 'static char * const kwlist[]', and update perf.pyi type stubs (including srccode() and insn() return types). Signed-off-by: Ian Rogers --- tools/perf/python/perf.pyi | 22 +++- tools/perf/util/python.c | 249 +++++++++++++++++++++++++++++++++++-- 2 files changed, 253 insertions(+), 18 deletions(-) diff --git a/tools/perf/python/perf.pyi b/tools/perf/python/perf.pyi index 7217bca05821..a8d0e61e8d10 100644 --- a/tools/perf/python/perf.pyi +++ b/tools/perf/python/perf.pyi @@ -182,6 +182,8 @@ class _sample_members: sample_time: int sample_id: int sample_stream_id: int + machine_pid: int + vcpu: int sample_period: int sample_cpu: int @@ -206,10 +208,18 @@ class sample_event(_sample_members): symbol: str sym_start: int sym_end: int + sym_offset: Optional[int] + addr_dso: Optional[str] + addr_symbol: Optional[str] + addr_sym_offset: Optional[int] + branch_type: int + in_tx: int + flags: int + transaction: int brstack: Optional['branch_stack'] callchain: Optional['callchain'] - def srccode(self) -> str: ... - def insn(self) -> str: ... + def srccode(self) -> Optional[tuple[Optional[str], int, Optional[str]]]: ... + def insn(self) -> Optional[bytes]: ... def __getattr__(self, name: str) -> Any: ... class mmap_event(_sample_members): @@ -286,8 +296,9 @@ class read_event(_sample_members): class switch_event(_sample_members): """Represents a SWITCH or SWITCH_CPU_WIDE record.""" type: int - next_prev_pid: int - next_prev_tid: int + misc: int + next_prev_pid: Optional[int] + next_prev_tid: Optional[int] evsel: Optional['evsel'] class branch_entry: @@ -429,7 +440,8 @@ class session: self, data: data, sample: Optional[Callable[[sample_event], None]] = None, - stat: Optional[Callable[[Any, Optional[str]], None]] = None + stat: Optional[Callable[[Any, Optional[str]], None]] = None, + context_switch: Optional[Callable[[switch_event], None]] = None, ) -> None: """Initialize a perf session. diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index fbfa71b1c4b6..49f37198f4b3 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -106,6 +107,8 @@ struct pyrf_event { sample_member_def(sample_time, time, T_ULONGLONG, "event timestamp"), \ sample_member_def(sample_id, id, T_ULONGLONG, "event id"), \ sample_member_def(sample_stream_id, stream_id, T_ULONGLONG, "event stream id"), \ + sample_member_def(machine_pid, machine_pid, T_UINT, "event machine pid"), \ + sample_member_def(vcpu, vcpu, T_UINT, "event vcpu"), \ sample_member_def(sample_period, period, T_ULONGLONG, "event period"), \ sample_member_def(sample_cpu, cpu, T_UINT, "event cpu"), @@ -581,6 +584,7 @@ static PyMemberDef pyrf_sample_event__members[] = { sample_member_def(sample_data_src, data_src, T_ULONGLONG, "event data source"), sample_member_def(sample_insn_count, insn_cnt, T_ULONGLONG, "event instruction count"), sample_member_def(sample_cyc_count, cyc_cnt, T_ULONGLONG, "event cycle count"), + sample_member_def(flags, flags, T_UINT, "event flags"), member_def(perf_event_header, type, T_UINT, "event type"), { .name = NULL, }, }; @@ -679,6 +683,7 @@ static int pyrf_sample_event__resolve_al(struct pyrf_event *pevent) struct evsel *evsel = pevent->sample.evsel; struct evlist *evlist = evsel ? evsel->evlist : NULL; struct perf_session *session = evlist ? evlist__session(evlist) : NULL; + struct machine *machine; if (pevent->al_resolved) return 0; @@ -686,8 +691,14 @@ static int pyrf_sample_event__resolve_al(struct pyrf_event *pevent) if (!session) return -1; + machine = pevent->sample.machine_pid ? + machines__find(&session->machines, pevent->sample.machine_pid) : + &session->machines.host; + if (!machine) + machine = &session->machines.host; + addr_location__init(&pevent->al); - if (machine__resolve(&session->machines.host, &pevent->al, &pevent->sample) < 0) { + if (machine__resolve(machine, &pevent->al, &pevent->sample) < 0) { addr_location__exit(&pevent->al); return -1; } @@ -771,6 +782,15 @@ static PyObject *pyrf_sample_event__get_sym_start(struct pyrf_event *pevent, return PyLong_FromUnsignedLongLong(pevent->al.sym->start); } +static PyObject *pyrf_sample_event__get_sym_offset(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.sym) + Py_RETURN_NONE; + + return PyLong_FromUnsignedLongLong(pevent->al.addr - pevent->al.sym->start); +} + static PyObject *pyrf_sample_event__get_sym_end(struct pyrf_event *pevent, void *closure __maybe_unused) { @@ -1166,7 +1186,115 @@ pyrf_sample_event__getattro(struct pyrf_event *pevent, PyObject *attr_name) return obj ?: PyObject_GenericGetAttr((PyObject *) pevent, attr_name); } + +static int pyrf_sample_event__resolve_addr_al(struct pyrf_event *pevent, + struct addr_location *addr_al) +{ + addr_location__init(addr_al); + if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.thread) + return -1; + + thread__find_symbol_fb(pevent->al.thread, pevent->sample.cpumode, + pevent->sample.addr, addr_al); + return 0; +} + +static PyObject *pyrf_sample_event__get_addr_dso(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + struct addr_location addr_al; + PyObject *ret = Py_None; + + if (pyrf_sample_event__resolve_addr_al(pevent, &addr_al) == 0 && addr_al.map) + ret = PyUnicode_FromString(dso__name(map__dso(addr_al.map))); + else + Py_INCREF(Py_None); + + addr_location__exit(&addr_al); + return ret; +} + +static PyObject *pyrf_sample_event__get_addr_symbol(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + struct addr_location addr_al; + PyObject *ret = Py_None; + + if (pyrf_sample_event__resolve_addr_al(pevent, &addr_al) == 0 && addr_al.sym) + ret = PyUnicode_FromString(addr_al.sym->name); + else + Py_INCREF(Py_None); + + addr_location__exit(&addr_al); + return ret; +} + +static PyObject *pyrf_sample_event__get_addr_sym_offset(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + struct addr_location addr_al; + PyObject *ret = Py_None; + + if (pyrf_sample_event__resolve_addr_al(pevent, &addr_al) == 0 && addr_al.sym) + ret = PyLong_FromUnsignedLongLong(addr_al.addr - addr_al.sym->start); + else + Py_INCREF(Py_None); + + addr_location__exit(&addr_al); + return ret; +} + +static PyObject *pyrf_sample_event__get_branch_type(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLong(pevent->sample.flags & PERF_BRANCH_MASK); +} + +static PyObject *pyrf_sample_event__get_in_tx(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLong(!!(pevent->sample.flags & PERF_IP_FLAG_IN_TX)); +} + +static PyObject *pyrf_sample_event__get_transaction(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLongLong(pevent->sample.transaction); +} + static PyGetSetDef pyrf_sample_event__getset[] = { + + { + .name = "addr_dso", + .get = (getter)pyrf_sample_event__get_addr_dso, + .doc = "event destination dso.", + }, + { + .name = "addr_symbol", + .get = (getter)pyrf_sample_event__get_addr_symbol, + .doc = "event destination symbol.", + }, + { + .name = "addr_sym_offset", + .get = (getter)pyrf_sample_event__get_addr_sym_offset, + .doc = "event destination symbol offset.", + }, + { + .name = "branch_type", + .get = (getter)pyrf_sample_event__get_branch_type, + .doc = "branch type.", + }, + { + .name = "in_tx", + .get = (getter)pyrf_sample_event__get_in_tx, + .doc = "in transaction flag.", + }, + { + .name = "transaction", + .get = (getter)pyrf_sample_event__get_transaction, + .doc = "transaction execution.", + }, + { .name = "callchain", .get = pyrf_sample_event__get_callchain, @@ -1227,6 +1355,12 @@ static PyGetSetDef pyrf_sample_event__getset[] = { .set = NULL, .doc = "event map page offset.", }, + { + .name = "sym_offset", + .get = (getter)pyrf_sample_event__get_sym_offset, + .set = NULL, + .doc = "event symbol offset.", + }, { .name = "symbol", .get = (getter)pyrf_sample_event__get_symbol, @@ -1283,8 +1417,45 @@ static const char pyrf_context_switch_event__doc[] = PyDoc_STR("perf context_swi static PyMemberDef pyrf_context_switch_event__members[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), - member_def(perf_record_switch, next_prev_pid, T_UINT, "next/prev pid"), - member_def(perf_record_switch, next_prev_tid, T_UINT, "next/prev tid"), + member_def(perf_event_header, misc, T_USHORT, "event misc"), + { .name = NULL, }, +}; + +static PyObject *pyrf_context_switch_event__get_next_prev_pid(const struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + if (pevent->event.header.type == PERF_RECORD_SWITCH_CPU_WIDE) + return PyLong_FromUnsignedLong(pevent->event.context_switch.next_prev_pid); + Py_RETURN_NONE; +} + +static PyObject *pyrf_context_switch_event__get_next_prev_tid(const struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + if (pevent->event.header.type == PERF_RECORD_SWITCH_CPU_WIDE) + return PyLong_FromUnsignedLong(pevent->event.context_switch.next_prev_tid); + Py_RETURN_NONE; +} + +static PyGetSetDef pyrf_context_switch_event__getset[] = { + { + .name = "evsel", + .get = pyrf_event__get_evsel, + .set = NULL, + .doc = "tracking event.", + }, + { + .name = "next_prev_pid", + .get = (getter)pyrf_context_switch_event__get_next_prev_pid, + .set = NULL, + .doc = "next/prev pid for CPU-wide switch, or None.", + }, + { + .name = "next_prev_tid", + .get = (getter)pyrf_context_switch_event__get_next_prev_tid, + .set = NULL, + .doc = "next/prev tid for CPU-wide switch, or None.", + }, { .name = NULL, }, }; @@ -1292,11 +1463,19 @@ static PyObject *pyrf_context_switch_event__repr(const struct pyrf_event *pevent { PyObject *ret; char *s; - - if (asprintf(&s, "{ type: context_switch, next_prev_pid: %u, next_prev_tid: %u, switch_out: %u }", - pevent->event.context_switch.next_prev_pid, - pevent->event.context_switch.next_prev_tid, - !!(pevent->event.header.misc & PERF_RECORD_MISC_SWITCH_OUT)) < 0) { + int res; + + if (pevent->event.header.type == PERF_RECORD_SWITCH_CPU_WIDE) { + res = asprintf(&s, + "{ type: context_switch, next_prev_pid: %u, next_prev_tid: %u, switch_out: %u }", + pevent->event.context_switch.next_prev_pid, + pevent->event.context_switch.next_prev_tid, + !!(pevent->event.header.misc & PERF_RECORD_MISC_SWITCH_OUT)); + } else { + res = asprintf(&s, "{ type: context_switch, switch_out: %u }", + !!(pevent->event.header.misc & PERF_RECORD_MISC_SWITCH_OUT)); + } + if (res < 0) { ret = PyErr_NoMemory(); } else { ret = PyUnicode_FromString(s); @@ -1313,7 +1492,7 @@ static PyTypeObject pyrf_context_switch_event__type = { .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_context_switch_event__doc, .tp_members = pyrf_context_switch_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_context_switch_event__getset, .tp_repr = (reprfunc)pyrf_context_switch_event__repr, }; @@ -1450,6 +1629,16 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev return PyErr_Format(PyExc_OSError, "perf: can't parse sample, err=%d", err); } + if (session && session->evlist && perf_guest && pevent->sample.id) { + struct perf_sample_id *sid = evlist__id2sid(session->evlist, pevent->sample.id); + + if (sid) { + pevent->sample.machine_pid = sid->machine_pid; + pevent->sample.vcpu = sid->vcpu.cpu; + } + } + if (machine && machine->pid > 0 && !pevent->sample.machine_pid) + pevent->sample.machine_pid = machine->pid; sample = &pevent->sample; if (machine && sample->callchain) { struct addr_location al; @@ -3837,6 +4026,7 @@ struct pyrf_session { struct pyrf_data *pdata; PyObject *sample; PyObject *stat; + PyObject *context_switch; }; static int pyrf_session_tool__sample(const struct perf_tool *tool, @@ -3861,6 +4051,33 @@ static int pyrf_session_tool__sample(const struct perf_tool *tool, return 0; } +static int pyrf_session_tool__context_switch(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + PyObject *pyevent = pyrf_event__new(event, sample->evsel, psession->session, machine); + PyObject *ret; + + if (perf_event__process_switch(tool, event, sample, machine) < 0) { + Py_XDECREF(pyevent); + return -1; + } + + if (pyevent == NULL) + return -ENOMEM; + + ret = PyObject_CallFunction(psession->context_switch, "O", pyevent); + if (!ret) { + Py_DECREF(pyevent); + return -1; + } + Py_DECREF(ret); + Py_DECREF(pyevent); + return 0; +} + static int pyrf_session_tool__stat(const struct perf_tool *tool, struct perf_session *session, union perf_event *event) @@ -3943,13 +4160,13 @@ static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObje static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { struct pyrf_data *pdata; - PyObject *sample = NULL, *stat = NULL; - static char *kwlist[] = { "data", "sample", "stat", NULL }; + PyObject *sample = NULL, *stat = NULL, *context_switch = NULL; + static char * const kwlist[] = { "data", "sample", "stat", "context_switch", NULL }; struct pyrf_session *psession; struct perf_session *session; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|OO", kwlist, &pyrf_data__type, &pdata, - &sample, &stat)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|OOO", kwlist, &pyrf_data__type, &pdata, + &sample, &stat, &context_switch)) return NULL; psession = PyObject_New(struct pyrf_session, type); @@ -3959,6 +4176,7 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject psession->session = NULL; psession->sample = NULL; psession->stat = NULL; + psession->context_switch = NULL; psession->pdata = NULL; Py_INCREF(pdata); @@ -3982,6 +4200,7 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject ADD_TOOL(sample); ADD_TOOL(stat); + ADD_TOOL(context_switch); #undef ADD_TOOL if (stat) @@ -4000,6 +4219,9 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject psession->tool.build_id = perf_event__process_build_id; psession->tool.attr = perf_event__process_attr; psession->tool.feature = perf_event__process_feature; +#ifdef HAVE_LIBTRACEEVENT + psession->tool.tracing_data = perf_event__process_tracing_data; +#endif session = perf_session__new(&pdata->data, &psession->tool); if (IS_ERR(session)) { @@ -4030,6 +4252,7 @@ static void pyrf_session__delete(struct pyrf_session *psession) Py_XDECREF(psession->pdata); Py_XDECREF(psession->sample); Py_XDECREF(psession->stat); + Py_XDECREF(psession->context_switch); Py_TYPE(psession)->tp_free((PyObject *)psession); } -- 2.55.0.1082.g2b9226bbc0-goog