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
81enum 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
93typedef struct sb_slot {
94	char *msg;
95	struct sb_slot *prev;
96	struct sb_slot *next;
97} sb_slot_t;
98
99static WINDOW *sw[SW_COUNT];
100static int win_cols, win_rows;
101static 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 */
107static void
108pt_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
124static void
125pt_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
137void
138pt_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 */
170void
171pt_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 */
228void
229pt_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
274void
275pt_display_update(void)
276{
277	(void) doupdate();
278}
279
280void
281pt_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
298void
299pt_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 */
322void
323pt_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
393void
394pt_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
507void
508pt_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
549void
550pt_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
586void
587pt_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
638void
639pt_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