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
85enum 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
97typedef struct sb_slot {
98	char *msg;
99	struct sb_slot *prev;
100	struct sb_slot *next;
101} sb_slot_t;
102
103static WINDOW *sw[SW_COUNT];
104static int win_cols, win_rows;
105static 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 */
111static void
112pt_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
128static void
129pt_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
141void
142pt_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 */
174void
175pt_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 */
233void
234pt_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
279void
280pt_display_update(void)
281{
282	(void) doupdate();
283}
284
285void
286pt_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
303void
304pt_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 */
327void
328pt_display_mod_status_bar(char *msg)
329{
330	sb_slot_t *new, *n;
331	boolean_t found = B_FALSE, first = B_FALSE;
332
333	if (msg == NULL) {
334		pt_error("can't add 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		for (n = status_bar; n != NULL; n = n->next) {
343
344			if (strcmp(msg, n->msg) == 0) {
345				if (n != status_bar)
346					n->prev->next = n->next;
347				else
348					first = B_TRUE;
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);
360				found = B_TRUE;
361			}
362		}
363
364		/*
365		 * Found and removed at least one occurrance of msg, refresh
366		 * the bar and return.
367		 */
368		if (found) {
369			return;
370		} else {
371			/*
372			 * Inserting a new msg, walk to the end of the bar.
373			 */
374			for (n = status_bar; n->next != NULL; n = n->next)
375				;
376		}
377	}
378
379	if ((new = calloc(1, sizeof (sb_slot_t))) == NULL) {
380		pt_error("failed to allocate a new status bar slot\n");
381	} else {
382		new->msg = strdup(msg);
383
384		/*
385		 * Check if it's the first entry.
386		 */
387		if (status_bar == NULL) {
388			status_bar = new;
389			new->prev = NULL;
390		} else {
391			new->prev = n;
392			n->next = new;
393		}
394		new->next = NULL;
395	}
396}
397
398void
399pt_display_states(void)
400{
401	char		c[100];
402	int		i;
403	double		total_pstates = 0.0, avg, res;
404	uint64_t	p0_speed, p1_speed;
405
406	print(sw[SW_IDLE], 0, 0, "%s\tAvg\tResidency\n", g_msg_idle_state);
407
408	if (g_features & FEATURE_CSTATE) {
409		res =  (((double)g_cstate_info[0].total_time / g_total_c_time))
410		    * 100;
411		(void) sprintf(c, "C0 (cpu running)\t\t(%.1f%%)\n", (float)res);
412		print(sw[SW_IDLE], 1, 0, "%s", c);
413
414		for (i = 1; i <= g_max_cstate; i++) {
415			/*
416			 * In situations where the load is too intensive, the
417			 * system might not transition at all.
418			 */
419			if (g_cstate_info[i].events > 0)
420				avg = (((double)g_cstate_info[i].total_time/
421				    MICROSEC)/g_cstate_info[i].events);
422			else
423				avg = 0;
424
425			res = ((double)g_cstate_info[i].total_time/
426			    g_total_c_time) * 100;
427
428			(void) sprintf(c, "C%d\t\t\t%.1fms\t(%.1f%%)\n",
429			    i, (float)avg, (float)res);
430			print(sw[SW_IDLE], i + 1, 0, "%s", c);
431		}
432	}
433
434	if (!PT_ON_DUMP)
435		(void) wnoutrefresh(sw[SW_IDLE]);
436
437	print(sw[SW_FREQ], 0, 0, "%s\n", g_msg_freq_state);
438
439	if (g_features & FEATURE_PSTATE) {
440		for (i = 0; i < g_npstates; i++) {
441			total_pstates +=
442			    (double)(g_pstate_info[i].total_time/
443			    g_ncpus_observed/MICROSEC);
444		}
445
446		/*
447		 * display ACPI_PSTATE from P(n) to P(1)
448		 */
449		for (i = 0;  i < g_npstates - 1; i++) {
450			(void) sprintf(c, "%4lu Mhz\t%.1f%%",
451			    (long)g_pstate_info[i].speed,
452			    100 * (g_pstate_info[i].total_time/
453			    g_ncpus_observed/MICROSEC/total_pstates));
454			print(sw[SW_FREQ], i+1, 0, "%s\n", c);
455		}
456
457		/*
458		 * Display ACPI_PSTATE P0 according to if turbo
459		 * mode is supported
460		 */
461		if (g_turbo_supported) {
462			int p_diff = 1;
463			p0_speed = g_pstate_info[g_npstates - 1].speed;
464			p1_speed = g_pstate_info[g_npstates - 2].speed;
465
466			/*
467			 * AMD systems don't have a visible extra Pstate
468			 * indicating turbo mode as Intel does. Use the
469			 * actual P0 frequency in that case.
470			 */
471			if (p0_speed != p1_speed + 1) {
472				p1_speed = p0_speed;
473				p_diff = 0;
474			}
475
476			/*
477			 * If g_turbo_ratio <= 1.0, it will be ignored.
478			 * we display P(0) as P(1) + p_diff.
479			 */
480			if (g_turbo_ratio <= 1.0) {
481				p0_speed = p1_speed + p_diff;
482			} else {
483				/*
484				 * If g_turbo_ratio > 1.0, that means
485				 * turbo mode works. So, P(0) = ratio *
486				 *  P(1);
487				 */
488				p0_speed = (uint64_t)(p1_speed *
489				    g_turbo_ratio);
490				if (p0_speed < (p1_speed + p_diff))
491					p0_speed = p1_speed + p_diff;
492			}
493			/*
494			 * Reset the ratio for the next round
495			 */
496			g_turbo_ratio = 0.0;
497
498			/*
499			 * Setup the string for the display
500			 */
501			(void) sprintf(c, "%4lu Mhz(turbo)\t%.1f%%",
502			    (long)p0_speed,
503			    100 * (g_pstate_info[i].total_time/
504			    g_ncpus_observed/MICROSEC/total_pstates));
505		} else {
506			(void) sprintf(c, "%4lu Mhz\t%.1f%%",
507			    (long)g_pstate_info[i].speed,
508			    100 * (g_pstate_info[i].total_time/
509			    g_ncpus_observed/MICROSEC/total_pstates));
510		}
511		print(sw[SW_FREQ], i+1, 0, "%s\n", c);
512	} else {
513		if (g_npstates == 1) {
514			(void) sprintf(c, "%4lu Mhz\t%.1f%%",
515			    (long)g_pstate_info[0].speed, 100.0);
516			print(sw[SW_FREQ], 1, 0, "%s\n", c);
517		}
518	}
519
520	if (!PT_ON_DUMP)
521		(void) wnoutrefresh(sw[SW_FREQ]);
522}
523
524void
525pt_display_acpi_power(uint32_t flag, double rate, double rem_cap, double cap,
526    uint32_t state)
527{
528	char	buffer[1024];
529
530	(void) sprintf(buffer, "no ACPI power usage estimate available");
531
532	if (!PT_ON_DUMP)
533		(void) werase(sw[SW_POWER]);
534
535	if (flag) {
536		char *c;
537		(void) sprintf(buffer, "Power usage (ACPI estimate): %.3fW",
538		    rate);
539		(void) strcat(buffer, " ");
540		c = &buffer[strlen(buffer)];
541		switch (state) {
542		case 0:
543			(void) sprintf(c, "(running on AC power, fully "
544			    "charged)");
545			break;
546		case 1:
547			(void) sprintf(c, "(discharging: %3.1f hours)",
548			    (uint32_t)rem_cap/rate);
549			break;
550		case 2:
551			(void) sprintf(c, "(charging: %3.1f hours)",
552			    (uint32_t)(cap - rem_cap)/rate);
553			break;
554		case 4:
555			(void) sprintf(c, "(##critically low battery power##)");
556			break;
557		}
558
559	}
560
561	print(sw[SW_POWER], 0, 0, "%s\n", buffer);
562	if (!PT_ON_DUMP)
563		(void) wnoutrefresh(sw[SW_POWER]);
564}
565
566void
567pt_display_wakeups(double interval)
568{
569	char		c[100];
570	int		i, event_sum = 0;
571	event_info_t	*event = g_event_info;
572
573	if (!PT_ON_DUMP) {
574		(void) werase(sw[SW_WAKEUPS]);
575		(void) wbkgd(sw[SW_WAKEUPS], COLOR_PAIR(PT_COLOR_RED));
576		(void) wattron(sw[SW_WAKEUPS], A_BOLD);
577	}
578
579	/*
580	 * calculate the actual total event number
581	 */
582	for (i = 0; i < g_top_events; i++, event++)
583		event_sum += event->total_count;
584
585	/*
586	 * g_total_events is the sum of the number of Cx->C0 transition,
587	 * So when the system is very busy, the idle thread will have no
588	 * chance or very seldom to be scheduled, this could cause >100%
589	 * event report. Re-assign g_total_events to the actual event
590	 * number is a way to avoid this issue.
591	 */
592	if (event_sum > g_total_events)
593		g_total_events = event_sum;
594
595	(void) sprintf(c, "Wakeups-from-idle per second: %4.1f\tinterval: "
596	    "%.1fs", (double)(g_total_events/interval), interval);
597	print(sw[SW_WAKEUPS], 0, 0, "%s\n", c);
598
599	if (!PT_ON_DUMP)
600		(void) wnoutrefresh(sw[SW_WAKEUPS]);
601}
602
603void
604pt_display_events(double interval)
605{
606	char		c[100];
607	int		i;
608	double		events;
609	event_info_t	*event = g_event_info;
610
611	if (!PT_ON_DUMP) {
612		(void) werase(sw[SW_EVENTS]);
613		(void) wbkgd(sw[SW_EVENTS], COLOR_PAIR(PT_COLOR_DEFAULT));
614		(void) wattron(sw[SW_EVENTS], COLOR_PAIR(PT_COLOR_DEFAULT));
615	}
616
617	/*
618	 * Sort the event report list
619	 */
620	if (g_top_events > EVENT_NUM_MAX)
621		g_top_events = EVENT_NUM_MAX;
622
623	qsort((void *)g_event_info, g_top_events, sizeof (event_info_t),
624	    pt_event_compare);
625
626	if (PT_ON_CPU)
627		(void) sprintf(c, "Top causes for wakeups on CPU %d:\n",
628		    g_observed_cpu);
629	else
630		(void) sprintf(c, "Top causes for wakeups:\n");
631
632	print(sw[SW_EVENTS], 0, 0, "%s", c);
633
634	for (i = 0; i < g_top_events; i++, event++) {
635
636		if (g_total_events > 0 && event->total_count > 0)
637			events = (double)event->total_count/
638			    (double)g_total_events;
639		else
640			continue;
641
642		(void) sprintf(c, "%4.1f%% (%5.1f)", 100 * events,
643		    (double)event->total_count/interval);
644		print(sw[SW_EVENTS], i+1, 0, "%s", c);
645		print(sw[SW_EVENTS], i+1, 16, "%20s :",
646		    event->offender_name);
647		print(sw[SW_EVENTS], i+1, 40, "%-64s\n",
648		    event->offense_name);
649	}
650
651	if (!PT_ON_DUMP)
652		(void) wnoutrefresh(sw[SW_EVENTS]);
653}
654
655void
656pt_display_suggestions(char *sug)
657{
658	(void) werase(sw[SW_SUGG]);
659
660	if (sug != NULL)
661		print(sw[SW_SUGG], 0, 0, "%s", sug);
662
663	(void) wnoutrefresh(sw[SW_SUGG]);
664}
665