fwt_software/src/core/HostMetrics.cpp

254 lines
8.1 KiB
C++

#include "fgc/HostMetrics.h"
#include <algorithm>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
#include <sys/statvfs.h>
#include <unistd.h>
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<double>(after.busy - before.busy) / static_cast<double>(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<fs::path> cpuDirs(const std::string& root) {
std::vector<std::pair<int, fs::path>> 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<fs::path> 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<double>(cur) / 1000.0);
sum += static_cast<double>(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<double>(hw) / 1000.0);
const long long cap = readNumber(dir / "cpufreq/scaling_max_freq");
if (cap > 0) {
const double mhz = static_cast<double>(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<double>(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<double>(vfs.f_frsize);
const double total = static_cast<double>(vfs.f_blocks) * frsize;
const double avail = static_cast<double>(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<int>(n) : 0;
return l;
}
} // namespace fgc::hostmetrics