/*
 * Copyright (c) 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 "rocm_smi/rocm_smi_kfd.h"

#include <dirent.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>

#include <algorithm>
#include <atomic>
#include <cassert>
#include <cctype>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <limits>
#include <mutex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "rocm_smi/rocm_smi.h"
#include "rocm_smi/rocm_smi_exception.h"
#include "rocm_smi/rocm_smi_io_link.h"
#include "rocm_smi/rocm_smi_kfd_data_manager.h"
#include "rocm_smi/rocm_smi_logger.h"
#include "rocm_smi/rocm_smi_main.h"
#include "rocm_smi/rocm_smi_utils.h"

namespace amd::smi {

static bool is_number(const std::string& s);
static const char* kKFDProcPathRoot = "/sys/class/kfd/kfd/proc";
static const char* kKFDNodesPathRoot = "/sys/class/kfd/kfd/topology/nodes";
static const char* kKFDVramPrefix = "vram_";

// Tri-state result of inspecting a PID's open file descriptors for /dev/kfd.
// kInaccessible means we could not read /proc/<pid>/fd at all (the process
// belongs to another user, or exited) — the outcome is unknown, not negative.
enum class KfdOpenState { kHasKfd, kNoKfd, kInaccessible };

// Inspect a PID's fd links to determine whether it has /dev/kfd open.
static KfdOpenState PidKfdOpenState(const std::string& pid_str) {
  std::string fd_dir_path = "/proc/" + pid_str + "/fd";
  DIR* fd_dir = opendir(fd_dir_path.c_str());
  // EACCES/EPERM (another user's process) or ENOENT (process exited) leave the
  // answer undetermined; callers must not treat this as "no KFD open".
  if (!fd_dir) return KfdOpenState::kInaccessible;

  bool found = false;
  struct dirent* fd_entry;
  while ((fd_entry = readdir(fd_dir)) != nullptr) {
    if (fd_entry->d_name[0] == '.') continue;
    std::string fd_link = fd_dir_path + "/" + fd_entry->d_name;
    char target[PATH_MAX];
    ssize_t len = readlink(fd_link.c_str(), target, sizeof(target) - 1);
    if (len > 0) {
      target[len] = '\0';
      if (strcmp(target, "/dev/kfd") == 0) {
        found = true;
        break;
      }
    }
  }
  closedir(fd_dir);
  return found ? KfdOpenState::kHasKfd : KfdOpenState::kNoKfd;
}

// Check whether a given PID definitively has /dev/kfd open.
static bool PidHasKfdOpen(const std::string& pid_str) {
  return PidKfdOpenState(pid_str) == KfdOpenState::kHasKfd;
}

// Detect whether KFD sysfs PIDs are in a different PID namespace from ours.
// When running inside a container with PID namespace isolation, KFD sysfs
// reports host PIDs that are not visible in the container's /proc. We detect
// this by checking numeric entries under kKFDProcPathRoot against /proc.
// For each numeric KFD PID: if it exists in /proc and has /dev/kfd open we
// share its namespace; if it exists but lacks /dev/kfd, a container-local PID
// reused the number (namespaced). A PID that is gone (exited) or whose fd dir
// is unreadable (another user's process) is inconclusive and skipped, so a
// permission error is never mistaken for namespace isolation.
// If all KFD entries are inconclusive, we conservatively assume we are not
// namespaced (the KFD entries will be cleaned up shortly anyway).
// Result is cached for the lifetime of the process; PID namespace is assumed stable.
static bool IsKfdPidNamespaced() {
  static std::atomic<int> cached{-1};
  int val = cached.load(std::memory_order_acquire);
  if (val >= 0) return val;

  DIR* kfd_dir = opendir(kKFDProcPathRoot);
  if (!kfd_dir) {
    cached.store(0, std::memory_order_release);
    return false;
  }

  bool namespaced = false;
  bool determined = false;
  struct dirent* de;
  while ((de = readdir(kfd_dir)) != nullptr) {
    std::string name(de->d_name);
    if (!is_number(name)) continue;

    std::string proc_path = "/proc/" + name;
    struct stat st;
    if (stat(proc_path.c_str(), &st) != 0) {
      // PID not in /proc — could be a short-lived process that already exited.
      // Skip to the next KFD entry to avoid a false positive race condition.
      continue;
    }
    // PID exists in /proc; check whether the same process has /dev/kfd open.
    KfdOpenState state = PidKfdOpenState(name);
    if (state == KfdOpenState::kInaccessible) {
      // /proc/<pid>/fd unreadable (another user's process, e.g. a root-owned
      // GPU job seen by a non-root caller). Cannot determine namespace from
      // this entry, so skip it rather than falsely flagging namespaced.
      continue;
    }
    // Absence of /dev/kfd here means a container-local process reused this host
    // PID number, i.e. a different namespace.
    if (state == KfdOpenState::kNoKfd) {
      namespaced = true;
    }
    determined = true;
    break;
  }
  closedir(kfd_dir);

  // If every KFD entry was inconclusive (exited or unreadable), conservatively
  // assume we are not namespaced — the stale KFD entries will be reaped soon.
  if (!determined) {
    namespaced = false;
  }

  cached.store(namespaced ? 1 : 0, std::memory_order_release);
  return namespaced;
}

// Enumerate container-local PIDs that have /dev/kfd open by scanning /proc.
// Used as a fallback when KFD sysfs PIDs are not visible in this namespace.
static int ScanProcForKfdPids(rsmi_process_info_t* procs, uint32_t num_allocated,
                              uint32_t* num_found) {
  *num_found = 0;

  DIR* proc_dir = opendir("/proc");
  if (!proc_dir) return errno;

  const pid_t self = getpid();
  struct dirent* dentry;

  while ((dentry = readdir(proc_dir)) != nullptr) {
    std::string pid_str(dentry->d_name);
    if (!is_number(pid_str)) continue;

    uint32_t pid = static_cast<uint32_t>(strtoul(pid_str.c_str(), nullptr, 10));
    if (pid == static_cast<uint32_t>(self)) continue;

    if (PidHasKfdOpen(pid_str)) {
      if (procs && *num_found < num_allocated) {
        procs[*num_found] = {};
        procs[*num_found].process_id = pid;
      }
      ++(*num_found);
    }
  }

  closedir(proc_dir);
  return 0;
}

// Collect GPU IDs from KFD vram_* files for any host-PID KFD entry.
// Used as a namespace fallback when container-local PIDs have no KFD sysfs entry.
// NOTE: Uses the first host-PID entry found; assumes all container processes
// share the same GPU set (valid for typical single-container deployments).
static void CollectGpuIdsFromKfdVram(std::unordered_set<uint64_t>* gpu_set) {
  DIR* kfd_proc_dir = opendir(kKFDProcPathRoot);
  if (!kfd_proc_dir) return;

  struct dirent* de;
  while ((de = readdir(kfd_proc_dir)) != nullptr) {
    std::string entry(de->d_name);
    if (!is_number(entry)) continue;

    std::string host_proc = std::string(kKFDProcPathRoot) + "/" + entry;
    DIR* pd = opendir(host_proc.c_str());
    if (!pd) continue;

    struct dirent* pe;
    while ((pe = readdir(pd)) != nullptr) {
      std::string fname(pe->d_name);
      if (fname.rfind("vram_", 0) != 0) continue;
      std::string gpu_id_str = fname.substr(strlen(kKFDVramPrefix));
      if (!gpu_id_str.empty() && std::all_of(gpu_id_str.begin(), gpu_id_str.end(),
                                             [](unsigned char ch) { return std::isdigit(ch); })) {
        gpu_set->insert(strtoull(gpu_id_str.c_str(), nullptr, 10));
      }
    }
    closedir(pd);

    if (!gpu_set->empty()) break;
  }
  closedir(kfd_proc_dir);
}

static const char* kKFDContextPrefix = "context_";  // Prefix for secondary KFD contexts

// KFD Node Property strings
// static const char *kKFDNodePropCPU_CORES_COUNTStr =    "cpu_cores_count";
// static const char *kKFDNodePropSIMD_COUNTStr =         "simd_count";
// static const char *kKFDNodePropMEM_BANKS_COUNTStr =    "mem_banks_count";
// static const char *kKFDNodePropCACHES_COUNTStr =       "caches_count";
// static const char *kKFDNodePropIO_LINKS_COUNTStr =     "io_links_count";
// static const char *kKFDNodePropCPU_CORE_ID_BASEStr =   "cpu_core_id_base";
// static const char *kKFDNodePropSIMD_ID_BASEStr =       "simd_id_base";
// static const char *kKFDNodePropMAX_WAVES_PER_SIMDStr = "max_waves_per_simd";
// static const char *kKFDNodePropLDS_SIZE_IN_KBStr =     "lds_size_in_kb";
// static const char *kKFDNodePropGDS_SIZE_IN_KBStr =     "gds_size_in_kb";
// static const char *kKFDNodePropNUM_GWSStr =            "num_gws";
// static const char *kKFDNodePropWAVE_FRONT_SIZEStr =    "wave_front_size";

static const char* kKFDNodePropARRAY_COUNTStr = "array_count";
static const char* kKFDNodePropSIMD_ARRAYS_PER_ENGINEStr = "simd_arrays_per_engine";
static const char* kKFDNodePropCU_PER_SIMD_ARRAYStr = "cu_per_simd_array";
// static const char *kKFDNodePropSIMD_PER_CUStr = "simd_per_cu";
// static const char *kKFDNodePropMAX_SLOTS_SCRATCH_CUStr =
//                                                     "max_slots_scratch_cu";

// static const char *kKFDNodePropVENDOR_IDStr =          "vendor_id";
// static const char *kKFDNodePropDEVICE_IDStr =          "device_id";
static const char* kKFDNodePropLOCATION_IDStr = "location_id";
static const char* kKFDNodePropDOMAINStr = "domain";
// static const char *kKFDNodePropDRM_RENDER_MINORStr =   "drm_render_minor";
static const char* kKFDNodePropHIVE_IDStr = "hive_id";
// static const char *kKFDNodePropNUM_SDMA_ENGINESStr =   "num_sdma_engines";
// static const char *kKFDNodePropNUM_SDMA_XGMI_ENGINESStr =
//                                                   "num_sdma_xgmi_engines";
// static const char *kKFDNodePropNUM_SDMA_QUEUES_PER_ENGINEStr =
//                                              "num_sdma_queues_per_engine";
// static const char *kKFDNodePropNUM_CP_QUEUESStr =      "num_cp_queues";
// static const char *kKFDNodePropMAX_ENGINE_CLK_FCOMPUTEStr =
//                                                 "max_engine_clk_fcompute";
// static const char *kKFDNodePropLOCAL_MEM_SIZEStr =     "local_mem_size";
// static const char *kKFDNodePropFW_VERSIONStr =         "fw_version";
// static const char *kKFDNodePropCAPABILITYStr =         "capability";
// static const char *kKFDNodePropDEBUG_PROPStr =         "debug_prop";
// static const char *kKFDNodePropSDMA_FW_VERSIOStr =     "sdma_fw_versio";
// static const char *kKFDNodePropMAX_ENGINE_CLK_CCOMPUTEStr =
//                                                "max_engine_clk_ccompute";

// KFD process file prefixes for extracting GPU IDs
static const char* kKFDStatsPrefix = "stats_";
static const char* kKFDCountersPrefix = "counters_";
static const char* kKFDSdmaPrefix = "sdma_";

static bool is_number(const std::string& s) {
  return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit);
}

// Helper function to get secondary context directories under a KFD process
// Returns a vector of full paths to context_xxxx directories
// For example: /sys/class/kfd/kfd/proc/1685/context_0
static std::vector<std::string> GetSecondaryContextPaths(const std::string& proc_path) noexcept {
  std::vector<std::string> context_paths;

  DIR* dir = opendir(proc_path.c_str());
  if (!dir) {
    return context_paths;
  }

  struct dirent* entry;
  while ((entry = readdir(dir)) != nullptr) {
    // Skip . and ..
    if (entry->d_name[0] == '.') continue;

    // Check if the entry starts with "context_"
    if (strncmp(entry->d_name, kKFDContextPrefix, strlen(kKFDContextPrefix)) == 0) {
      std::string context_path = proc_path + "/" + entry->d_name;
      // Verify it's a directory
      struct stat st;
      if (stat(context_path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
        context_paths.push_back(context_path);
      }
    }
  }

  closedir(dir);
  return context_paths;
}

static std::string KFDDevicePath(uint32_t dev_id) {
  std::string node_path = kKFDNodesPathRoot;
  node_path += '/';
  node_path += std::to_string(dev_id);
  return node_path;
}

// A generic function to extract out a property from file.
// return empty string if file or property not found
// Assume the property_name is at the beginning of the line.
static std::string get_properties_from_file(const std::string& file_name,
                                            const std::string& property_name) {
  std::ifstream infile(file_name);
  if (!infile) return "";
  std::string line;
  while (std::getline(infile, line)) {
    std::istringstream iss(line);
    // the property name is at the beginning of the line
    if (line.rfind(property_name.c_str(), 0) == 0) {
      return line.substr(property_name.length());
    }
  }
  return "";
}

static int OpenKFDNodeFile(uint32_t dev_id, std::string node_file, std::ifstream* fs) {
  std::string line;
  int ret;
  std::string f_path;
  bool reg_file;

  assert(fs != nullptr);

  f_path = KFDDevicePath(dev_id);
  f_path += "/";
  f_path += node_file;

  ret = isRegularFile(f_path, &reg_file);

  if (ret != 0) {
    return ret;
  }
  if (!reg_file) {
    return ENOENT;
  }

  fs->open(f_path);

  if (!fs->is_open()) {
    return errno;
  }

  return 0;
}

bool KFDNodeSupported(uint32_t node_indx) {
  std::ifstream fs;
  bool ret = true;
  int err;
  err = OpenKFDNodeFile(node_indx, "properties", &fs);

  if (err == ENOENT) {
    return false;
  }
  if (fs.peek() == std::ifstream::traits_type::eof()) {
    ret = false;
  }
  fs.close();
  return ret;
}

int ReadKFDDeviceProperties(uint32_t kfd_node_id, std::vector<std::string>* retVec) {
  std::string line;
  int ret;
  std::ifstream fs;
  std::string properties_path;
  std::ostringstream ss;

  assert(retVec != nullptr);

  ret = OpenKFDNodeFile(kfd_node_id, "properties", &fs);

  if (ret) {
    return ret;
  }

  ss << __PRETTY_FUNCTION__ << " | properties file contains = {";
  while (std::getline(fs, line)) {
    retVec->push_back(line);
    ss << line << ",\n";
  }
  ss << "}";
  // Leaving below to debug any future properties file changes
  // LOG_DEBUG(ss);

  if (retVec->empty()) {
    fs.close();
    return ENOENT;
  }
  // Remove any *trailing* empty (whitespace) lines
  while (!retVec->empty() && retVec->back().find_first_not_of(" \t\n\v\f\r") == std::string::npos) {
    retVec->pop_back();
  }

  fs.close();

  // Return error if vector became empty after removing whitespace-only lines
  if (retVec->empty()) {
    return ENOENT;
  }

  return 0;
}

static int ReadKFDGpuId(uint32_t kfd_node_id, uint64_t* gpu_id) {
  std::string line;
  int ret;
  std::ifstream fs;
  std::string gpu_id_str;

  assert(gpu_id != nullptr);

  ret = OpenKFDNodeFile(kfd_node_id, "gpu_id", &fs);

  if (ret) {
    fs.close();
    return ret;
  }

  std::stringstream ss;
  ss << fs.rdbuf();
  fs.close();

  gpu_id_str = ss.str();

  gpu_id_str.erase(std::remove(gpu_id_str.begin(), gpu_id_str.end(), '\n'), gpu_id_str.end());

  if (!is_number(gpu_id_str)) {
    return ENXIO;
  }

  *gpu_id = std::stoull(gpu_id_str);
  return 0;
}

static int ReadKFDGpuName(uint32_t kfd_node_id, std::string* gpu_name) {
  std::string line;
  int ret;
  std::ifstream fs;

  assert(gpu_name != nullptr);

  ret = OpenKFDNodeFile(kfd_node_id, "name", &fs);

  if (ret) {
    fs.close();
    return ret;
  }

  std::stringstream ss;
  ss << fs.rdbuf();
  fs.close();

  *gpu_name = ss.str();

  gpu_name->erase(std::remove(gpu_name->begin(), gpu_name->end(), '\n'), gpu_name->end());

  return 0;
}

int GetProcessInfo(rsmi_process_info_t* procs, uint32_t num_allocated, uint32_t* num_procs_found) {
  assert(num_procs_found != nullptr);

  *num_procs_found = 0;

  // In a PID namespace, KFD sysfs PIDs are not local; scan /proc instead.
  if (IsKfdPidNamespaced()) {
    return ScanProcForKfdPids(procs, num_allocated, num_procs_found);
  }

  errno = 0;
  auto proc_dir = opendir(kKFDProcPathRoot);

  if (proc_dir == nullptr) {
    perror("Unable to open process directory");
    return errno;
  }
  auto dentry = readdir(proc_dir);

  std::string proc_id_str;
  std::string tmp;
  // Keep track of PIDs we've already seen to avoid duplicates
  // (e.g., if both "1234" and "pid:1234-id:1" exist)
  std::unordered_set<uint32_t> seen_pids;

  while (dentry != nullptr) {
    if (dentry->d_name[0] == '.') {
      dentry = readdir(proc_dir);
      continue;
    }

    proc_id_str = dentry->d_name;

    // Check if the entry is a plain number (traditional format)
    if (is_number(proc_id_str)) {
      uint32_t pid = static_cast<uint32_t>(std::stoul(proc_id_str));
      if (seen_pids.find(pid) == seen_pids.end()) {
        seen_pids.insert(pid);
        if (procs && *num_procs_found < num_allocated) {
          procs[*num_procs_found].process_id = pid;
        }
        ++(*num_procs_found);
      }
    }
    // Check for "pid:XXXX-id:X" format (alternative format for multi-context processes)
    else if (proc_id_str.find("pid:") == 0) {
      // Extract PID from "pid:XXXX-id:X" format
      size_t dash_pos = proc_id_str.find('-');
      if (dash_pos != std::string::npos) {
        std::string pid_part =
            proc_id_str.substr(4, dash_pos - 4);  // Extract XXXX from "pid:XXXX-id:X"
        if (is_number(pid_part)) {
          uint32_t pid = static_cast<uint32_t>(std::stoul(pid_part));
          if (seen_pids.find(pid) == seen_pids.end()) {
            seen_pids.insert(pid);
            if (procs && *num_procs_found < num_allocated) {
              procs[*num_procs_found].process_id = pid;
            }
            ++(*num_procs_found);
          }
        }
      }
    } else {
      // Skip unexpected entries that don't match known formats
      // (e.g., non-numeric, non-pid: format files/directories)
      dentry = readdir(proc_dir);
      continue;
    }

    dentry = readdir(proc_dir);
  }

  errno = 0;
  if (closedir(proc_dir)) {
    return errno;
  }
  return 0;
}

// Return the KFD proc-root "pid:<pid>-id:<n>" alternate-context dirs for `pid`
// (a rarely-used layout for multi-context processes), as full paths.
//
// Scanning the proc root per PID makes a sweep over P processes O(P^2), so the
// scan is cached process-wide and rebuilt when older than kTtl; a lookup can
// therefore lag a just-created context by up to kTtl, which is fine for a
// monitoring read. kTtl is a fixed internal constant on purpose: raising it
// hides running processes for longer, lowering it restores the O(P^2) scan.
static std::vector<std::string> KfdAltContextRootDirsForPid(long pid) {
  static std::mutex mtx;
  static std::unordered_map<long, std::vector<std::string>> index;
  static std::chrono::steady_clock::time_point built{};
  static bool valid = false;
  constexpr std::chrono::milliseconds kTtl{500};

  std::lock_guard<std::mutex> lock(mtx);
  auto now = std::chrono::steady_clock::now();
  if (!valid || (now - built) > kTtl) {
    index.clear();
    DIR* proc_root = opendir(kKFDProcPathRoot);
    if (proc_root) {
      struct dirent* entry;
      while ((entry = readdir(proc_root)) != nullptr) {
        if (entry->d_name[0] == '.') continue;
        // Match exactly "pid:<owner>-id:<n>" and bucket the full path by <owner>.
        if (strncmp(entry->d_name, "pid:", 4) != 0) continue;
        char* end = nullptr;
        long owner = strtol(entry->d_name + 4, &end, 10);
        if (end == entry->d_name + 4) continue;      // no digits after "pid:"
        if (strncmp(end, "-id:", 4) != 0) continue;  // require "pid:<owner>-id:"
        index[owner].push_back(std::string(kKFDProcPathRoot) + "/" + entry->d_name);
      }
      closedir(proc_root);
    }
    built = now;
    valid = true;
  }
  auto it = index.find(pid);
  if (it == index.end()) return {};
  return it->second;
}

int GetKfdGpuIdsForPid(long pid, std::unordered_set<uint64_t>* out) {
  if (!out) return EINVAL;
  out->clear();

  std::string pdir = std::string(kKFDProcPathRoot) + "/" + std::to_string(pid);

  // Helper lambda to extract GPU IDs from files in a directory
  auto extract_gpu_ids_from_dir = [&out](const std::string& dir_path) {
    DIR* d = opendir(dir_path.c_str());
    if (!d) return;

    struct dirent* e;
    while ((e = readdir(d))) {
      if (e->d_name[0] == '.') continue;  // skip "."/".." and hidden entries

      // Grab KFD GPU id from one of these fields
      if (!strncmp(e->d_name, kKFDStatsPrefix, strlen(kKFDStatsPrefix))) {
        out->insert(strtoull(e->d_name + strlen(kKFDStatsPrefix), nullptr, 10));
      } else if (!strncmp(e->d_name, kKFDVramPrefix, strlen(kKFDVramPrefix))) {
        out->insert(strtoull(e->d_name + strlen(kKFDVramPrefix), nullptr, 10));
      } else if (!strncmp(e->d_name, kKFDCountersPrefix, strlen(kKFDCountersPrefix))) {
        out->insert(strtoull(e->d_name + strlen(kKFDCountersPrefix), nullptr, 10));
      } else if (!strncmp(e->d_name, kKFDSdmaPrefix, strlen(kKFDSdmaPrefix))) {
        out->insert(strtoull(e->d_name + strlen(kKFDSdmaPrefix), nullptr, 10));
      }
    }
    closedir(d);
  };

  DIR* d = opendir(pdir.c_str());

  if (!d) {
    // Return success with empty set so 'GetProcessGPUs()' can use 'vram_*' fallback.
    if (IsKfdPidNamespaced()) {
      return 0;
    }
    perror(("Unable to open KFD process directory for process " + std::to_string(pid)).c_str());
    return errno ? errno : ESRCH;
  }
  closedir(d);

  // Use the lambda for the primary process directory (instead of duplicating code)
  extract_gpu_ids_from_dir(pdir);

  // Also check secondary contexts (context_xxxx directories)
  // These are created by the KFD multiple contexts feature
  std::vector<std::string> context_paths = GetSecondaryContextPaths(pdir);
  for (const auto& context_path : context_paths) {
    extract_gpu_ids_from_dir(context_path);
  }

  // Also check for "pid:PID-id:X" format directories at the parent level
  // (another format used for multi-context processes). Resolved from a cached
  // single scan of the KFD proc root so a sweep over many PIDs does not re-scan
  // the whole root once per PID.
  for (const auto& alternate_path : KfdAltContextRootDirsForPid(pid)) {
    extract_gpu_ids_from_dir(alternate_path);

    // Also check for context_xxxx in this alternate path
    std::vector<std::string> alt_context_paths = GetSecondaryContextPaths(alternate_path);
    for (const auto& alt_context_path : alt_context_paths) {
      extract_gpu_ids_from_dir(alt_context_path);
    }
  }

  return 0;
}

// Read the gpuid files found in all the <queue id> dirs and put them in
// gpus_found.
// Directory structure:
//     /sys/class/kfd/kfd/proc/<pid>/queues/<queue id>/gpuid
//     /sys/class/kfd/kfd/proc/<pid>/context_<id>/queues/<queue id>/gpuid (for secondary contexts)

int GetProcessGPUs(uint32_t pid, std::unordered_set<uint64_t>* gpu_set) {
  int err;

  assert(gpu_set != nullptr);
  if (gpu_set == nullptr) {
    return RSMI_STATUS_INVALID_ARGS;
  }

  std::string proc_path = std::string(kKFDProcPathRoot) + "/" + std::to_string(pid);

  // Helper lambda to read GPU IDs from queues in a given base path
  auto read_gpus_from_queues = [&](const std::string& base_path) -> int {
    std::string queues_dir = base_path + "/queues";
    auto queues_dir_hd = opendir(queues_dir.c_str());

    if (queues_dir_hd == nullptr) {
      // Directory doesn't exist, which is okay for secondary contexts
      return 0;
    }

    auto q_dentry = readdir(queues_dir_hd);
    std::string tmp;

    while (q_dentry != nullptr) {
      if (q_dentry->d_name[0] == '.') {
        q_dentry = readdir(queues_dir_hd);
        continue;
      }

      if (!is_number(q_dentry->d_name)) {
        q_dentry = readdir(queues_dir_hd);
        continue;
      }

      std::string q_gpu_id_str = queues_dir + '/' + q_dentry->d_name + "/gpuid";

      int read_err = ReadSysfsStr(q_gpu_id_str, &tmp);
      if (read_err) {
        q_dentry = readdir(queues_dir_hd);
        continue;
      }

      uint64_t val;
      try {
        val = std::stoull(tmp);
      } catch (...) {
        std::cerr << "Error; read invalid data: " << tmp << " from " << q_gpu_id_str << std::endl;
        closedir(queues_dir_hd);
        return ENXIO;  // Return "no such device" if we read an invalid gpu id
      }
      gpu_set->insert(val);

      q_dentry = readdir(queues_dir_hd);
    }

    closedir(queues_dir_hd);
    return 0;
  };

  // Read from primary process queues
  err = read_gpus_from_queues(proc_path);
  if (err != 0 && err != ESRCH) {
    return err;
  }

  // Read from secondary context queues
  std::vector<std::string> context_paths = GetSecondaryContextPaths(proc_path);
  for (const auto& context_path : context_paths) {
    err = read_gpus_from_queues(context_path);
    if (err != 0 && err != ESRCH) {
      return err;
    }
  }

  // Also check for "pid:PID-id:X" format directories at the parent level
  // (another format used for multi-context processes). Resolved from a cached
  // single scan of the KFD proc root so a sweep over many PIDs does not re-scan
  // the whole root once per PID.
  for (const auto& alternate_path : KfdAltContextRootDirsForPid(pid)) {
    err = read_gpus_from_queues(alternate_path);
    if (err != 0 && err != ESRCH) {
      return err;
    }

    // Also check for context_xxxx in this alternate path
    std::vector<std::string> alt_context_paths = GetSecondaryContextPaths(alternate_path);
    for (const auto& alt_context_path : alt_context_paths) {
      err = read_gpus_from_queues(alt_context_path);
      if (err != 0 && err != ESRCH) {
        return err;
      }
    }
  }

  // Active queues are the authoritative signal for GPUs a process is actively
  // running work on. A process can also hold a VRAM allocation on a GPU without
  // a live queue (e.g. memory staged before/after a kernel), so also attribute
  // it to any GPU whose per-node vram_<gpuid> file reports a non-zero size.
  // The stats/counters/sdma per-node files are deliberately not used: opening
  // /dev/kfd creates them for the whole topology, so they exist (as zero) for
  // every GPU and would attribute an idle process to all of them.
  {
    DIR* pd = opendir(proc_path.c_str());
    if (pd) {
      struct dirent* de;
      while ((de = readdir(pd))) {
        if (strncmp(de->d_name, kKFDVramPrefix, strlen(kKFDVramPrefix)) != 0) continue;
        std::string gpu_id_str = de->d_name + strlen(kKFDVramPrefix);
        if (gpu_id_str.empty() || !is_number(gpu_id_str)) continue;

        std::string vram_bytes;
        if (ReadSysfsStr(std::string(proc_path) + "/" + de->d_name, &vram_bytes) != 0) continue;
        try {
          if (std::stoull(vram_bytes) > 0) {
            gpu_set->insert(strtoull(gpu_id_str.c_str(), nullptr, 10));
          }
        } catch (...) {
          // Unreadable/garbage size: skip, treat as no allocation.
        }
      }
      closedir(pd);
    }
  }

  // PID namespace: fall back to discovering GPU IDs from KFD vram_* files.
  if (gpu_set->empty() && IsKfdPidNamespaced()) {
    std::ostringstream ss;
    ss << __PRETTY_FUNCTION__ << " | PID namespace detected, falling back to "
       << "KFD vram_* files for GPU discovery (pid=" << pid << ")";
    LOG_DEBUG(ss);
    CollectGpuIdsFromKfdVram(gpu_set);
  }

  return 0;
}

static int CheckValidProcessInfoData(const std::string& s, int sysfs_ret) {
  if (sysfs_ret == 0 && !is_number(s)) {
    return EINVAL;
  }
  return sysfs_ret;
}

static int GetProcessKFDStats(std::string path, uint32_t& val) {
  std::string tmp;
  int err = ReadSysfsStr(path, &tmp);
  auto sysfs_data_errcode = CheckValidProcessInfoData(tmp, err);

  if (!(sysfs_data_errcode == 0 || sysfs_data_errcode == ENOENT)) {
    return sysfs_data_errcode;
  } else if (sysfs_data_errcode == 0) {
    // Update KFD stat by the process
    val = static_cast<uint32_t>(std::stoul(tmp));
  } else {
    // Some GFX revisions do not provide KFD stats debugfs method
    // which may cause ENOENT
    val = KFD_STATS_INVALID;
  }

  return 0;
}

int GetProcessInfoForPID(uint32_t pid, rsmi_process_info_t* proc,
                         std::unordered_set<uint64_t>* gpu_set) {
  assert(proc != nullptr);
  assert(gpu_set != nullptr);
  int err;
  std::string tmp;
  std::unordered_set<uint64_t>::iterator itr;
  uint32_t kfd_stat;

  std::string proc_str_path = std::string(kKFDProcPathRoot) + "/" + std::to_string(pid);

  if (!FileExists(proc_str_path.c_str())) {
    // PID namespace: no KFD sysfs entry for this PID; return sentinel
    // values so callers can distinguish "unavailable" from "zero usage".
    if (IsKfdPidNamespaced()) {
      proc->process_id = pid;
      proc->vram_usage = std::numeric_limits<uint64_t>::max();
      proc->sdma_usage = std::numeric_limits<uint64_t>::max();
      proc->cu_occupancy = std::numeric_limits<uint32_t>::max();
      proc->evicted_time = std::numeric_limits<uint32_t>::max();
      return 0;
    }
    return ESRCH;
  }
  proc->process_id = pid;

  proc->vram_usage = 0;
  proc->sdma_usage = 0;
  // Default to invalid to display N/A if cu_occupancy file is unavailable
  proc->cu_occupancy = KFD_STATS_INVALID;
  proc->evicted_time = 0;

  // Collect all paths to read metrics from: primary process + secondary contexts
  std::vector<std::string> metric_paths;
  metric_paths.push_back(proc_str_path);

  // Add secondary context paths (context_xxxx directories)
  // These are created by the KFD multiple contexts feature
  std::vector<std::string> context_paths = GetSecondaryContextPaths(proc_str_path);
  for (const auto& context_path : context_paths) {
    metric_paths.push_back(context_path);
  }

  // Also check for "pid:PID-id:X" format directories at the parent level
  // (another format used for multi-context processes). Resolved from a cached
  // single scan of the KFD proc root so a sweep over many PIDs does not re-scan
  // the whole root once per PID.
  for (const auto& alternate_path : KfdAltContextRootDirsForPid(pid)) {
    metric_paths.push_back(alternate_path);

    // Also check for context_xxxx in this alternate path
    std::vector<std::string> alt_context_paths = GetSecondaryContextPaths(alternate_path);
    for (const auto& alt_context_path : alt_context_paths) {
      metric_paths.push_back(alt_context_path);
    }
  }

  for (const auto& gpu_id : *gpu_set) {
    // Aggregate metrics from primary process and all secondary contexts
    for (const auto& metric_base_path : metric_paths) {
      std::string vram_str_path = metric_base_path + "/vram_" + std::to_string(gpu_id);

      err = ReadSysfsStr(vram_str_path, &tmp);
      auto sysfs_data_errcode = CheckValidProcessInfoData(tmp, err);

      // Report all errors, except ENOENT (2), which should be ignored
      // and the proc->vram_usage should be unmodified
      if (!(sysfs_data_errcode == 0 || sysfs_data_errcode == ENOENT)) {
        return sysfs_data_errcode;
      }
      // Do not store any invalid values
      else if (sysfs_data_errcode == 0) {
        proc->vram_usage += std::stoull(tmp);
      }

      std::string sdma_str_path = metric_base_path + "/sdma_" + std::to_string(gpu_id);

      err = ReadSysfsStr(sdma_str_path, &tmp);
      sysfs_data_errcode = CheckValidProcessInfoData(tmp, err);

      if (!(sysfs_data_errcode == 0 || sysfs_data_errcode == ENOENT)) {
        return sysfs_data_errcode;
      } else if (sysfs_data_errcode == 0) {
        proc->sdma_usage += std::stoull(tmp);
      }

      // Build the path and read from Sysfs file, info that
      // encodes Compute Unit usage by a process of interest
      std::string cu_occupancy_path =
          metric_base_path + "/stats_" + std::to_string(gpu_id) + "/cu_occupancy";

      err = GetProcessKFDStats(cu_occupancy_path, kfd_stat);
      if (err != 0) {
        // ENOENT is acceptable for secondary contexts where stats may not exist
        // Only return error for: non-ENOENT errors, OR primary process with existing file
        bool is_primary = (metric_base_path == proc_str_path);
        bool file_exists = FileExists(cu_occupancy_path.c_str());
        if (err != ENOENT || (is_primary && file_exists)) {
          return err;
        }
      } else {
        // Aggregate cu_occupancy (use max value as it represents peak usage)
        if (kfd_stat != KFD_STATS_INVALID &&
            (proc->cu_occupancy == KFD_STATS_INVALID || kfd_stat > proc->cu_occupancy)) {
          proc->cu_occupancy = kfd_stat;
        }
      }

      std::string evicted_time_path =
          metric_base_path + "/stats_" + std::to_string(gpu_id) + "/evicted_ms";

      err = GetProcessKFDStats(evicted_time_path, kfd_stat);
      if (err != 0) {
        // ENOENT is acceptable for secondary contexts where stats may not exist
        // Only return error for: non-ENOENT errors, OR primary process with existing file
        bool is_primary_ctx = (metric_base_path == proc_str_path);
        bool file_found = FileExists(evicted_time_path.c_str());
        if (err != ENOENT || (is_primary_ctx && file_found)) {
          return err;
        }
      } else {
        // Aggregate evicted_time (sum all evicted times)
        if (kfd_stat != KFD_STATS_INVALID) {
          // Handle potential overflow by checking before addition
          if (proc->evicted_time <= UINT32_MAX - kfd_stat) {
            proc->evicted_time += kfd_stat;
          } else {
            proc->evicted_time = UINT32_MAX;  // Cap at max value
          }
        }
      }
    }  // End of metric_paths loop
  }

  return 0;
}

int DiscoverKFDNodes(std::map<uint64_t, std::shared_ptr<KFDNode>>* nodes) {
  assert(nodes != nullptr);

  if (nodes == nullptr) {
    return EINVAL;
  }
  assert(nodes->empty());

  nodes->clear();

  std::shared_ptr<KFDNode> node;
  uint32_t node_indx;

  auto kfd_node_dir = opendir(kKFDNodesPathRoot);
  if (kfd_node_dir == nullptr) {
    return errno;
  }

  auto dentry = readdir(kfd_node_dir);
  while (dentry != nullptr) {
    if (dentry->d_name[0] == '.') {
      dentry = readdir(kfd_node_dir);
      continue;
    }

    if (!is_number(dentry->d_name)) {
      dentry = readdir(kfd_node_dir);
      continue;
    }

    node_indx = static_cast<uint32_t>(std::stoi(dentry->d_name));

    if (!KFDNodeSupported(node_indx)) {
      dentry = readdir(kfd_node_dir);
      continue;
    }

    node = std::make_shared<KFDNode>(node_indx);

    node->Initialize();

    if (node->gpu_id() == 0) {
      // Don't add; this is a cpu node.
      dentry = readdir(kfd_node_dir);
      continue;
    }

    uint64_t kfd_gpu_node_bus_fn;
    uint64_t kfd_gpu_node_domain;
    int ret;
    ret = node->get_property_value(kKFDNodePropLOCATION_IDStr, &kfd_gpu_node_bus_fn);
    if (ret != 0) {
      std::cerr << "Failed to open properties file for kfd node " << node->node_index() << "."
                << std::endl;
      closedir(kfd_node_dir);
      return ret;
    }
    ret = node->get_property_value(kKFDNodePropDOMAINStr, &kfd_gpu_node_domain);
    if (ret != 0) {
      std::cerr << "Failed to get \"domain\" property from properties "
                   "files for kfd node "
                << node->node_index() << "." << std::endl;
      closedir(kfd_node_dir);
      return ret;
    }

    uint64_t kfd_bdfid = (kfd_gpu_node_domain << 32) | (kfd_gpu_node_bus_fn);
    (*nodes)[kfd_bdfid] = node;

    dentry = readdir(kfd_node_dir);
  }

  if (closedir(kfd_node_dir)) {
    std::string err_str = "Failed to close KFD node directory ";
    err_str += kKFDNodesPathRoot;
    err_str += ".";
    perror(err_str.c_str());
    return 1;
  }
  return 0;
}

KFDNode::~KFDNode() = default;

int KFDNode::ReadProperties(void) {
  int ret;

  std::vector<std::string> propVec;

  assert(properties_.empty());
  if (!properties_.empty()) {
    return 0;
  }

  ret = ReadKFDDeviceProperties(node_indx_, &propVec);

  if (ret) {
    return ret;
  }

  std::string key_str;
  std::string val_str;
  uint64_t val_int;  // Assume all properties are unsigned integers for now
  std::istringstream fs;
  std::ostringstream ss;

  for (const auto& i : propVec) {
    fs.str(i);
    fs >> key_str;
    fs >> val_str;
    // Leaving below to debug any new properties file changes
    // ss << __PRETTY_FUNCTION__ << " | key = " << key_str
    //    << "; val = " << val_str;
    // LOG_TRACE(ss);
    val_int = std::stoull(val_str);
    properties_[key_str] = val_int;

    fs.str("");
    fs.clear();
  }

  return 0;
}

int KFDNode::Initialize(void) {
  int ret = 0;
  ret = ReadProperties();
  if (ret) {
    return ret;
  }

  ret = ReadKFDGpuId(node_indx_, &gpu_id_);
  if (ret || (gpu_id_ == 0)) {
    return ret;
  }

  ret = ReadKFDGpuName(node_indx_, &name_);

  ret = get_property_value(kKFDNodePropHIVE_IDStr, &xgmi_hive_id_);
  if (ret != 0) {
    throw amd::smi::rsmi_exception(RSMI_INITIALIZATION_ERROR,
                                   "Failed to initialize rocm_smi library (get xgmi hive id).");
  }

  std::map<uint32_t, std::shared_ptr<IOLink>> io_link_map_tmp;
  ret = DiscoverIOLinksPerNode(node_indx_, &io_link_map_tmp);
  if (ret != 0) {
    throw amd::smi::rsmi_exception(
        RSMI_INITIALIZATION_ERROR,
        "Failed to initialize rocm_smi library (IO Links discovery per node).");
  }

  std::map<uint32_t, std::shared_ptr<IOLink>>::iterator it;
  uint32_t node_to;
  uint64_t node_to_gpu_id;
  std::shared_ptr<IOLink> link;
  bool numa_node_found = false;
  for (it = io_link_map_tmp.begin(); it != io_link_map_tmp.end(); it++) {
    io_link_map_[it->first] = it->second;
    node_to = it->first;
    link = it->second;
    ret = ReadKFDGpuId(node_to, &node_to_gpu_id);
    if (ret) {
      continue;
    }
    if (node_to_gpu_id == 0) {  //  CPU node
      if (numa_node_found) {
        if (numa_node_weight_ > link->weight()) {
          numa_node_number_ = node_to;
          numa_node_weight_ = link->weight();
          numa_node_type_ = link->type();
        }
      } else {
        numa_node_number_ = node_to;
        numa_node_weight_ = link->weight();
        numa_node_type_ = link->type();
        numa_node_found = true;
      }
    } else {
      io_link_type_[node_to] = link->type();
      io_link_weight_[node_to] = link->weight();
      io_link_max_bandwidth_[node_to] = link->max_bandwidth();
      io_link_min_bandwidth_[node_to] = link->min_bandwidth();
    }
  }

  // Pre-compute the total number of compute units a device has
  uint64_t tmp_val;
  ret = get_property_value(kKFDNodePropSIMD_ARRAYS_PER_ENGINEStr, &tmp_val);
  if (ret != 0) {
    throw amd::smi::rsmi_exception(RSMI_INITIALIZATION_ERROR,
                                   "Failed to initialize rocm_smi library "
                                   "(get number of shader arrays per engine).");
  }
  cu_count_ = uint32_t(tmp_val);
  ret = get_property_value(kKFDNodePropARRAY_COUNTStr, &tmp_val);
  if (ret != 0) {
    throw amd::smi::rsmi_exception(
        RSMI_INITIALIZATION_ERROR,
        "Failed to initialize rocm_smi library (get number of shader arrays).");
  }
  cu_count_ = cu_count_ * uint32_t(tmp_val);
  ret = get_property_value(kKFDNodePropCU_PER_SIMD_ARRAYStr, &tmp_val);
  if (ret != 0) {
    throw amd::smi::rsmi_exception(
        RSMI_INITIALIZATION_ERROR,
        "Failed to initialize rocm_smi library (get number of CU's per array).");
  }
  cu_count_ = cu_count_ * uint32_t(tmp_val);

  return ret;
}

int KFDNode::get_property_value(std::string property, uint64_t* value) {
  assert(value != nullptr);
  if (value == nullptr) {
    return EINVAL;
  }
  if (properties_.find(property) == properties_.end()) {
    return EINVAL;
  }
  *value = properties_[property];
  return 0;
}

int KFDNode::get_io_link_type(uint32_t node_to, IO_LINK_TYPE* type) {
  assert(type != nullptr);
  if (type == nullptr) {
    return EINVAL;
  }
  if (io_link_type_.find(node_to) == io_link_type_.end()) {
    return EINVAL;
  }
  *type = io_link_type_[node_to];
  return 0;
}

int KFDNode::get_io_link_weight(uint32_t node_to, uint64_t* weight) {
  assert(weight != nullptr);
  if (weight == nullptr) {
    return EINVAL;
  }
  if (io_link_weight_.find(node_to) == io_link_weight_.end()) {
    return EINVAL;
  }
  *weight = io_link_weight_[node_to];
  return 0;
}

int KFDNode::get_io_link_bandwidth(uint32_t node_to, uint64_t* max_bandwidth,
                                   uint64_t* min_bandwidth) {
  assert(max_bandwidth != nullptr && min_bandwidth != nullptr);
  if (max_bandwidth == nullptr || min_bandwidth == nullptr) {
    return EINVAL;
  }

  if (io_link_max_bandwidth_.find(node_to) == io_link_max_bandwidth_.end() ||
      io_link_min_bandwidth_.find(node_to) == io_link_min_bandwidth_.end()) {
    return EINVAL;
  }

  *max_bandwidth = io_link_max_bandwidth_[node_to];
  *min_bandwidth = io_link_min_bandwidth_[node_to];

  return 0;
}
// /sys/class/kfd/kfd/topology/nodes/*/mem_banks/*/properties
// size_in_bytes 68702699520
int KFDNode::get_total_memory(uint64_t* total) {
  std::ostringstream ss;
  if (total == nullptr) {
    return EINVAL;
  }
  *total = 0;

  std::string f_path = kKFDNodesPathRoot;
  f_path += "/";
  f_path += std::to_string(node_indx_);
  f_path += "/mem_banks";
  int subDirCount = subDirectoryCountInPath(f_path);
  ss << __PRETTY_FUNCTION__ << " | [before loop] Within " << f_path
     << " has subdirectory count = " << std::to_string(subDirCount);
  LOG_DEBUG(ss);

  auto kfd_node_dir = opendir(f_path.c_str());
  if (kfd_node_dir == nullptr) {
    return errno;
  }
  auto dentry = readdir(kfd_node_dir);
  while (dentry != nullptr && subDirCount > 0) {
    ss << __PRETTY_FUNCTION__ << " | [inside loop] Within " << f_path
       << " has subdirectory count = " << std::to_string(subDirCount);
    LOG_DEBUG(ss);
    if (dentry->d_name[0] == '.') {
      dentry = readdir(kfd_node_dir);
      continue;
    }

    if (!is_number(dentry->d_name)) {
      dentry = readdir(kfd_node_dir);
      continue;
    }

    // read "size_in_bytes 68702699520" line
    const std::string size_in_bytes_property = "size_in_bytes ";
    std::string memory_bank_file = f_path + "/" + dentry->d_name + "/properties";
    std::ifstream fs(memory_bank_file);
    if (!fs) {
      dentry = readdir(kfd_node_dir);
      continue;
    }
    std::string line;
    while (std::getline(fs, line)) {
      if (line.substr(0, size_in_bytes_property.length()) == size_in_bytes_property) {
        auto bytes = line.substr(size_in_bytes_property.length());
        try {
          *total += std::stol(bytes);
          break;
        } catch (...) {
          dentry = readdir(kfd_node_dir);
          continue;
        }
      }
    }  // end loop for lines in property file
    subDirCount--;
  }  // end loop for mem_bank directory

  if (closedir(kfd_node_dir)) {
    std::string err_str = "Failed to close KFD node directory ";
    err_str += f_path;
    err_str += ".";
    perror(err_str.c_str());
    return 1;
  }
  return 0;
}

// ioctl on kfd node device
int KFDNode::get_used_memory_orig(uint64_t* used) {
  if (used == nullptr) return EINVAL;
  static const char* kPathKFDIoctl = "/dev/kfd";

  int kfd_fd = open(kPathKFDIoctl, O_RDWR | O_CLOEXEC);
  if (kfd_fd <= 0) {
    return 1;
  }
  struct kfd_ioctl_get_available_memory_args mem = {0, 0, 0};
  mem.gpu_id = static_cast<uint32_t>(gpu_id_);
  if (ioctl(kfd_fd, AMDKFD_IOC_AVAILABLE_MEMORY, &mem) != 0) {
    close(kfd_fd);
    return 1;
  }
  close(kfd_fd);

  // used = total - available
  uint64_t total = 0;
  int ret = get_total_memory(&total);
  if (ret != 0) {
    return ret;
  }

  if (total > 0 && mem.available < total) {
    *used = total - mem.available;
    return 0;
  } else {
    return ENXIO;  // case ENXIO:   return RSMI_STATUS_UNEXPECTED_DATA;
  }
}

// Order of logic:
// If AMDSMI_KFD_USE_ORIG_VRAM is set, use original ioctl method
// else if AMDSMI_KFD_CACHE_TTL_MS > 0, use batched KFD fork with caching
//           |-- this one gives best performance when monitoring multiple devices
// else use 1 KFD fork per device
//    (see AMDSMI_KFD_DISABLE_INOTIFY_POLLING, AMDSMI_KFD_INOTIFY_POLL_MS,
//     & AMDSMI_KFD_CLEANUP_POLL_US -> rocm_smi_kfd_data_manager.h)
int KFDNode::get_used_memory(uint64_t* used) {
  if (used == nullptr) return EINVAL;

  *used = 0;
  int ret = 0;
  uint64_t available = 0;
  std::ostringstream ss;
  amd::smi::kfd::KFDManagerConfig kfd_cfg = amd::smi::kfd::GetCurrentConfig();
  // measure time
  auto start_time = std::chrono::steady_clock::now();
  if (kfd_cfg.use_original_vram_fcn) {
    int orig_ret = get_used_memory_orig(used);
    ss << __PRETTY_FUNCTION__ << " | [original] gpu_id: " << gpu_id_ << "; val: " << *used
       << "; ret: " << orig_ret << "; Time took: "
       << std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() -
                                                                start_time)
              .count()
       << " microseconds";
    LOG_DEBUG(ss);
    return orig_ret;
  }

  if (kfd_cfg.cache_ttl_ms > 0) {
    amd::smi::RocmSMI& smi = amd::smi::RocmSMI::getInstance();
    std::vector<uint32_t> gpu_ids;
    auto devices = smi.devices();
    for (auto& dev : devices) {
      gpu_ids.push_back(static_cast<uint32_t>(dev->kfd_gpu_id()));
    }
    ret =
        amd::smi::kfd::QueryAvailableVramBatch(gpu_ids, static_cast<uint32_t>(gpu_id_), &available);
    ss << __PRETTY_FUNCTION__
       << " | [Batch & cached - 1 batched kfd fork for all devices] gpu_id: " << gpu_id_
       << "; val: " << available << "; ret: " << ret << "; Time took: "
       << std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() -
                                                                start_time)
              .count()
       << " microseconds";
    LOG_DEBUG(ss);
  } else {
    ret = amd::smi::kfd::QueryAvailableVram(static_cast<uint32_t>(gpu_id_), &available);

    ss << __PRETTY_FUNCTION__ << " | [1 kfd fork per device] gpu_id: " << gpu_id_
       << "; val: " << available << "; ret: " << ret << "; Time took: "
       << std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() -
                                                                start_time)
              .count()
       << " microseconds";
    LOG_DEBUG(ss);
  }

  if (ret != 0) {
    return ret;
  }

  // used = total - available
  uint64_t total = 0;
  ret = get_total_memory(&total);
  if (ret != 0) {
    return ret;
  }
  if (total > 0 && available < total) {
    *used = total - available;
    return 0;
  } else {
    return ENXIO;  // case ENXIO:   return RSMI_STATUS_UNEXPECTED_DATA;
  }
}

int KFDNode::get_cache_info(rsmi_gpu_cache_info_t* info) {
  if (info == nullptr) return EINVAL;
  uint64_t caches_count = 0;
  int ret = get_property_value("caches_count", &caches_count);
  if (ret != 0) return ret;

  // /sys/class/kfd/kfd/topology/nodes/1/caches/0/properties
  std::string f_path = kKFDNodesPathRoot;
  f_path += "/";
  f_path += std::to_string(node_indx_);
  f_path += "/";
  f_path += "caches/";

  info->num_cache_types = 0;
  for (unsigned int cache_id = 0; cache_id < caches_count; cache_id++) {
    const auto prop_file = f_path + std::to_string(cache_id) + "/properties";
    try {
      std::string level = get_properties_from_file(prop_file, "level ");
      int cache_level = std::stoi(level);
      if (cache_level < 0) continue;

      std::string type = get_properties_from_file(prop_file, "type ");
      int cache_type = std::stoi(type);
      if (cache_type <= 0) continue;

      std::string size = get_properties_from_file(prop_file, "size ");
      int cache_size = std::stoi(size);
      if (cache_size <= 0) continue;

      std::string sibling_map = get_properties_from_file(prop_file, "sibling_map ");
      uint32_t num_cu_shared =
          static_cast<uint32_t>(std::count(sibling_map.begin(), sibling_map.end(), '1'));

      bool is_count_already = false;
      for (unsigned int i = 0; i < info->num_cache_types; i++) {
        if (info->cache[i].cache_level == static_cast<uint32_t>(cache_level) &&
            info->cache[i].flags == static_cast<uint32_t>(cache_type) &&
            info->cache[i].cache_size_kb == static_cast<uint32_t>(cache_size) &&
            info->cache[i].max_num_cu_shared == num_cu_shared) {
          is_count_already = true;
          info->cache[i].num_cache_instance++;
          break;
        }
      }
      if (is_count_already) continue;

      if (info->num_cache_types >= RSMI_MAX_CACHE_TYPES) return 1;

      info->cache[info->num_cache_types].cache_level = cache_level;
      info->cache[info->num_cache_types].cache_size_kb = cache_size;
      info->cache[info->num_cache_types].max_num_cu_shared = num_cu_shared;
      info->cache[info->num_cache_types].num_cache_instance = 1;
      info->cache[info->num_cache_types].flags = cache_type;
      info->num_cache_types++;
    } catch (...) {
      continue;
    }
  }
  return 0;
}

// /sys/class/kfd/kfd/topology/nodes/*/properties
int read_node_properties(uint32_t node, std::string property_name, uint64_t* val) {
  std::ostringstream ss;
  std::string propertiesFullPath =
      "/sys/class/kfd/kfd/topology/nodes/" + std::to_string(node) + "/properties";
  int retVal = EINVAL;
  if (property_name.empty() || val == nullptr) {
    ss << __PRETTY_FUNCTION__ << " | File: " << propertiesFullPath
       << " | Issue: Could not read node #" << std::to_string(node)
       << ", property_name is empty or *val is nullptr "
       << " | return = " << std::to_string(retVal) << " | ";
    LOG_DEBUG(ss);
    return retVal;
  }
  std::shared_ptr<KFDNode> myNode = std::shared_ptr<KFDNode>(new KFDNode(node));
  myNode->Initialize();
  if (KFDNodeSupported(node)) {
    retVal = myNode->get_property_value(property_name, val);
    ss << __PRETTY_FUNCTION__ << " | File: " << propertiesFullPath << " | Successfully read node #"
       << std::to_string(node) << " for property_name = " << property_name << " | Data ("
       << property_name << ") * val = " << std::to_string(*val)
       << " | return = " << std::to_string(retVal) << " | ";
    LOG_DEBUG(ss);
  } else {
    retVal = 1;
    ss << __PRETTY_FUNCTION__ << " | File: " << propertiesFullPath
       << " | Issue: Could not read node #" << std::to_string(node)
       << ", KFD node was an unsupported node."
       << " | return = " << std::to_string(retVal) << " | ";
    LOG_ERROR(ss);
  }
  return retVal;
}

// /sys/class/kfd/kfd/topology/nodes/*/gpu_id
int get_gpu_id(uint32_t node, uint64_t* gpu_id) {
  std::ostringstream ss;
  std::string gpu_id_FullPath =
      "/sys/class/kfd/kfd/topology/nodes/" + std::to_string(node) + "/gpu_id";
  int retVal = EINVAL;
  if (gpu_id == nullptr) {
    ss << __PRETTY_FUNCTION__ << " | File: " << gpu_id_FullPath << " | Issue: Could not read node #"
       << std::to_string(node) << ", gpu_id is a nullptr "
       << " | return = " << std::to_string(retVal) << " | ";
    LOG_DEBUG(ss);
    return retVal;
  }
  std::shared_ptr<KFDNode> myNode = std::shared_ptr<KFDNode>(new KFDNode(node));
  myNode->Initialize();
  if (KFDNodeSupported(node)) {
    retVal = ReadKFDGpuId(node, gpu_id);
    ss << __PRETTY_FUNCTION__ << " | File: " << gpu_id_FullPath << " | Successfully read node #"
       << std::to_string(node) << " for gpu_id"
       << " | Data (gpu_id) *gpu_id = " << std::to_string(*gpu_id)
       << " | return = " << std::to_string(retVal) << " | ";
    LOG_DEBUG(ss);
  } else {
    retVal = 1;
    ss << __PRETTY_FUNCTION__ << " | File: " << gpu_id_FullPath << " | Issue: Could not read node #"
       << std::to_string(node) << ", KFD node was an unsupported node."
       << " | return = " << std::to_string(retVal) << " | ";
    LOG_ERROR(ss);
  }
  return retVal;
}

// /sys/class/kfd/kfd/topology/nodes/*/properties | grep gfx_target_version
int KFDNode::get_gfx_target_version(uint64_t* gfx_target_version) {
  std::ostringstream ss;
  std::string properties_path =
      "/sys/class/kfd/kfd/topology/nodes/" + std::to_string(this->node_indx_) + "/properties";
  uint64_t gfx_version = 0;
  int ret = read_node_properties(this->node_indx_, "gfx_target_version", &gfx_version);
  *gfx_target_version = gfx_version;
  ss << __PRETTY_FUNCTION__ << " | File: " << properties_path
     << " | Read node: " << std::to_string(this->node_indx_) << " for gfx_target_version"
     << " | Data (*gfx_target_version): " << std::to_string(*gfx_target_version)
     << " | Return: " << getRSMIStatusString(amd::smi::ErrnoToRsmiStatus(ret), false) << " | ";
  LOG_DEBUG(ss);
  return ret;
}

int32_t KFDNode::get_simd_per_cu(uint64_t* simd_per_cu) const {
  const std::string properties_path("/sys/class/kfd/kfd/topology/nodes/" +
                                    std::to_string(this->node_indx_) + "/properties");

  auto tmp_simd_per_cu = uint64_t(0);
  auto ret = read_node_properties(this->node_indx_, "simd_per_cu", &tmp_simd_per_cu);
  *simd_per_cu = tmp_simd_per_cu;
  return ret;
}

int32_t KFDNode::get_simd_count(uint64_t* simd_count) const {
  const std::string properties_path("/sys/class/kfd/kfd/topology/nodes/" +
                                    std::to_string(this->node_indx_) + "/properties");

  auto tmp_simd_count = uint64_t(0);
  auto ret = read_node_properties(this->node_indx_, "simd_count", &tmp_simd_count);
  *simd_count = tmp_simd_count;
  return ret;
}

// Public interface for device
// /sys/class/kfd/kfd/topology/nodes/*/gpu_id
int KFDNode::get_gpu_id(uint64_t* gpu_id) {
  std::ostringstream ss;
  std::string gpuid_path =
      "/sys/class/kfd/kfd/topology/nodes/" + std::to_string(this->node_indx_) + "/gpu_id";
  const uint64_t undefined_gpu_id = std::numeric_limits<uint64_t>::max();
  std::string gpu_id_string = "";
  *gpu_id = undefined_gpu_id;
  int ret = ReadSysfsStr(gpuid_path, &gpu_id_string);
  if (ret != 0 || gpu_id_string.empty()) {
    ss << __PRETTY_FUNCTION__ << " | File: " << gpuid_path << " | Data (*gpu_id): empty or nullptr"
       << " | Issue: Could not read node #" << std::to_string(this->node_indx_)
       << ". KFD node was an unsupported node or value read was empty."
       << " | Return: " << getRSMIStatusString(amd::smi::ErrnoToRsmiStatus(ret), false) << " | ";
    LOG_ERROR(ss);
    return ret;
  }
  *gpu_id = std::stoull(gpu_id_string);
  if (*gpu_id == 0) {  // CPU node - return not supported
    *gpu_id = undefined_gpu_id;
    ret = ENOENT;  // map to RSMI_STATUS_NOT_SUPPORTED
  }
  ss << __PRETTY_FUNCTION__ << " | File: " << gpuid_path
     << " | Read node #: " << std::to_string(this->node_indx_)
     << " | Data (*gpu_id): " << std::to_string(*gpu_id)
     << " | Return: " << getRSMIStatusString(amd::smi::ErrnoToRsmiStatus(ret), false) << " | ";
  LOG_DEBUG(ss);
  return ret;
}

// Public interface for device
// /sys/class/kfd/kfd/topology/nodes/<node_id>
int KFDNode::get_node_id(uint32_t* node_id) {
  std::ostringstream ss;
  int ret = 0;
  std::string nodeid_path = "/sys/class/kfd/kfd/topology/nodes/" + std::to_string(this->node_indx_);
  *node_id = this->node_indx_;
  ss << __PRETTY_FUNCTION__ << " | File: " << nodeid_path
     << " | Read node #: " << std::to_string(this->node_indx_)
     << " | Data (*node_id): " << std::to_string(*node_id)
     << " | Return: " << getRSMIStatusString(amd::smi::ErrnoToRsmiStatus(ret), false) << " | ";
  LOG_DEBUG(ss);
  return ret;
}

}  // namespace amd::smi
