Skip to content

Commit f6d3fac

Browse files
committed
benchmarks/lockhammer/src/cpufreq-scaling-detect.c
Inspect CPU frequency scaling driver and governor settings. Detect if the combination will provide for a stable performance measurement, in particular when the CPU frequency changes under load. Warn if the driver/governor settings are known to affect performance. Change-Id: Ic4036f302b9f1ab60b1bbbaea44cff01e3439316
1 parent ad23dee commit f6d3fac

File tree

1 file changed

+330
-0
lines changed

1 file changed

+330
-0
lines changed
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
2+
/*
3+
* Copyright (c) 2024-2025, The Linux Foundation. All rights reserved.
4+
*
5+
* SPDX-License-Identifier: BSD-3-Clause
6+
*
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are
9+
* met:
10+
* * Redistributions of source code must retain the above copyright
11+
* notice, this list of conditions and the following disclaimer.
12+
* * Redistributions in binary form must reproduce the above
13+
* copyright notice, this list of conditions and the following
14+
* disclaimer in the documentation and/or other materials provided
15+
* with the distribution.
16+
* * Neither the name of The Linux Foundation nor the names of its
17+
* contributors may be used to endorse or promote products derived
18+
* from this software without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
21+
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
22+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
23+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
24+
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27+
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28+
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29+
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
30+
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
33+
#define _GNU_SOURCE
34+
#include <stdio.h>
35+
#include <stdlib.h>
36+
#include <string.h>
37+
38+
#include "verbose.h"
39+
40+
41+
/*
42+
* Check if the cpufreq driver and governor are acceptable configurations.
43+
*
44+
* A good configuration runs all CPUs at scaling_max_freq regardless of
45+
* the number of threads (CPUs) active.
46+
*
47+
* A bad configuration reduces the CPU frequency based on load and then
48+
* takes some time to return to a higher frequency.
49+
*
50+
* Dynamic CPU frequency scaling is a complicated subject because there are
51+
* many different behaviors for driver and governor combinations. These
52+
* behaviors also vary by processor and platform, so the frequency behavior
53+
* while under load must be examined using external analysis tools, such as
54+
* "perf stat -e cycles -I 1000 -C $cpu taskset -c $cpu busy-loop-program".
55+
*
56+
* In any event, warn if "conservative" or "ondemand" is used.
57+
*/
58+
59+
typedef struct {
60+
const char * scaling_driver;
61+
const char * scaling_governor;
62+
} scaling_t;
63+
64+
scaling_t known_good_cpufreq_scalers[] = {
65+
{.scaling_driver = "", .scaling_governor = ""}, // for the case where there is no cpu scaling
66+
{.scaling_driver = "cppc_cpufreq", .scaling_governor = "performance"},
67+
{.scaling_driver = "acpi-cpufreq", .scaling_governor = "performance"},
68+
};
69+
70+
scaling_t known_bad_cpufreq_scalers[] = {
71+
{.scaling_driver = NULL, .scaling_governor = "conservative"},
72+
{.scaling_driver = NULL, .scaling_governor = "ondemand"},
73+
};
74+
75+
76+
77+
// get_proc_file_first_line - return the first line from the filename in a malloc'ed buffer, which must be free'd later
78+
static char * get_proc_file_first_line (const char * filename) {
79+
FILE * f = fopen (filename, "r");
80+
if (f == NULL) {
81+
// system may not have cpufreq available
82+
// fprintf(stderr, "couldn't open %s for reading\n", filename);
83+
goto return_empty_string;
84+
}
85+
86+
size_t line_len = 0;
87+
char * line = NULL; // have getline mallocate a buffer
88+
ssize_t nread;
89+
90+
while ((nread = getline(&line, &line_len, f)) != -1) {
91+
//printf("nread = %zd, line_len = %zu, line = %s\n", nread, line_len, line);
92+
93+
// getline includes the newline if it was there
94+
// nread includes the newline
95+
96+
if (nread == 0) {
97+
break;
98+
}
99+
100+
// strip newline if it exists
101+
if (line[nread-1] == '\n') {
102+
line[nread-1] = '\0';
103+
}
104+
105+
fclose(f);
106+
return line;
107+
}
108+
109+
fprintf(stderr, "unexpectedly getline() returned -1 from reading %s\n", filename);
110+
111+
free(line);
112+
fclose(f);
113+
114+
return_empty_string:
115+
line = malloc(1);
116+
*line = '\0';
117+
return line;
118+
}
119+
120+
121+
static char * get_scaling_string(unsigned long cpunum, const char * filename) {
122+
char full_path[200];
123+
124+
int r = snprintf(full_path, sizeof(full_path),
125+
"/sys/devices/system/cpu/cpufreq/policy%lu/%s", cpunum, filename);
126+
127+
if (r >= sizeof(full_path)) {
128+
fprintf(stderr, "couldn't construct full_path to read %s\n", filename);
129+
exit(-1);
130+
}
131+
132+
return get_proc_file_first_line(full_path);
133+
}
134+
135+
static char * get_scaling_governor(unsigned long cpunum) {
136+
return get_scaling_string(cpunum, "scaling_governor");
137+
}
138+
139+
static char * get_scaling_driver(unsigned long cpunum) {
140+
return get_scaling_string(cpunum, "scaling_driver");
141+
}
142+
143+
static char * get_scaling_max_freq(unsigned long cpunum) {
144+
return get_scaling_string(cpunum, "scaling_max_freq");
145+
}
146+
147+
static char * get_scaling_min_freq(unsigned long cpunum) {
148+
return get_scaling_string(cpunum, "scaling_min_freq");
149+
}
150+
151+
static int get_boost_value(void) {
152+
char * pc = get_proc_file_first_line("/sys/devices/system/cpu/cpufreq/boost");
153+
long x = strtol(pc, NULL, 0); // XXX: if pc is empty string (due to the boost file not existing) this will parse to 0.
154+
free(pc);
155+
return x;
156+
}
157+
158+
// return 1 if found in scaling_list
159+
static int search_cpufreq_list(scaling_t * scaling_list, size_t list_len, char * scaling_driver, char * scaling_governor) {
160+
for (size_t i = 0; i < list_len; i++) {
161+
if (scaling_list[i].scaling_driver &&
162+
strcmp(scaling_driver, scaling_list[i].scaling_driver)) {
163+
continue;
164+
}
165+
166+
if (scaling_list[i].scaling_governor &&
167+
strcmp(scaling_governor, scaling_list[i].scaling_governor)) {
168+
continue;
169+
}
170+
171+
return 1;
172+
}
173+
174+
return 0;
175+
}
176+
177+
static int check_scaling_min_max_freq_are_equal(unsigned long cpunum, int ignore, int verbose, int suppress) {
178+
char * scaling_min_freq = get_scaling_min_freq(cpunum);
179+
char * scaling_max_freq = get_scaling_max_freq(cpunum);
180+
int are_equal = 1;
181+
182+
if (strcmp(scaling_min_freq, scaling_max_freq)) {
183+
are_equal = 0;
184+
if (! suppress)
185+
printf("%s: CPU %lu scaling_min_freq != scaling_max_freq (%s to %s)\n",
186+
ignore? "WARNING" : "ERROR", cpunum, scaling_min_freq, scaling_max_freq);
187+
} else if (verbose >= VERBOSE_MORE)
188+
if (! suppress)
189+
printf("%s: CPU %lu scaling_min_freq == scaling_max_freq (%s to %s)\n",
190+
"INFO", cpunum, scaling_min_freq, scaling_max_freq);
191+
192+
free(scaling_min_freq);
193+
free(scaling_max_freq);
194+
195+
return are_equal;
196+
}
197+
198+
// returns 1 if OK, 0 if not
199+
int check_cpufreq_boost_is_OK(int ignore, int verbose, int suppress) {
200+
// OK == boost is off
201+
202+
int boost = get_boost_value();
203+
if (verbose >= VERBOSE_MORE) {
204+
printf("boost = %d\n", boost);
205+
}
206+
207+
if (0 == boost) {
208+
return 1;
209+
}
210+
211+
if (! suppress)
212+
printf("%s: boost is enabled (value = %d), so CPU will adjust frequency based on load, which may affect performance.\n",
213+
ignore ? "WARNING" : "ERROR", boost);
214+
return ignore ? 1 : 0;
215+
}
216+
217+
// returns 1 if OK, 0 if not
218+
int check_cpufreq_governor_is_OK_on_cpunum (unsigned long cpunum, int ignore, int verbose, int suppress) {
219+
char * scaling_driver = get_scaling_driver(cpunum);
220+
char * scaling_governor = get_scaling_governor(cpunum);
221+
222+
const size_t known_good_cpufreq_scalers_list_len = sizeof(known_good_cpufreq_scalers)/sizeof(known_good_cpufreq_scalers[0]);
223+
224+
if (search_cpufreq_list(known_good_cpufreq_scalers, known_good_cpufreq_scalers_list_len, scaling_driver, scaling_governor)) {
225+
if (verbose >= VERBOSE_YES)
226+
printf("found on CPU %lu scaling {driver=%s, governor=%s}\n",
227+
cpunum, scaling_driver, scaling_governor);
228+
free(scaling_driver);
229+
free(scaling_governor);
230+
231+
if (check_scaling_min_max_freq_are_equal(cpunum, ignore, verbose, suppress) || ignore) {
232+
return 1;
233+
}
234+
235+
return 0;
236+
}
237+
238+
const size_t known_bad_cpufreq_scalers_list_len = sizeof(known_bad_cpufreq_scalers)/sizeof(known_bad_cpufreq_scalers[0]);
239+
240+
if (search_cpufreq_list(known_bad_cpufreq_scalers, known_bad_cpufreq_scalers_list_len, scaling_driver, scaling_governor)) {
241+
if (! suppress)
242+
printf("%s: On CPU %lu, cpu scaling {driver=%s, governor=%s} is known bad\n",
243+
ignore ? "WARNING" : "ERROR", cpunum, scaling_driver, scaling_governor);
244+
free(scaling_driver);
245+
free(scaling_governor);
246+
return 0;
247+
}
248+
249+
// The intel_pstate driver's "performance" governor adjusts the frequency
250+
// based on load. This may be done by the operating system or autonomously
251+
// by the processor itself. Other cpufreq "performance" governors have the
252+
// CPU run at or in between the scaling_min/max_freq limit parameters, and
253+
// by setting them equal, the CPU should run at that freq. However, with
254+
// intel_pstate, setting the min/max frequency limits to be equal does not
255+
// ensure that the CPU operates at that specific frequency. For example, a
256+
// CPU may run at an operating frequency lower than the min frequency if
257+
// enough cores are active.
258+
259+
if ((0 == strcmp(scaling_driver, "intel_pstate"))) {
260+
if (! suppress)
261+
printf("%s: intel_pstate governor detected on CPU %lu may change frequency depending on the load\n",
262+
ignore ? "WARNING" : "ERROR", cpunum);
263+
264+
if (check_scaling_min_max_freq_are_equal(cpunum, ignore, verbose, suppress)) {
265+
if (! suppress)
266+
printf("%s: intel_pstate CPU %lu frequency limits are equal, but the actual operating frequency may be autonomously determined by the hardware. Please check using perf stat!\n",
267+
ignore ? "WARNING" : "ERROR", cpunum);
268+
}
269+
270+
// Either way, we can't really do anything about it, other than inform which driver+governor is in use
271+
272+
free(scaling_driver);
273+
free(scaling_governor);
274+
return ignore;
275+
}
276+
277+
if (! suppress)
278+
printf("%s: On CPU %lu, cpu scaling {driver=%s, governor=%s} is not known good nor known bad\n",
279+
ignore ? "WARNING" : "ERROR", cpunum, scaling_driver, scaling_governor);
280+
281+
// TODO: for {driver!=intel_pstate,governor=any} and {driver=intel_pstate,governor=any},
282+
// check that scaling_max_freq and scaling_min_freq are equal so that frequency is not
283+
// changed due to load.
284+
285+
free(scaling_driver);
286+
free(scaling_governor);
287+
288+
return 0;
289+
}
290+
291+
#ifdef TEST
292+
int is_scaling_governor(unsigned long cpunum, const char * expect_governor) {
293+
char * governor_string = get_scaling_governor(cpunum);
294+
295+
if (governor_string) {
296+
int retval = strcmp(governor_string, expect_governor);
297+
free(governor_string);
298+
return retval;
299+
}
300+
301+
return 0;
302+
}
303+
304+
int main (int argc, char ** argv) {
305+
306+
const char * expect_governor = "ondemand";
307+
unsigned long cpunum = 0;
308+
309+
if (argc > 1) {
310+
cpunum = strtoul(argv[1], NULL, 0);
311+
if (argc > 2) {
312+
expect_governor = argv[2];
313+
}
314+
}
315+
316+
if (is_scaling_governor(cpunum, expect_governor)) {
317+
printf("scaling_governor for cpu%lu is %s\n", cpunum, expect_governor);
318+
} else {
319+
printf("scaling_governor for cpu%lu is not %s\n", cpunum, expect_governor);
320+
}
321+
322+
char *test_string = get_proc_file_first_line("/proc/this-does-not-exist");
323+
printf("test_string = %s, strlen = %zu\n", test_string, strlen(test_string));
324+
free(test_string);
325+
326+
return 0;
327+
}
328+
#endif
329+
330+
/* vim: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */

0 commit comments

Comments
 (0)