From b507cc3aceda26eff851230223a62c6fb471573c Mon Sep 17 00:00:00 2001 From: Marek Polacek Date: Mon, 25 Oct 2010 03:44:34 +0200 Subject: powertop: new applet Signed-off-by: Marek Polacek Signed-off-by: Denys Vlasenko --- procps/powertop.c | 886 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 886 insertions(+) create mode 100644 procps/powertop.c (limited to 'procps') diff --git a/procps/powertop.c b/procps/powertop.c new file mode 100644 index 0000000..f35aa5c --- /dev/null +++ b/procps/powertop.c @@ -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 + * + * 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(" : ") + 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, " : %s", name); + else + sprintf(irq_desc, " : %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; +} -- cgit v1.1