improve temperature throttling

- enhance CPU temperature sensor detection
- add a new parameter to give a sensor identifier

patch from Vlad Meşco, thanks!
This commit is contained in:
Solene Rapenne 2022-10-31 18:21:28 +01:00
parent de605f3552
commit ee170551b0
2 changed files with 280 additions and 13 deletions

283
main.c
View File

@ -11,6 +11,27 @@
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/errno.h>
#include <assert.h>
/* I have to redefined this because obsdfreqd is compiled with
-Wall and I don't know of a compiler agnostic way to disable
warnings, and I don't want to assume `cc' is clang forever */
struct ctlnameconst {
const char* ctl_name; /* struct ctlname has this nonconst */
int ctl_type;
};
#if __STDC_VERSION__ >= 201112L
/* be polite to maintainers and give them a better compile error */
# include <stddef.h> /* offsetof */
static_assert(sizeof(struct ctlnameconst) == sizeof(struct ctlname),
"struct ctlname changed, update ctlnameconst");
static_assert(offsetof(struct ctlnameconst, ctl_name) == offsetof(struct ctlname, ctl_name),
"struct ctlname changed, update ctlnameconst");
static_assert(offsetof(struct ctlnameconst, ctl_type) == offsetof(struct ctlname, ctl_type),
"struct ctlname changed, update ctlnameconst");
#endif
int hard_min_freq, batt_min, wall_min;
int hard_max_freq, batt_max, wall_max;
@ -23,6 +44,12 @@ int temp_max, batt_tmax, wall_tmax;
int verbose = 0;
int max;
/* sysctl path to the temperature sensor we're tracking */
int temperature_mib[5];
/* if false(0), get_temp() will error out */
int temperature_sensor_found;
void try_to_find_default_temperature_sensor(void);
int get_temp(void);
void set_policy(const char*);
void quit_gracefully(int signum);
@ -30,21 +57,95 @@ void usage(void);
void switch_wall(void);
void switch_batt(void);
void assign_values_from_param(char*, int*, int*);
void parse_sensor_path(char*);
/* heuristics to find a good temperature sensor */
void try_to_find_default_temperature_sensor(void) {
/* pick the first temperature sensor, but...
bonify devices named `cpuN'
TODO and we should probably blacklist devices like:
- GPU temperature sensors
- battery temperature sensors
- UPS temperature sensors
...just in case the CPU thermometer is not cpu0, or,
for some bizzare reason, there is no CPU thermometer.
There may be other strange cases, like OpenBSD running
on a Raspberry PI where I can connect an external
temperature sensor and sensor_install that; but I have
a feeling external thermometers would be at the very end */
int dev = 0;
temperature_mib[0] = CTL_HW;
temperature_mib[1] = HW_SENSORS;
for(dev = 0; ; ++dev) {
struct sensordev sensordev;
size_t sensordevlen = sizeof(struct sensordev);
memset(&sensordev, 0, sizeof(struct sensordev));
temperature_mib[2] = dev;
if(-1 == sysctl(
temperature_mib, 3,
&sensordev, &sensordevlen,
NULL, 0)
) {
/* end of array */
break;
}
/* does it have temperature sensors? */
if(sensordev.maxnumt[SENSOR_TEMP] <= 0) {
/* no */
continue;
}
/* potentially use this */
if(!temperature_sensor_found) {
temperature_sensor_found = 1;
temperature_mib[2] = sensordev.num;
temperature_mib[3] = SENSOR_TEMP;
temperature_mib[4] = 0;
}
/* is it called cpuSOMETHING? */
if(strncmp(sensordev.xname, "cpu", 3) == 0) {
/* definitely use this! */
temperature_sensor_found = 1;
temperature_mib[2] = sensordev.num;
temperature_mib[3] = SENSOR_TEMP;
temperature_mib[4] = 0;
/* stop here, this is our best candidate */
break;
}
}
if(verbose) {
if(!temperature_sensor_found) {
fprintf(stderr, "Could not find any temperature sensors.\n");
} else {
/* we need to get the device name again in order to print it */
struct sensordev sensordev;
size_t sensordevlen = sizeof(struct sensordev);
memset(&sensordev, 0, sizeof(struct sensordev));
if(0 == sysctl(temperature_mib, 3, &sensordev, &sensordevlen, NULL, 0)) {
fprintf(stderr,
"Using hw.sensors.%s.temp%d as the temperature sensor\n",
sensordev.xname,
temperature_mib[4]);
} else {
/* panic? */
err(1, "Failed to read sensordev a second time?");
}
}
}
}
int get_temp() {
struct sensor sensor;
int mib[5];
int value = 0;
size_t len = sizeof(sensor);
mib[0] = CTL_HW;
mib[1] = HW_SENSORS;
mib[2] = 0;
mib[3] = SENSOR_TEMP;
mib[4] = 0;
if (sysctl(mib, 5, &sensor, &len, NULL, 0) == -1)
err(1, "sysctl to get temperature");
if (sysctl(temperature_mib, 5, &sensor, &len, NULL, 0) == -1)
err(1, "failed to read temperature");
// convert from uK to C
value = (sensor.value - 273150000) / 1000 / 1000;
@ -62,7 +163,7 @@ void set_policy(const char* policy) {
/* restore policy auto upon exit */
void quit_gracefully(int signum) {
printf("Caught signal %d, set auto policy\n", signum);
fprintf(stderr, "Caught signal %d, set auto policy\n", signum);
set_policy("auto");
exit(0);
}
@ -71,7 +172,7 @@ void usage(void) {
fprintf(stderr,
"obsdfreqd [-hv] [-d downstepfrequency] [-i inertia] [-m maxfrequency]\n"
" [-l minfrequency] [-r threshold] [-s stepfrequency]\n"
" [-t timefreq] [-T maxtemperature]\n");
" [-t timefreq] [-T maxtemperature [-S sensorpath]]\n");
}
/* switch to wall profile */
@ -125,6 +226,147 @@ void assign_values_from_param(char* parameter, int* charging, int* battery) {
*battery = *charging;
}
/* parse optarg for a sensor path, sysctl style;
* expecting hw.sensors.*.temp*;
* sets temperature_mid and temperature_sensor_found
* IFF it's a valid path, else sets errno to something
* relevant.
*/
void parse_sensor_path(char* path) {
const struct ctlnameconst ctlnames[] = CTL_NAMES;
const struct ctlnameconst ctlhwnames[] = CTL_HW_NAMES;
const size_t prefix_len =
/*hw*/strlen(ctlnames[CTL_HW].ctl_name) +
/*.*/1 +
/*sensors*/strlen(ctlhwnames[HW_SENSORS].ctl_name) +
/*.*/1;
char* prefix = malloc(prefix_len + 1);
/* these are pointers into optarg */
char* sensordevname = NULL, *sensortype = NULL, *sensornumts = NULL;
int sensornumt = -1;
const size_t tempslen = strlen(sensor_type_s[SENSOR_TEMP]);
const char* errstr = NULL;
int dev = 0;
if(prefix == NULL) {
errno = ENOMEM;
return;
}
memset(prefix, 0, prefix_len + 1);
strcpy(prefix, ctlnames[CTL_HW].ctl_name);
strcat(prefix, ".");
strcat(prefix, ctlhwnames[HW_SENSORS].ctl_name);
strcat(prefix, ".");
/* we only allow hw.sensors...; everything else is irrelevant */
if(strncmp(path, prefix, prefix_len) != 0) {
if(verbose)
fprintf(stderr,
"Valid temperature sensors start with %s\n"
"Run `sysctl -a' to find sensors",
prefix);
errno = EINVAL;
goto badprefix;
}
/* parse sensordevname and tail */
sensordevname = path + prefix_len;
sensortype = strchr(sensordevname, '.');
if(!sensortype) {
if(verbose)
fprintf(stderr,
"Valid sensor names are of the form hw.sensors.device.temp42\n"
"for example hw.sensors.cpu0.temp0\n");
errno = EINVAL;
goto badprefix;
}
*sensortype++ = '\0'; /* save sensordevname, we need to find it later */
/* assert tail strats with "temp" */
if(strncmp(sensortype, sensor_type_s[SENSOR_TEMP], tempslen) != 0) {
if(verbose)
fprintf(stderr,
"%s does not look like a temperature sensor\n",
path);
errno = EINVAL;
goto badprefix;
}
/* convert remaining string to number = numt */
sensornumts = sensortype + tempslen;
sensornumt = strtonum(sensornumts, 0, INT_MAX, &errstr);
if(errstr != NULL) {
if(verbose)
fprintf(stderr,
"%s does not look like a temperature sensor\n",
path);
errno = EINVAL;
goto badprefix;
}
/* the string format looks good, now let's see if we can actually
find the sensor */
temperature_mib[0] = CTL_HW;
temperature_mib[1] = HW_SENSORS;
/* this is a NULL terminated array of sensordev's */
for(dev = 0; ; ++dev) {
struct sensordev sensordev;
size_t sensordevlen = sizeof(struct sensordev);
memset(&sensordev, 0, sizeof(struct sensordev));
temperature_mib[2] = dev;
if(-1 == sysctl(
temperature_mib, 3,
&sensordev, &sensordevlen,
NULL, 0)
) {
/* probably end of array; sysctl sets errno to ENOENT */
/* the man page doesn't say what other errno's it could
set, so let's assume it *is* ENOENT */
if(verbose)
/* using warn to print errno in case it wasn't ENOENT */
warn("No such sensor device: %s\n", sensordevname);
goto badprefix;
}
assert(sensordevlen == sizeof(struct sensordev));
if(strcmp(sensordevname, sensordev.xname) == 0) {
/* found it; before we yield success, check if it has
a temperature sensor */
if(sensornumt >= sensordev.maxnumt[SENSOR_TEMP]) {
/* no dice */
if(verbose)
fprintf(stderr,
"temp%d is out of range, max is temp%d\n",
sensornumt,
sensordev.maxnumt[SENSOR_TEMP]);
errno = ENOENT;
goto badprefix;
}
temperature_mib[2] = sensordev.num;
break;
}
}
/* okay, we have it; finish populating mib and claim we're done */
temperature_mib[3] = SENSOR_TEMP;
temperature_mib[4] = sensornumt;
temperature_sensor_found = 1;
if(verbose) {
fprintf(stderr,
"Using hw.sensors.%s.temp%d as the temperature sensor\n",
sensordevname,
sensornumt);
}
badprefix:
free(prefix);
}
int main(int argc, char *argv[]) {
int opt;
@ -165,7 +407,7 @@ int main(int argc, char *argv[]) {
err(1, "unveil failed");
unveil(NULL, NULL);
while((opt = getopt(argc, argv, "d:hi:l:m:r:s:t:T:v")) != -1) {
while((opt = getopt(argc, argv, "d:hi:l:m:r:s:S:t:T:v")) != -1) {
switch(opt) {
case 'd':
assign_values_from_param(optarg, &wall_down_step, &batt_down_step);
@ -200,6 +442,11 @@ int main(int argc, char *argv[]) {
if(step > 100 || step <= 0)
err(1, "step must be positive and up to 100");
break;
case 'S':
parse_sensor_path(optarg);
if(!temperature_sensor_found)
err(1, "invalid temperature sensor");
break;
case 't':
assign_values_from_param(optarg, &wall_timefreq, &batt_timefreq);
if(wall_timefreq <= 0 || batt_timefreq <= 0)
@ -236,6 +483,18 @@ int main(int argc, char *argv[]) {
}
}
/* initialize default temp sensor if we need to */
if(temp_max > 0 && !temperature_sensor_found) {
try_to_find_default_temperature_sensor();
}
/* warn if -S was specified, but not -T */
if(temp_max <= 0 && temperature_sensor_found) {
errno = EINVAL;
warn("you have specified a temperature sensor without setting\ntemperature limits with -T");
errno = 0;
}
/* avoid weird reading for first delta */
if (sysctl(mib_load, 2, &cpu_previous, &len_cpu, NULL, 0) == -1)
err(1, "sysctl");

View File

@ -14,7 +14,9 @@
.Op Fl r Ar threshold
.Op Fl s Ar stepfrequency
.Op Fl t Ar timefrequency
.Op Fl T Ar maxtemperature
.Oo Fl T Ar maxtemperature
.Op Fl S Ar sensorpath
.Oc
.Sh DESCRIPTION
.Nm
is a program to play with the CPU frequency scheduler by providing
@ -51,6 +53,12 @@ Defines the maximum temperature in Celcius
.Nm
will allow before lowering the maximum frequency until the temperature
cooled down.
.It Fl S Ar sensorpath
In conjunction with
.Sy -T,
.No allows you to specify which sensor to use for temperature readings. If unspecified,
.Nm
will try to guess which temperature sensor to use (usually hw.sensors.cpu0.temp0).
.El
.Sh EXIT STATUS
.Ex