From 040171400a7f48252a68f30d11894dca85ccf9f8 Mon Sep 17 00:00:00 2001 From: "Emilio B. Pedrollo" Date: Tue, 30 Sep 2025 15:28:42 -0300 Subject: [PATCH] feat: Introduce cpu frequency display modes --- README.md | 3 ++ src/btop_config.cpp | 7 ++- src/btop_config.hpp | 3 ++ src/btop_draw.cpp | 16 ++++++- src/btop_menu.cpp | 19 ++++++++ src/linux/btop_collect.cpp | 96 +++++++++++++++++++++++++++++--------- 6 files changed, 118 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index e2229e58..cc8ef6b0 100644 --- a/README.md +++ b/README.md @@ -1429,6 +1429,9 @@ base_10_sizes = False #* Show CPU frequency. show_cpu_freq = True +#* How to calculate CPU frequency, available values: "first", "range", "lowest", "highest" and "average". +freq_mode = "first" + #* Draw a clock at top of screen, formatting according to strftime, empty string to disable. #* Special formatting: /host = hostname | /user = username | /uptime = system uptime clock_format = "%H:%M" diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 086c3b21..92906c04 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -150,7 +150,9 @@ namespace Config { {"base_10_sizes", "#* Use base 10 for bits/bytes sizes, KB = 1000 instead of KiB = 1024."}, {"show_cpu_freq", "#* Show CPU frequency."}, - + #ifdef __linux__ + {"freq_mode", "#* How to calculate CPU frequency, available values: \"first\", \"range\", \"lowest\", \"highest\" and \"average\"."}, + #endif {"clock_format", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.\n" "#* Special formatting: /host = hostname | /user = username | /uptime = system uptime"}, @@ -243,6 +245,9 @@ namespace Config { {"selected_battery", "Auto"}, {"cpu_core_map", ""}, {"temp_scale", "celsius"}, + #ifdef __linux__ + {"freq_mode", "first"}, + #endif {"clock_format", "%X"}, {"custom_cpu_name", ""}, {"disks_filter", ""}, diff --git a/src/btop_config.hpp b/src/btop_config.hpp index 075fced4..44277625 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -50,6 +50,9 @@ namespace Config { #endif }; const vector temp_scales = { "celsius", "fahrenheit", "kelvin", "rankine" }; +#ifdef __linux__ + const vector freq_modes = { "first", "range", "lowest", "highest", "average" }; +#endif #ifdef GPU_SUPPORT const vector show_gpu_values = { "Auto", "On", "Off" }; #endif diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 13728d62..f8557d00 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -812,9 +812,16 @@ namespace Cpu { + Theme::c("graph_text") + "up" + Mv::r(1) + upstr; } + #ifdef __linux__ + const bool freq_range = Config::getS("freq_mode") == "range"; + #else + const bool freq_range = false; + #endif + //? Cpu clock and cpu meter if (Config::getB("show_cpu_freq") and not cpuHz.empty()) - out += Mv::to(b_y, b_x + b_width - 10) + Fx::ub + Theme::c("div_line") + Symbols::h_line * (7 - cpuHz.size()) + out += Mv::to(b_y, b_x + b_width - (freq_range ? 20 : 10)) + Fx::ub + Theme::c("div_line") + + Symbols::h_line * ((freq_range ? 17 : 7) - cpuHz.size()) + Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right; out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(safeVal(cpu.cpu_percent, "total"s).back()) @@ -2117,9 +2124,14 @@ namespace Draw { auto& custom = Config::getS("custom_cpu_name"); static const bool hasCpuHz = not Cpu::get_cpuHz().empty(); + #ifdef __linux__ + static const bool freq_range = Config::getS("freq_mode") == "range"; + #else + static const bool freq_range = false; + #endif const string cpu_title = uresize( (custom.empty() ? Cpu::cpuName : custom), - b_width - (Config::getB("show_cpu_freq") and hasCpuHz ? 14 : 4) + b_width - (Config::getB("show_cpu_freq") and hasCpuHz ? (freq_range ? 24 : 14) : 5) ); box += createBox(b_x, b_y, b_width, b_height, "", false, cpu_title); } diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index ae2355d8..527559e7 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -500,6 +500,22 @@ namespace Menu { "", "Can cause slowdowns on systems with many", "cores and certain kernel versions."}, + #ifdef __linux__ + {"freq_mode", + "How the CPU frequency will be displayed.", + "", + "First, get the frequency from the first", + "core.", + "", + "Range, show the lowest and the highest", + "frequency.", + "", + "Lowest, the lowest frequency.", + "", + "Highest, the highest frequency.", + "", + "Average, sum and divide."}, + #endif {"custom_cpu_name", "Custom cpu model name in cpu percentage box.", "", @@ -1202,6 +1218,9 @@ static int optionsMenu(const string& key) { {"color_theme", std::cref(Theme::themes)}, {"log_level", std::cref(Logger::log_levels)}, {"temp_scale", std::cref(Config::temp_scales)}, + #ifdef __linux__ + {"freq_mode", std::cref(Config::freq_modes)}, + #endif {"proc_sorting", std::cref(Proc::sort_vector)}, {"graph_symbol", std::cref(Config::valid_graph_symbols)}, {"graph_symbol_cpu", std::cref(Config::valid_graph_symbols_def)}, diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index d91b00c6..90104c9c 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -93,10 +93,10 @@ long long get_monotonicTimeUSec() namespace Cpu { vector core_old_totals; vector core_old_idles; + vector core_freq; vector available_fields = {"Auto", "total"}; vector available_sensors = {"Auto"}; cpu_info current_cpu; - fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq"; bool got_sensors{}; bool cpu_temp_only{}; bool supports_watts = true; @@ -289,11 +289,19 @@ namespace Shared { } //? Init for namespace Cpu - if (not fs::exists(Cpu::freq_path) or access(Cpu::freq_path.c_str(), R_OK) == -1) Cpu::freq_path.clear(); Cpu::current_cpu.core_percent.insert(Cpu::current_cpu.core_percent.begin(), Shared::coreCount, {}); Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {}); Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0); Cpu::core_old_idles.insert(Cpu::core_old_idles.begin(), Shared::coreCount, 0); + + for (int i = 0; i < Shared::coreCount; ++i) { + Cpu::core_freq.push_back("/sys/devices/system/cpu/cpufreq/policy" + to_string(i) + "/scaling_cur_freq"); + if (not fs::exists(Cpu::core_freq[i]) or access(Cpu::core_freq[i].c_str(), R_OK) == -1) { + Cpu::core_freq[i].clear(); + Cpu::core_freq.pop_back(); + } + } + Cpu::collect(); if (Runner::coreNum_reset) Runner::coreNum_reset = false; for (auto& [field, vec] : Cpu::current_cpu.cpu_percent) { @@ -552,6 +560,26 @@ namespace Cpu { } } + static string normalize_frequency(double hz) { + string str; + if (hz > 999999) { + str = fmt::format("{:.1f}", hz / 1'000'000); + str.resize(3); + if (str.back() == '.') str.pop_back(); + str += " THz"; + } + else if (hz > 999) { + str = fmt::format("{:.1f}", hz / 1'000); + str.resize(3); + if (str.back() == '.') str.pop_back(); + str += " GHz"; + } + else { + str = fmt::format("{:.0f} MHz", hz); + } + return str; + } + string get_cpuHz() { static int failed{}; @@ -559,13 +587,49 @@ namespace Cpu { return ""s; string cpuhz; + + const auto &freq_mode = Config::getS("freq_mode"); + try { - double hz{}; - //? Try to get freq from /sys/devices/system/cpu/cpufreq/policy first (faster) - if (not freq_path.empty()) { - hz = stod(readfile(freq_path, "0.0")) / 1000; - if (hz <= 0.0 and ++failed >= 2) - freq_path.clear(); + double hz = 0.0; + // Read frequencies from all CPU cores + vector frequencies; + unsigned long cpu_count = freq_mode == "first" ? std::min(static_cast(1),Cpu::core_freq.size()) : Cpu::core_freq.size(); + for (unsigned int i = 0; i < cpu_count; ++i) { + if (not Cpu::core_freq[i].empty()) { + double core_hz = stod(readfile(Cpu::core_freq[i], "0.0")) / 1000; + if (core_hz <= 0.0 and ++failed >= 2) { + Cpu::core_freq[i].clear(); + Cpu::core_freq.erase(Cpu::core_freq.begin() + i--); + } else { + frequencies.push_back(core_hz); + } + } + } + + if (not frequencies.empty()) { + if (freq_mode == "first") { + hz = frequencies.front(); + } + if (freq_mode == "average") { + hz = std::accumulate(frequencies.begin(), frequencies.end(), 0.0) / static_cast(frequencies.size()); + } + else if (freq_mode == "highest") { + hz = *std::max_element(frequencies.begin(), frequencies.end()); + } + else if (freq_mode == "lowest") { + hz = *std::min_element(frequencies.begin(), frequencies.end()); + } + else if (freq_mode == "range") { + auto [min_hz,max_hz] = std::minmax_element(frequencies.begin(), frequencies.end()); + + // Format as range + string min_str, max_str; + min_str = normalize_frequency(*min_hz); + max_str = normalize_frequency(*max_hz); + + return min_str + " - " + max_str; + } } //? If freq from /sys failed or is missing try to use /proc/cpuinfo if (hz <= 0.0) { @@ -588,21 +652,7 @@ namespace Cpu { if (hz <= 1 or hz >= 999999999) throw std::runtime_error("Failed to read /sys/devices/system/cpu/cpufreq/policy and /proc/cpuinfo."); - if (hz > 999999) { - cpuhz = fmt::format("{:.1f}", hz / 1'000'000); - cpuhz.resize(3); - if (cpuhz.back() == '.') cpuhz.pop_back(); - cpuhz += " THz"; - } - else if (hz > 999) { - cpuhz = fmt::format("{:.1f}", hz / 1'000); - cpuhz.resize(3); - if (cpuhz.back() == '.') cpuhz.pop_back(); - cpuhz += " GHz"; - } - else { - cpuhz = fmt::format("{:.0f} MHz", hz); - } + cpuhz = normalize_frequency(hz); } catch (const std::exception& e) {