Spdk/scripts/bpf/trace.py

116 lines
3.7 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
from argparse import ArgumentParser
from dataclasses import dataclass
from typing import Dict, List, TypeVar
import json
import sys
@dataclass
class TracepointArgument:
"""Describes an SPDK tracepoint argument"""
TYPE_INT = 0
TYPE_PTR = 1
TYPE_STR = 2
name: str
argtype: int
@dataclass
class Tracepoint:
"""Describes an SPDK tracepoint, equivalent to struct spdk_trace_tpoint"""
name: str
id: int
new_object: bool
args: List[TracepointArgument]
@dataclass
class TraceEntry:
"""Describes an SPDK tracepoint entry, equivalent to struct spdk_trace_entry"""
lcore: int
tpoint: Tracepoint
tsc: int
poller: str
size: int
object_id: str
object_ptr: int
time: int
args: Dict[str, TypeVar('ArgumentType', str, int)]
class Trace:
"""Stores, parses, and prints out SPDK traces"""
def __init__(self, file):
self._json = json.load(file)
self._argfmt = {TracepointArgument.TYPE_PTR: lambda a: f'0x{a:x}'}
self.tpoints = {t.id: t for t in self._parse_tpoints()}
self.tsc_rate = self._json['tsc_rate']
def _parse_tpoints(self):
for tpoint in self._json.get('tpoints', []):
yield Tracepoint(
name=tpoint['name'], id=tpoint['id'],
new_object=tpoint['new_object'],
args=[TracepointArgument(name=a['name'],
argtype=a['type'])
for a in tpoint.get('args', [])])
def _parse_entry(self, entry):
tpoint = self.tpoints[entry['tpoint']]
obj = entry.get('object', {})
return TraceEntry(tpoint=tpoint, lcore=entry['lcore'], tsc=entry['tsc'],
size=entry.get('size'), object_id=obj.get('id'),
object_ptr=obj.get('value'), time=obj.get('time'),
poller=entry.get('poller'),
args={n.name: v for n, v in zip(tpoint.args, entry.get('args', []))})
def _entries(self):
for entry in self._json.get('entries', []):
yield self._parse_entry(entry)
def _format_args(self, entry):
args = []
for arg, (name, value) in zip(entry.tpoint.args, entry.args.items()):
args.append('{}: {}'.format(name, self._argfmt.get(arg.argtype,
lambda a: a)(value)))
return args
def print(self):
def get_us(tsc, off):
return ((tsc - off) * 10 ** 6) / self.tsc_rate
offset = None
for e in self._entries():
offset = e.tsc if offset is None else offset
timestamp = get_us(e.tsc, offset)
diff = get_us(e.time, 0) if e.time is not None else None
args = ', '.join(self._format_args(e))
fields = [
f'{e.lcore:3}',
f'{timestamp:16.3f}',
f'{e.poller:3}' if e.poller is not None else ' ' * 3,
f'{e.tpoint.name:24}',
f'size: {e.size:6}' if e.size is not None else ' ' * (len('size: ') + 6),
f'id: {e.object_id:8}' if e.object_id is not None else None,
f'time: {diff:<8.3f}' if diff is not None else None,
args
]
print(' '.join([*filter(lambda f: f is not None, fields)]).rstrip())
def main(argv):
parser = ArgumentParser(description='SPDK trace annotation script')
parser.add_argument('-i', '--input',
help='JSON-formatted trace file produced by spdk_trace app')
args = parser.parse_args(argv)
file = open(args.input, 'r') if args.input is not None else sys.stdin
Trace(file).print()
if __name__ == '__main__':
main(sys.argv[1:])