#include /* maybe have a read pp_table function */ /* would be neat to wrap it in a class but eh namespace is good enough */ namespace gpu { /* takes pointer to a target gpu::sensors struct and updates its hwmon path and detects gpu_metrics */ void detectHwmon(gpuSensors* target, std::string pci) { target->hwmon = getFirstDir("/sys/bus/pci/devices/" + pci + "/hwmon"); std::println("[gpu::detectHwmon] Detected path \"{}\"", target->hwmon); if (FILE* file = fopen((target->hwmon + REL_METRICS_PATH).c_str(), "r")) { fclose(file); std::println("[gpu::detectHwmon] found metrics file"); target->hasGpuMetrics = true; } } /* some metrics are missing in gpu_metrics, we always fill them from fs */ void fillMissingMetrics(gpuSensors* g) { g->vcore = std::stol(readFile(g->hwmon + "/in0_input", 4)) / 1000.0f; g->fan_pwm = std::stoi(readFile(g->hwmon + "/pwm1", 4)) * 100 / 256; g->fan_speed = std::stol(readFile(g->hwmon + "/fan1_input", 4)); g->power_cap = std::stol(readFile(g->hwmon + "/power1_cap", 16)) / 1000000; g->power_cap_max = std::stol(readFile(g->hwmon + "/power1_cap_max", 16)) / 1000000; g->vram_used = std::stoul(readFile(g->hwmon + "/../../mem_info_vram_used", 16)); g->vram_total = std::stoul(readFile(g->hwmon + "/../../mem_info_vram_total", 16)); } /* get sensors for pre-vega cards (rx480/580) */ void updateFromFs(gpuSensors* g) { g->clock_core = std::stoll(readFile(g->hwmon + "/freq1_input", 16)) / 1000000; g->clock_mem = std::stoll(readFile(g->hwmon + "/freq2_input", 16)) / 1000000; g->power_avg = std::stol(readFile(g->hwmon + "/power1_input", 16)) / 1000000; g->temp_edge = std::stol(readFile(g->hwmon + "/temp1_input", 2)); g->usage_core = std::stoi(readFile(g->hwmon + "/../../gpu_busy_percent")); } /* get sensors for vega and newer (rx5700xt, rx7800xt [*]) */ void updateFromMetrics(gpuSensors* g) { char raw_metrics[MAX_METRICS_LENGTH]; if (!readFile2(g->hwmon + REL_METRICS_PATH, raw_metrics, sizeof(raw_metrics))) return; auto header = reinterpret_cast(raw_metrics); switch (header->format_revision * 10 + header->content_revision) { /* rx5700xt, rx7800xt */ case 13: { auto metrics = reinterpret_cast(raw_metrics); g->usage_core = metrics->average_gfx_activity; g->usage_umc = metrics->average_umc_activity; g->usage_media = metrics->average_mm_activity; g->temp_edge = metrics->temperature_edge; g->temp_hotspot = metrics->temperature_hotspot; g->temp_mem = metrics->temperature_mem; g->temp_vrm_gfx = metrics->temperature_vrgfx; g->temp_vrm_soc = metrics->temperature_vrsoc; g->temp_vrm_mem = metrics->temperature_vrmem; g->power_avg = metrics->average_socket_power; g->fan_speed = metrics->current_fan_speed; // hacks for rx7800xt // g->clock_core = metrics->average_vclk1_frequency; // why not gfxclock? // g->clock_mem = metrics->average_gfxclk_frequency; // why gfxclock? min mem clock seems to be 96mhz, this is probably including time sleeping // hacks for rx5700xt g->clock_core = metrics->average_gfxclk_frequency; g->clock_mem = metrics->average_uclk_frequency * 2; // mem controller clock, actual mem is 2x that break; } /* ryzen 5 5600g */ case 22: { auto metrics = reinterpret_cast(raw_metrics); g->temp_edge = metrics->temperature_gfx / 100; break; } /* werid hardware i probably dont like, maybe fallback to reading from fs? */ default: { std::println("[gpu::updateFromMetrics] Unsupported gpu_metrics_v{}_{}", header->format_revision, header->content_revision); break; } } } /* reads values from system and updates the target gpu::sensors struct */ void update(gpuSensors* g) { fillMissingMetrics(g); if (g->hasGpuMetrics) gpu::updateFromMetrics(g); else gpu::updateFromFs(g); if (g->fan_speed > 9999) g->fan_speed = 9999; // sanity check fan speed } } // namespace gpu