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
check_opt_dup(lt_cmd_option_id_t id,uint64_t value)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
print_usage(const char * execname,int long_help)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
signal_handler(int sig)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
to_int(const char * str,int * result)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
main(int argc,char * argv[])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 }
570