1 /*
2  * out_curses.c          Curses Output
3  *
4  * Copyright (c) 2001-2013 Thomas Graf <tgraf@suug.ch>
5  * Copyright (c) 2013 Red Hat, Inc.
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a
8  * copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation
10  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11  * and/or sell copies of the Software, and to permit persons to whom the
12  * Software is furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included
15  * in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23  * DEALINGS IN THE SOFTWARE.
24  */
25 
26 #include <bmon/bmon.h>
27 #include <bmon/conf.h>
28 #include <bmon/attr.h>
29 #include <bmon/element.h>
30 #include <bmon/element_cfg.h>
31 #include <bmon/input.h>
32 #include <bmon/history.h>
33 #include <bmon/graph.h>
34 #include <bmon/output.h>
35 #include <bmon/utils.h>
36 
37 enum {
38 	GRAPH_DISPLAY_SIDE_BY_SIDE = 1,
39 	GRAPH_DISPLAY_STANDARD = 2,
40 };
41 
42 enum {
43 	KEY_TOGGLE_LIST		= 'l',
44 	KEY_TOGGLE_GRAPH	= 'g',
45 	KEY_TOGGLE_DETAILS	= 'd',
46 	KEY_TOGGLE_INFO		= 'i',
47 	KEY_COLLECT_HISTORY	= 'h',
48 };
49 
50 #define DETAILS_COLS		40
51 
52 #define LIST_COL_1		31
53 #define LIST_COL_2		55
54 
55 /* Set to element_current() before drawing */
56 static struct element *current_element;
57 
58 static struct attr *current_attr;
59 
60 /* Length of list to draw, updated in draw_content() */
61 static int list_length;
62 
63 static int list_req;
64 
65 /* Number of graphs to draw (may be < c_ngraph) */
66 static int ngraph;
67 
68 /*
69  * Offset in number of lines within the the element list of the currently
70  * selected element. Updated while summing up required lines.
71  */
72 static unsigned int selection_offset;
73 
74 /*
75  * Offset in number of lines of the first element to be drawn. Updated
76  * in draw_content()
77  */
78 static int offset;
79 
80 /*
81  * Offset to the first graph to draw in number of attributes with graphs.
82  */
83 static unsigned int graph_offset;
84 
85 static int graph_display = GRAPH_DISPLAY_STANDARD;
86 
87 /*
88  * Number of detail columns
89  */
90 static int detail_cols;
91 static int info_cols;
92 
93 static int initialized;
94 static int print_help;
95 static int quit_mode;
96 static int help_page;
97 
98 /* Current row */
99 static int row;
100 
101 /* Number of rows */
102 static int rows;
103 
104 /* Number of columns */
105 static int cols;
106 
107 static int c_show_graph = 1;
108 static int c_ngraph = 1;
109 static int c_use_colors = 1;
110 static int c_show_details = 0;
111 static int c_show_list = 1;
112 static int c_show_info = 0;
113 static int c_list_min = 6;
114 
115 static struct graph_cfg c_graph_cfg = {
116 	.gc_width		= 60,
117 	.gc_height		= 6,
118 	.gc_foreground		= '|',
119 	.gc_background		= '.',
120 	.gc_noise		= ':',
121 	.gc_unknown		= '?',
122 };
123 
124 #define NEXT_ROW()			\
125 	do {				\
126 		row++;			\
127 		if (row >= rows - 1)	\
128 			return;		\
129 		move(row, 0);		\
130 	} while(0)
131 
apply_layout(int layout)132 static void apply_layout(int layout)
133 {
134 	if (c_use_colors)
135 		attrset(COLOR_PAIR(layout) | cfg_layout[layout].l_attr);
136 	else
137 		attrset(cfg_layout[layout].l_attr);
138 }
139 
float2str(double value,int width,int prec,char * buf,size_t len)140 static char *float2str(double value, int width, int prec, char *buf, size_t len)
141 {
142 	snprintf(buf, len, "%'*.*f", width, value == 0.0f ? 0 : prec, value);
143 
144 	return buf;
145 }
146 
put_line(const char * fmt,...)147 static void put_line(const char *fmt, ...)
148 {
149 	va_list args;
150 	char buf[2048];
151 	int x, y __unused__;
152 
153 	memset(buf, 0, sizeof(buf));
154 	getyx(stdscr, y, x);
155 
156 	va_start(args, fmt);
157 	vsnprintf(buf, sizeof(buf), fmt, args);
158 	va_end(args);
159 
160 	if (strlen(buf) > cols-x)
161 		buf[cols - x] = '\0';
162 	else
163 		memset(&buf[strlen(buf)], ' ', cols - strlen(buf)-x);
164 
165 	addstr(buf);
166 }
167 
center_text(const char * fmt,...)168 static void center_text(const char *fmt, ...)
169 {
170 	va_list args;
171 	char *str;
172 	unsigned int col;
173 
174 	va_start(args, fmt);
175 	if (vasprintf(&str, fmt, args) < 0) {
176 		fprintf(stderr, "vasprintf: Out of memory\n");
177 		exit(ENOMEM);
178 	}
179 	va_end(args);
180 
181 	col = (cols / 2) - (strlen(str) / 2);
182 	if (col > cols - 1)
183 		col = cols - 1;
184 
185 	move(row, col);
186 	addstr(str);
187 	move(row, 0);
188 
189 	free(str);
190 }
191 
curses_init(void)192 static int curses_init(void)
193 {
194 	if (!initscr()) {
195 		fprintf(stderr, "Unable to initialize curses screen\n");
196 		return -EOPNOTSUPP;
197 	}
198 
199 	initialized = 1;
200 
201 	if (!has_colors())
202 		c_use_colors = 0;
203 
204 	if (c_use_colors) {
205 		int i;
206 
207 		start_color();
208 
209 #if defined HAVE_USE_DEFAULT_COLORS
210 		use_default_colors();
211 #endif
212 		for (i = 1; i < LAYOUT_MAX+1; i++)
213 			init_pair(i, cfg_layout[i].l_fg, cfg_layout[i].l_bg);
214 	}
215 
216 	keypad(stdscr, TRUE);
217 	nonl();
218 	cbreak();
219 	noecho();
220 	nodelay(stdscr, TRUE);	  /* getch etc. must be non-blocking */
221 	clear();
222 	curs_set(0);
223 
224 	return 0;
225 }
226 
curses_shutdown(void)227 static void curses_shutdown(void)
228 {
229 	if (initialized)
230 		endwin();
231 }
232 
233 struct detail_arg
234 {
235 	int nattr;
236 };
237 
draw_attr_detail(struct element * e,struct attr * a,void * arg)238 static void draw_attr_detail(struct element *e, struct attr *a, void *arg)
239 {
240 	char *rx_u, *tx_u, buf1[32], buf2[32];
241 	int rxprec, txprec, ncol;
242 	struct detail_arg *da = arg;
243 
244 	double rx = unit_value2str(rate_get_total(&a->a_rx_rate),
245 				   a->a_def->ad_unit,
246 				   &rx_u, &rxprec);
247 	double tx = unit_value2str(rate_get_total(&a->a_tx_rate),
248 				   a->a_def->ad_unit,
249 				   &tx_u, &txprec);
250 
251 	if (da->nattr >= detail_cols) {
252 		NEXT_ROW();
253 		da->nattr = 0;
254 	}
255 
256 	ncol = (da->nattr * DETAILS_COLS) - 1;
257 	move(row, ncol);
258 	if (ncol > 0)
259 		addch(ACS_VLINE);
260 
261 	put_line(" %-14.14s %8s%-3s %8s%-3s",
262 		 a->a_def->ad_description,
263 		 (a->a_flags & ATTR_RX_ENABLED) ?
264 		 float2str(rx, 8, rxprec, buf1, sizeof(buf1)) : "-", rx_u,
265 		 (a->a_flags & ATTR_TX_ENABLED) ?
266 		 float2str(tx, 8, txprec, buf2, sizeof(buf2)) : "-", tx_u);
267 
268 	da->nattr++;
269 }
270 
draw_details(void)271 static void draw_details(void)
272 {
273 	int i;
274 	struct detail_arg arg = {
275 		.nattr = 0,
276 	};
277 
278 	if (!current_element->e_nattrs)
279 		return;
280 
281 	for (i = 1; i < detail_cols; i++)
282 		mvaddch(row, (i * DETAILS_COLS) - 1, ACS_TTEE);
283 
284 	NEXT_ROW();
285 	put_line("");
286 	for (i = 0; i < detail_cols; i++) {
287 		if (i > 0)
288 			mvaddch(row, (i * DETAILS_COLS) - 1, ACS_VLINE);
289 		move(row, (i * DETAILS_COLS) + 22);
290 		put_line("RX          TX");
291 	}
292 
293 	NEXT_ROW();
294 	element_foreach_attr(current_element, draw_attr_detail, &arg);
295 
296 	/*
297 	 * If the last row was incomplete, not all vlines have been drawn.
298 	 * draw them here
299 	 */
300 	for (i = 1; i < detail_cols; i++)
301 		mvaddch(row, (i * DETAILS_COLS - 1), ACS_VLINE);
302 }
303 
print_message(const char * text)304 static void print_message(const char *text)
305 {
306 	int i, y = (rows/2) - 2;
307 	int len = strlen(text);
308 	int x = (cols/2) - (len / 2);
309 
310 	attrset(A_STANDOUT);
311 	mvaddch(y - 2, x - 1, ACS_ULCORNER);
312 	mvaddch(y + 2, x - 1, ACS_LLCORNER);
313 	mvaddch(y - 2, x + len, ACS_URCORNER);
314 	mvaddch(y + 2, x + len, ACS_LRCORNER);
315 
316 	for (i = 0; i < 3; i++) {
317 		mvaddch(y - 1 + i, x + len, ACS_VLINE);
318 		mvaddch(y - 1 + i, x - 1 ,ACS_VLINE);
319 	}
320 
321 	for (i = 0; i < len; i++) {
322 		mvaddch(y - 2, x + i, ACS_HLINE);
323 		mvaddch(y - 1, x + i, ' ');
324 		mvaddch(y + 1, x + i, ' ');
325 		mvaddch(y + 2, x + i, ACS_HLINE);
326 	}
327 
328 	mvaddstr(y, x, text);
329 	attroff(A_STANDOUT);
330 
331 	row = y + 2;
332 }
333 
draw_help(void)334 static void draw_help(void)
335 {
336 #define HW 46
337 #define HH 19
338 	int i, y = (rows/2) - (HH/2);
339 	int x = (cols/2) - (HW/2);
340 	char pad[HW+1];
341 
342 	memset(pad, ' ', sizeof(pad));
343 	pad[sizeof(pad) - 1] = '\0';
344 
345 	attron(A_STANDOUT);
346 
347 	for (i = 0; i < HH; i++)
348 		mvaddnstr(y + i, x, pad, -1);
349 
350 	mvaddch(y  - 1, x - 1, ACS_ULCORNER);
351 	mvaddch(y + HH, x - 1, ACS_LLCORNER);
352 
353 	mvaddch(y  - 1, x + HW, ACS_URCORNER);
354 	mvaddch(y + HH, x + HW, ACS_LRCORNER);
355 
356 	for (i = 0; i < HH; i++) {
357 		mvaddch(y + i, x -  1, ACS_VLINE);
358 		mvaddch(y + i, x + HW, ACS_VLINE);
359 	}
360 
361 	for (i = 0; i < HW; i++) {
362 		mvaddch(y  - 1, x + i, ACS_HLINE);
363 		mvaddch(y + HH, x + i, ACS_HLINE);
364 	}
365 
366 	attron(A_BOLD);
367 	mvaddnstr(y- 1, x+15, "QUICK REFERENCE", -1);
368 	attron(A_UNDERLINE);
369 	mvaddnstr(y+ 0, x+1, "Navigation", -1);
370 	attroff(A_BOLD | A_UNDERLINE);
371 
372 	mvaddnstr(y+ 1, x+3, "Up, Down      Previous/Next element", -1);
373 	mvaddnstr(y+ 2, x+3, "PgUp, PgDown  Scroll up/down entire page", -1);
374 	mvaddnstr(y+ 3, x+3, "Left, Right   Previous/Next attribute", -1);
375 	mvaddnstr(y+ 4, x+3, "[, ]          Previous/Next group", -1);
376 	mvaddnstr(y+ 5, x+3, "?             Toggle quick reference", -1);
377 	mvaddnstr(y+ 6, x+3, "q             Quit bmon", -1);
378 
379 	attron(A_BOLD | A_UNDERLINE);
380 	mvaddnstr(y+ 8, x+1, "Display Settings", -1);
381 	attroff(A_BOLD | A_UNDERLINE);
382 
383 	mvaddnstr(y+ 9, x+3, "d             Toggle detailed statistics", -1);
384 	mvaddnstr(y+10, x+3, "l             Toggle element list", -1);
385 	mvaddnstr(y+11, x+3, "i             Toggle additional info", -1);
386 
387 	attron(A_BOLD | A_UNDERLINE);
388 	mvaddnstr(y+13, x+1, "Graph Settings", -1);
389 	attroff(A_BOLD | A_UNDERLINE);
390 
391 	mvaddnstr(y+14, x+3, "g             Toggle graphical statistics", -1);
392 	mvaddnstr(y+15, x+3, "H             Start recording history data", -1);
393 	mvaddnstr(y+16, x+3, "TAB           Switch time unit of graph", -1);
394 	mvaddnstr(y+17, x+3, "<, >          Change number of graphs", -1);
395 	mvaddnstr(y+18, x+3, "r             Reset counter of element", -1);
396 
397 	attroff(A_STANDOUT);
398 
399 	row = y + HH;
400 }
401 
lines_required_for_header(void)402 static int lines_required_for_header(void)
403 {
404 	return 1;
405 }
406 
draw_header(void)407 static void draw_header(void)
408 {
409 	apply_layout(LAYOUT_STATUSBAR);
410 
411 	if (current_element)
412 		put_line(" %s %c%s%c",
413 			current_element->e_name,
414 			current_element->e_description ? '(' : ' ',
415 			current_element->e_description ? : "",
416 			current_element->e_description ? ')' : ' ');
417 	else
418 		put_line("");
419 
420 	move(row, COLS - strlen(PACKAGE_STRING) - 1);
421 	put_line("%s", PACKAGE_STRING);
422 	move(row, 0);
423 	apply_layout(LAYOUT_LIST);
424 }
425 
lines_required_for_statusbar(void)426 static int lines_required_for_statusbar(void)
427 {
428 	return 1;
429 }
430 
draw_statusbar(void)431 static void draw_statusbar(void)
432 {
433 	static const char *help_text = "Press ? for help";
434 	char s[27];
435 	time_t t = time(NULL);
436 
437 	apply_layout(LAYOUT_STATUSBAR);
438 
439 	asctime_r(localtime(&t), s);
440 	s[strlen(s) - 1] = '\0';
441 
442 	row = rows-1;
443 	move(row, 0);
444 	put_line(" %s", s);
445 
446 	move(row, COLS - strlen(help_text) - 1);
447 	put_line("%s", help_text);
448 
449 	move(row, 0);
450 }
451 
count_attr_graph(struct element * g,struct attr * a,void * arg)452 static void count_attr_graph(struct element *g, struct attr *a, void *arg)
453 {
454 	if (a == current_attr)
455 		graph_offset = ngraph;
456 
457 	ngraph++;
458 }
459 
lines_required_for_graph(void)460 static int lines_required_for_graph(void)
461 {
462 	int lines = 0;
463 
464 	ngraph = 0;
465 
466 	if (c_show_graph && current_element) {
467 		graph_display = GRAPH_DISPLAY_STANDARD;
468 
469 		element_foreach_attr(current_element, count_attr_graph, NULL);
470 
471 		if (ngraph > c_ngraph)
472 			ngraph = c_ngraph;
473 
474 		/* check if we have room to draw graphs on the same level */
475 		if (cols > (2 * (c_graph_cfg.gc_width + 10)))
476 			graph_display = GRAPH_DISPLAY_SIDE_BY_SIDE;
477 
478 		/* +2 = header + time axis */
479 		lines = ngraph * (graph_display * (c_graph_cfg.gc_height + 2));
480 	}
481 
482 	return lines + 1;
483 }
484 
lines_required_for_details(void)485 static int lines_required_for_details(void)
486 {
487 	int lines = 1;
488 
489 	if (c_show_details && current_element) {
490 		lines++;	/* header */
491 
492 		detail_cols = cols / DETAILS_COLS;
493 
494 		if (!detail_cols)
495 			detail_cols = 1;
496 
497 		lines += (current_element->e_nattrs / detail_cols);
498 		if (current_element->e_nattrs % detail_cols)
499 			lines++;
500 	}
501 
502 	return lines;
503 }
504 
count_element_lines(struct element_group * g,struct element * e,void * arg)505 static void count_element_lines(struct element_group *g, struct element *e,
506 				void *arg)
507 {
508 	int *lines = arg;
509 
510 	if (e == current_element)
511 		selection_offset = *lines;
512 
513 	(*lines)++;
514 }
515 
count_group_lines(struct element_group * g,void * arg)516 static void count_group_lines(struct element_group *g, void *arg)
517 {
518 	int *lines = arg;
519 
520 	/* group title */
521 	(*lines)++;
522 
523 	group_foreach_element(g, &count_element_lines, arg);
524 }
525 
lines_required_for_list(void)526 static int lines_required_for_list(void)
527 {
528 	int lines = 0;
529 
530 	if (c_show_list)
531 		group_foreach(&count_group_lines, &lines);
532 	else
533 		lines = 1;
534 
535 	return lines;
536 }
537 
line_visible(int line)538 static inline int line_visible(int line)
539 {
540 	return line >= offset && line < (offset + list_length);
541 }
542 
draw_attr(double rate1,int prec1,char * unit1,double rate2,int prec2,char * unit2,float usage,int ncol)543 static void draw_attr(double rate1, int prec1, char *unit1,
544 		      double rate2, int prec2, char *unit2,
545 		      float usage, int ncol)
546 {
547 	char buf[32];
548 
549 	move(row, ncol);
550 	addch(ACS_VLINE);
551 	printw("%7s%-3s",
552 		float2str(rate1, 7, prec1, buf, sizeof(buf)), unit1);
553 
554 	printw("%7s%-3s",
555 		float2str(rate2, 7, prec2, buf, sizeof(buf)), unit2);
556 
557 	if (usage != FLT_MAX)
558 		printw("%2.0f%%", usage);
559 	else
560 		printw("%3s", "");
561 }
562 
draw_element(struct element_group * g,struct element * e,void * arg)563 static void draw_element(struct element_group *g, struct element *e,
564 			 void *arg)
565 {
566 	int *line = arg;
567 
568 	apply_layout(LAYOUT_LIST);
569 
570 	if (line_visible(*line)) {
571 		char *rxu1 = "", *txu1 = "", *rxu2 = "", *txu2 = "";
572 		double rx1 = 0.0f, tx1 = 0.0f, rx2 = 0.0f, tx2 = 0.0f;
573 		char pad[IFNAMSIZ + 32];
574 		int rx1prec = 0, tx1prec = 0, rx2prec = 0, tx2prec = 0;
575 		struct attr *a;
576 
577 		NEXT_ROW();
578 
579 		if (e->e_key_attr[GT_MAJOR] &&
580 		    (a = attr_lookup(e, e->e_key_attr[GT_MAJOR]->ad_id)))
581 			attr_rate2float(a, &rx1, &rxu1, &rx1prec,
582 					&tx1, &txu1, &tx1prec);
583 
584 		if (e->e_key_attr[GT_MINOR] &&
585 		    (a = attr_lookup(e, e->e_key_attr[GT_MINOR]->ad_id)))
586 			attr_rate2float(a, &rx2, &rxu2, &rx2prec,
587 					&tx2, &txu2, &tx2prec);
588 
589 		memset(pad, 0, sizeof(pad));
590 		memset(pad, ' ', e->e_level < 6 ? e->e_level * 2 : 12);
591 
592 		strncat(pad, e->e_name, sizeof(pad) - strlen(pad) - 1);
593 
594 		if (e->e_description) {
595 			strncat(pad, " (", sizeof(pad) - strlen(pad) - 1);
596 			strncat(pad, e->e_description, sizeof(pad) - strlen(pad) - 1);
597 			strncat(pad, ")", sizeof(pad) - strlen(pad) - 1);
598 		}
599 
600 		if (*line == offset) {
601 			attron(A_BOLD);
602 			addch(ACS_UARROW);
603 			attroff(A_BOLD);
604 			addch(' ');
605 		} else if (e == current_element) {
606 			apply_layout(LAYOUT_SELECTED);
607 			addch(' ');
608 			attron(A_BOLD);
609 			addch(ACS_RARROW);
610 			attroff(A_BOLD);
611 			apply_layout(LAYOUT_LIST);
612 		} else if (*line == offset + list_length - 1 &&
613 		           *line < (list_req - 1)) {
614 			attron(A_BOLD);
615 			addch(ACS_DARROW);
616 			attroff(A_BOLD);
617 			addch(' ');
618 		} else
619 			printw("  ");
620 
621 		put_line("%-30.30s", pad);
622 
623 		draw_attr(rx1, rx1prec, rxu1, rx2, rx2prec, rxu2,
624 			  e->e_rx_usage, LIST_COL_1);
625 
626 		draw_attr(tx1, tx1prec, txu1, tx2, tx2prec, txu2,
627 			  e->e_tx_usage, LIST_COL_2);
628 
629 	}
630 
631 	(*line)++;
632 }
633 
draw_group(struct element_group * g,void * arg)634 static void draw_group(struct element_group *g, void *arg)
635 {
636 	apply_layout(LAYOUT_HEADER);
637 	int *line = arg;
638 
639 	if (line_visible(*line)) {
640 		NEXT_ROW();
641 		attron(A_BOLD);
642 		put_line("%s", g->g_hdr->gh_title);
643 
644 		attroff(A_BOLD);
645 		mvaddch(row, LIST_COL_1, ACS_VLINE);
646 		attron(A_BOLD);
647 		put_line("%7s   %7s     %%",
648 			g->g_hdr->gh_column[0],
649 			g->g_hdr->gh_column[1]);
650 
651 		attroff(A_BOLD);
652 		mvaddch(row, LIST_COL_2, ACS_VLINE);
653 		attron(A_BOLD);
654 		put_line("%7s   %7s     %%",
655 			g->g_hdr->gh_column[2],
656 			g->g_hdr->gh_column[3]);
657 	}
658 
659 	(*line)++;
660 
661 	group_foreach_element(g, draw_element, arg);
662 }
663 
draw_element_list(void)664 static void draw_element_list(void)
665 {
666 	int line = 0;
667 
668 	group_foreach(draw_group, &line);
669 }
670 
attr_visible(int nattr)671 static inline int attr_visible(int nattr)
672 {
673 	return nattr >= graph_offset && nattr < (graph_offset + ngraph);
674 }
675 
draw_graph_centered(struct graph * g,int row,int ncol,const char * text)676 static void draw_graph_centered(struct graph *g, int row, int ncol,
677 				const char *text)
678 {
679 	int hcenter = (g->g_cfg.gc_width / 2) - (strlen(text) / 2) + 8;
680 
681 	if (hcenter < 9)
682 		hcenter = 9;
683 
684 	mvprintw(row, ncol + hcenter, "%.*s", g->g_cfg.gc_width, text);
685 }
686 
draw_table(struct graph * g,struct graph_table * tbl,struct attr * a,struct history * h,const char * hdr,int ncol,int layout)687 static void draw_table(struct graph *g, struct graph_table *tbl,
688 		       struct attr *a, struct history *h,
689 		       const char *hdr, int ncol, int layout)
690 {
691 	int i, save_row;
692 	char buf[32];
693 
694 	if (!tbl->gt_table) {
695 		for (i = g->g_cfg.gc_height; i >= 0; i--) {
696 			move(++row, ncol);
697 			put_line("");
698 		}
699 		return;
700 	}
701 
702 	move(++row, ncol);
703 	put_line("%8s", tbl->gt_y_unit ? : "");
704 
705 	snprintf(buf, sizeof(buf), "(%s %s/%s)",
706 		 hdr, a->a_def->ad_description,
707 		 h ? h->h_definition->hd_name : "?");
708 
709 	draw_graph_centered(g, row, ncol, buf);
710 
711 	//move(row, ncol + g->g_cfg.gc_width - 3);
712 	//put_line("[err %.2f%%]", rtiming.rt_variance.v_error);
713 
714     memset(buf, 0, strlen(buf));
715 	for (i = (g->g_cfg.gc_height - 1); i >= 0; i--) {
716 		move(++row, ncol);
717         sprintf(buf, "%'8.2f ", tbl->gt_scale[i]);
718         addstr(buf);
719         apply_layout(layout);
720         put_line("%s", tbl->gt_table + (i * graph_row_size(&g->g_cfg)));
721         apply_layout(LAYOUT_LIST);
722 	}
723 
724 	move(++row, ncol);
725 	put_line("         1");
726 
727 	for (i = 1; i <= g->g_cfg.gc_width; i++) {
728 		if (i % 5 == 0) {
729 			move(row, ncol + i + 7);
730 			printw("%2d", i);
731 		}
732 	}
733 
734 	if (!h) {
735 		const char *t1 = " No history data available. ";
736 		const char *t2 = " Press h to start collecting history. ";
737 		int vcenter = g->g_cfg.gc_height / 2;
738 
739 		save_row = row;
740 		draw_graph_centered(g, save_row - vcenter - 1, ncol, t1);
741 		draw_graph_centered(g, save_row - vcenter, ncol, t2);
742 		row = save_row;
743 	}
744 }
745 
draw_history_graph(struct attr * a,struct history * h)746 static void draw_history_graph(struct attr *a, struct history *h)
747 {
748 	struct graph *g;
749 	int ncol = 0, save_row;
750 
751 	g = graph_alloc(h, &c_graph_cfg);
752 	graph_refill(g, h);
753 
754 	save_row = row;
755 	draw_table(g, &g->g_rx, a, h, "RX", ncol, LAYOUT_RX_GRAPH);
756 
757 	if (graph_display == GRAPH_DISPLAY_SIDE_BY_SIDE) {
758 		ncol = cols / 2;
759 		row = save_row;
760 	}
761 
762 	draw_table(g, &g->g_tx, a, h, "TX", ncol, LAYOUT_TX_GRAPH);
763 
764 	graph_free(g);
765 }
766 
draw_attr_graph(struct element * e,struct attr * a,void * arg)767 static void draw_attr_graph(struct element *e, struct attr *a, void *arg)
768 {
769 	int *nattr = arg;
770 
771 	if (attr_visible(*nattr)) {
772 		struct history_def *sel;
773 		struct history *h;
774 
775 		sel = history_current();
776 		c_graph_cfg.gc_unit = a->a_def->ad_unit;
777 
778 		list_for_each_entry(h, &a->a_history_list, h_list) {
779 			if (h->h_definition != sel)
780 				continue;
781 
782 			draw_history_graph(a, h);
783 			goto out;
784 		}
785 
786 		draw_history_graph(a, NULL);
787 	}
788 
789 out:
790 	(*nattr)++;
791 }
792 
draw_graph(void)793 static void draw_graph(void)
794 {
795 	int nattr = 0;
796 
797 	element_foreach_attr(current_element, &draw_attr_graph, &nattr);
798 }
799 
lines_required_for_info(void)800 static int lines_required_for_info(void)
801 {
802 	int lines = 1;
803 
804 	if (c_show_info) {
805 		info_cols = cols / DETAILS_COLS;
806 
807 		if (!info_cols)
808 			info_cols = 1;
809 
810 		lines += (current_element->e_ninfo / info_cols);
811 		if (current_element->e_ninfo % info_cols)
812 			lines++;
813 	}
814 
815 	return lines;
816 }
817 
__draw_info(struct element * e,struct info * info,int * ninfo)818 static void __draw_info(struct element *e, struct info *info, int *ninfo)
819 {
820 	int ncol;
821 
822 	ncol = ((*ninfo) * DETAILS_COLS) - 1;
823 	move(row, ncol);
824 	if (ncol > 0)
825 		addch(ACS_VLINE);
826 
827 	put_line(" %-14.14s %22.22s", info->i_name, info->i_value);
828 
829 	if (++(*ninfo) >= info_cols) {
830 		NEXT_ROW();
831 		*ninfo = 0;
832 	}
833 }
834 
draw_info(void)835 static void draw_info(void)
836 {
837 	struct info *info;
838 	int i, ninfo = 0;
839 
840 	if (!current_element->e_ninfo)
841 		return;
842 
843 	for (i = 1; i < detail_cols; i++)
844 		mvaddch(row, (i * DETAILS_COLS) - 1,
845 			c_show_details ? ACS_PLUS : ACS_TTEE);
846 
847 	NEXT_ROW();
848 	list_for_each_entry(info, &current_element->e_info_list, i_list)
849 		__draw_info(current_element, info, &ninfo);
850 
851 	/*
852 	 * If the last row was incomplete, not all vlines have been drawn.
853 	 * draw them here
854 	 */
855 	for (i = 1; i < info_cols; i++)
856 		mvaddch(row, (i * DETAILS_COLS - 1), ACS_VLINE);
857 }
858 
draw_content(void)859 static void draw_content(void)
860 {
861 	int graph_req, details_req, lines_available, total_req;
862 	int info_req, empty_lines;
863 	int disable_graph = 0, disable_details = 0, disable_info = 0;
864 
865 	if (!current_element)
866 		return;
867 
868 	/*
869 	 * Reset selection offset. Will be set in lines_required_for_list().
870 	 */
871 	selection_offset = 0;
872 	offset = 0;
873 
874 	/* Reset graph offset, will be set in lines_required_for_graph() */
875 	graph_offset = 0;
876 
877 	lines_available = rows - lines_required_for_statusbar()
878 			  - lines_required_for_header();
879 
880 	list_req = lines_required_for_list();
881 	graph_req = lines_required_for_graph();
882 	details_req = lines_required_for_details();
883 	info_req = lines_required_for_info();
884 
885 	total_req = list_req + graph_req + details_req + info_req;
886 
887 	if (total_req <= lines_available) {
888 		/*
889 		 * Enough lines available for all data to displayed, all
890 		 * is good. Display the full list.
891 		 */
892 		list_length = list_req;
893 		goto draw;
894 	}
895 
896 	/*
897 	 * Not enough lines available for full list and all details
898 	 * requested...
899 	 */
900 
901 	if (c_show_list) {
902 		/*
903 		 * ... try shortening the list first.
904 		 */
905 		list_length = lines_available - (total_req - list_req);
906 		if (list_length >= c_list_min)
907 			goto draw;
908 	}
909 
910 	if (c_show_info) {
911 		/* try disabling info */
912 		list_length = lines_available - (total_req - info_req + 1);
913 		if (list_length >= c_list_min) {
914 			disable_info = 1;
915 			goto draw;
916 		}
917 	}
918 
919 	if (c_show_details) {
920 		/* ... try disabling details */
921 		list_length = lines_available - (total_req - details_req + 1);
922 		if (list_length >= c_list_min) {
923 			disable_details = 1;
924 			goto draw;
925 		}
926 	}
927 
928 	/* ... try disabling graph, details, and info */
929 	list_length = lines_available - 1 - 1 - 1;
930 	if (list_length >= c_list_min) {
931 		disable_graph = 1;
932 		disable_details = 1;
933 		disable_info = 1;
934 		goto draw;
935 	}
936 
937 	NEXT_ROW();
938 	put_line("A minimum of %d lines is required to display content.\n",
939 		 (rows - lines_available) + c_list_min + 2);
940 	return;
941 
942 draw:
943 	if (selection_offset && list_length > 0) {
944 		/*
945 		 * Vertically align the selected element in the middle
946 		 * of the list.
947 		 */
948 		offset = selection_offset - (list_length / 2);
949 
950 		/*
951 		 * If element 0..(list_length/2) is selected, offset is
952 		 * negative here. Start drawing from first element.
953 		 */
954 		if (offset < 0)
955 			offset = 0;
956 
957 		/*
958 		 * Ensure the full list length is used if one of the
959 		 * last (list_length/2) elements is selected.
960 		 */
961 		if (offset > (list_req - list_length))
962 			offset = (list_req - list_length);
963 
964 		if (offset >= list_req)
965 			BUG();
966 	}
967 
968 	if (c_show_list) {
969 		draw_element_list();
970 	} else {
971 		NEXT_ROW();
972 		hline(ACS_HLINE, cols);
973 		center_text(" Press %c to enable list view ",
974 			    KEY_TOGGLE_LIST);
975 	}
976 
977 	/*
978 	 * Graphical statistics
979 	 */
980 	NEXT_ROW();
981 	hline(ACS_HLINE, cols);
982 	mvaddch(row, LIST_COL_1, ACS_BTEE);
983 	mvaddch(row, LIST_COL_2, ACS_BTEE);
984 
985 	if (!c_show_graph)
986 		center_text(" Press %c to enable graphical statistics ",
987 			    KEY_TOGGLE_GRAPH);
988 	else {
989 		if (disable_graph)
990 			center_text(" Increase screen height to see graphical statistics ");
991 		else
992 			draw_graph();
993 	}
994 
995 	empty_lines = rows - row - details_req - info_req
996 		      - lines_required_for_statusbar() - 1;
997 
998 	while (empty_lines-- > 0) {
999 		NEXT_ROW();
1000 		put_line("");
1001 	}
1002 
1003 	/*
1004 	 * Detailed statistics
1005 	 */
1006 	NEXT_ROW();
1007 	hline(ACS_HLINE, cols);
1008 
1009 	if (!c_show_details)
1010 		center_text(" Press %c to enable detailed statistics ",
1011 			    KEY_TOGGLE_DETAILS);
1012 	else {
1013 		if (disable_details)
1014 			center_text(" Increase screen height to see detailed statistics ");
1015 		else
1016 			draw_details();
1017 	}
1018 
1019 	/*
1020 	 * Additional information
1021 	 */
1022 	NEXT_ROW();
1023 	hline(ACS_HLINE, cols);
1024 
1025 	if (!c_show_info)
1026 		center_text(" Press %c to enable additional information ",
1027 			    KEY_TOGGLE_INFO);
1028 	else {
1029 		if (disable_info)
1030 			center_text(" Increase screen height to see additional information ");
1031 		else
1032 			draw_info();
1033 	}
1034 }
1035 
1036 
curses_draw(void)1037 static void curses_draw(void)
1038 {
1039 	row = 0;
1040 	move(0,0);
1041 
1042 	getmaxyx(stdscr, rows, cols);
1043 
1044 	if (rows < 4) {
1045 		clear();
1046 		put_line("Screen must be at least 4 rows in height");
1047 		goto out;
1048 	}
1049 
1050 	if (cols < 48) {
1051 		clear();
1052 		put_line("Screen must be at least 48 columns width");
1053 		goto out;
1054 	}
1055 
1056 	current_element = element_current();
1057 	current_attr = attr_current();
1058 
1059 	draw_header();
1060 
1061 	apply_layout(LAYOUT_DEFAULT);
1062 	draw_content();
1063 
1064 	/* fill empty lines with blanks */
1065 	while (row < (rows - 1 - lines_required_for_statusbar())) {
1066 		move(++row, 0);
1067 		put_line("");
1068 	}
1069 
1070 	draw_statusbar();
1071 
1072 	if (quit_mode)
1073 		print_message(" Really Quit? (y/n) ");
1074 	else if (print_help) {
1075 		if (help_page == 0)
1076 			draw_help();
1077 #if 0
1078 		else
1079 			draw_help_2();
1080 #endif
1081 	}
1082 
1083 out:
1084 	attrset(0);
1085 	refresh();
1086 }
1087 
__reset_attr_counter(struct element * e,struct attr * a,void * arg)1088 static void __reset_attr_counter(struct element *e, struct attr *a, void *arg)
1089 {
1090 	attr_reset_counter(a);
1091 }
1092 
reset_counters(void)1093 static void reset_counters(void)
1094 {
1095 	element_foreach_attr(current_element, __reset_attr_counter, NULL);
1096 }
1097 
handle_input(int ch)1098 static int handle_input(int ch)
1099 {
1100 	switch (ch)
1101 	{
1102 		case 'q':
1103 			if (print_help)
1104 				print_help = 0;
1105 			else
1106 				quit_mode = quit_mode ? 0 : 1;
1107 			return 1;
1108 
1109 		case 0x1b:
1110 			quit_mode = 0;
1111 			print_help = 0;
1112 			return 1;
1113 
1114 		case 'y':
1115 			if (quit_mode)
1116 				exit(0);
1117 			break;
1118 
1119 		case 'n':
1120 			if (quit_mode)
1121 				quit_mode = 0;
1122 			return 1;
1123 
1124 		case 12:
1125 		case KEY_CLEAR:
1126 #ifdef HAVE_REDRAWWIN
1127 			redrawwin(stdscr);
1128 #endif
1129 			clear();
1130 			return 1;
1131 
1132 		case '?':
1133 			clear();
1134 			print_help = print_help ? 0 : 1;
1135 			return 1;
1136 
1137 		case KEY_TOGGLE_GRAPH:
1138 			c_show_graph = !c_show_graph;
1139 			if (c_show_graph && !c_ngraph)
1140 				c_ngraph = 1;
1141 			return 1;
1142 
1143 		case KEY_TOGGLE_DETAILS:
1144 			c_show_details = !c_show_details;
1145 			return 1;
1146 
1147 		case KEY_TOGGLE_LIST:
1148 			c_show_list = !c_show_list;
1149 			return 1;
1150 
1151 		case KEY_TOGGLE_INFO:
1152 			c_show_info = !c_show_info;
1153 			return 1;
1154 
1155 		case KEY_COLLECT_HISTORY:
1156 			if (current_attr) {
1157 				attr_start_collecting_history(current_attr);
1158 				return 1;
1159 			}
1160 			break;
1161 
1162 		case KEY_PPAGE:
1163 			{
1164 				int i;
1165 				for (i = 1; i < list_length; i++)
1166 					element_select_prev();
1167 			}
1168 			return 1;
1169 
1170 		case KEY_NPAGE:
1171 			{
1172 				int i;
1173 				for (i = 1; i < list_length; i++)
1174 					element_select_next();
1175 			}
1176 			return 1;
1177 
1178 		case KEY_DOWN:
1179 			element_select_next();
1180 			return 1;
1181 
1182 		case KEY_UP:
1183 			element_select_prev();
1184 			return 1;
1185 
1186 		case KEY_LEFT:
1187 			attr_select_prev();
1188 			return 1;
1189 
1190 		case KEY_RIGHT:
1191 			attr_select_next();
1192 			return 1;
1193 
1194 		case ']':
1195 			group_select_next();
1196 			return 1;
1197 
1198 		case '[':
1199 			group_select_prev();
1200 			return 1;
1201 
1202 		case '<':
1203 			c_ngraph--;
1204 			if (c_ngraph <= 1)
1205 				c_ngraph = 1;
1206 			return 1;
1207 
1208 		case '>':
1209 			c_ngraph++;
1210 			if (c_ngraph > 32)
1211 				c_ngraph = 32;
1212 			return 1;
1213 
1214 		case '\t':
1215 			history_select_next();
1216 			return 1;
1217 
1218 		case 'r':
1219 			reset_counters();
1220 			return 1;
1221 	}
1222 
1223 	return 0;
1224 }
1225 
curses_pre(void)1226 static void curses_pre(void)
1227 {
1228 	static int init = 0;
1229 
1230 	if (!init) {
1231 		curses_init();
1232 		init = 1;
1233 	}
1234 
1235 	for (;;) {
1236 		int ch = getch();
1237 
1238 		if (ch == -1)
1239 			break;
1240 
1241 		if (handle_input(ch))
1242 			curses_draw();
1243 	}
1244 }
1245 
print_module_help(void)1246 static void print_module_help(void)
1247 {
1248 	printf(
1249 	"curses - Curses Output\n" \
1250 	"\n" \
1251 	"  Interactive curses UI. Press '?' to see help.\n" \
1252 	"  Author: Thomas Graf <tgraf@suug.ch>\n" \
1253 	"\n" \
1254 	"  Options:\n" \
1255 	"    fgchar=CHAR    Foreground character (default: '*')\n" \
1256 	"    bgchar=CHAR    Background character (default: '.')\n" \
1257 	"    nchar=CHAR     Noise character (default: ':')\n" \
1258 	"    uchar=CHAR     Unknown character (default: '?')\n" \
1259 	"    gheight=NUM    Height of graph (default: 6)\n" \
1260 	"    gwidth=NUM     Width of graph (default: 60)\n" \
1261 	"    ngraph=NUM     Number of graphs (default: 1)\n" \
1262 	"    nocolors       Do not use colors\n" \
1263 	"    graph          Show graphical stats by default\n" \
1264 	"    details        Show detailed stats by default\n" \
1265 	"    info           Show additional info screen by default\n" \
1266 	"    minlist=INT    Minimum item list length\n");
1267 }
1268 
curses_parse_opt(const char * type,const char * value)1269 static void curses_parse_opt(const char *type, const char *value)
1270 {
1271 	if (!strcasecmp(type, "fgchar") && value)
1272 		c_graph_cfg.gc_foreground = value[0];
1273 	else if (!strcasecmp(type, "bgchar") && value)
1274 		c_graph_cfg.gc_background = value[0];
1275 	else if (!strcasecmp(type, "nchar") && value)
1276 		c_graph_cfg.gc_noise = value[0];
1277 	else if (!strcasecmp(type, "uchar") && value)
1278 		c_graph_cfg.gc_unknown = value[0];
1279 	else if (!strcasecmp(type, "gheight") && value)
1280 		c_graph_cfg.gc_height = strtol(value, NULL, 0);
1281 	else if (!strcasecmp(type, "gwidth") && value)
1282 		c_graph_cfg.gc_width = strtol(value, NULL, 0);
1283 	else if (!strcasecmp(type, "ngraph") && value) {
1284 		c_ngraph = strtol(value, NULL, 0);
1285 		c_show_graph = !!c_ngraph;
1286 	} else if (!strcasecmp(type, "details"))
1287 		c_show_details = 1;
1288 	else if (!strcasecmp(type, "info"))
1289 		c_show_info = 1;
1290 	else if (!strcasecmp(type, "nocolors"))
1291 		c_use_colors = 0;
1292 	else if (!strcasecmp(type, "minlist") && value)
1293 		c_list_min = strtol(value, NULL, 0);
1294 	else if (!strcasecmp(type, "help")) {
1295 		print_module_help();
1296 		exit(0);
1297 	}
1298 }
1299 
1300 static struct bmon_module curses_ops = {
1301 	.m_name		= "curses",
1302 	.m_flags	= BMON_MODULE_DEFAULT,
1303 	.m_shutdown	= curses_shutdown,
1304 	.m_pre		= curses_pre,
1305 	.m_do		= curses_draw,
1306 	.m_parse_opt	= curses_parse_opt,
1307 };
1308 
do_curses_init(void)1309 static void __init do_curses_init(void)
1310 {
1311 	output_register(&curses_ops);
1312 }
1313