latencytop.c revision 10673:b22eb20aa9ca
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
38lt_config_t g_config;
39
40typedef 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 */
58static int
59check_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 */
95static void
96print_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 */
146static void
147signal_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 */
155static int
156to_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 */
179int
180main(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
468end_ubreak:
469	lt_dtrace_deinit();
470	lt_stat_free_all();
471
472end_nodtrace:
473	lt_table_deinit();
474
475end_notable:
476	lt_klog_deinit();
477
478end_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