363f85dc72
* Squashed commit of the following: commit b76f2635f4b65599f03812a73d0cf410f5ada213 Author: Mythreya <mythreya.kuricheti@amd.com> Date: Fri Apr 26 00:29:09 2024 +0000 Changed for PR feedback commit bedb8ad566ff42fbf117b19202c26c507abcf8ac Author: Jonathan R. Madsen <jonathanrmadsen@gmail.com> Date: Thu Apr 25 19:20:06 2024 -0500 Fix installation commit a98f8a69459a1450a1be9c98e20b3c1e7f2568c2 Author: Jonathan R. Madsen <jonathanrmadsen@gmail.com> Date: Thu Apr 25 19:16:35 2024 -0500 Restructure the headers commit 46489a020ffafdd5f4ce3f580469ff233ef67fe1 Author: Mythreya <mythreya.kuricheti@amd.com> Date: Tue Apr 23 23:31:10 2024 +0000 Update hsa include commit 8e795282cce348fc6aa736b7857b21aeb32aa20a Author: Mythreya <mythreya.kuricheti@amd.com> Date: Tue Apr 23 23:02:32 2024 +0000 Report page migration events as start/end * Updated tests accordingly * Page migration events are reported independently commit 8784e5ad4895a626a2a8e4ac12f8021b34172bd4 Author: Mythreya <mythreya.kuricheti@amd.com> Date: Tue Apr 16 17:01:57 2024 +0000 Update handling of dropped page migration events Previously, we dropped all locally buffered events when we detect that KFD has dropped some events. This may drop too many pending events too eagerly. When we receive an end event and cannot find the corresponding start, we can be sure that KFD has dropped some events in the immediate past. When this happens, we look through all locally buffered events and report the start events that are older than 10s as partial events --- they have no "end" information (we expect that the end events have been dropped). We also set the polling timeout to 10s to prevent the local buffer from getting too large with events waiting to be paired up. Updated tests commit 2e8e0b07eeda9b5990e1ae8d28dcd3a035ce38e1 Author: Mythreya <mythreya.kuricheti@amd.com> Date: Tue Apr 16 17:01:31 2024 +0000 Docs for triggers * Fix page migration sample * Fix hasher, kfd install * Add hsa include * Install KFD include dir * Updates from code review - single timestamp field - node_id -> agent_id - from_node -> from_agent - to_node -> to_agent * Misc revisions * Remove page-migration install target * Update page-migration pytest * Tweak to serialization * Address PR comments * Update page-migration test * Add cli args, update iterations * Address PR comments * Add abi.cpp for static_asserts * Update page_migration gtest with only runtime tests * Moved helpers into utils.hpp --------- Co-authored-by: Jonathan R. Madsen <jonathanrmadsen@gmail.com>
892 rindas
26 KiB
C++
892 rindas
26 KiB
C++
// 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 "lib/rocprofiler-sdk/page_migration/page_migration.hpp"
|
|
#include "lib/common/logging.hpp"
|
|
#include "lib/common/mpl.hpp"
|
|
#include "lib/common/static_object.hpp"
|
|
#include "lib/rocprofiler-sdk/agent.hpp"
|
|
#include "lib/rocprofiler-sdk/buffer.hpp"
|
|
#include "lib/rocprofiler-sdk/context/context.hpp"
|
|
#include "lib/rocprofiler-sdk/details/kfd_ioctl.h"
|
|
#include "lib/rocprofiler-sdk/internal_threading.hpp"
|
|
#include "lib/rocprofiler-sdk/page_migration/utils.hpp"
|
|
|
|
#include <rocprofiler-sdk/agent.h>
|
|
#include <rocprofiler-sdk/buffer_tracing.h>
|
|
#include <rocprofiler-sdk/fwd.h>
|
|
#include <rocprofiler-sdk/hsa/api_id.h>
|
|
#include <rocprofiler-sdk/hsa/table_id.h>
|
|
|
|
#include <fmt/core.h>
|
|
#include <hsa/amd_hsa_signal.h>
|
|
#include <hsa/hsa.h>
|
|
|
|
#include <sys/poll.h>
|
|
#include <unistd.h>
|
|
#include <atomic>
|
|
#include <cerrno>
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <ratio>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <thread>
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <sys/eventfd.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#define ROCPROFILER_LIB_ROCPROFILER_SDK_PAGE_MIGRATION_PAGE_MIGRATION_CPP_IMPL 1
|
|
#include "page_migration.def.cpp"
|
|
#undef ROCPROFILER_LIB_ROCPROFILER_SDK_PAGE_MIGRATION_PAGE_MIGRATION_CPP_IMPL
|
|
|
|
namespace rocprofiler
|
|
{
|
|
namespace page_migration
|
|
{
|
|
template <typename T>
|
|
using small_vector = common::container::small_vector<T>;
|
|
|
|
using context_t = context::context;
|
|
using context_array_t = common::container::small_vector<const context_t*>;
|
|
|
|
template <size_t>
|
|
struct page_migration_info;
|
|
|
|
template <size_t>
|
|
struct kfd_event_info;
|
|
|
|
template <typename EnumT, int ValueE>
|
|
struct page_migration_enum_info;
|
|
|
|
template <typename EnumT>
|
|
struct page_migration_bounds;
|
|
|
|
// Parsing and utilities
|
|
namespace
|
|
{
|
|
constexpr auto
|
|
page_to_bytes(size_t val)
|
|
{
|
|
// each page is 4KB = 4096 bytes
|
|
return val << 12;
|
|
}
|
|
|
|
template <size_t>
|
|
page_migration_record_t parse_event(std::string_view)
|
|
{
|
|
ROCP_FATAL_IF(false) << page_migration_info<ROCPROFILER_PAGE_MIGRATION_NONE>::format_str;
|
|
return {};
|
|
}
|
|
|
|
auto
|
|
get_node_agent_id(uint32_t _node_id)
|
|
{
|
|
using agent_id_map_t = std::unordered_map<uint64_t, rocprofiler_agent_id_t>;
|
|
static auto*& _data = static_object<agent_id_map_t>::construct([]() {
|
|
auto _v = std::unordered_map<uint64_t, rocprofiler_agent_id_t>{};
|
|
for(const auto* agent : agent::get_agents())
|
|
_v.emplace(agent->gpu_id, agent->id);
|
|
return _v;
|
|
}());
|
|
|
|
CHECK(_data != nullptr);
|
|
ROCP_FATAL_IF(_data->count(_node_id) == 0) << "page_migration: unknown node id: " << _node_id;
|
|
return _data->at(_node_id);
|
|
}
|
|
|
|
template <>
|
|
page_migration_record_t
|
|
parse_event<ROCPROFILER_PAGE_MIGRATION_PAGE_FAULT_START>(std::string_view str)
|
|
{
|
|
auto rec = page_migration_record_t{};
|
|
auto& e = rec.args.page_fault_start;
|
|
uint32_t kind{};
|
|
uint32_t _node_id = 0;
|
|
|
|
char fault;
|
|
std::sscanf(str.data(),
|
|
page_migration_info<ROCPROFILER_PAGE_MIGRATION_PAGE_FAULT_START>::format_str.data(),
|
|
&kind,
|
|
&rec.timestamp,
|
|
&rec.pid,
|
|
&e.address,
|
|
&_node_id,
|
|
&fault);
|
|
|
|
e.read_fault = (fault == 'R');
|
|
e.address = page_to_bytes(e.address);
|
|
e.agent_id = get_node_agent_id(_node_id);
|
|
|
|
ROCP_TRACE << fmt::format("Page fault start [ ts: {} pid: {} addr: 0x{:X} node: {} ] \n",
|
|
rec.timestamp,
|
|
rec.pid,
|
|
e.address,
|
|
e.agent_id.handle);
|
|
|
|
return rec;
|
|
}
|
|
|
|
template <>
|
|
page_migration_record_t
|
|
parse_event<ROCPROFILER_PAGE_MIGRATION_PAGE_FAULT_END>(std::string_view str)
|
|
{
|
|
auto rec = page_migration_record_t{};
|
|
auto& e = rec.args.page_fault_end;
|
|
uint32_t kind{};
|
|
uint32_t _node_id = 0;
|
|
|
|
char migrated;
|
|
std::sscanf(str.data(),
|
|
page_migration_info<ROCPROFILER_PAGE_MIGRATION_PAGE_FAULT_END>::format_str.data(),
|
|
&kind,
|
|
&rec.timestamp,
|
|
&rec.pid,
|
|
&e.address,
|
|
&_node_id,
|
|
&migrated);
|
|
|
|
// M or U -> migrated / unmigrated?
|
|
if(migrated == 'M')
|
|
e.migrated = true;
|
|
else if(migrated == 'U')
|
|
e.migrated = false;
|
|
else
|
|
ROCP_WARNING << "Unknown PAGE_FAULT_END migrated/unmigrated state";
|
|
|
|
e.address = page_to_bytes(e.address);
|
|
e.agent_id = get_node_agent_id(_node_id);
|
|
|
|
ROCP_TRACE << fmt::format(
|
|
"Page fault end [ ts: {} pid: {} addr: 0x{:X} node: {} migrated: {} ] \n",
|
|
rec.timestamp,
|
|
rec.pid,
|
|
e.address,
|
|
e.agent_id.handle,
|
|
migrated);
|
|
|
|
return rec;
|
|
}
|
|
|
|
template <>
|
|
page_migration_record_t
|
|
parse_event<ROCPROFILER_PAGE_MIGRATION_PAGE_MIGRATE_START>(std::string_view str)
|
|
{
|
|
auto rec = page_migration_record_t{};
|
|
auto& e = rec.args.page_migrate_start;
|
|
uint32_t kind{};
|
|
uint32_t trigger{};
|
|
uint32_t _from_node = 0;
|
|
uint32_t _to_node = 0;
|
|
uint32_t _prefetch_node = 0;
|
|
uint32_t _preferred_node = 0;
|
|
|
|
std::sscanf(
|
|
str.data(),
|
|
page_migration_info<ROCPROFILER_PAGE_MIGRATION_PAGE_MIGRATE_START>::format_str.data(),
|
|
&kind,
|
|
&rec.timestamp,
|
|
&rec.pid,
|
|
&e.start_addr,
|
|
&e.end_addr,
|
|
&_from_node,
|
|
&_to_node,
|
|
&_prefetch_node,
|
|
&_preferred_node,
|
|
&trigger);
|
|
|
|
e.end_addr += e.start_addr;
|
|
e.trigger = static_cast<migrate_trigger_t>(trigger);
|
|
e.start_addr = page_to_bytes(e.start_addr);
|
|
e.end_addr = page_to_bytes(e.end_addr) - 1;
|
|
e.from_agent = get_node_agent_id(_from_node);
|
|
e.to_agent = get_node_agent_id(_to_node);
|
|
e.prefetch_agent = get_node_agent_id(_prefetch_node);
|
|
e.preferred_agent = get_node_agent_id(_preferred_node);
|
|
|
|
ROCP_TRACE << fmt::format(
|
|
"Page migrate start [ ts: {} pid: {} addr s: 0x{:X} addr "
|
|
"e: 0x{:X} size: {}B from node: {} to node: {} prefetch node: {} preferred node: {} "
|
|
"trigger: {} ] \n",
|
|
rec.timestamp,
|
|
rec.pid,
|
|
e.start_addr,
|
|
e.end_addr,
|
|
(e.end_addr - e.start_addr),
|
|
e.from_agent.handle,
|
|
e.to_agent.handle,
|
|
e.prefetch_agent.handle,
|
|
e.preferred_agent.handle,
|
|
trigger);
|
|
|
|
return rec;
|
|
}
|
|
|
|
template <>
|
|
page_migration_record_t
|
|
parse_event<ROCPROFILER_PAGE_MIGRATION_PAGE_MIGRATE_END>(std::string_view str)
|
|
{
|
|
auto rec = page_migration_record_t{};
|
|
auto& e = rec.args.page_migrate_end;
|
|
uint32_t kind{};
|
|
uint32_t trigger{};
|
|
uint32_t _from_node = 0;
|
|
uint32_t _to_node = 0;
|
|
|
|
std::sscanf(str.data(),
|
|
page_migration_info<ROCPROFILER_PAGE_MIGRATION_PAGE_MIGRATE_END>::format_str.data(),
|
|
&kind,
|
|
&rec.timestamp,
|
|
&rec.pid,
|
|
&e.start_addr,
|
|
&e.end_addr,
|
|
&_from_node,
|
|
&_to_node,
|
|
&trigger);
|
|
|
|
e.end_addr += e.start_addr;
|
|
e.trigger = static_cast<migrate_trigger_t>(trigger);
|
|
e.start_addr = page_to_bytes(e.start_addr);
|
|
e.end_addr = page_to_bytes(e.end_addr) - 1;
|
|
e.from_agent = get_node_agent_id(_from_node);
|
|
e.to_agent = get_node_agent_id(_to_node);
|
|
|
|
ROCP_TRACE << fmt::format("Page migrate end [ ts: {} pid: {} addr s: 0x{:X} addr e: "
|
|
"0x{:X} from node: {} to node: {} trigger: {} ] \n",
|
|
rec.timestamp,
|
|
rec.pid,
|
|
e.start_addr,
|
|
e.end_addr,
|
|
e.from_agent.handle,
|
|
e.to_agent.handle,
|
|
trigger);
|
|
|
|
return rec;
|
|
}
|
|
|
|
template <>
|
|
page_migration_record_t
|
|
parse_event<ROCPROFILER_PAGE_MIGRATION_QUEUE_EVICTION>(std::string_view str)
|
|
{
|
|
auto rec = page_migration_record_t{};
|
|
auto& e = rec.args.queue_eviction;
|
|
uint32_t kind{};
|
|
uint32_t trigger{};
|
|
uint32_t _node_id = 0;
|
|
|
|
std::sscanf(str.data(),
|
|
page_migration_info<ROCPROFILER_PAGE_MIGRATION_QUEUE_EVICTION>::format_str.data(),
|
|
&kind,
|
|
&rec.timestamp,
|
|
&rec.pid,
|
|
&_node_id,
|
|
&trigger);
|
|
|
|
e.trigger = static_cast<queue_suspend_trigger_t>(trigger);
|
|
e.agent_id = get_node_agent_id(_node_id);
|
|
|
|
ROCP_TRACE << fmt::format("Queue evict [ ts: {} pid: {} node: {} trigger: {} ] \n",
|
|
rec.timestamp,
|
|
rec.pid,
|
|
e.agent_id.handle,
|
|
trigger);
|
|
|
|
return rec;
|
|
}
|
|
|
|
template <>
|
|
page_migration_record_t
|
|
parse_event<ROCPROFILER_PAGE_MIGRATION_QUEUE_RESTORE>(std::string_view str)
|
|
{
|
|
auto rec = page_migration_record_t{};
|
|
auto& e = rec.args.queue_restore;
|
|
uint32_t kind{};
|
|
uint32_t _node_id = 0;
|
|
|
|
std::sscanf(str.data(),
|
|
page_migration_info<ROCPROFILER_PAGE_MIGRATION_QUEUE_RESTORE>::format_str.data(),
|
|
&kind,
|
|
&rec.timestamp,
|
|
&rec.pid,
|
|
&_node_id);
|
|
// check if we have a valid char at the end. -1 has \0
|
|
if(str[str.size() - 2] == 'R')
|
|
e.rescheduled = true;
|
|
else
|
|
e.rescheduled = false;
|
|
e.agent_id = get_node_agent_id(_node_id);
|
|
|
|
ROCP_TRACE << fmt::format(
|
|
"Queue restore [ ts: {} pid: {} node: {} ] \n", rec.timestamp, rec.pid, e.agent_id.handle);
|
|
|
|
return rec;
|
|
}
|
|
|
|
template <>
|
|
page_migration_record_t
|
|
parse_event<ROCPROFILER_PAGE_MIGRATION_UNMAP_FROM_GPU>(std::string_view str)
|
|
{
|
|
auto rec = page_migration_record_t{};
|
|
auto& e = rec.args.unmap_from_gpu;
|
|
uint32_t kind{};
|
|
uint32_t trigger{};
|
|
uint32_t _node_id = 0;
|
|
|
|
std::sscanf(str.data(),
|
|
page_migration_info<ROCPROFILER_PAGE_MIGRATION_UNMAP_FROM_GPU>::format_str.data(),
|
|
&kind,
|
|
&rec.timestamp,
|
|
&rec.pid,
|
|
&e.start_addr,
|
|
&e.end_addr,
|
|
&_node_id,
|
|
&trigger);
|
|
|
|
e.end_addr += e.start_addr;
|
|
e.trigger = static_cast<unmap_from_gpu_trigger_t>(trigger);
|
|
e.start_addr = page_to_bytes(e.start_addr);
|
|
e.end_addr = page_to_bytes(e.end_addr);
|
|
e.agent_id = get_node_agent_id(_node_id);
|
|
|
|
ROCP_TRACE << fmt::format(
|
|
"Unmap from GPU [ ts: {} pid: {} start addr: 0x{:X} end addr: 0x{:X} "
|
|
"node: {} trigger {} ] \n",
|
|
rec.timestamp,
|
|
rec.pid,
|
|
e.start_addr,
|
|
e.end_addr,
|
|
e.agent_id.handle,
|
|
trigger);
|
|
|
|
return rec;
|
|
}
|
|
|
|
template <>
|
|
page_migration_record_t parse_event<ROCPROFILER_PAGE_MIGRATION_NONE>(std::string_view)
|
|
{
|
|
throw std::runtime_error(
|
|
"ROCPROFILER_PAGE_MIGRATION_NONE for parsing page migration events should not happen");
|
|
}
|
|
|
|
template <size_t OpInx, size_t... OpInxs>
|
|
page_migration_record_t
|
|
parse_event(size_t event_id, std::string_view strn, std::index_sequence<OpInx, OpInxs...>)
|
|
{
|
|
if(OpInx == static_cast<uint32_t>(event_id))
|
|
{
|
|
auto rec = parse_event<OpInx>(strn);
|
|
rec.size = sizeof(page_migration_record_t);
|
|
rec.kind = ROCPROFILER_BUFFER_TRACING_PAGE_MIGRATION;
|
|
rec.operation = static_cast<rocprofiler_page_migration_operation_t>(OpInx);
|
|
return rec;
|
|
}
|
|
|
|
if constexpr(sizeof...(OpInxs) > 0)
|
|
return parse_event(event_id, strn, std::index_sequence<OpInxs...>{});
|
|
|
|
return page_migration_record_t{};
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------------*/
|
|
|
|
} // namespace
|
|
|
|
size_t
|
|
get_rocprof_op(const std::string_view event_data)
|
|
{
|
|
uint32_t kfd_event_id{};
|
|
std::sscanf(event_data.data(), "%x ", &kfd_event_id);
|
|
|
|
auto rocprof_id =
|
|
kfd_to_rocprof_op(static_cast<kfd_event_id_t>(kfd_event_id),
|
|
std::make_index_sequence<ROCPROFILER_PAGE_MIGRATION_LAST>{});
|
|
|
|
ROCP_CI_LOG_IF(WARNING, rocprof_id == 0)
|
|
<< fmt::format("Failed to parse KFD event ID {}. Parsed ID: {}, SDK ID: {}\n",
|
|
event_data[0],
|
|
kfd_event_id,
|
|
rocprof_id);
|
|
|
|
return rocprof_id;
|
|
}
|
|
|
|
void
|
|
kfd_readlines(const std::string_view str, void(handler)(std::string_view))
|
|
{
|
|
const auto find_newline = [&](auto b) { return std::find(b, str.cend(), '\n'); };
|
|
const auto* cursor = str.cbegin();
|
|
|
|
for(const auto* pos = find_newline(cursor); pos != str.cend(); pos = find_newline(cursor))
|
|
{
|
|
size_t char_count = pos - cursor;
|
|
assert(char_count > 0);
|
|
std::string_view event_str{cursor, char_count};
|
|
|
|
ROCP_INFO << fmt::format("KFD event: [{}]", event_str);
|
|
handler(event_str);
|
|
|
|
cursor = pos + 1;
|
|
}
|
|
}
|
|
|
|
// Event capture and reporting
|
|
namespace
|
|
{
|
|
constexpr auto kfd_ioctl_version = (1000 * KFD_IOCTL_MAJOR_VERSION) + KFD_IOCTL_MINOR_VERSION;
|
|
// Support has been added in kfdv >= 1.10+
|
|
static_assert(kfd_ioctl_version >= 1010, "KFD SMI support missing in kfd_ioctl.h");
|
|
|
|
auto
|
|
get_contexts(int operation)
|
|
{
|
|
auto active_contexts = context::get_active_contexts([](const auto* ctx) {
|
|
return (ctx->buffered_tracer &&
|
|
ctx->buffered_tracer->domains(ROCPROFILER_BUFFER_TRACING_PAGE_MIGRATION));
|
|
});
|
|
auto operation_ctxs = context::context_array_t{};
|
|
|
|
for(const auto* itr : active_contexts)
|
|
{
|
|
// if the given domain + op is not enabled, skip this context
|
|
if(itr->buffered_tracer->domains(ROCPROFILER_BUFFER_TRACING_PAGE_MIGRATION, operation))
|
|
{
|
|
operation_ctxs.emplace_back(itr);
|
|
}
|
|
}
|
|
|
|
return operation_ctxs;
|
|
}
|
|
|
|
void
|
|
handle_reporting(std::string_view event_data)
|
|
{
|
|
const auto op_inx = get_rocprof_op(event_data);
|
|
auto buffered_contexts = get_contexts(op_inx);
|
|
if(buffered_contexts.empty()) return;
|
|
|
|
// Parse and process the event
|
|
auto record = parse_event(
|
|
op_inx, event_data, std::make_index_sequence<ROCPROFILER_PAGE_MIGRATION_LAST>{});
|
|
|
|
for(const auto& itr : buffered_contexts)
|
|
{
|
|
auto* buffer = buffer::get_buffer(
|
|
itr->buffered_tracer->buffer_data.at(ROCPROFILER_BUFFER_TRACING_PAGE_MIGRATION));
|
|
CHECK_NOTNULL(buffer)->emplace(
|
|
ROCPROFILER_BUFFER_CATEGORY_TRACING, ROCPROFILER_BUFFER_TRACING_PAGE_MIGRATION, record);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// KFD utils
|
|
namespace kfd
|
|
{
|
|
void poll_events(small_vector<pollfd>);
|
|
|
|
using fd_flags_t = decltype(EFD_NONBLOCK);
|
|
using fd_t = decltype(pollfd::fd);
|
|
constexpr auto KFD_DEVICE_PATH{"/dev/kfd"};
|
|
|
|
SPECIALIZE_KFD_IOC_IOCTL(kfd_ioctl_get_version_args, AMDKFD_IOC_GET_VERSION);
|
|
SPECIALIZE_KFD_IOC_IOCTL(kfd_ioctl_smi_events_args, AMDKFD_IOC_SMI_EVENTS);
|
|
|
|
namespace
|
|
{
|
|
template <typename T>
|
|
auto
|
|
ioctl(int kfd_fd, T& args)
|
|
{
|
|
// from hsaKmt library (hsakmt/src/libhsakmt.c)
|
|
int exit_code{};
|
|
|
|
do
|
|
{
|
|
exit_code = ::ioctl(kfd_fd, IOC_event<T>::value, static_cast<void*>(&args));
|
|
} while(exit_code == -1 && (errno == EINTR || errno == EAGAIN));
|
|
|
|
if(exit_code == -1 && errno == EBADF)
|
|
{
|
|
/* In case pthread_atfork didn't catch it, this will
|
|
* make any subsequent hsaKmt calls fail in CHECK_KFD_OPEN.
|
|
*/
|
|
CHECK(true && "KFD file descriptor not valid in this process\n");
|
|
}
|
|
return exit_code;
|
|
}
|
|
|
|
struct kfd_device_fd
|
|
{
|
|
fd_t fd{-1};
|
|
|
|
kfd_device_fd()
|
|
{
|
|
fd = ::open(KFD_DEVICE_PATH, O_RDWR | O_CLOEXEC);
|
|
ROCP_FATAL_IF(fd == -1) << "Error opening KFD handle @ " << KFD_DEVICE_PATH;
|
|
}
|
|
|
|
~kfd_device_fd()
|
|
{
|
|
if(fd >= 0) close(fd);
|
|
}
|
|
};
|
|
|
|
const kfd_ioctl_get_version_args
|
|
get_version()
|
|
{
|
|
static kfd_ioctl_get_version_args version = [&]() {
|
|
auto args = kfd_ioctl_get_version_args{0, 0};
|
|
kfd_device_fd kfd_fd{};
|
|
|
|
if(ioctl(kfd_fd.fd, args) != -1)
|
|
ROCP_INFO << fmt::format("KFD v{}.{}", args.major_version, args.minor_version);
|
|
else
|
|
ROCP_ERROR << fmt::format("Could not determine KFD version");
|
|
return args;
|
|
}();
|
|
|
|
return version;
|
|
}
|
|
|
|
struct poll_kfd_t
|
|
{
|
|
static constexpr auto DEFAULT_FLAGS{EFD_CLOEXEC};
|
|
|
|
struct gpu_fd_t
|
|
{
|
|
unsigned int node_id = 0;
|
|
fd_t fd = {};
|
|
const rocprofiler_agent_t* agent = nullptr;
|
|
};
|
|
|
|
kfd_device_fd kfd_fd = {};
|
|
small_vector<pollfd> file_handles = {};
|
|
pollfd thread_notify = {};
|
|
std::thread bg_thread = {};
|
|
bool active = {false};
|
|
|
|
poll_kfd_t() = default;
|
|
|
|
poll_kfd_t(const small_vector<size_t>& rprof_ev)
|
|
: kfd_fd{kfd_device_fd{}}
|
|
{
|
|
const auto kfd_flags =
|
|
kfd_bitmask(rprof_ev, std::make_index_sequence<ROCPROFILER_PAGE_MIGRATION_LAST>{});
|
|
|
|
ROCP_TRACE << fmt::format("Setting KFD flags to [0b{:b}] \n", kfd_flags);
|
|
|
|
// Create fd for notifying thread when we want to wake it up, and an eventfd for any events
|
|
// to this thread
|
|
file_handles.emplace_back(
|
|
pollfd{.fd = eventfd(0, DEFAULT_FLAGS), .events = 0, .revents = 0});
|
|
|
|
fd_t thread_pipes[2]{};
|
|
|
|
[&]() {
|
|
const auto retcode = pipe2(&thread_pipes[0], DEFAULT_FLAGS);
|
|
|
|
if(retcode != 0)
|
|
throw std::runtime_error{
|
|
fmt::format("Pipe creation for thread notify failed with {} code\n", retcode)};
|
|
}();
|
|
|
|
thread_notify = pollfd{
|
|
.fd = thread_pipes[1],
|
|
.events = POLLIN,
|
|
.revents = 0,
|
|
};
|
|
|
|
// add pipe listening end to fds to watch
|
|
file_handles.emplace_back(pollfd{thread_pipes[0], POLLIN, 0});
|
|
|
|
// get FD, start thread, and then enable events
|
|
for(const auto& agent : agent::get_agents())
|
|
{
|
|
if(agent->type == ROCPROFILER_AGENT_TYPE_GPU)
|
|
{
|
|
auto gpu_event_fd = get_node_fd(agent->gpu_id);
|
|
file_handles.emplace_back(pollfd{gpu_event_fd, POLLIN, 0});
|
|
ROCP_TRACE << fmt::format(
|
|
"GPU node {} with fd {} added\n", agent->gpu_id, gpu_event_fd);
|
|
}
|
|
}
|
|
|
|
// Enable KFD masked events by writing flags to kfd fd
|
|
for(size_t i = 2; i < file_handles.size(); ++i)
|
|
{
|
|
auto& fd = file_handles[i];
|
|
auto write_size = write(fd.fd, &kfd_flags, sizeof(kfd_flags));
|
|
ROCP_TRACE << fmt::format(
|
|
"Writing {} to GPU fd {} ({} bytes)\n", kfd_flags, fd.fd, write_size);
|
|
CHECK(write_size == sizeof(kfd_flags));
|
|
}
|
|
|
|
// start bg thread
|
|
internal_threading::notify_pre_internal_thread_create(ROCPROFILER_LIBRARY);
|
|
bg_thread = std::thread{poll_events, file_handles};
|
|
internal_threading::notify_post_internal_thread_create(ROCPROFILER_LIBRARY);
|
|
|
|
active = true;
|
|
}
|
|
|
|
static auto get_event_id(const std::string_view& strn)
|
|
{
|
|
uint32_t event_id{std::numeric_limits<uint32_t>::max()};
|
|
std::sscanf(strn.data(), "%x ", &event_id);
|
|
|
|
CHECK(event_id <= KFD_SMI_EVENT_ALL_PROCESS);
|
|
}
|
|
|
|
poll_kfd_t(const poll_kfd_t&) = delete;
|
|
poll_kfd_t& operator=(const poll_kfd_t&) = delete;
|
|
|
|
poll_kfd_t(poll_kfd_t&&) = default;
|
|
poll_kfd_t& operator=(poll_kfd_t&&) = default;
|
|
|
|
~poll_kfd_t();
|
|
|
|
node_fd_t get_node_fd(int gpu_node_id) const
|
|
{
|
|
kfd_ioctl_smi_events_args args{};
|
|
args.gpuid = gpu_node_id;
|
|
|
|
if(auto ret = ioctl(kfd_fd.fd, args); ret == -1)
|
|
ROCP_ERROR << fmt::format(
|
|
"Could not get GPU node {} file descriptor (exit code: {})", gpu_node_id, ret);
|
|
return args.anon_fd;
|
|
}
|
|
};
|
|
|
|
// for all contexts
|
|
struct page_migration_config
|
|
{
|
|
bool should_exit() const { return m_should_exit.load(); }
|
|
void set_exit(bool val) { m_should_exit.store(val); }
|
|
|
|
uint64_t enabled_events = 0;
|
|
kfd::poll_kfd_t* kfd_handle = nullptr;
|
|
|
|
private:
|
|
std::atomic<bool> m_should_exit = false;
|
|
};
|
|
|
|
page_migration_config&
|
|
get_config()
|
|
{
|
|
static auto& state = *common::static_object<page_migration_config>::construct();
|
|
return state;
|
|
}
|
|
|
|
kfd::poll_kfd_t::~poll_kfd_t()
|
|
{
|
|
ROCP_TRACE << fmt::format("Terminating poll_kfd\n");
|
|
if(!active) return;
|
|
|
|
// wake thread up
|
|
kfd::get_config().set_exit(true);
|
|
auto bytes_written{-1};
|
|
do
|
|
{
|
|
bytes_written = write(thread_notify.fd, "E", 1);
|
|
} while(bytes_written == -1 && (errno == EINTR || errno == EAGAIN));
|
|
|
|
if(bg_thread.joinable()) bg_thread.join();
|
|
ROCP_TRACE << fmt::format("Background thread terminated\n");
|
|
|
|
for(const auto& f : file_handles)
|
|
close(f.fd);
|
|
}
|
|
} // namespace
|
|
|
|
void
|
|
poll_events(small_vector<pollfd> file_handles)
|
|
{
|
|
// storage to write records to, 1MB
|
|
constexpr size_t PREALLOCATE_ELEMENT_COUNT{1024 * 128};
|
|
std::string scratch_buffer(PREALLOCATE_ELEMENT_COUNT, '\0');
|
|
auto& exitfd = file_handles[1];
|
|
|
|
// Wait or spin on events.
|
|
// 0 -> return immediately even if no events
|
|
// -1 -> wait indefinitely
|
|
|
|
pthread_setname_np(pthread_self(), "bg:pagemigr");
|
|
|
|
for(auto& fd : file_handles)
|
|
{
|
|
ROCP_TRACE << fmt::format(
|
|
"Handle = {}, events = {}, revents = {}\n", fd.fd, fd.events, fd.revents);
|
|
}
|
|
|
|
while(!kfd::get_config().should_exit())
|
|
{
|
|
auto poll_ret = poll(file_handles.data(), file_handles.size(), -1);
|
|
|
|
if(poll_ret == -1)
|
|
throw std::runtime_error{"Background thread file descriptors are invalid"};
|
|
|
|
if((exitfd.revents & POLLIN) != 0)
|
|
{
|
|
ROCP_INFO << "Terminating background thread\n";
|
|
return;
|
|
}
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
// 0 and 1 are for generic and pipe-notify handles
|
|
for(size_t i = 2; i < file_handles.size(); ++i)
|
|
{
|
|
auto& fd = file_handles[i];
|
|
|
|
// We have data to read, perhaps multiple events
|
|
if((fd.revents & POLLIN) != 0)
|
|
{
|
|
size_t status_size = read(fd.fd, scratch_buffer.data(), scratch_buffer.size());
|
|
auto event_strings = std::string_view{scratch_buffer.data(), status_size};
|
|
kfd_readlines(event_strings, handle_reporting);
|
|
}
|
|
fd.revents = 0;
|
|
}
|
|
}
|
|
}
|
|
} // namespace kfd
|
|
|
|
template <size_t Idx, size_t... IdxTail>
|
|
const char*
|
|
name_by_id(const uint32_t id, std::index_sequence<Idx, IdxTail...>)
|
|
{
|
|
if(Idx == id) return page_migration_info<Idx>::name;
|
|
if constexpr(sizeof...(IdxTail) > 0)
|
|
return name_by_id(id, std::index_sequence<IdxTail...>{});
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
template <size_t... Idx>
|
|
void
|
|
get_ids(std::vector<uint32_t>& _id_list, std::index_sequence<Idx...>)
|
|
{
|
|
auto _emplace = [](auto& _vec, uint32_t _v) {
|
|
if(_v < static_cast<uint32_t>(ROCPROFILER_HSA_AMD_EXT_API_ID_LAST)) _vec.emplace_back(_v);
|
|
};
|
|
|
|
(_emplace(_id_list, page_migration_info<Idx>::operation), ...);
|
|
}
|
|
|
|
bool
|
|
context_filter(const context::context* ctx)
|
|
{
|
|
return (ctx->buffered_tracer &&
|
|
(ctx->buffered_tracer->domains(ROCPROFILER_BUFFER_TRACING_PAGE_MIGRATION)));
|
|
}
|
|
|
|
template <size_t... Idx>
|
|
void
|
|
to_bitmask(small_vector<size_t>& _id_list, std::index_sequence<Idx...>)
|
|
{
|
|
auto _emplace = [](auto& _vec, uint32_t _v) {
|
|
if(_v < static_cast<uint32_t>(ROCPROFILER_HSA_AMD_EXT_API_ID_LAST)) _vec.emplace_back(_v);
|
|
};
|
|
|
|
(_emplace(_id_list, page_migration_info<Idx>::operation), ...);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
rocprofiler_status_t
|
|
init(const small_vector<size_t>& event_ids)
|
|
{
|
|
// Check if version is more than 1.11
|
|
auto ver = kfd::get_version();
|
|
if(ver.major_version * 1000 + ver.minor_version > 1011)
|
|
{
|
|
if(!context::get_registered_contexts(context_filter).empty())
|
|
{
|
|
if(!kfd::get_config().kfd_handle)
|
|
kfd::get_config().kfd_handle = new kfd::poll_kfd_t{event_ids};
|
|
}
|
|
return ROCPROFILER_STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
// Add a buffer record with this info
|
|
ROCP_ERROR << fmt::format(
|
|
"KFD does not support SVM event reporting in v{}.{} (requires v1.11)",
|
|
ver.major_version,
|
|
ver.minor_version);
|
|
return ROCPROFILER_STATUS_ERROR_INCOMPATIBLE_KERNEL;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
rocprofiler_status_t
|
|
init()
|
|
{
|
|
// Testing page migration
|
|
return init({ROCPROFILER_PAGE_MIGRATION_PAGE_MIGRATE_START,
|
|
ROCPROFILER_PAGE_MIGRATION_PAGE_MIGRATE_END,
|
|
ROCPROFILER_PAGE_MIGRATION_PAGE_FAULT_START,
|
|
ROCPROFILER_PAGE_MIGRATION_PAGE_FAULT_END,
|
|
ROCPROFILER_PAGE_MIGRATION_QUEUE_EVICTION,
|
|
ROCPROFILER_PAGE_MIGRATION_QUEUE_RESTORE,
|
|
ROCPROFILER_PAGE_MIGRATION_UNMAP_FROM_GPU});
|
|
}
|
|
|
|
void
|
|
finalize()
|
|
{
|
|
if(kfd::get_config().kfd_handle)
|
|
{
|
|
kfd::poll_kfd_t* _handle = nullptr;
|
|
std::swap(kfd::get_config().kfd_handle, _handle);
|
|
delete _handle;
|
|
}
|
|
}
|
|
|
|
const char*
|
|
name_by_id(uint32_t id)
|
|
{
|
|
return name_by_id(id, std::make_index_sequence<ROCPROFILER_PAGE_MIGRATION_LAST>{});
|
|
}
|
|
|
|
std::vector<uint32_t>
|
|
get_ids()
|
|
{
|
|
auto _data = std::vector<uint32_t>{};
|
|
_data.reserve(ROCPROFILER_PAGE_MIGRATION_LAST);
|
|
get_ids(_data, std::make_index_sequence<ROCPROFILER_PAGE_MIGRATION_LAST>{});
|
|
return _data;
|
|
}
|
|
} // namespace page_migration
|
|
} // namespace rocprofiler
|