#include #include #include #include #include #include #include #include #include #include #include #include #include "data.h" #include "wait.h" #include "term.h" #include "freelist.h" #include "top.h" static const char *topwait_help_title[] = { "Topwait version 1.0", "", "A top synchronization objects display for Unix", NULL }; static const char *topwait_help_text[] = { "t\t- show only objects of specified type (all, sv, mutex, mrlock, sema)", NULL }; void *topwait_get_first(top_state *ts); void *topwait_get_next(top_state *ts, void*); static void topwait_zero_interval_counters(top_state *ts); static int topwait_display_objs(top_state *ts, int lineno); static int topwait_display_stats(top_state *ts, int lineno); static int topwait_display_help_title(top_state *ts, int lineno); static int topwait_display_help_text(top_state *ts, int lineno); static int topwait_handle_event(top_state *ts, tstamp_event_entry_t*); static int topwait_handle_keypress(top_state *ts, int ch); static sort_order_proto(order_load); static sort_order_proto(order_swtch); static sort_order_t topwait_sort_orders[] = { "load", order_load, "switch", order_swtch, NULL, NULL }; static top_impl_t topwait_impl = { NULL, /* topwait_destroy */ NULL, /* topwait_usage */ topwait_sort_orders, "task", NULL, /* topwait_getopts */ topwait_get_first, topwait_get_next, topwait_zero_interval_counters, topwait_display_objs, topwait_display_stats, topwait_display_help_title, topwait_display_help_text, topwait_handle_event, topwait_handle_keypress, NULL, /* topwait_handle_option */ NULL /* topwait_init */ }; typedef struct wait_lock_type_s { const char *wt_name; int wt_type; } wait_lock_type_t; #define SHOW_ALL_TYPES 0 static wait_lock_type_t lock_types[] = { "all", SHOW_ALL_TYPES, "sv", SVWAIT, "mutex", MUTEXWAIT, "mrlock", MRWAIT, "sema", SEMAWAIT, NULL, NULL }; #define UNPACK_WAIT_FLAGS(ev, f) { f = (int)((ev->qual[1])>>32); } #define STAT_NLINES 3 static wait_state ws; static freelist wp_freelist, wl_freelist; static int show_type = SHOW_ALL_TYPES; static char title[160]; static double cycle_rate; static void pid_leaving_state(wait_pid_state *wp, tstamp_event_entry_t *ev); static int set_show_type(const char *type); FREELIST_IMPL_FUNCS(wait_pid_state, wp) FREELIST_IMPL_FUNCS(wait_lock_state, wl) static void wl_up(wait_lock_state *wl, uint64_t tstamp) { wl->wl_load++; wl->wl_nswtch++; assert(wl->wl_load > 0); if (wl->wl_load == 1) wl->wl_wait_when = tstamp; if (wl->wl_load > wl->wl_max_load) wl->wl_max_load = wl->wl_load; assert(wl->wl_max_load >= 1); } static void wl_down(wait_lock_state *wl, uint64_t tstamp) { assert(wl->wl_load > 0); wl->wl_load--; if (wl->wl_load == 0) wl->wl_wait_total += (tstamp - wl->wl_wait_when); } #define PID_ENTER_STATE(wpv, state, ev) \ { pid_leaving_state(wpv, ev); \ (wpv) -> wp_tstamp = (ev) -> tstamp; \ ws.ws_ ## state ++; wpv -> wp_reason = wp_ ## state; } static void wait_block(tstamp_event_entry_t *ev) { pid_t pid = (pid_t) ev->qual[0]; int flags; hashable *hash; wait_pid_state *wp; ws.ws_nswtch.total++; if ((hash = htab_find(&ws.ws_pids, HASH_PID(pid), pid)) != NULL) wp = HASHABLE_TO_WP(hash); else { wp = wp_alloc(&wp_freelist); wp->wp_pid = pid; wp->wp_reason = wp_running; wp->wp_tstamp = ev->tstamp; wp->wp_locking = NULL; ws.ws_running++; htab_insert(&ws.ws_pids, HASH_PID(pid), pid, WP_TO_HASHABLE(wp)); } UNPACK_WAIT_FLAGS(ev, flags); if (flags & RESCHED_LOCK) { wait_lock_state *wl; uint64_t lock = ev->qual[2]; /* swtch event logging race condition. */ if (lock == 0) return; ws.ws_nswtch.wait++; if ((hash = htab_find(&ws.ws_locks, HASH_LOCK(lock), lock)) != NULL) wl = HASHABLE_TO_WL(hash); else { wl = wl_alloc(&wl_freelist); wl->wl_addr = lock; wl->wl_type = flags; wl->wl_load = 0; wl->wl_max_load = 0; wl->wl_nswtch = 0; wl->wl_wait_total = 0; htab_insert(&ws.ws_locks, HASH_LOCK(lock), lock, WL_TO_HASHABLE(wl)); wl->wl_name[0] = '\0'; if (ev->jumbocnt > 0) { const tstamp_event_resched_data_t* rdp = (const tstamp_event_resched_data_t*) ev->qual; if (rdp->wchanname[0]) strncpy(wl->wl_name, rdp->wchanname, 16); } } #if 0 if (wp->wp_tstamp > ev->tstamp) { /* If this event becomes visible after the process has already switched states, we should remain in the old state, but properly credit the blocking time to the lock. */ wl_up(wl, ev->tstamp); wl_down(wl, wp->wp_tstamp); } else { #endif wl_up(wl, ev->tstamp); PID_ENTER_STATE(wp, waiting, ev); wp->wp_locking = wl; #if 0 } #endif } else { switch (flags) { case MUSTRUNCPU: /* switching cpus */ case GANGWAIT: /* waiting for gang to run */ case RESCHED_P: /* preempted in user mode */ case RESCHED_KP: /* preempted in kernel mode */ ws.ws_nswtch.preempt++; PID_ENTER_STATE(wp, preempted, ev); break; case RESCHED_S: /* stopped by signal */ PID_ENTER_STATE(wp, stopped, ev); break; case RESCHED_Y: /* voluntary yield */ ws.ws_nswtch.yield++; PID_ENTER_STATE(wp, yielding, ev); break; case RESCHED_D: /* died */ PID_ENTER_STATE(wp, dead, ev); break; default: PID_ENTER_STATE(wp, unknown, ev); break; } } } static void wp_unwait(wait_pid_state *wp, tstamp_event_entry_t *ev) { wait_lock_state *wl; wl = wp->wp_locking; wl_down(wl, ev->tstamp); } static void pid_leaving_state(wait_pid_state *wp, tstamp_event_entry_t *ev) { #define LEAVE_STATE(s) case wp_ ## s : ws.ws_ ## s --; break switch (wp->wp_reason) { case wp_waiting: wp_unwait(wp, ev); break; } switch (wp->wp_reason) { LEAVE_STATE(running); LEAVE_STATE(preempted); LEAVE_STATE(yielding); LEAVE_STATE(waiting); LEAVE_STATE(stopped); LEAVE_STATE(dead); LEAVE_STATE(unknown); default: #ifndef NDEBUG abort(); #endif /* !NDEBUG */ break; } #undef LEAVE_STATE } static void wait_unblock(tstamp_event_entry_t *ev) { pid_t pid = (pid_t) ev->qual[0]; wait_pid_state *wp; hashable *hash; if ((hash = htab_find(&ws.ws_pids, HASH_PID(pid), pid)) == NULL) return; /* XXX assume running */ wp = HASHABLE_TO_WP(hash); PID_ENTER_STATE(wp, preempted, ev); } static void wait_run(tstamp_event_entry_t *ev) { pid_t pid = (pid_t) ev->qual[0]; wait_pid_state *wp; hashable *hash; if ((hash = htab_find(&ws.ws_pids, HASH_PID(pid), pid)) == NULL) return; /* XXX assume running */ wp = HASHABLE_TO_WP(hash); PID_ENTER_STATE(wp, running, ev); } static void wait_exit(tstamp_event_entry_t *ev) { pid_t pid = (pid_t) ev->qual[0]; wait_pid_state *wp; hashable *hash; if ((hash = htab_find(&ws.ws_pids, HASH_PID(pid), pid)) == NULL) return; /* XXX missed one */ wp = HASHABLE_TO_WP(hash); PID_ENTER_STATE(wp, dead, ev); htab_remove(&ws.ws_pids, HASH_PID(pid), hash); wp_free(&wp_freelist, wp); } /*ARGSUSED*/ int topwait_handle_event(top_state *ts, tstamp_event_entry_t *ev) { switch (ev->evt) { case TSTAMP_EV_EXIT: /* process exit */ wait_exit(ev); break; case EVENT_TASK_STATECHANGE: /* task suspend */ wait_block(ev); break; case TSTAMP_EV_EODISP: /* dispatch to runq */ wait_unblock(ev); break; case TSTAMP_EV_EOSWITCH: case TSTAMP_EV_EOSWITCH_RTPRI: /* switch in (runq to processor) */ wait_run(ev); break; default: return 0; } return 1; } static void wait_init(wait_state *ws) { htab_init(&ws->ws_pids, N_PID_BUCKETS); htab_init(&ws->ws_locks, N_LOCK_BUCKETS); ws->ws_running = 0; ws->ws_preempted = 0; ws->ws_yielding = 0; ws->ws_waiting = 0; ws->ws_stopped = 0; ws->ws_dead = 0; ws->ws_unknown = 0; } static sort_order_proto(order_load) { const wait_lock_state *wla, *wlb; wla = *(wait_lock_state**)a; wlb = *(wait_lock_state**)b; return (wlb->wl_max_load - wla->wl_max_load); } static sort_order_proto(order_swtch) { const wait_lock_state *wla, *wlb; wla = *(wait_lock_state**)a; wlb = *(wait_lock_state**)b; return (wlb->wl_nswtch - wla->wl_nswtch); } static filter_proto(wait_filter) { const wait_lock_state *wl; wl = *(wait_lock_state**)obj; if (wl->wl_max_load == 0) return 0; if (show_type == SHOW_ALL_TYPES) return 1; else return (wl->wl_type == show_type); } /*ARGSUSED*/ void* topwait_get_first(top_state *ts) { hashable *wl_hash; wait_lock_state *wl; wl_hash = htab_begin(&ws.ws_locks); if (wl_hash == NULL) return NULL; wl = HASHABLE_TO_WL(wl_hash); return wl; } /*ARGSUSED*/ void* topwait_get_next(top_state *ts, void *last) { hashable *wl_hash; wait_lock_state *wl; wl = (wait_lock_state*) last; wl_hash = WL_TO_HASHABLE(wl); wl_hash = htab_next(&ws.ws_locks, HASH_LOCK(wl->wl_addr), wl_hash); if (wl_hash == NULL) return NULL; wl = HASHABLE_TO_WL(wl_hash); return wl; } static int set_show_type(const char *type) { wait_lock_type_t *wt; for (wt = lock_types; wt->wt_name != NULL; wt++) if (!strcmp(wt->wt_name, type)) break; if (wt->wt_name != NULL) { show_type = wt->wt_type; return 1; } else return 0; } static void display_init(void) { sprintf(title, "%16s %16s %7s %4s %6s %7s", "WCHAN", "NAME", "TYPE", "LOAD", "%SWTCH", "SWT/SEC"); } int topwait_display_help_title(top_state *ts, int line_start) { return term_draw_lines(top_term(ts), line_start, topwait_help_title); } int topwait_display_help_text(top_state *ts, int line_start) { return term_draw_lines(top_term(ts), line_start, topwait_help_text); } int topwait_display_stats(top_state *ts, int line_start) { int line; char linebuf[160]; int nprocs = ws.ws_preempted + ws.ws_yielding + ws.ws_waiting + ws.ws_stopped + ws.ws_unknown; line = line_start; #define proc_percent(type) int_percent(ws.ws_ ## type, nprocs) sprintf(linebuf, "%d processes: %d%% (%d) preempt, %d%% (%d) yield, " "%d%% (%d) wait,", nprocs, proc_percent(preempted), ws.ws_preempted, proc_percent(yielding), ws.ws_yielding, proc_percent(waiting), ws.ws_waiting); term_draw_line(top_term(ts), line++, linebuf); sprintf(linebuf, " %d%% (%d) stop, %d%% (%d) unk", proc_percent(stopped), ws.ws_stopped, proc_percent(unknown), ws.ws_unknown); term_draw_line(top_term(ts), line++, linebuf); #undef proc_percent #define swtch_percent(type) \ int_percent(ws.ws_nswtch. ## type ,ws.ws_nswtch.total) sprintf(linebuf, "%llu switches: %d%% (%llu) preempt, " "%d%% (%llu) wait, " "%d%% (%llu) yield", ws.ws_nswtch.total, swtch_percent(preempt), ws.ws_nswtch.preempt, swtch_percent(wait), ws.ws_nswtch.wait, swtch_percent(yield), ws.ws_nswtch.yield); term_draw_line(top_term(ts), line++, linebuf); #undef swtch_percent return line - line_start; } int topwait_display_objs(top_state *ts, int line_start) { char linebuf[160]; int ii; wait_lock_state **wl; double period; int line; line = line_start; term_draw_bold_line(top_term(ts), line++, title); wl = (wait_lock_state**) ts->ts_top_objs; period = (LAST_TSTAMP - FIRST_TSTAMP) / cycle_rate; for (ii = 0; ii < ts->ts_ntop_objs; ii++, wl++) { const char *locktype; double swtch_percent; int swtch_rate; switch ((*wl)->wl_type) { case SEMAWAIT: locktype = "sema"; break; case MUTEXWAIT: locktype = "mutex"; break; case SVWAIT: locktype = "sv"; break; case MRWAIT: locktype = "mrlock"; break; default: locktype = "???"; break; } if (ws.ws_nswtch.total == 0) swtch_percent = 0; else swtch_percent = ((double) (*wl)->wl_nswtch) * 100 / ws.ws_nswtch.total; if (period) swtch_rate = (*wl)->wl_nswtch / period; else swtch_rate = 0; sprintf(linebuf, "%016llx %16s %7s %4d %6.1f %7d", (*wl)->wl_addr, (*wl)->wl_name, locktype, (*wl)->wl_max_load, swtch_percent, swtch_rate); term_draw_line(top_term(ts), line++, linebuf); } return line - line_start; } /*ARGSUSED*/ void topwait_zero_interval_counters(top_state *ts) { wait_lock_state *wl; hashable *wl_hash; for (wl_hash = htab_begin(&ws.ws_locks); wl_hash != NULL; wl_hash = htab_next(&ws.ws_locks, HASH_LOCK(wl->wl_addr), WL_TO_HASHABLE(wl))) { wl = HASHABLE_TO_WL(wl_hash); wl->wl_max_load = wl->wl_load; wl->wl_nswtch = 0; wl->wl_wait_total = 0; } memset(&ws.ws_nswtch, 0, sizeof(ws.ws_nswtch)); } int topwait_handle_keypress(top_state *ts, int ch) { switch (ch) { case 't': { char line[80]; term_getstr(top_term(ts), 0, top_prompt_line(ts), "Type to display", line, sizeof(line)); if (!*line) break; if (set_show_type(line)) { top_update(ts); } else { top_error("Invalid lock type: %s", line); } } break; default: return 0; } return 1; } int top_impl_init(top_state *ts) { unsigned int cycle_period; FREELIST_INIT(&wp_freelist); FREELIST_INIT(&wl_freelist); wait_init(&ws); display_init(); top_set_impl(ts, &topwait_impl); top_set_sort_order(ts, order_swtch); top_set_filter_out(ts, wait_filter); syssgi(SGI_QUERY_CYCLECNTR, &cycle_period); cycle_rate = 1.0E12/cycle_period; return 1; } #ifndef NDEBUG void topwait_lock_htab_dump(void) { htab_dump(&ws.ws_locks, stdout); } #endif /* !NDEBUG */