xref: /spdk/scripts/bpf/trace.py (revision a6e5d032dd7607f43596c0d2940111f1819dc313)
1b0d3f29aSKonrad Sztyber#!/usr/bin/env python3
217538bdcSpaul luse#  SPDX-License-Identifier: BSD-3-Clause
317538bdcSpaul luse#  Copyright (C) 2021 Intel Corporation
417538bdcSpaul luse#  All rights reserved.
517538bdcSpaul luse#
6b0d3f29aSKonrad Sztyber
7555ca7adSMike Gerdts
8b0d3f29aSKonrad Sztyberfrom argparse import ArgumentParser
9e61fbe91SKonrad Sztyberfrom dataclasses import dataclass, field
10e61fbe91SKonrad Sztyberfrom itertools import islice
11b0d3f29aSKonrad Sztyberfrom typing import Dict, List, TypeVar
125d5d9cbbSKonrad Sztyberimport ctypes as ct
13597688b2SKonrad Sztyberimport ijson
145d5d9cbbSKonrad Sztyberimport magic
1501ae68f7SKonrad Sztyberimport os
1601ae68f7SKonrad Sztyberimport re
1701ae68f7SKonrad Sztyberimport subprocess
18b0d3f29aSKonrad Sztyberimport sys
1901ae68f7SKonrad Sztyberimport tempfile
2001ae68f7SKonrad Sztyber
21e61fbe91SKonrad SztyberTSC_MAX = (1 << 64) - 1
225d5d9cbbSKonrad SztyberUCHAR_MAX = (1 << 8) - 1
23fc04b134SJim HarrisTRACE_MAX_LCORE = 1024
245d5d9cbbSKonrad SztyberTRACE_MAX_GROUP_ID = 16
255d5d9cbbSKonrad SztyberTRACE_MAX_TPOINT_ID = TRACE_MAX_GROUP_ID * 64
26e36f0d36SJim HarrisTRACE_MAX_ARGS_COUNT = 8
2770c17160SKrzysztof KarasTRACE_MAX_RELATIONS = 16
285d5d9cbbSKonrad SztyberTRACE_INVALID_OBJECT = (1 << 64) - 1
295d5d9cbbSKonrad SztyberOBJECT_NONE = 0
3026d44a12SJim HarrisOWNER_TYPE_NONE = 0
31e61fbe91SKonrad Sztyber
3201ae68f7SKonrad Sztyber
3301ae68f7SKonrad Sztyber@dataclass
3401ae68f7SKonrad Sztyberclass DTraceArgument:
3501ae68f7SKonrad Sztyber    """Describes a DTrace probe (usdt) argument"""
3601ae68f7SKonrad Sztyber    name: str
3701ae68f7SKonrad Sztyber    pos: int
3801ae68f7SKonrad Sztyber    type: type
3901ae68f7SKonrad Sztyber
4001ae68f7SKonrad Sztyber
4101ae68f7SKonrad Sztyber@dataclass
4201ae68f7SKonrad Sztyberclass DTraceProbe:
4301ae68f7SKonrad Sztyber    """Describes a DTrace probe (usdt) point"""
4401ae68f7SKonrad Sztyber    name: str
4501ae68f7SKonrad Sztyber    args: Dict[str, DTraceArgument]
4601ae68f7SKonrad Sztyber
4701ae68f7SKonrad Sztyber    def __init__(self, name, args):
4801ae68f7SKonrad Sztyber        self.name = name
4901ae68f7SKonrad Sztyber        self.args = {a.name: a for a in args}
5001ae68f7SKonrad Sztyber
5101ae68f7SKonrad Sztyber
5201ae68f7SKonrad Sztyber@dataclass
5301ae68f7SKonrad Sztyberclass DTraceEntry:
5401ae68f7SKonrad Sztyber    """Describes a single DTrace probe invocation"""
5501ae68f7SKonrad Sztyber    name: str
5601ae68f7SKonrad Sztyber    args: Dict[str, TypeVar('ArgumentType', str, int)]
5701ae68f7SKonrad Sztyber
5801ae68f7SKonrad Sztyber    def __init__(self, probe, args):
5901ae68f7SKonrad Sztyber        valmap = {int: lambda x: int(x, 16),
6001ae68f7SKonrad Sztyber                  str: lambda x: x.strip().strip("'")}
6101ae68f7SKonrad Sztyber        self.name = probe.name
6201ae68f7SKonrad Sztyber        self.args = {}
6301ae68f7SKonrad Sztyber        for name, value in args.items():
6401ae68f7SKonrad Sztyber            arg = probe.args.get(name)
6501ae68f7SKonrad Sztyber            if arg is None:
6601ae68f7SKonrad Sztyber                raise ValueError(f'Unexpected argument: {name}')
6701ae68f7SKonrad Sztyber            self.args[name] = valmap[arg.type](value)
6801ae68f7SKonrad Sztyber
6901ae68f7SKonrad Sztyber
7001ae68f7SKonrad Sztyberclass DTrace:
7101ae68f7SKonrad Sztyber    """Generates bpftrace script based on the supplied probe points, parses its
7201ae68f7SKonrad Sztyber    output and stores is as a list of DTraceEntry sorted by their tsc.
7301ae68f7SKonrad Sztyber    """
7401ae68f7SKonrad Sztyber    def __init__(self, probes, file=None):
7501ae68f7SKonrad Sztyber        self._avail_probes = self._list_probes()
7601ae68f7SKonrad Sztyber        self._probes = {p.name: p for p in probes}
7701ae68f7SKonrad Sztyber        self.entries = self._parse(file) if file is not None else []
7801ae68f7SKonrad Sztyber        # Sanitize the probe definitions
7901ae68f7SKonrad Sztyber        for probe in probes:
8001ae68f7SKonrad Sztyber            if probe.name not in self._avail_probes:
8101ae68f7SKonrad Sztyber                raise ValueError(f'Couldn\'t find probe: "{probe.name}"')
8201ae68f7SKonrad Sztyber            for arg in probe.args.values():
8301ae68f7SKonrad Sztyber                if arg.pos >= self._avail_probes[probe.name]:
8401ae68f7SKonrad Sztyber                    raise ValueError('Invalid probe argument position')
8501ae68f7SKonrad Sztyber                if arg.type not in (int, str):
8601ae68f7SKonrad Sztyber                    raise ValueError('Invalid argument type')
8701ae68f7SKonrad Sztyber
8801ae68f7SKonrad Sztyber    def _parse(self, file):
8901ae68f7SKonrad Sztyber        regex = re.compile(r'(\w+): (.*)')
9001ae68f7SKonrad Sztyber        entries = []
9101ae68f7SKonrad Sztyber
9201ae68f7SKonrad Sztyber        for line in file.readlines():
9301ae68f7SKonrad Sztyber            match = regex.match(line)
9401ae68f7SKonrad Sztyber            if match is None:
9501ae68f7SKonrad Sztyber                continue
9601ae68f7SKonrad Sztyber            name, args = match.groups()
9701ae68f7SKonrad Sztyber            probe = self._probes.get(name)
9801ae68f7SKonrad Sztyber            # Skip the line if we don't recognize the probe name
9901ae68f7SKonrad Sztyber            if probe is None:
10001ae68f7SKonrad Sztyber                continue
10101ae68f7SKonrad Sztyber            entries.append(DTraceEntry(probe, args=dict(a.strip().split('=')
10201ae68f7SKonrad Sztyber                                                        for a in args.split(','))))
10301ae68f7SKonrad Sztyber        entries.sort(key=lambda e: e.args['tsc'])
10401ae68f7SKonrad Sztyber        return entries
10501ae68f7SKonrad Sztyber
10601ae68f7SKonrad Sztyber    def _list_probes(self):
10701ae68f7SKonrad Sztyber        files = subprocess.check_output(['git', 'ls-files', '*.[ch]',
10801ae68f7SKonrad Sztyber                                        ':!:include/spdk_internal/usdt.h'])
10901ae68f7SKonrad Sztyber        files = filter(lambda f: len(f) > 0, str(files, 'ascii').split('\n'))
1107c30df4eSJim Harris        regex = re.compile(r'SPDK_DTRACE_PROBE([0-9]*)_TICKS\((\w+)')
11101ae68f7SKonrad Sztyber        probes = {}
11201ae68f7SKonrad Sztyber
11301ae68f7SKonrad Sztyber        for fname in files:
11401ae68f7SKonrad Sztyber            with open(fname, 'r') as file:
11501ae68f7SKonrad Sztyber                for match in regex.finditer(file.read()):
11601ae68f7SKonrad Sztyber                    nargs, name = match.group(1), match.group(2)
11701ae68f7SKonrad Sztyber                    nargs = int(nargs) if len(nargs) > 0 else 0
11801ae68f7SKonrad Sztyber                    # Add one to accommodate for the tsc being the first arg
11901ae68f7SKonrad Sztyber                    probes[name] = nargs + 1
12001ae68f7SKonrad Sztyber        return probes
12101ae68f7SKonrad Sztyber
12201ae68f7SKonrad Sztyber    def _gen_usdt(self, probe):
12301ae68f7SKonrad Sztyber        usdt = (f'usdt:__EXE__:{probe.name} {{' +
12401ae68f7SKonrad Sztyber                f'printf("{probe.name}: ')
12501ae68f7SKonrad Sztyber        args = probe.args
12601ae68f7SKonrad Sztyber        if len(args) > 0:
12701ae68f7SKonrad Sztyber            argtype = {int: '0x%lx', str: '\'%s\''}
12801ae68f7SKonrad Sztyber            argcast = {int: lambda x: x, str: lambda x: f'str({x})'}
12901ae68f7SKonrad Sztyber            argstr = [f'{a.name}={argtype[a.type]}' for a in args.values()]
13001ae68f7SKonrad Sztyber            argval = [f'{argcast[a.type](f"arg{a.pos}")}' for a in args.values()]
13101ae68f7SKonrad Sztyber            usdt += ', '.join(argstr) + '\\n", ' + ', '.join(argval)
13201ae68f7SKonrad Sztyber        else:
13301ae68f7SKonrad Sztyber            usdt += '\\n"'
13401ae68f7SKonrad Sztyber        usdt += ');}'
13501ae68f7SKonrad Sztyber        return usdt
13601ae68f7SKonrad Sztyber
13701ae68f7SKonrad Sztyber    def generate(self):
13801ae68f7SKonrad Sztyber        return '\n'.join([self._gen_usdt(p) for p in self._probes.values()])
13901ae68f7SKonrad Sztyber
14001ae68f7SKonrad Sztyber    def record(self, pid):
14101ae68f7SKonrad Sztyber        with tempfile.NamedTemporaryFile(mode='w+') as script:
14201ae68f7SKonrad Sztyber            script.write(self.generate())
14301ae68f7SKonrad Sztyber            script.flush()
14401ae68f7SKonrad Sztyber            try:
14501ae68f7SKonrad Sztyber                subprocess.run([f'{os.path.dirname(__file__)}/../bpftrace.sh',
14601ae68f7SKonrad Sztyber                                f'{pid}', f'{script.name}'])
14701ae68f7SKonrad Sztyber            except KeyboardInterrupt:
14801ae68f7SKonrad Sztyber                pass
149b0d3f29aSKonrad Sztyber
150b0d3f29aSKonrad Sztyber
151b0d3f29aSKonrad Sztyber@dataclass
152b0d3f29aSKonrad Sztyberclass TracepointArgument:
153b0d3f29aSKonrad Sztyber    """Describes an SPDK tracepoint argument"""
154b0d3f29aSKonrad Sztyber    TYPE_INT = 0
155b0d3f29aSKonrad Sztyber    TYPE_PTR = 1
156b0d3f29aSKonrad Sztyber    TYPE_STR = 2
157b0d3f29aSKonrad Sztyber    name: str
158b0d3f29aSKonrad Sztyber    argtype: int
159b0d3f29aSKonrad Sztyber
160b0d3f29aSKonrad Sztyber
161b0d3f29aSKonrad Sztyber@dataclass
162b0d3f29aSKonrad Sztyberclass Tracepoint:
163b0d3f29aSKonrad Sztyber    """Describes an SPDK tracepoint, equivalent to struct spdk_trace_tpoint"""
164b0d3f29aSKonrad Sztyber    name: str
165b0d3f29aSKonrad Sztyber    id: int
166b0d3f29aSKonrad Sztyber    new_object: bool
167b14196faSKonrad Sztyber    object_type: int
168b14196faSKonrad Sztyber    owner_type: int
169b0d3f29aSKonrad Sztyber    args: List[TracepointArgument]
170b0d3f29aSKonrad Sztyber
171b0d3f29aSKonrad Sztyber
172b0d3f29aSKonrad Sztyber@dataclass
173b0d3f29aSKonrad Sztyberclass TraceEntry:
174b0d3f29aSKonrad Sztyber    """Describes an SPDK tracepoint entry, equivalent to struct spdk_trace_entry"""
175b0d3f29aSKonrad Sztyber    lcore: int
176b0d3f29aSKonrad Sztyber    tpoint: Tracepoint
177b0d3f29aSKonrad Sztyber    tsc: int
178*a6e5d032SJim Harris    owner: str
179b0d3f29aSKonrad Sztyber    size: int
180b0d3f29aSKonrad Sztyber    object_id: str
181b0d3f29aSKonrad Sztyber    object_ptr: int
182b0d3f29aSKonrad Sztyber    time: int
183b0d3f29aSKonrad Sztyber    args: Dict[str, TypeVar('ArgumentType', str, int)]
1845594c7c8SKrzysztof Karas    related: str
185b0d3f29aSKonrad Sztyber
186b0d3f29aSKonrad Sztyber
187d1732fceSKonrad Sztyberclass TraceProvider:
188d1732fceSKonrad Sztyber    """Defines interface for objects providing traces and tracepoint definitions"""
189d1732fceSKonrad Sztyber
190d1732fceSKonrad Sztyber    def tpoints(self):
191d1732fceSKonrad Sztyber        """Returns tracepoint definitions as a dict of (tracepoint_name, tracepoint)"""
192d1732fceSKonrad Sztyber        raise NotImplementedError()
193d1732fceSKonrad Sztyber
194d1732fceSKonrad Sztyber    def entries(self):
195d1732fceSKonrad Sztyber        """Generator returning subsequent trace entries"""
196d1732fceSKonrad Sztyber        raise NotImplementedError()
197d1732fceSKonrad Sztyber
198d1732fceSKonrad Sztyber    def tsc_rate(self):
199d1732fceSKonrad Sztyber        """Returns the TSC rate that was in place when traces were collected"""
200d1732fceSKonrad Sztyber        raise NotImplementedError()
201d1732fceSKonrad Sztyber
202d1732fceSKonrad Sztyber
203d1732fceSKonrad Sztyberclass JsonProvider(TraceProvider):
204d1732fceSKonrad Sztyber    """Trace provider based on JSON-formatted output produced by spdk_trace app"""
205b0d3f29aSKonrad Sztyber    def __init__(self, file):
206597688b2SKonrad Sztyber        self._parser = ijson.parse(file)
207d1732fceSKonrad Sztyber        self._tpoints = {}
208597688b2SKonrad Sztyber        self._parse_defs()
209b0d3f29aSKonrad Sztyber
210597688b2SKonrad Sztyber    def _parse_tpoints(self, tpoints):
211597688b2SKonrad Sztyber        for tpoint in tpoints:
212597688b2SKonrad Sztyber            tpoint_id = tpoint['id']
213d1732fceSKonrad Sztyber            self._tpoints[tpoint_id] = Tracepoint(
214597688b2SKonrad Sztyber                name=tpoint['name'], id=tpoint_id,
215b14196faSKonrad Sztyber                new_object=tpoint['new_object'], object_type=OBJECT_NONE,
21626d44a12SJim Harris                owner_type=OWNER_TYPE_NONE,
217b0d3f29aSKonrad Sztyber                args=[TracepointArgument(name=a['name'],
218b0d3f29aSKonrad Sztyber                                         argtype=a['type'])
219b0d3f29aSKonrad Sztyber                      for a in tpoint.get('args', [])])
220b0d3f29aSKonrad Sztyber
221597688b2SKonrad Sztyber    def _parse_defs(self):
222597688b2SKonrad Sztyber        builder = None
223597688b2SKonrad Sztyber        for prefix, event, value in self._parser:
224597688b2SKonrad Sztyber            # If we reach entries array, there are no more tracepoint definitions
225597688b2SKonrad Sztyber            if prefix == 'entries':
226597688b2SKonrad Sztyber                break
227597688b2SKonrad Sztyber            elif prefix == 'tsc_rate':
228d1732fceSKonrad Sztyber                self._tsc_rate = value
229597688b2SKonrad Sztyber                continue
230597688b2SKonrad Sztyber
231597688b2SKonrad Sztyber            if (prefix, event) == ('tpoints', 'start_array'):
232597688b2SKonrad Sztyber                builder = ijson.ObjectBuilder()
233597688b2SKonrad Sztyber            if builder is not None:
234597688b2SKonrad Sztyber                builder.event(event, value)
235597688b2SKonrad Sztyber            if (prefix, event) == ('tpoints', 'end_array'):
236597688b2SKonrad Sztyber                self._parse_tpoints(builder.value)
237597688b2SKonrad Sztyber                builder = None
238597688b2SKonrad Sztyber
239b0d3f29aSKonrad Sztyber    def _parse_entry(self, entry):
240d1732fceSKonrad Sztyber        tpoint = self._tpoints[entry['tpoint']]
241b0d3f29aSKonrad Sztyber        obj = entry.get('object', {})
242b0d3f29aSKonrad Sztyber        return TraceEntry(tpoint=tpoint, lcore=entry['lcore'], tsc=entry['tsc'],
243b0d3f29aSKonrad Sztyber                          size=entry.get('size'), object_id=obj.get('id'),
2445594c7c8SKrzysztof Karas                          object_ptr=obj.get('value'), related=entry.get('related'),
245*a6e5d032SJim Harris                          time=obj.get('time'), owner=entry.get('owner'),
246b0d3f29aSKonrad Sztyber                          args={n.name: v for n, v in zip(tpoint.args, entry.get('args', []))})
247b0d3f29aSKonrad Sztyber
248d1732fceSKonrad Sztyber    def tsc_rate(self):
249d1732fceSKonrad Sztyber        return self._tsc_rate
250d1732fceSKonrad Sztyber
251d1732fceSKonrad Sztyber    def tpoints(self):
252d1732fceSKonrad Sztyber        return self._tpoints
253d1732fceSKonrad Sztyber
254d1732fceSKonrad Sztyber    def entries(self):
255597688b2SKonrad Sztyber        builder = None
256597688b2SKonrad Sztyber        for prefix, event, value in self._parser:
257597688b2SKonrad Sztyber            if (prefix, event) == ('entries.item', 'start_map'):
258597688b2SKonrad Sztyber                builder = ijson.ObjectBuilder()
259597688b2SKonrad Sztyber            if builder is not None:
260597688b2SKonrad Sztyber                builder.event(event, value)
261597688b2SKonrad Sztyber            if (prefix, event) == ('entries.item', 'end_map'):
262597688b2SKonrad Sztyber                yield self._parse_entry(builder.value)
263597688b2SKonrad Sztyber                builder = None
264b0d3f29aSKonrad Sztyber
265d1732fceSKonrad Sztyber
2665d5d9cbbSKonrad Sztyberclass CParserOpts(ct.Structure):
2675d5d9cbbSKonrad Sztyber    _fields_ = [('filename', ct.c_char_p),
2685d5d9cbbSKonrad Sztyber                ('mode', ct.c_int),
2695d5d9cbbSKonrad Sztyber                ('lcore', ct.c_uint16)]
2705d5d9cbbSKonrad Sztyber
2715d5d9cbbSKonrad Sztyber
2725d5d9cbbSKonrad Sztyberclass CTraceOwner(ct.Structure):
2735d5d9cbbSKonrad Sztyber    _fields_ = [('type', ct.c_uint8),
2745d5d9cbbSKonrad Sztyber                ('id_prefix', ct.c_char)]
2755d5d9cbbSKonrad Sztyber
2765d5d9cbbSKonrad Sztyber
2775d5d9cbbSKonrad Sztyberclass CTraceObject(ct.Structure):
2785d5d9cbbSKonrad Sztyber    _fields_ = [('type', ct.c_uint8),
2795d5d9cbbSKonrad Sztyber                ('id_prefix', ct.c_char)]
2805d5d9cbbSKonrad Sztyber
2815d5d9cbbSKonrad Sztyber
2825d5d9cbbSKonrad Sztyberclass CTpointArgument(ct.Structure):
2835d5d9cbbSKonrad Sztyber    _fields_ = [('name', ct.c_char * 14),
2845d5d9cbbSKonrad Sztyber                ('type', ct.c_uint8),
2855d5d9cbbSKonrad Sztyber                ('size', ct.c_uint8)]
2865d5d9cbbSKonrad Sztyber
2875d5d9cbbSKonrad Sztyber
28870c17160SKrzysztof Karasclass CTpointRelatedObject(ct.Structure):
28970c17160SKrzysztof Karas    _fields_ = [('object_type', ct.c_uint8),
29070c17160SKrzysztof Karas                ('arg_index', ct.c_uint8)]
29170c17160SKrzysztof Karas
29270c17160SKrzysztof Karas
2935d5d9cbbSKonrad Sztyberclass CTracepoint(ct.Structure):
2945d5d9cbbSKonrad Sztyber    _fields_ = [('name', ct.c_char * 24),
2955d5d9cbbSKonrad Sztyber                ('tpoint_id', ct.c_uint16),
2965d5d9cbbSKonrad Sztyber                ('owner_type', ct.c_uint8),
2975d5d9cbbSKonrad Sztyber                ('object_type', ct.c_uint8),
2985d5d9cbbSKonrad Sztyber                ('new_object', ct.c_uint8),
2995d5d9cbbSKonrad Sztyber                ('num_args', ct.c_uint8),
30070c17160SKrzysztof Karas                ('args', CTpointArgument * TRACE_MAX_ARGS_COUNT),
30170c17160SKrzysztof Karas                ('related_objects', CTpointRelatedObject * TRACE_MAX_RELATIONS)]
3025d5d9cbbSKonrad Sztyber
3035d5d9cbbSKonrad Sztyber
3046da17199SJim Harrisclass CTraceFile(ct.Structure):
3056da17199SJim Harris    _fields_ = [('file_size', ct.c_uint64),
3066da17199SJim Harris                ('tsc_rate', ct.c_uint64),
3075d5d9cbbSKonrad Sztyber                ('tpoint_mask', ct.c_uint64 * TRACE_MAX_GROUP_ID),
3085d5d9cbbSKonrad Sztyber                ('owner', CTraceOwner * (UCHAR_MAX + 1)),
3095d5d9cbbSKonrad Sztyber                ('object', CTraceObject * (UCHAR_MAX + 1)),
3105d5d9cbbSKonrad Sztyber                ('tpoint', CTracepoint * TRACE_MAX_TPOINT_ID)]
3115d5d9cbbSKonrad Sztyber
3125d5d9cbbSKonrad Sztyber
3135d5d9cbbSKonrad Sztyberclass CTraceEntry(ct.Structure):
3145d5d9cbbSKonrad Sztyber    _fields_ = [('tsc', ct.c_uint64),
3155d5d9cbbSKonrad Sztyber                ('tpoint_id', ct.c_uint16),
316*a6e5d032SJim Harris                ('owner_id', ct.c_uint16),
3175d5d9cbbSKonrad Sztyber                ('size', ct.c_uint32),
3185d5d9cbbSKonrad Sztyber                ('object_id', ct.c_uint64)]
3195d5d9cbbSKonrad Sztyber
3205d5d9cbbSKonrad Sztyber
3215d5d9cbbSKonrad Sztyberclass CTraceParserArgument(ct.Union):
3225d5d9cbbSKonrad Sztyber    _fields_ = [('integer', ct.c_uint64),
3235d5d9cbbSKonrad Sztyber                ('pointer', ct.c_void_p),
3245d5d9cbbSKonrad Sztyber                ('string', ct.c_char * (UCHAR_MAX + 1))]
3255d5d9cbbSKonrad Sztyber
3265d5d9cbbSKonrad Sztyber
3275d5d9cbbSKonrad Sztyberclass CTraceParserEntry(ct.Structure):
3285d5d9cbbSKonrad Sztyber    _fields_ = [('entry', ct.POINTER(CTraceEntry)),
3295d5d9cbbSKonrad Sztyber                ('object_index', ct.c_uint64),
3305d5d9cbbSKonrad Sztyber                ('object_start', ct.c_uint64),
3315d5d9cbbSKonrad Sztyber                ('lcore', ct.c_uint16),
33270c17160SKrzysztof Karas                ('related_index', ct.c_uint64),
33370c17160SKrzysztof Karas                ('related_type', ct.c_uint8),
3345d5d9cbbSKonrad Sztyber                ('args', CTraceParserArgument * TRACE_MAX_ARGS_COUNT)]
3355d5d9cbbSKonrad Sztyber
3365d5d9cbbSKonrad Sztyber
3375d5d9cbbSKonrad Sztyberclass NativeProvider(TraceProvider):
3385d5d9cbbSKonrad Sztyber    """Trace provider based on SPDK's trace library"""
3395d5d9cbbSKonrad Sztyber    def __init__(self, file):
3405d5d9cbbSKonrad Sztyber        self._setup_binding(file.name)
3415d5d9cbbSKonrad Sztyber        self._parse_defs()
3425d5d9cbbSKonrad Sztyber
3435d5d9cbbSKonrad Sztyber    def __del__(self):
3445d5d9cbbSKonrad Sztyber        if hasattr(self, '_parser'):
3455d5d9cbbSKonrad Sztyber            self._lib.spdk_trace_parser_cleanup(self._parser)
3465d5d9cbbSKonrad Sztyber
3475d5d9cbbSKonrad Sztyber    def _setup_binding(self, filename):
3485d5d9cbbSKonrad Sztyber        self._lib = ct.CDLL('build/lib/libspdk_trace_parser.so')
3495d5d9cbbSKonrad Sztyber        self._lib.spdk_trace_parser_init.restype = ct.c_void_p
3505d5d9cbbSKonrad Sztyber        self._lib.spdk_trace_parser_init.errcheck = lambda r, *_: ct.c_void_p(r)
35123dbcec5SJim Harris        self._lib.spdk_trace_parser_get_file.restype = ct.POINTER(CTraceFile)
3525d5d9cbbSKonrad Sztyber        opts = CParserOpts(filename=bytes(filename, 'ascii'), mode=0,
3535d5d9cbbSKonrad Sztyber                           lcore=TRACE_MAX_LCORE)
3545d5d9cbbSKonrad Sztyber        self._parser = self._lib.spdk_trace_parser_init(ct.byref(opts))
3555d5d9cbbSKonrad Sztyber        if not self._parser:
3565d5d9cbbSKonrad Sztyber            raise ValueError('Failed to construct SPDK trace parser')
3575d5d9cbbSKonrad Sztyber
3585d5d9cbbSKonrad Sztyber    def _parse_tpoints(self, tpoints):
3595d5d9cbbSKonrad Sztyber        self._tpoints = {}
3605d5d9cbbSKonrad Sztyber        for tpoint in tpoints:
3615d5d9cbbSKonrad Sztyber            if len(tpoint.name) == 0:
3625d5d9cbbSKonrad Sztyber                continue
3635d5d9cbbSKonrad Sztyber            self._tpoints[tpoint.tpoint_id] = Tracepoint(
3645d5d9cbbSKonrad Sztyber                name=str(tpoint.name, 'ascii'), object_type=tpoint.object_type,
3655d5d9cbbSKonrad Sztyber                owner_type=tpoint.owner_type, id=tpoint.tpoint_id,
3665d5d9cbbSKonrad Sztyber                new_object=bool(tpoint.new_object),
3675d5d9cbbSKonrad Sztyber                args=[TracepointArgument(name=str(a.name, 'ascii'), argtype=a.type)
3685d5d9cbbSKonrad Sztyber                      for a in tpoint.args[:tpoint.num_args]])
3695d5d9cbbSKonrad Sztyber
3705d5d9cbbSKonrad Sztyber    def _parse_defs(self):
37123dbcec5SJim Harris        flags = self._lib.spdk_trace_parser_get_file(self._parser)
3725d5d9cbbSKonrad Sztyber        self._tsc_rate = flags.contents.tsc_rate
3735d5d9cbbSKonrad Sztyber        self._parse_tpoints(flags.contents.tpoint)
3745d5d9cbbSKonrad Sztyber
3755d5d9cbbSKonrad Sztyber        def conv_objs(arr):
3765d5d9cbbSKonrad Sztyber            return {int(o.type): str(o.id_prefix, 'ascii') for o in arr if o.id_prefix != b'\x00'}
3775d5d9cbbSKonrad Sztyber        self._owners = conv_objs(flags.contents.owner)
3785d5d9cbbSKonrad Sztyber        self._objects = conv_objs(flags.contents.object)
3795d5d9cbbSKonrad Sztyber
3805d5d9cbbSKonrad Sztyber    def tsc_rate(self):
3815d5d9cbbSKonrad Sztyber        return self._tsc_rate
3825d5d9cbbSKonrad Sztyber
3835d5d9cbbSKonrad Sztyber    def tpoints(self):
3845d5d9cbbSKonrad Sztyber        return self._tpoints
3855d5d9cbbSKonrad Sztyber
3865d5d9cbbSKonrad Sztyber    def entries(self):
3875d5d9cbbSKonrad Sztyber        pe = CTraceParserEntry()
3885d5d9cbbSKonrad Sztyber        argconv = {TracepointArgument.TYPE_INT: lambda a: a.integer,
3895d5d9cbbSKonrad Sztyber                   TracepointArgument.TYPE_PTR: lambda a: int(a.pointer or 0),
3905d5d9cbbSKonrad Sztyber                   TracepointArgument.TYPE_STR: lambda a: str(a.string, 'ascii')}
3915d5d9cbbSKonrad Sztyber
3925d5d9cbbSKonrad Sztyber        while self._lib.spdk_trace_parser_next_entry(self._parser, ct.byref(pe)):
3935d5d9cbbSKonrad Sztyber            entry = pe.entry.contents
3945d5d9cbbSKonrad Sztyber            lcore = pe.lcore
3955d5d9cbbSKonrad Sztyber            tpoint = self._tpoints[entry.tpoint_id]
3965d5d9cbbSKonrad Sztyber            args = {a.name: argconv[a.argtype](pe.args[i]) for i, a in enumerate(tpoint.args)}
3975d5d9cbbSKonrad Sztyber
3985d5d9cbbSKonrad Sztyber            if tpoint.object_type != OBJECT_NONE:
3995d5d9cbbSKonrad Sztyber                if pe.object_index != TRACE_INVALID_OBJECT:
4005d5d9cbbSKonrad Sztyber                    object_id = '{}{}'.format(self._objects[tpoint.object_type], pe.object_index)
4015d5d9cbbSKonrad Sztyber                    ts = entry.tsc - pe.object_start
4025d5d9cbbSKonrad Sztyber                else:
4035d5d9cbbSKonrad Sztyber                    object_id, ts = 'n/a', None
4045d5d9cbbSKonrad Sztyber            elif entry.object_id != 0:
4055d5d9cbbSKonrad Sztyber                object_id, ts = '{:x}'.format(entry.object_id), None
4065d5d9cbbSKonrad Sztyber            else:
4075d5d9cbbSKonrad Sztyber                object_id, ts = None, None
4085d5d9cbbSKonrad Sztyber
40926d44a12SJim Harris            if tpoint.owner_type != OWNER_TYPE_NONE:
410*a6e5d032SJim Harris                owner_id = '{}{:02}'.format(self._owners[tpoint.owner_type], entry.owner_id)
4115d5d9cbbSKonrad Sztyber            else:
412*a6e5d032SJim Harris                owner_id = None
4135d5d9cbbSKonrad Sztyber
4143c271d5eSKrzysztof Karas            if pe.related_type != OBJECT_NONE:
4153c271d5eSKrzysztof Karas                related = '{}{}'.format(self._objects[pe.related_type], pe.related_index)
4163c271d5eSKrzysztof Karas            else:
4173c271d5eSKrzysztof Karas                related = None
4183c271d5eSKrzysztof Karas
4195d5d9cbbSKonrad Sztyber            yield TraceEntry(tpoint=tpoint, lcore=lcore, tsc=entry.tsc,
4205d5d9cbbSKonrad Sztyber                             size=entry.size, object_id=object_id,
421*a6e5d032SJim Harris                             object_ptr=entry.object_id, owner=owner_id, time=ts,
4223c271d5eSKrzysztof Karas                             args=args, related=related)
4235d5d9cbbSKonrad Sztyber
4245d5d9cbbSKonrad Sztyber
425d1732fceSKonrad Sztyberclass Trace:
426d1732fceSKonrad Sztyber    """Stores, parses, and prints out SPDK traces"""
427d1732fceSKonrad Sztyber    def __init__(self, file):
4285d5d9cbbSKonrad Sztyber        if file == sys.stdin or magic.from_file(file.name, mime=True) == 'application/json':
429d1732fceSKonrad Sztyber            self._provider = JsonProvider(file)
4305d5d9cbbSKonrad Sztyber        else:
4315d5d9cbbSKonrad Sztyber            self._provider = NativeProvider(file)
432d1732fceSKonrad Sztyber        self._objects = []
433d1732fceSKonrad Sztyber        self._argfmt = {TracepointArgument.TYPE_PTR: lambda a: f'0x{a:x}'}
434d1732fceSKonrad Sztyber        self.tpoints = self._provider.tpoints()
435d1732fceSKonrad Sztyber
436e61fbe91SKonrad Sztyber    def _annotate_args(self, entry):
437e61fbe91SKonrad Sztyber        annotations = {}
438e61fbe91SKonrad Sztyber        for obj in self._objects:
439e61fbe91SKonrad Sztyber            current = obj.annotate(entry)
440e61fbe91SKonrad Sztyber            if current is None:
441e61fbe91SKonrad Sztyber                continue
442e61fbe91SKonrad Sztyber            annotations.update(current)
443e61fbe91SKonrad Sztyber        return annotations
444e61fbe91SKonrad Sztyber
445b0d3f29aSKonrad Sztyber    def _format_args(self, entry):
446e61fbe91SKonrad Sztyber        annotations = self._annotate_args(entry)
447b0d3f29aSKonrad Sztyber        args = []
448b0d3f29aSKonrad Sztyber        for arg, (name, value) in zip(entry.tpoint.args, entry.args.items()):
449e61fbe91SKonrad Sztyber            annot = annotations.get(name)
450e61fbe91SKonrad Sztyber            if annot is not None:
451e61fbe91SKonrad Sztyber                args.append('{}({})'.format(name, ', '.join(f'{n}={v}' for n, v in annot.items())))
452e61fbe91SKonrad Sztyber            else:
453b0d3f29aSKonrad Sztyber                args.append('{}: {}'.format(name, self._argfmt.get(arg.argtype,
454b0d3f29aSKonrad Sztyber                                                                   lambda a: a)(value)))
455b0d3f29aSKonrad Sztyber        return args
456b0d3f29aSKonrad Sztyber
457e61fbe91SKonrad Sztyber    def register_object(self, obj):
458e61fbe91SKonrad Sztyber        self._objects.append(obj)
459e61fbe91SKonrad Sztyber
460b0d3f29aSKonrad Sztyber    def print(self):
461b0d3f29aSKonrad Sztyber        def get_us(tsc, off):
462d1732fceSKonrad Sztyber            return ((tsc - off) * 10 ** 6) / self._provider.tsc_rate()
463b0d3f29aSKonrad Sztyber
464b0d3f29aSKonrad Sztyber        offset = None
465d1732fceSKonrad Sztyber        for e in self._provider.entries():
466b0d3f29aSKonrad Sztyber            offset = e.tsc if offset is None else offset
467b0d3f29aSKonrad Sztyber            timestamp = get_us(e.tsc, offset)
468b0d3f29aSKonrad Sztyber            diff = get_us(e.time, 0) if e.time is not None else None
469b0d3f29aSKonrad Sztyber            args = ', '.join(self._format_args(e))
4705594c7c8SKrzysztof Karas            related = ' (' + e.related + ')' if e.related is not None else ''
471b0d3f29aSKonrad Sztyber
472e89224d1SKonrad Sztyber            print(('{:3} {:16.3f} {:3} {:24} {:12}'.format(
473*a6e5d032SJim Harris                e.lcore, timestamp, e.owner if e.owner is not None else '',
474e89224d1SKonrad Sztyber                e.tpoint.name, f'size: {e.size}' if e.size else '') +
4755594c7c8SKrzysztof Karas                (f'id: {e.object_id + related:12} ' if e.object_id is not None else '') +
476e89224d1SKonrad Sztyber                (f'time: {diff:<8.3f} ' if diff is not None else '') +
477e89224d1SKonrad Sztyber                args).rstrip())
478b0d3f29aSKonrad Sztyber
479b0d3f29aSKonrad Sztyber
480e61fbe91SKonrad Sztyberclass SPDKObject:
481e61fbe91SKonrad Sztyber    """Describes a specific type of an SPDK objects (e.g. qpair, thread, etc.)"""
482e61fbe91SKonrad Sztyber    @dataclass
483e61fbe91SKonrad Sztyber    class Lifetime:
4841ff3715dSJosh Soref        """Describes a lifetime and properties of a particular SPDK object."""
485e61fbe91SKonrad Sztyber        begin: int
486e61fbe91SKonrad Sztyber        end: int
487e61fbe91SKonrad Sztyber        ptr: int
488e61fbe91SKonrad Sztyber        properties: dict = field(default_factory=dict)
489e61fbe91SKonrad Sztyber
490e61fbe91SKonrad Sztyber    def __init__(self, trace: Trace, tpoints: List[str]):
491e61fbe91SKonrad Sztyber        self.tpoints = {}
492e61fbe91SKonrad Sztyber        for name in tpoints:
493e61fbe91SKonrad Sztyber            tpoint = next((t for t in trace.tpoints.values() if t.name == name), None)
494e61fbe91SKonrad Sztyber            if tpoint is None:
4951ff3715dSJosh Soref                # Some tpoints might be undefined if configured without specific subsystems
496e61fbe91SKonrad Sztyber                continue
497e61fbe91SKonrad Sztyber            self.tpoints[tpoint.id] = tpoint
498e61fbe91SKonrad Sztyber
499e61fbe91SKonrad Sztyber    def _annotate(self, entry: TraceEntry):
500e61fbe91SKonrad Sztyber        """Abstract annotation method to be implemented by subclasses."""
501e61fbe91SKonrad Sztyber        raise NotImplementedError()
502e61fbe91SKonrad Sztyber
503e61fbe91SKonrad Sztyber    def annotate(self, entry: TraceEntry):
504e61fbe91SKonrad Sztyber        """Annotates a tpoint entry and returns a dict indexed by argname with values representing
5051ff3715dSJosh Soref        various object properties.  For instance, {"qpair": {"qid": 1, "subnqn": "nqn"}} could be
506e61fbe91SKonrad Sztyber        returned to annotate an argument called "qpair" with two items: "qid" and "subnqn".
507e61fbe91SKonrad Sztyber        """
508e61fbe91SKonrad Sztyber        if entry.tpoint.id not in self.tpoints:
509e61fbe91SKonrad Sztyber            return None
510e61fbe91SKonrad Sztyber        return self._annotate(entry)
511e61fbe91SKonrad Sztyber
512e61fbe91SKonrad Sztyber
513e61fbe91SKonrad Sztyberclass QPair(SPDKObject):
514e61fbe91SKonrad Sztyber    def __init__(self, trace: Trace, dtrace: DTrace):
515e61fbe91SKonrad Sztyber        super().__init__(trace, tpoints=[
516e61fbe91SKonrad Sztyber            'RDMA_REQ_NEW',
517e61fbe91SKonrad Sztyber            'RDMA_REQ_NEED_BUFFER',
518e61fbe91SKonrad Sztyber            'RDMA_REQ_TX_PENDING_C2H',
519e61fbe91SKonrad Sztyber            'RDMA_REQ_TX_PENDING_H2C',
520e61fbe91SKonrad Sztyber            'RDMA_REQ_TX_H2C',
521e61fbe91SKonrad Sztyber            'RDMA_REQ_RDY_TO_EXECUTE',
522e61fbe91SKonrad Sztyber            'RDMA_REQ_EXECUTING',
523e61fbe91SKonrad Sztyber            'RDMA_REQ_EXECUTED',
524e61fbe91SKonrad Sztyber            'RDMA_REQ_RDY_TO_COMPL',
525e61fbe91SKonrad Sztyber            'RDMA_REQ_COMPLETING_C2H',
526e61fbe91SKonrad Sztyber            'RDMA_REQ_COMPLETING',
5276d4ad9a2SKrzysztof Karas            'RDMA_REQ_COMPLETED',
5286d4ad9a2SKrzysztof Karas            'TCP_REQ_NEW',
5296d4ad9a2SKrzysztof Karas            'TCP_REQ_NEED_BUFFER',
5306d4ad9a2SKrzysztof Karas            'TCP_REQ_TX_H_TO_C',
5316d4ad9a2SKrzysztof Karas            'TCP_REQ_RDY_TO_EXECUTE',
5326d4ad9a2SKrzysztof Karas            'TCP_REQ_EXECUTING',
5336d4ad9a2SKrzysztof Karas            'TCP_REQ_EXECUTED',
5346d4ad9a2SKrzysztof Karas            'TCP_REQ_RDY_TO_COMPLETE',
5356d4ad9a2SKrzysztof Karas            'TCP_REQ_TRANSFER_C2H',
5366d4ad9a2SKrzysztof Karas            'TCP_REQ_COMPLETED',
5376d4ad9a2SKrzysztof Karas            'TCP_WRITE_START',
5386d4ad9a2SKrzysztof Karas            'TCP_WRITE_DONE',
5396d4ad9a2SKrzysztof Karas            'TCP_READ_DONE',
5406d4ad9a2SKrzysztof Karas            'TCP_REQ_AWAIT_R2T_ACK'])
541e61fbe91SKonrad Sztyber        self._objects = []
542e61fbe91SKonrad Sztyber        self._find_objects(dtrace.entries)
543e61fbe91SKonrad Sztyber
544e61fbe91SKonrad Sztyber    def _find_objects(self, dprobes):
545e61fbe91SKonrad Sztyber        def probe_match(probe, other):
546e61fbe91SKonrad Sztyber            return probe.args['qpair'] == other.args['qpair']
547e61fbe91SKonrad Sztyber
548e61fbe91SKonrad Sztyber        for i, dprobe in enumerate(dprobes):
549e61fbe91SKonrad Sztyber            if dprobe.name != 'nvmf_poll_group_add_qpair':
550e61fbe91SKonrad Sztyber                continue
551e61fbe91SKonrad Sztyber            # We've found a new qpair, now find the probe indicating its destruction
552e61fbe91SKonrad Sztyber            last_idx, last = next((((i + j + 1), d) for j, d in enumerate(islice(dprobes, i, None))
553e61fbe91SKonrad Sztyber                                   if d.name == 'nvmf_poll_group_remove_qpair' and
554e61fbe91SKonrad Sztyber                                   probe_match(d, dprobe)), (None, None))
555e61fbe91SKonrad Sztyber            obj = SPDKObject.Lifetime(begin=dprobe.args['tsc'],
556e61fbe91SKonrad Sztyber                                      end=last.args['tsc'] if last is not None else TSC_MAX,
557e61fbe91SKonrad Sztyber                                      ptr=dprobe.args['qpair'],
558e61fbe91SKonrad Sztyber                                      properties={'ptr': hex(dprobe.args['qpair']),
559e61fbe91SKonrad Sztyber                                                  'thread': dprobe.args['thread']})
560e61fbe91SKonrad Sztyber            for other in filter(lambda p: probe_match(p, dprobe), dprobes[i:last_idx]):
561e61fbe91SKonrad Sztyber                if other.name == 'nvmf_ctrlr_add_qpair':
562e61fbe91SKonrad Sztyber                    for prop in ['qid', 'subnqn', 'hostnqn']:
563e61fbe91SKonrad Sztyber                        obj.properties[prop] = other.args[prop]
564e61fbe91SKonrad Sztyber            self._objects.append(obj)
565e61fbe91SKonrad Sztyber
566e61fbe91SKonrad Sztyber    def _annotate(self, entry):
567e61fbe91SKonrad Sztyber        qpair = entry.args.get('qpair')
568e61fbe91SKonrad Sztyber        if qpair is None:
569e61fbe91SKonrad Sztyber            return None
570e61fbe91SKonrad Sztyber        for obj in self._objects:
571e61fbe91SKonrad Sztyber            if obj.ptr == qpair and obj.begin <= entry.tsc <= obj.end:
572e61fbe91SKonrad Sztyber                return {'qpair': obj.properties}
573e61fbe91SKonrad Sztyber        return None
574e61fbe91SKonrad Sztyber
575e61fbe91SKonrad Sztyber
576e61fbe91SKonrad Sztyberdef build_dtrace(file=None):
57701ae68f7SKonrad Sztyber    return DTrace([
57801ae68f7SKonrad Sztyber        DTraceProbe(
57901ae68f7SKonrad Sztyber            name='nvmf_poll_group_add_qpair',
58001ae68f7SKonrad Sztyber            args=[DTraceArgument(name='tsc', pos=0, type=int),
58101ae68f7SKonrad Sztyber                  DTraceArgument(name='qpair', pos=1, type=int),
58201ae68f7SKonrad Sztyber                  DTraceArgument(name='thread', pos=2, type=int)]),
58301ae68f7SKonrad Sztyber        DTraceProbe(
58401ae68f7SKonrad Sztyber            name='nvmf_poll_group_remove_qpair',
58501ae68f7SKonrad Sztyber            args=[DTraceArgument(name='tsc', pos=0, type=int),
58601ae68f7SKonrad Sztyber                  DTraceArgument(name='qpair', pos=1, type=int),
58701ae68f7SKonrad Sztyber                  DTraceArgument(name='thread', pos=2, type=int)]),
58801ae68f7SKonrad Sztyber        DTraceProbe(
58901ae68f7SKonrad Sztyber            name='nvmf_ctrlr_add_qpair',
59001ae68f7SKonrad Sztyber            args=[DTraceArgument(name='tsc', pos=0, type=int),
59101ae68f7SKonrad Sztyber                  DTraceArgument(name='qpair', pos=1, type=int),
59201ae68f7SKonrad Sztyber                  DTraceArgument(name='qid', pos=2, type=int),
59301ae68f7SKonrad Sztyber                  DTraceArgument(name='subnqn', pos=3, type=str),
594e61fbe91SKonrad Sztyber                  DTraceArgument(name='hostnqn', pos=4, type=str)])], file)
595e61fbe91SKonrad Sztyber
596e61fbe91SKonrad Sztyber
597e61fbe91SKonrad Sztyberdef print_trace(trace_file, dtrace_file):
598e61fbe91SKonrad Sztyber    dtrace = build_dtrace(dtrace_file)
599e61fbe91SKonrad Sztyber    trace = Trace(trace_file)
600e61fbe91SKonrad Sztyber    trace.register_object(QPair(trace, dtrace))
601e61fbe91SKonrad Sztyber    trace.print()
60201ae68f7SKonrad Sztyber
60301ae68f7SKonrad Sztyber
604b0d3f29aSKonrad Sztyberdef main(argv):
605b0d3f29aSKonrad Sztyber    parser = ArgumentParser(description='SPDK trace annotation script')
606b0d3f29aSKonrad Sztyber    parser.add_argument('-i', '--input',
6075d5d9cbbSKonrad Sztyber                        help='Trace file to annotate (either JSON generated by spdk_trace or ' +
6085d5d9cbbSKonrad Sztyber                             'raw binary produced by the SPDK application itself)')
60901ae68f7SKonrad Sztyber    parser.add_argument('-g', '--generate', help='Generate bpftrace script', action='store_true')
61001ae68f7SKonrad Sztyber    parser.add_argument('-r', '--record', help='Record BPF traces on PID', metavar='PID', type=int)
611e61fbe91SKonrad Sztyber    parser.add_argument('-b', '--bpftrace', help='BPF trace script to use for annotations')
612b0d3f29aSKonrad Sztyber    args = parser.parse_args(argv)
613b0d3f29aSKonrad Sztyber
61401ae68f7SKonrad Sztyber    if args.generate:
61501ae68f7SKonrad Sztyber        print(build_dtrace().generate())
61601ae68f7SKonrad Sztyber    elif args.record:
61701ae68f7SKonrad Sztyber        build_dtrace().record(args.record)
61801ae68f7SKonrad Sztyber    else:
619e61fbe91SKonrad Sztyber        print_trace(open(args.input, 'r') if args.input is not None else sys.stdin,
620e61fbe91SKonrad Sztyber                    open(args.bpftrace) if args.bpftrace is not None else None)
621b0d3f29aSKonrad Sztyber
622b0d3f29aSKonrad Sztyber
623b0d3f29aSKonrad Sztyberif __name__ == '__main__':
624bc71c2e6SKonrad Sztyber    # In order for the changes to LD_LIBRARY_PATH to be visible to the loader,
625bc71c2e6SKonrad Sztyber    # they need to be applied before starting a process, so we need to
626bc71c2e6SKonrad Sztyber    # re-execute the script after updating it.
627bc71c2e6SKonrad Sztyber    if os.environ.get('SPDK_BPF_TRACE_PY') is None:
628bc71c2e6SKonrad Sztyber        rootdir = f'{os.path.dirname(__file__)}/../..'
629bc71c2e6SKonrad Sztyber        os.environ['LD_LIBRARY_PATH'] = ':'.join([os.environ.get('LD_LIBRARY_PATH', ''),
630bc71c2e6SKonrad Sztyber                                                  f'{rootdir}/build/lib'])
631bc71c2e6SKonrad Sztyber        os.environ['SPDK_BPF_TRACE_PY'] = '1'
632bc71c2e6SKonrad Sztyber        os.execv(sys.argv[0], sys.argv)
633bc71c2e6SKonrad Sztyber    else:
634364dbc8fSKonrad Sztyber        try:
635b0d3f29aSKonrad Sztyber            main(sys.argv[1:])
636364dbc8fSKonrad Sztyber        except (KeyboardInterrupt, BrokenPipeError):
637364dbc8fSKonrad Sztyber            pass
638