From 882f49c2fef80492cedc57db49b4ff836e79fcfd Mon Sep 17 00:00:00 2001 From: Maximilian Lindner <46794237+SR4ven@users.noreply.github.com> Date: Sat, 26 Apr 2025 22:16:07 +0200 Subject: [PATCH 1/3] Add support for cgroup2 in CPU quota parsing On systems using cgroup2, CPU quota auto detection would fail. Now, cgroup v2 cpu.max will be parsed with a fallback to cgroup v1 --- src/util.cc | 103 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 18 deletions(-) diff --git a/src/util.cc b/src/util.cc index 7000a6ad..ab632151 100644 --- a/src/util.cc +++ b/src/util.cc @@ -39,6 +39,7 @@ #include #endif +#include #include #if defined(__APPLE__) || defined(__FreeBSD__) @@ -689,16 +690,31 @@ map ParseMountInfo(map& subsystems) { MountPoint mp; if (!mp.parse(line)) continue; - if (mp.fsType != "cgroup") - continue; - for (size_t i = 0; i < mp.superOptions.size(); i++) { - string opt = mp.superOptions[i].AsString(); - map::iterator subsys = subsystems.find(opt); - if (subsys == subsystems.end()) + if (mp.fsType == "cgroup") { + for (size_t i = 0; i < mp.superOptions.size(); i++) { + string opt = mp.superOptions[i].AsString(); + map::iterator subsys = subsystems.find(opt); + if (subsys == subsystems.end()) + continue; + string newPath = mp.translate(subsys->second.name); + if (!newPath.empty()) + cgroups.insert(make_pair(opt, newPath)); + } + } else if (mp.fsType == "cgroup2") { + // Find cgroup2 entry in format "0::/path/to/cgroup" + auto subsys = std::find_if(subsystems.begin(), subsystems.end(), + [](const auto& sys) { + return sys.first == "" && sys.second.id == 0; + }); + if (subsys == subsystems.end()) { continue; - string newPath = mp.translate(subsys->second.name); - if (!newPath.empty()) - cgroups.insert(make_pair(opt, newPath)); + } + std::string path = mp.mountPoint.AsString(); + if (subsys->second.name != "/") { + // Append the relative path for the cgroup to the mount point + path.append(subsys->second.name); + } + cgroups.insert(std::make_pair("cgroup2", path)); } } return cgroups; @@ -722,23 +738,74 @@ map ParseSelfCGroup() { return cgroups; } -int ParseCPUFromCGroup() { - map subsystems = ParseSelfCGroup(); - map cgroups = ParseMountInfo(subsystems); - map::iterator cpu = cgroups.find("cpu"); - if (cpu == cgroups.end()) - return -1; - std::pair quota = readCount(cpu->second + "/cpu.cfs_quota_us"); +int ParseCgroupV1(std::string& path) { + std::pair quota = readCount(path + "/cpu.cfs_quota_us"); if (!quota.second || quota.first == -1) return -1; - std::pair period = - readCount(cpu->second + "/cpu.cfs_period_us"); + std::pair period = readCount(path + "/cpu.cfs_period_us"); if (!period.second) return -1; if (period.first == 0) return -1; return quota.first / period.first; } + +int ParseCgroupV2(std::string& path) { + // Read CPU quota from cgroup v2 + std::ifstream cpu_max(path + "/cpu.max"); + if (!cpu_max.is_open()) { + return -1; + } + std::string max_line; + if (!std::getline(cpu_max, max_line) || max_line.empty()) { + return -1; + } + // Format is "quota period" or "max period" + size_t space_pos = max_line.find(' '); + if (space_pos == string::npos) { + return -1; + } + std::string quota_str = max_line.substr(0, space_pos); + std::string period_str = max_line.substr(space_pos + 1); + if (quota_str == "max") { + return -1; // No CPU limit set + } + // Convert quota string to integer + char* quota_end = nullptr; + errno = 0; + int64_t quota = strtoll(quota_str.c_str(), "a_end, 10); + // Check for conversion errors + if (errno == ERANGE || quota_end == quota_str.c_str() || *quota_end != '\0' || + quota <= 0) { + return -1; + } + // Convert period string to integer + char* period_end = nullptr; + errno = 0; + int64_t period = strtoll(period_str.c_str(), &period_end, 10); + // Check for conversion errors + if (errno == ERANGE || period_end == period_str.c_str() || + *period_end != '\0' || period <= 0) { + return -1; + } + return quota / period; +} + +int ParseCPUFromCGroup() { + map subsystems = ParseSelfCGroup(); + map cgroups = ParseMountInfo(subsystems); + const auto cgroup2 = cgroups.find("cgroup2"); + const auto cpu = cgroups.find("cpu"); + + // Prefer cgroup v2 if both v1 and v2 should be present + if (cgroup2 != cgroups.end()) { + return ParseCgroupV2(cgroup2->second); + } + if (cpu != cgroups.end()) { + return ParseCgroupV1(cpu->second); + } + return -1; +} #endif int GetProcessorCount() { From d9b1efd20893113dbc29c15398a71897c8152835 Mon Sep 17 00:00:00 2001 From: Maximilian Lindner <46794237+SR4ven@users.noreply.github.com> Date: Wed, 7 May 2025 21:36:53 +0200 Subject: [PATCH 2/3] Add std:: namespace, braces and replace insert with emplace --- src/util.cc | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/util.cc b/src/util.cc index ab632151..fe093a4d 100644 --- a/src/util.cc +++ b/src/util.cc @@ -692,13 +692,15 @@ map ParseMountInfo(map& subsystems) { continue; if (mp.fsType == "cgroup") { for (size_t i = 0; i < mp.superOptions.size(); i++) { - string opt = mp.superOptions[i].AsString(); - map::iterator subsys = subsystems.find(opt); - if (subsys == subsystems.end()) + std::string opt = mp.superOptions[i].AsString(); + auto subsys = subsystems.find(opt); + if (subsys == subsystems.end()) { continue; - string newPath = mp.translate(subsys->second.name); - if (!newPath.empty()) - cgroups.insert(make_pair(opt, newPath)); + } + std::string newPath = mp.translate(subsys->second.name); + if (!newPath.empty()) { + cgroups.emplace(opt, newPath); + } } } else if (mp.fsType == "cgroup2") { // Find cgroup2 entry in format "0::/path/to/cgroup" @@ -714,7 +716,7 @@ map ParseMountInfo(map& subsystems) { // Append the relative path for the cgroup to the mount point path.append(subsys->second.name); } - cgroups.insert(std::make_pair("cgroup2", path)); + cgroups.emplace("cgroup2", path); } } return cgroups; @@ -792,16 +794,15 @@ int ParseCgroupV2(std::string& path) { } int ParseCPUFromCGroup() { - map subsystems = ParseSelfCGroup(); - map cgroups = ParseMountInfo(subsystems); - const auto cgroup2 = cgroups.find("cgroup2"); - const auto cpu = cgroups.find("cpu"); + std::map subsystems = ParseSelfCGroup(); + std::map cgroups = ParseMountInfo(subsystems); // Prefer cgroup v2 if both v1 and v2 should be present - if (cgroup2 != cgroups.end()) { + if (const auto cgroup2 = cgroups.find("cgroup2"); cgroup2 != cgroups.end()) { return ParseCgroupV2(cgroup2->second); } - if (cpu != cgroups.end()) { + + if (const auto cpu = cgroups.find("cpu"); cpu != cgroups.end()) { return ParseCgroupV1(cpu->second); } return -1; From cb8570867eac073bce3e28325998c93e1618ebd4 Mon Sep 17 00:00:00 2001 From: Maximilian Lindner <46794237+SR4ven@users.noreply.github.com> Date: Wed, 7 May 2025 22:43:32 +0200 Subject: [PATCH 3/3] Use auto --- src/util.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.cc b/src/util.cc index fe093a4d..d88a6ba2 100644 --- a/src/util.cc +++ b/src/util.cc @@ -794,8 +794,8 @@ int ParseCgroupV2(std::string& path) { } int ParseCPUFromCGroup() { - std::map subsystems = ParseSelfCGroup(); - std::map cgroups = ParseMountInfo(subsystems); + auto subsystems = ParseSelfCGroup(); + auto cgroups = ParseMountInfo(subsystems); // Prefer cgroup v2 if both v1 and v2 should be present if (const auto cgroup2 = cgroups.find("cgroup2"); cgroup2 != cgroups.end()) {