Files
TheRock/rocm-systems/projects/rocprof-trace-decoder/scripts/sqtt_memory_trace.py
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

284 lines
10 KiB
Python

#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""
Parse and analyze SQTT address trace data.
Reads shaderdata JSON files containing address trace records (emitted by
SQTT_TRACE_ADDRESSES=memory|lds|memory,lds) and outputs per-lane addresses
as JSON for downstream analysis (cache line utilization, stride detection,
memory coalescing analysis).
Usage:
# Dump all address traces as JSON
python3 sqtt_memory_trace.py build/test/ -o addresses.json
# Filter to specific CU/SIMD
python3 sqtt_memory_trace.py build/test/ --cu 0 --simd 0 -o addresses.json
# With demangling (for kernel context in output)
python3 sqtt_memory_trace.py build/test/ --demangle -o addresses.json
# Print summary stats to stderr
python3 sqtt_memory_trace.py build/test/ --summary
"""
import argparse
import json
import os
import sys
from typing import Optional
from sqtt_data import (
AddressTrace,
build_kernel_to_co,
discover_base_dir,
load_funcmaps,
load_occupancy,
load_shaderdata,
make_funcmap_resolver,
merge_funcmaps,
preprocess_records,
)
def format_address_trace(trace: AddressTrace) -> dict:
"""Convert an AddressTrace to a JSON-serializable dict."""
is_64bit = trace.kind not in ("lds_load", "lds_store", "lds_atomic",
"ds_permute", "ds_bpermute")
fmt = "0x{:016x}" if is_64bit else "0x{:08x}"
result = {
"kind": trace.kind,
"marker_id": trace.marker_id,
"time": trace.time,
"shader_engine": trace.shader_engine,
"cu": trace.cu,
"simd": trace.simd,
"wave": trace.wave_id,
"exec_mask": f"0x{trace.exec_mask:016x}",
"active_lanes": trace.active_lane_count(),
"addresses": [fmt.format(a) for a in trace.addresses],
}
if trace.source_loc:
result["source"] = trace.source_loc
return result
def compute_deltas(addresses: list[int]) -> list[int]:
"""Compute inter-lane address deltas (stride pattern)."""
if len(addresses) < 2:
return []
return [addresses[i+1] - addresses[i] for i in range(len(addresses) - 1)]
def format_addr(addr: int, is_64bit: bool) -> str:
return f"0x{addr:016x}" if is_64bit else f"0x{addr:08x}"
def print_summary(traces: list[AddressTrace], file=sys.stderr):
"""Print hierarchical summary: source → marker_id → wave → addresses + deltas."""
if not traces:
print("No address traces found.", file=file)
return
# Global stats
total_addrs = sum(len(t.addresses) for t in traces)
shader_engines = set()
cus = set()
simds = set()
by_kind: dict[str, int] = {}
for t in traces:
by_kind[t.kind] = by_kind.get(t.kind, 0) + 1
shader_engines.add(t.shader_engine)
cus.add(t.cu)
simds.add(t.simd)
print(f"\nAddress trace summary:", file=file)
print(f" Total operations: {len(traces)}", file=file)
print(f" Total addresses: {total_addrs}", file=file)
print(f" Shader engines: {sorted(shader_engines)}", file=file)
print(f" CUs seen: {sorted(cus)}", file=file)
print(f" SIMDs seen: {sorted(simds)}", file=file)
print(f" By kind:", file=file)
for kind in sorted(by_kind):
print(f" {kind}: {by_kind[kind]}", file=file)
# Group by source location (or marker_id if no source)
from collections import defaultdict
by_source: dict[str, list[AddressTrace]] = defaultdict(list)
for t in traces:
key = t.source_loc if t.source_loc else f"marker#{t.marker_id}"
by_source[key].append(t)
print(f"\n Hierarchical breakdown:", file=file)
for src_key in sorted(by_source.keys()):
src_traces = by_source[src_key]
kinds = set(t.kind for t in src_traces)
kind_str = ",".join(sorted(kinds))
print(f"\n {src_key} ({kind_str}, {len(src_traces)} ops)", file=file)
# Group by marker_id within source
by_mid: dict[int, list[AddressTrace]] = defaultdict(list)
for t in src_traces:
by_mid[t.marker_id].append(t)
for mid in sorted(by_mid.keys()):
mid_traces = by_mid[mid]
print(f" marker_id={mid} ({len(mid_traces)} samples)", file=file)
# Group by wave (SE, CU, SIMD, wave_id)
by_wave: dict[tuple[int, int, int, int], list[AddressTrace]] = defaultdict(list)
for t in mid_traces:
by_wave[(t.shader_engine, t.cu, t.simd, t.wave_id)].append(t)
for (se, cu, simd, wid) in sorted(by_wave.keys()):
wave_traces = by_wave[(se, cu, simd, wid)]
is_64bit = wave_traces[0].kind in ("load", "store")
print(f" wave SE={se} CU={cu} SIMD={simd} wave={wid} "
f"({len(wave_traces)} samples)", file=file)
for t in wave_traces:
n_active = t.active_lane_count()
addrs = t.addresses
deltas = compute_deltas(addrs)
unique_deltas = sorted(set(deltas)) if deltas else []
# Compact delta display
if unique_deltas:
delta_strs = [str(d) for d in unique_deltas]
delta_display = ",".join(delta_strs)
else:
delta_display = "-"
if addrs:
first = format_addr(addrs[0], is_64bit)
last = format_addr(addrs[-1], is_64bit)
print(f" t={t.time} lanes={n_active} "
f"range=[{first}..{last}] "
f"deltas=[{delta_display}]", file=file)
else:
print(f" t={t.time} lanes={n_active} "
f"(no addresses)", file=file)
def main():
parser = argparse.ArgumentParser(
description="Parse and analyze SQTT address trace data")
parser.add_argument("base_dir",
help="Base directory containing trace output.")
parser.add_argument("--code-objects", "-c", nargs="+",
help="Code object files (.out) with .sqtt_funcmap sections.")
parser.add_argument("--demangle", "-d", action="store_true",
help="Demangle C++ function names")
parser.add_argument("--cu", type=int, default=None,
help="Filter to specific CU/WGP ID")
parser.add_argument("--simd", type=int, default=None,
help="Filter to specific SIMD ID")
parser.add_argument("--wave", type=int, default=None,
help="Filter to specific wave ID")
parser.add_argument("--output", "-o", default=None,
help="Output JSON file (default: stdout)")
parser.add_argument("--summary", "-s", action="store_true",
help="Print summary statistics to stderr")
args = parser.parse_args()
base_dir = args.base_dir
if not os.path.isdir(base_dir):
print(f"Error: {base_dir} is not a directory", file=sys.stderr)
sys.exit(1)
trace_dirs, discovered_cos = discover_base_dir(base_dir)
code_objects = args.code_objects or discovered_cos
if not trace_dirs:
print(f"Error: no shaderdata_*.json files found under {base_dir}",
file=sys.stderr)
sys.exit(1)
if not code_objects:
print("Error: no code objects found. Use --code-objects.", file=sys.stderr)
sys.exit(1)
print(f"Found {len(trace_dirs)} trace dir(s), "
f"{len(code_objects)} code object(s)", file=sys.stderr)
per_co_raw = load_funcmaps(code_objects, do_demangle=False)
kernel_to_co = build_kernel_to_co(per_co_raw)
per_co = (load_funcmaps(code_objects, do_demangle=True)
if args.demangle else per_co_raw)
# Build merged funcmap for preprocessing.
merged = merge_funcmaps(per_co.values())
# Collect address traces from all directories
all_traces: list[AddressTrace] = []
total_records = 0
for td in trace_dirs:
records = load_shaderdata(td)
if not records:
continue
total_records += len(records)
dispatches, wave_spans = load_occupancy(td)
resolver = make_funcmap_resolver(
per_co, dispatches, kernel_to_co, wave_spans, merged)
_, addr_traces = preprocess_records(records, merged, resolver)
# Apply filters
for t in addr_traces:
if args.cu is not None and t.cu != args.cu:
continue
if args.simd is not None and t.simd != args.simd:
continue
if args.wave is not None and t.wave_id != args.wave:
continue
all_traces.append(t)
print(f" {os.path.basename(td)}: {len(records)} records, "
f"{len(addr_traces)} address traces", file=sys.stderr)
print(f"Total: {total_records} records, "
f"{len(all_traces)} address traces (after filters)",
file=sys.stderr)
if args.summary:
print_summary(all_traces)
# Output JSON
output = [format_address_trace(t) for t in all_traces]
if args.output:
with open(args.output, "w") as f:
json.dump(output, f, indent=2)
f.write("\n")
print(f"Wrote {len(output)} traces to {args.output}", file=sys.stderr)
else:
json.dump(output, sys.stdout, indent=2)
sys.stdout.write("\n")
if __name__ == "__main__":
main()