xref: /illumos-gate/usr/src/cmd/latencytop/latencytop.c (revision 1edba515a3484e0f74b638b203d462b3112ac84d)
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 }
571