/* * 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 #include #include #include #include #include #include #include #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= | 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); }