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