Mercurial > illumos > illumos-gate
changeset 10111:30fced71ad43
6825817 Integrate latencyTOP into OpenSolaris
PSARC 2009/339 LatencyTOP for OpenSolaris
line wrap: on
line diff
--- a/usr/src/Makefile.lint Fri Jul 17 16:43:38 2009 +0100 +++ b/usr/src/Makefile.lint Fri Jul 17 09:57:00 2009 -0700 @@ -231,6 +231,7 @@ cmd/pools \ cmd/power \ cmd/powertop \ + cmd/latencytop \ cmd/ppgsz \ cmd/praudit \ cmd/prctl \
--- a/usr/src/cmd/Makefile Fri Jul 17 16:43:38 2009 +0100 +++ b/usr/src/cmd/Makefile Fri Jul 17 09:57:00 2009 -0700 @@ -227,6 +227,7 @@ kstat \ last \ lastcomm \ + latencytop \ ldap \ ldapcachemgr \ lgrpinfo \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/Makefile Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,47 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright (c) 2008-2009, Intel Corporation. +# All Rights Reserved. +# + +PROG = latencytop + +include ../Makefile.cmd + +$(64ONLY)SUBDIRS= $(MACH) +$(BUILD64)SUBDIRS += $(MACH64) + +all := TARGET = all +install := TARGET = install +clean := TARGET = clean +clobber := TARGET = clobber +lint := TARGET = lint + +.KEEP_STATE: + +all install clean clobber lint: $(SUBDIRS) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include ../Makefile.targ
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/Makefile.com Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,75 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright (c) 2008-2009, Intel Corporation. +# All Rights Reserved. +# + +PROG = latencytop +OBJS = latencytop.o display.o dwrapper.o klog.o stat.o table.o util.o conststr.o +SRCS = $(OBJS:%.o=../common/%.c) + +include ../../Makefile.cmd + +CFLAGS += $(CCVERBOSE) +CFLAGS64 += $(CCVERBOSE) + +CPPFLAGS += -DEMBED_CONFIGS -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include +C99MODE = $(C99_ENABLE) +LDLIBS += -lcurses -ldtrace +all install := LDLIBS += -lglib-2.0 + +LINTFLAGS += -erroff=E_NAME_USED_NOT_DEF2 + +LINTFLAGS64 += -erroff=E_NAME_USED_NOT_DEF2 + +FILEMODE = 0555 +GROUP = bin + +CLEANFILES += $(OBJS) + +.KEEP_STATE: + +all: dumpcfg $(PROG) + +install: dumpcfg $(SUBDIRS) + -$(RM) $(ROOTPROG) + -$(LN) $(ISAEXEC) $(ROOTPROG) + +$(PROG): $(OBJS) + $(LINK.c) -o $@ $(OBJS) $(LDLIBS) + $(POST_PROCESS) + +dumpcfg: + cd ../common; xxd -i latencytop.d >./conststr.c; xxd -i latencytop.trans >>./conststr.c; cd .. + +rmcfg: + rm -f ../common/conststr.c + +clean: rmcfg + $(RM) $(CLEANFILES) + +lint: dumpcfg lint_SRCS + +%.o: ../common/%.c + $(COMPILE.c) $< + +include ../../Makefile.targ
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/amd64/Makefile Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,28 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright (c) 2008-2009, Intel Corporation. +# All Rights Reserved. +# + +include ../Makefile.com +include ../../Makefile.cmd.64 + +install: all $(ROOTPROG64)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/common/display.c Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,998 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2008-2009, Intel Corporation. + * All Rights Reserved. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/time.h> +#include <dirent.h> +#include <curses.h> +#include <time.h> +#include <wchar.h> +#include <ctype.h> +#include <stdarg.h> + +#include "latencytop.h" + +#define LT_WINDOW_X 80 +#define LT_WINDOW_Y 24 + +#define LT_COLOR_DEFAULT 1 +#define LT_COLOR_HEADER 2 + +/* Windows created by libcurses */ +static WINDOW *titlebar = NULL; +static WINDOW *captionbar = NULL; +static WINDOW *sysglobal_window = NULL; +static WINDOW *taskbar = NULL; +static WINDOW *process_window = NULL; +static WINDOW *hintbar = NULL; +/* Screen dimention */ +static int screen_width = 1, screen_height = 1; +/* Is display initialized, i.e. window pointers set up. */ +static int display_initialized = FALSE; +/* Is initscr() called */ +static int curses_inited = FALSE; + +/* Changed by user key press */ +static pid_t selected_pid = INVALID_PID; +static id_t selected_tid = INVALID_TID; +static lt_sort_t sort_type = LT_SORT_TOTAL; +static int thread_mode = FALSE; +/* what kind of list are we showing now */ +static int current_list_type = LT_LIST_CAUSE; +static int show_help = FALSE; + +/* Help functions that append/prepend blank to the string */ +#define fill_space_right(a, b, c) fill_space((a), (b), (c), TRUE) +#define fill_space_left(a, b, c) fill_space((a), (b), (c), FALSE) + +static void +fill_space(char *buffer, int len, int buffer_limit, int is_right) +{ + int i = 0; + int tofill; + + if (len >= buffer_limit) { + len = buffer_limit - 1; + } + + i = strlen(buffer); + if (i >= len) { + return; + } + + tofill = len - i; + + if (is_right) { + (void) memset(&buffer[i], ' ', tofill); + buffer[len] = 0; + } else { + (void) memmove(&buffer[tofill], buffer, i+1); + (void) memset(buffer, ' ', tofill); + } +} + +/* Formats a human readable string out of nanosecond value */ +static const char * +get_time_string(double nanoseconds, char *buffer, int len, int fill_width) +{ + const double ONE_USEC = 1000.0; + const double ONE_MSEC = 1000000.0; + const double ONE_SEC = 1000000000.0; + + if (nanoseconds < (ONE_USEC - .5)) { + (void) snprintf(buffer, len, "%3.1f nsec", nanoseconds); + } else if (nanoseconds < (ONE_MSEC - .5 * ONE_USEC)) { + (void) snprintf(buffer, len, + "%3.1f usec", nanoseconds / ONE_USEC); + } else if (nanoseconds < (ONE_SEC - .5 * ONE_MSEC)) { + (void) snprintf(buffer, len, + "%3.1f msec", nanoseconds / ONE_MSEC); + } else if (nanoseconds < 999.5 * ONE_SEC) { + (void) snprintf(buffer, len, + "%3.1f sec", nanoseconds / ONE_SEC); + } else { + (void) snprintf(buffer, len, + "%.0e sec", nanoseconds / ONE_SEC); + } + fill_space_left(buffer, fill_width, len); + return (buffer); +} + +/* + * Print statistics in a window. + * IN: window - the global or process statistics window. + * begin_line - where to start printing. + * count - how many lines should we print. + * list - a stat_list. + */ +#define WIDTH_REASON_STRING 36 +#define WIDTH_COUNT 12 +#define WIDTH_SUM 12 +#define WIDTH_MAX 12 +#define WIDTH_PCT 8 +#define BEGIN_COUNT WIDTH_REASON_STRING +#define BEGIN_SUM (BEGIN_COUNT + WIDTH_COUNT) +#define BEGIN_MAX (BEGIN_SUM + WIDTH_SUM) +#define BEGIN_PCT (BEGIN_MAX + WIDTH_MAX) + +static void +print_statistics(WINDOW * window, int begin_line, int count, void *list) +{ + uint64_t total; + int i = 0; + + if (!display_initialized) { + return; + } + + total = lt_stat_list_get_gtotal(list); + if (total == 0) { + return; + } + + while (i < count && lt_stat_list_has_item(list, i)) { + /* + * We intentionally make tmp[] hold one character less + * than WIDTH_REASON_STRING, so it will look nice on the + * screen. + */ + char tmp[WIDTH_REASON_STRING]; + const char *reason = lt_stat_list_get_reason(list, i); + uint64_t count = lt_stat_list_get_count(list, i); + + if (count == 0) { + continue; + } + + (void) snprintf(tmp, sizeof (tmp), "%s", reason); + (void) mvwprintw(window, i + begin_line, 0, "%s", tmp); + (void) snprintf(tmp, sizeof (tmp), "%d", + lt_stat_list_get_count(list, i)); + fill_space_left(tmp, WIDTH_COUNT, sizeof (tmp)); + (void) mvwprintw(window, i + begin_line, BEGIN_COUNT, + "%s", tmp); + (void) mvwprintw(window, i + begin_line, BEGIN_SUM, + "%s", get_time_string( + (double)lt_stat_list_get_sum(list, i) / count, + tmp, sizeof (tmp), WIDTH_SUM)); + (void) mvwprintw(window, i + begin_line, BEGIN_MAX, + "%s", get_time_string( + (double)lt_stat_list_get_max(list, i), + tmp, sizeof (tmp), WIDTH_MAX)); + if (LT_LIST_SPECIALS != current_list_type) { + (void) snprintf(tmp, sizeof (tmp), "%.1f %%", + (double)lt_stat_list_get_sum(list, i) + / total * 100.0); + } else { + (void) snprintf(tmp, sizeof (tmp), "--- "); + } + fill_space_left(tmp, WIDTH_PCT, sizeof (tmp)); + + (void) mvwprintw(window, i + begin_line, BEGIN_PCT, + "%s", tmp); + i++; + } +} + +/* + * Print global statistics. Calls print_statistics(). + */ +static void +print_sysglobal(void) +{ + void *list; + char header[256]; + + if (!display_initialized) { + return; + } + + (void) werase(sysglobal_window); + + (void) wattron(sysglobal_window, A_REVERSE); + (void) snprintf(header, sizeof (header), + "%s", lt_text("System wide latencies")); + fill_space_right(header, screen_width, sizeof (header)); + (void) mvwprintw(sysglobal_window, 0, 0, "%s", header); + (void) wattroff(sysglobal_window, A_REVERSE); + + list = lt_stat_list_create(current_list_type, + LT_LEVEL_GLOBAL, 0, 0, 10, sort_type); + print_statistics(sysglobal_window, 1, 10, list); + lt_stat_list_free(list); + + (void) wrefresh(sysglobal_window); +} + +/* + * Prints current operation mode: process/thread, window 1/2/3. + */ +static void +print_current_mode() +{ + char type; + + if (!display_initialized) { + return; + } + + switch (current_list_type) { + case LT_LIST_CAUSE: + type = 'C'; + break; + case LT_LIST_SPECIALS: + type = 'S'; + break; + case LT_LIST_SOBJ: + type = 'L'; + break; + default: + type = '?'; + break; + } + + (void) mvwprintw(process_window, 0, screen_width - 2, "%c%c", + type, thread_mode ? 'T' : 'P'); +} + +/* + * Print per-process statistics. Calls print_statistics(). + * This one is used in per-process mode. + */ +static void +print_process(unsigned int pid) +{ + void *list; + char header[256]; + char tmp[30]; + + if (!display_initialized) { + return; + } + + list = lt_stat_list_create(current_list_type, LT_LEVEL_PROCESS, + pid, 0, 8, sort_type); + + (void) werase(process_window); + (void) wattron(process_window, A_REVERSE); + + (void) snprintf(header, sizeof (header), "Process %s (%i) ", + lt_stat_proc_get_name(pid), pid); + fill_space_right(header, screen_width, sizeof (header)); + (void) mvwprintw(process_window, 0, 0, "%s", header); + + if (current_list_type != LT_LIST_SPECIALS) { + (void) mvwprintw(process_window, 0, 40, + lt_text("Total: %s from %d threads"), + get_time_string((double)lt_stat_list_get_gtotal(list), + tmp, sizeof (tmp), 12), + lt_stat_proc_get_nthreads(pid)); + } + print_current_mode(); + + (void) wattroff(process_window, A_REVERSE); + + print_statistics(process_window, 1, 8, list); + lt_stat_list_free(list); + + (void) wrefresh(process_window); +} + +/* + * List all processes in task bar. + * This one is used in per-process mode. + */ +static void +print_taskbar_process(pid_t *pidlist, int pidlist_len, int pidlist_index) +{ + const int ITEM_WIDTH = 8; + + int number_item; + int i; + int xpos = 0; + + if (!display_initialized) { + return; + } + + number_item = (screen_width / ITEM_WIDTH) - 1; + i = pidlist_index - (pidlist_index % number_item); + + (void) werase(taskbar); + if (i != 0) { + (void) mvwprintw(taskbar, 0, xpos, "<-"); + } + xpos = ITEM_WIDTH / 2; + + while (xpos + ITEM_WIDTH <= screen_width && i < pidlist_len) { + char str[ITEM_WIDTH+1]; + int slen; + const char *pname = lt_stat_proc_get_name(pidlist[i]); + + if (pname && pname[0]) { + (void) snprintf(str, sizeof (str) - 1, "%s", pname); + } else { + (void) snprintf(str, sizeof (str) - 1, + "<%d>", pidlist[i]); + } + + slen = strlen(str); + if (slen < ITEM_WIDTH) { + (void) memset(&str[slen], ' ', ITEM_WIDTH - slen); + } + + str[sizeof (str) - 1] = 0; + + if (i == pidlist_index) { + (void) wattron(taskbar, A_REVERSE); + } + (void) mvwprintw(taskbar, 0, xpos, "%s", str); + if (i == pidlist_index) { + (void) wattroff(taskbar, A_REVERSE); + } + + xpos += ITEM_WIDTH; + i++; + } + + if (i != pidlist_len) { + (void) mvwprintw(taskbar, 0, screen_width - 2, "->"); + } + (void) wrefresh(taskbar); +} + +/* + * List all processes in task bar. + * This one is used in per-thread mode. + */ +static void +print_taskbar_thread(pid_t *pidlist, id_t *tidlist, int list_len, + int list_index) +{ + const int ITEM_WIDTH = 12; + + int number_item; + int i; + int xpos = 0; + const char *pname = NULL; + pid_t last_pid = INVALID_PID; + + + if (!display_initialized) { + return; + } + + number_item = (screen_width - 8) / ITEM_WIDTH; + i = list_index - (list_index % number_item); + + (void) werase(taskbar); + if (i != 0) { + (void) mvwprintw(taskbar, 0, xpos, "<-"); + } + xpos = 4; + + while (xpos + ITEM_WIDTH <= screen_width && i < list_len) { + char str[ITEM_WIDTH+1]; + int slen, tlen; + + if (pidlist[i] != last_pid) { + pname = lt_stat_proc_get_name(pidlist[i]); + last_pid = pidlist[i]; + } + /* + * Calculate thread id length, leave enough space by print + * shorter process name. + */ + tlen = snprintf(NULL, 0, "_%d", tidlist[i]); + + if (pname && pname[0]) { + (void) snprintf(str, sizeof (str) - tlen - 1, + "%s", pname); + } else { + (void) snprintf(str, sizeof (str) - tlen - 1, + "<%d>", pidlist[i]); + } + slen = strlen(str); + + (void) snprintf(&str[slen], sizeof (str) - slen, + "_%d", tidlist[i]); + + slen += tlen; + + if (slen < ITEM_WIDTH) { + (void) memset(&str[slen], ' ', ITEM_WIDTH - slen); + } + str[sizeof (str) - 1] = 0; + + if (i == list_index) { + (void) wattron(taskbar, A_REVERSE); + } + (void) mvwprintw(taskbar, 0, xpos, "%s", str); + if (i == list_index) { + (void) wattroff(taskbar, A_REVERSE); + } + + xpos += ITEM_WIDTH; + i++; + } + + if (i != list_len) { + (void) mvwprintw(taskbar, 0, screen_width - 2, "->"); + } + (void) wrefresh(taskbar); +} + +/* + * Print statistics. Calls print_statistics(). + * This one is used in per-thread mode. + */ +static void +print_thread(pid_t pid, id_t tid) +{ + void *list; + char header[256]; + char tmp[30]; + + if (!display_initialized) { + return; + } + + list = lt_stat_list_create(current_list_type, LT_LEVEL_THREAD, + pid, tid, 8, sort_type); + + (void) werase(process_window); + (void) wattron(process_window, A_REVERSE); + + (void) snprintf(header, sizeof (header), + "Process %s (%i), LWP %d", + lt_stat_proc_get_name(pid), pid, tid); + fill_space_right(header, screen_width, sizeof (header)); + (void) mvwprintw(process_window, 0, 0, "%s", header); + + if (current_list_type != LT_LIST_SPECIALS) { + (void) mvwprintw(process_window, 0, 40, lt_text("Total: %s"), + get_time_string( + (double)lt_stat_list_get_gtotal(list), + tmp, sizeof (tmp), 12)); + } + print_current_mode(); + + (void) wattroff(process_window, A_REVERSE); + + print_statistics(process_window, 1, 8, list); + lt_stat_list_free(list); + + (void) wrefresh(process_window); +} + +/* + * Update hint string at the bottom line. The message to print is stored in + * hint. If hint is NULL, the function will pick a message from useful tips + * and display it. + */ +static void +print_hint(const char *hint) +{ + const char *HINTS[] = { + "Press '<' or '>' to switch between processes.", + "Press 'q' to exit.", + "Press 'r' to refresh immediately.", + "Press 't' to toggle Process/Thread display mode.", + "Press 'h' for help.", + "Use 'c', 'a', 'm', 'p' to change sort criteria." + "Use '1', '2', '3' to switch between windows." + }; + const uint64_t update_interval = 5000; /* 5 seconds */ + + static int index = 0; + static uint64_t next_hint = 0; + uint64_t now = lt_millisecond(); + + if (!display_initialized) { + return; + } + + if (hint == NULL) { + if (now < next_hint) { + return; + } + hint = HINTS[index]; + index = (index + 1) % (sizeof (HINTS) / sizeof (HINTS[0])); + next_hint = now + update_interval; + } else { + /* + * To ensure important message + * show at least 2 cycles. + */ + next_hint = now + update_interval * 2; + } + + (void) werase(hintbar); + (void) mvwprintw(hintbar, 0, (screen_width - strlen(hint)) / 2, + "%s", lt_text(hint)); + (void) wrefresh(hintbar); +} + +/* + * Get information from existing statistics, and create a PID list + * or PID/TID list based on current display mode. + */ +static void +get_plist(pid_t **plist, id_t **tlist, int *list_len, int *list_index) +{ + if (!thread_mode) { + /* Per-process mode */ + *list_len = lt_stat_proc_list_create(plist, NULL); + + /* Search for previous selected PID */ + for (*list_index = 0; *list_index < *list_len && + (*plist)[*list_index] != selected_pid; + ++*list_index) { + } + + if (*list_index >= *list_len) { + /* + * The old selected pid is gone. + * Select the first one + */ + *list_index = 0; + } + } else { + /* Per-thread mode */ + *list_len = lt_stat_proc_list_create(plist, tlist); + + /* Search for previous selected PID & TID */ + for (*list_index = 0; *list_index < *list_len; + ++*list_index) { + if ((*plist)[*list_index] == selected_pid && + (*tlist)[*list_index] == selected_tid) { + break; + } + } + + if (*list_index >= *list_len) { + /* + * The old selected pid/tid is gone. + * Select the first one in the pid + */ + for (*list_index = 0; + *list_index < *list_len && + (*plist)[*list_index] != selected_pid; + ++*list_index) { + } + } + if (*list_index >= *list_len) { + /* + * The old selected pid is gone. + * Select the first one + */ + *list_index = 0; + } + } +} + +static void +print_help(void) +{ + const char *HELP[] = { + TITLE, + COPYRIGHT, + "", + "These single-character commands are available:", + "< - Move to previous process/thread.", + "> - Move to next process/thread.", + "q - Exit.", + "r - Refresh.", + "t - Toggle process/thread mode.", + "c - Sort by count.", + "a - Sort by average.", + "m - Sort by maximum.", + "p - Sort by percent.", + "1 - Show list by causes.", + "2 - Show list of special entries.", + "3 - Show list by synchronization objects.", + "h - Show this help.", + "", + "Press any key to continue..." + }; + int i; + + if (!display_initialized) { + return; + } + + for (i = 0; i < sizeof (HELP) / sizeof (HELP[0]); ++i) { + (void) mvwprintw(stdscr, i, 0, "%s", HELP[i]); + } + (void) refresh(); +} + +/* + * Print title on screen + */ +static void +print_title(void) +{ + if (!display_initialized) { + return; + } + + (void) wattrset(titlebar, COLOR_PAIR(LT_COLOR_HEADER)); + (void) wbkgd(titlebar, COLOR_PAIR(LT_COLOR_HEADER)); + (void) werase(titlebar); + + (void) mvwprintw(titlebar, 0, (screen_width - strlen(TITLE)) / 2, + "%s", TITLE); + (void) wrefresh(titlebar); + + (void) werase(captionbar); + (void) mvwprintw(captionbar, 0, 0, "%s", lt_text( + " Cause " + "Count Average Maximum Percent")); + (void) wrefresh(captionbar); + + (void) wattrset(hintbar, COLOR_PAIR(LT_COLOR_HEADER)); + (void) wbkgd(hintbar, COLOR_PAIR(LT_COLOR_HEADER)); +} + +/* + * Signal handler on terminal resize + */ +/* ARGSUSED */ +static void +on_resize(int sig) +{ + lt_gpipe_break("r"); +} + +/* + * Initialize display part. Screen will be cleared when this function returns. + */ +void +lt_display_init(void) +{ + if (display_initialized) { + return; + } + + /* Window resize signal */ + (void) signal(SIGWINCH, on_resize); + + /* Initialize curses lib. */ + (void) initscr(); + (void) start_color(); + (void) keypad(stdscr, TRUE); + (void) nonl(); + (void) cbreak(); + (void) noecho(); + (void) curs_set(0); + + /* Set up color pairs */ + (void) init_pair(LT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK); + (void) init_pair(LT_COLOR_HEADER, COLOR_BLACK, COLOR_WHITE); + + curses_inited = TRUE; + + getmaxyx(stdscr, screen_height, screen_width); + if (screen_width < LT_WINDOW_X || screen_height < LT_WINDOW_Y) { + (void) mvwprintw(stdscr, 0, 0, "Terminal size is too small."); + (void) mvwprintw(stdscr, 1, 0, + "Please resize it to 80x24 or larger."); + (void) mvwprintw(stdscr, 2, 0, "Press q to quit."); + (void) refresh(); + return; + } + + /* Setup all windows on screen. */ + titlebar = subwin(stdscr, 1, screen_width, 0, 0); + captionbar = subwin(stdscr, 1, screen_width, 1, 0); + sysglobal_window = subwin(stdscr, screen_height / 2 - 1, + screen_width, 2, 0); + process_window = subwin(stdscr, screen_height / 2 - 3, + screen_width, screen_height / 2 + 1, 0); + taskbar = subwin(stdscr, 1, screen_width, screen_height - 2, 0); + hintbar = subwin(stdscr, 1, screen_width, screen_height - 1, 0); + (void) werase(stdscr); + (void) refresh(); + + display_initialized = TRUE; + + print_title(); +} + +/* + * The event loop. Display data on screen and handles key press. Will return + * after "duration" seconds, unless exit or refresh hotkey is pressed. + * Return 0 means main() should exit. 1 means to loop again. + */ +int +lt_display_loop(int duration) +{ + uint64_t start; + int remaining; + struct timeval timeout; + fd_set read_fd; + int need_refresh = TRUE; + pid_t *plist = NULL; + id_t *tlist = NULL; + int list_len = 0; + int list_index = 0; + int retval = 1; + int next_snap; + int gpipe; + + start = lt_millisecond(); + gpipe = lt_gpipe_readfd(); + + if (!show_help) { + print_hint(NULL); + print_sysglobal(); + } + get_plist(&plist, &tlist, &list_len, &list_index); + + for (;;) { + if (list_len != 0 && need_refresh && !show_help) { + if (!thread_mode) { + print_taskbar_process(plist, list_len, + list_index); + print_process(plist[list_index]); + } else { + print_taskbar_thread(plist, tlist, + list_len, list_index); + print_thread(plist[list_index], + tlist[list_index]); + } + } + need_refresh = TRUE; /* Usually we need refresh. */ + + remaining = duration - (int)(lt_millisecond() - start); + if (remaining <= 0) { + break; + } + /* Embedded dtrace snap action here. */ + next_snap = lt_dtrace_work(0); + if (next_snap == 0) { + /* + * Just did a snap, check again to get time for + * next shot. + */ + next_snap = lt_dtrace_work(0); + } + if (next_snap > 0 && remaining > next_snap) { + remaining = next_snap; + } + + timeout.tv_sec = remaining / 1000; + timeout.tv_usec = (remaining % 1000) * 1000; + FD_ZERO(&read_fd); + FD_SET(0, &read_fd); + FD_SET(gpipe, &read_fd); + + /* Wait for keyboard input, or signal from gpipe */ + if (select(gpipe + 1, &read_fd, NULL, NULL, &timeout) > 0) { + int k = 0; + + if (FD_ISSET(gpipe, &read_fd)) { + /* data from pipe has priority */ + char ch; /* Need this for big-endian */ + (void) read(gpipe, &ch, 1); + k = ch; + } else { + k = getch(); + } + + /* + * We check if we need to update hint line whenever we + * get chance. + * NOTE: current implementation depends on + * g_config.snap_interval, but it's OK because it + * doesn't have to be precise. + */ + print_hint(NULL); + /* + * If help is on, and a key press happens, + * we need to clear the help and go on. + */ + if (show_help) { + (void) werase(stdscr); + (void) refresh(); + print_title(); + print_sysglobal(); + show_help = FALSE; + /* Drop this key and continue */ + continue; + } + + switch (k) { + case 'Q': + case 'q': + retval = 0; + goto quit; + case 'R': + case 'r': + lt_display_deinit(); + lt_display_init(); + goto quit; + case 'H': + case 'h': + show_help = TRUE; + (void) werase(stdscr); + (void) refresh(); + print_help(); + break; + case ',': + case '<': + case KEY_LEFT: + --list_index; + if (list_index < 0) { + list_index = 0; + } + break; + case '.': + case '>': + case KEY_RIGHT: + ++list_index; + if (list_index >= list_len) { + list_index = list_len - 1; + } + break; + case 'a': + case 'A': + sort_type = LT_SORT_AVG; + print_sysglobal(); + break; + case 'p': + case 'P': + sort_type = LT_SORT_TOTAL; + print_sysglobal(); + break; + case 'm': + case 'M': + sort_type = LT_SORT_MAX; + print_sysglobal(); + break; + case 'c': + case 'C': + sort_type = LT_SORT_COUNT; + print_sysglobal(); + break; + case 't': + case 'T': + if (plist != NULL) { + selected_pid = plist[list_index]; + } + selected_tid = INVALID_TID; + thread_mode = !thread_mode; + get_plist(&plist, &tlist, + &list_len, &list_index); + break; + case '1': + case '!': + current_list_type = LT_LIST_CAUSE; + print_sysglobal(); + break; + case '2': + case '@': + if (g_config.low_overhead_mode) { + lt_display_error("Switching mode is " + "not available for '-f low'."); + } else { + current_list_type = LT_LIST_SPECIALS; + print_sysglobal(); + } + break; + case '3': + case '#': + if (g_config.trace_syncobj) { + current_list_type = LT_LIST_SOBJ; + print_sysglobal(); + } else if (g_config.low_overhead_mode) { + lt_display_error("Switching mode is " + "not available for '-f low'."); + } else { + lt_display_error("Tracing " + "synchronization objects is " + "disabled."); + } + break; + default: + /* Wake up for nothing, no need to refresh */ + need_refresh = FALSE; + break; + } + } else { + need_refresh = FALSE; + } + } + +quit: + if (plist != NULL) { + selected_pid = plist[list_index]; + } + if (tlist != NULL) { + selected_tid = tlist[list_index]; + } + lt_stat_proc_list_free(plist, tlist); + + return (retval); +} + +/* + * Close display part. + */ +void +lt_display_deinit(void) +{ + if (curses_inited) { + (void) clear(); + (void) refresh(); + (void) endwin(); + } + + titlebar = NULL; + captionbar = NULL; + sysglobal_window = NULL; + taskbar = NULL; + process_window = NULL; + hintbar = NULL; + screen_width = 1; + screen_height = 1; + + display_initialized = FALSE; + curses_inited = FALSE; +} + +/* + * Print error message. + */ +/* ARGSUSED */ +void +lt_display_error(const char *fmt, ...) +{ + va_list vl; + char tmp[81]; + int l; + + va_start(vl, fmt); + (void) vsnprintf(tmp, sizeof (tmp), fmt, vl); + va_end(vl); + + l = strlen(tmp); + while (l > 0 && (tmp[l - 1] == '\n' || tmp[l - 1] == '\r')) { + tmp[l - 1] = 0; + --l; + } + + if (!display_initialized) { + (void) printf("%s\n", tmp); + } else if (!show_help) { + print_hint(tmp); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/common/dwrapper.c Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,548 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2008-2009, Intel Corporation. + * All Rights Reserved. + */ + +#include <unistd.h> +#include <stdio.h> +#include <dtrace.h> +#include <string.h> +#include <stdlib.h> +#include <memory.h> +#include <limits.h> + +#include "latencytop.h" + +static dtrace_hdl_t *g_dtp = NULL; /* The dtrace handle */ +static pid_t pid_self = -1; /* PID of our own process */ + +/* + * Checks if the process is latencytop itself or sched (if we are not tracing + * sched), we should ignore them. + */ +#define SHOULD_IGNORE(pid) \ + ((!g_config.trace_sched && 0 == (pid)) || pid_self == (pid)) + +/* + * Get an integer value from dtrace record. + */ +static uint64_t +rec_get_value(void *a, size_t b) +{ + uint64_t ret = 0; + + switch (b) { + case sizeof (uint64_t): + ret = *((uint64_t *)(a)); + break; + case sizeof (uint32_t): + ret = *((uint32_t *)(a)); + break; + case sizeof (uint16_t): + ret = *((uint16_t *)(a)); + break; + case sizeof (uint8_t): + ret = *((uint8_t *)(a)); + break; + default: + break; + } + + return (ret); +} + +/* + * Callback to process each aggregation in the snapshot. + * This one processes lt_call_*, which contains on/off cpu activites. + */ +static int +aggwalk_call(const dtrace_aggdata_t *data, lt_stat_type_t stat_type) +{ + const int REC_PID = 1; + const int REC_TID = 2; + const int REC_STACK = 3; + const int REC_AGG = 4; + const int NREC = 5; + + dtrace_aggdesc_t *aggdesc = data->dtada_desc; + dtrace_syminfo_t dts; + GElf_Sym sym; + caddr_t addr; + pid_t pid; + id_t tid; + unsigned int stack_depth; + unsigned int pc_size; + uint64_t pc; + uint64_t agg_value; + char *ptr = NULL; + char *buffer = NULL; + int ptrsize; + unsigned int buffersize; + + if (aggdesc->dtagd_nrecs < NREC) { + /* Not enough records */ + goto err; + } + + if (aggdesc->dtagd_rec[REC_PID].dtrd_action != DTRACEACT_DIFEXPR) { + /* Record is not PID, this is an error. */ + goto err; + } + pid = rec_get_value( + data->dtada_data + aggdesc->dtagd_rec[REC_PID].dtrd_offset, + aggdesc->dtagd_rec[REC_PID].dtrd_size); + if (SHOULD_IGNORE(pid)) { + goto done; + } + + if (aggdesc->dtagd_rec[REC_TID].dtrd_action != DTRACEACT_DIFEXPR) { + /* Record is not TID, this is an error. */ + goto err; + } + tid = rec_get_value( + data->dtada_data + aggdesc->dtagd_rec[REC_TID].dtrd_offset, + aggdesc->dtagd_rec[REC_TID].dtrd_size); + + if (aggdesc->dtagd_rec[REC_STACK].dtrd_action != DTRACEACT_STACK) { + /* Record is not stack(), this is an error. */ + goto err; + } + + /* Parse stack array from dtagd_rec */ + stack_depth = aggdesc->dtagd_rec[REC_STACK].dtrd_arg; + pc_size = aggdesc->dtagd_rec[REC_STACK].dtrd_size / stack_depth; + addr = data->dtada_data + aggdesc->dtagd_rec[REC_STACK].dtrd_offset; + buffersize = (stack_depth * (2 * PATH_MAX + 2) + 1) * sizeof (char); + buffer = (char *)lt_malloc(buffersize); + ptr = buffer; + ptrsize = buffersize; + + /* Print the stack */ + while (stack_depth > 0) { + pc = rec_get_value(addr, pc_size); + if (pc == 0) { + break; + } + addr += pc_size; + if (dtrace_lookup_by_addr(g_dtp, pc, &sym, &dts) == 0) { + int len; + len = snprintf(ptr, ptrsize, + "%s`%s ", dts.dts_object, dts.dts_name); + ptrsize -= len; + if (ptrsize <= 0) { + /* + * Snprintf returns "desired" length, so + * reaching here means our buffer is full. + * Move ptr to last byte in the buffer and + * break early. + */ + ptr = &buffer[buffersize-1]; + break; + } else { + ptr += len; + } + } + } + + if (ptr != buffer) { + /* + * We have printed something, + * so it is safe to remove last ' '. + */ + *(ptr-1) = 0; + } + + /* Parsing aggregation data */ + if (!DTRACEACT_ISAGG(aggdesc->dtagd_rec[REC_AGG].dtrd_action)) { + /* Record is not aggregation, this is an error. */ + goto err; + } + agg_value = rec_get_value( + data->dtada_data + aggdesc->dtagd_rec[REC_AGG].dtrd_offset, + aggdesc->dtagd_rec[REC_AGG].dtrd_size); + + lt_stat_update(pid, tid, buffer, stat_type, agg_value); + +done: + if (buffer != NULL) { + free(buffer); + } + return (0); + +err: + if (buffer != NULL) { + free(buffer); + } + return (-1); +} + +/* + * Callback to process each aggregation in the snapshot. + * This one processes lt_named_*, which contains data such as lock spinning. + */ +static int +aggwalk_named(const dtrace_aggdata_t *data, lt_stat_type_t stat_type) +{ + const int REC_PID = 1; + const int REC_TID = 2; + const int REC_TYPE = 3; + const int REC_AGG = 4; + const int NREC = 5; + + dtrace_aggdesc_t *aggdesc = data->dtada_desc; + pid_t pid; + id_t tid; + uint64_t agg_value; + int cause_id; + char *type = NULL; + + if (aggdesc->dtagd_nrecs < NREC) { + /* Not enough records */ + return (-1); + } + + if (aggdesc->dtagd_rec[REC_PID].dtrd_action != DTRACEACT_DIFEXPR) { + /* Record is not PID, this is an error. */ + return (-1); + } + pid = rec_get_value( + data->dtada_data + aggdesc->dtagd_rec[REC_PID].dtrd_offset, + aggdesc->dtagd_rec[REC_PID].dtrd_size); + if (SHOULD_IGNORE(pid)) { + return (0); + } + if (aggdesc->dtagd_rec[REC_TID].dtrd_action != DTRACEACT_DIFEXPR) { + /* Record is not TID, this is an error. */ + return (-1); + } + tid = rec_get_value( + data->dtada_data + aggdesc->dtagd_rec[REC_TID].dtrd_offset, + aggdesc->dtagd_rec[REC_TID].dtrd_size); + + if (aggdesc->dtagd_rec[REC_TYPE].dtrd_action != DTRACEACT_DIFEXPR) { + /* Record is not type, this is an error. */ + return (-1); + } + type = (char *)data->dtada_data + + aggdesc->dtagd_rec[REC_TYPE].dtrd_offset; + cause_id = lt_table_lookup_named_cause(type, 1); + + /* Parsing aggregation data */ + if (!DTRACEACT_ISAGG(aggdesc->dtagd_rec[REC_AGG].dtrd_action)) { + /* Record is not aggregation, this is an error. */ + return (-1); + } + agg_value = rec_get_value( + data->dtada_data + aggdesc->dtagd_rec[REC_AGG].dtrd_offset, + aggdesc->dtagd_rec[REC_AGG].dtrd_size); + + lt_stat_update_cause(pid, tid, cause_id, stat_type, agg_value); + + return (0); + +} + +/* + * Callback to process each aggregation in the snapshot. + * This one processes lt_sync_*, which traces synchronization objects. + */ +static int +aggwalk_sync(const dtrace_aggdata_t *data, lt_stat_type_t stat_type) +{ + const int REC_PID = 1; + const int REC_TID = 2; + const int REC_STYPE = 3; + const int REC_WCHAN = 4; + const int REC_AGG = 5; + const int NREC = 6; + + dtrace_aggdesc_t *aggdesc = data->dtada_desc; + pid_t pid; + id_t tid; + uint64_t agg_value; + int stype; + unsigned long long wchan; + + if (aggdesc->dtagd_nrecs < NREC) { + /* Not enough records */ + return (-1); + } + + if (aggdesc->dtagd_rec[REC_PID].dtrd_action != DTRACEACT_DIFEXPR) { + /* Record is not PID, this is an error. */ + return (-1); + } + pid = rec_get_value( + data->dtada_data + aggdesc->dtagd_rec[REC_PID].dtrd_offset, + aggdesc->dtagd_rec[REC_PID].dtrd_size); + if (SHOULD_IGNORE(pid)) { + return (0); + } + + if (aggdesc->dtagd_rec[REC_TID].dtrd_action != DTRACEACT_DIFEXPR) { + /* Record is not TID, this is an error. */ + return (-1); + } + tid = rec_get_value( + data->dtada_data + aggdesc->dtagd_rec[REC_TID].dtrd_offset, + aggdesc->dtagd_rec[REC_TID].dtrd_size); + + if (aggdesc->dtagd_rec[REC_STYPE].dtrd_action != DTRACEACT_DIFEXPR) { + /* Record is not stype, this is an error. */ + return (-1); + } + stype = rec_get_value( + data->dtada_data + aggdesc->dtagd_rec[REC_STYPE].dtrd_offset, + aggdesc->dtagd_rec[REC_STYPE].dtrd_size); + + if (aggdesc->dtagd_rec[REC_WCHAN].dtrd_action != DTRACEACT_DIFEXPR) { + /* Record is not wchan, this is an error. */ + return (-1); + } + wchan = rec_get_value( + data->dtada_data + aggdesc->dtagd_rec[REC_WCHAN].dtrd_offset, + aggdesc->dtagd_rec[REC_WCHAN].dtrd_size); + + /* Parsing aggregation data */ + if (!DTRACEACT_ISAGG(aggdesc->dtagd_rec[REC_AGG].dtrd_action)) { + /* Record is not aggregation, this is an error. */ + return (-1); + } + agg_value = rec_get_value( + data->dtada_data + aggdesc->dtagd_rec[REC_AGG].dtrd_offset, + aggdesc->dtagd_rec[REC_AGG].dtrd_size); + + lt_stat_update_sobj(pid, tid, stype, wchan, stat_type, agg_value); + + return (0); +} + +/* + * Callback to process each aggregation in the snapshot. + * This one dispatches to different aggwalk_*(). + */ +/* ARGSUSED */ +static int +aggwalk(const dtrace_aggdata_t *data, void *arg) +{ + char *tmp; + char buffer[32]; + lt_stat_type_t stat_type = LT_STAT_COUNT; + int (*func)(const dtrace_aggdata_t *, lt_stat_type_t); + + (void) strncpy(buffer, data->dtada_desc->dtagd_name, sizeof (buffer)); + buffer[sizeof (buffer) - 1] = 0; + + tmp = strtok(buffer, "_"); + if (strcmp(tmp, "lt") != 0) { + goto done; + } + + tmp = strtok(NULL, "_"); + if (strcmp(tmp, "call") == 0) { + func = aggwalk_call; + } else if (strcmp(tmp, "named") == 0) { + func = aggwalk_named; + } else if (strcmp(tmp, "sync") == 0) { + func = aggwalk_sync; + } else { + goto done; + } + + tmp = strtok(NULL, "_"); + if (strcmp(tmp, "count") == 0) { + stat_type = LT_STAT_COUNT; + } else if (strcmp(tmp, "sum") == 0) { + stat_type = LT_STAT_SUM; + } else if (strcmp(tmp, "max") == 0) { + stat_type = LT_STAT_MAX; + } else { + goto done; + } + + (void) func(data, stat_type); + +done: + /* We have our data, remove it from DTrace. */ + return (DTRACE_AGGWALK_REMOVE); +} + +/* + * Callback to handle DTrace drop data events. + */ +/*ARGSUSED*/ +static int +drop_handler(const dtrace_dropdata_t *data, void *user) +{ + lt_display_error("Drop: %s\n", data->dtdda_msg); + /* + * Pretend nothing happened. So our program can continue. + */ + return (DTRACE_HANDLE_OK); +} + +/* + * DTrace initialization. The D script is running when this function returns. + */ +int +lt_dtrace_init(void) +{ + dtrace_prog_t *prog; + dtrace_proginfo_t info; + int err; + FILE *fp_script = NULL; + + pid_self = getpid(); + /* Open dtrace, set up handler */ + g_dtp = dtrace_open(DTRACE_VERSION, 0, &err); + if (g_dtp == NULL) { + lt_display_error("Cannot open dtrace library: %s\n", + dtrace_errmsg(NULL, err)); + return (-1); + } + + if (dtrace_handle_drop(g_dtp, &drop_handler, NULL) == -1) { + lt_display_error("Cannot install DTrace handle: %s\n", + dtrace_errmsg(NULL, err)); + return (-1); + } + + /* Load D script, set up macro and compile */ +#ifdef EMBED_CONFIGS + /* Create a temp file because libdtrace use cpp(1) on files only. */ + fp_script = tmpfile(); + if (fp_script == NULL) { + lt_display_error("Cannot create tmp file\n"); + return (-1); + } + (void) fwrite(latencytop_d, latencytop_d_len, 1, fp_script); + (void) fseek(fp_script, 0, SEEK_SET); +#else + fp_script = fopen(DEFAULT_D_SCRIPT_NAME, "r"); + if (fp_script == NULL) { + lt_display_error("Cannot open script file %s\n", + DEFAULT_D_SCRIPT_NAME); + return (-1); + } +#endif /* EMBED_CONFIGS */ + + if (g_config.enable_filter) { + (void) dtrace_setopt(g_dtp, "define", "ENABLE_FILTER"); + } + if (g_config.trace_syncobj) { + (void) dtrace_setopt(g_dtp, "define", "ENABLE_SYNCOBJ"); + } + if (g_config.trace_sched) { + (void) dtrace_setopt(g_dtp, "define", "ENABLE_SCHED"); + } + if (g_config.low_overhead_mode) { + (void) dtrace_setopt(g_dtp, "define", "ENABLE_LOW_OVERHEAD"); + } + + prog = dtrace_program_fcompile(g_dtp, fp_script, + DTRACE_C_CPP, 0, NULL); + (void) fclose(fp_script); + if (prog == NULL) { + lt_display_error("Failed to compile D script.\n"); + return (dtrace_errno(g_dtp)); + } + + /* Execute the D script */ + if (dtrace_program_exec(g_dtp, prog, &info) == -1) { + lt_display_error("Failed to enable probes.\n"); + return (dtrace_errno(g_dtp)); + } + if (dtrace_go(g_dtp) != 0) { + lt_display_error("Failed to run D script.\n"); + return (dtrace_errno(g_dtp)); + } + return (0); +} + +/* + * Worker function to move aggregator data to user space. + * Needs to be called periodically to prevent running out of kernel memory. + */ +int +lt_dtrace_work(int force) +{ + static uint64_t last_snap = 0; + uint64_t now = lt_millisecond(); + + if (!force && now - last_snap < g_config.snap_interval) { + return (last_snap + g_config.snap_interval - now); + } + + if (dtrace_status(g_dtp) == -1) { + lt_display_error("Failed when getting status: %s\n", + dtrace_errmsg(g_dtp, dtrace_errno(g_dtp))); + return (-1); + } + + if (dtrace_aggregate_snap(g_dtp) != 0) { + lt_display_error("Failed to snap aggregate: %s\n", + dtrace_errmsg(g_dtp, dtrace_errno(g_dtp))); + return (-1); + } + + last_snap = now; + return (0); +} + +/* + * Walk through aggregator and collect data to LatencyTOP. + * Different from lt_dtrace_work, this one moves data from libdtrace + * to latencytop. + * This needs to be called immediately before update UI. + */ +int +lt_dtrace_collect(void) +{ + if (lt_dtrace_work(1) != 0) { + return (-1); + } + + if (dtrace_aggregate_walk(g_dtp, aggwalk, NULL) != 0) { + lt_display_error("Failed to sort aggregate: %s\n", + dtrace_errmsg(g_dtp, dtrace_errno(g_dtp))); + return (-1); + } + + /* + * Probably no need to clear again, because we removed everything. + * Paranoid. + */ + dtrace_aggregate_clear(g_dtp); + + return (0); +} + +/* + * Clean up and close DTrace. + */ +void +lt_dtrace_deinit(void) +{ + (void) dtrace_stop(g_dtp); + dtrace_close(g_dtp); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/common/klog.c Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,231 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2008-2009, Intel Corporation. + * All Rights Reserved. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <memory.h> +#include <string.h> +#include <procfs.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <limits.h> +#include <unistd.h> + +#include "latencytop.h" + +static GHashTable *proc_table = NULL; /* pid -> char * */ +static GHashTable *klog_table = NULL; /* char * -> uint64_t total */ +static char klog_filename[PATH_MAX] = DEFAULT_KLOG_FILE; +static int klog_level = LT_KLOG_LEVEL_NONE; + +static void +print_proc(void *key, const char *args, FILE *fp) +{ + pid_t pid = LT_POINTER_TO_INT(key); + char tmp[16]; + + (void) snprintf(tmp, sizeof (tmp), "%ld,", (long)pid); + (void) fprintf(fp, "%-8s \"%s\"\n", tmp, args); +} + +static void +print_stat(const char *key, lt_stat_data_t *log, FILE *fp) +{ + (void) fprintf(fp, "%lld, %lld, %lld, %s\n", + (long long)log->total, + (long long)log->count, + (long long)log->max, + key); +} + +/* + * Initialize kernel stack logging. + */ +void +lt_klog_init(void) +{ + if (klog_table != NULL || proc_table != NULL) { + return; + } + + klog_table = g_hash_table_new_full(g_str_hash, g_str_equal, + (GDestroyNotify)free, (GDestroyNotify)free); + lt_check_null(klog_table); + + proc_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify)free); + lt_check_null(proc_table); +} + +/* + * Set log file path. + */ +int +lt_klog_set_log_file(const char *filename) +{ + FILE *fp; + int file_exist; + + if (strlen(filename) >= sizeof (klog_filename)) { + return (-1); + } + + file_exist = lt_file_exist(filename); + /* Test if we can write to the file */ + fp = fopen(filename, "a"); + if (fp == NULL) { + return (-2); + } + (void) fclose(fp); + /* Don't leave empty file behind */ + if (!file_exist) { + (void) unlink(filename); + } + + (void) strncpy(klog_filename, filename, + sizeof (klog_filename)); + + return (0); +} + +/* + * Set log level. + */ +int +lt_klog_set_log_level(int level) +{ + if (level < 0 || level > (int)LT_KLOG_LEVEL_ALL) { + return (-1); + } + + klog_level = level; + + return (0); +} + +/* + * Write the log to file. + */ +void +lt_klog_write(void) +{ + FILE *fp; + char buffer[32]; + + if (klog_level == LT_KLOG_LEVEL_NONE) { + return; + } + + g_assert(klog_table != NULL && proc_table != NULL); + + fp = fopen(klog_filename, "a"); + if (fp == NULL) { + return; + } + + lt_time_str(buffer, sizeof (buffer)); + + (void) fprintf(fp, "# Log generated %s by %s\n", buffer, TITLE); + (void) fprintf(fp, "# List of processes\n"); + (void) fprintf(fp, "PID, CMD\n"); + g_hash_table_foreach(proc_table, + (GHFunc)print_proc, fp); + + (void) fprintf(fp, "# Statistics\n"); + (void) fprintf(fp, "TOTAL, COUNT, MAX, PID, KSTACK\n"); + g_hash_table_foreach(klog_table, + (GHFunc)print_stat, fp); + + (void) fclose(fp); +} + +/* + * Clean up function. This will cause all log in memory be written to the + * log file. + */ +void +lt_klog_deinit(void) +{ + if (klog_table != NULL) { + g_hash_table_destroy(klog_table); + klog_table = NULL; + } + + if (proc_table != NULL) { + g_hash_table_destroy(proc_table); + proc_table = NULL; + } +} + +/* + * Log a stack and its statistics. Only "total" will be logged, others are + * internally discarded. + */ +/* ARGSUSED */ +void +lt_klog_log(int level, pid_t pid, char *stack, + lt_stat_type_t type, uint64_t value) +{ + lt_stat_data_t *entry = NULL; + char *psargs; + char *str; + int str_len; + + if ((level & klog_level) == 0) { + return; + } + g_assert(klog_table != NULL && proc_table != NULL); + + psargs = (char *)g_hash_table_lookup(proc_table, + LT_INT_TO_POINTER(pid)); + if (psargs == NULL) { + psargs = lt_get_proc_field(pid, LT_FIELD_PSARGS); + if (psargs == NULL) { + psargs = lt_get_proc_field(pid, LT_FIELD_FNAME); + } + + if (psargs == NULL) { + return; + } + + g_hash_table_insert(proc_table, + LT_INT_TO_POINTER(pid), psargs); + } + + str_len = strlen(stack) + 20; + str = lt_malloc(str_len); + (void) snprintf(str, str_len, "%ld, \"%s\"", pid, stack); + + entry = (lt_stat_data_t *)g_hash_table_lookup(klog_table, str); + if (entry == NULL) { + entry = (lt_stat_data_t *)lt_zalloc(sizeof (lt_stat_data_t)); + g_hash_table_insert(klog_table, str, entry); + } else { + free(str); + } + + lt_update_stat_value(entry, type, value); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/common/latencytop.c Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,362 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2008-2009, Intel Corporation. + * All Rights Reserved. + */ + +#include <unistd.h> +#include <getopt.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <libgen.h> +#include <signal.h> +#include "latencytop.h" + +#define CMPOPT(a, b) strncmp((a), (b), sizeof (b)) + +lt_config_t g_config; + +/* + * Prints help for command line parameters. + */ +static void +print_usage(const char *execname) +{ + char buffer[PATH_MAX]; + (void) snprintf(buffer, sizeof (buffer), "%s", execname); + (void) fprintf(stderr, "\nUsage: %s [option(s)]\n", basename(buffer)); + (void) fprintf(stderr, "Options:\n" + " -h, --help\n" + " Print this help.\n" + " -t, --interval TIME\n" + " Set refresh interval to TIME. " + "Valid range [1...60] seconds, default = 5\n" + /* + * Keep this option private, until we have chance to properly document + * the format of translation rules. + */ +#if 0 + " -c, --config FILE\n" + " Use translation rules defined in FILE.\n" +#endif + " -o, --output-log-file FILE\n" + " Output kernel log to FILE. Default = " + DEFAULT_KLOG_FILE "\n" + " -k, --kernel-log-level LEVEL\n" + " Set kernel log level to LEVEL.\n" + " 0(default) = None, 1 = Unmapped, 2 = Mapped, 3 = All.\n" + " -f, --feature [no]feature1,[no]feature2,...\n" + " Enable/disable features in LatencyTOP.\n" + " [no]filter:\n" + " Filter large interruptible latencies, e.g. sleep.\n" + " [no]sched:\n" + " Monitors sched (PID=0).\n" + " [no]sobj:\n" + " Monitors synchronization objects.\n" + " [no]low:\n" + " Lower overhead by sampling small latencies.\n" + " -l, --log-period TIME\n" + " Write and restart log every TIME seconds, TIME > 60s\n"); +} + +/* + * Properly shut down when latencytop receives SIGINT or SIGTERM. + */ +/* ARGSUSED */ +static void +signal_handler(int sig) +{ + lt_gpipe_break("q"); +} + +/* + * Convert string to integer, return error if extra characters are found. + */ +static int +to_int(const char *str, int *result) +{ + char *tail = NULL; + long ret; + + if (str == NULL || result == NULL) { + return (-1); + } + + ret = strtol(str, &tail, 10); + + if (tail != NULL && *tail != '\0') { + return (-1); + } + + *result = (int)ret; + + return (0); +} + +/* + * The main function. + */ +int +main(int argc, char *argv[]) +{ + const char *opt_string = "t:o:k:hf:l:c:"; + struct option const longopts[] = + { + {"interval", required_argument, NULL, 't'}, + {"output-log-file", required_argument, NULL, 'o'}, + {"kernel-log-level", required_argument, NULL, 'k'}, + {"help", no_argument, NULL, 'h'}, + {"feature", required_argument, NULL, 'f'}, + {"log-period", required_argument, NULL, 'l'}, + {"config", required_argument, NULL, 'c'}, + {NULL, 0, NULL, 0} + }; + + int optc; + int longind = 0; + int running = 1; + int unknown_option = FALSE; + int refresh_interval = 5; + int klog_level = 0; + int log_interval = 0; + long long last_logged = 0; + char *token = NULL; + int retval = 0; + int gpipe; + int err; + uint64_t collect_end; + uint64_t current_time; + uint64_t delta_time; + + lt_gpipe_init(); + (void) signal(SIGINT, signal_handler); + (void) signal(SIGTERM, signal_handler); + + (void) printf("%s\n%s\n", TITLE, COPYRIGHT); + + /* Default global settings */ + g_config.enable_filter = 0; + g_config.trace_sched = 0; + g_config.trace_syncobj = 1; + g_config.low_overhead_mode = 0; + g_config.snap_interval = 1000; /* DTrace snapshot every 1 sec */ +#ifdef EMBED_CONFIGS + g_config.config_name = NULL; +#else + g_config.config_name = lt_strdup(DEFAULT_CONFIG_NAME); +#endif + + /* Parse command line arguments. */ + while ((optc = getopt_long(argc, argv, opt_string, + longopts, &longind)) != -1) { + switch (optc) { + case 'h': + print_usage(argv[0]); + goto end_none; + case 't': + if (to_int(optarg, &refresh_interval) != 0 || + refresh_interval < 1 || refresh_interval > 60) { + lt_display_error( + "Invalid refresh interval: %s\n", optarg); + unknown_option = TRUE; + } + break; + case 'k': + if (to_int(optarg, &klog_level) != 0 || + lt_klog_set_log_level(klog_level) != 0) { + lt_display_error( + "Invalid log level: %s\n", optarg); + unknown_option = TRUE; + } + break; + case 'o': + err = lt_klog_set_log_file(optarg); + if (err == 0) { + (void) printf("Writing to log file %s.\n", + optarg); + } else if (err == -1) { + lt_display_error( + "Log file name is too long: %s\n", + optarg); + unknown_option = TRUE; + } else if (err == -2) { + lt_display_error( + "Cannot write to log file: %s\n", + optarg); + unknown_option = TRUE; + } + break; + case 'f': + for (token = strtok(optarg, ","); token != NULL; + token = strtok(NULL, ",")) { + int v = TRUE; + if (strncmp(token, "no", 2) == 0) { + v = FALSE; + token = &token[2]; + } + if (CMPOPT(token, "filter") == 0) { + g_config.enable_filter = v; + } else if (CMPOPT(token, "sched") == 0) { + g_config.trace_sched = v; + } else if (CMPOPT(token, "sobj") == 0) { + g_config.trace_syncobj = v; + } else if (CMPOPT(token, "low") == 0) { + g_config.low_overhead_mode = v; + } else { + lt_display_error( + "Unknown feature: %s\n", token); + unknown_option = TRUE; + } + } + break; + case 'l': + if (to_int(optarg, &log_interval) != 0 || + log_interval < 60) { + lt_display_error( + "Invalid refresh interval: %s\n", optarg); + unknown_option = TRUE; + } + break; + case 'c': + if (strlen(optarg) > PATH_MAX) { + lt_display_error( + "Configuration name is too long.\n"); + unknown_option = TRUE; + } else { + g_config.config_name = lt_strdup(optarg); + } + break; + default: + unknown_option = TRUE; + break; + } + } + + /* Throw error for commands like: "latencytop 12345678" */ + if (optind < argc) { + int tmpind = optind; + (void) printf("Unknown option(s): "); + while (tmpind < argc) { + (void) printf("%s ", argv[tmpind++]); + } + (void) printf("\n"); + unknown_option = TRUE; + } + + if (unknown_option) { + print_usage(argv[0]); + retval = 1; + goto end_none; + } + + /* Initialization */ + lt_klog_init(); + if (lt_table_init() != 0) { + lt_display_error("Unable to load configuration table.\n"); + retval = 1; + goto end_notable; + } + if (lt_dtrace_init() != 0) { + lt_display_error("Unable to initialize dtrace.\n"); + retval = 1; + goto end_nodtrace; + } + + last_logged = lt_millisecond(); + + (void) printf("Collecting data for %d seconds...\n", + refresh_interval); + + gpipe = lt_gpipe_readfd(); + collect_end = last_logged + refresh_interval * 1000; + for (;;) { + fd_set read_fd; + struct timeval timeout; + int tsleep = collect_end - lt_millisecond(); + + if (tsleep <= 0) { + break; + } + + if (tsleep > g_config.snap_interval * 1000) { + tsleep = g_config.snap_interval * 1000; + } + + timeout.tv_sec = tsleep / 1000; + timeout.tv_usec = (tsleep % 1000) * 1000; + + FD_ZERO(&read_fd); + FD_SET(gpipe, &read_fd); + if (select(gpipe + 1, &read_fd, NULL, NULL, &timeout) > 0) { + goto end_ubreak; + } + + (void) lt_dtrace_work(0); + } + + lt_display_init(); + + do { + current_time = lt_millisecond(); + + lt_stat_clear_all(); + (void) lt_dtrace_collect(); + + delta_time = current_time; + current_time = lt_millisecond(); + delta_time = current_time - delta_time; + + if (log_interval > 0 && + current_time - last_logged > log_interval * 1000) { + lt_klog_write(); + last_logged = current_time; + } + + running = lt_display_loop(refresh_interval * 1000 - + delta_time); + } while (running != 0); + + lt_klog_write(); + + /* Cleanup */ + lt_display_deinit(); + +end_ubreak: + lt_dtrace_deinit(); + lt_stat_free_all(); + +end_nodtrace: + lt_table_deinit(); + +end_notable: + lt_klog_deinit(); + +end_none: + lt_gpipe_deinit(); + if (g_config.config_name != NULL) { + free(g_config.config_name); + } + + return (retval); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/common/latencytop.d Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,305 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2008-2009, Intel Corporation. + * All Rights Reserved. + */ + +#pragma D option aggsize=8m +#pragma D option bufsize=16m +#pragma D option dynvarsize=16m +#pragma D option aggrate=0 +#pragma D option stackframes=64 + +#if defined(ENABLE_SCHED) +#define TRACE_FILTER +#define TRACE_FILTER_COND(a) / (a) / +#else +#define TRACE_FILTER / pid != 0 / +#define TRACE_FILTER_COND(a) / pid != 0 && (a) / +#endif + +#define FILTER_THRESHOLD 5000000 +/* From thread.h */ +#define T_WAKEABLE 2 + +/* + * This array is used to store the timestamp when threads are enqueued + * to dispq. + * self-> is not accessible when enqueue happens. + */ +unsigned long long lt_timestamps[int, int]; + +self unsigned int lt_is_block_wakeable; +self unsigned long long lt_sleep_start; +self unsigned long long lt_sleep_duration; +self unsigned long long lt_sch_delay; +self unsigned int lt_counter; /* only used in low overhead */ +self unsigned long long lt_timestamp; /* only used in low overhead */ + +/* + * Make sure we leave nothing behind, + * otherwise memory will eventually run out. + */ +proc:::lwp-exit +{ + lt_timestamps[curpsinfo->pr_pid, curlwpsinfo->pr_lwpid] = 0; + self->lt_sleep_start = 0; + self->lt_is_block_wakeable = 0; + self->lt_counter = 0; + self->lt_timestamp = 0; +} + +#if !defined(ENABLE_LOW_OVERHEAD) +/* + * Log timestamp when a thread is off CPU. + */ +sched::resume:off-cpu +TRACE_FILTER_COND(curlwpsinfo->pr_state == SSLEEP) +{ + self->lt_sleep_start = timestamp; + self->lt_is_block_wakeable = curthread->t_flag & T_WAKEABLE; + lt_timestamps[curpsinfo->pr_pid, curlwpsinfo->pr_lwpid] = + self->lt_sleep_start; +} + +/* + * Log timestamp when a thread is put on a dispatch queue and becomes runnable. + */ +sched:::enqueue +/ lt_timestamps[args[1]->pr_pid, args[0]->pr_lwpid] != 0 / +{ + lt_timestamps[args[1]->pr_pid, args[0]->pr_lwpid] = timestamp; +} + +/* + * Calculate latencies when the thread is actually on CPU. + * This is necessary to get the right stack(). + */ +this unsigned long long end; +this unsigned long long now; +sched::resume:on-cpu +/ self->lt_sleep_start != 0 / +{ + this->end = lt_timestamps[curpsinfo->pr_pid, curlwpsinfo->pr_lwpid]; + this->now = timestamp; + lt_timestamps[curpsinfo->pr_pid, curlwpsinfo->pr_lwpid] = 0; + this->end = (this->end != 0 && this->end != self->lt_sleep_start) + ? this->end : this->now; + self->lt_sch_delay = this->now - this->end; + self->lt_sleep_duration = this->end - self->lt_sleep_start; + self->lt_sleep_start = 0; +} + +/* + * Filter: drop all "large" latencies when it is wakeable, + * trying to filter sleep() etc. + */ +#if defined(ENABLE_FILTER) +sched::resume:on-cpu +/ self->lt_sleep_duration > FILTER_THRESHOLD && + self->lt_is_block_wakeable != 0 / +{ + self->lt_sch_delay = 0; + self->lt_sleep_duration = 0; + self->lt_is_block_wakeable = 0; +} +#endif /* defined(ENABLE_FILTER) */ + +/* + * Write sleep time to the aggregation. + * lt_sleep_duration is from thread off cpu to it is enqueued again. + */ +sched::resume:on-cpu +/ self->lt_sleep_duration != 0 / +{ + @lt_call_count[pid, tid, stack()] = count(); + @lt_call_sum[pid, tid, stack()] = sum(self->lt_sleep_duration); + @lt_call_max[pid, tid, stack()] = max(self->lt_sleep_duration); + self->lt_is_block_wakeable = 0; /* Clean the flag to avoid leak */ + self->lt_sleep_duration = 0; +} + +/* + * Write time spent in queue to the aggregation. + * lt_sch_delay: the interval between "thread runnable" and "thread on cpu". + */ +sched::resume:on-cpu +/ self->lt_sch_delay != 0 / +{ + @lt_named_count[pid, tid, "Wait for available CPU"] = count(); + @lt_named_sum[pid, tid, "Wait for available CPU"] = + sum(self->lt_sch_delay); + @lt_named_max[pid, tid, "Wait for available CPU"] = + max(self->lt_sch_delay); + self->lt_sch_delay = 0; +} + +/* + * Probes that tracks lock spinning + */ +lockstat:::adaptive-spin +TRACE_FILTER +{ + @lt_named_count[pid, tid, "Adapt. lock spin"] = count(); + @lt_named_sum[pid, tid, "Adapt. lock spin"] = sum(arg1); + @lt_named_max[pid, tid, "Adapt. lock spin"] = max(arg1); +} + +lockstat:::spin-spin +TRACE_FILTER +{ + @lt_named_count[pid, tid, "Spinlock spin"] = count(); + @lt_named_sum[pid, tid, "Spinlock spin"] = sum(arg1); + @lt_named_max[pid, tid, "Spinlock spin"] = max(arg1); +} + +/* + * Probes that tracks lock blocking + */ +lockstat:::adaptive-block +TRACE_FILTER +{ + @lt_named_count[pid, tid, "#Adapt. lock block"] = count(); + @lt_named_sum[pid, tid, "#Adapt. lock block"] = sum(arg1); + @lt_named_max[pid, tid, "#Adapt. lock block"] = max(arg1); +} + +lockstat:::rw-block +TRACE_FILTER +{ + @lt_named_count[pid, tid, "#RW. lock block"] = count(); + @lt_named_sum[pid, tid, "#RW. lock block"] = sum(arg1); + @lt_named_max[pid, tid, "#RW. lock block"] = max(arg1); +} + +#if defined(ENABLE_SYNCOBJ) +/* + * Probes that tracks synchronization objects. + */ +this int stype; +this unsigned long long wchan; +this unsigned long long wtime; +sched:::wakeup +/* + * Currently we are not able to track wakeup from sched, because all lwpid + * are zero for when we trace sched. That makes lt_timestamps not usable. + */ +/ args[1]->pr_pid != 0 && + lt_timestamps[args[1]->pr_pid, args[0]->pr_lwpid] != 0 / +{ + this->stype = args[0]->pr_stype; + this->wchan = args[0]->pr_wchan; + /* + * We can use lt_timestamps[] here, because + * wakeup is always fired before enqueue. + * After enqueue, lt_timestamps[] will be overwritten. + */ + this->wtime = timestamp - + lt_timestamps[args[1]->pr_pid, args[0]->pr_lwpid]; + @lt_sync_count[args[1]->pr_pid, args[0]->pr_lwpid, this->stype, + this->wchan] = count(); + @lt_sync_sum[args[1]->pr_pid, args[0]->pr_lwpid, this->stype, + this->wchan] = sum(this->wtime); + @lt_sync_max[args[1]->pr_pid, args[0]->pr_lwpid, this->stype, + this->wchan] = max(this->wtime); +} +#endif /* defined(ENABLE_SYNCOBJ) */ + +#else /* !defined(ENABLE_LOW_OVERHEAD) */ + +/* + * This is the low overhead mode. + * In order to reduce the number of instructions executed during each + * off-cpu and on-cpu event, we do: + * 1. Use sampling, only update aggregations roughly 1/100 times (SAMPLE_TIMES). + * 2. Do not track anything other than needed for "main" window. + * 3. Use as few thread local variables as possible. + */ + +#define SAMPLE_TIMES 100 +#define SAMPLE_THRESHOLD 50000000 + +/* + * Log timestamp when a thread is off CPU. + */ +sched::resume:off-cpu +TRACE_FILTER_COND(curlwpsinfo->pr_state == SSLEEP) +{ + self->lt_timestamp = timestamp; +#if defined(ENABLE_FILTER) + self->lt_is_block_wakeable = curthread->t_flag & T_WAKEABLE; +#endif /* defined(ENABLE_FILTER) */ +} + +/* + * Calculate latencies when the thread is actually on CPU. + */ +this int need_skip; +sched::resume:on-cpu +/ self->lt_timestamp != 0 / +{ + self->lt_timestamp = timestamp - self->lt_timestamp; + +#if defined(ENABLE_FILTER) + self->lt_timestamp = + (self->lt_timestamp > FILTER_THRESHOLD && + self->lt_is_block_wakeable != 0) ? 0 : self->lt_timestamp; + self->lt_is_block_wakeable = 0; +#endif /* defined(ENABLE_FILTER) */ + + this->need_skip = (self->lt_counter < (SAMPLE_TIMES - 1) && + self->lt_timestamp <= SAMPLE_THRESHOLD) ? 1 : 0; + self->lt_timestamp = this->need_skip ? 0 : self->lt_timestamp; + self->lt_counter += this->need_skip; +} + +/* + * Log large ones first. + */ +sched::resume:on-cpu +/ self->lt_timestamp > SAMPLE_THRESHOLD / +{ + @lt_call_count[pid, tid, stack()] = sum(1); + @lt_call_sum[pid, tid, stack()] = sum(self->lt_timestamp); + @lt_call_max[pid, tid, stack()] = max(self->lt_timestamp); + + self->lt_timestamp = 0; +} + +/* + * If we fall to this probe, this must be a small latency and counter + * reaches SAMPLE_TIMES. + */ +sched::resume:on-cpu +/ self->lt_timestamp != 0 / +{ + /* Need +1 because lt_counter has not been updated in this cycle. */ + @lt_call_count[pid, tid, stack()] = sum(self->lt_counter + 1); + @lt_call_sum[pid, tid, stack()] = + sum((self->lt_counter + 1) * self->lt_timestamp); + @lt_call_max[pid, tid, stack()] = max(self->lt_timestamp); + + self->lt_timestamp = 0; + self->lt_counter = 0; +} + +#endif /* !defined(ENABLE_LOW_OVERHEAD) */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/common/latencytop.h Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,272 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2008-2009, Intel Corporation. + * All Rights Reserved. + */ + +#ifndef _LATENCYTOP_H +#define _LATENCYTOP_H + +#include <sys/types.h> + +/* + * GLib header file, including TRUE and FALSE definitions + */ +#include <glib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Lint seems to be confused by glib header file. + */ +#ifdef __lint +#undef g_assert +#define g_assert(x) ((void)(x)) +#undef TRUE +#define TRUE 1 +#endif + +/* + * To avoid compiler warning, we define our own convertion. + */ +#define LT_POINTER_TO_INT(a) ((int)(long)(a)) +#define LT_INT_TO_POINTER(a) ((void *)(unsigned long)(a)) + +#define TITLE "LatencyTOP for OpenSolaris, version 1.0" +#define COPYRIGHT "Copyright (c) 2008-2009, Intel Corporation." +#define DEFAULT_KLOG_FILE "/var/log/latencytop.log" + +#define INVALID_PID (~0) +#define INVALID_TID (~0) +#define PID_SYS_GLOBAL INVALID_PID +#define INVALID_CAUSE 0 +#define HIGHER_PRIORITY(a, b) ((a) > (b)) + +#ifdef EMBED_CONFIGS +/* + * LatencyTOP configurations embedded in the binary. + * Array will be generated by /usr/bin/xxd. + */ +extern unsigned char latencytop_d[]; +extern unsigned int latencytop_d_len; +extern unsigned char latencytop_trans[]; +extern unsigned int latencytop_trans_len; +#else +/* + * LatencyTOP configurations is externally. This is easy for debugging. + */ +#define DEFAULT_CONFIG_NAME "./latencytop.trans" +#define DEFAULT_D_SCRIPT_NAME "./latencytop.d" +#endif + +typedef enum { + LT_STAT_COUNT, + LT_STAT_MAX, + LT_STAT_SUM, +} lt_stat_type_t; + +#define LT_KLOG_LEVEL_NONE 0 /* Log nothing */ +#define LT_KLOG_LEVEL_UNMAPPED 1 /* Log only stacks not mapped */ +#define LT_KLOG_LEVEL_MAPPED 2 /* Log only stacks mapped */ +#define LT_KLOG_LEVEL_ALL 3 /* Log all stacks, mapped or not */ + +typedef enum { + LT_LEVEL_GLOBAL, /* System wide statistics */ + LT_LEVEL_PROCESS, /* Per-process statistics */ + LT_LEVEL_THREAD, /* Per-thread statistics */ +} lt_stat_level_t; + +typedef enum { + LT_SORT_TOTAL, + LT_SORT_MAX, + LT_SORT_AVG, + LT_SORT_COUNT, +} lt_sort_t; + +typedef enum { + LT_FIELD_FNAME, + LT_FIELD_PSARGS, +} lt_field_t; + +typedef enum { + LT_LIST_CAUSE, /* Lists latency by the causes (default) */ + LT_LIST_SPECIALS, /* Lists only "special" causes. */ + LT_LIST_SOBJ /* Lists synchronization objects. */ +} lt_list_type_t; + +/* + * Data entry which contains the statistics. + */ +typedef struct { + uint64_t count; + uint64_t total; + uint64_t max; +} lt_stat_data_t; + +/* + * Data entry stored along with its name. + */ +typedef struct { + enum { + STAT_CAUSE, + STAT_SOBJ + } type; + const char *string; + lt_stat_data_t data; + union { + struct { + int id; + int flags; + } cause; + struct { + int id; + } sobj; + } type_data; +} lt_stat_entry_t; + +typedef struct { + int enable_filter; + int trace_sched; + int trace_syncobj; + int low_overhead_mode; + int snap_interval; + char *config_name; +} lt_config_t; + +extern lt_config_t g_config; /* The global settings */ + +/* + * Causes can be disabled in the configuration file. + * Once disabled, DTrace script will still capture such causes, + * but they will not be counted in LatencyTOP. + */ +#define CAUSE_FLAG_DISABLED 1 +/* + * Causes with this flag will not show and count as part of summary in + * "kstack window". + */ +#define CAUSE_FLAG_HIDE_IN_SUMMARY 2 +/* + * This is generated from D script (named cause), which is "special". + */ +#define CAUSE_FLAG_SPECIAL 4 +#define CAUSE_ALL_FLAGS 0xffffffff + +/* + * These functions collect data using DTrace. + */ +extern int lt_dtrace_init(void); +extern int lt_dtrace_work(int); +extern int lt_dtrace_collect(void); +extern void lt_dtrace_deinit(void); + +/* + * These functions maintain configuration, e.g. symbol to cause mapping. + */ +extern int lt_table_init(void); +extern int lt_table_lookup_cause(const char *, int *, int *); +extern const char *lt_table_get_cause_name(int); +extern int lt_table_get_cause_flag(int, int); +extern int lt_table_lookup_named_cause(char *, int); +extern void lt_table_deinit(void); + +/* + * These functions update tatistic data of all causes collected from DTrace. + */ +extern void lt_stat_update(pid_t, id_t, char *, lt_stat_type_t, uint64_t); +extern void lt_stat_update_cause(pid_t, id_t, int, lt_stat_type_t, uint64_t); +extern void lt_stat_update_sobj(pid_t, id_t, int, unsigned long long, + lt_stat_type_t, uint64_t); +extern void lt_stat_clear_all(void); +extern void lt_stat_free_all(void); + +/* + * These functions produce lists for display part. + * Note: after a call to lt_stat_update_*, the old lists will be invalid. + */ +extern void *lt_stat_list_create(lt_list_type_t, lt_stat_level_t, + pid_t, id_t, int, lt_sort_t); +extern int lt_stat_list_has_item(void *, int); +extern const char *lt_stat_list_get_reason(void *, int); +extern uint64_t lt_stat_list_get_max(void *, int); +extern uint64_t lt_stat_list_get_sum(void *, int); +extern uint64_t lt_stat_list_get_count(void *, int); +extern uint64_t lt_stat_list_get_gtotal(void *); +extern void lt_stat_list_free(void *); + +/* + * These functions produce process and thread list. + */ +extern int lt_stat_proc_list_create(pid_t **, id_t **); +extern void lt_stat_proc_list_free(pid_t *, id_t *); +extern const char *lt_stat_proc_get_name(pid_t); +extern int lt_stat_proc_get_nthreads(pid_t); + +/* + * Console based display functions using ncurses. + */ +extern void lt_display_init(void); +extern int lt_display_loop(int); +extern void lt_display_error(const char *, ...); +extern void lt_display_deinit(void); + +/* + * Write statistics to a log file. + * Useful for debug and offline analysis. + */ +extern void lt_klog_init(void); +extern void lt_klog_deinit(void); +extern int lt_klog_set_log_file(const char *); +extern int lt_klog_set_log_level(int); +extern void lt_klog_write(void); +extern void lt_klog_log(int, pid_t, char *, lt_stat_type_t, + uint64_t); + +/* + * Utility functions. + */ +extern uint64_t lt_millisecond(void); +extern const char *lt_text(const char *); +extern void *lt_malloc(size_t); +extern void *lt_zalloc(size_t); +extern char *lt_strdup(const char *); +extern void lt_check_null(void *); +extern void lt_time_str(char *, int); +extern char *lt_get_proc_field(pid_t, lt_field_t); +extern void lt_update_stat_value(lt_stat_data_t *, lt_stat_type_t, uint64_t); +extern int lt_sort_by_total_desc(lt_stat_entry_t *, lt_stat_entry_t *); +extern int lt_sort_by_max_desc(lt_stat_entry_t *, lt_stat_entry_t *); +extern int lt_sort_by_count_desc(lt_stat_entry_t *, lt_stat_entry_t *); +extern int lt_sort_by_avg_desc(lt_stat_entry_t *, lt_stat_entry_t *); +extern void lt_gpipe_init(void); +extern void lt_gpipe_deinit(void); +extern void lt_gpipe_break(const char *); +extern int lt_gpipe_readfd(void); +extern int lt_file_exist(const char *); + +#ifdef __cplusplus +} +#endif + +#endif /* _LATENCYTOP_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/common/latencytop.trans Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,425 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright (c) 2008-2009, Intel Corporation. +# All Rights Reserved. +# +# LatencyTOP 0.1 configuration +# + +# Format: +# <priority> <module>`<function> <Category> +# Special command: +# ; <command> <option value> +# <command>: +# disable_category <category name> : do not count and display <category name> + +# Stream ops +40 genunix`strdoioctl Stream ioctl +40 genunix`strclose Stream close +40 genunix`strread Stream read +40 genunix`strwrite Stream write + +# Door ops +50 doorfs`door_call Door call +50 doorfs`door_return Door release + +# sockfs +50 sockfs`socktpi_close Close socket +50 sockfs`sotpi_connect Create socket connection +50 sockfs`socktpi_write Write to socket +50 sockfs`socktpi_read Read from socket + +# SCSI +40 sd`sd_ssc_send Execute USCSI command +40 sd`sdread SCSI read +40 sd`sdwrite SCSI write +30 sd`sd_check_media SCSI wait for device ready + +# UFS +50 ufs`ufs_sync UFS sync +50 ufs`ufs_fsync UFS sync +50 ufs`ufs_remove UFS remove file +50 ufs`ufs_create UFS create file +40 ufs`ufs_getpage UFS getpage +40 ufs`ufs_putpage UFS putpage +40 ufs`ufs_iget_alloced UFS get inode +40 ufs`ufs_ialloc UFS create inode +40 ufs`ldl_waito UFS log +40 ufs`alloc UFS alloc block + +# ZFS +50 zfs`zil_commit ZFS intent log commit +40 zfs`zfs_fsync ZFS sync +40 zfs`zfs_read ZFS read +40 zfs`zfs_write ZFS write +40 zfs`zfs_getattr ZFS get file attribute +40 zfs`zfs_remove ZFS remove file +40 zfs`zfs_create ZFS create file +40 zfs`zfs_close ZFS close file +40 zfs`zfs_open ZFS open file +40 zfs`zfs_getsecattr ZFS security check +40 zfs`zfs_umount ZFS unmount +30 zfs`zio_wait ZFS wait for I/O complete +30 zfs`spa_export ZFS export storage pool +30 zfs`spa_import ZFS import storage pool +30 zfs`zio_execute ZFS execute IO +30 zfs`spa_sync ZFS sync transaction group + +# Page fault +60 unix`pagefault Page fault + +# FIFO +50 fifofs`fifo_read Read from pipe/FIFO +50 fifofs`fifo_write Write to pipe/FIFO + +# Module +60 genunix`modload Loading kernel module + +# Devfs +50 devfs`dv_find Devfs lookup +50 dev`sdev_lookup Devfs lookup + +# Misc. +40 genunix`lookuppnat File system directory operation + +# +# FSFlush daemon +# + +15 genunix`fsflush Sleep in daemon (fsflush) + +# +# Pageout +# + +15 genunix`pageout_scanner Sleep in daemon (pageout) +15 genunix`pageout Sleep in daemon (pageout) + +# +# Syscalls +# +# Syscalls have priority 10, this is the lowest priority defined as default. +# This is to ensure a latency is traced to one of the syscalls if nothing +# else matches. +# + +15 unix`trap Processor Trap +15 genunix`post_syscall Post Syscall +5 unix`sys_syscall32 Syscall + +10 genunix`indir Syscall: indir +10 genunix`rexit Syscall: exit +10 genunix`forkall Syscall: forkall +10 genunix`read Syscall: read +10 genunix`read32 Syscall: read +10 genunix`write Syscall: write +10 genunix`write32 Syscall: write +10 genunix`open Syscall: open +10 genunix`open32 Syscall: open +10 genunix`close Syscall: close +10 genunix`wait Syscall: wait +10 genunix`creat Syscall: creat +10 genunix`creat32 Syscall: creat +10 genunix`link Syscall: link +10 genunix`unlink Syscall: unlink +10 genunix`exec Syscall: exec +10 genunix`chdir Syscall: chdir +10 genunix`gtime Syscall: time +10 genunix`mknod Syscall: mknod +10 genunix`chmod Syscall: chmod +10 genunix`chown Syscall: chown +10 genunix`brk Syscall: brk +10 genunix`stat Syscall: stat +10 genunix`stat32 Syscall: stat +10 genunix`lseek64 Syscall: lseek +10 genunix`lseek32 Syscall: lseek +10 genunix`getpid Syscall: getpid +10 genunix`mount Syscall: mount +10 genunix`umount Syscall: umount +10 genunix`setuid Syscall: setuid +10 genunix`getuid Syscall: getuid +10 genunix`stime Syscall: stime +10 genunix`stime32 Syscall: stime +10 genunix`pcsample Syscall: pcsample +10 genunix`alarm Syscall: alarm +10 genunix`fstat Syscall: fstat +10 genunix`fstat32 Syscall: fstat +10 genunix`pause Syscall: pause +10 genunix`utime Syscall: utime +10 genunix`stty Syscall: stty +10 genunix`gtty Syscall: gtty +10 genunix`access Syscall: access +10 genunix`nice Syscall: nice +10 genunix`statfs32 Syscall: statfs +10 genunix`syssync Syscall: sync +10 genunix`kill Syscall: kill +10 genunix`fstatfs32 Syscall: fstatfs +10 genunix`setpgrp Syscall: setpgrp +10 genunix`uucopystr Syscall: uucopystr +10 genunix`dup Syscall: dup +10 genunix`times Syscall: times +10 genunix`times32 Syscall: times +10 genunix`profil Syscall: prof +10 genunix`setgid Syscall: setgid +10 genunix`getgid Syscall: getgid +10 genunix`ssig Syscall: sig +10 unix`sysi86 Syscall: sysi86 +10 genunix`ioctl Syscall: ioctl +10 genunix`uadmin Syscall: uadmin +10 genunix`utssys64 Syscall: utssys +10 genunix`utssys32 Syscall: utssys +10 genunix`fdsync Syscall: fdsync +10 genunix`exece Syscall: exece +10 genunix`umask Syscall: umask +10 genunix`chroot Syscall: chroot +10 genunix`fcntl Syscall: fcntl +10 genunix`ulimit Syscall: ulimit +10 genunix`ulimit32 Syscall: ulimit +10 genunix`tasksys Syscall: tasksys +10 genunix`getpagesizes Syscall: getpagesizes +10 genunix`getpagesizes32 Syscall: getpagesizes +10 genunix`rctlsys Syscall: rctlsys +10 genunix`sidsys Syscall: sidsys +10 genunix`fsat64 Syscall: fsat +10 genunix`fsat32 Syscall: fsat +10 genunix`syslwp_park Syscall: lwp_park +10 genunix`sendfilev Syscall: sendfilev +10 genunix`rmdir Syscall: rmdir +10 genunix`mkdir Syscall: mkdir +10 genunix`getdents64 Syscall: getdents +10 genunix`getdents32 Syscall: getdents +10 genunix`privsys Syscall: privsys +10 genunix`privsys32 Syscall: privsys +10 genunix`ucredsys Syscall: ucredsys +10 genunix`ucredsys32 Syscall: ucredsys +10 genunix`sysfs Syscall: sysfs +10 genunix`getmsg Syscall: getmsg +10 genunix`getmsg32 Syscall: getmsg +10 genunix`putmsg Syscall: putmsg +10 genunix`putmsg32 Syscall: putmsg +10 genunix`poll Syscall: poll +10 genunix`lstat Syscall: lstat +10 genunix`lstat32 Syscall: lstat +10 genunix`symlink Syscall: symlink +10 genunix`readlink Syscall: readlink +10 genunix`readlink32 Syscall: readlink +10 genunix`setgroups Syscall: setgroups +10 genunix`getgroups Syscall: getgroups +10 genunix`fchmod Syscall: fchmod +10 genunix`fchown Syscall: fchown +10 genunix`sigprocmask Syscall: sigprocmask +10 genunix`sigsuspend Syscall: sigsuspend +10 genunix`sigaltstack Syscall: sigaltstack +10 genunix`sigaltstack32 Syscall: sigaltstack +10 genunix`sigaction Syscall: sigaction +10 genunix`sigaction32 Syscall: sigaction +10 genunix`sigpending Syscall: sigpending +10 genunix`getsetcontext Syscall: getsetcontext +10 genunix`getsetcontext32 Syscall: getsetcontext +10 genunix`statvfs Syscall: statvfs +10 genunix`statvfs32 Syscall: statvfs +10 genunix`fstatvfs Syscall: fstatvfs +10 genunix`fstatvfs32 Syscall: fstatvfs +10 genunix`getloadavg Syscall: getloadavg +10 genunix`waitsys Syscall: waitsys +10 genunix`waitsys32 Syscall: waitsys +10 genunix`sigsendsys Syscall: sigsendset +10 unix`hrtsys Syscall: hrtsys +10 genunix`sigresend Syscall: sigresend +10 genunix`priocntlsys Syscall: priocntlsys +10 genunix`pathconf Syscall: pathconf +10 genunix`mincore Syscall: mincore +10 genunix`smmap64 Syscall: mmap +10 genunix`smmap32 Syscall: mmap +10 genunix`mprotect Syscall: mprotect +10 genunix`munmap Syscall: munmap +10 genunix`fpathconf Syscall: fpathconf +10 genunix`vfork Syscall: vfork +10 genunix`fchdir Syscall: fchdir +10 genunix`readv Syscall: readv +10 genunix`readv32 Syscall: readv +10 genunix`writev Syscall: writev +10 genunix`writev32 Syscall: writev +10 genunix`xstat Syscall: xstat +10 genunix`xstat32 Syscall: xstat +10 genunix`lxstat Syscall: lxstat +10 genunix`lxstat32 Syscall: lxstat +10 genunix`fxstat Syscall: fxstat +10 genunix`fxstat32 Syscall: fxstat +10 genunix`xmknod Syscall: xmknod +10 genunix`setrlimit64 Syscall: setrlimit +10 genunix`setrlimit32 Syscall: setrlimit +10 genunix`getrlimit64 Syscall: getrlimit +10 genunix`getrlimit32 Syscall: getrlimit +10 genunix`lchown Syscall: lchown +10 genunix`memcntl Syscall: memcntl +10 genunix`getpmsg Syscall: getpmsg +10 genunix`getpmsg32 Syscall: getpmsg +10 genunix`putpmsg Syscall: putpmsg +10 genunix`putpmsg32 Syscall: putpmsg +10 genunix`rename Syscall: rename +10 genunix`uname Syscall: uname +10 genunix`setegid Syscall: setegid +10 genunix`sysconfig Syscall: sysconfig +10 genunix`adjtime Syscall: adjtime +10 genunix`systeminfo Syscall: systeminfo +10 genunix`seteuid Syscall: seteuid +10 genunix`forksys Syscall: forksys +10 genunix`fork1 Syscall: fork1 +10 genunix`sigtimedwait Syscall: sigtimedwait +10 genunix`lwp_info Syscall: lwp_info +10 genunix`yield Syscall: yield +10 genunix`lwp_sema_wait Syscall: lwp_sema_wait +10 genunix`lwp_sema_post Syscall: lwp_sema_post +10 genunix`lwp_sema_trywait Syscall: lwp_sema_trywait +10 genunix`lwp_detach Syscall: lwp_detach +10 genunix`corectl Syscall: corectl +10 genunix`modctl Syscall: modctl +10 genunix`fchroot Syscall: fchroot +10 genunix`utimes Syscall: utimes +10 genunix`vhangup Syscall: vhangup +10 genunix`gettimeofday Syscall: gettimeofday +10 genunix`getitimer Syscall: getitimer +10 genunix`setitimer Syscall: setitimer +10 genunix`syslwp_create Syscall: lwp_create +10 genunix`syslwp_exit Syscall: lwp_exit +10 genunix`syslwp_suspend Syscall: lwp_suspend +10 genunix`syslwp_continue Syscall: lwp_continue +10 genunix`lwp_kill Syscall: lwp_kill +10 genunix`lwp_self Syscall: lwp_self +10 genunix`lwp_sigmask Syscall: lwp_sigmask +10 genunix`syslwp_private Syscall: lwp_private +10 genunix`lwp_wait Syscall: lwp_wait +10 genunix`lwp_mutex_wakeup Syscall: lwp_mutex_wakeup +10 genunix`lwp_mutex_lock Syscall: lwp_mutex_lock +10 genunix`lwp_cond_wait Syscall: lwp_cond_wait +10 genunix`lwp_cond_signal Syscall: lwp_cond_signal +10 genunix`lwp_cond_broadcast Syscall: lwp_cond_broadcast +10 genunix`pread Syscall: pread +10 genunix`pread32 Syscall: pread +10 genunix`pwrite Syscall: pwrite +10 genunix`pwrite32 Syscall: pwrite +10 genunix`llseek32 Syscall: llseek +10 genunix`brandsys Syscall: brandsys +10 genunix`lgrpsys Syscall: lgrpsys +10 genunix`rusagesys Syscall: rusagesys +10 portfs`portfs Syscall: portfs +10 portfs`portfs32 Syscall: portfs +10 genunix`pollsys Syscall: pollsys +10 genunix`labelsys Syscall: labelsys +10 genunix`acl Syscall: acl +10 genunix`auditsys Syscall: auditsys +10 genunix`processor_bind Syscall: processor_bind +10 genunix`processor_info Syscall: processor_info +10 genunix`p_online Syscall: p_online +10 genunix`sigqueue Syscall: sigqueue +10 genunix`sigqueue32 Syscall: sigqueue +10 genunix`clock_gettime Syscall: clock_gettime +10 genunix`clock_settime Syscall: clock_settime +10 genunix`clock_getres Syscall: clock_getres +10 genunix`timer_create Syscall: timer_create +10 genunix`timer_delete Syscall: timer_delete +10 genunix`timer_settime Syscall: timer_settime +10 genunix`timer_gettime Syscall: timer_gettime +10 genunix`timer_getoverrun Syscall: timer_getoverrun +10 genunix`nanosleep Syscall: nanosleep +10 genunix`facl Syscall: facl +10 doorfs`doorfs Syscall: door +10 doorfs`doorfs32 Syscall: door +10 genunix`setreuid Syscall: setreuid +10 genunix`setregid Syscall: setregid +10 genunix`install_utrap Syscall: install_utrap +10 genunix`signotify Syscall: signotify +10 genunix`schedctl Syscall: schedctl +10 genunix`sparc_utrap_install Syscall: sparc_utrap_install +10 genunix`resolvepath Syscall: resolvepath +10 genunix`lwp_mutex_timedlock Syscall: lwp_mutex_timedlock +10 genunix`lwp_sema_timedwait Syscall: lwp_sema_timedwait +10 genunix`lwp_rwlock_sys Syscall: lwp_rwlock_sys +10 genunix`getdents64 Syscall: getdents64 +10 genunix`smmaplf32 Syscall: smmaplf32 +10 genunix`stat64 Syscall: stat64 +10 genunix`stat64_32 Syscall: stat64 +10 genunix`lstat64 Syscall: lstat64 +10 genunix`lstat64_32 Syscall: lstat64 +10 genunix`fstat64 Syscall: fstat64 +10 genunix`fstat64_32 Syscall: fstat64 +10 genunix`statvfs64 Syscall: statvfs64 +10 genunix`statvfs64_32 Syscall: statvfs64 +10 genunix`fstatvfs64 Syscall: fstatvfs64 +10 genunix`fstatvfs64_32 Syscall: fstatvfs64 +10 genunix`setrlimit64 Syscall: setrlimit64 +10 genunix`getrlimit64 Syscall: getrlimit64 +10 genunix`pread64 Syscall: pread64 +10 genunix`pwrite64 Syscall: pwrite64 +10 genunix`creat64 Syscall: creat64 +10 genunix`open64 Syscall: open64 +10 genunix`zone Syscall: zone +10 genunix`getcwd Syscall: getcwd +10 sockfs`so_socket Syscall: so_socket +10 sockfs`so_socketpair Syscall: so_socketpair +10 sockfs`bind Syscall: bind +10 sockfs`listen Syscall: listen +10 sockfs`accept Syscall: accept +10 sockfs`connect Syscall: connect +10 sockfs`shutdown Syscall: shutdown +10 sockfs`recv Syscall: recv +10 sockfs`recv32 Syscall: recv +10 sockfs`recvfrom Syscall: recvfrom +10 sockfs`recvfrom32 Syscall: recvfrom +10 sockfs`recvmsg Syscall: recvmsg +10 sockfs`send Syscall: send +10 sockfs`send32 Syscall: send +10 sockfs`sendmsg Syscall: sendmsg +10 sockfs`sendto Syscall: sendto +10 sockfs`sendto32 Syscall: sendto +10 sockfs`getpeername Syscall: getpeername +10 sockfs`getsockname Syscall: getsockname +10 sockfs`getsockopt Syscall: getsockopt +10 sockfs`setsockopt Syscall: setsockopt +10 sockfs`sockconfig Syscall: sockconfig +10 genunix`ntp_gettime Syscall: ntp_gettime +10 genunix`ntp_adjtime Syscall: ntp_adjtime +10 genunix`lwp_mutex_unlock Syscall: lwp_mutex_unlock +10 genunix`lwp_mutex_trylock Syscall: lwp_mutex_trylock +10 genunix`lwp_mutex_register Syscall: lwp_mutex_register +10 genunix`cladm Syscall: cladm +10 genunix`uucopy Syscall: uucopy +10 genunix`umount2 Syscall: umount2 + +# sched (pid = 0) +5 genunix`taskq_d_thread Sleep in daemon (sched) +5 ufs`trans_roll Sleep in daemon (sched) +5 zfs`txg_sync_thread Sleep in daemon (sched) +5 genunix`taskq_thread Sleep in daemon (sched) +5 zfs`l2arc_feed_thread Sleep in daemon (sched) +5 scsi`scsi_watch_thread Sleep in daemon (sched) +5 genunix`seg_pasync_thread Sleep in daemon (sched) +5 zfs`arc_reclaim_thread Sleep in daemon (sched) +5 zfs`txg_thread_wait Sleep in daemon (sched) +5 ip`squeue_worker Sleep in daemon (sched) +5 genunix`thread_reaper Sleep in daemon (sched) +5 zfs`txg_quiesce_thread Sleep in daemon (sched) +5 ufs`ufs_thread_delete Sleep in daemon (sched) + +#; disable_cause Sleep in daemon (fsflush) +#; disable_cause Sleep in daemon (pageout) +#; disable_cause Sleep in daemon (sched)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/common/stat.c Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,995 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2008-2009, Intel Corporation. + * All Rights Reserved. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <memory.h> +#include <string.h> +#include <limits.h> +#include <sys/stat.h> + +#include "latencytop.h" + +/* Statistics for each process/thread. */ +typedef struct _lt_stat_collection lt_stat_collection_t; +typedef gboolean (*check_child_func_t) (gpointer key, + lt_stat_collection_t *stat, void *user); + +typedef struct { + lt_stat_entry_t summary; + /* cause_id -> stat entry */ + GHashTable *sctable; +} lt_datagroup_t; + +#define NGROUPS 2 +#define GROUP_CAUSE 0 +#define GROUP_SOBJ 1 + +/* + * A data collection (i.e. a "bucket"). E.g. system, process or thread. + * Collections are hierarchic, 1 sys -> many processes -> more threads. + */ +struct _lt_stat_collection { + lt_stat_level_t level; + unsigned int id; + char *name; + lt_datagroup_t groups[NGROUPS]; + /* + * The following fields: parent, children and check_child_func + * maintain the tree structure. + */ + lt_stat_collection_t *parent; /* Parent node */ + GHashTable *children; /* pid (or tid) -> lt_stat_collection_t */ + check_child_func_t check_child_func; /* Release dead children */ +}; + +/* Internal data struct backs up a stat_list */ +typedef struct _lt_stat_list lt_stat_list_t; +typedef void (*free_list_func_t)(lt_stat_list_t *); +struct _lt_stat_list { + int entry_count; + lt_stat_entry_t **entries; + uint64_t gtotal; + free_list_func_t free_func; +}; + +/* The root collection: system level statistics */ +static lt_stat_collection_t *stat_system = NULL; + +/* + * The data structure which supports synchronization objects. + * We don't use normal "cause table" because this needs to be cleared + * every time we refresh, so that dead synchronization objects don't + * eat up memory little by little. + */ +typedef struct { + int sobj_type; + unsigned long long sobj_addr; +} lt_sobj_id_t; +typedef struct { + lt_sobj_id_t sobj_id; + int cause_id; + char string[32]; /* Enough to hold "%s: 0x%llX" */ +} lt_sobj_t; + +static GHashTable *sobj_table = NULL; +static int sobj_table_len = 0; + +/* + * Hash synchronize object ID by returning lower 32bit of its address. + */ +static guint +sobj_id_hash(lt_sobj_id_t *id) +{ + g_assert(id != NULL); + return (id->sobj_addr & 0xFFFFFFFF); +} + +/* + * Test if two synchronization objects are the same. + */ +static gboolean +sobj_id_equal(lt_sobj_id_t *a, lt_sobj_id_t *b) +{ + g_assert(a != NULL && b != NULL); + return (a->sobj_type == b->sobj_type && a->sobj_addr == b->sobj_addr); +} + +/* + * Lookup the cause_id of an synchronization object. + * Note this cause_id is only unique in GROUP_SOBJ, and changes after refresh. + */ +static lt_sobj_t * +lookup_sobj(lt_sobj_id_t *id) +{ + const char *stype_str[] = { + "None", + "Mutex", + "RWLock", + "CV", + "Sema", + "User", + "User_PI", + "Shuttle" + }; + const int stype_str_len = + sizeof (stype_str) / sizeof (stype_str[0]); + lt_sobj_t *ret = NULL; + + g_assert(id != NULL); + if (id->sobj_type < 0 || id->sobj_type >= stype_str_len) { + return (NULL); + } + + if (sobj_table != NULL) { + ret = (lt_sobj_t *)g_hash_table_lookup(sobj_table, id); + } else { + sobj_table = g_hash_table_new_full( + (GHashFunc)sobj_id_hash, (GEqualFunc)sobj_id_equal, + NULL, (GDestroyNotify)free); + lt_check_null(sobj_table); + } + + if (ret == NULL) { + ret = (lt_sobj_t *)lt_zalloc(sizeof (lt_sobj_t)); + ret->cause_id = ++sobj_table_len; + (void) snprintf(ret->string, sizeof (ret->string), + "%s: 0x%llX", stype_str[id->sobj_type], id->sobj_addr); + ret->sobj_id.sobj_type = id->sobj_type; + ret->sobj_id.sobj_addr = id->sobj_addr; + + g_hash_table_insert(sobj_table, &ret->sobj_id, ret); + } + + return (ret); +} + +/* + * Check if a process is alive by looking at /proc/pid + */ +/* ARGSUSED */ +static gboolean +check_process(gpointer key, lt_stat_collection_t *stat, void *user) +{ + char name[PATH_MAX]; + + (void) snprintf(name, PATH_MAX, "/proc/%u", stat->id); + /* Don't remove (return FALSE) if file exists */ + return (lt_file_exist(name) ? FALSE : TRUE); +} + +/* + * Check if a thread is alive by looking at /proc/pid/lwp/tid + */ +/* ARGSUSED */ +static gboolean +check_thread(gpointer key, lt_stat_collection_t *stat, void *user) +{ + char name[PATH_MAX]; + + g_assert(stat->parent != NULL); + g_assert(stat->parent->level == LT_LEVEL_PROCESS); + + (void) snprintf(name, PATH_MAX, "/proc/%u/lwp/%u", + stat->parent->id, stat->id); + /* Don't remove (return FALSE) if file exists */ + return (lt_file_exist(name) ? FALSE : TRUE); +} + +/* + * Helper function to free a stat node. + */ +static void +free_stat(lt_stat_collection_t *stat) +{ + int i; + + if (stat == NULL) { + return; + } + + for (i = 0; i < NGROUPS; ++i) { + if (stat->groups[i].sctable != NULL) { + g_hash_table_destroy(stat->groups[i].sctable); + } + } + + if (stat->children != NULL) { + g_hash_table_destroy(stat->children); + } + + if (stat->name != NULL) { + free(stat->name); + } + + free(stat); +} + +/* + * Helper function zeroing a stat node. + */ +/* ARGSUSED */ +static void +clear_stat(gpointer key, lt_stat_collection_t *stat, void *user) +{ + int i; + + g_assert(stat != NULL); + + for (i = 0; i < NGROUPS; ++i) { + if (stat->groups[i].sctable != NULL) { + g_hash_table_destroy(stat->groups[i].sctable); + stat->groups[i].sctable = NULL; + } + + stat->groups[i].summary.data.count = 0; + stat->groups[i].summary.data.total = 0; + stat->groups[i].summary.data.max = 0; + } + + if (stat->children != NULL) { + g_hash_table_foreach_remove(stat->children, + (GHRFunc)stat->check_child_func, NULL); + g_hash_table_foreach(stat->children, + (GHFunc)clear_stat, NULL); + } +} + +/* + * Update a collection for the value given. + * Recursively update its parent until it reaches the root. + */ +static void +update_stat_entry(lt_stat_collection_t *stat, int cause_id, + lt_stat_type_t type, uint64_t value, + const char *string, int group_to_use) +{ + lt_stat_entry_t *entry = NULL; + lt_datagroup_t *group; + + if (group_to_use < 0 || group_to_use >= NGROUPS) { + return; + } + group = &(stat->groups[group_to_use]); + + if (group->sctable != NULL) { + entry = (lt_stat_entry_t *)g_hash_table_lookup( + group->sctable, LT_INT_TO_POINTER(cause_id)); + } else { + group->sctable = g_hash_table_new_full( + g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify)free); + lt_check_null(group->sctable); + } + + if (entry == NULL) { + entry = (lt_stat_entry_t *)lt_zalloc(sizeof (lt_stat_entry_t)); + entry->string = string; + + switch (group_to_use) { + case GROUP_CAUSE: + entry->type = STAT_CAUSE; + entry->type_data.cause.id = cause_id; + entry->type_data.cause.flags = + lt_table_get_cause_flag(cause_id, CAUSE_ALL_FLAGS); + /* hide the first '#' */ + if ((entry->type_data.cause.flags + & CAUSE_FLAG_HIDE_IN_SUMMARY) != 0) { + ++entry->string; + } + break; + case GROUP_SOBJ: + entry->type = STAT_SOBJ; + entry->type_data.sobj.id = cause_id; + break; + } + + g_hash_table_insert(group->sctable, LT_INT_TO_POINTER(cause_id), + entry); + } + + lt_update_stat_value(&entry->data, type, value); + + if (group_to_use == GROUP_SOBJ || + (entry->type_data.cause.flags & CAUSE_FLAG_HIDE_IN_SUMMARY) == 0) { + lt_update_stat_value(&group->summary.data, type, value); + } + + if (stat->parent != NULL) { + update_stat_entry(stat->parent, cause_id, type, value, + string, group_to_use); + } +} + +/* + * Identify the cause from a stack trace. + * Returns the cause_id. + */ +static int +find_cause(char *stack) +{ + int cause_temp; + int prio_temp; + int cause = INVALID_CAUSE; + int priority = 0; + int found = 0; + + while (stack != NULL) { + char *sep; + sep = strchr(stack, ' '); + if (sep != NULL) { + *sep = 0; + } + + found = lt_table_lookup_cause(stack, &cause_temp, &prio_temp); + if (found && (cause == INVALID_CAUSE || + HIGHER_PRIORITY(prio_temp, priority))) { + cause = cause_temp; + priority = prio_temp; + } + + if (sep != NULL) { + *sep = ' '; + stack = sep + 1; + } else { + stack = NULL; + } + } + return (cause); +} + +/* + * Create a new collection, hook it to the parent. + */ +static lt_stat_collection_t * +new_collection(lt_stat_level_t level, unsigned int id, char *name, + lt_stat_collection_t *parent, check_child_func_t check_child_func) +{ + int i; + lt_stat_collection_t *ret; + + ret = (lt_stat_collection_t *) + lt_zalloc(sizeof (lt_stat_collection_t)); + + ret->level = level; + ret->check_child_func = check_child_func; + ret->id = id; + ret->name = name; + + for (i = 0; i < NGROUPS; ++i) { + ret->groups[i].summary.string = (const char *)name; + } + + if (parent != NULL) { + ret->parent = parent; + if (parent->children == NULL) { + parent->children = g_hash_table_new_full( + g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify)free_stat); + lt_check_null(parent->children); + } + g_hash_table_insert(parent->children, + LT_INT_TO_POINTER((int)id), ret); + } + + return (ret); +} + +/* + * Finds the "leaf" collection, use given pid and tid. + */ +static lt_stat_collection_t * +get_stat_c(pid_t pid, id_t tid) +{ + lt_stat_collection_t *stat_p = NULL; + lt_stat_collection_t *stat_t = NULL; + + if (stat_system == NULL) { + stat_system = new_collection(LT_LEVEL_GLOBAL, + PID_SYS_GLOBAL, lt_strdup("SYSTEM"), NULL, check_process); + } else if (stat_system->children != NULL) { + stat_p = (lt_stat_collection_t *) + g_hash_table_lookup(stat_system->children, + LT_INT_TO_POINTER(pid)); + } + + if (stat_p == NULL) { + char *fname; + + fname = lt_get_proc_field(pid, LT_FIELD_FNAME); + if (fname == NULL) { + /* + * we cannot get process execname, + * process is probably already dead. + */ + return (NULL); + } + + stat_p = new_collection(LT_LEVEL_PROCESS, + (unsigned int)pid, fname, stat_system, check_thread); + } else if (stat_p->children != NULL) { + stat_t = (lt_stat_collection_t *) + g_hash_table_lookup(stat_p->children, + LT_INT_TO_POINTER(tid)); + } + + if (stat_t == NULL) { + const int tname_size = 16; /* Enough for "Thread %d" */ + char *tname; + + tname = (char *)lt_malloc(tname_size); + (void) snprintf(tname, tname_size, "Thread %d", tid); + + stat_t = new_collection(LT_LEVEL_THREAD, + (unsigned int)tid, tname, stat_p, NULL); + } + + return (stat_t); +} + +/* + * Update the statistics given cause_id directly. Value will be added to + * internal statistics. + */ +void +lt_stat_update_cause(pid_t pid, id_t tid, int cause_id, lt_stat_type_t type, + uint64_t value) +{ + const char *string; + lt_stat_collection_t *stat_t = NULL; + + if (cause_id < 0 || value == 0) { + return; + } + + if (lt_table_get_cause_flag(cause_id, CAUSE_FLAG_DISABLED)) { + /* we don't care about this cause, ignore. */ + return; + } + + stat_t = get_stat_c(pid, tid); + if (stat_t == NULL) { + /* cannot get fname, process must be dead. */ + return; + } + + string = lt_table_get_cause_name(cause_id); + + update_stat_entry(stat_t, cause_id, type, value, string, GROUP_CAUSE); +} + +/* + * Update the statistics given the stack trace. + * Internally it will be mapped to a cause using lt_table_lookup_cause(), + * and call lt_stat_update_cause(). + */ +void +lt_stat_update(pid_t pid, id_t tid, char *stack, lt_stat_type_t type, + uint64_t value) +{ + int cause_id = INVALID_CAUSE; + + if (value == 0) { + return; + } + + cause_id = find_cause(stack); + if (cause_id == INVALID_CAUSE) { + cause_id = lt_table_lookup_named_cause(stack, 1); + lt_klog_log(LT_KLOG_LEVEL_UNMAPPED, pid, stack, type, value); + } else { + lt_klog_log(LT_KLOG_LEVEL_MAPPED, pid, stack, type, value); + } + + lt_stat_update_cause(pid, tid, cause_id, type, value); +} + +/* + * Zero all statistics, but keep the data structure in memory + * to be filled with new data immediately after. + */ +void +lt_stat_clear_all(void) +{ + if (stat_system != NULL) { + clear_stat(NULL, stat_system, NULL); + } + + if (sobj_table != NULL) { + g_hash_table_destroy(sobj_table); + sobj_table = NULL; + } +} + +/* + * Clean up function that frees all memory used by statistics. + */ +void +lt_stat_free_all(void) +{ + if (stat_system != NULL) { + free_stat(stat_system); + stat_system = NULL; + } + + if (sobj_table != NULL) { + g_hash_table_destroy(sobj_table); + sobj_table = NULL; + } +} + +/* + * Get top N causes of latency for a process. Returns handle to a stat_list. + * Use pid = PID_SYS_GLOBAL to get global top list. + * Call lt_stat_list_free after use. + */ +void * +lt_stat_list_create(lt_list_type_t list_type, lt_stat_level_t level, + pid_t pid, id_t tid, int count, lt_sort_t sort_by) +{ + GCompareFunc func; + GList *list, *walk; + lt_stat_collection_t *stat_c = NULL; + lt_stat_list_t *ret; + lt_datagroup_t *group; + + if (level == LT_LEVEL_GLOBAL) { + /* Use global entry */ + stat_c = stat_system; + } else if (stat_system != NULL && stat_system->children != NULL) { + /* Find process entry first */ + stat_c = (lt_stat_collection_t *)g_hash_table_lookup( + stat_system->children, LT_INT_TO_POINTER(pid)); + + if (level == LT_LEVEL_THREAD) { + /* + * If we request thread entry, find it based on + * process entry. + */ + if (stat_c != NULL && stat_c->children != NULL) { + stat_c = (lt_stat_collection_t *) + g_hash_table_lookup(stat_c->children, + LT_INT_TO_POINTER(tid)); + } else { + /* + * Couldn't find thread entry, set it to NULL + * so we will return empty list later. + */ + stat_c = NULL; + } + } + } + + ret = (lt_stat_list_t *)lt_zalloc(sizeof (lt_stat_list_t)); + ret->entries = (lt_stat_entry_t **) + lt_zalloc(count * sizeof (lt_stat_entry_t *)); + + if (stat_c == NULL) { + /* empty list */ + return (ret); + } + + if (list_type == LT_LIST_SOBJ) { + group = &(stat_c->groups[GROUP_SOBJ]); + } else { + group = &(stat_c->groups[GROUP_CAUSE]); + } + + if (group->sctable == NULL) { + /* empty list */ + return (ret); + } + + ret->gtotal = group->summary.data.total; + + list = g_hash_table_get_values(group->sctable); + + switch (sort_by) { + case LT_SORT_TOTAL: + func = (GCompareFunc)lt_sort_by_total_desc; + break; + case LT_SORT_MAX: + func = (GCompareFunc)lt_sort_by_max_desc; + break; + case LT_SORT_AVG: + func = (GCompareFunc)lt_sort_by_avg_desc; + break; + case LT_SORT_COUNT: + func = (GCompareFunc)lt_sort_by_count_desc; + break; + } + list = g_list_sort(list, func); + + for (walk = list; + walk != NULL && count > 0; + walk = g_list_next(walk), --count) { + lt_stat_entry_t *data = (lt_stat_entry_t *)walk->data; + + if (list_type == LT_LIST_CAUSE && + data->type == STAT_CAUSE && + (data->type_data.cause.flags & CAUSE_FLAG_HIDE_IN_SUMMARY) + != 0) { + continue; + } + if (list_type == LT_LIST_SPECIALS && + data->type == STAT_CAUSE && + (data->type_data.cause.flags & CAUSE_FLAG_SPECIAL) + == 0) { + continue; + } + if (data->data.count == 0) { + break; + } + ret->entries[ret->entry_count++] = data; + } + + g_list_free(list); + + return (ret); +} + +/* + * Free memory allocated by lt_stat_list_create(). + */ +void +lt_stat_list_free(void *ptr) +{ + lt_stat_list_t *list = (lt_stat_list_t *)ptr; + + if (list == NULL) { + return; + } + + if (list->free_func != NULL) { + list->free_func(list); + } + + if (list->entries != NULL) { + free(list->entries); + } + + free(list); +} + +/* + * Check if the list has item number i. + */ +int +lt_stat_list_has_item(void *ptr, int i) +{ + lt_stat_list_t *list = (lt_stat_list_t *)ptr; + + if (list == NULL || i < 0 || i >= list->entry_count || + list->entries[i] == NULL) { + return (0); + } + return (1); +} + +/* + * Get the display name of item number i in the list. + */ +const char * +lt_stat_list_get_reason(void *ptr, int i) +{ + lt_stat_list_t *list = (lt_stat_list_t *)ptr; + if (list == NULL || i < 0 || i >= list->entry_count || + list->entries[i] == NULL) { + return (NULL); + } + + g_assert(list->entries[i]->string != NULL); + + return (list->entries[i]->string); +} + +/* + * Get the max. of item number i in the list. + */ +uint64_t +lt_stat_list_get_max(void *ptr, int i) +{ + lt_stat_list_t *list = (lt_stat_list_t *)ptr; + + if (list == NULL || i < 0 || i >= list->entry_count || + list->entries[i] == NULL) { + return (0); + } + + return (list->entries[i]->data.max); +} + +/* + * Get the total of item number i in the list. + */ +uint64_t +lt_stat_list_get_sum(void *ptr, int i) +{ + lt_stat_list_t *list = (lt_stat_list_t *)ptr; + + if (list == NULL || i < 0 || i >= list->entry_count || + list->entries[i] == NULL) { + return (0); + } + + return (list->entries[i]->data.total); +} + +/* + * Get the count of item number i in the list. + */ +uint64_t +lt_stat_list_get_count(void *ptr, int i) +{ + lt_stat_list_t *list = (lt_stat_list_t *)ptr; + + if (list == NULL || i < 0 || i >= list->entry_count || + list->entries[i] == NULL) { + return (0); + } + + return (list->entries[i]->data.count); +} + +/* + * Get grand total of all latencies in the pid where the list is drawn. + */ +uint64_t +lt_stat_list_get_gtotal(void *ptr) +{ + lt_stat_list_t *list = (lt_stat_list_t *)ptr; + + if (list == NULL) { + return (0); + } + return (list->gtotal); +} + +/* + * ============================================================================ + * Process and thread list. + * They share a lot of static variables as stat part does, + * so put them in the same file. + */ + +/* + * Helper function, sort by PID/TID ascend. + */ +static int +sort_id(lt_stat_collection_t *a, lt_stat_collection_t *b) +{ + return ((int)(a->id - b->id)); +} + +/* + * Get current list of processes. Call lt_stat_proc_list_free after use. + */ +static int +plist_create(pid_t ** list) +{ + GList *pid_list, *walk; + int ret, count; + + ret = g_hash_table_size(stat_system->children); + *list = (pid_t *)lt_malloc(sizeof (pid_t) * ret); + + pid_list = g_hash_table_get_values(stat_system->children); + pid_list = g_list_sort(pid_list, (GCompareFunc)sort_id); + + for (walk = pid_list, count = 0; + walk != NULL && count < ret; + walk = g_list_next(walk), ++count) { + (*list)[count] = (int) + ((lt_stat_collection_t *)(walk->data))->id; + } + + g_list_free(pid_list); + + return (ret); +} + +/* + * Count how many threads are found so far in a process. + * Only thread caused SSLEEP will be found. + */ +/* ARGSUSED */ +static void +count_threads(gpointer key, lt_stat_collection_t *stat_c, int *ret) +{ + g_assert(ret != NULL); + + if (stat_c->children != NULL) { + *ret += g_hash_table_size(stat_c->children); + } +} + +/* + * Get current list of processes+threads. + * Call lt_stat_proc_list_free after use. + */ +static int +tlist_create(pid_t ** plist, id_t ** tlist) +{ + GList *pid_list, *walk_p; + GList *tid_list, *walk_t; + int ret = 0; + int count = 0; + + g_hash_table_foreach(stat_system->children, + (GHFunc)count_threads, &ret); + + *plist = (pid_t *)lt_malloc(sizeof (pid_t) * ret); + *tlist = (id_t *)lt_malloc(sizeof (id_t) * ret); + + pid_list = g_hash_table_get_values(stat_system->children); + pid_list = g_list_sort(pid_list, (GCompareFunc)sort_id); + + for (walk_p = pid_list; walk_p != NULL; + walk_p = g_list_next(walk_p)) { + lt_stat_collection_t *stat_p = + (lt_stat_collection_t *)walk_p->data; + + if (stat_p->children == NULL) { + continue; + } + + tid_list = g_hash_table_get_values(stat_p->children); + tid_list = g_list_sort(tid_list, (GCompareFunc)sort_id); + + for (walk_t = tid_list; walk_t != NULL; + walk_t = g_list_next(walk_t)) { + lt_stat_collection_t *stat_t = + (lt_stat_collection_t *)walk_t->data; + + (*plist)[count] = (int)stat_p->id; + (*tlist)[count] = (int)stat_t->id; + + ++count; + } + g_list_free(tid_list); + } + + g_list_free(pid_list); + g_assert(count == ret); + + return (ret); +} + +/* + * List processes that have been processed in LatencyTOP. + */ +int +lt_stat_proc_list_create(pid_t ** plist, id_t ** tlist) +{ + if (plist == NULL) { + return (-1); + } + + if (stat_system == NULL || stat_system->children == NULL) { + *plist = NULL; + + if (tlist != NULL) { + *tlist = NULL; + } + + return (0); + } + + if (tlist == NULL) { + return (plist_create(plist)); + } else { + return (tlist_create(plist, tlist)); + } +} + +/* + * Free memory allocated by lt_stat_proc_list_create(). + */ +void +lt_stat_proc_list_free(pid_t *plist, id_t *tlist) +{ + if (plist != NULL) { + free(plist); + } + + if (tlist != NULL) { + free(tlist); + } +} + +/* + * Get execname of a PID. + */ +const char * +lt_stat_proc_get_name(pid_t pid) +{ + lt_stat_collection_t *stat_p = NULL; + + if (stat_system == NULL || stat_system->children == NULL) { + return (NULL); + } + + stat_p = (lt_stat_collection_t *) + g_hash_table_lookup(stat_system->children, LT_INT_TO_POINTER(pid)); + + if (stat_p != NULL) { + return (stat_p->name); + } else { + return (NULL); + } +} + +/* + * Get number of threads. + */ +int +lt_stat_proc_get_nthreads(pid_t pid) +{ + lt_stat_collection_t *stat_p = NULL; + + if (stat_system == NULL || stat_system->children == NULL) { + return (0); + } + + stat_p = (lt_stat_collection_t *) + g_hash_table_lookup(stat_system->children, LT_INT_TO_POINTER(pid)); + + if (stat_p != NULL) { + return (g_hash_table_size(stat_p->children)); + } else { + return (0); + } +} + +/* + * Update the statistics for synchronization objects. + */ +void +lt_stat_update_sobj(pid_t pid, id_t tid, int stype, + unsigned long long wchan, + lt_stat_type_t type, uint64_t value) +{ + lt_sobj_id_t id; + lt_sobj_t *sobj; + int cause_id; + lt_stat_collection_t *stat_t = NULL; + + stat_t = get_stat_c(pid, tid); + if (stat_t == NULL) { + return; + } + + id.sobj_type = stype; + id.sobj_addr = wchan; + sobj = lookup_sobj(&id); + if (sobj == NULL) { + return; + } + + cause_id = sobj->cause_id; + + update_stat_entry(stat_t, cause_id, type, value, + sobj->string, GROUP_SOBJ); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/common/table.c Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,560 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2008-2009, Intel Corporation. + * All Rights Reserved. + */ + +#include <stdlib.h> +#include <string.h> +#include <memory.h> +#include <stdio.h> +#include <ctype.h> + +#include "latencytop.h" + +/* + * Structure that holds detail of a cause. + */ +typedef struct { + int cause_id; + int flags; + char *name; +} lt_cause_t; + +/* + * Structure that represents a matched cause. + */ +typedef struct { + int priority; + int cause_id; +} lt_match_t; + +/* All lt_cause_t that are created. */ +static GPtrArray *causes_array = NULL; +static int causes_array_len = 0; +/* + * This hash table maps a symbol to a cause entry. + * key type is "char *" and value type is "lt_match_t *". + */ +static GHashTable *symbol_lookup_table = NULL; +/* + * This hash table maps a cause name to an cause id. + * Note only cause names that are found in D script is put in this table. + * key type is "char *" and value type is "int" (which is cause_id). + */ +static GHashTable *named_causes = NULL; + +/* + * Help function to free one lt_cause_t structure. + */ +/* ARGSUSED */ +static void +free_cause(lt_cause_t *cause, void *user) +{ + g_assert(cause != NULL && cause->name != NULL); + + free(cause->name); + free(cause); +} + +/* + * Add a cause. + * Note this function takes ownership of char *name. + */ +static lt_cause_t * +new_cause(char *name, int flags) +{ + lt_cause_t *entry; + + g_assert(name != NULL); + + entry = (lt_cause_t *)lt_malloc(sizeof (lt_cause_t)); + entry->flags = flags; + entry->name = name; + entry->cause_id = causes_array_len; + + g_ptr_array_add(causes_array, entry); + ++causes_array_len; + + return (entry); +} + +/* + * Set a cause to "disabled" state. + */ +static void +disable_cause(char *cause_str, GHashTable *cause_table) +{ + lt_cause_t *cause; + + cause = (lt_cause_t *)g_hash_table_lookup(cause_table, cause_str); + if (cause != NULL) { + cause->flags |= CAUSE_FLAG_DISABLED; + } +} + +/* + * Helper functions that reads a line from a char * array. + */ +static int +read_line_from_mem(const char *mem, int mem_len, char *line, int line_len, + int *index) +{ + g_assert(mem != NULL && line != NULL && index != NULL); + + if (line_len <= 0 || mem_len <= 0) { + return (0); + } + if (*index >= mem_len) { + return (0); + } + + while (line_len > 1 && *index < mem_len) { + *line = mem[(*index)++]; + --line_len; + ++line; + if (*(line-1) == '\r' || *(line-1) == '\n') { + break; + } + } + *line = 0; + + return (1); +} + +/* + * The main loop that parses the translation rules one line at a time, + * and construct latencytop lookup data structure from it. + */ +static int +parse_config(const char *work, int work_len) +{ + char line[256]; + int len; + char *begin, *end, *tmp; + int priority = 0; + char *match; + char *match_dup; + char *cause_str; + lt_cause_t *cause; + lt_match_t *match_entry; + int current = 0; + GHashTable *cause_lookup; + GSequence *cmd_disable; + + cause_lookup = g_hash_table_new(g_str_hash, g_str_equal); + lt_check_null(cause_lookup); + + cmd_disable = g_sequence_new((GDestroyNotify)free); + lt_check_null(cmd_disable); + + while (read_line_from_mem(work, work_len, line, sizeof (line), + ¤t)) { + len = strlen(line); + if (line[len-1] != '\n' && line[len-1] != '\r' && + current < work_len) { + lt_display_error("Configuration line too long.\n"); + goto err; + } + + begin = line; + while (isspace(*begin)) { + ++begin; + } + if (*begin == '\0') { + /* empty line, ignore */ + continue; + } + + /* Delete trailing spaces. */ + end = begin + strlen(begin) - 1; + while (isspace(*end)) { + --end; + } + end[1] = 0; + + if (*begin == '#') { + continue; + } else if (*begin == ';') { + char old_chr = 0; + /* special command */ + /* ; disable_cause FSFlush Daemon */ + /* ^ */ + ++begin; + + while (isspace(*begin)) { + ++begin; + } + /* ; disable_cause FSFlush Daemon */ + /* ^ */ + if (*begin == '\0') { + continue; + } + + for (tmp = begin; + *tmp != '\0' && !isspace(*tmp); + ++tmp) { + } + old_chr = *tmp; + *tmp = 0; + + if (strcmp("disable_cause", begin) == 0) { + if (old_chr == '\0') { + /* Must have an argument */ + lt_display_error( + "Invalid command format: %s\n", + begin); + goto err; + } + + begin = tmp+1; + while (isspace(*begin)) { + ++begin; + } + + g_sequence_append(cmd_disable, + lt_strdup(begin)); + } else { + *tmp = old_chr; + lt_display_error( + "Unknown command: %s\n", begin); + goto err; + } + continue; + } + + g_assert(*begin != '#' && *begin != ';'); + + /* 10 genunix`indir Syscall indir */ + /* ^ */ + priority = strtol(begin, &tmp, 10); + if (tmp == begin || priority == 0) { + lt_display_error( + "Invalid configuration line: %s\n", line); + goto err; + } + begin = tmp; + + /* 10 genunix`indir Syscall indir */ + /* ^ */ + while (isspace(*begin)) { + ++begin; + } + if (*begin == 0) { + lt_display_error( + "Invalid configuration line: %s\n", line); + goto err; + } + + /* 10 genunix`indir Syscall indir */ + /* -----^ */ + for (tmp = begin; + *tmp != '\0' && !isspace(*tmp); + ++tmp) { + } + if (*tmp == '\0') { + lt_display_error( + "Invalid configuration line: %s\n", line); + goto err; + } + *tmp = 0; + match = begin; + + /* Check if we have mapped this function before. */ + match_entry = (lt_match_t *) + g_hash_table_lookup(symbol_lookup_table, match); + if (match_entry != NULL && + HIGHER_PRIORITY(match_entry->priority, priority)) { + /* We already have a higher entry. Ignore this. */ + continue; + } + + begin = tmp+1; + + /* 10 genunix`indir Syscall indir */ + /* -------------------------------------^ */ + while (isspace(*begin)) { + ++begin; + } + if (*begin == 0) { + lt_display_error( + "Invalid configuration line: %s\n", line); + goto err; + } + cause_str = begin; + + /* Check if we have mapped this cause before. */ + cause = (lt_cause_t *) + g_hash_table_lookup(cause_lookup, cause_str); + if (cause == NULL) { + char *cause_dup = lt_strdup(cause_str); + cause = new_cause(cause_dup, 0); + g_hash_table_insert(cause_lookup, cause_dup, cause); + } + + match_entry = (lt_match_t *)lt_malloc(sizeof (lt_match_t)); + g_assert(NULL != match_entry); + match_entry->priority = priority; + match_entry->cause_id = cause->cause_id; + match_dup = lt_strdup(match); + + g_hash_table_insert(symbol_lookup_table, match_dup, + match_entry); + } + + g_sequence_foreach(cmd_disable, (GFunc)disable_cause, cause_lookup); + g_sequence_free(cmd_disable); + g_hash_table_destroy(cause_lookup); + + return (0); + +err: + g_sequence_free(cmd_disable); + g_hash_table_destroy(cause_lookup); + + return (-1); +} + +/* + * Init function, called when latencytop starts. + * It loads the translation rules from a file. + * A configuration file defines some causes and symbols matching these causes. + */ +int +lt_table_init(void) +{ + char *config_loaded = NULL; + int config_loaded_len = 0; + const char *work = NULL; + int work_len = 0; + lt_cause_t *cause; + +#ifdef EMBED_CONFIGS + work = (char *)latencytop_trans; + work_len = latencytop_trans_len; +#endif + + if (g_config.config_name != NULL) { + FILE *fp; + + fp = fopen(g_config.config_name, "r"); + if (NULL == fp) { + lt_display_error( + "Unable to open configuration file.\n"); + return (-1); + } + + (void) fseek(fp, 0, SEEK_END); + config_loaded_len = (int)ftell(fp); + config_loaded = (char *)lt_malloc(config_loaded_len); + (void) fseek(fp, 0, SEEK_SET); + + if (fread(config_loaded, config_loaded_len, 1, fp) == 0) { + lt_display_error( + "Unable to read configuration file.\n"); + (void) fclose(fp); + free(config_loaded); + return (-1); + } + + (void) fclose(fp); + (void) printf("Loaded configuration from %s\n", + g_config.config_name); + + work = config_loaded; + work_len = config_loaded_len; + } + + g_assert(work != NULL && work_len != 0); + + lt_table_deinit(); + causes_array = g_ptr_array_new(); + lt_check_null(causes_array); + + /* 0 is not used, to keep a place for bugs etc. */ + cause = new_cause(lt_strdup("Nothing"), CAUSE_FLAG_DISABLED); + g_assert(cause->cause_id == INVALID_CAUSE); + + symbol_lookup_table = g_hash_table_new_full( + g_str_hash, g_str_equal, + (GDestroyNotify)free, (GDestroyNotify)free); + lt_check_null(symbol_lookup_table); + + if (parse_config(work, work_len) != 0) { + return (-1); + } + + if (config_loaded != NULL) { + free(config_loaded); + } + + return (0); +} + +/* + * Some causes, such as "lock spinning", does not have stack trace. + * Instead, their names are explicitly specified in DTrace script. + * This function will resolve such causes, and dynamically add them + * to the global tables when first met (lazy initialization). + * auto_create: set to TRUE will create the entry if it is not found. + * Returns cause_id of the cause. + */ +int +lt_table_lookup_named_cause(char *name, int auto_create) +{ + int cause_id = INVALID_CAUSE; + + if (named_causes == NULL) { + named_causes = g_hash_table_new_full( + g_str_hash, g_str_equal, (GDestroyNotify)free, NULL); + lt_check_null(named_causes); + } else { + cause_id = LT_POINTER_TO_INT(g_hash_table_lookup( + named_causes, name)); + } + + if (cause_id == INVALID_CAUSE && auto_create) { + int flags = CAUSE_FLAG_SPECIAL; + lt_cause_t *cause; + + if (name[0] == '#') { + flags |= CAUSE_FLAG_HIDE_IN_SUMMARY; + } + + cause = new_cause(lt_strdup(name), flags); + if (cause == NULL) { + return (INVALID_CAUSE); + } + cause_id = cause->cause_id; + + g_hash_table_insert(named_causes, lt_strdup(name), + LT_INT_TO_POINTER(cause_id)); + } + + return (cause_id); +} + +/* + * Try to map a symbol on stack to a known cause. + * module_func has the format "module_name`function_name". + * cause_id and priority will be set if a cause is found. + * Returns 1 if found, 0 if not found. + */ +int +lt_table_lookup_cause(const char *module_func, int *cause_id, int *priority) +{ + lt_match_t *match; + + g_assert(module_func != NULL && cause_id != NULL && priority != NULL); + + if (symbol_lookup_table == NULL) { + return (0); + } + + match = (lt_match_t *) + g_hash_table_lookup(symbol_lookup_table, module_func); + if (match == NULL) { + char *func = strchr(module_func, '`'); + + if (func != NULL) { + match = (lt_match_t *) + g_hash_table_lookup(symbol_lookup_table, func); + } + } + + if (match == NULL) { + return (0); + } else { + *cause_id = match->cause_id; + *priority = match->priority; + return (1); + } +} + +/* + * Get the display name of a cause. Cause_id must be valid, + * which is usually return from lt_table_lookup_cause() or + * lt_table_lookup_named_cause(). + */ +const char * +lt_table_get_cause_name(int cause_id) +{ + lt_cause_t *cause; + + if (cause_id < 0 || cause_id >= causes_array_len) { + return (NULL); + } + + cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id); + if (cause == NULL) { + return (NULL); + } else { + return (cause->name); + } +} + +/* + * Check a cause's flag, e.g. if it has CAUSE_FLAG_DISABLED. + * Use CAUSE_ALL_FLAGS to get all flags at once. + */ +int +lt_table_get_cause_flag(int cause_id, int flag) +{ + lt_cause_t *cause; + + if (cause_id < 0 || cause_id >= causes_array_len) { + return (0); + } + cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id); + + if (cause == NULL) { + return (0); + } else { + return (cause->flags & flag); + } +} + +/* + * Clean up function. + * Free the resource used for symbol table. E.g. symbols, causes. + */ +void +lt_table_deinit(void) +{ + if (symbol_lookup_table != NULL) { + g_hash_table_destroy(symbol_lookup_table); + symbol_lookup_table = NULL; + } + + if (named_causes != NULL) { + g_hash_table_destroy(named_causes); + named_causes = NULL; + } + + if (causes_array != NULL) { + g_ptr_array_foreach(causes_array, (GFunc)free_cause, NULL); + g_ptr_array_free(causes_array, TRUE); + causes_array = NULL; + } + + causes_array_len = 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/common/util.c Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,323 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2008-2009, Intel Corporation. + * All Rights Reserved. + */ + +#include <unistd.h> +#include <libintl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <procfs.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "latencytop.h" + +/* pipe that breaks the event loop (and exit early) */ +static int signal_pipe[2]; + +/* + * Get current system time in milliseconds (1e-3). + */ +uint64_t +lt_millisecond(void) +{ + struct timeval p; + (void) gettimeofday(&p, NULL); + return ((uint64_t)p.tv_sec * 1000 + p.tv_usec / 1000); +} + +/* + * Wrapper of gettext(). + */ +const char * +lt_text(const char *text) +{ + if (text == NULL) { + return (""); + } + + return (gettext(text)); +} + +/* + * Checks if OOM happens by comparing pointers with NULL in various places. + */ +void +lt_check_null(void *p) +{ + if (p == NULL) { + (void) printf("Out of memory!\n"); + g_assert(0); + exit(2); + } +} + +/* + * Safe malloc. + */ +void * +lt_malloc(size_t size) +{ + void *ret = malloc(size); + + lt_check_null(ret); + + return (ret); +} + +/* + * Safe alloc with memory cleared. + * Named it "zalloc" because its signature is different from + * calloc() in stdlib. + */ +void * +lt_zalloc(size_t size) +{ + void *ret = lt_malloc(size); + (void) memset(ret, 0, size); + return (ret); +} + +/* + * Safe strdup. + */ +char * +lt_strdup(const char *str) +{ + char *ret = strdup(str); + + lt_check_null(ret); + + return (ret); +} + +/* + * Get string for current time, e.g. YYYY-MM-DD + */ +void +lt_time_str(char *buffer, int len) +{ + struct tm tms; + time_t t; + int i; + + (void) time(&t); + (void) gmtime_r(&t, &tms); + (void) asctime_r(&tms, buffer, len); + + for (i = strlen(buffer)-1; i > 0; --i) { + if (isspace(buffer[i])) { + buffer[i] = 0; + } else { + break; + } + } +} + +/* + * Retrieves process exeutable name etc. from /proc. + */ +char * +lt_get_proc_field(pid_t pid, lt_field_t field) +{ + char name[PATH_MAX]; + int fd; + int ret; + psinfo_t psinfo; + + (void) snprintf(name, PATH_MAX, "/proc/%d/psinfo", (int)pid); + fd = open(name, O_RDONLY); + if (fd == -1) { + return (NULL); + } + + ret = read(fd, (char *)&psinfo, sizeof (psinfo_t)); + (void) close(fd); + if (ret < 0) { + return (NULL); + } + + switch (field) { + case LT_FIELD_FNAME: + return (lt_strdup(psinfo.pr_fname)); + case LT_FIELD_PSARGS: + return (lt_strdup(psinfo.pr_psargs)); + } + return (NULL); +} + +/* + * Help function to update the data structure. + */ +void +lt_update_stat_value(lt_stat_data_t *entry, + lt_stat_type_t type, uint64_t value) +{ + switch (type) { + case LT_STAT_COUNT: + entry->count += value; + break; + case LT_STAT_SUM: + entry->total += value; + break; + case LT_STAT_MAX: + if (value > entry->max) { + entry->max = value; + } + break; + default: + break; + } +} + +/* + * Help function to sort by total. + */ +int +lt_sort_by_total_desc(lt_stat_entry_t *a, lt_stat_entry_t *b) +{ + g_assert(a != NULL && b != NULL); + /* + * ->data.total is int64, so we can't simply return + * b->data.total - a->data.total + */ + if (b->data.total > a->data.total) { + return (1); + } else if (b->data.total < a->data.total) { + return (-1); + } else { + return (0); + } +} + +/* + * Help function to sort by max. + */ +int +lt_sort_by_max_desc(lt_stat_entry_t *a, lt_stat_entry_t *b) +{ + g_assert(a != NULL && b != NULL); + + if (b->data.max > a->data.max) { + return (1); + } else if (b->data.max < a->data.max) { + return (-1); + } else { + return (0); + } +} + +/* + * Help function to sort by count. + */ +int +lt_sort_by_count_desc(lt_stat_entry_t *a, lt_stat_entry_t *b) +{ + g_assert(a != NULL && b != NULL); + + if (b->data.count > a->data.count) { + return (1); + } else if (b->data.count < a->data.count) { + return (-1); + } else { + return (0); + } +} + +/* + * Help function to sort by average. + */ +int +lt_sort_by_avg_desc(lt_stat_entry_t *a, lt_stat_entry_t *b) +{ + double avg_a, avg_b; + + g_assert(a != NULL && b != NULL); + + avg_a = (double)a->data.total / a->data.count; + avg_b = (double)b->data.total / b->data.count; + + if (avg_b > avg_a) { + return (1); + } else if (avg_b < avg_a) { + return (-1); + } else { + return (0); + } +} + +/* + * Create pipe for signal handler and wakeup. + */ +void +lt_gpipe_init(void) +{ + (void) pipe(signal_pipe); +} + +/* + * Release pipe used in signal handler. + */ +void +lt_gpipe_deinit(void) +{ + (void) close(signal_pipe[0]); + (void) close(signal_pipe[1]); +} + +/* + * Break from main loop early. + */ +void +lt_gpipe_break(const char *ch) +{ + (void) write(signal_pipe[1], ch, 1); +} + +/* + * Returns fd# used to detect "break main loop". + */ +int +lt_gpipe_readfd(void) +{ + return (signal_pipe[0]); +} + +/* + * Check if a file exists. + */ +int +lt_file_exist(const char *name) +{ + struct stat64 st; + + if (stat64(name, &st) == 0) { + return (1); + } else { + return (0); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/i386/Makefile Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,27 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright (c) 2008-2009, Intel Corporation. +# All Rights Reserved. +# + +include ../Makefile.com + +install: all $(ROOTPROG32)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/cmd/latencytop/sparcv9/Makefile Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,31 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright (c) 2008-2009, Intel Corporation. +# All Rights Reserved. +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com +include ../../Makefile.cmd.64 + +install: all $(ROOTPROG64)
--- a/usr/src/pkgdefs/Makefile Fri Jul 17 16:43:38 2009 +0100 +++ b/usr/src/pkgdefs/Makefile Fri Jul 17 09:57:00 2009 -0700 @@ -316,6 +316,7 @@ SUNWixgbe \ SUNWkrbr \ SUNWkrbu \ + SUNWlatencytop \ SUNWldskint \ SUNWlibsasl \ SUNWllc \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWlatencytop/Makefile Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,34 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2008-2009, Intel Corporation. +# All Rights Reserved. +# + +include ../Makefile.com + +.KEEP_STATE: + +all: $(FILES) depend +install: all pkg + +include ../Makefile.targ
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWlatencytop/depend Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,31 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2008-2009, Intel Corporation. +# All Rights Reserved. +# + +P SUNWcsu Core Solaris, (Usr) +P SUNWcslr Core Solaris Libraries (Root) +P SUNWdtrc DTrace Clients +P SUNWdtrp DTrace Providers +P SUNWGlib GLIB - Library of useful routines for C programming
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWlatencytop/pkginfo.tmpl Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,50 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2008-2009, Intel Corporation. +# All Rights Reserved. +# + +# +# This required package information file describes characteristics of the +# package, such as package abbreviation, full package name, package version, +# and package architecture. +# +PKG="SUNWlatencytop" +NAME="LatencyTOP tool" +ARCH="ISA" +VERSION="ONVERS,REV=0.0.0" +CATEGORY="system" +SUNW_PRODNAME="SunOS" +SUNW_PRODVERS="RELEASE/VERSION" +DESC="LatencyTOP tool" +BASEDIR=/ +SUNW_PKGVERS="1.0" +SUNW_PKGTYPE="usr" +VENDOR="Sun Microsystems, Inc." +HOTLINE="Please contact your local service provider" +EMAIL="" +MAXINST="1000" +CLASSES="none" +SUNW_PKG_ALLZONES="true" +SUNW_PKG_HOLLOW="false" +SUNW_PKG_THISZONE="false"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWlatencytop/prototype_com Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,48 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2008-2009, Intel Corporation. +# All Rights Reserved. +# + +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# packaging files +i pkginfo +i copyright +i depend +# +# source locations relative to the prototype file +# +# SUNWlatencytop +# +d none usr 0755 root sys +d none usr/bin 0755 root bin +l none usr/bin/latencytop=../../usr/lib/isaexec
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWlatencytop/prototype_i386 Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,54 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2008-2009, Intel Corporation. +# All Rights Reserved. +# + +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# +# List files which are I386 specific here +# +# source locations relative to the prototype file +# +# +# SUNWlatencytop +# +d none usr/bin/i86 755 root bin +f none usr/bin/i86/latencytop 555 root bin +d none usr/bin/amd64 755 root bin +f none usr/bin/amd64/latencytop 555 root bin
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWlatencytop/prototype_sparc Fri Jul 17 09:57:00 2009 -0700 @@ -0,0 +1,52 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2008-2009, Intel Corporation. +# All Rights Reserved. +# + +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# +# List files which are SPARC specific here +# +# source locations relative to the prototype file +# +# +# SUNWlatencytop +# +d none usr/bin/sparcv9 755 root bin +f none usr/bin/sparcv9/latencytop 555 root bin