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 <stdlib.h>
27 #include <string.h>
28 #include <memory.h>
29 #include <stdio.h>
30 #include <ctype.h>
31
32 #include "latencytop.h"
33
34 /*
35 * Structure that holds detail of a cause.
36 */
37 typedef struct {
38 int lt_c_cause_id;
39 int lt_c_flags;
40 char *lt_c_name;
41 } lt_cause_t;
42
43 /*
44 * Structure that represents a matched cause.
45 */
46 typedef struct {
47 int lt_mt_priority;
48 int lt_mt_cause_id;
49 } lt_match_t;
50
51 /* All lt_cause_t that are created. */
52 static GHashTable *cause_lookup = NULL;
53 static GPtrArray *causes_array = NULL;
54 static int causes_array_len = 0;
55
56 /*
57 * This hash table maps a symbol to a cause.
58 * key is of type "char *" and value is of type "lt_match_t *".
59 */
60 static GHashTable *symbol_lookup_table = NULL;
61
62 /*
63 * The dtrace translation rules we get from the script
64 */
65 char *dtrans = NULL;
66
67 /*
68 * These structures are only used inside .trans parser.
69 */
70 typedef struct {
71 int lt_dm_priority;
72 char *lt_dm_macro;
73 } lt_dmacro_t;
74
75 typedef struct {
76 GSequence *lt_pr_cmd_disable;
77 GHashTable *lt_pr_dmacro;
78 } lt_parser_t;
79
80 /* ARGSUSED */
81 static void
free_cause(lt_cause_t * cause,void * user)82 free_cause(lt_cause_t *cause, void *user)
83 {
84 g_assert(cause != NULL && cause->lt_c_name != NULL);
85
86 free(cause->lt_c_name);
87 free(cause);
88 }
89
90 static void
free_dmacro(lt_dmacro_t * d)91 free_dmacro(lt_dmacro_t *d)
92 {
93 g_assert(d->lt_dm_macro != NULL);
94 free(d->lt_dm_macro);
95 free(d);
96 }
97
98 /*
99 * Add a cause.
100 */
101 static lt_cause_t *
new_cause(char * name,int flags)102 new_cause(char *name, int flags)
103 {
104 lt_cause_t *entry;
105
106 g_assert(name != NULL);
107
108 entry = (lt_cause_t *)lt_malloc(sizeof (lt_cause_t));
109 entry->lt_c_flags = flags;
110 entry->lt_c_name = name;
111 entry->lt_c_cause_id = causes_array_len;
112
113 g_ptr_array_add(causes_array, entry);
114 ++causes_array_len;
115
116 return (entry);
117 }
118
119 /*
120 * Set a cause to "disabled" state.
121 */
122 static void
disable_cause(char * cause_str,GHashTable * cause_table)123 disable_cause(char *cause_str, GHashTable *cause_table)
124 {
125 lt_cause_t *cause;
126
127 cause = (lt_cause_t *)g_hash_table_lookup(cause_table, cause_str);
128
129 if (cause != NULL) {
130 cause->lt_c_flags |= CAUSE_FLAG_DISABLED;
131 }
132 }
133
134 /*
135 * Helper functions that reads a line from a character array.
136 */
137 static int
read_line_from_mem(const char * mem,int mem_len,char * line,int line_len,int * index)138 read_line_from_mem(const char *mem, int mem_len, char *line, int line_len,
139 int *index)
140 {
141 g_assert(mem != NULL && line != NULL && index != NULL);
142
143 if (line_len <= 0 || mem_len <= 0) {
144 return (0);
145 }
146
147 if (*index >= mem_len) {
148 return (0);
149 }
150
151 while (line_len > 1 && *index < mem_len) {
152 *line = mem[(*index)++];
153 --line_len;
154 ++line;
155
156 if (*(line-1) == '\r' || *(line-1) == '\n') {
157 break;
158 }
159 }
160 *line = '\0';
161
162 return (1);
163 }
164
165 /*
166 * Parse special command from configuration file. Special command
167 * has the following format :
168
169 * disable_cause <cause name>
170 */
171 static int
parse_config_cmd(char * begin,lt_parser_t * parser)172 parse_config_cmd(char *begin, lt_parser_t *parser)
173 {
174 char *tmp;
175 char old_chr = 0;
176
177 /*
178 * disable_cause FSFlush Daemon
179 * ^
180 */
181 if (*begin == '\0') {
182 return (0);
183 }
184
185 for (tmp = begin;
186 *tmp != '\0' && !isspace(*tmp);
187 ++tmp) {
188 }
189 old_chr = *tmp;
190 *tmp = '\0';
191
192 if (strcmp("disable_cause", begin) == 0) {
193 if (old_chr == '\0') {
194 /* Must have an argument */
195 lt_display_error(
196 "Invalid command format: %s\n",
197 begin);
198 return (-1);
199 }
200
201 begin = tmp+1;
202 while (isspace(*begin)) {
203 ++begin;
204 }
205
206 g_sequence_append(parser->lt_pr_cmd_disable,
207 lt_strdup(begin));
208 } else {
209 *tmp = old_chr;
210 lt_display_error(
211 "Unknown command: %s\n", begin);
212 return (-1);
213 }
214
215 return (0);
216 }
217
218 /*
219 * Parse symbol translation from configuration file. Symbol translation
220 * has the following format :
221 *
222 * <priority> <symbol name> <cause>
223 *
224 * Finally check if that cause has already been mapped.
225 */
226 static int
parse_sym_trans(char * begin)227 parse_sym_trans(char *begin)
228 {
229 int priority = 0;
230 char *match;
231 char *match_dup;
232 char *cause_str;
233 lt_cause_t *cause;
234 lt_match_t *match_entry;
235 char *tmp;
236
237 /*
238 * 10 genunix`pread Syscall pread
239 * ^
240 */
241 priority = strtol(begin, &tmp, 10);
242
243 if (tmp == begin || priority == 0) {
244 return (-1);
245 }
246
247 begin = tmp;
248
249 /*
250 * 10 genunix`pread Syscall pread
251 * --^
252 */
253
254 if (!isspace(*begin)) {
255 /* At least one space char after <priority> */
256 return (-1);
257 }
258
259 while (isspace(*begin)) {
260 ++begin;
261 }
262
263 if (*begin == 0) {
264 return (-1);
265 }
266
267 /*
268 * 10 genunix`pread Syscall pread
269 * -----^
270 */
271 for (tmp = begin;
272 *tmp != '\0' && !isspace(*tmp);
273 ++tmp) {
274 }
275
276 if (*tmp == '\0') {
277 return (-1);
278 }
279
280 *tmp = '\0';
281 match = begin;
282
283 /* Check if we have mapped this function before. */
284 match_entry = (lt_match_t *)
285 g_hash_table_lookup(symbol_lookup_table, match);
286
287 if (match_entry != NULL &&
288 HIGHER_PRIORITY(match_entry->lt_mt_priority, priority)) {
289 /* We already have a higher entry. Ignore this. */
290 return (0);
291 }
292
293 begin = tmp + 1;
294
295 /*
296 * 10 genunix`pread Syscall pread
297 * -------------------------------------^
298 */
299 while (isspace(*begin)) {
300 ++begin;
301 }
302
303 if (*begin == 0) {
304 return (-1);
305 }
306
307 cause_str = begin;
308
309 /* Check if we have mapped this cause before. */
310 cause = (lt_cause_t *)
311 g_hash_table_lookup(cause_lookup, cause_str);
312
313 if (cause == NULL) {
314 char *cause_dup = lt_strdup(cause_str);
315 cause = new_cause(cause_dup, 0);
316 g_hash_table_insert(cause_lookup, cause_dup, cause);
317 }
318
319 match_entry = (lt_match_t *)lt_malloc(sizeof (lt_match_t));
320 match_entry->lt_mt_priority = priority;
321 match_entry->lt_mt_cause_id = cause->lt_c_cause_id;
322 match_dup = lt_strdup(match);
323
324 g_hash_table_insert(symbol_lookup_table, match_dup,
325 match_entry);
326
327 return (0);
328 }
329
330 /*
331 * Parse D macro. D macros have the following format :
332 *
333 * <priority> <entry probe> <return probe> <cause>
334 *
335 * Finally check if that cause has already been mapped.
336 */
337 static int
parse_dmacro(char * begin,lt_parser_t * parser)338 parse_dmacro(char *begin, lt_parser_t *parser)
339 {
340 int priority = 0;
341 char *entryprobe;
342 char *returnprobe;
343 char *cause_str;
344 char buf[512];
345 char probepair[512];
346 char *tmp = NULL;
347 lt_cause_t *cause;
348 lt_dmacro_t *dmacro;
349
350 /*
351 * 10 syscall::pread:entry syscall::pread:return Syscall pread
352 * ^
353 */
354 priority = strtol(begin, &tmp, 10);
355
356 if (tmp == begin || priority == 0) {
357 return (-1);
358 }
359
360 begin = tmp;
361
362 /*
363 * 10 syscall::pread:entry syscall::pread:return Syscall pread
364 * --^
365 */
366 while (isspace(*begin)) {
367 ++begin;
368 }
369
370 if (*begin == 0) {
371 return (-1);
372 }
373
374 /*
375 * 10 syscall::pread:entry syscall::pread:return Syscall pread
376 * -----^
377 */
378 for (tmp = begin;
379 *tmp != '\0' && !isspace(*tmp);
380 ++tmp) {
381 }
382
383 if (*tmp == '\0') {
384 return (-1);
385 }
386
387 *tmp = '\0';
388 entryprobe = begin;
389 begin = tmp + 1;
390
391 while (isspace(*begin)) {
392 ++begin;
393 }
394
395 /*
396 * 10 syscall::pread:entry syscall::pread:return Syscall pread
397 * -----------------------------^
398 */
399 for (tmp = begin;
400 *tmp != '\0' && !isspace(*tmp);
401 ++tmp) {
402 }
403
404 if (*tmp == '\0') {
405 return (-1);
406 }
407
408 *tmp = '\0';
409 returnprobe = begin;
410 begin = tmp + 1;
411
412 while (isspace(*begin)) {
413 ++begin;
414 }
415
416 /*
417 * 10 syscall::pread:entry syscall::pread:return Syscall pread
418 * -----------------------------------------------------^
419 */
420 if (*begin == 0) {
421 return (-1);
422 }
423
424 cause_str = begin;
425
426 dmacro = NULL;
427
428 /* Check if we have mapped this cause before. */
429 cause = (lt_cause_t *)
430 g_hash_table_lookup(cause_lookup, cause_str);
431
432 if (cause == NULL) {
433 char *cause_dup = lt_strdup(cause_str);
434 cause = new_cause(cause_dup, 0);
435 g_hash_table_insert(cause_lookup, cause_dup, cause);
436 }
437
438 (void) snprintf(buf, sizeof (buf), "\nTRANSLATE(%s, %s, \"%s\", %d)\n",
439 entryprobe, returnprobe, cause_str, priority);
440
441 (void) snprintf(probepair, sizeof (probepair), "%s %s", entryprobe,
442 returnprobe);
443
444 g_assert(cause != NULL);
445 g_assert(parser->lt_pr_dmacro != NULL);
446
447 dmacro = g_hash_table_lookup(parser->lt_pr_dmacro, probepair);
448
449 if (dmacro == NULL) {
450 dmacro = (lt_dmacro_t *)lt_malloc(sizeof (lt_dmacro_t));
451 dmacro->lt_dm_priority = priority;
452 dmacro->lt_dm_macro = lt_strdup(buf);
453 g_hash_table_insert(parser->lt_pr_dmacro, lt_strdup(probepair),
454 dmacro);
455 } else if (dmacro->lt_dm_priority < priority) {
456 free(dmacro->lt_dm_macro);
457 dmacro->lt_dm_priority = priority;
458 dmacro->lt_dm_macro = lt_strdup(buf);
459 }
460
461 return (0);
462 }
463
464 /*
465 * Helper function to collect TRANSLATE() macros.
466 */
467 /* ARGSUSED */
468 static void
genscript(void * key,lt_dmacro_t * dmacro,GString * str)469 genscript(void *key, lt_dmacro_t *dmacro, GString *str)
470 {
471 g_string_append(str, dmacro->lt_dm_macro);
472 }
473
474 /*
475 * Main logic that parses translation rules one line at a time,
476 * and creates a lookup table from it. The syntax for the translation
477 * is as follows :
478 *
479 * # <--- comment
480 * D <D macro rule> <--- D macro
481 * S <Symbol translation> <--- Symbols
482 * disable_cause <cause> <--- special command
483 */
484 static int
parse_config(const char * work,int work_len)485 parse_config(const char *work, int work_len)
486 {
487 char line[256];
488 int len;
489 char *begin, *end;
490 int current = 0;
491 lt_parser_t parser;
492 int ret = 0;
493 char flag;
494 GString *script;
495
496 cause_lookup = g_hash_table_new(g_str_hash, g_str_equal);
497 lt_check_null(cause_lookup);
498
499 parser.lt_pr_cmd_disable = g_sequence_new((GDestroyNotify)free);
500 lt_check_null(parser.lt_pr_cmd_disable);
501
502 parser.lt_pr_dmacro = g_hash_table_new_full(g_str_hash,
503 g_str_equal, (GDestroyNotify)free, (GDestroyNotify)free_dmacro);
504 lt_check_null(parser.lt_pr_dmacro);
505
506 while (read_line_from_mem(work, work_len, line, sizeof (line),
507 ¤t)) {
508 len = strlen(line);
509
510 if (line[len-1] != '\n' && line[len-1] != '\r' &&
511 current < work_len) {
512 lt_display_error("Configuration line too long.\n");
513 goto err;
514 }
515
516 begin = line;
517
518 while (isspace(*begin)) {
519 ++begin;
520 }
521
522 if (*begin == '\0') {
523 /* Ignore empty line */
524 continue;
525 }
526
527 /* Delete trailing spaces. */
528 end = begin + strlen(begin) - 1;
529
530 while (isspace(*end)) {
531 --end;
532 }
533
534 end[1] = '\0';
535
536 flag = *begin;
537 ++begin;
538
539 switch (flag) {
540 case '#':
541 ret = 0;
542 break;
543 case ';':
544 ret = parse_config_cmd(begin, &parser);
545 break;
546 case 'D':
547 case 'd':
548 if (!isspace(*begin)) {
549 lt_display_error(
550 "No space after flag char: %s\n", line);
551 }
552 while (isspace(*begin)) {
553 ++begin;
554 }
555 ret = parse_dmacro(begin, &parser);
556 break;
557 case 'S':
558 case 's':
559 if (!isspace(*begin)) {
560 lt_display_error(
561 "No space after flag char: %s\n", line);
562 }
563 while (isspace(*begin)) {
564 ++begin;
565 }
566 ret = parse_sym_trans(begin);
567 break;
568 default:
569 ret = -1;
570 break;
571 }
572
573 if (ret != 0) {
574 lt_display_error(
575 "Invalid configuration line: %s\n", line);
576 goto err;
577 }
578 }
579
580 script = g_string_new(NULL);
581 g_hash_table_foreach(parser.lt_pr_dmacro, (GHFunc)genscript, script);
582 dtrans = g_string_free(script, FALSE);
583
584 if (dtrans != NULL && strlen(dtrans) == 0) {
585 free(dtrans);
586 dtrans = NULL;
587 }
588
589 g_sequence_foreach(parser.lt_pr_cmd_disable, (GFunc)disable_cause,
590 cause_lookup);
591 g_sequence_free(parser.lt_pr_cmd_disable);
592
593 return (0);
594
595 err:
596 g_sequence_free(parser.lt_pr_cmd_disable);
597 g_hash_table_destroy(parser.lt_pr_dmacro);
598 return (-1);
599
600 }
601
602 /*
603 * Init function, called when latencytop starts.
604 * It loads translation rules from the configuration file. The configuration
605 * file defines some causes and symbols that match those causes.
606 */
607 int
lt_table_init(void)608 lt_table_init(void)
609 {
610 char *config_loaded = NULL;
611 int config_loaded_len = 0;
612 const char *work = NULL;
613 int work_len = 0;
614 lt_cause_t *cause;
615
616 #ifdef EMBED_CONFIGS
617 work = &latencytop_trans_start;
618 work_len = (int)(&latencytop_trans_end - &latencytop_trans_start);
619 #endif
620
621 if (g_config.lt_cfg_config_name != NULL) {
622 FILE *fp;
623 fp = fopen(g_config.lt_cfg_config_name, "r");
624
625 if (NULL == fp) {
626 lt_display_error(
627 "Unable to open configuration file.\n");
628 return (-1);
629 }
630
631 (void) fseek(fp, 0, SEEK_END);
632 config_loaded_len = (int)ftell(fp);
633 config_loaded = (char *)lt_malloc(config_loaded_len);
634 (void) fseek(fp, 0, SEEK_SET);
635
636 /* A zero-byte translation is valid */
637 if (config_loaded_len != 0 &&
638 fread(config_loaded, config_loaded_len, 1, fp) == 0) {
639 lt_display_error(
640 "Unable to read configuration file.\n");
641 (void) fclose(fp);
642 free(config_loaded);
643 return (-1);
644 }
645
646 (void) fclose(fp);
647 (void) printf("Loaded configuration from %s\n",
648 g_config.lt_cfg_config_name);
649
650 work = config_loaded;
651 work_len = config_loaded_len;
652 }
653
654 lt_table_deinit();
655 causes_array = g_ptr_array_new();
656 lt_check_null(causes_array);
657
658 /* 0 is not used, but it is kept as a place for bugs etc. */
659 cause = new_cause(lt_strdup("Nothing"), CAUSE_FLAG_DISABLED);
660 g_assert(cause->lt_c_cause_id == INVALID_CAUSE);
661
662 symbol_lookup_table = g_hash_table_new_full(
663 g_str_hash, g_str_equal,
664 (GDestroyNotify)free, (GDestroyNotify)free);
665 lt_check_null(symbol_lookup_table);
666
667 if (work_len != 0 && parse_config(work, work_len) != 0) {
668 return (-1);
669 }
670
671 if (config_loaded != NULL) {
672 free(config_loaded);
673 }
674
675 return (0);
676 }
677
678 /*
679 * Some causes, such as "lock spinning", do not have stack trace. Names
680 * of such causes are explicitly specified in the D script.
681 * This function resolves such causes and dynamically adds them
682 * to the global tables when they are found first. If auto_create is set
683 * to TRUE, the entry will be created if it is not found.
684 * Return cause_id of the cause.
685 */
686 int
lt_table_cause_from_name(char * name,int auto_create,int flags)687 lt_table_cause_from_name(char *name, int auto_create, int flags)
688 {
689 lt_cause_t *cause = NULL;
690
691 if (cause_lookup == NULL) {
692 cause_lookup = g_hash_table_new(g_str_hash, g_str_equal);
693 lt_check_null(cause_lookup);
694 } else {
695 cause = (lt_cause_t *)
696 g_hash_table_lookup(cause_lookup, name);
697 }
698
699 if (cause == NULL && auto_create) {
700 char *cause_dup;
701
702 if (name[0] == '#') {
703 flags |= CAUSE_FLAG_HIDE_IN_SUMMARY;
704 }
705
706 cause_dup = lt_strdup(name);
707 cause = new_cause(cause_dup, flags);
708 g_hash_table_insert(cause_lookup, cause_dup, cause);
709 }
710
711 return (cause == NULL ? INVALID_CAUSE : cause->lt_c_cause_id);
712 }
713
714 /*
715 * Try to map a symbol on stack to a known cause.
716 * module_func has the format "module_name`function_name".
717 * cause_id and priority will be set if a cause is found.
718 * If cause is found return 1, otherwise return 0.
719 */
720 int
lt_table_cause_from_stack(const char * module_func,int * cause_id,int * priority)721 lt_table_cause_from_stack(const char *module_func, int *cause_id, int *priority)
722 {
723 lt_match_t *match;
724
725 g_assert(module_func != NULL && cause_id != NULL && priority != NULL);
726
727 if (symbol_lookup_table == NULL) {
728 return (0);
729 }
730
731 match = (lt_match_t *)
732 g_hash_table_lookup(symbol_lookup_table, module_func);
733
734 if (match == NULL) {
735 char *func = strchr(module_func, '`');
736
737 if (func != NULL) {
738 match = (lt_match_t *)
739 g_hash_table_lookup(symbol_lookup_table, func);
740 }
741 }
742
743 if (match == NULL) {
744 return (0);
745 } else {
746 *cause_id = match->lt_mt_cause_id;
747 *priority = match->lt_mt_priority;
748 return (1);
749 }
750 }
751
752 /*
753 * Get the display name of a cause. cause_id must be valid,
754 * it is usually returned from lt_table_cause_from_stack() or
755 * lt_table_cause_from_name().
756 */
757 const char *
lt_table_get_cause_name(int cause_id)758 lt_table_get_cause_name(int cause_id)
759 {
760 lt_cause_t *cause;
761
762 if (cause_id < 0 || cause_id >= causes_array_len) {
763 return (NULL);
764 }
765
766 cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id);
767
768 if (cause == NULL) {
769 return (NULL);
770 } else {
771 return (cause->lt_c_name);
772 }
773 }
774
775 /*
776 * Check cause flag.
777 * If CAUSE_ALL_FLAGS is passed in, all flags are returned.
778 */
779 int
lt_table_get_cause_flag(int cause_id,int flag)780 lt_table_get_cause_flag(int cause_id, int flag)
781 {
782 lt_cause_t *cause;
783
784 if (cause_id < 0 || cause_id >= causes_array_len) {
785 return (0);
786 }
787
788 cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id);
789
790 if (cause == NULL) {
791 return (0);
792 } else {
793 return (cause->lt_c_flags & flag);
794 }
795 }
796
797 /*
798 * Append macros to D script, if any.
799 */
800 int
lt_table_append_trans(FILE * fp)801 lt_table_append_trans(FILE *fp)
802 {
803 if (dtrans != NULL) {
804 if (fwrite(dtrans, strlen(dtrans), 1, fp) != 1) {
805 return (-1);
806 }
807 }
808
809 return (0);
810 }
811
812 /*
813 * Clean up function.
814 * Free the resources used for symbol table (symbols, causes etc.).
815 */
816 void
lt_table_deinit(void)817 lt_table_deinit(void)
818 {
819 if (symbol_lookup_table != NULL) {
820 g_hash_table_destroy(symbol_lookup_table);
821 symbol_lookup_table = NULL;
822 }
823
824 if (cause_lookup != NULL) {
825 g_hash_table_destroy(cause_lookup);
826 cause_lookup = NULL;
827 }
828
829 if (causes_array != NULL) {
830 g_ptr_array_foreach(causes_array, (GFunc)free_cause, NULL);
831 g_ptr_array_free(causes_array, TRUE);
832 causes_array = NULL;
833 causes_array_len = 0;
834 }
835
836 if (dtrans != NULL) {
837 g_free(dtrans);
838 dtrans = NULL;
839 }
840 }
841