This repository has been archived on 2023-08-23. You can view files and clone it, but cannot push or open issues or pull requests.
obsdfreqd/main.c

604 lines
19 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#include <math.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <time.h>
#include <sys/time.h>
#include <sys/sensors.h>
#include <sys/sched.h>
#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;
int threshold, batt_threshold, wall_threshold;
int down_step, batt_down_step, wall_down_step;
int inertia, batt_inertia, wall_inertia;
int step, batt_step, wall_step;
int timefreq, batt_timefreq, wall_timefreq;
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);
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 value = 0;
size_t len = sizeof(sensor);
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;
return(value);
}
/* define the policy to auto or manual */
void set_policy(const char* policy) {
int mib[2] = { CTL_HW, HW_PERFPOLICY };
if (sysctl(mib, 2, NULL, 0, (void *)policy, strlen(policy) + 1) == -1)
err(1, "sysctl: setting policy");
}
/* restore policy auto upon exit */
void quit_gracefully(int signum) {
fprintf(stderr, "Caught signal %d, set auto policy\n", signum);
set_policy("auto");
exit(0);
}
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 [-S sensorpath]]\n");
}
/* switch to wall profile */
void switch_wall() {
hard_min_freq = wall_min;
hard_max_freq = max = wall_max;
threshold = wall_threshold;
down_step = wall_down_step;
inertia = wall_inertia;
step = wall_step;
timefreq = wall_timefreq;
temp_max = wall_tmax;
}
/* switch to battery profile */
void switch_batt() {
hard_min_freq = batt_min;
hard_max_freq = max = batt_max;
threshold = batt_threshold;
down_step = batt_down_step;
inertia = batt_inertia;
step = batt_step;
timefreq = batt_timefreq;
temp_max = batt_tmax;
}
/* assign values to variable if comma separated
* if not, assign value to two variables
*/
void assign_values_from_param(char* parameter, int* charging, int* battery) {
int count = 0;
char *token = strtok(parameter, ",");
while (token != NULL) {
if(count == 0)
*charging = atoi(token);
if(count == 1)
*battery = atoi(token);
token = strtok(NULL, ",");
count++;
if(count > 1)
break;
}
if(count == 0) {
*charging = atoi(parameter);
*battery = *charging;
}
if(count == 1)
*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;
int mib_perf[2] = { CTL_HW, HW_SETPERF };
int mib_powerplug[2] = { CTL_HW, HW_POWER };
int mib_load[2] = { CTL_KERN, KERN_CPTIME };
long cpu[CPUSTATES], cpu_previous[CPUSTATES];
int frequency = 0;
int current_mode = 0;
int value, current_frequency, inertia_timer = 0;
int cpu_usage_percent = 0, cpu_usage;
float temp;
size_t len, len_cpu;
// battery defaults
hard_min_freq = batt_min= 0;
hard_max_freq = batt_max= 100;
threshold = batt_threshold= 30;
down_step = batt_down_step= 100;
inertia = batt_inertia= 5;
step = batt_step= 100;
timefreq = batt_timefreq= 100;
temp_max = batt_tmax= 0;
// wall defaults
wall_min= 0;
wall_max= 100;
wall_threshold= 30;
wall_down_step= 30;
wall_inertia= 10;
wall_step= 100;
wall_timefreq= 100;
wall_tmax= 0;
if (unveil("/var/empty", "r") == -1)
err(1, "unveil failed");
unveil(NULL, NULL);
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);
if(down_step > 100 || down_step <= 0)
err(1, "decay step must be positive and up to 100");
break;
case 'i':
assign_values_from_param(optarg, &wall_inertia, &batt_inertia);
if(inertia < 0)
err(1, "inertia must be positive");
break;
case 'l':
assign_values_from_param(optarg, &wall_min, &batt_min);
if(hard_min_freq > 100 || hard_min_freq < 0)
err(1, "minimum frequency must be between 0 and 100");
break;
case 'm':
assign_values_from_param(optarg, &wall_max, &batt_max);
if(hard_max_freq > 100 || hard_max_freq < 0)
err(1, "maximum frequency must be between 0 and 100");
break;
case 'v':
verbose = 1;
break;
case 'r':
assign_values_from_param(optarg, &wall_threshold, &batt_threshold);
if(threshold < 0)
err(1, "CPU use threshold must be positive");
break;
case 's':
assign_values_from_param(optarg, &wall_step, &batt_step);
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)
err(1, "time frequency must be positive");
break;
case 'T':
assign_values_from_param(optarg, &wall_tmax, &batt_tmax);
if(wall_tmax <= 0 || batt_tmax <= 0)
err(1, "temperature must be positive");
break;
case 'h':
default:
usage();
return 1;
}
}
len = sizeof(value);
len_cpu = sizeof(cpu);
signal(SIGINT, quit_gracefully);
signal(SIGTERM, quit_gracefully);
set_policy("manual");
switch_batt();
if(hard_max_freq < hard_min_freq)
err(1, "maximum frequency can't be smaller than minimum frequency");
if (verbose) {
if(temp_max > 0) {
printf("mode;Temperature;maximum_frequency;current_frequency;cpu usage;inertia;new frequency\n");
} else {
printf("mode;current_frequency;cpu usage;inertia;new frequency\n");
}
}
/* 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");
usleep(1000*500);
/* main loop */
for(;;) {
/* get if using power plug or not */
if (sysctl(mib_powerplug, 2, &value, &len, NULL, 0) == -1)
err(1, "sysctl");
if(verbose) printf("%i;", value);
if(current_mode != value) {
current_mode = value;
if(value == 0)
switch_batt();
else
switch_wall();
}
/* manage temperature */
if(temp_max > 0) {
temp = get_temp();
if(temp > temp_max) {
if(max > hard_min_freq)
max--;
} else {
if(max < hard_max_freq)
max++;
}
if(verbose) printf("%.0f;%i;", temp, max);
}
/* get current frequency */
if (sysctl(mib_perf, 2, &current_frequency, &len, NULL, 0) == -1)
err(1, "sysctl");
if(verbose) printf("%i;", current_frequency);
/* get where the CPU time is spent, last field is IDLE */
if (sysctl(mib_load, 2, &cpu, &len_cpu, NULL, 0) == -1)
err(1, "sysctl");
/* calculate delta between old and last cpu readings */
cpu_usage = cpu[0]-cpu_previous[0] +
cpu[1]-cpu_previous[1] +
cpu[2]-cpu_previous[2] +
cpu[3]-cpu_previous[3] +
cpu[4]-cpu_previous[4] +
cpu[5]-cpu_previous[5];
cpu_usage_percent = 100-round(100*(cpu[5]-cpu_previous[5])/cpu_usage);
memcpy(cpu_previous, cpu, sizeof(cpu));
if(verbose) printf("%i;", cpu_usage_percent);
/* change frequency */
len = sizeof(frequency);
/* small brain condition to increase CPU */
if(cpu_usage_percent > threshold) {
/* increase frequency by step if under max */
if(frequency+step < max)
frequency = frequency + step;
else
frequency = max;
/* don't try to set frequency more than 100% */
if( frequency > hard_max_freq )
frequency = hard_max_freq;
if(inertia_timer < inertia)
inertia_timer++;
if (sysctl(mib_perf, 2, NULL, 0, &frequency, len) == -1)
err(1, "sysctl");
} else {
if(inertia_timer == 0) {
/* keep frequency more than min */
if(frequency-down_step < hard_min_freq)
frequency = hard_min_freq;
else
frequency = frequency - down_step;
/* don't try to set frequency below 0% */
if (frequency < hard_min_freq )
frequency = hard_min_freq;
if (sysctl(mib_perf, 2, NULL, 0, &frequency, len) == -1)
err(1, "sysctl");
} else {
inertia_timer--;
}
}
if(verbose) printf("%i;%i\n", inertia_timer, frequency);
usleep(1000*timefreq);
}
return(0);
}