#include "staprun.h" #include #include #ifdef HAVE_MONITOR_LIBS #include #include #include #include #include #define COMP_FNS 7 #define MAX_INDEX_LEN 5 #define MAX_COLS 256 #define MAX_DATA 262144 /* XXX: pass procfs.read().maxsize(NNN) from stap */ #define MAX_HISTORY 8192 #define MIN(X,Y) (((X) < (Y)) ? (X) : (Y)) #define MAX(X,Y) (((X) > (Y)) ? (X) : (Y)) #define HIGHLIGHT(NAME,IDX,CUR) (((IDX == CUR)) ? (NAME "*") : (NAME)) typedef struct History_Queue { char *lines[MAX_HISTORY]; /* each malloc/strdup'd(). */ int oldest; int newest; int count; } History_Queue; enum probe_attributes { p_index, p_state, p_hits, p_min, p_avg, p_max, p_name, num_attributes }; static enum state { normal, exited, insert, } monitor_state; static History_Queue h_queue; static pthread_mutex_t mutex; static char probe[MAX_INDEX_LEN]; static WINDOW *status = NULL; static WINDOW *output = NULL; static WINDOW *help = NULL; static WINDOW *status_border = NULL; static WINDOW *help_border = NULL; static PANEL *status_panel = NULL; static PANEL *output_panel = NULL; static PANEL *help_panel = NULL; static PANEL *status_border_panel = NULL; static PANEL *help_border_panel = NULL; static int comp_fn_index = 0; static int num_probes = 0; static int probe_scroll = 0; static int output_scroll = 0; static time_t elapsed_time = 0; static time_t start_time = 0; static int resized = 0; static int input = 0; /* fresh input received in monitor_input() */ static int rendered = 0; static int status_hidden = 0; static int active_window = 1; /* 0 for status window, 1 for output window */ /* Forward declarations */ static int comp_index(const void *p1, const void *p2); static int comp_state(const void *p1, const void *p2); static int comp_hits(const void *p1, const void *p2); static int comp_min(const void *p1, const void *p2); static int comp_avg(const void *p1, const void *p2); static int comp_max(const void *p1, const void *p2); static int comp_name(const void *p1, const void *p2); static int (*comp_fn[COMP_FNS])(const void *p1, const void *p2) = { comp_index, comp_state, comp_hits, comp_min, comp_avg, comp_max, comp_name }; static int comp_index(const void *p1, const void *p2) { json_object *j1 = *(json_object**)p1; json_object *j2 = *(json_object**)p2; json_object *index1, *index2; json_object_object_get_ex(j1, "index", &index1); json_object_object_get_ex(j2, "index", &index2); return json_object_get_int(index1) - json_object_get_int(index2); } static int comp_state(const void *p1, const void *p2) { json_object *j1 = *(json_object**)p1; json_object *j2 = *(json_object**)p2; json_object *state1, *state2; json_object_object_get_ex(j1, "state", &state1); json_object_object_get_ex(j2, "state", &state2); return strcmp(json_object_get_string(state1), json_object_get_string(state2)); } static int comp_hits(const void *p1, const void *p2) { json_object *j1 = *(json_object**)p1; json_object *j2 = *(json_object**)p2; json_object *hits1, *hits2; json_object_object_get_ex(j1, "hits", &hits1); json_object_object_get_ex(j2, "hits", &hits2); return json_object_get_int(hits2) - json_object_get_int(hits1); } static int comp_min(const void *p1, const void *p2) { json_object *j1 = *(json_object**)p1; json_object *j2 = *(json_object**)p2; json_object *min1, *min2; json_object_object_get_ex(j1, "min", &min1); json_object_object_get_ex(j2, "min", &min2); return json_object_get_int(min2) - json_object_get_int(min1); } static int comp_avg(const void *p1, const void *p2) { json_object *j1 = *(json_object**)p1; json_object *j2 = *(json_object**)p2; json_object *avg1, *avg2; json_object_object_get_ex(j1, "avg", &avg1); json_object_object_get_ex(j2, "avg", &avg2); return json_object_get_int(avg2) - json_object_get_int(avg1); } static int comp_max(const void *p1, const void *p2) { json_object *j1 = *(json_object**)p1; json_object *j2 = *(json_object**)p2; json_object *max1, *max2; json_object_object_get_ex(j1, "max", &max1); json_object_object_get_ex(j2, "max", &max2); return json_object_get_int(max2) - json_object_get_int(max1); } static int comp_name(const void *p1, const void *p2) { json_object *j1 = *(json_object**)p1; json_object *j2 = *(json_object**)p2; json_object *name1, *name2; json_object_object_get_ex(j1, "name", &name1); json_object_object_get_ex(j2, "name", &name2); return strcmp(json_object_get_string(name1), json_object_get_string(name2)); } static void write_command(const char *msg) { char path[PATH_MAX]; FILE* fp; size_t len = strlen(msg); if (sprintf_chk(path, "/proc/systemtap/%s/monitor_control", modname)) return; if (!(fp = fopen(path, "w"))) return; if (fwrite(msg, 1, len, fp) != len) { fclose(fp); return; } fclose(fp); } static void clear_screen() { if (status) { del_panel(status_panel); del_panel(status_border_panel); delwin(status); delwin(status_border); status = NULL; } if (output) { del_panel(output_panel); delwin(output); output = NULL; } if (help) { del_panel(help_border_panel); del_panel(help_panel); delwin(help_border); delwin(help); help = NULL; } } static void setup_status_window() { /* Status window not needed when hidden */ if (!status_hidden) { status_border = newwin(LINES/2, COLS, 0, 0); status_border_panel = new_panel(status_border); status = newwin(LINES/2-2, COLS-2, 1, 1); status_panel = new_panel(status); box(status_border, 0 ,0); } } static void setup_output_window() { /* Use the entire screen if not displaying status */ if (status_hidden) output = newwin(LINES,COLS,0,0); else output = newwin(LINES/2,COLS,LINES/2,0); output_panel = new_panel(output); scrollok(output, TRUE); } static void setup_help_window() { help_border = newwin((3.0/4)*LINES+2, (3.0/4)*COLS+2, LINES/8-1, COLS/8-1); help = newwin((3.0/4)*LINES, (3.0/4)*COLS, LINES/8, COLS/8); help_border_panel = new_panel(help_border); help_panel = new_panel(help); hide_panel(help_border_panel); hide_panel(help_panel); box(help_border, 0 ,0); wattron(help, A_BOLD); wprintw(help, "MONITOR MODE COMMANDS\n"); wattroff(help, A_BOLD); wprintw(help, "h - Show/hide help page\n"); wprintw(help, "c - Reset all global variables to their initial states\n"); wprintw(help, "s - Rotate sort column for probes\n"); wprintw(help, "t - Open a prompt to enter the index of a probe to toggle\n"); wprintw(help, "p/r - Pause/resume script by toggling off/on all probes\n"); wprintw(help, "x - Hide/show the status window\n"); wprintw(help, "q - Quit script\n"); wprintw(help, "j,k/DownArrow,UpArrow - Scroll down/up by one entry\n"); wprintw(help, "PgDown/PgUp - Scroll down/up by one page\n"); wprintw(help, "Home/End - Scroll to beginning/end\n"); wprintw(help, "Tab - Toggle scroll window\n"); } static void handle_resize() { usleep (500*1000); /* prevent too many allocations */ endwin(); refresh(); clear_screen(); setup_status_window(); setup_output_window(); setup_help_window(); resized = 0; } void monitor_winch(__attribute__((unused)) int signum) { resized = 1; } void monitor_setup(void) { pthread_mutex_init(&mutex, NULL); probe[0] = '\0'; start_time = time(NULL); initscr(); curs_set(0); cbreak(); noecho(); keypad(stdscr, TRUE); nodelay(stdscr, TRUE); setup_status_window(); setup_output_window(); setup_help_window(); } void monitor_cleanup(void) { pthread_mutex_destroy(&mutex); monitor_end = 1; endwin(); } void monitor_render(void) { FILE *monitor_fp; char path[PATH_MAX]; time_t current_time = time(NULL); int monitor_x, monitor_y, max_cols, max_rows, cur_y, cur_x; int i; int discard; if (resized) handle_resize(); /* Bound scrolling by window height and entry count */ getmaxyx(output, max_rows, max_cols); output_scroll = MIN(MAX(0, h_queue.count-max_rows+1), output_scroll); /* Render previously recorded output */ wclear(output); pthread_mutex_lock(&mutex); for (i = 0; i < h_queue.count-output_scroll; i++) wprintw(output, "%s", h_queue.lines[(h_queue.oldest+i) % MAX_HISTORY]); pthread_mutex_unlock(&mutex); update_panels(); doupdate(); if (status_hidden) return; if (!input && rendered && (elapsed_time = current_time - start_time) < monitor_interval) return; start_time = current_time; input = 0; getmaxyx(status, monitor_y, monitor_x); max_rows = monitor_y; max_cols = MIN(MAX_COLS, monitor_x); static json_object *jso = NULL; /* survives across refresh calls */ char json[MAX_DATA]; size_t bytes = 0; /* Render monitor mode statistics */ if (sprintf_chk(path, "/proc/systemtap/%s/monitor_status", modname)) return; monitor_fp = fopen(path, "r"); if (monitor_fp) { bytes = fread(json, sizeof(char), sizeof(json), monitor_fp); fclose(monitor_fp); } if (bytes >= 1) { /* Free allocated memory */ if (jso) json_object_put(jso); jso = json_tokener_parse(json); } wclear(status); if (jso) { char monitor_str[MAX_COLS]; char monitor_out[MAX_COLS]; int printed; int col; int i; size_t width[num_attributes] = {0}; json_object *jso_uptime, *jso_uid, *jso_mem, *jso_name, *jso_globals, *jso_probe_list; struct json_object_iterator it, it_end; rendered = 1; json_object_object_get_ex(jso, "uptime", &jso_uptime); json_object_object_get_ex(jso, "uid", &jso_uid); json_object_object_get_ex(jso, "memory", &jso_mem); json_object_object_get_ex(jso, "module_name", &jso_name); json_object_object_get_ex(jso, "globals", &jso_globals); json_object_object_get_ex(jso, "probe_list", &jso_probe_list); num_probes = json_object_array_length(jso_probe_list); wmove(status, 0, 0); wprintw(status, "uptime: %s uid: %s memory: %s\n", json_object_get_string(jso_uptime), json_object_get_string(jso_uid), json_object_get_string(jso_mem)); wprintw(status, "module_name: %s probes: %d \n", json_object_get_string(jso_name), num_probes); col = 0; col += snprintf(monitor_out, max_cols, "globals: "); it = json_object_iter_begin(jso_globals); it_end = json_object_iter_end(jso_globals); while (!json_object_iter_equal(&it, &it_end)) { printed = snprintf(monitor_str, max_cols, "%s: %s ", json_object_iter_peek_name(&it), json_object_get_string(json_object_iter_peek_value(&it))) + 1; /* Prevent line folding for globals */ if ((max_cols - (col + printed)) >= 0) { col += snprintf(monitor_out+col, printed, "%s", monitor_str); json_object_iter_next(&it); } else if (printed > max_cols) { /* Skip globals that do not fit on one line */ json_object_iter_next(&it); } else { wprintw(status, "%s\n", monitor_out); col = 0; } } if (col != 0) wprintw(status, "%s\n", monitor_out); /* Find max width of each field for alignment uses */ for (i = 0; i < num_probes; i++) { json_object *probe, *field; probe = json_object_array_get_idx(jso_probe_list, i); json_object_object_get_ex(probe, "index", &field); width[p_index] = MAX(width[p_index], strlen(json_object_get_string(field))); json_object_object_get_ex(probe, "state", &field); width[p_state] = MAX(width[p_state], strlen(json_object_get_string(field))); json_object_object_get_ex(probe, "hits", &field); width[p_hits] = MAX(width[p_hits], strlen(json_object_get_string(field))); json_object_object_get_ex(probe, "min", &field); width[p_min] = MAX(width[p_min], strlen(json_object_get_string(field))); json_object_object_get_ex(probe, "avg", &field); width[p_avg] = MAX(width[p_avg], strlen(json_object_get_string(field))); json_object_object_get_ex(probe, "max", &field); width[p_max] = MAX(width[p_max], strlen(json_object_get_string(field))); json_object_object_get_ex(probe, "name", &field); width[p_name] = MAX(width[p_name], strlen(json_object_get_string(field))); } json_object_array_sort(jso_probe_list, comp_fn[comp_fn_index]); if (active_window == 0) wattron(status, A_BOLD); wprintw(status, "\n%*s\t%*s\t%*s\t%*s\t%*s\t%*s\t%s\n", width[p_index], HIGHLIGHT("index", p_index, comp_fn_index), width[p_state], HIGHLIGHT("state", p_state, comp_fn_index), width[p_hits], HIGHLIGHT("hits", p_hits, comp_fn_index), width[p_min], HIGHLIGHT("min", p_min, comp_fn_index), width[p_avg], HIGHLIGHT("avg", p_avg, comp_fn_index), width[p_max], HIGHLIGHT("max", p_max, comp_fn_index), HIGHLIGHT("name", p_name, comp_fn_index)); if (active_window == 0) wattroff(status, A_BOLD); getyx(status, cur_y, discard); if (probe_scroll >= num_probes) probe_scroll = num_probes-1; for (i = probe_scroll; i < MIN(num_probes, probe_scroll+max_rows-cur_y); i++) { json_object *probe, *field; probe = json_object_array_get_idx(jso_probe_list, i); json_object_object_get_ex(probe, "index", &field); wprintw(status, "%*s\t", width[p_index], json_object_get_string(field)); json_object_object_get_ex(probe, "state", &field); wprintw(status, "%*s\t", width[p_state], json_object_get_string(field)); json_object_object_get_ex(probe, "hits", &field); wprintw(status, "%*s\t", width[p_hits], json_object_get_string(field)); json_object_object_get_ex(probe, "min", &field); wprintw(status, "%*s\t", width[p_min], json_object_get_string(field)); json_object_object_get_ex(probe, "avg", &field); wprintw(status, "%*s\t", width[p_avg], json_object_get_string(field)); json_object_object_get_ex(probe, "max", &field); wprintw(status, "%*s\t", width[p_max], json_object_get_string(field)); getyx(status, discard, cur_x); json_object_object_get_ex(probe, "name", &field); wprintw(status, "%.*s", max_cols-cur_x-1, json_object_get_string(field)); wprintw(status, "\n"); } mvwprintw(status, max_rows-1, 0, "press h for help\n"); } else /* ! jso */ { wmove(status, 0, 0); wprintw(status, "(data not available)"); } if (monitor_state == insert) mvwprintw(status, max_rows-1, 0, "enter probe index: %s\n", probe); else if (monitor_state == exited) mvwprintw(status, max_rows-1, 0, "EXITED: press q again to quit\n"); update_panels(); doupdate(); (void) discard; /* Unused */ } /* Called in staprun/relay.c */ void monitor_remember_output_line(const char* buf, const size_t bytes) { pthread_mutex_lock(&mutex); free (h_queue.lines[h_queue.newest]); h_queue.lines[h_queue.newest] = strndup(buf, bytes); h_queue.newest = (h_queue.newest+1) % MAX_HISTORY; if (h_queue.count < MAX_HISTORY) h_queue.count++; /* and h_queue.oldest stays at 0 */ else h_queue.oldest = (h_queue.oldest+1) % MAX_HISTORY; pthread_mutex_unlock(&mutex); } void monitor_exited(void) { monitor_state = exited; input = 1; } void monitor_input(void) { static int i = 0; int ch; int max_rows; int cur_y; int discard; getmaxyx(output, max_rows, discard); getyx(status, cur_y, discard); switch (monitor_state) { case normal: case exited: ch = getch(); switch (ch) { case '\t': active_window ^= 1; break; case 'j': case KEY_DOWN: if (active_window == 0) probe_scroll++; else output_scroll--; break; case 'k': case KEY_UP: if (active_window == 0) probe_scroll--; else output_scroll++; break; case KEY_NPAGE: if (active_window == 0) probe_scroll += max_rows-cur_y; else output_scroll -= max_rows-1; break; case KEY_PPAGE: if (active_window == 0) probe_scroll -= max_rows-cur_y; else output_scroll += max_rows-1; break; case KEY_HOME: if (active_window == 0) probe_scroll = 0; else output_scroll = h_queue.count-max_rows+1; break; case KEY_END: if (active_window == 0) probe_scroll = num_probes-1; else output_scroll = 0; break; case 's': comp_fn_index++; if (comp_fn_index == COMP_FNS) comp_fn_index = 0; break; case 'c': write_command("clear"); break; case 'r': write_command("resume"); break; case 'p': write_command("pause"); break; case 'q': if (monitor_state == exited) { cleanup_and_exit(0, 0 /* error_detected unavailable here */ ); /* NOTREACHED */ } else write_command("quit"); break; case 't': monitor_state = insert; break; case 'h': if (panel_hidden(help_panel)) { show_panel(help_border_panel); show_panel(help_panel); } else { hide_panel(help_border_panel); hide_panel(help_panel); } break; case 'x': status_hidden ^= 1; handle_resize(); break; } probe_scroll = MAX(0, probe_scroll); output_scroll = MAX(0, output_scroll); if (ch != ERR) input = 1; break; case insert: ch = getch(); if (ch == '\n' || i == MAX_INDEX_LEN) { write_command(probe); monitor_state = normal; i = 0; probe[0] = '\0'; rendered = 0; } else if (ch == KEY_BACKSPACE && i > 0) { probe[--i] = '\0'; input = 1; } else if (ch != ERR && ch != KEY_BACKSPACE) { probe[i++] = ch; probe[i] = '\0'; input = 1; } break; } (void) discard; /* Unused */ } #else /* ! HAVE_MONITOR_LIBS */ void monitor_winch(int signum) { (void) signum; } void monitor_setup(void) {} void monitor_cleanup(void) {} void monitor_render(void) {} void monitor_input(void) {} void monitor_exited(void) {} void monitor_remember_output_line(const char* buf, const size_t bytes) { (void)buf; (void) bytes; } #endif void *redirect_stdin(void *arg) { char path[PATH_MAX]; int c; int fd; snprintf(path, PATH_MAX - 25, "/proc/systemtap/%s/__stdin", modname); /* destination file may not exist yet */ while ((fd = open(path, O_WRONLY)) == -1) { if (errno != ENOENT) { _perr("Unexpected failure during file open.\n"); exit(1); } usleep(2000); } while ((c = getchar()) != EOF) { char ch = (char)c; if (! write(fd, &ch, sizeof(ch)) && errno != ENOENT) { _perr("Unexpected failure during write.\n"); exit(1); } } read_stdin_cleanup(); close(fd); /* if it pleases m'lud coverity */ return arg; } void read_stdin_setup(void) { pthread_t t; if (isatty(STDIN_FILENO)) { /* enter non-canonical mode */ struct termios oldterm, newterm; if (tcgetattr(STDIN_FILENO, &oldterm) != 0) { _perr("Failed to reconfigure terminal.\n"); exit(1); } newterm = oldterm; newterm.c_lflag &= ~ICANON & ~ECHO; if (tcsetattr(STDIN_FILENO, TCSANOW, &newterm) != 0) { _perr("Failed to reconfigure terminal.\n"); exit(1); } } if (setvbuf(stdin, NULL, _IONBF, 0) != 0) { _perr("Failed to reconfigure input stream.\n"); exit(1); } if (pthread_create(&t, NULL, redirect_stdin, NULL) != 0) { _perr("Failed to create thread.\n"); exit(1); } if (pthread_detach(t) != 0) { _perr("Failed to detach thread.\n"); exit(1); } return; } void read_stdin_cleanup(void) { if (isatty(STDIN_FILENO)) { struct termios newterm; if (tcgetattr(STDIN_FILENO, &newterm) != 0) { _perr("Failed to reconfigure terminal.\n"); return; } newterm.c_lflag |= ICANON | ECHO; if (tcsetattr(STDIN_FILENO, TCSANOW, &newterm) != 0) { _perr("Failed to reconfigure terminal.\n"); } } return; } /* vim: set sw=2 ts=8 cino=>4,n-2,{2,^-2,t0,(0,u0,w1,M1 : */