xref: /titanic_50/usr/src/cmd/latencytop/common/latencytop.c (revision f38cb554a534c6df738be3f4d23327e69888e634)
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 }
570