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