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