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
412 lines
15 KiB
Python
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
|