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(const char *msg) 329 { 330 sb_slot_t *new, *n; 331 boolean_t found = B_FALSE; 332 333 if (msg == NULL) { 334 pt_error("can't add/remove 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 n = status_bar; 343 while (n != NULL) { 344 boolean_t first = (n == status_bar); 345 346 if (strcmp(msg, n->msg) == 0) { 347 if (n != status_bar) 348 n->prev->next = n->next; 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->msg); 360 free(n); 361 found = B_TRUE; 362 n = status_bar; 363 continue; 364 } 365 n = n->next; 366 } 367 368 if (found) { 369 /* 370 * Found and removed at least one occurrance of msg. 371 */ 372 return; 373 } else if (status_bar != NULL) { 374 /* 375 * Inserting a new msg, walk to the end of the bar. 376 */ 377 for (n = status_bar; n->next != NULL; n = n->next) 378 ; 379 } 380 } 381 382 if ((new = calloc(1, sizeof (sb_slot_t))) == NULL) { 383 pt_error("failed to allocate a new status bar slot\n"); 384 } else { 385 new->msg = strdup(msg); 386 if (new->msg == NULL) { 387 pt_error("failed to allocate status bar message\n"); 388 free(new); 389 return; 390 } 391 392 /* 393 * Check if it's the first entry. 394 */ 395 if (status_bar == NULL) { 396 status_bar = new; 397 new->prev = NULL; 398 } else { 399 new->prev = n; 400 n->next = new; 401 } 402 new->next = NULL; 403 } 404 } 405 406 void 407 pt_display_states(void) 408 { 409 char c[100]; 410 int i; 411 double total_pstates = 0.0, avg, res; 412 uint64_t p0_speed, p1_speed; 413 414 print(sw[SW_IDLE], 0, 0, "%s\tAvg\tResidency\n", g_msg_idle_state); 415 416 if (g_features & FEATURE_CSTATE) { 417 res = (((double)g_cstate_info[0].total_time / g_total_c_time)) 418 * 100; 419 (void) sprintf(c, "C0 (cpu running)\t\t(%.1f%%)\n", (float)res); 420 print(sw[SW_IDLE], 1, 0, "%s", c); 421 422 for (i = 1; i <= g_max_cstate; i++) { 423 /* 424 * In situations where the load is too intensive, the 425 * system might not transition at all. 426 */ 427 if (g_cstate_info[i].events > 0) 428 avg = (((double)g_cstate_info[i].total_time/ 429 MICROSEC)/g_cstate_info[i].events); 430 else 431 avg = 0; 432 433 res = ((double)g_cstate_info[i].total_time/ 434 g_total_c_time) * 100; 435 436 (void) sprintf(c, "C%d\t\t\t%.1fms\t(%.1f%%)\n", 437 i, (float)avg, (float)res); 438 print(sw[SW_IDLE], i + 1, 0, "%s", c); 439 } 440 } 441 442 if (!PT_ON_DUMP) 443 (void) wnoutrefresh(sw[SW_IDLE]); 444 445 print(sw[SW_FREQ], 0, 0, "%s\n", g_msg_freq_state); 446 447 if (g_features & FEATURE_PSTATE) { 448 for (i = 0; i < g_npstates; i++) { 449 total_pstates += 450 (double)(g_pstate_info[i].total_time/ 451 g_ncpus_observed/MICROSEC); 452 } 453 454 /* 455 * display ACPI_PSTATE from P(n) to P(1) 456 */ 457 for (i = 0; i < g_npstates - 1; i++) { 458 (void) sprintf(c, "%4lu Mhz\t%.1f%%", 459 (long)g_pstate_info[i].speed, 460 100 * (g_pstate_info[i].total_time/ 461 g_ncpus_observed/MICROSEC/total_pstates)); 462 print(sw[SW_FREQ], i+1, 0, "%s\n", c); 463 } 464 465 /* 466 * Display ACPI_PSTATE P0 according to if turbo 467 * mode is supported 468 */ 469 if (g_turbo_supported) { 470 int p_diff = 1; 471 p0_speed = g_pstate_info[g_npstates - 1].speed; 472 p1_speed = g_pstate_info[g_npstates - 2].speed; 473 474 /* 475 * AMD systems don't have a visible extra Pstate 476 * indicating turbo mode as Intel does. Use the 477 * actual P0 frequency in that case. 478 */ 479 if (p0_speed != p1_speed + 1) { 480 p1_speed = p0_speed; 481 p_diff = 0; 482 } 483 484 /* 485 * If g_turbo_ratio <= 1.0, it will be ignored. 486 * we display P(0) as P(1) + p_diff. 487 */ 488 if (g_turbo_ratio <= 1.0) { 489 p0_speed = p1_speed + p_diff; 490 } else { 491 /* 492 * If g_turbo_ratio > 1.0, that means 493 * turbo mode works. So, P(0) = ratio * 494 * P(1); 495 */ 496 p0_speed = (uint64_t)(p1_speed * 497 g_turbo_ratio); 498 if (p0_speed < (p1_speed + p_diff)) 499 p0_speed = p1_speed + p_diff; 500 } 501 /* 502 * Reset the ratio for the next round 503 */ 504 g_turbo_ratio = 0.0; 505 506 /* 507 * Setup the string for the display 508 */ 509 (void) sprintf(c, "%4lu Mhz(turbo)\t%.1f%%", 510 (long)p0_speed, 511 100 * (g_pstate_info[i].total_time/ 512 g_ncpus_observed/MICROSEC/total_pstates)); 513 } else { 514 (void) sprintf(c, "%4lu Mhz\t%.1f%%", 515 (long)g_pstate_info[i].speed, 516 100 * (g_pstate_info[i].total_time/ 517 g_ncpus_observed/MICROSEC/total_pstates)); 518 } 519 print(sw[SW_FREQ], i+1, 0, "%s\n", c); 520 } else { 521 if (g_npstates == 1) { 522 (void) sprintf(c, "%4lu Mhz\t%.1f%%", 523 (long)g_pstate_info[0].speed, 100.0); 524 print(sw[SW_FREQ], 1, 0, "%s\n", c); 525 } 526 } 527 528 if (!PT_ON_DUMP) 529 (void) wnoutrefresh(sw[SW_FREQ]); 530 } 531 532 void 533 pt_display_acpi_power(uint32_t flag, double rate, double rem_cap, double cap, 534 uint32_t state) 535 { 536 char buffer[1024]; 537 538 (void) sprintf(buffer, "no ACPI power usage estimate available"); 539 540 if (!PT_ON_DUMP) 541 (void) werase(sw[SW_POWER]); 542 543 if (flag) { 544 char *c; 545 (void) sprintf(buffer, "Power usage (ACPI estimate): %.3fW", 546 rate); 547 (void) strcat(buffer, " "); 548 c = &buffer[strlen(buffer)]; 549 switch (state) { 550 case 0: 551 (void) sprintf(c, "(running on AC power, fully " 552 "charged)"); 553 break; 554 case 1: 555 (void) sprintf(c, "(discharging: %3.1f hours)", 556 (uint32_t)rem_cap/rate); 557 break; 558 case 2: 559 (void) sprintf(c, "(charging: %3.1f hours)", 560 (uint32_t)(cap - rem_cap)/rate); 561 break; 562 case 4: 563 (void) sprintf(c, "(##critically low battery power##)"); 564 break; 565 } 566 567 } 568 569 print(sw[SW_POWER], 0, 0, "%s\n", buffer); 570 if (!PT_ON_DUMP) 571 (void) wnoutrefresh(sw[SW_POWER]); 572 } 573 574 void 575 pt_display_wakeups(double interval) 576 { 577 char c[100]; 578 int i, event_sum = 0; 579 event_info_t *event = g_event_info; 580 581 if (!PT_ON_DUMP) { 582 (void) werase(sw[SW_WAKEUPS]); 583 (void) wbkgd(sw[SW_WAKEUPS], COLOR_PAIR(PT_COLOR_RED)); 584 (void) wattron(sw[SW_WAKEUPS], A_BOLD); 585 } 586 587 /* 588 * calculate the actual total event number 589 */ 590 for (i = 0; i < g_top_events; i++, event++) 591 event_sum += event->total_count; 592 593 /* 594 * g_total_events is the sum of the number of Cx->C0 transition, 595 * So when the system is very busy, the idle thread will have no 596 * chance or very seldom to be scheduled, this could cause >100% 597 * event report. Re-assign g_total_events to the actual event 598 * number is a way to avoid this issue. 599 */ 600 if (event_sum > g_total_events) 601 g_total_events = event_sum; 602 603 (void) sprintf(c, "Wakeups-from-idle per second: %4.1f\tinterval: " 604 "%.1fs", (double)(g_total_events/interval), interval); 605 print(sw[SW_WAKEUPS], 0, 0, "%s\n", c); 606 607 if (!PT_ON_DUMP) 608 (void) wnoutrefresh(sw[SW_WAKEUPS]); 609 } 610 611 void 612 pt_display_events(double interval) 613 { 614 char c[100]; 615 int i; 616 double events; 617 event_info_t *event = g_event_info; 618 619 if (!PT_ON_DUMP) { 620 (void) werase(sw[SW_EVENTS]); 621 (void) wbkgd(sw[SW_EVENTS], COLOR_PAIR(PT_COLOR_DEFAULT)); 622 (void) wattron(sw[SW_EVENTS], COLOR_PAIR(PT_COLOR_DEFAULT)); 623 } 624 625 /* 626 * Sort the event report list 627 */ 628 if (g_top_events > EVENT_NUM_MAX) 629 g_top_events = EVENT_NUM_MAX; 630 631 qsort((void *)g_event_info, g_top_events, sizeof (event_info_t), 632 pt_event_compare); 633 634 if (PT_ON_CPU) 635 (void) sprintf(c, "Top causes for wakeups on CPU %d:\n", 636 g_observed_cpu); 637 else 638 (void) sprintf(c, "Top causes for wakeups:\n"); 639 640 print(sw[SW_EVENTS], 0, 0, "%s", c); 641 642 for (i = 0; i < g_top_events; i++, event++) { 643 644 if (g_total_events > 0 && event->total_count > 0) 645 events = (double)event->total_count/ 646 (double)g_total_events; 647 else 648 continue; 649 650 (void) sprintf(c, "%4.1f%% (%5.1f)", 100 * events, 651 (double)event->total_count/interval); 652 print(sw[SW_EVENTS], i+1, 0, "%s", c); 653 print(sw[SW_EVENTS], i+1, 16, "%20s :", 654 event->offender_name); 655 print(sw[SW_EVENTS], i+1, 40, "%-64s\n", 656 event->offense_name); 657 } 658 659 if (!PT_ON_DUMP) 660 (void) wnoutrefresh(sw[SW_EVENTS]); 661 } 662 663 void 664 pt_display_suggestions(char *sug) 665 { 666 (void) werase(sw[SW_SUGG]); 667 668 if (sug != NULL) 669 print(sw[SW_SUGG], 0, 0, "%s", sug); 670 671 (void) wnoutrefresh(sw[SW_SUGG]); 672 } 673