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