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
721 lines
24 KiB
Python
721 lines
24 KiB
Python
# Copyright (c) Advanced Micro Devices, Inc.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""
|
|
Unit tests for memory-chart renderers.
|
|
|
|
Covers:
|
|
- mem_chart_gfx11.py - RDNA3.5 (Rich-based) memory architecture visualization
|
|
- mem_chart_gfx9.py - CDNA (plotille-based) memory architecture visualization
|
|
"""
|
|
|
|
import re
|
|
|
|
import common
|
|
import pytest
|
|
|
|
from utils import mem_chart_gfx9, mem_chart_gfx11
|
|
|
|
DEFAULT_TITLE = "3. Memory Chart (Normalization: per_kernel)"
|
|
|
|
# =============================================================================
|
|
# Tests for format_bw_human_readable function (gfx11)
|
|
# =============================================================================
|
|
|
|
|
|
class TestFormatBwHumanReadable:
|
|
"""Tests for format_bw_human_readable - converts Bytes/s to human-readable."""
|
|
|
|
def test_terabytes_per_second(self):
|
|
"""Test TB/s formatting for values >= 1e12."""
|
|
# 1 TB/s
|
|
result = mem_chart_gfx11.format_bw_human_readable(1e12, "Bytes/s", 1)
|
|
assert result == "1.0 TB/s"
|
|
|
|
# 2.5 TB/s
|
|
result = mem_chart_gfx11.format_bw_human_readable(2.5e12, "Bytes/s", 1)
|
|
assert result == "2.5 TB/s"
|
|
|
|
# Large value with higher precision
|
|
result = mem_chart_gfx11.format_bw_human_readable(1.234e12, "Bytes/s", 2)
|
|
assert result == "1.23 TB/s"
|
|
|
|
def test_gigabytes_per_second(self):
|
|
"""Test GB/s formatting for values >= 1e9 and < 1e12."""
|
|
# 1 GB/s
|
|
result = mem_chart_gfx11.format_bw_human_readable(1e9, "Bytes/s", 1)
|
|
assert result == "1.0 GB/s"
|
|
|
|
# 100 GB/s (typical HBM bandwidth)
|
|
result = mem_chart_gfx11.format_bw_human_readable(100e9, "Bytes/s", 1)
|
|
assert result == "100.0 GB/s"
|
|
|
|
# 512.5 GB/s
|
|
result = mem_chart_gfx11.format_bw_human_readable(512.5e9, "Bytes/s", 1)
|
|
assert result == "512.5 GB/s"
|
|
|
|
def test_megabytes_per_second(self):
|
|
"""Test MB/s formatting for values >= 1e6 and < 1e9."""
|
|
# 1 MB/s
|
|
result = mem_chart_gfx11.format_bw_human_readable(1e6, "Bytes/s", 1)
|
|
assert result == "1.0 MB/s"
|
|
|
|
# 500 MB/s
|
|
result = mem_chart_gfx11.format_bw_human_readable(500e6, "Bytes/s", 1)
|
|
assert result == "500.0 MB/s"
|
|
|
|
def test_kilobytes_per_second(self):
|
|
"""Test KB/s formatting for values >= 1e3 and < 1e6."""
|
|
# 1 KB/s
|
|
result = mem_chart_gfx11.format_bw_human_readable(1e3, "Bytes/s", 1)
|
|
assert result == "1.0 KB/s"
|
|
|
|
# 512 KB/s
|
|
result = mem_chart_gfx11.format_bw_human_readable(512e3, "Bytes/s", 1)
|
|
assert result == "512.0 KB/s"
|
|
|
|
def test_bytes_per_second(self):
|
|
"""Test B/s formatting for values < 1e3."""
|
|
# 500 B/s
|
|
result = mem_chart_gfx11.format_bw_human_readable(500, "Bytes/s", 1)
|
|
assert result == "500.0 B/s"
|
|
|
|
# 0 B/s
|
|
result = mem_chart_gfx11.format_bw_human_readable(0, "Bytes/s", 1)
|
|
assert result == "0.0 B/s"
|
|
|
|
def test_legacy_gbps_unit_conversion(self):
|
|
"""Test conversion from legacy GB/s unit to human-readable."""
|
|
# Input is 100 GB/s, should convert to Bytes/s first then format
|
|
result = mem_chart_gfx11.format_bw_human_readable(100, "GB/s", 1)
|
|
assert result == "100.0 GB/s"
|
|
|
|
# 1500 GB/s -> 1.5 TB/s
|
|
result = mem_chart_gfx11.format_bw_human_readable(1500, "GB/s", 1)
|
|
assert result == "1.5 TB/s"
|
|
|
|
def test_none_value(self):
|
|
"""Test handling of None values."""
|
|
result = mem_chart_gfx11.format_bw_human_readable(None, "Bytes/s", 1)
|
|
assert result == "N/A"
|
|
|
|
def test_invalid_string_value(self):
|
|
"""Test handling of non-numeric string values."""
|
|
result = mem_chart_gfx11.format_bw_human_readable("invalid", "Bytes/s", 1)
|
|
assert result == "N/A"
|
|
|
|
def test_precision_parameter(self):
|
|
"""Test different precision values."""
|
|
value = 123.456789e9 # 123.456789 GB/s
|
|
|
|
assert (
|
|
mem_chart_gfx11.format_bw_human_readable(value, "Bytes/s", 0) == "123 GB/s"
|
|
)
|
|
assert (
|
|
mem_chart_gfx11.format_bw_human_readable(value, "Bytes/s", 1)
|
|
== "123.5 GB/s"
|
|
)
|
|
assert (
|
|
mem_chart_gfx11.format_bw_human_readable(value, "Bytes/s", 2)
|
|
== "123.46 GB/s"
|
|
)
|
|
assert (
|
|
mem_chart_gfx11.format_bw_human_readable(value, "Bytes/s", 3)
|
|
== "123.457 GB/s"
|
|
)
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for format_value function (gfx11)
|
|
# =============================================================================
|
|
|
|
|
|
class TestFormatValue:
|
|
"""Tests for format_value - general value formatting."""
|
|
|
|
def test_percentage_formatting(self):
|
|
"""Test percentage formatting."""
|
|
result = mem_chart_gfx11.format_value(85.5, "%", 1)
|
|
assert result == "85.5%"
|
|
|
|
result = mem_chart_gfx11.format_value(100, "%", 0)
|
|
assert result == "100%"
|
|
|
|
def test_bytes_per_second_unit(self):
|
|
"""Test Bytes/s formatting routes to human-readable."""
|
|
result = mem_chart_gfx11.format_value(100e9, "Bytes/s", 1)
|
|
assert "GB/s" in result
|
|
|
|
def test_gbps_unit(self):
|
|
"""Test GB/s formatting routes to human-readable."""
|
|
result = mem_chart_gfx11.format_value(100, "GB/s", 1)
|
|
assert "GB/s" in result
|
|
|
|
def test_none_value(self):
|
|
"""Test handling of None values."""
|
|
result = mem_chart_gfx11.format_value(None, "%", 1)
|
|
assert result == "N/A"
|
|
|
|
def test_string_value_conversion(self):
|
|
"""Test conversion of string values to float."""
|
|
result = mem_chart_gfx11.format_value("50.5", "%", 1)
|
|
assert result == "50.5%"
|
|
|
|
def test_invalid_string_value(self):
|
|
"""Test handling of non-convertible string values."""
|
|
result = mem_chart_gfx11.format_value("invalid", "%", 1)
|
|
assert result == "invalid"
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for format_sci function (gfx11)
|
|
# =============================================================================
|
|
|
|
|
|
class TestFormatSci:
|
|
"""Tests for format_sci - scientific notation formatting."""
|
|
|
|
def test_small_numbers_no_scientific(self):
|
|
"""Test that small numbers are displayed as integers."""
|
|
assert mem_chart_gfx11.format_sci(100) == "100"
|
|
assert mem_chart_gfx11.format_sci(999) == "999"
|
|
|
|
def test_large_numbers_scientific(self):
|
|
"""Test that large numbers are displayed in scientific notation."""
|
|
result = mem_chart_gfx11.format_sci(1000000)
|
|
assert "e" in result.lower()
|
|
|
|
result = mem_chart_gfx11.format_sci(12345678, 2)
|
|
assert "e" in result.lower()
|
|
|
|
def test_none_value(self):
|
|
"""Test handling of None values."""
|
|
result = mem_chart_gfx11.format_sci(None)
|
|
assert result == "N/A"
|
|
|
|
def test_invalid_value(self):
|
|
"""Test handling of invalid values."""
|
|
result = mem_chart_gfx11.format_sci("invalid")
|
|
assert result == "N/A"
|
|
|
|
def test_negative_numbers(self):
|
|
"""Test negative number handling."""
|
|
result = mem_chart_gfx11.format_sci(-500)
|
|
assert result == "-500"
|
|
|
|
result = mem_chart_gfx11.format_sci(-1000000)
|
|
assert "e" in result.lower()
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for bar function (gfx11)
|
|
# =============================================================================
|
|
|
|
|
|
class TestBar:
|
|
"""Tests for bar - progress bar visualization."""
|
|
|
|
def test_full_bar(self):
|
|
"""Test 100% progress bar."""
|
|
result = mem_chart_gfx11.bar(100, 10)
|
|
assert "█" * 10 in result
|
|
assert "░" not in result
|
|
|
|
def test_empty_bar(self):
|
|
"""Test 0% progress bar."""
|
|
result = mem_chart_gfx11.bar(0, 10)
|
|
assert "░" * 10 in result
|
|
assert "█" not in result
|
|
|
|
def test_partial_bar(self):
|
|
"""Test 50% progress bar."""
|
|
result = mem_chart_gfx11.bar(50, 10)
|
|
assert "█" * 5 in result
|
|
assert "░" * 5 in result
|
|
|
|
def test_none_value(self):
|
|
"""Test None value returns empty bar."""
|
|
result = mem_chart_gfx11.bar(None, 10)
|
|
assert "░" * 10 in result
|
|
|
|
def test_invalid_value(self):
|
|
"""Test invalid value returns empty bar."""
|
|
result = mem_chart_gfx11.bar("invalid", 10)
|
|
assert "░" * 10 in result
|
|
|
|
def test_over_100_clamped(self):
|
|
"""Test values over 100% are clamped."""
|
|
result = mem_chart_gfx11.bar(150, 10)
|
|
assert "█" * 10 in result
|
|
|
|
def test_negative_clamped(self):
|
|
"""Test negative values are clamped to 0."""
|
|
result = mem_chart_gfx11.bar(-50, 10)
|
|
assert "░" * 10 in result
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for metric_line function (gfx11)
|
|
# =============================================================================
|
|
|
|
|
|
class TestMetricLine:
|
|
"""Tests for metric_line - formatted metric display."""
|
|
|
|
def test_basic_metric(self):
|
|
"""Test basic metric line formatting."""
|
|
result = mem_chart_gfx11.metric_line("Util", 75.5, "%", "green")
|
|
assert "Util" in result
|
|
assert "75.5%" in result
|
|
assert "green" in result
|
|
|
|
def test_with_none_value(self):
|
|
"""Test metric line with None value."""
|
|
result = mem_chart_gfx11.metric_line("BW", None, "GB/s", "cyan")
|
|
assert "BW" in result
|
|
assert "N/A" in result
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for get_sample_metrics function (gfx11)
|
|
# =============================================================================
|
|
|
|
|
|
class TestGetSampleMetrics:
|
|
"""Tests for get_sample_metrics - returns sample test data."""
|
|
|
|
def test_returns_dict(self):
|
|
"""Test that sample metrics returns a dictionary."""
|
|
metrics = mem_chart_gfx11.get_sample_metrics()
|
|
assert isinstance(metrics, dict)
|
|
|
|
def test_contains_key_metrics(self):
|
|
"""Test that sample metrics contains expected keys."""
|
|
metrics = mem_chart_gfx11.get_sample_metrics()
|
|
|
|
# Check for key bandwidth metrics
|
|
assert "TCP-GL1 Read Bandwidth" in metrics
|
|
assert "GL1-GL2 Read Bandwidth" in metrics
|
|
assert "GL2 Cache Read BW" in metrics
|
|
assert "DRAM Read Bandwidth" in metrics
|
|
|
|
# Check for utilization metrics
|
|
assert "GL1 Cache Utilization" in metrics
|
|
assert "GL2 Cache Utilization" in metrics
|
|
|
|
# Check for hit rate metrics
|
|
assert "GL0 Cache Hit Rate (TCP Cache)" in metrics
|
|
assert "GL1 Cache Hit Rate" in metrics
|
|
assert "GL2 Cache Hit Rate" in metrics
|
|
|
|
def test_bandwidth_values_in_bytes_per_second(self):
|
|
"""Test that bandwidth values are in Bytes/s format."""
|
|
metrics = mem_chart_gfx11.get_sample_metrics()
|
|
|
|
# TCP-GL1 Read Bandwidth should be 96 GB/s = 96e9 Bytes/s
|
|
assert metrics["TCP-GL1 Read Bandwidth"] == 96e9
|
|
|
|
# DRAM Read Bandwidth should be 100 GB/s = 100e9 Bytes/s
|
|
assert metrics["DRAM Read Bandwidth"] == 100e9
|
|
|
|
def test_returns_copy(self):
|
|
"""Test that sample metrics returns a copy (not the original)."""
|
|
metrics1 = mem_chart_gfx11.get_sample_metrics()
|
|
metrics2 = mem_chart_gfx11.get_sample_metrics()
|
|
|
|
# Modify metrics1 and verify metrics2 is not affected
|
|
metrics1["TCP-GL1 Read Bandwidth"] = 0
|
|
assert metrics2["TCP-GL1 Read Bandwidth"] == 96e9
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for plot_mem_chart function (gfx11)
|
|
# =============================================================================
|
|
|
|
|
|
class TestPlotMemChartGfx11:
|
|
"""Tests for gfx11 plot_mem_chart - main chart generation."""
|
|
|
|
def test_returns_string(self):
|
|
"""Test that plot_mem_chart returns a string."""
|
|
metrics = mem_chart_gfx11.get_sample_metrics()
|
|
result = mem_chart_gfx11.plot_mem_chart(metrics, chart_title=DEFAULT_TITLE)
|
|
clean = common.strip_ansi(result)
|
|
|
|
assert isinstance(result, str)
|
|
assert len(result) > 0
|
|
assert "3. Memory Chart" in clean
|
|
assert "Normalization: per_kernel" in clean
|
|
assert "GPU" in clean and "System Memory" in clean
|
|
|
|
def test_contains_complete_rdna35_architecture(self):
|
|
"""RDNA3.5 output contains every rendered memory component."""
|
|
metrics = mem_chart_gfx11.get_sample_metrics()
|
|
output = common.strip_ansi(
|
|
mem_chart_gfx11.plot_mem_chart(metrics, chart_title=DEFAULT_TITLE)
|
|
)
|
|
expected_components = (
|
|
"Kernel",
|
|
"LDS",
|
|
"GL0 (TCP Cache)",
|
|
"SQC",
|
|
"GL1 Cache",
|
|
"GL2 Cache",
|
|
"GCEA",
|
|
"DRAM",
|
|
)
|
|
|
|
for component in expected_components:
|
|
assert component in output, f"Missing RDNA3.5 component: {component}"
|
|
|
|
def test_gfx115x_contains_heading_scope_and_directional_connectors(self):
|
|
"""RDNA3.5 output exposes chart scope and connector directions."""
|
|
metrics = mem_chart_gfx11.get_sample_metrics()
|
|
output = common.strip_ansi(
|
|
mem_chart_gfx11.plot_mem_chart(metrics, chart_title=DEFAULT_TITLE)
|
|
)
|
|
|
|
assert DEFAULT_TITLE in output
|
|
assert "GPU" in output
|
|
assert "System Memory" in output
|
|
assert "Read BW" in output
|
|
assert "Write BW" in output
|
|
assert "Atomic" in output
|
|
assert re.search(r"<(?!-+>)-{3,}", output)
|
|
assert re.search(r"(?<![<-])-{3,}>", output)
|
|
assert re.search(r"<-{3,}>", output)
|
|
|
|
def test_contains_bandwidth_values(self):
|
|
"""Test that output contains formatted bandwidth values."""
|
|
metrics = mem_chart_gfx11.get_sample_metrics()
|
|
result = mem_chart_gfx11.plot_mem_chart(metrics, chart_title=DEFAULT_TITLE)
|
|
|
|
# Should contain GB/s units since sample data uses GB/s range
|
|
assert "GB/s" in result
|
|
|
|
def test_normalize_mem_chart_metrics_flat_ordered(self):
|
|
"""Metrics are flattened to panel YAML order; extras dropped; missing None."""
|
|
raw = {"GL0 Cache Hit Rate (TCP Cache)": 1.0, "noise_key": 99}
|
|
norm = mem_chart_gfx11.normalize_mem_chart_metrics(raw)
|
|
assert list(norm.keys()) == list(mem_chart_gfx11.MEM_CHART_PANEL_METRIC_KEYS)
|
|
assert norm["GL0 Cache Hit Rate (TCP Cache)"] == 1.0
|
|
assert "noise_key" not in norm
|
|
assert norm["ICache Requests"] is None
|
|
|
|
def test_empty_metrics(self):
|
|
"""Test with empty metrics dictionary."""
|
|
result = mem_chart_gfx11.plot_mem_chart({}, chart_title=DEFAULT_TITLE)
|
|
|
|
# Should still produce output (with N/A values)
|
|
assert isinstance(result, str)
|
|
assert len(result) > 0
|
|
|
|
def test_partial_metrics(self):
|
|
"""Test with partial metrics (some missing)."""
|
|
partial_metrics = {
|
|
"TCP-GL1 Read Bandwidth": 50e9,
|
|
"GL1 Cache Utilization": 65.0,
|
|
# Missing many other metrics
|
|
}
|
|
result = mem_chart_gfx11.plot_mem_chart(
|
|
partial_metrics, chart_title=DEFAULT_TITLE
|
|
)
|
|
|
|
assert isinstance(result, str)
|
|
assert len(result) > 0
|
|
|
|
def test_extreme_bandwidth_values(self):
|
|
"""Test with extreme bandwidth values."""
|
|
extreme_metrics = {
|
|
"DRAM Read Bandwidth": 10e12, # 10 TB/s
|
|
"DRAM Write Bandwidth": 5e12, # 5 TB/s
|
|
}
|
|
result = mem_chart_gfx11.plot_mem_chart(
|
|
extreme_metrics, chart_title=DEFAULT_TITLE
|
|
)
|
|
|
|
assert "TB/s" in result
|
|
|
|
def test_zero_bandwidth_values(self):
|
|
"""Test with zero bandwidth values."""
|
|
zero_metrics = {
|
|
"DRAM Read Bandwidth": 0,
|
|
"DRAM Write Bandwidth": 0,
|
|
}
|
|
result = mem_chart_gfx11.plot_mem_chart(zero_metrics, chart_title=DEFAULT_TITLE)
|
|
|
|
assert "B/s" in result # Zero formats as "0.0 B/s"
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for DEFAULT_SAMPLE_METRICS constant (gfx11)
|
|
# =============================================================================
|
|
|
|
|
|
class TestDefaultSampleMetrics:
|
|
"""Tests for DEFAULT_SAMPLE_METRICS constant."""
|
|
|
|
def test_keys_match_mem_chart_panel_yaml(self):
|
|
"""Keys match gfx1151 Memory Chart YAML (MEM_CHART_PANEL_METRIC_KEYS)."""
|
|
assert set(mem_chart_gfx11.DEFAULT_SAMPLE_METRICS) == set(
|
|
mem_chart_gfx11.MEM_CHART_PANEL_METRIC_KEYS
|
|
)
|
|
assert len(mem_chart_gfx11.DEFAULT_SAMPLE_METRICS) == len(
|
|
mem_chart_gfx11.MEM_CHART_PANEL_METRIC_KEYS
|
|
)
|
|
|
|
def test_all_bandwidth_values_positive(self):
|
|
"""Test that all bandwidth values are positive."""
|
|
for key, value in mem_chart_gfx11.DEFAULT_SAMPLE_METRICS.items():
|
|
if "Bandwidth" in key:
|
|
assert value >= 0, f"{key} should be non-negative"
|
|
|
|
def test_all_rate_values_in_range(self):
|
|
"""Test that rate/percentage values are in valid range."""
|
|
for key, value in mem_chart_gfx11.DEFAULT_SAMPLE_METRICS.items():
|
|
if "Rate" in key or "Utilization" in key:
|
|
assert 0 <= value <= 100, f"{key} should be between 0 and 100"
|
|
|
|
def test_has_all_memory_hierarchy_levels(self):
|
|
"""Test that all memory hierarchy levels are represented."""
|
|
metrics = mem_chart_gfx11.DEFAULT_SAMPLE_METRICS
|
|
|
|
# GL0 (TCP)
|
|
assert any("TCP" in k for k in metrics.keys())
|
|
|
|
# LDS
|
|
assert any("LDS" in k for k in metrics.keys())
|
|
|
|
# GL1 Cache
|
|
assert any("GL1" in k for k in metrics.keys())
|
|
|
|
# GL2 Cache
|
|
assert any("GL2" in k for k in metrics.keys())
|
|
|
|
# DRAM
|
|
assert any("DRAM" in k for k in metrics.keys())
|
|
|
|
|
|
# =============================================================================
|
|
# Integration Tests (gfx11)
|
|
# =============================================================================
|
|
|
|
|
|
class TestIntegrationGfx11:
|
|
"""Integration tests for complete gfx11 workflows."""
|
|
|
|
def test_full_workflow_with_sample_data(self):
|
|
"""Test complete workflow with sample data."""
|
|
# Get sample metrics
|
|
metrics = mem_chart_gfx11.get_sample_metrics()
|
|
|
|
# Generate chart
|
|
chart = mem_chart_gfx11.plot_mem_chart(
|
|
metrics,
|
|
chart_title="3. Memory Chart (Normalization: per_dispatch)",
|
|
)
|
|
|
|
# Verify chart contains expected elements
|
|
assert isinstance(chart, str)
|
|
assert len(chart) > 100 # Should be substantial output
|
|
assert "Kernel" in chart
|
|
assert "Legend" in chart
|
|
|
|
def test_bandwidth_unit_consistency(self):
|
|
"""Test that bandwidth units are consistently formatted."""
|
|
# Create metrics with known bandwidth values
|
|
metrics = {
|
|
"TCP-GL1 Read Bandwidth": 100e9, # 100 GB/s
|
|
"TCP-GL1 Write Bandwidth": 50e9, # 50 GB/s
|
|
"GL1-GL2 Read Bandwidth": 75e9, # 75 GB/s
|
|
"GL1-GL2 Write Bandwidth": 25e9, # 25 GB/s
|
|
"DRAM Read Bandwidth": 200e9, # 200 GB/s
|
|
"DRAM Write Bandwidth": 100e9, # 100 GB/s
|
|
}
|
|
|
|
chart = mem_chart_gfx11.plot_mem_chart(metrics, chart_title=DEFAULT_TITLE)
|
|
|
|
# All values should show in GB/s since they're in that range
|
|
assert chart.count("GB/s") >= 6
|
|
|
|
def test_mixed_bandwidth_scales(self):
|
|
"""Test chart with mixed bandwidth scales."""
|
|
metrics = {
|
|
"DRAM Read Bandwidth": 1.5e12, # 1.5 TB/s
|
|
"GL2 Cache Read BW": 500e9, # 500 GB/s
|
|
"GL1-GL2 Read Bandwidth": 100e6, # 100 MB/s
|
|
"TCP-GL1 Read Bandwidth": 50e3, # 50 KB/s
|
|
}
|
|
|
|
chart = mem_chart_gfx11.plot_mem_chart(metrics, chart_title=DEFAULT_TITLE)
|
|
|
|
# Should contain multiple unit types
|
|
assert "TB/s" in chart
|
|
assert "GB/s" in chart
|
|
|
|
|
|
# =============================================================================
|
|
# Tests for plot_mem_chart function (gfx9)
|
|
# =============================================================================
|
|
|
|
|
|
GFX9_SAMPLE_METRICS = {
|
|
"Wavefront Occupancy": 1,
|
|
"Wave Life": 2,
|
|
"SALU": 3,
|
|
"SMEM": 4,
|
|
"VALU": 5,
|
|
"Matrix Ops": 6,
|
|
"VMEM": 7,
|
|
"LDS": 8,
|
|
"GWS": 9,
|
|
"BR": 10,
|
|
"Active CUs": 11,
|
|
"Num CUs": 12,
|
|
"VGPR": 13,
|
|
"SGPR": 14,
|
|
"LDS Allocation": 15,
|
|
"Scratch Allocation": 16,
|
|
"Wavefronts": 17,
|
|
"Workgroups": 18,
|
|
"LDS Req": 19,
|
|
"LDS Util": 20,
|
|
"LDS Latency": 21,
|
|
"VL1 Rd": 22,
|
|
"VL1 Wr": 23,
|
|
"VL1 Atomic": 24,
|
|
"VL1 Hit": 25,
|
|
"VL1 Lat": 26,
|
|
"VL1 Coalesce": 27,
|
|
"VL1 Stall": 28,
|
|
"sL1D Rd": 29,
|
|
"sL1D Hit": 30,
|
|
"sL1D Lat": 31,
|
|
"IL1 Fetch": 32,
|
|
"IL1 Hit": 33,
|
|
"IL1 Lat": 34,
|
|
"VL1_L2 Rd": 36,
|
|
"VL1_L2 Wr": 37,
|
|
"VL1_L2 Atomic": 38,
|
|
"sL1D_L2 Rd": 39,
|
|
"sL1D_L2 Wr": 40,
|
|
"sL1D_L2 Atomic": 41,
|
|
"IL1_L2 Rd": 42,
|
|
"L2 Hit": 43,
|
|
"L2 Rd": 44,
|
|
"L2 Wr": 45,
|
|
"L2 Atomic": 46,
|
|
"L2 Rd Lat": 47,
|
|
"L2 Wr Lat": 48,
|
|
"Fabric_L2 Rd": 49,
|
|
"Fabric_L2 Wr": 50,
|
|
"Fabric_L2 Atomic": 51,
|
|
"Fabric Rd Lat": 52,
|
|
"Fabric Wr Lat": 53,
|
|
"Fabric Atomic Lat": 54,
|
|
"HBM Rd": 55,
|
|
"HBM Wr": 56,
|
|
}
|
|
|
|
|
|
class TestPlotMemChartGfx9:
|
|
"""Tests for gfx9 plot_mem_chart - CDNA memory chart generation."""
|
|
|
|
def test_returns_non_empty_string(self):
|
|
"""Full sample metrics produce a non-empty chart string."""
|
|
result = mem_chart_gfx9.plot_mem_chart(
|
|
dict(GFX9_SAMPLE_METRICS), chart_title=DEFAULT_TITLE
|
|
)
|
|
assert isinstance(result, str)
|
|
assert len(result) > 0
|
|
|
|
def test_empty_metrics(self):
|
|
"""Empty metric dict still produces a non-empty chart (N/A placeholders)."""
|
|
result = mem_chart_gfx9.plot_mem_chart({}, chart_title=DEFAULT_TITLE)
|
|
assert isinstance(result, str)
|
|
assert len(result) > 0
|
|
|
|
def test_partial_metrics(self):
|
|
"""Partial metric dict still produces a non-empty chart."""
|
|
partial = {
|
|
"Wavefront Occupancy": 4,
|
|
"L2 Hit": 75,
|
|
"HBM Rd": 100,
|
|
}
|
|
result = mem_chart_gfx9.plot_mem_chart(partial, chart_title=DEFAULT_TITLE)
|
|
assert isinstance(result, str)
|
|
assert len(result) > 0
|
|
|
|
def test_contains_complete_cdna_architecture(self):
|
|
"""CDNA output contains every component enabled by the renderer."""
|
|
output = common.strip_ansi(
|
|
mem_chart_gfx9.plot_mem_chart(
|
|
dict(GFX9_SAMPLE_METRICS),
|
|
chart_title=DEFAULT_TITLE,
|
|
)
|
|
)
|
|
expected_components = (
|
|
"Instr Buff",
|
|
"Instr Dispatch",
|
|
"Exec",
|
|
"LDS",
|
|
"Vector L1 Cache",
|
|
"Scalar L1D Cache",
|
|
"Instr L1 Cache",
|
|
"L2 Cache",
|
|
"xGMI/PCIe",
|
|
"Fabric",
|
|
"HBM",
|
|
)
|
|
|
|
for component in expected_components:
|
|
assert component in output, f"Missing CDNA component: {component}"
|
|
|
|
assert re.search(r"(?<!x)GMI(?!/PCIe)", output)
|
|
|
|
def test_gfx9_heading_outside_chart_and_directional_connectors(self):
|
|
"""CDNA output prints the heading once outside the chart, with connectors."""
|
|
output = common.strip_ansi(
|
|
mem_chart_gfx9.plot_mem_chart(
|
|
dict(GFX9_SAMPLE_METRICS),
|
|
chart_title=DEFAULT_TITLE,
|
|
)
|
|
)
|
|
output_lines = output.splitlines()
|
|
|
|
assert output_lines[0] == DEFAULT_TITLE
|
|
assert "3. Memory Chart" not in "\n".join(output_lines[1:])
|
|
assert "Instr Buff" in output_lines[4]
|
|
assert output.count("(Normalization: per_kernel)") == 1
|
|
|
|
for connector_label in ("Req", "Fetch", "Rd", "Wr", "Atomic"):
|
|
assert connector_label in output
|
|
|
|
assert re.search(r"<(?!-+>)-{3,}", output)
|
|
assert re.search(r"(?<![<-])-{3,}>", output)
|
|
assert re.search(r"<-{3,}>", output)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("renderer", "metrics"),
|
|
[
|
|
pytest.param(
|
|
mem_chart_gfx11.plot_mem_chart,
|
|
mem_chart_gfx11.get_sample_metrics(),
|
|
id="gfx115x",
|
|
),
|
|
pytest.param(
|
|
mem_chart_gfx9.plot_mem_chart,
|
|
dict(GFX9_SAMPLE_METRICS),
|
|
id="gfx9",
|
|
),
|
|
],
|
|
)
|
|
def test_chart_title_appears_as_first_line(renderer, metrics):
|
|
"""Both renderers print the caller's chart_title as the first line."""
|
|
chart_title = "7. Memory Chart (Normalization: per_kernel)"
|
|
output = common.strip_ansi(renderer(metrics, chart_title=chart_title))
|
|
|
|
assert output.strip().splitlines()[0] == chart_title
|
|
assert "3. Memory Chart" not in output
|