File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / bmon / src / out_curses.c
Revision 1.1.1.3 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Mon Oct 21 14:58:35 2019 UTC (5 years, 8 months ago) by misho
Branches: bmon, MAIN
CVS tags: v4_0p0, HEAD
bmon ver 4.0

    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: 
  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: 
  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: 
  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: 
  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: 
  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: 
  227: static void curses_shutdown(void)
  228: {
  229: 	if (initialized)
  230: 		endwin();
  231: }
  232: 
  233: struct detail_arg
  234: {
  235: 	int nattr;
  236: };
  237: 
  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: 
  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: 
  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: 
  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: 
  402: static int lines_required_for_header(void)
  403: {
  404: 	return 1;
  405: }
  406: 
  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: 
  426: static int lines_required_for_statusbar(void)
  427: {
  428: 	return 1;
  429: }
  430: 
  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: 
  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: 
  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: 
  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: 
  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: 
  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: 
  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: 
  538: static inline int line_visible(int line)
  539: {
  540: 	return line >= offset && line < (offset + list_length);
  541: }
  542: 
  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: 
  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: 
  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: 
  664: static void draw_element_list(void)
  665: {
  666: 	int line = 0;
  667: 
  668: 	group_foreach(draw_group, &line);
  669: }
  670: 
  671: static inline int attr_visible(int nattr)
  672: {
  673: 	return nattr >= graph_offset && nattr < (graph_offset + ngraph);
  674: }
  675: 
  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: 
  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: 
  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: 
  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: 
  793: static void draw_graph(void)
  794: {
  795: 	int nattr = 0;
  796: 
  797: 	element_foreach_attr(current_element, &draw_attr_graph, &nattr);
  798: }
  799: 
  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: 
  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: 
  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: 
  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: 
 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: 
 1088: static void __reset_attr_counter(struct element *e, struct attr *a, void *arg)
 1089: {
 1090: 	attr_reset_counter(a);
 1091: }
 1092: 
 1093: static void reset_counters(void)
 1094: {
 1095: 	element_foreach_attr(current_element, __reset_attr_counter, NULL);
 1096: }
 1097: 
 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: 
 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: 
 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: 
 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: 
 1309: static void __init do_curses_init(void)
 1310: {
 1311: 	output_register(&curses_ops);
 1312: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>