6937487be7
Back in 2007, commit 0c97c9d437
("'simple' error message functions by
Loic Grenie") introduced bb_simple_perror_msg() to allow for a lower
overhead call to bb_perror_msg() when only a string was being printed
with no parameters. This saves space for some CPU architectures because
it avoids the overhead of a call to a variadic function. However there
has never been a simple version of bb_error_msg(), and since 2007 many
new calls to bb_perror_msg() have been added that only take a single
parameter and so could have been using bb_simple_perror_message().
This changeset introduces 'simple' versions of bb_info_msg(),
bb_error_msg(), bb_error_msg_and_die(), bb_herror_msg() and
bb_herror_msg_and_die(), and replaces all calls that only take a
single parameter, or use something like ("%s", arg), with calls to the
corresponding 'simple' version.
Since it is likely that single parameter calls to the variadic functions
may be accidentally reintroduced in the future a new debugging config
option WARN_SIMPLE_MSG has been introduced. This uses some macro magic
which will cause any such calls to generate a warning, but this is
turned off by default to avoid use of the unpleasant macros in normal
circumstances.
This is a large changeset due to the number of calls that have been
replaced. The only files that contain changes other than simple
substitution of function calls are libbb.h, libbb/herror_msg.c,
libbb/verror_msg.c and libbb/xfuncs_printf.c. In miscutils/devfsd.c,
networking/udhcp/common.h and util-linux/mdev.c additonal macros have
been added for logging so that single parameter and multiple parameter
logging variants exist.
The amount of space saved varies considerably by architecture, and was
found to be as follows (for 'defconfig' using GCC 7.4):
Arm: -92 bytes
MIPS: -52 bytes
PPC: -1836 bytes
x86_64: -938 bytes
Note that for the MIPS architecture only an exception had to be made
disabling the 'simple' calls for 'udhcp' (in networking/udhcp/common.h)
because it made these files larger on MIPS.
Signed-off-by: James Byrne <james.byrne@origamienergy.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
865 lines
21 KiB
C
865 lines
21 KiB
C
/* 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.
|
|
*/
|
|
//config:config POWERTOP
|
|
//config: bool "powertop (9.6 kb)"
|
|
//config: default y
|
|
//config: help
|
|
//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))
|
|
|
|
//kbuild:lib-$(CONFIG_POWERTOP) += powertop.o
|
|
|
|
// XXX This should be configurable
|
|
#define ENABLE_FEATURE_POWERTOP_PROCIRQ 1
|
|
|
|
#include "libbb.h"
|
|
|
|
|
|
//#define debug(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__)
|
|
#define debug(fmt, ...) ((void)0)
|
|
|
|
|
|
#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
|
|
|
|
/* Max filename length of entry in /sys/devices subsystem */
|
|
#define BIG_SYSNAME_LEN 16
|
|
|
|
#define ESC "\033"
|
|
|
|
typedef unsigned long long ullong;
|
|
|
|
struct line {
|
|
char *string;
|
|
int count;
|
|
/*int disk_count;*/
|
|
};
|
|
|
|
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
|
|
struct irqdata {
|
|
smallint active;
|
|
int number;
|
|
ullong count;
|
|
char irq_desc[32];
|
|
};
|
|
#endif
|
|
|
|
struct globals {
|
|
struct line *lines; /* the most often used member */
|
|
int lines_cnt;
|
|
int lines_cumulative_count;
|
|
int maxcstate;
|
|
unsigned total_cpus;
|
|
smallint cant_enable_timer_stats;
|
|
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
|
|
# 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
|
|
ullong start_usage[MAX_CSTATE_COUNT];
|
|
ullong last_usage[MAX_CSTATE_COUNT];
|
|
ullong start_duration[MAX_CSTATE_COUNT];
|
|
ullong last_duration[MAX_CSTATE_COUNT];
|
|
#if ENABLE_FEATURE_POWERTOP_INTERACTIVE
|
|
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_POWERTOP_INTERACTIVE
|
|
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 NOINLINE void clear_lines(void)
|
|
{
|
|
int i;
|
|
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)
|
|
{
|
|
int i;
|
|
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*/);
|
|
}
|
|
|
|
static void sort_lines(void)
|
|
{
|
|
qsort(G.lines, G.lines_cnt, sizeof(G.lines[0]), line_compare);
|
|
}
|
|
|
|
/* Save C-state usage and duration. Also update maxcstate. */
|
|
static void read_cstate_counts(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;
|
|
int len;
|
|
|
|
len = strlen(d->d_name); /* "CPUnn" */
|
|
if (len < 3 || len > BIG_SYSNAME_LEN)
|
|
continue;
|
|
|
|
sprintf(buf, "%s/%s/power", "/proc/acpi/processor", d->d_name);
|
|
fp = fopen_for_read(buf);
|
|
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 = strstr(buf, "age[");
|
|
if (!p)
|
|
continue;
|
|
p += 4;
|
|
usage[level] += bb_strtoull(p, NULL, 10) + 1;
|
|
p = strstr(buf, "ation[");
|
|
if (!p)
|
|
continue;
|
|
p += 6;
|
|
duration[level] += bb_strtoull(p, NULL, 10);
|
|
|
|
if (level >= MAX_CSTATE_COUNT-1)
|
|
break;
|
|
level++;
|
|
if (level > G.maxcstate) /* update maxcstate */
|
|
G.maxcstate = level;
|
|
}
|
|
fclose(fp);
|
|
}
|
|
closedir(dir);
|
|
}
|
|
|
|
/* Add line and/or update count */
|
|
static void save_line(const char *string, int count)
|
|
{
|
|
int 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;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Add new line */
|
|
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;*/
|
|
G.lines_cnt++;
|
|
}
|
|
|
|
#if ENABLE_FEATURE_POWERTOP_PROCIRQ
|
|
static int is_hpet_irq(const char *name)
|
|
{
|
|
char *p;
|
|
# if BLOATY_HPET_IRQ_NUM_DETECTION
|
|
long hpet_chan;
|
|
|
|
/* Learn the range of existing hpet timers. This is done once */
|
|
if (!G.scanned_timer_list) {
|
|
FILE *fp;
|
|
char buf[80];
|
|
|
|
G.scanned_timer_list = 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))
|
|
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);
|
|
}
|
|
# endif
|
|
//TODO: optimize
|
|
p = strstr(name, "hpet");
|
|
if (!p)
|
|
return 0;
|
|
p += 4;
|
|
if (!isdigit(*p))
|
|
return 0;
|
|
# if BLOATY_HPET_IRQ_NUM_DETECTION
|
|
hpet_chan = xatoi_positive(p);
|
|
if (hpet_chan < G.percpu_hpet_start || hpet_chan > G.percpu_hpet_end)
|
|
return 0;
|
|
# endif
|
|
return 1;
|
|
}
|
|
|
|
/* 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 = G.interrupts[i].count;
|
|
G.interrupts[i].count = count;
|
|
return count - old;
|
|
}
|
|
if (!G.interrupts[i].active && unused > i)
|
|
unused = i;
|
|
}
|
|
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 process_irq_counts(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;
|
|
ullong count;
|
|
ullong delta;
|
|
|
|
p = strchr(buf, ':');
|
|
if (!p)
|
|
continue;
|
|
/* 0: 143646045 153901007 IO-APIC-edge timer
|
|
* ^
|
|
*/
|
|
*p = '\0';
|
|
/* Deal with non-maskable interrupts -- make up fake numbers */
|
|
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;
|
|
}
|
|
p++;
|
|
/* 0: 143646045 153901007 IO-APIC-edge timer
|
|
* ^
|
|
*/
|
|
/* Sum counts for this IRQ */
|
|
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 (nr < 20000) {
|
|
/* Skip to the interrupt name, e.g. 'timer' */
|
|
p = strchr(p, ' ');
|
|
if (!p)
|
|
continue;
|
|
p = skip_whitespace(p);
|
|
}
|
|
|
|
name = p;
|
|
chomp(p);
|
|
/* Save description of the interrupt */
|
|
if (nr >= 20000)
|
|
sprintf(irq_desc, " <kernel IPI> : %s", name);
|
|
else
|
|
sprintf(irq_desc, " <interrupt> : %s", name);
|
|
|
|
delta = save_irq_count(nr, count);
|
|
|
|
/* Skip per CPU timer interrupts */
|
|
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);
|
|
}
|
|
#else /* !ENABLE_FEATURE_POWERTOP_PROCIRQ */
|
|
# 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)
|
|
{
|
|
/* 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
|
|
|
|
#ifdef __i386__
|
|
static NOINLINE void print_intel_cstates(void)
|
|
{
|
|
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, "%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.
|
|
*/
|
|
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 %s the following C-states: ", "CPU supports");
|
|
i = 0;
|
|
while (edx) {
|
|
if (edx & 7)
|
|
printf("C%u ", i);
|
|
edx >>= 4;
|
|
i++;
|
|
}
|
|
bb_putchar('\n');
|
|
|
|
/* Print BIOS C-States */
|
|
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');
|
|
}
|
|
#else
|
|
# define print_intel_cstates() ((void)0)
|
|
#endif
|
|
|
|
static void show_timerstats(void)
|
|
{
|
|
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 (!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
|
|
) {
|
|
/* NB: upstream powertop prints "(wakeups/sec)",
|
|
* we print just "(wakeup counts)".
|
|
*/
|
|
/*char c = ' ';
|
|
if (G.lines[i].disk_count)
|
|
c = 'D';*/
|
|
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_simple_error_msg("no stats available; run as root or"
|
|
" enable the timer_stats module");
|
|
}
|
|
}
|
|
|
|
// 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"
|
|
//usage: "Analyze power consumption on Intel-based laptops"
|
|
|
|
int powertop_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
|
|
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[MAX_CSTATE_COUNT + 2][64];
|
|
#if ENABLE_FEATURE_POWERTOP_INTERACTIVE
|
|
struct pollfd pfd[1];
|
|
|
|
pfd[0].fd = 0;
|
|
pfd[0].events = POLLIN;
|
|
#endif
|
|
|
|
INIT_G();
|
|
|
|
#if ENABLE_FEATURE_POWERTOP_PROCIRQ && BLOATY_HPET_IRQ_NUM_DETECTION
|
|
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_simple_error_msg("run as root to collect enough information");
|
|
|
|
/* Get number of CPUs */
|
|
G.total_cpus = get_cpu_count();
|
|
|
|
puts("Collecting data for "DEFAULT_SLEEP_STR" seconds");
|
|
|
|
#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);
|
|
/* So we don't forget to reset term settings */
|
|
die_func = reset_term;
|
|
#endif
|
|
|
|
/* Collect initial data */
|
|
process_irq_counts();
|
|
|
|
/* Read initial usage and duration */
|
|
read_cstate_counts(G.start_usage, G.start_duration);
|
|
|
|
/* 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();
|
|
|
|
G.cant_enable_timer_stats |= stop_timer(); /* 1 on error */
|
|
|
|
/* The main loop */
|
|
for (;;) {
|
|
//double maxsleep = 0.0;
|
|
ullong totalticks, totalevents;
|
|
int i;
|
|
|
|
G.cant_enable_timer_stats |= start_timer(); /* 1 on error */
|
|
#if !ENABLE_FEATURE_POWERTOP_INTERACTIVE
|
|
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
|
|
G.cant_enable_timer_stats |= stop_timer(); /* 1 on error */
|
|
|
|
clear_lines();
|
|
process_irq_counts();
|
|
|
|
/* Clear the stats */
|
|
memset(cur_duration, 0, sizeof(cur_duration));
|
|
memset(cur_usage, 0, sizeof(cur_usage));
|
|
|
|
/* Read them */
|
|
read_cstate_counts(cur_usage, cur_duration);
|
|
|
|
/* Count totalticks and totalevents */
|
|
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];
|
|
}
|
|
}
|
|
|
|
/* 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 */
|
|
strcpy(cstate_lines[0], "C-state information is not available\n");
|
|
} else {
|
|
double percentage;
|
|
unsigned newticks;
|
|
|
|
newticks = G.total_cpus * DEFAULT_SLEEP * FREQ_ACPI_1000 - totalticks;
|
|
/* Handle rounding errors: do not display negative values */
|
|
if ((int)newticks < 0)
|
|
newticks = 0;
|
|
|
|
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);
|
|
|
|
/* 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);
|
|
sprintf(cstate_lines[i + 2], "C%u\t\t%5.1fms (%4.1f%%)\n",
|
|
i + 1, slept, percentage);
|
|
//if (maxsleep < slept)
|
|
// maxsleep = slept;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (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
|
|
/* 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();
|
|
show_timerstats();
|
|
fflush(stdout);
|
|
|
|
/* Clear the stats */
|
|
memset(cur_duration, 0, sizeof(cur_duration));
|
|
memset(cur_usage, 0, sizeof(cur_usage));
|
|
|
|
/* Get new values */
|
|
read_cstate_counts(cur_usage, cur_duration);
|
|
|
|
/* 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');
|
|
#if ENABLE_FEATURE_POWERTOP_INTERACTIVE
|
|
reset_term();
|
|
#endif
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|