Files
rocm-systems/source/lib/rocprofiler-sdk/page_migration/page_migration.cpp
T
Kuricheti, Mythreya e7d45624d0 Fix page-migration background thread on fork (#31)
* Fix page-migration background thread on fork

After falling off main in the forked child, all the children
try to join on on the parent's monitoring thread. This results
in a deadlock. Parent is waiting for the child to exit, but
the child is trying to join the parent's thread which is
signaled from the parent's static destructors.

Even with just one parent and child, due to copy-on-write
semantics, a child signalling the background thread to join
will still block (thread's updated state is not visible
in the child).

This fix creates background treads on fork per-child with a
pthread_atfork handler, ensuring that each child has its own
monitoring thread.

* Formatting fixes

* Detach page-migration background thread and update test timeout

* Attach files with ctest

* Update corr-id assert

* Tweak on-fork, simplify background thread

* Revert thread detach
2024-12-05 19:58:38 -06:00

931 строка
27 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;
int32_t _error_code = 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,
&_error_code);
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);
// For older kernel versions, no event is generated in the error case,
// so the default value of 0 is correct for any given end event.
e.error_code = _error_code;
ROCP_TRACE << fmt::format("Page migrate end [ ts: {} pid: {} addr s: 0x{:X} addr e: "
"0x{:X} from node: {} to node: {} trigger: {} error code: {}] \n",
rec.timestamp,
rec.pid,
e.start_addr,
e.end_addr,
e.from_agent.handle,
e.to_agent.handle,
trigger,
_error_code);
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_DROPPED_EVENT>(std::string_view str)
{
auto rec = page_migration_record_t{};
auto& e = rec.args.dropped_event;
uint32_t kind{};
std::sscanf(str.data(),
page_migration_info<ROCPROFILER_PAGE_MIGRATION_DROPPED_EVENT>::format_str.data(),
&kind,
&rec.timestamp,
&rec.pid,
&e.dropped_events_count);
ROCP_TRACE << fmt::format("Dropped events [ ts: {} pid: {} dropped count: {} ] \n",
rec.timestamp,
rec.pid,
e.dropped_events_count);
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
namespace
{
void poll_events(small_vector<pollfd>);
}
// KFD utils
namespace kfd
{
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 = {};
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{}}
{
small_vector<pollfd> file_handles = {};
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()
{
ROCP_TRACE << fmt::format("Terminating poll_kfd\n");
if(!active) return;
// wake thread up
auto bytes_written{-1};
do
{
bytes_written = write(thread_notify.fd, "E", 1);
} while(bytes_written == -1 && (errno == EINTR || errno == EAGAIN));
bg_thread.join();
close(thread_notify.fd);
ROCP_TRACE << fmt::format("Background thread signalled\n");
}
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;
}
};
} // namespace
} // namespace kfd
// for all contexts
struct config
{
private:
kfd::poll_kfd_t kfd_handle{};
static inline config* _config{nullptr};
config(const small_vector<size_t>& _event_ids)
: kfd_handle{_event_ids}
{}
public:
static void init(const small_vector<size_t>& event_ids) { _config = new config{event_ids}; }
static void reset()
{
config* ptr = nullptr;
std::swap(ptr, _config);
delete ptr;
}
static void reset_on_fork() { _config = nullptr; }
};
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(true)
{
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)
{
for(const auto& f : file_handles)
{
close(f.fd);
}
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;
}
}
}
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), ...);
}
template <size_t... Inxs>
rocprofiler_status_t init(std::index_sequence<Inxs...>)
{
static const small_vector<size_t> event_ids{Inxs...};
// 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())
{
config::init(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
} // namespace page_migration
} // namespace rocprofiler
namespace rocprofiler::page_migration
{
rocprofiler_status_t
init()
{
pthread_atfork(nullptr, nullptr, []() {
// null out child's copy on fork and reinitialize
// otherwise all children wait on the same thread to join
config::reset_on_fork();
init(std::make_index_sequence<ROCPROFILER_PAGE_MIGRATION_LAST>{});
});
return init(std::make_index_sequence<ROCPROFILER_PAGE_MIGRATION_LAST>{});
}
void
finalize()
{
config::reset();
}
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 rocprofiler::page_migration