Files
asepharyana 1cdb82a76f Sync config from arch
- hypr/apps.lua
- hypr/autostart.lua
- hypr/envs.lua
- hypr/hyprland.lua
- hypr/hyprsunset.conf
- hypr/input.lua
- hypr/looknfeel.lua
- hypr/omasettings.lua
- hypr/xdph.conf
- omarchy/branding/about.txt
- omarchy/branding/screensaver.txt
- omarchy/extensions/omarchy-menu.jsonc
- omarchy/hooks/battery-low.d/play-warning-sound.sample
- omarchy/hooks/font-set.d/show-font-notification.sample
- omarchy/hooks/post-boot.d/weather.sample
- omarchy/hooks/post-update.d/install-voxtype.hook
- omarchy/hooks/post-update.d/setup-agent.hook
- omarchy/hooks/post-update.d/setup-fingerprint.hook
- omarchy/hooks/post-update.d/show-update-notification.sample
- omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample
- omarchy/hooks/theme-set.d/show-theme-notification.sample
- omarchy/shell.json
- omarchy/shell.toml
- omarchy/theme.name
- omarchy/themes/azure-glow/README.md
- omarchy/themes/azure-glow/alacritty.toml
- omarchy/themes/azure-glow/btop.theme
- omarchy/themes/azure-glow/hyprland.conf
- omarchy/themes/azure-glow/hyprlock.conf
- omarchy/themes/azure-glow/icons.theme
- … 269 more
2026-09-23 15:19:12 +07:00

2071 lines
66 KiB
C++

#include <algorithm>
#include <array>
#include <cctype>
#include <cerrno>
#include <chrono>
#include <climits>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <dirent.h>
#include <dlfcn.h>
#include <fstream>
#include <glob.h>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <limits>
#include <mntent.h>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <unistd.h>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
namespace {
using Clock = std::chrono::steady_clock;
constexpr std::uint64_t kKernelThreadFlag = 0x00200000;
struct Paths {
std::string proc;
std::string sys;
std::string passwd;
std::string root;
std::string udev_data;
std::string nvidia_fixture;
std::string statfs_fixture;
};
std::string EnvOr(const char *name, const char *fallback) {
const char *value = std::getenv(name);
return value && *value ? value : fallback;
}
Paths UserPaths() {
return {
EnvOr("OMARCHY_SYSTEM_STATS_PROC_PATH", "/proc"),
EnvOr("OMARCHY_SYSTEM_STATS_SYS_PATH", "/sys"),
EnvOr("OMARCHY_SYSTEM_STATS_PASSWD_PATH", "/etc/passwd"),
EnvOr("OMARCHY_SYSTEM_STATS_ROOT_PATH", "/"),
EnvOr("OMARCHY_SYSTEM_STATS_UDEV_DATA_PATH", "/run/udev/data/+dmi:id"),
EnvOr("OMARCHY_SYSTEM_STATS_NVIDIA_FIXTURE_PATH", ""),
EnvOr("OMARCHY_SYSTEM_STATS_STATFS_FIXTURE_PATH", ""),
};
}
Paths PowerPaths() {
Paths paths = UserPaths();
if (geteuid() == 0) {
paths.proc = "/proc";
paths.sys = "/sys";
paths.passwd = "/etc/passwd";
paths.root = "/";
paths.udev_data = "/run/udev/data/+dmi:id";
paths.nvidia_fixture.clear();
paths.statfs_fixture.clear();
}
return paths;
}
std::string Join(const std::string &left, const std::string &right) {
if (left.empty())
return right;
if (right.empty())
return left;
if (left.back() == '/')
return left + (right.front() == '/' ? right.substr(1) : right);
return left + (right.front() == '/' ? right : "/" + right);
}
bool IsDigits(std::string_view value) {
return !value.empty() &&
std::all_of(value.begin(), value.end(),
[](unsigned char ch) { return std::isdigit(ch); });
}
std::string Trim(std::string value) {
const auto first = value.find_first_not_of(" \t\r\n");
if (first == std::string::npos)
return "";
const auto last = value.find_last_not_of(" \t\r\n");
return value.substr(first, last - first + 1);
}
std::string Sanitize(std::string value) {
for (char &ch : value) {
if (ch == '\t' || ch == '\r' || ch == '\n')
ch = ' ';
}
return Trim(std::move(value));
}
std::string Lowercase(std::string value) {
std::transform(value.begin(), value.end(), value.begin(),
[](unsigned char character) {
return static_cast<char>(std::tolower(character));
});
return value;
}
bool StartsWith(std::string_view value, std::string_view prefix) {
return value.size() >= prefix.size() &&
value.substr(0, prefix.size()) == prefix;
}
bool EndsWith(std::string_view value, std::string_view suffix) {
return value.size() >= suffix.size() &&
value.substr(value.size() - suffix.size()) == suffix;
}
std::optional<std::string> ReadText(const std::string &path) {
std::ifstream stream(path, std::ios::binary);
if (!stream)
return std::nullopt;
std::ostringstream output;
output << stream.rdbuf();
if (!stream.good() && !stream.eof())
return std::nullopt;
return output.str();
}
std::optional<std::string> ReadLine(const std::string &path) {
std::ifstream stream(path);
std::string line;
if (!stream || !std::getline(stream, line))
return std::nullopt;
return Trim(std::move(line));
}
std::optional<std::uint64_t> ParseUnsigned(std::string_view value,
int base = 10) {
if (value.empty() || base < 2 || base > 16 || value.front() == '-')
return std::nullopt;
std::size_t index = value.front() == '+' ? 1 : 0;
if (base == 16 && value.size() >= index + 2 && value[index] == '0' &&
(value[index + 1] == 'x' || value[index + 1] == 'X'))
index += 2;
if (index >= value.size())
return std::nullopt;
std::uint64_t parsed = 0;
for (; index < value.size(); ++index) {
const unsigned char character = value[index];
unsigned int digit = 0;
if (character >= '0' && character <= '9')
digit = character - '0';
else if (character >= 'a' && character <= 'f')
digit = character - 'a' + 10;
else if (character >= 'A' && character <= 'F')
digit = character - 'A' + 10;
else
return std::nullopt;
if (digit >= static_cast<unsigned int>(base) ||
parsed > (std::numeric_limits<std::uint64_t>::max() - digit) /
static_cast<unsigned int>(base))
return std::nullopt;
parsed = parsed * static_cast<unsigned int>(base) + digit;
}
return parsed;
}
std::optional<double> ParseDouble(std::string_view value) {
if (value.empty())
return std::nullopt;
std::string input(value);
char *end = nullptr;
errno = 0;
const double parsed = std::strtod(input.c_str(), &end);
if (errno || end == input.c_str() || *end != '\0' || !std::isfinite(parsed))
return std::nullopt;
return parsed;
}
std::optional<std::uint64_t> ReadUnsigned(const std::string &path) {
const auto line = ReadLine(path);
return line ? ParseUnsigned(*line) : std::nullopt;
}
std::string UptimeSample(const Paths &paths) {
const auto line = ReadLine(Join(paths.proc, "uptime"));
if (!line)
return "0";
std::istringstream fields(*line);
std::string sample;
fields >> sample;
return ParseDouble(sample) ? sample : "0";
}
bool Exists(const std::string &path) {
struct stat status{};
return stat(path.c_str(), &status) == 0;
}
bool IsDirectory(const std::string &path) {
struct stat status{};
return stat(path.c_str(), &status) == 0 && S_ISDIR(status.st_mode);
}
std::vector<std::string> DirectoryNames(const std::string &path) {
std::vector<std::string> names;
DIR *directory = opendir(path.c_str());
if (!directory)
return names;
while (dirent *entry = readdir(directory)) {
std::string name(entry->d_name);
if (name != "." && name != "..")
names.push_back(std::move(name));
}
closedir(directory);
return names;
}
std::vector<std::string> GlobPaths(const std::string &pattern) {
glob_t matches{};
std::vector<std::string> paths;
if (glob(pattern.c_str(), GLOB_NOSORT, nullptr, &matches) == 0) {
paths.reserve(matches.gl_pathc);
for (std::size_t index = 0; index < matches.gl_pathc; ++index)
paths.emplace_back(matches.gl_pathv[index]);
}
globfree(&matches);
return paths;
}
std::string RealPath(const std::string &path) {
std::array<char, PATH_MAX> resolved{};
return realpath(path.c_str(), resolved.data()) ? resolved.data() : "";
}
std::string BaseName(const std::string &path) {
const auto slash = path.find_last_of('/');
return slash == std::string::npos ? path : path.substr(slash + 1);
}
std::vector<std::string> SplitWhitespace(const std::string &value) {
std::istringstream stream(value);
std::vector<std::string> fields;
std::string field;
while (stream >> field)
fields.push_back(std::move(field));
return fields;
}
std::unordered_set<int> ParseCpuList(const std::string &list) {
std::unordered_set<int> cpus;
const std::string value = Trim(list);
std::size_t index = 0;
while (index < value.size()) {
if (value[index] == ',' || value[index] == ' ') {
++index;
continue;
}
if (!std::isdigit(static_cast<unsigned char>(value[index]))) {
++index;
continue;
}
int start = 0;
while (index < value.size() &&
std::isdigit(static_cast<unsigned char>(value[index]))) {
start = start * 10 + (value[index] - '0');
++index;
}
int end = start;
if (index < value.size() && value[index] == '-') {
++index;
end = 0;
bool any = false;
while (index < value.size() &&
std::isdigit(static_cast<unsigned char>(value[index]))) {
end = end * 10 + (value[index] - '0');
any = true;
++index;
}
if (!any)
end = start;
}
if (end < start)
std::swap(start, end);
for (int cpu = start; cpu <= end && cpu < 4096; ++cpu)
cpus.insert(cpu);
}
return cpus;
}
bool CpuListContains(const std::string &list, int cpu) {
return ParseCpuList(list).count(cpu) != 0;
}
std::uint64_t PositiveEnv(const char *name, std::uint64_t fallback) {
const char *value = std::getenv(name);
if (!value)
return fallback;
const auto parsed = ParseUnsigned(value);
return parsed && *parsed > 0 ? *parsed : fallback;
}
std::uint64_t SystemValue(int name, std::uint64_t fallback) {
const long value = sysconf(name);
return value > 0 ? static_cast<std::uint64_t>(value) : fallback;
}
class ResourceCollector {
public:
explicit ResourceCollector(Paths paths) : paths_(std::move(paths)) {}
void Collect(std::ostream &output) {
DiscoverTopology();
output << "schema\tactivity-resources\t1\n";
output << "sample\t" << UptimeSample(paths_) << '\n';
EmitMemorySpeed(output);
EmitCpu(output);
EmitCpuTopology(output);
EmitMemory(output);
EmitTasks(output);
EmitNetwork(output);
EmitDisks(output);
EmitCpuFrequency(output);
}
void BarWidget(std::ostream &output) {
std::ifstream stat_stream(Join(paths_.proc, "stat"));
std::string line;
if (std::getline(stat_stream, line)) {
auto fields = SplitWhitespace(line);
if (fields.size() >= 5 && fields[0] == "cpu") {
std::uint64_t total = 0;
for (std::size_t index = 1; index < fields.size() && index <= 8;
++index)
total += ParseUnsigned(fields[index]).value_or(0);
const auto idle =
ParseUnsigned(fields[4]).value_or(0) +
(fields.size() > 5 ? ParseUnsigned(fields[5]).value_or(0) : 0);
output << "cpu\t" << idle << '\t' << total << '\n';
}
}
auto memory = MemoryValues();
const auto total = memory["MemTotal"];
const auto available = memory["MemAvailable"];
if (total > 0) {
const double percent =
static_cast<double>(total - std::min(total, available)) /
static_cast<double>(total) * 100.0;
output << "memory\t" << std::fixed << std::setprecision(2) << percent
<< "\n";
output.unsetf(std::ios::floatfield);
}
const auto load = ReadLine(Join(paths_.proc, "loadavg"));
if (load) {
const auto fields = SplitWhitespace(*load);
if (!fields.empty())
output << "load\t" << fields[0] << '\n';
}
}
private:
struct BlockDevice {
std::string name;
std::string path;
};
struct CpuTopo {
int id = -1;
int core_id = -1;
std::string cluster;
std::string l2;
std::string l3;
std::string domain;
std::string cls;
std::uint64_t max_khz = 0;
std::uint64_t capacity = 0;
};
Paths paths_;
bool topology_discovered_ = false;
bool memory_speed_checked_ = false;
int memory_speed_mts_ = -1;
std::vector<std::string> cpu_frequency_paths_;
std::vector<BlockDevice> block_devices_;
std::vector<CpuTopo> cpu_topo_;
void DiscoverTopology() {
if (topology_discovered_)
return;
topology_discovered_ = true;
const std::string frequency_root =
Join(paths_.sys, "devices/system/cpu/cpufreq");
for (const auto &name : DirectoryNames(frequency_root)) {
if (!StartsWith(name, "policy") ||
!IsDigits(std::string_view(name).substr(6)))
continue;
const std::string path =
Join(Join(frequency_root, name), "scaling_cur_freq");
if (Exists(path))
cpu_frequency_paths_.push_back(path);
}
const std::string block_root = Join(paths_.sys, "class/block");
for (const auto &name : DirectoryNames(block_root)) {
if (StartsWith(name, "loop") || StartsWith(name, "ram") ||
StartsWith(name, "zram"))
continue;
const std::string path = Join(block_root, name);
if (Exists(Join(path, "partition")))
continue;
if (!Exists(Join(path, "device/uevent")))
continue;
block_devices_.push_back({name, path});
}
std::sort(block_devices_.begin(), block_devices_.end(),
[](const BlockDevice &left, const BlockDevice &right) {
return left.name < right.name;
});
DiscoverCpuTopology();
}
static std::string CacheSharedList(const std::string &cpu_path, const char *level) {
const std::string cache = Join(cpu_path, "cache");
for (const auto &name : DirectoryNames(cache)) {
if (!StartsWith(name, "index"))
continue;
const std::string index = Join(cache, name);
if (ReadLine(Join(index, "level")).value_or("") != level)
continue;
if (ReadLine(Join(index, "type")).value_or("") != "Unified")
continue;
return ReadLine(Join(index, "shared_cpu_list")).value_or("");
}
return "";
}
void DiscoverCpuTopology() {
cpu_topo_.clear();
const std::string cpu_root = Join(paths_.sys, "devices/system/cpu");
const std::string core_list =
ReadLine(Join(paths_.sys, "devices/cpu_core/cpus")).value_or("");
const std::string atom_list =
ReadLine(Join(paths_.sys, "devices/cpu_atom/cpus")).value_or("");
const bool intel_hybrid = !core_list.empty() || !atom_list.empty();
for (const auto &name : DirectoryNames(cpu_root)) {
if (!StartsWith(name, "cpu") || !IsDigits(std::string_view(name).substr(3)))
continue;
const int id = static_cast<int>(ParseUnsigned(name.substr(3)).value_or(4096));
if (id >= 4096)
continue;
const std::string path = Join(cpu_root, name);
const std::string topo = Join(path, "topology");
if (!IsDirectory(topo))
continue;
if (Exists(Join(path, "online")) &&
ReadLine(Join(path, "online")).value_or("1") == "0")
continue;
CpuTopo cpu;
cpu.id = id;
cpu.core_id = static_cast<int>(
ParseUnsigned(ReadLine(Join(topo, "core_id")).value_or("")).value_or(id));
cpu.cluster = ReadLine(Join(topo, "cluster_cpus_list")).value_or("");
if (cpu.cluster.empty())
cpu.cluster = std::to_string(id);
cpu.l2 = CacheSharedList(path, "2");
cpu.l3 = CacheSharedList(path, "3");
cpu.domain = !cpu.l3.empty() ? cpu.l3 : (!cpu.l2.empty() ? cpu.l2 : cpu.cluster);
cpu.max_khz =
ParseUnsigned(ReadLine(Join(path, "cpufreq/cpuinfo_max_freq")).value_or(""))
.value_or(0);
cpu.capacity =
ParseUnsigned(ReadLine(Join(path, "cpu_capacity")).value_or("")).value_or(0);
cpu_topo_.push_back(std::move(cpu));
}
std::sort(cpu_topo_.begin(), cpu_topo_.end(),
[](const CpuTopo &left, const CpuTopo &right) { return left.id < right.id; });
std::unordered_set<std::string> p_l3;
for (const auto &cpu : cpu_topo_) {
if (intel_hybrid && CpuListContains(core_list, cpu.id) && !cpu.l3.empty())
p_l3.insert(cpu.l3);
}
for (auto &cpu : cpu_topo_) {
if (intel_hybrid && CpuListContains(core_list, cpu.id)) {
cpu.cls = "performance";
continue;
}
if (intel_hybrid && CpuListContains(atom_list, cpu.id)) {
cpu.cls = (cpu.l3.empty() || p_l3.count(cpu.l3) == 0) ? "lowpower"
: "efficiency";
continue;
}
}
ClassifyUnlabeledCpus();
}
void ClassifyUnlabeledCpus() {
std::vector<std::uint64_t> keys;
keys.reserve(cpu_topo_.size());
bool any_capacity = false;
for (const auto &cpu : cpu_topo_) {
if (!cpu.cls.empty())
continue;
if (cpu.capacity > 0)
any_capacity = true;
keys.push_back(cpu.capacity > 0 ? cpu.capacity : cpu.max_khz);
}
if (keys.empty())
return;
std::sort(keys.begin(), keys.end());
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
std::vector<std::uint64_t> groups;
for (const auto key : keys) {
if (groups.empty() || key > groups.back() * 112 / 100)
groups.push_back(key);
}
if (groups.size() <= 1) {
for (auto &cpu : cpu_topo_)
if (cpu.cls.empty())
cpu.cls = "performance";
return;
}
const std::uint64_t high = groups.back();
const std::uint64_t low = groups.front();
for (auto &cpu : cpu_topo_) {
if (!cpu.cls.empty())
continue;
const std::uint64_t key = any_capacity && cpu.capacity > 0 ? cpu.capacity
: cpu.max_khz;
std::uint64_t nearest = groups[0];
auto distance = [](std::uint64_t left, std::uint64_t right) {
return left > right ? left - right : right - left;
};
for (const auto group : groups) {
if (distance(key, group) < distance(key, nearest))
nearest = group;
}
if (nearest == high)
cpu.cls = "performance";
else if (nearest == low && groups.size() >= 3)
cpu.cls = "lowpower";
else
cpu.cls = "efficiency";
}
}
void EmitCpuTopology(std::ostream &output) const {
if (cpu_topo_.empty())
return;
for (const auto &cpu : cpu_topo_) {
output << "cpu-topo\t" << cpu.id << '\t'
<< (cpu.cls.empty() ? "performance" : cpu.cls) << '\t' << cpu.domain
<< '\t' << cpu.cluster << '\t' << cpu.core_id << '\t' << cpu.max_khz
<< '\n';
}
}
void EmitCpu(std::ostream &output) const {
std::ifstream stream(Join(paths_.proc, "stat"));
std::string line;
while (std::getline(stream, line)) {
const auto fields = SplitWhitespace(line);
if (fields.empty() || !StartsWith(fields[0], "cpu"))
break;
if (fields[0] != "cpu" &&
!IsDigits(std::string_view(fields[0]).substr(3)))
break;
std::uint64_t total = 0;
std::array<std::uint64_t, 8> counters{};
for (std::size_t index = 0; index < counters.size(); ++index) {
if (index + 1 < fields.size())
counters[index] = ParseUnsigned(fields[index + 1]).value_or(0);
total += counters[index];
}
output << "cpu\t" << fields[0] << '\t' << total << '\t'
<< counters[3] + counters[4] << '\n';
}
}
std::unordered_map<std::string, std::uint64_t> MemoryValues() const {
std::unordered_map<std::string, std::uint64_t> values;
std::ifstream stream(Join(paths_.proc, "meminfo"));
std::string line;
while (std::getline(stream, line)) {
std::istringstream fields(line);
std::string key;
std::uint64_t value = 0;
if (!(fields >> key >> value))
continue;
if (!key.empty() && key.back() == ':')
key.pop_back();
values[key] = value;
}
return values;
}
void EmitMemory(std::ostream &output) const {
const auto values = MemoryValues();
const auto get = [&values](const char *key) {
const auto found = values.find(key);
return found == values.end() ? std::uint64_t{0} : found->second;
};
output << "memory\t" << get("MemTotal") << '\t' << get("MemAvailable")
<< '\t' << get("SwapTotal") << '\t' << get("SwapFree") << '\t'
<< get("Cached") + get("SReclaimable") << '\n';
}
void EmitMemorySpeed(std::ostream &output) {
if (!memory_speed_checked_) {
memory_speed_checked_ = true;
int configured = 0;
int rated = 0;
std::ifstream stream(paths_.udev_data);
std::string line;
while (std::getline(stream, line)) {
if (StartsWith(line, "E:"))
line.erase(0, 2);
const auto equal = line.find('=');
if (equal == std::string::npos)
continue;
const std::string key = line.substr(0, equal);
const std::string value = line.substr(equal + 1);
int candidate = 0;
if (EndsWith(key, "_SPEED_MTS")) {
candidate = static_cast<int>(ParseUnsigned(value).value_or(0));
} else if (EndsWith(key, "_SPEED_GTS")) {
candidate = static_cast<int>(
std::lround(ParseDouble(value).value_or(0) * 1000.0));
}
if (candidate <= 0 || !StartsWith(key, "MEMORY_DEVICE_"))
continue;
if (key.find("_CONFIGURED_SPEED_") != std::string::npos) {
if (configured == 0 || candidate < configured)
configured = candidate;
} else if (key.find("_SPEED_") != std::string::npos) {
if (rated == 0 || candidate < rated)
rated = candidate;
}
}
memory_speed_mts_ =
configured > 0 ? configured : (rated > 0 ? rated : -1);
}
if (memory_speed_mts_ > 0)
output << "frequency\tmemory\t" << memory_speed_mts_ << "\tMT/s\n";
}
void EmitTasks(std::ostream &output) const {
const auto line = ReadLine(Join(paths_.proc, "loadavg"));
if (!line)
return;
const auto fields = SplitWhitespace(*line);
if (fields.size() < 4)
return;
const auto slash = fields[3].find('/');
if (slash == std::string::npos)
return;
output << "tasks\t" << fields[3].substr(0, slash) << '\t'
<< fields[3].substr(slash + 1) << '\n';
}
std::string DefaultInterface() const {
std::ifstream stream(Join(paths_.proc, "net/route"));
std::string line;
std::getline(stream, line);
std::string selected;
std::uint64_t selected_metric = std::numeric_limits<std::uint64_t>::max();
while (std::getline(stream, line)) {
const auto fields = SplitWhitespace(line);
if (fields.size() < 8 || fields[1] != "00000000" ||
fields[7] != "00000000")
continue;
const auto flags = ParseUnsigned(fields[3], 16);
const auto metric = ParseUnsigned(fields[6]);
if (!flags || !metric || !(*flags & 1))
continue;
if (*metric < selected_metric) {
selected = fields[0];
selected_metric = *metric;
}
}
return selected;
}
void EmitNetwork(std::ostream &output) const {
const std::string selected = DefaultInterface();
std::ifstream stream(Join(paths_.proc, "net/dev"));
std::string line;
std::getline(stream, line);
std::getline(stream, line);
while (std::getline(stream, line)) {
const auto colon = line.find(':');
if (colon == std::string::npos)
continue;
const std::string interface = Trim(line.substr(0, colon));
if (interface.empty() || interface == "lo")
continue;
const auto fields = SplitWhitespace(line.substr(colon + 1));
if (fields.size() < 16)
continue;
const std::string net_path =
Join(Join(paths_.sys, "class/net"), interface);
const std::string state =
ReadLine(Join(net_path, "operstate")).value_or("unknown");
const bool physical = Exists(Join(net_path, "device/uevent"));
output << "network\t" << interface << '\t' << fields[0] << '\t'
<< fields[8] << '\t' << Sanitize(state) << '\t'
<< (interface == selected ? 1 : 0) << '\t' << (physical ? 1 : 0)
<< '\n';
}
}
void EmitDisks(std::ostream &output) const {
for (const auto &device : block_devices_) {
const auto dev = ReadLine(Join(device.path, "dev"));
const auto stat = ReadLine(Join(device.path, "stat"));
if (!stat)
continue;
const auto fields = SplitWhitespace(*stat);
if (fields.size() < 7)
continue;
output << "disk\t" << (dev ? *dev : "") << '\t' << device.name << '\t'
<< fields[2] << '\t' << fields[6] << '\n';
}
}
void EmitCpuFrequency(std::ostream &output) const {
double total = 0;
std::size_t count = 0;
for (const auto &path : cpu_frequency_paths_) {
const auto value = ReadUnsigned(path);
if (!value)
continue;
total += static_cast<double>(*value) / 1000.0;
++count;
}
if (count == 0) {
std::ifstream stream(Join(paths_.proc, "cpuinfo"));
std::string line;
while (std::getline(stream, line)) {
if (!StartsWith(line, "cpu MHz"))
continue;
const auto colon = line.find(':');
if (colon == std::string::npos)
continue;
const auto value = ParseDouble(Trim(line.substr(colon + 1)));
if (value) {
total += *value;
++count;
}
}
}
if (count > 0)
output << "frequency\tcpu\t"
<< std::llround(total / static_cast<double>(count)) << "\tMHz\n";
}
};
class ProcessCollector {
public:
explicit ProcessCollector(Paths paths)
: paths_(std::move(paths)),
clock_ticks_(PositiveEnv("OMARCHY_SYSTEM_STATS_CLOCK_TICKS",
SystemValue(_SC_CLK_TCK, 100))),
page_kib_(std::max<std::uint64_t>(
1, PositiveEnv("OMARCHY_SYSTEM_STATS_PAGE_SIZE",
SystemValue(_SC_PAGESIZE, 4096)) /
1024)) {}
void Collect(std::ostream &output) {
LoadUsers();
++generation_;
output << "schema\tactivity-processes\t1\n";
output << "sample\t" << UptimeSample(paths_) << '\t' << clock_ticks_ << '\t'
<< SystemBusyTicks() << '\n';
for (const auto &name : DirectoryNames(paths_.proc)) {
if (!IsDigits(name))
continue;
const auto parsed_pid = ParseUnsigned(name);
if (!parsed_pid || *parsed_pid == static_cast<std::uint64_t>(getpid()))
continue;
EmitProcess(static_cast<pid_t>(*parsed_pid), Join(paths_.proc, name),
output);
}
for (auto iterator = cache_.begin(); iterator != cache_.end();) {
if (iterator->second.generation != generation_)
iterator = cache_.erase(iterator);
else
++iterator;
}
}
private:
struct Metadata {
std::uint64_t start_ticks = 0;
std::string user;
std::uint64_t generation = 0;
};
Paths paths_;
std::uint64_t clock_ticks_;
std::uint64_t page_kib_;
bool users_loaded_ = false;
std::unordered_map<uid_t, std::string> users_;
std::unordered_map<pid_t, Metadata> cache_;
std::uint64_t generation_ = 0;
void LoadUsers() {
if (users_loaded_)
return;
users_loaded_ = true;
std::ifstream stream(paths_.passwd);
std::string line;
while (std::getline(stream, line)) {
std::vector<std::string> fields;
std::size_t begin = 0;
while (begin <= line.size()) {
const auto colon = line.find(':', begin);
fields.push_back(line.substr(begin, colon == std::string::npos
? std::string::npos
: colon - begin));
if (colon == std::string::npos)
break;
begin = colon + 1;
}
if (fields.size() < 3)
continue;
const auto uid = ParseUnsigned(fields[2]);
if (uid)
users_[static_cast<uid_t>(*uid)] = Sanitize(fields[0]);
}
}
std::uint64_t SystemBusyTicks() const {
const auto line = ReadLine(Join(paths_.proc, "stat"));
if (!line)
return 0;
const auto fields = SplitWhitespace(*line);
if (fields.size() < 9 || fields[0] != "cpu")
return 0;
std::uint64_t total = 0;
for (const std::size_t index : {1U, 2U, 3U, 6U, 7U, 8U})
total += ParseUnsigned(fields[index]).value_or(0);
return total;
}
std::string ResolveUser(uid_t uid) const {
const auto found = users_.find(uid);
return found == users_.end() ? std::to_string(uid) : found->second;
}
static std::optional<uid_t> ProcessUid(const std::string &path) {
std::ifstream stream(Join(path, "status"));
std::string line;
while (std::getline(stream, line)) {
if (!StartsWith(line, "Uid:"))
continue;
const auto fields = SplitWhitespace(line);
const auto uid =
fields.size() > 1 ? ParseUnsigned(fields[1]) : std::nullopt;
if (uid && *uid <= std::numeric_limits<uid_t>::max())
return static_cast<uid_t>(*uid);
return std::nullopt;
}
return std::nullopt;
}
void EmitProcess(pid_t pid, const std::string &path, std::ostream &output) {
const auto raw_value = ReadText(Join(path, "stat"));
if (!raw_value)
return;
const std::string &raw = *raw_value;
const auto command_start = raw.find('(');
const auto command_end = raw.rfind(") ");
if (command_start == std::string::npos ||
command_end == std::string::npos || command_start == 0 ||
command_end <= command_start)
return;
const auto parsed_pid = ParseUnsigned(Trim(raw.substr(0, command_start)));
if (!parsed_pid || *parsed_pid != static_cast<std::uint64_t>(pid))
return;
const auto fields = SplitWhitespace(raw.substr(command_end + 2));
if (fields.size() < 22)
return;
const auto flags = ParseUnsigned(fields[6]);
const auto user_ticks = ParseUnsigned(fields[11]);
const auto system_ticks = ParseUnsigned(fields[12]);
const auto start_ticks = ParseUnsigned(fields[19]);
const auto resident_pages = ParseUnsigned(fields[21]);
if (!flags || !user_ticks || !system_ticks || !start_ticks ||
!resident_pages || (*flags & kKernelThreadFlag) || *start_ticks == 0 ||
fields[0].size() != 1)
return;
auto &metadata = cache_[pid];
if (metadata.start_ticks != *start_ticks) {
const auto uid = ProcessUid(path);
if (!uid)
return;
metadata.start_ticks = *start_ticks;
metadata.user = ResolveUser(*uid);
}
metadata.generation = generation_;
output << "process\t" << pid << '\t' << metadata.user << '\t' << fields[0]
<< '\t' << *start_ticks << '\t' << *user_ticks + *system_ticks
<< '\t' << *resident_pages * page_kib_ << '\t'
<< Sanitize(raw.substr(command_start + 1,
command_end - command_start - 1))
<< '\n';
}
};
class ThermalCollector {
public:
explicit ThermalCollector(Paths paths) : paths_(std::move(paths)) {}
void Collect(std::ostream &output) {
output << "schema\tactivity-thermals\t1\n";
output << "sample\t" << UptimeSample(paths_) << '\n';
auto value = CachedValue();
if (!value) {
Invalidate();
Discover();
value = CachedValue();
}
if (cached_path_.empty())
return;
output << "temperature\t" << cached_id_ << '\t' << Sanitize(cached_chip_)
<< '\t' << Sanitize(cached_label_) << '\t' << *value << '\n';
}
private:
Paths paths_;
std::string cached_path_;
std::string cached_id_;
std::string cached_chip_;
std::string cached_label_;
static int Rank(const std::string &chip, const std::string &label) {
std::string chip_lower = chip;
std::string label_lower = label;
chip_lower = Lowercase(std::move(chip_lower));
label_lower = Lowercase(std::move(label_lower));
if (chip_lower == "coretemp" &&
label_lower.find("package id") != std::string::npos)
return 0;
if (chip_lower == "k10temp" && label_lower == "tctl")
return 1;
if (label_lower.find("cpu") != std::string::npos ||
label_lower.find("package") != std::string::npos ||
label_lower == "tctl")
return 2;
if (chip_lower == "coretemp" || chip_lower == "k10temp")
return 3;
return 100;
}
std::optional<std::uint64_t> CachedValue() const {
if (cached_path_.empty())
return std::nullopt;
const auto value = ReadUnsigned(cached_path_);
if (value && *value > 0 && *value < 150000)
return value;
return std::nullopt;
}
void Invalidate() {
cached_path_.clear();
cached_id_.clear();
cached_chip_.clear();
cached_label_.clear();
}
void Discover() {
const std::string root = Join(paths_.sys, "class/hwmon");
int best_rank = 100;
std::uint64_t best_value = 0;
for (const auto &hwmon : DirectoryNames(root)) {
if (!StartsWith(hwmon, "hwmon"))
continue;
const std::string hwmon_path = Join(root, hwmon);
const std::string chip =
ReadLine(Join(hwmon_path, "name")).value_or("unknown");
for (const auto &name : DirectoryNames(hwmon_path)) {
if (!StartsWith(name, "temp") || !EndsWith(name, "_input"))
continue;
const auto value = ReadUnsigned(Join(hwmon_path, name));
if (!value || *value == 0 || *value >= 150000)
continue;
const std::string sensor = name.substr(0, name.size() - 6);
const std::string label =
ReadLine(Join(hwmon_path, sensor + "_label")).value_or(chip);
const int rank = Rank(chip, label);
if (rank >= 100 || rank > best_rank ||
(rank == best_rank && *value <= best_value))
continue;
best_rank = rank;
best_value = *value;
cached_path_ = Join(hwmon_path, name);
cached_id_ = hwmon + "/" + sensor;
cached_chip_ = chip;
cached_label_ = label;
}
}
}
};
class StorageCollector {
public:
explicit StorageCollector(Paths paths) : paths_(std::move(paths)) {}
void Collect(std::ostream &output) const {
output << "schema\tactivity-storage\t1\n";
output << "sample\t" << UptimeSample(paths_) << '\n';
auto volumes = !paths_.statfs_fixture.empty()
? VolumesFromFixture(paths_.statfs_fixture)
: VolumesFromMounts();
std::sort(volumes.begin(), volumes.end(),
[](const Volume &left, const Volume &right) {
if (left.path == "/")
return right.path != "/";
if (right.path == "/")
return false;
return left.path < right.path;
});
for (const auto &volume : volumes)
output << "storage\t" << Sanitize(volume.path) << '\t' << volume.total
<< '\t' << volume.used << '\t' << volume.available << '\n';
}
private:
struct Volume {
std::string path;
std::string source;
std::uint64_t total = 0;
std::uint64_t used = 0;
std::uint64_t available = 0;
unsigned long fsid = 0;
};
Paths paths_;
static bool FillVolume(Volume *volume, std::uint64_t block_size,
std::uint64_t blocks, std::uint64_t free_blocks,
std::uint64_t available_blocks) {
if (!volume || block_size == 0 || blocks == 0)
return false;
free_blocks = std::min(free_blocks, blocks);
available_blocks = std::min(available_blocks, blocks);
volume->total = block_size * blocks;
volume->used = block_size * (blocks - free_blocks);
volume->available = block_size * available_blocks;
return true;
}
static bool FillFromStatvfs(const std::string &path, Volume *volume) {
struct statvfs values{};
if (statvfs(path.c_str(), &values) != 0)
return false;
const std::uint64_t block_size =
values.f_frsize ? values.f_frsize : values.f_bsize;
if (!FillVolume(volume, block_size, values.f_blocks, values.f_bfree,
values.f_bavail))
return false;
volume->fsid = values.f_fsid;
return true;
}
static std::vector<Volume> VolumesFromFixture(const std::string &path) {
std::ifstream stream(path);
std::vector<Volume> volumes;
std::string line;
while (std::getline(stream, line)) {
const auto fields = SplitWhitespace(line);
Volume volume;
std::size_t offset = 0;
if (fields.size() >= 5) {
volume.path = fields[0];
offset = 1;
} else if (fields.size() >= 4) {
volume.path = "/";
} else {
continue;
}
if (!FillVolume(&volume, ParseUnsigned(fields[offset]).value_or(0),
ParseUnsigned(fields[offset + 1]).value_or(0),
ParseUnsigned(fields[offset + 2]).value_or(0),
ParseUnsigned(fields[offset + 3]).value_or(0)))
continue;
if (volume.path.empty())
volume.path = "/";
volumes.push_back(std::move(volume));
}
return volumes;
}
static bool IsLocalFsType(std::string_view fstype) {
return fstype == "ext2" || fstype == "ext3" || fstype == "ext4" ||
fstype == "xfs" || fstype == "btrfs" || fstype == "f2fs" ||
fstype == "bcachefs" || fstype == "zfs" || fstype == "zfs3" ||
fstype == "nilfs2" || fstype == "jfs" || fstype == "reiserfs" ||
fstype == "reiser4" || fstype == "vfat" || fstype == "msdos" ||
fstype == "exfat" || fstype == "ntfs" || fstype == "ntfs3" ||
fstype == "fuseblk" || fstype == "ufs" || fstype == "erofs";
}
static bool IsSkippedMountpoint(std::string_view path) {
if (path == "/boot" || path == "/boot/efi" || path == "/boot/EFI" ||
path == "/efi")
return true;
if (path == "/snap" || StartsWith(path, "/snap/"))
return true;
if (path == "/run" ||
(StartsWith(path, "/run/") && !StartsWith(path, "/run/media/")))
return true;
return StartsWith(path, "/proc") || StartsWith(path, "/sys") ||
StartsWith(path, "/dev");
}
static bool HasBindOption(const char *options) {
if (!options || !*options)
return false;
std::string_view view(options);
std::size_t start = 0;
while (start <= view.size()) {
const auto comma = view.find(',', start);
const auto option = view.substr(
start, comma == std::string_view::npos ? view.size() - start
: comma - start);
if (option == "bind")
return true;
if (comma == std::string_view::npos)
break;
start = comma + 1;
}
return false;
}
static bool SameStoragePool(const Volume &left, const Volume &right) {
if (left.fsid != 0 && left.fsid == right.fsid)
return true;
return !left.source.empty() && left.source == right.source;
}
std::vector<Volume> VolumesFromMounts() const {
std::vector<Volume> volumes;
Volume root;
root.path = "/";
if (FillFromStatvfs(paths_.root, &root))
volumes.push_back(root);
FILE *mounts = setmntent(Join(paths_.proc, "mounts").c_str(), "r");
if (!mounts)
return volumes;
while (mntent *entry = getmntent(mounts)) {
if (!entry->mnt_dir || !entry->mnt_type)
continue;
const std::string path = entry->mnt_dir;
const std::string source = entry->mnt_fsname ? entry->mnt_fsname : "";
if (path == "/") {
if (!volumes.empty() && volumes[0].path == "/")
volumes[0].source = source;
continue;
}
if (path.empty() || !IsLocalFsType(entry->mnt_type) ||
IsSkippedMountpoint(path) || HasBindOption(entry->mnt_opts))
continue;
Volume volume;
volume.path = path;
volume.source = source;
if (!FillFromStatvfs(path, &volume))
continue;
bool duplicate = false;
for (const auto &existing : volumes) {
if (existing.path == volume.path || SameStoragePool(existing, volume)) {
duplicate = true;
break;
}
}
if (!duplicate)
volumes.push_back(std::move(volume));
}
endmntent(mounts);
return volumes;
}
};
class PowerCollector {
public:
explicit PowerCollector(Paths paths) : paths_(std::move(paths)) {}
void Collect(std::ostream &output) {
output << "schema\tactivity-process-power\t1\n";
output << "sample\t" << UptimeSample(paths_) << '\n';
DiscoverPackages();
for (const auto &domain : domains_)
EmitPackage(domain, output);
}
private:
struct PackageDomain {
std::string path;
std::string id;
std::string name;
int priority = 0;
};
Paths paths_;
bool packages_discovered_ = false;
std::vector<PackageDomain> domains_;
void DiscoverPackages() {
if (packages_discovered_)
return;
packages_discovered_ = true;
const std::string root = Join(paths_.sys, "class/powercap");
std::unordered_map<std::string, PackageDomain> domains;
for (const auto &parent : DirectoryNames(root)) {
const std::string parent_path = Join(root, parent);
AddPackage(parent_path, domains);
if (!IsDirectory(parent_path))
continue;
for (const auto &child : DirectoryNames(parent_path))
AddPackage(Join(parent_path, child), domains);
}
domains_.reserve(domains.size());
for (auto &[key, domain] : domains)
domains_.push_back(std::move(domain));
std::sort(
domains_.begin(), domains_.end(),
[](const auto &left, const auto &right) { return left.id < right.id; });
}
static bool TopLevelRaplId(const std::string &id) {
const auto colon = id.find(':');
if (colon == std::string::npos ||
id.find(':', colon + 1) != std::string::npos)
return false;
return IsDigits(std::string_view(id).substr(colon + 1));
}
void
AddPackage(const std::string &path,
std::unordered_map<std::string, PackageDomain> &domains) const {
if (!IsDirectory(path))
return;
const std::string id = BaseName(path);
if (!TopLevelRaplId(id))
return;
const auto raw_name = ReadLine(Join(path, "name"));
if (!raw_name)
return;
std::string name = *raw_name;
name = Lowercase(std::move(name));
if (name != "package" && !StartsWith(name, "package-"))
return;
const auto colon = id.rfind(':');
const std::string key = name == "package" ? id.substr(colon + 1) : name;
const int priority = id.find("-mmio:") == std::string::npos ? 0 : 1;
const auto current = domains.find(key);
if (current != domains.end() &&
(current->second.priority < priority ||
(current->second.priority == priority && current->second.id <= id)))
return;
domains[key] = {path, id, Sanitize(name), priority};
}
static void EmitPackage(const PackageDomain &domain, std::ostream &output) {
const auto energy = ReadUnsigned(Join(domain.path, "energy_uj"));
const auto maximum = ReadUnsigned(Join(domain.path, "max_energy_range_uj"));
output << "package\t" << domain.id << '\t' << domain.name << '\t';
if (energy)
output << *energy;
output << '\t';
if (maximum)
output << *maximum;
output << '\n';
}
};
std::string GpuVendor(std::string vendor) {
vendor = Lowercase(std::move(vendor));
if (vendor == "8086")
return "Intel";
if (vendor == "1002")
return "AMD";
if (vendor == "10de")
return "NVIDIA";
return "GPU";
}
std::string NormalizeHex(std::string value) {
value = Trim(std::move(value));
if (StartsWith(value, "0x"))
value.erase(0, 2);
return Lowercase(std::move(value));
}
std::optional<std::uint64_t> MaxFrequency(
const std::vector<std::pair<std::string, std::uint64_t>> &patterns) {
std::optional<std::uint64_t> best;
for (const auto &[pattern, divisor] : patterns) {
for (const auto &path : GlobPaths(pattern)) {
const auto value = ReadUnsigned(path);
if (!value || divisor == 0)
continue;
const std::uint64_t mhz = *value / divisor;
if (!best || mhz > *best)
best = mhz;
}
}
return best;
}
struct GpuAdapter {
std::string id;
std::string vendor_hex;
std::string vendor;
std::string driver;
std::string name;
std::string card_path;
std::string device_path;
double utilization = -1;
std::int64_t memory_used = -1;
std::int64_t memory_total = -1;
std::string memory_kind = "unknown";
double frequency_mhz = -1;
};
struct NvidiaReading {
std::string id;
std::string name;
double utilization = -1;
std::int64_t memory_used = -1;
std::int64_t memory_total = -1;
double frequency_mhz = -1;
};
class NvidiaProvider {
public:
explicit NvidiaProvider(std::string fixture) : fixture_(std::move(fixture)) {}
~NvidiaProvider() {
if (initialized_ && shutdown_)
shutdown_();
if (library_)
dlclose(library_);
}
std::vector<NvidiaReading> Read() {
if (!fixture_.empty())
return ReadFixture();
if (!Initialize())
return {};
unsigned int count = 0;
if (get_count_(&count) != 0)
return {};
std::vector<NvidiaReading> readings;
for (unsigned int index = 0; index < count; ++index) {
void *device = nullptr;
if (get_handle_(index, &device) != 0 || !device)
continue;
NvidiaReading reading;
NvmlPciInfo pci{};
if (get_pci_(device, &pci) == 0) {
reading.id =
NormalizeBus(pci.bus_id[0] ? pci.bus_id : pci.bus_id_legacy);
}
std::array<char, 128> name{};
if (get_name_(device, name.data(), name.size()) == 0)
reading.name = Sanitize(name.data());
NvmlUtilization utilization{};
if (get_utilization_(device, &utilization) == 0)
reading.utilization = utilization.gpu;
NvmlMemory memory{};
if (get_memory_(device, &memory) == 0 && memory.total > 0) {
reading.memory_used =
static_cast<std::int64_t>(std::min(memory.used, memory.total));
reading.memory_total = static_cast<std::int64_t>(memory.total);
}
unsigned int clock = 0;
if (get_clock_(device, 0, &clock) == 0)
reading.frequency_mhz = clock;
if (!reading.id.empty())
readings.push_back(std::move(reading));
}
return readings;
}
private:
struct NvmlPciInfo {
char bus_id_legacy[16];
unsigned int domain;
unsigned int bus;
unsigned int device;
unsigned int pci_device_id;
unsigned int pci_subsystem_id;
char bus_id[32];
};
struct NvmlUtilization {
unsigned int gpu;
unsigned int memory;
};
struct NvmlMemory {
unsigned long long total;
unsigned long long free;
unsigned long long used;
};
using Init = int (*)();
using Shutdown = int (*)();
using GetCount = int (*)(unsigned int *);
using GetHandle = int (*)(unsigned int, void **);
using GetPci = int (*)(void *, NvmlPciInfo *);
using GetName = int (*)(void *, char *, unsigned int);
using GetUtilization = int (*)(void *, NvmlUtilization *);
using GetMemory = int (*)(void *, NvmlMemory *);
using GetClock = int (*)(void *, unsigned int, unsigned int *);
std::string fixture_;
void *library_ = nullptr;
bool attempted_ = false;
bool initialized_ = false;
Shutdown shutdown_ = nullptr;
GetCount get_count_ = nullptr;
GetHandle get_handle_ = nullptr;
GetPci get_pci_ = nullptr;
GetName get_name_ = nullptr;
GetUtilization get_utilization_ = nullptr;
GetMemory get_memory_ = nullptr;
GetClock get_clock_ = nullptr;
template <typename Function> Function Symbol(const char *name) {
return reinterpret_cast<Function>(dlsym(library_, name));
}
bool Initialize() {
if (attempted_)
return initialized_;
attempted_ = true;
library_ = dlopen("libnvidia-ml.so.1", RTLD_NOW | RTLD_LOCAL);
if (!library_)
return false;
const auto init = Symbol<Init>("nvmlInit_v2");
shutdown_ = Symbol<Shutdown>("nvmlShutdown");
get_count_ = Symbol<GetCount>("nvmlDeviceGetCount_v2");
get_handle_ = Symbol<GetHandle>("nvmlDeviceGetHandleByIndex_v2");
get_pci_ = Symbol<GetPci>("nvmlDeviceGetPciInfo_v3");
if (!get_pci_)
get_pci_ = Symbol<GetPci>("nvmlDeviceGetPciInfo_v2");
get_name_ = Symbol<GetName>("nvmlDeviceGetName");
get_utilization_ = Symbol<GetUtilization>("nvmlDeviceGetUtilizationRates");
get_memory_ = Symbol<GetMemory>("nvmlDeviceGetMemoryInfo");
get_clock_ = Symbol<GetClock>("nvmlDeviceGetClockInfo");
if (!init || !shutdown_ || !get_count_ || !get_handle_ || !get_pci_ ||
!get_name_ || !get_utilization_ || !get_memory_ || !get_clock_)
return false;
initialized_ = init() == 0;
return initialized_;
}
static std::string NormalizeBus(std::string bus) {
bus = Trim(std::move(bus));
bus = Lowercase(std::move(bus));
const auto first_colon = bus.find(':');
if (first_colon == 8 && bus.size() >= 12)
bus.erase(0, 4);
return bus;
}
std::vector<NvidiaReading> ReadFixture() const {
std::vector<NvidiaReading> readings;
std::ifstream stream(fixture_);
std::string line;
while (std::getline(stream, line)) {
std::vector<std::string> fields;
std::size_t begin = 0;
while (begin <= line.size()) {
const auto tab = line.find('\t', begin);
fields.push_back(line.substr(
begin, tab == std::string::npos ? std::string::npos : tab - begin));
if (tab == std::string::npos)
break;
begin = tab + 1;
}
if (fields.size() < 6)
continue;
NvidiaReading reading;
reading.id = NormalizeBus(fields[0]);
reading.name = Sanitize(fields[1]);
reading.utilization = ParseDouble(fields[2]).value_or(-1);
reading.memory_used =
static_cast<std::int64_t>(ParseUnsigned(fields[3]).value_or(0));
reading.memory_total =
static_cast<std::int64_t>(ParseUnsigned(fields[4]).value_or(0));
reading.frequency_mhz = ParseDouble(fields[5]).value_or(-1);
if (!reading.id.empty())
readings.push_back(std::move(reading));
}
return readings;
}
};
std::string PciDeviceName(const std::string &vendor_hex,
const std::string &device_hex) {
std::ifstream stream("/usr/share/hwdata/pci.ids");
std::string line;
bool in_vendor = false;
std::string vendor_name;
while (std::getline(stream, line)) {
if (line.empty() || line[0] == '#')
continue;
if (line[0] != '\t') {
if (line.size() < 6 || line[4] != ' ' || line[5] != ' ') {
in_vendor = false;
continue;
}
std::string id = line.substr(0, 4);
id = Lowercase(std::move(id));
in_vendor = id == vendor_hex;
vendor_name = in_vendor ? Trim(line.substr(6)) : "";
continue;
}
if (!in_vendor || line.size() < 7 || line[1] == '\t')
continue;
std::string id = line.substr(1, 4);
id = Lowercase(std::move(id));
if (id == device_hex)
return Sanitize(vendor_name + " " + Trim(line.substr(7)));
}
return "";
}
double ActiveDpmClock(const std::string &path) {
std::ifstream stream(path);
std::string line;
while (std::getline(stream, line)) {
if (line.find('*') == std::string::npos)
continue;
std::string lower = line;
lower = Lowercase(std::move(lower));
const auto mhz = lower.find("mhz");
if (mhz == std::string::npos)
continue;
std::size_t begin = mhz;
while (begin > 0 &&
(std::isdigit(static_cast<unsigned char>(lower[begin - 1])) ||
lower[begin - 1] == '.'))
--begin;
return ParseDouble(lower.substr(begin, mhz - begin)).value_or(-1);
}
return -1;
}
class GpuCollector {
public:
explicit GpuCollector(Paths paths)
: paths_(std::move(paths)), nvidia_(paths_.nvidia_fixture),
discovery_interval_(std::chrono::milliseconds(PositiveEnv(
"OMARCHY_SYSTEM_STATS_GPU_DISCOVERY_INTERVAL_MS", 10000))) {}
void Collect(std::ostream &output) {
DiscoverAdapters();
RefreshDynamicValues();
ApplyNvidia();
output << "schema\tactivity-gpus\t1\n";
output << "sample\t" << UptimeSample(paths_) << '\n';
for (const auto &gpu : adapters_) {
output << "gpu\t" << gpu.id << '\t' << gpu.vendor << '\t' << gpu.driver
<< '\t' << Sanitize(gpu.name) << '\t';
EmitNumber(output, gpu.utilization);
output << '\t' << gpu.memory_used << '\t' << gpu.memory_total << '\t'
<< gpu.memory_kind << '\t';
EmitNumber(output, gpu.frequency_mhz);
output << '\n';
}
EmitDrmClients(output);
}
private:
struct EngineValue {
double busy = 0;
double total = -1;
double capacity = 1;
std::string kind;
};
struct ClientSample {
std::string client;
std::string pdev;
std::unordered_map<std::string, double> resident;
std::unordered_map<std::string, EngineValue> engines;
};
struct EngineRow {
std::string pdev;
std::string client;
std::string name;
EngineValue value;
};
Paths paths_;
NvidiaProvider nvidia_;
bool adapters_discovered_ = false;
bool nvidia_present_ = false;
std::vector<GpuAdapter> adapters_;
std::vector<std::string> fdinfo_paths_;
Clock::time_point next_fd_discovery_{};
std::chrono::milliseconds discovery_interval_;
static void EmitNumber(std::ostream &output, double value) {
if (!std::isfinite(value) || value < 0) {
output << -1;
} else if (std::fabs(value - std::round(value)) < 0.0001) {
output << static_cast<long long>(std::llround(value));
} else {
output << std::fixed << std::setprecision(2) << value;
output.unsetf(std::ios::floatfield);
}
}
void DiscoverAdapters() {
if (adapters_discovered_)
return;
adapters_discovered_ = true;
const std::string drm_root = Join(paths_.sys, "class/drm");
std::unordered_set<std::string> seen;
for (const auto &name : DirectoryNames(drm_root)) {
if (!StartsWith(name, "card") ||
!IsDigits(std::string_view(name).substr(4)))
continue;
const std::string card_path = Join(drm_root, name);
const std::string device_path = Join(card_path, "device");
if (!Exists(device_path))
continue;
const std::string real_device = RealPath(device_path);
std::string id =
BaseName(real_device.empty() ? device_path : real_device);
if (id.find(':') == std::string::npos)
id = name;
id = Lowercase(std::move(id));
if (!seen.insert(id).second)
continue;
GpuAdapter gpu;
gpu.id = id;
gpu.card_path = card_path;
gpu.device_path = device_path;
gpu.vendor_hex =
NormalizeHex(ReadLine(Join(device_path, "vendor")).value_or(""));
if (gpu.vendor_hex == "10de")
nvidia_present_ = true;
gpu.vendor = GpuVendor(gpu.vendor_hex);
const std::string driver_path = RealPath(Join(device_path, "driver"));
gpu.driver = driver_path.empty() ? "unknown" : BaseName(driver_path);
gpu.name = ReadLine(Join(device_path, "product_name")).value_or("");
if (gpu.name.empty()) {
const std::string device_hex =
NormalizeHex(ReadLine(Join(device_path, "device")).value_or(""));
gpu.name = PciDeviceName(gpu.vendor_hex, device_hex);
}
if (gpu.name.empty())
gpu.name = gpu.vendor + " GPU";
adapters_.push_back(std::move(gpu));
}
if (!nvidia_present_)
nvidia_present_ =
!DirectoryNames(Join(paths_.proc, "driver/nvidia/gpus")).empty();
}
void RefreshDynamicValues() {
for (auto &gpu : adapters_) {
gpu.utilization = -1;
gpu.memory_used = -1;
gpu.memory_total = -1;
gpu.memory_kind = "unknown";
gpu.frequency_mhz = -1;
if (gpu.vendor_hex == "1002") {
const auto busy = ReadLine(Join(gpu.device_path, "gpu_busy_percent"));
if (busy)
gpu.utilization = ParseDouble(*busy).value_or(-1);
auto used = ReadUnsigned(Join(gpu.device_path, "mem_info_vram_used"));
auto total = ReadUnsigned(Join(gpu.device_path, "mem_info_vram_total"));
if (used && total && *total > 0) {
gpu.memory_used = static_cast<std::int64_t>(std::min(*used, *total));
gpu.memory_total = static_cast<std::int64_t>(*total);
gpu.memory_kind = "vram";
} else {
used = ReadUnsigned(Join(gpu.device_path, "mem_info_gtt_used"));
total = ReadUnsigned(Join(gpu.device_path, "mem_info_gtt_total"));
if (used && total && *total > 0) {
gpu.memory_used =
static_cast<std::int64_t>(std::min(*used, *total));
gpu.memory_kind = "shared";
}
}
}
if (gpu.vendor_hex == "8086") {
auto frequency = MaxFrequency({
{Join(gpu.device_path, "tile*/gt*/freq*/act_freq"), 1},
{Join(gpu.card_path, "gt/gt*/rps_act_freq_mhz"), 1},
{Join(gpu.card_path, "gt_act_freq_mhz"), 1},
});
if (!frequency) {
frequency = MaxFrequency({
{Join(gpu.device_path, "tile*/gt*/freq*/cur_freq"), 1},
{Join(gpu.card_path, "gt/gt*/rps_cur_freq_mhz"), 1},
{Join(gpu.card_path, "gt_cur_freq_mhz"), 1},
});
}
if (frequency)
gpu.frequency_mhz = *frequency;
} else if (gpu.vendor_hex == "1002") {
const auto frequency = MaxFrequency(
{{Join(gpu.device_path, "hwmon/hwmon*/freq1_input"), 1000000}});
if (frequency)
gpu.frequency_mhz = *frequency;
else
gpu.frequency_mhz =
ActiveDpmClock(Join(gpu.device_path, "pp_dpm_sclk"));
}
if (gpu.frequency_mhz < 0) {
const auto frequency = MaxFrequency({
{Join(gpu.device_path, "devfreq/*/cur_freq"), 1000000},
{Join(gpu.device_path, "hwmon/hwmon*/freq1_input"), 1000000},
});
if (frequency)
gpu.frequency_mhz = *frequency;
}
}
}
void ApplyNvidia() {
if (!nvidia_present_ && paths_.nvidia_fixture.empty())
return;
for (const auto &reading : nvidia_.Read()) {
auto found =
std::find_if(adapters_.begin(), adapters_.end(),
[&](const auto &gpu) { return gpu.id == reading.id; });
if (found == adapters_.end()) {
GpuAdapter gpu;
gpu.id = reading.id;
gpu.vendor_hex = "10de";
gpu.vendor = "NVIDIA";
gpu.driver = "nvidia";
gpu.name = reading.name.empty() ? "NVIDIA GPU" : reading.name;
adapters_.push_back(std::move(gpu));
found = std::prev(adapters_.end());
}
found->vendor = "NVIDIA";
found->driver = "nvidia";
if (!reading.name.empty())
found->name = reading.name;
found->utilization = reading.utilization;
found->frequency_mhz = reading.frequency_mhz;
if (reading.memory_total > 0) {
found->memory_used =
std::min(reading.memory_used, reading.memory_total);
found->memory_total = reading.memory_total;
found->memory_kind = "vram";
}
}
}
void DiscoverFdinfoPaths() {
std::vector<std::string> discovered;
for (const auto &pid : DirectoryNames(paths_.proc)) {
if (!IsDigits(pid) || ParseUnsigned(pid).value_or(0) ==
static_cast<std::uint64_t>(getpid()))
continue;
const std::string fd_root = Join(Join(paths_.proc, pid), "fd");
DIR *directory = opendir(fd_root.c_str());
if (!directory)
continue;
while (dirent *entry = readdir(directory)) {
const std::string fd(entry->d_name);
if (!IsDigits(fd))
continue;
const std::string link_path = Join(fd_root, fd);
std::array<char, PATH_MAX> target{};
const ssize_t length =
readlink(link_path.c_str(), target.data(), target.size() - 1);
if (length <= 0)
continue;
target[static_cast<std::size_t>(length)] = '\0';
if (!StartsWith(target.data(), "/dev/dri/"))
continue;
const std::string fdinfo =
Join(Join(Join(paths_.proc, pid), "fdinfo"), fd);
if (Exists(fdinfo))
discovered.push_back(fdinfo);
}
closedir(directory);
}
std::sort(discovered.begin(), discovered.end());
discovered.erase(std::unique(discovered.begin(), discovered.end()),
discovered.end());
fdinfo_paths_ = std::move(discovered);
next_fd_discovery_ = Clock::now() + discovery_interval_;
}
static double ByteValue(double amount, const std::string &unit) {
if (unit == "KiB")
return amount * 1024.0;
if (unit == "MiB")
return amount * 1024.0 * 1024.0;
if (unit == "GiB")
return amount * 1024.0 * 1024.0 * 1024.0;
return amount;
}
static std::optional<ClientSample> ParseFdinfo(const std::string &path) {
std::ifstream stream(path);
if (!stream)
return std::nullopt;
ClientSample sample;
std::unordered_map<std::string, double> busy;
std::unordered_map<std::string, double> totals;
std::unordered_map<std::string, double> capacities;
std::unordered_map<std::string, std::string> kinds;
std::string line;
while (std::getline(stream, line)) {
auto fields = SplitWhitespace(line);
if (fields.empty())
continue;
std::string key = fields[0];
if (!key.empty() && key.back() == ':')
key.pop_back();
if (key == "drm-client-id" && fields.size() > 1)
sample.client = fields[1];
else if (key == "drm-pdev" && fields.size() > 1) {
sample.pdev = fields[1];
sample.pdev = Lowercase(std::move(sample.pdev));
} else if (StartsWith(key, "drm-resident-") && fields.size() > 1) {
const std::string region = key.substr(std::strlen("drm-resident-"));
const double amount = ParseDouble(fields[1]).value_or(0);
sample.resident[region] =
ByteValue(amount, fields.size() > 2 ? fields[2] : "");
} else if (StartsWith(key, "drm-engine-capacity-") && fields.size() > 1) {
capacities[key.substr(std::strlen("drm-engine-capacity-"))] =
ParseDouble(fields[1]).value_or(1);
} else if (StartsWith(key, "drm-total-cycles-") && fields.size() > 1) {
totals[key.substr(std::strlen("drm-total-cycles-"))] =
ParseDouble(fields[1]).value_or(-1);
} else if (StartsWith(key, "drm-cycles-") && fields.size() > 1) {
const std::string engine = key.substr(std::strlen("drm-cycles-"));
busy[engine] = ParseDouble(fields[1]).value_or(0);
kinds[engine] = "cycles";
} else if (StartsWith(key, "drm-engine-") && fields.size() > 1) {
const std::string engine = key.substr(std::strlen("drm-engine-"));
busy[engine] = ByteValue(ParseDouble(fields[1]).value_or(0),
fields.size() > 2 ? fields[2] : "");
kinds[engine] = "time";
}
}
if (sample.client.empty() || sample.pdev.empty())
return std::nullopt;
for (const auto &[name, value] : busy) {
if (kinds[name] == "cycles" && !totals.count(name))
continue;
sample.engines[name] = {
value,
totals.count(name) ? totals[name] : -1,
capacities.count(name) && capacities[name] > 0 ? capacities[name] : 1,
kinds[name],
};
}
return sample;
}
void EmitDrmClients(std::ostream &output) {
if (adapters_.empty())
return;
if (Clock::now() >= next_fd_discovery_)
DiscoverFdinfoPaths();
std::unordered_set<std::string> known;
for (const auto &gpu : adapters_)
known.insert(gpu.id);
std::unordered_set<std::string> clients;
std::unordered_map<std::string, double> dedicated;
std::unordered_map<std::string, double> shared;
std::vector<EngineRow> engines;
std::vector<std::string> retained;
for (const auto &path : fdinfo_paths_) {
const auto sample = ParseFdinfo(path);
if (!sample)
continue;
retained.push_back(path);
if (!known.count(sample->pdev))
continue;
const std::string client_key = sample->pdev + "\t" + sample->client;
if (!clients.insert(client_key).second)
continue;
for (const auto &[region, bytes] : sample->resident) {
if (region.find("vram") != std::string::npos ||
region.find("local") != std::string::npos)
dedicated[sample->pdev] += bytes;
else
shared[sample->pdev] += bytes;
}
for (const auto &[name, value] : sample->engines)
engines.push_back({sample->pdev, sample->client, name, value});
}
fdinfo_paths_ = std::move(retained);
for (const auto &engine : engines) {
output << "engine\t" << engine.pdev << '\t' << engine.client << '\t'
<< engine.name << '\t' << std::llround(engine.value.busy) << '\t'
<< std::llround(engine.value.total) << '\t'
<< std::llround(engine.value.capacity) << '\t' << engine.value.kind
<< '\n';
}
for (const auto &[id, bytes] : dedicated)
output << "gpu-memory\t" << id << "\tvram\t" << std::llround(bytes)
<< '\n';
for (const auto &[id, bytes] : shared)
output << "gpu-memory\t" << id << "\tshared\t" << std::llround(bytes)
<< '\n';
}
};
class Sampler {
public:
explicit Sampler(Paths paths)
: resources_(paths), processes_(paths), thermals_(paths), storage_(paths),
gpus_(paths), power_(PowerPaths()) {}
void Collect(const std::string &kind, std::ostream &output) {
if (kind == "resources")
resources_.Collect(output);
else if (kind == "processes")
processes_.Collect(output);
else if (kind == "thermals")
thermals_.Collect(output);
else if (kind == "gpus")
gpus_.Collect(output);
else if (kind == "storage")
storage_.Collect(output);
else if (kind == "power")
power_.Collect(output);
}
void BarWidget(std::ostream &output) { resources_.BarWidget(output); }
private:
ResourceCollector resources_;
ProcessCollector processes_;
ThermalCollector thermals_;
StorageCollector storage_;
GpuCollector gpus_;
PowerCollector power_;
};
void Reader(Sampler &sampler) {
std::string request;
while (std::getline(std::cin, request)) {
request = Trim(std::move(request));
if (request != "resources" && request != "processes" &&
request != "thermals" && request != "gpus" && request != "storage")
continue;
sampler.Collect(request, std::cout);
std::cout << "snapshot-end\t" << request << '\n' << std::flush;
}
}
void PowerReader(Sampler &sampler) {
std::string request;
while (std::getline(std::cin, request)) {
request = Trim(std::move(request));
if (request != "sample")
continue;
sampler.Collect("power", std::cout);
std::cout << "snapshot-end\tpower\n" << std::flush;
}
}
int Run(int argc, char **argv) {
Sampler sampler(UserPaths());
if (argc == 1)
return 0;
if (argc != 2)
return 64;
const std::string mode(argv[1]);
if (mode == "--bar-widget")
sampler.BarWidget(std::cout);
else if (mode == "--activity-reader")
Reader(sampler);
else if (mode == "--activity-resources")
sampler.Collect("resources", std::cout);
else if (mode == "--activity-processes")
sampler.Collect("processes", std::cout);
else if (mode == "--activity-thermals")
sampler.Collect("thermals", std::cout);
else if (mode == "--activity-gpus")
sampler.Collect("gpus", std::cout);
else if (mode == "--activity-process-power")
sampler.Collect("power", std::cout);
else if (mode == "--activity-process-power-reader")
PowerReader(sampler);
else if (mode == "--activity-storage")
sampler.Collect("storage", std::cout);
else if (mode == "--version")
std::cout << "activity-sampler 2.1.1\n";
else {
std::cerr << "Usage: activity-sampler "
"[--bar-widget|--activity-reader|--activity-resources|"
"--activity-processes|--activity-thermals|--activity-gpus|"
"--activity-process-power|--activity-process-power-reader|--"
"activity-storage]\n";
return 64;
}
return std::cout.good() ? 0 : 1;
}
} // namespace
int main(int argc, char **argv) {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return Run(argc, argv);
}