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
284 lines
10 KiB
Python
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()
|