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 */
42boolean_t lt_drop_detected = 0;
43
44lt_config_t g_config;
45
46typedef 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 */
65static int
66check_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 */
103static void
104print_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 */
157static void
158signal_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 */
166static int
167to_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 */
190int
191main(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
549end_ubreak:
550	if (no_dtrace_cleanup == B_FALSE || lt_dtrace_deinit() != 0)
551		retval = 1;
552
553	lt_stat_free_all();
554
555end_nodtrace:
556	lt_table_deinit();
557
558end_notable:
559	lt_klog_deinit();
560
561end_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