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
479 lines
16 KiB
Python
479 lines
16 KiB
Python
# Copyright (c) Advanced Micro Devices, Inc.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""Unit tests for utils.parser.build_dfs, apply_filters, apply_kernel_filter,
|
|
utils_analysis filter resolution, and utils_common.expand_placeholder_ranges."""
|
|
|
|
from collections import OrderedDict
|
|
from types import SimpleNamespace
|
|
from typing import Any
|
|
|
|
import common
|
|
import pandas as pd
|
|
import pytest
|
|
|
|
from utils import schema
|
|
from utils.parser import apply_filters, apply_kernel_filter, build_dfs
|
|
from utils.utils_common import (
|
|
convert_filter_blocks_to_panel_ids,
|
|
expand_placeholder_ranges,
|
|
)
|
|
|
|
# =============================================================================
|
|
# Helpers to build in-memory panel configs
|
|
# =============================================================================
|
|
|
|
|
|
def _metric_panel(
|
|
panel_id: int,
|
|
table_id: int,
|
|
metrics: dict[str, dict[str, str]],
|
|
title: str = "Test Panel",
|
|
) -> dict[str, Any]:
|
|
"""Build a minimal panel with a single metric_table data source."""
|
|
return {
|
|
"id": panel_id,
|
|
"title": title,
|
|
"data source": [
|
|
{
|
|
"metric_table": {
|
|
"id": table_id,
|
|
"title": f"Table {table_id}",
|
|
"header": {"metric": "Metric", "value": "Avg"},
|
|
"metric": metrics,
|
|
}
|
|
}
|
|
],
|
|
}
|
|
|
|
|
|
def _raw_csv_panel(panel_id: int, table_id: int, source: str) -> dict[str, Any]:
|
|
return {
|
|
"id": panel_id,
|
|
"title": "Raw CSV Panel",
|
|
"data source": [{"raw_csv_table": {"id": table_id, "source": source}}],
|
|
}
|
|
|
|
|
|
def _pc_sampling_panel(panel_id: int, table_id: int, source: str) -> dict[str, Any]:
|
|
return {
|
|
"id": panel_id,
|
|
"title": "PC Sampling Panel",
|
|
"data source": [{"pc_sampling_table": {"id": table_id, "source": source}}],
|
|
}
|
|
|
|
|
|
def _make_arch_config(panels: list[tuple[int, dict[str, Any]]]) -> schema.ArchConfig:
|
|
ac = schema.ArchConfig()
|
|
ac.panel_configs = OrderedDict(panels)
|
|
return ac
|
|
|
|
|
|
def _sys_info() -> dict[str, Any]:
|
|
return {"total_l2_chan": 4}
|
|
|
|
|
|
# =============================================================================
|
|
# convert_filter_blocks_to_panel_ids
|
|
# =============================================================================
|
|
|
|
|
|
class TestResolveFilterBlocksToPanelIds:
|
|
def test_empty_list_returns_empty_set(self):
|
|
assert convert_filter_blocks_to_panel_ids([]) == set()
|
|
|
|
def test_numeric_ids_resolve_to_file_ids(self):
|
|
result = convert_filter_blocks_to_panel_ids(["2", "11.1", "11.1.5"])
|
|
assert result == {200, 1100}
|
|
|
|
def test_alias_tokens_resolve_via_arch_map(self, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"utils.utils_common.get_arch_alias_to_panel_id",
|
|
lambda arch: {"lds": "10", "roofline": "4"},
|
|
)
|
|
result = convert_filter_blocks_to_panel_ids(["lds", "roofline"], arch="gfx942")
|
|
assert result == {1000, 400}
|
|
|
|
def test_mixed_alias_and_numeric_tokens(self, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"utils.utils_common.get_arch_alias_to_panel_id",
|
|
lambda arch: {"lds": "10"},
|
|
)
|
|
result = convert_filter_blocks_to_panel_ids(["lds", "2", "11.1"], arch="gfx942")
|
|
assert result == {1000, 200, 1100}
|
|
|
|
def test_unknown_alias_raises(self, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"utils.utils_common.get_arch_alias_to_panel_id",
|
|
lambda arch: {"lds": "10"},
|
|
)
|
|
with pytest.raises(SystemExit):
|
|
convert_filter_blocks_to_panel_ids(["bogus"], arch="gfx942")
|
|
|
|
|
|
# =============================================================================
|
|
# build_dfs
|
|
# =============================================================================
|
|
|
|
|
|
def _two_block_config() -> schema.ArchConfig:
|
|
"""Config with data_source-0 (1), system panel (100), block 2, block 11."""
|
|
return _make_arch_config([
|
|
(0, _raw_csv_panel(0, 1, "kernel_top.csv")),
|
|
(100, _raw_csv_panel(100, 101, "sysinfo.csv")),
|
|
(
|
|
200,
|
|
_metric_panel(
|
|
200,
|
|
201,
|
|
metrics={
|
|
"M1": {"value": "AVG(COUNTER_A)"},
|
|
"M2": {"value": "AVG(COUNTER_B)"},
|
|
},
|
|
),
|
|
),
|
|
(
|
|
1100,
|
|
_metric_panel(
|
|
1100,
|
|
1101,
|
|
metrics={"X1": {"value": "AVG(COUNTER_C)"}},
|
|
),
|
|
),
|
|
])
|
|
|
|
|
|
class TestBuildDfs:
|
|
def test_no_filter_builds_all_metrics(self):
|
|
ac = _two_block_config()
|
|
build_dfs(ac, filter_metrics=None, sys_info=_sys_info(), profiling_config={})
|
|
|
|
assert set(ac.dfs.keys()) == {1, 101, 201, 1101}
|
|
assert len(ac.dfs[201]) == 2
|
|
assert len(ac.dfs[1101]) == 1
|
|
|
|
def test_filter_metrics_overrides_profiling_filter_blocks(self):
|
|
ac = _two_block_config()
|
|
build_dfs(
|
|
ac,
|
|
filter_metrics=["2"],
|
|
sys_info=_sys_info(),
|
|
profiling_config={"filter_blocks": ["11"]}, # ignored
|
|
)
|
|
|
|
# filter_metrics wins -> block 200 present, block 1100 absent.
|
|
assert set(ac.dfs.keys()) == {1, 101, 201}
|
|
assert len(ac.dfs[201]) == 2
|
|
|
|
def test_profiling_filter_blocks_keeps_only_matching_block(self):
|
|
ac = _two_block_config()
|
|
build_dfs(
|
|
ac,
|
|
filter_metrics=None,
|
|
sys_info=_sys_info(),
|
|
profiling_config={"filter_blocks": ["11"]},
|
|
)
|
|
|
|
# System panel always present; block 1100 in filter; block 200 dropped.
|
|
assert set(ac.dfs.keys()) == {1, 101, 1101}
|
|
|
|
def test_system_panels_and_data_source_zero_always_present(self):
|
|
ac = _two_block_config()
|
|
build_dfs(
|
|
ac,
|
|
filter_metrics=None,
|
|
sys_info=_sys_info(),
|
|
profiling_config={"filter_blocks": ["99"]}, # excludes blocks 2 and 11
|
|
)
|
|
|
|
# data_source 0 and system panel survive; M-blocks filtered out.
|
|
assert set(ac.dfs.keys()) == {1, 101}
|
|
|
|
def test_placeholder_range_entries_are_expanded(self):
|
|
# Use an integer range value so we don't depend on sys_info plumbing here.
|
|
metrics: dict[str, Any] = {
|
|
"Channel_::_1": {"value": "AVG(TCC_HIT[::_1])"},
|
|
"placeholder_range": {"::_1": 3},
|
|
}
|
|
ac = _make_arch_config([
|
|
(1800, _metric_panel(1800, 1801, metrics=metrics)),
|
|
])
|
|
build_dfs(ac, filter_metrics=None, sys_info=_sys_info(), profiling_config={})
|
|
|
|
df = ac.dfs[1801]
|
|
# Three expanded rows: Channel_0, Channel_1, Channel_2
|
|
assert len(df) == 3
|
|
assert set(df["Metric"]) == {"Channel_0", "Channel_1", "Channel_2"}
|
|
|
|
def test_metric_level_filter_drops_siblings_keeps_headers(self):
|
|
ac = _two_block_config()
|
|
build_dfs(
|
|
ac, filter_metrics=["2.1.0"], sys_info=_sys_info(), profiling_config={}
|
|
)
|
|
|
|
df = ac.dfs[201]
|
|
# Headers preserved
|
|
assert list(df.columns) == ["Metric", "Avg"]
|
|
# Only the matching metric remains
|
|
assert list(df["Metric"]) == ["M1"]
|
|
|
|
def test_whole_block_filter_keeps_every_metric_in_block(self):
|
|
ac = _two_block_config()
|
|
build_dfs(ac, filter_metrics=["2"], sys_info=_sys_info(), profiling_config={})
|
|
|
|
df = ac.dfs[201]
|
|
assert list(df["Metric"]) == ["M1", "M2"]
|
|
|
|
def test_profile_side_alias_resolves_to_panel_id(self, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"utils.utils_common.get_arch_alias_to_panel_id",
|
|
lambda arch: {"lds": "11"},
|
|
)
|
|
ac = _two_block_config()
|
|
build_dfs(
|
|
ac,
|
|
filter_metrics=None,
|
|
sys_info=_sys_info(),
|
|
profiling_config={"filter_blocks": ["lds"]},
|
|
arch="gfx942",
|
|
)
|
|
assert set(ac.dfs.keys()) == {1, 101, 1101}
|
|
|
|
def test_analyze_side_alias_resolves_to_panel_id(self, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"utils.utils_common.get_arch_alias_to_panel_id",
|
|
lambda arch: {"lds": "11"},
|
|
)
|
|
ac = _two_block_config()
|
|
build_dfs(
|
|
ac,
|
|
filter_metrics=["lds"],
|
|
sys_info=_sys_info(),
|
|
profiling_config={"filter_blocks": ["2"]}, # ignored
|
|
arch="gfx942",
|
|
)
|
|
assert set(ac.dfs.keys()) == {1, 101, 1101}
|
|
|
|
def test_analyze_mixed_numeric_and_alias_filter(self, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"utils.utils_common.get_arch_alias_to_panel_id",
|
|
lambda arch: {"lds": "11"},
|
|
)
|
|
ac = _two_block_config()
|
|
build_dfs(
|
|
ac,
|
|
filter_metrics=["2.1.0", "lds"],
|
|
sys_info=_sys_info(),
|
|
profiling_config={},
|
|
arch="gfx942",
|
|
)
|
|
# Numeric token keeps only M1 in block 2; alias keeps block 11.
|
|
assert set(ac.dfs.keys()) == {1, 101, 201, 1101}
|
|
assert list(ac.dfs[201]["Metric"]) == ["M1"]
|
|
assert list(ac.dfs[1101]["Metric"]) == ["X1"]
|
|
|
|
def test_metric_counters_only_for_built_metrics(self):
|
|
ac = _make_arch_config([
|
|
(
|
|
200,
|
|
_metric_panel(
|
|
200,
|
|
201,
|
|
metrics={
|
|
"Kept": {"value": "AVG(COUNTER_KEPT)"},
|
|
"Dropped": {"value": "AVG(COUNTER_DROPPED)"},
|
|
},
|
|
),
|
|
),
|
|
])
|
|
build_dfs(
|
|
ac, filter_metrics=["2.1.0"], sys_info=_sys_info(), profiling_config={}
|
|
)
|
|
|
|
assert "Kept" in ac.metric_counters
|
|
assert "Dropped" not in ac.metric_counters
|
|
assert ac.metric_counters["Kept"] == ["COUNTER_KEPT"]
|
|
assert ac.dfs_expressions[201] == ["AVG(COUNTER_KEPT)"]
|
|
|
|
|
|
# =============================================================================
|
|
# expand_placeholder_ranges
|
|
# =============================================================================
|
|
|
|
|
|
def _placeholder_panel(range_value: Any) -> OrderedDict[int, dict[str, Any]]:
|
|
metrics: dict[str, Any] = {
|
|
"Channel_::_1": {"value": "AVG(TCC_HIT[::_1])"},
|
|
"placeholder_range": {"::_1": range_value},
|
|
}
|
|
return OrderedDict([(1800, _metric_panel(1800, 1801, metrics=metrics))])
|
|
|
|
|
|
class TestExpandPlaceholderRanges:
|
|
def test_integer_placeholder_value_expands_n_times(self):
|
|
configs = _placeholder_panel(3)
|
|
result = expand_placeholder_ranges(configs, _sys_info())
|
|
|
|
expanded = result[1800]["data source"][0]["metric_table"]["metric"]
|
|
assert list(expanded) == ["Channel_0", "Channel_1", "Channel_2"]
|
|
|
|
def test_total_l2_chan_resolves_from_sys_info(self):
|
|
configs = _placeholder_panel("$total_l2_chan")
|
|
result = expand_placeholder_ranges(configs, {"total_l2_chan": 4})
|
|
|
|
expanded = result[1800]["data source"][0]["metric_table"]["metric"]
|
|
assert list(expanded) == [
|
|
"Channel_0",
|
|
"Channel_1",
|
|
"Channel_2",
|
|
"Channel_3",
|
|
]
|
|
|
|
def test_unsupported_builtin_var_exits(self):
|
|
configs = _placeholder_panel("$unsupported")
|
|
with pytest.raises(SystemExit):
|
|
expand_placeholder_ranges(configs, _sys_info())
|
|
|
|
def test_none_sys_info_clears_metric_dict(self):
|
|
configs = _placeholder_panel(3)
|
|
result = expand_placeholder_ranges(configs, None)
|
|
|
|
expanded = result[1800]["data source"][0]["metric_table"]["metric"]
|
|
assert expanded == {}
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for utils.parser.apply_filters
|
|
# =============================================================================
|
|
|
|
|
|
def _filter_workload() -> SimpleNamespace:
|
|
"""Workload stub exposing raw_pmc and filter attributes for apply_filters."""
|
|
return SimpleNamespace(
|
|
raw_pmc=pd.DataFrame({
|
|
"GPU_ID": [0, 0, 1, 1],
|
|
"Kernel_Name": ["vecCopy", "vecAdd", "vecCopy", "vecMul"],
|
|
"Dispatch_ID": [0, 1, 2, 3],
|
|
}),
|
|
filter_gpu_ids=None,
|
|
filter_kernel_ids=None,
|
|
filter_dispatch_ids=None,
|
|
)
|
|
|
|
|
|
def _kernel_filter_workload() -> SimpleNamespace:
|
|
"""Workload stub with dfs populated for apply_kernel_filter tests."""
|
|
return SimpleNamespace(
|
|
dfs={
|
|
1: pd.DataFrame({
|
|
"Kernel_Name": ["kernel_a", "kernel_b", "kernel_c"],
|
|
"Count": [2, 1, 1],
|
|
"Sum(ns)": [900, 800, 200],
|
|
"Selected": ["", "", ""],
|
|
}),
|
|
2: pd.DataFrame({
|
|
"Dispatch_ID": [1, 2, 3, 4],
|
|
"Kernel_Name": ["kernel_a", "kernel_b", "kernel_a", "kernel_c"],
|
|
"GPU_ID": [0, 0, 1, 0],
|
|
}),
|
|
},
|
|
filter_kernel_ids=[],
|
|
filter_dispatch_ids=None,
|
|
)
|
|
|
|
|
|
def _flat_raw_df() -> pd.DataFrame:
|
|
"""Flat single-index raw_pmc DataFrame for apply_kernel_filter tests."""
|
|
return pd.DataFrame({
|
|
"Kernel_Name": ["kernel_a", "kernel_b", "kernel_a", "kernel_c"],
|
|
"GPU_ID": [0, 0, 1, 0],
|
|
"Dispatch_ID": [1, 2, 3, 4],
|
|
})
|
|
|
|
|
|
class TestApplyFilters:
|
|
"""Tests for utils.parser.apply_filters."""
|
|
|
|
def test_gpu_string_filter(self) -> None:
|
|
"""A string GPU filter keeps only matching rows."""
|
|
workload = _filter_workload()
|
|
workload.filter_gpu_ids = "0"
|
|
assert len(apply_filters(workload, "/tmp", False, False)) == 2
|
|
|
|
def test_kernel_name_filter(self) -> None:
|
|
"""A kernel-name filter keeps only matching rows."""
|
|
workload = _filter_workload()
|
|
workload.filter_kernel_ids = ["vecCopy"]
|
|
assert len(apply_filters(workload, "/tmp", False, False)) == 2
|
|
|
|
def test_dispatch_id_filter(self) -> None:
|
|
"""A dispatch-ID filter keeps only matching rows."""
|
|
workload = _filter_workload()
|
|
workload.filter_dispatch_ids = ["0", "1"]
|
|
assert len(apply_filters(workload, "/tmp", False, False)) == 2
|
|
|
|
def test_gpu_integer_list_filter(self) -> None:
|
|
"""A GPU filter given as a list of integers keeps all matching rows."""
|
|
workload = _filter_workload()
|
|
workload.filter_gpu_ids = [0, 1]
|
|
assert len(apply_filters(workload, "/tmp", False, False)) == 4
|
|
|
|
|
|
class TestApplyKernelFilter:
|
|
"""Tests for utils.parser.apply_kernel_filter."""
|
|
|
|
def test_integer_ids_select_and_mark(self) -> None:
|
|
"""Integer kernel IDs filter rows and set the Selected marker."""
|
|
workload = _kernel_filter_workload()
|
|
workload.filter_kernel_ids = [0]
|
|
result_df = apply_kernel_filter(_flat_raw_df(), workload)
|
|
|
|
assert len(result_df) == 2
|
|
assert all(result_df["Kernel_Name"] == "kernel_a")
|
|
assert workload.dfs[1].loc[0, "Selected"] == "*"
|
|
|
|
def test_multiple_integer_ids(self) -> None:
|
|
"""Multiple integer kernel IDs keep the union of matching rows."""
|
|
workload = _kernel_filter_workload()
|
|
workload.filter_kernel_ids = [0, 1]
|
|
result_df = apply_kernel_filter(_flat_raw_df(), workload)
|
|
assert len(result_df) == 3
|
|
|
|
def test_invalid_id_errors(self, monkeypatch) -> None:
|
|
"""An out-of-bounds kernel ID triggers console_error and exits."""
|
|
error_calls = []
|
|
|
|
def record_and_exit(*args, **_kwargs):
|
|
error_calls.append(args)
|
|
raise SystemExit(1)
|
|
|
|
common.patch_console(
|
|
monkeypatch, "utils.parser", "error", error=record_and_exit
|
|
)
|
|
workload = _kernel_filter_workload()
|
|
workload.filter_kernel_ids = [99]
|
|
with pytest.raises(SystemExit):
|
|
apply_kernel_filter(_flat_raw_df(), workload)
|
|
assert error_calls
|
|
assert "99" in str(error_calls[0])
|
|
|
|
def test_exact_name_match(self) -> None:
|
|
"""A string kernel name filters to the exact match."""
|
|
workload = _kernel_filter_workload()
|
|
workload.filter_kernel_ids = ["kernel_b"]
|
|
result_df = apply_kernel_filter(_flat_raw_df(), workload)
|
|
assert len(result_df) == 1
|
|
assert result_df["Kernel_Name"].iloc[0] == "kernel_b"
|
|
|
|
def test_name_match_strips_whitespace(self) -> None:
|
|
"""Kernel names with surrounding whitespace are stripped before matching."""
|
|
raw_df_with_whitespace = pd.DataFrame({
|
|
"Kernel_Name": [" kernel_a ", "kernel_b", "kernel_a"],
|
|
"GPU_ID": [0, 0, 1],
|
|
"Dispatch_ID": [1, 2, 3],
|
|
})
|
|
workload = _kernel_filter_workload()
|
|
workload.filter_kernel_ids = ["kernel_a"]
|
|
result_df = apply_kernel_filter(raw_df_with_whitespace, workload)
|
|
assert len(result_df) == 2
|