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
393 lines
13 KiB
Python
393 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# MIT License
|
|
#
|
|
# Copyright (c) 2024-2025 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.
|
|
|
|
import sys
|
|
import pytest
|
|
|
|
|
|
def test_perfetto_data(pftrace_data, json_data):
|
|
import rocprofiler_sdk.tests.rocprofv3 as rocprofv3
|
|
|
|
rocprofv3.test_perfetto_data(
|
|
pftrace_data,
|
|
json_data,
|
|
("hip", "marker", "kernel", "memory_copy"),
|
|
)
|
|
|
|
|
|
def test_otf2_data(otf2_data, json_data):
|
|
import rocprofiler_sdk.tests.rocprofv3 as rocprofv3
|
|
|
|
rocprofv3.test_otf2_data(
|
|
otf2_data,
|
|
json_data,
|
|
("hip", "marker", "kernel", "memory_copy", "memory_allocation"),
|
|
)
|
|
|
|
|
|
def test_otf2_system_tree_node_data(otf2_system_tree_node_data):
|
|
import rocprofiler_sdk.tests.rocprofv3 as rocprofv3
|
|
|
|
rocprofv3.test_otf2_system_tree_node(
|
|
otf2_system_tree_node_data,
|
|
)
|
|
|
|
|
|
def test_csv_data(csv_data, json_data):
|
|
import rocprofiler_sdk.tests.rocprofv3 as rocprofv3
|
|
|
|
rocprofv3.test_csv_data(
|
|
csv_data,
|
|
json_data,
|
|
(
|
|
"agent",
|
|
"counter_collection",
|
|
"kernel",
|
|
"memory_allocation",
|
|
"memory_copy",
|
|
"regions",
|
|
),
|
|
)
|
|
|
|
|
|
def test_arg_annotations_exist(pftrace_reader):
|
|
import rocprofiler_sdk.tests.rocprofv3 as rocprofv3
|
|
|
|
rocprofv3.test_perfetto_arg_annotations(pftrace_reader)
|
|
|
|
|
|
def test_event_id_annotations(pftrace_reader):
|
|
import rocprofiler_sdk.tests.rocprofv3 as rocprofv3
|
|
|
|
rocprofv3.test_perfetto_event_id_annotations(pftrace_reader)
|
|
|
|
|
|
#########################################################################################
|
|
#
|
|
# ROCPD Summary Validation
|
|
#
|
|
#########################################################################################
|
|
|
|
|
|
def _validate_summary_region_category_filtering(
|
|
csv_files, expected_categories=None, allow_none=False
|
|
):
|
|
"""
|
|
Test that summary output contains ONLY the expected categories.
|
|
|
|
Args:
|
|
csv_files: List of CSV file paths to validate
|
|
expected_categories: List of category names that should be present (e.g., ['kernel', 'hip'])
|
|
allow_none: If True, allows no region summaries (for --region-categories NONE test)
|
|
"""
|
|
import os
|
|
|
|
if not csv_files:
|
|
raise ValueError("No CSV files provided for validation")
|
|
|
|
basenames = [os.path.basename(f) for f in csv_files]
|
|
|
|
assert len(basenames) > 0, "No summary files provided for validation"
|
|
|
|
print(f"\nValidating {len(basenames)} summary files:")
|
|
for name in sorted(basenames):
|
|
print(f" - {name}")
|
|
|
|
# For NONE test: ensure no region-based summaries are generated
|
|
if allow_none:
|
|
# Region summaries have filenames like "rocm_hip_*.csv", "rocm_hsa_*.csv"
|
|
region_files = [f for f in basenames if f.lower().startswith("rocm_")]
|
|
assert len(region_files) == 0, (
|
|
f"--region-categories NONE should not generate region summaries, "
|
|
f"but found: {region_files}"
|
|
)
|
|
|
|
# Check that expected categories are present and ONLY those categories exist
|
|
if expected_categories:
|
|
# 1. Check all expected categories are present
|
|
for category in expected_categories:
|
|
category_lower = category.lower()
|
|
matching_files = [f for f in basenames if category_lower in f.lower()]
|
|
assert len(matching_files) > 0, (
|
|
f"Expected category '{category}' not found. "
|
|
f"No files matching '{category_lower}' in {basenames}"
|
|
)
|
|
|
|
# 2. Check no unexpected categories exist
|
|
for filename in basenames:
|
|
filename_lower = filename.lower()
|
|
matches_expected = any(
|
|
cat.lower() in filename_lower for cat in expected_categories
|
|
)
|
|
assert matches_expected, (
|
|
f"Unexpected file '{filename}' found. "
|
|
f"Does not match any expected category: {expected_categories}"
|
|
)
|
|
|
|
|
|
def test_summary_region_category_kernel(summary_kernel_csv_files):
|
|
"""Test that --region-categories KERNEL only produces kernel summaries."""
|
|
_validate_summary_region_category_filtering(
|
|
summary_kernel_csv_files,
|
|
expected_categories=["kernel"],
|
|
)
|
|
|
|
|
|
def test_summary_region_category_hip(summary_hip_csv_files):
|
|
"""Test that --region-categories HIP only produces HIP summaries."""
|
|
_validate_summary_region_category_filtering(
|
|
summary_hip_csv_files,
|
|
expected_categories=["hip"],
|
|
)
|
|
|
|
|
|
def test_summary_region_category_multiple(summary_multiple_csv_files):
|
|
"""Test that --region-categories HIP KERNEL produces those summaries."""
|
|
_validate_summary_region_category_filtering(
|
|
summary_multiple_csv_files,
|
|
expected_categories=["hip", "kernel"],
|
|
)
|
|
|
|
|
|
def test_summary_region_category_none(summary_none_csv_files):
|
|
"""Test that --region-categories NONE includes views but no regions."""
|
|
_validate_summary_region_category_filtering(
|
|
summary_none_csv_files,
|
|
expected_categories=["kernel", "memory"],
|
|
allow_none=True,
|
|
)
|
|
|
|
|
|
def _kernel_names(df):
|
|
"""Extract kernel names from dataframe as list of strings."""
|
|
return [str(name) for name in df["Name"].tolist()]
|
|
|
|
|
|
def _extract_kernel_names_from_json(json_data, name_type="full"):
|
|
"""
|
|
Extract kernel names from JSON data (source of truth).
|
|
|
|
Extracts names only for kernels that were actually dispatched.
|
|
|
|
Args:
|
|
json_data: JSON data from rocprofiler output
|
|
name_type: Type of name to extract - "full", "truncated", or "mangled"
|
|
|
|
Returns:
|
|
Set of kernel names from dispatched kernels in JSON
|
|
"""
|
|
tool_data = json_data.get("rocprofiler-sdk-tool")
|
|
assert tool_data, "Missing rocprofiler-sdk-tool in JSON"
|
|
|
|
kernel_symbols = tool_data.get("kernel_symbols", [])
|
|
assert kernel_symbols, "No kernel_symbols found in JSON"
|
|
kernel_dispatches = tool_data.get("buffer_records", {}).get("kernel_dispatch", [])
|
|
assert kernel_dispatches, "No kernel_dispatch records found in JSON"
|
|
|
|
# Map to JSON fields based on what summary.py actually uses:
|
|
# - "full": uses display_name which equals formatted_kernel_name
|
|
# - "truncated": uses truncated_kernel_name
|
|
# - "mangled": uses kernel_name (raw mangled name)
|
|
name_field_map = {
|
|
"mangled": "kernel_name",
|
|
"full": "formatted_kernel_name",
|
|
"truncated": "truncated_kernel_name",
|
|
}
|
|
|
|
assert name_type in name_field_map, f"Unknown kernel name type: {name_type}"
|
|
field = name_field_map[name_type]
|
|
|
|
# Build lookup table of kernel symbols by ID
|
|
symbols_by_id = {
|
|
symbol["kernel_id"]: symbol
|
|
for symbol in kernel_symbols
|
|
if symbol.get("kernel_id", 0) > 0
|
|
}
|
|
|
|
# Extract names only from dispatched kernels
|
|
names = set()
|
|
for dispatch in kernel_dispatches:
|
|
kernel_id = dispatch["dispatch_info"]["kernel_id"]
|
|
|
|
assert kernel_id in symbols_by_id, (
|
|
f"kernel_dispatch references kernel_id={kernel_id}, "
|
|
"but no matching kernel symbol was found"
|
|
)
|
|
|
|
name = symbols_by_id[kernel_id].get(field)
|
|
assert name, f"kernel_id={kernel_id} does not contain expected field '{field}'"
|
|
|
|
names.add(str(name))
|
|
|
|
return names
|
|
|
|
|
|
def _verify_names_match_json(csv_df, json_data, name_type):
|
|
"""
|
|
Verify CSV kernel names match JSON source exactly.
|
|
|
|
Args:
|
|
csv_df: DataFrame from CSV summary
|
|
json_data: Original JSON data (source of truth)
|
|
name_type: "full", "truncated", or "mangled"
|
|
"""
|
|
json_kernel_names = _extract_kernel_names_from_json(json_data, name_type)
|
|
csv_kernel_names = set(_kernel_names(csv_df))
|
|
|
|
assert json_kernel_names, "No kernel names found in JSON data"
|
|
assert csv_kernel_names, "No kernel names found in CSV data"
|
|
|
|
# Verify exact match: CSV and JSON should have the same dispatched kernel names
|
|
missing_in_csv = json_kernel_names - csv_kernel_names
|
|
unexpected_in_csv = csv_kernel_names - json_kernel_names
|
|
|
|
assert not missing_in_csv and not unexpected_in_csv, (
|
|
f"CSV kernel names do not match JSON ({name_type} format).\n"
|
|
f"Missing in CSV: {sorted(list(missing_in_csv))[:5]}\n"
|
|
f"Unexpected in CSV: {sorted(list(unexpected_in_csv))[:5]}"
|
|
)
|
|
|
|
|
|
def _looks_demangled(name: str) -> bool:
|
|
"""Check if kernel name appears to be demangled (has C++ syntax)."""
|
|
return "(" in name or "<" in name
|
|
|
|
|
|
def _looks_mangled_cpp(name: str) -> bool:
|
|
"""Check if kernel name appears to be C++ mangled (starts with _Z)."""
|
|
return name.startswith("_Z")
|
|
|
|
|
|
def _assert_demangled_names(df):
|
|
"""Verify that at least one kernel name is demangled."""
|
|
names = _kernel_names(df)
|
|
|
|
assert any(
|
|
_looks_demangled(name) for name in names
|
|
), "Expected at least one demangled kernel name, but none contained '(' or '<'."
|
|
|
|
|
|
def _assert_truncated_names(df):
|
|
"""Verify that all kernel names are truncated."""
|
|
names = _kernel_names(df)
|
|
|
|
invalid_names = [name for name in names if _looks_demangled(name)]
|
|
assert not invalid_names, (
|
|
"Expected all kernel names to be truncated, "
|
|
f"but found non-truncated name: {invalid_names[0]}"
|
|
)
|
|
|
|
|
|
def _assert_mangled_names(df):
|
|
"""Verify that kernel names remain mangled/raw."""
|
|
names = _kernel_names(df)
|
|
|
|
demangled_names = [name for name in names if _looks_demangled(name)]
|
|
assert not demangled_names, (
|
|
"Expected no demangled kernel names when mangled names are requested, "
|
|
f"but found: {demangled_names[0]}"
|
|
)
|
|
|
|
mangled_names = [name for name in names if _looks_mangled_cpp(name)]
|
|
assert mangled_names, (
|
|
"Expected at least one C++ mangled kernel name starting with '_Z', "
|
|
"but none were found."
|
|
)
|
|
|
|
|
|
def _assert_statistics_match(df1, df2, name1, name2):
|
|
"""
|
|
Helper function to verify that statistics match between two kernel summaries.
|
|
|
|
Args:
|
|
df1: First dataframe
|
|
df2: Second dataframe (reference)
|
|
name1: Label for first dataframe (e.g., "truncated")
|
|
name2: Label for second dataframe (e.g., "full")
|
|
"""
|
|
# Verify we have data
|
|
assert len(df1) > 0, f"No kernels found in {name1} summary"
|
|
assert len(df2) > 0, f"No kernels found in {name2} summary"
|
|
|
|
# Verify same number of entries
|
|
assert len(df1) == len(
|
|
df2
|
|
), f"Mismatch in number of kernels: {name1}={len(df1)}, {name2}={len(df2)}"
|
|
|
|
# Verify statistics are preserved
|
|
total_calls_1 = df1["Calls"].sum()
|
|
total_calls_2 = df2["Calls"].sum()
|
|
assert (
|
|
total_calls_1 == total_calls_2
|
|
), f"Total call count mismatch: {name1}={total_calls_1}, {name2}={total_calls_2}"
|
|
|
|
total_duration_1 = df1["Duration (Nsec)"].sum()
|
|
total_duration_2 = df2["Duration (Nsec)"].sum()
|
|
assert (
|
|
total_duration_1 == total_duration_2
|
|
), f"Total duration mismatch: {name1}={total_duration_1}, {name2}={total_duration_2}"
|
|
|
|
|
|
def test_summary_truncate_kernels(csv_kernels_truncated, csv_kernels_full, json_data):
|
|
"""
|
|
Test that --truncate-kernels flag only affects kernel names, not statistics.
|
|
|
|
This test verifies:
|
|
1. Full kernel names and truncated kernel names are valid
|
|
2. Statistics (calls, duration) are preserved between truncated and full summaries
|
|
3. Names match the JSON source of truth exactly
|
|
"""
|
|
|
|
_assert_demangled_names(csv_kernels_full)
|
|
_assert_truncated_names(csv_kernels_truncated)
|
|
_assert_statistics_match(csv_kernels_truncated, csv_kernels_full, "truncated", "full")
|
|
|
|
# Verify against JSON source of truth
|
|
_verify_names_match_json(csv_kernels_full, json_data, "full")
|
|
_verify_names_match_json(csv_kernels_truncated, json_data, "truncated")
|
|
|
|
|
|
def test_summary_mangled_kernels(csv_kernels_mangled, csv_kernels_full, json_data):
|
|
"""
|
|
Test that --mangled-kernels flag only affects kernel names, not statistics.
|
|
|
|
This test verifies:
|
|
1. Full kernel names and mangled kernel names are valid
|
|
2. Statistics (calls, duration) are preserved between mangled and demangled summaries
|
|
3. Names match the JSON source of truth exactly
|
|
"""
|
|
|
|
_assert_demangled_names(csv_kernels_full)
|
|
_assert_mangled_names(csv_kernels_mangled)
|
|
_assert_statistics_match(csv_kernels_mangled, csv_kernels_full, "mangled", "full")
|
|
|
|
# Verify against JSON source of truth
|
|
_verify_names_match_json(csv_kernels_full, json_data, "full")
|
|
_verify_names_match_json(csv_kernels_mangled, json_data, "mangled")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
exit_code = pytest.main(["-x", __file__] + sys.argv[1:])
|
|
sys.exit(exit_code)
|