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
check_opt_dup(lt_cmd_option_id_t id,uint64_t value)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
print_usage(const char * execname,int long_help)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
signal_handler(int sig)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
to_int(const char * str,int * result)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
main(int argc,char * argv[])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