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