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
273 lines
9.6 KiB
Python
273 lines
9.6 KiB
Python
#!/usr/bin/env python3
|
|
# MIT License
|
|
#
|
|
# Copyright (c) 2024-2025 Advanced Micro Devices, Inc.
|
|
#
|
|
# 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.
|
|
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
|
|
def extract_json_kernel_records(json_root):
|
|
"""Extract kernel dispatch records from JSON output."""
|
|
assert "rocprofiler-sdk-tool" in json_root, "missing rocprofiler-sdk-tool in JSON"
|
|
tool = json_root["rocprofiler-sdk-tool"]
|
|
if isinstance(tool, list) and len(tool) > 0:
|
|
tool = tool[0]
|
|
|
|
assert "buffer_records" in tool, "missing buffer_records in JSON"
|
|
buffer_records = tool["buffer_records"]
|
|
|
|
for key in ("kernel_dispatch", "kernel_trace", "kernel_dispatch_trace"):
|
|
if (
|
|
key in buffer_records
|
|
and isinstance(buffer_records[key], list)
|
|
and len(buffer_records[key]) > 0
|
|
):
|
|
return buffer_records[key]
|
|
|
|
raise AssertionError(
|
|
"no kernel dispatch records found in JSON buffer_records keys="
|
|
f"{list(buffer_records.keys())}"
|
|
)
|
|
|
|
|
|
def _as_int(value, *, field="value"):
|
|
assert value is not None, f"missing {field}"
|
|
try:
|
|
return int(value)
|
|
except Exception as exc:
|
|
raise AssertionError(
|
|
f"failed to parse int for {field}: {value!r} ({exc})"
|
|
) from exc
|
|
|
|
|
|
def _extract_dispatch_id_from_json_record(record):
|
|
"""
|
|
Prefer dispatch_info.dispatch_id.
|
|
Fallback to correlation_id.internal (or correlation_id if scalar).
|
|
Return int.
|
|
"""
|
|
dispatch_info = record.get("dispatch_info", {})
|
|
dispatch_id = None
|
|
|
|
if isinstance(dispatch_info, dict):
|
|
dispatch_id = dispatch_info.get("dispatch_id", None)
|
|
|
|
if dispatch_id is None:
|
|
correlation_id = record.get("correlation_id", {})
|
|
if isinstance(correlation_id, dict):
|
|
dispatch_id = correlation_id.get("internal", None)
|
|
else:
|
|
dispatch_id = correlation_id
|
|
|
|
return _as_int(dispatch_id, field="dispatch_id/correlation_id")
|
|
|
|
|
|
def build_json_duration_map(records):
|
|
"""
|
|
Build map:
|
|
key(int dispatch_id) -> (start, end, duration)
|
|
"""
|
|
result = {}
|
|
|
|
for record in records:
|
|
dispatch_id = _extract_dispatch_id_from_json_record(record)
|
|
|
|
start = _as_int(record.get("start_timestamp"), field="start_timestamp")
|
|
end = _as_int(record.get("end_timestamp"), field="end_timestamp")
|
|
|
|
assert start > 0 and end > 0, f"invalid timestamps start={start} end={end}"
|
|
assert end >= start, f"end before start: start={start} end={end}"
|
|
|
|
assert (
|
|
dispatch_id not in result
|
|
), f"duplicate dispatch_id {dispatch_id} found in JSON records"
|
|
result[dispatch_id] = (start, end, end - start)
|
|
|
|
assert len(result) > 0, "no kernel records extracted from JSON"
|
|
return result
|
|
|
|
|
|
def load_kernel_rows_via_rocpd(db_path):
|
|
"""
|
|
Use rocpd Python API and reuse rocpd.csv kernel query logic directly.
|
|
Returns list[dict].
|
|
"""
|
|
try:
|
|
import rocpd
|
|
from rocpd import csv as rocpd_csv
|
|
from rocpd import output_config as rocpd_output_config
|
|
except Exception as exc:
|
|
raise AssertionError(
|
|
"failed to import rocpd python modules. "
|
|
f"Ensure PYTHONPATH is set for rocprofiler-sdk build tree. ({exc})"
|
|
) from exc
|
|
|
|
data = rocpd.connect([db_path])
|
|
|
|
config = rocpd_output_config.output_config()
|
|
query = rocpd_csv.get_kernel_csv_query(config)
|
|
|
|
cursor = rocpd.execute(data, query)
|
|
columns = [desc[0] for desc in cursor.description]
|
|
rows = [dict(zip(columns, row)) for row in cursor.fetchall()]
|
|
|
|
assert len(rows) > 0, f"no rows returned from kernel query in db: {db_path}"
|
|
return rows
|
|
|
|
|
|
_DB_ROW_COLUMN_NAMES = ["dispatch_id", "start_timestamp", "end_timestamp", "duration"]
|
|
|
|
|
|
def _extract_values_from_db_row(row):
|
|
"""
|
|
Extract (dispatch_id, start, end, duration) from a DB row dict.
|
|
Builds a single case-insensitive lookup table for the row, then fetches
|
|
each required column. Column names reflect the output of
|
|
rocpd_csv.get_kernel_csv_query(), which aliases and extends the raw
|
|
rocpd_kernel_dispatch schema columns.
|
|
"""
|
|
lowered_row = {k.lower(): v for k, v in row.items()}
|
|
dispatch_id, start, end, duration = [
|
|
_as_int(lowered_row.get(name), field=name) for name in _DB_ROW_COLUMN_NAMES
|
|
]
|
|
return dispatch_id, start, end, duration
|
|
|
|
|
|
def test_rocpd_kernel_trace_duration(json_data, db_path):
|
|
"""
|
|
Validate that kernel trace Duration exists on the rocpd CSV path and matches JSON.
|
|
|
|
Strategy:
|
|
- JSON and rocpd DB are generated from the SAME rocprofv3 execution
|
|
(ROCPROF_OUTPUT_FORMAT=json,rocpd)
|
|
- Read kernel records from JSON
|
|
- Read kernel rows from rocpd DB using rocpd Python API
|
|
- Reuse rocpd.csv.get_kernel_csv_query(config) so test and CSV path stay aligned
|
|
- Enforce:
|
|
* Duration == End_Timestamp - Start_Timestamp
|
|
* Start/End/Duration EXACTLY match JSON for each dispatch_id
|
|
* All DB rows match JSON rows
|
|
"""
|
|
db_rows = load_kernel_rows_via_rocpd(db_path)
|
|
|
|
json_records = extract_json_kernel_records(json_data)
|
|
json_map = build_json_duration_map(json_records)
|
|
|
|
total_count = len(db_rows)
|
|
matched_count = 0
|
|
mismatches = []
|
|
missing_in_json = []
|
|
|
|
for row in db_rows:
|
|
dispatch_id, start, end, duration = _extract_values_from_db_row(row)
|
|
|
|
assert (
|
|
start > 0 and end > 0
|
|
), f"invalid DB timestamps: start={start} end={end} dispatch_id={dispatch_id}"
|
|
assert (
|
|
end >= start
|
|
), f"DB end before start: start={start} end={end} dispatch_id={dispatch_id}"
|
|
assert duration == (end - start), (
|
|
f"DB duration mismatch: duration={duration} != end-start={end - start} "
|
|
f"dispatch_id={dispatch_id}"
|
|
)
|
|
|
|
if dispatch_id not in json_map:
|
|
missing_in_json.append(dispatch_id)
|
|
continue
|
|
|
|
matched_count += 1
|
|
json_start, json_end, json_duration = json_map[dispatch_id]
|
|
|
|
start_diff = start - json_start
|
|
end_diff = end - json_end
|
|
duration_diff = duration - json_duration
|
|
|
|
if start_diff != 0 or end_diff != 0 or duration_diff != 0:
|
|
mismatches.append(
|
|
{
|
|
"dispatch_id": dispatch_id,
|
|
"db_start": start,
|
|
"json_start": json_start,
|
|
"start_diff": start_diff,
|
|
"db_end": end,
|
|
"json_end": json_end,
|
|
"end_diff": end_diff,
|
|
"db_duration": duration,
|
|
"json_duration": json_duration,
|
|
"duration_diff": duration_diff,
|
|
}
|
|
)
|
|
|
|
if missing_in_json:
|
|
sample = missing_in_json[:10]
|
|
raise AssertionError(
|
|
"Some DB kernel rows had dispatch_id not present in JSON records. "
|
|
"Since JSON and rocpd come from the same execution, dispatch IDs should align.\n"
|
|
f"Missing count: {len(missing_in_json)}/{total_count}\n"
|
|
f"Sample missing dispatch_ids: {sample}"
|
|
)
|
|
|
|
if mismatches:
|
|
lines = [
|
|
"",
|
|
"TIMESTAMP/DURATION MISMATCHES DETECTED",
|
|
f"{'Dispatch':<12} {'StartDiff':<12} {'EndDiff':<12} {'DurDiff':<12}",
|
|
"=" * 56,
|
|
]
|
|
|
|
for item in mismatches[:10]:
|
|
lines.append(
|
|
f"{item['dispatch_id']:<12} "
|
|
f"{item['start_diff']:<12} "
|
|
f"{item['end_diff']:<12} "
|
|
f"{item['duration_diff']:<12}"
|
|
)
|
|
|
|
if len(mismatches) > 10:
|
|
lines.append(f"... and {len(mismatches) - 10} more mismatches")
|
|
|
|
first = mismatches[0]
|
|
detail = (
|
|
f"\n\nExample mismatch for dispatch {first['dispatch_id']}:\n"
|
|
f" DB: start={first['db_start']}, end={first['db_end']}, "
|
|
f"duration={first['db_duration']}\n"
|
|
f" JSON: start={first['json_start']}, end={first['json_end']}, "
|
|
f"duration={first['json_duration']}\n"
|
|
f" Diff: start={first['start_diff']}, end={first['end_diff']}, "
|
|
f"duration={first['duration_diff']}\n"
|
|
f"Total mismatches: {len(mismatches)}/{total_count}\n"
|
|
"NOTE: Since JSON and rocpd come from the same execution, these should be identical."
|
|
)
|
|
raise AssertionError("\n".join(lines) + detail)
|
|
|
|
assert matched_count > 0, "No DB rows matched JSON records"
|
|
assert (
|
|
matched_count == total_count
|
|
), f"Only {matched_count}/{total_count} DB rows matched JSON"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
rc = pytest.main(["-x", __file__] + sys.argv[1:])
|
|
sys.exit(rc)
|