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 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 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 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 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 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 275 pt_display_update(void) 276 { 277 (void) doupdate(); 278 } 279 280 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 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 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 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 int p_diff = 1; 458 p0_speed = g_pstate_info[g_npstates - 1].speed; 459 p1_speed = g_pstate_info[g_npstates - 2].speed; 460 461 /* 462 * AMD systems don't have a visible extra Pstate 463 * indicating turbo mode as Intel does. Use the 464 * actual P0 frequency in that case. 465 */ 466 if (p0_speed != p1_speed + 1) { 467 p1_speed = p0_speed; 468 p_diff = 0; 469 } 470 471 /* 472 * If g_turbo_ratio <= 1.0, it will be ignored. 473 * we display P(0) as P(1) + p_diff. 474 */ 475 if (g_turbo_ratio <= 1.0) { 476 p0_speed = p1_speed + p_diff; 477 } else { 478 /* 479 * If g_turbo_ratio > 1.0, that means 480 * turbo mode works. So, P(0) = ratio * 481 * P(1); 482 */ 483 p0_speed = (uint64_t)(p1_speed * 484 g_turbo_ratio); 485 if (p0_speed < (p1_speed + p_diff)) 486 p0_speed = p1_speed + p_diff; 487 } 488 /* 489 * Reset the ratio for the next round 490 */ 491 g_turbo_ratio = 0.0; 492 493 /* 494 * Setup the string for the display 495 */ 496 (void) sprintf(c, "%4lu Mhz(turbo)\t%.1f%%", 497 (long)p0_speed, 498 100 * (g_pstate_info[i].total_time/ 499 g_ncpus_observed/MICROSEC/total_pstates)); 500 } else { 501 (void) sprintf(c, "%4lu Mhz\t%.1f%%", 502 (long)g_pstate_info[i].speed, 503 100 * (g_pstate_info[i].total_time/ 504 g_ncpus_observed/MICROSEC/total_pstates)); 505 } 506 print(sw[SW_FREQ], i+1, 0, "%s\n", c); 507 } else { 508 if (g_npstates == 1) { 509 (void) sprintf(c, "%4lu Mhz\t%.1f%%", 510 (long)g_pstate_info[0].speed, 100.0); 511 print(sw[SW_FREQ], 1, 0, "%s\n", c); 512 } 513 } 514 515 if (!PT_ON_DUMP) 516 (void) wnoutrefresh(sw[SW_FREQ]); 517 } 518 519 void 520 pt_display_acpi_power(uint32_t flag, double rate, double rem_cap, double cap, 521 uint32_t state) 522 { 523 char buffer[1024]; 524 525 (void) sprintf(buffer, "no ACPI power usage estimate available"); 526 527 if (!PT_ON_DUMP) 528 (void) werase(sw[SW_POWER]); 529 530 if (flag) { 531 char *c; 532 (void) sprintf(buffer, "Power usage (ACPI estimate): %.3fW", 533 rate); 534 (void) strcat(buffer, " "); 535 c = &buffer[strlen(buffer)]; 536 switch (state) { 537 case 0: 538 (void) sprintf(c, "(running on AC power, fully " 539 "charged)"); 540 break; 541 case 1: 542 (void) sprintf(c, "(discharging: %3.1f hours)", 543 (uint32_t)rem_cap/rate); 544 break; 545 case 2: 546 (void) sprintf(c, "(charging: %3.1f hours)", 547 (uint32_t)(cap - rem_cap)/rate); 548 break; 549 case 4: 550 (void) sprintf(c, "(##critically low battery power##)"); 551 break; 552 } 553 554 } 555 556 print(sw[SW_POWER], 0, 0, "%s\n", buffer); 557 if (!PT_ON_DUMP) 558 (void) wnoutrefresh(sw[SW_POWER]); 559 } 560 561 void 562 pt_display_wakeups(double interval) 563 { 564 char c[100]; 565 int i, event_sum = 0; 566 event_info_t *event = g_event_info; 567 568 if (!PT_ON_DUMP) { 569 (void) werase(sw[SW_WAKEUPS]); 570 (void) wbkgd(sw[SW_WAKEUPS], COLOR_PAIR(PT_COLOR_RED)); 571 (void) wattron(sw[SW_WAKEUPS], A_BOLD); 572 } 573 574 /* 575 * calculate the actual total event number 576 */ 577 for (i = 0; i < g_top_events; i++, event++) 578 event_sum += event->total_count; 579 580 /* 581 * g_total_events is the sum of the number of Cx->C0 transition, 582 * So when the system is very busy, the idle thread will have no 583 * chance or very seldom to be scheduled, this could cause >100% 584 * event report. Re-assign g_total_events to the actual event 585 * number is a way to avoid this issue. 586 */ 587 if (event_sum > g_total_events) 588 g_total_events = event_sum; 589 590 (void) sprintf(c, "Wakeups-from-idle per second: %4.1f\tinterval: " 591 "%.1fs", (double)(g_total_events/interval), interval); 592 print(sw[SW_WAKEUPS], 0, 0, "%s\n", c); 593 594 if (!PT_ON_DUMP) 595 (void) wnoutrefresh(sw[SW_WAKEUPS]); 596 } 597 598 void 599 pt_display_events(double interval) 600 { 601 char c[100]; 602 int i; 603 double events; 604 event_info_t *event = g_event_info; 605 606 if (!PT_ON_DUMP) { 607 (void) werase(sw[SW_EVENTS]); 608 (void) wbkgd(sw[SW_EVENTS], COLOR_PAIR(PT_COLOR_DEFAULT)); 609 (void) wattron(sw[SW_EVENTS], COLOR_PAIR(PT_COLOR_DEFAULT)); 610 } 611 612 /* 613 * Sort the event report list 614 */ 615 if (g_top_events > EVENT_NUM_MAX) 616 g_top_events = EVENT_NUM_MAX; 617 618 qsort((void *)g_event_info, g_top_events, sizeof (event_info_t), 619 pt_event_compare); 620 621 if (PT_ON_CPU) 622 (void) sprintf(c, "Top causes for wakeups on CPU %d:\n", 623 g_observed_cpu); 624 else 625 (void) sprintf(c, "Top causes for wakeups:\n"); 626 627 print(sw[SW_EVENTS], 0, 0, "%s", c); 628 629 for (i = 0; i < g_top_events; i++, event++) { 630 631 if (g_total_events > 0 && event->total_count > 0) 632 events = (double)event->total_count/ 633 (double)g_total_events; 634 else 635 continue; 636 637 (void) sprintf(c, "%4.1f%% (%5.1f)", 100 * events, 638 (double)event->total_count/interval); 639 print(sw[SW_EVENTS], i+1, 0, "%s", c); 640 print(sw[SW_EVENTS], i+1, 16, "%20s :", 641 event->offender_name); 642 print(sw[SW_EVENTS], i+1, 40, "%-64s\n", 643 event->offense_name); 644 } 645 646 if (!PT_ON_DUMP) 647 (void) wnoutrefresh(sw[SW_EVENTS]); 648 } 649 650 void 651 pt_display_suggestions(char *sug) 652 { 653 (void) werase(sw[SW_SUGG]); 654 655 if (sug != NULL) 656 print(sw[SW_SUGG], 0, 0, "%s", sug); 657 658 (void) wnoutrefresh(sw[SW_SUGG]); 659 } 660