Skip to content

Commit bbcc7db

Browse files
rantalaclaude
andcommitted
cpu_information: report turbo/boost state, hardware peak, and P/E class
The existing per-CPU line printed only the kernel governor's scaling window via scaling_min_freq / scaling_max_freq. On a system with boost disabled, or on hybrid CPUs where different cores have different boost ceilings, that window is the same number on every core and conveys nothing about whether the CPU can actually run faster than displayed. Add three signals so the benchmark banner reflects what the silicon can do: 1. Turbo/boost state -- a single global line. Reads /sys/devices/system/cpu/cpufreq/boost first (AMD CPB, acpi-cpufreq, amd-pstate); falls back to /sys/devices/system/cpu/intel_pstate/no_turbo (intel_pstate, inverse-sense). Reported as on/off; the line is omitted when neither file is readable. 2. Hardware peak frequency -- per CPU, when it exceeds the scaling cap. Reads amd_pstate_max_freq first (the per-core boost ceiling reported by amd-pstate; differs across cores on AMD hybrid parts), then cpuinfo_max_freq (Intel turbo cap). Suppressed when equal to scaling_max_freq so the line stays short on non-hybrid / boost-on systems. 3. P-core / E-core label -- per CPU. Resolved from /sys/devices/cpu_core/cpus (P) and /sys/devices/cpu_atom/cpus (E), the Intel hybrid PMU sysfs. Empty on non-hybrid Intel and on AMD (where the hardware-peak value already exposes the split, e.g. 5090 MHz Zen 5 vs 3506 MHz Zen 5c on a Ryzen AI 7 PRO 350). Sample on the AMD Ryzen AI 7 PRO 350 (boost disabled, 2 GHz scaling cap): CPU information: CPUs allowed: 0-1,4-5 Turbo/boost: off CPU0, scaling [623MHz .. 2000MHz], hw peak 5090MHz CPU1, scaling [623MHz .. 2000MHz], hw peak 3506MHz CPU4, scaling [623MHz .. 2000MHz], hw peak 5090MHz CPU5, scaling [623MHz .. 2000MHz], hw peak 3506MHz Incidental cleanup: the scaling range now prints [min .. max] (was [max .. min]) since "scaling ... hw peak X" only reads naturally with an ascending range. API shape: cpu_scaling_min_freq / cpu_scaling_max_freq collapse into a single cpu_info_for(cpu) returning a struct (scaling min/max, hw peak, core class). cpu_boost_enabled() returns std::optional<bool>. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 36b3187 commit bbcc7db

3 files changed

Lines changed: 93 additions & 20 deletions

File tree

src/sortstring.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -521,16 +521,26 @@ cpu_information(void)
521521
std::printf("CPU information:\n");
522522
if (!cpus_al.empty())
523523
std::printf(" CPUs allowed: %s\n", cpus_al.c_str());
524+
if (auto boost = cpu_boost_enabled())
525+
std::printf(" Turbo/boost: %s\n", *boost ? "on" : "off");
524526
for (int i=0; i < maxcpu; ++i) {
525-
if (CPU_ISSET_S(i, cpus_setsize, cpus)) {
526-
std::printf(" CPU%d", i);
527-
auto min_freq = cpu_scaling_min_freq(i);
528-
auto max_freq = cpu_scaling_max_freq(i);
529-
if (min_freq && max_freq)
530-
std::printf(", scaling frequencies: [%dMHz .. %dMHz]",
531-
*max_freq/1000, *min_freq/1000);
532-
std::puts("");
527+
if (!CPU_ISSET_S(i, cpus_setsize, cpus))
528+
continue;
529+
const cpu_info info = cpu_info_for(i);
530+
std::printf(" CPU%d", i);
531+
switch (info.klass) {
532+
case cpu_info::core_class::performance: std::printf(" (P-core)"); break;
533+
case cpu_info::core_class::efficiency: std::printf(" (E-core)"); break;
534+
case cpu_info::core_class::unknown: break;
533535
}
536+
if (info.scaling_min_khz && info.scaling_max_khz)
537+
std::printf(", scaling [%dMHz .. %dMHz]",
538+
*info.scaling_min_khz/1000,
539+
*info.scaling_max_khz/1000);
540+
if (info.hw_max_khz && info.scaling_max_khz &&
541+
*info.hw_max_khz > *info.scaling_max_khz)
542+
std::printf(", hw peak %dMHz", *info.hw_max_khz/1000);
543+
std::puts("");
534544
}
535545
std::putchar('\n');
536546
if (cpus)

src/util/cpus_allowed.cpp

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,36 @@ read_int_file(const std::string &path)
115115
return v;
116116
}
117117

118+
/* Parse a Linux cpulist string ("0-3,5,7-9") and report whether `cpu`
119+
* is a member. Returns std::nullopt if the file cannot be read; false
120+
* if the file is readable but `cpu` is not listed. */
121+
std::optional<bool>
122+
cpu_in_cpulist_file(int cpu, const std::string &path)
123+
{
124+
std::ifstream f(path);
125+
if (!f) return std::nullopt;
126+
std::string list;
127+
std::getline(f, list);
128+
std::stringstream ss(list);
129+
std::string tok;
130+
while (std::getline(ss, tok, ',')) {
131+
auto dash = tok.find('-');
132+
int lo, hi;
133+
try {
134+
if (dash == std::string::npos) {
135+
lo = hi = std::stoi(tok);
136+
} else {
137+
lo = std::stoi(tok.substr(0, dash));
138+
hi = std::stoi(tok.substr(dash + 1));
139+
}
140+
} catch (...) {
141+
continue;
142+
}
143+
if (cpu >= lo && cpu <= hi) return true;
144+
}
145+
return false;
146+
}
147+
118148
} // namespace
119149

120150
std::string
@@ -141,18 +171,39 @@ cpus_allowed()
141171
return info;
142172
}
143173

144-
std::optional<int>
145-
cpu_scaling_min_freq(int cpu)
174+
cpu_info
175+
cpu_info_for(int cpu)
146176
{
147-
std::ostringstream path;
148-
path << "/sys/devices/system/cpu/cpu" << cpu << "/cpufreq/scaling_min_freq";
149-
return read_int_file(path.str());
177+
std::ostringstream base;
178+
base << "/sys/devices/system/cpu/cpu" << cpu << "/cpufreq/";
179+
const std::string b = base.str();
180+
181+
cpu_info info;
182+
info.scaling_min_khz = read_int_file(b + "scaling_min_freq");
183+
info.scaling_max_khz = read_int_file(b + "scaling_max_freq");
184+
/* Hardware peak (boost-capable) frequency:
185+
* - AMD pstate: amd_pstate_max_freq is the per-core boost cap;
186+
* cpuinfo_max_freq tracks scaling_max_freq when boost is off.
187+
* - Intel: cpuinfo_max_freq is the turbo cap. */
188+
info.hw_max_khz = read_int_file(b + "amd_pstate_max_freq");
189+
if (!info.hw_max_khz)
190+
info.hw_max_khz = read_int_file(b + "cpuinfo_max_freq");
191+
/* Intel hybrid topology: cpu_core PMU lists P-cores, cpu_atom E-cores. */
192+
if (cpu_in_cpulist_file(cpu, "/sys/devices/cpu_core/cpus").value_or(false))
193+
info.klass = cpu_info::core_class::performance;
194+
else if (cpu_in_cpulist_file(cpu, "/sys/devices/cpu_atom/cpus").value_or(false))
195+
info.klass = cpu_info::core_class::efficiency;
196+
return info;
150197
}
151198

152-
std::optional<int>
153-
cpu_scaling_max_freq(int cpu)
199+
/* Turbo/boost state, reported as the inverse-sense pair the kernel
200+
* exposes for each driver family. */
201+
std::optional<bool>
202+
cpu_boost_enabled()
154203
{
155-
std::ostringstream path;
156-
path << "/sys/devices/system/cpu/cpu" << cpu << "/cpufreq/scaling_max_freq";
157-
return read_int_file(path.str());
204+
if (auto v = read_int_file("/sys/devices/system/cpu/cpufreq/boost"))
205+
return *v != 0;
206+
if (auto v = read_int_file("/sys/devices/system/cpu/intel_pstate/no_turbo"))
207+
return *v == 0;
208+
return std::nullopt;
158209
}

src/util/cpus_allowed.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,21 @@ struct cpus_info {
3535
int maxcpu = 0;
3636
};
3737

38+
struct cpu_info {
39+
enum class core_class {
40+
unknown,
41+
performance, // Intel P-core (cpu_core PMU)
42+
efficiency, // Intel E-core (cpu_atom PMU)
43+
};
44+
std::optional<int> scaling_min_khz;
45+
std::optional<int> scaling_max_khz;
46+
std::optional<int> hw_max_khz; // amd_pstate_max_freq or cpuinfo_max_freq
47+
core_class klass = core_class::unknown;
48+
};
49+
3850
[[nodiscard]] std::string cpus_allowed_list();
3951
[[nodiscard]] cpus_info cpus_allowed();
40-
[[nodiscard]] std::optional<int> cpu_scaling_max_freq(int cpu);
41-
[[nodiscard]] std::optional<int> cpu_scaling_min_freq(int cpu);
52+
[[nodiscard]] cpu_info cpu_info_for(int cpu);
53+
[[nodiscard]] std::optional<bool> cpu_boost_enabled();
4254

4355
#endif /* CPUS_ALLOWED_H */

0 commit comments

Comments
 (0)