/* * Copyright 2009, Intel Corporation * Copyright 2009, Sun Microsystems, Inc * * This file is part of PowerTOP * * This program file is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file named COPYING; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * Authors: * Arjan van de Ven * Eric C Saxe * Aubrey Li */ /* * GPL Disclaimer * * For the avoidance of doubt, except that if any license choice other * than GPL or LGPL is available it will apply instead, Sun elects to * use only the General Public License version 2 (GPLv2) at this time * for any software where a choice of GPL license versions is made * available with the language indicating that GPLv2 or any later * version may be used, or where a choice of which version of the GPL * is applied is otherwise unspecified. */ #include #include #include #include #include #include #include "powertop.h" /* * Minimum terminal height and width to run PowerTOP on curses mode. */ #define PT_MIN_COLS 70 #define PT_MIN_ROWS 15 /* * Display colors */ #define PT_COLOR_DEFAULT 1 #define PT_COLOR_HEADER_BAR 2 #define PT_COLOR_ERROR 3 #define PT_COLOR_RED 4 #define PT_COLOR_YELLOW 5 #define PT_COLOR_GREEN 6 #define PT_COLOR_BRIGHT 7 #define PT_COLOR_BLUE 8 /* * Constants for pt_display_setup() */ #define SINGLE_LINE_SW 1 #define LENGTH_SUGG_SW 2 #define TITLE_LINE 1 #define BLANK_LINE 1 #define NEXT_LINE 1 #define print(win, y, x, fmt, args...) \ if (PT_ON_DUMP) \ (void) printf(fmt, ## args); \ else \ (void) mvwprintw(win, y, x, fmt, ## args); enum pt_subwindows { SW_TITLE, SW_IDLE, SW_FREQ, SW_WAKEUPS, SW_POWER, SW_EVENTS, SW_SUGG, SW_STATUS, SW_COUNT }; typedef struct sb_slot { char *msg; struct sb_slot *prev; struct sb_slot *next; } sb_slot_t; static WINDOW *sw[SW_COUNT]; static int win_cols, win_rows; static sb_slot_t *status_bar; /* * Delete all subwindows and reset the terminal to a non-visual mode. This * routine is used during resize events and before exiting. */ static void pt_display_cleanup(void) { int i; for (i = 0; i < SW_COUNT; i++) { if (sw[i] != NULL) { (void) delwin(sw[i]); sw[i] = NULL; } } (void) endwin(); (void) fflush(stdout); (void) putchar('\r'); } static void pt_display_get_size(void) { getmaxyx(stdscr, win_rows, win_cols); if (win_rows < PT_MIN_ROWS || win_cols < PT_MIN_COLS) { pt_display_cleanup(); (void) printf("\n\nPowerTOP cannot run in such a small " "terminal window. Please resize it.\n\n"); exit(EXIT_FAILURE); } } void pt_display_resize(void) { pt_display_cleanup(); (void) pt_display_init_curses(); pt_display_setup(B_TRUE); pt_display_title_bar(); pt_display_states(); if (g_features & FEATURE_EVENTS) { pt_display_wakeups(g_interval_length); pt_display_events(g_interval_length); } pt_battery_print(); pt_sugg_pick(); pt_display_status_bar(); pt_display_update(); g_sig_resize = B_FALSE; (void) signal(SIGWINCH, pt_sig_handler); } /* * This part was re-written to be human readable and easy to modify. Please * try to keep it that way and help us save some time. * * Friendly reminder: * subwin(WINDOW *orig, int nlines, int ncols, int begin_y, int begin_x) */ void pt_display_setup(boolean_t resized) { /* * These variables are used to properly set the initial y position and * number of lines in each subwindow, as the number of supported CPU * states affects their placement. */ int cstate_lines, event_lines, pos_y = 0; /* * In theory, all systems have at least two idle states. We add two here * since we have to use DTrace to figure out how many this box has. */ cstate_lines = TITLE_LINE + max((g_max_cstate+2), g_npstates); sw[SW_TITLE] = subwin(stdscr, SINGLE_LINE_SW, win_cols, pos_y, 0); pos_y += NEXT_LINE + BLANK_LINE; sw[SW_IDLE] = subwin(stdscr, cstate_lines, win_cols/2 + 1, pos_y, 0); sw[SW_FREQ] = subwin(stdscr, cstate_lines, win_cols/2 - 8, pos_y, win_cols/2 + 8); pos_y += cstate_lines + BLANK_LINE; sw[SW_WAKEUPS] = subwin(stdscr, SINGLE_LINE_SW, win_cols, pos_y, 0); pos_y += NEXT_LINE; sw[SW_POWER] = subwin(stdscr, SINGLE_LINE_SW, win_cols, pos_y, 0); pos_y += NEXT_LINE + BLANK_LINE; event_lines = win_rows - SINGLE_LINE_SW - NEXT_LINE - LENGTH_SUGG_SW - pos_y; if (event_lines > 0) { sw[SW_EVENTS] = subwin(stdscr, event_lines, win_cols, pos_y, 0); } else { (void) printf("\n\nPowerTOP cannot run in such a small " "terminal window, please resize it.\n\n"); exit(EXIT_FAILURE); } pos_y += event_lines + NEXT_LINE; sw[SW_SUGG] = subwin(stdscr, SINGLE_LINE_SW, win_cols, pos_y, 0); pos_y += BLANK_LINE + NEXT_LINE; sw[SW_STATUS] = subwin(stdscr, SINGLE_LINE_SW, win_cols, pos_y, 0); if (!resized) { status_bar = NULL; pt_display_mod_status_bar("Q - Quit"); pt_display_mod_status_bar("R - Refresh"); } } /* * This routine handles all the necessary curses initialization. */ void pt_display_init_curses(void) { (void) initscr(); (void) atexit(pt_display_cleanup); pt_display_get_size(); (void) start_color(); /* * Enable keyboard mapping */ (void) keypad(stdscr, TRUE); /* * Tell curses not to do NL->CR/NL on output */ (void) nonl(); /* * Take input chars one at a time, no wait for \n */ (void) cbreak(); /* * Dont echo input */ (void) noecho(); /* * Turn off cursor */ (void) curs_set(0); (void) init_pair(PT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK); (void) init_pair(PT_COLOR_HEADER_BAR, COLOR_BLACK, COLOR_WHITE); (void) init_pair(PT_COLOR_ERROR, COLOR_BLACK, COLOR_RED); (void) init_pair(PT_COLOR_RED, COLOR_WHITE, COLOR_RED); (void) init_pair(PT_COLOR_YELLOW, COLOR_WHITE, COLOR_YELLOW); (void) init_pair(PT_COLOR_GREEN, COLOR_WHITE, COLOR_GREEN); (void) init_pair(PT_COLOR_BLUE, COLOR_WHITE, COLOR_BLUE); (void) init_pair(PT_COLOR_BRIGHT, COLOR_WHITE, COLOR_BLACK); } void pt_display_update(void) { (void) doupdate(); } void pt_display_title_bar(void) { char title_pad[10]; (void) wattrset(sw[SW_TITLE], COLOR_PAIR(PT_COLOR_HEADER_BAR)); (void) wbkgd(sw[SW_TITLE], COLOR_PAIR(PT_COLOR_HEADER_BAR)); (void) werase(sw[SW_TITLE]); (void) snprintf(title_pad, 10, "%%%ds", (win_cols - strlen(TITLE))/2 + strlen(TITLE)); /* LINTED: E_SEC_PRINTF_VAR_FMT */ print(sw[SW_TITLE], 0, 0, title_pad, TITLE); (void) wnoutrefresh(sw[SW_TITLE]); } void pt_display_status_bar(void) { sb_slot_t *n = status_bar; int x = 0; (void) werase(sw[SW_STATUS]); while (n && x < win_cols) { (void) wattron(sw[SW_STATUS], A_REVERSE); print(sw[SW_STATUS], 0, x, "%s", n->msg); (void) wattroff(sw[SW_STATUS], A_REVERSE); x += strlen(n->msg) + 1; n = n->next; } (void) wnoutrefresh(sw[SW_STATUS]); } /* * Adds or removes items to the status bar automatically. * Only one instance of an item allowed. */ void pt_display_mod_status_bar(char *msg) { sb_slot_t *new, *n; boolean_t found = B_FALSE, first = B_FALSE; if (msg == NULL) { pt_error("can't add an empty status bar item\n"); return; } if (status_bar != NULL) { /* * Non-empty status bar. Look for an entry matching this msg. */ for (n = status_bar; n != NULL; n = n->next) { if (strcmp(msg, n->msg) == 0) { if (n != status_bar) n->prev->next = n->next; else first = B_TRUE; if (n->next != NULL) { n->next->prev = n->prev; if (first) status_bar = n->next; } else { if (first) status_bar = NULL; } free(n); found = B_TRUE; } } /* * Found and removed at least one occurrance of msg, refresh * the bar and return. */ if (found) { return; } else { /* * Inserting a new msg, walk to the end of the bar. */ for (n = status_bar; n->next != NULL; n = n->next) ; } } if ((new = calloc(1, sizeof (sb_slot_t))) == NULL) { pt_error("failed to allocate a new status bar slot\n"); } else { new->msg = strdup(msg); /* * Check if it's the first entry. */ if (status_bar == NULL) { status_bar = new; new->prev = NULL; } else { new->prev = n; n->next = new; } new->next = NULL; } } void pt_display_states(void) { char c[100]; int i; double total_pstates = 0.0, avg, res; uint64_t p0_speed, p1_speed; print(sw[SW_IDLE], 0, 0, "%s\tAvg\tResidency\n", g_msg_idle_state); if (g_features & FEATURE_CSTATE) { res = (((double)g_cstate_info[0].total_time / g_total_c_time)) * 100; (void) sprintf(c, "C0 (cpu running)\t\t(%.1f%%)\n", (float)res); print(sw[SW_IDLE], 1, 0, "%s", c); for (i = 1; i <= g_max_cstate; i++) { /* * In situations where the load is too intensive, the * system might not transition at all. */ if (g_cstate_info[i].events > 0) avg = (((double)g_cstate_info[i].total_time/ MICROSEC)/g_cstate_info[i].events); else avg = 0; res = ((double)g_cstate_info[i].total_time/ g_total_c_time) * 100; (void) sprintf(c, "C%d\t\t\t%.1fms\t(%.1f%%)\n", i, (float)avg, (float)res); print(sw[SW_IDLE], i + 1, 0, "%s", c); } } if (!PT_ON_DUMP) (void) wnoutrefresh(sw[SW_IDLE]); print(sw[SW_FREQ], 0, 0, "%s\n", g_msg_freq_state); if (g_features & FEATURE_PSTATE) { for (i = 0; i < g_npstates; i++) { total_pstates += (double)(g_pstate_info[i].total_time/ g_ncpus_observed/MICROSEC); } /* * display ACPI_PSTATE from P(n) to P(1) */ for (i = 0; i < g_npstates - 1; i++) { (void) sprintf(c, "%4lu Mhz\t%.1f%%", (long)g_pstate_info[i].speed, 100 * (g_pstate_info[i].total_time/ g_ncpus_observed/MICROSEC/total_pstates)); print(sw[SW_FREQ], i+1, 0, "%s\n", c); } /* * Display ACPI_PSTATE P0 according to if turbo * mode is supported */ if (g_turbo_supported) { int p_diff = 1; p0_speed = g_pstate_info[g_npstates - 1].speed; p1_speed = g_pstate_info[g_npstates - 2].speed; /* * AMD systems don't have a visible extra Pstate * indicating turbo mode as Intel does. Use the * actual P0 frequency in that case. */ if (p0_speed != p1_speed + 1) { p1_speed = p0_speed; p_diff = 0; } /* * If g_turbo_ratio <= 1.0, it will be ignored. * we display P(0) as P(1) + p_diff. */ if (g_turbo_ratio <= 1.0) { p0_speed = p1_speed + p_diff; } else { /* * If g_turbo_ratio > 1.0, that means * turbo mode works. So, P(0) = ratio * * P(1); */ p0_speed = (uint64_t)(p1_speed * g_turbo_ratio); if (p0_speed < (p1_speed + p_diff)) p0_speed = p1_speed + p_diff; } /* * Reset the ratio for the next round */ g_turbo_ratio = 0.0; /* * Setup the string for the display */ (void) sprintf(c, "%4lu Mhz(turbo)\t%.1f%%", (long)p0_speed, 100 * (g_pstate_info[i].total_time/ g_ncpus_observed/MICROSEC/total_pstates)); } else { (void) sprintf(c, "%4lu Mhz\t%.1f%%", (long)g_pstate_info[i].speed, 100 * (g_pstate_info[i].total_time/ g_ncpus_observed/MICROSEC/total_pstates)); } print(sw[SW_FREQ], i+1, 0, "%s\n", c); } else { if (g_npstates == 1) { (void) sprintf(c, "%4lu Mhz\t%.1f%%", (long)g_pstate_info[0].speed, 100.0); print(sw[SW_FREQ], 1, 0, "%s\n", c); } } if (!PT_ON_DUMP) (void) wnoutrefresh(sw[SW_FREQ]); } void pt_display_acpi_power(uint32_t flag, double rate, double rem_cap, double cap, uint32_t state) { char buffer[1024]; (void) sprintf(buffer, "no ACPI power usage estimate available"); if (!PT_ON_DUMP) (void) werase(sw[SW_POWER]); if (flag) { char *c; (void) sprintf(buffer, "Power usage (ACPI estimate): %.3fW", rate); (void) strcat(buffer, " "); c = &buffer[strlen(buffer)]; switch (state) { case 0: (void) sprintf(c, "(running on AC power, fully " "charged)"); break; case 1: (void) sprintf(c, "(discharging: %3.1f hours)", (uint32_t)rem_cap/rate); break; case 2: (void) sprintf(c, "(charging: %3.1f hours)", (uint32_t)(cap - rem_cap)/rate); break; case 4: (void) sprintf(c, "(##critically low battery power##)"); break; } } print(sw[SW_POWER], 0, 0, "%s\n", buffer); if (!PT_ON_DUMP) (void) wnoutrefresh(sw[SW_POWER]); } void pt_display_wakeups(double interval) { char c[100]; int i, event_sum = 0; event_info_t *event = g_event_info; if (!PT_ON_DUMP) { (void) werase(sw[SW_WAKEUPS]); (void) wbkgd(sw[SW_WAKEUPS], COLOR_PAIR(PT_COLOR_RED)); (void) wattron(sw[SW_WAKEUPS], A_BOLD); } /* * calculate the actual total event number */ for (i = 0; i < g_top_events; i++, event++) event_sum += event->total_count; /* * g_total_events is the sum of the number of Cx->C0 transition, * So when the system is very busy, the idle thread will have no * chance or very seldom to be scheduled, this could cause >100% * event report. Re-assign g_total_events to the actual event * number is a way to avoid this issue. */ if (event_sum > g_total_events) g_total_events = event_sum; (void) sprintf(c, "Wakeups-from-idle per second: %4.1f\tinterval: " "%.1fs", (double)(g_total_events/interval), interval); print(sw[SW_WAKEUPS], 0, 0, "%s\n", c); if (!PT_ON_DUMP) (void) wnoutrefresh(sw[SW_WAKEUPS]); } void pt_display_events(double interval) { char c[100]; int i; double events; event_info_t *event = g_event_info; if (!PT_ON_DUMP) { (void) werase(sw[SW_EVENTS]); (void) wbkgd(sw[SW_EVENTS], COLOR_PAIR(PT_COLOR_DEFAULT)); (void) wattron(sw[SW_EVENTS], COLOR_PAIR(PT_COLOR_DEFAULT)); } /* * Sort the event report list */ if (g_top_events > EVENT_NUM_MAX) g_top_events = EVENT_NUM_MAX; qsort((void *)g_event_info, g_top_events, sizeof (event_info_t), pt_event_compare); if (PT_ON_CPU) (void) sprintf(c, "Top causes for wakeups on CPU %d:\n", g_observed_cpu); else (void) sprintf(c, "Top causes for wakeups:\n"); print(sw[SW_EVENTS], 0, 0, "%s", c); for (i = 0; i < g_top_events; i++, event++) { if (g_total_events > 0 && event->total_count > 0) events = (double)event->total_count/ (double)g_total_events; else continue; (void) sprintf(c, "%4.1f%% (%5.1f)", 100 * events, (double)event->total_count/interval); print(sw[SW_EVENTS], i+1, 0, "%s", c); print(sw[SW_EVENTS], i+1, 16, "%20s :", event->offender_name); print(sw[SW_EVENTS], i+1, 40, "%-64s\n", event->offense_name); } if (!PT_ON_DUMP) (void) wnoutrefresh(sw[SW_EVENTS]); } void pt_display_suggestions(char *sug) { (void) werase(sw[SW_SUGG]); if (sug != NULL) print(sw[SW_SUGG], 0, 0, "%s", sug); (void) wnoutrefresh(sw[SW_SUGG]); }