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, ¤t_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>