#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(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 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 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 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(base) || parsed > (std::numeric_limits::max() - digit) / static_cast(base)) return std::nullopt; parsed = parsed * static_cast(base) + digit; } return parsed; } std::optional 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 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 DirectoryNames(const std::string &path) { std::vector 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 GlobPaths(const std::string &pattern) { glob_t matches{}; std::vector 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 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 SplitWhitespace(const std::string &value) { std::istringstream stream(value); std::vector fields; std::string field; while (stream >> field) fields.push_back(std::move(field)); return fields; } std::unordered_set ParseCpuList(const std::string &list) { std::unordered_set 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(value[index]))) { ++index; continue; } int start = 0; while (index < value.size() && std::isdigit(static_cast(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(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(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(total - std::min(total, available)) / static_cast(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 cpu_frequency_paths_; std::vector block_devices_; std::vector 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(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( 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 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 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 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 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 MemoryValues() const { std::unordered_map 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(ParseUnsigned(value).value_or(0)); } else if (EndsWith(key, "_SPEED_GTS")) { candidate = static_cast( 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::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(*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(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( 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(getpid())) continue; EmitProcess(static_cast(*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 users_; std::unordered_map 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 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)] = 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 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::max()) return static_cast(*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(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 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 VolumesFromFixture(const std::string &path) { std::ifstream stream(path); std::vector 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 VolumesFromMounts() const { std::vector 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 domains_; void DiscoverPackages() { if (packages_discovered_) return; packages_discovered_ = true; const std::string root = Join(paths_.sys, "class/powercap"); std::unordered_map 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 &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 MaxFrequency( const std::vector> &patterns) { std::optional 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 Read() { if (!fixture_.empty()) return ReadFixture(); if (!Initialize()) return {}; unsigned int count = 0; if (get_count_(&count) != 0) return {}; std::vector 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 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::min(memory.used, memory.total)); reading.memory_total = static_cast(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 Function Symbol(const char *name) { return reinterpret_cast(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("nvmlInit_v2"); shutdown_ = Symbol("nvmlShutdown"); get_count_ = Symbol("nvmlDeviceGetCount_v2"); get_handle_ = Symbol("nvmlDeviceGetHandleByIndex_v2"); get_pci_ = Symbol("nvmlDeviceGetPciInfo_v3"); if (!get_pci_) get_pci_ = Symbol("nvmlDeviceGetPciInfo_v2"); get_name_ = Symbol("nvmlDeviceGetName"); get_utilization_ = Symbol("nvmlDeviceGetUtilizationRates"); get_memory_ = Symbol("nvmlDeviceGetMemoryInfo"); get_clock_ = Symbol("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 ReadFixture() const { std::vector readings; std::ifstream stream(fixture_); std::string line; while (std::getline(stream, line)) { std::vector 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(ParseUnsigned(fields[3]).value_or(0)); reading.memory_total = static_cast(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(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 resident; std::unordered_map 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 adapters_; std::vector 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(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 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::min(*used, *total)); gpu.memory_total = static_cast(*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::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 discovered; for (const auto &pid : DirectoryNames(paths_.proc)) { if (!IsDigits(pid) || ParseUnsigned(pid).value_or(0) == static_cast(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 target{}; const ssize_t length = readlink(link_path.c_str(), target.data(), target.size() - 1); if (length <= 0) continue; target[static_cast(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 ParseFdinfo(const std::string &path) { std::ifstream stream(path); if (!stream) return std::nullopt; ClientSample sample; std::unordered_map busy; std::unordered_map totals; std::unordered_map capacities; std::unordered_map 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 known; for (const auto &gpu : adapters_) known.insert(gpu.id); std::unordered_set clients; std::unordered_map dedicated; std::unordered_map shared; std::vector engines; std::vector 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); }