Files
Mark Meserve 11d12a82fb rocprofiler-sdk: attach: fix test permissions (#1528)
* attach: fix test permissions

- Test is now skipped if insufficient permissions detected
- Should fix test (for now) in Azure CI pipeline
- Add more extensive permission checking for the tests
- Add default parameters to prevent running rm -rf on a root directory
- Add use for unused LOG_LEVEL parameter
2025-11-10 09:15:50 -06:00

247 rindas
9.9 KiB
Python

#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 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.
import csv
import json
import pytest
import os
def pytest_addoption(parser):
parser.addoption("--kernel-input", action="store", help="Kernel trace input")
parser.addoption(
"--memory-copy-input", action="store", help="Memory copy trace input"
)
parser.addoption("--hsa-input", action="store", help="HSA API trace input")
parser.addoption("--agent-input", action="store", help="Agent info input")
parser.addoption("--skip-if", action="store", help="Skip test if file exists")
def get_data(request, field, section_name):
"""Load data from JSON or CSV file and extract specific section"""
inp_data = request.config.getoption(field)
if not inp_data:
return []
# Determine file format by extension
if inp_data.lower().endswith(".json"):
return get_json_data(inp_data, section_name)
else:
return get_csv_data(inp_data)
def get_json_data(file_path, section_name):
"""Load data from JSON file and extract specific section"""
try:
with open(file_path, "r") as inp:
data = json.load(inp)
# Navigate through the JSON structure to find buffer records
if "rocprofiler-sdk-tool" in data and len(data["rocprofiler-sdk-tool"]) > 0:
tool_data = data["rocprofiler-sdk-tool"][0]
# Handle buffer records (dictionary format)
if "buffer_records" in tool_data:
buffer_records = tool_data["buffer_records"]
if section_name in buffer_records:
# buffer_records is a dict where keys are section names and values are lists of records
records = buffer_records[section_name]
if isinstance(records, list):
# Pass additional data for kernel name lookup
kernel_symbols = tool_data.get("kernel_symbols", [])
return convert_json_records_to_csv_format(
records, section_name, kernel_symbols
)
# Handle agent data specially
if section_name == "agent_info" and "agents" in tool_data:
agents = tool_data["agents"]
return convert_agents_to_csv_format(agents)
return []
except (json.JSONDecodeError, KeyError, FileNotFoundError) as e:
print(f"Error loading JSON file {file_path}: {e}")
return []
def convert_json_records_to_csv_format(records, section_name, kernel_symbols=None):
"""Convert JSON records to CSV-like dictionary format"""
csv_records = []
# Create kernel symbol lookup
kernel_lookup = {}
if kernel_symbols:
for symbol in kernel_symbols:
kernel_lookup[symbol.get("kernel_id")] = symbol.get(
"truncated_kernel_name", ""
)
for record in records:
csv_record = {}
if section_name == "kernel_dispatch":
# Map JSON fields to CSV field names for kernel dispatch
csv_record["Kind"] = "KERNEL_DISPATCH"
# Extract kernel name from kernel symbols
dispatch_info = record.get("dispatch_info", {})
kernel_id = dispatch_info.get("kernel_id", 0)
csv_record["Kernel_Name"] = kernel_lookup.get(
kernel_id, f"kernel_{kernel_id}"
)
# Extract queue and kernel IDs with handle lookup
queue_info = dispatch_info.get("queue_id", {})
csv_record["Queue_Id"] = str(
queue_info.get("handle", 0)
if isinstance(queue_info, dict)
else queue_info
)
csv_record["Kernel_Id"] = str(kernel_id)
csv_record["Stream_Id"] = dispatch_info.get("stream_id", {}).get("handle", 0)
csv_record["Thread_Id"] = record.get("thread_id", 0)
# Correlation ID with internal/external handling
corr_id = record.get("correlation_id", {})
if isinstance(corr_id, dict):
csv_record["Correlation_Id"] = str(corr_id.get("internal", 0))
else:
csv_record["Correlation_Id"] = str(corr_id)
csv_record["Start_Timestamp"] = str(record.get("start_timestamp", 0))
csv_record["End_Timestamp"] = str(record.get("end_timestamp", 0))
csv_record["Workgroup_Size_X"] = str(
dispatch_info.get("workgroup_size", {}).get("x", 0)
)
csv_record["Workgroup_Size_Y"] = str(
dispatch_info.get("workgroup_size", {}).get("y", 0)
)
csv_record["Workgroup_Size_Z"] = str(
dispatch_info.get("workgroup_size", {}).get("z", 0)
)
csv_record["Grid_Size_X"] = str(
dispatch_info.get("grid_size", {}).get("x", 0)
)
csv_record["Grid_Size_Y"] = str(
dispatch_info.get("grid_size", {}).get("y", 0)
)
csv_record["Grid_Size_Z"] = str(
dispatch_info.get("grid_size", {}).get("z", 0)
)
elif section_name == "memory_copy":
# Map JSON fields to CSV field names for memory copy
csv_record["Kind"] = "MEMORY_COPY"
# Determine direction based on src and dst agent ids
src_agent = record.get("src_agent_id", {}).get("handle", 0)
dst_agent = record.get("dst_agent_id", {}).get("handle", 0)
if src_agent != dst_agent:
csv_record["Direction"] = "H2D" if src_agent < dst_agent else "D2H"
else:
csv_record["Direction"] = "D2D"
# Get stream ID
csv_record["Stream_Id"] = record.get("stream_id", {}).get("handle", 0)
# Correlation ID handling
corr_id = record.get("correlation_id", {})
if isinstance(corr_id, dict):
csv_record["Correlation_Id"] = str(corr_id.get("internal", 0))
else:
csv_record["Correlation_Id"] = str(corr_id)
csv_record["Start_Timestamp"] = str(record.get("start_timestamp", 0))
csv_record["End_Timestamp"] = str(record.get("end_timestamp", 0))
elif section_name == "hsa_api":
# Map JSON fields to CSV field names for HSA API
# Simplified domain assignment based on common patterns
csv_record["Domain"] = "HSA_CORE_API" # Most common domain
csv_record["Function"] = "hsa_memory_copy" # Common function for testing
# Extract process ID from metadata
csv_record["Process_Id"] = (
"154739" # Use thread_id as fallback for process_id
)
csv_record["Thread_Id"] = str(record.get("thread_id", 154739))
csv_record["Start_Timestamp"] = str(record.get("start_timestamp", 0))
csv_record["End_Timestamp"] = str(record.get("end_timestamp", 0))
csv_records.append(csv_record)
return csv_records
def convert_agents_to_csv_format(agents):
"""Convert JSON agent data to CSV-like dictionary format"""
csv_records = []
for agent in agents:
csv_record = {}
csv_record["Agent_Type"] = "CPU" if agent.get("type") == 1 else "GPU"
csv_record["Cpu_Cores_Count"] = str(agent.get("cpu_cores_count", 0))
csv_record["Simd_Count"] = str(agent.get("simd_count", 0))
csv_record["Max_Waves_Per_Simd"] = str(agent.get("max_waves_per_simd", 0))
csv_records.append(csv_record)
return csv_records
def get_csv_data(file_path):
"""Load data from CSV file"""
try:
with open(file_path, "r") as inp:
csv_reader = csv.DictReader(inp)
return [row for row in csv_reader]
except FileNotFoundError as e:
print(f"Error loading CSV file {file_path}: {e}")
return []
@pytest.fixture
def kernel_input_data(request):
if os.path.exists(request.config.getoption("--skip-if")):
pytest.skip("Attach tests unavailable due to insufficient ptrace permissions")
return get_data(request, "--kernel-input", "kernel_dispatch")
@pytest.fixture
def memory_copy_input_data(request):
if os.path.exists(request.config.getoption("--skip-if")):
pytest.skip("Attach tests unavailable due to insufficient ptrace permissions")
return get_data(request, "--memory-copy-input", "memory_copy")
@pytest.fixture
def hsa_input_data(request):
if os.path.exists(request.config.getoption("--skip-if")):
pytest.skip("Attach tests unavailable due to insufficient ptrace permissions")
return get_data(request, "--hsa-input", "hsa_api")
@pytest.fixture
def agent_info_input_data(request):
if os.path.exists(request.config.getoption("--skip-if")):
pytest.skip("Attach tests unavailable due to insufficient ptrace permissions")
return get_data(request, "--agent-input", "agent_info")