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