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