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