xref: /illumos-gate/usr/src/cmd/powertop/common/display.c (revision e8d3efa1c56e5f2b5368600a2baeb7b1d54a07f8)
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