Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

6 changed files with 71 additions and 412 deletions

2
.gitignore vendored
View File

@ -1,2 +0,0 @@
obsdfreqd
*.orig

View File

@ -3,7 +3,8 @@ LDFLAGS = -lm
CFLAGS += -pedantic -Wall -Wextra -Wmissing-prototypes \
-Wunused-function -Wshadow -Wstrict-overflow -fno-strict-aliasing \
-Wunused-variable -Wstrict-prototypes -Wwrite-strings -O2
-Wunused-variable -Wstrict-prototypes -Wwrite-strings \
-Os
obsdfreqd: main.c
${CC} ${CFLAGS} ${LDFLAGS} main.c -o $@
@ -15,4 +16,5 @@ clean:
install: obsdfreqd
install -o root -g wheel -m 555 obsdfreqd ${PREFIX}/sbin/obsdfreqd
install -o root -g wheel -m 555 obsdfreqd.rc ${DESTDIR}/etc/rc.d/obsdfreqd
install -o root -g wheel -m 444 obsdfreqd.1 ${PREFIX}/man/man1/obsdfreqd.1

View File

@ -1,20 +1,15 @@
# Project moved
Due to technical issues with tildegit.org, the project moved to sourcehut.
- New upstream URL: https://git.sr.ht/~solene/obsdfreqd
# obsdfreqd
Userland CPU frequency scheduling for OpenBSD >= 7.1
# TLDR
- `pkg_add obsdfreqd`
- clone this repository
- as root `make install`
- as root `rcctl enable obsdfreqd` and `rcctl stop apmd ; rcctl disable apmd`
- as root `rcctl start obsdfreqd`
- apmd can be kept but not with flag `-A`
- if any tuning is needed, it may be `-T` for temperature limit
- most interesting flag for end users is `-T`
# Compilation
@ -24,25 +19,10 @@ As easy as `make`
Run `obsdfreqd` as root, quit with `Ctrl+C`.
# Source installation
# Installation
`make install` as root, enable the service using `rcctl enable obsdfreqd`.
Create `/etc/rc.d/obsdfreqd` with this content and make it executable:
```
#!/bin/ksh
daemon="/usr/local/sbin/obsdfreqd"
. /etc/rc.d/rc.subr
pexp="${daemon}.*"
rc_reload=NO
rc_bg=YES
rc_cmd $1
```
`make install` as root, enable the service using `rcctl enable
obsdfreqd`.
Start the service with `rcctl start obsdfreqd`.
@ -58,11 +38,10 @@ Parameters are applied when both plugged on the wall or on battery, parameters c
- `-i inertia` sets the number of cycles after which the frequency will decay, 0 is the default
- `-m maxfrequency` sets the maximum frequency the CPU can reach in percent, 100% is default
- `-l minfrequency` sets the minimum frequency the CPU must be lowered to, 0% is default
- `-r threshold` sets the CPU usage in % that will trigger the frequency increase, defaults to 80% of a single core.
- `-r threshold` sets the CPU usage in % that will trigger the frequency increase, 30% is the default
- `-s stepfrequency` sets the percent of frequency added every cycle when increasing, 10% is default
- `-t timefreq` sets the milliseconds between each poll, 300 is the default
- `-T maxtemperature` sets the temperature threshold under which the maximum frequency will be temporary lowered until the CPU cools down
- `-S sensor` specify a sensor (using its sysctl path) to use with `-T`
**Example**:

361
main.c
View File

@ -11,28 +11,6 @@
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/errno.h>
#include <assert.h>
#include <syslog.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;
@ -45,114 +23,28 @@ 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);
int get_cpu_online(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 mib[5];
int value = 0;
size_t len = sizeof(sensor);
if (!temperature_sensor_found) {
errno = ENOENT;
err(1, "no temperature sensor");
}
mib[0] = CTL_HW;
mib[1] = HW_SENSORS;
mib[2] = 0;
mib[3] = SENSOR_TEMP;
mib[4] = 0;
if (sysctl(temperature_mib, 5, &sensor, &len, NULL, 0) == -1)
err(1, "failed to read temperature");
if (sysctl(mib, 5, &sensor, &len, NULL, 0) == -1)
err(1, "sysctl to get temperature");
// convert from uK to C
value = (sensor.value - 273150000) / 1000 / 1000;
@ -160,21 +52,6 @@ int get_temp() {
return(value);
}
/* get the number of online CPUs */
int get_cpu_online(void) {
int mib_cpu[2] = { CTL_HW, HW_NCPUONLINE };
int ncpu = 1;
size_t len_ncpu = sizeof(len_ncpu);
if (sysctl(mib_cpu, 2, &ncpu, &len_ncpu, NULL, 0) == -1)
err(1, "sysctl");
if (ncpu <= 0)
err(1, "cpuonline detection");
return(ncpu);
}
/* define the policy to auto or manual */
void set_policy(const char* policy) {
int mib[2] = { CTL_HW, HW_PERFPOLICY };
@ -185,39 +62,13 @@ void set_policy(const char* policy) {
/* restore policy auto upon exit */
void quit_gracefully(int signum) {
char message[48];
strncpy(message, "Caught signal ", 14);
switch(signum) {
case SIGINT:
strncat(message, "SIGINT", 6);
break;
case SIGTERM:
strncat(message, "SIGTERM", 7);
break;
default:
break;
}
strncat(message, ", set auto policy\n", 18);
if (isatty(STDERR_FILENO)) {
write(STDERR_FILENO, message, strlen(message));
} else {
struct syslog_data data = SYSLOG_DATA_INIT;
syslog_r(LOG_DAEMON|LOG_INFO, &data, "%s", message);
}
printf("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");
printf("obsdfreqd [-h] [-v] [-i cycles] [-l min_freq] [-m max_freq] [-d percent_down_freq_step] [-r threshold] [-s percent_freq_step] [-t milliseconds]\n");
}
/* switch to wall profile */
@ -247,15 +98,9 @@ void switch_batt() {
/* assign values to variable if comma separated
* if not, assign value to two variables
*/
void assign_values_from_param(char* parameterk, int* charging, int* battery) {
char *parameter = strdup(parameterk);
void assign_values_from_param(char* parameter, int* charging, int* battery) {
int count = 0;
char *token = NULL;
if(parameter == NULL)
err(1, "malloc failed");
token = strtok(parameter, ",");
char *token = strtok(parameter, ",");
while (token != NULL) {
if(count == 0)
@ -275,157 +120,8 @@ void assign_values_from_param(char* parameterk, int* charging, int* battery) {
if(count == 1)
*battery = *charging;
free(parameter);
}
/* 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* pathk) {
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* path = NULL;
char* prefix = NULL;
/* 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;
path = strdup(pathk);
if(path == NULL)
err(1, "malloc failed");
prefix = malloc(prefix_len + 1);
if(prefix == NULL)
err(1, "malloc failed");
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",
pathk);
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);
free(path);
}
int main(int argc, char *argv[]) {
int opt;
@ -441,17 +137,11 @@ int main(int argc, char *argv[]) {
int cpu_usage_percent = 0, cpu_usage;
float temp;
size_t len, len_cpu;
int ncpu;
ncpu = get_cpu_online();
// battery defaults
hard_min_freq = batt_min= 0;
hard_max_freq = batt_max= 100;
// threshold defaults to 80% usage of one CPU
threshold = batt_threshold= floor(100/ncpu*0.8);
threshold = batt_threshold= 30;
down_step = batt_down_step= 100;
inertia = batt_inertia= 5;
step = batt_step= 100;
@ -472,7 +162,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:S:t:T:v")) != -1) {
while((opt = getopt(argc, argv, "d:hi:l:m:r:s:t:T:v")) != -1) {
switch(opt) {
case 'd':
assign_values_from_param(optarg, &wall_down_step, &batt_down_step);
@ -486,14 +176,12 @@ int main(int argc, char *argv[]) {
break;
case 'l':
assign_values_from_param(optarg, &wall_min, &batt_min);
if(wall_min > 100 || wall_min < 0 ||
batt_min > 100 || batt_min < 0)
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(wall_max > 100 || wall_max < 0 ||
batt_max > 100 || batt_max < 0)
if(hard_max_freq > 100 || hard_max_freq < 0)
err(1, "maximum frequency must be between 0 and 100");
break;
case 'v':
@ -509,11 +197,6 @@ 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)
@ -550,18 +233,6 @@ 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

@ -6,18 +6,18 @@
.Nd manage the CPU frequency on OpenBSD from userland
.Sh SYNOPSIS
.Nm
.Op Fl hv
.Op Fl v
.Op Fl h
.Op Fl d Ar downstepfrequency
.Op Fl i Ar inertia
.Op Fl m Ar maxfrequency
.Op Fl l Ar minfrequency
.Op Fl r Ar threshold
.Op Fl s Ar stepfrequency
.Op Fl t Ar timefrequency
.Oo Fl T Ar maxtemperature
.Op Fl S Ar sensorpath
.Oc
.Op Fl t Ar timefreq
.Op Fl T Ar maxtemperature
.Sh DESCRIPTION
.Pp
.Nm
is a program to play with the CPU frequency scheduler by providing
many variables involved in the process.
@ -25,54 +25,51 @@ many variables involved in the process.
will switch the frequency policy to manual to manage the frequency itself
and will restore the policy to auto upon exit.
.Sh OPTIONS
Arguments can be comma separated to provide different values for when on
battery, if so, the first value is used when power plug is connected and second
value when on battery.
.Bl -tag -width Ds
.It Fl v
Arguments can be comma separated to provide different values for when on battery,
if so, the first value is used when power plug is connected and second value
when on battery.
.Bl -tag -width
.It Op Fl v
Enable verbose output in CSV format.
.It Fl h
.It Op Fl h
Display usage
.It Fl d Ar downstepfrequency
.It Op Fl d Ar downstepfrequency
Defines the frequency decrease step in percent.
.It Fl i Ar inertia
Defines the number of cycles under the CPU threshold before decreasing
frequency.
.It Fl m Ar maxfrequency
.It Op Fl i Ar inertia
Defines the number of cycles under the CPU threshold before decreasing frequency.
.It Op Fl m Ar maxfrequency
Defines the maximum frequency in percent the CPU can be increased to.
.It Fl l Ar minfrequency
.It Op Fl l Ar minfrequency
Defines the minimum frequency in percent the CPU can be reduced to.
.It Fl r Ar threshold
.It Op Fl r Ar threshold
Defines the CPU usage in percent above which the frequency is increased.
The default is to use a value that match the usage of 80% of a single core.
Example: for a 4 CPU system, the threshold will be 100/4*0.8 = 20%
.It Fl s Ar stepfrequency
.It Op Fl s Ar stepfrequency
Defines the frequency increase step in percent.
.It Fl t Ar timefrequency
.It Op Fl t Ar timefreq
Defines the time in milliseconds between each cycle.
.It Fl T Ar maxtemperature
.It Op Fl T Ar maxtemperature
Defines the maximum temperature in Celcius
.Nm
will allow before lowering the maximum frequency until the temperature
will allow before lowering the maximum frequency until it 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
.Ed
.Sh EXIT STATUS
.Ex
.Pp
In case of a fatal error,
.Nm
will exit on a status code 1.
In normal operations,
.Nm
will exit on a status code 0.
.Sh EXAMPLES
Temperature throttling at 50°C
This is a balanced power profile.
.Bd -literal -offset indent
obsdfreqd -T 50
obsdfreqd -t 150 -r 30 -d 10 -i 2
.Ed
.Pp
Temperature throttling at 50°C using sensor hw.sensors.acpithinkpad0.temp0
Use different values for when on battery to save power.
.Bd -literal -offset indent
obsdfreqd -T 50 -S hw.sensors.acpithinkpad0.temp0
obsdfreqd -m 100,60 -r 25,40 -t 90,300 -d 10,100 -i 10,0
.Ed
.Sh DIAGNOSTICS
.Nm
@ -82,6 +79,7 @@ has a verbose mode to get realtime information about frequency.
.Sh LICENSE
See the LICENSE file for the terms of redistribution.
.Sh BUGS
.Pp
When set to run a startup, if you also start the
.Xr apmd 8
service, make sure to start it with

11
obsdfreqd.rc Normal file
View File

@ -0,0 +1,11 @@
#!/bin/ksh
daemon="/usr/local/sbin/obsdfreqd"
. /etc/rc.d/rc.subr
pexp="${daemon}.*"
rc_reload=NO
rc_bg=YES
rc_cmd $1