From: Julien Desfossez Date: Tue, 21 Feb 2012 22:22:52 +0000 (-0500) Subject: rename lttngtop/ to src/ X-Git-Tag: v0.2~70 X-Git-Url: https://git.lttng.org/?p=lttngtop.git;a=commitdiff_plain;h=37010c3c1a006bf7f3181a1746751c99645ad4bb rename lttngtop/ to src/ Signed-off-by: Julien Desfossez --- diff --git a/Makefile.am b/Makefile.am index 009425c..733b0cd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,4 +2,4 @@ AM_CFLAGS = $(PACKAGE_CFLAGS) ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = lttngtop +SUBDIRS = src diff --git a/configure.ac b/configure.ac index d38cefb..495a5e0 100644 --- a/configure.ac +++ b/configure.ac @@ -66,6 +66,6 @@ AC_SUBST(PACKAGE_CFLAGS) AC_CONFIG_FILES([ Makefile - lttngtop/Makefile + src/Makefile ]) AC_OUTPUT diff --git a/lttngtop/Makefile.am b/lttngtop/Makefile.am deleted file mode 100644 index 5d6ca3e..0000000 --- a/lttngtop/Makefile.am +++ /dev/null @@ -1,12 +0,0 @@ -AM_CFLAGS = $(PACKAGE_CFLAGS) - -bin_PROGRAMS = lttngtop - -lttngtop_SOURCES = \ - lttngtop.c \ - common.c \ - cursesdisplay.c \ - cputop.c \ - iostreamtop.c - -lttngtop_LDADD = -lbabeltrace -lctf diff --git a/lttngtop/common.c b/lttngtop/common.c deleted file mode 100644 index 2a0f1f4..0000000 --- a/lttngtop/common.c +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (C) 2011 Julien Desfossez - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License Version 2 as - * published by the Free Software Foundation; - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -#include -#include -#include "common.h" - -struct processtop *find_process_tid(struct lttngtop *ctx, int tid, char *comm) -{ - gint i; - struct processtop *tmp; - - for (i = 0; i < ctx->process_table->len; i++) { - tmp = g_ptr_array_index(ctx->process_table, i); - if (tmp && tmp->tid == tid) - return tmp; - } - return NULL; -} - -struct processtop* add_proc(struct lttngtop *ctx, int tid, char *comm, - unsigned long timestamp) -{ - struct processtop *newproc; - - /* if the PID already exists, we just rename the process */ - /* FIXME : need to integrate with clone/fork/exit to be accurate */ - newproc = find_process_tid(ctx, tid, comm); - if (!newproc) { - newproc = malloc(sizeof(struct processtop)); - memset(newproc, 0, sizeof(struct processtop)); - newproc->tid = tid; - newproc->birth = timestamp; - newproc->process_files_table = g_ptr_array_new(); - newproc->threads = g_ptr_array_new(); - newproc->perf = g_hash_table_new(g_direct_hash, g_direct_equal); - newproc->iostream = malloc(sizeof(struct iostream)); - newproc->iostream->ret_read = 0; - newproc->iostream->ret_write = 0; - newproc->iostream->ret_total = 0; - newproc->iostream->syscall_info = NULL; - g_ptr_array_add(ctx->process_table, newproc); - } - newproc->comm = strdup(comm); - - return newproc; -} - -struct processtop* update_proc(struct processtop* proc, int pid, int tid, - int ppid, char *comm) -{ - if (proc) { - proc->pid = pid; - proc->tid = tid; - proc->ppid = ppid; - if (strcmp(proc->comm, comm) != 0) { - free(proc->comm); - proc->comm = strdup(comm); - } - } - return proc; -} - -/* - * This function just sets the time of death of a process. - * When we rotate the cputime we remove it from the process list. - */ -void death_proc(struct lttngtop *ctx, int tid, char *comm, - unsigned long timestamp) -{ - struct processtop *tmp; - tmp = find_process_tid(ctx, tid, comm); - if (tmp && strcmp(tmp->comm, comm) == 0) - tmp->death = timestamp; -} - -struct processtop* get_proc(struct lttngtop *ctx, int tid, char *comm, - unsigned long timestamp) -{ - struct processtop *tmp; - tmp = find_process_tid(ctx, tid, comm); - if (tmp && strcmp(tmp->comm, comm) == 0) - return tmp; - return add_proc(ctx, tid, comm, timestamp); -} - -void add_thread(struct processtop *parent, struct processtop *thread) -{ - gint i; - struct processtop *tmp; - - for (i = 0; i < parent->threads->len; i++) { - tmp = g_ptr_array_index(parent->threads, i); - if (tmp == thread) - return; - } - g_ptr_array_add(parent->threads, thread); -} - -struct cputime* add_cpu(int cpu) -{ - struct cputime *newcpu; - - newcpu = malloc(sizeof(struct cputime)); - newcpu->id = cpu; - newcpu->current_task = NULL; - newcpu->perf = g_hash_table_new(g_direct_hash, g_direct_equal); - - g_ptr_array_add(lttngtop.cpu_table, newcpu); - - return newcpu; -} -struct cputime* get_cpu(int cpu) -{ - gint i; - struct cputime *tmp; - - for (i = 0; i < lttngtop.cpu_table->len; i++) { - tmp = g_ptr_array_index(lttngtop.cpu_table, i); - if (tmp->id == cpu) - return tmp; - } - - return add_cpu(cpu); -} - -/* - * At the end of a sampling period, we need to display the cpu time for each - * process and to reset it to zero for the next period - */ -void rotate_cputime(unsigned long end) -{ - gint i; - struct cputime *tmp; - unsigned long elapsed; - - for (i = 0; i < lttngtop.cpu_table->len; i++) { - tmp = g_ptr_array_index(lttngtop.cpu_table, i); - elapsed = end - tmp->task_start; - if (tmp->current_task) { - tmp->current_task->totalcpunsec += elapsed; - tmp->current_task->threadstotalcpunsec += elapsed; - if (tmp->current_task->pid != tmp->current_task->tid && - tmp->current_task->threadparent) { - tmp->current_task->threadparent->threadstotalcpunsec += elapsed; - } - } - tmp->task_start = end; - } -} - -void reset_perf_counter(gpointer key, gpointer value, gpointer user_data) -{ - ((struct perfcounter*) value)->count = 0; -} - -void copy_perf_counter(gpointer key, gpointer value, gpointer new_table) -{ - struct perfcounter *newperf; - - newperf = malloc(sizeof(struct perfcounter)); - newperf->count = ((struct perfcounter *) value)->count; - newperf->visible = ((struct perfcounter *) value)->visible; - newperf->sort = ((struct perfcounter *) value)->sort; - g_hash_table_insert((GHashTable *) new_table, key, newperf); -} - -void rotate_perfcounter() { - int i; - struct processtop *tmp; - for (i = 0; i < lttngtop.process_table->len; i++) { - tmp = g_ptr_array_index(lttngtop.process_table, i); - g_hash_table_foreach(tmp->perf, reset_perf_counter, NULL); - } -} - -void cleanup_processtop() -{ - gint i; - struct processtop *tmp; - - for (i = 0; i < lttngtop.process_table->len; i++) { - tmp = g_ptr_array_index(lttngtop.process_table, i); - tmp->totalcpunsec = 0; - tmp->threadstotalcpunsec = 0; - tmp->iostream->ret_read = 0; - tmp->iostream->ret_write = 0; - } -} - -struct lttngtop* get_copy_lttngtop(unsigned long start, unsigned long end) -{ - gint i, j; - unsigned long time; - struct lttngtop *dst; - struct processtop *tmp, *tmp2, *new; - struct cputime *tmpcpu, *newcpu; - struct files *tmpfile, *newfile; - - dst = malloc(sizeof(struct lttngtop)); - dst = memset(dst, 0, sizeof(struct lttngtop)); - dst->start = start; - dst->end = end; - dst->process_table = g_ptr_array_new(); - dst->files_table = g_ptr_array_new(); - dst->cpu_table = g_ptr_array_new(); - dst->perf_list = g_hash_table_new(g_direct_hash, g_direct_equal); - - rotate_cputime(end); - - g_hash_table_foreach(lttngtop.perf_list, copy_perf_counter, dst->perf_list); - for (i = 0; i < lttngtop.process_table->len; i++) { - tmp = g_ptr_array_index(lttngtop.process_table, i); - new = malloc(sizeof(struct processtop)); - - memcpy(new, tmp, sizeof(struct processtop)); - new->threads = g_ptr_array_new(); - new->comm = strdup(tmp->comm); - new->process_files_table = g_ptr_array_new(); - new->perf = g_hash_table_new(g_direct_hash, g_direct_equal); - g_hash_table_foreach(tmp->perf, copy_perf_counter, new->perf); - - new->iostream = malloc(sizeof(struct iostream)); - memcpy(new->iostream, tmp->iostream, sizeof(struct iostream)); - /* compute the stream speed */ - if (end - start != 0) - { - time = (end - start)/NSEC_PER_SEC; - new->iostream->ret_read = new->iostream->ret_read/(time); - new->iostream->ret_write = new->iostream->ret_write/(time); - } - - for (j = 0; j < tmp->process_files_table->len; j++) { - tmpfile = g_ptr_array_index(tmp->process_files_table, j); - newfile = malloc(sizeof(struct files)); - - memcpy(newfile, tmpfile, sizeof(struct files)); - - newfile->name = strdup(tmpfile->name); - newfile->ref = new; - - g_ptr_array_add(new->process_files_table, newfile); - g_ptr_array_add(dst->files_table, newfile); - - /* - * if the process died during the last period, we remove all - * files associated with if after the copy - */ - if (tmp->death > 0 && tmp->death < end) { - g_ptr_array_remove(tmp->process_files_table, tmpfile); - free(tmpfile); - } - } - g_ptr_array_add(dst->process_table, new); - - /* - * if the process died during the last period, we remove it from - * the current process list after the copy - */ - if (tmp->death > 0 && tmp->death < end) { - g_ptr_array_remove(lttngtop.process_table, tmp); - g_ptr_array_free(tmp->threads, TRUE); - free(tmp->comm); - g_ptr_array_free(tmp->process_files_table, TRUE); - g_hash_table_destroy(tmp->perf); - free(tmp); - } - } - rotate_perfcounter(); - - for (i = 0; i < lttngtop.cpu_table->len; i++) { - tmpcpu = g_ptr_array_index(lttngtop.cpu_table, i); - newcpu = malloc(sizeof(struct cputime)); - memcpy(newcpu, tmpcpu, sizeof(struct cputime)); - newcpu->perf = g_hash_table_new(g_direct_hash, g_direct_equal); - g_hash_table_foreach(tmpcpu->perf, copy_perf_counter, newcpu->perf); - /* - * note : we don't care about the current process pointer in the copy - * so the reference is invalid after the memcpy - */ - g_ptr_array_add(dst->cpu_table, newcpu); - } - /* create the threads index if required */ - for (i = 0; i < dst->process_table->len; i++) { - tmp = g_ptr_array_index(dst->process_table, i); - if (tmp->pid == tmp->tid) { - for (j = 0; j < dst->process_table->len; j++) { - tmp2 = g_ptr_array_index(dst->process_table, j); - if (tmp2->pid == tmp->pid) { - tmp2->threadparent = tmp; - g_ptr_array_add(tmp->threads, tmp2); - } - } - } - } - - // update_global_stats(dst); - cleanup_processtop(); - - return dst; -} - diff --git a/lttngtop/common.h b/lttngtop/common.h deleted file mode 100644 index e81256c..0000000 --- a/lttngtop/common.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2011 Julien Desfossez - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License Version 2 as - * published by the Free Software Foundation; - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -#ifndef _COMMON_H -#define _COMMON_H - -#include -#include -#include "lttngtoptypes.h" -#include "cputop.h" - -#define NSEC_PER_USEC 1000 -#define NSEC_PER_SEC 1000000000L - -sem_t goodtodisplay, goodtoupdate, timer, pause_sem, end_trace_sem, bootstrap; - -GPtrArray *copies; /* struct lttngtop */ -pthread_mutex_t perf_list_mutex; - -struct lttngtop *data; - -struct processtop *find_process_tid(struct lttngtop *ctx, int pid, char *comm); -struct processtop* add_proc(struct lttngtop *ctx, int pid, char *comm, - unsigned long timestamp); -struct processtop* update_proc(struct processtop* proc, int pid, int tid, - int ppid, char *comm); -void add_thread(struct processtop *parent, struct processtop *thread); -struct processtop* get_proc(struct lttngtop *ctx, int tid, char *comm, - unsigned long timestamp); -void death_proc(struct lttngtop *ctx, int tid, char *comm, - unsigned long timestamp); -struct cputime* add_cpu(int cpu); -struct cputime* get_cpu(int cpu); -struct lttngtop* get_copy_lttngtop(unsigned long start, unsigned long end); -struct perfcounter *add_perf_counter(GPtrArray *perf, GQuark quark, - unsigned long count); -struct perfcounter *get_perf_counter(const char *name, struct processtop *proc, - struct cputime *cpu); - -#endif /* _COMMON_H */ diff --git a/lttngtop/cputop.c b/lttngtop/cputop.c deleted file mode 100644 index 2ade0db..0000000 --- a/lttngtop/cputop.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2011 Julien Desfossez - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License Version 2 as - * published by the Free Software Foundation; - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -#include - -#include "lttngtoptypes.h" -#include "common.h" -#include "cputop.h" - -void update_cputop_data(unsigned long timestamp, int64_t cpu, int prev_pid, - int next_pid, char *prev_comm, char *next_comm) -{ - struct cputime *tmpcpu; - unsigned long elapsed; - - tmpcpu = get_cpu(cpu); - - if (tmpcpu->current_task && tmpcpu->current_task->pid == prev_pid) { - elapsed = timestamp - tmpcpu->task_start; - tmpcpu->current_task->totalcpunsec += elapsed; - tmpcpu->current_task->threadstotalcpunsec += elapsed; - if (tmpcpu->current_task->pid != tmpcpu->current_task->tid) - tmpcpu->current_task->threadparent->threadstotalcpunsec += elapsed; - } - - if (next_pid != 0) - tmpcpu->current_task = get_proc(<tngtop, next_pid, next_comm, timestamp); - else - tmpcpu->current_task = NULL; - - tmpcpu->task_start = timestamp; -} - -enum bt_cb_ret handle_sched_switch(struct bt_ctf_event *call_data, - void *private_data) -{ - struct definition *scope; - unsigned long timestamp; - uint64_t cpu_id; - char *prev_comm, *next_comm; - int prev_tid, next_tid; - - timestamp = bt_ctf_get_timestamp(call_data); - if (timestamp == -1ULL) - goto error; - - scope = bt_ctf_get_top_level_scope(call_data, - BT_EVENT_FIELDS); - prev_comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, - scope, "_prev_comm")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing prev_comm context info\n"); - goto error; - } - - next_comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, - scope, "_next_comm")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing next_comm context info\n"); - goto error; - } - - prev_tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, - scope, "_prev_tid")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing prev_tid context info\n"); - goto error; - } - - next_tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, - scope, "_next_tid")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing next_tid context info\n"); - goto error; - } - - scope = bt_ctf_get_top_level_scope(call_data, - BT_STREAM_PACKET_CONTEXT); - cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(call_data, - scope, "cpu_id")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing cpu_id context info\n"); - goto error; - } - - update_cputop_data(timestamp, cpu_id, prev_tid, next_tid, - prev_comm, next_comm); - - return BT_CB_OK; - -error: - return BT_CB_ERROR_STOP; -} - -enum bt_cb_ret handle_sched_process_free(struct bt_ctf_event *call_data, - void *private_data) -{ - struct definition *scope; - unsigned long timestamp; - char *comm; - int tid; - - timestamp = bt_ctf_get_timestamp(call_data); - if (timestamp == -1ULL) - goto error; - - scope = bt_ctf_get_top_level_scope(call_data, - BT_EVENT_FIELDS); - comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, - scope, "_comm")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing procname context info\n"); - goto error; - } - - tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, - scope, "_tid")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing tid context info\n"); - goto error; - } - - death_proc(<tngtop, tid, comm, timestamp); - - return BT_CB_OK; - -error: - return BT_CB_ERROR_STOP; - -} - diff --git a/lttngtop/cputop.h b/lttngtop/cputop.h deleted file mode 100644 index 8b57823..0000000 --- a/lttngtop/cputop.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2011 Julien Desfossez - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License Version 2 as - * published by the Free Software Foundation; - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -#ifndef _LTTNGTOP_H -#define _LTTNGTOP_H - -#include -#include -#include -#include - -enum bt_cb_ret handle_sched_switch(struct bt_ctf_event *hook_data, - void *call_data); - -enum bt_cb_ret handle_sched_process_free(struct bt_ctf_event *call_data, - void *private_data); - -#endif /* _LTTNGTOP_H */ diff --git a/lttngtop/cursesdisplay.c b/lttngtop/cursesdisplay.c deleted file mode 100644 index 0f79485..0000000 --- a/lttngtop/cursesdisplay.c +++ /dev/null @@ -1,1003 +0,0 @@ -/* - * Copyright (C) 2011 Julien Desfossez - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License Version 2 as - * published by the Free Software Foundation; - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cursesdisplay.h" -#include "lttngtoptypes.h" -#include "common.h" - -#define DEFAULT_DELAY 15 -#define MAX_LINE_LENGTH 50 -#define MAX_LOG_LINES 4 - -/* to prevent concurrent updates of the different windows */ -sem_t update_display_sem; - -char *termtype; -WINDOW *footer, *header, *center, *status; -WINDOW *perf_panel_window = NULL; -PANEL *perf_panel, *main_panel; - -int perf_panel_visible = 0; -int perf_line_selected = 0; - -int last_display_index, currently_displayed_index; - -struct processtop *selected_process = NULL; -int selected_tid; -char *selected_comm; -int selected_ret; - -int selected_line = 0; /* select bar position */ -int selected_in_list = 0; /* selection relative to the whole list */ -int list_offset = 0; /* first index in the list to display (scroll) */ -int nb_log_lines = 0; -char log_lines[MAX_LINE_LENGTH * MAX_LOG_LINES + MAX_LOG_LINES]; - -int max_elements = 80; - -int toggle_threads = -1; -int toggle_pause = -1; -int toggle_tree = -1; - -int max_center_lines; - -pthread_t keyboard_thread; - -void reset_ncurses() -{ - curs_set(1); - endwin(); - exit(0); -} - -static void handle_sigterm(int signal) -{ - reset_ncurses(); -} - -void init_screen() -{ - initscr(); - noecho(); - halfdelay(DEFAULT_DELAY); - nonl(); - intrflush(stdscr, false); - keypad(stdscr, true); - curs_set(0); - - if (has_colors()) { - start_color(); - init_pair(1, COLOR_RED, COLOR_BLACK); /* - */ - init_pair(2, COLOR_GREEN, COLOR_BLACK); /* + */ - init_pair(3, COLOR_BLACK, COLOR_WHITE); /* keys */ - init_pair(4, COLOR_WHITE, COLOR_GREEN); /* keys activated */ - init_pair(5, COLOR_WHITE, COLOR_BLUE); /* select line */ - } - termtype = getenv("TERM"); - if (!strcmp(termtype, "xterm") || !strcmp(termtype, "xterm-color") || - !strcmp(termtype, "vt220")) { - define_key("\033[H", KEY_HOME); - define_key("\033[F", KEY_END); - define_key("\033OP", KEY_F(1)); - define_key("\033OQ", KEY_F(2)); - define_key("\033OR", KEY_F(3)); - define_key("\033OS", KEY_F(4)); - define_key("\0330U", KEY_F(6)); - define_key("\033[11~", KEY_F(1)); - define_key("\033[12~", KEY_F(2)); - define_key("\033[13~", KEY_F(3)); - define_key("\033[14~", KEY_F(4)); - define_key("\033[16~", KEY_F(6)); - define_key("\033[17;2~", KEY_F(18)); - } - signal(SIGTERM, handle_sigterm); - mousemask(BUTTON1_CLICKED, NULL); - refresh(); -} - -WINDOW *create_window(int height, int width, int startx, int starty) -{ - WINDOW *win; - win = newwin(height, width, startx, starty); - box(win, 0 , 0); - wrefresh(win); - return win; -} - -WINDOW *create_window_no_border(int height, int width, int startx, int starty) -{ - WINDOW *win; - win = newwin(height, width, startx, starty); - wrefresh(win); - return win; -} - -void print_digit(WINDOW *win, int digit) -{ - if (digit < 0) { - wattron(win, COLOR_PAIR(1)); - wprintw(win, "%d", digit); - wattroff(win, COLOR_PAIR(1)); - } else if (digit > 0) { - wattron(win, COLOR_PAIR(2)); - wprintw(win, "+%d", digit); - wattroff(win, COLOR_PAIR(2)); - } else { - wprintw(win, "0"); - } -} - -void print_digits(WINDOW *win, int first, int second) -{ - wprintw(win, "("); - print_digit(win, first); - wprintw(win, ", "); - print_digit(win, second); - wprintw(win, ")"); -} - -void print_headers(int line, char *desc, int value, int first, int second) -{ - wattron(header, A_BOLD); - mvwprintw(header, line, 4, "%s", desc); - wattroff(header, A_BOLD); - mvwprintw(header, line, 16, "N/A", value); - wmove(header, line, 24); - print_digits(header, first, second); - wmove(header, line, 40); -} - -void set_window_title(WINDOW *win, char *title) -{ - wattron(win, A_BOLD); - mvwprintw(win, 0, 1, title); - wattroff(win, A_BOLD); -} - -void print_log(char *str) -{ - int i; - int current_line = 1; - int current_char = 1; - char *tmp, *tmp2; - /* rotate the line buffer */ - if (nb_log_lines >= MAX_LOG_LINES) { - tmp = strndup(log_lines, MAX_LINE_LENGTH * MAX_LOG_LINES + MAX_LOG_LINES); - tmp2 = strchr(tmp, '\n'); - memset(log_lines, '\0', strlen(log_lines)); - strncat(log_lines, tmp2 + 1, strlen(tmp2) - 1); - log_lines[strlen(log_lines)] = '\n'; - log_lines[strlen(log_lines)] = '\0'; - free(tmp); - } - nb_log_lines++; - - strncat(log_lines, str, MAX_LINE_LENGTH - 1); - - if (nb_log_lines < MAX_LOG_LINES) - log_lines[strlen(log_lines)] = '\n'; - log_lines[strlen(log_lines)] = '\0'; - - werase(status); - box(status, 0 , 0); - set_window_title(status, "Status"); - for (i = 0; i < strlen(log_lines); i++) { - if (log_lines[i] == '\n') { - wmove(status, ++current_line, 1); - current_char = 1; - } else { - mvwprintw(status, current_line, current_char++, "%c", log_lines[i]); - } - } - wrefresh(status); -} - -void print_key(WINDOW *win, char *key, char *desc, int toggle) -{ - int pair; - if (toggle > 0) - pair = 4; - else - pair = 3; - wattron(win, COLOR_PAIR(pair)); - wprintw(footer, "%s", key); - wattroff(win, COLOR_PAIR(pair)); - wprintw(footer, ":%s", desc); -} - -void update_footer() -{ - sem_wait(&update_display_sem); - werase(footer); - wmove(footer, 1, 1); - print_key(footer, "F2", "CPUtop ", current_view == cpu); - print_key(footer, "F3", "PerfTop ", current_view == perf); - print_key(footer, "F6", "IOTop ", current_view == iostream); - print_key(footer, "Enter", "Details ", current_view == process_details); - print_key(footer, "q", "Quit | ", 0); - print_key(footer, "P", "Perf Pref ", 0); - print_key(footer, "p", "Pause ", toggle_pause); - - wrefresh(footer); - sem_post(&update_display_sem); -} - -void basic_header() -{ - werase(header); - box(header, 0 , 0); - set_window_title(header, "Statistics for interval [gathering data...["); - wattron(header, A_BOLD); - mvwprintw(header, 1, 4, "CPUs"); - mvwprintw(header, 2, 4, "Processes"); - mvwprintw(header, 3, 4, "Threads"); - mvwprintw(header, 4, 4, "Files"); - mvwprintw(header, 5, 4, "Network"); - mvwprintw(header, 6, 4, "IO"); - wattroff(header, A_BOLD); - wrefresh(header); -} - -void update_header() -{ - werase(header); - box(header, 0 , 0); - set_window_title(header, "Statistics for interval "); - wattron(header, A_BOLD); - /* - wprintw(header, "[%lu.%lu, %lu.%lu[", - data->start.tv_sec, data->start.tv_nsec, - data->end.tv_sec, data->end.tv_nsec); - */ - wprintw(header, "[%lu, %lu[", - data->start, - data->end); - mvwprintw(header, 1, 4, "CPUs"); - wattroff(header, A_BOLD); - wprintw(header, "\t%d\t(max/cpu : %0.2f%)", data->cpu_table->len, - 100.0/data->cpu_table->len); - print_headers(2, "Processes", data->nbproc, data->nbnewproc, - -1*(data->nbdeadproc)); - print_headers(3, "Threads", data->nbthreads, data->nbnewthreads, - -1*(data->nbdeadthreads)); - print_headers(4, "Files", data->nbfiles, data->nbnewfiles, - -1*(data->nbclosedfiles)); - mvwprintw(header, 4, 43, "N/A kbytes/sec"); - print_headers(5, "Network", 114, 0, 0); - mvwprintw(header, 5, 43, "N/A Mbytes/sec"); - wrefresh(header); -} - -gint sort_by_cpu_desc(gconstpointer p1, gconstpointer p2) -{ - struct processtop *n1 = *(struct processtop **)p1; - struct processtop *n2 = *(struct processtop **)p2; - unsigned long totaln1 = n1->totalcpunsec; - unsigned long totaln2 = n2->totalcpunsec; - - if (totaln1 < totaln2) - return 1; - if (totaln1 == totaln2) - return 0; - return -1; -} - -gint sort_by_cpu_group_by_threads_desc(gconstpointer p1, gconstpointer p2) -{ - struct processtop *n1 = *(struct processtop **)p1; - struct processtop *n2 = *(struct processtop **)p2; - unsigned long totaln1 = n1->threadstotalcpunsec; - unsigned long totaln2 = n2->threadstotalcpunsec; - - if (totaln1 < totaln2) - return 1; - if (totaln1 == totaln2) - return 0; - return -1; -} - -void update_cputop_display() -{ - int i; - int header_offset = 2; - struct processtop *tmp; - unsigned long elapsed; - double maxcputime; - int nblinedisplayed = 0; - int current_line = 0; - - elapsed = data->end - data->start; - maxcputime = elapsed * data->cpu_table->len / 100.0; - - g_ptr_array_sort(data->process_table, sort_by_cpu_desc); - - set_window_title(center, "CPU Top"); - wattron(center, A_BOLD); - mvwprintw(center, 1, 1, "CPU(%)"); - mvwprintw(center, 1, 12, "TGID"); - mvwprintw(center, 1, 22, "PID"); - mvwprintw(center, 1, 32, "NAME"); - wattroff(center, A_BOLD); - - max_center_lines = LINES - 7 - 7 - 1 - header_offset; - - /* iterate the process (thread) list */ - for (i = list_offset; i < data->process_table->len && - nblinedisplayed < max_center_lines; i++) { - tmp = g_ptr_array_index(data->process_table, i); - - if (current_line == selected_line) { - selected_process = tmp; - selected_tid = tmp->tid; - selected_comm = tmp->comm; - wattron(center, COLOR_PAIR(5)); - mvwhline(center, current_line + header_offset, 1, ' ', COLS-3); - } - /* CPU(%) */ - mvwprintw(center, current_line + header_offset, 1, "%1.2f", - tmp->totalcpunsec / maxcputime); - /* TGID */ - mvwprintw(center, current_line + header_offset, 12, "%d", tmp->pid); - /* PID */ - mvwprintw(center, current_line + header_offset, 22, "%d", tmp->tid); - /* NAME */ - mvwprintw(center, current_line + header_offset, 32, "%s", tmp->comm); - wattroff(center, COLOR_PAIR(5)); - nblinedisplayed++; - current_line++; - } -} - -gint sort_perf(gconstpointer p1, gconstpointer p2, gpointer key) -{ - struct processtop *n1 = *(struct processtop **) p1; - struct processtop *n2 = *(struct processtop **) p2; - - struct perfcounter *tmp1, *tmp2; - unsigned long totaln2 = 0; - unsigned long totaln1 = 0; - - if (!key) - return 0; - - tmp1 = g_hash_table_lookup(n1->perf, key); - if (!tmp1) - totaln1 = 0; - else - totaln1 = tmp1->count; - - tmp2 = g_hash_table_lookup(n2->perf, key); - if (!tmp2) - totaln2 = 0; - else - totaln2 = tmp2->count; - - if (totaln1 < totaln2) - return 1; - if (totaln1 == totaln2) { - totaln1 = n1->tid; - totaln2 = n2->tid; - if (totaln1 < totaln2) - return 1; - return -1; - } - return -1; -} - -void print_key_title(char *key, int line) -{ - wattron(center, A_BOLD); - mvwprintw(center, line, 1, "%s\t", key); - wattroff(center, A_BOLD); -} - -void update_process_details() -{ - unsigned long elapsed; - double maxcputime; - struct processtop *tmp = find_process_tid(data, selected_tid, selected_comm); - - set_window_title(center, "Process details"); - - - elapsed = data->end - data->start; - maxcputime = elapsed * data->cpu_table->len / 100.0; - - print_key_title("Name", 1); - wprintw(center, "%s", selected_comm); - print_key_title("TID", 2); - wprintw(center, "%d", selected_tid); - if (!tmp) { - print_key_title("Does not exit at this time", 3); - return; - } - - print_key_title("PID", 3); - wprintw(center, "%d", tmp->pid); - print_key_title("PPID", 4); - wprintw(center, "%d", tmp->ppid); - print_key_title("CPU", 5); - wprintw(center, "%1.2f %%", tmp->totalcpunsec/maxcputime); -} - -void update_perf() -{ - int i, j; - int nblinedisplayed = 0; - int current_line = 0; - struct processtop *tmp; - int header_offset = 2; - int perf_row = 40; - struct perfcounter *perfn1, *perfn2; - GList *perflist; - char *perf_key = NULL; - int value; - - set_window_title(center, "Perf Top"); - wattron(center, A_BOLD); - mvwprintw(center, 1, 1, "PID"); - mvwprintw(center, 1, 11, "TID"); - mvwprintw(center, 1, 22, "NAME"); - - perf_row = 40; - perflist = g_list_first(g_hash_table_get_keys(data->perf_list)); - while (perflist) { - perfn1 = g_hash_table_lookup(data->perf_list, perflist->data); - /* + 6 to strip the "_perf_" prefix */ - if (perfn1->visible) { - mvwprintw(center, 1, perf_row, "%s", - (char *) perflist->data + 6); - perf_row += 20; - } - if (perfn1->sort) { - perf_key = (char *) perflist->data; - } - perflist = g_list_next(perflist); - } - wattroff(center, A_BOLD); - - g_ptr_array_sort_with_data(data->process_table, sort_perf, perf_key); - for (i = 0; i < data->process_table->len && - nblinedisplayed < max_center_lines; i++) { - GList *perf_keys; - tmp = g_ptr_array_index(data->process_table, i); - - if (current_line == selected_line) { - selected_process = tmp; - wattron(center, COLOR_PAIR(5)); - mvwhline(center, current_line + header_offset, 1, ' ', COLS-3); - } - - mvwprintw(center, current_line + header_offset, 1, "%d", tmp->pid); - mvwprintw(center, current_line + header_offset, 11, "%d", tmp->tid); - mvwprintw(center, current_line + header_offset, 22, "%s", tmp->comm); - - /* FIXME : sometimes there is a segfault here, I have no idea why :-( */ - perf_keys = g_hash_table_get_keys(data->perf_list); - if (perf_keys) - perflist = g_list_first(perf_keys); - else - perflist = NULL; - - perf_row = 40; - j = 0; - while (perflist) { - j++; - perfn1 = g_hash_table_lookup(data->perf_list, perflist->data); - if (!perfn1) { - perflist = g_list_next(perflist); - continue; - } - if (perfn1->visible) { - perfn2 = g_hash_table_lookup(tmp->perf, perflist->data); - if (perfn2) - value = perfn2->count; - else - value = 0; - mvwprintw(center, current_line + header_offset, perf_row, "%d", value); - perf_row += 20; - } - perflist = g_list_next(perflist); - } - - wattroff(center, COLOR_PAIR(5)); - nblinedisplayed++; - current_line++; - } -} - -void update_fileio() -{ - int i; - int offset; - - set_window_title(center, "IO Top"); - wattron(center, A_BOLD); - mvwprintw(center, 1, 10, "READ"); - mvwprintw(center, 2, 1, "bytes"); - mvwprintw(center, 2, 15, "bytes/sec"); - - mvwprintw(center, 1, 39, "WRITE"); - mvwprintw(center, 2, 33, "bytes"); - mvwprintw(center, 2, 45, "bytes/sec"); - - if (toggle_threads > 0) { - mvwprintw(center, 1, 60, "TGID"); - mvwprintw(center, 1, 70, "PID"); - offset = 8; - } else { - mvwprintw(center, 1, 60, "PID(TGID)"); - offset = 0; - } - mvwprintw(center, 1, 72 + offset, "NAME"); - wattroff(center, A_BOLD); - - for (i = 3; i < LINES - 3 - 8 - 1; i++) { - mvwprintw(center, i, 1, "%d", i*1000); - mvwprintw(center, i, 15, "%dk", i); - mvwprintw(center, i, 28, "| %d", i*2000); - mvwprintw(center, i, 45, "%dk", i*2); - if (toggle_threads > 0) { - mvwprintw(center, i, 57, "| %d", i); - mvwprintw(center, i, 70, "%d", i); - } else { - mvwprintw(center, i, 57, "| %d", i); - } - mvwprintw(center, i, 72 + offset, "process_%d", i); - } -} - -gint sort_by_ret_desc(gconstpointer p1, gconstpointer p2) -{ - struct processtop *n1 = *(struct processtop **)p1; - struct processtop *n2 = *(struct processtop **)p2; - unsigned long totaln1 = n1->iostream->ret_total; - unsigned long totaln2 = n2->iostream->ret_total; - - if (totaln1 < totaln2) - return 1; - if (totaln1 == totaln2) - return 0; - return -1; -} - -void update_iostream() -{ - int i; - int header_offset = 2; - struct processtop *tmp; - int nblinedisplayed = 0; - int current_line = 0; - - set_window_title(center, "IO Top"); - wattron(center, A_BOLD); - mvwprintw(center, 1, 1, "READ (B/s)"); - mvwprintw(center, 1, 20, "WRITE (B/s)"); - - mvwprintw(center, 1, 40, "TOTAL STREAM"); - - mvwprintw(center, 1, 60, "TGID"); - mvwprintw(center, 1, 80, "PID"); - - mvwprintw(center, 1, 92, "NAME"); - wattroff(center, A_BOLD); - - g_ptr_array_sort(data->process_table, sort_by_ret_desc); - - for (i = list_offset; i < data->process_table->len && - nblinedisplayed < max_center_lines; i++) { - tmp = g_ptr_array_index(data->process_table, i); - - if (current_line == selected_line) { - selected_process = tmp; - selected_tid = tmp->tid; - selected_comm = tmp->comm; - wattron(center, COLOR_PAIR(5)); - mvwhline(center, current_line + header_offset, 1, ' ', COLS-3); - } - /* READ (bytes/sec) */ - mvwprintw(center, current_line + header_offset, 1, "%lu", - tmp->iostream->ret_read); - - /* WRITE (bytes/sec) */ - mvwprintw(center, current_line + header_offset, 20, "%lu", - tmp->iostream->ret_write); - - /* TOTAL STREAM */ - if(tmp->iostream->ret_total >= 1000000) - mvwprintw(center, current_line + header_offset, 40, "%lu MB", - tmp->iostream->ret_total/1000000); - else if(tmp->iostream->ret_total >=1000) - mvwprintw(center, current_line + header_offset, 40, "%lu KB", - tmp->iostream->ret_total/1000); - else - mvwprintw(center, current_line + header_offset, 40, "%lu B", - tmp->iostream->ret_total); - /* TGID */ - mvwprintw(center, current_line + header_offset, 60, "%d", tmp->pid); - /* PID */ - mvwprintw(center, current_line + header_offset, 80, "%d", tmp->tid); - /* NAME */ - mvwprintw(center, current_line + header_offset, 92, "%s", tmp->comm); - wattroff(center, COLOR_PAIR(5)); - nblinedisplayed++; - current_line++; - } -} - -void update_current_view() -{ - sem_wait(&update_display_sem); - if (!data) - return; - update_header(); - - werase(center); - box(center, 0, 0); - switch (current_view) { - case cpu: - update_cputop_display(); - break; - case perf: - update_perf(); - break; - case process_details: - update_process_details(); - break; - case fileio: - update_fileio(); - break; - case iostream: - update_iostream(); - break; - case tree: - update_cputop_display(); - break; - default: - break; - } - update_panels(); - doupdate(); - sem_post(&update_display_sem); -} - -void setup_perf_panel() -{ - int size; - if (!data) - return; - if (perf_panel_window) { - del_panel(perf_panel); - delwin(perf_panel_window); - } - size = g_hash_table_size(data->perf_list); - perf_panel_window = create_window(size + 2, 30, 10, 10); - perf_panel = new_panel(perf_panel_window); - perf_panel_visible = 0; - hide_panel(perf_panel); -} - -void update_perf_panel(int line_selected, int toggle_view, int toggle_sort) -{ - int i; - struct perfcounter *perf; - GList *perflist; - - if (!data) - return; - - werase(perf_panel_window); - box(perf_panel_window, 0 , 0); - set_window_title(perf_panel_window, "Perf Preferences "); - wattron(perf_panel_window, A_BOLD); - mvwprintw(perf_panel_window, g_hash_table_size(data->perf_list) + 1, 1, " 's' to sort"); - wattroff(perf_panel_window, A_BOLD); - - if (toggle_sort == 1) { - i = 0; - perflist = g_list_first(g_hash_table_get_keys(data->perf_list)); - while (perflist) { - perf = g_hash_table_lookup(data->perf_list, perflist->data); - if (i != line_selected) - perf->sort = 0; - else - perf->sort = 1; - i++; - perflist = g_list_next(perflist); - } - update_current_view(); - } - - i = 0; - perflist = g_list_first(g_hash_table_get_keys(data->perf_list)); - while (perflist) { - perf = g_hash_table_lookup(data->perf_list, perflist->data); - if (i == line_selected && toggle_view == 1) { - perf->visible = perf->visible == 1 ? 0:1; - update_current_view(); - } - if (i == line_selected) { - wattron(perf_panel_window, COLOR_PAIR(5)); - mvwhline(perf_panel_window, i + 1, 1, ' ', 30 - 2); - } - if (perf->sort == 1) - wattron(perf_panel_window, A_BOLD); - mvwprintw(perf_panel_window, i + 1, 1, "[%c] %s", - perf->visible == 1 ? 'x' : ' ', - (char *) perflist->data + 6); - wattroff(perf_panel_window, A_BOLD); - wattroff(perf_panel_window, COLOR_PAIR(5)); - i++; - perflist = g_list_next(perflist); - } - update_panels(); - doupdate(); -} - - -void toggle_perf_panel(void) -{ - if (perf_panel_visible) { - hide_panel(perf_panel); - perf_panel_visible = 0; - } else { - setup_perf_panel(); - update_perf_panel(perf_line_selected, 0, 0); - show_panel(perf_panel); - perf_panel_visible = 1; - } - update_panels(); - doupdate(); -} - -void display(unsigned int index) -{ - last_display_index = index; - currently_displayed_index = index; - data = g_ptr_array_index(copies, index); - if (!data) - return; - max_elements = data->process_table->len; - update_current_view(); - update_footer(); - update_panels(); - doupdate(); -} - -void pause_display() -{ - toggle_pause = 1; - print_log("Pause"); - sem_wait(&pause_sem); -} - -void resume_display() -{ - toggle_pause = -1; - print_log("Resume"); - sem_post(&pause_sem); -} - -void *handle_keyboard(void *p) -{ - int ch; - while((ch = getch())) { - switch(ch) { - /* Move the cursor and scroll */ - case KEY_DOWN: - if (perf_panel_visible) { - if (perf_line_selected < g_hash_table_size(data->perf_list) - 1) - perf_line_selected++; - update_perf_panel(perf_line_selected, 0, 0); - } else { - if (selected_line < (max_center_lines - 1) && - selected_line < max_elements - 1) { - selected_line++; - selected_in_list++; - } else if (selected_in_list < (max_elements - 1) - && (list_offset < (max_elements - max_center_lines))) { - selected_in_list++; - list_offset++; - } - update_current_view(); - } - break; - case KEY_NPAGE: - if ((selected_line + 10 < max_center_lines - 1) && - ((selected_line + 10) < max_elements - 1)) { - selected_line += 10; - selected_in_list += 10; - } else if (max_elements > max_center_lines) { - selected_line = max_center_lines - 1; - if (selected_in_list + 10 < max_elements - 1) { - selected_in_list += 10; - list_offset += (selected_in_list - max_center_lines + 1); - } - } else if (selected_line + 10 > max_elements) { - selected_line = max_elements - 1; - } - update_current_view(); - break; - case KEY_UP: - if (perf_panel_visible) { - if (perf_line_selected > 0) - perf_line_selected--; - update_perf_panel(perf_line_selected, 0, 0); - } else { - if (selected_line > 0) { - selected_line--; - selected_in_list--; - } else if (selected_in_list > 0 && list_offset > 0) { - selected_in_list--; - list_offset--; - } - update_current_view(); - } - break; - case KEY_PPAGE: - if (selected_line - 10 > 0) - selected_line -= 10; - else - selected_line = 0; - update_current_view(); - break; - - /* Navigate the history with arrows */ - case KEY_LEFT: - if (currently_displayed_index > 0) { - currently_displayed_index--; - print_log("Going back in time"); - } else { - print_log("Cannot rewind, last data is already displayed"); - } - data = g_ptr_array_index(copies, currently_displayed_index); - max_elements = data->process_table->len; - - /* we force to pause the display when moving in time */ - if (toggle_pause < 0) - pause_display(); - - update_current_view(); - update_footer(); - break; - case KEY_RIGHT: - if (currently_displayed_index < last_display_index) { - currently_displayed_index++; - print_log("Going forward in time"); - data = g_ptr_array_index(copies, currently_displayed_index); - max_elements = data->process_table->len; - update_current_view(); - update_footer(); - } else { - print_log("Manually moving forward"); - sem_post(&timer); - /* we force to resume the refresh when moving forward */ - if (toggle_pause > 0) - resume_display(); - } - - break; - case ' ': - if (perf_panel_visible) - update_perf_panel(perf_line_selected, 1, 0); - break; - case 's': - if (perf_panel_visible) - update_perf_panel(perf_line_selected, 0, 1); - break; - - case 13: /* FIXME : KEY_ENTER ?? */ - if (current_view == cpu) { - current_view = process_details; - } - update_current_view(); - break; - - case KEY_F(1): - toggle_tree *= -1; - current_view = cpu; - update_current_view(); - break; - case KEY_F(2): - current_view = cpu; - update_current_view(); - break; - case KEY_F(3): - current_view = perf; - toggle_tree = -1; - update_current_view(); - break; - case KEY_F(4): - current_view = fileio; - toggle_tree = -1; - update_current_view(); - break; - case KEY_F(5): - current_view = netio; - toggle_tree = -1; - update_current_view(); - break; - case KEY_F(6): - current_view = iostream; - toggle_tree = -1; - update_current_view(); - break; - case KEY_F(10): - case 'q': - reset_ncurses(); - break; - case 't': - toggle_threads *= -1; - update_current_view(); - break; - case 'p': - if (toggle_pause < 0) { - pause_display(); - } else { - resume_display(); - } - break; - case 'P': - toggle_perf_panel(); - break; - default: - /* - * commented because it makes the list refresh in different order - * if we sort and there are equal values - if (data) - update_current_view(); - */ - break; - } - update_footer(); - } - return NULL; -} - -void init_ncurses() -{ - sem_init(&update_display_sem, 0, 1); - init_screen(); - - header = create_window(7, COLS - 1, 0, 0); - center = create_window(LINES - 7 - 7, COLS - 1, 7, 0); - status = create_window(MAX_LOG_LINES + 2, COLS - 1, LINES - 7, 0); - footer = create_window(1, COLS - 1, LINES - 1, 0); - - print_log("Starting display"); - - main_panel = new_panel(center); - setup_perf_panel(); - - current_view = cpu; - - basic_header(); - update_footer(); - - pthread_create(&keyboard_thread, NULL, handle_keyboard, (void *)NULL); -} - diff --git a/lttngtop/cursesdisplay.h b/lttngtop/cursesdisplay.h deleted file mode 100644 index 6a54252..0000000 --- a/lttngtop/cursesdisplay.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2011 Julien Desfossez - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License Version 2 as - * published by the Free Software Foundation; - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -#ifndef CURSESDISPLAY_H -#define CURSESDISPLAY_H - -#include -#include -#include "common.h" - -enum current_view_list -{ - cpu = 1, - perf, - process_details, - fileio, - netio, - iostream, - tree, -} current_view; - -void display(unsigned int); -void init_ncurses(); -void reset_ncurses(); - -#endif // CURSESDISPLAY_H diff --git a/lttngtop/iostreamtop.c b/lttngtop/iostreamtop.c deleted file mode 100644 index 1594df7..0000000 --- a/lttngtop/iostreamtop.c +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2011 Mathieu Bain - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License Version 2 as - * published by the Free Software Foundation; - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -#include - -#include "lttngtoptypes.h" -#include "common.h" -#include "iostreamtop.h" - -#include - -int update_iostream_ret(struct lttngtop *ctx, int tid, char *comm, - unsigned long timestamp, int cpu_id, int ret) -{ - struct processtop *tmp; - int err = 0; - - tmp = get_proc(ctx, tid, comm, timestamp); - if ((tmp->iostream->syscall_info != NULL) && (tmp->iostream->syscall_info->cpu_id == cpu_id)) { - if (tmp->iostream->syscall_info->type == __NR_read && ret > 0) { - tmp->iostream->ret_read += ret; - tmp->iostream->ret_total += ret; - } else if(tmp->iostream->syscall_info->type == __NR_write && ret > 0) { - tmp->iostream->ret_write += ret; - tmp->iostream->ret_total += ret; - } else{ - err = -1; - } - free(tmp->iostream->syscall_info); - tmp->iostream->syscall_info = NULL; - } - - return err; -} - -enum bt_cb_ret handle_exit_syscall(struct bt_ctf_event *call_data, - void *private_data) -{ - struct definition *scope; - unsigned long timestamp; - char *comm; - uint64_t ret, tid; - int64_t cpu_id; - - timestamp = bt_ctf_get_timestamp(call_data); - if (timestamp == -1ULL) - goto error; - - scope = bt_ctf_get_top_level_scope(call_data, - BT_STREAM_EVENT_CONTEXT); - comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, - scope, "_procname")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing procname context info\n"); - goto error; - } - - tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, - scope, "_tid")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing tid context info\n"); - goto error; - } - - scope = bt_ctf_get_top_level_scope(call_data, - BT_EVENT_FIELDS); - ret = bt_ctf_get_int64(bt_ctf_get_field(call_data, - scope, "_ret")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing ret context info\n"); - goto error; - } - - scope = bt_ctf_get_top_level_scope(call_data, - BT_STREAM_PACKET_CONTEXT); - cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(call_data, - scope, "cpu_id")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing cpu_id context info\n"); - goto error; - } - - /* - * if we encounter an exit_syscall and it is not for a syscall read or write - * we just abort the execution of this callback - */ - if ((update_iostream_ret(<tngtop, tid, comm, timestamp, cpu_id, ret)) < 0) - return BT_CB_ERROR_CONTINUE; - - return BT_CB_OK; - -error: - return BT_CB_ERROR_STOP; -} - - -enum bt_cb_ret handle_sys_write(struct bt_ctf_event *call_data, - void *private_data) -{ - struct definition *scope; - struct processtop *tmp; - struct syscalls *syscall_info; - unsigned long timestamp; - uint64_t cpu_id; - char *comm; - int64_t tid; - - timestamp = bt_ctf_get_timestamp(call_data); - if (timestamp == -1ULL) - goto error; - - scope = bt_ctf_get_top_level_scope(call_data, - BT_STREAM_EVENT_CONTEXT); - comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, - scope, "_procname")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing procname context info\n"); - goto error; - } - - tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, - scope, "_tid")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing tid context info\n"); - goto error; - } - - scope = bt_ctf_get_top_level_scope(call_data, - BT_STREAM_PACKET_CONTEXT); - cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(call_data, - scope, "cpu_id")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing cpu_id context info\n"); - goto error; - } - - syscall_info = malloc(sizeof(struct syscalls)); - syscall_info->cpu_id = cpu_id; - syscall_info->type = __NR_write; - syscall_info->tid = tid; - tmp = get_proc(<tngtop, tid, comm, timestamp); - tmp->iostream->syscall_info = syscall_info; - - return BT_CB_OK; - -error: - return BT_CB_ERROR_STOP; -} - -enum bt_cb_ret handle_sys_read(struct bt_ctf_event *call_data, - void *private_data) -{ - struct processtop *tmp; - struct definition *scope; - struct syscalls * syscall_info; - unsigned long timestamp; - uint64_t cpu_id; - char *comm; - int64_t tid; - - timestamp = bt_ctf_get_timestamp(call_data); - if (timestamp == -1ULL) - goto error; - - scope = bt_ctf_get_top_level_scope(call_data, - BT_STREAM_EVENT_CONTEXT); - comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, - scope, "_procname")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing procname context info\n"); - goto error; - } - - tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, - scope, "_tid")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing tid context info\n"); - goto error; - } - - scope = bt_ctf_get_top_level_scope(call_data, - BT_STREAM_PACKET_CONTEXT); - cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(call_data, - scope, "cpu_id")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "Missing cpu_id context info\n"); - goto error; - } - - syscall_info = malloc(sizeof(struct syscalls)); - syscall_info->cpu_id = cpu_id; - syscall_info->type = __NR_read; - syscall_info->tid = tid; - tmp = get_proc(<tngtop, tid, comm, timestamp); - tmp->iostream->syscall_info = syscall_info; - - return BT_CB_OK; - -error: - return BT_CB_ERROR_STOP; -} - diff --git a/lttngtop/iostreamtop.h b/lttngtop/iostreamtop.h deleted file mode 100644 index bde4dbf..0000000 --- a/lttngtop/iostreamtop.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2011 Mathieu Bain - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License Version 2 as - * published by the Free Software Foundation; - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -#ifndef _IOSTREANTOP_H -#define _IOSTREAMTOP_H - -#include -#include -#include -#include - -/* -#define SYS_READ 1 -#define SYS_WRITE 2 -*/ - -enum bt_cb_ret handle_exit_syscall(struct bt_ctf_event *call_data, - void *private_data); - -enum bt_cb_ret handle_sys_write(struct bt_ctf_event *call_data, - void *private_data); - -enum bt_cb_ret handle_sys_read(struct bt_ctf_event *call_data, - void *private_data); - -#endif /* _IOSTREAMTOP_H */ diff --git a/lttngtop/lttngtop.c b/lttngtop/lttngtop.c deleted file mode 100644 index 5d526f3..0000000 --- a/lttngtop/lttngtop.c +++ /dev/null @@ -1,572 +0,0 @@ -/* - * Copyright (C) 2011 Julien Desfossez - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License Version 2 as - * published by the Free Software Foundation; - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "lttngtoptypes.h" -#include "cputop.h" -#include "iostreamtop.h" -#include "cursesdisplay.h" -#include "common.h" - -#define DEFAULT_FILE_ARRAY_SIZE 1 - -const char *opt_input_path; - -struct lttngtop *copy; -pthread_t display_thread; -pthread_t timer_thread; - -unsigned long refresh_display = 1 * NSEC_PER_SEC; -unsigned long last_display_update = 0; -int quit = 0; - -enum { - OPT_NONE = 0, - OPT_HELP, - OPT_LIST, - OPT_VERBOSE, - OPT_DEBUG, - OPT_NAMES, -}; - -static struct poptOption long_options[] = { - /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */ - { "help", 'h', POPT_ARG_NONE, NULL, OPT_HELP, NULL, NULL }, - { NULL, 0, 0, NULL, 0, NULL, NULL }, -}; - -void *refresh_thread(void *p) -{ - while (1) { - sem_wait(&pause_sem); - sem_post(&pause_sem); - sem_post(&timer); - sleep(refresh_display/NSEC_PER_SEC); - } -} - -void *ncurses_display(void *p) -{ - unsigned int current_display_index = 0; - - sem_wait(&bootstrap); - init_ncurses(); - - while (1) { - sem_wait(&timer); - sem_wait(&goodtodisplay); - sem_wait(&pause_sem); - - copy = g_ptr_array_index(copies, current_display_index); - if (copy) - display(current_display_index++); - - sem_post(&goodtoupdate); - sem_post(&pause_sem); - - if (quit) { - reset_ncurses(); - pthread_exit(0); - } - } -} - -/* - * hook on each event to check the timestamp and refresh the display if - * necessary - */ -enum bt_cb_ret check_timestamp(struct bt_ctf_event *call_data, void *private_data) -{ - unsigned long timestamp; - - timestamp = bt_ctf_get_timestamp(call_data); - if (timestamp == -1ULL) - goto error; - - if (last_display_update == 0) - last_display_update = timestamp; - - if (timestamp - last_display_update >= refresh_display) { - sem_wait(&goodtoupdate); - g_ptr_array_add(copies, get_copy_lttngtop(last_display_update, - timestamp)); - sem_post(&goodtodisplay); - sem_post(&bootstrap); - last_display_update = timestamp; - } - return BT_CB_OK; - -error: - fprintf(stderr, "check_timestamp callback error\n"); - return BT_CB_ERROR_STOP; -} - -/* - * get_perf_counter : get or create and return a perf_counter struct for - * either a process or a cpu (only one of the 2 parameters mandatory) - */ -struct perfcounter *get_perf_counter(const char *name, struct processtop *proc, - struct cputime *cpu) -{ - struct perfcounter *ret, *global; - GHashTable *table; - - if (proc) - table = proc->perf; - else if (cpu) - table = cpu->perf; - else - goto error; - - ret = g_hash_table_lookup(table, (gpointer) name); - if (ret) - goto end; - - ret = malloc(sizeof(struct perfcounter)); - memset(ret, 0, sizeof(struct perfcounter)); - /* by default, make it visible in the UI */ - ret->visible = 1; - g_hash_table_insert(table, (gpointer) name, ret); - - global = g_hash_table_lookup(lttngtop.perf_list, (gpointer) name); - if (!global) { - global = malloc(sizeof(struct perfcounter)); - memset(global, 0, sizeof(struct perfcounter)); - memcpy(global, ret, sizeof(struct perfcounter)); - /* by default, sort on the first perf context */ - if (g_hash_table_size(lttngtop.perf_list) == 0) - global->sort = 1; - g_hash_table_insert(lttngtop.perf_list, (gpointer) name, global); - } - -end: - return ret; - -error: - return NULL; -} - -void update_perf_value(struct processtop *proc, struct cputime *cpu, - const char *name, int value) -{ - struct perfcounter *cpu_perf, *process_perf; - - cpu_perf = get_perf_counter(name, NULL, cpu); - if (cpu_perf->count < value) { - process_perf = get_perf_counter(name, proc, NULL); - process_perf->count += value - cpu_perf->count; - cpu_perf->count = value; - } -} - -void extract_perf_counter_scope(struct bt_ctf_event *event, - struct definition *scope, - struct processtop *proc, - struct cputime *cpu) -{ - struct definition const * const *list = NULL; - unsigned int count; - int i, ret; - - if (!scope) - goto end; - - ret = bt_ctf_get_field_list(event, scope, &list, &count); - if (ret < 0) - goto end; - - for (i = 0; i < count; i++) { - const char *name = bt_ctf_field_name(list[i]); - if (strncmp(name, "_perf_", 6) == 0) { - int value = bt_ctf_get_uint64(list[i]); - if (bt_ctf_field_get_error()) - continue; - update_perf_value(proc, cpu, name, value); - } - } - -end: - return; -} - -void update_perf_counter(struct processtop *proc, struct bt_ctf_event *event) -{ - struct definition *scope; - uint64_t cpu_id; - struct cputime *cpu; - - scope = bt_ctf_get_top_level_scope(event, BT_STREAM_PACKET_CONTEXT); - cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(event, scope, "cpu_id")); - if (bt_ctf_field_get_error()) { - fprintf(stderr, "[error] get cpu_id\n"); - goto end; - } - cpu = get_cpu(cpu_id); - - scope = bt_ctf_get_top_level_scope(event, BT_STREAM_EVENT_CONTEXT); - extract_perf_counter_scope(event, scope, proc, cpu); - - scope = bt_ctf_get_top_level_scope(event, BT_STREAM_PACKET_CONTEXT); - extract_perf_counter_scope(event, scope, proc, cpu); - - scope = bt_ctf_get_top_level_scope(event, BT_EVENT_CONTEXT); - extract_perf_counter_scope(event, scope, proc, cpu); - -end: - return; -} - -enum bt_cb_ret fix_process_table(struct bt_ctf_event *call_data, - void *private_data) -{ - int pid, tid, ppid; - char *comm; - struct processtop *parent, *child; - struct definition *scope; - unsigned long timestamp; - - /* FIXME : check context pid, tid, ppid and comm */ - - timestamp = bt_ctf_get_timestamp(call_data); - if (timestamp == -1ULL) - goto error; - - scope = bt_ctf_get_top_level_scope(call_data, BT_STREAM_EVENT_CONTEXT); - - pid = bt_ctf_get_int64(bt_ctf_get_field(call_data, scope, "_pid")); - if (bt_ctf_field_get_error()) { -// fprintf(stderr, "Missing pid context info\n"); - goto error; - } - tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, scope, "_tid")); - if (bt_ctf_field_get_error()) { -// fprintf(stderr, "Missing tid context info\n"); - goto error; - } - ppid = bt_ctf_get_int64(bt_ctf_get_field(call_data, scope, "_ppid")); - if (bt_ctf_field_get_error()) { -// fprintf(stderr, "Missing ppid context info\n"); - goto error; - } - comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, scope, "_procname")); - if (bt_ctf_field_get_error()) { -// fprintf(stderr, "Missing procname context info\n"); - goto error; - } - - /* find or create the current process */ - child = find_process_tid(<tngtop, tid, comm); - if (!child) - child = add_proc(<tngtop, tid, comm, timestamp); - update_proc(child, pid, tid, ppid, comm); - - if (pid != tid) { - /* find or create the parent */ - parent = find_process_tid(<tngtop, pid, comm); - if (!parent) { - parent = add_proc(<tngtop, pid, comm, timestamp); - parent->pid = pid; - } - - /* attach the parent to the current process */ - child->threadparent = parent; - add_thread(parent, child); - } - - update_perf_counter(child, call_data); - - return BT_CB_OK; - -error: - return BT_CB_ERROR_STOP; -} - -void init_lttngtop() -{ - copies = g_ptr_array_new(); - lttngtop.perf_list = g_hash_table_new(g_direct_hash, g_direct_equal); - - sem_init(&goodtodisplay, 0, 0); - sem_init(&goodtoupdate, 0, 1); - sem_init(&timer, 0, 1); - sem_init(&bootstrap, 0, 0); - sem_init(&pause_sem, 0, 1); - sem_init(&end_trace_sem, 0, 0); - - lttngtop.process_table = g_ptr_array_new(); - lttngtop.files_table = g_ptr_array_new(); - lttngtop.cpu_table = g_ptr_array_new(); -} - -void usage(FILE *fd) -{ - -} - -/* - * Return 0 if caller should continue, < 0 if caller should return - * error, > 0 if caller should exit without reporting error. - */ -static int parse_options(int argc, char **argv) -{ - poptContext pc; - int opt, ret = 0; - - if (argc == 1) { - usage(stdout); - return 1; /* exit cleanly */ - } - - pc = poptGetContext(NULL, argc, (const char **) argv, long_options, 0); - poptReadDefaultConfig(pc, 0); - - while ((opt = poptGetNextOpt(pc)) != -1) { - switch (opt) { - case OPT_HELP: - usage(stdout); - ret = 1; /* exit cleanly */ - goto end; - case OPT_LIST: - // list_formats(stdout); - ret = 1; - goto end; - case OPT_VERBOSE: -// babeltrace_verbose = 1; - break; - case OPT_DEBUG: -// babeltrace_debug = 1; - break; - case OPT_NAMES: -// opt_field_names = 1; - break; - default: - ret = -EINVAL; - goto end; - } - } - - opt_input_path = poptGetArg(pc); - if (!opt_input_path) { - ret = -EINVAL; - goto end; - } -end: - if (pc) { - poptFreeContext(pc); - } - return ret; -} - -void iter_trace(struct bt_context *bt_ctx) -{ - struct bt_ctf_iter *iter; - struct bt_iter_pos begin_pos; - struct bt_ctf_event *event; - int ret = 0; - - begin_pos.type = BT_SEEK_BEGIN; - iter = bt_ctf_iter_create(bt_ctx, &begin_pos, NULL); - - /* at each event check if we need to refresh */ - bt_ctf_iter_add_callback(iter, 0, NULL, 0, - check_timestamp, - NULL, NULL, NULL); - /* at each event, verify the status of the process table */ - bt_ctf_iter_add_callback(iter, 0, NULL, 0, - fix_process_table, - NULL, NULL, NULL); - /* to handle the scheduling events */ - bt_ctf_iter_add_callback(iter, - g_quark_from_static_string("sched_switch"), - NULL, 0, handle_sched_switch, NULL, NULL, NULL); - /* to clean up the process table */ - bt_ctf_iter_add_callback(iter, - g_quark_from_static_string("sched_process_free"), - NULL, 0, handle_sched_process_free, NULL, NULL, NULL); - - /* for IO top */ - bt_ctf_iter_add_callback(iter, - g_quark_from_static_string("exit_syscall"), - NULL, 0, handle_exit_syscall, NULL, NULL, NULL); - bt_ctf_iter_add_callback(iter, - g_quark_from_static_string("sys_write"), - NULL, 0, handle_sys_write, NULL, NULL, NULL); - bt_ctf_iter_add_callback(iter, - g_quark_from_static_string("sys_read"), - NULL, 0, handle_sys_read, NULL, NULL, NULL); - while ((event = bt_ctf_iter_read_event(iter)) != NULL) { - ret = bt_iter_next(bt_ctf_get_iter(iter)); - if (ret < 0) - goto end_iter; - } - - /* block until quit, we reached the end of the trace */ - sem_wait(&end_trace_sem); - -end_iter: - bt_iter_destroy(bt_ctf_get_iter(iter)); -} - -/* - * bt_context_add_traces_recursive: Open a trace recursively - * (copied from BSD code in converter/babeltrace.c) - * - * Find each trace present in the subdirectory starting from the given - * path, and add them to the context. The packet_seek parameter can be - * NULL: this specify to use the default format packet_seek. - * - * Return: 0 on success, nonzero on failure. - * Unable to open toplevel: failure. - * Unable to open some subdirectory or file: warn and continue; - */ -int bt_context_add_traces_recursive(struct bt_context *ctx, const char *path, - const char *format_str, - void (*packet_seek)(struct stream_pos *pos, - size_t offset, int whence)) -{ - FTS *tree; - FTSENT *node; - GArray *trace_ids; - char lpath[PATH_MAX]; - char * const paths[2] = { lpath, NULL }; - int ret; - - /* - * Need to copy path, because fts_open can change it. - * It is the pointer array, not the strings, that are constant. - */ - strncpy(lpath, path, PATH_MAX); - lpath[PATH_MAX - 1] = '\0'; - - tree = fts_open(paths, FTS_NOCHDIR | FTS_LOGICAL, 0); - if (tree == NULL) { - fprintf(stderr, "[error] [Context] Cannot traverse \"%s\" for reading.\n", - path); - return -EINVAL; - } - - trace_ids = g_array_new(FALSE, TRUE, sizeof(int)); - - while ((node = fts_read(tree))) { - int dirfd, metafd; - - if (!(node->fts_info & FTS_D)) - continue; - - dirfd = open(node->fts_accpath, 0); - if (dirfd < 0) { - fprintf(stderr, "[error] [Context] Unable to open trace " - "directory file descriptor.\n"); - ret = dirfd; - goto error; - } - metafd = openat(dirfd, "metadata", O_RDONLY); - if (metafd < 0) { - ret = close(dirfd); - if (ret < 0) { - perror("close"); - goto error; - } - } else { - int trace_id; - - ret = close(metafd); - if (ret < 0) { - perror("close"); - goto error; - } - ret = close(dirfd); - if (ret < 0) { - perror("close"); - goto error; - } - - trace_id = bt_context_add_trace(ctx, - node->fts_accpath, format_str, - packet_seek, NULL, NULL); - if (trace_id < 0) { - fprintf(stderr, "[error] [Context] opening trace \"%s\" from %s " - "for reading.\n", node->fts_accpath, path); - ret = trace_id; - goto error; - } - g_array_append_val(trace_ids, trace_id); - } - } - - g_array_free(trace_ids, TRUE); - return 0; - -error: - return ret; -} - -int main(int argc, char **argv) -{ - int ret; - struct bt_context *bt_ctx = NULL; - - ret = parse_options(argc, argv); - if (ret < 0) { - fprintf(stdout, "Error parsing options.\n\n"); - usage(stdout); - exit(EXIT_FAILURE); - } else if (ret > 0) { - exit(EXIT_SUCCESS); - } - - init_lttngtop(); - - bt_ctx = bt_context_create(); - ret = bt_context_add_traces_recursive(bt_ctx, opt_input_path, "ctf", NULL); - if (ret < 0) { - printf("[error] Opening the trace\n"); - goto end; - } - - pthread_create(&display_thread, NULL, ncurses_display, (void *) NULL); - pthread_create(&timer_thread, NULL, refresh_thread, (void *) NULL); - - iter_trace(bt_ctx); - - quit = 1; - pthread_join(display_thread, NULL); - -end: - return 0; -} diff --git a/lttngtop/lttngtoptypes.h b/lttngtop/lttngtoptypes.h deleted file mode 100644 index f10a684..0000000 --- a/lttngtop/lttngtoptypes.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2011 Julien Desfossez - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License Version 2 as - * published by the Free Software Foundation; - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, - * MA 02111-1307, USA. - */ - -#ifndef LTTNGTOPTYPES_H -#define LTTNGTOPTYPES_H - -#include - -struct lttngtop { - GPtrArray *process_table; /* struct processtop */ - GPtrArray *files_table; /* struct files */ - GPtrArray *cpu_table; /* struct cputime */ - GHashTable *perf_list; /* struct perfcounter */ - unsigned long start; - unsigned long end; - unsigned int nbproc; - unsigned int nbnewproc; - unsigned int nbdeadproc; - unsigned int nbthreads; - unsigned int nbnewthreads; - unsigned int nbdeadthreads; - unsigned int nbfiles; - unsigned int nbnewfiles; - unsigned int nbclosedfiles; -} lttngtop; - -struct processtop { - unsigned int puuid; - int pid; - char *comm; - int tid; - int ppid; - int oldpid; - int oldtid; - int oldppid; - unsigned long birth; - unsigned long death; - unsigned long lastactivity; - GPtrArray *process_files_table; - GPtrArray *threads; - GHashTable *perf; - struct processtop *threadparent; - unsigned long totalfileread; - unsigned long totalfilewrite; - unsigned long totalcpunsec; - unsigned long threadstotalcpunsec; - /* IO speed for this process */ - struct iostream *iostream; -}; - -struct perfcounter -{ - unsigned long count; - int visible; - int sort; -}; - -struct cputime { - guint id; - struct processtop *current_task; - unsigned long task_start; - GHashTable *perf; -}; - -/* - * used for "relative seeks" (with fd, for example fs.lseek) - * and for "absolute seeks" (events occuring on a device without - * any link to a particular process) - */ -struct seeks { - unsigned long offset; - unsigned long count; -}; - -struct ioctls { - unsigned int command; - unsigned long count; -}; - -struct files { - struct processtop *ref; - unsigned int fuuid; - int fd; - char *name; - int oldfd; - int device; - int openmode; - unsigned long openedat; - unsigned long closedat; - unsigned long lastaccess; - unsigned long read; - unsigned long write; - unsigned long nbpoll; - unsigned long nbselect; - unsigned long nbopen; - unsigned long nbclose; - //struct *seeks; /* relative seeks inside the file */ - //struct *ioctls; - /* XXX : average wait time */ -}; - -struct sockets { - int fd; - int parent_fd; /* on accept a new fd is created from the bound socket */ - int family; - int type; - int protocol; - int sock_address; - unsigned long openedat; - unsigned long closedat; - unsigned long bind_address; - unsigned long remote_address; - //struct *sock_options; -}; - -struct sock_options { - int name; - int value; -}; - -struct vmas { - unsigned long start; - unsigned long end; - unsigned long flags; - unsigned long prot; - char *description; /* filename or description if possible (stack, heap) */ - unsigned long page_faults; -}; - -struct syscalls { - unsigned int id; - unsigned long count; - unsigned int cpu_id; - unsigned int type; - unsigned int tid; -}; - -struct signals { - int dest_pid; - int id; - unsigned long count; -}; - -struct iostream { - struct syscalls *syscall_info; /* NULL if there is no waiting for an exit_syscall */ - unsigned long ret_read; /* value returned by an I/O syscall_exit for a sys_read*/ - unsigned long ret_write; /* value returned by an I/O syscall_exit for a sys_write*/ - unsigned long ret_total; -}; - -#endif /* LTTNGTOPTYPES_H */ diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..5d6ca3e --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,12 @@ +AM_CFLAGS = $(PACKAGE_CFLAGS) + +bin_PROGRAMS = lttngtop + +lttngtop_SOURCES = \ + lttngtop.c \ + common.c \ + cursesdisplay.c \ + cputop.c \ + iostreamtop.c + +lttngtop_LDADD = -lbabeltrace -lctf diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..2a0f1f4 --- /dev/null +++ b/src/common.c @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include +#include +#include "common.h" + +struct processtop *find_process_tid(struct lttngtop *ctx, int tid, char *comm) +{ + gint i; + struct processtop *tmp; + + for (i = 0; i < ctx->process_table->len; i++) { + tmp = g_ptr_array_index(ctx->process_table, i); + if (tmp && tmp->tid == tid) + return tmp; + } + return NULL; +} + +struct processtop* add_proc(struct lttngtop *ctx, int tid, char *comm, + unsigned long timestamp) +{ + struct processtop *newproc; + + /* if the PID already exists, we just rename the process */ + /* FIXME : need to integrate with clone/fork/exit to be accurate */ + newproc = find_process_tid(ctx, tid, comm); + if (!newproc) { + newproc = malloc(sizeof(struct processtop)); + memset(newproc, 0, sizeof(struct processtop)); + newproc->tid = tid; + newproc->birth = timestamp; + newproc->process_files_table = g_ptr_array_new(); + newproc->threads = g_ptr_array_new(); + newproc->perf = g_hash_table_new(g_direct_hash, g_direct_equal); + newproc->iostream = malloc(sizeof(struct iostream)); + newproc->iostream->ret_read = 0; + newproc->iostream->ret_write = 0; + newproc->iostream->ret_total = 0; + newproc->iostream->syscall_info = NULL; + g_ptr_array_add(ctx->process_table, newproc); + } + newproc->comm = strdup(comm); + + return newproc; +} + +struct processtop* update_proc(struct processtop* proc, int pid, int tid, + int ppid, char *comm) +{ + if (proc) { + proc->pid = pid; + proc->tid = tid; + proc->ppid = ppid; + if (strcmp(proc->comm, comm) != 0) { + free(proc->comm); + proc->comm = strdup(comm); + } + } + return proc; +} + +/* + * This function just sets the time of death of a process. + * When we rotate the cputime we remove it from the process list. + */ +void death_proc(struct lttngtop *ctx, int tid, char *comm, + unsigned long timestamp) +{ + struct processtop *tmp; + tmp = find_process_tid(ctx, tid, comm); + if (tmp && strcmp(tmp->comm, comm) == 0) + tmp->death = timestamp; +} + +struct processtop* get_proc(struct lttngtop *ctx, int tid, char *comm, + unsigned long timestamp) +{ + struct processtop *tmp; + tmp = find_process_tid(ctx, tid, comm); + if (tmp && strcmp(tmp->comm, comm) == 0) + return tmp; + return add_proc(ctx, tid, comm, timestamp); +} + +void add_thread(struct processtop *parent, struct processtop *thread) +{ + gint i; + struct processtop *tmp; + + for (i = 0; i < parent->threads->len; i++) { + tmp = g_ptr_array_index(parent->threads, i); + if (tmp == thread) + return; + } + g_ptr_array_add(parent->threads, thread); +} + +struct cputime* add_cpu(int cpu) +{ + struct cputime *newcpu; + + newcpu = malloc(sizeof(struct cputime)); + newcpu->id = cpu; + newcpu->current_task = NULL; + newcpu->perf = g_hash_table_new(g_direct_hash, g_direct_equal); + + g_ptr_array_add(lttngtop.cpu_table, newcpu); + + return newcpu; +} +struct cputime* get_cpu(int cpu) +{ + gint i; + struct cputime *tmp; + + for (i = 0; i < lttngtop.cpu_table->len; i++) { + tmp = g_ptr_array_index(lttngtop.cpu_table, i); + if (tmp->id == cpu) + return tmp; + } + + return add_cpu(cpu); +} + +/* + * At the end of a sampling period, we need to display the cpu time for each + * process and to reset it to zero for the next period + */ +void rotate_cputime(unsigned long end) +{ + gint i; + struct cputime *tmp; + unsigned long elapsed; + + for (i = 0; i < lttngtop.cpu_table->len; i++) { + tmp = g_ptr_array_index(lttngtop.cpu_table, i); + elapsed = end - tmp->task_start; + if (tmp->current_task) { + tmp->current_task->totalcpunsec += elapsed; + tmp->current_task->threadstotalcpunsec += elapsed; + if (tmp->current_task->pid != tmp->current_task->tid && + tmp->current_task->threadparent) { + tmp->current_task->threadparent->threadstotalcpunsec += elapsed; + } + } + tmp->task_start = end; + } +} + +void reset_perf_counter(gpointer key, gpointer value, gpointer user_data) +{ + ((struct perfcounter*) value)->count = 0; +} + +void copy_perf_counter(gpointer key, gpointer value, gpointer new_table) +{ + struct perfcounter *newperf; + + newperf = malloc(sizeof(struct perfcounter)); + newperf->count = ((struct perfcounter *) value)->count; + newperf->visible = ((struct perfcounter *) value)->visible; + newperf->sort = ((struct perfcounter *) value)->sort; + g_hash_table_insert((GHashTable *) new_table, key, newperf); +} + +void rotate_perfcounter() { + int i; + struct processtop *tmp; + for (i = 0; i < lttngtop.process_table->len; i++) { + tmp = g_ptr_array_index(lttngtop.process_table, i); + g_hash_table_foreach(tmp->perf, reset_perf_counter, NULL); + } +} + +void cleanup_processtop() +{ + gint i; + struct processtop *tmp; + + for (i = 0; i < lttngtop.process_table->len; i++) { + tmp = g_ptr_array_index(lttngtop.process_table, i); + tmp->totalcpunsec = 0; + tmp->threadstotalcpunsec = 0; + tmp->iostream->ret_read = 0; + tmp->iostream->ret_write = 0; + } +} + +struct lttngtop* get_copy_lttngtop(unsigned long start, unsigned long end) +{ + gint i, j; + unsigned long time; + struct lttngtop *dst; + struct processtop *tmp, *tmp2, *new; + struct cputime *tmpcpu, *newcpu; + struct files *tmpfile, *newfile; + + dst = malloc(sizeof(struct lttngtop)); + dst = memset(dst, 0, sizeof(struct lttngtop)); + dst->start = start; + dst->end = end; + dst->process_table = g_ptr_array_new(); + dst->files_table = g_ptr_array_new(); + dst->cpu_table = g_ptr_array_new(); + dst->perf_list = g_hash_table_new(g_direct_hash, g_direct_equal); + + rotate_cputime(end); + + g_hash_table_foreach(lttngtop.perf_list, copy_perf_counter, dst->perf_list); + for (i = 0; i < lttngtop.process_table->len; i++) { + tmp = g_ptr_array_index(lttngtop.process_table, i); + new = malloc(sizeof(struct processtop)); + + memcpy(new, tmp, sizeof(struct processtop)); + new->threads = g_ptr_array_new(); + new->comm = strdup(tmp->comm); + new->process_files_table = g_ptr_array_new(); + new->perf = g_hash_table_new(g_direct_hash, g_direct_equal); + g_hash_table_foreach(tmp->perf, copy_perf_counter, new->perf); + + new->iostream = malloc(sizeof(struct iostream)); + memcpy(new->iostream, tmp->iostream, sizeof(struct iostream)); + /* compute the stream speed */ + if (end - start != 0) + { + time = (end - start)/NSEC_PER_SEC; + new->iostream->ret_read = new->iostream->ret_read/(time); + new->iostream->ret_write = new->iostream->ret_write/(time); + } + + for (j = 0; j < tmp->process_files_table->len; j++) { + tmpfile = g_ptr_array_index(tmp->process_files_table, j); + newfile = malloc(sizeof(struct files)); + + memcpy(newfile, tmpfile, sizeof(struct files)); + + newfile->name = strdup(tmpfile->name); + newfile->ref = new; + + g_ptr_array_add(new->process_files_table, newfile); + g_ptr_array_add(dst->files_table, newfile); + + /* + * if the process died during the last period, we remove all + * files associated with if after the copy + */ + if (tmp->death > 0 && tmp->death < end) { + g_ptr_array_remove(tmp->process_files_table, tmpfile); + free(tmpfile); + } + } + g_ptr_array_add(dst->process_table, new); + + /* + * if the process died during the last period, we remove it from + * the current process list after the copy + */ + if (tmp->death > 0 && tmp->death < end) { + g_ptr_array_remove(lttngtop.process_table, tmp); + g_ptr_array_free(tmp->threads, TRUE); + free(tmp->comm); + g_ptr_array_free(tmp->process_files_table, TRUE); + g_hash_table_destroy(tmp->perf); + free(tmp); + } + } + rotate_perfcounter(); + + for (i = 0; i < lttngtop.cpu_table->len; i++) { + tmpcpu = g_ptr_array_index(lttngtop.cpu_table, i); + newcpu = malloc(sizeof(struct cputime)); + memcpy(newcpu, tmpcpu, sizeof(struct cputime)); + newcpu->perf = g_hash_table_new(g_direct_hash, g_direct_equal); + g_hash_table_foreach(tmpcpu->perf, copy_perf_counter, newcpu->perf); + /* + * note : we don't care about the current process pointer in the copy + * so the reference is invalid after the memcpy + */ + g_ptr_array_add(dst->cpu_table, newcpu); + } + /* create the threads index if required */ + for (i = 0; i < dst->process_table->len; i++) { + tmp = g_ptr_array_index(dst->process_table, i); + if (tmp->pid == tmp->tid) { + for (j = 0; j < dst->process_table->len; j++) { + tmp2 = g_ptr_array_index(dst->process_table, j); + if (tmp2->pid == tmp->pid) { + tmp2->threadparent = tmp; + g_ptr_array_add(tmp->threads, tmp2); + } + } + } + } + + // update_global_stats(dst); + cleanup_processtop(); + + return dst; +} + diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..e81256c --- /dev/null +++ b/src/common.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _COMMON_H +#define _COMMON_H + +#include +#include +#include "lttngtoptypes.h" +#include "cputop.h" + +#define NSEC_PER_USEC 1000 +#define NSEC_PER_SEC 1000000000L + +sem_t goodtodisplay, goodtoupdate, timer, pause_sem, end_trace_sem, bootstrap; + +GPtrArray *copies; /* struct lttngtop */ +pthread_mutex_t perf_list_mutex; + +struct lttngtop *data; + +struct processtop *find_process_tid(struct lttngtop *ctx, int pid, char *comm); +struct processtop* add_proc(struct lttngtop *ctx, int pid, char *comm, + unsigned long timestamp); +struct processtop* update_proc(struct processtop* proc, int pid, int tid, + int ppid, char *comm); +void add_thread(struct processtop *parent, struct processtop *thread); +struct processtop* get_proc(struct lttngtop *ctx, int tid, char *comm, + unsigned long timestamp); +void death_proc(struct lttngtop *ctx, int tid, char *comm, + unsigned long timestamp); +struct cputime* add_cpu(int cpu); +struct cputime* get_cpu(int cpu); +struct lttngtop* get_copy_lttngtop(unsigned long start, unsigned long end); +struct perfcounter *add_perf_counter(GPtrArray *perf, GQuark quark, + unsigned long count); +struct perfcounter *get_perf_counter(const char *name, struct processtop *proc, + struct cputime *cpu); + +#endif /* _COMMON_H */ diff --git a/src/cputop.c b/src/cputop.c new file mode 100644 index 0000000..2ade0db --- /dev/null +++ b/src/cputop.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include + +#include "lttngtoptypes.h" +#include "common.h" +#include "cputop.h" + +void update_cputop_data(unsigned long timestamp, int64_t cpu, int prev_pid, + int next_pid, char *prev_comm, char *next_comm) +{ + struct cputime *tmpcpu; + unsigned long elapsed; + + tmpcpu = get_cpu(cpu); + + if (tmpcpu->current_task && tmpcpu->current_task->pid == prev_pid) { + elapsed = timestamp - tmpcpu->task_start; + tmpcpu->current_task->totalcpunsec += elapsed; + tmpcpu->current_task->threadstotalcpunsec += elapsed; + if (tmpcpu->current_task->pid != tmpcpu->current_task->tid) + tmpcpu->current_task->threadparent->threadstotalcpunsec += elapsed; + } + + if (next_pid != 0) + tmpcpu->current_task = get_proc(<tngtop, next_pid, next_comm, timestamp); + else + tmpcpu->current_task = NULL; + + tmpcpu->task_start = timestamp; +} + +enum bt_cb_ret handle_sched_switch(struct bt_ctf_event *call_data, + void *private_data) +{ + struct definition *scope; + unsigned long timestamp; + uint64_t cpu_id; + char *prev_comm, *next_comm; + int prev_tid, next_tid; + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + scope = bt_ctf_get_top_level_scope(call_data, + BT_EVENT_FIELDS); + prev_comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, + scope, "_prev_comm")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing prev_comm context info\n"); + goto error; + } + + next_comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, + scope, "_next_comm")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing next_comm context info\n"); + goto error; + } + + prev_tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_prev_tid")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing prev_tid context info\n"); + goto error; + } + + next_tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_next_tid")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing next_tid context info\n"); + goto error; + } + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_PACKET_CONTEXT); + cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(call_data, + scope, "cpu_id")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing cpu_id context info\n"); + goto error; + } + + update_cputop_data(timestamp, cpu_id, prev_tid, next_tid, + prev_comm, next_comm); + + return BT_CB_OK; + +error: + return BT_CB_ERROR_STOP; +} + +enum bt_cb_ret handle_sched_process_free(struct bt_ctf_event *call_data, + void *private_data) +{ + struct definition *scope; + unsigned long timestamp; + char *comm; + int tid; + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + scope = bt_ctf_get_top_level_scope(call_data, + BT_EVENT_FIELDS); + comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, + scope, "_comm")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing procname context info\n"); + goto error; + } + + tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_tid")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing tid context info\n"); + goto error; + } + + death_proc(<tngtop, tid, comm, timestamp); + + return BT_CB_OK; + +error: + return BT_CB_ERROR_STOP; + +} + diff --git a/src/cputop.h b/src/cputop.h new file mode 100644 index 0000000..8b57823 --- /dev/null +++ b/src/cputop.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _LTTNGTOP_H +#define _LTTNGTOP_H + +#include +#include +#include +#include + +enum bt_cb_ret handle_sched_switch(struct bt_ctf_event *hook_data, + void *call_data); + +enum bt_cb_ret handle_sched_process_free(struct bt_ctf_event *call_data, + void *private_data); + +#endif /* _LTTNGTOP_H */ diff --git a/src/cursesdisplay.c b/src/cursesdisplay.c new file mode 100644 index 0000000..0f79485 --- /dev/null +++ b/src/cursesdisplay.c @@ -0,0 +1,1003 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cursesdisplay.h" +#include "lttngtoptypes.h" +#include "common.h" + +#define DEFAULT_DELAY 15 +#define MAX_LINE_LENGTH 50 +#define MAX_LOG_LINES 4 + +/* to prevent concurrent updates of the different windows */ +sem_t update_display_sem; + +char *termtype; +WINDOW *footer, *header, *center, *status; +WINDOW *perf_panel_window = NULL; +PANEL *perf_panel, *main_panel; + +int perf_panel_visible = 0; +int perf_line_selected = 0; + +int last_display_index, currently_displayed_index; + +struct processtop *selected_process = NULL; +int selected_tid; +char *selected_comm; +int selected_ret; + +int selected_line = 0; /* select bar position */ +int selected_in_list = 0; /* selection relative to the whole list */ +int list_offset = 0; /* first index in the list to display (scroll) */ +int nb_log_lines = 0; +char log_lines[MAX_LINE_LENGTH * MAX_LOG_LINES + MAX_LOG_LINES]; + +int max_elements = 80; + +int toggle_threads = -1; +int toggle_pause = -1; +int toggle_tree = -1; + +int max_center_lines; + +pthread_t keyboard_thread; + +void reset_ncurses() +{ + curs_set(1); + endwin(); + exit(0); +} + +static void handle_sigterm(int signal) +{ + reset_ncurses(); +} + +void init_screen() +{ + initscr(); + noecho(); + halfdelay(DEFAULT_DELAY); + nonl(); + intrflush(stdscr, false); + keypad(stdscr, true); + curs_set(0); + + if (has_colors()) { + start_color(); + init_pair(1, COLOR_RED, COLOR_BLACK); /* - */ + init_pair(2, COLOR_GREEN, COLOR_BLACK); /* + */ + init_pair(3, COLOR_BLACK, COLOR_WHITE); /* keys */ + init_pair(4, COLOR_WHITE, COLOR_GREEN); /* keys activated */ + init_pair(5, COLOR_WHITE, COLOR_BLUE); /* select line */ + } + termtype = getenv("TERM"); + if (!strcmp(termtype, "xterm") || !strcmp(termtype, "xterm-color") || + !strcmp(termtype, "vt220")) { + define_key("\033[H", KEY_HOME); + define_key("\033[F", KEY_END); + define_key("\033OP", KEY_F(1)); + define_key("\033OQ", KEY_F(2)); + define_key("\033OR", KEY_F(3)); + define_key("\033OS", KEY_F(4)); + define_key("\0330U", KEY_F(6)); + define_key("\033[11~", KEY_F(1)); + define_key("\033[12~", KEY_F(2)); + define_key("\033[13~", KEY_F(3)); + define_key("\033[14~", KEY_F(4)); + define_key("\033[16~", KEY_F(6)); + define_key("\033[17;2~", KEY_F(18)); + } + signal(SIGTERM, handle_sigterm); + mousemask(BUTTON1_CLICKED, NULL); + refresh(); +} + +WINDOW *create_window(int height, int width, int startx, int starty) +{ + WINDOW *win; + win = newwin(height, width, startx, starty); + box(win, 0 , 0); + wrefresh(win); + return win; +} + +WINDOW *create_window_no_border(int height, int width, int startx, int starty) +{ + WINDOW *win; + win = newwin(height, width, startx, starty); + wrefresh(win); + return win; +} + +void print_digit(WINDOW *win, int digit) +{ + if (digit < 0) { + wattron(win, COLOR_PAIR(1)); + wprintw(win, "%d", digit); + wattroff(win, COLOR_PAIR(1)); + } else if (digit > 0) { + wattron(win, COLOR_PAIR(2)); + wprintw(win, "+%d", digit); + wattroff(win, COLOR_PAIR(2)); + } else { + wprintw(win, "0"); + } +} + +void print_digits(WINDOW *win, int first, int second) +{ + wprintw(win, "("); + print_digit(win, first); + wprintw(win, ", "); + print_digit(win, second); + wprintw(win, ")"); +} + +void print_headers(int line, char *desc, int value, int first, int second) +{ + wattron(header, A_BOLD); + mvwprintw(header, line, 4, "%s", desc); + wattroff(header, A_BOLD); + mvwprintw(header, line, 16, "N/A", value); + wmove(header, line, 24); + print_digits(header, first, second); + wmove(header, line, 40); +} + +void set_window_title(WINDOW *win, char *title) +{ + wattron(win, A_BOLD); + mvwprintw(win, 0, 1, title); + wattroff(win, A_BOLD); +} + +void print_log(char *str) +{ + int i; + int current_line = 1; + int current_char = 1; + char *tmp, *tmp2; + /* rotate the line buffer */ + if (nb_log_lines >= MAX_LOG_LINES) { + tmp = strndup(log_lines, MAX_LINE_LENGTH * MAX_LOG_LINES + MAX_LOG_LINES); + tmp2 = strchr(tmp, '\n'); + memset(log_lines, '\0', strlen(log_lines)); + strncat(log_lines, tmp2 + 1, strlen(tmp2) - 1); + log_lines[strlen(log_lines)] = '\n'; + log_lines[strlen(log_lines)] = '\0'; + free(tmp); + } + nb_log_lines++; + + strncat(log_lines, str, MAX_LINE_LENGTH - 1); + + if (nb_log_lines < MAX_LOG_LINES) + log_lines[strlen(log_lines)] = '\n'; + log_lines[strlen(log_lines)] = '\0'; + + werase(status); + box(status, 0 , 0); + set_window_title(status, "Status"); + for (i = 0; i < strlen(log_lines); i++) { + if (log_lines[i] == '\n') { + wmove(status, ++current_line, 1); + current_char = 1; + } else { + mvwprintw(status, current_line, current_char++, "%c", log_lines[i]); + } + } + wrefresh(status); +} + +void print_key(WINDOW *win, char *key, char *desc, int toggle) +{ + int pair; + if (toggle > 0) + pair = 4; + else + pair = 3; + wattron(win, COLOR_PAIR(pair)); + wprintw(footer, "%s", key); + wattroff(win, COLOR_PAIR(pair)); + wprintw(footer, ":%s", desc); +} + +void update_footer() +{ + sem_wait(&update_display_sem); + werase(footer); + wmove(footer, 1, 1); + print_key(footer, "F2", "CPUtop ", current_view == cpu); + print_key(footer, "F3", "PerfTop ", current_view == perf); + print_key(footer, "F6", "IOTop ", current_view == iostream); + print_key(footer, "Enter", "Details ", current_view == process_details); + print_key(footer, "q", "Quit | ", 0); + print_key(footer, "P", "Perf Pref ", 0); + print_key(footer, "p", "Pause ", toggle_pause); + + wrefresh(footer); + sem_post(&update_display_sem); +} + +void basic_header() +{ + werase(header); + box(header, 0 , 0); + set_window_title(header, "Statistics for interval [gathering data...["); + wattron(header, A_BOLD); + mvwprintw(header, 1, 4, "CPUs"); + mvwprintw(header, 2, 4, "Processes"); + mvwprintw(header, 3, 4, "Threads"); + mvwprintw(header, 4, 4, "Files"); + mvwprintw(header, 5, 4, "Network"); + mvwprintw(header, 6, 4, "IO"); + wattroff(header, A_BOLD); + wrefresh(header); +} + +void update_header() +{ + werase(header); + box(header, 0 , 0); + set_window_title(header, "Statistics for interval "); + wattron(header, A_BOLD); + /* + wprintw(header, "[%lu.%lu, %lu.%lu[", + data->start.tv_sec, data->start.tv_nsec, + data->end.tv_sec, data->end.tv_nsec); + */ + wprintw(header, "[%lu, %lu[", + data->start, + data->end); + mvwprintw(header, 1, 4, "CPUs"); + wattroff(header, A_BOLD); + wprintw(header, "\t%d\t(max/cpu : %0.2f%)", data->cpu_table->len, + 100.0/data->cpu_table->len); + print_headers(2, "Processes", data->nbproc, data->nbnewproc, + -1*(data->nbdeadproc)); + print_headers(3, "Threads", data->nbthreads, data->nbnewthreads, + -1*(data->nbdeadthreads)); + print_headers(4, "Files", data->nbfiles, data->nbnewfiles, + -1*(data->nbclosedfiles)); + mvwprintw(header, 4, 43, "N/A kbytes/sec"); + print_headers(5, "Network", 114, 0, 0); + mvwprintw(header, 5, 43, "N/A Mbytes/sec"); + wrefresh(header); +} + +gint sort_by_cpu_desc(gconstpointer p1, gconstpointer p2) +{ + struct processtop *n1 = *(struct processtop **)p1; + struct processtop *n2 = *(struct processtop **)p2; + unsigned long totaln1 = n1->totalcpunsec; + unsigned long totaln2 = n2->totalcpunsec; + + if (totaln1 < totaln2) + return 1; + if (totaln1 == totaln2) + return 0; + return -1; +} + +gint sort_by_cpu_group_by_threads_desc(gconstpointer p1, gconstpointer p2) +{ + struct processtop *n1 = *(struct processtop **)p1; + struct processtop *n2 = *(struct processtop **)p2; + unsigned long totaln1 = n1->threadstotalcpunsec; + unsigned long totaln2 = n2->threadstotalcpunsec; + + if (totaln1 < totaln2) + return 1; + if (totaln1 == totaln2) + return 0; + return -1; +} + +void update_cputop_display() +{ + int i; + int header_offset = 2; + struct processtop *tmp; + unsigned long elapsed; + double maxcputime; + int nblinedisplayed = 0; + int current_line = 0; + + elapsed = data->end - data->start; + maxcputime = elapsed * data->cpu_table->len / 100.0; + + g_ptr_array_sort(data->process_table, sort_by_cpu_desc); + + set_window_title(center, "CPU Top"); + wattron(center, A_BOLD); + mvwprintw(center, 1, 1, "CPU(%)"); + mvwprintw(center, 1, 12, "TGID"); + mvwprintw(center, 1, 22, "PID"); + mvwprintw(center, 1, 32, "NAME"); + wattroff(center, A_BOLD); + + max_center_lines = LINES - 7 - 7 - 1 - header_offset; + + /* iterate the process (thread) list */ + for (i = list_offset; i < data->process_table->len && + nblinedisplayed < max_center_lines; i++) { + tmp = g_ptr_array_index(data->process_table, i); + + if (current_line == selected_line) { + selected_process = tmp; + selected_tid = tmp->tid; + selected_comm = tmp->comm; + wattron(center, COLOR_PAIR(5)); + mvwhline(center, current_line + header_offset, 1, ' ', COLS-3); + } + /* CPU(%) */ + mvwprintw(center, current_line + header_offset, 1, "%1.2f", + tmp->totalcpunsec / maxcputime); + /* TGID */ + mvwprintw(center, current_line + header_offset, 12, "%d", tmp->pid); + /* PID */ + mvwprintw(center, current_line + header_offset, 22, "%d", tmp->tid); + /* NAME */ + mvwprintw(center, current_line + header_offset, 32, "%s", tmp->comm); + wattroff(center, COLOR_PAIR(5)); + nblinedisplayed++; + current_line++; + } +} + +gint sort_perf(gconstpointer p1, gconstpointer p2, gpointer key) +{ + struct processtop *n1 = *(struct processtop **) p1; + struct processtop *n2 = *(struct processtop **) p2; + + struct perfcounter *tmp1, *tmp2; + unsigned long totaln2 = 0; + unsigned long totaln1 = 0; + + if (!key) + return 0; + + tmp1 = g_hash_table_lookup(n1->perf, key); + if (!tmp1) + totaln1 = 0; + else + totaln1 = tmp1->count; + + tmp2 = g_hash_table_lookup(n2->perf, key); + if (!tmp2) + totaln2 = 0; + else + totaln2 = tmp2->count; + + if (totaln1 < totaln2) + return 1; + if (totaln1 == totaln2) { + totaln1 = n1->tid; + totaln2 = n2->tid; + if (totaln1 < totaln2) + return 1; + return -1; + } + return -1; +} + +void print_key_title(char *key, int line) +{ + wattron(center, A_BOLD); + mvwprintw(center, line, 1, "%s\t", key); + wattroff(center, A_BOLD); +} + +void update_process_details() +{ + unsigned long elapsed; + double maxcputime; + struct processtop *tmp = find_process_tid(data, selected_tid, selected_comm); + + set_window_title(center, "Process details"); + + + elapsed = data->end - data->start; + maxcputime = elapsed * data->cpu_table->len / 100.0; + + print_key_title("Name", 1); + wprintw(center, "%s", selected_comm); + print_key_title("TID", 2); + wprintw(center, "%d", selected_tid); + if (!tmp) { + print_key_title("Does not exit at this time", 3); + return; + } + + print_key_title("PID", 3); + wprintw(center, "%d", tmp->pid); + print_key_title("PPID", 4); + wprintw(center, "%d", tmp->ppid); + print_key_title("CPU", 5); + wprintw(center, "%1.2f %%", tmp->totalcpunsec/maxcputime); +} + +void update_perf() +{ + int i, j; + int nblinedisplayed = 0; + int current_line = 0; + struct processtop *tmp; + int header_offset = 2; + int perf_row = 40; + struct perfcounter *perfn1, *perfn2; + GList *perflist; + char *perf_key = NULL; + int value; + + set_window_title(center, "Perf Top"); + wattron(center, A_BOLD); + mvwprintw(center, 1, 1, "PID"); + mvwprintw(center, 1, 11, "TID"); + mvwprintw(center, 1, 22, "NAME"); + + perf_row = 40; + perflist = g_list_first(g_hash_table_get_keys(data->perf_list)); + while (perflist) { + perfn1 = g_hash_table_lookup(data->perf_list, perflist->data); + /* + 6 to strip the "_perf_" prefix */ + if (perfn1->visible) { + mvwprintw(center, 1, perf_row, "%s", + (char *) perflist->data + 6); + perf_row += 20; + } + if (perfn1->sort) { + perf_key = (char *) perflist->data; + } + perflist = g_list_next(perflist); + } + wattroff(center, A_BOLD); + + g_ptr_array_sort_with_data(data->process_table, sort_perf, perf_key); + for (i = 0; i < data->process_table->len && + nblinedisplayed < max_center_lines; i++) { + GList *perf_keys; + tmp = g_ptr_array_index(data->process_table, i); + + if (current_line == selected_line) { + selected_process = tmp; + wattron(center, COLOR_PAIR(5)); + mvwhline(center, current_line + header_offset, 1, ' ', COLS-3); + } + + mvwprintw(center, current_line + header_offset, 1, "%d", tmp->pid); + mvwprintw(center, current_line + header_offset, 11, "%d", tmp->tid); + mvwprintw(center, current_line + header_offset, 22, "%s", tmp->comm); + + /* FIXME : sometimes there is a segfault here, I have no idea why :-( */ + perf_keys = g_hash_table_get_keys(data->perf_list); + if (perf_keys) + perflist = g_list_first(perf_keys); + else + perflist = NULL; + + perf_row = 40; + j = 0; + while (perflist) { + j++; + perfn1 = g_hash_table_lookup(data->perf_list, perflist->data); + if (!perfn1) { + perflist = g_list_next(perflist); + continue; + } + if (perfn1->visible) { + perfn2 = g_hash_table_lookup(tmp->perf, perflist->data); + if (perfn2) + value = perfn2->count; + else + value = 0; + mvwprintw(center, current_line + header_offset, perf_row, "%d", value); + perf_row += 20; + } + perflist = g_list_next(perflist); + } + + wattroff(center, COLOR_PAIR(5)); + nblinedisplayed++; + current_line++; + } +} + +void update_fileio() +{ + int i; + int offset; + + set_window_title(center, "IO Top"); + wattron(center, A_BOLD); + mvwprintw(center, 1, 10, "READ"); + mvwprintw(center, 2, 1, "bytes"); + mvwprintw(center, 2, 15, "bytes/sec"); + + mvwprintw(center, 1, 39, "WRITE"); + mvwprintw(center, 2, 33, "bytes"); + mvwprintw(center, 2, 45, "bytes/sec"); + + if (toggle_threads > 0) { + mvwprintw(center, 1, 60, "TGID"); + mvwprintw(center, 1, 70, "PID"); + offset = 8; + } else { + mvwprintw(center, 1, 60, "PID(TGID)"); + offset = 0; + } + mvwprintw(center, 1, 72 + offset, "NAME"); + wattroff(center, A_BOLD); + + for (i = 3; i < LINES - 3 - 8 - 1; i++) { + mvwprintw(center, i, 1, "%d", i*1000); + mvwprintw(center, i, 15, "%dk", i); + mvwprintw(center, i, 28, "| %d", i*2000); + mvwprintw(center, i, 45, "%dk", i*2); + if (toggle_threads > 0) { + mvwprintw(center, i, 57, "| %d", i); + mvwprintw(center, i, 70, "%d", i); + } else { + mvwprintw(center, i, 57, "| %d", i); + } + mvwprintw(center, i, 72 + offset, "process_%d", i); + } +} + +gint sort_by_ret_desc(gconstpointer p1, gconstpointer p2) +{ + struct processtop *n1 = *(struct processtop **)p1; + struct processtop *n2 = *(struct processtop **)p2; + unsigned long totaln1 = n1->iostream->ret_total; + unsigned long totaln2 = n2->iostream->ret_total; + + if (totaln1 < totaln2) + return 1; + if (totaln1 == totaln2) + return 0; + return -1; +} + +void update_iostream() +{ + int i; + int header_offset = 2; + struct processtop *tmp; + int nblinedisplayed = 0; + int current_line = 0; + + set_window_title(center, "IO Top"); + wattron(center, A_BOLD); + mvwprintw(center, 1, 1, "READ (B/s)"); + mvwprintw(center, 1, 20, "WRITE (B/s)"); + + mvwprintw(center, 1, 40, "TOTAL STREAM"); + + mvwprintw(center, 1, 60, "TGID"); + mvwprintw(center, 1, 80, "PID"); + + mvwprintw(center, 1, 92, "NAME"); + wattroff(center, A_BOLD); + + g_ptr_array_sort(data->process_table, sort_by_ret_desc); + + for (i = list_offset; i < data->process_table->len && + nblinedisplayed < max_center_lines; i++) { + tmp = g_ptr_array_index(data->process_table, i); + + if (current_line == selected_line) { + selected_process = tmp; + selected_tid = tmp->tid; + selected_comm = tmp->comm; + wattron(center, COLOR_PAIR(5)); + mvwhline(center, current_line + header_offset, 1, ' ', COLS-3); + } + /* READ (bytes/sec) */ + mvwprintw(center, current_line + header_offset, 1, "%lu", + tmp->iostream->ret_read); + + /* WRITE (bytes/sec) */ + mvwprintw(center, current_line + header_offset, 20, "%lu", + tmp->iostream->ret_write); + + /* TOTAL STREAM */ + if(tmp->iostream->ret_total >= 1000000) + mvwprintw(center, current_line + header_offset, 40, "%lu MB", + tmp->iostream->ret_total/1000000); + else if(tmp->iostream->ret_total >=1000) + mvwprintw(center, current_line + header_offset, 40, "%lu KB", + tmp->iostream->ret_total/1000); + else + mvwprintw(center, current_line + header_offset, 40, "%lu B", + tmp->iostream->ret_total); + /* TGID */ + mvwprintw(center, current_line + header_offset, 60, "%d", tmp->pid); + /* PID */ + mvwprintw(center, current_line + header_offset, 80, "%d", tmp->tid); + /* NAME */ + mvwprintw(center, current_line + header_offset, 92, "%s", tmp->comm); + wattroff(center, COLOR_PAIR(5)); + nblinedisplayed++; + current_line++; + } +} + +void update_current_view() +{ + sem_wait(&update_display_sem); + if (!data) + return; + update_header(); + + werase(center); + box(center, 0, 0); + switch (current_view) { + case cpu: + update_cputop_display(); + break; + case perf: + update_perf(); + break; + case process_details: + update_process_details(); + break; + case fileio: + update_fileio(); + break; + case iostream: + update_iostream(); + break; + case tree: + update_cputop_display(); + break; + default: + break; + } + update_panels(); + doupdate(); + sem_post(&update_display_sem); +} + +void setup_perf_panel() +{ + int size; + if (!data) + return; + if (perf_panel_window) { + del_panel(perf_panel); + delwin(perf_panel_window); + } + size = g_hash_table_size(data->perf_list); + perf_panel_window = create_window(size + 2, 30, 10, 10); + perf_panel = new_panel(perf_panel_window); + perf_panel_visible = 0; + hide_panel(perf_panel); +} + +void update_perf_panel(int line_selected, int toggle_view, int toggle_sort) +{ + int i; + struct perfcounter *perf; + GList *perflist; + + if (!data) + return; + + werase(perf_panel_window); + box(perf_panel_window, 0 , 0); + set_window_title(perf_panel_window, "Perf Preferences "); + wattron(perf_panel_window, A_BOLD); + mvwprintw(perf_panel_window, g_hash_table_size(data->perf_list) + 1, 1, " 's' to sort"); + wattroff(perf_panel_window, A_BOLD); + + if (toggle_sort == 1) { + i = 0; + perflist = g_list_first(g_hash_table_get_keys(data->perf_list)); + while (perflist) { + perf = g_hash_table_lookup(data->perf_list, perflist->data); + if (i != line_selected) + perf->sort = 0; + else + perf->sort = 1; + i++; + perflist = g_list_next(perflist); + } + update_current_view(); + } + + i = 0; + perflist = g_list_first(g_hash_table_get_keys(data->perf_list)); + while (perflist) { + perf = g_hash_table_lookup(data->perf_list, perflist->data); + if (i == line_selected && toggle_view == 1) { + perf->visible = perf->visible == 1 ? 0:1; + update_current_view(); + } + if (i == line_selected) { + wattron(perf_panel_window, COLOR_PAIR(5)); + mvwhline(perf_panel_window, i + 1, 1, ' ', 30 - 2); + } + if (perf->sort == 1) + wattron(perf_panel_window, A_BOLD); + mvwprintw(perf_panel_window, i + 1, 1, "[%c] %s", + perf->visible == 1 ? 'x' : ' ', + (char *) perflist->data + 6); + wattroff(perf_panel_window, A_BOLD); + wattroff(perf_panel_window, COLOR_PAIR(5)); + i++; + perflist = g_list_next(perflist); + } + update_panels(); + doupdate(); +} + + +void toggle_perf_panel(void) +{ + if (perf_panel_visible) { + hide_panel(perf_panel); + perf_panel_visible = 0; + } else { + setup_perf_panel(); + update_perf_panel(perf_line_selected, 0, 0); + show_panel(perf_panel); + perf_panel_visible = 1; + } + update_panels(); + doupdate(); +} + +void display(unsigned int index) +{ + last_display_index = index; + currently_displayed_index = index; + data = g_ptr_array_index(copies, index); + if (!data) + return; + max_elements = data->process_table->len; + update_current_view(); + update_footer(); + update_panels(); + doupdate(); +} + +void pause_display() +{ + toggle_pause = 1; + print_log("Pause"); + sem_wait(&pause_sem); +} + +void resume_display() +{ + toggle_pause = -1; + print_log("Resume"); + sem_post(&pause_sem); +} + +void *handle_keyboard(void *p) +{ + int ch; + while((ch = getch())) { + switch(ch) { + /* Move the cursor and scroll */ + case KEY_DOWN: + if (perf_panel_visible) { + if (perf_line_selected < g_hash_table_size(data->perf_list) - 1) + perf_line_selected++; + update_perf_panel(perf_line_selected, 0, 0); + } else { + if (selected_line < (max_center_lines - 1) && + selected_line < max_elements - 1) { + selected_line++; + selected_in_list++; + } else if (selected_in_list < (max_elements - 1) + && (list_offset < (max_elements - max_center_lines))) { + selected_in_list++; + list_offset++; + } + update_current_view(); + } + break; + case KEY_NPAGE: + if ((selected_line + 10 < max_center_lines - 1) && + ((selected_line + 10) < max_elements - 1)) { + selected_line += 10; + selected_in_list += 10; + } else if (max_elements > max_center_lines) { + selected_line = max_center_lines - 1; + if (selected_in_list + 10 < max_elements - 1) { + selected_in_list += 10; + list_offset += (selected_in_list - max_center_lines + 1); + } + } else if (selected_line + 10 > max_elements) { + selected_line = max_elements - 1; + } + update_current_view(); + break; + case KEY_UP: + if (perf_panel_visible) { + if (perf_line_selected > 0) + perf_line_selected--; + update_perf_panel(perf_line_selected, 0, 0); + } else { + if (selected_line > 0) { + selected_line--; + selected_in_list--; + } else if (selected_in_list > 0 && list_offset > 0) { + selected_in_list--; + list_offset--; + } + update_current_view(); + } + break; + case KEY_PPAGE: + if (selected_line - 10 > 0) + selected_line -= 10; + else + selected_line = 0; + update_current_view(); + break; + + /* Navigate the history with arrows */ + case KEY_LEFT: + if (currently_displayed_index > 0) { + currently_displayed_index--; + print_log("Going back in time"); + } else { + print_log("Cannot rewind, last data is already displayed"); + } + data = g_ptr_array_index(copies, currently_displayed_index); + max_elements = data->process_table->len; + + /* we force to pause the display when moving in time */ + if (toggle_pause < 0) + pause_display(); + + update_current_view(); + update_footer(); + break; + case KEY_RIGHT: + if (currently_displayed_index < last_display_index) { + currently_displayed_index++; + print_log("Going forward in time"); + data = g_ptr_array_index(copies, currently_displayed_index); + max_elements = data->process_table->len; + update_current_view(); + update_footer(); + } else { + print_log("Manually moving forward"); + sem_post(&timer); + /* we force to resume the refresh when moving forward */ + if (toggle_pause > 0) + resume_display(); + } + + break; + case ' ': + if (perf_panel_visible) + update_perf_panel(perf_line_selected, 1, 0); + break; + case 's': + if (perf_panel_visible) + update_perf_panel(perf_line_selected, 0, 1); + break; + + case 13: /* FIXME : KEY_ENTER ?? */ + if (current_view == cpu) { + current_view = process_details; + } + update_current_view(); + break; + + case KEY_F(1): + toggle_tree *= -1; + current_view = cpu; + update_current_view(); + break; + case KEY_F(2): + current_view = cpu; + update_current_view(); + break; + case KEY_F(3): + current_view = perf; + toggle_tree = -1; + update_current_view(); + break; + case KEY_F(4): + current_view = fileio; + toggle_tree = -1; + update_current_view(); + break; + case KEY_F(5): + current_view = netio; + toggle_tree = -1; + update_current_view(); + break; + case KEY_F(6): + current_view = iostream; + toggle_tree = -1; + update_current_view(); + break; + case KEY_F(10): + case 'q': + reset_ncurses(); + break; + case 't': + toggle_threads *= -1; + update_current_view(); + break; + case 'p': + if (toggle_pause < 0) { + pause_display(); + } else { + resume_display(); + } + break; + case 'P': + toggle_perf_panel(); + break; + default: + /* + * commented because it makes the list refresh in different order + * if we sort and there are equal values + if (data) + update_current_view(); + */ + break; + } + update_footer(); + } + return NULL; +} + +void init_ncurses() +{ + sem_init(&update_display_sem, 0, 1); + init_screen(); + + header = create_window(7, COLS - 1, 0, 0); + center = create_window(LINES - 7 - 7, COLS - 1, 7, 0); + status = create_window(MAX_LOG_LINES + 2, COLS - 1, LINES - 7, 0); + footer = create_window(1, COLS - 1, LINES - 1, 0); + + print_log("Starting display"); + + main_panel = new_panel(center); + setup_perf_panel(); + + current_view = cpu; + + basic_header(); + update_footer(); + + pthread_create(&keyboard_thread, NULL, handle_keyboard, (void *)NULL); +} + diff --git a/src/cursesdisplay.h b/src/cursesdisplay.h new file mode 100644 index 0000000..6a54252 --- /dev/null +++ b/src/cursesdisplay.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef CURSESDISPLAY_H +#define CURSESDISPLAY_H + +#include +#include +#include "common.h" + +enum current_view_list +{ + cpu = 1, + perf, + process_details, + fileio, + netio, + iostream, + tree, +} current_view; + +void display(unsigned int); +void init_ncurses(); +void reset_ncurses(); + +#endif // CURSESDISPLAY_H diff --git a/src/iostreamtop.c b/src/iostreamtop.c new file mode 100644 index 0000000..1594df7 --- /dev/null +++ b/src/iostreamtop.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2011 Mathieu Bain + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include + +#include "lttngtoptypes.h" +#include "common.h" +#include "iostreamtop.h" + +#include + +int update_iostream_ret(struct lttngtop *ctx, int tid, char *comm, + unsigned long timestamp, int cpu_id, int ret) +{ + struct processtop *tmp; + int err = 0; + + tmp = get_proc(ctx, tid, comm, timestamp); + if ((tmp->iostream->syscall_info != NULL) && (tmp->iostream->syscall_info->cpu_id == cpu_id)) { + if (tmp->iostream->syscall_info->type == __NR_read && ret > 0) { + tmp->iostream->ret_read += ret; + tmp->iostream->ret_total += ret; + } else if(tmp->iostream->syscall_info->type == __NR_write && ret > 0) { + tmp->iostream->ret_write += ret; + tmp->iostream->ret_total += ret; + } else{ + err = -1; + } + free(tmp->iostream->syscall_info); + tmp->iostream->syscall_info = NULL; + } + + return err; +} + +enum bt_cb_ret handle_exit_syscall(struct bt_ctf_event *call_data, + void *private_data) +{ + struct definition *scope; + unsigned long timestamp; + char *comm; + uint64_t ret, tid; + int64_t cpu_id; + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_EVENT_CONTEXT); + comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, + scope, "_procname")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing procname context info\n"); + goto error; + } + + tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_tid")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing tid context info\n"); + goto error; + } + + scope = bt_ctf_get_top_level_scope(call_data, + BT_EVENT_FIELDS); + ret = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_ret")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing ret context info\n"); + goto error; + } + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_PACKET_CONTEXT); + cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(call_data, + scope, "cpu_id")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing cpu_id context info\n"); + goto error; + } + + /* + * if we encounter an exit_syscall and it is not for a syscall read or write + * we just abort the execution of this callback + */ + if ((update_iostream_ret(<tngtop, tid, comm, timestamp, cpu_id, ret)) < 0) + return BT_CB_ERROR_CONTINUE; + + return BT_CB_OK; + +error: + return BT_CB_ERROR_STOP; +} + + +enum bt_cb_ret handle_sys_write(struct bt_ctf_event *call_data, + void *private_data) +{ + struct definition *scope; + struct processtop *tmp; + struct syscalls *syscall_info; + unsigned long timestamp; + uint64_t cpu_id; + char *comm; + int64_t tid; + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_EVENT_CONTEXT); + comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, + scope, "_procname")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing procname context info\n"); + goto error; + } + + tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_tid")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing tid context info\n"); + goto error; + } + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_PACKET_CONTEXT); + cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(call_data, + scope, "cpu_id")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing cpu_id context info\n"); + goto error; + } + + syscall_info = malloc(sizeof(struct syscalls)); + syscall_info->cpu_id = cpu_id; + syscall_info->type = __NR_write; + syscall_info->tid = tid; + tmp = get_proc(<tngtop, tid, comm, timestamp); + tmp->iostream->syscall_info = syscall_info; + + return BT_CB_OK; + +error: + return BT_CB_ERROR_STOP; +} + +enum bt_cb_ret handle_sys_read(struct bt_ctf_event *call_data, + void *private_data) +{ + struct processtop *tmp; + struct definition *scope; + struct syscalls * syscall_info; + unsigned long timestamp; + uint64_t cpu_id; + char *comm; + int64_t tid; + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_EVENT_CONTEXT); + comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, + scope, "_procname")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing procname context info\n"); + goto error; + } + + tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, + scope, "_tid")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing tid context info\n"); + goto error; + } + + scope = bt_ctf_get_top_level_scope(call_data, + BT_STREAM_PACKET_CONTEXT); + cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(call_data, + scope, "cpu_id")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "Missing cpu_id context info\n"); + goto error; + } + + syscall_info = malloc(sizeof(struct syscalls)); + syscall_info->cpu_id = cpu_id; + syscall_info->type = __NR_read; + syscall_info->tid = tid; + tmp = get_proc(<tngtop, tid, comm, timestamp); + tmp->iostream->syscall_info = syscall_info; + + return BT_CB_OK; + +error: + return BT_CB_ERROR_STOP; +} + diff --git a/src/iostreamtop.h b/src/iostreamtop.h new file mode 100644 index 0000000..bde4dbf --- /dev/null +++ b/src/iostreamtop.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011 Mathieu Bain + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _IOSTREANTOP_H +#define _IOSTREAMTOP_H + +#include +#include +#include +#include + +/* +#define SYS_READ 1 +#define SYS_WRITE 2 +*/ + +enum bt_cb_ret handle_exit_syscall(struct bt_ctf_event *call_data, + void *private_data); + +enum bt_cb_ret handle_sys_write(struct bt_ctf_event *call_data, + void *private_data); + +enum bt_cb_ret handle_sys_read(struct bt_ctf_event *call_data, + void *private_data); + +#endif /* _IOSTREAMTOP_H */ diff --git a/src/lttngtop.c b/src/lttngtop.c new file mode 100644 index 0000000..5d526f3 --- /dev/null +++ b/src/lttngtop.c @@ -0,0 +1,572 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lttngtoptypes.h" +#include "cputop.h" +#include "iostreamtop.h" +#include "cursesdisplay.h" +#include "common.h" + +#define DEFAULT_FILE_ARRAY_SIZE 1 + +const char *opt_input_path; + +struct lttngtop *copy; +pthread_t display_thread; +pthread_t timer_thread; + +unsigned long refresh_display = 1 * NSEC_PER_SEC; +unsigned long last_display_update = 0; +int quit = 0; + +enum { + OPT_NONE = 0, + OPT_HELP, + OPT_LIST, + OPT_VERBOSE, + OPT_DEBUG, + OPT_NAMES, +}; + +static struct poptOption long_options[] = { + /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */ + { "help", 'h', POPT_ARG_NONE, NULL, OPT_HELP, NULL, NULL }, + { NULL, 0, 0, NULL, 0, NULL, NULL }, +}; + +void *refresh_thread(void *p) +{ + while (1) { + sem_wait(&pause_sem); + sem_post(&pause_sem); + sem_post(&timer); + sleep(refresh_display/NSEC_PER_SEC); + } +} + +void *ncurses_display(void *p) +{ + unsigned int current_display_index = 0; + + sem_wait(&bootstrap); + init_ncurses(); + + while (1) { + sem_wait(&timer); + sem_wait(&goodtodisplay); + sem_wait(&pause_sem); + + copy = g_ptr_array_index(copies, current_display_index); + if (copy) + display(current_display_index++); + + sem_post(&goodtoupdate); + sem_post(&pause_sem); + + if (quit) { + reset_ncurses(); + pthread_exit(0); + } + } +} + +/* + * hook on each event to check the timestamp and refresh the display if + * necessary + */ +enum bt_cb_ret check_timestamp(struct bt_ctf_event *call_data, void *private_data) +{ + unsigned long timestamp; + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + if (last_display_update == 0) + last_display_update = timestamp; + + if (timestamp - last_display_update >= refresh_display) { + sem_wait(&goodtoupdate); + g_ptr_array_add(copies, get_copy_lttngtop(last_display_update, + timestamp)); + sem_post(&goodtodisplay); + sem_post(&bootstrap); + last_display_update = timestamp; + } + return BT_CB_OK; + +error: + fprintf(stderr, "check_timestamp callback error\n"); + return BT_CB_ERROR_STOP; +} + +/* + * get_perf_counter : get or create and return a perf_counter struct for + * either a process or a cpu (only one of the 2 parameters mandatory) + */ +struct perfcounter *get_perf_counter(const char *name, struct processtop *proc, + struct cputime *cpu) +{ + struct perfcounter *ret, *global; + GHashTable *table; + + if (proc) + table = proc->perf; + else if (cpu) + table = cpu->perf; + else + goto error; + + ret = g_hash_table_lookup(table, (gpointer) name); + if (ret) + goto end; + + ret = malloc(sizeof(struct perfcounter)); + memset(ret, 0, sizeof(struct perfcounter)); + /* by default, make it visible in the UI */ + ret->visible = 1; + g_hash_table_insert(table, (gpointer) name, ret); + + global = g_hash_table_lookup(lttngtop.perf_list, (gpointer) name); + if (!global) { + global = malloc(sizeof(struct perfcounter)); + memset(global, 0, sizeof(struct perfcounter)); + memcpy(global, ret, sizeof(struct perfcounter)); + /* by default, sort on the first perf context */ + if (g_hash_table_size(lttngtop.perf_list) == 0) + global->sort = 1; + g_hash_table_insert(lttngtop.perf_list, (gpointer) name, global); + } + +end: + return ret; + +error: + return NULL; +} + +void update_perf_value(struct processtop *proc, struct cputime *cpu, + const char *name, int value) +{ + struct perfcounter *cpu_perf, *process_perf; + + cpu_perf = get_perf_counter(name, NULL, cpu); + if (cpu_perf->count < value) { + process_perf = get_perf_counter(name, proc, NULL); + process_perf->count += value - cpu_perf->count; + cpu_perf->count = value; + } +} + +void extract_perf_counter_scope(struct bt_ctf_event *event, + struct definition *scope, + struct processtop *proc, + struct cputime *cpu) +{ + struct definition const * const *list = NULL; + unsigned int count; + int i, ret; + + if (!scope) + goto end; + + ret = bt_ctf_get_field_list(event, scope, &list, &count); + if (ret < 0) + goto end; + + for (i = 0; i < count; i++) { + const char *name = bt_ctf_field_name(list[i]); + if (strncmp(name, "_perf_", 6) == 0) { + int value = bt_ctf_get_uint64(list[i]); + if (bt_ctf_field_get_error()) + continue; + update_perf_value(proc, cpu, name, value); + } + } + +end: + return; +} + +void update_perf_counter(struct processtop *proc, struct bt_ctf_event *event) +{ + struct definition *scope; + uint64_t cpu_id; + struct cputime *cpu; + + scope = bt_ctf_get_top_level_scope(event, BT_STREAM_PACKET_CONTEXT); + cpu_id = bt_ctf_get_uint64(bt_ctf_get_field(event, scope, "cpu_id")); + if (bt_ctf_field_get_error()) { + fprintf(stderr, "[error] get cpu_id\n"); + goto end; + } + cpu = get_cpu(cpu_id); + + scope = bt_ctf_get_top_level_scope(event, BT_STREAM_EVENT_CONTEXT); + extract_perf_counter_scope(event, scope, proc, cpu); + + scope = bt_ctf_get_top_level_scope(event, BT_STREAM_PACKET_CONTEXT); + extract_perf_counter_scope(event, scope, proc, cpu); + + scope = bt_ctf_get_top_level_scope(event, BT_EVENT_CONTEXT); + extract_perf_counter_scope(event, scope, proc, cpu); + +end: + return; +} + +enum bt_cb_ret fix_process_table(struct bt_ctf_event *call_data, + void *private_data) +{ + int pid, tid, ppid; + char *comm; + struct processtop *parent, *child; + struct definition *scope; + unsigned long timestamp; + + /* FIXME : check context pid, tid, ppid and comm */ + + timestamp = bt_ctf_get_timestamp(call_data); + if (timestamp == -1ULL) + goto error; + + scope = bt_ctf_get_top_level_scope(call_data, BT_STREAM_EVENT_CONTEXT); + + pid = bt_ctf_get_int64(bt_ctf_get_field(call_data, scope, "_pid")); + if (bt_ctf_field_get_error()) { +// fprintf(stderr, "Missing pid context info\n"); + goto error; + } + tid = bt_ctf_get_int64(bt_ctf_get_field(call_data, scope, "_tid")); + if (bt_ctf_field_get_error()) { +// fprintf(stderr, "Missing tid context info\n"); + goto error; + } + ppid = bt_ctf_get_int64(bt_ctf_get_field(call_data, scope, "_ppid")); + if (bt_ctf_field_get_error()) { +// fprintf(stderr, "Missing ppid context info\n"); + goto error; + } + comm = bt_ctf_get_char_array(bt_ctf_get_field(call_data, scope, "_procname")); + if (bt_ctf_field_get_error()) { +// fprintf(stderr, "Missing procname context info\n"); + goto error; + } + + /* find or create the current process */ + child = find_process_tid(<tngtop, tid, comm); + if (!child) + child = add_proc(<tngtop, tid, comm, timestamp); + update_proc(child, pid, tid, ppid, comm); + + if (pid != tid) { + /* find or create the parent */ + parent = find_process_tid(<tngtop, pid, comm); + if (!parent) { + parent = add_proc(<tngtop, pid, comm, timestamp); + parent->pid = pid; + } + + /* attach the parent to the current process */ + child->threadparent = parent; + add_thread(parent, child); + } + + update_perf_counter(child, call_data); + + return BT_CB_OK; + +error: + return BT_CB_ERROR_STOP; +} + +void init_lttngtop() +{ + copies = g_ptr_array_new(); + lttngtop.perf_list = g_hash_table_new(g_direct_hash, g_direct_equal); + + sem_init(&goodtodisplay, 0, 0); + sem_init(&goodtoupdate, 0, 1); + sem_init(&timer, 0, 1); + sem_init(&bootstrap, 0, 0); + sem_init(&pause_sem, 0, 1); + sem_init(&end_trace_sem, 0, 0); + + lttngtop.process_table = g_ptr_array_new(); + lttngtop.files_table = g_ptr_array_new(); + lttngtop.cpu_table = g_ptr_array_new(); +} + +void usage(FILE *fd) +{ + +} + +/* + * Return 0 if caller should continue, < 0 if caller should return + * error, > 0 if caller should exit without reporting error. + */ +static int parse_options(int argc, char **argv) +{ + poptContext pc; + int opt, ret = 0; + + if (argc == 1) { + usage(stdout); + return 1; /* exit cleanly */ + } + + pc = poptGetContext(NULL, argc, (const char **) argv, long_options, 0); + poptReadDefaultConfig(pc, 0); + + while ((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + case OPT_HELP: + usage(stdout); + ret = 1; /* exit cleanly */ + goto end; + case OPT_LIST: + // list_formats(stdout); + ret = 1; + goto end; + case OPT_VERBOSE: +// babeltrace_verbose = 1; + break; + case OPT_DEBUG: +// babeltrace_debug = 1; + break; + case OPT_NAMES: +// opt_field_names = 1; + break; + default: + ret = -EINVAL; + goto end; + } + } + + opt_input_path = poptGetArg(pc); + if (!opt_input_path) { + ret = -EINVAL; + goto end; + } +end: + if (pc) { + poptFreeContext(pc); + } + return ret; +} + +void iter_trace(struct bt_context *bt_ctx) +{ + struct bt_ctf_iter *iter; + struct bt_iter_pos begin_pos; + struct bt_ctf_event *event; + int ret = 0; + + begin_pos.type = BT_SEEK_BEGIN; + iter = bt_ctf_iter_create(bt_ctx, &begin_pos, NULL); + + /* at each event check if we need to refresh */ + bt_ctf_iter_add_callback(iter, 0, NULL, 0, + check_timestamp, + NULL, NULL, NULL); + /* at each event, verify the status of the process table */ + bt_ctf_iter_add_callback(iter, 0, NULL, 0, + fix_process_table, + NULL, NULL, NULL); + /* to handle the scheduling events */ + bt_ctf_iter_add_callback(iter, + g_quark_from_static_string("sched_switch"), + NULL, 0, handle_sched_switch, NULL, NULL, NULL); + /* to clean up the process table */ + bt_ctf_iter_add_callback(iter, + g_quark_from_static_string("sched_process_free"), + NULL, 0, handle_sched_process_free, NULL, NULL, NULL); + + /* for IO top */ + bt_ctf_iter_add_callback(iter, + g_quark_from_static_string("exit_syscall"), + NULL, 0, handle_exit_syscall, NULL, NULL, NULL); + bt_ctf_iter_add_callback(iter, + g_quark_from_static_string("sys_write"), + NULL, 0, handle_sys_write, NULL, NULL, NULL); + bt_ctf_iter_add_callback(iter, + g_quark_from_static_string("sys_read"), + NULL, 0, handle_sys_read, NULL, NULL, NULL); + while ((event = bt_ctf_iter_read_event(iter)) != NULL) { + ret = bt_iter_next(bt_ctf_get_iter(iter)); + if (ret < 0) + goto end_iter; + } + + /* block until quit, we reached the end of the trace */ + sem_wait(&end_trace_sem); + +end_iter: + bt_iter_destroy(bt_ctf_get_iter(iter)); +} + +/* + * bt_context_add_traces_recursive: Open a trace recursively + * (copied from BSD code in converter/babeltrace.c) + * + * Find each trace present in the subdirectory starting from the given + * path, and add them to the context. The packet_seek parameter can be + * NULL: this specify to use the default format packet_seek. + * + * Return: 0 on success, nonzero on failure. + * Unable to open toplevel: failure. + * Unable to open some subdirectory or file: warn and continue; + */ +int bt_context_add_traces_recursive(struct bt_context *ctx, const char *path, + const char *format_str, + void (*packet_seek)(struct stream_pos *pos, + size_t offset, int whence)) +{ + FTS *tree; + FTSENT *node; + GArray *trace_ids; + char lpath[PATH_MAX]; + char * const paths[2] = { lpath, NULL }; + int ret; + + /* + * Need to copy path, because fts_open can change it. + * It is the pointer array, not the strings, that are constant. + */ + strncpy(lpath, path, PATH_MAX); + lpath[PATH_MAX - 1] = '\0'; + + tree = fts_open(paths, FTS_NOCHDIR | FTS_LOGICAL, 0); + if (tree == NULL) { + fprintf(stderr, "[error] [Context] Cannot traverse \"%s\" for reading.\n", + path); + return -EINVAL; + } + + trace_ids = g_array_new(FALSE, TRUE, sizeof(int)); + + while ((node = fts_read(tree))) { + int dirfd, metafd; + + if (!(node->fts_info & FTS_D)) + continue; + + dirfd = open(node->fts_accpath, 0); + if (dirfd < 0) { + fprintf(stderr, "[error] [Context] Unable to open trace " + "directory file descriptor.\n"); + ret = dirfd; + goto error; + } + metafd = openat(dirfd, "metadata", O_RDONLY); + if (metafd < 0) { + ret = close(dirfd); + if (ret < 0) { + perror("close"); + goto error; + } + } else { + int trace_id; + + ret = close(metafd); + if (ret < 0) { + perror("close"); + goto error; + } + ret = close(dirfd); + if (ret < 0) { + perror("close"); + goto error; + } + + trace_id = bt_context_add_trace(ctx, + node->fts_accpath, format_str, + packet_seek, NULL, NULL); + if (trace_id < 0) { + fprintf(stderr, "[error] [Context] opening trace \"%s\" from %s " + "for reading.\n", node->fts_accpath, path); + ret = trace_id; + goto error; + } + g_array_append_val(trace_ids, trace_id); + } + } + + g_array_free(trace_ids, TRUE); + return 0; + +error: + return ret; +} + +int main(int argc, char **argv) +{ + int ret; + struct bt_context *bt_ctx = NULL; + + ret = parse_options(argc, argv); + if (ret < 0) { + fprintf(stdout, "Error parsing options.\n\n"); + usage(stdout); + exit(EXIT_FAILURE); + } else if (ret > 0) { + exit(EXIT_SUCCESS); + } + + init_lttngtop(); + + bt_ctx = bt_context_create(); + ret = bt_context_add_traces_recursive(bt_ctx, opt_input_path, "ctf", NULL); + if (ret < 0) { + printf("[error] Opening the trace\n"); + goto end; + } + + pthread_create(&display_thread, NULL, ncurses_display, (void *) NULL); + pthread_create(&timer_thread, NULL, refresh_thread, (void *) NULL); + + iter_trace(bt_ctx); + + quit = 1; + pthread_join(display_thread, NULL); + +end: + return 0; +} diff --git a/src/lttngtoptypes.h b/src/lttngtoptypes.h new file mode 100644 index 0000000..f10a684 --- /dev/null +++ b/src/lttngtoptypes.h @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2011 Julien Desfossez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef LTTNGTOPTYPES_H +#define LTTNGTOPTYPES_H + +#include + +struct lttngtop { + GPtrArray *process_table; /* struct processtop */ + GPtrArray *files_table; /* struct files */ + GPtrArray *cpu_table; /* struct cputime */ + GHashTable *perf_list; /* struct perfcounter */ + unsigned long start; + unsigned long end; + unsigned int nbproc; + unsigned int nbnewproc; + unsigned int nbdeadproc; + unsigned int nbthreads; + unsigned int nbnewthreads; + unsigned int nbdeadthreads; + unsigned int nbfiles; + unsigned int nbnewfiles; + unsigned int nbclosedfiles; +} lttngtop; + +struct processtop { + unsigned int puuid; + int pid; + char *comm; + int tid; + int ppid; + int oldpid; + int oldtid; + int oldppid; + unsigned long birth; + unsigned long death; + unsigned long lastactivity; + GPtrArray *process_files_table; + GPtrArray *threads; + GHashTable *perf; + struct processtop *threadparent; + unsigned long totalfileread; + unsigned long totalfilewrite; + unsigned long totalcpunsec; + unsigned long threadstotalcpunsec; + /* IO speed for this process */ + struct iostream *iostream; +}; + +struct perfcounter +{ + unsigned long count; + int visible; + int sort; +}; + +struct cputime { + guint id; + struct processtop *current_task; + unsigned long task_start; + GHashTable *perf; +}; + +/* + * used for "relative seeks" (with fd, for example fs.lseek) + * and for "absolute seeks" (events occuring on a device without + * any link to a particular process) + */ +struct seeks { + unsigned long offset; + unsigned long count; +}; + +struct ioctls { + unsigned int command; + unsigned long count; +}; + +struct files { + struct processtop *ref; + unsigned int fuuid; + int fd; + char *name; + int oldfd; + int device; + int openmode; + unsigned long openedat; + unsigned long closedat; + unsigned long lastaccess; + unsigned long read; + unsigned long write; + unsigned long nbpoll; + unsigned long nbselect; + unsigned long nbopen; + unsigned long nbclose; + //struct *seeks; /* relative seeks inside the file */ + //struct *ioctls; + /* XXX : average wait time */ +}; + +struct sockets { + int fd; + int parent_fd; /* on accept a new fd is created from the bound socket */ + int family; + int type; + int protocol; + int sock_address; + unsigned long openedat; + unsigned long closedat; + unsigned long bind_address; + unsigned long remote_address; + //struct *sock_options; +}; + +struct sock_options { + int name; + int value; +}; + +struct vmas { + unsigned long start; + unsigned long end; + unsigned long flags; + unsigned long prot; + char *description; /* filename or description if possible (stack, heap) */ + unsigned long page_faults; +}; + +struct syscalls { + unsigned int id; + unsigned long count; + unsigned int cpu_id; + unsigned int type; + unsigned int tid; +}; + +struct signals { + int dest_pid; + int id; + unsigned long count; +}; + +struct iostream { + struct syscalls *syscall_info; /* NULL if there is no waiting for an exit_syscall */ + unsigned long ret_read; /* value returned by an I/O syscall_exit for a sys_read*/ + unsigned long ret_write; /* value returned by an I/O syscall_exit for a sys_write*/ + unsigned long ret_total; +}; + +#endif /* LTTNGTOPTYPES_H */