From 8c10226374fe1c73ad2bd88e243bd09bb61f099b Mon Sep 17 00:00:00 2001 From: Felix DeGrood Date: Mon, 20 Oct 2025 19:01:37 +0000 Subject: [PATCH] intel/tools: add eop timestamp to intel_measure Adds end of pipe to end of pipe timestamp parsing. Does not require inserting stall between events to get accurate values but events will sometimes be running in parallel. Reviewed-by: Casey Bowman Part-of: --- src/intel/tools/intel_measure.py | 136 ++++++++++++++++++++++++------- 1 file changed, 105 insertions(+), 31 deletions(-) diff --git a/src/intel/tools/intel_measure.py b/src/intel/tools/intel_measure.py index f9f99e1e222..f9fe3a6b15f 100755 --- a/src/intel/tools/intel_measure.py +++ b/src/intel/tools/intel_measure.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from __future__ import annotations +from typing import Optional import argparse import re @@ -10,14 +11,31 @@ def error(msg, err_code=1): print('ERROR: ' + msg) sys.exit(err_code) +class Line: + def __init__(self, line_str: str): + self.valid = False + if line_str.count(',') < 4: + return + (frame, cmd_buf, timestamp, self.name, self.args) = line_str.strip().split(',', 4) + if self.name.count('_') < 2: + return + (self.intel, begin_or_end, self.func) = self.name.split('_', 2) + + self.frame: int = int(frame) + self.cmd_buf: int = int(cmd_buf) + self.timestamp: float = float(timestamp) + self.is_begin: bool = begin_or_end == 'begin' + self.valid: bool = True + class Utrace_Parser: def __init__(self, cl_args: argparse.Namespace): self.cl_args = cl_args - self.events: Dict[str, float] = {} + self.events: dict[str, float] = {} self.renderpass: int = 0 self.next_renderpass: int = 1 self.idx: int = 0 self.prev_end_ts_ns: float = 0 + self.last_eop: float = 0 self.func: str self.args: str @@ -26,9 +44,15 @@ class Utrace_Parser: self.cs: str def get_header(self) -> str: - if self.cl_args.verbose: - return 'start_ts,end_ts,frame,cmdbuf,rp,idx,event,count,vs,fs,cs,gap_us,time_us,details' - return 'frame,cmdbuf,rp,idx,event,count,vs,fs,cs,gap_us,time_us' + if self.cl_args.mode == 'top': + if self.cl_args.verbose: + return 'start_ts,end_ts,frame,cmdbuf,rp,idx,event,count,vs,fs,cs,gap_us,time_us,details' + return 'frame,cmdbuf,rp,idx,event,count,vs,fs,cs,gap_us,time_us' + elif self.cl_args.mode == 'eop': + if self.cl_args.verbose: + return 'end_ts,frame,cmdbuf,rp,idx,event,count,vs,fs,cs,time_us,details' + return 'frame,cmdbuf,rp,idx,event,count,vs,fs,cs,time_us' + raise RuntimeError('invalid mode') def extract_count(self) -> None: if 'count' in self.args: @@ -68,18 +92,16 @@ class Utrace_Parser: self.func = match.group(2).lower() self.args = match.group(1) + match.group(3) - def parse_line(self, line) -> str: - if line.count(',') < 4: + def parse_line(self, line_str: str) -> Optional[str]: + line = Line(line_str) + if not line.valid: return None - (frame, cmd_buf, timestamp, name, args) = line.strip().split(',', 4) - - if name.count('_') < 2: - return None - (intel, begin_or_end, self.func) = name.split('_', 2) + self.func = line.func if self.func == 'render_pass': - if begin_or_end == 'begin': + if line.is_begin: self.renderpass = self.next_renderpass + self.last_eop = line.timestamp return self.next_renderpass = self.renderpass + 1 self.renderpass = 0 @@ -88,41 +110,51 @@ class Utrace_Parser: ignore_funcs = {'frame': True, 'cmd_buffer': True} if ignore_funcs.get(self.func, None): if self.prev_end_ts_ns == 0: - self.prev_end_ts_ns = float(timestamp) + self.prev_end_ts_ns = line.timestamp + self.last_eop = line.timestamp return None - if begin_or_end == 'begin': - self.events[self.func] = float(timestamp) - return None - - # end event - begin_ts_ns = self.events.pop(self.func, 0) - end_ts_ns = float(timestamp) - gap_ts_us = (begin_ts_ns - self.prev_end_ts_ns) / 1000 - delta_ts_us = (end_ts_ns - begin_ts_ns) / 1000 - self.count = 0 self.vs = '' self.fs = '' self.cs = '' - self.args = args.replace(',', ';').strip(';') + self.args = line.args.replace(',', ';').strip(';') + + if self.cl_args.mode == 'top': + result = self.parse_top(line) + elif self.cl_args.mode == 'eop': + result = self.parse_eop(line) + else: + raise RuntimeError('invalid mode') + return result + + def parse_top(self, line: Line) -> Optional[str]: + if line.is_begin: + self.events[self.func] = line.timestamp + return None + + # end event + begin_ts_ns = self.events.pop(self.func, 0) + end_ts_ns = line.timestamp + gap_ts_us = (begin_ts_ns - self.prev_end_ts_ns) / 1000 + delta_ts_us = (end_ts_ns - begin_ts_ns) / 1000 self.extract_count() - if 'draw' in name or 'compute' in name: + if 'draw' in line.name or 'compute' in line.name: self.extract_shaders() self.idx += 1 - elif 'blorp' in name: + elif 'blorp' in line.name: self.extract_blorp_op() self.idx += 1 self.args = self.args.strip().lstrip('+') if self.cl_args.verbose: - out = [f'{begin_ts_ns:.0f}', f'{end_ts_ns:.0f}', int(frame), - int(cmd_buf), self.renderpass, self.idx, self.func, + out = [f'{begin_ts_ns:.0f}', f'{end_ts_ns:.0f}', line.frame, + line.cmd_buf, self.renderpass, self.idx, self.func, self.count, self.vs, self.fs, self.cs, f'{gap_ts_us:.3f}', f'{delta_ts_us:.3f}', self.args] else: - out = [int(frame), int(cmd_buf), self.renderpass, self.idx, + out = [line.frame, line.cmd_buf, self.renderpass, self.idx, self.func, self.count, self.vs, self.fs, self.cs, f'{gap_ts_us:.3f}', f'{delta_ts_us:.3f}'] @@ -130,14 +162,53 @@ class Utrace_Parser: result = ','.join(v if isinstance(v, str) else str(v) for v in out) return result + def parse_eop(self, line: Line) -> Optional[str]: + if line.is_begin: + return None + + # end event + start_ts_ns = self.last_eop + end_ts_ns = line.timestamp + delta_ts_us = max(0, (end_ts_ns - start_ts_ns) / 1000) + + self.extract_count() + if 'draw' in line.name or 'compute' in line.name: + self.extract_shaders() + self.idx += 1 + elif 'blorp' in line.name: + self.extract_blorp_op() + self.idx += 1 + self.args = self.args.strip().lstrip('+') + + if self.cl_args.verbose: + out = [f'{end_ts_ns:.0f}', line.frame, + line.cmd_buf, self.renderpass, self.idx, self.func, + self.count, self.vs, self.fs, self.cs, + f'{delta_ts_us:.3f}', self.args] + else: + out = [line.frame, line.cmd_buf, self.renderpass, self.idx, + self.func, self.count, self.vs, self.fs, self.cs, + f'{delta_ts_us:.3f}'] + + self.last_eop = line.timestamp + result = ','.join(v if isinstance(v, str) else str(v) for v in out) + return result + class CustomArgumentParser(argparse.ArgumentParser): examples = """ Examples ======== -> Generate csv of gpu events while running . Use stalls to avoid overlapping events: +> Generate csv of EndToEndOfPipe gpu events while running . Events running +> in parallel. + + MESA_GPU_TRACES=print_csv MESA_GPU_TRACEFILE=/tmp/ut.csv + intel_measure.py --eop /tmp/ut.csv > im.csv + +> Generate csv of TopToEndOfPipe gpu events while running . Use stalls to +> avoid overlapping events: MESA_GPU_TRACES=print_csv MESA_GPU_TRACEFILE=/tmp/ut.csv INTEL_DEBUG=stall - intel_measure.py /tmp/ut.csv > im.csv + intel_measure.py --top /tmp/ut.csv > im.csv """ def format_help(self): @@ -148,6 +219,9 @@ def main(): cl_parser.add_argument('utrace_log', type=str, help='path to utrace log to parse') cl_parser.add_argument('-v', '--verbose', default=False, action='store_true', help='dump all fields to output') cl_parser.add_argument('-f', '--file', type=str, default=None, help='save results to file') + cl_parser.add_argument( '--eop', action='store_const', const='eop', dest='mode', help='use end-to-end-of-pipe gpu measurements (default)') + cl_parser.add_argument( '--top', action='store_const', const='top', dest='mode', help='use top-to-end-of-pipe gpu measurements') + cl_parser.set_defaults(mode='top') cl_args = cl_parser.parse_args() parser = Utrace_Parser(cl_args)