Files
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

412 lines
15 KiB
Python

#!/usr/bin/env python3
###############################################################################
# MIT License
#
# Copyright (c) 2026 Advanced Micro Devices, Inc.
###############################################################################
"""Tests for multi-database analysis view unioning across ATTACHed shards.
Ports rocm-systems#4982. Real rocpd DB files store `kernels`, `memory_copies`,
`regions` as SQL VIEWs — not base tables. When PerfxpertConnection ATTACHes
multiple shards, analysis code does `SELECT * FROM kernels` against the
unified connection and expects to see rows from ALL shards. Before the
fix the UNION ALL logic only handled base tables in ``_PERFXPERT_TABLES``,
so queries against the analysis views silently returned rows from the
first shard only.
"""
import sqlite3
def _create_test_db(path, kernel_name, kernel_duration):
"""Create a minimal DB with real analysis VIEWs over backing tables."""
conn = sqlite3.connect(str(path))
conn.execute(
"CREATE TABLE kernels_data (name TEXT, start INTEGER, end INTEGER, "
"duration INTEGER)"
)
conn.execute(
"INSERT INTO kernels_data VALUES (?, 100, ?, ?)",
(kernel_name, 100 + kernel_duration, kernel_duration),
)
conn.execute(
"CREATE VIEW kernels AS "
"SELECT name, start, end, duration FROM kernels_data"
)
conn.execute(
"CREATE TABLE memory_copies_data "
"(name TEXT, start INTEGER, end INTEGER, duration INTEGER, "
"size INTEGER)"
)
conn.execute(
"CREATE VIEW memory_copies AS "
"SELECT name, start, end, duration, size FROM memory_copies_data"
)
conn.execute(
"CREATE TABLE regions_data "
"(name TEXT, category TEXT, start INTEGER, end INTEGER, "
"duration INTEGER)"
)
conn.execute(
"CREATE VIEW regions AS "
"SELECT name, category, start, end, duration FROM regions_data"
)
conn.execute(
"CREATE TABLE pmc_events "
"(id INTEGER PRIMARY KEY, dispatch_id INTEGER, "
"counter_name TEXT, counter_value REAL)"
)
conn.commit()
conn.close()
class TestMultiFileUnionViews:
def test_time_breakdown_percentages_do_not_exceed_100_for_multidb_overlap(
self, tmp_path
):
"""Multi-DB breakdown must preserve wall-clock runtime and expose a
separate normalization runtime for overlap-safe percentages."""
db0 = tmp_path / "shard0.db"
db1 = tmp_path / "shard1.db"
_create_test_db(db0, "kernel_A", 80)
_create_test_db(db1, "kernel_B", 80)
from perfxpert.analyze import compute_time_breakdown
from perfxpert.connection import PerfxpertConnection
breakdown = compute_time_breakdown(PerfxpertConnection([str(db0), str(db1)]))
assert breakdown["total_kernel_time"] == 160
assert breakdown["total_runtime"] == 80
assert breakdown["normalized_runtime"] == 160
assert breakdown["kernel_percent"] == 100.0
assert breakdown["memcpy_percent"] == 0.0
assert breakdown["overhead_percent"] == 0.0
def test_time_breakdown_uses_memcpy_runtime_for_multidb_overlap(self, tmp_path):
db0 = tmp_path / "shard0.db"
db1 = tmp_path / "shard1.db"
_create_test_db(db0, "kernel_A", 0)
_create_test_db(db1, "kernel_B", 0)
for path, name in [(db0, "copy0"), (db1, "copy1")]:
conn = sqlite3.connect(str(path))
conn.execute("DELETE FROM kernels_data")
conn.execute(
"INSERT INTO memory_copies_data VALUES (?, 100, 180, 80, 1024)",
(name,),
)
conn.commit()
conn.close()
from perfxpert.analyze import compute_time_breakdown
from perfxpert.connection import PerfxpertConnection
breakdown = compute_time_breakdown(PerfxpertConnection([str(db0), str(db1)]))
assert breakdown["total_kernel_time"] == 0
assert breakdown["total_memcpy_time"] == 160
assert breakdown["total_runtime"] == 80
assert breakdown["normalized_runtime"] == 160
assert breakdown["kernel_percent"] == 0.0
assert breakdown["memcpy_percent"] == 100.0
assert breakdown["overhead_percent"] == 0.0
def test_time_breakdown_counts_shards_with_only_one_activity_source(self, tmp_path):
db0 = tmp_path / "shard0.db"
db1 = tmp_path / "shard1.db"
_create_test_db(db0, "kernel_A", 80)
_create_test_db(db1, "kernel_B", 0)
conn = sqlite3.connect(str(db1))
conn.execute("DELETE FROM kernels_data")
conn.execute(
"INSERT INTO memory_copies_data VALUES ('copy_B', 100, 120, 20, 1024)"
)
conn.commit()
conn.close()
from perfxpert.analyze import compute_time_breakdown
from perfxpert.connection import PerfxpertConnection
breakdown = compute_time_breakdown(PerfxpertConnection([str(db0), str(db1)]))
assert breakdown["total_kernel_time"] == 80
assert breakdown["total_memcpy_time"] == 20
assert breakdown["total_runtime"] == 80
assert breakdown["normalized_runtime"] == 100
assert breakdown["kernel_percent"] == 80.0
assert breakdown["memcpy_percent"] == 20.0
assert breakdown["overhead_percent"] == 0.0
def test_time_breakdown_uses_one_envelope_per_shard_across_sources(self, tmp_path):
db0 = tmp_path / "shard0.db"
db1 = tmp_path / "shard1.db"
_create_test_db(db0, "kernel_A", 80)
_create_test_db(db1, "kernel_B", 20)
conn = sqlite3.connect(str(db0))
conn.execute(
"INSERT INTO memory_copies_data VALUES ('copy_A', 300, 340, 40, 1024)"
)
conn.commit()
conn.close()
from perfxpert.analyze import compute_time_breakdown
from perfxpert.connection import PerfxpertConnection
breakdown = compute_time_breakdown(PerfxpertConnection([str(db0), str(db1)]))
assert breakdown["total_runtime"] == 240
assert breakdown["normalized_runtime"] == 260
assert breakdown["total_kernel_time"] == 100
assert breakdown["total_memcpy_time"] == 40
def test_kernels_visible_from_both_dbs(self, tmp_path):
db0 = tmp_path / "shard0.db"
db1 = tmp_path / "shard1.db"
_create_test_db(db0, "kernel_A", 1000)
_create_test_db(db1, "kernel_B", 2000)
for path in (db0, db1):
conn = sqlite3.connect(str(path))
obj_type = conn.execute(
"SELECT type FROM sqlite_master WHERE name='kernels'"
).fetchone()[0]
conn.close()
assert obj_type == "view"
from perfxpert.connection import (
PerfxpertConnection,
execute_statement,
)
conn = PerfxpertConnection([str(db0), str(db1)])
rows = execute_statement(
conn, "SELECT name FROM kernels ORDER BY name"
).fetchall()
names = [r[0] for r in rows]
assert "kernel_A" in names
assert "kernel_B" in names
assert len(rows) == 2
def test_memory_copies_from_both_dbs(self, tmp_path):
db0 = tmp_path / "shard0.db"
db1 = tmp_path / "shard1.db"
_create_test_db(db0, "k", 100)
_create_test_db(db1, "k", 100)
for path, mc_name in [(db0, "h2d_0"), (db1, "h2d_1")]:
c = sqlite3.connect(str(path))
c.execute(
"INSERT INTO memory_copies_data VALUES (?, 0, 100, 100, 1024)",
(mc_name,),
)
c.commit()
c.close()
from perfxpert.connection import (
PerfxpertConnection,
execute_statement,
)
conn = PerfxpertConnection([str(db0), str(db1)])
rows = execute_statement(
conn, "SELECT name FROM memory_copies"
).fetchall()
assert len(rows) == 2
def test_regions_from_both_dbs(self, tmp_path):
db0 = tmp_path / "shard0.db"
db1 = tmp_path / "shard1.db"
_create_test_db(db0, "k", 100)
_create_test_db(db1, "k", 100)
for path, rname in [(db0, "region_0"), (db1, "region_1")]:
c = sqlite3.connect(str(path))
c.execute(
"INSERT INTO regions_data VALUES (?, 'api', 0, 100, 100)",
(rname,),
)
c.commit()
c.close()
from perfxpert.connection import (
PerfxpertConnection,
execute_statement,
)
conn = PerfxpertConnection([str(db0), str(db1)])
rows = execute_statement(conn, "SELECT name FROM regions").fetchall()
assert len(rows) == 2
def test_single_db_still_works(self, tmp_path):
"""Single-DB path must not regress — only the multi-DB union path is special."""
db0 = tmp_path / "single.db"
_create_test_db(db0, "kernel_only", 500)
from perfxpert.connection import (
PerfxpertConnection,
execute_statement,
)
conn = PerfxpertConnection([str(db0)])
rows = execute_statement(conn, "SELECT name FROM kernels").fetchall()
assert len(rows) == 1
assert rows[0][0] == "kernel_only"
def test_mixed_schema_shards_use_column_intersection(self, tmp_path):
"""Cycle-2 I-2 regression: shard[1] with an extra column must not
raise at query time. The unified view must SELECT only the
intersection of columns, and a RuntimeWarning must be emitted so
the caller knows a column was dropped."""
import sqlite3
import warnings
db0 = tmp_path / "shard0.db"
db1 = tmp_path / "shard1.db"
# shard0: baseline schema (no "extra_col")
_create_test_db(db0, "kernel_A", 1000)
# shard1: identical table but with an extra "extra_col" column
conn1 = sqlite3.connect(str(db1))
conn1.execute(
"CREATE TABLE kernels (name TEXT, start INTEGER, end INTEGER, "
"duration INTEGER, extra_col TEXT)"
)
conn1.execute(
"INSERT INTO kernels VALUES ('kernel_B', 100, 200, 100, 'x')"
)
conn1.execute(
"CREATE TABLE pmc_events "
"(id INTEGER PRIMARY KEY, dispatch_id INTEGER, "
"counter_name TEXT, counter_value REAL)"
)
conn1.commit()
conn1.close()
from perfxpert.connection import (
PerfxpertConnection,
execute_statement,
)
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
conn = PerfxpertConnection([str(db0), str(db1)])
# Rows from both shards must be visible — no OperationalError at
# query time.
rows = execute_statement(
conn, "SELECT name FROM kernels ORDER BY name"
).fetchall()
assert {r[0] for r in rows} == {"kernel_A", "kernel_B"}
# The dropped "extra_col" must NOT be addressable through the
# unified view (confirming intersection semantics).
with __import__("pytest").raises(sqlite3.OperationalError):
execute_statement(conn, "SELECT extra_col FROM kernels").fetchall()
# A RuntimeWarning must have surfaced, naming the dropped column.
matches = [
w for w in caught
if issubclass(w.category, RuntimeWarning)
and "extra_col" in str(w.message)
]
assert matches, (
f"expected RuntimeWarning mentioning 'extra_col'; got: "
f"{[str(w.message) for w in caught]}"
)
def test_mixed_schema_shards_extra_col_on_shard0(self, tmp_path):
"""Symmetric case: extra column on shard[0] instead of shard[1]
must not leak through the unified view (intersection is
order-independent)."""
import sqlite3
import warnings
db0 = tmp_path / "shard0.db"
db1 = tmp_path / "shard1.db"
# shard0: extra column
conn0 = sqlite3.connect(str(db0))
conn0.execute(
"CREATE TABLE kernels (name TEXT, start INTEGER, end INTEGER, "
"duration INTEGER, extra_col TEXT)"
)
conn0.execute(
"INSERT INTO kernels VALUES ('kernel_A', 0, 100, 100, 'y')"
)
conn0.execute(
"CREATE TABLE pmc_events "
"(id INTEGER PRIMARY KEY, dispatch_id INTEGER, "
"counter_name TEXT, counter_value REAL)"
)
conn0.commit()
conn0.close()
# shard1: baseline schema
_create_test_db(db1, "kernel_B", 2000)
from perfxpert.connection import (
PerfxpertConnection,
execute_statement,
)
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
conn = PerfxpertConnection([str(db0), str(db1)])
rows = execute_statement(
conn, "SELECT name FROM kernels ORDER BY name"
).fetchall()
assert {r[0] for r in rows} == {"kernel_A", "kernel_B"}
with __import__("pytest").raises(sqlite3.OperationalError):
execute_statement(conn, "SELECT extra_col FROM kernels").fetchall()
assert any(
issubclass(w.category, RuntimeWarning)
and "extra_col" in str(w.message)
for w in caught
)
def test_view_missing_from_one_shard_does_not_break(self, tmp_path):
"""If a shard lacks one of the analysis views, union the rest."""
db0 = tmp_path / "shard0.db"
db1 = tmp_path / "shard1.db"
_create_test_db(db0, "kernel_A", 1000)
# shard1 has kernels but NO memory_copies / regions
conn1 = sqlite3.connect(str(db1))
conn1.execute(
"CREATE TABLE kernels (name TEXT, start INTEGER, end INTEGER, "
"duration INTEGER)"
)
conn1.execute(
"INSERT INTO kernels VALUES ('kernel_B', 100, 200, 100)"
)
conn1.execute(
"CREATE TABLE pmc_events "
"(id INTEGER PRIMARY KEY, dispatch_id INTEGER, "
"counter_name TEXT, counter_value REAL)"
)
conn1.commit()
conn1.close()
from perfxpert.connection import (
PerfxpertConnection,
execute_statement,
)
conn = PerfxpertConnection([str(db0), str(db1)])
# kernels exists in both shards → both names visible
rows = execute_statement(
conn, "SELECT name FROM kernels ORDER BY name"
).fetchall()
assert {r[0] for r in rows} == {"kernel_A", "kernel_B"}
# memory_copies exists only in shard0 → still queryable
rows_mc = execute_statement(
conn, "SELECT COUNT(*) FROM memory_copies"
).fetchone()
assert rows_mc[0] == 0