1 /*
2 * Copyright 2009, Intel Corporation
3 * Copyright 2009, Sun Microsystems, Inc
4 *
5 * This file is part of PowerTOP
6 *
7 * This program file is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; version 2 of the License.
10 *
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program in a file named COPYING; if not, write to the
18 * Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301 USA
21 *
22 * Authors:
23 * Arjan van de Ven <arjan@linux.intel.com>
24 * Eric C Saxe <eric.saxe@sun.com>
25 * Aubrey Li <aubrey.li@intel.com>
26 */
27
28 /*
29 * GPL Disclaimer
30 *
31 * For the avoidance of doubt, except that if any license choice other
32 * than GPL or LGPL is available it will apply instead, Sun elects to
33 * use only the General Public License version 2 (GPLv2) at this time
34 * for any software where a choice of GPL license versions is made
35 * available with the language indicating that GPLv2 or any later
36 * version may be used, or where a choice of which version of the GPL
37 * is applied is otherwise unspecified.
38 */
39
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <curses.h>
44 #include <signal.h>
45 #include <fcntl.h>
46 #include "powertop.h"
47
48 /*
49 * Minimum terminal height and width to run PowerTOP on curses mode.
50 */
51 #define PT_MIN_COLS 70
52 #define PT_MIN_ROWS 15
53
54 /*
55 * Display colors
56 */
57 #define PT_COLOR_DEFAULT 1
58 #define PT_COLOR_HEADER_BAR 2
59 #define PT_COLOR_ERROR 3
60 #define PT_COLOR_RED 4
61 #define PT_COLOR_YELLOW 5
62 #define PT_COLOR_GREEN 6
63 #define PT_COLOR_BRIGHT 7
64 #define PT_COLOR_BLUE 8
65
66 /*
67 * Constants for pt_display_setup()
68 */
69 #define SINGLE_LINE_SW 1
70 #define LENGTH_SUGG_SW 2
71 #define TITLE_LINE 1
72 #define BLANK_LINE 1
73 #define NEXT_LINE 1
74
75 #define print(win, y, x, fmt, args...) \
76 if (PT_ON_DUMP) \
77 (void) printf(fmt, ## args); \
78 else \
79 (void) mvwprintw(win, y, x, fmt, ## args);
80
81 enum pt_subwindows {
82 SW_TITLE,
83 SW_IDLE,
84 SW_FREQ,
85 SW_WAKEUPS,
86 SW_POWER,
87 SW_EVENTS,
88 SW_SUGG,
89 SW_STATUS,
90 SW_COUNT
91 };
92
93 typedef struct sb_slot {
94 char *msg;
95 struct sb_slot *prev;
96 struct sb_slot *next;
97 } sb_slot_t;
98
99 static WINDOW *sw[SW_COUNT];
100 static int win_cols, win_rows;
101 static sb_slot_t *status_bar;
102
103 /*
104 * Delete all subwindows and reset the terminal to a non-visual mode. This
105 * routine is used during resize events and before exiting.
106 */
107 static void
pt_display_cleanup(void)108 pt_display_cleanup(void)
109 {
110 int i;
111
112 for (i = 0; i < SW_COUNT; i++) {
113 if (sw[i] != NULL) {
114 (void) delwin(sw[i]);
115 sw[i] = NULL;
116 }
117 }
118
119 (void) endwin();
120 (void) fflush(stdout);
121 (void) putchar('\r');
122 }
123
124 static void
pt_display_get_size(void)125 pt_display_get_size(void)
126 {
127 getmaxyx(stdscr, win_rows, win_cols);
128
129 if (win_rows < PT_MIN_ROWS || win_cols < PT_MIN_COLS) {
130 pt_display_cleanup();
131 (void) printf("\n\nPowerTOP cannot run in such a small "
132 "terminal window. Please resize it.\n\n");
133 exit(EXIT_FAILURE);
134 }
135 }
136
137 void
pt_display_resize(void)138 pt_display_resize(void)
139 {
140 pt_display_cleanup();
141 (void) pt_display_init_curses();
142 pt_display_setup(B_TRUE);
143
144 pt_display_title_bar();
145
146 pt_display_states();
147
148 if (g_features & FEATURE_EVENTS) {
149 pt_display_wakeups(g_interval_length);
150 pt_display_events(g_interval_length);
151 }
152
153 pt_battery_print();
154 pt_sugg_pick();
155 pt_display_status_bar();
156
157 pt_display_update();
158
159 g_sig_resize = B_FALSE;
160 (void) signal(SIGWINCH, pt_sig_handler);
161 }
162
163 /*
164 * This part was re-written to be human readable and easy to modify. Please
165 * try to keep it that way and help us save some time.
166 *
167 * Friendly reminder:
168 * subwin(WINDOW *orig, int nlines, int ncols, int begin_y, int begin_x)
169 */
170 void
pt_display_setup(boolean_t resized)171 pt_display_setup(boolean_t resized)
172 {
173 /*
174 * These variables are used to properly set the initial y position and
175 * number of lines in each subwindow, as the number of supported CPU
176 * states affects their placement.
177 */
178 int cstate_lines, event_lines, pos_y = 0;
179
180 /*
181 * In theory, all systems have at least two idle states. We add two here
182 * since we have to use DTrace to figure out how many this box has.
183 */
184 cstate_lines = TITLE_LINE + max((g_max_cstate+2), g_npstates);
185
186 sw[SW_TITLE] = subwin(stdscr, SINGLE_LINE_SW, win_cols, pos_y, 0);
187
188 pos_y += NEXT_LINE + BLANK_LINE;
189 sw[SW_IDLE] = subwin(stdscr, cstate_lines, win_cols/2 + 1, pos_y, 0);
190 sw[SW_FREQ] = subwin(stdscr, cstate_lines, win_cols/2 - 8, pos_y,
191 win_cols/2 + 8);
192
193 pos_y += cstate_lines + BLANK_LINE;
194 sw[SW_WAKEUPS] = subwin(stdscr, SINGLE_LINE_SW, win_cols, pos_y, 0);
195
196 pos_y += NEXT_LINE;
197 sw[SW_POWER] = subwin(stdscr, SINGLE_LINE_SW, win_cols, pos_y, 0);
198
199 pos_y += NEXT_LINE + BLANK_LINE;
200 event_lines = win_rows - SINGLE_LINE_SW - NEXT_LINE - LENGTH_SUGG_SW -
201 pos_y;
202
203 if (event_lines > 0) {
204 sw[SW_EVENTS] = subwin(stdscr, event_lines, win_cols, pos_y, 0);
205 } else {
206 (void) printf("\n\nPowerTOP cannot run in such a small "
207 "terminal window, please resize it.\n\n");
208 exit(EXIT_FAILURE);
209 }
210
211 pos_y += event_lines + NEXT_LINE;
212 sw[SW_SUGG] = subwin(stdscr, SINGLE_LINE_SW, win_cols, pos_y, 0);
213
214 pos_y += BLANK_LINE + NEXT_LINE;
215 sw[SW_STATUS] = subwin(stdscr, SINGLE_LINE_SW, win_cols, pos_y, 0);
216
217 if (!resized) {
218 status_bar = NULL;
219
220 pt_display_mod_status_bar("Q - Quit");
221 pt_display_mod_status_bar("R - Refresh");
222 }
223 }
224
225 /*
226 * This routine handles all the necessary curses initialization.
227 */
228 void
pt_display_init_curses(void)229 pt_display_init_curses(void)
230 {
231 (void) initscr();
232
233 (void) atexit(pt_display_cleanup);
234
235 pt_display_get_size();
236
237 (void) start_color();
238
239 /*
240 * Enable keyboard mapping
241 */
242 (void) keypad(stdscr, TRUE);
243
244 /*
245 * Tell curses not to do NL->CR/NL on output
246 */
247 (void) nonl();
248
249 /*
250 * Take input chars one at a time, no wait for \n
251 */
252 (void) cbreak();
253
254 /*
255 * Dont echo input
256 */
257 (void) noecho();
258
259 /*
260 * Turn off cursor
261 */
262 (void) curs_set(0);
263
264 (void) init_pair(PT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK);
265 (void) init_pair(PT_COLOR_HEADER_BAR, COLOR_BLACK, COLOR_WHITE);
266 (void) init_pair(PT_COLOR_ERROR, COLOR_BLACK, COLOR_RED);
267 (void) init_pair(PT_COLOR_RED, COLOR_WHITE, COLOR_RED);
268 (void) init_pair(PT_COLOR_YELLOW, COLOR_WHITE, COLOR_YELLOW);
269 (void) init_pair(PT_COLOR_GREEN, COLOR_WHITE, COLOR_GREEN);
270 (void) init_pair(PT_COLOR_BLUE, COLOR_WHITE, COLOR_BLUE);
271 (void) init_pair(PT_COLOR_BRIGHT, COLOR_WHITE, COLOR_BLACK);
272 }
273
274 void
pt_display_update(void)275 pt_display_update(void)
276 {
277 (void) doupdate();
278 }
279
280 void
pt_display_title_bar(void)281 pt_display_title_bar(void)
282 {
283 char title_pad[10];
284
285 (void) wattrset(sw[SW_TITLE], COLOR_PAIR(PT_COLOR_HEADER_BAR));
286 (void) wbkgd(sw[SW_TITLE], COLOR_PAIR(PT_COLOR_HEADER_BAR));
287 (void) werase(sw[SW_TITLE]);
288
289 (void) snprintf(title_pad, 10, "%%%ds",
290 (win_cols - strlen(TITLE))/2 + strlen(TITLE));
291
292 /* LINTED: E_SEC_PRINTF_VAR_FMT */
293 print(sw[SW_TITLE], 0, 0, title_pad, TITLE);
294
295 (void) wnoutrefresh(sw[SW_TITLE]);
296 }
297
298 void
pt_display_status_bar(void)299 pt_display_status_bar(void)
300 {
301 sb_slot_t *n = status_bar;
302 int x = 0;
303
304 (void) werase(sw[SW_STATUS]);
305
306 while (n && x < win_cols) {
307 (void) wattron(sw[SW_STATUS], A_REVERSE);
308 print(sw[SW_STATUS], 0, x, "%s", n->msg);
309 (void) wattroff(sw[SW_STATUS], A_REVERSE);
310 x += strlen(n->msg) + 1;
311
312 n = n->next;
313 }
314
315 (void) wnoutrefresh(sw[SW_STATUS]);
316 }
317
318 /*
319 * Adds or removes items to the status bar automatically.
320 * Only one instance of an item allowed.
321 */
322 void
pt_display_mod_status_bar(char * msg)323 pt_display_mod_status_bar(char *msg)
324 {
325 sb_slot_t *new, *n;
326 boolean_t found = B_FALSE, first = B_FALSE;
327
328 if (msg == NULL) {
329 pt_error("can't add an empty status bar item\n");
330 return;
331 }
332
333 if (status_bar != NULL) {
334 /*
335 * Non-empty status bar. Look for an entry matching this msg.
336 */
337 for (n = status_bar; n != NULL; n = n->next) {
338
339 if (strcmp(msg, n->msg) == 0) {
340 if (n != status_bar)
341 n->prev->next = n->next;
342 else
343 first = B_TRUE;
344
345 if (n->next != NULL) {
346 n->next->prev = n->prev;
347 if (first)
348 status_bar = n->next;
349 } else {
350 if (first)
351 status_bar = NULL;
352 }
353
354 free(n);
355 found = B_TRUE;
356 }
357 }
358
359 /*
360 * Found and removed at least one occurrance of msg, refresh
361 * the bar and return.
362 */
363 if (found) {
364 return;
365 } else {
366 /*
367 * Inserting a new msg, walk to the end of the bar.
368 */
369 for (n = status_bar; n->next != NULL; n = n->next)
370 ;
371 }
372 }
373
374 if ((new = calloc(1, sizeof (sb_slot_t))) == NULL) {
375 pt_error("failed to allocate a new status bar slot\n");
376 } else {
377 new->msg = strdup(msg);
378
379 /*
380 * Check if it's the first entry.
381 */
382 if (status_bar == NULL) {
383 status_bar = new;
384 new->prev = NULL;
385 } else {
386 new->prev = n;
387 n->next = new;
388 }
389 new->next = NULL;
390 }
391 }
392
393 void
pt_display_states(void)394 pt_display_states(void)
395 {
396 char c[100];
397 int i;
398 double total_pstates = 0.0, avg, res;
399 uint64_t p0_speed, p1_speed;
400
401 print(sw[SW_IDLE], 0, 0, "%s\tAvg\tResidency\n", g_msg_idle_state);
402
403 if (g_features & FEATURE_CSTATE) {
404 res = (((double)g_cstate_info[0].total_time / g_total_c_time))
405 * 100;
406 (void) sprintf(c, "C0 (cpu running)\t\t(%.1f%%)\n", (float)res);
407 print(sw[SW_IDLE], 1, 0, "%s", c);
408
409 for (i = 1; i <= g_max_cstate; i++) {
410 /*
411 * In situations where the load is too intensive, the
412 * system might not transition at all.
413 */
414 if (g_cstate_info[i].events > 0)
415 avg = (((double)g_cstate_info[i].total_time/
416 MICROSEC)/g_cstate_info[i].events);
417 else
418 avg = 0;
419
420 res = ((double)g_cstate_info[i].total_time/
421 g_total_c_time) * 100;
422
423 (void) sprintf(c, "C%d\t\t\t%.1fms\t(%.1f%%)\n",
424 i, (float)avg, (float)res);
425 print(sw[SW_IDLE], i + 1, 0, "%s", c);
426 }
427 }
428
429 if (!PT_ON_DUMP)
430 (void) wnoutrefresh(sw[SW_IDLE]);
431
432 print(sw[SW_FREQ], 0, 0, "%s\n", g_msg_freq_state);
433
434 if (g_features & FEATURE_PSTATE) {
435 for (i = 0; i < g_npstates; i++) {
436 total_pstates +=
437 (double)(g_pstate_info[i].total_time/
438 g_ncpus_observed/MICROSEC);
439 }
440
441 /*
442 * display ACPI_PSTATE from P(n) to P(1)
443 */
444 for (i = 0; i < g_npstates - 1; i++) {
445 (void) sprintf(c, "%4lu Mhz\t%.1f%%",
446 (long)g_pstate_info[i].speed,
447 100 * (g_pstate_info[i].total_time/
448 g_ncpus_observed/MICROSEC/total_pstates));
449 print(sw[SW_FREQ], i+1, 0, "%s\n", c);
450 }
451
452 /*
453 * Display ACPI_PSTATE P0 according to if turbo
454 * mode is supported
455 */
456 if (g_turbo_supported) {
457 p1_speed = g_pstate_info[g_npstates - 2].speed;
458
459 /*
460 * If g_turbo_ratio <= 1.0, it will be ignored.
461 * we display P(0) as P(1) + 1.
462 */
463 if (g_turbo_ratio <= 1.0) {
464 p0_speed = p1_speed + 1;
465 } else {
466 /*
467 * If g_turbo_ratio > 1.0, that means
468 * turbo mode works. So, P(0) = ratio *
469 * P(1);
470 */
471 p0_speed = (uint64_t)(p1_speed *
472 g_turbo_ratio);
473 if (p0_speed < (p1_speed + 1))
474 p0_speed = p1_speed + 1;
475 }
476 /*
477 * Reset the ratio for the next round
478 */
479 g_turbo_ratio = 0.0;
480
481 /*
482 * Setup the string for the display
483 */
484 (void) sprintf(c, "%4lu Mhz(turbo)\t%.1f%%",
485 (long)p0_speed,
486 100 * (g_pstate_info[i].total_time/
487 g_ncpus_observed/MICROSEC/total_pstates));
488 } else {
489 (void) sprintf(c, "%4lu Mhz\t%.1f%%",
490 (long)g_pstate_info[i].speed,
491 100 * (g_pstate_info[i].total_time/
492 g_ncpus_observed/MICROSEC/total_pstates));
493 }
494 print(sw[SW_FREQ], i+1, 0, "%s\n", c);
495 } else {
496 if (g_npstates == 1) {
497 (void) sprintf(c, "%4lu Mhz\t%.1f%%",
498 (long)g_pstate_info[0].speed, 100.0);
499 print(sw[SW_FREQ], 1, 0, "%s\n", c);
500 }
501 }
502
503 if (!PT_ON_DUMP)
504 (void) wnoutrefresh(sw[SW_FREQ]);
505 }
506
507 void
pt_display_acpi_power(uint32_t flag,double rate,double rem_cap,double cap,uint32_t state)508 pt_display_acpi_power(uint32_t flag, double rate, double rem_cap, double cap,
509 uint32_t state)
510 {
511 char buffer[1024];
512
513 (void) sprintf(buffer, "no ACPI power usage estimate available");
514
515 if (!PT_ON_DUMP)
516 (void) werase(sw[SW_POWER]);
517
518 if (flag) {
519 char *c;
520 (void) sprintf(buffer, "Power usage (ACPI estimate): %.3fW",
521 rate);
522 (void) strcat(buffer, " ");
523 c = &buffer[strlen(buffer)];
524 switch (state) {
525 case 0:
526 (void) sprintf(c, "(running on AC power, fully "
527 "charged)");
528 break;
529 case 1:
530 (void) sprintf(c, "(discharging: %3.1f hours)",
531 (uint32_t)rem_cap/rate);
532 break;
533 case 2:
534 (void) sprintf(c, "(charging: %3.1f hours)",
535 (uint32_t)(cap - rem_cap)/rate);
536 break;
537 case 4:
538 (void) sprintf(c, "(##critically low battery power##)");
539 break;
540 }
541
542 }
543
544 print(sw[SW_POWER], 0, 0, "%s\n", buffer);
545 if (!PT_ON_DUMP)
546 (void) wnoutrefresh(sw[SW_POWER]);
547 }
548
549 void
pt_display_wakeups(double interval)550 pt_display_wakeups(double interval)
551 {
552 char c[100];
553 int i, event_sum = 0;
554 event_info_t *event = g_event_info;
555
556 if (!PT_ON_DUMP) {
557 (void) werase(sw[SW_WAKEUPS]);
558 (void) wbkgd(sw[SW_WAKEUPS], COLOR_PAIR(PT_COLOR_RED));
559 (void) wattron(sw[SW_WAKEUPS], A_BOLD);
560 }
561
562 /*
563 * calculate the actual total event number
564 */
565 for (i = 0; i < g_top_events; i++, event++)
566 event_sum += event->total_count;
567
568 /*
569 * g_total_events is the sum of the number of Cx->C0 transition,
570 * So when the system is very busy, the idle thread will have no
571 * chance or very seldom to be scheduled, this could cause >100%
572 * event report. Re-assign g_total_events to the actual event
573 * number is a way to avoid this issue.
574 */
575 if (event_sum > g_total_events)
576 g_total_events = event_sum;
577
578 (void) sprintf(c, "Wakeups-from-idle per second: %4.1f\tinterval: "
579 "%.1fs", (double)(g_total_events/interval), interval);
580 print(sw[SW_WAKEUPS], 0, 0, "%s\n", c);
581
582 if (!PT_ON_DUMP)
583 (void) wnoutrefresh(sw[SW_WAKEUPS]);
584 }
585
586 void
pt_display_events(double interval)587 pt_display_events(double interval)
588 {
589 char c[100];
590 int i;
591 double events;
592 event_info_t *event = g_event_info;
593
594 if (!PT_ON_DUMP) {
595 (void) werase(sw[SW_EVENTS]);
596 (void) wbkgd(sw[SW_EVENTS], COLOR_PAIR(PT_COLOR_DEFAULT));
597 (void) wattron(sw[SW_EVENTS], COLOR_PAIR(PT_COLOR_DEFAULT));
598 }
599
600 /*
601 * Sort the event report list
602 */
603 if (g_top_events > EVENT_NUM_MAX)
604 g_top_events = EVENT_NUM_MAX;
605
606 qsort((void *)g_event_info, g_top_events, sizeof (event_info_t),
607 pt_event_compare);
608
609 if (PT_ON_CPU)
610 (void) sprintf(c, "Top causes for wakeups on CPU %d:\n",
611 g_observed_cpu);
612 else
613 (void) sprintf(c, "Top causes for wakeups:\n");
614
615 print(sw[SW_EVENTS], 0, 0, "%s", c);
616
617 for (i = 0; i < g_top_events; i++, event++) {
618
619 if (g_total_events > 0 && event->total_count > 0)
620 events = (double)event->total_count/
621 (double)g_total_events;
622 else
623 continue;
624
625 (void) sprintf(c, "%4.1f%% (%5.1f)", 100 * events,
626 (double)event->total_count/interval);
627 print(sw[SW_EVENTS], i+1, 0, "%s", c);
628 print(sw[SW_EVENTS], i+1, 16, "%20s :",
629 event->offender_name);
630 print(sw[SW_EVENTS], i+1, 40, "%-64s\n",
631 event->offense_name);
632 }
633
634 if (!PT_ON_DUMP)
635 (void) wnoutrefresh(sw[SW_EVENTS]);
636 }
637
638 void
pt_display_suggestions(char * sug)639 pt_display_suggestions(char *sug)
640 {
641 (void) werase(sw[SW_SUGG]);
642
643 if (sug != NULL)
644 print(sw[SW_SUGG], 0, 0, "%s", sug);
645
646 (void) wnoutrefresh(sw[SW_SUGG]);
647 }
648