1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright (c) 2008-2009, Intel Corporation. 23 * All Rights Reserved. 24 */ 25 26 #include <unistd.h> 27 #include <getopt.h> 28 #include <stdio.h> 29 #include <string.h> 30 #include <stdlib.h> 31 #include <limits.h> 32 #include <libgen.h> 33 #include <signal.h> 34 #include "latencytop.h" 35 36 #define CMPOPT(a, b) strncmp((a), (b), sizeof (b)) 37 38 /* 39 * This variable is used to check if "dynamic variable drop" in dtrace 40 * has happened. 41 */ 42 boolean_t lt_drop_detected = 0; 43 44 lt_config_t g_config; 45 46 typedef enum { 47 LT_CMDOPT_INTERVAL, 48 LT_CMDOPT_LOG_FILE, 49 LT_CMDOPT_LOG_LEVEL, 50 LT_CMDOPT_LOG_INTERVAL, 51 LT_CMDOPT_CONFIG_FILE, 52 LT_CMDOPT_F_FILTER, 53 LT_CMDOPT_F_SCHED, 54 LT_CMDOPT_F_SOBJ, 55 LT_CMDOPT_F_LOW, 56 LT_CMDOPT__LAST /* Must be last one */ 57 } lt_cmd_option_id_t; 58 59 /* 60 * Check for duplicate command line options. 61 * Returns TRUE if duplicate options with different values are found, 62 * returns FALSE otherwise. 63 */ 64 static int 65 check_opt_dup(lt_cmd_option_id_t id, uint64_t value) { 66 67 static int opt_set[(int)LT_CMDOPT__LAST]; 68 static uint64_t opt_val[(int)LT_CMDOPT__LAST]; 69 70 const char *errmsg[] = { 71 "-t is set more than once with different values.", 72 "-o is set more than once.", 73 "-k is set more than once with different values.", 74 "-l is set more than once with different values.", 75 "-c is set more than once.", 76 "-f [no]filter is set more than once with different values.", 77 "-f [no]sched is set more than once with different values.", 78 "-f [no]sobj is set more than once with different values.", 79 "-f [no]low is set more than once with different values.", 80 }; 81 82 g_assert(sizeof (errmsg)/sizeof (errmsg[0]) == (int)LT_CMDOPT__LAST); 83 84 if (!opt_set[(int)id]) { 85 opt_set[(int)id] = TRUE; 86 opt_val[(int)id] = value; 87 return (FALSE); 88 } 89 90 if (opt_val[(int)id] != value) { 91 (void) fprintf(stderr, "%s\n", errmsg[(int)id]); 92 return (TRUE); 93 } 94 95 return (FALSE); 96 } 97 98 /* 99 * Print command-line help message. 100 */ 101 static void 102 print_usage(const char *execname, int long_help) 103 { 104 char buffer[PATH_MAX]; 105 (void) snprintf(buffer, sizeof (buffer), "%s", execname); 106 107 if (!long_help) { 108 /* Print short help to stderr. */ 109 (void) fprintf(stderr, "Usage: %s [option(s)], ", 110 basename(buffer)); 111 (void) fprintf(stderr, "use '%s -h' for details.\n", 112 basename(buffer)); 113 return; 114 } 115 116 (void) printf("Usage: %s [option(s)]\n", basename(buffer)); 117 (void) printf("Options:\n" 118 " -h, --help\n" 119 " Print this help.\n" 120 " -t, --interval TIME\n" 121 " Set refresh interval to TIME. " 122 "Valid range [1...60] seconds, default = 5\n" 123 /* 124 * Option "-c, --config FILE" is not user-visible for now. 125 * When we have chance to properly document the format of translation 126 * rules, we'll make it user-visible. 127 */ 128 " -o, --output-log-file FILE\n" 129 " Output kernel log to FILE. Default = " 130 DEFAULT_KLOG_FILE "\n" 131 " -k, --kernel-log-level LEVEL\n" 132 " Set kernel log level to LEVEL.\n" 133 " 0(default) = None, 1 = Unmapped, 2 = Mapped, 3 = All.\n" 134 " -f, --feature [no]feature1,[no]feature2,...\n" 135 " Enable/disable features in LatencyTOP.\n" 136 " [no]filter:\n" 137 " Filter large interruptible latencies, e.g. sleep.\n" 138 " [no]sched:\n" 139 " Monitors sched (PID=0).\n" 140 " [no]sobj:\n" 141 " Monitors synchronization objects.\n" 142 " [no]low:\n" 143 " Lower overhead by sampling small latencies.\n" 144 " -l, --log-period TIME\n" 145 " Write and restart log every TIME seconds, TIME >= 60\n"); 146 } 147 148 /* 149 * Properly exit latencytop when it receives SIGINT or SIGTERM. 150 */ 151 /* ARGSUSED */ 152 static void 153 signal_handler(int sig) 154 { 155 lt_gpipe_break("q"); 156 } 157 158 /* 159 * Convert string to integer. It returns error if extra characters are found. 160 */ 161 static int 162 to_int(const char *str, int *result) 163 { 164 char *tail = NULL; 165 long ret; 166 167 if (str == NULL || result == NULL) { 168 return (-1); 169 } 170 171 ret = strtol(str, &tail, 10); 172 173 if (tail != NULL && *tail != '\0') { 174 return (-1); 175 } 176 177 *result = (int)ret; 178 179 return (0); 180 } 181 182 /* 183 * The main function. 184 */ 185 int 186 main(int argc, char *argv[]) 187 { 188 const char *opt_string = "t:o:k:hf:l:c:"; 189 struct option const longopts[] = { 190 {"interval", required_argument, NULL, 't'}, 191 {"output-log-file", required_argument, NULL, 'o'}, 192 {"kernel-log-level", required_argument, NULL, 'k'}, 193 {"help", no_argument, NULL, 'h'}, 194 {"feature", required_argument, NULL, 'f'}, 195 {"log-period", required_argument, NULL, 'l'}, 196 {"config", required_argument, NULL, 'c'}, 197 {NULL, 0, NULL, 0} 198 }; 199 200 int optc; 201 int longind = 0; 202 int running = 1; 203 int unknown_option = FALSE; 204 int refresh_interval = 5; 205 int klog_level = 0; 206 int log_interval = 0; 207 long long last_logged = 0; 208 char *token = NULL; 209 int retval = 0; 210 int gpipe; 211 int err; 212 uint64_t collect_end; 213 uint64_t current_time; 214 uint64_t delta_time; 215 char logfile[PATH_MAX] = ""; 216 boolean_t no_dtrace_cleanup = B_TRUE; 217 218 lt_gpipe_init(); 219 (void) signal(SIGINT, signal_handler); 220 (void) signal(SIGTERM, signal_handler); 221 222 /* Default global settings */ 223 g_config.lt_cfg_enable_filter = 0; 224 g_config.lt_cfg_trace_sched = 0; 225 g_config.lt_cfg_trace_syncobj = 1; 226 g_config.lt_cfg_low_overhead_mode = 0; 227 /* dtrace snapshot every 1 second */ 228 g_config.lt_cfg_snap_interval = 1000; 229 #ifdef EMBED_CONFIGS 230 g_config.lt_cfg_config_name = NULL; 231 #else 232 g_config.lt_cfg_config_name = lt_strdup(DEFAULT_CONFIG_NAME); 233 #endif 234 235 /* Parse command line arguments. */ 236 while ((optc = getopt_long(argc, argv, opt_string, 237 longopts, &longind)) != -1) { 238 switch (optc) { 239 case 'h': 240 print_usage(argv[0], TRUE); 241 goto end_none; 242 case 't': 243 if (to_int(optarg, &refresh_interval) != 0 || 244 refresh_interval < 1 || refresh_interval > 60) { 245 lt_display_error( 246 "Invalid refresh interval: %s\n", optarg); 247 unknown_option = TRUE; 248 } else if (check_opt_dup(LT_CMDOPT_INTERVAL, 249 refresh_interval)) { 250 unknown_option = TRUE; 251 } 252 253 break; 254 case 'k': 255 if (to_int(optarg, &klog_level) != 0 || 256 lt_klog_set_log_level(klog_level) != 0) { 257 lt_display_error( 258 "Invalid log level: %s\n", optarg); 259 unknown_option = TRUE; 260 } else if (check_opt_dup(LT_CMDOPT_LOG_LEVEL, 261 refresh_interval)) { 262 unknown_option = TRUE; 263 } 264 265 break; 266 case 'o': 267 if (check_opt_dup(LT_CMDOPT_LOG_FILE, optind)) { 268 unknown_option = TRUE; 269 } else if (strlen(optarg) >= sizeof (logfile)) { 270 lt_display_error( 271 "Log file name is too long: %s\n", 272 optarg); 273 unknown_option = TRUE; 274 } else { 275 (void) strncpy(logfile, optarg, 276 sizeof (logfile)); 277 } 278 279 break; 280 case 'f': 281 for (token = strtok(optarg, ","); token != NULL; 282 token = strtok(NULL, ",")) { 283 int v = TRUE; 284 285 if (strncmp(token, "no", 2) == 0) { 286 v = FALSE; 287 token = &token[2]; 288 } 289 290 if (CMPOPT(token, "filter") == 0) { 291 if (check_opt_dup(LT_CMDOPT_F_FILTER, 292 v)) { 293 unknown_option = TRUE; 294 } else { 295 g_config.lt_cfg_enable_filter 296 = v; 297 } 298 } else if (CMPOPT(token, "sched") == 0) { 299 if (check_opt_dup(LT_CMDOPT_F_SCHED, 300 v)) { 301 unknown_option = TRUE; 302 } else { 303 g_config.lt_cfg_trace_sched 304 = v; 305 } 306 } else if (CMPOPT(token, "sobj") == 0) { 307 if (check_opt_dup(LT_CMDOPT_F_SOBJ, 308 v)) { 309 unknown_option = TRUE; 310 } else { 311 g_config.lt_cfg_trace_syncobj 312 = v; 313 } 314 } else if (CMPOPT(token, "low") == 0) { 315 if (check_opt_dup(LT_CMDOPT_F_LOW, 316 v)) { 317 unknown_option = TRUE; 318 } else { 319 g_config. 320 lt_cfg_low_overhead_mode 321 = v; 322 } 323 } else { 324 lt_display_error( 325 "Unknown feature: %s\n", token); 326 unknown_option = TRUE; 327 } 328 } 329 330 break; 331 case 'l': 332 if (to_int(optarg, &log_interval) != 0 || 333 log_interval < 60) { 334 lt_display_error( 335 "Invalid log interval: %s\n", optarg); 336 unknown_option = TRUE; 337 } else if (check_opt_dup(LT_CMDOPT_LOG_INTERVAL, 338 log_interval)) { 339 unknown_option = TRUE; 340 } 341 342 break; 343 case 'c': 344 if (strlen(optarg) >= PATH_MAX) { 345 lt_display_error( 346 "Configuration name is too long.\n"); 347 unknown_option = TRUE; 348 } else if (check_opt_dup(LT_CMDOPT_CONFIG_FILE, 349 optind)) { 350 unknown_option = TRUE; 351 } else { 352 g_config.lt_cfg_config_name = 353 lt_strdup(optarg); 354 } 355 356 break; 357 default: 358 unknown_option = TRUE; 359 break; 360 } 361 } 362 363 if (!unknown_option && strlen(logfile) > 0) { 364 err = lt_klog_set_log_file(logfile); 365 366 if (err == -1) { 367 lt_display_error("Log file name is too long: %s\n", 368 logfile); 369 unknown_option = TRUE; 370 } else if (err == -2) { 371 lt_display_error("Cannot write to log file: %s\n", 372 logfile); 373 unknown_option = TRUE; 374 } 375 } 376 377 /* Throw error for invalid/junk arguments */ 378 if (optind < argc) { 379 int tmpind = optind; 380 (void) fprintf(stderr, "Unknown option(s): "); 381 382 while (tmpind < argc) { 383 (void) fprintf(stderr, "%s ", argv[tmpind++]); 384 } 385 386 (void) fprintf(stderr, "\n"); 387 unknown_option = TRUE; 388 } 389 390 if (unknown_option) { 391 print_usage(argv[0], FALSE); 392 retval = 1; 393 goto end_none; 394 } 395 396 (void) printf("%s\n%s\n", TITLE, COPYRIGHT); 397 398 /* 399 * Initialization 400 */ 401 lt_klog_init(); 402 403 if (lt_table_init() != 0) { 404 lt_display_error("Unable to load configuration table.\n"); 405 retval = 1; 406 goto end_notable; 407 } 408 409 if (lt_dtrace_init() != 0) { 410 lt_display_error("Unable to initialize dtrace.\n"); 411 retval = 1; 412 goto end_nodtrace; 413 } 414 415 last_logged = lt_millisecond(); 416 417 (void) printf("Collecting data for %d seconds...\n", 418 refresh_interval); 419 420 gpipe = lt_gpipe_readfd(); 421 collect_end = last_logged + refresh_interval * 1000; 422 for (;;) { 423 fd_set read_fd; 424 struct timeval timeout; 425 int tsleep = collect_end - lt_millisecond(); 426 427 if (tsleep <= 0) { 428 break; 429 } 430 431 /* 432 * Interval when we call dtrace_status() and collect 433 * aggregated data. 434 */ 435 if (tsleep > g_config.lt_cfg_snap_interval) { 436 tsleep = g_config.lt_cfg_snap_interval; 437 } 438 439 timeout.tv_sec = tsleep / 1000; 440 timeout.tv_usec = (tsleep % 1000) * 1000; 441 442 FD_ZERO(&read_fd); 443 FD_SET(gpipe, &read_fd); 444 445 if (select(gpipe + 1, &read_fd, NULL, NULL, &timeout) > 0) { 446 goto end_ubreak; 447 } 448 449 (void) lt_dtrace_work(0); 450 } 451 452 lt_display_init(); 453 454 do { 455 current_time = lt_millisecond(); 456 457 lt_stat_clear_all(); 458 (void) lt_dtrace_collect(); 459 460 delta_time = current_time; 461 current_time = lt_millisecond(); 462 delta_time = current_time - delta_time; 463 464 if (log_interval > 0 && 465 current_time - last_logged > log_interval * 1000) { 466 lt_klog_write(); 467 last_logged = current_time; 468 } 469 470 running = lt_display_loop(refresh_interval * 1000 - 471 delta_time); 472 473 /* 474 * This is to avoid dynamic variable drop 475 * in DTrace. 476 */ 477 if (lt_drop_detected == B_TRUE) { 478 if (lt_dtrace_deinit() != 0) { 479 no_dtrace_cleanup = B_FALSE; 480 retval = 1; 481 break; 482 } 483 484 lt_drop_detected = B_FALSE; 485 if (lt_dtrace_init() != 0) { 486 retval = 1; 487 break; 488 } 489 } 490 } while (running != 0); 491 492 lt_klog_write(); 493 494 /* Cleanup */ 495 lt_display_deinit(); 496 497 end_ubreak: 498 if (no_dtrace_cleanup == B_FALSE || lt_dtrace_deinit() != 0) 499 retval = 1; 500 501 lt_stat_free_all(); 502 503 end_nodtrace: 504 lt_table_deinit(); 505 506 end_notable: 507 lt_klog_deinit(); 508 509 end_none: 510 lt_gpipe_deinit(); 511 512 if (g_config.lt_cfg_config_name != NULL) { 513 free(g_config.lt_cfg_config_name); 514 } 515 516 return (retval); 517 } 518