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 <casey.g.bowman@intel.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/37983>
This commit is contained in:
Felix DeGrood
2025-10-20 19:01:37 +00:00
committed by Marge Bot
parent 5ba9776bc5
commit 8c10226374
+105 -31
View File
@@ -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 <cmd>. Use stalls to avoid overlapping events:
> Generate csv of EndToEndOfPipe gpu events while running <cmd>. Events running
> in parallel.
MESA_GPU_TRACES=print_csv MESA_GPU_TRACEFILE=/tmp/ut.csv <cmd>
intel_measure.py --eop /tmp/ut.csv > im.csv
> Generate csv of TopToEndOfPipe gpu events while running <cmd>. Use stalls to
> avoid overlapping events:
MESA_GPU_TRACES=print_csv MESA_GPU_TRACEFILE=/tmp/ut.csv INTEL_DEBUG=stall <cmd>
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)