#include "fgc/HostMetrics.h" #include #include #include #include #include #include #include #include namespace fgc::hostmetrics { namespace fs = std::filesystem; namespace { std::string readFile(const fs::path& p) { std::ifstream f(p); if (!f) return {}; std::ostringstream ss; ss << f.rdbuf(); return ss.str(); } } // namespace double parseMilliCelsius(const std::string& s) { try { return std::stod(s) / 1000.0; // sysfs reports milli-degrees C } catch (...) { return 0.0; } } MemInfo parseMeminfo(const std::string& text) { MemInfo m; std::istringstream in(text); std::string line; double mem_total_kb = 0, mem_avail_kb = 0, swap_total_kb = 0, swap_free_kb = 0; bool have_total = false, have_avail = false; while (std::getline(in, line)) { std::istringstream ls(line); std::string key, val, unit; if (!(ls >> key >> val)) continue; double kb = 0; try { kb = std::stod(val); } catch (...) { continue; } if (key == "MemTotal:") { mem_total_kb = kb; have_total = true; } else if (key == "MemAvailable:") { mem_avail_kb = kb; have_avail = true; } else if (key == "SwapTotal:") swap_total_kb = kb; else if (key == "SwapFree:") swap_free_kb = kb; } m.ok = have_total && have_avail; m.total_mb = mem_total_kb / 1024.0; m.avail_mb = mem_avail_kb / 1024.0; m.swap_used_mb = (swap_total_kb - swap_free_kb) / 1024.0; return m; } CpuTimes parseProcStatCpu(const std::string& text) { CpuTimes t; std::istringstream in(text); std::string line; while (std::getline(in, line)) { std::istringstream ls(line); std::string key; if (!(ls >> key) || key != "cpu") continue; // user nice system idle iowait irq softirq steal [guest guest_nice] // guest time is already inside user/nice, so it is not added again. long long v[8] = {0, 0, 0, 0, 0, 0, 0, 0}; int n = 0; while (n < 8 && (ls >> v[n])) ++n; if (n < 4) return t; const long long idle = v[3] + v[4]; long long total = 0; for (int i = 0; i < n; ++i) total += v[i]; t.ok = true; t.total = total; t.busy = total - idle; return t; } return t; } double busyPercent(const CpuTimes& before, const CpuTimes& after) { if (!before.ok || !after.ok) return -1.0; const long long dt = after.total - before.total; if (dt <= 0) return -1.0; const double pct = 100.0 * static_cast(after.busy - before.busy) / static_cast(dt); return pct < 0.0 ? 0.0 : (pct > 100.0 ? 100.0 : pct); } namespace { fs::path under(const std::string& root, const char* path) { return fs::path(root.empty() ? "/" : root) / path; } // cpu0, cpu1, ... in number order - not cpufreq/, cpuidle/ and the rest. std::vector cpuDirs(const std::string& root) { std::vector> found; std::error_code ec; for (auto& e : fs::directory_iterator(under(root, "sys/devices/system/cpu"), ec)) { const auto name = e.path().filename().string(); if (name.size() < 4 || name.rfind("cpu", 0) != 0) continue; bool digits = true; for (size_t i = 3; i < name.size(); ++i) if (name[i] < '0' || name[i] > '9') digits = false; if (digits) found.emplace_back(std::stoi(name.substr(3)), e.path()); } std::sort(found.begin(), found.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); std::vector out; for (auto& f : found) out.push_back(f.second); return out; } // A sysfs number, or -1 when the file is missing or unreadable. long long readNumber(const fs::path& p) { std::string s = readFile(p); if (s.empty()) return -1; try { return std::stoll(s); } catch (...) { return -1; } } } // namespace CpuFreqInfo readCpuFreq(const std::string& root) { CpuFreqInfo f; double sum = 0.0; for (const auto& dir : cpuDirs(root)) { const long long cur = readNumber(dir / "cpufreq/scaling_cur_freq"); // kHz if (cur < 0) continue; f.core_mhz.push_back(static_cast(cur) / 1000.0); sum += static_cast(cur) / 1000.0; const long long hw = readNumber(dir / "cpufreq/cpuinfo_max_freq"); if (hw > 0) f.hw_max_mhz = std::max(f.hw_max_mhz, static_cast(hw) / 1000.0); const long long cap = readNumber(dir / "cpufreq/scaling_max_freq"); if (cap > 0) { const double mhz = static_cast(cap) / 1000.0; f.cap_mhz = f.cap_mhz > 0.0 ? std::min(f.cap_mhz, mhz) : mhz; } } if (!f.core_mhz.empty()) { f.ok = true; f.mean_mhz = sum / static_cast(f.core_mhz.size()); } return f; } ThrottleCount readThrottleCount(const std::string& root) { ThrottleCount c; bool package_seen = false; for (const auto& dir : cpuDirs(root)) { // Every core shows the same package count; take it once. if (!package_seen) { const long long pkg = readNumber(dir / "thermal_throttle/package_throttle_count"); if (pkg >= 0) { package_seen = true; c.ok = true; c.events += pkg; } } const long long core = readNumber(dir / "thermal_throttle/core_throttle_count"); if (core >= 0) { c.ok = true; c.events += core; } } return c; } CpuTimes readCpuTimes(const std::string& root) { // A fake tree without its own proc/stat uses the real one: how busy the CPU // is stays real in a simulation (raise it with tools/thermal-sim/heat-load.sh). const fs::path p = under(root, "proc/stat"); std::error_code ec; return parseProcStatCpu(readFile(fs::exists(p, ec) ? p : fs::path("/proc/stat"))); } ThermalInfo readThermal(const std::string& root_dir) { ThermalInfo t; const fs::path root = under(root_dir, "sys/class/thermal"); std::error_code ec; if (!fs::exists(root, ec)) return t; bool have_pkg = false; for (auto& e : fs::directory_iterator(root, ec)) { const auto name = e.path().filename().string(); if (name.rfind("thermal_zone", 0) != 0) continue; std::string temp_s = readFile(e.path() / "temp"); if (temp_s.empty()) continue; double c = parseMilliCelsius(temp_s); std::string type = readFile(e.path() / "type"); // strip trailing newline while (!type.empty() && (type.back() == '\n' || type.back() == '\r')) type.pop_back(); if (type.empty()) type = name; if (type == "x86_pkg_temp" && (!have_pkg || c > t.cpu_temp_c)) { have_pkg = true; t.cpu_temp_c = c; t.cpu_zone = type; } if (!t.ok || c > t.max_temp_c) { t.ok = true; t.max_temp_c = c; t.hottest_zone = type; } } if (t.ok && !have_pkg) { t.cpu_temp_c = t.max_temp_c; t.cpu_zone = t.hottest_zone; } return t; } DiskInfo readDisk(const std::string& path) { DiskInfo d; d.path = path; struct statvfs vfs {}; if (statvfs(path.c_str(), &vfs) != 0) return d; const double frsize = static_cast(vfs.f_frsize); const double total = static_cast(vfs.f_blocks) * frsize; const double avail = static_cast(vfs.f_bavail) * frsize; const double gb = 1024.0 * 1024.0 * 1024.0; d.ok = true; d.total_gb = total / gb; d.free_gb = avail / gb; d.used_pct = total > 0 ? 100.0 * (total - avail) / total : 0.0; return d; } MemInfo readMeminfo() { return parseMeminfo(readFile("/proc/meminfo")); } LoadInfo readLoad() { LoadInfo l; double avg[3] = {0, 0, 0}; if (getloadavg(avg, 3) == 3) { l.ok = true; l.load1 = avg[0]; l.load5 = avg[1]; l.load15 = avg[2]; } long n = sysconf(_SC_NPROCESSORS_ONLN); l.cpus = (n > 0) ? static_cast(n) : 0; return l; } } // namespace fgc::hostmetrics