Add support for AQL dimensions (#262)

* Add support for AQL dimension changes

Adds support for returning dimensions from AQLProfile through rocprofiler
to tools. Includes a much larger expanded test suite that covers nearly
all files in counter collection.

Specific changes below:

samples/counter_collection/print_functional_counters: Modified to check
the validity of dimensions returned in comparison to the actual underlying
data obtained from a kernel execution.

rocprofiler-sdk/aql/helpers: adds function calls to support fetching
dimension information from AQLProfile.

rocprofiler-sdk/aql/packet_construct: modified to allow for events
to be exported to aid evaluate_ast in decoding the output buffer.

lib/rocprofiler-sdk/counters: Instance count now derived from dimension
sizes. rocprofiler_query_counter_dimensions now moved to a callback format
to improve usability.

rocprofiler-sdk/counters/core: Code migrations and exports of functions
for testing.

rocprofiler-sdk/counters/dimensions: Generates a dimension cache to be
used when querying dimension information for a counter id.

rocprofiler-sdk/counters/evaluate_ast: Modified to pass back correct
dimension information and to check/determine output dimensions for derived
counters.

rocprofiler-sdk/counters/id_decode: Modified to have a map between
dimension name -> dimension along with a conversion from the aql profile
id for a dimension (string) -> integer based id (happens only once during
init).

rocprofiler-sdk/hsa/queue: Modified to allow for making testing easier.
Specifically to allow Queue to now be mocked in unit tests for counter
collection.

* Merge with changes for serialization

* Added suggestions

* source formatting (clang-format v11) (#457)

Co-authored-by: bwelton <bwelton@users.noreply.github.com>

* Minor fix

* Test change

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: bwelton <bwelton@users.noreply.github.com>

[ROCm/rocprofiler-sdk commit: 3eb6a27bc6]
This commit is contained in:
Benjamin Welton
2024-02-07 20:03:21 -08:00
committed by GitHub
orang tua 4daf20d431
melakukan 8e620cc11d
29 mengubah file dengan 1670 tambahan dan 293 penghapusan
@@ -62,7 +62,7 @@ kernelC(T* C_d, const T* A_d, size_t N)
void
launchKernels()
{
const int NUM_LAUNCH = 50000;
const int NUM_LAUNCH = 20000;
// Normal HIP Calls
int* gpuMem;
[[maybe_unused]] hipDeviceProp_t devProp;
@@ -13,6 +13,8 @@
#include <rocprofiler-sdk/registration.h>
#include <rocprofiler-sdk/rocprofiler.h>
#define PRINT_ONLY_FAILING true
/**
* Tests the collection of all counters on the agent the test is run on.
*/
@@ -55,13 +57,92 @@ get_buffer()
return buf;
}
// 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() {}
void maybe_forward(const rocprofiler_record_dimension_info_t& dim)
{
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);
}
bool check_seen(std::stringstream& out,
std::vector<std::pair<rocprofiler_record_dimension_info_t, size_t>>& pos_stack)
{
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;
}
std::pair<rocprofiler_record_dimension_info_t, size_t> vector_pos;
std::vector<std::unique_ptr<validate_dim_presence>> sub_vectors;
bool has_value{false};
};
struct CaptureRecords
{
std::shared_mutex m_mutex{};
// <counter id handle, expected instances>
std::map<uint64_t, size_t> expected{};
std::map<uint64_t, std::string> expected_counter_names{};
std::vector<rocprofiler_counter_id_t> remaining{};
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{};
// <counter_id handle, instances seen>
std::map<uint64_t, size_t> captured{};
};
@@ -82,8 +163,9 @@ buffered_callback(rocprofiler_context_id_t,
void*,
uint64_t)
{
auto& cap = *get_capture();
auto wlock = std::unique_lock{cap.m_mutex};
auto& cap = *get_capture();
auto wlock = std::unique_lock{cap.m_mutex};
std::map<uint64_t, size_t> seen_counters;
for(size_t i = 0; i < num_headers; ++i)
{
@@ -95,6 +177,7 @@ buffered_callback(rocprofiler_context_id_t,
rocprofiler_counter_id_t counter;
auto* record = static_cast<rocprofiler_record_counter_t*>(header->payload);
rocprofiler_query_record_counter_id(record->id, &counter);
cap.expected_data_dims.at(counter.handle).mark_seen(record->id);
seen_counters.emplace(counter.handle, 0).first->second++;
}
}
@@ -146,15 +229,38 @@ dispatch_callback(rocprofiler_queue_id_t /*queue_id*/,
for(auto& found_counter : counters_needed)
{
size_t expected = 0;
rocprofiler_query_counter_instance_count(agent->id, found_counter, &expected);
cap.remaining.push_back(found_counter);
cap.expected.emplace(found_counter.handle, expected);
const char* name;
size_t name_size;
ROCPROFILER_CALL(rocprofiler_query_counter_name(found_counter, &name, &name_size),
"Could not query name");
cap.expected_counter_names.emplace(found_counter.handle, std::string(name));
size_t expected = 0;
ROCPROFILER_CALL(
rocprofiler_query_counter_instance_count(agent->id, found_counter, &expected),
"COULD NOT QUERY INSTANCES");
cap.remaining.push_back(found_counter);
cap.expected.emplace(found_counter.handle, expected);
auto& info_vector =
cap.expected_data_dims.emplace(found_counter.handle, validate_dim_presence{})
.first->second;
ROCPROFILER_CALL(rocprofiler_iterate_counter_dimensions(
found_counter,
[](rocprofiler_counter_id_t,
const rocprofiler_record_dimension_info_t* dim_info,
size_t num_dims,
void* user_data) {
validate_dim_presence* dim_presence =
static_cast<validate_dim_presence*>(user_data);
for(size_t i = 0; i < num_dims; i++)
{
dim_presence->maybe_forward(dim_info[i]);
}
return ROCPROFILER_STATUS_SUCCESS;
},
static_cast<void*>(&info_vector)),
"Could not fetch dimension info");
}
if(cap.expected.empty())
{
@@ -247,11 +353,25 @@ tool_fini(void*)
<< " (" << name << ")"
<< " expected " << expected << " instances and got " << *actual_size << "\n";
}
else
else if(!actual_size)
{
std::clog << "[ERROR] Counter ID: " << counter_id << " (" << name
<< ") is missing from output\n";
}
else
{
// Counter collected OK
std::stringstream ss;
std::vector<std::pair<rocprofiler_record_dimension_info_t, size_t>> stack;
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();
}
}
}
}
@@ -63,22 +63,38 @@ rocprofiler_query_record_dimension_position(rocprofiler_counter_instance_id_t i
size_t* pos) ROCPROFILER_NONNULL(3);
/**
* @brief Return information about the dimension for a specified counter. This call
* is primary for future use not related to this alpha since the only dimension
* supported is 0 (flat array without dimension).
* @brief Callback that gives a list of available dimensions for a counter
*
* @param [in] id Counter id the dimension data is for
* @param [in] dim_info An array of dimensions for the counter
* @ref rocprofiler_iterate_counter_dimensions was called on.
* @param [in] num_dims Number of dimensions
* @param [in] user_data User data supplied by
* @ref rocprofiler_iterate_agent_supported_counters
*/
typedef rocprofiler_status_t (*rocprofiler_available_dimensions_cb_t)(
rocprofiler_counter_id_t id,
const rocprofiler_record_dimension_info_t* dim_info,
size_t num_dims,
void* user_data);
/**
* @brief Return information about the dimensions that exists for a specific counter
* and the extent of each dimension.
*
* @param [in] id counter id to query dimension info for.
* @param [in] dim dimension (currently only 0 is allowed)
* @param [out] info info on the dimension (name, instance_size)
* @param [in] info_cb Callback to return dimension information for counter
* @param [in] user_data data to pass into the callback
* @return ::rocprofiler_status_t
* @retval ROCPROFILER_STATUS_SUCCESS if dimension exists
* @retval ROCPROFILER_STATUS_ERROR if the dimension does not
* @retval ROCPROFILER_STATUS_ERROR_HSA_NOT_LOADED if HSA is not loaded when this is called
* @retval ROCPROFILER_STATUS_ERROR_COUNTER_NOT_FOUND if counter is not found
* @retval ROCPROFILER_STATUS_ERROR_DIM_NOT_FOUND if counter does not have this dimension
*/
rocprofiler_status_t ROCPROFILER_API
rocprofiler_query_record_dimension_info(rocprofiler_counter_id_t id,
rocprofiler_counter_dimension_id_t dim,
rocprofiler_record_dimension_info_t* info)
ROCPROFILER_NONNULL(3);
rocprofiler_iterate_counter_dimensions(rocprofiler_counter_id_t id,
rocprofiler_available_dimensions_cb_t info_cb,
void* user_data);
/**
* @brief Query Counter name. Name is a pointer controlled by rocprofiler and
@@ -68,6 +68,13 @@ typedef void (*rocprofiler_profile_counting_dispatch_callback_t)(
* one dispatch (denoted by correlation id). Will trigger the
* callback based on the parameters setup in buffer_id_t.
*
* NOTE: Interface is up for comment as to whether restrictions
* on agent should be made here (limiting the CB based on agent)
* or if the restriction should be performed by the tool in
* @ref rocprofiler_profile_counting_dispatch_callback_t (i.e.
* tool code checking the agent param to see if they want to profile
* it).
*
* Interface is up for comment as to whether restrictions
* on agent should be made here (limiting the CB based on agent)
* or if the restriction should be performed by the tool in
@@ -79,7 +79,14 @@ typedef enum // NOLINT(performance-enum-size)
ROCPROFILER_STATUS_ERROR_INVALID_ARGUMENT, ///< Function invoked with one or more invalid
///< arguments
ROCPROFILER_STATUS_ERROR_METRIC_NOT_VALID_FOR_AGENT, ///< Invalid metric supplied to agent.
ROCPROFILER_STATUS_ERROR_FINALIZED, ///< invalid because rocprofiler has been finalized
ROCPROFILER_STATUS_ERROR_FINALIZED, ///< invalid because rocprofiler has been finalized
ROCPROFILER_STATUS_ERROR_HSA_NOT_LOADED, ///< Call requires HSA to be loaded before performed
ROCPROFILER_STATUS_ERROR_DIM_NOT_FOUND, ///< Dimension is not found for counter
ROCPROFILER_STATUS_ERROR_PROFILE_COUNTER_NOT_FOUND, ///< Profile could not find counter for GPU
///< agent
ROCPROFILER_STATUS_ERROR_AST_GENERATION_FAILED, ///< AST could not be generated correctly
ROCPROFILER_STATUS_ERROR_AST_NOT_FOUND, ///< AST was not found
ROCPROFILER_STATUS_ERROR_AQL_NO_EVENT_COORD, ///< Event coordinate was not found by AQL profile
ROCPROFILER_STATUS_LAST,
} rocprofiler_status_t;
@@ -479,6 +486,8 @@ typedef struct
{
const char* name;
size_t instance_size;
rocprofiler_counter_dimension_id_t
id; //<< Id for this dimension used by @ref rocprofiler_query_record_dimension_position
} rocprofiler_record_dimension_info_t;
/**
@@ -1,5 +1,5 @@
set(ROCPROFILER_LIB_AQL_SOURCES helpers.cpp packet_construct.cpp)
set(ROCPROFILER_LIB_AQL_HEADERS helpers.hpp packet_construct.hpp)
set(ROCPROFILER_LIB_AQL_HEADERS helpers.hpp packet_construct.hpp aql_profile_v2.h)
target_sources(rocprofiler-object-library PRIVATE ${ROCPROFILER_LIB_AQL_SOURCES}
${ROCPROFILER_LIB_AQL_HEADERS})
@@ -0,0 +1,33 @@
// This file should be removed when it appears in AQL Profile
#pragma ONCE
#include <hsa/hsa.h>
#define PUBLIC_API __attribute__((visibility("default")))
extern "C" {
/**
* @brief Callback for iteration of all possible event coordinate IDs and coordinate names.
* @param [in] id Integer identifying the dimension.
* @param [in] name Name of the dimension
* @param [in] data User data supplied to @ref aqlprofile_iterate_event_ids
* @return hsa_status_t
* @retval HSA_STATUS_SUCCESS Continues iteration
* @retval OTHERS Any other HSA return values stops iteration, passing back this value through
* @ref aqlprofile_iterate_event_ids
*/
typedef hsa_status_t (*aqlprofile_eventname_callback_t)(int id, const char* name, void* data);
/**
* @brief Iterate over all possible event coordinate IDs and their names.
* @param [in] callback Callback to use for iteration of dimensions
* @param [in] user_data Data to supply to callback @ref aqlprofile_eventname_callback_t
* @return hsa_status_t
* @retval HSA_STATUS_SUCCESS if successful
* @retval HSA_STATUS_ERROR if error on interation
* @retval OTHERS If @ref aqlprofile_eventname_callback_t returns non-HSA_STATUS_SUCCESS,
* that value is returned.
*/
PUBLIC_API hsa_status_t
aqlprofile_iterate_event_ids(aqlprofile_eventname_callback_t callback, void* user_data);
}
@@ -25,6 +25,12 @@
#include <fmt/core.h>
#include <glog/logging.h>
#include <rocprofiler-sdk/fwd.h>
#include "lib/common/synchronized.hpp"
#include "lib/common/utility.hpp"
#include "lib/rocprofiler-sdk/counters/id_decode.hpp"
namespace rocprofiler
{
namespace aql
@@ -60,5 +66,52 @@ get_block_counters(hsa_agent_t agent, const hsa_ven_amd_aqlprofile_event_t& even
}
return max_block_counters;
}
rocprofiler_status_t
set_dim_id_from_sample(rocprofiler_counter_instance_id_t& id,
hsa_agent_t agent,
hsa_ven_amd_aqlprofile_event_t event,
uint32_t sample_id)
{
auto callback =
[](int, int sid, int, int coordinate, const char*, void* userdata) -> hsa_status_t {
if(const auto* rocprof_id =
rocprofiler::common::get_val(counters::aqlprofile_id_to_rocprof_instance(), sid))
{
counters::set_dim_in_rec(*static_cast<rocprofiler_counter_instance_id_t*>(userdata),
*rocprof_id,
static_cast<size_t>(coordinate));
}
return HSA_STATUS_SUCCESS;
};
if(hsa_ven_amd_aqlprofile_iterate_event_coord(
agent, event, sample_id, callback, static_cast<void*>(&id)) != HSA_STATUS_SUCCESS)
{
return ROCPROFILER_STATUS_ERROR_AQL_NO_EVENT_COORD;
}
return ROCPROFILER_STATUS_SUCCESS;
}
rocprofiler_status_t
get_dim_info(hsa_agent_t agent,
hsa_ven_amd_aqlprofile_event_t event,
uint32_t sample_id,
std::map<int, uint64_t>& dims)
{
auto callback = [](int, int id, int extent, int, const char*, void* userdata) -> hsa_status_t {
auto& map = *static_cast<std::map<int, uint64_t>*>(userdata);
map.emplace(id, extent);
return HSA_STATUS_SUCCESS;
};
if(hsa_ven_amd_aqlprofile_iterate_event_coord(
agent, event, sample_id, callback, static_cast<void*>(&dims)) != HSA_STATUS_SUCCESS)
{
return ROCPROFILER_STATUS_ERROR_AQL_NO_EVENT_COORD;
}
return ROCPROFILER_STATUS_SUCCESS;
}
} // namespace aql
} // namespace rocprofiler
} // namespace rocprofiler
@@ -23,9 +23,13 @@
#pragma once
#include <functional>
#include <map>
#include <string>
#include <hsa/hsa_ven_amd_aqlprofile.h>
#include <rocprofiler-sdk/fwd.h>
#include "lib/rocprofiler-sdk/counters/metrics.hpp"
namespace rocprofiler
@@ -39,5 +43,19 @@ get_query_info(hsa_agent_t agent, const counters::Metric& metric);
// Query HSA_VEN_AMD_AQLPROFILE_INFO_BLOCK_COUNTERS from aqlprofiler
uint32_t
get_block_counters(hsa_agent_t agent, const hsa_ven_amd_aqlprofile_event_t& event);
// Query dimimension ids for counter event. Returns AQLProfiler ID -> extent
rocprofiler_status_t
get_dim_info(hsa_agent_t agent,
hsa_ven_amd_aqlprofile_event_t event,
uint32_t sample_id,
std::map<int, uint64_t>& dims);
// Set dimension ids into id for sample
rocprofiler_status_t
set_dim_id_from_sample(rocprofiler_counter_instance_id_t& id,
hsa_agent_t agent,
hsa_ven_amd_aqlprofile_event_t event,
uint32_t sample_id);
} // namespace aql
} // namespace rocprofiler
@@ -190,6 +190,19 @@ AQLPacketConstruct::event_to_metric(const hsa_ven_amd_aqlprofile_event_t& event)
return nullptr;
}
const std::vector<hsa_ven_amd_aqlprofile_event_t>&
AQLPacketConstruct::get_counter_events(const counters::Metric& metric) const
{
for(const auto& prof_metric : _metrics)
{
if(prof_metric.metric.id() == metric.id())
{
return prof_metric.instances;
}
}
throw std::runtime_error(fmt::format("Cannot Find Events for {}", metric));
}
void
AQLPacketConstruct::can_collect()
{
@@ -56,6 +56,11 @@ public:
const counters::Metric* event_to_metric(const hsa_ven_amd_aqlprofile_event_t& event) const;
std::vector<hsa_ven_amd_aqlprofile_event_t> get_all_events() const;
const std::vector<hsa_ven_amd_aqlprofile_event_t>& get_counter_events(
const counters::Metric&) const;
hsa_agent_t hsa_agent() const { return _agent.get_hsa_agent(); }
private:
struct AQLProfileMetric
{
@@ -25,9 +25,12 @@
#include <fmt/core.h>
#include "lib/common/container/small_vector.hpp"
#include "lib/common/static_object.hpp"
#include "lib/common/synchronized.hpp"
#include "lib/rocprofiler-sdk/agent.hpp"
#include "lib/rocprofiler-sdk/aql/helpers.hpp"
#include "lib/rocprofiler-sdk/counters/dimensions.hpp"
#include "lib/rocprofiler-sdk/counters/evaluate_ast.hpp"
#include "lib/rocprofiler-sdk/counters/id_decode.hpp"
#include "lib/rocprofiler-sdk/counters/metrics.hpp"
@@ -73,55 +76,27 @@ rocprofiler_query_counter_name(rocprofiler_counter_id_t counter_id, const char**
* @return rocprofiler_status_t
*/
rocprofiler_status_t
rocprofiler_query_counter_instance_count(rocprofiler_agent_id_t agent_id,
rocprofiler_query_counter_instance_count(rocprofiler_agent_id_t,
rocprofiler_counter_id_t counter_id,
size_t* instance_count)
{
const rocprofiler_agent_t* agent = rocprofiler::agent::get_agent(agent_id);
if(!agent) return ROCPROFILER_STATUS_ERROR_AGENT_NOT_FOUND;
if(agent->type != ROCPROFILER_AGENT_TYPE_GPU) return ROCPROFILER_STATUS_ERROR;
const auto& id_map = *CHECK_NOTNULL(rocprofiler::counters::getMetricIdMap());
const auto* metric_ptr = rocprofiler::common::get_val(id_map, counter_id.handle);
if(!metric_ptr) return ROCPROFILER_STATUS_ERROR_COUNTER_NOT_FOUND;
*instance_count = 0;
// Special counters do not have hardware metrics and will always have an instance
// count of 1 (i.e. MAX_WAVE_SIZE)
if(!metric_ptr->special().empty())
if(rocprofiler::counters::get_dimension_cache().empty())
{
*instance_count = 1;
return ROCPROFILER_STATUS_SUCCESS;
return ROCPROFILER_STATUS_ERROR_HSA_NOT_LOADED;
}
// Returns the set of hardware counters needed to evaluate the metric.
// For derived metrics, this can be more than one counter. In that case,
// we return the maximum instance count among all underlying counters.
auto req_counters = rocprofiler::counters::get_required_hardware_counters(
rocprofiler::counters::get_ast_map(), std::string(agent->name), *metric_ptr);
if(!req_counters) return ROCPROFILER_STATUS_ERROR_COUNTER_NOT_FOUND;
const auto* dims = rocprofiler::common::get_val(rocprofiler::counters::get_dimension_cache(),
counter_id.handle);
if(!dims) return ROCPROFILER_STATUS_ERROR_COUNTER_NOT_FOUND;
for(const auto& counter : *req_counters)
for(const auto& metric_dim : *dims)
{
if(!counter.special().empty())
{
*instance_count = std::max(size_t(1), *instance_count);
continue;
}
try
{
auto dims = rocprofiler::counters::getBlockDimensions(agent->name, counter);
for(const auto& dim : dims)
{
*instance_count = std::max(static_cast<size_t>(dim.size()), *instance_count);
}
} catch(std::runtime_error& err)
{
LOG(ERROR) << fmt::format("Could not lookup instance count for counter {}", counter);
return ROCPROFILER_STATUS_ERROR_COUNTER_NOT_FOUND;
}
if(*instance_count == 0)
*instance_count = metric_dim.size();
else if(metric_dim.size() > 0)
*instance_count = metric_dim.size() * *instance_count;
}
return ROCPROFILER_STATUS_SUCCESS;
@@ -180,20 +155,36 @@ rocprofiler_query_record_dimension_position(rocprofiler_counter_instance_id_t i
}
rocprofiler_status_t
rocprofiler_query_record_dimension_info(rocprofiler_counter_id_t,
rocprofiler_counter_dimension_id_t dim,
rocprofiler_record_dimension_info_t* info)
rocprofiler_iterate_counter_dimensions(rocprofiler_counter_id_t id,
rocprofiler_available_dimensions_cb_t info_cb,
void* user_data)
{
if(const auto* ptr = rocprofiler::common::get_val(
rocprofiler::counters::dimension_map(),
static_cast<rocprofiler::counters::rocprofiler_profile_counter_instance_types>(dim)))
if(rocprofiler::counters::get_dimension_cache().empty())
{
info->name = ptr->c_str();
// TODO: Needs info on the instance size per block to fill in.
// counter_id will be used to lookup this information.
info->instance_size = 0;
return ROCPROFILER_STATUS_SUCCESS;
return ROCPROFILER_STATUS_ERROR_HSA_NOT_LOADED;
}
return ROCPROFILER_STATUS_ERROR;
const auto* dims =
rocprofiler::common::get_val(rocprofiler::counters::get_dimension_cache(), id.handle);
if(!dims) return ROCPROFILER_STATUS_ERROR_COUNTER_NOT_FOUND;
// This is likely faster than a map lookup given the limited number of dims.
rocprofiler::common::container::small_vector<rocprofiler_record_dimension_info_t, 6> user_dims;
for(const auto& internal_dim : *dims)
{
auto& dim = user_dims.emplace_back();
dim.name = internal_dim.name().c_str();
dim.instance_size = internal_dim.size();
dim.id = static_cast<rocprofiler_counter_dimension_id_t>(internal_dim.type());
}
if(user_dims.empty())
{
return ROCPROFILER_STATUS_ERROR_DIM_NOT_FOUND;
}
info_cb(id, user_dims.data(), user_dims.size(), user_data);
return ROCPROFILER_STATUS_SUCCESS;
}
}
@@ -37,9 +37,6 @@ namespace rocprofiler
{
namespace counters
{
using ClientID = int64_t;
using inst_pkt_t = common::container::
small_vector<std::pair<std::unique_ptr<rocprofiler::hsa::AQLPacket>, ClientID>, 4>;
class CounterController
{
public:
@@ -133,6 +130,113 @@ destroy_counter_profile(uint64_t id)
get_controller().destroy_profile(id);
}
std::shared_ptr<profile_config>
get_profile_config(rocprofiler_profile_config_id_t id)
{
try
{
return get_controller().get_profile_cfg(id);
} catch(std::out_of_range&)
{
return nullptr;
}
}
rocprofiler_status_t
counter_callback_info::setup_profile_config(const hsa::AgentCache& agent,
std::shared_ptr<profile_config>& profile)
{
if(profile->pkt_generator || !profile->reqired_hw_counters.empty())
{
return ROCPROFILER_STATUS_SUCCESS;
}
// Sets up the packet generator for the profile. This must be delayed until after HSA is loaded.
// This call needs to be thread protected in that only one thread must be setting up profile at
// the same time.
auto& config = *profile;
auto agent_name = std::string(config.agent->name);
for(const auto& metric : config.metrics)
{
auto req_counters = get_required_hardware_counters(get_ast_map(), agent_name, metric);
if(!req_counters)
{
LOG(ERROR) << fmt::format("Could not find counter {}", metric.name());
return ROCPROFILER_STATUS_ERROR_PROFILE_COUNTER_NOT_FOUND;
}
// Special metrics are those that are not hw counters but other
// constants like MAX_WAVE_SIZE
for(const auto& req_metric : *req_counters)
{
if(req_metric.special().empty())
{
config.reqired_hw_counters.insert(req_metric);
}
else
{
config.required_special_counters.insert(req_metric);
}
}
const auto& asts = get_ast_map();
const auto* agent_map = rocprofiler::common::get_val(asts, agent_name);
if(!agent_map)
{
LOG(ERROR) << fmt::format("Coult not build AST for {}", agent_name);
return ROCPROFILER_STATUS_ERROR_AST_GENERATION_FAILED;
}
const auto* counter_ast = rocprofiler::common::get_val(*agent_map, metric.name());
if(!counter_ast)
{
LOG(ERROR) << fmt::format("Coult not find AST for {}", metric.name());
return ROCPROFILER_STATUS_ERROR_AST_NOT_FOUND;
}
config.asts.push_back(*counter_ast);
config.asts.back().set_dimensions();
}
profile->pkt_generator = std::make_unique<rocprofiler::aql::AQLPacketConstruct>(
agent,
std::vector<counters::Metric>{profile->reqired_hw_counters.begin(),
profile->reqired_hw_counters.end()});
return ROCPROFILER_STATUS_SUCCESS;
}
rocprofiler_status_t
counter_callback_info::get_packet(std::unique_ptr<rocprofiler::hsa::AQLPacket>& ret_pkt,
const hsa::AgentCache& agent,
std::shared_ptr<profile_config>& profile)
{
rocprofiler_status_t status;
// Check packet cache
profile->packets.wlock([&](auto& pkt_vector) {
status = counter_callback_info::setup_profile_config(agent, profile);
if(!pkt_vector.empty() && status == ROCPROFILER_STATUS_SUCCESS)
{
ret_pkt = std::move(pkt_vector.back());
pkt_vector.pop_back();
}
});
if(status != ROCPROFILER_STATUS_SUCCESS) return status;
if(!ret_pkt)
{
// If we do not have a packet in the cache, create one.
ret_pkt =
profile->pkt_generator->construct_packet(hsa::get_queue_controller().get_ext_table());
}
ret_pkt->before_krn_pkt.clear();
ret_pkt->after_krn_pkt.clear();
packet_return_map.wlock([&](auto& data) { data.emplace(ret_pkt.get(), profile); });
return ROCPROFILER_STATUS_SUCCESS;
}
/**
* Callback we get from HSA interceptor when a kernel packet is being enqueued.
*
@@ -174,85 +278,11 @@ queue_cb(const std::shared_ptr<counter_callback_info>& info,
CHECK(prof_config);
std::unique_ptr<rocprofiler::hsa::AQLPacket> ret_pkt;
auto status = info->get_packet(ret_pkt, queue.get_agent(), prof_config);
CHECK_EQ(status, ROCPROFILER_STATUS_SUCCESS) << rocprofiler_get_status_string(status);
// Check packet cache
prof_config->packets.wlock([&](auto& pkt_vector) {
// Delay packet generator construction until first HSA packet is processed
// This ensures that HSA exists
if(!prof_config->pkt_generator)
{
// One time setup of profile config
if(prof_config->reqired_hw_counters.empty())
{
auto& config = *prof_config;
auto agent_name = std::string(config.agent->name);
for(const auto& metric : config.metrics)
{
auto req_counters =
get_required_hardware_counters(get_ast_map(), agent_name, metric);
if(!req_counters)
{
throw std::runtime_error(
fmt::format("Could not find counter {}", metric.name()));
}
// Special metrics are those that are not hw counters but other
// constants like MAX_WAVE_SIZE
for(const auto& req_metric : *req_counters)
{
if(req_metric.special().empty())
{
config.reqired_hw_counters.insert(req_metric);
}
else
{
config.required_special_counters.insert(req_metric);
}
}
const auto& asts = get_ast_map();
const auto* agent_map = rocprofiler::common::get_val(asts, agent_name);
if(!agent_map)
throw std::runtime_error(
fmt::format("Coult not build AST for {}", agent_name));
const auto* counter_ast =
rocprofiler::common::get_val(*agent_map, metric.name());
if(!counter_ast)
{
throw std::runtime_error(
fmt::format("Coult not find AST for {}", metric.name()));
}
config.asts.push_back(*counter_ast);
config.asts.back().set_dimensions();
}
}
prof_config->pkt_generator = std::make_unique<rocprofiler::aql::AQLPacketConstruct>(
queue.get_agent(),
std::vector<counters::Metric>{prof_config->reqired_hw_counters.begin(),
prof_config->reqired_hw_counters.end()});
}
if(!pkt_vector.empty())
{
ret_pkt = std::move(pkt_vector.back());
pkt_vector.pop_back();
}
});
if(!ret_pkt)
{
// If we do not have a packet in the cache, create one.
ret_pkt = prof_config->pkt_generator->construct_packet(
hsa::get_queue_controller().get_ext_table());
}
ret_pkt->before_krn_pkt.clear();
ret_pkt->after_krn_pkt.clear();
if(ret_pkt->empty) return ret_pkt;
info->packet_return_map.wlock([&](auto& data) { data.emplace(ret_pkt.get(), prof_config); });
auto&& CreateBarrierPacket =
[](hsa_signal_t* dependency_signal,
hsa_signal_t* completion_signal,
@@ -312,7 +342,11 @@ completed_cb(const std::shared_ptr<counter_callback_info>& info,
});
if(!pkt) return;
hsa::profiler_serializer_kernel_completion_signal(session.queue.block_signal);
if(!pkt->empty)
{
hsa::profiler_serializer_kernel_completion_signal(session.queue.block_signal);
}
auto decoded_pkt = EvaluateAST::read_pkt(prof_config->pkt_generator.get(), *pkt);
EvaluateAST::read_special_counters(
@@ -88,6 +88,13 @@ struct counter_callback_info
rocprofiler::common::Synchronized<
std::unordered_map<rocprofiler::hsa::AQLPacket*, std::shared_ptr<profile_config>>>
packet_return_map{};
static rocprofiler_status_t setup_profile_config(const hsa::AgentCache&,
std::shared_ptr<profile_config>&);
rocprofiler_status_t get_packet(std::unique_ptr<rocprofiler::hsa::AQLPacket>&,
const hsa::AgentCache&,
std::shared_ptr<profile_config>&);
};
uint64_t
@@ -107,5 +114,26 @@ start_context(const context::context*);
void
stop_context(const context::context*);
std::unique_ptr<rocprofiler::hsa::AQLPacket>
queue_cb(const std::shared_ptr<counter_callback_info>& info,
const hsa::Queue& queue,
const hsa::rocprofiler_packet& pkt,
uint64_t kernel_id,
const hsa::Queue::queue_info_session_t::external_corr_id_map_t& extern_corr_ids,
const context::correlation_id* correlation_id);
using ClientID = int64_t;
using inst_pkt_t = common::container::
small_vector<std::pair<std::unique_ptr<rocprofiler::hsa::AQLPacket>, ClientID>, 4>;
void
completed_cb(const std::shared_ptr<counter_callback_info>&,
const hsa::Queue&,
hsa::rocprofiler_packet,
const hsa::Queue::queue_info_session_t&,
inst_pkt_t& pkts);
std::shared_ptr<profile_config> get_profile_config(rocprofiler_profile_config_id_t);
} // namespace counters
} // namespace rocprofiler
@@ -29,10 +29,12 @@
#include <fmt/core.h>
#include "lib/common/static_object.hpp"
#include "lib/common/synchronized.hpp"
#include "lib/common/utility.hpp"
#include "lib/rocprofiler-sdk/aql/helpers.hpp"
#include "lib/rocprofiler-sdk/aql/packet_construct.hpp"
#include "lib/rocprofiler-sdk/counters/evaluate_ast.hpp"
#include "lib/rocprofiler-sdk/hsa/queue_controller.hpp"
namespace rocprofiler
@@ -45,35 +47,82 @@ getBlockDimensions(std::string_view agent, const Metric& metric)
if(!metric.special().empty())
{
// Special non-hardware counters without dimension data
return std::vector<MetricDimension>{
{dimension_map().at(ROCPROFILER_DIMENSION_NONE), 1, ROCPROFILER_DIMENSION_NONE}};
return std::vector<MetricDimension>{{dimension_map().at(ROCPROFILER_DIMENSION_INSTANCE),
1,
ROCPROFILER_DIMENSION_INSTANCE}};
}
std::unordered_map<rocprofiler_profile_counter_instance_types, uint64_t> count;
std::vector<MetricDimension> ret;
for(const auto& [_, maybe_agent] : hsa::get_queue_controller().get_supported_agents())
{
if(maybe_agent.name() == agent)
{
// To be returned when instance counting is functional with AQL profiler
// return std::vector<MetricDimension>{
// {dimension_map().at(ROCPROFILER_DIMENSION_SHADER_ENGINE),
// maybe_agent.get_rocp_agent()->num_shader_banks,
// ROCPROFILER_DIMENSION_SHADER_ENGINE},
// {dimension_map().at(ROCPROFILER_DIMENSION_XCC),
// maybe_agent.get_rocp_agent()->num_xcc,
// ROCPROFILER_DIMENSION_XCC},
// {dimension_map().at(ROCPROFILER_DIMENSION_CU),
// maybe_agent.get_rocp_agent()->cu_count,
// ROCPROFILER_DIMENSION_CU},
// {dimension_map().at(ROCPROFILER_DIMENSION_AGENT),
// maybe_agent.get_rocp_agent()->id.handle,
// ROCPROFILER_DIMENSION_AGENT}};
aql::AQLPacketConstruct pkt_gen(maybe_agent, {metric});
return std::vector<MetricDimension>{
{metric.block(), pkt_gen.get_all_events().size(), ROCPROFILER_DIMENSION_NONE}};
const auto& events = pkt_gen.get_counter_events(metric);
for(const auto& event : events)
{
std::map<int, uint64_t> dims;
auto status = aql::get_dim_info(maybe_agent.get_hsa_agent(), event, 0, dims);
CHECK_EQ(status, ROCPROFILER_STATUS_SUCCESS)
<< rocprofiler_get_status_string(status);
for(const auto& [id, extent] : dims)
{
if(const auto* inst_type =
rocprofiler::common::get_val(aqlprofile_id_to_rocprof_instance(), id))
{
count.emplace(*inst_type, 0).first->second = extent;
}
else
{
LOG(ERROR) << "Unknown AQL Profiler Dimension " << id << " " << extent;
}
}
}
}
}
return {};
ret.reserve(count.size());
for(const auto& [dim, size] : count)
{
ret.emplace_back(dimension_map().at(dim), size, dim);
}
return ret;
}
const std::unordered_map<uint64_t, std::vector<MetricDimension>>&
get_dimension_cache()
{
static auto*& cache =
common::static_object<std::unordered_map<uint64_t, std::vector<MetricDimension>>>::
construct([]() -> std::unordered_map<uint64_t, std::vector<MetricDimension>> {
std::unordered_map<uint64_t, std::vector<MetricDimension>> dims;
/**
* Fails if HSA is not loaded by retruning nothing. This should not remain after
* AQL is transistioned away from HSA.
*/
if(rocprofiler::hsa::get_queue_controller().get_supported_agents().empty())
{
return {};
}
const auto& asts = counters::get_ast_map();
for(const auto& [gfx, metrics] : asts)
{
for(const auto& [_, ast] : metrics)
{
auto ast_copy = ast;
dims.emplace(ast.out_id().handle, ast_copy.set_dimensions());
}
}
return dims;
}());
return *cache;
}
} // namespace counters
@@ -39,10 +39,10 @@ namespace counters
class MetricDimension
{
public:
MetricDimension(std::string name,
MetricDimension(std::string_view name,
uint64_t dim_size,
rocprofiler_profile_counter_instance_types type)
: name_(std::move(name))
: name_(name)
, size_(dim_size)
, type_(type){};
@@ -60,44 +60,31 @@ private:
rocprofiler_profile_counter_instance_types type_;
};
/*
{
AgentBlockDimensionsMap = {
"gfx906":{},
"gfx908":{
"TCC": [dim_1, dim_2 ... dim_n]
"SQ": [dim_1, dim_2 ... dim_n]
"TCP": [dim_1, dim_2 ... dim_n]
}
}
}
*/
// // map block_name -> MetricDimension
// using BlockDimensionMap = std::unordered_map<std::string, std::vector<MetricDimension>>;
// // map agent_name -> BlockDimensionMap
// using AgentBlockDimensionsMap = std::unordered_map<std::string, BlockDimensionMap&>;
// // map dimension_id -> MetricDimension
// using DimensionIdMap = std::unordered_map<uint64_t, MetricDimension>;
// // get the complete AgentBlockDimensionsMap
// const AgentBlockDimensionsMap&
// getAgentBlockDimensionsMap();
// // get specific dimensions for an agent, block_name
// const std::vector<MetricDimension>&
// getBlockDimension(const std::string& agent,
// std::string block_name,
// rocprofiler_profile_counter_instance_types dim);
// get all dimensions for an agent, block_name
std::vector<MetricDimension>
getBlockDimensions(std::string_view agent, const counters::Metric&);
// // get a specific dimension by id
// const MetricDimension&
// getDimensionById(uint64_t id);
const std::unordered_map<uint64_t, std::vector<MetricDimension>>&
get_dimension_cache();
} // namespace counters
} // namespace rocprofiler
namespace fmt
{
// fmt::format support for metric
template <>
struct formatter<rocprofiler::counters::MetricDimension>
{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx)
{
return ctx.begin();
}
template <typename Ctx>
auto format(rocprofiler::counters::MetricDimension const& dims, Ctx& ctx) const
{
return fmt::format_to(ctx.out(), "[{}, {}]", dims.name(), dims.size());
}
};
} // namespace fmt
@@ -21,13 +21,16 @@
// SOFTWARE.
#include "lib/rocprofiler-sdk/counters/evaluate_ast.hpp"
#include <fmt/core.h>
#include <exception>
#include <optional>
#include <numeric>
#include <optional>
#include <stdexcept>
#include <fmt/core.h>
#include <fmt/ranges.h>
#include <rocprofiler-sdk/rocprofiler.h>
#include "lib/common/synchronized.hpp"
#include "lib/common/utility.hpp"
#include "lib/rocprofiler-sdk/counters/dimensions.hpp"
@@ -221,41 +224,47 @@ EvaluateAST::EvaluateAST(rocprofiler_counter_id_t out_id,
}
}
DimensionTypes
std::vector<MetricDimension>
EvaluateAST::set_dimensions()
{
if(_dimension_types != DIMENSION_LAST)
if(!_dimension_types.empty())
{
return _dimension_types;
}
auto get_dim_types = [&](auto& metric) {
int dim_types = 0;
for(const auto& dim : getBlockDimensions(_agent, metric))
{
dim_types |= (dim.type() != ROCPROFILER_DIMENSION_NONE) ? (0x1 << dim.type()) : 0;
}
return static_cast<DimensionTypes>(dim_types);
};
auto get_dim_types = [&](auto& metric) { return getBlockDimensions(_agent, metric); };
switch(_type)
{
case NONE:
case RANGE_NODE:
case CONSTANT_NODE:
case NUMBER_NODE: break;
case NUMBER_NODE:
{
_dimension_types =
std::vector<MetricDimension>{{dimension_map().at(ROCPROFILER_DIMENSION_INSTANCE),
1,
ROCPROFILER_DIMENSION_INSTANCE}};
}
break;
case ADDITION_NODE:
case SUBTRACTION_NODE:
case MULTIPLY_NODE:
case DIVIDE_NODE:
{
if(_children[0].set_dimensions() != _children[1].set_dimensions() &&
_children[0].type() != NUMBER_NODE && _children[1].type() != NUMBER_NODE)
throw std::runtime_error(fmt::format("Dimension mis-mismatch: {} and {}",
_children[0].metric(),
_children[1].metric()));
_dimension_types = (_children[0].type() != NUMBER_NODE) ? _children[0].set_dimensions()
: _children[1].set_dimensions();
auto first = _children[0].set_dimensions();
auto second = _children[1].set_dimensions();
// - first.size() > 1 && second.size() > 1
// This is an explicit compatibility change to allow existing integer * COUNTER
// derived counters to function
if(first != second && first.size() > 1 && second.size() > 1)
throw std::runtime_error(
fmt::format("Dimension mis-mismatch: {} (dims: {}) and {} (dims: {})",
_children[0].metric(),
fmt::join(_children[0].set_dimensions(), ","),
_children[1].metric(),
fmt::join(_children[1].set_dimensions(), ",")));
_dimension_types = first.size() > second.size() ? first : second;
}
break;
case REFERENCE_NODE:
@@ -265,19 +274,11 @@ EvaluateAST::set_dimensions()
break;
case REDUCE_NODE:
{
// There is only one child node in case of REDUCE_NODE and that
// child node denotes the expression on which the reduce is applied.
// The resulting dimension of REDUCE_NODE will be the child's dimension
// minus the dimensions specified in the reduce_dimension_set.
int original_dim = static_cast<int>(_children[0].set_dimensions());
int turn_off_dims = 0;
for(auto dim : _reduce_dimension_set)
{
turn_off_dims |= (dim != ROCPROFILER_DIMENSION_NONE) ? (0x1 << dim) : 1;
}
int final_dims = _reduce_dimension_set.empty() ? ROCPROFILER_DIMENSION_NONE
: (original_dim & ~turn_off_dims);
_dimension_types = static_cast<DimensionTypes>(final_dims);
// Reduction down to a single instance supported for now.
_dimension_types =
std::vector<MetricDimension>{{dimension_map().at(ROCPROFILER_DIMENSION_INSTANCE),
1,
ROCPROFILER_DIMENSION_INSTANCE}};
}
break;
case SELECT_NODE:
@@ -387,11 +388,12 @@ using property_function_t = int64_t (*)(const rocprofiler_agent_t&);
return static_cast<int64_t>(value); \
}) \
}
} // namespace
int64_t
get_agent_property(const std::string& property, const rocprofiler_agent_t& agent)
get_agent_property(std::string_view property, const rocprofiler_agent_t& agent)
{
static std::unordered_map<std::string, property_function_t> props = {
static std::unordered_map<std::string_view, property_function_t> props = {
GEN_MAP_ENTRY("cpu_cores_count", agent_info.cpu_cores_count),
GEN_MAP_ENTRY("simd_count", agent_info.simd_count),
GEN_MAP_ENTRY("mem_banks_count", agent_info.mem_banks_count),
@@ -427,10 +429,8 @@ get_agent_property(const std::string& property, const rocprofiler_agent_t& agent
return (*func)(agent);
}
LOG(ERROR) << fmt::format("Unsupported special property {}", property);
return 0.0;
}
} // namespace
void
EvaluateAST::read_special_counters(
@@ -477,7 +477,14 @@ EvaluateAST::read_pkt(const aql::AQLPacketConstruct* pkt_gen, hsa::AQLPacket& pk
auto& next_rec = vec.emplace_back();
set_counter_in_rec(next_rec.id, {.handle = metric->id()});
// Actual dimension info needs to be used here in the future
set_dim_in_rec(next_rec.id, ROCPROFILER_DIMENSION_NONE, vec.size() - 1);
auto aql_status = aql::set_dim_id_from_sample(next_rec.id,
it.pkt_gen->hsa_agent(),
info_data->pmc_data.event,
info_data->sample_id);
CHECK_EQ(aql_status, ROCPROFILER_STATUS_SUCCESS)
<< rocprofiler_get_status_string(aql_status);
// set_dim_in_rec(next_rec.id, ROCPROFILER_DIMENSION_NONE, vec.size() - 1);
// Note: in the near future we need to use hw_counter here instead
next_rec.counter_value = info_data->pmc_data.result;
return HSA_STATUS_SUCCESS;
@@ -104,9 +104,9 @@ public:
* of this AST. Can throw if the AST is invalid (i.e. dimension mismatch in
* child nodes of this AST). This is done in a recursive fashion.
*
* @return DimensionTypes dimension of the output of this AST.
* @return std::vector<MetricDimension> dimension of the output of this AST.
*/
DimensionTypes set_dimensions();
std::vector<MetricDimension> set_dimensions();
bool validate_raw_ast(const std::unordered_map<std::string, Metric>& metrics);
@@ -152,11 +152,11 @@ public:
const std::set<counters::Metric>& required_special_counters,
std::unordered_map<uint64_t, std::vector<rocprofiler_record_counter_t>>& out_map);
NodeType type() const { return _type; }
ReduceOperation reduce_op() const { return _reduce_op; }
const std::vector<EvaluateAST>& children() const { return _children; }
const Metric& metric() const { return _metric; }
DimensionTypes dimension_types() const { return _dimension_types; }
NodeType type() const { return _type; }
ReduceOperation reduce_op() const { return _reduce_op; }
const std::vector<EvaluateAST>& children() const { return _children; }
const Metric& metric() const { return _metric; }
const std::vector<MetricDimension>& dimension_types() const { return _dimension_types; }
/**
* @brief When an evaluation is complete, set the output id of the results. This is called
@@ -166,6 +166,8 @@ public:
*/
void set_out_id(std::vector<rocprofiler_record_counter_t>& results) const;
const rocprofiler_counter_id_t& out_id() const { return _out_id; }
private:
NodeType _type{NONE};
ReduceOperation _reduce_op{REDUCE_NONE};
@@ -173,7 +175,7 @@ private:
double _raw_value{0};
std::vector<EvaluateAST> _children;
std::string _agent;
DimensionTypes _dimension_types{DIMENSION_LAST};
std::vector<MetricDimension> _dimension_types{};
std::vector<rocprofiler_record_counter_t> _static_value;
std::unordered_set<rocprofiler_profile_counter_instance_types> _reduce_dimension_set;
bool _expanded{false};
@@ -197,6 +199,8 @@ std::optional<std::set<Metric>>
get_required_hardware_counters(const std::unordered_map<std::string, EvaluateASTMap>& asts,
const std::string& agent,
const Metric& metric);
int64_t
get_agent_property(std::string_view property, const rocprofiler_agent_t& agent);
} // namespace counters
} // namespace rocprofiler
@@ -22,28 +22,71 @@
#include "lib/rocprofiler-sdk/counters/id_decode.hpp"
#include <hsa/hsa_ven_amd_aqlprofile.h>
#include <string>
#include <unordered_map>
#include "lib/common/static_object.hpp"
#include "lib/common/utility.hpp"
#include "lib/rocprofiler-sdk/aql/aql_profile_v2.h"
namespace rocprofiler
{
namespace counters
{
const std::unordered_map<rocprofiler_profile_counter_instance_types, std::string>&
const std::unordered_map<rocprofiler_profile_counter_instance_types, std::string_view>&
dimension_map()
{
static std::unordered_map<rocprofiler_profile_counter_instance_types, std::string> map = {
{ROCPROFILER_DIMENSION_NONE, "DIMENSION_NONE"},
{ROCPROFILER_DIMENSION_XCC, "DIMENSION_XCC"},
{ROCPROFILER_DIMENSION_SHADER_ENGINE, "DIMENSION_SHADER_ENGINE"},
{ROCPROFILER_DIMENSION_AGENT, "DIMENSION_AGENT"},
{ROCPROFILER_DIMENSION_PMC_CHANNEL, "DIMENSION_PMC_CHANNEL"},
{ROCPROFILER_DIMENSION_CU, "DIMENSION_CU"},
static std::unordered_map<rocprofiler_profile_counter_instance_types, std::string_view> map = {
{ROCPROFILER_DIMENSION_NONE, std::string_view("DIMENSION_NONE")},
{ROCPROFILER_DIMENSION_XCC, std::string_view("DIMENSION_XCC")},
{ROCPROFILER_DIMENSION_SHADER_ENGINE, std::string_view("DIMENSION_SHADER_ENGINE")},
{ROCPROFILER_DIMENSION_AGENT, std::string_view("DIMENSION_AGENT")},
{ROCPROFILER_DIMENSION_SHADER_ARRAY, std::string_view("DIMENSION_SHADER_ARRAY")},
{ROCPROFILER_DIMENSION_CU, std::string_view("DIMENSION_CU")},
{ROCPROFILER_DIMENSION_INSTANCE, std::string_view("DIMENSION_INSTANCE")},
};
return map;
}
const std::unordered_map<int, rocprofiler_profile_counter_instance_types>&
aqlprofile_id_to_rocprof_instance()
{
using dims_map_t = std::unordered_map<int, rocprofiler_profile_counter_instance_types>;
static auto*& aql_to_rocprof_dims =
common::static_object<dims_map_t>::construct([]() -> dims_map_t {
dims_map_t data;
aqlprofile_iterate_event_ids(
[](int id, const char* name, void* userdata) -> hsa_status_t {
const std::unordered_map<std::string_view,
rocprofiler_profile_counter_instance_types>
aql_string_to_dim = {
{"XCD", ROCPROFILER_DIMENSION_XCC},
{"SE", ROCPROFILER_DIMENSION_SHADER_ENGINE},
{"SA", ROCPROFILER_DIMENSION_SHADER_ARRAY},
{"CU", ROCPROFILER_DIMENSION_CU},
{"INSTANCE", ROCPROFILER_DIMENSION_INSTANCE},
};
if(const auto* inst_type =
rocprofiler::common::get_val(aql_string_to_dim, name))
{
// Supported instance type
auto& map = *static_cast<
std::unordered_map<int, rocprofiler_profile_counter_instance_types>*>(
userdata);
map.emplace(id, *inst_type);
}
return HSA_STATUS_SUCCESS;
},
static_cast<void*>(&data));
return data;
}());
return *aql_to_rocprof_dims;
}
} // namespace counters
} // namespace rocprofiler
@@ -22,6 +22,7 @@
#pragma once
#include <map>
#include <unordered_map>
#include <rocprofiler-sdk/fwd.h>
@@ -41,12 +42,13 @@ enum rocprofiler_profile_counter_instance_types
ROCPROFILER_DIMENSION_XCC, ///< XCC dimension of result
ROCPROFILER_DIMENSION_SHADER_ENGINE, ///< SE dimension of result
ROCPROFILER_DIMENSION_AGENT, ///< Agent dimension
ROCPROFILER_DIMENSION_PMC_CHANNEL, ///< PMC Channel dimension of result
ROCPROFILER_DIMENSION_SHADER_ARRAY, ///< Number of shader arrays
ROCPROFILER_DIMENSION_CU, ///< Number of compute units
ROCPROFILER_DIMENSION_INSTANCE, ///< Number of instances
ROCPROFILER_DIMENSION_LAST
};
const std::unordered_map<rocprofiler_profile_counter_instance_types, std::string>&
const std::unordered_map<rocprofiler_profile_counter_instance_types, std::string_view>&
dimension_map();
inline rocprofiler_counter_id_t
@@ -62,6 +64,9 @@ inline size_t
rec_to_dim_pos(rocprofiler_counter_instance_id_t id,
rocprofiler_profile_counter_instance_types dim);
const std::unordered_map<int, rocprofiler_profile_counter_instance_types>&
aqlprofile_id_to_rocprof_instance();
} // namespace counters
} // namespace rocprofiler
@@ -3,7 +3,7 @@ rocprofiler_deactivate_clang_tidy()
include(GoogleTest)
set(ROCPROFILER_LIB_COUNTER_TEST_SOURCES metrics_test.cpp evaluate_ast_test.cpp
dimension.cpp init_order.cpp)
dimension.cpp init_order.cpp core.cpp)
add_executable(counter-test)
@@ -11,7 +11,8 @@ target_sources(counter-test PRIVATE ${ROCPROFILER_LIB_COUNTER_TEST_SOURCES})
target_link_libraries(
counter-test
PRIVATE rocprofiler::rocprofiler-hip rocprofiler::rocprofiler-common-library
PRIVATE rocprofiler::rocprofiler-hsa-runtime rocprofiler::rocprofiler-hip
rocprofiler::rocprofiler-common-library
rocprofiler::rocprofiler-static-library GTest::gtest GTest::gtest_main)
gtest_add_tests(
@@ -0,0 +1,648 @@
// MIT License
//
// Copyright (c) 2023 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.
#include <algorithm>
#include <cstdint>
#include <sstream>
#include <tuple>
#include <fmt/core.h>
#include <gtest/gtest.h>
#include <hsa/hsa.h>
#include <hsa/hsa_api_trace.h>
#include <hsa/hsa_ext_amd.h>
#include <rocprofiler-sdk/rocprofiler.h>
#include "lib/common/static_object.hpp"
#include "lib/common/utility.hpp"
#include "lib/rocprofiler-sdk/agent.hpp"
#include "lib/rocprofiler-sdk/buffer.hpp"
#include "lib/rocprofiler-sdk/context/context.hpp"
#include "lib/rocprofiler-sdk/counters/core.hpp"
#include "lib/rocprofiler-sdk/counters/id_decode.hpp"
#include "lib/rocprofiler-sdk/counters/metrics.hpp"
#include "lib/rocprofiler-sdk/hsa/agent_cache.hpp"
#include "lib/rocprofiler-sdk/hsa/queue.hpp"
#include "lib/rocprofiler-sdk/hsa/queue_controller.hpp"
#include "lib/rocprofiler-sdk/registration.hpp"
#include "rocprofiler-sdk/registration.h"
using namespace rocprofiler::counters;
using namespace rocprofiler;
#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 << ")"; \
ASSERT_EQ(CHECKSTATUS, ROCPROFILER_STATUS_SUCCESS) << errmsg.str(); \
} \
}
namespace
{
AmdExtTable&
get_ext_table()
{
static auto _v = []() {
auto val = AmdExtTable{};
val.hsa_amd_memory_pool_get_info_fn = hsa_amd_memory_pool_get_info;
val.hsa_amd_agent_iterate_memory_pools_fn = hsa_amd_agent_iterate_memory_pools;
val.hsa_amd_memory_pool_allocate_fn = hsa_amd_memory_pool_allocate;
val.hsa_amd_memory_pool_free_fn = hsa_amd_memory_pool_free;
val.hsa_amd_agent_memory_pool_get_info_fn = hsa_amd_agent_memory_pool_get_info;
val.hsa_amd_agents_allow_access_fn = hsa_amd_agents_allow_access;
return val;
}();
return _v;
}
CoreApiTable&
get_api_table()
{
static auto _v = []() {
auto val = CoreApiTable{};
val.hsa_iterate_agents_fn = hsa_iterate_agents;
val.hsa_agent_get_info_fn = hsa_agent_get_info;
val.hsa_queue_create_fn = hsa_queue_create;
val.hsa_queue_destroy_fn = hsa_queue_destroy;
return val;
}();
return _v;
}
auto
findDeviceMetrics(const hsa::AgentCache& agent, const std::unordered_set<std::string>& metrics)
{
std::vector<counters::Metric> ret;
auto all_counters = counters::getMetricMap();
LOG(ERROR) << "Looking up counters for " << std::string(agent.name());
auto gfx_metrics = common::get_val(*all_counters, std::string(agent.name()));
if(!gfx_metrics)
{
LOG(ERROR) << "No counters found for " << std::string(agent.name());
return ret;
}
for(auto& counter : *gfx_metrics)
{
if(metrics.count(counter.name()) > 0 || metrics.empty())
{
ret.push_back(counter);
}
}
return ret;
}
void
test_init()
{
HsaApiTable table;
table.amd_ext_ = &get_ext_table();
table.core_ = &get_api_table();
agent::construct_agent_cache(&table);
hsa::get_queue_controller().init(get_api_table(), get_ext_table());
}
} // namespace
namespace
{
rocprofiler_context_id_t&
get_client_ctx()
{
static rocprofiler_context_id_t ctx;
return ctx;
}
struct buf_check
{
size_t expected_size{0};
bool is_special{false};
double special_val{0.0};
};
void
buffered_callback(rocprofiler_context_id_t,
rocprofiler_buffer_id_t,
rocprofiler_record_header_t** headers,
size_t num_headers,
void* user_data,
uint64_t)
{
buf_check& expected = *static_cast<buf_check*>(user_data);
if(expected.is_special)
{
// Special values are single value constants (from agent_t)
expected.expected_size = 1;
}
std::set<double> seen_data;
std::set<uint64_t> seen_dims;
for(size_t i = 0; i < num_headers; ++i)
{
auto* header = headers[i];
if(header->category == ROCPROFILER_BUFFER_CATEGORY_COUNTERS && header->kind == 0)
{
// Print the returned counter data.
auto* record = static_cast<rocprofiler_record_counter_t*>(header->payload);
seen_dims.insert(record->id);
seen_data.insert(record->counter_value);
}
}
/**
* Specific counters default to a size of 128 even if they have less data (primarily
* TCP). This is a known quirk on AQL profile's end where it will allocate for 128 entries
* but return less (and the data may be duplicated across entries). Skip these entires for
* testing purposes since we cannot determine what mock data will be in the return (and its
* arch dependent).
*/
if(expected.expected_size == 128) return;
EXPECT_EQ(seen_dims.size(), expected.expected_size);
EXPECT_EQ(seen_data.size(), expected.expected_size);
ASSERT_FALSE(seen_data.empty());
if(expected.is_special)
{
EXPECT_EQ(*seen_data.begin(), expected.special_val);
}
else
{
EXPECT_EQ(*seen_data.begin(), 1.0);
EXPECT_EQ(*seen_data.rbegin(), double(seen_data.size()));
}
}
void
null_dispatch_callback(rocprofiler_queue_id_t,
const rocprofiler_agent_t*,
rocprofiler_correlation_id_t,
const hsa_kernel_dispatch_packet_t*,
uint64_t,
void*,
rocprofiler_profile_config_id_t*)
{}
void
null_buffered_callback(rocprofiler_context_id_t,
rocprofiler_buffer_id_t,
rocprofiler_record_header_t**,
size_t,
void*,
uint64_t)
{}
} // namespace
TEST(core, check_packet_generation)
{
ASSERT_EQ(hsa_init(), HSA_STATUS_SUCCESS);
test_init();
auto agents = hsa::get_queue_controller().get_supported_agents();
ASSERT_GT(agents.size(), 0);
for(const auto& [_, agent] : agents)
{
auto metrics = findDeviceMetrics(agent, {});
ASSERT_FALSE(metrics.empty());
ASSERT_TRUE(agent.get_rocp_agent());
for(auto& metric : metrics)
{
/**
* Check profile construction
*/
rocprofiler_profile_config_id_t cfg_id = {};
rocprofiler_counter_id_t id = {.handle = metric.id()};
ROCPROFILER_CALL(
rocprofiler_create_profile_config(agent.get_rocp_agent()->id, &id, 1, &cfg_id),
"Unable to create profile");
auto profile = counters::get_profile_config(cfg_id);
ASSERT_TRUE(profile);
EXPECT_EQ(counters::counter_callback_info::setup_profile_config(agent, profile),
ROCPROFILER_STATUS_SUCCESS)
<< fmt::format("Could not build profile for {}", metric.name());
/**
* Check that a packet generator was created and there is an AST with constructed
* dimensions
*/
EXPECT_TRUE(profile->pkt_generator) << "No packet generator created";
EXPECT_EQ(profile->asts.size(), 1);
EXPECT_FALSE(profile->asts.at(0).dimension_types().empty());
/**
* Check packet generation
*/
counters::counter_callback_info cb_info;
std::unique_ptr<hsa::AQLPacket> pkt;
EXPECT_EQ(cb_info.get_packet(pkt, agent, profile), ROCPROFILER_STATUS_SUCCESS)
<< "Unable to generate packet";
EXPECT_TRUE(pkt) << "Expected a packet to be generated";
cb_info.packet_return_map.wlock([&](const auto& data) {
EXPECT_EQ(data.size(), 1) << "Incorrect packet size";
const auto* ptr = common::get_val(data, pkt.get());
EXPECT_TRUE(ptr) << "Could not find pkt";
});
/**
* Check that required hardware counters match
*/
ASSERT_TRUE(profile->agent);
auto name_str = std::string(profile->agent->name);
auto req_counters =
counters::get_required_hardware_counters(counters::get_ast_map(), name_str, metric);
for(const auto& req_metric : *req_counters)
{
if(req_metric.special().empty())
{
EXPECT_GT(profile->reqired_hw_counters.count(req_metric), 0)
<< "Could not find metric - " << req_metric.name();
profile->reqired_hw_counters.erase(req_metric);
}
else
{
EXPECT_GT(profile->required_special_counters.count(req_metric), 0)
<< "Could not find metric - " << req_metric.name();
profile->required_special_counters.erase(req_metric);
}
}
EXPECT_TRUE(profile->required_special_counters.empty())
<< "Special counters list larger than expected";
EXPECT_TRUE(profile->reqired_hw_counters.empty())
<< "HW Counter list larger than expected";
}
}
}
namespace rocprofiler
{
namespace hsa
{
class FakeQueue : public Queue
{
public:
FakeQueue(const AgentCache& a, rocprofiler_queue_id_t id)
: Queue(a)
, _agent(a)
, _id(id)
{}
virtual const AgentCache& get_agent() const override final { return _agent; };
virtual rocprofiler_queue_id_t get_id() const override final { return _id; };
~FakeQueue() {}
private:
const AgentCache& _agent;
rocprofiler_queue_id_t _id = {};
};
} // namespace hsa
} // namespace rocprofiler
namespace
{
struct expected_dispatch
{
// To pass back
rocprofiler_profile_config_id_t id;
rocprofiler_queue_id_t queue_id;
const rocprofiler_agent_t* agent;
rocprofiler_correlation_id_t correlation_id;
hsa_kernel_dispatch_packet_t* dispatch_packet;
uint64_t kernel_id;
rocprofiler_profile_config_id_t* config;
};
void
user_dispatch_cb(rocprofiler_queue_id_t queue_id,
const rocprofiler_agent_t* agent,
rocprofiler_correlation_id_t correlation_id,
const hsa_kernel_dispatch_packet_t* dispatch_packet,
uint64_t kernel_id,
void* callback_data_args,
rocprofiler_profile_config_id_t* config)
{
expected_dispatch& expected = *static_cast<expected_dispatch*>(callback_data_args);
ASSERT_EQ(expected.agent, agent);
ASSERT_EQ(expected.queue_id.handle, queue_id.handle);
ASSERT_EQ(expected.correlation_id.internal, correlation_id.internal);
ASSERT_EQ(expected.correlation_id.external.ptr, correlation_id.external.ptr);
ASSERT_EQ(expected.correlation_id.external.value, correlation_id.external.value);
ASSERT_EQ(expected.dispatch_packet, dispatch_packet);
ASSERT_EQ(expected.kernel_id, kernel_id);
config->handle = expected.id.handle;
}
} // namespace
namespace rocprofiler
{
namespace buffer
{
uint64_t
get_buffer_offset();
}
} // namespace rocprofiler
TEST(core, check_callbacks)
{
int64_t count = 0;
ASSERT_EQ(hsa_init(), HSA_STATUS_SUCCESS);
test_init();
registration::init_logging();
registration::set_init_status(-1);
context::push_client(1);
ROCPROFILER_CALL(rocprofiler_create_context(&get_client_ctx()), "context creation failed");
auto agents = hsa::get_queue_controller().get_supported_agents();
ASSERT_GT(agents.size(), 0);
hsa::get_queue_controller().disable_serialization();
for(const auto& [_, agent] : agents)
{
/**
* Setup
*/
rocprofiler_queue_id_t qid = {.handle = static_cast<uint64_t>(count++)};
hsa::FakeQueue fq(agent, qid);
auto metrics = findDeviceMetrics(agent, {});
ASSERT_FALSE(metrics.empty());
ASSERT_TRUE(agent.get_rocp_agent());
for(auto& metric : metrics)
{
/**
* Do not check expression evaluation here. This is checked as part of evaluate_ast
* tests in a more controlled manner (aka, not requiring construction of the AST here).
*/
if(!metric.expression().empty()) continue;
/**
* Setup
*/
expected_dispatch expected = {};
rocprofiler_counter_id_t id = {.handle = metric.id()};
ROCPROFILER_CALL(
rocprofiler_create_profile_config(agent.get_rocp_agent()->id, &id, 1, &expected.id),
"Unable to create profile");
auto profile = counters::get_profile_config(expected.id);
ASSERT_TRUE(profile);
std::shared_ptr<counters::counter_callback_info> cb_info =
std::make_shared<counters::counter_callback_info>();
cb_info->user_cb = user_dispatch_cb;
cb_info->callback_args = static_cast<void*>(&expected);
context::correlation_id corr_id;
corr_id.internal = count++;
hsa::rocprofiler_packet pkt;
pkt.ext_amd_aql_pm4.header = count++;
expected.correlation_id = {.internal = corr_id.internal,
.external = context::null_user_data};
expected.dispatch_packet = &pkt.kernel_dispatch;
expected.kernel_id = count++;
expected.queue_id = qid;
expected.agent = fq.get_agent().get_rocp_agent();
hsa::Queue::queue_info_session_t::external_corr_id_map_t extern_ids = {};
auto ret_pkt =
counters::queue_cb(cb_info, fq, pkt, expected.kernel_id, extern_ids, &corr_id);
ASSERT_TRUE(ret_pkt) << fmt::format("Expected a packet to be generated for - {}",
metric.name());
/**
* Fake some data for the counter
*/
size_t* fake_data = static_cast<size_t*>(ret_pkt->profile.output_buffer.ptr);
for(size_t i = 0; i < (ret_pkt->profile.output_buffer.size / sizeof(size_t)); i++)
{
fake_data[i] = i + 1;
}
/**
* Create the buffer and run test
*/
rocprofiler_buffer_id_t opt_buff_id = {.handle = 0};
buf_check check = {
.expected_size = ret_pkt->profile.output_buffer.size / sizeof(size_t),
.is_special = !metric.special().empty(),
.special_val = (metric.special().empty() ? 0.0
: double(counters::get_agent_property(
std::string_view(metric.name()),
*agent.get_rocp_agent())))};
ROCPROFILER_CALL(rocprofiler_create_buffer(get_client_ctx(),
500 * sizeof(size_t),
500 * sizeof(size_t),
ROCPROFILER_BUFFER_POLICY_LOSSLESS,
buffered_callback,
&check,
&opt_buff_id),
"Could not create buffer");
cb_info->buffer = opt_buff_id;
hsa::Queue::queue_info_session_t sess = {.queue = fq, .correlation_id = &corr_id};
counters::inst_pkt_t pkts;
pkts.emplace_back(
std::make_pair(std::move(ret_pkt), static_cast<counters::ClientID>(0)));
completed_cb(cb_info, fq, pkt, sess, pkts);
rocprofiler_flush_buffer(opt_buff_id);
rocprofiler_destroy_buffer(opt_buff_id);
}
}
registration::set_init_status(1);
registration::finalize();
}
TEST(core, destroy_counter_profile)
{
ASSERT_EQ(hsa_init(), HSA_STATUS_SUCCESS);
test_init();
registration::init_logging();
registration::set_init_status(-1);
context::push_client(1);
ROCPROFILER_CALL(rocprofiler_create_context(&get_client_ctx()), "context creation failed");
auto agents = hsa::get_queue_controller().get_supported_agents();
ASSERT_GT(agents.size(), 0);
for(const auto& [_, agent] : agents)
{
auto metrics = findDeviceMetrics(agent, {});
ASSERT_FALSE(metrics.empty());
ASSERT_TRUE(agent.get_rocp_agent());
for(auto& metric : metrics)
{
expected_dispatch expected = {};
rocprofiler_counter_id_t id = {.handle = metric.id()};
ROCPROFILER_CALL(
rocprofiler_create_profile_config(agent.get_rocp_agent()->id, &id, 1, &expected.id),
"Unable to create profile");
ROCPROFILER_CALL(rocprofiler_destroy_profile_config(expected.id),
"Could not delete profile id");
/**
* Check the profile was actually destroyed
*/
auto profile = counters::get_profile_config(expected.id);
EXPECT_FALSE(profile);
}
}
registration::set_init_status(1);
registration::finalize();
}
TEST(core, start_stop_ctx)
{
ASSERT_EQ(hsa_init(), HSA_STATUS_SUCCESS);
test_init();
registration::init_logging();
registration::set_init_status(-1);
context::push_client(1);
ROCPROFILER_CALL(rocprofiler_create_context(&get_client_ctx()), "context creation failed");
rocprofiler_buffer_id_t opt_buff_id = {.handle = 0};
ROCPROFILER_CALL(rocprofiler_create_buffer(get_client_ctx(),
500 * sizeof(size_t),
500 * sizeof(size_t),
ROCPROFILER_BUFFER_POLICY_LOSSLESS,
null_buffered_callback,
nullptr,
&opt_buff_id),
"Could not create buffer");
ROCPROFILER_CALL(rocprofiler_configure_buffered_dispatch_profile_counting_service(
get_client_ctx(), opt_buff_id, null_dispatch_callback, (void*) 0x12345),
"Could not setup buffered service");
ROCPROFILER_CALL(rocprofiler_start_context(get_client_ctx()), "start context");
/**
* Check that the context was actually started
*/
auto* ctx_p = context::get_mutable_registered_context(get_client_ctx());
ASSERT_TRUE(ctx_p);
auto& ctx = *ctx_p;
ASSERT_TRUE(ctx.counter_collection);
ASSERT_EQ(ctx.counter_collection->callbacks.size(), 1);
EXPECT_EQ(ctx.counter_collection->callbacks.at(0)->user_cb, null_dispatch_callback);
EXPECT_EQ(ctx.counter_collection->callbacks.at(0)->callback_args, (void*) 0x12345);
EXPECT_EQ(ctx.counter_collection->callbacks.at(0)->context.handle, get_client_ctx().handle);
ASSERT_TRUE(ctx.counter_collection->callbacks.at(0)->buffer);
EXPECT_EQ(ctx.counter_collection->callbacks.at(0)->buffer->handle, opt_buff_id.handle);
bool found = false;
ctx.counter_collection->enabled.rlock([&](const auto& data) { found = data; });
EXPECT_TRUE(found);
found = false;
hsa::get_queue_controller().iterate_callbacks([&](auto cid, const auto&) {
if(cid == ctx.counter_collection->callbacks.at(0)->queue_id)
{
found = true;
}
});
EXPECT_TRUE(found);
/**
* Check if context can be disabled correctly
*/
ROCPROFILER_CALL(rocprofiler_stop_context(get_client_ctx()), "stop context");
found = false;
hsa::get_queue_controller().iterate_callbacks([&](auto cid, const auto&) {
if(cid == ctx.counter_collection->callbacks.at(0)->queue_id)
{
found = true;
}
});
EXPECT_FALSE(found);
found = false;
ctx.counter_collection->enabled.rlock([&](const auto& data) { found = data; });
EXPECT_FALSE(found);
rocprofiler_flush_buffer(opt_buff_id);
rocprofiler_destroy_buffer(opt_buff_id);
registration::set_init_status(1);
registration::finalize();
}
TEST(core, public_api_iterate_agents)
{
ASSERT_EQ(hsa_init(), HSA_STATUS_SUCCESS);
test_init();
registration::init_logging();
registration::set_init_status(-1);
context::push_client(1);
auto agents = hsa::get_queue_controller().get_supported_agents();
for(const auto& [_, agent] : agents)
{
std::set<uint64_t> from_api;
// Iterate through the agents and get the counters available on that agent
ROCPROFILER_CALL(rocprofiler_iterate_agent_supported_counters(
agent.get_rocp_agent()->id,
[](rocprofiler_agent_id_t,
rocprofiler_counter_id_t* counters,
size_t num_counters,
void* user_data) {
std::set<uint64_t>* vec =
static_cast<std::set<uint64_t>*>(user_data);
for(size_t i = 0; i < num_counters; i++)
{
vec->insert(counters[i].handle);
}
return ROCPROFILER_STATUS_SUCCESS;
},
static_cast<void*>(&from_api)),
"Could not fetch supported counters");
auto expected = findDeviceMetrics(agent, {});
for(const auto& x : expected)
{
ASSERT_GT(from_api.count(x.id()), 0);
from_api.erase(x.id());
}
EXPECT_TRUE(from_api.empty());
}
}
@@ -22,8 +22,51 @@
#include <gtest/gtest.h>
#include <fmt/core.h>
#include <hsa/hsa.h>
#include <hsa/hsa_api_trace.h>
#include <hsa/hsa_ext_amd.h>
#include <rocprofiler-sdk/rocprofiler.h>
#include "lib/common/static_object.hpp"
#include "lib/common/utility.hpp"
#include "lib/rocprofiler-sdk/agent.hpp"
#include "lib/rocprofiler-sdk/aql/packet_construct.hpp"
#include "lib/rocprofiler-sdk/buffer.hpp"
#include "lib/rocprofiler-sdk/context/context.hpp"
#include "lib/rocprofiler-sdk/counters/core.hpp"
#include "lib/rocprofiler-sdk/counters/dimensions.hpp"
#include "lib/rocprofiler-sdk/counters/id_decode.hpp"
#include "lib/rocprofiler-sdk/counters/metrics.hpp"
#include "lib/rocprofiler-sdk/hsa/agent_cache.hpp"
#include "lib/rocprofiler-sdk/hsa/queue.hpp"
#include "lib/rocprofiler-sdk/hsa/queue_controller.hpp"
#include "lib/rocprofiler-sdk/registration.hpp"
#include "rocprofiler-sdk/registration.h"
namespace
{
void
check_dim_pos(rocprofiler_counter_instance_id_t test_id,
rocprofiler::counters::rocprofiler_profile_counter_instance_types dim,
size_t expected)
{
EXPECT_EQ(rec_to_dim_pos(test_id, dim), expected);
size_t pos = 0;
rocprofiler_query_record_dimension_position(
test_id, static_cast<rocprofiler_counter_dimension_id_t>(dim), &pos);
EXPECT_EQ(pos, expected);
}
void
check_counter_id(rocprofiler_counter_instance_id_t id, uint64_t expected_handle)
{
rocprofiler_counter_id_t api_id = {.handle = 0};
rocprofiler_query_record_counter_id(id, &api_id);
EXPECT_EQ(rocprofiler::counters::rec_to_counter_id(id).handle, expected_handle);
EXPECT_EQ(rocprofiler::counters::rec_to_counter_id(id).handle, api_id.handle);
}
} // namespace
TEST(dimension, set_get)
{
@@ -41,7 +84,7 @@ TEST(dimension, set_get)
set_counter_in_rec(test_id, test_counter);
// 0x0141000000000000 = decimal counter id 321 << DIM_BIT_LENGTH
EXPECT_EQ(test_id, 0x0141000000000000);
EXPECT_EQ(rec_to_counter_id(test_id).handle, 321);
check_counter_id(test_id, 321);
// Test multiples of i, setting/getting those values across all
// dimensions
@@ -51,7 +94,7 @@ TEST(dimension, set_get)
{
auto dim = static_cast<rocprofiler_profile_counter_instance_types>(i);
set_dim_in_rec(test_id, dim, i);
EXPECT_EQ(rec_to_dim_pos(test_id, dim), i);
check_dim_pos(test_id, dim, i);
set_dim_in_rec(test_id, dim, i * multi_factor);
for(size_t j = 1; j < static_cast<size_t>(ROCPROFILER_DIMENSION_LAST); j++)
{
@@ -59,10 +102,10 @@ TEST(dimension, set_get)
set_dim_in_rec(test_id,
static_cast<rocprofiler_profile_counter_instance_types>(j),
max_counter_val);
EXPECT_EQ(rec_to_dim_pos(
test_id, static_cast<rocprofiler_profile_counter_instance_types>(j)),
max_counter_val);
EXPECT_EQ(rec_to_dim_pos(test_id, dim), i * multi_factor);
check_dim_pos(test_id,
static_cast<rocprofiler_profile_counter_instance_types>(j),
max_counter_val);
check_dim_pos(test_id, dim, i * multi_factor);
}
for(size_t j = static_cast<size_t>(ROCPROFILER_DIMENSION_LAST - 1); j > 0; j--)
@@ -71,9 +114,9 @@ TEST(dimension, set_get)
set_dim_in_rec(test_id,
static_cast<rocprofiler_profile_counter_instance_types>(j),
max_counter_val);
EXPECT_EQ(rec_to_dim_pos(test_id, (rocprofiler_profile_counter_instance_types) j),
max_counter_val);
EXPECT_EQ(rec_to_dim_pos(test_id, dim), i * multi_factor);
check_dim_pos(
test_id, (rocprofiler_profile_counter_instance_types) j, max_counter_val);
check_dim_pos(test_id, dim, i * multi_factor);
}
// Check that name exists
@@ -87,18 +130,217 @@ TEST(dimension, set_get)
{
auto dim = static_cast<rocprofiler_profile_counter_instance_types>(i);
set_dim_in_rec(test_id, dim, i * 5);
EXPECT_EQ(rec_to_dim_pos(test_id, dim), i * 5);
check_dim_pos(test_id, dim, i * 5);
set_dim_in_rec(test_id, dim, i * 3);
EXPECT_EQ(rec_to_dim_pos(test_id, dim), i * 3);
check_dim_pos(test_id, dim, i * 3);
}
test_counter.handle = 123;
set_counter_in_rec(test_id, test_counter);
EXPECT_EQ(rec_to_counter_id(test_id).handle, 123);
check_counter_id(test_id, 123);
// Test that all bits can be set/fetched for dims, 0xFAFBFCFDFEFF is a random
// collection of 48 bits.
set_dim_in_rec(test_id, ROCPROFILER_DIMENSION_NONE, 0xFAFBFCFDFEFF);
EXPECT_EQ(rec_to_dim_pos(test_id, ROCPROFILER_DIMENSION_NONE), 0xFAFBFCFDFEFF);
EXPECT_EQ(rec_to_counter_id(test_id).handle, 123);
check_dim_pos(test_id, ROCPROFILER_DIMENSION_NONE, 0xFAFBFCFDFEFF);
check_counter_id(test_id, 123);
}
using namespace rocprofiler;
namespace
{
AmdExtTable&
get_ext_table()
{
static auto _v = []() {
auto val = AmdExtTable{};
val.hsa_amd_memory_pool_get_info_fn = hsa_amd_memory_pool_get_info;
val.hsa_amd_agent_iterate_memory_pools_fn = hsa_amd_agent_iterate_memory_pools;
val.hsa_amd_memory_pool_allocate_fn = hsa_amd_memory_pool_allocate;
val.hsa_amd_memory_pool_free_fn = hsa_amd_memory_pool_free;
val.hsa_amd_agent_memory_pool_get_info_fn = hsa_amd_agent_memory_pool_get_info;
val.hsa_amd_agents_allow_access_fn = hsa_amd_agents_allow_access;
return val;
}();
return _v;
}
CoreApiTable&
get_api_table()
{
static auto _v = []() {
auto val = CoreApiTable{};
val.hsa_iterate_agents_fn = hsa_iterate_agents;
val.hsa_agent_get_info_fn = hsa_agent_get_info;
val.hsa_queue_create_fn = hsa_queue_create;
val.hsa_queue_destroy_fn = hsa_queue_destroy;
return val;
}();
return _v;
}
auto
findDeviceMetrics(const hsa::AgentCache& agent, const std::unordered_set<std::string>& metrics)
{
std::vector<counters::Metric> ret;
auto all_counters = counters::getMetricMap();
LOG(ERROR) << "Looking up counters for " << std::string(agent.name());
auto gfx_metrics = common::get_val(*all_counters, std::string(agent.name()));
if(!gfx_metrics)
{
LOG(ERROR) << "No counters found for " << std::string(agent.name());
return ret;
}
for(auto& counter : *gfx_metrics)
{
if(metrics.count(counter.name()) > 0 || metrics.empty())
{
ret.push_back(counter);
}
}
return ret;
}
void
test_init()
{
HsaApiTable table;
table.amd_ext_ = &get_ext_table();
table.core_ = &get_api_table();
agent::construct_agent_cache(&table);
hsa::get_queue_controller().init(get_api_table(), get_ext_table());
}
} // namespace
TEST(dimension, block_dim_test)
{
ASSERT_EQ(hsa_init(), HSA_STATUS_SUCCESS);
test_init();
auto agents = hsa::get_queue_controller().get_supported_agents();
ASSERT_GT(agents.size(), 0);
for(const auto& [_, agent] : agents)
{
auto metrics = findDeviceMetrics(agent, {});
ASSERT_FALSE(metrics.empty());
ASSERT_TRUE(agent.get_rocp_agent());
// aql::AQLPacketConstruct pkt(agent, metrics);
// auto test_pkt = pkt.construct_packet(get_ext_table());
for(const auto& metric : metrics)
{
/**
* Calculate expected dimensions from AQL Profiler
*/
std::unordered_map<counters::rocprofiler_profile_counter_instance_types, uint64_t>
rocp_dims;
LOG(ERROR) << metric.name() << " " << metric.special();
if(!metric.special().empty())
{
rocp_dims[counters::rocprofiler_profile_counter_instance_types::
ROCPROFILER_DIMENSION_INSTANCE] = 1;
}
else if(!metric.expression().empty())
{
continue;
}
else
{
aql::AQLPacketConstruct pkt_gen(agent, {metric});
const auto& events = pkt_gen.get_counter_events(metric);
for(const auto& event : events)
{
std::map<int, uint64_t> dims;
auto status = aql::get_dim_info(agent.get_hsa_agent(), event, 0, dims);
CHECK_EQ(status, ROCPROFILER_STATUS_SUCCESS)
<< rocprofiler_get_status_string(status);
for(const auto& [id, extent] : dims)
{
if(const auto* inst_type = rocprofiler::common::get_val(
counters::aqlprofile_id_to_rocprof_instance(), id))
{
rocp_dims.emplace(*inst_type, 0).first->second = extent;
}
}
}
}
/**
* Compare with actual
*/
auto dims = getBlockDimensions(agent.name(), metric);
EXPECT_FALSE(dims.empty());
EXPECT_EQ(dims.size(), rocp_dims.size());
for(const auto& dim : dims)
{
const auto* ptr = rocprofiler::common::get_val(rocp_dims, dim.type());
ASSERT_TRUE(ptr) << fmt::format("{}", dim);
EXPECT_EQ(*ptr, dim.size()) << fmt::format("{}", dim);
EXPECT_EQ(std::string(counters::dimension_map().at(dim.type())), dim.name())
<< fmt::format("{}", dim);
}
/**
* Check this value exists in the dimension cache
*/
const auto* dim_cache =
rocprofiler::common::get_val(counters::get_dimension_cache(), metric.id());
ASSERT_TRUE(dim_cache);
EXPECT_EQ(fmt::format("{}", fmt::join(dims, "|")),
fmt::format("{}", fmt::join(*dim_cache, "|")));
/**
* Check counter instance count public API
*/
size_t instance_count = 0;
size_t calculated_instance_count = 0;
rocprofiler_query_counter_instance_count(
agent.get_rocp_agent()->id, {.handle = metric.id()}, &instance_count);
for(const auto& dim : dims)
{
if(calculated_instance_count == 0)
calculated_instance_count = dim.size();
else if(dim.size() > 0)
calculated_instance_count = dim.size() * calculated_instance_count;
}
EXPECT_EQ(instance_count, calculated_instance_count);
/**
* Check the public API returns this value
*/
rocprofiler_iterate_counter_dimensions(
{.handle = metric.id()},
[](rocprofiler_counter_id_t,
const rocprofiler_record_dimension_info_t* dim_info,
size_t num_dims,
void* user_data) -> rocprofiler_status_t {
auto expected_dims = *static_cast<
std::unordered_map<counters::rocprofiler_profile_counter_instance_types,
uint64_t>*>(user_data);
EXPECT_EQ(num_dims, expected_dims.size());
for(size_t i = 0; i < num_dims; i++)
{
const auto* lookup_ptr = rocprofiler::common::get_val(
expected_dims,
static_cast<counters::rocprofiler_profile_counter_instance_types>(
dim_info[i].id));
EXPECT_TRUE(lookup_ptr);
if(!lookup_ptr) return ROCPROFILER_STATUS_ERROR;
EXPECT_EQ(*lookup_ptr, dim_info[i].instance_size);
EXPECT_EQ(
counters::dimension_map().at(
static_cast<counters::rocprofiler_profile_counter_instance_types>(
dim_info[i].id)),
std::string(dim_info[i].name));
}
return ROCPROFILER_STATUS_SUCCESS;
},
static_cast<void*>(&rocp_dims));
}
}
hsa_shut_down();
}
@@ -106,6 +106,8 @@ private:
MetricIdMap copy_ = *CHECK_NOTNULL(rocprofiler::counters::getMetricIdMap());
};
namespace
{
metric_map_order&
get_metric_map()
{
@@ -145,6 +147,7 @@ get_buffer()
static rocprofiler_buffer_id_t buf = {};
return buf;
}
} // namespace
// Test that metrics map remains in scope at exit
TEST(counters_init_order, metric_map_order)
@@ -27,6 +27,8 @@
#include <algorithm>
#include <rocprofiler-sdk/rocprofiler.h>
#include "lib/rocprofiler-sdk/agent.hpp"
#include "lib/rocprofiler-sdk/counters/metrics.hpp"
@@ -175,3 +177,17 @@ TEST(metrics, check_agent_valid)
}
}
}
TEST(metrics, check_public_api_query)
{
const auto* id_map = counters::getMetricIdMap();
for(const auto& [id, metric] : *id_map)
{
const char* name = nullptr;
size_t size = 0;
ASSERT_EQ(rocprofiler_query_counter_name({.handle = id}, &name, &size),
ROCPROFILER_STATUS_SUCCESS);
EXPECT_EQ(std::string(name), metric.name());
EXPECT_EQ(size, metric.name().size());
}
}
@@ -156,10 +156,14 @@ public:
AmdExtTable ext_api,
hsa_queue_t** queue);
~Queue();
Queue(const AgentCache& agent)
: _agent(agent)
{}
const hsa_queue_t* intercept_queue() const { return _intercept_queue; };
const AgentCache& get_agent() const { return _agent; }
virtual ~Queue();
const hsa_queue_t* intercept_queue() const { return _intercept_queue; };
virtual const AgentCache& get_agent() const { return _agent; }
void create_signal(uint32_t attribute, hsa_signal_t* signal) const;
void signal_async_handler(const hsa_signal_t& signal, Queue::queue_info_session_t* data) const;
@@ -167,7 +171,7 @@ public:
template <typename FuncT>
void signal_callback(FuncT&& func) const;
rocprofiler_queue_id_t get_id() const;
virtual rocprofiler_queue_id_t get_id() const;
// Fast check to see if we have any callbacks we need to notify
int get_notifiers() const { return _notifiers; }
@@ -269,6 +269,12 @@ QueueController::profiler_serializer(FuncT&& lambda)
_profiler_serializer.wlock(std::forward<FuncT>(lambda));
}
void
QueueController::disable_serialization()
{
profiler_serializer([](auto& serializer) { serializer.enabled = false; });
}
namespace
{
/*
@@ -291,6 +297,7 @@ profiler_serializer_ready_signal_handler(hsa_signal_value_t /* signal_value */,
auto* hsa_queue = static_cast<hsa_queue_t*>(data);
const auto* queue = get_queue_controller().get_queue(*hsa_queue);
get_queue_controller().profiler_serializer([&](auto& serializer) {
if(!serializer.enabled) return;
{
std::lock_guard<std::mutex> cv_lock(queue->cv_mutex);
if(queue->get_state() == queue_state::to_destroy)
@@ -322,6 +329,7 @@ void
profiler_serializer_kernel_completion_signal(hsa_signal_t queue_block_signal)
{
get_queue_controller().profiler_serializer([queue_block_signal](auto& serializer) {
if(!serializer.enabled) return;
assert(serializer.dispatch_queue != nullptr);
serializer.dispatch_queue = nullptr;
get_queue_controller().get_core_table().hsa_signal_store_screlease_fn(queue_block_signal,
@@ -370,6 +378,17 @@ QueueController::iterate_queues(const queue_iterator_cb_t& cb) const
});
}
void
QueueController::iterate_callbacks(const callback_iterator_cb_t& cb) const
{
_callback_cache.rlock([&cb](const auto& map) {
for(const auto& [cid, tuple] : map)
{
cb(cid, tuple);
}
});
}
QueueController&
get_queue_controller()
{
@@ -54,13 +54,17 @@ struct profiler_serializer_t
{
const Queue* dispatch_queue{nullptr};
std::deque<const Queue*> dispatch_ready;
bool enabled{true};
};
// Tracks and manages HSA queues
class QueueController
{
public:
using queue_iterator_cb_t = std::function<void(const Queue*)>;
using agent_callback_tuple_t =
std::tuple<rocprofiler_agent_t, Queue::queue_cb_t, Queue::completed_cb_t>;
using queue_iterator_cb_t = std::function<void(const Queue*)>;
using callback_iterator_cb_t = std::function<void(ClientID, const agent_callback_tuple_t&)>;
QueueController() = default;
// Initializes the QueueInterceptor. This must be delayed until
@@ -96,9 +100,16 @@ public:
template <typename FuncT>
void profiler_serializer(FuncT&& lambda);
void iterate_callbacks(const callback_iterator_cb_t&) const;
/**
* Disable serialization for QueueController, has no effect if counter collection
* is not in use (which defaults to no serialization mechanism). Should only be used for
* testing.
*/
void disable_serialization();
private:
using agent_callback_tuple_t =
std::tuple<rocprofiler_agent_t, Queue::queue_cb_t, Queue::completed_cb_t>;
using queue_map_t = std::unordered_map<hsa_queue_t*, std::unique_ptr<Queue>>;
using client_id_map_t = std::unordered_map<ClientID, agent_callback_tuple_t>;
using agent_cache_map_t = std::unordered_map<uint32_t, AgentCache>;
@@ -75,7 +75,18 @@ ROCPROFILER_STATUS_STRING(ROCPROFILER_STATUS_ERROR_METRIC_NOT_VALID_FOR_AGENT,
"Metric is not valid for the agent")
ROCPROFILER_STATUS_STRING(ROCPROFILER_STATUS_ERROR_FINALIZED,
"Invalid request because rocprofiler has finalized")
ROCPROFILER_STATUS_STRING(ROCPROFILER_STATUS_ERROR_HSA_NOT_LOADED,
"Function call requires that HSA is loaded")
ROCPROFILER_STATUS_STRING(ROCPROFILER_STATUS_ERROR_DIM_NOT_FOUND,
"Dimension is not found for counter")
ROCPROFILER_STATUS_STRING(ROCPROFILER_STATUS_ERROR_PROFILE_COUNTER_NOT_FOUND,
"Profile could not find counter for GPU")
ROCPROFILER_STATUS_STRING(ROCPROFILER_STATUS_ERROR_AST_GENERATION_FAILED,
"AST could not be generated correctly")
ROCPROFILER_STATUS_STRING(ROCPROFILER_STATUS_ERROR_AST_NOT_FOUND, "AST was not found")
ROCPROFILER_STATUS_STRING(
ROCPROFILER_STATUS_ERROR_AQL_NO_EVENT_COORD,
"AQL Profiler was not able to find event coordinates for defined counters")
template <size_t Idx, size_t... Tail>
const char*
get_status_name(rocprofiler_status_t status, std::index_sequence<Idx, Tail...>)