Files
TheRock/rocm-systems/projects/rocprofiler-sdk/tests/rocprofv3/ompt/validate.py
T
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

242 lines
10 KiB
Python

#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2024-2026 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.
# OMPT is a rocpd-only trace: rocprofv3 writes OMPT records to the rocpd SQLite
# database (and they are exported to csv/perfetto/otf2 via `rocpd convert`). The
# direct json/csv/perfetto/otf2 generators do not contain OMPT records, so these
# validators read OMPT from the rocpd database.
#
# In rocpd, ranged OMPT operations are stored as named rows in the `regions` view
# and instantaneous operations (start == end, e.g. omp_parallel_begin) as named
# rows in the `samples` view. The `regions_and_samples` view unions both, so the
# OMPT operation set is queried from there. The "OMPT" category string is set in
# source/lib/output/domain_type.cpp.
import os
import sqlite3
import sys
from collections import Counter
import pytest
OMPT_CATEGORY = "OMPT"
# The "parallel" --ompt-trace category resolves (in tool.cpp::resolve_ompt_ops) to
# exactly these host-side operations. A --ompt-trace parallel run must not record
# any operation outside this set.
PARALLEL_CATEGORY_OPS = {
"omp_parallel_begin",
"omp_parallel_end",
"omp_implicit_task",
"omp_work",
"omp_dispatch",
"omp_reduction",
"omp_masked",
}
# Representative host-side (CPU) OpenMP operations
HOST_PARALLEL_OPS = ("omp_parallel_begin", "omp_parallel_end")
HOST_WORK_OPS = ("omp_work", "omp_sync_region", "omp_dispatch")
def _ompt_op_counts(conn):
"""Return Counter[operation_name] -> row count for OMPT records in rocpd,
spanning both ranged regions and instant samples."""
rows = conn.execute(
"SELECT name, COUNT(*) FROM regions_and_samples WHERE category = ? GROUP BY name",
(OMPT_CATEGORY,),
).fetchall()
return Counter(dict(rows))
def _is_target_submit_operation(op_name):
"""The SDK enum spells the operation 'target_submit_emi'; accept anything that
contains 'target_submit'."""
return "target_submit" in op_name.lower()
def test_agent_info(rocpd_conn):
"""Every agent recorded in the rocpd database must be a well-formed CPU or GPU.
Catches regressions where rocprofv3 fails to enumerate agents alongside OMPT
tracing."""
rows = rocpd_conn.execute("SELECT type FROM rocpd_info_agent").fetchall()
assert len(rows) > 0, "rocpd database contains no agents"
for (agent_type,) in rows:
assert agent_type in ("CPU", "GPU"), f"unexpected agent type {agent_type!r}"
def test_rocpd_contains_ompt_records(rocpd_conn):
"""The rocpd database must contain well-formed OMPT records (regions + samples)."""
counts = _ompt_op_counts(rocpd_conn)
assert sum(counts.values()) > 0, "rocpd database has no OMPT records"
rows = rocpd_conn.execute(
"SELECT tid, start, end FROM regions_and_samples WHERE category = ?",
(OMPT_CATEGORY,),
).fetchall()
for tid, start, end in rows:
assert tid > 0, "OMPT record has non-positive thread id"
assert start > 0, "OMPT record has non-positive start timestamp"
# instant records have start == end; ranged records have start < end
assert end >= start, "OMPT record has end before start"
def test_ompt_host_side_events_present(rocpd_conn):
"""A default (unfiltered) --ompt-trace should capture host-side OpenMP activity,
not just target-offload events: the parallel-region lifecycle (parallel begin/end
+ implicit_task) and at least one work-related callback."""
ops = set(_ompt_op_counts(rocpd_conn))
if not ops:
pytest.skip("no OMPT records present; OMPT tracing was likely not run")
assert any(op in ops for op in HOST_PARALLEL_OPS), (
f"expected a host-side parallel-region event ({HOST_PARALLEL_OPS}); "
f"saw: {sorted(ops)}"
)
assert (
"omp_implicit_task" in ops
), f"expected host-side 'omp_implicit_task' events; saw: {sorted(ops)}"
assert any(op in ops for op in HOST_WORK_OPS), (
f"expected at least one work-related host event ({HOST_WORK_OPS}); "
f"saw: {sorted(ops)}"
)
def test_ompt_target_correlates_with_kernel_dispatch(rocpd_conn):
"""AC5 -- every OMPT target_submit record must share its internal correlation id
(stored as the rocpd event/region stack_id) with a kernel-dispatch record. This
validates that the rocprofv3 pipeline preserves the OMPT <-> kernel-dispatch
correlation the SDK promises. Skipped when kernel_dispatch records are absent (the
granular-target variant runs without --kernel-trace)."""
kernel_ids = {r[0] for r in rocpd_conn.execute("SELECT stack_id FROM kernels")}
if not kernel_ids:
pytest.skip("no kernel dispatch records; this run was without --kernel-trace")
submit_ids = {
r[0]
for r in rocpd_conn.execute(
"SELECT stack_id FROM regions WHERE category = ? AND name LIKE '%target_submit%'",
(OMPT_CATEGORY,),
)
}
assert submit_ids, (
"Expected at least one OMPT target_submit_emi record on a target-offload "
"program"
)
unmatched = submit_ids - kernel_ids
assert not unmatched, (
f"{len(unmatched)} OMPT target_submit correlation id(s) did not match any "
f"kernel_dispatch; first unmatched: {sorted(unmatched)[:5]}"
)
def test_granular_target_filter_only_target_ops(rocpd_conn):
"""AC2 -- when --ompt-trace target is supplied, the captured records must only be
target-category operations (target_emi, target_data_op_emi, target_submit_emi)."""
ops = set(_ompt_op_counts(rocpd_conn))
if not ops:
pytest.skip("no OMPT records present; the granular-filter run was likely not run")
leaks = {op for op in ops if "target" not in op.lower()}
assert not leaks, (
f"--ompt-trace target leaked non-target operations into the trace: "
f"{sorted(leaks)}"
)
assert any(
_is_target_submit_operation(op) for op in ops
), f"--ompt-trace target produced no target_submit records; saw: {sorted(ops)}"
def test_granular_host_filter_only_parallel_ops(rocpd_conn):
"""Counterpart to the target filter for a host-side filter: --ompt-trace parallel
must record only the parallel-category operations and must not leak
task/sync/mutex/target ops."""
ops = set(_ompt_op_counts(rocpd_conn))
if not ops:
pytest.skip("no OMPT records present; the host-filter run was likely not run")
leaks = {op for op in ops if op not in PARALLEL_CATEGORY_OPS}
assert not leaks, (
f"--ompt-trace parallel leaked non-parallel-category operations into the "
f"trace: {sorted(leaks)}"
)
assert any(
op in ops
for op in ("omp_parallel_begin", "omp_parallel_end", "omp_implicit_task")
), f"--ompt-trace parallel produced no parallel-region records; saw: {sorted(ops)}"
def test_ompt_all_form_is_complete(rocpd_conn):
"""--ompt-trace all must behave like bare --ompt-trace and produce the complete
OpenMP trace: both host-side (parallel/implicit-task) and, on a target-offload
workload, target_submit events."""
ops = set(_ompt_op_counts(rocpd_conn))
if not ops:
pytest.skip("no OMPT records present; OMPT tracing was likely not run")
assert any(op in ops for op in HOST_PARALLEL_OPS), (
f"--ompt-trace all is missing host-side parallel events ({HOST_PARALLEL_OPS}); "
f"saw: {sorted(ops)}"
)
assert (
"omp_implicit_task" in ops
), f"--ompt-trace all is missing host-side 'omp_implicit_task'; saw: {sorted(ops)}"
assert any(_is_target_submit_operation(op) for op in ops), (
f"--ompt-trace all is missing target_submit events on a target-offload "
f"workload; saw: {sorted(ops)}"
)
def test_sys_trace_implicit_ompt_in_rocpd(request):
"""Regression guard for the --sys-trace implicit-OMPT path. Profiling with
--sys-trace (no explicit --ompt-trace) and a non-rocpd --output-format must
still implicitly enable OMPT and auto-add the rocpd output so OMPT data is not
dropped. A missing database here means that auto-add regressed, so this asserts
the database exists (rather than skipping like the shared rocpd_conn fixture)
and that it actually contains OMPT records."""
filename = request.config.getoption("--rocpd-input")
assert os.path.isfile(filename), (
f"rocpd database '{filename}' was not created; --sys-trace should implicitly "
"enable OMPT and auto-add the 'rocpd' output format even when another "
"--output-format was requested"
)
conn = sqlite3.connect(filename)
try:
counts = _ompt_op_counts(conn)
assert (
sum(counts.values()) > 0
), "rocpd database contains no OMPT records under --sys-trace"
assert any(op in counts for op in HOST_PARALLEL_OPS), (
f"expected host-side parallel-region events ({HOST_PARALLEL_OPS}) under "
f"--sys-trace; saw: {sorted(counts)}"
)
finally:
conn.close()
if __name__ == "__main__":
exit_code = pytest.main(["-x", __file__] + sys.argv[1:])
sys.exit(exit_code)