Files

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

465 строки
17 KiB
C++
Исходник Постоянная ссылка Обычный вид История

2025-01-23 06:41:20 +05:30
// MIT License
//
// Copyright (c) 2023-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.
2023-11-17 01:49:51 -08:00
#include "client.hpp"
#include <rocprofiler-sdk/registration.h>
#include <rocprofiler-sdk/rocprofiler.h>
2023-11-17 01:49:51 -08:00
#include <unistd.h>
#include <cstdint>
2023-11-17 01:49:51 -08:00
#include <map>
2025-06-03 21:50:50 -05:00
#include <memory>
2023-11-17 01:49:51 -08:00
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <sstream>
#include <vector>
#define PRINT_ONLY_FAILING false
2024-02-07 20:03:21 -08:00
2023-11-17 01:49:51 -08:00
/**
* Tests the collection of all counters on the agent the test is run on.
*/
#define ROCPROFILER_CALL(result, msg) \
{ \
rocprofiler_status_t CHECKSTATUS = result; \
if(CHECKSTATUS != ROCPROFILER_STATUS_SUCCESS) \
{ \
std::string status_msg = rocprofiler_get_status_string(CHECKSTATUS); \
std::cerr << "[" #result "][" << __FILE__ << ":" << __LINE__ << "] " << msg \
<< " failed with error code " << CHECKSTATUS << ": " << status_msg \
<< std::endl; \
std::stringstream errmsg{}; \
errmsg << "[" #result "][" << __FILE__ << ":" << __LINE__ << "] " << msg " failure (" \
<< status_msg << ")"; \
throw std::runtime_error(errmsg.str()); \
} \
}
int
start()
{
return 1;
}
namespace
{
rocprofiler_context_id_t&
get_client_ctx()
{
static rocprofiler_context_id_t ctx{0};
2023-11-17 01:49:51 -08:00
return ctx;
}
rocprofiler_buffer_id_t&
get_buffer()
{
static rocprofiler_buffer_id_t buf = {};
return buf;
}
2024-02-07 20:03:21 -08:00
// Struct to validate that all dimension values are present. Does
// so by creating a tree of dimension values expected. If all are marked as
// having values, then all values are present in the output.
struct validate_dim_presence
{
validate_dim_presence() {}
2025-03-26 02:12:03 -05:00
void maybe_forward(const rocprofiler_counter_record_dimension_info_t& dim)
2024-02-07 20:03:21 -08:00
{
if(sub_vectors.empty())
{
for(size_t i = 0; i < dim.instance_size; i++)
{
sub_vectors.emplace_back(std::make_unique<validate_dim_presence>());
sub_vectors.back()->vector_pos = std::make_pair(dim, i);
}
}
else
{
for(auto& vec : sub_vectors)
{
vec->maybe_forward(dim);
}
}
}
void mark_seen(const rocprofiler_counter_instance_id_t& id)
{
if(sub_vectors.empty())
{
has_value = true;
return;
}
size_t pos = 0;
ROCPROFILER_CALL(rocprofiler_query_record_dimension_position(
id, sub_vectors.at(0)->vector_pos.first.id, &pos),
"Could not query position");
sub_vectors.at(pos)->mark_seen(id);
}
2025-03-26 02:12:03 -05:00
bool check_seen(
std::stringstream& out,
std::vector<std::pair<rocprofiler_counter_record_dimension_info_t, size_t>>& pos_stack)
2024-02-07 20:03:21 -08:00
{
bool ret = true;
if(sub_vectors.empty())
{
if(!has_value)
{
ret = false;
out << "\tMissing Value at [";
}
else
{
out << "\tHas Value at [";
}
for(const auto& [dim, pos] : pos_stack)
{
out << dim.name << ":" << pos << ",";
}
out << "]\n";
return ret;
}
for(size_t i = 0; i < sub_vectors.size(); i++)
{
pos_stack.push_back(sub_vectors[i]->vector_pos);
if(!sub_vectors[i]->check_seen(out, pos_stack)) ret = false;
pos_stack.pop_back();
}
return ret;
}
2025-03-26 02:12:03 -05:00
std::pair<rocprofiler_counter_record_dimension_info_t, size_t> vector_pos;
std::vector<std::unique_ptr<validate_dim_presence>> sub_vectors;
bool has_value{false};
2024-02-07 20:03:21 -08:00
};
2023-11-17 01:49:51 -08:00
struct CaptureRecords
{
std::shared_mutex m_mutex{};
// <counter id handle, expected instances>
2024-02-07 20:03:21 -08:00
std::map<uint64_t, size_t> expected{};
// expected dims that we should see data for
std::map<uint64_t, validate_dim_presence> expected_data_dims{};
std::map<uint64_t, std::string> expected_counter_names{};
std::vector<rocprofiler_counter_id_t> remaining{};
2023-11-17 01:49:51 -08:00
// <counter_id handle, instances seen>
std::map<uint64_t, size_t> captured{};
};
CaptureRecords* REC = new CaptureRecords;
CaptureRecords*
get_capture()
{
return REC;
}
void
buffered_callback(rocprofiler_context_id_t,
rocprofiler_buffer_id_t,
rocprofiler_record_header_t** headers,
size_t num_headers,
void*,
uint64_t)
{
2024-02-07 20:03:21 -08:00
auto& cap = *get_capture();
auto wlock = std::unique_lock{cap.m_mutex};
2023-11-17 01:49:51 -08:00
std::map<uint64_t, size_t> seen_counters;
for(size_t i = 0; i < num_headers; ++i)
{
auto* header = headers[i];
if(header->category == ROCPROFILER_BUFFER_CATEGORY_COUNTERS &&
header->kind == ROCPROFILER_COUNTER_RECORD_VALUE)
2023-11-17 01:49:51 -08:00
{
2023-11-30 14:58:54 -08:00
// Record the counters we have in the buffer and the number of instances of
// the counter we have seen.
2023-11-17 01:49:51 -08:00
rocprofiler_counter_id_t counter;
2025-03-26 02:12:03 -05:00
auto* record = static_cast<rocprofiler_counter_record_t*>(header->payload);
2023-11-17 01:49:51 -08:00
rocprofiler_query_record_counter_id(record->id, &counter);
2024-02-07 20:03:21 -08:00
cap.expected_data_dims.at(counter.handle).mark_seen(record->id);
2023-11-17 01:49:51 -08:00
seen_counters.emplace(counter.handle, 0).first->second++;
}
}
2023-11-30 14:58:54 -08:00
// Store these counts for post execution comparison
2023-11-17 01:49:51 -08:00
for(const auto& [counter_id, instances] : seen_counters)
{
cap.captured.emplace(counter_id, 0).first->second += instances;
}
}
using agent_map_t = std::map<uint64_t, const rocprofiler_agent_v0_t*>;
agent_map_t
get_agent_info()
{
auto iterate_cb = [](rocprofiler_agent_version_t agents_ver,
const void** agents_arr,
size_t num_agents,
void* user_data) {
if(agents_ver != ROCPROFILER_AGENT_INFO_VERSION_0)
throw std::runtime_error{"unexpected rocprofiler agent version"};
auto* agents_v = static_cast<agent_map_t*>(user_data);
for(size_t i = 0; i < num_agents; ++i)
{
const auto* itr = static_cast<const rocprofiler_agent_v0_t*>(agents_arr[i]);
agents_v->emplace(itr->id.handle, itr);
}
return ROCPROFILER_STATUS_SUCCESS;
};
auto _agents = agent_map_t{};
ROCPROFILER_CALL(
rocprofiler_query_available_agents(ROCPROFILER_AGENT_INFO_VERSION_0,
iterate_cb,
sizeof(rocprofiler_agent_t),
const_cast<void*>(static_cast<const void*>(&_agents))),
"query available agents");
return _agents;
}
2023-11-17 01:49:51 -08:00
void
dispatch_callback(rocprofiler_dispatch_counting_service_data_t dispatch_data,
rocprofiler_counter_config_id_t* config,
rocprofiler_user_data_t* /*user_data*/,
void* /*callback_data_args*/)
2023-11-17 01:49:51 -08:00
{
static auto agents = get_agent_info();
2023-11-17 01:49:51 -08:00
auto& cap = *get_capture();
auto wlock = std::unique_lock{cap.m_mutex};
2023-11-30 14:58:54 -08:00
/**
* Fetch all counters that are available for this agent if we haven't already.
* Each of these counters will be collected 1 by 1 for each dispatch until we
* have tried all counters. This requires the program to have at least counters
* number of kernel launches to test all counters.
*/
2023-11-17 01:49:51 -08:00
if(cap.expected.empty())
{
std::vector<rocprofiler_counter_id_t> counters_needed;
2024-01-25 23:47:40 -06:00
ROCPROFILER_CALL(rocprofiler_iterate_agent_supported_counters(
dispatch_data.dispatch_info.agent_id,
2024-01-25 23:47:40 -06:00
[](rocprofiler_agent_id_t,
rocprofiler_counter_id_t* counters,
size_t num_counters,
void* user_data) {
std::vector<rocprofiler_counter_id_t>* vec =
static_cast<std::vector<rocprofiler_counter_id_t>*>(user_data);
for(size_t i = 0; i < num_counters; i++)
{
vec->push_back(counters[i]);
}
return ROCPROFILER_STATUS_SUCCESS;
},
static_cast<void*>(&counters_needed)),
"Could not fetch supported counters");
2023-11-17 01:49:51 -08:00
for(auto& found_counter : counters_needed)
{
rocprofiler_counter_info_v1_t info;
ROCPROFILER_CALL(
rocprofiler_query_counter_info(
found_counter, ROCPROFILER_COUNTER_INFO_VERSION_1, static_cast<void*>(&info)),
"Could not query counter_id");
cap.expected_counter_names.emplace(found_counter.handle, std::string(info.name));
2024-02-07 20:03:21 -08:00
cap.remaining.push_back(found_counter);
// Query instance count for the specific agent being dispatched to
size_t instance_count = 0;
ROCPROFILER_CALL(
rocprofiler_query_counter_instance_count(
dispatch_data.dispatch_info.agent_id, found_counter, &instance_count),
"Could not query counter instance count");
cap.expected.emplace(found_counter.handle, instance_count);
2024-02-07 20:03:21 -08:00
auto& info_vector =
cap.expected_data_dims.emplace(found_counter.handle, validate_dim_presence{})
.first->second;
for(uint64_t i = 0; i < info.dimensions_count; i++)
{
info_vector.maybe_forward(*info.dimensions[i]);
}
2023-11-17 01:49:51 -08:00
}
if(cap.expected.empty())
{
std::clog << "No counters found for agent "
<< dispatch_data.dispatch_info.agent_id.handle << " ("
<< agents.at(dispatch_data.dispatch_info.agent_id.handle)->name << ")";
}
2023-11-17 01:49:51 -08:00
}
if(cap.remaining.empty()) return;
rocprofiler_counter_config_id_t profile = {.handle = 0};
2023-11-17 01:49:51 -08:00
2023-11-30 14:58:54 -08:00
// Select the next counter to collect.
if(rocprofiler_create_counter_config(
2024-06-19 00:11:03 -07:00
dispatch_data.dispatch_info.agent_id, &(cap.remaining.back()), 1, &profile) ==
ROCPROFILER_STATUS_SUCCESS)
{
*config = profile;
std::clog << "Attempting to read counter "
<< cap.expected_counter_names.at(cap.remaining.back().handle) << "\n";
2024-06-19 00:11:03 -07:00
}
2023-11-17 01:49:51 -08:00
cap.remaining.pop_back();
}
int
tool_init(rocprofiler_client_finalize_t, void*)
{
get_capture();
ROCPROFILER_CALL(rocprofiler_create_context(&get_client_ctx()), "context creation failed");
ROCPROFILER_CALL(rocprofiler_create_buffer(get_client_ctx(),
4096,
2048,
ROCPROFILER_BUFFER_POLICY_LOSSLESS,
buffered_callback,
nullptr,
&get_buffer()),
"buffer creation failed");
auto client_thread = rocprofiler_callback_thread_t{};
ROCPROFILER_CALL(rocprofiler_create_callback_thread(&client_thread),
"failure creating callback thread");
ROCPROFILER_CALL(rocprofiler_assign_callback_thread(get_buffer(), client_thread),
"failed to assign thread for buffer");
ROCPROFILER_CALL(rocprofiler_configure_buffer_dispatch_counting_service(
2023-11-17 01:49:51 -08:00
get_client_ctx(), get_buffer(), dispatch_callback, nullptr),
"Could not setup buffered service");
rocprofiler_start_context(get_client_ctx());
// no errors
return 0;
}
void
tool_fini(void*)
{
rocprofiler_flush_buffer(get_buffer());
rocprofiler_stop_context(get_client_ctx());
// Flush buffer isn't waiting....
sleep(2);
std::clog << "In tool fini\n";
2023-11-28 10:04:37 -06:00
auto& cap = *get_capture();
auto wlock = std::unique_lock{cap.m_mutex};
2023-11-17 01:49:51 -08:00
2023-11-30 14:58:54 -08:00
// Print out errors in counters that were not collected or had differences in instance
// count information.
2023-11-17 01:49:51 -08:00
if(cap.captured.size() != cap.expected.size())
{
std::clog << "[ERROR] Expected " << cap.expected.size() << " counters collected but got "
<< cap.captured.size() << "\n";
}
for(const auto& [counter_id, expected] : cap.expected)
{
std::string name = "UNKNOWN";
if(auto pos = cap.expected_counter_names.find(counter_id);
pos != cap.expected_counter_names.end())
{
name = pos->second;
}
std::optional<size_t> actual_size;
if(auto pos = cap.captured.find(counter_id); pos != cap.captured.end())
{
actual_size = pos->second;
}
if(actual_size && *actual_size != expected)
{
std::clog << (*actual_size == expected ? "" : "[ERROR]") << "Counter ID: " << counter_id
<< " (" << name << ")"
<< " expected " << expected << " instances and got " << *actual_size << "\n";
}
2024-02-07 20:03:21 -08:00
else if(!actual_size)
2023-11-17 01:49:51 -08:00
{
std::clog << "[ERROR] Counter ID: " << counter_id << " (" << name
<< ") is missing from output\n";
}
2024-02-07 20:03:21 -08:00
else
{
// Counter collected OK
2025-03-26 02:12:03 -05:00
std::stringstream ss;
std::vector<std::pair<rocprofiler_counter_record_dimension_info_t, size_t>> stack;
2024-02-07 20:03:21 -08:00
bool passed = cap.expected_data_dims.at(counter_id).check_seen(ss, stack);
if(!PRINT_ONLY_FAILING || !passed)
{
std::clog << (passed ? "[OK] " : "[ERROR] ") << "Counter ID: " << counter_id << " ("
<< name << ")"
<< " Expected: " << expected << " Got: " << *actual_size << "\n";
std::clog << ss.str();
}
}
2023-11-17 01:49:51 -08:00
}
}
} // namespace
extern "C" rocprofiler_tool_configure_result_t*
rocprofiler_configure(uint32_t version,
const char* runtime_version,
uint32_t priority,
2023-11-17 01:49:51 -08:00
rocprofiler_client_id_t* id)
{
// set the client name
id->name = "CounterClientSample";
// compute major/minor/patch version info
uint32_t major = version / 10000;
uint32_t minor = (version % 10000) / 100;
uint32_t patch = version % 100;
// generate info string
auto info = std::stringstream{};
info << id->name << " (priority=" << priority << ") is using rocprofiler-sdk v" << major << "."
<< minor << "." << patch << " (" << runtime_version << ")";
2023-11-17 01:49:51 -08:00
std::clog << info.str() << std::endl;
// create configure data
static auto cfg =
rocprofiler_tool_configure_result_t{sizeof(rocprofiler_tool_configure_result_t),
&tool_init,
&tool_fini,
static_cast<void*>(nullptr)};
// return pointer to configure data
return &cfg;
}