OpenSolaris_b135/cmd/latencytop/common/latencytop.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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))

/*
 * This variable is used to check if "dynamic variable drop" in dtrace
 * has happened.
 */
boolean_t lt_drop_detected = 0;

lt_config_t g_config;

typedef enum {
	LT_CMDOPT_INTERVAL,
	LT_CMDOPT_LOG_FILE,
	LT_CMDOPT_LOG_LEVEL,
	LT_CMDOPT_LOG_INTERVAL,
	LT_CMDOPT_CONFIG_FILE,
	LT_CMDOPT_F_FILTER,
	LT_CMDOPT_F_SCHED,
	LT_CMDOPT_F_SOBJ,
	LT_CMDOPT_F_LOW,
	LT_CMDOPT_SELECT,
	LT_CMDOPT__LAST	/* Must be the last one */
} lt_cmd_option_id_t;

/*
 * Check for duplicate command line options.
 * Returns TRUE if duplicate options with different values are found,
 * returns FALSE otherwise.
 */
static int
check_opt_dup(lt_cmd_option_id_t id, uint64_t value) {

	static int opt_set[(int)LT_CMDOPT__LAST];
	static uint64_t opt_val[(int)LT_CMDOPT__LAST];

	const char *errmsg[] = {
		"-t is set more than once with different values.",
		"-o is set more than once.",
		"-k is set more than once with different values.",
		"-l is set more than once with different values.",
		"-c is set more than once.",
		"-f [no]filter is set more than once with different values.",
		"-f [no]sched is set more than once with different values.",
		"-f [no]sobj is set more than once with different values.",
		"-f [no]low is set more than once with different values.",
		"-s is set more than once with different values."
	};

	g_assert(sizeof (errmsg)/sizeof (errmsg[0]) == (int)LT_CMDOPT__LAST);

	if (!opt_set[(int)id]) {
		opt_set[(int)id] = TRUE;
		opt_val[(int)id] = value;
		return (FALSE);
	}

	if (opt_val[(int)id] != value) {
		(void) fprintf(stderr, "%s\n", errmsg[(int)id]);
		return (TRUE);
	}

	return (FALSE);
}

/*
 * Print command-line help message.
 */
static void
print_usage(const char *execname, int long_help)
{
	char buffer[PATH_MAX];
	(void) snprintf(buffer, sizeof (buffer), "%s", execname);

	if (!long_help) {
		/* Print short help to stderr. */
		(void) fprintf(stderr, "Usage: %s [option(s)], ",
		    basename(buffer));
		(void) fprintf(stderr, "use '%s -h' for details.\n",
		    basename(buffer));
		return;
	}

	(void) printf("Usage: %s [option(s)]\n", basename(buffer));
	(void) printf("Options:\n"
	    "    -h, --help\n"
	    "        Print this help.\n"
	    "    -t, --interval TIME\n"
	    "        Set refresh interval to TIME. "
	    "Valid range [1...60] seconds, default = 5\n"
	/*
	 * Option "-c, --config FILE" is not user-visible for now.
	 * When we have chance to properly document the format of translation
	 * rules, we'll make it user-visible.
	 */
	    "    -o, --output-log-file FILE\n"
	    "        Output kernel log to FILE. Default = "
	    DEFAULT_KLOG_FILE "\n"
	    "    -k, --kernel-log-level LEVEL\n"
	    "        Set kernel log level to LEVEL.\n"
	    "        0(default) = None, 1 = Unmapped, 2 = Mapped, 3 = All.\n"
	    "    -f, --feature [no]feature1,[no]feature2,...\n"
	    "        Enable/disable features in LatencyTOP.\n"
	    "        [no]filter:\n"
	    "        Filter large interruptible latencies, e.g. sleep.\n"
	    "        [no]sched:\n"
	    "        Monitors sched (PID=0).\n"
	    "        [no]sobj:\n"
	    "        Monitors synchronization objects.\n"
	    "        [no]low:\n"
	    "        Lower overhead by sampling small latencies.\n"
	    "    -l, --log-period TIME\n"
	    "        Write and restart log every TIME seconds, TIME >= 60\n"
	    "    -s --select [ pid=<pid> | pgid=<pgid> ]\n"
	    "        Monitor only the given process or processes in the "
	    "given process group.\n");
}

/*
 * Properly exit latencytop when it receives SIGINT or SIGTERM.
 */
/* ARGSUSED */
static void
signal_handler(int sig)
{
	lt_gpipe_break("q");
}

/*
 * Convert string to integer. It returns error if extra characters are found.
 */
static int
to_int(const char *str, int *result)
{
	char *tail = NULL;
	long ret;

	if (str == NULL || result == NULL) {
		return (-1);
	}

	ret = strtol(str, &tail, 10);

	if (tail != NULL && *tail != '\0') {
		return (-1);
	}

	*result = (int)ret;

	return (0);
}

/*
 * The main function.
 */
int
main(int argc, char *argv[])
{
	const char *opt_string = "t:o:k:hf:l:c:s:";
	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'},
		{"select", required_argument, NULL, 's'},
		{NULL, 0, NULL, 0}
	};

	int optc;
	int longind = 0;
	int running = 1;
	int unknown_option = FALSE;
	int refresh_interval = 5;
	int klog_level = 0;
	int log_interval = 0;
	long long last_logged = 0;
	char *token = NULL;
	int retval = 0;
	int gpipe;
	int err;
	uint64_t collect_end;
	uint64_t current_time;
	uint64_t delta_time;
	char logfile[PATH_MAX] = "";
	int select_id;
	int select_value;
	char *select_str;
	boolean_t no_dtrace_cleanup = B_TRUE;

	lt_gpipe_init();
	(void) signal(SIGINT, signal_handler);
	(void) signal(SIGTERM, signal_handler);

	/* Default global settings */
	g_config.lt_cfg_enable_filter = 0;
	g_config.lt_cfg_trace_sched = 0;
	g_config.lt_cfg_trace_syncobj = 1;
	g_config.lt_cfg_low_overhead_mode = 0;
	g_config.lt_cfg_trace_pid = 0;
	g_config.lt_cfg_trace_pgid = 0;
	/* dtrace snapshot every 1 second */
	g_config.lt_cfg_snap_interval = 1000;
#ifdef EMBED_CONFIGS
	g_config.lt_cfg_config_name = NULL;
#else
	g_config.lt_cfg_config_name = lt_strdup(DEFAULT_CONFIG_NAME);
#endif

	/* Parse command line arguments. */
	while ((optc = getopt_long(argc, argv, opt_string,
	    longopts, &longind)) != -1) {
		switch (optc) {
		case 'h':
			print_usage(argv[0], TRUE);
			goto end_none;
		case 't':
			if (to_int(optarg, &refresh_interval) != 0 ||
			    refresh_interval < 1 || refresh_interval > 60) {
				lt_display_error(
				    "Invalid refresh interval: %s\n", optarg);
				unknown_option = TRUE;
			} else if (check_opt_dup(LT_CMDOPT_INTERVAL,
			    refresh_interval)) {
				unknown_option = TRUE;
			}

			break;
		case 'k':
			if (to_int(optarg, &klog_level) != 0 ||
			    lt_klog_set_log_level(klog_level) != 0) {
				lt_display_error(
				    "Invalid log level: %s\n", optarg);
				unknown_option = TRUE;
			} else if (check_opt_dup(LT_CMDOPT_LOG_LEVEL,
			    refresh_interval)) {
				unknown_option = TRUE;
			}

			break;
		case 'o':
			if (check_opt_dup(LT_CMDOPT_LOG_FILE, optind)) {
				unknown_option = TRUE;
			} else if (strlen(optarg) >= sizeof (logfile)) {
				lt_display_error(
				    "Log file name is too long: %s\n",
				    optarg);
				unknown_option = TRUE;
			} else {
				(void) strncpy(logfile, optarg,
				    sizeof (logfile));
			}

			break;
		case 'f':
			for (token = strtok(optarg, ","); token != NULL;
			    token = strtok(NULL, ",")) {
				int v = TRUE;

				if (strncmp(token, "no", 2) == 0) {
					v = FALSE;
					token = &token[2];
				}

				if (CMPOPT(token, "filter") == 0) {
					if (check_opt_dup(LT_CMDOPT_F_FILTER,
					    v)) {
						unknown_option = TRUE;
					} else {
						g_config.lt_cfg_enable_filter
						    = v;
					}
				} else if (CMPOPT(token, "sched") == 0) {
					if (check_opt_dup(LT_CMDOPT_F_SCHED,
					    v)) {
						unknown_option = TRUE;
					} else {
						g_config.lt_cfg_trace_sched
						    = v;
					}
				} else if (CMPOPT(token, "sobj") == 0) {
					if (check_opt_dup(LT_CMDOPT_F_SOBJ,
					    v)) {
						unknown_option = TRUE;
					} else {
						g_config.lt_cfg_trace_syncobj
						    = v;
					}
				} else if (CMPOPT(token, "low") == 0) {
					if (check_opt_dup(LT_CMDOPT_F_LOW,
					    v)) {
						unknown_option = TRUE;
					} else {
						g_config.
						    lt_cfg_low_overhead_mode
						    = v;
					}
				} else {
					lt_display_error(
					    "Unknown feature: %s\n", token);
					unknown_option = TRUE;
				}
			}

			break;
		case 'l':
			if (to_int(optarg, &log_interval) != 0 ||
			    log_interval < 60) {
				lt_display_error(
				    "Invalid log interval: %s\n", optarg);
				unknown_option = TRUE;
			} else if (check_opt_dup(LT_CMDOPT_LOG_INTERVAL,
			    log_interval)) {
				unknown_option = TRUE;
			}

			break;
		case 'c':
			if (strlen(optarg) >= PATH_MAX) {
				lt_display_error(
				    "Configuration name is too long.\n");
				unknown_option = TRUE;
			} else if (check_opt_dup(LT_CMDOPT_CONFIG_FILE,
			    optind)) {
				unknown_option = TRUE;
			} else {
				g_config.lt_cfg_config_name =
				    lt_strdup(optarg);
			}

			break;
		case 's':
			if (strncmp(optarg, "pid=", 4) == 0) {
				select_id = 0;
				select_str = &optarg[4];
			} else if (strncmp(optarg, "pgid=", 5) == 0) {
				select_id = 1;
				select_str = &optarg[5];
			} else {
				lt_display_error(
				    "Invalid select option: %s\n", optarg);
				unknown_option = TRUE;
				break;
			}

			if (to_int(select_str, &select_value) != 0) {
				lt_display_error(
				    "Invalid select option: %s\n", optarg);
				unknown_option = TRUE;
				break;
			}

			if (select_value <= 0) {
				lt_display_error(
				    "Process/process group ID must be "
				    "greater than 0: %s\n", optarg);
				unknown_option = TRUE;
				break;
			}

			if (check_opt_dup(LT_CMDOPT_SELECT,
			    (((uint64_t)select_id) << 32) | select_value)) {
				unknown_option = TRUE;
				break;
			}

			if (select_id == 0) {
				g_config.lt_cfg_trace_pid = select_value;
			} else {
				g_config.lt_cfg_trace_pgid = select_value;
			}
			break;
		default:
			unknown_option = TRUE;
			break;
		}
	}

	if (!unknown_option && strlen(logfile) > 0) {
		err = lt_klog_set_log_file(logfile);

		if (err == -1) {
			lt_display_error("Log file name is too long: %s\n",
			    logfile);
			unknown_option = TRUE;
		} else if (err == -2) {
			lt_display_error("Cannot write to log file: %s\n",
			    logfile);
			unknown_option = TRUE;
		}
	}

	/* Throw error for invalid/junk arguments */
	if (optind  < argc) {
		int tmpind = optind;
		(void) fprintf(stderr, "Unknown option(s): ");

		while (tmpind < argc) {
			(void) fprintf(stderr, "%s ", argv[tmpind++]);
		}

		(void) fprintf(stderr, "\n");
		unknown_option = TRUE;
	}

	if (unknown_option) {
		print_usage(argv[0], FALSE);
		retval = 1;
		goto end_none;
	}

	(void) printf("%s\n%s\n", TITLE, COPYRIGHT);

	/*
	 * Initialization
	 */
	lt_klog_init();

	if (lt_table_init() != 0) {
		lt_display_error("Unable to load configuration table.\n");
		retval = 1;
		goto end_notable;
	}

	if (lt_dtrace_init() != 0) {
		lt_display_error("Unable to initialize dtrace.\n");
		retval = 1;
		goto end_nodtrace;
	}

	last_logged = lt_millisecond();

	(void) printf("Collecting data for %d seconds...\n",
	    refresh_interval);

	gpipe = lt_gpipe_readfd();
	collect_end = last_logged + refresh_interval * 1000;
	for (;;) {
		fd_set read_fd;
		struct timeval timeout;
		int tsleep = collect_end - lt_millisecond();

		if (tsleep <= 0) {
			break;
		}

		/*
		 * Interval when we call dtrace_status() and collect
		 * aggregated data.
		 */
		if (tsleep > g_config.lt_cfg_snap_interval) {
			tsleep = g_config.lt_cfg_snap_interval;
		}

		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);

		/*
		 * This is to avoid dynamic variable drop
		 * in DTrace.
		 */
		if (lt_drop_detected == B_TRUE) {
			if (lt_dtrace_deinit() != 0) {
				no_dtrace_cleanup = B_FALSE;
				retval = 1;
				break;
			}

			lt_drop_detected = B_FALSE;
			if (lt_dtrace_init() != 0) {
				retval = 1;
				break;
			}
		}
	} while (running != 0);

	lt_klog_write();

	/* Cleanup */
	lt_display_deinit();

end_ubreak:
	if (no_dtrace_cleanup == B_FALSE || lt_dtrace_deinit() != 0)
		retval = 1;

	lt_stat_free_all();

end_nodtrace:
	lt_table_deinit();

end_notable:
	lt_klog_deinit();

end_none:
	lt_gpipe_deinit();

	if (g_config.lt_cfg_config_name != NULL) {
		free(g_config.lt_cfg_config_name);
	}

	return (retval);
}