diff options
Diffstat (limited to 'procps')
-rw-r--r-- | procps/powertop.c | 880 | ||||
-rw-r--r-- | procps/top.c | 3 |
2 files changed, 882 insertions, 1 deletions
diff --git a/procps/powertop.c b/procps/powertop.c new file mode 100644 index 000000000..5e028f074 --- /dev/null +++ b/procps/powertop.c | |||
@@ -0,0 +1,880 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * A mini 'powertop' utility: | ||
4 | * Analyze power consumption on Intel-based laptops. | ||
5 | * Based on powertop 1.11. | ||
6 | * | ||
7 | * Copyright (C) 2010 Marek Polacek <mmpolacek@gmail.com> | ||
8 | * | ||
9 | * Licensed under GPLv2, see file LICENSE in this source tree. | ||
10 | */ | ||
11 | |||
12 | //applet:IF_POWERTOP(APPLET(powertop, _BB_DIR_BIN, _BB_SUID_DROP)) | ||
13 | |||
14 | //kbuild:lib-$(CONFIG_POWERTOP) += powertop.o | ||
15 | |||
16 | //config:config POWERTOP | ||
17 | //config: bool "powertop" | ||
18 | //config: default y | ||
19 | //config: help | ||
20 | //config: Analyze power consumption on Intel-based laptops | ||
21 | |||
22 | // XXX This should be configurable | ||
23 | #define ENABLE_FEATURE_POWERTOP_PROCIRQ 1 | ||
24 | |||
25 | #include "libbb.h" | ||
26 | |||
27 | |||
28 | //#define debug(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__) | ||
29 | #define debug(fmt, ...) ((void)0) | ||
30 | |||
31 | |||
32 | #define BLOATY_HPET_IRQ_NUM_DETECTION 0 | ||
33 | #define MAX_CSTATE_COUNT 8 | ||
34 | #define IRQCOUNT 40 | ||
35 | |||
36 | |||
37 | #define DEFAULT_SLEEP 10 | ||
38 | #define DEFAULT_SLEEP_STR "10" | ||
39 | |||
40 | /* Frequency of the ACPI timer */ | ||
41 | #define FREQ_ACPI 3579.545 | ||
42 | #define FREQ_ACPI_1000 3579545 | ||
43 | |||
44 | /* Max filename length of entry in /sys/devices subsystem */ | ||
45 | #define BIG_SYSNAME_LEN 16 | ||
46 | |||
47 | typedef unsigned long long ullong; | ||
48 | |||
49 | struct line { | ||
50 | char *string; | ||
51 | int count; | ||
52 | /*int disk_count;*/ | ||
53 | }; | ||
54 | |||
55 | #if ENABLE_FEATURE_POWERTOP_PROCIRQ | ||
56 | struct irqdata { | ||
57 | smallint active; | ||
58 | int number; | ||
59 | ullong count; | ||
60 | char irq_desc[32]; | ||
61 | }; | ||
62 | #endif | ||
63 | |||
64 | struct globals { | ||
65 | struct line *lines; /* the most often used member */ | ||
66 | int lines_cnt; | ||
67 | int lines_cumulative_count; | ||
68 | int maxcstate; | ||
69 | unsigned total_cpus; | ||
70 | smallint cant_enable_timer_stats; | ||
71 | #if ENABLE_FEATURE_POWERTOP_PROCIRQ | ||
72 | # if BLOATY_HPET_IRQ_NUM_DETECTION | ||
73 | smallint scanned_timer_list; | ||
74 | int percpu_hpet_start; | ||
75 | int percpu_hpet_end; | ||
76 | # endif | ||
77 | int interrupt_0; | ||
78 | int total_interrupt; | ||
79 | struct irqdata interrupts[IRQCOUNT]; | ||
80 | #endif | ||
81 | ullong start_usage[MAX_CSTATE_COUNT]; | ||
82 | ullong last_usage[MAX_CSTATE_COUNT]; | ||
83 | ullong start_duration[MAX_CSTATE_COUNT]; | ||
84 | ullong last_duration[MAX_CSTATE_COUNT]; | ||
85 | #if ENABLE_FEATURE_USE_TERMIOS | ||
86 | struct termios init_settings; | ||
87 | #endif | ||
88 | }; | ||
89 | #define G (*ptr_to_globals) | ||
90 | #define INIT_G() do { \ | ||
91 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ | ||
92 | } while (0) | ||
93 | |||
94 | #if ENABLE_FEATURE_USE_TERMIOS | ||
95 | static void reset_term(void) | ||
96 | { | ||
97 | tcsetattr_stdin_TCSANOW(&G.init_settings); | ||
98 | } | ||
99 | |||
100 | static void sig_handler(int signo UNUSED_PARAM) | ||
101 | { | ||
102 | reset_term(); | ||
103 | _exit(EXIT_FAILURE); | ||
104 | } | ||
105 | #endif | ||
106 | |||
107 | static int write_str_to_file(const char *fname, const char *str) | ||
108 | { | ||
109 | FILE *fp = fopen_for_write(fname); | ||
110 | if (!fp) | ||
111 | return 1; | ||
112 | fputs(str, fp); | ||
113 | fclose(fp); | ||
114 | return 0; | ||
115 | } | ||
116 | |||
117 | /* Make it more readable */ | ||
118 | #define start_timer() write_str_to_file("/proc/timer_stats", "1\n") | ||
119 | #define stop_timer() write_str_to_file("/proc/timer_stats", "0\n") | ||
120 | |||
121 | static NOINLINE void clear_lines(void) | ||
122 | { | ||
123 | int i; | ||
124 | if (G.lines) { | ||
125 | for (i = 0; i < G.lines_cnt; i++) | ||
126 | free(G.lines[i].string); | ||
127 | free(G.lines); | ||
128 | G.lines_cnt = 0; | ||
129 | G.lines = NULL; | ||
130 | } | ||
131 | } | ||
132 | |||
133 | static void update_lines_cumulative_count(void) | ||
134 | { | ||
135 | int i; | ||
136 | for (i = 0; i < G.lines_cnt; i++) | ||
137 | G.lines_cumulative_count += G.lines[i].count; | ||
138 | } | ||
139 | |||
140 | static int line_compare(const void *p1, const void *p2) | ||
141 | { | ||
142 | const struct line *a = p1; | ||
143 | const struct line *b = p2; | ||
144 | return (b->count /*+ 50 * b->disk_count*/) - (a->count /*+ 50 * a->disk_count*/); | ||
145 | } | ||
146 | |||
147 | static void sort_lines(void) | ||
148 | { | ||
149 | qsort(G.lines, G.lines_cnt, sizeof(G.lines[0]), line_compare); | ||
150 | } | ||
151 | |||
152 | /* Save C-state usage and duration. Also update maxcstate. */ | ||
153 | static void read_cstate_counts(ullong *usage, ullong *duration) | ||
154 | { | ||
155 | DIR *dir; | ||
156 | struct dirent *d; | ||
157 | |||
158 | dir = opendir("/proc/acpi/processor"); | ||
159 | if (!dir) | ||
160 | return; | ||
161 | |||
162 | while ((d = readdir(dir)) != NULL) { | ||
163 | FILE *fp; | ||
164 | char buf[192]; | ||
165 | int level; | ||
166 | int len; | ||
167 | |||
168 | len = strlen(d->d_name); /* "CPUnn" */ | ||
169 | if (len < 3 || len > BIG_SYSNAME_LEN) | ||
170 | continue; | ||
171 | |||
172 | sprintf(buf, "/proc/acpi/processor/%s/power", d->d_name); | ||
173 | fp = fopen_for_read(buf); | ||
174 | if (!fp) | ||
175 | continue; | ||
176 | |||
177 | // Example file contents: | ||
178 | // active state: C0 | ||
179 | // max_cstate: C8 | ||
180 | // maximum allowed latency: 2000000000 usec | ||
181 | // states: | ||
182 | // C1: type[C1] promotion[--] demotion[--] latency[001] usage[00006173] duration[00000000000000000000] | ||
183 | // C2: type[C2] promotion[--] demotion[--] latency[001] usage[00085191] duration[00000000000083024907] | ||
184 | // C3: type[C3] promotion[--] demotion[--] latency[017] usage[01017622] duration[00000000017921327182] | ||
185 | level = 0; | ||
186 | while (fgets(buf, sizeof(buf), fp)) { | ||
187 | char *p = strstr(buf, "age["); | ||
188 | if (!p) | ||
189 | continue; | ||
190 | p += 4; | ||
191 | usage[level] += bb_strtoull(p, NULL, 10) + 1; | ||
192 | p = strstr(buf, "ation["); | ||
193 | if (!p) | ||
194 | continue; | ||
195 | p += 6; | ||
196 | duration[level] += bb_strtoull(p, NULL, 10); | ||
197 | |||
198 | if (level >= MAX_CSTATE_COUNT-1) | ||
199 | break; | ||
200 | level++; | ||
201 | if (level > G.maxcstate) /* update maxcstate */ | ||
202 | G.maxcstate = level; | ||
203 | } | ||
204 | fclose(fp); | ||
205 | } | ||
206 | closedir(dir); | ||
207 | } | ||
208 | |||
209 | /* Add line and/or update count */ | ||
210 | static void save_line(const char *string, int count) | ||
211 | { | ||
212 | int i; | ||
213 | for (i = 0; i < G.lines_cnt; i++) { | ||
214 | if (strcmp(string, G.lines[i].string) == 0) { | ||
215 | /* It's already there, only update count */ | ||
216 | G.lines[i].count += count; | ||
217 | return; | ||
218 | } | ||
219 | } | ||
220 | |||
221 | /* Add new line */ | ||
222 | G.lines = xrealloc_vector(G.lines, 4, G.lines_cnt); | ||
223 | G.lines[G.lines_cnt].string = xstrdup(string); | ||
224 | G.lines[G.lines_cnt].count = count; | ||
225 | /*G.lines[G.lines_cnt].disk_count = 0;*/ | ||
226 | G.lines_cnt++; | ||
227 | } | ||
228 | |||
229 | #if ENABLE_FEATURE_POWERTOP_PROCIRQ | ||
230 | static int is_hpet_irq(const char *name) | ||
231 | { | ||
232 | char *p; | ||
233 | # if BLOATY_HPET_IRQ_NUM_DETECTION | ||
234 | long hpet_chan; | ||
235 | |||
236 | /* Learn the range of existing hpet timers. This is done once */ | ||
237 | if (!G.scanned_timer_list) { | ||
238 | FILE *fp; | ||
239 | char buf[80]; | ||
240 | |||
241 | G.scanned_timer_list = true; | ||
242 | fp = fopen_for_read("/proc/timer_list"); | ||
243 | if (!fp) | ||
244 | return 0; | ||
245 | |||
246 | while (fgets(buf, sizeof(buf), fp)) { | ||
247 | p = strstr(buf, "Clock Event Device: hpet"); | ||
248 | if (!p) | ||
249 | continue; | ||
250 | p += sizeof("Clock Event Device: hpet")-1; | ||
251 | if (!isdigit(*p)) | ||
252 | continue; | ||
253 | hpet_chan = xatoi_positive(p); | ||
254 | if (hpet_chan < G.percpu_hpet_start) | ||
255 | G.percpu_hpet_start = hpet_chan; | ||
256 | if (hpet_chan > G.percpu_hpet_end) | ||
257 | G.percpu_hpet_end = hpet_chan; | ||
258 | } | ||
259 | fclose(fp); | ||
260 | } | ||
261 | # endif | ||
262 | //TODO: optimize | ||
263 | p = strstr(name, "hpet"); | ||
264 | if (!p) | ||
265 | return 0; | ||
266 | p += 4; | ||
267 | if (!isdigit(*p)) | ||
268 | return 0; | ||
269 | # if BLOATY_HPET_IRQ_NUM_DETECTION | ||
270 | hpet_chan = xatoi_positive(p); | ||
271 | if (hpet_chan < G.percpu_hpet_start || hpet_chan > G.percpu_hpet_end) | ||
272 | return 0; | ||
273 | # endif | ||
274 | return 1; | ||
275 | } | ||
276 | |||
277 | /* Save new IRQ count, return delta from old one */ | ||
278 | static int save_irq_count(int irq, ullong count) | ||
279 | { | ||
280 | int unused = IRQCOUNT; | ||
281 | int i; | ||
282 | for (i = 0; i < IRQCOUNT; i++) { | ||
283 | if (G.interrupts[i].active && G.interrupts[i].number == irq) { | ||
284 | ullong old = G.interrupts[i].count; | ||
285 | G.interrupts[i].count = count; | ||
286 | return count - old; | ||
287 | } | ||
288 | if (!G.interrupts[i].active && unused > i) | ||
289 | unused = i; | ||
290 | } | ||
291 | if (unused < IRQCOUNT) { | ||
292 | G.interrupts[unused].active = 1; | ||
293 | G.interrupts[unused].count = count; | ||
294 | G.interrupts[unused].number = irq; | ||
295 | } | ||
296 | return count; | ||
297 | } | ||
298 | |||
299 | /* Read /proc/interrupts, save IRQ counts and IRQ description */ | ||
300 | static void process_irq_counts(void) | ||
301 | { | ||
302 | FILE *fp; | ||
303 | char buf[128]; | ||
304 | |||
305 | /* Reset values */ | ||
306 | G.interrupt_0 = 0; | ||
307 | G.total_interrupt = 0; | ||
308 | |||
309 | fp = xfopen_for_read("/proc/interrupts"); | ||
310 | while (fgets(buf, sizeof(buf), fp)) { | ||
311 | char irq_desc[sizeof(" <kernel IPI> : ") + sizeof(buf)]; | ||
312 | char *p; | ||
313 | const char *name; | ||
314 | int nr; | ||
315 | ullong count; | ||
316 | ullong delta; | ||
317 | |||
318 | p = strchr(buf, ':'); | ||
319 | if (!p) | ||
320 | continue; | ||
321 | /* 0: 143646045 153901007 IO-APIC-edge timer | ||
322 | * ^ | ||
323 | */ | ||
324 | /* Deal with non-maskable interrupts -- make up fake numbers */ | ||
325 | nr = -1; | ||
326 | if (buf[0] != ' ' && !isdigit(buf[0])) { | ||
327 | //TODO: optimize | ||
328 | if (strncmp(buf, "NMI:", 4) == 0) | ||
329 | nr = 20000; | ||
330 | if (strncmp(buf, "RES:", 4) == 0) | ||
331 | nr = 20001; | ||
332 | if (strncmp(buf, "CAL:", 4) == 0) | ||
333 | nr = 20002; | ||
334 | if (strncmp(buf, "TLB:", 4) == 0) | ||
335 | nr = 20003; | ||
336 | if (strncmp(buf, "TRM:", 4) == 0) | ||
337 | nr = 20004; | ||
338 | if (strncmp(buf, "THR:", 4) == 0) | ||
339 | nr = 20005; | ||
340 | if (strncmp(buf, "SPU:", 4) == 0) | ||
341 | nr = 20006; | ||
342 | } else { | ||
343 | /* bb_strtou doesn't eat leading spaces, using strtoul */ | ||
344 | nr = strtoul(buf, NULL, 10); | ||
345 | } | ||
346 | if (nr == -1) | ||
347 | continue; | ||
348 | |||
349 | p++; | ||
350 | /* 0: 143646045 153901007 IO-APIC-edge timer | ||
351 | * ^ | ||
352 | */ | ||
353 | /* Sum counts for this IRQ */ | ||
354 | count = 0; | ||
355 | while (1) { | ||
356 | char *tmp; | ||
357 | p = skip_whitespace(p); | ||
358 | if (!isdigit(*p)) | ||
359 | break; | ||
360 | count += bb_strtoull(p, &tmp, 10); | ||
361 | p = tmp; | ||
362 | } | ||
363 | /* 0: 143646045 153901007 IO-APIC-edge timer | ||
364 | * NMI: 1 2 Non-maskable interrupts | ||
365 | * ^ | ||
366 | */ | ||
367 | if (nr < 20000) { | ||
368 | /* Skip to the interrupt name, e.g. 'timer' */ | ||
369 | p = strchr(p, ' '); | ||
370 | if (!p) | ||
371 | continue; | ||
372 | p = skip_whitespace(p); | ||
373 | } | ||
374 | |||
375 | name = p; | ||
376 | strchrnul(name, '\n')[0] = '\0'; | ||
377 | /* Save description of the interrupt */ | ||
378 | if (nr >= 20000) | ||
379 | sprintf(irq_desc, " <kernel IPI> : %s", name); | ||
380 | else | ||
381 | sprintf(irq_desc, " <interrupt> : %s", name); | ||
382 | |||
383 | delta = save_irq_count(nr, count); | ||
384 | |||
385 | /* Skip per CPU timer interrupts */ | ||
386 | if (is_hpet_irq(name)) | ||
387 | continue; | ||
388 | |||
389 | if (nr != 0 && delta != 0) | ||
390 | save_line(irq_desc, delta); | ||
391 | |||
392 | if (nr == 0) | ||
393 | G.interrupt_0 = delta; | ||
394 | else | ||
395 | G.total_interrupt += delta; | ||
396 | } | ||
397 | |||
398 | fclose(fp); | ||
399 | } | ||
400 | #else /* !ENABLE_FEATURE_POWERTOP_PROCIRQ */ | ||
401 | # define process_irq_counts() ((void)0) | ||
402 | #endif | ||
403 | |||
404 | static NOINLINE int process_timer_stats(void) | ||
405 | { | ||
406 | char buf[128]; | ||
407 | char line[15 + 3 + 128]; | ||
408 | int n; | ||
409 | ullong totalticks; | ||
410 | FILE *fp; | ||
411 | |||
412 | buf[0] = '\0'; | ||
413 | totalticks = 0; | ||
414 | |||
415 | fp = NULL; | ||
416 | if (!G.cant_enable_timer_stats) | ||
417 | fp = fopen_for_read("/proc/timer_stats"); | ||
418 | if (fp) { | ||
419 | // Example file contents: | ||
420 | // Timer Stats Version: v0.2 | ||
421 | // Sample period: 1.329 s | ||
422 | // 76, 0 swapper hrtimer_start_range_ns (tick_sched_timer) | ||
423 | // 88, 0 swapper hrtimer_start_range_ns (tick_sched_timer) | ||
424 | // 24, 3787 firefox hrtimer_start_range_ns (hrtimer_wakeup) | ||
425 | // 46D, 1136 kondemand/1 do_dbs_timer (delayed_work_timer_fn) | ||
426 | // ... | ||
427 | // 1, 1656 Xorg hrtimer_start_range_ns (hrtimer_wakeup) | ||
428 | // 1, 2159 udisks-daemon hrtimer_start_range_ns (hrtimer_wakeup) | ||
429 | // 331 total events, 249.059 events/sec | ||
430 | while (fgets(buf, sizeof(buf), fp)) { | ||
431 | const char *count, *process, *func; | ||
432 | char *p; | ||
433 | int cnt; | ||
434 | |||
435 | count = skip_whitespace(buf); | ||
436 | p = strchr(count, ','); | ||
437 | if (!p) | ||
438 | continue; | ||
439 | *p++ = '\0'; | ||
440 | if (strcmp(strchrnul(count, ' '), " total events") == 0) | ||
441 | break; | ||
442 | p = skip_whitespace(p); /* points to pid */ | ||
443 | |||
444 | /* Find char ' ', then eat remaining spaces */ | ||
445 | #define ADVANCE(p) do { \ | ||
446 | (p) = strchr((p), ' '); \ | ||
447 | if (!(p)) \ | ||
448 | continue; \ | ||
449 | *(p) = '\0'; \ | ||
450 | (p)++; \ | ||
451 | (p) = skip_whitespace(p); \ | ||
452 | } while (0) | ||
453 | /* Get process name */ | ||
454 | ADVANCE(p); | ||
455 | process = p; | ||
456 | /* Get function */ | ||
457 | ADVANCE(p); | ||
458 | func = p; | ||
459 | #undef ADVANCE | ||
460 | //if (strcmp(process, "swapper") == 0 | ||
461 | // && strcmp(func, "hrtimer_start_range_ns (tick_sched_timer)\n") == 0 | ||
462 | //) { | ||
463 | // process = "[kernel scheduler]"; | ||
464 | // func = "Load balancing tick"; | ||
465 | //} | ||
466 | |||
467 | if (strncmp(func, "tick_nohz_", 10) == 0) | ||
468 | continue; | ||
469 | if (strncmp(func, "tick_setup_sched_timer", 20) == 0) | ||
470 | continue; | ||
471 | //if (strcmp(process, "powertop") == 0) | ||
472 | // continue; | ||
473 | |||
474 | if (strcmp(process, "insmod") == 0) | ||
475 | process = "[kernel module]"; | ||
476 | if (strcmp(process, "modprobe") == 0) | ||
477 | process = "[kernel module]"; | ||
478 | if (strcmp(process, "swapper") == 0) | ||
479 | process = "<kernel core>"; | ||
480 | |||
481 | strchrnul(p, '\n')[0] = '\0'; | ||
482 | |||
483 | { | ||
484 | char *tmp; | ||
485 | cnt = bb_strtoull(count, &tmp, 10); | ||
486 | p = tmp; | ||
487 | } | ||
488 | while (*p != '\0') { | ||
489 | if (*p++ == 'D') /* deferred */ | ||
490 | goto skip; | ||
491 | } | ||
492 | |||
493 | //if (strchr(process, '[')) | ||
494 | sprintf(line, "%15.15s : %s", process, func); | ||
495 | //else | ||
496 | // sprintf(line, "%s", process); | ||
497 | save_line(line, cnt); | ||
498 | skip: ; | ||
499 | } | ||
500 | fclose(fp); | ||
501 | } | ||
502 | |||
503 | n = 0; | ||
504 | #if ENABLE_FEATURE_POWERTOP_PROCIRQ | ||
505 | if (strstr(buf, "total events")) { | ||
506 | n = bb_strtoull(buf, NULL, 10) / G.total_cpus; | ||
507 | if (n > 0 && n < G.interrupt_0) { | ||
508 | sprintf(line, " <interrupt> : %s", "extra timer interrupt"); | ||
509 | save_line(line, G.interrupt_0 - n); | ||
510 | } | ||
511 | } | ||
512 | #endif | ||
513 | return n; | ||
514 | } | ||
515 | |||
516 | #ifdef __i386__ | ||
517 | /* | ||
518 | * Get information about CPU using CPUID opcode. | ||
519 | */ | ||
520 | static void cpuid(unsigned int *eax, unsigned int *ebx, unsigned int *ecx, | ||
521 | unsigned int *edx) | ||
522 | { | ||
523 | /* EAX value specifies what information to return */ | ||
524 | __asm__( | ||
525 | " pushl %%ebx\n" /* Save EBX */ | ||
526 | " cpuid\n" | ||
527 | " movl %%ebx, %1\n" /* Save content of EBX */ | ||
528 | " popl %%ebx\n" /* Restore EBX */ | ||
529 | : "=a"(*eax), /* Output */ | ||
530 | "=r"(*ebx), | ||
531 | "=c"(*ecx), | ||
532 | "=d"(*edx) | ||
533 | : "0"(*eax), /* Input */ | ||
534 | "1"(*ebx), | ||
535 | "2"(*ecx), | ||
536 | "3"(*edx) | ||
537 | /* No clobbered registers */ | ||
538 | ); | ||
539 | } | ||
540 | #endif | ||
541 | |||
542 | static NOINLINE void print_intel_cstates(void) | ||
543 | { | ||
544 | #ifdef __i386__ | ||
545 | int bios_table[8] = { 0 }; | ||
546 | int nbios = 0; | ||
547 | DIR *cpudir; | ||
548 | struct dirent *d; | ||
549 | int i; | ||
550 | unsigned eax, ebx, ecx, edx; | ||
551 | |||
552 | cpudir = opendir("/sys/devices/system/cpu"); | ||
553 | if (!cpudir) | ||
554 | return; | ||
555 | |||
556 | /* Loop over cpuN entries */ | ||
557 | while ((d = readdir(cpudir)) != NULL) { | ||
558 | DIR *dir; | ||
559 | int len; | ||
560 | char fname[sizeof("/sys/devices/system/cpu//cpuidle//desc") + 2*BIG_SYSNAME_LEN]; | ||
561 | |||
562 | len = strlen(d->d_name); | ||
563 | if (len < 3 || len > BIG_SYSNAME_LEN) | ||
564 | continue; | ||
565 | |||
566 | if (!isdigit(d->d_name[3])) | ||
567 | continue; | ||
568 | |||
569 | len = sprintf(fname, "/sys/devices/system/cpu/%s/cpuidle", d->d_name); | ||
570 | dir = opendir(fname); | ||
571 | if (!dir) | ||
572 | continue; | ||
573 | |||
574 | /* | ||
575 | * Every C-state has its own stateN directory, that | ||
576 | * contains a 'time' and a 'usage' file. | ||
577 | */ | ||
578 | while ((d = readdir(dir)) != NULL) { | ||
579 | FILE *fp; | ||
580 | char buf[64]; | ||
581 | int n; | ||
582 | |||
583 | n = strlen(d->d_name); | ||
584 | if (n < 3 || n > BIG_SYSNAME_LEN) | ||
585 | continue; | ||
586 | |||
587 | sprintf(fname + len, "/%s/desc", d->d_name); | ||
588 | fp = fopen_for_read(fname); | ||
589 | if (fp) { | ||
590 | char *p = fgets(buf, sizeof(buf), fp); | ||
591 | fclose(fp); | ||
592 | if (!p) | ||
593 | break; | ||
594 | p = strstr(p, "MWAIT "); | ||
595 | if (p) { | ||
596 | int pos; | ||
597 | p += sizeof("MWAIT ") - 1; | ||
598 | pos = (bb_strtoull(p, NULL, 16) >> 4) + 1; | ||
599 | if (pos >= ARRAY_SIZE(bios_table)) | ||
600 | continue; | ||
601 | bios_table[pos]++; | ||
602 | nbios++; | ||
603 | } | ||
604 | } | ||
605 | } | ||
606 | closedir(dir); | ||
607 | } | ||
608 | closedir(cpudir); | ||
609 | |||
610 | if (!nbios) | ||
611 | return; | ||
612 | |||
613 | eax = 5; | ||
614 | ebx = ecx = edx = 0; | ||
615 | cpuid(&eax, &ebx, &ecx, &edx); | ||
616 | if (!edx || !(ecx & 1)) | ||
617 | return; | ||
618 | |||
619 | printf("Your CPU supports the following C-states: "); | ||
620 | i = 0; | ||
621 | while (edx) { | ||
622 | if (edx & 7) | ||
623 | printf("C%u ", i); | ||
624 | edx >>= 4; | ||
625 | i++; | ||
626 | } | ||
627 | bb_putchar('\n'); | ||
628 | |||
629 | /* Print BIOS C-States */ | ||
630 | printf("Your BIOS reports the following C-states: "); | ||
631 | for (i = 0; i < 8; i++) | ||
632 | if (bios_table[i]) | ||
633 | printf("C%u ", i); | ||
634 | |||
635 | bb_putchar('\n'); | ||
636 | #endif | ||
637 | } | ||
638 | |||
639 | static void show_timerstats(void) | ||
640 | { | ||
641 | unsigned lines; | ||
642 | |||
643 | /* Get terminal height */ | ||
644 | get_terminal_width_height(STDOUT_FILENO, NULL, &lines); | ||
645 | |||
646 | /* We don't have whole terminal just for timerstats */ | ||
647 | lines -= 12; | ||
648 | |||
649 | if (!G.cant_enable_timer_stats) { | ||
650 | int i, n = 0; | ||
651 | char strbuf6[6]; | ||
652 | |||
653 | strbuf6[5] = '\0'; | ||
654 | puts("\nTop causes for wakeups:"); | ||
655 | for (i = 0; i < G.lines_cnt; i++) { | ||
656 | if ((G.lines[i].count > 0 /*|| G.lines[i].disk_count > 0*/) | ||
657 | && n++ < lines | ||
658 | ) { | ||
659 | /* NB: upstream powertop prints "(wakeups/sec)", | ||
660 | * we print just "(wakeup counts)". | ||
661 | */ | ||
662 | /*char c = ' '; | ||
663 | if (G.lines[i].disk_count) | ||
664 | c = 'D';*/ | ||
665 | smart_ulltoa5(G.lines[i].count, strbuf6, " KMGTPEZY"); | ||
666 | printf(/*" %5.1f%% (%s)%c %s\n"*/ | ||
667 | " %5.1f%% (%s) %s\n", | ||
668 | G.lines[i].count * 100.0 / G.lines_cumulative_count, | ||
669 | strbuf6, /*c,*/ | ||
670 | G.lines[i].string); | ||
671 | } | ||
672 | } | ||
673 | } else { | ||
674 | bb_putchar('\n'); | ||
675 | bb_error_msg("no stats available; run as root or" | ||
676 | " enable the cpufreq_stats module"); | ||
677 | } | ||
678 | } | ||
679 | |||
680 | // Example display from powertop version 1.11 | ||
681 | // Cn Avg residency P-states (frequencies) | ||
682 | // C0 (cpu running) ( 0.5%) 2.00 Ghz 0.0% | ||
683 | // polling 0.0ms ( 0.0%) 1.67 Ghz 0.0% | ||
684 | // C1 mwait 0.0ms ( 0.0%) 1333 Mhz 0.1% | ||
685 | // C2 mwait 0.1ms ( 0.1%) 1000 Mhz 99.9% | ||
686 | // C3 mwait 12.1ms (99.4%) | ||
687 | // | ||
688 | // Wakeups-from-idle per second : 93.6 interval: 15.0s | ||
689 | // no ACPI power usage estimate available | ||
690 | // | ||
691 | // Top causes for wakeups: | ||
692 | // 32.4% ( 26.7) <interrupt> : extra timer interrupt | ||
693 | // 29.0% ( 23.9) <kernel core> : hrtimer_start_range_ns (tick_sched_timer) | ||
694 | // 9.0% ( 7.5) <kernel core> : hrtimer_start (tick_sched_timer) | ||
695 | // 6.5% ( 5.3) <interrupt> : ata_piix | ||
696 | // 5.0% ( 4.1) inetd : hrtimer_start_range_ns (hrtimer_wakeup) | ||
697 | |||
698 | //usage:#define powertop_trivial_usage | ||
699 | //usage: "" | ||
700 | //usage:#define powertop_full_usage "\n\n" | ||
701 | //usage: "Analyze power consumption on Intel-based laptops\n" | ||
702 | |||
703 | int powertop_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
704 | int powertop_main(int UNUSED_PARAM argc, char UNUSED_PARAM **argv) | ||
705 | { | ||
706 | ullong cur_usage[MAX_CSTATE_COUNT]; | ||
707 | ullong cur_duration[MAX_CSTATE_COUNT]; | ||
708 | char cstate_lines[MAX_CSTATE_COUNT + 2][64]; | ||
709 | #if ENABLE_FEATURE_USE_TERMIOS | ||
710 | struct termios new_settings; | ||
711 | struct pollfd pfd[1]; | ||
712 | |||
713 | pfd[0].fd = 0; | ||
714 | pfd[0].events = POLLIN; | ||
715 | #endif | ||
716 | |||
717 | INIT_G(); | ||
718 | |||
719 | #if ENABLE_FEATURE_POWERTOP_PROCIRQ && BLOATY_HPET_IRQ_NUM_DETECTION | ||
720 | G.percpu_hpet_start = INT_MAX; | ||
721 | G.percpu_hpet_end = INT_MIN; | ||
722 | #endif | ||
723 | |||
724 | /* Print warning when we don't have superuser privileges */ | ||
725 | if (geteuid() != 0) | ||
726 | bb_error_msg("run as root to collect enough information"); | ||
727 | |||
728 | /* Get number of CPUs */ | ||
729 | G.total_cpus = get_cpu_count(); | ||
730 | |||
731 | printf("Collecting data for "DEFAULT_SLEEP_STR" seconds\n"); | ||
732 | |||
733 | #if ENABLE_FEATURE_USE_TERMIOS | ||
734 | tcgetattr(0, (void *)&G.init_settings); | ||
735 | memcpy(&new_settings, &G.init_settings, sizeof(new_settings)); | ||
736 | /* Turn on unbuffered input, turn off echoing */ | ||
737 | new_settings.c_lflag &= ~(ISIG | ICANON | ECHO | ECHONL); | ||
738 | /* So we don't forget to reset term settings */ | ||
739 | atexit(reset_term); | ||
740 | bb_signals(BB_FATAL_SIGS, sig_handler); | ||
741 | tcsetattr_stdin_TCSANOW(&new_settings); | ||
742 | #endif | ||
743 | |||
744 | /* Collect initial data */ | ||
745 | process_irq_counts(); | ||
746 | |||
747 | /* Read initial usage and duration */ | ||
748 | read_cstate_counts(G.start_usage, G.start_duration); | ||
749 | |||
750 | /* Copy them to "last" */ | ||
751 | memcpy(G.last_usage, G.start_usage, sizeof(G.last_usage)); | ||
752 | memcpy(G.last_duration, G.start_duration, sizeof(G.last_duration)); | ||
753 | |||
754 | /* Display C-states */ | ||
755 | print_intel_cstates(); | ||
756 | |||
757 | G.cant_enable_timer_stats |= stop_timer(); /* 1 on error */ | ||
758 | |||
759 | /* The main loop */ | ||
760 | for (;;) { | ||
761 | //double maxsleep = 0.0; | ||
762 | ullong totalticks, totalevents; | ||
763 | int i; | ||
764 | |||
765 | G.cant_enable_timer_stats |= start_timer(); /* 1 on error */ | ||
766 | #if !ENABLE_FEATURE_USE_TERMIOS | ||
767 | sleep(DEFAULT_SLEEP); | ||
768 | #else | ||
769 | if (safe_poll(pfd, 1, DEFAULT_SLEEP * 1000) > 0) { | ||
770 | unsigned char c; | ||
771 | if (safe_read(STDIN_FILENO, &c, 1) != 1) | ||
772 | break; /* EOF/error */ | ||
773 | if (c == G.init_settings.c_cc[VINTR]) | ||
774 | break; /* ^C */ | ||
775 | if ((c | 0x20) == 'q') | ||
776 | break; | ||
777 | } | ||
778 | #endif | ||
779 | G.cant_enable_timer_stats |= stop_timer(); /* 1 on error */ | ||
780 | |||
781 | clear_lines(); | ||
782 | process_irq_counts(); | ||
783 | |||
784 | /* Clear the stats */ | ||
785 | memset(cur_duration, 0, sizeof(cur_duration)); | ||
786 | memset(cur_usage, 0, sizeof(cur_usage)); | ||
787 | |||
788 | /* Read them */ | ||
789 | read_cstate_counts(cur_usage, cur_duration); | ||
790 | |||
791 | /* Count totalticks and totalevents */ | ||
792 | totalticks = totalevents = 0; | ||
793 | for (i = 0; i < MAX_CSTATE_COUNT; i++) { | ||
794 | if (cur_usage[i] != 0) { | ||
795 | totalticks += cur_duration[i] - G.last_duration[i]; | ||
796 | totalevents += cur_usage[i] - G.last_usage[i]; | ||
797 | } | ||
798 | } | ||
799 | |||
800 | /* Clear the screen */ | ||
801 | printf("\033[H\033[J"); | ||
802 | |||
803 | /* Clear C-state lines */ | ||
804 | memset(&cstate_lines, 0, sizeof(cstate_lines)); | ||
805 | |||
806 | if (totalevents == 0 && G.maxcstate <= 1) { | ||
807 | /* This should not happen */ | ||
808 | strcpy(cstate_lines[0], "C-state information is not available\n"); | ||
809 | } else { | ||
810 | double percentage; | ||
811 | unsigned newticks; | ||
812 | |||
813 | newticks = G.total_cpus * DEFAULT_SLEEP * FREQ_ACPI_1000 - totalticks; | ||
814 | /* Handle rounding errors: do not display negative values */ | ||
815 | if ((int)newticks < 0) | ||
816 | newticks = 0; | ||
817 | |||
818 | sprintf(cstate_lines[0], "Cn\t\t Avg residency\n"); | ||
819 | percentage = newticks * 100.0 / (G.total_cpus * DEFAULT_SLEEP * FREQ_ACPI_1000); | ||
820 | sprintf(cstate_lines[1], "C0 (cpu running) (%4.1f%%)\n", percentage); | ||
821 | |||
822 | /* Compute values for individual C-states */ | ||
823 | for (i = 0; i < MAX_CSTATE_COUNT; i++) { | ||
824 | if (cur_usage[i] != 0) { | ||
825 | double slept; | ||
826 | slept = (cur_duration[i] - G.last_duration[i]) | ||
827 | / (cur_usage[i] - G.last_usage[i] + 0.1) / FREQ_ACPI; | ||
828 | percentage = (cur_duration[i] - G.last_duration[i]) * 100 | ||
829 | / (G.total_cpus * DEFAULT_SLEEP * FREQ_ACPI_1000); | ||
830 | sprintf(cstate_lines[i + 2], "C%u\t\t%5.1fms (%4.1f%%)\n", | ||
831 | i + 1, slept, percentage); | ||
832 | //if (maxsleep < slept) | ||
833 | // maxsleep = slept; | ||
834 | } | ||
835 | } | ||
836 | } | ||
837 | |||
838 | for (i = 0; i < MAX_CSTATE_COUNT + 2; i++) | ||
839 | if (cstate_lines[i][0]) | ||
840 | fputs(cstate_lines[i], stdout); | ||
841 | |||
842 | i = process_timer_stats(); | ||
843 | #if ENABLE_FEATURE_POWERTOP_PROCIRQ | ||
844 | if (totalevents == 0) { | ||
845 | /* No C-state info available, use timerstats */ | ||
846 | totalevents = i * G.total_cpus + G.total_interrupt; | ||
847 | if (i < 0) | ||
848 | totalevents += G.interrupt_0 - i; | ||
849 | } | ||
850 | #endif | ||
851 | /* Upstream powertop prints wakeups per sec per CPU, | ||
852 | * we print just raw wakeup counts. | ||
853 | */ | ||
854 | //TODO: show real seconds (think about manual refresh) | ||
855 | printf("\nWakeups-from-idle in %u seconds: %llu\n", | ||
856 | DEFAULT_SLEEP, | ||
857 | totalevents | ||
858 | ); | ||
859 | |||
860 | update_lines_cumulative_count(); | ||
861 | sort_lines(); | ||
862 | show_timerstats(); | ||
863 | fflush(stdout); | ||
864 | |||
865 | /* Clear the stats */ | ||
866 | memset(cur_duration, 0, sizeof(cur_duration)); | ||
867 | memset(cur_usage, 0, sizeof(cur_usage)); | ||
868 | |||
869 | /* Get new values */ | ||
870 | read_cstate_counts(cur_usage, cur_duration); | ||
871 | |||
872 | /* Save them */ | ||
873 | memcpy(G.last_usage, cur_usage, sizeof(G.last_usage)); | ||
874 | memcpy(G.last_duration, cur_duration, sizeof(G.last_duration)); | ||
875 | } /* for (;;) */ | ||
876 | |||
877 | bb_putchar('\n'); | ||
878 | |||
879 | return EXIT_SUCCESS; | ||
880 | } | ||
diff --git a/procps/top.c b/procps/top.c index 4f37878de..f9106fac7 100644 --- a/procps/top.c +++ b/procps/top.c | |||
@@ -649,8 +649,9 @@ static void reset_term(void) | |||
649 | static void sig_catcher(int sig UNUSED_PARAM) | 649 | static void sig_catcher(int sig UNUSED_PARAM) |
650 | { | 650 | { |
651 | reset_term(); | 651 | reset_term(); |
652 | exit(EXIT_FAILURE); | 652 | _exit(EXIT_FAILURE); |
653 | } | 653 | } |
654 | |||
654 | #endif /* FEATURE_USE_TERMIOS */ | 655 | #endif /* FEATURE_USE_TERMIOS */ |
655 | 656 | ||
656 | /* | 657 | /* |