Import powerd(8), a utility for managing power consumption. Currently, it

just uses cpufreq(4) but in the future it should also do things like spin
down disks.  This is based on the work by Colin Percival (called "estctrl").
This commit is contained in:
njl 2005-02-26 21:17:31 +00:00
parent b6b864f028
commit 0b00d0e028
3 changed files with 526 additions and 0 deletions

8
usr.sbin/powerd/Makefile Normal file
View File

@ -0,0 +1,8 @@
# $FreeBSD$
PROG= powerd
MAN= powerd.8
SRCS= powerd.c
WARNS?= 6
.include <bsd.prog.mk>

128
usr.sbin/powerd/powerd.8 Normal file
View File

@ -0,0 +1,128 @@
.\" Copyright (c) 2005 Nate Lawson
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd February 26, 2005
.Dt POWERD 8
.Os
.Sh NAME
.Nm powerd
.Nd system power control utility
.Sh SYNOPSIS
.Nm
.Op Fl a mode
.Op Fl b mode
.Op Fl i percent
.Op Fl n mode
.Op Fl p ival
.Op Fl r percent
.Op Fl v
.Sh DESCRIPTION
The
.Nm
utility monitors the system state and sets various power control options
accordingly.
It offers three modes (max, min, and adaptive) that can be
individually selected while on AC power or batteries.
.Pp
Maximum mode chooses the highest performance values.
Minimum mode selects the lowest performance values to get the most power
savings.
Adaptive mode attempts to strike a balance by degrading performance when
the system appears idle and increasing it when the system is busy.
It offers a good balance between a small performance loss for greatly
increased power savings.
The default mode is
adaptive.
.Pp
The
.Nm
utility recognizes the following runtime options:
.Bl -tag -width -i_percent
.It Fl a Ar mode
Selects the
.Ar mode
to use while on AC power.
.It Fl b Ar mode
Selects the
.Ar mode
to use while on battery power.
.It Fl i Ar percent
Specifies the CPU idle percent level when
adaptive
mode should begin to degrade performance to save power.
The default is 75% or higher.
.It Fl n Ar mode
Selects the
.Ar mode
to use normally when the AC line state is unknown.
.It Fl p Ar ival
Specifies a different polling interval (in milliseconds) for AC line state
and system idle levels.
The default is 500 ms.
.It Fl r Ar percent
Specifies the CPU idle percent level where
adaptive
mode should consider the CPU running and increase performance.
The default is 50% or lower.
.It Fl v
Verbose mode.
Messages about power changes will be printed to stdout and
.Nm
will operate in the foreground.
.El
.Sh SEE ALSO
.Xr acpi 4 ,
.Xr apm 4 ,
.Xr cpufreq 4
.Sh HISTORY
The
.Nm
utility first appeared in
.Fx 5.5 .
.Sh AUTHORS
.An Colin Percival
first wrote
.Pa estctrl ,
the utility that
.Nm
is based on.
.An Nate Lawson
then updated it for
.Xr cpufreq 4 ,
added features, and wrote this man page.
.Sh BUGS
The
.Nm
utility should also power down idle disks and other components besides the CPU.
.Pp
If
.Nm
is used with power_profile, they may override each other.
.Pp
.Nm
should probably use the
.Xr devctl 4
interface instead of polling for AC line state.

390
usr.sbin/powerd/powerd.c Normal file
View File

@ -0,0 +1,390 @@
/*-
* Copyright (c) 2004 Colin Percival
* Copyright (c) 2005 Nate Lawson
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted providing that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <machine/apm_bios.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <sys/resource.h>
#define DEFAULT_ACTIVE_PERCENT 50
#define DEFAULT_IDLE_PERCENT 75
#define DEFAULT_POLL_INTERVAL 500
enum modes_t {
MODE_MIN,
MODE_ADAPTIVE,
MODE_MAX,
};
enum power_src_t {
SRC_AC,
SRC_BATTERY,
SRC_UNKNOWN,
};
const char *modes[] = {
"AC",
"battery",
"unknown"
};
#define ACPIAC "hw.acpi.acline"
#define APMDEV "/dev/apm"
static int read_usage_times(long *idle, long *total);
static int read_freqs(int *numfreqs, int **freqs);
static int set_freq(int freq);
static void parse_mode(char *arg, int *mode, int ch);
static void usage(void);
/* Sysctl data structures. */
static int cp_time_mib[2];
static int freq_mib[4];
static int levels_mib[4];
static int acline_mib[3];
/* Configuration */
static int cpu_running_mark;
static int cpu_idle_mark;
static int poll_ival;
static int
read_usage_times(long *idle, long *total)
{
static long idle_old, total_old;
long cp_time[CPUSTATES], i, total_new;
size_t cp_time_len;
int error;
cp_time_len = sizeof(cp_time);
error = sysctl(cp_time_mib, 2, cp_time, &cp_time_len, NULL, 0);
if (error)
return (error);
for (total_new = 0, i = 0; i < CPUSTATES; i++)
total_new += cp_time[i];
if (idle)
*idle = cp_time[CP_IDLE] - idle_old;
if (total)
*total = total_new - total_old;
idle_old = cp_time[CP_IDLE];
total_old = total_new;
return (0);
}
static int
read_freqs(int *numfreqs, int **freqs)
{
char *freqstr, *p, *q;
int i;
size_t len = 0;
if (sysctl(levels_mib, 4, NULL, &len, NULL, 0))
return (-1);
if ((freqstr = malloc(len)) == NULL)
return (-1);
if (sysctl(levels_mib, 4, freqstr, &len, NULL, 0))
return (-1);
*numfreqs = 1;
for (p = freqstr; *p != '\0'; p++)
if (*p == ' ')
(*numfreqs)++;
if ((*freqs = malloc(*numfreqs * sizeof(int))) == NULL) {
free(freqstr);
return (-1);
}
for (i = 0, p = freqstr; i < *numfreqs; i++) {
q = strchr(p, ' ');
if (q != NULL)
*q = '\0';
if (sscanf(p, "%d/%*d", &(*freqs)[i]) != 1) {
free(freqstr);
free(*freqs);
return (-1);
}
p = q + 1;
}
free(freqstr);
return (0);
}
static int
set_freq(int freq)
{
if (sysctl(freq_mib, 4, NULL, NULL, &freq, sizeof(freq)))
return (-1);
return (0);
}
static void
parse_mode(char *arg, int *mode, int ch)
{
if (strcmp(arg, "min") == 0)
*mode = MODE_MIN;
else if (strcmp(arg, "max") == 0)
*mode = MODE_MAX;
else if (strcmp(arg, "adaptive") == 0)
*mode = MODE_ADAPTIVE;
else
errx(1, "bad option: -%c %s", (char)ch, optarg);
}
static void
usage(void)
{
fprintf(stderr,
"usage: powerd [-v] [-a mode] [-b mode] [-i %%] [-n mode] [-p ival] [-r %%]\n");
exit(1);
}
int
main(int argc, char * argv[])
{
struct apm_info info;
long idle, total;
int apm_fd, curfreq, *freqs, i, numfreqs;
int ch, mode_ac, mode_battery, mode_none, acline, mode, vflag;
size_t len;
/* Default mode for all AC states is adaptive. */
mode_ac = mode_battery = mode_none = MODE_ADAPTIVE;
cpu_running_mark = DEFAULT_ACTIVE_PERCENT;
cpu_idle_mark = DEFAULT_IDLE_PERCENT;
poll_ival = DEFAULT_POLL_INTERVAL;
vflag = 0;
while ((ch = getopt(argc, argv, "a:b:i:n:p:r:v")) != EOF)
switch (ch) {
case 'a':
parse_mode(optarg, &mode_ac, ch);
break;
case 'b':
parse_mode(optarg, &mode_battery, ch);
break;
case 'i':
cpu_idle_mark = atoi(optarg);
if (cpu_idle_mark < 0 || cpu_idle_mark > 100) {
warnx("%d is not a valid percent",
cpu_idle_mark);
usage();
}
break;
case 'n':
parse_mode(optarg, &mode_none, ch);
break;
case 'p':
poll_ival = atoi(optarg);
if (poll_ival < 5) {
warnx("poll interval is in units of ms");
usage();
}
break;
case 'r':
cpu_running_mark = atoi(optarg);
if (cpu_running_mark < 0 || cpu_running_mark > 100) {
warnx("%d is not a valid percent",
cpu_running_mark);
usage();
}
break;
case 'v':
vflag = 1;
break;
default:
usage();
}
/* Poll interval is in units of ms. */
poll_ival *= 1000;
/* Look up various sysctl MIBs. */
len = 2;
if (sysctlnametomib("kern.cp_time", cp_time_mib, &len))
err(1, "lookup kern.cp_time");
len = 4;
if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
err(1, "lookup freq");
len = 4;
if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))
err(1, "lookup freq_levels");
/* Check if we can read the idle time and supported freqs. */
if (read_usage_times(NULL, NULL))
err(1, "read_usage_times");
if (read_freqs(&numfreqs, &freqs))
err(1, "error reading supported CPU frequencies");
/* Decide whether to use ACPI or APM to read the AC line status. */
len = sizeof(acline);
if (sysctlbyname(ACPIAC, &acline, &len, NULL, 0)) {
/* ACPI disabled, try APM */
apm_fd = open(APMDEV, O_RDONLY);
if (apm_fd == -1) {
warnx("cannot read AC line status, "
"using default settings");
}
} else {
len = 3;
if (sysctlnametomib(ACPIAC, acline_mib, &len))
err(1, "lookup acline");
apm_fd = -1;
}
/* Run in the background unless in verbose mode. */
if (!vflag)
daemon(0, 0);
/* Main loop. */
for (;;) {
/* Check status every few milliseconds. */
usleep(poll_ival);
/* Read the current AC status and record the mode. */
if (apm_fd != -1) {
if (ioctl(apm_fd, APMIO_GETINFO, &info) == -1)
acline = SRC_UNKNOWN;
else
acline = info.ai_acline ? SRC_AC : SRC_BATTERY;
} else {
len = sizeof(acline);
if (sysctl(acline_mib, 3, &acline, &len, NULL, 0))
acline = SRC_UNKNOWN;
else
acline = acline ? SRC_AC : SRC_BATTERY;
}
switch (acline) {
case SRC_AC:
mode = mode_ac;
break;
case SRC_BATTERY:
mode = mode_battery;
break;
case SRC_UNKNOWN:
mode = mode_none;
break;
default:
errx(1, "invalid AC line status %d", acline);
}
/* Read the current frequency. */
len = sizeof(curfreq);
if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0))
err(1, "error reading current CPU frequency");
/* Always switch to the lowest frequency in min mode. */
if (mode == MODE_MIN) {
if (curfreq != freqs[numfreqs - 1]) {
if (vflag) {
printf("now operating on %s power; "
"changing frequency to %d MHz\n",
modes[acline], freqs[numfreqs - 1]);
}
if (set_freq(freqs[numfreqs - 1]))
err(1, "error setting CPU freq %d",
freqs[numfreqs - 1]);
}
continue;
}
/* Always switch to the highest frequency in max mode. */
if (mode == MODE_MAX) {
if (curfreq != freqs[0]) {
if (vflag) {
printf("Now operating on %s power; "
"changing frequency to %d MHz\n",
modes[acline], freqs[0]);
}
if (set_freq(freqs[0]))
err(1, "error setting CPU freq %d",
freqs[0]);
}
continue;
}
/* Adaptive mode; get the current CPU usage times. */
if (read_usage_times(&idle, &total))
err(1, "read_usage_times");
/*
* If we're idle less than the active mark, jump the CPU to
* its fastest speed if we're not there yet. If we're idle
* more than the idle mark, drop down to the first setting
* that is half the current speed (exponential backoff).
*/
if (idle < (total * cpu_running_mark) / 100 &&
curfreq < freqs[0]) {
if (vflag) {
printf("idle time < %d%%, increasing clock"
" speed from %d MHz to %d MHz\n",
cpu_running_mark, curfreq, freqs[0]);
}
if (set_freq(freqs[0]))
err(1, "error setting CPU frequency %d",
freqs[0]);
} else if (idle > (total * cpu_idle_mark) / 100 &&
curfreq > freqs[numfreqs - 1]) {
for (i = 0; i < numfreqs - 1; i++) {
if (freqs[i] <= curfreq / 2)
break;
}
if (vflag) {
printf("idle time > %d%%, decreasing clock"
" speed from %d MHz to %d MHz\n",
cpu_idle_mark, curfreq, freqs[i]);
}
if (set_freq(freqs[i]))
err(1, "error setting CPU frequency %d",
freqs[i]);
}
}
/* NOTREACHED */
if (apm_fd != -1)
close(apm_fd);
exit(0);
}