//////////////////////////////////////////////////////////////////////////////// // // The University of Illinois/NCSA // Open Source License (NCSA) // // Copyright (c) 2022-2022, Advanced Micro Devices, Inc. All rights reserved. // // Developed by: // // AMD Research and AMD HSA Software Development // // Advanced Micro Devices, Inc. // // www.amd.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal with 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: // // - Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimers. // - Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimers in // the documentation and/or other materials provided with the distribution. // - Neither the names of Advanced Micro Devices, Inc, // nor the names of its contributors may be used to endorse or promote // products derived from this Software without specific prior written // permission. // // 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 CONTRIBUTORS 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 WITH THE SOFTWARE. // //////////////////////////////////////////////////////////////////////////////// #include "core/inc/svm_profiler.h" #include #include #include #include #include "hsakmt/hsakmt.h" #include "core/util/utils.h" #include "core/inc/runtime.h" #include "core/inc/agent.h" #include "core/inc/amd_gpu_agent.h" #include "core/util/os.h" namespace rocr { namespace AMD { static const char* smi_event_string(uint32_t event) { static const char* strings[] = {"NONE", "VMFAULT", "THERMAL_THROTTLE", "GPU_PRE_RESET", "GPU_POST_RESET", "MIGRATE_START", "MIGRATE_END", "PAGE_FAULT_START", "PAGE_FAULT_END", "QUEUE_EVICTION", "QUEUE_RESTORE", "UNMAP_FROM_GPU", "UNKNOWN"}; event = std::min(event, sizeof(strings) / sizeof(char*) - 1); return strings[event]; } static const char* smi_migrate_string(uint32_t trigger) { static const char* strings[] = {"PREFETCH", "PAGEFAULT_GPU", "PAGEFAULT_CPU", "TTM_EVICTION", "UNKNOWN"}; trigger = std::min(trigger, sizeof(strings) / sizeof(char*) - 1); return strings[trigger]; } static const char* smi_eviction_string(uint32_t trigger) { static const char* strings[] = {"SVM", "USERPTR", "TTM", "SUSPEND", "CRIU_CHECKPOINT", "CRIU_RESTORE", "UNKNOWN"}; trigger = std::min(trigger, sizeof(strings) / sizeof(char*) - 1); return strings[trigger]; } static const char* smi_unmap_string(uint32_t trigger) { static const char* strings[] = {"MMU_NOTIFY", "MMU_NOTIFY_MIGRATE", "UNMAP_FROM_CPU", "UNKNOWN"}; trigger = std::min(trigger, sizeof(strings) / sizeof(char*) - 1); return strings[trigger]; } void SvmProfileControl::PollSmiRun(void* _profileControl) { SvmProfileControl* profileControl = (SvmProfileControl*)_profileControl; profileControl->PollSmi(); } void SvmProfileControl::PollSmi() { if (core::Runtime::runtime_singleton_->flag().svm_profile().empty()) { return; } FILE* logFile = fopen(core::Runtime::runtime_singleton_->flag().svm_profile().c_str(), "a"); if (logFile == NULL) { return; } MAKE_NAMED_SCOPE_GUARD(logGuard, [&]() { fclose(logFile); }); std::vector files; files.resize(core::Runtime::runtime_singleton_->gpu_agents().size() + 1); files[0].fd = event; files[0].events = POLLIN; files[0].revents = 0; HSAuint64 events = 0; events = HSA_SMI_EVENT_MASK_FROM_INDEX(HSA_SMI_EVENT_MIGRATE_START) | HSA_SMI_EVENT_MASK_FROM_INDEX(HSA_SMI_EVENT_MIGRATE_END) | HSA_SMI_EVENT_MASK_FROM_INDEX(HSA_SMI_EVENT_PAGE_FAULT_START) | HSA_SMI_EVENT_MASK_FROM_INDEX(HSA_SMI_EVENT_PAGE_FAULT_END) | HSA_SMI_EVENT_MASK_FROM_INDEX(HSA_SMI_EVENT_QUEUE_EVICTION) | HSA_SMI_EVENT_MASK_FROM_INDEX(HSA_SMI_EVENT_QUEUE_RESTORE) | HSA_SMI_EVENT_MASK_FROM_INDEX(HSA_SMI_EVENT_UNMAP_FROM_GPU); for (int i = 0; i < core::Runtime::runtime_singleton_->gpu_agents().size(); i++) { auto err = hsaKmtOpenSMI(core::Runtime::runtime_singleton_->gpu_agents()[i]->node_id(), &files[i + 1].fd); assert(err == HSAKMT_STATUS_SUCCESS); files[i + 1].events = POLLIN; files[i + 1].revents = 0; // Enable collecting masked events. auto wrote = write(files[i + 1].fd, &events, sizeof(events)); assert(wrote == sizeof(events)); } MAKE_NAMED_SCOPE_GUARD(smiGuard, [&]() { for (int i = 1; i < files.size(); i++) { close(files[i].fd); } }); std::vector smi_records; smi_records.resize(core::Runtime::runtime_singleton_->gpu_agents().size() + 1); char buffer[HSA_SMI_EVENT_MSG_SIZE + 1]; auto format_agent = [this](uint32_t gpuid) { std::string ret; core::Agent* agent = core::Runtime::runtime_singleton_->agent_by_gpuid(gpuid); if (agent->device_type() == core::Agent::kAmdCpuDevice) return std::string("CPU"); else return format("GPU%u(%p)", ((AMD::GpuAgent*)agent)->enumeration_index(), agent->public_handle()); }; while (!exit) { int ready = poll(&files[0], files.size(), -1); if (ready < 1) { assert(false && "poll failed!"); return; } for (int i = 1; i < files.size(); i++) { if (files[i].revents & POLLIN) { memset(buffer, 0, sizeof(buffer)); auto len = read(files[i].fd, buffer, sizeof(buffer) - 1); if (len > 0) { buffer[len] = '\0'; // printf("%s\n", buffer); // fprintf(logFile, "%s\n", buffer); smi_records[i] += buffer; while (true) { size_t pos = smi_records[i].find('\n'); if (pos == std::string::npos) break; std::string line = smi_records[i].substr(0, pos); smi_records[i].erase(0, pos + 1); const char* cursor; cursor = line.c_str(); // Event records follow the format: // event_id timestamp -pid event_specific_info trigger // timestamp, pid, and trigger are in dec. All other are hex. // event_specific substring is listed for each event type. // See kfd_ioctl.h for more info. int event_id; uint64_t time; int pid; int offset = 0; int args = sscanf(cursor, "%x %lu -%u%n", &event_id, &time, &pid, &offset); assert(args == 3 && "Parsing error!"); std::string detail; cursor += offset + 1; switch (event_id) { //@addr(size) from->to prefetch_location:preferred_location case HSA_SMI_EVENT_MIGRATE_START: { uint64_t addr; uint32_t size; uint32_t from, to; uint32_t trigger = 0; uint32_t fetch, pref; args = sscanf(cursor, "@%lx(%x) %x->%x %x:%x %u", &addr, &size, &from, &to, &fetch, &pref, &trigger); assert(args == 7 && "Parsing error!"); addr *= 4096; size *= 4096; std::string from_agent = format_agent(from); std::string to_agent = format_agent(to); std::string range = format("[%p, %p]", addr, addr + size - 1); std::string cause = smi_migrate_string(trigger); detail = cause + " " + from_agent + "->" + to_agent + " " + range; break; } //@addr(size) from->to case HSA_SMI_EVENT_MIGRATE_END: { uint64_t addr; uint32_t size; uint32_t from, to; uint32_t trigger; args = sscanf(cursor, "@%lx(%x) %x->%x %u", &addr, &size, &from, &to, &trigger); assert(args == 5 && "Parsing error!"); addr *= 4096; size *= 4096; std::string from_agent = format_agent(from); std::string to_agent = format_agent(to); std::string range = format("[%p, %p]", addr, addr + size - 1); std::string cause = smi_migrate_string(trigger); detail = cause + " " + from_agent + "->" + to_agent + " " + range; break; } //@addr(gpu_id) W/R case HSA_SMI_EVENT_PAGE_FAULT_START: { uint64_t addr; uint32_t gpuid; char mode; args = sscanf(cursor, "@%lx(%x) %c", &addr, &gpuid, &mode); addr *= 4096; assert(args == 3 && "Parsing error!"); std::string agent = format_agent(gpuid); std::string range = std::to_string(addr); std::string cause = (mode == 'W') ? "Write" : "Read"; detail = cause + " " + agent + " " + range; break; } //@addr(gpu_id) M/U (migration / page table update) case HSA_SMI_EVENT_PAGE_FAULT_END: { uint64_t addr; uint32_t gpuid; char mode; args = sscanf(cursor, "@%lx(%x) %c", &addr, &gpuid, &mode); assert(args == 3 && "Parsing error!"); addr *= 4096; std::string agent = format_agent(gpuid); std::string range = std::to_string(addr); std::string cause = (mode == 'M') ? "Migration" : "Map"; detail = cause + " " + agent + " " + range; break; } // gpu_id case HSA_SMI_EVENT_QUEUE_EVICTION: { uint32_t gpuid; uint32_t trigger; args = sscanf(cursor, "%x %u", &gpuid, &trigger); assert(args == 2 && "Parsing error!"); std::string agent = format_agent(gpuid); std::string cause = smi_eviction_string(trigger); detail = cause + " " + agent; break; } // gpu_id case HSA_SMI_EVENT_QUEUE_RESTORE: { uint32_t gpuid; uint32_t trigger; args = sscanf(cursor, "%x %u", &gpuid, &trigger); assert(args == 2 && "Parsing error!"); std::string agent = format_agent(gpuid); std::string cause = smi_eviction_string(trigger); detail = cause + " " + agent; break; } //@addr(size) gpu_id case HSA_SMI_EVENT_UNMAP_FROM_GPU: { uint64_t addr; uint32_t size; uint32_t gpuid; uint32_t trigger; args = sscanf(cursor, "@%lx(%x) %x %u", &addr, &size, &gpuid, &trigger); assert(args == 4 && "Parsing error!"); addr *= 4096; size *= 4096; std::string gpu = format_agent(gpuid); std::string range = format("[%p, %p]", addr, addr + size - 1); std::string cause = smi_unmap_string(trigger); detail = cause + " " + gpu + " " + range; break; } default:; } std::string record = std::string("ROCr HMM event: ") + std::to_string(time) + " " + smi_event_string(event_id) + " " + detail; // printf("%s\n", record.c_str()); fprintf(logFile, "%s\n", record.c_str()); } } else { auto err = errno; const char* msg = strerror(err); // printf("ROCr HMM event error: Read returned %ld, %s (%d)\n", len, msg, err); fprintf(logFile, "ROCr HMM event error: Read returned %ld, %s (%d)\n", len, msg, err); } files[i].revents = 0; } } if (files[0].revents & POLLIN) return; } } SvmProfileControl::SvmProfileControl() : event(-1), exit(false) { event = eventfd(0, EFD_CLOEXEC); if (event == -1) return; poll_smi_thread_ = os::CreateThread(PollSmiRun, (void*)this); if (poll_smi_thread_ == NULL) { assert(false && "Poll SMI thread creation error."); return; } } SvmProfileControl::~SvmProfileControl() { if (event != -1) eventfd_write(event, 1); if (poll_smi_thread_ != NULL) { exit = true; os::WaitForThread(poll_smi_thread_); os::CloseThread(poll_smi_thread_); poll_smi_thread_ = NULL; } close(event); } template std::string SvmProfileControl::format(const char* format, Args... args) { int len = snprintf(&format_buffer[0], format_buffer.size(), format, args...); if (len + 1 > format_buffer.size()) { format_buffer.resize(len + 1); snprintf(&format_buffer[0], format_buffer.size(), format, args...); } return std::string(&format_buffer[0]); } } // namespace AMD } // namespace rocr