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 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 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 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 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 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 280 pt_display_update(void) 281 { 282 (void) doupdate(); 283 } 284 285 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 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 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 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 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 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 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 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