summaryrefslogtreecommitdiff
path: root/usr
diff options
context:
space:
mode:
authorKrishnendu Sadhukhan - Sun Microsystems <Krishnendu.Sadhukhan@Sun.COM>2009-09-28 13:53:34 -0700
committerKrishnendu Sadhukhan - Sun Microsystems <Krishnendu.Sadhukhan@Sun.COM>2009-09-28 13:53:34 -0700
commit15db28971f91c98efb449aebf46024ac72779fa3 (patch)
treed06fdb7297e4a23b6951bd0158c25421ea0084c0 /usr
parentb693757a2e07699c354cbc85e1ab83b588553262 (diff)
downloadillumos-joyent-15db28971f91c98efb449aebf46024ac72779fa3.tar.gz
PSARC/2009/339 LatencyTOP for OpenSolaris
6825817 Integrate latencyTOP into OpenSolaris Contributed by Lejun Zhu <lejun.zhu@intel.com>
Diffstat (limited to 'usr')
-rw-r--r--usr/src/Makefile.lint1
-rw-r--r--usr/src/cmd/Makefile1
-rw-r--r--usr/src/cmd/latencytop/Makefile47
-rw-r--r--usr/src/cmd/latencytop/Makefile.com79
-rw-r--r--usr/src/cmd/latencytop/amd64/Makefile30
-rw-r--r--usr/src/cmd/latencytop/common/display.c1040
-rw-r--r--usr/src/cmd/latencytop/common/dwrapper.c573
-rw-r--r--usr/src/cmd/latencytop/common/klog.c226
-rw-r--r--usr/src/cmd/latencytop/common/latencytop.c486
-rw-r--r--usr/src/cmd/latencytop/common/latencytop.d404
-rw-r--r--usr/src/cmd/latencytop/common/latencytop.h269
-rw-r--r--usr/src/cmd/latencytop/common/latencytop.trans44
-rw-r--r--usr/src/cmd/latencytop/common/stat.c1050
-rw-r--r--usr/src/cmd/latencytop/common/table.c840
-rw-r--r--usr/src/cmd/latencytop/common/util.c312
-rw-r--r--usr/src/cmd/latencytop/i386/Makefile29
-rw-r--r--usr/src/cmd/latencytop/sparcv9/Makefile30
-rw-r--r--usr/src/pkgdefs/Makefile1
-rw-r--r--usr/src/pkgdefs/SUNWlatencytop/Makefile34
-rw-r--r--usr/src/pkgdefs/SUNWlatencytop/depend31
-rw-r--r--usr/src/pkgdefs/SUNWlatencytop/pkginfo.tmpl50
-rw-r--r--usr/src/pkgdefs/SUNWlatencytop/prototype_com48
-rw-r--r--usr/src/pkgdefs/SUNWlatencytop/prototype_i38654
-rw-r--r--usr/src/pkgdefs/SUNWlatencytop/prototype_sparc52
24 files changed, 5731 insertions, 0 deletions
diff --git a/usr/src/Makefile.lint b/usr/src/Makefile.lint
index 9b7d236939..83326731bf 100644
--- a/usr/src/Makefile.lint
+++ b/usr/src/Makefile.lint
@@ -233,6 +233,7 @@ COMMON_SUBDIRS = \
cmd/pools \
cmd/power \
cmd/powertop \
+ cmd/latencytop \
cmd/ppgsz \
cmd/praudit \
cmd/prctl \
diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile
index 3ddc3f1d43..d21840f8bd 100644
--- a/usr/src/cmd/Makefile
+++ b/usr/src/cmd/Makefile
@@ -227,6 +227,7 @@ COMMON_SUBDIRS= \
kstat \
last \
lastcomm \
+ latencytop \
ldap \
ldapcachemgr \
lgrpinfo \
diff --git a/usr/src/cmd/latencytop/Makefile b/usr/src/cmd/latencytop/Makefile
new file mode 100644
index 0000000000..3d9815ddc7
--- /dev/null
+++ b/usr/src/cmd/latencytop/Makefile
@@ -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
diff --git a/usr/src/cmd/latencytop/Makefile.com b/usr/src/cmd/latencytop/Makefile.com
new file mode 100644
index 0000000000..98e65bafc7
--- /dev/null
+++ b/usr/src/cmd/latencytop/Makefile.com
@@ -0,0 +1,79 @@
+#
+# 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
+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
+
+ELFWRAP = elfwrap
+WRAPOBJ = latencytop_wrap.o
+
+CLEANFILES += $(OBJS) $(WRAPOBJ) ./latencytop_d ./latencytop_trans
+
+.KEEP_STATE:
+
+all: $(PROG)
+
+install: $(SUBDIRS)
+ -$(RM) $(ROOTPROG)
+ -$(LN) $(ISAEXEC) $(ROOTPROG)
+
+$(PROG): $(OBJS) $(WRAPOBJ)
+ $(LINK.c) -o $@ $(OBJS) $(WRAPOBJ) $(LDLIBS)
+ $(POST_PROCESS)
+
+$(WRAPOBJ): latencytop_d latencytop_trans
+ $(ELFWRAP) $(WRAPOPT) -o $(WRAPOBJ) latencytop_d latencytop_trans
+
+latencytop_d:
+ cp ../common/latencytop.d ./latencytop_d
+
+latencytop_trans:
+ cp ../common/latencytop.trans ./latencytop_trans
+
+clean:
+ $(RM) $(CLEANFILES)
+
+lint: lint_SRCS
+
+%.o: ../common/%.c
+ $(COMPILE.c) $<
+
+include ../../Makefile.targ
diff --git a/usr/src/cmd/latencytop/amd64/Makefile b/usr/src/cmd/latencytop/amd64/Makefile
new file mode 100644
index 0000000000..79b3cceb54
--- /dev/null
+++ b/usr/src/cmd/latencytop/amd64/Makefile
@@ -0,0 +1,30 @@
+#
+# 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
+
+WRAPOPT = -64
+
+install: all $(ROOTPROG64)
diff --git a/usr/src/cmd/latencytop/common/display.c b/usr/src/cmd/latencytop/common/display.c
new file mode 100644
index 0000000000..edc2c20ae1
--- /dev/null
+++ b/usr/src/cmd/latencytop/common/display.c
@@ -0,0 +1,1040 @@
+/*
+ * 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 <signal.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 dimension */
+static int screen_width = 1, screen_height = 1;
+/* Is display initialized, i.e. are window pointers set up. */
+static int display_initialized = FALSE;
+/* Is initscr() called */
+static int curses_inited = FALSE;
+
+/* To handle user key presses */
+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;
+/* Type of list being displayed */
+static int current_list_type = LT_LIST_CAUSE;
+static int show_help = FALSE;
+
+/* Help functions that append/prepend a blank to the given 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);
+ }
+}
+
+/* Convert the nanosecond value to a human readable string */
+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);
+}
+
+/* Used in print_statistics below */
+#define WIDTH_REASON_STRING 36
+#define WIDTH_COUNT 12
+#define WIDTH_AVG 12
+#define WIDTH_MAX 12
+#define WIDTH_PCT 8
+#define BEGIN_COUNT WIDTH_REASON_STRING
+#define BEGIN_AVG (BEGIN_COUNT + WIDTH_COUNT)
+#define BEGIN_MAX (BEGIN_AVG + WIDTH_AVG)
+#define BEGIN_PCT (BEGIN_MAX + WIDTH_MAX)
+
+/*
+ * Print statistics in global/process pane. Called by print_sysglobal
+ * print_process.
+ *
+ * Parameters:
+ * window - the global or process statistics window.
+ * begin_line - where to start printing.
+ * count - how many lines should be printed.
+ * list - a stat_list.
+ */
+static void
+print_statistics(WINDOW * window, int begin_line, int nlines, 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 < nlines && lt_stat_list_has_item(list, i)) {
+
+ 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), "%llu", count);
+ 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_AVG,
+ "%s", get_time_string(
+ (double)lt_stat_list_get_sum(list, i) / count,
+ tmp, sizeof (tmp), WIDTH_AVG));
+
+ (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 statistics in global pane.
+ */
+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", "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. Mode is combination of:
+ *
+ * "Process or Thread", and "1 or 2 or 3".
+ */
+static void
+print_current_mode()
+{
+ char type;
+
+ if (!display_initialized) {
+ return;
+ }
+
+ switch (current_list_type) {
+ case LT_LIST_CAUSE:
+ type = '1';
+ break;
+ case LT_LIST_SPECIALS:
+ type = '2';
+ break;
+ case LT_LIST_SOBJ:
+ type = '3';
+ break;
+ default:
+ type = '?';
+ break;
+ }
+
+ (void) mvwprintw(process_window, 0, screen_width - 8, "View: %c%c",
+ type, thread_mode ? 'T' : 'P');
+}
+
+/*
+ * Print per-process statistics in process pane.
+ * This is called when mode of operation is process.
+ */
+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), %d threads",
+ lt_stat_proc_get_name(pid), pid, lt_stat_proc_get_nthreads(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, 48, "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);
+}
+
+/*
+ * Display the list of processes that are tracked, in task bar.
+ * This one is called when mode of operation is process.
+ */
+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);
+}
+
+/*
+ * Display the list of processes that are tracked, in task bar.
+ * This one is called when mode of operation is thread.
+ */
+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 length of thread's ID; use shorter process name
+ * in order to save space on the screen.
+ */
+ 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 per-thread statistics in process pane.
+ * This is called when mode of operation is thread.
+ */
+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, 48, "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 display its own message.
+ */
+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 {
+ /*
+ * Important messages are displayed at least every 2 cycles.
+ */
+ next_hint = now + update_interval * 2;
+ }
+
+ (void) werase(hintbar);
+ (void) mvwprintw(hintbar, 0, (screen_width - strlen(hint)) / 2,
+ "%s", hint);
+ (void) wrefresh(hintbar);
+}
+
+/*
+ * Create a PID list or a PID/TID list (if operation mode is thread) from
+ * available statistics.
+ */
+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 previously selected PID */
+ for (*list_index = 0; *list_index < *list_len &&
+ (*plist)[*list_index] != selected_pid;
+ ++*list_index) {
+ }
+
+ if (*list_index >= *list_len) {
+ /*
+ * The previously 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 previously 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 previously selected pid/tid is gone.
+ * Select the first one.
+ */
+ for (*list_index = 0;
+ *list_index < *list_len &&
+ (*plist)[*list_index] != selected_pid;
+ ++*list_index) {
+ }
+ }
+
+ if (*list_index >= *list_len) {
+ /*
+ * The previously selected pid is gone.
+ * Select the first one
+ */
+ *list_index = 0;
+ }
+ }
+}
+
+/* Print help message when user presses 'h' hot key */
+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",
+ " Cause "
+ "Count Average Maximum Percent");
+ (void) wrefresh(captionbar);
+
+ (void) wattrset(hintbar, COLOR_PAIR(LT_COLOR_HEADER));
+ (void) wbkgd(hintbar, COLOR_PAIR(LT_COLOR_HEADER));
+}
+
+/*
+ * Handle signal from terminal resize
+ */
+/* ARGSUSED */
+static void
+on_resize(int sig)
+{
+ lt_gpipe_break("r");
+}
+
+/*
+ * Initialize display. Display 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 library */
+ (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;
+ }
+
+ /* Set up all window panes */
+ 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 for display. It displays data on screen and handles hotkey
+ * presses.
+ *
+ * Parameter :
+ * duration - returns after 'duration'
+ *
+ * The function also returns if user presses 'q', 'Ctrl+C' or 'r'.
+ *
+ * Return value:
+ * 0 - main() exits
+ * 1 - main() calls it 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 time for the next one.
+ */
+ 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;
+ (void) read(gpipe, &ch, 1);
+ k = ch; /* Need this for big-endianness */
+ } else {
+ k = getch();
+ }
+
+ /*
+ * Check if we need to update the hint line whenever we
+ * get a chance.
+ * NOTE: current implementation depends on
+ * g_config.lt_cfg_snap_interval, but it's OK because it
+ * doesn't have to be precise.
+ */
+ print_hint(NULL);
+ /*
+ * If help is on display right now, and a key press
+ * happens, we need to clear the help and continue.
+ */
+ 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.lt_cfg_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.lt_cfg_trace_syncobj) {
+ current_list_type = LT_LIST_SOBJ;
+ print_sysglobal();
+ } else if (g_config.lt_cfg_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 refresh is needed */
+ 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);
+}
+
+/*
+ * Clean up display.
+ */
+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 message when display error happens.
+ */
+/* 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) fprintf(stderr, "%s\n", tmp);
+ } else if (!show_help) {
+ print_hint(tmp);
+ }
+
+}
diff --git a/usr/src/cmd/latencytop/common/dwrapper.c b/usr/src/cmd/latencytop/common/dwrapper.c
new file mode 100644
index 0000000000..d1920e5500
--- /dev/null
+++ b/usr/src/cmd/latencytop/common/dwrapper.c
@@ -0,0 +1,573 @@
+/*
+ * 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; /* dtrace handle */
+static pid_t pid_self = -1; /* PID of our own process */
+
+/*
+ * Ignore sched if sched is not tracked.
+ * Also ignore ourselves (i.e., latencytop).
+ */
+#define SHOULD_IGNORE(pid) \
+ ((!g_config.lt_cfg_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 aggregation lt_call_* (related to on/off cpu
+ * activities) in the snapshot.
+ */
+static int
+aggwalk_call(const dtrace_aggdata_t *data, lt_stat_type_t stat_type)
+{
+ 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;
+ char *tag = NULL;
+ unsigned int priority;
+ enum { REC_PID = 1, REC_TID, REC_STACK, REC_TAG, REC_PRIO, REC_AGG,
+ NREC };
+
+ /* Check action type */
+ if ((aggdesc->dtagd_nrecs < NREC) ||
+ (aggdesc->dtagd_rec[REC_PID].dtrd_action != DTRACEACT_DIFEXPR) ||
+ (aggdesc->dtagd_rec[REC_TID].dtrd_action != DTRACEACT_DIFEXPR) ||
+ (aggdesc->dtagd_rec[REC_TAG].dtrd_action != DTRACEACT_DIFEXPR) ||
+ (aggdesc->dtagd_rec[REC_PRIO].dtrd_action != DTRACEACT_DIFEXPR) ||
+ (!DTRACEACT_ISAGG(aggdesc->dtagd_rec[REC_AGG].dtrd_action)) ||
+ (aggdesc->dtagd_rec[REC_STACK].dtrd_action != DTRACEACT_STACK)) {
+
+ 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);
+ }
+
+ tid = rec_get_value(
+ data->dtada_data + aggdesc->dtagd_rec[REC_TID].dtrd_offset,
+ aggdesc->dtagd_rec[REC_TID].dtrd_size);
+
+ /* 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 the last byte of the buffer and
+ * break.
+ */
+ ptr = &buffer[buffersize-1];
+ break;
+ } else {
+ ptr += len;
+ }
+ }
+ }
+
+ if (ptr != buffer) {
+ /*
+ * We have printed something, so it is safe to remove
+ * the last ' '.
+ */
+ *(ptr-1) = '\0';
+ }
+
+ tag = (char *)data->dtada_data +
+ aggdesc->dtagd_rec[REC_TAG].dtrd_offset;
+
+ priority = rec_get_value(
+ data->dtada_data + aggdesc->dtagd_rec[REC_PRIO].dtrd_offset,
+ aggdesc->dtagd_rec[REC_PRIO].dtrd_size);
+
+ 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, tag, priority, stat_type, agg_value);
+
+ if (buffer != NULL) {
+ free(buffer);
+ }
+
+ return (0);
+}
+
+/*
+ * Callback to process aggregation lt_named_* (related to lock spinning etc.),
+ * in the snapshot.
+ */
+static int
+aggwalk_named(const dtrace_aggdata_t *data, lt_stat_type_t stat_type)
+{
+ dtrace_aggdesc_t *aggdesc = data->dtada_desc;
+ pid_t pid;
+ id_t tid;
+ uint64_t agg_value;
+ int cause_id;
+ char *type = NULL;
+ enum { REC_PID = 1, REC_TID, REC_TYPE, REC_AGG, NREC };
+
+ /* Check action type */
+ if ((aggdesc->dtagd_nrecs < NREC) ||
+ (aggdesc->dtagd_rec[REC_PID].dtrd_action != DTRACEACT_DIFEXPR) ||
+ (aggdesc->dtagd_rec[REC_TID].dtrd_action != DTRACEACT_DIFEXPR) ||
+ (aggdesc->dtagd_rec[REC_TYPE].dtrd_action != DTRACEACT_DIFEXPR) ||
+ (!DTRACEACT_ISAGG(aggdesc->dtagd_rec[REC_AGG].dtrd_action))) {
+
+ 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);
+ }
+
+ tid = rec_get_value(
+ data->dtada_data + aggdesc->dtagd_rec[REC_TID].dtrd_offset,
+ aggdesc->dtagd_rec[REC_TID].dtrd_size);
+
+ type = (char *)data->dtada_data
+ + aggdesc->dtagd_rec[REC_TYPE].dtrd_offset;
+ cause_id = lt_table_cause_from_name(type, 1, CAUSE_FLAG_SPECIAL);
+
+ 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 aggregation lt_sync_* (related to synchronization
+ * objects), in the snapshot.
+ */
+static int
+aggwalk_sync(const dtrace_aggdata_t *data, lt_stat_type_t stat_type)
+{
+ dtrace_aggdesc_t *aggdesc = data->dtada_desc;
+ pid_t pid;
+ id_t tid;
+ uint64_t agg_value;
+ int stype;
+ unsigned long long wchan;
+ enum { REC_PID = 1, REC_TID, REC_STYPE, REC_WCHAN, REC_AGG, NREC };
+
+ /* Check action type */
+ if ((aggdesc->dtagd_nrecs < NREC) ||
+ (aggdesc->dtagd_rec[REC_PID].dtrd_action != DTRACEACT_DIFEXPR) ||
+ (aggdesc->dtagd_rec[REC_TID].dtrd_action != DTRACEACT_DIFEXPR) ||
+ (aggdesc->dtagd_rec[REC_STYPE].dtrd_action != DTRACEACT_DIFEXPR) ||
+ (aggdesc->dtagd_rec[REC_WCHAN].dtrd_action != DTRACEACT_DIFEXPR) ||
+ (!DTRACEACT_ISAGG(aggdesc->dtagd_rec[REC_AGG].dtrd_action))) {
+
+ 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);
+ }
+
+ tid = rec_get_value(
+ data->dtada_data + aggdesc->dtagd_rec[REC_TID].dtrd_offset,
+ aggdesc->dtagd_rec[REC_TID].dtrd_size);
+
+ stype = rec_get_value(
+ data->dtada_data + aggdesc->dtagd_rec[REC_STYPE].dtrd_offset,
+ aggdesc->dtagd_rec[REC_STYPE].dtrd_size);
+
+ wchan = rec_get_value(
+ data->dtada_data + aggdesc->dtagd_rec[REC_WCHAN].dtrd_offset,
+ aggdesc->dtagd_rec[REC_WCHAN].dtrd_size);
+
+ 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 various aggregations in the snapshot. Called by
+ * different aggwalk_* functions.
+ */
+/* ARGSUSED */
+static int
+aggwalk(const dtrace_aggdata_t *data, void *arg)
+{
+ char *tmp;
+ char buffer[32];
+ lt_stat_type_t stat_type;
+ 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 (tmp == NULL || strcmp(tmp, "lt") != 0) {
+ goto done;
+ }
+
+ tmp = strtok(NULL, "_");
+
+ if (tmp == NULL) {
+ goto done;
+ } else 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 (tmp == NULL) {
+ goto done;
+ } else 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, so remove it from DTrace now */
+ return (DTRACE_AGGWALK_REMOVE);
+}
+
+/*
+ * Callback to handle event caused by DTrace dropping data.
+ */
+/*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 just continue */
+ return (DTRACE_HANDLE_OK);
+}
+
+#ifndef EMBED_CONFIGS
+/*
+ * Copy the content from a "real" file into a temp file.
+ */
+static int
+copy_tmp_file(const char *src, FILE *dst)
+{
+ FILE *tmp = NULL;
+ char buffer[256];
+ int bytes;
+
+ if ((tmp = fopen(src, "r")) == NULL) {
+ return (-1);
+ }
+
+ while ((bytes = fread(buffer, 1, sizeof (buffer), tmp)) > 0) {
+ if (fwrite(buffer, bytes, 1, dst) != 1) {
+ return (-1);
+ }
+ }
+
+ (void) fclose(tmp);
+
+ return (0);
+}
+#endif
+
+/*
+ * DTrace initialization. D script starts 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();
+
+ if ((g_dtp = dtrace_open(DTRACE_VERSION, 0, &err)) == 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);
+ }
+
+ if (g_config.lt_cfg_enable_filter) {
+ if ((err = dtrace_setopt(g_dtp, "define",
+ "ENABLE_FILTER")) != 0) {
+ lt_display_error(
+ "Failed to set option ENABLE_FILTER.\n");
+ return (err);
+ }
+ }
+
+ if (g_config.lt_cfg_trace_syncobj) {
+ if ((err = dtrace_setopt(g_dtp, "define",
+ "ENABLE_SYNCOBJ")) != 0) {
+ lt_display_error(
+ "Failed to set option ENABLE_SYNCOBJ.\n");
+ return (err);
+ }
+ }
+
+ if (g_config.lt_cfg_trace_sched) {
+ if ((err = dtrace_setopt(g_dtp, "define",
+ "ENABLE_SCHED")) != 0) {
+ lt_display_error(
+ "Failed to set option ENABLE_SYNCOBJ.\n");
+ return (err);
+ }
+ }
+
+ if (g_config.lt_cfg_low_overhead_mode) {
+ if ((err = dtrace_setopt(g_dtp, "define",
+ "ENABLE_LOW_OVERHEAD")) != 0) {
+ lt_display_error(
+ "Failed to set option ENABLE_SYNCOBJ.\n");
+ return (err);
+ }
+ }
+
+ /* Create a temp file; libdtrace needs it for cpp(1) */
+ if ((fp_script = tmpfile()) == NULL) {
+ lt_display_error("Cannot create tmp file\n");
+ return (-1);
+ }
+
+ /* Copy the main D script into the temp file */
+#ifdef EMBED_CONFIGS
+ if (fwrite(&latencytop_d_start,
+ (size_t)(&latencytop_d_end - &latencytop_d_start), 1, fp_script)
+ != 1) {
+ lt_display_error("Could not copy D script, fwrite() failed\n");
+ (void) fclose(fp_script);
+ return (-1);
+ }
+#else
+ if (copy_tmp_file(DEFAULT_D_SCRIPT_NAME, fp_script) != 0) {
+ lt_display_error("Cannot open script file %s\n",
+ DEFAULT_D_SCRIPT_NAME);
+ (void) fclose(fp_script);
+ return (-1);
+ }
+#endif /* EMBED_CONFIGS */
+
+ if (lt_table_append_trans(fp_script) != 0) {
+ (void) fclose(fp_script);
+ return (-1);
+ }
+
+ (void) fseek(fp_script, 0, SEEK_SET);
+
+ if ((prog = dtrace_program_fcompile(g_dtp, fp_script,
+ DTRACE_C_CPP, 0, NULL)) == NULL) {
+ lt_display_error("Failed to compile D script.\n");
+ (void) fclose(fp_script);
+ return (dtrace_errno(g_dtp));
+ }
+
+ (void) fclose(fp_script);
+
+ /* 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 aggregate data to user space. Called periodically
+ * to prevent the kernel from running out of 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.lt_cfg_snap_interval) {
+ return (last_snap + g_config.lt_cfg_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 dtrace aggregator and collect data for latencytop to display.
+ * Called immediately before UI update.
+ */
+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 we don't need to clear again, because we have removed
+ * everything. Paranoid ?
+ */
+ dtrace_aggregate_clear(g_dtp);
+
+ return (0);
+}
+
+/*
+ * dtrace clean up.
+ */
+void
+lt_dtrace_deinit(void)
+{
+ (void) dtrace_stop(g_dtp);
+ dtrace_close(g_dtp);
+}
diff --git a/usr/src/cmd/latencytop/common/klog.c b/usr/src/cmd/latencytop/common/klog.c
new file mode 100644
index 0000000000..104145ecae
--- /dev/null
+++ b/usr/src/cmd/latencytop/common/klog.c
@@ -0,0 +1,226 @@
+/*
+ * 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)
+{
+ (void) fprintf(fp, "%-8ld \"%s\"\n", (long)key, 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->lt_s_total,
+ (long long)log->lt_s_count,
+ (long long)log->lt_s_max,
+ key);
+}
+
+/*
+ * Initialization for kernel 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;
+
+ g_assert(strlen(filename) < sizeof (klog_filename));
+
+ 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 around */
+ 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 content to log 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 at %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. It flushes all log content in memory to 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;
+ }
+}
+
+/*
+ * Write a kernel stack and its statistics to log file. 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);
+}
diff --git a/usr/src/cmd/latencytop/common/latencytop.c b/usr/src/cmd/latencytop/common/latencytop.c
new file mode 100644
index 0000000000..81451eadf6
--- /dev/null
+++ b/usr/src/cmd/latencytop/common/latencytop.c
@@ -0,0 +1,486 @@
+/*
+ * 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;
+
+typedef enum {
+ LT_CMDOPT_INTERVAL,
+ LT_CMDOPT_LOG_FILE,
+ LT_CMDOPT_LOG_LEVEL,
+ LT_CMDOPT_LOG_INTERVAL,
+ LT_CMDOPT_CONFIG_FILE,
+ LT_CMDOPT_F_FILTER,
+ LT_CMDOPT_F_SCHED,
+ LT_CMDOPT_F_SOBJ,
+ LT_CMDOPT_F_LOW,
+ LT_CMDOPT__LAST /* Must be last one */
+} lt_cmd_option_id_t;
+
+/*
+ * Check for duplicate command line options.
+ * Returns TRUE if duplicate options with different values are found,
+ * returns FALSE otherwise.
+ */
+static int
+check_opt_dup(lt_cmd_option_id_t id, uint64_t value) {
+
+ static int opt_set[(int)LT_CMDOPT__LAST];
+ static uint64_t opt_val[(int)LT_CMDOPT__LAST];
+
+ const char *errmsg[] = {
+ "-t is set more than once with different values.",
+ "-o is set more than once.",
+ "-k is set more than once with different values.",
+ "-l is set more than once with different values.",
+ "-c is set more than once.",
+ "-f [no]filter is set more than once with different values.",
+ "-f [no]sched is set more than once with different values.",
+ "-f [no]sobj is set more than once with different values.",
+ "-f [no]low is set more than once with different values.",
+ };
+
+ g_assert(sizeof (errmsg)/sizeof (errmsg[0]) == (int)LT_CMDOPT__LAST);
+
+ if (!opt_set[(int)id]) {
+ opt_set[(int)id] = TRUE;
+ opt_val[(int)id] = value;
+ return (FALSE);
+ }
+
+ if (opt_val[(int)id] != value) {
+ (void) fprintf(stderr, "%s\n", errmsg[(int)id]);
+ return (TRUE);
+ }
+
+ return (FALSE);
+}
+
+/*
+ * Print command-line help message.
+ */
+static void
+print_usage(const char *execname, int long_help)
+{
+ char buffer[PATH_MAX];
+ (void) snprintf(buffer, sizeof (buffer), "%s", execname);
+
+ if (!long_help) {
+ /* Print short help to stderr. */
+ (void) fprintf(stderr, "Usage: %s [option(s)], ",
+ basename(buffer));
+ (void) fprintf(stderr, "use '%s -h' for details.\n",
+ basename(buffer));
+ return;
+ }
+
+ (void) printf("Usage: %s [option(s)]\n", basename(buffer));
+ (void) printf("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"
+ /*
+ * Option "-c, --config FILE" is not user-visible for now.
+ * When we have chance to properly document the format of translation
+ * rules, we'll make it user-visible.
+ */
+ " -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 >= 60\n");
+}
+
+/*
+ * Properly exit latencytop when it receives SIGINT or SIGTERM.
+ */
+/* ARGSUSED */
+static void
+signal_handler(int sig)
+{
+ lt_gpipe_break("q");
+}
+
+/*
+ * Convert string to integer. It returns 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;
+ char logfile[PATH_MAX] = "";
+
+ lt_gpipe_init();
+ (void) signal(SIGINT, signal_handler);
+ (void) signal(SIGTERM, signal_handler);
+
+ /* Default global settings */
+ g_config.lt_cfg_enable_filter = 0;
+ g_config.lt_cfg_trace_sched = 0;
+ g_config.lt_cfg_trace_syncobj = 1;
+ g_config.lt_cfg_low_overhead_mode = 0;
+ /* dtrace snapshot every 1 second */
+ g_config.lt_cfg_snap_interval = 1000;
+#ifdef EMBED_CONFIGS
+ g_config.lt_cfg_config_name = NULL;
+#else
+ g_config.lt_cfg_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], TRUE);
+ 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;
+ } else if (check_opt_dup(LT_CMDOPT_INTERVAL,
+ refresh_interval)) {
+ 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;
+ } else if (check_opt_dup(LT_CMDOPT_LOG_LEVEL,
+ refresh_interval)) {
+ unknown_option = TRUE;
+ }
+
+ break;
+ case 'o':
+ if (check_opt_dup(LT_CMDOPT_LOG_FILE, optind)) {
+ unknown_option = TRUE;
+ } else if (strlen(optarg) >= sizeof (logfile)) {
+ lt_display_error(
+ "Log file name is too long: %s\n",
+ optarg);
+ unknown_option = TRUE;
+ } else {
+ (void) strncpy(logfile, optarg,
+ sizeof (logfile));
+ }
+
+ 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) {
+ if (check_opt_dup(LT_CMDOPT_F_FILTER,
+ v)) {
+ unknown_option = TRUE;
+ } else {
+ g_config.lt_cfg_enable_filter
+ = v;
+ }
+ } else if (CMPOPT(token, "sched") == 0) {
+ if (check_opt_dup(LT_CMDOPT_F_SCHED,
+ v)) {
+ unknown_option = TRUE;
+ } else {
+ g_config.lt_cfg_trace_sched
+ = v;
+ }
+ } else if (CMPOPT(token, "sobj") == 0) {
+ if (check_opt_dup(LT_CMDOPT_F_SOBJ,
+ v)) {
+ unknown_option = TRUE;
+ } else {
+ g_config.lt_cfg_trace_syncobj
+ = v;
+ }
+ } else if (CMPOPT(token, "low") == 0) {
+ if (check_opt_dup(LT_CMDOPT_F_LOW,
+ v)) {
+ unknown_option = TRUE;
+ } else {
+ g_config.
+ lt_cfg_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 log interval: %s\n", optarg);
+ unknown_option = TRUE;
+ } else if (check_opt_dup(LT_CMDOPT_LOG_INTERVAL,
+ log_interval)) {
+ unknown_option = TRUE;
+ }
+
+ break;
+ case 'c':
+ if (strlen(optarg) >= PATH_MAX) {
+ lt_display_error(
+ "Configuration name is too long.\n");
+ unknown_option = TRUE;
+ } else if (check_opt_dup(LT_CMDOPT_CONFIG_FILE,
+ optind)) {
+ unknown_option = TRUE;
+ } else {
+ g_config.lt_cfg_config_name =
+ lt_strdup(optarg);
+ }
+
+ break;
+ default:
+ unknown_option = TRUE;
+ break;
+ }
+ }
+
+ if (!unknown_option && strlen(logfile) > 0) {
+ err = lt_klog_set_log_file(logfile);
+
+ if (err == -1) {
+ lt_display_error("Log file name is too long: %s\n",
+ logfile);
+ unknown_option = TRUE;
+ } else if (err == -2) {
+ lt_display_error("Cannot write to log file: %s\n",
+ logfile);
+ unknown_option = TRUE;
+ }
+ }
+
+ /* Throw error for invalid/junk arguments */
+ if (optind < argc) {
+ int tmpind = optind;
+ (void) fprintf(stderr, "Unknown option(s): ");
+
+ while (tmpind < argc) {
+ (void) fprintf(stderr, "%s ", argv[tmpind++]);
+ }
+
+ (void) fprintf(stderr, "\n");
+ unknown_option = TRUE;
+ }
+
+ if (unknown_option) {
+ print_usage(argv[0], FALSE);
+ retval = 1;
+ goto end_none;
+ }
+
+ (void) printf("%s\n%s\n", TITLE, COPYRIGHT);
+
+ /*
+ * 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.lt_cfg_snap_interval * 1000) {
+ tsleep = g_config.lt_cfg_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.lt_cfg_config_name != NULL) {
+ free(g_config.lt_cfg_config_name);
+ }
+
+ return (retval);
+}
diff --git a/usr/src/cmd/latencytop/common/latencytop.d b/usr/src/cmd/latencytop/common/latencytop.d
new file mode 100644
index 0000000000..95381b12de
--- /dev/null
+++ b/usr/src/cmd/latencytop/common/latencytop.d
@@ -0,0 +1,404 @@
+/*
+ * 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.
+ */
+
+#define MAX_TAG 8
+#define MAX_STACK 64
+
+#pragma D option aggsize=8m
+#pragma D option bufsize=16m
+#pragma D option dynvarsize=16m
+#pragma D option aggrate=0
+#pragma D option stackframes=MAX_STACK
+/*
+ * Our D script needs to compile even if some of the TRANSLATE probes cannot
+ * be found. Missing probes can be caused by older kernel, different
+ * architecture, unloaded modules etc.
+ */
+#pragma D option zdefs
+
+#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
+
+/* Threshold to filter WAKEABLE latencies. */
+#define FILTER_THRESHOLD 5000000
+/* From thread.h */
+#define T_WAKEABLE 2
+
+/*
+ * This array is used to store timestamp of when threads are enqueued
+ * to dispatch queue.
+ * 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 */
+self unsigned int lt_stackp;
+self unsigned int lt_prio[int];
+self string lt_cause[int];
+
+this unsigned int priority;
+this string cause;
+
+/*
+ * Clean up everything, otherwise we will run out of memory.
+ */
+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;
+
+ /*
+ * Workaround: no way to clear associative array.
+ * We have to manually clear 0 ~ (MAX_TAG-1).
+ */
+
+ self->lt_prio[0] = 0;
+ self->lt_prio[1] = 0;
+ self->lt_prio[2] = 0;
+ self->lt_prio[3] = 0;
+ self->lt_prio[4] = 0;
+ self->lt_prio[5] = 0;
+ self->lt_prio[6] = 0;
+ self->lt_prio[7] = 0;
+
+ self->lt_cause[0] = 0;
+ self->lt_cause[1] = 0;
+ self->lt_cause[2] = 0;
+ self->lt_cause[3] = 0;
+ self->lt_cause[4] = 0;
+ self->lt_cause[5] = 0;
+ self->lt_cause[6] = 0;
+ self->lt_cause[7] = 0;
+}
+
+#if !defined(ENABLE_LOW_OVERHEAD)
+/*
+ * Log timestamp when a thread is taken off the 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 latency when the thread is actually on the CPU.
+ * This is necessary in order 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" latency when it is interruptible, i.e., 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 the duration between the time when a thread is taken
+ * off the CPU and the time when it is enqueued again.
+ */
+sched::resume:on-cpu
+/self->lt_sleep_duration != 0/
+{
+ this->cause = self->lt_stackp > 0 ?
+ self->lt_cause[self->lt_stackp - 1] : "";
+ this->priority = self->lt_stackp > 0 ?
+ self->lt_prio[self->lt_stackp - 1] : 0;
+
+ @lt_call_count[pid, tid, stack(), this->cause,
+ this->priority] = count();
+ @lt_call_sum[pid, tid, stack(), this->cause,
+ this->priority] = sum(self->lt_sleep_duration);
+ @lt_call_max[pid, tid, stack(), this->cause,
+ this->priority] = max(self->lt_sleep_duration);
+
+ self->lt_is_block_wakeable = 0; /* Clear the flag to avoid leak */
+ self->lt_sleep_duration = 0;
+}
+
+/*
+ * Write time spent in queue to the aggregation.
+ * lt_sch_delay is the interval between the time when a thread becomes
+ * runnable and the time when it is actually on the 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 to track latency caused by spinning on a lock.
+ */
+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 to track latency caused by blocking on a lock.
+ */
+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 to track latency caused by synchronization objects.
+ */
+this int stype;
+this unsigned long long wchan;
+this unsigned long long wtime;
+
+sched:::wakeup
+/*
+ * Currently we are unable to track wakeup from sched, because all its LWP IDs
+ * are zero when we trace it and that makes lt_timestamps unusable.
+ */
+/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 the following:
+ *
+ * 1. Use sampling and update aggregations only roughly 1/100 times
+ * (SAMPLE_TIMES).
+ * 2. Do not track anything other than what is 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 latency when a thread is actually on the 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;
+}
+
+/*
+ * Track large latency first.
+ */
+sched::resume:on-cpu
+/self->lt_timestamp > SAMPLE_THRESHOLD/
+{
+ this->cause = self->lt_stackp > 0 ?
+ self->lt_cause[self->lt_stackp - 1] : "";
+ this->priority = self->lt_stackp > 0 ?
+ self->lt_prio[self->lt_stackp - 1] : 0;
+
+ @lt_call_count[pid, tid, stack(), this->cause,
+ this->priority] = sum(1);
+ @lt_call_sum[pid, tid, stack(), this->cause,
+ this->priority] = sum(self->lt_timestamp);
+ @lt_call_max[pid, tid, stack(), this->cause,
+ this->priority] = max(self->lt_timestamp);
+
+ self->lt_timestamp = 0;
+}
+
+/*
+ * If we fall back to this probe, that means the latency is small and counter
+ * has reached SAMPLE_TIMES.
+ */
+sched::resume:on-cpu
+/self->lt_timestamp != 0/
+{
+ this->cause = self->lt_stackp > 0 ?
+ self->lt_cause[self->lt_stackp - 1] : "";
+ this->priority = self->lt_stackp > 0 ?
+ self->lt_prio[self->lt_stackp - 1] : 0;
+
+ /* Need +1 because lt_counter has not been updated in this cycle. */
+ @lt_call_count[pid, tid, stack(), this->cause,
+ this->priority] = sum(self->lt_counter + 1);
+ @lt_call_sum[pid, tid, stack(), this->cause,
+ this->priority] = sum((self->lt_counter + 1) * self->lt_timestamp);
+ @lt_call_max[pid, tid, stack(), this->cause,
+ this->priority] = max(self->lt_timestamp);
+
+ self->lt_timestamp = 0;
+ self->lt_counter = 0;
+}
+
+#endif /* !defined(ENABLE_LOW_OVERHEAD) */
+
+#define TRANSLATE(entryprobe, returnprobe, cause, priority) \
+entryprobe \
+TRACE_FILTER_COND(self->lt_stackp == 0 || \
+ (self->lt_stackp < MAX_TAG && \
+ self->lt_prio[self->lt_stackp - 1] <= priority) ) \
+{ \
+ self->lt_prio[self->lt_stackp] = priority; \
+ self->lt_cause[self->lt_stackp] = cause; \
+ ++self->lt_stackp; \
+} \
+returnprobe \
+TRACE_FILTER_COND(self->lt_stackp > 0 && \
+ self->lt_cause[self->lt_stackp - 1] == cause) \
+{ \
+ --self->lt_stackp; \
+ self->lt_cause[self->lt_stackp] = NULL; \
+}
+
+/*
+ * Syscalls have a priority of 10. This is to make sure that latency is
+ * traced to one of the syscalls only if nothing else matches.
+ * We put this special probe here because it uses "probefunc" variable instead
+ * of a constant string.
+ */
+
+TRANSLATE(syscall:::entry, syscall:::return, probefunc, 10)
diff --git a/usr/src/cmd/latencytop/common/latencytop.h b/usr/src/cmd/latencytop/common/latencytop.h
new file mode 100644
index 0000000000..df0f992c47
--- /dev/null
+++ b/usr/src/cmd/latencytop/common/latencytop.h
@@ -0,0 +1,269 @@
+/*
+ * 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>
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Without this 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
+
+/*
+ * We define our own conversions in order to avoid compiler warnings.
+ */
+#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 configuration is embedded in the binary.
+ * Array will be generated by elfwrap.
+ */
+extern char latencytop_d_start;
+extern char latencytop_d_end;
+extern char latencytop_trans_start;
+extern char latencytop_trans_end;
+#else
+/*
+ * LatencyTOP configuration is provided externally by user.
+ */
+#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, /* List latency by causes (default) */
+ LT_LIST_SPECIALS, /* List only "special" causes */
+ LT_LIST_SOBJ /* List synchronization objects */
+} lt_list_type_t;
+
+/*
+ * Data structure which contains statistics.
+ */
+typedef struct {
+ uint64_t lt_s_count;
+ uint64_t lt_s_total;
+ uint64_t lt_s_max;
+} lt_stat_data_t;
+
+/*
+ * Data structure that stores statistics along with the name.
+ */
+typedef struct {
+ enum {
+ STAT_CAUSE,
+ STAT_SOBJ
+ } lt_se_type;
+ const char *lt_se_string;
+ lt_stat_data_t lt_se_data;
+ union {
+ struct {
+ int lt_se_c_id;
+ int lt_se_c_flags;
+ } lt_se_t_cause;
+ struct {
+ int lt_se_s_id;
+ } lt_se_t_sobj;
+ } lt_se_tsdata; /* type specific data */
+} lt_stat_entry_t;
+
+typedef struct {
+ int lt_cfg_enable_filter;
+ int lt_cfg_trace_sched;
+ int lt_cfg_trace_syncobj;
+ int lt_cfg_low_overhead_mode;
+ int lt_cfg_snap_interval;
+ char *lt_cfg_config_name;
+} lt_config_t;
+
+extern lt_config_t g_config; /* The global settings */
+
+/*
+ * Causes can be disabled through the configuration file.
+ * When disabled, though D script will continue to capture causes, they will
+ * not be counted by LatencyTOP.
+ */
+#define CAUSE_FLAG_DISABLED 1
+/*
+ * This flag will not show and count causes as part of summary in
+ * "kstack window".
+ */
+#define CAUSE_FLAG_HIDE_IN_SUMMARY 2
+/*
+ * This is generated from D script (named cause), and is "special".
+ */
+#define CAUSE_FLAG_SPECIAL 4
+#define CAUSE_ALL_FLAGS 0xffffffff
+
+/*
+ * These functions collect statistics 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_cause_from_stack(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_cause_from_name(char *, int, int);
+extern int lt_table_append_trans(FILE *fp);
+extern void lt_table_deinit(void);
+
+/*
+ * These functions update statistic of all causes of latency, collected
+ * from DTrace.
+ */
+extern void lt_stat_update(pid_t, id_t, char *, char *, unsigned int,
+ 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 panes.
+ * Note: after a call to lt_stat_update_*, the old lists will become 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 the process list and the 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);
+
+/*
+ * These functions use ncurses to create console-based display.
+ */
+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 log file - useful for debugging 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 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 */
diff --git a/usr/src/cmd/latencytop/common/latencytop.trans b/usr/src/cmd/latencytop/common/latencytop.trans
new file mode 100644
index 0000000000..fbec48060b
--- /dev/null
+++ b/usr/src/cmd/latencytop/common/latencytop.trans
@@ -0,0 +1,44 @@
+#
+# 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 1.0 configuration
+#
+
+#
+# Format:
+# D <priority> <probe 1> <probe 2> ... <probe n> <Cause>
+# ; <special_command> <option value>
+# # comments
+#
+# D: We use dtrace probes to tag latency to different causes. The entry probe
+# pushes the cause and the priority to a thread local stack, and the return
+# probe pops them. These probes are transformed into:
+# TRANSLATE(entryprobe, returnprobe, cause, priority)
+# and are append to the embedded D script.
+#
+# <special_command>:
+# disable_category <category name> : do not count and display <category name>
+#
+
+# ZFS
+D 60 sdt:zfs:zil_commit_writer:zil-cw1 sdt:zfs:zil_commit_writer:zil-cw4 ZFS ZIL writer I/O
diff --git a/usr/src/cmd/latencytop/common/stat.c b/usr/src/cmd/latencytop/common/stat.c
new file mode 100644
index 0000000000..330173c302
--- /dev/null
+++ b/usr/src/cmd/latencytop/common/stat.c
@@ -0,0 +1,1050 @@
+/*
+ * 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 lt_grp_summary;
+ /* cause_id -> stat entry */
+ GHashTable *lt_grp_cidlist;
+} lt_datagroup_t;
+
+#define NGROUPS 2
+#define GROUP_CAUSE 0
+#define GROUP_SOBJ 1
+
+/*
+ * A data collection hierarchy involving three entities - system, process
+ * and thread. The hierarchic relationship is as follows :
+ *
+ * 1 system -> 1 or more processes -> 1 or more threads
+ */
+struct _lt_stat_collection {
+ lt_stat_level_t lt_sc_level;
+ unsigned int lt_sc_id;
+ char *lt_sc_name;
+ lt_datagroup_t lt_sc_groups[NGROUPS];
+ /*
+ * The following fields: lt_sc_parent, lt_sc_children and
+ * lt_sc_check_child_func maintain the tree structure.
+ */
+ lt_stat_collection_t *lt_sc_parent; /* Parent node */
+ GHashTable *lt_sc_children; /* pid/tid -> lt_stat_collection_t */
+ check_child_func_t lt_sc_check_child_func; /* Release dead children */
+};
+
+/* Internal data structure to back 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 lt_sl_entry_count;
+ lt_stat_entry_t **lt_sl_entries;
+ uint64_t lt_sl_gtotal;
+ free_list_func_t lt_sl_free_func;
+};
+
+/* Root of the collection hierarchy: system level statistics */
+static lt_stat_collection_t *stat_system = NULL;
+
+/*
+ * Data structure to hold synchronization objects.
+ * We don't use normal "cause table" because this needs to be cleared
+ * every time we refresh in order to make sure that stale synchronization
+ * objects don't consume memory.
+ */
+typedef struct {
+ int lt_soi_type;
+ unsigned long long lt_soi_addr;
+} lt_sobj_id_t;
+
+typedef struct {
+ lt_sobj_id_t lt_so_oid;
+ int lt_so_cause_id;
+ char lt_so_string[32]; /* Enough to hold "%s: 0x%llX" */
+} lt_sobj_t;
+
+static GHashTable *sobj_table = NULL;
+static int sobj_table_len = 0;
+
+/*
+ * Lower 32-bit of the address of synchronization objects is used to hash
+ * them.
+ */
+static guint
+sobj_id_hash(lt_sobj_id_t *id)
+{
+ g_assert(id != NULL);
+ return (id->lt_soi_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->lt_soi_type == b->lt_soi_type &&
+ a->lt_soi_addr == b->lt_soi_addr);
+}
+
+/*
+ * Look up the cause_id of a synchronization object.
+ * Note that this cause_id is only unique in GROUP_SOBJ, and changes after
+ * a 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->lt_soi_type < 0 || id->lt_soi_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->lt_so_cause_id = ++sobj_table_len;
+ (void) snprintf(ret->lt_so_string, sizeof (ret->lt_so_string),
+ "%s: 0x%llX", stype_str[id->lt_soi_type], id->lt_soi_addr);
+ ret->lt_so_oid.lt_soi_type = id->lt_soi_type;
+ ret->lt_so_oid.lt_soi_addr = id->lt_soi_addr;
+
+ g_hash_table_insert(sobj_table, &ret->lt_so_oid, ret);
+ }
+
+ return (ret);
+}
+
+/*
+ * Check if a process exists by using /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->lt_sc_id);
+ return (lt_file_exist(name) ? FALSE : TRUE);
+}
+
+/*
+ * Check if a thread exists by using /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->lt_sc_parent != NULL);
+ g_assert(stat->lt_sc_parent->lt_sc_level == LT_LEVEL_PROCESS);
+
+ (void) snprintf(name, PATH_MAX, "/proc/%u/lwp/%u",
+ stat->lt_sc_parent->lt_sc_id, stat->lt_sc_id);
+ 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->lt_sc_groups[i].lt_grp_cidlist != NULL) {
+ g_hash_table_destroy(stat->lt_sc_groups[i].
+ lt_grp_cidlist);
+ }
+ }
+
+ if (stat->lt_sc_children != NULL) {
+ g_hash_table_destroy(stat->lt_sc_children);
+ }
+
+ if (stat->lt_sc_name != NULL) {
+ free(stat->lt_sc_name);
+ }
+
+ free(stat);
+}
+
+/*
+ * Helper function to initialize 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->lt_sc_groups[i].lt_grp_cidlist != NULL) {
+ g_hash_table_destroy(stat->lt_sc_groups[i].
+ lt_grp_cidlist);
+ stat->lt_sc_groups[i].lt_grp_cidlist = NULL;
+ }
+
+ stat->lt_sc_groups[i].lt_grp_summary.lt_se_data.lt_s_count = 0;
+ stat->lt_sc_groups[i].lt_grp_summary.lt_se_data.lt_s_total = 0;
+ stat->lt_sc_groups[i].lt_grp_summary.lt_se_data.lt_s_max = 0;
+ }
+
+ if (stat->lt_sc_children != NULL) {
+ g_hash_table_foreach_remove(stat->lt_sc_children,
+ (GHRFunc)stat->lt_sc_check_child_func, NULL);
+ g_hash_table_foreach(stat->lt_sc_children,
+ (GHFunc)clear_stat, NULL);
+ }
+}
+
+/*
+ * Update a collection with the given value.
+ * Recursively update parents in the hierarchy until the root is reached.
+ */
+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->lt_sc_groups[group_to_use]);
+
+ if (group->lt_grp_cidlist != NULL) {
+ entry = (lt_stat_entry_t *)g_hash_table_lookup(
+ group->lt_grp_cidlist, LT_INT_TO_POINTER(cause_id));
+ } else {
+ group->lt_grp_cidlist = g_hash_table_new_full(
+ g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify)free);
+ lt_check_null(group->lt_grp_cidlist);
+ }
+
+ if (entry == NULL) {
+ entry = (lt_stat_entry_t *)lt_zalloc(sizeof (lt_stat_entry_t));
+ entry->lt_se_string = string;
+
+ switch (group_to_use) {
+ case GROUP_CAUSE:
+ entry->lt_se_type = STAT_CAUSE;
+ entry->lt_se_tsdata.lt_se_t_cause.lt_se_c_id = cause_id;
+ entry->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags =
+ lt_table_get_cause_flag(cause_id, CAUSE_ALL_FLAGS);
+
+ /* hide the first '#' */
+ if ((entry->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags
+ & CAUSE_FLAG_HIDE_IN_SUMMARY) != 0) {
+ ++entry->lt_se_string;
+ }
+
+ break;
+ case GROUP_SOBJ:
+ entry->lt_se_type = STAT_SOBJ;
+ entry->lt_se_tsdata.lt_se_t_sobj.lt_se_s_id = cause_id;
+ break;
+ }
+
+ g_hash_table_insert(group->lt_grp_cidlist,
+ LT_INT_TO_POINTER(cause_id), entry);
+ }
+
+ lt_update_stat_value(&entry->lt_se_data, type, value);
+
+ if (group_to_use == GROUP_SOBJ ||
+ (entry->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags &
+ CAUSE_FLAG_HIDE_IN_SUMMARY) == 0) {
+ lt_update_stat_value(&group->lt_grp_summary.lt_se_data, type,
+ value);
+ }
+
+ if (stat->lt_sc_parent != NULL) {
+ update_stat_entry(stat->lt_sc_parent, cause_id, type, value,
+ string, group_to_use);
+ }
+}
+
+/*
+ * Identify the cause of latency from the given stack trace.
+ * Return cause_id.
+ */
+static void
+find_cause(char *stack, int *cause_id, int *cause_priority)
+{
+ int cause_temp;
+ int prio_temp;
+ int cause = INVALID_CAUSE;
+ int priority = 0;
+ int found = 0;
+
+ g_assert(cause_id != NULL);
+ g_assert(cause_priority != NULL);
+
+ while (stack != NULL) {
+ char *sep;
+ sep = strchr(stack, ' ');
+
+ if (sep != NULL) {
+ *sep = '\0';
+ }
+
+ found = lt_table_cause_from_stack(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;
+ }
+ }
+
+ *cause_id = cause;
+ *cause_priority = priority;
+}
+
+/*
+ * Create a new collection and 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->lt_sc_level = level;
+ ret->lt_sc_check_child_func = check_child_func;
+ ret->lt_sc_id = id;
+ ret->lt_sc_name = name;
+
+ for (i = 0; i < NGROUPS; ++i) {
+ ret->lt_sc_groups[i].lt_grp_summary.lt_se_string =
+ (const char *)name;
+ }
+
+ if (parent != NULL) {
+ ret->lt_sc_parent = parent;
+
+ if (parent->lt_sc_children == NULL) {
+ parent->lt_sc_children = g_hash_table_new_full(
+ g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify)free_stat);
+ lt_check_null(parent->lt_sc_children);
+ }
+
+ g_hash_table_insert(parent->lt_sc_children,
+ LT_INT_TO_POINTER((int)id), ret);
+ }
+
+ return (ret);
+}
+
+/*
+ * Find the "leaf" in the collection hierarchy, using the 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->lt_sc_children != NULL) {
+ stat_p = (lt_stat_collection_t *)
+ g_hash_table_lookup(stat_system->lt_sc_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 could not get the executable name of the
+ * process; the 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->lt_sc_children != NULL) {
+ stat_t = (lt_stat_collection_t *)
+ g_hash_table_lookup(stat_p->lt_sc_children,
+ LT_INT_TO_POINTER(tid));
+ }
+
+ if (stat_t == NULL) {
+ const int tname_size = 16; /* Enough for "Thread %d" */
+ char *tname;
+
+ tname = (char *)lt_zalloc(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 statistics with the given cause_id. Values 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)) {
+ /* Ignore this cause */
+ return;
+ }
+
+ stat_t = get_stat_c(pid, tid);
+
+ if (stat_t == NULL) {
+ /* 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 statistics with the given stack trace.
+ * The stack trace is mapped to a cause and lt_stat_update_cause() is called
+ * to update statistics.
+ */
+void
+lt_stat_update(pid_t pid, id_t tid, char *stack, char *tag,
+ unsigned int tag_priority, lt_stat_type_t type, uint64_t value)
+{
+ int tag_cause_id = INVALID_CAUSE;
+ int stack_cause_id = INVALID_CAUSE;
+ int cause_id = INVALID_CAUSE;
+ int stack_priority = 0;
+
+ if (value == 0) {
+ return;
+ }
+
+ find_cause(stack, &stack_cause_id, &stack_priority);
+
+ if (tag_priority != 0) {
+ tag_cause_id = lt_table_cause_from_name(tag, 0, 0);
+
+ if (tag_cause_id == INVALID_CAUSE) {
+ /* This must be a syscall tag */
+ char tmp[64];
+ (void) snprintf(tmp, sizeof (tmp), "Syscall: %s", tag);
+ tag_cause_id = lt_table_cause_from_name(tmp, 1, 0);
+ }
+ }
+
+ cause_id = (tag_priority > stack_priority) ? tag_cause_id :
+ stack_cause_id;
+
+ if (cause_id == INVALID_CAUSE) {
+ /*
+ * We got an unmapped stack. Set SPECIAL flag to display it
+ * in pane 2. This makes it easier to find the cause.
+ */
+ cause_id = lt_table_cause_from_name(stack, 1,
+ CAUSE_FLAG_SPECIAL);
+ 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 out all statistics, but keep the data structures in memory
+ * to be used to hold new data immediately following.
+ */
+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 for 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. Return handle to a stat_list.
+ * Use pid = PID_SYS_GLOBAL to get global top list.
+ * Call lt_stat_list_free after use to clean up.
+ */
+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->lt_sc_children != NULL) {
+ /* Find process entry first */
+ stat_c = (lt_stat_collection_t *)g_hash_table_lookup(
+ stat_system->lt_sc_children, LT_INT_TO_POINTER(pid));
+
+ if (level == LT_LEVEL_THREAD) {
+ /*
+ * If thread entry is requested, find it based on
+ * process entry.
+ */
+ if (stat_c != NULL && stat_c->lt_sc_children != NULL) {
+ stat_c = (lt_stat_collection_t *)
+ g_hash_table_lookup(stat_c->lt_sc_children,
+ LT_INT_TO_POINTER(tid));
+ } else {
+ /*
+ * Thread entry was not found; set it to NULL,
+ * so that we can return empty list later.
+ */
+ stat_c = NULL;
+ }
+ }
+ }
+
+ ret = (lt_stat_list_t *)lt_zalloc(sizeof (lt_stat_list_t));
+ ret->lt_sl_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->lt_sc_groups[GROUP_SOBJ]);
+ } else {
+ group = &(stat_c->lt_sc_groups[GROUP_CAUSE]);
+ }
+
+ if (group->lt_grp_cidlist == NULL) {
+ /* Empty list */
+ return (ret);
+ }
+
+ ret->lt_sl_gtotal = group->lt_grp_summary.lt_se_data.lt_s_total;
+
+ list = g_hash_table_get_values(group->lt_grp_cidlist);
+
+ 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->lt_se_type == STAT_CAUSE &&
+ (data->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags &
+ CAUSE_FLAG_HIDE_IN_SUMMARY) != 0) {
+ continue;
+ }
+
+ if (list_type == LT_LIST_SPECIALS &&
+ data->lt_se_type == STAT_CAUSE &&
+ (data->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags &
+ CAUSE_FLAG_SPECIAL) == 0) {
+ continue;
+ }
+
+ if (data->lt_se_data.lt_s_count == 0) {
+ break;
+ }
+
+ ret->lt_sl_entries[ret->lt_sl_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->lt_sl_free_func != NULL) {
+ list->lt_sl_free_func(list);
+ }
+
+ if (list->lt_sl_entries != NULL) {
+ free(list->lt_sl_entries);
+ }
+
+ free(list);
+}
+
+/*
+ * Check if the given list contains the given item.
+ */
+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->lt_sl_entry_count ||
+ list->lt_sl_entries[i] == NULL) {
+ return (0);
+ }
+
+ return (1);
+}
+
+/*
+ * Get display name of the given item i in the given 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->lt_sl_entry_count ||
+ list->lt_sl_entries[i] == NULL) {
+ return (NULL);
+ }
+
+ g_assert(list->lt_sl_entries[i]->lt_se_string != NULL);
+
+ return (list->lt_sl_entries[i]->lt_se_string);
+}
+
+/*
+ * Get maximum value of the given item i in the given 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->lt_sl_entry_count ||
+ list->lt_sl_entries[i] == NULL) {
+ return (0);
+ }
+
+ return (list->lt_sl_entries[i]->lt_se_data.lt_s_max);
+}
+
+/*
+ * Get total value of the given item i in the given 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->lt_sl_entry_count ||
+ list->lt_sl_entries[i] == NULL) {
+ return (0);
+ }
+
+ return (list->lt_sl_entries[i]->lt_se_data.lt_s_total);
+}
+
+/*
+ * Get count value of the given item i in the given 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->lt_sl_entry_count ||
+ list->lt_sl_entries[i] == NULL) {
+ return (0);
+ }
+
+ return (list->lt_sl_entries[i]->lt_se_data.lt_s_count);
+}
+
+/*
+ * Get grand total of all latency in the list.
+ */
+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->lt_sl_gtotal);
+}
+
+/*
+ * ============================================================================
+ * Process and thread list.
+ * They share a lot of the static variables that are used for keeping
+ * statistics, hence they are located in this 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->lt_sc_id - b->lt_sc_id));
+}
+
+/*
+ * Get the current list of processes. Call lt_stat_proc_list_free after use
+ * to clean up.
+ */
+static int
+plist_create(pid_t ** list)
+{
+ GList *pid_list, *walk;
+ int ret, count;
+
+ ret = g_hash_table_size(stat_system->lt_sc_children);
+ *list = (pid_t *)lt_malloc(sizeof (pid_t) * ret);
+
+ pid_list = g_hash_table_get_values(stat_system->lt_sc_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))->lt_sc_id;
+ }
+
+ g_list_free(pid_list);
+
+ return (ret);
+}
+
+/*
+ * Count the no. of threads currently present in a process.
+ * Only thread that have SSLEEP are counted.
+ */
+/* ARGSUSED */
+static void
+count_threads(gpointer key, lt_stat_collection_t *stat_c, int *ret)
+{
+ g_assert(ret != NULL);
+
+ if (stat_c->lt_sc_children != NULL) {
+ *ret += g_hash_table_size(stat_c->lt_sc_children);
+ }
+}
+
+/*
+ * Get current list of processes and threads.
+ * Call lt_stat_proc_list_free after use to clean up.
+ */
+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->lt_sc_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->lt_sc_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->lt_sc_children == NULL) {
+ continue;
+ }
+
+ tid_list = g_hash_table_get_values(stat_p->lt_sc_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->lt_sc_id;
+ (*tlist)[count] = (int)stat_t->lt_sc_id;
+
+ ++count;
+ }
+ g_list_free(tid_list);
+ }
+
+ g_list_free(pid_list);
+ g_assert(count == ret);
+
+ return (ret);
+}
+
+/*
+ * List of processes that are tracked by LatencyTOP.
+ */
+int
+lt_stat_proc_list_create(pid_t ** plist, id_t ** tlist)
+{
+ if (plist == NULL) {
+ return (-1);
+ }
+
+ if (stat_system == NULL || stat_system->lt_sc_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 executable name of the given process (ID).
+ */
+const char *
+lt_stat_proc_get_name(pid_t pid)
+{
+ lt_stat_collection_t *stat_p = NULL;
+
+ if (stat_system == NULL || stat_system->lt_sc_children == NULL) {
+ return (NULL);
+ }
+
+ stat_p = (lt_stat_collection_t *)g_hash_table_lookup(
+ stat_system->lt_sc_children, LT_INT_TO_POINTER(pid));
+
+ if (stat_p != NULL) {
+ return (stat_p->lt_sc_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->lt_sc_children == NULL) {
+ return (0);
+ }
+
+ stat_p = (lt_stat_collection_t *)g_hash_table_lookup(
+ stat_system->lt_sc_children, LT_INT_TO_POINTER(pid));
+
+ if (stat_p != NULL) {
+ return (g_hash_table_size(stat_p->lt_sc_children));
+ } else {
+ return (0);
+ }
+}
+
+/*
+ * Update 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.lt_soi_type = stype;
+ id.lt_soi_addr = wchan;
+ sobj = lookup_sobj(&id);
+
+ if (sobj == NULL) {
+ return;
+ }
+
+ cause_id = sobj->lt_so_cause_id;
+
+ update_stat_entry(stat_t, cause_id, type, value,
+ sobj->lt_so_string, GROUP_SOBJ);
+}
diff --git a/usr/src/cmd/latencytop/common/table.c b/usr/src/cmd/latencytop/common/table.c
new file mode 100644
index 0000000000..fcf4bb7a4d
--- /dev/null
+++ b/usr/src/cmd/latencytop/common/table.c
@@ -0,0 +1,840 @@
+/*
+ * 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 lt_c_cause_id;
+ int lt_c_flags;
+ char *lt_c_name;
+} lt_cause_t;
+
+/*
+ * Structure that represents a matched cause.
+ */
+typedef struct {
+ int lt_mt_priority;
+ int lt_mt_cause_id;
+} lt_match_t;
+
+/* All lt_cause_t that are created. */
+static GHashTable *cause_lookup = NULL;
+static GPtrArray *causes_array = NULL;
+static int causes_array_len = 0;
+
+/*
+ * This hash table maps a symbol to a cause.
+ * key is of type "char *" and value is of type "lt_match_t *".
+ */
+static GHashTable *symbol_lookup_table = NULL;
+
+/*
+ * The dtrace translation rules we get from the script
+ */
+char *dtrans = NULL;
+
+/*
+ * These structures are only used inside .trans parser.
+ */
+typedef struct {
+ int lt_dm_priority;
+ char *lt_dm_macro;
+} lt_dmacro_t;
+
+typedef struct {
+ GSequence *lt_pr_cmd_disable;
+ GHashTable *lt_pr_dmacro;
+} lt_parser_t;
+
+/* ARGSUSED */
+static void
+free_cause(lt_cause_t *cause, void *user)
+{
+ g_assert(cause != NULL && cause->lt_c_name != NULL);
+
+ free(cause->lt_c_name);
+ free(cause);
+}
+
+static void
+free_dmacro(lt_dmacro_t *d)
+{
+ g_assert(d->lt_dm_macro != NULL);
+ free(d->lt_dm_macro);
+ free(d);
+}
+
+/*
+ * Add a cause.
+ */
+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->lt_c_flags = flags;
+ entry->lt_c_name = name;
+ entry->lt_c_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->lt_c_flags |= CAUSE_FLAG_DISABLED;
+ }
+}
+
+/*
+ * Helper functions that reads a line from a character 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);
+}
+
+/*
+ * Parse special command from configuration file. Special command
+ * has the following format :
+
+ * disable_cause <cause name>
+ */
+static int
+parse_config_cmd(char *begin, lt_parser_t *parser)
+{
+ char *tmp;
+ char old_chr = 0;
+
+ /*
+ * disable_cause FSFlush Daemon
+ * ^
+ */
+ if (*begin == '\0') {
+ return (0);
+ }
+
+ 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);
+ return (-1);
+ }
+
+ begin = tmp+1;
+ while (isspace(*begin)) {
+ ++begin;
+ }
+
+ g_sequence_append(parser->lt_pr_cmd_disable,
+ lt_strdup(begin));
+ } else {
+ *tmp = old_chr;
+ lt_display_error(
+ "Unknown command: %s\n", begin);
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Parse symbol translation from configuration file. Symbol translation
+ * has the following format :
+ *
+ * <priority> <symbol name> <cause>
+ *
+ * Finally check if that cause has already been mapped.
+ */
+static int
+parse_sym_trans(char *begin)
+{
+ int priority = 0;
+ char *match;
+ char *match_dup;
+ char *cause_str;
+ lt_cause_t *cause;
+ lt_match_t *match_entry;
+ char *tmp;
+
+ /*
+ * 10 genunix`pread Syscall pread
+ * ^
+ */
+ priority = strtol(begin, &tmp, 10);
+
+ if (tmp == begin || priority == 0) {
+ return (-1);
+ }
+
+ begin = tmp;
+
+ /*
+ * 10 genunix`pread Syscall pread
+ * --^
+ */
+
+ if (!isspace(*begin)) {
+ /* At least one space char after <priority> */
+ return (-1);
+ }
+
+ while (isspace(*begin)) {
+ ++begin;
+ }
+
+ if (*begin == 0) {
+ return (-1);
+ }
+
+ /*
+ * 10 genunix`pread Syscall pread
+ * -----^
+ */
+ for (tmp = begin;
+ *tmp != '\0' && !isspace(*tmp);
+ ++tmp) {
+ }
+
+ if (*tmp == '\0') {
+ return (-1);
+ }
+
+ *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->lt_mt_priority, priority)) {
+ /* We already have a higher entry. Ignore this. */
+ return (0);
+ }
+
+ begin = tmp + 1;
+
+ /*
+ * 10 genunix`pread Syscall pread
+ * -------------------------------------^
+ */
+ while (isspace(*begin)) {
+ ++begin;
+ }
+
+ if (*begin == 0) {
+ return (-1);
+ }
+
+ 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));
+ match_entry->lt_mt_priority = priority;
+ match_entry->lt_mt_cause_id = cause->lt_c_cause_id;
+ match_dup = lt_strdup(match);
+
+ g_hash_table_insert(symbol_lookup_table, match_dup,
+ match_entry);
+
+ return (0);
+}
+
+/*
+ * Parse D macro. D macros have the following format :
+ *
+ * <priority> <entry probe> <return probe> <cause>
+ *
+ * Finally check if that cause has already been mapped.
+ */
+static int
+parse_dmacro(char *begin, lt_parser_t *parser)
+{
+ int priority = 0;
+ char *entryprobe;
+ char *returnprobe;
+ char *cause_str;
+ char buf[512];
+ char probepair[512];
+ char *tmp = NULL;
+ lt_cause_t *cause;
+ lt_dmacro_t *dmacro;
+
+ /*
+ * 10 syscall::pread:entry syscall::pread:return Syscall pread
+ * ^
+ */
+ priority = strtol(begin, &tmp, 10);
+
+ if (tmp == begin || priority == 0) {
+ return (-1);
+ }
+
+ begin = tmp;
+
+ /*
+ * 10 syscall::pread:entry syscall::pread:return Syscall pread
+ * --^
+ */
+ while (isspace(*begin)) {
+ ++begin;
+ }
+
+ if (*begin == 0) {
+ return (-1);
+ }
+
+ /*
+ * 10 syscall::pread:entry syscall::pread:return Syscall pread
+ * -----^
+ */
+ for (tmp = begin;
+ *tmp != '\0' && !isspace(*tmp);
+ ++tmp) {
+ }
+
+ if (*tmp == '\0') {
+ return (-1);
+ }
+
+ *tmp = '\0';
+ entryprobe = begin;
+ begin = tmp + 1;
+
+ while (isspace(*begin)) {
+ ++begin;
+ }
+
+ /*
+ * 10 syscall::pread:entry syscall::pread:return Syscall pread
+ * -----------------------------^
+ */
+ for (tmp = begin;
+ *tmp != '\0' && !isspace(*tmp);
+ ++tmp) {
+ }
+
+ if (*tmp == '\0') {
+ return (-1);
+ }
+
+ *tmp = '\0';
+ returnprobe = begin;
+ begin = tmp + 1;
+
+ while (isspace(*begin)) {
+ ++begin;
+ }
+
+ /*
+ * 10 syscall::pread:entry syscall::pread:return Syscall pread
+ * -----------------------------------------------------^
+ */
+ if (*begin == 0) {
+ return (-1);
+ }
+
+ cause_str = begin;
+
+ dmacro = NULL;
+
+ /* 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);
+ }
+
+ (void) snprintf(buf, sizeof (buf), "\nTRANSLATE(%s, %s, \"%s\", %d)\n",
+ entryprobe, returnprobe, cause_str, priority);
+
+ (void) snprintf(probepair, sizeof (probepair), "%s %s", entryprobe,
+ returnprobe);
+
+ g_assert(cause != NULL);
+ g_assert(parser->lt_pr_dmacro != NULL);
+
+ dmacro = g_hash_table_lookup(parser->lt_pr_dmacro, probepair);
+
+ if (dmacro == NULL) {
+ dmacro = (lt_dmacro_t *)lt_malloc(sizeof (lt_dmacro_t));
+ dmacro->lt_dm_priority = priority;
+ dmacro->lt_dm_macro = lt_strdup(buf);
+ g_hash_table_insert(parser->lt_pr_dmacro, lt_strdup(probepair),
+ dmacro);
+ } else if (dmacro->lt_dm_priority < priority) {
+ free(dmacro->lt_dm_macro);
+ dmacro->lt_dm_priority = priority;
+ dmacro->lt_dm_macro = lt_strdup(buf);
+ }
+
+ return (0);
+}
+
+/*
+ * Helper function to collect TRANSLATE() macros.
+ */
+/* ARGSUSED */
+static void
+genscript(void *key, lt_dmacro_t *dmacro, GString *str)
+{
+ g_string_append(str, dmacro->lt_dm_macro);
+}
+
+/*
+ * Main logic that parses translation rules one line at a time,
+ * and creates a lookup table from it. The syntax for the translation
+ * is as follows :
+ *
+ * # <--- comment
+ * D <D macro rule> <--- D macro
+ * S <Symbol translation> <--- Symbols
+ * disable_cause <cause> <--- special command
+ */
+static int
+parse_config(const char *work, int work_len)
+{
+ char line[256];
+ int len;
+ char *begin, *end;
+ int current = 0;
+ lt_parser_t parser;
+ int ret = 0;
+ char flag;
+ GString *script;
+
+ cause_lookup = g_hash_table_new(g_str_hash, g_str_equal);
+ lt_check_null(cause_lookup);
+
+ parser.lt_pr_cmd_disable = g_sequence_new((GDestroyNotify)free);
+ lt_check_null(parser.lt_pr_cmd_disable);
+
+ parser.lt_pr_dmacro = g_hash_table_new_full(g_str_hash,
+ g_str_equal, (GDestroyNotify)free, (GDestroyNotify)free_dmacro);
+ lt_check_null(parser.lt_pr_dmacro);
+
+ while (read_line_from_mem(work, work_len, line, sizeof (line),
+ &current)) {
+ 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') {
+ /* Ignore empty line */
+ continue;
+ }
+
+ /* Delete trailing spaces. */
+ end = begin + strlen(begin) - 1;
+
+ while (isspace(*end)) {
+ --end;
+ }
+
+ end[1] = '\0';
+
+ flag = *begin;
+ ++begin;
+
+ switch (flag) {
+ case '#':
+ ret = 0;
+ break;
+ case ';':
+ ret = parse_config_cmd(begin, &parser);
+ break;
+ case 'D':
+ case 'd':
+ if (!isspace(*begin)) {
+ lt_display_error(
+ "No space after flag char: %s\n", line);
+ }
+ while (isspace(*begin)) {
+ ++begin;
+ }
+ ret = parse_dmacro(begin, &parser);
+ break;
+ case 'S':
+ case 's':
+ if (!isspace(*begin)) {
+ lt_display_error(
+ "No space after flag char: %s\n", line);
+ }
+ while (isspace(*begin)) {
+ ++begin;
+ }
+ ret = parse_sym_trans(begin);
+ break;
+ default:
+ ret = -1;
+ break;
+ }
+
+ if (ret != 0) {
+ lt_display_error(
+ "Invalid configuration line: %s\n", line);
+ goto err;
+ }
+ }
+
+ script = g_string_new(NULL);
+ g_hash_table_foreach(parser.lt_pr_dmacro, (GHFunc)genscript, script);
+ dtrans = g_string_free(script, FALSE);
+
+ if (dtrans != NULL && strlen(dtrans) == 0) {
+ free(dtrans);
+ dtrans = NULL;
+ }
+
+ g_sequence_foreach(parser.lt_pr_cmd_disable, (GFunc)disable_cause,
+ cause_lookup);
+ g_sequence_free(parser.lt_pr_cmd_disable);
+
+ return (0);
+
+err:
+ g_sequence_free(parser.lt_pr_cmd_disable);
+ g_hash_table_destroy(parser.lt_pr_dmacro);
+ return (-1);
+
+}
+
+/*
+ * Init function, called when latencytop starts.
+ * It loads translation rules from the configuration file. The configuration
+ * file defines some causes and symbols that match those 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 = &latencytop_trans_start;
+ work_len = (int)(&latencytop_trans_end - &latencytop_trans_start);
+#endif
+
+ if (g_config.lt_cfg_config_name != NULL) {
+ FILE *fp;
+ fp = fopen(g_config.lt_cfg_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);
+
+ /* A zero-byte translation is valid */
+ if (config_loaded_len != 0 &&
+ 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.lt_cfg_config_name);
+
+ work = config_loaded;
+ work_len = config_loaded_len;
+ }
+
+ lt_table_deinit();
+ causes_array = g_ptr_array_new();
+ lt_check_null(causes_array);
+
+ /* 0 is not used, but it is kept as a place for bugs etc. */
+ cause = new_cause(lt_strdup("Nothing"), CAUSE_FLAG_DISABLED);
+ g_assert(cause->lt_c_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 (work_len != 0 && parse_config(work, work_len) != 0) {
+ return (-1);
+ }
+
+ if (config_loaded != NULL) {
+ free(config_loaded);
+ }
+
+ return (0);
+}
+
+/*
+ * Some causes, such as "lock spinning", do not have stack trace. Names
+ * of such causes are explicitly specified in the D script.
+ * This function resolves such causes and dynamically adds them
+ * to the global tables when they are found first. If auto_create is set
+ * to TRUE, the entry will be created if it is not found.
+ * Return cause_id of the cause.
+ */
+int
+lt_table_cause_from_name(char *name, int auto_create, int flags)
+{
+ lt_cause_t *cause = NULL;
+
+ if (cause_lookup == NULL) {
+ cause_lookup = g_hash_table_new(g_str_hash, g_str_equal);
+ lt_check_null(cause_lookup);
+ } else {
+ cause = (lt_cause_t *)
+ g_hash_table_lookup(cause_lookup, name);
+ }
+
+ if (cause == NULL && auto_create) {
+ char *cause_dup;
+
+ if (name[0] == '#') {
+ flags |= CAUSE_FLAG_HIDE_IN_SUMMARY;
+ }
+
+ cause_dup = lt_strdup(name);
+ cause = new_cause(cause_dup, flags);
+ g_hash_table_insert(cause_lookup, cause_dup, cause);
+ }
+
+ return (cause == NULL ? INVALID_CAUSE : cause->lt_c_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.
+ * If cause is found return 1, otherwise return 0.
+ */
+int
+lt_table_cause_from_stack(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->lt_mt_cause_id;
+ *priority = match->lt_mt_priority;
+ return (1);
+ }
+}
+
+/*
+ * Get the display name of a cause. cause_id must be valid,
+ * it is usually returned from lt_table_cause_from_stack() or
+ * lt_table_cause_from_name().
+ */
+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->lt_c_name);
+ }
+}
+
+/*
+ * Check cause flag.
+ * If CAUSE_ALL_FLAGS is passed in, all flags are returned.
+ */
+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->lt_c_flags & flag);
+ }
+}
+
+/*
+ * Append macros to D script, if any.
+ */
+int
+lt_table_append_trans(FILE *fp)
+{
+ if (dtrans != NULL) {
+ if (fwrite(dtrans, strlen(dtrans), 1, fp) != 1) {
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Clean up function.
+ * Free the resources used for symbol table (symbols, causes etc.).
+ */
+void
+lt_table_deinit(void)
+{
+ if (symbol_lookup_table != NULL) {
+ g_hash_table_destroy(symbol_lookup_table);
+ symbol_lookup_table = NULL;
+ }
+
+ if (cause_lookup != NULL) {
+ g_hash_table_destroy(cause_lookup);
+ cause_lookup = 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;
+ }
+
+ if (dtrans != NULL) {
+ g_free(dtrans);
+ dtrans = NULL;
+ }
+}
diff --git a/usr/src/cmd/latencytop/common/util.c b/usr/src/cmd/latencytop/common/util.c
new file mode 100644
index 0000000000..0d4173f196
--- /dev/null
+++ b/usr/src/cmd/latencytop/common/util.c
@@ -0,0 +1,312 @@
+/*
+ * 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 exits 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);
+}
+
+/*
+ * Check if we are out of memory.
+ */
+void
+lt_check_null(void *p)
+{
+ if (p == NULL) {
+ (void) fprintf(stderr, "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.
+ * It is named "zalloc" because its signature is different from
+ * calloc() in stdlib.
+ */
+void *
+lt_zalloc(size_t size)
+{
+ void *ret = calloc(size, 1);
+
+ lt_check_null(ret);
+
+ 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 the process's executable name and arguments 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);
+}
+
+/*
+ * Helper 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->lt_s_count += value;
+ break;
+ case LT_STAT_SUM:
+ entry->lt_s_total += value;
+ break;
+ case LT_STAT_MAX:
+ if (value > entry->lt_s_max) {
+ entry->lt_s_max = value;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Helper function to sort on total.
+ */
+int
+lt_sort_by_total_desc(lt_stat_entry_t *a, lt_stat_entry_t *b)
+{
+ g_assert(a != NULL && b != NULL);
+ /*
+ * lt_s_total is of type int64_t, so we can't simply return
+ * (b->lt_se_data.lt_s_total - a->lt_se_data.lt_s_total).
+ */
+ if (b->lt_se_data.lt_s_total > a->lt_se_data.lt_s_total) {
+ return (1);
+ } else if (b->lt_se_data.lt_s_total < a->lt_se_data.lt_s_total) {
+ return (-1);
+ } else {
+ return (0);
+ }
+}
+
+/*
+ * Helper function to sort on 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->lt_se_data.lt_s_max > a->lt_se_data.lt_s_max) {
+ return (1);
+ } else if (b->lt_se_data.lt_s_max < a->lt_se_data.lt_s_max) {
+ return (-1);
+ } else {
+ return (0);
+ }
+}
+
+/*
+ * Helper function to sort on 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->lt_se_data.lt_s_count > a->lt_se_data.lt_s_count) {
+ return (1);
+ } else if (b->lt_se_data.lt_s_count < a->lt_se_data.lt_s_count) {
+ return (-1);
+ } else {
+ return (0);
+ }
+}
+
+/*
+ * Helper function to sort on 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->lt_se_data.lt_s_total / a->lt_se_data.lt_s_count;
+ avg_b = (double)b->lt_se_data.lt_s_total / b->lt_se_data.lt_s_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);
+}
+
+/*
+ * Close the pipe used in signal handler.
+ */
+void
+lt_gpipe_deinit(void)
+{
+ (void) close(signal_pipe[0]);
+ (void) close(signal_pipe[1]);
+}
+
+/*
+ * Break early from the main loop.
+ */
+void
+lt_gpipe_break(const char *ch)
+{
+ (void) write(signal_pipe[1], ch, 1);
+}
+
+int
+lt_gpipe_readfd(void)
+{
+ return (signal_pipe[0]);
+}
+
+/*
+ * Check if the given file exists.
+ */
+int
+lt_file_exist(const char *name)
+{
+ struct stat64 st;
+
+ if (stat64(name, &st) == 0) {
+ return (1);
+ } else {
+ return (0);
+ }
+}
diff --git a/usr/src/cmd/latencytop/i386/Makefile b/usr/src/cmd/latencytop/i386/Makefile
new file mode 100644
index 0000000000..abe250fe37
--- /dev/null
+++ b/usr/src/cmd/latencytop/i386/Makefile
@@ -0,0 +1,29 @@
+#
+# 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
+
+WRAPOPT =
+
+install: all $(ROOTPROG32)
diff --git a/usr/src/cmd/latencytop/sparcv9/Makefile b/usr/src/cmd/latencytop/sparcv9/Makefile
new file mode 100644
index 0000000000..79b3cceb54
--- /dev/null
+++ b/usr/src/cmd/latencytop/sparcv9/Makefile
@@ -0,0 +1,30 @@
+#
+# 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
+
+WRAPOPT = -64
+
+install: all $(ROOTPROG64)
diff --git a/usr/src/pkgdefs/Makefile b/usr/src/pkgdefs/Makefile
index 517cbd3dd9..6acc8f4491 100644
--- a/usr/src/pkgdefs/Makefile
+++ b/usr/src/pkgdefs/Makefile
@@ -323,6 +323,7 @@ COMMON_SUBDIRS= \
SUNWixgbe \
SUNWkrbr \
SUNWkrbu \
+ SUNWlatencytop \
SUNWldskint \
SUNWlibsasl \
SUNWllc \
diff --git a/usr/src/pkgdefs/SUNWlatencytop/Makefile b/usr/src/pkgdefs/SUNWlatencytop/Makefile
new file mode 100644
index 0000000000..44b2a96310
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWlatencytop/Makefile
@@ -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
diff --git a/usr/src/pkgdefs/SUNWlatencytop/depend b/usr/src/pkgdefs/SUNWlatencytop/depend
new file mode 100644
index 0000000000..02903f6fb9
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWlatencytop/depend
@@ -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
diff --git a/usr/src/pkgdefs/SUNWlatencytop/pkginfo.tmpl b/usr/src/pkgdefs/SUNWlatencytop/pkginfo.tmpl
new file mode 100644
index 0000000000..03b36c4bac
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWlatencytop/pkginfo.tmpl
@@ -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"
diff --git a/usr/src/pkgdefs/SUNWlatencytop/prototype_com b/usr/src/pkgdefs/SUNWlatencytop/prototype_com
new file mode 100644
index 0000000000..39b3623df6
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWlatencytop/prototype_com
@@ -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
diff --git a/usr/src/pkgdefs/SUNWlatencytop/prototype_i386 b/usr/src/pkgdefs/SUNWlatencytop/prototype_i386
new file mode 100644
index 0000000000..16c84a7b25
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWlatencytop/prototype_i386
@@ -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
diff --git a/usr/src/pkgdefs/SUNWlatencytop/prototype_sparc b/usr/src/pkgdefs/SUNWlatencytop/prototype_sparc
new file mode 100644
index 0000000000..1604553ba7
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWlatencytop/prototype_sparc
@@ -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