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