From 42a10aa0344cceeff405aa441deaa742cb566a4b Mon Sep 17 00:00:00 2001 From: Patryk Przybysz Date: Sun, 2 Aug 2026 02:29:42 +0200 Subject: [PATCH 1/2] feat(agent): migrate RaplCollector from MSR to powercap sysfs --- .../agent/src/collectors/rapl_collector.cc | 223 +++++++++++------- sources/agent/src/collectors/rapl_collector.h | 73 ++---- 2 files changed, 157 insertions(+), 139 deletions(-) diff --git a/sources/agent/src/collectors/rapl_collector.cc b/sources/agent/src/collectors/rapl_collector.cc index 2f8c64a..6a56c4a 100644 --- a/sources/agent/src/collectors/rapl_collector.cc +++ b/sources/agent/src/collectors/rapl_collector.cc @@ -1,58 +1,154 @@ #include "rapl_collector.h" -#include +#include #include #include +#include #include -#include -#include +#include +#include +#include namespace volta { namespace agent { namespace collectors { -RaplCollector::RaplCollector() = default; +namespace { -bool RaplCollector::Init() { +constexpr const char* kPowercapRoot = "/sys/class/powercap"; + +bool StartsWith(const std::string& s, const char* prefix) { + const size_t n = std::strlen(prefix); + return s.size() >= n && s.compare(0, n, prefix) == 0; +} + +} // namespace + +bool RaplCollector::IsPackageName(const std::string& name) { + if (name == "package") return true; + if (!StartsWith(name, "package-")) return false; + const char* p = name.c_str() + 8; + if (*p == '\0') return false; + while (*p) { + if (!std::isdigit(static_cast(*p))) return false; + ++p; + } + return true; +} + +std::optional RaplCollector::ParseSocketIndex(const std::string& name) { + if (name == "package") return 0; + if (!StartsWith(name, "package-")) return std::nullopt; try { - OpenMSR(); - uint64_t readout = ReadMSR(0, MSR_RAPL::POWER_UNIT); - power_units_ = pow(0.5, (double)(readout & 0xf)); - energy_units_ = pow(0.5, (double)((readout >> 8) & 0x1f)); - time_units_ = pow(0.5, (double)((readout >> 16) & 0xf)); - readout = ReadMSR(0, MSR_RAPL::PKG::ENERGY_STATUS); - last_value = energy_units_ * readout; - initialized_ = true; - return true; - } catch (const MSR_Open_Exception&) { - return false; - } catch (const MSR_Read_Exception&) { - return false; + return std::stoi(name.substr(8)); + } catch (...) { + return std::nullopt; } } -bool RaplCollector::IsSupported() { - const std::filesystem::path cpu_base = "/dev/cpu"; - std::error_code ec; - if (!std::filesystem::exists(cpu_base, ec) || - !std::filesystem::is_directory(cpu_base, ec)) { - return false; +uint64_t RaplCollector::DeltaEnergyUj(uint64_t now, uint64_t last, + uint64_t max_range_uj) { + if (now >= last) return now - last; + if (max_range_uj > 0 && last <= max_range_uj) { + return (max_range_uj - last) + now; } + return (std::numeric_limits::max() - last) + now + 1; +} + +bool RaplCollector::ReadU64File(const std::string& path, uint64_t* out) const { + std::ifstream in(path); + if (!in) return false; + uint64_t v = 0; + in >> v; + if (!in && !in.eof()) return false; + *out = v; + return true; +} - for (const auto& entry : std::filesystem::directory_iterator(cpu_base)) { - if (!entry.is_directory()) continue; +bool RaplCollector::ReadNameFile(const std::string& path, + std::string* out) const { + std::ifstream in(path); + if (!in) return false; + std::string line; + if (!std::getline(in, line)) return false; + while (!line.empty() && + (line.back() == '\n' || line.back() == '\r' || line.back() == ' ')) { + line.pop_back(); + } + *out = line; + return true; +} - const auto msr_path = entry.path() / "msr"; - if (std::filesystem::exists(msr_path, ec) && - access(msr_path.c_str(), R_OK) == 0) { - return true; - } +std::vector RaplCollector::DiscoverPackages() + const { + std::vector packages; + DIR* d = opendir(kPowercapRoot); + if (!d) return packages; + + while (dirent* ent = readdir(d)) { + if (ent->d_name[0] == '.') continue; + const std::string zone_dir = std::string(kPowercapRoot) + "/" + ent->d_name; + const std::string name_path = zone_dir + "/name"; + const std::string energy_path = zone_dir + "/energy_uj"; + + if (access(energy_path.c_str(), F_OK) != 0) continue; + + std::string name; + if (!ReadNameFile(name_path, &name)) continue; + if (!IsPackageName(name)) continue; + + PackageZone z; + z.path = zone_dir; + z.name = name; + auto sock = ParseSocketIndex(name); + z.socket_index = sock.value_or(-1); + uint64_t max_range = 0; + (void)ReadU64File(zone_dir + "/max_energy_range_uj", &max_range); + z.max_energy_range_uj = max_range; + packages.push_back(std::move(z)); } + closedir(d); + + std::sort(packages.begin(), packages.end(), + [](const PackageZone& a, const PackageZone& b) { + if (a.socket_index != b.socket_index) + return a.socket_index < b.socket_index; + return a.name < b.name; + }); + return packages; +} +bool RaplCollector::IsSupported() { + const auto packages = DiscoverPackages(); + for (const auto& p : packages) { + if (access((p.path + "/energy_uj").c_str(), R_OK) == 0) return true; + } return false; } +bool RaplCollector::Init() { + initialized_ = false; + auto packages = DiscoverPackages(); + if (packages.empty()) return false; + + auto it = + std::find_if(packages.begin(), packages.end(), + [](const PackageZone& z) { return z.socket_index == 0; }); + if (it == packages.end()) { + it = packages.begin(); + it->socket_index = 0; + } + + uint64_t energy = 0; + if (!ReadU64File(it->path + "/energy_uj", &energy)) return false; + + active_ = *it; + last_energy_uj_ = energy; + initialized_ = true; + return true; +} + void RaplCollector::SetRequestedMetrics( const std::vector& metrics) { requested_metrics_ = metrics; @@ -66,23 +162,21 @@ std::vector RaplCollector::Collect() { return {}; } - uint64_t readout; - try { - readout = ReadMSR(0, MSR_RAPL::PKG::ENERGY_STATUS); - } catch (const MSR_Read_Exception&) { - return {}; - } + uint64_t energy = 0; + if (!ReadU64File(active_.path + "/energy_uj", &energy)) return {}; + + const uint64_t delta_uj = + DeltaEnergyUj(energy, last_energy_uj_, active_.max_energy_range_uj); + last_energy_uj_ = energy; - double value = energy_units_ * readout; Metric m; m.type = MetricType::METRIC_TYPE_CPU_POWER_PACKAGE; CpuID cpu_id; - cpu_id.set_socket_index(0); + cpu_id.set_socket_index(active_.socket_index >= 0 ? active_.socket_index : 0); cpu_id.set_core_index(0); m.devId = DeviceId{std::move(cpu_id)}; - m.value = value - last_value; + m.value = static_cast(delta_uj) / 1.0e6; m.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); - last_value = value; return {m}; } @@ -90,53 +184,6 @@ std::vector RaplCollector::Satisfiable() { return {MetricType::METRIC_TYPE_CPU_POWER_PACKAGE}; } -uint64_t RaplCollector::ReadMSR(uint8_t core, uint32_t offset) { - uint64_t data; - if (core + 1 > MSR_files_.size()) { - throw MSR_Read_Exception(); - } - if (pread(MSR_files_[core], &data, sizeof data, offset) != sizeof data) { - throw MSR_Read_Exception(); - } - return data; -} - -void RaplCollector::OpenMSR() { - const std::filesystem::path cpu_base = "/dev/cpu"; - MSR_files_.clear(); - std::error_code ec; - - if (!std::filesystem::exists(cpu_base, ec)) { - throw MSR_Open_Exception(); - } - std::vector> cpu_entries; - for (const auto& entry : std::filesystem::directory_iterator(cpu_base)) { - if (!entry.is_directory()) continue; - const auto& dirname = entry.path().filename().string(); - if (!std::ranges::all_of(dirname, ::isdigit)) continue; - cpu_entries.emplace_back(std::stoi(dirname), entry.path()); - } - - std::ranges::sort(cpu_entries); - for (const auto& [id, path] : cpu_entries) { - int fd = open((path / "msr").c_str(), O_RDONLY); - if (fd >= 0) { - MSR_files_.push_back(fd); - } - } - if (MSR_files_.empty()) { - throw MSR_Open_Exception(); - } -} - -void RaplCollector::CloseMSR(int fd) { close(fd); } - -RaplCollector::~RaplCollector() { - for (auto file : MSR_files_) { - CloseMSR(file); - } -} - } // namespace collectors } // namespace agent } // namespace volta diff --git a/sources/agent/src/collectors/rapl_collector.h b/sources/agent/src/collectors/rapl_collector.h index 9e9817c..13daf6c 100644 --- a/sources/agent/src/collectors/rapl_collector.h +++ b/sources/agent/src/collectors/rapl_collector.h @@ -1,6 +1,11 @@ #ifndef VOLTA_AGENT_SRC_COLLECTORS_RAPL_COLLECTOR_H_ #define VOLTA_AGENT_SRC_COLLECTORS_RAPL_COLLECTOR_H_ +#include +#include +#include +#include + #include "collectors/collector.h" namespace volta { @@ -9,67 +14,33 @@ namespace collectors { class RaplCollector final : public RegisteredCollector { public: - RaplCollector(); - RaplCollector(const RaplCollector&) = delete; - RaplCollector& operator=(const RaplCollector&) = delete; bool Init() override; std::vector Collect() override; bool IsSupported() override; std::vector Satisfiable() override; void SetRequestedMetrics(const std::vector& metrics) override; - ~RaplCollector(); private: - uint64_t ReadMSR(uint8_t core, uint32_t offset); - void OpenMSR(); - void CloseMSR(int fd); - std::vector requested_metrics_; - bool initialized_ = false; - double power_units_ = 0, energy_units_ = 0, time_units_ = 0; - std::vector MSR_files_; - double last_value = 0; - - class MSR_Read_Exception : std::exception {}; - class MSR_Open_Exception : std::exception {}; - - struct MSR_RAPL { - static constexpr uint32_t POWER_UNIT = 0x606; - struct Units { - static constexpr uint32_t POWER_UNIT_OFFSET = 0; - static constexpr uint32_t POWER_UNIT_MASK = 0x0F; - static constexpr uint32_t ENERGY_UNIT_OFFSET = 0x08; - static constexpr uint32_t ENERGY_UNIT_MASK = 0x1F00; - static constexpr uint32_t TIME_UNIT_OFFSET = 0x10; - static constexpr uint32_t TIME_UNIT_MASK = 0xF000; - }; - - struct PKG { - static constexpr uint32_t POWER_LIMIT = 0x610; - static constexpr uint32_t ENERGY_STATUS = 0x611; - static constexpr uint32_t PERF_STATUS = 0x613; - static constexpr uint32_t POWER_INFO = 0x614; - }; + struct PackageZone { + std::string path; + std::string name; + int socket_index = 0; + uint64_t max_energy_range_uj = 0; + }; - struct PP0 { - static constexpr uint32_t POWER_LIMIT = 0x638; - static constexpr uint32_t ENERGY_STATUS = 0x639; - static constexpr uint32_t POLICY = 0x63A; - static constexpr uint32_t PERF_STATUS = 0x63B; - }; + static bool IsPackageName(const std::string& name); + static std::optional ParseSocketIndex(const std::string& name); + static uint64_t DeltaEnergyUj(uint64_t now, uint64_t last, + uint64_t max_range_uj); - struct PP1 { - static constexpr uint32_t POWER_LIMIT = 0x640; - static constexpr uint32_t ENERGY_STATUS = 0x641; - static constexpr uint32_t POLICY = 0x642; - }; + std::vector DiscoverPackages() const; + bool ReadU64File(const std::string& path, uint64_t* out) const; + bool ReadNameFile(const std::string& path, std::string* out) const; - struct DRAM { - static constexpr uint32_t POWER_LIMIT = 0x618; - static constexpr uint32_t ENERGY_STATUS = 0x619; - static constexpr uint32_t PERF_STATUS = 0x61B; - static constexpr uint32_t POWER_INFO = 0x61C; - }; - }; + std::vector requested_metrics_; + bool initialized_ = false; + PackageZone active_; + uint64_t last_energy_uj_ = 0; }; } // namespace collectors From 6dd649e7dde0c52b7c2d868dc2a07344999faf16 Mon Sep 17 00:00:00 2001 From: Patryk Przybysz Date: Sun, 2 Aug 2026 23:05:03 +0200 Subject: [PATCH 2/2] refactor(agent): use filesystem and regex for powercap discovery Replace dirent/access with std::filesystem directory iteration, validate energy_uj readability via ifstream in DiscoverPackages, store zone paths as std::filesystem::path, and parse package-N names with std::regex. --- .../agent/src/collectors/rapl_collector.cc | 71 ++++++++----------- sources/agent/src/collectors/rapl_collector.h | 7 +- 2 files changed, 32 insertions(+), 46 deletions(-) diff --git a/sources/agent/src/collectors/rapl_collector.cc b/sources/agent/src/collectors/rapl_collector.cc index 6a56c4a..aa8264b 100644 --- a/sources/agent/src/collectors/rapl_collector.cc +++ b/sources/agent/src/collectors/rapl_collector.cc @@ -1,14 +1,10 @@ #include "rapl_collector.h" -#include -#include - #include -#include #include -#include #include #include +#include namespace volta { namespace agent { @@ -17,31 +13,25 @@ namespace collectors { namespace { constexpr const char* kPowercapRoot = "/sys/class/powercap"; +constexpr double kMicrojoulesPerJoule = 1.0e6; -bool StartsWith(const std::string& s, const char* prefix) { - const size_t n = std::strlen(prefix); - return s.size() >= n && s.compare(0, n, prefix) == 0; -} +const std::regex kPackageNameRegex(R"(^package(-[0-9]+)?$)"); +const std::regex kPackageIndexRegex(R"(^package-([0-9]+)$)"); } // namespace bool RaplCollector::IsPackageName(const std::string& name) { - if (name == "package") return true; - if (!StartsWith(name, "package-")) return false; - const char* p = name.c_str() + 8; - if (*p == '\0') return false; - while (*p) { - if (!std::isdigit(static_cast(*p))) return false; - ++p; - } - return true; + return std::regex_match(name, kPackageNameRegex); } std::optional RaplCollector::ParseSocketIndex(const std::string& name) { if (name == "package") return 0; - if (!StartsWith(name, "package-")) return std::nullopt; + std::smatch match; + if (!std::regex_match(name, match, kPackageIndexRegex)) { + return std::nullopt; + } try { - return std::stoi(name.substr(8)); + return std::stoi(match[1].str()); } catch (...) { return std::nullopt; } @@ -56,7 +46,8 @@ uint64_t RaplCollector::DeltaEnergyUj(uint64_t now, uint64_t last, return (std::numeric_limits::max() - last) + now + 1; } -bool RaplCollector::ReadU64File(const std::string& path, uint64_t* out) const { +bool RaplCollector::ReadU64File(const std::filesystem::path& path, + uint64_t* out) const { std::ifstream in(path); if (!in) return false; uint64_t v = 0; @@ -66,7 +57,7 @@ bool RaplCollector::ReadU64File(const std::string& path, uint64_t* out) const { return true; } -bool RaplCollector::ReadNameFile(const std::string& path, +bool RaplCollector::ReadNameFile(const std::filesystem::path& path, std::string* out) const { std::ifstream in(path); if (!in) return false; @@ -83,19 +74,20 @@ bool RaplCollector::ReadNameFile(const std::string& path, std::vector RaplCollector::DiscoverPackages() const { std::vector packages; - DIR* d = opendir(kPowercapRoot); - if (!d) return packages; + std::error_code ec; + const std::filesystem::path root(kPowercapRoot); + if (!std::filesystem::is_directory(root, ec)) return packages; - while (dirent* ent = readdir(d)) { - if (ent->d_name[0] == '.') continue; - const std::string zone_dir = std::string(kPowercapRoot) + "/" + ent->d_name; - const std::string name_path = zone_dir + "/name"; - const std::string energy_path = zone_dir + "/energy_uj"; + for (const auto& entry : std::filesystem::directory_iterator(root, ec)) { + if (!entry.is_directory()) continue; - if (access(energy_path.c_str(), F_OK) != 0) continue; + const auto zone_dir = entry.path(); + const auto energy_path = zone_dir / "energy_uj"; + std::ifstream energy_in(energy_path); + if (!energy_in) continue; std::string name; - if (!ReadNameFile(name_path, &name)) continue; + if (!ReadNameFile(zone_dir / "name", &name)) continue; if (!IsPackageName(name)) continue; PackageZone z; @@ -104,11 +96,10 @@ std::vector RaplCollector::DiscoverPackages() auto sock = ParseSocketIndex(name); z.socket_index = sock.value_or(-1); uint64_t max_range = 0; - (void)ReadU64File(zone_dir + "/max_energy_range_uj", &max_range); + (void)ReadU64File(zone_dir / "max_energy_range_uj", &max_range); z.max_energy_range_uj = max_range; packages.push_back(std::move(z)); } - closedir(d); std::sort(packages.begin(), packages.end(), [](const PackageZone& a, const PackageZone& b) { @@ -119,13 +110,7 @@ std::vector RaplCollector::DiscoverPackages() return packages; } -bool RaplCollector::IsSupported() { - const auto packages = DiscoverPackages(); - for (const auto& p : packages) { - if (access((p.path + "/energy_uj").c_str(), R_OK) == 0) return true; - } - return false; -} +bool RaplCollector::IsSupported() { return !DiscoverPackages().empty(); } bool RaplCollector::Init() { initialized_ = false; @@ -141,7 +126,7 @@ bool RaplCollector::Init() { } uint64_t energy = 0; - if (!ReadU64File(it->path + "/energy_uj", &energy)) return false; + if (!ReadU64File(it->path / "energy_uj", &energy)) return false; active_ = *it; last_energy_uj_ = energy; @@ -163,7 +148,7 @@ std::vector RaplCollector::Collect() { } uint64_t energy = 0; - if (!ReadU64File(active_.path + "/energy_uj", &energy)) return {}; + if (!ReadU64File(active_.path / "energy_uj", &energy)) return {}; const uint64_t delta_uj = DeltaEnergyUj(energy, last_energy_uj_, active_.max_energy_range_uj); @@ -175,7 +160,7 @@ std::vector RaplCollector::Collect() { cpu_id.set_socket_index(active_.socket_index >= 0 ? active_.socket_index : 0); cpu_id.set_core_index(0); m.devId = DeviceId{std::move(cpu_id)}; - m.value = static_cast(delta_uj) / 1.0e6; + m.value = static_cast(delta_uj) / kMicrojoulesPerJoule; m.timestamp = std::chrono::system_clock::now().time_since_epoch().count(); return {m}; } diff --git a/sources/agent/src/collectors/rapl_collector.h b/sources/agent/src/collectors/rapl_collector.h index 13daf6c..3a06771 100644 --- a/sources/agent/src/collectors/rapl_collector.h +++ b/sources/agent/src/collectors/rapl_collector.h @@ -2,6 +2,7 @@ #define VOLTA_AGENT_SRC_COLLECTORS_RAPL_COLLECTOR_H_ #include +#include #include #include #include @@ -22,7 +23,7 @@ class RaplCollector final : public RegisteredCollector { private: struct PackageZone { - std::string path; + std::filesystem::path path; std::string name; int socket_index = 0; uint64_t max_energy_range_uj = 0; @@ -34,8 +35,8 @@ class RaplCollector final : public RegisteredCollector { uint64_t max_range_uj); std::vector DiscoverPackages() const; - bool ReadU64File(const std::string& path, uint64_t* out) const; - bool ReadNameFile(const std::string& path, std::string* out) const; + bool ReadU64File(const std::filesystem::path& path, uint64_t* out) const; + bool ReadNameFile(const std::filesystem::path& path, std::string* out) const; std::vector requested_metrics_; bool initialized_ = false;