//config: help
//config: Analyze power consumption on Intel-based laptops
+// XXX This should de configurable
+#define ENABLE_FEATURE_POWERTOP_PROCIRQ 1
+
#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 BLOATY_HPET_IRQ_NUM_DETECTION 0
+#define MAX_CSTATE_COUNT 8
+#define IRQCOUNT 40
+
#define DEFAULT_SLEEP 10
#define DEFAULT_SLEEP_STR "10"
/* Frequency of the ACPI timer */
#define FREQ_ACPI 3579.545
-#define FREQ_ACPI_1000 3579545
+#define FREQ_ACPI_1000 3579545
/* Max filename length of entry in /sys/devices subsystem */
#define BIG_SYSNAME_LEN 16
struct line {
char *string;
int count;
- int disk_count;
+ /*int disk_count;*/
};
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
-#define IRQCOUNT 40
-
struct irqdata {
- int active;
+ smallint active;
int number;
ullong count;
char irq_desc[32];
#endif
struct globals {
- bool timer_list_read;
- smallint nostats;
- int headline;
- int nlines;
+ int lines_cnt;
+ int lines_cumulative_count;
int linesize;
int maxcstate;
+ unsigned total_cpus;
+ struct line *lines;
+ smallint cant_enable_timer_stats;
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
- int total_interrupt;
- int interrupt_0;
+# if BLOATY_HPET_IRQ_NUM_DETECTION
+ smallint scanned_timer_list;
int percpu_hpet_start;
int percpu_hpet_end;
+# endif
+ int interrupt_0;
+ int total_interrupt;
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;
+ ullong start_usage[MAX_CSTATE_COUNT];
+ ullong last_usage[MAX_CSTATE_COUNT];
+ ullong start_duration[MAX_CSTATE_COUNT];
+ ullong last_duration[MAX_CSTATE_COUNT];
+ char cstate_names[MAX_CSTATE_COUNT][16];
#if ENABLE_FEATURE_USE_TERMIOS
struct termios init_settings;
#endif
static void sig_handler(int signo UNUSED_PARAM)
{
reset_term();
- exit(EXIT_FAILURE);
+ _exit(EXIT_FAILURE);
}
#endif
static void NOINLINE clear_lines(void)
{
int i;
-
- for (i = 0; i < G.headline; i++)
+ for (i = 0; i < G.lines_cnt; i++)
free(G.lines[i].string);
free(G.lines);
- G.headline = G.linesize = 0;
+ G.lines_cnt = 0;
+ G.linesize = 0;
G.lines = NULL;
}
-static void count_lines(void)
+static void update_lines_cumulative_count(void)
{
int i;
-
- for (i = 0; i < G.headline; i++)
- G.nlines += G.lines[i].count;
+ for (i = 0; i < G.lines_cnt; i++)
+ G.lines_cumulative_count += 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);
+ return (b->count /*+ 50 * b->disk_count*/) - (a->count /*+ 50 * a->disk_count*/);
}
-static void do_sort(void)
+static void sort_lines(void)
{
- qsort(G.lines, G.headline, sizeof(struct line), line_compare);
+ qsort(G.lines, G.lines_cnt, sizeof(G.lines[0]), line_compare);
}
-/*
- * Save C-state names, usage and duration. Also get maxcstate.
- * Reads data from /proc.
- */
-static void read_data(ullong *usage, ullong *duration)
+/* Save C-state usage and duration. Also update maxcstate. */
+static void read_cstate_counts(ullong *usage, ullong *duration)
{
DIR *dir;
struct dirent *d;
while ((d = readdir(dir)) != NULL) {
FILE *fp;
char buf[192];
- int level = 0;
+ int level;
int len;
- len = strlen(d->d_name);
+ len = strlen(d->d_name); /* "CPUnn" */
if (len < 3 || len > BIG_SYSNAME_LEN)
continue;
if (!fp)
continue;
+// Example file contents:
+// active state: C0
+// max_cstate: C8
+// maximum allowed latency: 2000000000 usec
+// states:
+// C1: type[C1] promotion[--] demotion[--] latency[001] usage[00006173] duration[00000000000000000000]
+// C2: type[C2] promotion[--] demotion[--] latency[001] usage[00085191] duration[00000000000083024907]
+// C3: type[C3] promotion[--] demotion[--] latency[017] usage[01017622] duration[00000000017921327182]
+ level = 0;
while (fgets(buf, sizeof(buf), fp)) {
- char *p;
-
- /* Get usage */
- p = strstr(buf, "age[");
+ char *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 */
+ if (level >= MAX_CSTATE_COUNT-1)
+ break;
level++;
-
- /* Also update maxcstate */
- if (level > G.maxcstate)
+ if (level > G.maxcstate) /* update maxcstate */
G.maxcstate = level;
}
fclose(fp);
}
/* Add line and/or update count */
-static void push_line(const char *string, int count)
+static void save_line(const char *string, int count)
{
int i;
-
- if (!string)
- return;
-
- /* Loop through entries */
- for (i = 0; i < G.headline; i++) {
+ for (i = 0; i < G.lines_cnt; i++) {
if (strcmp(string, G.lines[i].string) == 0) {
/* It's already there, only update count */
G.lines[i].count += count;
}
}
- 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++;
+ /* Add new line */
+ G.lines = xrealloc_vector(G.lines, 1, G.lines_cnt);
+ G.lines[G.lines_cnt].string = xstrdup(string);
+ G.lines[G.lines_cnt].count = count;
+ /*G.lines[G.lines_cnt].disk_count = 0;*/
+ G.lines_cnt++;
}
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
-static int percpu_hpet_timer(const char *name)
+static int is_hpet_irq(const char *name)
{
char *p;
+# if BLOATY_HPET_IRQ_NUM_DETECTION
long hpet_chan;
- /* This is done once */
- if (!G.timer_list_read) {
+ /* Learn the range of existing hpet timers. This is done once */
+ if (!G.scanned_timer_list) {
FILE *fp;
char buf[80];
- G.timer_list_read = true;
+ G.scanned_timer_list = true;
fp = fopen_for_read("/proc/timer_list");
if (!fp)
return 0;
if (!p)
continue;
p += sizeof("Clock Event Device: hpet")-1;
- if (!isdigit(p[0]))
+ if (!isdigit(*p))
continue;
hpet_chan = xatoi_positive(p);
if (hpet_chan < G.percpu_hpet_start)
}
fclose(fp);
}
-
+# endif
+//TODO: optimize
p = strstr(name, "hpet");
if (!p)
return 0;
-
p += 4;
- if (!isdigit(p[0]))
+ if (!isdigit(*p))
return 0;
-
+# if BLOATY_HPET_IRQ_NUM_DETECTION
hpet_chan = xatoi_positive(p);
- if (G.percpu_hpet_start <= hpet_chan && hpet_chan <= G.percpu_hpet_end)
- return 1;
-
- return 0;
+ if (hpet_chan < G.percpu_hpet_start || hpet_chan > G.percpu_hpet_end)
+ return 0;
+# endif
+ return 1;
}
-static int update_irq(int irq, ullong count)
+/* Save new IRQ count, return delta from old one */
+static int save_irq_count(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;
+ ullong 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;
-
+ if (unused < IRQCOUNT) {
+ 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)
+/* Read /proc/interrupts, save IRQ counts and IRQ description */
+static void process_irq_count_deltas(void)
{
FILE *fp;
char buf[128];
char irq_desc[sizeof(" <kernel IPI> : ") + sizeof(buf)];
char *p;
const char *name;
- int nr = -1;
+ int nr;
ullong count;
ullong delta;
- int special;
- /* Skip header */
p = strchr(buf, ':');
if (!p)
continue;
* ^
*/
/* Deal with non-maskable interrupts -- make up fake numbers */
- special = 0;
+ nr = -1;
if (buf[0] != ' ' && !isdigit(buf[0])) {
+//TODO: optimize
if (strncmp(buf, "NMI:", 4) == 0)
nr = 20000;
if (strncmp(buf, "RES:", 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 */
+ /* bb_strtou doesn't eat leading spaces, using strtoul */
+ nr = strtoul(buf, NULL, 10);
}
if (nr == -1)
continue;
/* 0: 143646045 153901007 IO-APIC-edge timer
* ^
*/
- /* Count sum of the IRQs */
+ /* Sum counts for this IRQ */
count = 0;
while (1) {
char *tmp;
* NMI: 1 2 Non-maskable interrupts
* ^
*/
- if (!special) {
+ if (nr < 20000) {
/* Skip to the interrupt name, e.g. 'timer' */
p = strchr(p, ' ');
if (!p)
name = p;
strchrnul(name, '\n')[0] = '\0';
/* Save description of the interrupt */
- if (special)
+ if (nr < 20000)
sprintf(irq_desc, " <kernel IPI> : %s", name);
else
sprintf(irq_desc, " <interrupt> : %s", name);
- delta = update_irq(nr, count);
+ delta = save_irq_count(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)
+ if (is_hpet_irq(name))
+ continue;
+
+ if (nr != 0 && delta != 0)
+ save_line(irq_desc, delta);
+
+ if (nr == 0)
G.interrupt_0 = delta;
else
G.total_interrupt += delta;
fclose(fp);
}
-#endif /* ENABLE_FEATURE_POWERTOP_PROCIRQ */
+#else /* !ENABLE_FEATURE_POWERTOP_PROCIRQ */
+# define process_irq_count_deltas() ((void)0)
+#endif
#ifdef __i386__
/*
printf("%s", cstate_lines[i]);
}
-static void show_timerstats(int nostats)
+static void show_timerstats(void)
{
unsigned lines;
/* We don't have whole terminal just for timerstats */
lines -= 12;
- if (!nostats) {
+ if (!G.cant_enable_timer_stats) {
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)
+ for (i = 0; i < G.lines_cnt; 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';
+ /*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 * 100.0 / G.lines_cumulative_count,
G.lines[i].count * 1.0 / DEFAULT_SLEEP, c,
G.lines[i].string);
}
}
}
+// Example display from powertop version 1.11
+// Cn Avg residency P-states (frequencies)
+// C0 (cpu running) ( 0.5%) 2.00 Ghz 0.0%
+// polling 0.0ms ( 0.0%) 1.67 Ghz 0.0%
+// C1 mwait 0.0ms ( 0.0%) 1333 Mhz 0.1%
+// C2 mwait 0.1ms ( 0.1%) 1000 Mhz 99.9%
+// C3 mwait 12.1ms (99.4%)
+//
+// Wakeups-from-idle per second : 93.6 interval: 15.0s
+// no ACPI power usage estimate available
+//
+// Top causes for wakeups:
+// 32.4% ( 26.7) <interrupt> : extra timer interrupt
+// 29.0% ( 23.9) <kernel core> : hrtimer_start_range_ns (tick_sched_timer)
+// 9.0% ( 7.5) <kernel core> : hrtimer_start (tick_sched_timer)
+// 6.5% ( 5.3) <interrupt> : ata_piix
+// 5.0% ( 4.1) inetd : hrtimer_start_range_ns (hrtimer_wakeup)
+
//usage:#define powertop_trivial_usage
//usage: ""
//usage:#define powertop_full_usage "\n\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];
+ ullong cur_usage[MAX_CSTATE_COUNT];
+ ullong cur_duration[MAX_CSTATE_COUNT];
char cstate_lines[12][64];
char buf[128];
#if ENABLE_FEATURE_USE_TERMIOS
INIT_G();
-#if ENABLE_FEATURE_POWERTOP_PROCIRQ
+#if ENABLE_FEATURE_POWERTOP_PROCIRQ && BLOATY_HPET_IRQ_NUM_DETECTION
G.percpu_hpet_start = INT_MAX;
G.percpu_hpet_end = INT_MIN;
#endif
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();
#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);
-
+ /* So we don't forget to reset term settings */
+ atexit(reset_term);
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
+ process_irq_count_deltas();
/* Read initial usage and duration */
- read_data(&G.start_usage[0], &G.start_duration[0]);
+ read_cstate_counts(G.start_usage, G.start_duration);
/* Copy them to "last" */
memcpy(G.last_usage, G.start_usage, sizeof(G.last_usage));
/* Display C-states */
print_intel_cstates();
- if (stop_timer())
- G.nostats = 1;
+ G.cant_enable_timer_stats |= stop_timer(); /* 1 on error */
/* The main loop */
for (;;) {
- double maxsleep = 0.0;
+ /*double maxsleep = 0.0;*/
ullong totalticks, totalevents;
int i;
FILE *fp;
- double newticks;
-
- if (start_timer())
- G.nostats = 1;
+ G.cant_enable_timer_stats |= start_timer(); /* 1 on error */
#if !ENABLE_FEATURE_USE_TERMIOS
sleep(DEFAULT_SLEEP);
#else
break;
}
#endif
-
- if (stop_timer())
- G.nostats = 1;
+ G.cant_enable_timer_stats |= stop_timer(); /* 1 on error */
clear_lines();
-#if ENABLE_FEATURE_POWERTOP_PROCIRQ
- do_proc_irq();
-#endif
+ process_irq_count_deltas();
/* 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;
+ read_cstate_counts(cur_usage, cur_duration);
/* Count totalticks and totalevents */
- for (i = 0; i < 8; i++) {
- if (cur_usage[i]) {
+ totalticks = totalevents = 0;
+ for (i = 0; i < MAX_CSTATE_COUNT; i++) {
+ if (cur_usage[i] != 0) {
totalticks += cur_duration[i] - G.last_duration[i];
totalevents += cur_usage[i] - G.last_usage[i];
}
} else {
double slept;
double percentage;
+ double newticks;
newticks = G.total_cpus * DEFAULT_SLEEP * FREQ_ACPI_1000 - totalticks;
percentage);
/* Compute values for individual C-states */
- for (i = 0; i < 8; i++) {
- if (cur_usage[i]) {
+ for (i = 0; i < MAX_CSTATE_COUNT; i++) {
+ if (cur_usage[i] != 0) {
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
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;
+ /*if (maxsleep < slept)
+ maxsleep = slept;*/
}
}
}
totalticks = 0;
fp = NULL;
- if (!G.nostats)
+ if (!G.cant_enable_timer_stats)
fp = fopen_for_read("/proc/timer_stats");
if (fp) {
+// Examlpe file contents:
+// Timer Stats Version: v0.2
+// Sample period: 1.329 s
+// 76, 0 swapper hrtimer_start_range_ns (tick_sched_timer)
+// 88, 0 swapper hrtimer_start_range_ns (tick_sched_timer)
+// 24, 3787 firefox hrtimer_start_range_ns (hrtimer_wakeup)
+// 46D, 1136 kondemand/1 do_dbs_timer (delayed_work_timer_fn)
+// ...
+// 1, 1656 Xorg hrtimer_start_range_ns (hrtimer_wakeup)
+// 1, 2159 udisks-daemon hrtimer_start_range_ns (hrtimer_wakeup)
+// 331 total events, 249.059 events/sec
while (fgets(buf, sizeof(buf), fp)) {
const char *count, *process, *func;
+ char *p;
char line[512];
int cnt = 0;
- bool defferable = false;
- char *p;
- int j = 0;
+// TODO: optimize
+ if (strstr(buf, "total events"))
+ break;
+ count = skip_whitespace(buf);
+ p = strchr(count, ',');
+ if (!p)
+ continue;
+ *p++ = '\0';
+ p = skip_whitespace(p); /* points to pid */
/* Find char ' ', then eat remaining spaces */
#define ADVANCE(p) do { \
(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 */
+ /* Get process name */
ADVANCE(p);
process = p;
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)
if (strcmp(process, "powertop") == 0)
continue;
- if (p)
- *p = '\0';
+ strchrnul(p, '\n')[0] = '\0';
cnt = bb_strtoull(count, &p, 10);
- while (*p != 0) {
+ while (*p != '\0') {
if (*p++ == 'D')
- defferable = true;
+ goto skip;
}
- if (defferable)
- continue;
if (strchr(process, '['))
sprintf(line, "%s %s", process, func);
else
sprintf(line, "%s", process);
- push_line(line, cnt);
+ save_line(line, cnt);
+ skip: ;
}
fclose(fp);
}
totalevents += G.interrupt_0 - n;
}
if (n > 0 && n < G.interrupt_0)
- push_line("[extra timer interrupt]", G.interrupt_0 - n);
+ save_line("[extra timer interrupt]", G.interrupt_0 - n);
}
#endif
- if (totalevents)
+ if (totalevents != 0)
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);
-
+ update_lines_cumulative_count();
+ sort_lines();
+ show_timerstats();
fflush(stdout);
/* Clear the stats */
memset(cur_usage, 0, sizeof(cur_usage));
/* Get new values */
- read_data(&cur_usage[0], &cur_duration[0]);
+ read_cstate_counts(cur_usage, cur_duration);
/* Save them */
memcpy(G.last_usage, cur_usage, sizeof(G.last_usage));