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