--- old/usr/src/cmd/latencytop/common/stat.c	Thu Apr 11 14:30:36 2024
+++ /dev/null	Thu Apr 11 08:00:00 2024
@@ -1,1050 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License (the "License").
- * You may not use this file except in compliance with the License.
- *
- * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
- * or http://www.opensolaris.org/os/licensing.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information: Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- */
-/*
- * Copyright (c) 2008-2009, Intel Corporation.
- * All Rights Reserved.
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <memory.h>
-#include <string.h>
-#include <limits.h>
-#include <sys/stat.h>
-
-#include "latencytop.h"
-
-/* Statistics for each process/thread. */
-typedef struct _lt_stat_collection lt_stat_collection_t;
-typedef gboolean (*check_child_func_t) (gpointer key,
-    lt_stat_collection_t *stat, void *user);
-
-typedef struct {
-	lt_stat_entry_t lt_grp_summary;
-	/* cause_id -> stat entry */
-	GHashTable *lt_grp_cidlist;
-} lt_datagroup_t;
-
-#define	NGROUPS			2
-#define	GROUP_CAUSE		0
-#define	GROUP_SOBJ		1
-
-/*
- * A data collection hierarchy involving three entities - system, process
- * and thread. The hierarchic relationship is as follows :
- *
- *		1 system -> 1 or more processes -> 1 or more threads
- */
-struct _lt_stat_collection {
-	lt_stat_level_t lt_sc_level;
-	unsigned int lt_sc_id;
-	char *lt_sc_name;
-	lt_datagroup_t lt_sc_groups[NGROUPS];
-	/*
-	 * The following fields: lt_sc_parent, lt_sc_children and
-	 * lt_sc_check_child_func maintain the tree structure.
-	 */
-	lt_stat_collection_t *lt_sc_parent;		/* Parent node */
-	GHashTable *lt_sc_children;	/* pid/tid -> lt_stat_collection_t */
-	check_child_func_t lt_sc_check_child_func; /* Release dead children */
-};
-
-/* Internal data structure to back up a stat_list */
-typedef struct _lt_stat_list lt_stat_list_t;
-typedef void (*free_list_func_t)(lt_stat_list_t *);
-struct _lt_stat_list {
-	int lt_sl_entry_count;
-	lt_stat_entry_t **lt_sl_entries;
-	uint64_t lt_sl_gtotal;
-	free_list_func_t lt_sl_free_func;
-};
-
-/* Root of the collection hierarchy: system level statistics */
-static lt_stat_collection_t *stat_system = NULL;
-
-/*
- * Data structure to hold synchronization objects.
- * We don't use normal "cause table" because this needs to be cleared
- * every time we refresh in order to make sure that stale synchronization
- * objects don't consume memory.
- */
-typedef struct {
-	int lt_soi_type;
-	unsigned long long lt_soi_addr;
-} lt_sobj_id_t;
-
-typedef struct {
-	lt_sobj_id_t lt_so_oid;
-	int lt_so_cause_id;
-	char lt_so_string[32];	/* Enough to hold "%s: 0x%llX" */
-} lt_sobj_t;
-
-static GHashTable *sobj_table = NULL;
-static int sobj_table_len = 0;
-
-/*
- * Lower 32-bit of the address of synchronization objects is used to hash
- * them.
- */
-static guint
-sobj_id_hash(lt_sobj_id_t *id)
-{
-	g_assert(id != NULL);
-	return (id->lt_soi_addr & 0xFFFFFFFF);
-}
-
-/*
- * Test if two synchronization objects are the same.
- */
-static gboolean
-sobj_id_equal(lt_sobj_id_t *a, lt_sobj_id_t *b)
-{
-	g_assert(a != NULL && b != NULL);
-	return (a->lt_soi_type == b->lt_soi_type &&
-	    a->lt_soi_addr == b->lt_soi_addr);
-}
-
-/*
- * Look up the cause_id of a synchronization object.
- * Note that this cause_id is only unique in GROUP_SOBJ, and changes after
- * a refresh.
- */
-static lt_sobj_t *
-lookup_sobj(lt_sobj_id_t *id)
-{
-	const char *stype_str[] = {
-		"None",
-		"Mutex",
-		"RWLock",
-		"CV",
-		"Sema",
-		"User",
-		"User_PI",
-		"Shuttle"
-	};
-	const int stype_str_len =
-	    sizeof (stype_str) / sizeof (stype_str[0]);
-	lt_sobj_t *ret = NULL;
-	g_assert(id != NULL);
-
-	if (id->lt_soi_type < 0 || id->lt_soi_type >= stype_str_len) {
-		return (NULL);
-	}
-
-	if (sobj_table != NULL) {
-		ret = (lt_sobj_t *)g_hash_table_lookup(sobj_table, id);
-	} else {
-		sobj_table = g_hash_table_new_full(
-		    (GHashFunc)sobj_id_hash, (GEqualFunc)sobj_id_equal,
-		    NULL, (GDestroyNotify)free);
-		lt_check_null(sobj_table);
-	}
-
-	if (ret == NULL) {
-		ret = (lt_sobj_t *)lt_zalloc(sizeof (lt_sobj_t));
-		ret->lt_so_cause_id = ++sobj_table_len;
-		(void) snprintf(ret->lt_so_string, sizeof (ret->lt_so_string),
-		    "%s: 0x%llX", stype_str[id->lt_soi_type], id->lt_soi_addr);
-		ret->lt_so_oid.lt_soi_type = id->lt_soi_type;
-		ret->lt_so_oid.lt_soi_addr = id->lt_soi_addr;
-
-		g_hash_table_insert(sobj_table, &ret->lt_so_oid, ret);
-	}
-
-	return (ret);
-}
-
-/*
- * Check if a process exists by using /proc/pid
- */
-/* ARGSUSED */
-static gboolean
-check_process(gpointer key, lt_stat_collection_t *stat, void *user)
-{
-	char name[PATH_MAX];
-
-	(void) snprintf(name, PATH_MAX, "/proc/%u", stat->lt_sc_id);
-	return (lt_file_exist(name) ? FALSE : TRUE);
-}
-
-/*
- * Check if a thread exists by using /proc/pid/lwp/tid
- */
-/* ARGSUSED */
-static gboolean
-check_thread(gpointer key, lt_stat_collection_t *stat, void *user)
-{
-	char name[PATH_MAX];
-
-	g_assert(stat->lt_sc_parent != NULL);
-	g_assert(stat->lt_sc_parent->lt_sc_level == LT_LEVEL_PROCESS);
-
-	(void) snprintf(name, PATH_MAX, "/proc/%u/lwp/%u",
-	    stat->lt_sc_parent->lt_sc_id, stat->lt_sc_id);
-	return (lt_file_exist(name) ? FALSE : TRUE);
-}
-
-/*
- * Helper function to free a stat node.
- */
-static void
-free_stat(lt_stat_collection_t *stat)
-{
-	int i;
-
-	if (stat == NULL) {
-		return;
-	}
-
-	for (i = 0; i < NGROUPS; ++i) {
-		if (stat->lt_sc_groups[i].lt_grp_cidlist != NULL) {
-			g_hash_table_destroy(stat->lt_sc_groups[i].
-			    lt_grp_cidlist);
-		}
-	}
-
-	if (stat->lt_sc_children != NULL) {
-		g_hash_table_destroy(stat->lt_sc_children);
-	}
-
-	if (stat->lt_sc_name != NULL) {
-		free(stat->lt_sc_name);
-	}
-
-	free(stat);
-}
-
-/*
- * Helper function to initialize a stat node.
- */
-/* ARGSUSED */
-static void
-clear_stat(gpointer key, lt_stat_collection_t *stat, void *user)
-{
-	int i;
-
-	g_assert(stat != NULL);
-
-	for (i = 0; i < NGROUPS; ++i) {
-		if (stat->lt_sc_groups[i].lt_grp_cidlist != NULL) {
-			g_hash_table_destroy(stat->lt_sc_groups[i].
-			    lt_grp_cidlist);
-			stat->lt_sc_groups[i].lt_grp_cidlist = NULL;
-		}
-
-		stat->lt_sc_groups[i].lt_grp_summary.lt_se_data.lt_s_count = 0;
-		stat->lt_sc_groups[i].lt_grp_summary.lt_se_data.lt_s_total = 0;
-		stat->lt_sc_groups[i].lt_grp_summary.lt_se_data.lt_s_max = 0;
-	}
-
-	if (stat->lt_sc_children != NULL) {
-		g_hash_table_foreach_remove(stat->lt_sc_children,
-		    (GHRFunc)stat->lt_sc_check_child_func, NULL);
-		g_hash_table_foreach(stat->lt_sc_children,
-		    (GHFunc)clear_stat, NULL);
-	}
-}
-
-/*
- * Update a collection with the given value.
- * Recursively update parents in the hierarchy  until the root is reached.
- */
-static void
-update_stat_entry(lt_stat_collection_t *stat, int cause_id,
-		lt_stat_type_t type, uint64_t value,
-		const char *string, int group_to_use)
-{
-	lt_stat_entry_t *entry = NULL;
-	lt_datagroup_t *group;
-
-	if (group_to_use < 0 || group_to_use >= NGROUPS) {
-		return;
-	}
-
-	group = &(stat->lt_sc_groups[group_to_use]);
-
-	if (group->lt_grp_cidlist != NULL) {
-		entry = (lt_stat_entry_t *)g_hash_table_lookup(
-		    group->lt_grp_cidlist, LT_INT_TO_POINTER(cause_id));
-	} else   {
-		group->lt_grp_cidlist = g_hash_table_new_full(
-		    g_direct_hash, g_direct_equal,
-		    NULL, (GDestroyNotify)free);
-		lt_check_null(group->lt_grp_cidlist);
-	}
-
-	if (entry == NULL) {
-		entry = (lt_stat_entry_t *)lt_zalloc(sizeof (lt_stat_entry_t));
-		entry->lt_se_string = string;
-
-		switch (group_to_use) {
-		case GROUP_CAUSE:
-			entry->lt_se_type = STAT_CAUSE;
-			entry->lt_se_tsdata.lt_se_t_cause.lt_se_c_id = cause_id;
-			entry->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags =
-			    lt_table_get_cause_flag(cause_id, CAUSE_ALL_FLAGS);
-
-			/* hide the first '#' */
-			if ((entry->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags
-			    & CAUSE_FLAG_HIDE_IN_SUMMARY) != 0) {
-				++entry->lt_se_string;
-			}
-
-			break;
-		case GROUP_SOBJ:
-			entry->lt_se_type = STAT_SOBJ;
-			entry->lt_se_tsdata.lt_se_t_sobj.lt_se_s_id = cause_id;
-			break;
-		}
-
-		g_hash_table_insert(group->lt_grp_cidlist,
-		    LT_INT_TO_POINTER(cause_id), entry);
-	}
-
-	lt_update_stat_value(&entry->lt_se_data, type, value);
-
-	if (group_to_use == GROUP_SOBJ ||
-	    (entry->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags &
-	    CAUSE_FLAG_HIDE_IN_SUMMARY) == 0) {
-		lt_update_stat_value(&group->lt_grp_summary.lt_se_data, type,
-		    value);
-	}
-
-	if (stat->lt_sc_parent != NULL) {
-		update_stat_entry(stat->lt_sc_parent, cause_id, type, value,
-		    string, group_to_use);
-	}
-}
-
-/*
- * Identify the cause of latency from the given stack trace.
- * Return cause_id.
- */
-static void
-find_cause(char *stack, int *cause_id, int *cause_priority)
-{
-	int cause_temp;
-	int prio_temp;
-	int cause = INVALID_CAUSE;
-	int priority = 0;
-	int found = 0;
-
-	g_assert(cause_id != NULL);
-	g_assert(cause_priority != NULL);
-
-	while (stack != NULL) {
-		char *sep;
-		sep = strchr(stack, ' ');
-
-		if (sep != NULL) {
-			*sep = '\0';
-		}
-
-		found = lt_table_cause_from_stack(stack, &cause_temp,
-		    &prio_temp);
-
-		if (found && (cause == INVALID_CAUSE ||
-		    HIGHER_PRIORITY(prio_temp, priority))) {
-			cause = cause_temp;
-			priority = prio_temp;
-		}
-
-		if (sep != NULL) {
-			*sep = ' ';
-			stack = sep + 1;
-		} else   {
-			stack = NULL;
-		}
-	}
-
-	*cause_id = cause;
-	*cause_priority = priority;
-}
-
-/*
- * Create a new collection and hook it to the parent.
- */
-static lt_stat_collection_t *
-new_collection(lt_stat_level_t level, unsigned int id, char *name,
-    lt_stat_collection_t *parent, check_child_func_t check_child_func)
-{
-	int i;
-	lt_stat_collection_t *ret;
-
-	ret = (lt_stat_collection_t *)
-	    lt_zalloc(sizeof (lt_stat_collection_t));
-
-	ret->lt_sc_level = level;
-	ret->lt_sc_check_child_func = check_child_func;
-	ret->lt_sc_id = id;
-	ret->lt_sc_name = name;
-
-	for (i = 0; i < NGROUPS; ++i) {
-		ret->lt_sc_groups[i].lt_grp_summary.lt_se_string =
-		    (const char *)name;
-	}
-
-	if (parent != NULL) {
-		ret->lt_sc_parent = parent;
-
-		if (parent->lt_sc_children == NULL) {
-			parent->lt_sc_children = g_hash_table_new_full(
-			    g_direct_hash, g_direct_equal,
-			    NULL, (GDestroyNotify)free_stat);
-			lt_check_null(parent->lt_sc_children);
-		}
-
-		g_hash_table_insert(parent->lt_sc_children,
-		    LT_INT_TO_POINTER((int)id), ret);
-	}
-
-	return (ret);
-}
-
-/*
- * Find the "leaf" in the collection hierarchy, using the given pid and tid.
- */
-static lt_stat_collection_t *
-get_stat_c(pid_t pid, id_t tid)
-{
-	lt_stat_collection_t *stat_p = NULL;
-	lt_stat_collection_t *stat_t = NULL;
-
-	if (stat_system == NULL) {
-		stat_system = new_collection(LT_LEVEL_GLOBAL,
-		    PID_SYS_GLOBAL, lt_strdup("SYSTEM"), NULL, check_process);
-	} else if (stat_system->lt_sc_children != NULL) {
-		stat_p = (lt_stat_collection_t *)
-		    g_hash_table_lookup(stat_system->lt_sc_children,
-		    LT_INT_TO_POINTER(pid));
-	}
-
-	if (stat_p == NULL) {
-		char *fname;
-		fname = lt_get_proc_field(pid, LT_FIELD_FNAME);
-
-		if (fname == NULL) {
-			/*
-			 * we could not get the executable name of the
-			 * process; the process is probably already dead.
-			 */
-			return (NULL);
-		}
-
-		stat_p = new_collection(LT_LEVEL_PROCESS,
-		    (unsigned int)pid, fname, stat_system, check_thread);
-	} else if (stat_p->lt_sc_children != NULL) {
-		stat_t = (lt_stat_collection_t *)
-		    g_hash_table_lookup(stat_p->lt_sc_children,
-		    LT_INT_TO_POINTER(tid));
-	}
-
-	if (stat_t == NULL) {
-		const int tname_size = 16; /* Enough for "Thread %d" */
-		char *tname;
-
-		tname = (char *)lt_zalloc(tname_size);
-		(void) snprintf(tname, tname_size, "Thread %d", tid);
-
-		stat_t = new_collection(LT_LEVEL_THREAD,
-		    (unsigned int)tid, tname, stat_p, NULL);
-	}
-
-	return (stat_t);
-}
-
-/*
- * Update statistics with the given cause_id. Values will be added to
- * internal statistics.
- */
-void
-lt_stat_update_cause(pid_t pid, id_t tid, int cause_id, lt_stat_type_t type,
-    uint64_t value)
-{
-	const char *string;
-	lt_stat_collection_t *stat_t = NULL;
-
-	if (cause_id < 0 || value == 0) {
-		return;
-	}
-
-	if (lt_table_get_cause_flag(cause_id, CAUSE_FLAG_DISABLED)) {
-		/* Ignore this cause */
-		return;
-	}
-
-	stat_t = get_stat_c(pid, tid);
-
-	if (stat_t == NULL) {
-		/* Process must be dead. */
-		return;
-	}
-
-	string = lt_table_get_cause_name(cause_id);
-
-	update_stat_entry(stat_t, cause_id, type, value, string, GROUP_CAUSE);
-}
-
-/*
- * Update statistics with the given stack trace.
- * The stack trace is mapped to a cause and lt_stat_update_cause() is called
- * to update statistics.
- */
-void
-lt_stat_update(pid_t pid, id_t tid, char *stack, char *tag,
-    unsigned int tag_priority, lt_stat_type_t type, uint64_t value)
-{
-	int tag_cause_id = INVALID_CAUSE;
-	int stack_cause_id = INVALID_CAUSE;
-	int cause_id = INVALID_CAUSE;
-	int stack_priority = 0;
-
-	if (value == 0) {
-		return;
-	}
-
-	find_cause(stack, &stack_cause_id, &stack_priority);
-
-	if (tag_priority != 0) {
-		tag_cause_id = lt_table_cause_from_name(tag, 0, 0);
-
-		if (tag_cause_id == INVALID_CAUSE) {
-			/* This must be a syscall tag */
-			char tmp[64];
-			(void) snprintf(tmp, sizeof (tmp), "Syscall: %s", tag);
-			tag_cause_id = lt_table_cause_from_name(tmp, 1, 0);
-		}
-	}
-
-	cause_id = (tag_priority > stack_priority) ? tag_cause_id :
-	    stack_cause_id;
-
-	if (cause_id == INVALID_CAUSE) {
-		/*
-		 * We got an unmapped stack. Set SPECIAL flag to display it
-		 * in pane 2. This makes it easier to find the cause.
-		 */
-		cause_id = lt_table_cause_from_name(stack, 1,
-		    CAUSE_FLAG_SPECIAL);
-		lt_klog_log(LT_KLOG_LEVEL_UNMAPPED, pid, stack, type, value);
-	} else   {
-		lt_klog_log(LT_KLOG_LEVEL_MAPPED, pid, stack, type, value);
-	}
-
-	lt_stat_update_cause(pid, tid, cause_id, type, value);
-}
-
-/*
- * Zero out all statistics, but keep the data structures in memory
- * to be used to hold new data immediately following.
- */
-void
-lt_stat_clear_all(void)
-{
-	if (stat_system != NULL) {
-		clear_stat(NULL, stat_system, NULL);
-	}
-
-	if (sobj_table != NULL) {
-		g_hash_table_destroy(sobj_table);
-		sobj_table = NULL;
-	}
-}
-
-/*
- * Clean up function that frees all memory used for statistics.
- */
-void
-lt_stat_free_all(void)
-{
-	if (stat_system != NULL) {
-		free_stat(stat_system);
-		stat_system = NULL;
-	}
-
-	if (sobj_table != NULL) {
-		g_hash_table_destroy(sobj_table);
-		sobj_table = NULL;
-	}
-}
-
-/*
- * Get top N causes of latency for a process. Return handle to a stat_list.
- * Use pid = PID_SYS_GLOBAL to get global top list.
- * Call lt_stat_list_free after use to clean up.
- */
-void *
-lt_stat_list_create(lt_list_type_t list_type, lt_stat_level_t level,
-    pid_t pid, id_t tid, int count, lt_sort_t sort_by)
-{
-	GCompareFunc func;
-	GList *list, *walk;
-	lt_stat_collection_t *stat_c = NULL;
-	lt_stat_list_t *ret;
-	lt_datagroup_t *group;
-
-	if (level == LT_LEVEL_GLOBAL) {
-		/* Use global entry */
-		stat_c = stat_system;
-	} else if (stat_system != NULL && stat_system->lt_sc_children != NULL) {
-		/* Find process entry first */
-		stat_c = (lt_stat_collection_t *)g_hash_table_lookup(
-		    stat_system->lt_sc_children, LT_INT_TO_POINTER(pid));
-
-		if (level == LT_LEVEL_THREAD) {
-			/*
-			 * If thread entry is requested, find it based on
-			 * process entry.
-			 */
-			if (stat_c != NULL && stat_c->lt_sc_children != NULL) {
-				stat_c = (lt_stat_collection_t *)
-				    g_hash_table_lookup(stat_c->lt_sc_children,
-				    LT_INT_TO_POINTER(tid));
-			} else {
-				/*
-				 * Thread entry was not found; set it to NULL,
-				 * so that we can return empty list later.
-				 */
-				stat_c = NULL;
-			}
-		}
-	}
-
-	ret = (lt_stat_list_t *)lt_zalloc(sizeof (lt_stat_list_t));
-	ret->lt_sl_entries = (lt_stat_entry_t **)
-	    lt_zalloc(count * sizeof (lt_stat_entry_t *));
-
-	if (stat_c == NULL) {
-		/* Empty list */
-		return (ret);
-	}
-
-	if (list_type == LT_LIST_SOBJ) {
-		group = &(stat_c->lt_sc_groups[GROUP_SOBJ]);
-	} else {
-		group = &(stat_c->lt_sc_groups[GROUP_CAUSE]);
-	}
-
-	if (group->lt_grp_cidlist == NULL) {
-		/* Empty list */
-		return (ret);
-	}
-
-	ret->lt_sl_gtotal = group->lt_grp_summary.lt_se_data.lt_s_total;
-
-	list = g_hash_table_get_values(group->lt_grp_cidlist);
-
-	switch (sort_by) {
-	case LT_SORT_TOTAL:
-		func = (GCompareFunc)lt_sort_by_total_desc;
-		break;
-	case LT_SORT_MAX:
-		func = (GCompareFunc)lt_sort_by_max_desc;
-		break;
-	case LT_SORT_AVG:
-		func = (GCompareFunc)lt_sort_by_avg_desc;
-		break;
-	case LT_SORT_COUNT:
-		func = (GCompareFunc)lt_sort_by_count_desc;
-		break;
-	}
-	list = g_list_sort(list, func);
-
-	for (walk = list;
-	    walk != NULL && count > 0;
-	    walk = g_list_next(walk), --count) {
-		lt_stat_entry_t *data = (lt_stat_entry_t *)walk->data;
-
-		if (list_type == LT_LIST_CAUSE &&
-		    data->lt_se_type == STAT_CAUSE &&
-		    (data->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags &
-		    CAUSE_FLAG_HIDE_IN_SUMMARY) != 0) {
-			continue;
-		}
-
-		if (list_type == LT_LIST_SPECIALS &&
-		    data->lt_se_type == STAT_CAUSE &&
-		    (data->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags &
-		    CAUSE_FLAG_SPECIAL) == 0) {
-			continue;
-		}
-
-		if (data->lt_se_data.lt_s_count == 0) {
-			break;
-		}
-
-		ret->lt_sl_entries[ret->lt_sl_entry_count++] = data;
-	}
-
-	g_list_free(list);
-
-	return (ret);
-}
-
-/*
- * Free memory allocated by lt_stat_list_create().
- */
-void
-lt_stat_list_free(void *ptr)
-{
-	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
-
-	if (list == NULL) {
-		return;
-	}
-
-	if (list->lt_sl_free_func != NULL) {
-		list->lt_sl_free_func(list);
-	}
-
-	if (list->lt_sl_entries != NULL) {
-		free(list->lt_sl_entries);
-	}
-
-	free(list);
-}
-
-/*
- * Check if the given list contains the given item.
- */
-int
-lt_stat_list_has_item(void *ptr, int i)
-{
-	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
-
-	if (list == NULL || i < 0 || i >= list->lt_sl_entry_count ||
-	    list->lt_sl_entries[i] == NULL) {
-		return (0);
-	}
-
-	return (1);
-}
-
-/*
- * Get display name of the given item i in the given list.
- */
-const char *
-lt_stat_list_get_reason(void *ptr, int i)
-{
-	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
-
-	if (list == NULL || i < 0 || i >= list->lt_sl_entry_count ||
-	    list->lt_sl_entries[i] == NULL) {
-		return (NULL);
-	}
-
-	g_assert(list->lt_sl_entries[i]->lt_se_string != NULL);
-
-	return (list->lt_sl_entries[i]->lt_se_string);
-}
-
-/*
- * Get maximum value of the given item i in the given list.
- */
-uint64_t
-lt_stat_list_get_max(void *ptr, int i)
-{
-	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
-
-	if (list == NULL || i < 0 || i >= list->lt_sl_entry_count ||
-	    list->lt_sl_entries[i] == NULL) {
-		return (0);
-	}
-
-	return (list->lt_sl_entries[i]->lt_se_data.lt_s_max);
-}
-
-/*
- * Get total value of the given item i in the given list.
- */
-uint64_t
-lt_stat_list_get_sum(void *ptr, int i)
-{
-	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
-
-	if (list == NULL || i < 0 || i >= list->lt_sl_entry_count ||
-	    list->lt_sl_entries[i] == NULL) {
-		return (0);
-	}
-
-	return (list->lt_sl_entries[i]->lt_se_data.lt_s_total);
-}
-
-/*
- * Get count value of the given item i in the given list.
- */
-uint64_t
-lt_stat_list_get_count(void *ptr, int i)
-{
-	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
-
-	if (list == NULL || i < 0 || i >= list->lt_sl_entry_count ||
-	    list->lt_sl_entries[i] == NULL) {
-		return (0);
-	}
-
-	return (list->lt_sl_entries[i]->lt_se_data.lt_s_count);
-}
-
-/*
- * Get grand total of all latency in the list.
- */
-uint64_t
-lt_stat_list_get_gtotal(void *ptr)
-{
-	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
-
-	if (list == NULL) {
-		return (0);
-	}
-
-	return (list->lt_sl_gtotal);
-}
-
-/*
- * ============================================================================
- * Process and thread list.
- * They share a lot of the static variables that are used for keeping
- * statistics, hence they are located in this file.
- */
-
-/*
- * Helper function, sort by PID/TID ascend.
- */
-static int
-sort_id(lt_stat_collection_t *a, lt_stat_collection_t *b)
-{
-	return ((int)(a->lt_sc_id - b->lt_sc_id));
-}
-
-/*
- * Get the current list of processes. Call lt_stat_proc_list_free after use
- * to clean up.
- */
-static int
-plist_create(pid_t ** list)
-{
-	GList *pid_list, *walk;
-	int ret, count;
-
-	ret = g_hash_table_size(stat_system->lt_sc_children);
-	*list = (pid_t *)lt_malloc(sizeof (pid_t) * ret);
-
-	pid_list = g_hash_table_get_values(stat_system->lt_sc_children);
-	pid_list = g_list_sort(pid_list, (GCompareFunc)sort_id);
-
-	for (walk = pid_list, count = 0;
-	    walk != NULL && count < ret;
-	    walk = g_list_next(walk), ++count) {
-		(*list)[count] = (int)
-		    ((lt_stat_collection_t *)(walk->data))->lt_sc_id;
-	}
-
-	g_list_free(pid_list);
-
-	return (ret);
-}
-
-/*
- * Count the no. of threads currently present in a process.
- * Only thread that have SSLEEP are counted.
- */
-/* ARGSUSED */
-static void
-count_threads(gpointer key, lt_stat_collection_t *stat_c, int *ret)
-{
-	g_assert(ret != NULL);
-
-	if (stat_c->lt_sc_children != NULL) {
-		*ret += g_hash_table_size(stat_c->lt_sc_children);
-	}
-}
-
-/*
- * Get current list of processes and threads.
- * Call lt_stat_proc_list_free after use to clean up.
- */
-static int
-tlist_create(pid_t ** plist, id_t ** tlist)
-{
-	GList *pid_list, *walk_p;
-	GList *tid_list, *walk_t;
-	int ret = 0;
-	int count = 0;
-
-	g_hash_table_foreach(stat_system->lt_sc_children,
-	    (GHFunc)count_threads, &ret);
-
-	*plist = (pid_t *)lt_malloc(sizeof (pid_t) * ret);
-	*tlist = (id_t *)lt_malloc(sizeof (id_t) * ret);
-
-	pid_list = g_hash_table_get_values(stat_system->lt_sc_children);
-	pid_list = g_list_sort(pid_list, (GCompareFunc)sort_id);
-
-	for (walk_p = pid_list; walk_p != NULL;
-	    walk_p = g_list_next(walk_p)) {
-		lt_stat_collection_t *stat_p =
-		    (lt_stat_collection_t *)walk_p->data;
-
-		if (stat_p->lt_sc_children == NULL) {
-			continue;
-		}
-
-		tid_list = g_hash_table_get_values(stat_p->lt_sc_children);
-		tid_list = g_list_sort(tid_list, (GCompareFunc)sort_id);
-
-		for (walk_t = tid_list; walk_t != NULL;
-		    walk_t = g_list_next(walk_t)) {
-			lt_stat_collection_t *stat_t =
-			    (lt_stat_collection_t *)walk_t->data;
-
-			(*plist)[count] = (int)stat_p->lt_sc_id;
-			(*tlist)[count] = (int)stat_t->lt_sc_id;
-
-			++count;
-		}
-		g_list_free(tid_list);
-	}
-
-	g_list_free(pid_list);
-	g_assert(count == ret);
-
-	return (ret);
-}
-
-/*
- * List of processes that are tracked by LatencyTOP.
- */
-int
-lt_stat_proc_list_create(pid_t ** plist, id_t ** tlist)
-{
-	if (plist == NULL) {
-		return (-1);
-	}
-
-	if (stat_system == NULL || stat_system->lt_sc_children == NULL) {
-		*plist = NULL;
-
-		if (tlist != NULL) {
-			*tlist = NULL;
-		}
-
-		return (0);
-	}
-
-	if (tlist == NULL) {
-		return (plist_create(plist));
-	} else {
-		return (tlist_create(plist, tlist));
-	}
-}
-
-/*
- * Free memory allocated by lt_stat_proc_list_create().
- */
-void
-lt_stat_proc_list_free(pid_t *plist, id_t *tlist)
-{
-	if (plist != NULL) {
-		free(plist);
-	}
-
-	if (tlist != NULL) {
-		free(tlist);
-	}
-}
-
-/*
- * Get executable name of the given process (ID).
- */
-const char *
-lt_stat_proc_get_name(pid_t pid)
-{
-	lt_stat_collection_t *stat_p = NULL;
-
-	if (stat_system == NULL || stat_system->lt_sc_children == NULL) {
-		return (NULL);
-	}
-
-	stat_p = (lt_stat_collection_t *)g_hash_table_lookup(
-	    stat_system->lt_sc_children, LT_INT_TO_POINTER(pid));
-
-	if (stat_p != NULL) {
-		return (stat_p->lt_sc_name);
-	} else   {
-		return (NULL);
-	}
-}
-
-/*
- * Get number of threads.
- */
-int
-lt_stat_proc_get_nthreads(pid_t pid)
-{
-	lt_stat_collection_t *stat_p = NULL;
-
-	if (stat_system == NULL || stat_system->lt_sc_children == NULL) {
-		return (0);
-	}
-
-	stat_p = (lt_stat_collection_t *)g_hash_table_lookup(
-	    stat_system->lt_sc_children, LT_INT_TO_POINTER(pid));
-
-	if (stat_p != NULL) {
-		return (g_hash_table_size(stat_p->lt_sc_children));
-	} else   {
-		return (0);
-	}
-}
-
-/*
- * Update statistics for synchronization objects.
- */
-void
-lt_stat_update_sobj(pid_t pid, id_t tid, int stype,
-    unsigned long long wchan,
-    lt_stat_type_t type, uint64_t value)
-{
-	lt_sobj_id_t id;
-	lt_sobj_t *sobj;
-	int cause_id;
-	lt_stat_collection_t *stat_t = NULL;
-
-	stat_t = get_stat_c(pid, tid);
-
-	if (stat_t == NULL) {
-		return;
-	}
-
-	id.lt_soi_type = stype;
-	id.lt_soi_addr = wchan;
-	sobj = lookup_sobj(&id);
-
-	if (sobj == NULL) {
-		return;
-	}
-
-	cause_id = sobj->lt_so_cause_id;
-
-	update_stat_entry(stat_t, cause_id, type, value,
-	    sobj->lt_so_string, GROUP_SOBJ);
-}
--- /dev/null	Thu Apr 11 08:00:00 2024
+++ new/usr/src/cmd/latencytop/stat.c	Thu Apr 11 14:30:36 2024
@@ -0,0 +1,1050 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2008-2009, Intel Corporation.
+ * All Rights Reserved.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/stat.h>
+
+#include "latencytop.h"
+
+/* Statistics for each process/thread. */
+typedef struct _lt_stat_collection lt_stat_collection_t;
+typedef gboolean (*check_child_func_t) (gpointer key,
+    lt_stat_collection_t *stat, void *user);
+
+typedef struct {
+	lt_stat_entry_t lt_grp_summary;
+	/* cause_id -> stat entry */
+	GHashTable *lt_grp_cidlist;
+} lt_datagroup_t;
+
+#define	NGROUPS			2
+#define	GROUP_CAUSE		0
+#define	GROUP_SOBJ		1
+
+/*
+ * A data collection hierarchy involving three entities - system, process
+ * and thread. The hierarchic relationship is as follows :
+ *
+ *		1 system -> 1 or more processes -> 1 or more threads
+ */
+struct _lt_stat_collection {
+	lt_stat_level_t lt_sc_level;
+	unsigned int lt_sc_id;
+	char *lt_sc_name;
+	lt_datagroup_t lt_sc_groups[NGROUPS];
+	/*
+	 * The following fields: lt_sc_parent, lt_sc_children and
+	 * lt_sc_check_child_func maintain the tree structure.
+	 */
+	lt_stat_collection_t *lt_sc_parent;		/* Parent node */
+	GHashTable *lt_sc_children;	/* pid/tid -> lt_stat_collection_t */
+	check_child_func_t lt_sc_check_child_func; /* Release dead children */
+};
+
+/* Internal data structure to back up a stat_list */
+typedef struct _lt_stat_list lt_stat_list_t;
+typedef void (*free_list_func_t)(lt_stat_list_t *);
+struct _lt_stat_list {
+	int lt_sl_entry_count;
+	lt_stat_entry_t **lt_sl_entries;
+	uint64_t lt_sl_gtotal;
+	free_list_func_t lt_sl_free_func;
+};
+
+/* Root of the collection hierarchy: system level statistics */
+static lt_stat_collection_t *stat_system = NULL;
+
+/*
+ * Data structure to hold synchronization objects.
+ * We don't use normal "cause table" because this needs to be cleared
+ * every time we refresh in order to make sure that stale synchronization
+ * objects don't consume memory.
+ */
+typedef struct {
+	int lt_soi_type;
+	unsigned long long lt_soi_addr;
+} lt_sobj_id_t;
+
+typedef struct {
+	lt_sobj_id_t lt_so_oid;
+	int lt_so_cause_id;
+	char lt_so_string[32];	/* Enough to hold "%s: 0x%llX" */
+} lt_sobj_t;
+
+static GHashTable *sobj_table = NULL;
+static int sobj_table_len = 0;
+
+/*
+ * Lower 32-bit of the address of synchronization objects is used to hash
+ * them.
+ */
+static guint
+sobj_id_hash(lt_sobj_id_t *id)
+{
+	g_assert(id != NULL);
+	return (id->lt_soi_addr & 0xFFFFFFFF);
+}
+
+/*
+ * Test if two synchronization objects are the same.
+ */
+static gboolean
+sobj_id_equal(lt_sobj_id_t *a, lt_sobj_id_t *b)
+{
+	g_assert(a != NULL && b != NULL);
+	return (a->lt_soi_type == b->lt_soi_type &&
+	    a->lt_soi_addr == b->lt_soi_addr);
+}
+
+/*
+ * Look up the cause_id of a synchronization object.
+ * Note that this cause_id is only unique in GROUP_SOBJ, and changes after
+ * a refresh.
+ */
+static lt_sobj_t *
+lookup_sobj(lt_sobj_id_t *id)
+{
+	const char *stype_str[] = {
+		"None",
+		"Mutex",
+		"RWLock",
+		"CV",
+		"Sema",
+		"User",
+		"User_PI",
+		"Shuttle"
+	};
+	const int stype_str_len =
+	    sizeof (stype_str) / sizeof (stype_str[0]);
+	lt_sobj_t *ret = NULL;
+	g_assert(id != NULL);
+
+	if (id->lt_soi_type < 0 || id->lt_soi_type >= stype_str_len) {
+		return (NULL);
+	}
+
+	if (sobj_table != NULL) {
+		ret = (lt_sobj_t *)g_hash_table_lookup(sobj_table, id);
+	} else {
+		sobj_table = g_hash_table_new_full(
+		    (GHashFunc)sobj_id_hash, (GEqualFunc)sobj_id_equal,
+		    NULL, (GDestroyNotify)free);
+		lt_check_null(sobj_table);
+	}
+
+	if (ret == NULL) {
+		ret = (lt_sobj_t *)lt_zalloc(sizeof (lt_sobj_t));
+		ret->lt_so_cause_id = ++sobj_table_len;
+		(void) snprintf(ret->lt_so_string, sizeof (ret->lt_so_string),
+		    "%s: 0x%llX", stype_str[id->lt_soi_type], id->lt_soi_addr);
+		ret->lt_so_oid.lt_soi_type = id->lt_soi_type;
+		ret->lt_so_oid.lt_soi_addr = id->lt_soi_addr;
+
+		g_hash_table_insert(sobj_table, &ret->lt_so_oid, ret);
+	}
+
+	return (ret);
+}
+
+/*
+ * Check if a process exists by using /proc/pid
+ */
+/* ARGSUSED */
+static gboolean
+check_process(gpointer key, lt_stat_collection_t *stat, void *user)
+{
+	char name[PATH_MAX];
+
+	(void) snprintf(name, PATH_MAX, "/proc/%u", stat->lt_sc_id);
+	return (lt_file_exist(name) ? FALSE : TRUE);
+}
+
+/*
+ * Check if a thread exists by using /proc/pid/lwp/tid
+ */
+/* ARGSUSED */
+static gboolean
+check_thread(gpointer key, lt_stat_collection_t *stat, void *user)
+{
+	char name[PATH_MAX];
+
+	g_assert(stat->lt_sc_parent != NULL);
+	g_assert(stat->lt_sc_parent->lt_sc_level == LT_LEVEL_PROCESS);
+
+	(void) snprintf(name, PATH_MAX, "/proc/%u/lwp/%u",
+	    stat->lt_sc_parent->lt_sc_id, stat->lt_sc_id);
+	return (lt_file_exist(name) ? FALSE : TRUE);
+}
+
+/*
+ * Helper function to free a stat node.
+ */
+static void
+free_stat(lt_stat_collection_t *stat)
+{
+	int i;
+
+	if (stat == NULL) {
+		return;
+	}
+
+	for (i = 0; i < NGROUPS; ++i) {
+		if (stat->lt_sc_groups[i].lt_grp_cidlist != NULL) {
+			g_hash_table_destroy(stat->lt_sc_groups[i].
+			    lt_grp_cidlist);
+		}
+	}
+
+	if (stat->lt_sc_children != NULL) {
+		g_hash_table_destroy(stat->lt_sc_children);
+	}
+
+	if (stat->lt_sc_name != NULL) {
+		free(stat->lt_sc_name);
+	}
+
+	free(stat);
+}
+
+/*
+ * Helper function to initialize a stat node.
+ */
+/* ARGSUSED */
+static void
+clear_stat(gpointer key, lt_stat_collection_t *stat, void *user)
+{
+	int i;
+
+	g_assert(stat != NULL);
+
+	for (i = 0; i < NGROUPS; ++i) {
+		if (stat->lt_sc_groups[i].lt_grp_cidlist != NULL) {
+			g_hash_table_destroy(stat->lt_sc_groups[i].
+			    lt_grp_cidlist);
+			stat->lt_sc_groups[i].lt_grp_cidlist = NULL;
+		}
+
+		stat->lt_sc_groups[i].lt_grp_summary.lt_se_data.lt_s_count = 0;
+		stat->lt_sc_groups[i].lt_grp_summary.lt_se_data.lt_s_total = 0;
+		stat->lt_sc_groups[i].lt_grp_summary.lt_se_data.lt_s_max = 0;
+	}
+
+	if (stat->lt_sc_children != NULL) {
+		g_hash_table_foreach_remove(stat->lt_sc_children,
+		    (GHRFunc)stat->lt_sc_check_child_func, NULL);
+		g_hash_table_foreach(stat->lt_sc_children,
+		    (GHFunc)clear_stat, NULL);
+	}
+}
+
+/*
+ * Update a collection with the given value.
+ * Recursively update parents in the hierarchy  until the root is reached.
+ */
+static void
+update_stat_entry(lt_stat_collection_t *stat, int cause_id,
+    lt_stat_type_t type, uint64_t value,
+    const char *string, int group_to_use)
+{
+	lt_stat_entry_t *entry = NULL;
+	lt_datagroup_t *group;
+
+	if (group_to_use < 0 || group_to_use >= NGROUPS) {
+		return;
+	}
+
+	group = &(stat->lt_sc_groups[group_to_use]);
+
+	if (group->lt_grp_cidlist != NULL) {
+		entry = (lt_stat_entry_t *)g_hash_table_lookup(
+		    group->lt_grp_cidlist, LT_INT_TO_POINTER(cause_id));
+	} else   {
+		group->lt_grp_cidlist = g_hash_table_new_full(
+		    g_direct_hash, g_direct_equal,
+		    NULL, (GDestroyNotify)free);
+		lt_check_null(group->lt_grp_cidlist);
+	}
+
+	if (entry == NULL) {
+		entry = (lt_stat_entry_t *)lt_zalloc(sizeof (lt_stat_entry_t));
+		entry->lt_se_string = string;
+
+		switch (group_to_use) {
+		case GROUP_CAUSE:
+			entry->lt_se_type = STAT_CAUSE;
+			entry->lt_se_tsdata.lt_se_t_cause.lt_se_c_id = cause_id;
+			entry->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags =
+			    lt_table_get_cause_flag(cause_id, CAUSE_ALL_FLAGS);
+
+			/* hide the first '#' */
+			if ((entry->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags
+			    & CAUSE_FLAG_HIDE_IN_SUMMARY) != 0) {
+				++entry->lt_se_string;
+			}
+
+			break;
+		case GROUP_SOBJ:
+			entry->lt_se_type = STAT_SOBJ;
+			entry->lt_se_tsdata.lt_se_t_sobj.lt_se_s_id = cause_id;
+			break;
+		}
+
+		g_hash_table_insert(group->lt_grp_cidlist,
+		    LT_INT_TO_POINTER(cause_id), entry);
+	}
+
+	lt_update_stat_value(&entry->lt_se_data, type, value);
+
+	if (group_to_use == GROUP_SOBJ ||
+	    (entry->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags &
+	    CAUSE_FLAG_HIDE_IN_SUMMARY) == 0) {
+		lt_update_stat_value(&group->lt_grp_summary.lt_se_data, type,
+		    value);
+	}
+
+	if (stat->lt_sc_parent != NULL) {
+		update_stat_entry(stat->lt_sc_parent, cause_id, type, value,
+		    string, group_to_use);
+	}
+}
+
+/*
+ * Identify the cause of latency from the given stack trace.
+ * Return cause_id.
+ */
+static void
+find_cause(char *stack, int *cause_id, int *cause_priority)
+{
+	int cause_temp;
+	int prio_temp;
+	int cause = INVALID_CAUSE;
+	int priority = 0;
+	int found = 0;
+
+	g_assert(cause_id != NULL);
+	g_assert(cause_priority != NULL);
+
+	while (stack != NULL) {
+		char *sep;
+		sep = strchr(stack, ' ');
+
+		if (sep != NULL) {
+			*sep = '\0';
+		}
+
+		found = lt_table_cause_from_stack(stack, &cause_temp,
+		    &prio_temp);
+
+		if (found && (cause == INVALID_CAUSE ||
+		    HIGHER_PRIORITY(prio_temp, priority))) {
+			cause = cause_temp;
+			priority = prio_temp;
+		}
+
+		if (sep != NULL) {
+			*sep = ' ';
+			stack = sep + 1;
+		} else   {
+			stack = NULL;
+		}
+	}
+
+	*cause_id = cause;
+	*cause_priority = priority;
+}
+
+/*
+ * Create a new collection and hook it to the parent.
+ */
+static lt_stat_collection_t *
+new_collection(lt_stat_level_t level, unsigned int id, char *name,
+    lt_stat_collection_t *parent, check_child_func_t check_child_func)
+{
+	int i;
+	lt_stat_collection_t *ret;
+
+	ret = (lt_stat_collection_t *)
+	    lt_zalloc(sizeof (lt_stat_collection_t));
+
+	ret->lt_sc_level = level;
+	ret->lt_sc_check_child_func = check_child_func;
+	ret->lt_sc_id = id;
+	ret->lt_sc_name = name;
+
+	for (i = 0; i < NGROUPS; ++i) {
+		ret->lt_sc_groups[i].lt_grp_summary.lt_se_string =
+		    (const char *)name;
+	}
+
+	if (parent != NULL) {
+		ret->lt_sc_parent = parent;
+
+		if (parent->lt_sc_children == NULL) {
+			parent->lt_sc_children = g_hash_table_new_full(
+			    g_direct_hash, g_direct_equal,
+			    NULL, (GDestroyNotify)free_stat);
+			lt_check_null(parent->lt_sc_children);
+		}
+
+		g_hash_table_insert(parent->lt_sc_children,
+		    LT_INT_TO_POINTER((int)id), ret);
+	}
+
+	return (ret);
+}
+
+/*
+ * Find the "leaf" in the collection hierarchy, using the given pid and tid.
+ */
+static lt_stat_collection_t *
+get_stat_c(pid_t pid, id_t tid)
+{
+	lt_stat_collection_t *stat_p = NULL;
+	lt_stat_collection_t *stat_t = NULL;
+
+	if (stat_system == NULL) {
+		stat_system = new_collection(LT_LEVEL_GLOBAL,
+		    PID_SYS_GLOBAL, lt_strdup("SYSTEM"), NULL, check_process);
+	} else if (stat_system->lt_sc_children != NULL) {
+		stat_p = (lt_stat_collection_t *)
+		    g_hash_table_lookup(stat_system->lt_sc_children,
+		    LT_INT_TO_POINTER(pid));
+	}
+
+	if (stat_p == NULL) {
+		char *fname;
+		fname = lt_get_proc_field(pid, LT_FIELD_FNAME);
+
+		if (fname == NULL) {
+			/*
+			 * we could not get the executable name of the
+			 * process; the process is probably already dead.
+			 */
+			return (NULL);
+		}
+
+		stat_p = new_collection(LT_LEVEL_PROCESS,
+		    (unsigned int)pid, fname, stat_system, check_thread);
+	} else if (stat_p->lt_sc_children != NULL) {
+		stat_t = (lt_stat_collection_t *)
+		    g_hash_table_lookup(stat_p->lt_sc_children,
+		    LT_INT_TO_POINTER(tid));
+	}
+
+	if (stat_t == NULL) {
+		const int tname_size = 16; /* Enough for "Thread %d" */
+		char *tname;
+
+		tname = (char *)lt_zalloc(tname_size);
+		(void) snprintf(tname, tname_size, "Thread %d", tid);
+
+		stat_t = new_collection(LT_LEVEL_THREAD,
+		    (unsigned int)tid, tname, stat_p, NULL);
+	}
+
+	return (stat_t);
+}
+
+/*
+ * Update statistics with the given cause_id. Values will be added to
+ * internal statistics.
+ */
+void
+lt_stat_update_cause(pid_t pid, id_t tid, int cause_id, lt_stat_type_t type,
+    uint64_t value)
+{
+	const char *string;
+	lt_stat_collection_t *stat_t = NULL;
+
+	if (cause_id < 0 || value == 0) {
+		return;
+	}
+
+	if (lt_table_get_cause_flag(cause_id, CAUSE_FLAG_DISABLED)) {
+		/* Ignore this cause */
+		return;
+	}
+
+	stat_t = get_stat_c(pid, tid);
+
+	if (stat_t == NULL) {
+		/* Process must be dead. */
+		return;
+	}
+
+	string = lt_table_get_cause_name(cause_id);
+
+	update_stat_entry(stat_t, cause_id, type, value, string, GROUP_CAUSE);
+}
+
+/*
+ * Update statistics with the given stack trace.
+ * The stack trace is mapped to a cause and lt_stat_update_cause() is called
+ * to update statistics.
+ */
+void
+lt_stat_update(pid_t pid, id_t tid, char *stack, char *tag,
+    unsigned int tag_priority, lt_stat_type_t type, uint64_t value)
+{
+	int tag_cause_id = INVALID_CAUSE;
+	int stack_cause_id = INVALID_CAUSE;
+	int cause_id = INVALID_CAUSE;
+	int stack_priority = 0;
+
+	if (value == 0) {
+		return;
+	}
+
+	find_cause(stack, &stack_cause_id, &stack_priority);
+
+	if (tag_priority != 0) {
+		tag_cause_id = lt_table_cause_from_name(tag, 0, 0);
+
+		if (tag_cause_id == INVALID_CAUSE) {
+			/* This must be a syscall tag */
+			char tmp[64];
+			(void) snprintf(tmp, sizeof (tmp), "Syscall: %s", tag);
+			tag_cause_id = lt_table_cause_from_name(tmp, 1, 0);
+		}
+	}
+
+	cause_id = (tag_priority > stack_priority) ? tag_cause_id :
+	    stack_cause_id;
+
+	if (cause_id == INVALID_CAUSE) {
+		/*
+		 * We got an unmapped stack. Set SPECIAL flag to display it
+		 * in pane 2. This makes it easier to find the cause.
+		 */
+		cause_id = lt_table_cause_from_name(stack, 1,
+		    CAUSE_FLAG_SPECIAL);
+		lt_klog_log(LT_KLOG_LEVEL_UNMAPPED, pid, stack, type, value);
+	} else   {
+		lt_klog_log(LT_KLOG_LEVEL_MAPPED, pid, stack, type, value);
+	}
+
+	lt_stat_update_cause(pid, tid, cause_id, type, value);
+}
+
+/*
+ * Zero out all statistics, but keep the data structures in memory
+ * to be used to hold new data immediately following.
+ */
+void
+lt_stat_clear_all(void)
+{
+	if (stat_system != NULL) {
+		clear_stat(NULL, stat_system, NULL);
+	}
+
+	if (sobj_table != NULL) {
+		g_hash_table_destroy(sobj_table);
+		sobj_table = NULL;
+	}
+}
+
+/*
+ * Clean up function that frees all memory used for statistics.
+ */
+void
+lt_stat_free_all(void)
+{
+	if (stat_system != NULL) {
+		free_stat(stat_system);
+		stat_system = NULL;
+	}
+
+	if (sobj_table != NULL) {
+		g_hash_table_destroy(sobj_table);
+		sobj_table = NULL;
+	}
+}
+
+/*
+ * Get top N causes of latency for a process. Return handle to a stat_list.
+ * Use pid = PID_SYS_GLOBAL to get global top list.
+ * Call lt_stat_list_free after use to clean up.
+ */
+void *
+lt_stat_list_create(lt_list_type_t list_type, lt_stat_level_t level,
+    pid_t pid, id_t tid, int count, lt_sort_t sort_by)
+{
+	GCompareFunc func;
+	GList *list, *walk;
+	lt_stat_collection_t *stat_c = NULL;
+	lt_stat_list_t *ret;
+	lt_datagroup_t *group;
+
+	if (level == LT_LEVEL_GLOBAL) {
+		/* Use global entry */
+		stat_c = stat_system;
+	} else if (stat_system != NULL && stat_system->lt_sc_children != NULL) {
+		/* Find process entry first */
+		stat_c = (lt_stat_collection_t *)g_hash_table_lookup(
+		    stat_system->lt_sc_children, LT_INT_TO_POINTER(pid));
+
+		if (level == LT_LEVEL_THREAD) {
+			/*
+			 * If thread entry is requested, find it based on
+			 * process entry.
+			 */
+			if (stat_c != NULL && stat_c->lt_sc_children != NULL) {
+				stat_c = (lt_stat_collection_t *)
+				    g_hash_table_lookup(stat_c->lt_sc_children,
+				    LT_INT_TO_POINTER(tid));
+			} else {
+				/*
+				 * Thread entry was not found; set it to NULL,
+				 * so that we can return empty list later.
+				 */
+				stat_c = NULL;
+			}
+		}
+	}
+
+	ret = (lt_stat_list_t *)lt_zalloc(sizeof (lt_stat_list_t));
+	ret->lt_sl_entries = (lt_stat_entry_t **)
+	    lt_zalloc(count * sizeof (lt_stat_entry_t *));
+
+	if (stat_c == NULL) {
+		/* Empty list */
+		return (ret);
+	}
+
+	if (list_type == LT_LIST_SOBJ) {
+		group = &(stat_c->lt_sc_groups[GROUP_SOBJ]);
+	} else {
+		group = &(stat_c->lt_sc_groups[GROUP_CAUSE]);
+	}
+
+	if (group->lt_grp_cidlist == NULL) {
+		/* Empty list */
+		return (ret);
+	}
+
+	ret->lt_sl_gtotal = group->lt_grp_summary.lt_se_data.lt_s_total;
+
+	list = g_hash_table_get_values(group->lt_grp_cidlist);
+
+	switch (sort_by) {
+	case LT_SORT_TOTAL:
+		func = (GCompareFunc)lt_sort_by_total_desc;
+		break;
+	case LT_SORT_MAX:
+		func = (GCompareFunc)lt_sort_by_max_desc;
+		break;
+	case LT_SORT_AVG:
+		func = (GCompareFunc)lt_sort_by_avg_desc;
+		break;
+	case LT_SORT_COUNT:
+		func = (GCompareFunc)lt_sort_by_count_desc;
+		break;
+	}
+	list = g_list_sort(list, func);
+
+	for (walk = list;
+	    walk != NULL && count > 0;
+	    walk = g_list_next(walk), --count) {
+		lt_stat_entry_t *data = (lt_stat_entry_t *)walk->data;
+
+		if (list_type == LT_LIST_CAUSE &&
+		    data->lt_se_type == STAT_CAUSE &&
+		    (data->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags &
+		    CAUSE_FLAG_HIDE_IN_SUMMARY) != 0) {
+			continue;
+		}
+
+		if (list_type == LT_LIST_SPECIALS &&
+		    data->lt_se_type == STAT_CAUSE &&
+		    (data->lt_se_tsdata.lt_se_t_cause.lt_se_c_flags &
+		    CAUSE_FLAG_SPECIAL) == 0) {
+			continue;
+		}
+
+		if (data->lt_se_data.lt_s_count == 0) {
+			break;
+		}
+
+		ret->lt_sl_entries[ret->lt_sl_entry_count++] = data;
+	}
+
+	g_list_free(list);
+
+	return (ret);
+}
+
+/*
+ * Free memory allocated by lt_stat_list_create().
+ */
+void
+lt_stat_list_free(void *ptr)
+{
+	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
+
+	if (list == NULL) {
+		return;
+	}
+
+	if (list->lt_sl_free_func != NULL) {
+		list->lt_sl_free_func(list);
+	}
+
+	if (list->lt_sl_entries != NULL) {
+		free(list->lt_sl_entries);
+	}
+
+	free(list);
+}
+
+/*
+ * Check if the given list contains the given item.
+ */
+int
+lt_stat_list_has_item(void *ptr, int i)
+{
+	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
+
+	if (list == NULL || i < 0 || i >= list->lt_sl_entry_count ||
+	    list->lt_sl_entries[i] == NULL) {
+		return (0);
+	}
+
+	return (1);
+}
+
+/*
+ * Get display name of the given item i in the given list.
+ */
+const char *
+lt_stat_list_get_reason(void *ptr, int i)
+{
+	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
+
+	if (list == NULL || i < 0 || i >= list->lt_sl_entry_count ||
+	    list->lt_sl_entries[i] == NULL) {
+		return (NULL);
+	}
+
+	g_assert(list->lt_sl_entries[i]->lt_se_string != NULL);
+
+	return (list->lt_sl_entries[i]->lt_se_string);
+}
+
+/*
+ * Get maximum value of the given item i in the given list.
+ */
+uint64_t
+lt_stat_list_get_max(void *ptr, int i)
+{
+	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
+
+	if (list == NULL || i < 0 || i >= list->lt_sl_entry_count ||
+	    list->lt_sl_entries[i] == NULL) {
+		return (0);
+	}
+
+	return (list->lt_sl_entries[i]->lt_se_data.lt_s_max);
+}
+
+/*
+ * Get total value of the given item i in the given list.
+ */
+uint64_t
+lt_stat_list_get_sum(void *ptr, int i)
+{
+	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
+
+	if (list == NULL || i < 0 || i >= list->lt_sl_entry_count ||
+	    list->lt_sl_entries[i] == NULL) {
+		return (0);
+	}
+
+	return (list->lt_sl_entries[i]->lt_se_data.lt_s_total);
+}
+
+/*
+ * Get count value of the given item i in the given list.
+ */
+uint64_t
+lt_stat_list_get_count(void *ptr, int i)
+{
+	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
+
+	if (list == NULL || i < 0 || i >= list->lt_sl_entry_count ||
+	    list->lt_sl_entries[i] == NULL) {
+		return (0);
+	}
+
+	return (list->lt_sl_entries[i]->lt_se_data.lt_s_count);
+}
+
+/*
+ * Get grand total of all latency in the list.
+ */
+uint64_t
+lt_stat_list_get_gtotal(void *ptr)
+{
+	lt_stat_list_t *list = (lt_stat_list_t *)ptr;
+
+	if (list == NULL) {
+		return (0);
+	}
+
+	return (list->lt_sl_gtotal);
+}
+
+/*
+ * ============================================================================
+ * Process and thread list.
+ * They share a lot of the static variables that are used for keeping
+ * statistics, hence they are located in this file.
+ */
+
+/*
+ * Helper function, sort by PID/TID ascend.
+ */
+static int
+sort_id(lt_stat_collection_t *a, lt_stat_collection_t *b)
+{
+	return ((int)(a->lt_sc_id - b->lt_sc_id));
+}
+
+/*
+ * Get the current list of processes. Call lt_stat_proc_list_free after use
+ * to clean up.
+ */
+static int
+plist_create(pid_t ** list)
+{
+	GList *pid_list, *walk;
+	int ret, count;
+
+	ret = g_hash_table_size(stat_system->lt_sc_children);
+	*list = (pid_t *)lt_malloc(sizeof (pid_t) * ret);
+
+	pid_list = g_hash_table_get_values(stat_system->lt_sc_children);
+	pid_list = g_list_sort(pid_list, (GCompareFunc)sort_id);
+
+	for (walk = pid_list, count = 0;
+	    walk != NULL && count < ret;
+	    walk = g_list_next(walk), ++count) {
+		(*list)[count] = (int)
+		    ((lt_stat_collection_t *)(walk->data))->lt_sc_id;
+	}
+
+	g_list_free(pid_list);
+
+	return (ret);
+}
+
+/*
+ * Count the no. of threads currently present in a process.
+ * Only thread that have SSLEEP are counted.
+ */
+/* ARGSUSED */
+static void
+count_threads(gpointer key, lt_stat_collection_t *stat_c, int *ret)
+{
+	g_assert(ret != NULL);
+
+	if (stat_c->lt_sc_children != NULL) {
+		*ret += g_hash_table_size(stat_c->lt_sc_children);
+	}
+}
+
+/*
+ * Get current list of processes and threads.
+ * Call lt_stat_proc_list_free after use to clean up.
+ */
+static int
+tlist_create(pid_t ** plist, id_t ** tlist)
+{
+	GList *pid_list, *walk_p;
+	GList *tid_list, *walk_t;
+	int ret = 0;
+	int count = 0;
+
+	g_hash_table_foreach(stat_system->lt_sc_children,
+	    (GHFunc)count_threads, &ret);
+
+	*plist = (pid_t *)lt_malloc(sizeof (pid_t) * ret);
+	*tlist = (id_t *)lt_malloc(sizeof (id_t) * ret);
+
+	pid_list = g_hash_table_get_values(stat_system->lt_sc_children);
+	pid_list = g_list_sort(pid_list, (GCompareFunc)sort_id);
+
+	for (walk_p = pid_list; walk_p != NULL;
+	    walk_p = g_list_next(walk_p)) {
+		lt_stat_collection_t *stat_p =
+		    (lt_stat_collection_t *)walk_p->data;
+
+		if (stat_p->lt_sc_children == NULL) {
+			continue;
+		}
+
+		tid_list = g_hash_table_get_values(stat_p->lt_sc_children);
+		tid_list = g_list_sort(tid_list, (GCompareFunc)sort_id);
+
+		for (walk_t = tid_list; walk_t != NULL;
+		    walk_t = g_list_next(walk_t)) {
+			lt_stat_collection_t *stat_t =
+			    (lt_stat_collection_t *)walk_t->data;
+
+			(*plist)[count] = (int)stat_p->lt_sc_id;
+			(*tlist)[count] = (int)stat_t->lt_sc_id;
+
+			++count;
+		}
+		g_list_free(tid_list);
+	}
+
+	g_list_free(pid_list);
+	g_assert(count == ret);
+
+	return (ret);
+}
+
+/*
+ * List of processes that are tracked by LatencyTOP.
+ */
+int
+lt_stat_proc_list_create(pid_t ** plist, id_t ** tlist)
+{
+	if (plist == NULL) {
+		return (-1);
+	}
+
+	if (stat_system == NULL || stat_system->lt_sc_children == NULL) {
+		*plist = NULL;
+
+		if (tlist != NULL) {
+			*tlist = NULL;
+		}
+
+		return (0);
+	}
+
+	if (tlist == NULL) {
+		return (plist_create(plist));
+	} else {
+		return (tlist_create(plist, tlist));
+	}
+}
+
+/*
+ * Free memory allocated by lt_stat_proc_list_create().
+ */
+void
+lt_stat_proc_list_free(pid_t *plist, id_t *tlist)
+{
+	if (plist != NULL) {
+		free(plist);
+	}
+
+	if (tlist != NULL) {
+		free(tlist);
+	}
+}
+
+/*
+ * Get executable name of the given process (ID).
+ */
+const char *
+lt_stat_proc_get_name(pid_t pid)
+{
+	lt_stat_collection_t *stat_p = NULL;
+
+	if (stat_system == NULL || stat_system->lt_sc_children == NULL) {
+		return (NULL);
+	}
+
+	stat_p = (lt_stat_collection_t *)g_hash_table_lookup(
+	    stat_system->lt_sc_children, LT_INT_TO_POINTER(pid));
+
+	if (stat_p != NULL) {
+		return (stat_p->lt_sc_name);
+	} else   {
+		return (NULL);
+	}
+}
+
+/*
+ * Get number of threads.
+ */
+int
+lt_stat_proc_get_nthreads(pid_t pid)
+{
+	lt_stat_collection_t *stat_p = NULL;
+
+	if (stat_system == NULL || stat_system->lt_sc_children == NULL) {
+		return (0);
+	}
+
+	stat_p = (lt_stat_collection_t *)g_hash_table_lookup(
+	    stat_system->lt_sc_children, LT_INT_TO_POINTER(pid));
+
+	if (stat_p != NULL) {
+		return (g_hash_table_size(stat_p->lt_sc_children));
+	} else   {
+		return (0);
+	}
+}
+
+/*
+ * Update statistics for synchronization objects.
+ */
+void
+lt_stat_update_sobj(pid_t pid, id_t tid, int stype,
+    unsigned long long wchan,
+    lt_stat_type_t type, uint64_t value)
+{
+	lt_sobj_id_t id;
+	lt_sobj_t *sobj;
+	int cause_id;
+	lt_stat_collection_t *stat_t = NULL;
+
+	stat_t = get_stat_c(pid, tid);
+
+	if (stat_t == NULL) {
+		return;
+	}
+
+	id.lt_soi_type = stype;
+	id.lt_soi_addr = wchan;
+	sobj = lookup_sobj(&id);
+
+	if (sobj == NULL) {
+		return;
+	}
+
+	cause_id = sobj->lt_so_cause_id;
+
+	update_stat_entry(stat_t, cause_id, type, value,
+	    sobj->lt_so_string, GROUP_SOBJ);
+}
