*
* 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: bool "powertop (9.1 kb)"
//config: default y
//config: help
-//config: Analyze power consumption on Intel-based laptops
+//config: Analyze power consumption on Intel-based laptops
+//config:
+//config:config FEATURE_POWERTOP_INTERACTIVE
+//config: bool "Accept keyboard commands"
+//config: default y
+//config: depends on POWERTOP
+//config: help
+//config: Without this, powertop will only refresh display every 10 seconds.
+//config: No keyboard commands will work, only ^C to terminate.
+
+//applet:IF_POWERTOP(APPLET(powertop, BB_DIR_USR_SBIN, BB_SUID_DROP))
-// XXX This should de configurable
+//kbuild:lib-$(CONFIG_POWERTOP) += powertop.o
+
+// XXX This should be configurable
#define ENABLE_FEATURE_POWERTOP_PROCIRQ 1
#include "libbb.h"
/* Max filename length of entry in /sys/devices subsystem */
#define BIG_SYSNAME_LEN 16
+#define ESC "\033"
+
typedef unsigned long long ullong;
struct line {
#endif
struct globals {
+ struct line *lines; /* the most often used member */
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
# if BLOATY_HPET_IRQ_NUM_DETECTION
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
+#if ENABLE_FEATURE_POWERTOP_INTERACTIVE
struct termios init_settings;
#endif
};
SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
} while (0)
-#if ENABLE_FEATURE_USE_TERMIOS
+#if ENABLE_FEATURE_POWERTOP_INTERACTIVE
static void reset_term(void)
{
tcsetattr_stdin_TCSANOW(&G.init_settings);
}
/* 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")
+#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)
+static NOINLINE void clear_lines(void)
{
int i;
- for (i = 0; i < G.lines_cnt; i++)
- free(G.lines[i].string);
- free(G.lines);
- G.lines_cnt = 0;
- G.linesize = 0;
- G.lines = NULL;
+ if (G.lines) {
+ for (i = 0; i < G.lines_cnt; i++)
+ free(G.lines[i].string);
+ free(G.lines);
+ G.lines_cnt = 0;
+ G.lines = NULL;
+ }
}
static void update_lines_cumulative_count(void)
if (len < 3 || len > BIG_SYSNAME_LEN)
continue;
- sprintf(buf, "/proc/acpi/processor/%s/power", d->d_name);
+ sprintf(buf, "%s/%s/power", "/proc/acpi/processor", d->d_name);
fp = fopen_for_read(buf);
if (!fp)
continue;
}
/* Add new line */
- G.lines = xrealloc_vector(G.lines, 1, G.lines_cnt);
+ G.lines = xrealloc_vector(G.lines, 4, 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;*/
}
/* Read /proc/interrupts, save IRQ counts and IRQ description */
-static void process_irq_count_deltas(void)
+static void process_irq_counts(void)
{
FILE *fp;
char buf[128];
/* 0: 143646045 153901007 IO-APIC-edge timer
* ^
*/
+ *p = '\0';
/* Deal with non-maskable interrupts -- make up fake numbers */
- 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 = 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;
+ nr = index_in_strings("NMI\0RES\0CAL\0TLB\0TRM\0THR\0SPU\0", buf);
+ if (nr >= 0) {
+ nr += 20000;
} else {
/* bb_strtou doesn't eat leading spaces, using strtoul */
+ errno = 0;
nr = strtoul(buf, NULL, 10);
+ if (errno)
+ continue;
}
- if (nr == -1)
- continue;
-
p++;
/* 0: 143646045 153901007 IO-APIC-edge timer
* ^
}
/* 0: 143646045 153901007 IO-APIC-edge timer
* NMI: 1 2 Non-maskable interrupts
- * ^
+ * ^
*/
if (nr < 20000) {
/* Skip to the interrupt name, e.g. 'timer' */
}
name = p;
- strchrnul(name, '\n')[0] = '\0';
+ chomp(p);
/* Save description of the interrupt */
- if (nr < 20000)
+ if (nr >= 20000)
sprintf(irq_desc, " <kernel IPI> : %s", name);
else
sprintf(irq_desc, " <interrupt> : %s", name);
fclose(fp);
}
#else /* !ENABLE_FEATURE_POWERTOP_PROCIRQ */
-# define process_irq_count_deltas() ((void)0)
+# define process_irq_counts() ((void)0)
#endif
+static NOINLINE int process_timer_stats(void)
+{
+ char buf[128];
+ char line[15 + 3 + 128];
+ int n;
+ FILE *fp;
+
+ buf[0] = '\0';
+
+ n = 0;
+ fp = NULL;
+ if (!G.cant_enable_timer_stats)
+ fp = fopen_for_read("/proc/timer_stats");
+ if (fp) {
+// Example 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;
+ int idx;
+ unsigned cnt;
+
+ count = skip_whitespace(buf);
+ p = strchr(count, ',');
+ if (!p)
+ continue;
+ *p++ = '\0';
+ cnt = bb_strtou(count, NULL, 10);
+ if (strcmp(skip_non_whitespace(count), " total events") == 0) {
+#if ENABLE_FEATURE_POWERTOP_PROCIRQ
+ n = cnt / G.total_cpus;
+ if (n > 0 && n < G.interrupt_0) {
+ sprintf(line, " <interrupt> : %s", "extra timer interrupt");
+ save_line(line, G.interrupt_0 - n);
+ }
+#endif
+ break;
+ }
+ if (strchr(count, 'D'))
+ continue; /* deferred */
+ p = skip_whitespace(p); /* points to pid now */
+ process = NULL;
+ get_func_name:
+ p = strchr(p, ' ');
+ if (!p)
+ continue;
+ *p++ = '\0';
+ p = skip_whitespace(p);
+ if (process == NULL) {
+ process = p;
+ goto get_func_name;
+ }
+ 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 (is_prefixed_with(func, "tick_nohz_"))
+ continue;
+ if (is_prefixed_with(func, "tick_setup_sched_timer"))
+ continue;
+ //if (strcmp(process, "powertop") == 0)
+ // continue;
+
+ idx = index_in_strings("insmod\0modprobe\0swapper\0", process);
+ if (idx != -1) {
+ process = idx < 2 ? "[kernel module]" : "<kernel core>";
+ }
+
+ chomp(p);
+
+ // 46D\01136\0kondemand/1\0do_dbs_timer (delayed_work_timer_fn)
+ // ^ ^ ^
+ // count process func
+
+ //if (strchr(process, '['))
+ sprintf(line, "%15.15s : %s", process, func);
+ //else
+ // sprintf(line, "%s", process);
+ save_line(line, cnt);
+ }
+ fclose(fp);
+ }
+
+ return n;
+}
+
#ifdef __i386__
/*
* Get information about CPU using CPUID opcode.
*/
static void cpuid(unsigned int *eax, unsigned int *ebx, unsigned int *ecx,
- unsigned int *edx)
+ unsigned int *edx)
{
/* EAX value specifies what information to return */
__asm__(
}
#endif
-static void NOINLINE print_intel_cstates(void)
-{
#ifdef __i386__
+static NOINLINE void print_intel_cstates(void)
+{
int bios_table[8] = { 0 };
int nbios = 0;
DIR *cpudir;
if (!isdigit(d->d_name[3]))
continue;
- len = sprintf(fname, "/sys/devices/system/cpu/%s/cpuidle", d->d_name);
+ len = sprintf(fname, "%s/%s/cpuidle", "/sys/devices/system/cpu", 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.
+ * contains a 'time' and a 'usage' file.
*/
while ((d = readdir(dir)) != NULL) {
FILE *fp;
if (!edx || !(ecx & 1))
return;
- printf("Your CPU supports the following C-states: ");
+ printf("Your %s the following C-states: ", "CPU supports");
i = 0;
while (edx) {
if (edx & 7)
bb_putchar('\n');
/* Print BIOS C-States */
- printf("Your BIOS reports the following C-states: ");
- for (i = 0; i < 8; i++)
+ printf("Your %s the following C-states: ", "BIOS reports");
+ for (i = 0; i < ARRAY_SIZE(bios_table); 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]);
}
+#else
+# define print_intel_cstates() ((void)0)
+#endif
static void show_timerstats(void)
{
if (!G.cant_enable_timer_stats) {
int i, n = 0;
+ char strbuf6[6];
puts("\nTop causes for wakeups:");
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)
+ /* NB: upstream powertop prints "(wakeups/sec)",
+ * we print just "(wakeup counts)".
+ */
+ /*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.lines_cumulative_count,
- G.lines[i].count * 1.0 / DEFAULT_SLEEP, c,
- G.lines[i].string);
+ smart_ulltoa5(G.lines[i].count, strbuf6, " KMGTPEZY")[0] = '\0';
+ printf(/*" %5.1f%% (%s)%c %s\n"*/
+ " %5.1f%% (%s) %s\n",
+ G.lines[i].count * 100.0 / G.lines_cumulative_count,
+ strbuf6, /*c,*/
+ G.lines[i].string);
}
}
} else {
bb_putchar('\n');
bb_error_msg("no stats available; run as root or"
- " enable the cpufreq_stats module");
+ " enable the timer_stats module");
}
}
// 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)
+// 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"
-//usage: "Analyze power consumption on Intel-based laptops\n"
+//usage: "Analyze power consumption on Intel-based laptops"
int powertop_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-int powertop_main(int UNUSED_PARAM argc, char UNUSED_PARAM **argv)
+int powertop_main(int argc UNUSED_PARAM, char UNUSED_PARAM **argv)
{
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
- struct termios new_settings;
+ char cstate_lines[MAX_CSTATE_COUNT + 2][64];
+#if ENABLE_FEATURE_POWERTOP_INTERACTIVE
struct pollfd pfd[1];
pfd[0].fd = 0;
/* Get number of CPUs */
G.total_cpus = get_cpu_count();
- printf("Collecting data for "DEFAULT_SLEEP_STR" seconds\n");
+ puts("Collecting data for "DEFAULT_SLEEP_STR" seconds");
-#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);
+#if ENABLE_FEATURE_POWERTOP_INTERACTIVE
+ /* Turn on unbuffered input; turn off echoing, ^C ^Z etc */
+ set_termios_to_raw(STDIN_FILENO, &G.init_settings, TERMIOS_CLEAR_ISIG);
bb_signals(BB_FATAL_SIGS, sig_handler);
- tcsetattr_stdin_TCSANOW(&new_settings);
+ /* So we don't forget to reset term settings */
+ die_func = reset_term;
#endif
/* Collect initial data */
- process_irq_count_deltas();
+ process_irq_counts();
/* Read initial usage and duration */
read_cstate_counts(G.start_usage, G.start_duration);
/* The main loop */
for (;;) {
- /*double maxsleep = 0.0;*/
+ //double maxsleep = 0.0;
ullong totalticks, totalevents;
int i;
- FILE *fp;
G.cant_enable_timer_stats |= start_timer(); /* 1 on error */
-#if !ENABLE_FEATURE_USE_TERMIOS
+#if !ENABLE_FEATURE_POWERTOP_INTERACTIVE
sleep(DEFAULT_SLEEP);
#else
if (safe_poll(pfd, 1, DEFAULT_SLEEP * 1000) > 0) {
G.cant_enable_timer_stats |= stop_timer(); /* 1 on error */
clear_lines();
- process_irq_count_deltas();
+ process_irq_counts();
/* Clear the stats */
memset(cur_duration, 0, sizeof(cur_duration));
}
}
- /* Show title bar */
- print_header();
+ /* Home; clear screen */
+ printf(ESC"[H" ESC"[J");
/* 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");
+ strcpy(cstate_lines[0], "C-state information is not available\n");
} else {
- double slept;
double percentage;
- double newticks;
+ unsigned newticks;
newticks = G.total_cpus * DEFAULT_SLEEP * FREQ_ACPI_1000 - totalticks;
-
/* Handle rounding errors: do not display negative values */
- if (newticks < 0)
+ if ((int)newticks < 0)
newticks = 0;
- sprintf(cstate_lines[0], "Cn\t Avg residency\n");
+ sprintf(cstate_lines[0], "Cn\t\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);
+ sprintf(cstate_lines[1], "C0 (cpu running) (%4.1f%%)\n", percentage);
/* Compute values for individual C-states */
for (i = 0; i < MAX_CSTATE_COUNT; i++) {
if (cur_usage[i] != 0) {
+ double slept;
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;*/
+ sprintf(cstate_lines[i + 2], "C%u\t\t%5.1fms (%4.1f%%)\n",
+ i + 1, 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.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;
-// 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) = strchr((p), ' '); \
- if (!(p)) \
- continue; \
- *(p) = '\0'; \
- (p)++; \
- (p) = skip_whitespace(p); \
-} while (0)
- /* Get process name */
- 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]";
-
- if (strncmp(func, "tick_nohz_", 10) == 0)
- continue;
- if (strncmp(func, "tick_setup_sched_timer", 20) == 0)
- continue;
- if (strcmp(process, "powertop") == 0)
- continue;
-
- strchrnul(p, '\n')[0] = '\0';
-
- cnt = bb_strtoull(count, &p, 10);
- while (*p != '\0') {
- if (*p++ == 'D')
- goto skip;
- }
-
- if (strchr(process, '['))
- sprintf(line, "%s %s", process, func);
- else
- sprintf(line, "%s", process);
- save_line(line, cnt);
- skip: ;
- }
- fclose(fp);
- }
+ for (i = 0; i < MAX_CSTATE_COUNT + 2; i++)
+ if (cstate_lines[i][0])
+ fputs(cstate_lines[i], stdout);
+ i = process_timer_stats();
#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)
- save_line("[extra timer interrupt]", G.interrupt_0 - n);
+ if (totalevents == 0) {
+ /* No C-state info available, use timerstats */
+ totalevents = i * G.total_cpus + G.total_interrupt;
+ if (i < 0)
+ totalevents += G.interrupt_0 - i;
}
#endif
- 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);
+ /* Upstream powertop prints wakeups per sec per CPU,
+ * we print just raw wakeup counts.
+ */
+//TODO: show real seconds (think about manual refresh)
+ printf("\nWakeups-from-idle in %u seconds: %llu\n",
+ DEFAULT_SLEEP,
+ totalevents
+ );
update_lines_cumulative_count();
sort_lines();
} /* for (;;) */
bb_putchar('\n');
+#if ENABLE_FEATURE_POWERTOP_INTERACTIVE
+ reset_term();
+#endif
return EXIT_SUCCESS;
}