powertop: new applet

Signed-off-by: Marek Polacek <mmpolacek@gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Marek Polacek 2010-10-25 03:44:34 +02:00 committed by Denys Vlasenko
parent ccb070450e
commit b507cc3ace
3 changed files with 897 additions and 11 deletions

View File

@ -43,6 +43,17 @@ config FEATURE_ETC_NETWORKS
a rarely used feature which allows you to use names
instead of IP/mask pairs in route command.
config FEATURE_USE_TERMIOS
bool "Use termios to manipulate the screen"
default y
depends on MORE || TOP || POWERTOP
help
This option allows utilities such as 'more' and 'top' to determine
the size of the screen. If you leave this disabled, your utilities
that display things on the screen will be especially primitive and
will be unable to determine the current screen size, and will be
unable to move the cursor.
config FEATURE_EDITING
bool "Command line editing"
default y

886
procps/powertop.c Normal file
View File

@ -0,0 +1,886 @@
/* vi: set sw=4 ts=4: */
/*
* A mini 'powertop' utility:
* Analyze power consumption on Intel-based laptops.
* Based on powertop 1.11.
*
* Copyright (C) 2010 Marek Polacek <mmpolacek@gmail.com>
*
* Licensed under GPLv2, see file LICENSE in this source tree.
*/
//applet:IF_POWERTOP(APPLET(powertop, _BB_DIR_BIN, _BB_SUID_DROP))
//kbuild:lib-$(CONFIG_POWERTOP) += powertop.o
//config:config POWERTOP
//config: bool "powertop"
//config: default y
//config: help
//config: Analyze power consumption on Intel-based laptops
#include "libbb.h"
//#define debug(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__)
#define debug(fmt, ...) ((void)0)
// XXX This should not be here
#define ENABLE_FEATURE_POWERTOP_PROCIRQ 1
#define DEFAULT_SLEEP 10
#define DEFAULT_SLEEP_STR "10"
/* Frequency of the ACPI timer */
#define FREQ_ACPI 3579.545
#define FREQ_ACPI_1000 3579545
/* Max filename length of entry in /sys/devices subsystem */
#define BIG_SYSNAME_LEN 16
typedef unsigned long long ullong;
struct line {
char *string;
int count;
int disk_count;
};
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
#define IRQCOUNT 40
struct irqdata {
int active;
int number;
ullong count;
char irq_desc[32];
};
#endif
struct globals {
bool timer_list_read;
smallint nostats;
int headline;
int nlines;
int linesize;
int maxcstate;
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
int total_interrupt;
int interrupt_0;
int percpu_hpet_start;
int percpu_hpet_end;
struct irqdata interrupts[IRQCOUNT];
#endif
unsigned total_cpus;
ullong start_usage[8];
ullong last_usage[8];
ullong start_duration[8];
ullong last_duration[8];
char cstate_names[8][16];
struct line *lines;
#if ENABLE_FEATURE_USE_TERMIOS
struct termios init_settings;
#endif
};
#define G (*ptr_to_globals)
#define INIT_G() do { \
SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
} while (0)
#if ENABLE_FEATURE_USE_TERMIOS
static void reset_term(void)
{
tcsetattr_stdin_TCSANOW(&G.init_settings);
}
static void sig_handler(int signo UNUSED_PARAM)
{
reset_term();
exit(EXIT_FAILURE);
}
#endif
static int write_str_to_file(const char *fname, const char *str)
{
FILE *fp = fopen_for_write(fname);
if (!fp)
return 1;
fputs(str, fp);
fclose(fp);
return 0;
}
/* Make it more readable */
#define start_timer() write_str_to_file("/proc/timer_stats", "1\n")
#define stop_timer() write_str_to_file("/proc/timer_stats", "0\n")
static void NOINLINE clear_lines(void)
{
int i;
for (i = 0; i < G.headline; i++)
free(G.lines[i].string);
free(G.lines);
G.headline = G.linesize = 0;
G.lines = NULL;
}
static void count_lines(void)
{
int i;
for (i = 0; i < G.headline; i++)
G.nlines += G.lines[i].count;
}
static int line_compare(const void *p1, const void *p2)
{
const struct line *a = p1;
const struct line *b = p2;
return (b->count + 50 * b->disk_count) - (a->count + 50 * a->disk_count);
}
static void do_sort(void)
{
qsort(G.lines, G.headline, sizeof(struct line), line_compare);
}
/*
* Save C-state names, usage and duration. Also get maxcstate.
* Reads data from /proc.
*/
static void read_data(ullong *usage, ullong *duration)
{
DIR *dir;
struct dirent *d;
dir = opendir("/proc/acpi/processor");
if (!dir)
return;
while ((d = readdir(dir)) != NULL) {
FILE *fp;
char buf[192];
int level = 0;
int len;
len = strlen(d->d_name);
if (len < 3 || len > BIG_SYSNAME_LEN)
continue;
sprintf(buf, "/proc/acpi/processor/%s/power", d->d_name);
fp = fopen_for_read(buf);
if (!fp)
continue;
while (fgets(buf, sizeof(buf), fp)) {
char *p;
/* Get usage */
p = strstr(buf, "age[");
if (!p)
continue;
p += 4;
usage[level] += bb_strtoull(p, NULL, 10) + 1;
/* Get duration */
p = strstr(buf, "ation[");
if (!p)
continue;
p += 6;
duration[level] += bb_strtoull(p, NULL, 10);
/* Increment level */
level++;
/* Also update maxcstate */
if (level > G.maxcstate)
G.maxcstate = level;
}
fclose(fp);
}
closedir(dir);
}
/* Add line and/or update count */
static void push_line(const char *string, int count)
{
int i;
if (!string)
return;
/* Loop through entries */
for (i = 0; i < G.headline; i++) {
if (strcmp(string, G.lines[i].string) == 0) {
/* It's already there, only update count */
G.lines[i].count += count;
return;
}
}
G.lines = xrealloc_vector(G.lines, 1, G.headline);
G.lines[G.headline].string = xstrdup(string);
G.lines[G.headline].count = count;
G.lines[G.headline].disk_count = 0;
/* We added a line */
G.headline++;
}
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
static int percpu_hpet_timer(const char *name)
{
char *p;
long hpet_chan;
/* This is done once */
if (!G.timer_list_read) {
FILE *fp;
char buf[80];
G.timer_list_read = true;
fp = fopen_for_read("/proc/timer_list");
if (!fp)
return 0;
while (fgets(buf, sizeof(buf), fp)) {
p = strstr(buf, "Clock Event Device: hpet");
if (!p)
continue;
p += sizeof("Clock Event Device: hpet")-1;
if (!isdigit(p[0]))
continue;
hpet_chan = xatoi_positive(p);
if (hpet_chan < G.percpu_hpet_start)
G.percpu_hpet_start = hpet_chan;
if (hpet_chan > G.percpu_hpet_end)
G.percpu_hpet_end = hpet_chan;
}
fclose(fp);
}
p = strstr(name, "hpet");
if (!p)
return 0;
p += 4;
if (!isdigit(p[0]))
return 0;
hpet_chan = xatoi_positive(p);
if (G.percpu_hpet_start <= hpet_chan && hpet_chan <= G.percpu_hpet_end)
return 1;
return 0;
}
static int update_irq(int irq, ullong count)
{
int unused = IRQCOUNT;
int i;
for (i = 0; i < IRQCOUNT; i++) {
if (G.interrupts[i].active && G.interrupts[i].number == irq) {
ullong old;
old = G.interrupts[i].count;
G.interrupts[i].count = count;
return count - old;
}
if (!G.interrupts[i].active && unused > i)
unused = i;
}
G.interrupts[unused].active = 1;
G.interrupts[unused].count = count;
G.interrupts[unused].number = irq;
return count;
}
/*
* Read /proc/interrupts, save IRQ counts and IRQ description.
*/
static void do_proc_irq(void)
{
FILE *fp;
char buf[128];
/* Reset values */
G.interrupt_0 = 0;
G.total_interrupt = 0;
fp = xfopen_for_read("/proc/interrupts");
while (fgets(buf, sizeof(buf), fp)) {
char irq_desc[sizeof(" <kernel IPI> : ") + sizeof(buf)];
char *p;
const char *name;
int nr = -1;
ullong count;
ullong delta;
int special;
/* Skip header */
p = strchr(buf, ':');
if (!p)
continue;
/* 0: 143646045 153901007 IO-APIC-edge timer
* ^
*/
/* Deal with non-maskable interrupts -- make up fake numbers */
special = 0;
if (buf[0] != ' ' && !isdigit(buf[0])) {
if (strncmp(buf, "NMI:", 4) == 0)
nr = 20000;
if (strncmp(buf, "RES:", 4) == 0)
nr = 20001;
if (strncmp(buf, "CAL:", 4) == 0)
nr = 20002;
if (strncmp(buf, "TLB:", 4) == 0)
nr = 20003;
if (strncmp(buf, "TRM:", 4) == 0)
nr = 20004;
if (strncmp(buf, "THR:", 4) == 0)
nr = 20005;
if (strncmp(buf, "SPU:", 4) == 0)
nr = 20006;
special = 1;
} else {
/* bb_strtou don't eat leading spaces, using strtoul */
nr = strtoul(buf, NULL, 10); /* xato*() wouldn't work */
}
if (nr == -1)
continue;
p++;
/* 0: 143646045 153901007 IO-APIC-edge timer
* ^
*/
/* Count sum of the IRQs */
count = 0;
while (1) {
char *tmp;
p = skip_whitespace(p);
if (!isdigit(*p))
break;
count += bb_strtoull(p, &tmp, 10);
p = tmp;
}
/* 0: 143646045 153901007 IO-APIC-edge timer
* NMI: 1 2 Non-maskable interrupts
* ^
*/
if (!special) {
/* Skip to the interrupt name, e.g. 'timer' */
p = strchr(p, ' ');
if (!p)
continue;
p = skip_whitespace(p);
}
name = p;
strchrnul(name, '\n')[0] = '\0';
/* Save description of the interrupt */
if (special)
sprintf(irq_desc, " <kernel IPI> : %s", name);
else
sprintf(irq_desc, " <interrupt> : %s", name);
delta = update_irq(nr, count);
/* Skip per CPU timer interrupts */
if (percpu_hpet_timer(name))
delta = 0;
if (nr > 0 && delta > 0)
push_line(irq_desc, delta);
if (!nr)
G.interrupt_0 = delta;
else
G.total_interrupt += delta;
}
fclose(fp);
}
#endif /* ENABLE_FEATURE_POWERTOP_PROCIRQ */
#ifdef __i386__
/*
* Get information about CPU using CPUID opcode.
*/
static void cpuid(unsigned int *eax, unsigned int *ebx, unsigned int *ecx,
unsigned int *edx)
{
/* EAX value specifies what information to return */
__asm__(
" pushl %%ebx\n" /* Save EBX */
" cpuid\n"
" movl %%ebx, %1\n" /* Save content of EBX */
" popl %%ebx\n" /* Restore EBX */
: "=a"(*eax), /* Output */
"=r"(*ebx),
"=c"(*ecx),
"=d"(*edx)
: "0"(*eax), /* Input */
"1"(*ebx),
"2"(*ecx),
"3"(*edx)
/* No clobbered registers */
);
}
#endif
static void NOINLINE print_intel_cstates(void)
{
#ifdef __i386__
int bios_table[8] = { 0 };
int nbios = 0;
DIR *cpudir;
struct dirent *d;
int i;
unsigned eax, ebx, ecx, edx;
cpudir = opendir("/sys/devices/system/cpu");
if (!cpudir)
return;
/* Loop over cpuN entries */
while ((d = readdir(cpudir)) != NULL) {
DIR *dir;
int len;
char fname[sizeof("/sys/devices/system/cpu//cpuidle//desc") + 2*BIG_SYSNAME_LEN];
len = strlen(d->d_name);
if (len < 3 || len > BIG_SYSNAME_LEN)
continue;
if (!isdigit(d->d_name[3]))
continue;
len = sprintf(fname, "/sys/devices/system/cpu/%s/cpuidle", d->d_name);
dir = opendir(fname);
if (!dir)
continue;
/*
* Every C-state has its own stateN directory, that
* contains a `time' and a `usage' file.
*/
while ((d = readdir(dir)) != NULL) {
FILE *fp;
char buf[64];
int n;
n = strlen(d->d_name);
if (n < 3 || n > BIG_SYSNAME_LEN)
continue;
sprintf(fname + len, "/%s/desc", d->d_name);
fp = fopen_for_read(fname);
if (fp) {
char *p = fgets(buf, sizeof(buf), fp);
fclose(fp);
if (!p)
break;
p = strstr(p, "MWAIT ");
if (p) {
int pos;
p += sizeof("MWAIT ") - 1;
pos = (bb_strtoull(p, NULL, 16) >> 4) + 1;
if (pos >= ARRAY_SIZE(bios_table))
continue;
bios_table[pos]++;
nbios++;
}
}
}
closedir(dir);
}
closedir(cpudir);
if (!nbios)
return;
eax = 5;
ebx = ecx = edx = 0;
cpuid(&eax, &ebx, &ecx, &edx);
if (!edx || !(ecx & 1))
return;
printf("Your CPU supports the following C-states: ");
i = 0;
while (edx) {
if (edx & 7)
printf("C%u ", i);
edx >>= 4;
i++;
}
bb_putchar('\n');
/* Print BIOS C-States */
printf("Your BIOS reports the following C-states: ");
for (i = 0; i < 8; i++)
if (bios_table[i])
printf("C%u ", i);
bb_putchar('\n');
#endif
}
static void print_header(void)
{
printf(
/* Clear the screen */
"\033[H\033[J"
/* Print the header */
"\033[7m%.*s\033[0m", 79, "PowerTOP (C) 2007 Intel Corporation\n"
);
}
static void show_cstates(char cstate_lines[][64])
{
int i;
for (i = 0; i < 10; i++)
if ((cstate_lines[i][0]))
printf("%s", cstate_lines[i]);
}
static void show_timerstats(int nostats)
{
unsigned lines;
/* Get terminal height */
get_terminal_width_height(STDOUT_FILENO, NULL, &lines);
/* We don't have whole terminal just for timerstats */
lines -= 12;
if (!nostats) {
int i, n = 0;
puts("\nTop causes for wakeups:");
for (i = 0; i < G.headline; i++) {
if ((G.lines[i].count > 0 || G.lines[i].disk_count > 0)
&& n++ < lines
) {
char c = ' ';
if (G.lines[i].disk_count)
c = 'D';
printf(" %5.1f%% (%5.1f)%c %s\n",
G.lines[i].count * 100.0 / G.nlines,
G.lines[i].count * 1.0 / DEFAULT_SLEEP, c,
G.lines[i].string);
}
}
} else {
bb_putchar('\n');
bb_error_msg("no stats available; run as root or"
" enable the cpufreq_stats module");
}
}
//usage:#define powertop_trivial_usage
//usage: ""
//usage:#define powertop_full_usage "\n\n"
//usage: "Analyze power consumption on Intel-based laptops\n"
int powertop_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int powertop_main(int UNUSED_PARAM argc, char UNUSED_PARAM **argv)
{
ullong cur_usage[8];
ullong cur_duration[8];
char cstate_lines[12][64];
char buf[128];
#if ENABLE_FEATURE_USE_TERMIOS
struct termios new_settings;
struct pollfd pfd[1];
pfd[0].fd = 0;
pfd[0].events = POLLIN;
#endif
INIT_G();
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
G.percpu_hpet_start = INT_MAX;
G.percpu_hpet_end = INT_MIN;
#endif
/* Print warning when we don't have superuser privileges */
if (geteuid() != 0)
bb_error_msg("run as root to collect enough information");
#if ENABLE_FEATURE_USE_TERMIOS
/* So we don't forget to reset term settings */
atexit(reset_term);
#endif
/* Get number of CPUs */
G.total_cpus = get_cpu_count();
printf("Collecting data for "DEFAULT_SLEEP_STR" seconds\n");
#if ENABLE_FEATURE_USE_TERMIOS
tcgetattr(0, (void *)&G.init_settings);
memcpy(&new_settings, &G.init_settings, sizeof(new_settings));
/* Turn on unbuffered input, turn off echoing */
new_settings.c_lflag &= ~(ISIG | ICANON | ECHO | ECHONL);
bb_signals(BB_FATAL_SIGS, sig_handler);
tcsetattr_stdin_TCSANOW(&new_settings);
#endif
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
/* Collect initial data */
do_proc_irq();
do_proc_irq();
#endif
/* Read initial usage and duration */
read_data(&G.start_usage[0], &G.start_duration[0]);
/* Copy them to "last" */
memcpy(G.last_usage, G.start_usage, sizeof(G.last_usage));
memcpy(G.last_duration, G.start_duration, sizeof(G.last_duration));
/* Display C-states */
print_intel_cstates();
if (stop_timer())
G.nostats = 1;
/* The main loop */
for (;;) {
double maxsleep = 0.0;
ullong totalticks, totalevents;
int i;
FILE *fp;
double newticks;
if (start_timer())
G.nostats = 1;
#if !ENABLE_FEATURE_USE_TERMIOS
sleep(DEFAULT_SLEEP);
#else
if (safe_poll(pfd, 1, DEFAULT_SLEEP * 1000) > 0) {
unsigned char c;
if (safe_read(STDIN_FILENO, &c, 1) != 1)
break; /* EOF/error */
if (c == G.init_settings.c_cc[VINTR])
break; /* ^C */
if ((c | 0x20) == 'q')
break;
}
#endif
if (stop_timer())
G.nostats = 1;
clear_lines();
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
do_proc_irq();
#endif
/* Clear the stats */
memset(cur_duration, 0, sizeof(cur_duration));
memset(cur_usage, 0, sizeof(cur_usage));
/* Read them */
read_data(&cur_usage[0], &cur_duration[0]);
totalticks = totalevents = 0;
/* Count totalticks and totalevents */
for (i = 0; i < 8; i++) {
if (cur_usage[i]) {
totalticks += cur_duration[i] - G.last_duration[i];
totalevents += cur_usage[i] - G.last_usage[i];
}
}
/* Show title bar */
print_header();
/* Clear C-state lines */
memset(&cstate_lines, 0, sizeof(cstate_lines));
if (totalevents == 0 && G.maxcstate <= 1) {
/* This should not happen */
sprintf(cstate_lines[5], "< Detailed C-state information is not "
"available.>\n");
} else {
double slept;
double percentage;
newticks = G.total_cpus * DEFAULT_SLEEP * FREQ_ACPI_1000 - totalticks;
/* Handle rounding errors: do not display negative values */
if (newticks < 0)
newticks = 0;
sprintf(cstate_lines[0], "Cn\t Avg residency\n");
percentage = newticks * 100.0 / (G.total_cpus * DEFAULT_SLEEP * FREQ_ACPI_1000);
sprintf(cstate_lines[1], "C0 (cpu running) (%4.1f%%)\n",
percentage);
/* Compute values for individual C-states */
for (i = 0; i < 8; i++) {
if (cur_usage[i]) {
slept = (cur_duration[i] - G.last_duration[i])
/ (cur_usage[i] - G.last_usage[i] + 0.1) / FREQ_ACPI;
percentage = (cur_duration[i] - G.last_duration[i]) * 100
/ (G.total_cpus * DEFAULT_SLEEP * FREQ_ACPI_1000);
if (!G.cstate_names[i][0])
sprintf(G.cstate_names[i], "C%u", i + 1);
sprintf(cstate_lines[i + 2], "%s\t%5.1fms (%4.1f%%)\n",
G.cstate_names[i], slept, percentage);
if (maxsleep < slept)
maxsleep = slept;
}
}
}
/* Display C-states */
show_cstates(cstate_lines);
/* Do timer_stats info */
buf[0] = '\0';
totalticks = 0;
fp = NULL;
if (!G.nostats)
fp = fopen_for_read("/proc/timer_stats");
if (fp) {
while (fgets(buf, sizeof(buf), fp)) {
const char *count, *process, *func;
char line[512];
int cnt = 0;
bool defferable = false;
char *p;
int j = 0;
/* Find char ' ', then eat remaining spaces */
#define ADVANCE(p) do { \
(p) = strchr((p), ' '); \
if (!(p)) \
continue; \
*(p) = '\0'; \
(p)++; \
(p) = skip_whitespace(p); \
} while (0)
if (strstr(buf, "total events"))
break;
while (isspace(buf[j]))
j++;
count = &buf[j];
p = (char *)count;
/* Skip PID */
p = strchr(p, ',');
if (!p)
continue;
*p = '\0';
p++;
p = skip_whitespace(p);
/* Get process */
ADVANCE(p);
process = p;
/* Get function */
ADVANCE(p);
func = p;
if (strcmp(process, "swapper") == 0
&& strcmp(func, "hrtimer_start_range_ns (tick_sched_timer)\n") == 0
) {
process = "[kernel scheduler]";
func = "Load balancing tick";
}
if (strcmp(process, "insmod") == 0)
process = "[kernel module]";
if (strcmp(process, "modprobe") == 0)
process = "[kernel module]";
if (strcmp(process, "swapper") == 0)
process = "[kernel core]";
p = strchr(p, '\n');
if (strncmp(func, "tick_nohz_", 10) == 0)
continue;
if (strncmp(func, "tick_setup_sched_timer", 20) == 0)
continue;
if (strcmp(process, "powertop") == 0)
continue;
if (p)
*p = '\0';
cnt = bb_strtoull(count, &p, 10);
while (*p != 0) {
if (*p++ == 'D')
defferable = true;
}
if (defferable)
continue;
if (strchr(process, '['))
sprintf(line, "%s %s", process, func);
else
sprintf(line, "%s", process);
push_line(line, cnt);
}
fclose(fp);
}
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
if (strstr(buf, "total events")) {
int n = bb_strtoull(buf, NULL, 10) / G.total_cpus;
if (totalevents == 0) {
/* No C-state info available, use timerstats */
totalevents = n * G.total_cpus + G.total_interrupt;
if (n < 0)
totalevents += G.interrupt_0 - n;
}
if (n > 0 && n < G.interrupt_0)
push_line("[extra timer interrupt]", G.interrupt_0 - n);
}
#endif
if (totalevents)
printf("\n\033[1mWakeups-from-idle per second : %4.1f\tinterval:"
"%ds\n\033[0m",
(double)totalevents / DEFAULT_SLEEP / G.total_cpus, DEFAULT_SLEEP);
count_lines();
do_sort();
show_timerstats(G.nostats);
fflush(stdout);
/* Clear the stats */
memset(cur_duration, 0, sizeof(cur_duration));
memset(cur_usage, 0, sizeof(cur_usage));
/* Get new values */
read_data(&cur_usage[0], &cur_duration[0]);
/* Save them */
memcpy(G.last_usage, cur_usage, sizeof(G.last_usage));
memcpy(G.last_duration, cur_duration, sizeof(G.last_duration));
} /* for (;;) */
bb_putchar('\n');
return EXIT_SUCCESS;
}

View File

@ -486,17 +486,6 @@ config MORE
you will probably find this utility very helpful. If you don't have
any need to reading text files, you can leave this disabled.
config FEATURE_USE_TERMIOS
bool "Use termios to manipulate the screen"
default y
depends on MORE || TOP
help
This option allows utilities such as 'more' and 'top' to determine
the size of the screen. If you leave this disabled, your utilities
that display things on the screen will be especially primitive and
will be unable to determine the current screen size, and will be
unable to move the cursor.
config MOUNT
bool "mount"
default y