Merge commit '00f1a4f432b3d8aad1aa270e91c44c57f03ef407'
[unleashed.git] / usr / src / cmd / latencytop / common / table.c
blobfcf4bb7a4d6752d939e95d0fac8fc76f86de1142
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright (c) 2008-2009, Intel Corporation.
23 * All Rights Reserved.
26 #include <stdlib.h>
27 #include <string.h>
28 #include <memory.h>
29 #include <stdio.h>
30 #include <ctype.h>
32 #include "latencytop.h"
35 * Structure that holds detail of a cause.
37 typedef struct {
38 int lt_c_cause_id;
39 int lt_c_flags;
40 char *lt_c_name;
41 } lt_cause_t;
44 * Structure that represents a matched cause.
46 typedef struct {
47 int lt_mt_priority;
48 int lt_mt_cause_id;
49 } lt_match_t;
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;
57 * This hash table maps a symbol to a cause.
58 * key is of type "char *" and value is of type "lt_match_t *".
60 static GHashTable *symbol_lookup_table = NULL;
63 * The dtrace translation rules we get from the script
65 char *dtrans = NULL;
68 * These structures are only used inside .trans parser.
70 typedef struct {
71 int lt_dm_priority;
72 char *lt_dm_macro;
73 } lt_dmacro_t;
75 typedef struct {
76 GSequence *lt_pr_cmd_disable;
77 GHashTable *lt_pr_dmacro;
78 } lt_parser_t;
80 /* ARGSUSED */
81 static void
82 free_cause(lt_cause_t *cause, void *user)
84 g_assert(cause != NULL && cause->lt_c_name != NULL);
86 free(cause->lt_c_name);
87 free(cause);
90 static void
91 free_dmacro(lt_dmacro_t *d)
93 g_assert(d->lt_dm_macro != NULL);
94 free(d->lt_dm_macro);
95 free(d);
99 * Add a cause.
101 static lt_cause_t *
102 new_cause(char *name, int flags)
104 lt_cause_t *entry;
106 g_assert(name != NULL);
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;
113 g_ptr_array_add(causes_array, entry);
114 ++causes_array_len;
116 return (entry);
120 * Set a cause to "disabled" state.
122 static void
123 disable_cause(char *cause_str, GHashTable *cause_table)
125 lt_cause_t *cause;
127 cause = (lt_cause_t *)g_hash_table_lookup(cause_table, cause_str);
129 if (cause != NULL) {
130 cause->lt_c_flags |= CAUSE_FLAG_DISABLED;
135 * Helper functions that reads a line from a character array.
137 static int
138 read_line_from_mem(const char *mem, int mem_len, char *line, int line_len,
139 int *index)
141 g_assert(mem != NULL && line != NULL && index != NULL);
143 if (line_len <= 0 || mem_len <= 0) {
144 return (0);
147 if (*index >= mem_len) {
148 return (0);
151 while (line_len > 1 && *index < mem_len) {
152 *line = mem[(*index)++];
153 --line_len;
154 ++line;
156 if (*(line-1) == '\r' || *(line-1) == '\n') {
157 break;
160 *line = '\0';
162 return (1);
166 * Parse special command from configuration file. Special command
167 * has the following format :
169 * disable_cause <cause name>
171 static int
172 parse_config_cmd(char *begin, lt_parser_t *parser)
174 char *tmp;
175 char old_chr = 0;
178 * disable_cause FSFlush Daemon
181 if (*begin == '\0') {
182 return (0);
185 for (tmp = begin;
186 *tmp != '\0' && !isspace(*tmp);
187 ++tmp) {
189 old_chr = *tmp;
190 *tmp = '\0';
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);
201 begin = tmp+1;
202 while (isspace(*begin)) {
203 ++begin;
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);
215 return (0);
219 * Parse symbol translation from configuration file. Symbol translation
220 * has the following format :
222 * <priority> <symbol name> <cause>
224 * Finally check if that cause has already been mapped.
226 static int
227 parse_sym_trans(char *begin)
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;
238 * 10 genunix`pread Syscall pread
241 priority = strtol(begin, &tmp, 10);
243 if (tmp == begin || priority == 0) {
244 return (-1);
247 begin = tmp;
250 * 10 genunix`pread Syscall pread
251 * --^
254 if (!isspace(*begin)) {
255 /* At least one space char after <priority> */
256 return (-1);
259 while (isspace(*begin)) {
260 ++begin;
263 if (*begin == 0) {
264 return (-1);
268 * 10 genunix`pread Syscall pread
269 * -----^
271 for (tmp = begin;
272 *tmp != '\0' && !isspace(*tmp);
273 ++tmp) {
276 if (*tmp == '\0') {
277 return (-1);
280 *tmp = '\0';
281 match = begin;
283 /* Check if we have mapped this function before. */
284 match_entry = (lt_match_t *)
285 g_hash_table_lookup(symbol_lookup_table, match);
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);
293 begin = tmp + 1;
296 * 10 genunix`pread Syscall pread
297 * -------------------------------------^
299 while (isspace(*begin)) {
300 ++begin;
303 if (*begin == 0) {
304 return (-1);
307 cause_str = begin;
309 /* Check if we have mapped this cause before. */
310 cause = (lt_cause_t *)
311 g_hash_table_lookup(cause_lookup, cause_str);
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);
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);
324 g_hash_table_insert(symbol_lookup_table, match_dup,
325 match_entry);
327 return (0);
331 * Parse D macro. D macros have the following format :
333 * <priority> <entry probe> <return probe> <cause>
335 * Finally check if that cause has already been mapped.
337 static int
338 parse_dmacro(char *begin, lt_parser_t *parser)
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;
351 * 10 syscall::pread:entry syscall::pread:return Syscall pread
354 priority = strtol(begin, &tmp, 10);
356 if (tmp == begin || priority == 0) {
357 return (-1);
360 begin = tmp;
363 * 10 syscall::pread:entry syscall::pread:return Syscall pread
364 * --^
366 while (isspace(*begin)) {
367 ++begin;
370 if (*begin == 0) {
371 return (-1);
375 * 10 syscall::pread:entry syscall::pread:return Syscall pread
376 * -----^
378 for (tmp = begin;
379 *tmp != '\0' && !isspace(*tmp);
380 ++tmp) {
383 if (*tmp == '\0') {
384 return (-1);
387 *tmp = '\0';
388 entryprobe = begin;
389 begin = tmp + 1;
391 while (isspace(*begin)) {
392 ++begin;
396 * 10 syscall::pread:entry syscall::pread:return Syscall pread
397 * -----------------------------^
399 for (tmp = begin;
400 *tmp != '\0' && !isspace(*tmp);
401 ++tmp) {
404 if (*tmp == '\0') {
405 return (-1);
408 *tmp = '\0';
409 returnprobe = begin;
410 begin = tmp + 1;
412 while (isspace(*begin)) {
413 ++begin;
417 * 10 syscall::pread:entry syscall::pread:return Syscall pread
418 * -----------------------------------------------------^
420 if (*begin == 0) {
421 return (-1);
424 cause_str = begin;
426 dmacro = NULL;
428 /* Check if we have mapped this cause before. */
429 cause = (lt_cause_t *)
430 g_hash_table_lookup(cause_lookup, cause_str);
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);
438 (void) snprintf(buf, sizeof (buf), "\nTRANSLATE(%s, %s, \"%s\", %d)\n",
439 entryprobe, returnprobe, cause_str, priority);
441 (void) snprintf(probepair, sizeof (probepair), "%s %s", entryprobe,
442 returnprobe);
444 g_assert(cause != NULL);
445 g_assert(parser->lt_pr_dmacro != NULL);
447 dmacro = g_hash_table_lookup(parser->lt_pr_dmacro, probepair);
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);
461 return (0);
465 * Helper function to collect TRANSLATE() macros.
467 /* ARGSUSED */
468 static void
469 genscript(void *key, lt_dmacro_t *dmacro, GString *str)
471 g_string_append(str, dmacro->lt_dm_macro);
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 :
479 * # <--- comment
480 * D <D macro rule> <--- D macro
481 * S <Symbol translation> <--- Symbols
482 * disable_cause <cause> <--- special command
484 static int
485 parse_config(const char *work, int work_len)
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;
496 cause_lookup = g_hash_table_new(g_str_hash, g_str_equal);
497 lt_check_null(cause_lookup);
499 parser.lt_pr_cmd_disable = g_sequence_new((GDestroyNotify)free);
500 lt_check_null(parser.lt_pr_cmd_disable);
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);
506 while (read_line_from_mem(work, work_len, line, sizeof (line),
507 &current)) {
508 len = strlen(line);
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;
516 begin = line;
518 while (isspace(*begin)) {
519 ++begin;
522 if (*begin == '\0') {
523 /* Ignore empty line */
524 continue;
527 /* Delete trailing spaces. */
528 end = begin + strlen(begin) - 1;
530 while (isspace(*end)) {
531 --end;
534 end[1] = '\0';
536 flag = *begin;
537 ++begin;
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);
552 while (isspace(*begin)) {
553 ++begin;
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);
563 while (isspace(*begin)) {
564 ++begin;
566 ret = parse_sym_trans(begin);
567 break;
568 default:
569 ret = -1;
570 break;
573 if (ret != 0) {
574 lt_display_error(
575 "Invalid configuration line: %s\n", line);
576 goto err;
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);
584 if (dtrans != NULL && strlen(dtrans) == 0) {
585 free(dtrans);
586 dtrans = NULL;
589 g_sequence_foreach(parser.lt_pr_cmd_disable, (GFunc)disable_cause,
590 cause_lookup);
591 g_sequence_free(parser.lt_pr_cmd_disable);
593 return (0);
595 err:
596 g_sequence_free(parser.lt_pr_cmd_disable);
597 g_hash_table_destroy(parser.lt_pr_dmacro);
598 return (-1);
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.
608 lt_table_init(void)
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;
616 #ifdef EMBED_CONFIGS
617 work = &latencytop_trans_start;
618 work_len = (int)(&latencytop_trans_end - &latencytop_trans_start);
619 #endif
621 if (g_config.lt_cfg_config_name != NULL) {
622 FILE *fp;
623 fp = fopen(g_config.lt_cfg_config_name, "r");
625 if (NULL == fp) {
626 lt_display_error(
627 "Unable to open configuration file.\n");
628 return (-1);
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);
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);
646 (void) fclose(fp);
647 (void) printf("Loaded configuration from %s\n",
648 g_config.lt_cfg_config_name);
650 work = config_loaded;
651 work_len = config_loaded_len;
654 lt_table_deinit();
655 causes_array = g_ptr_array_new();
656 lt_check_null(causes_array);
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);
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);
667 if (work_len != 0 && parse_config(work, work_len) != 0) {
668 return (-1);
671 if (config_loaded != NULL) {
672 free(config_loaded);
675 return (0);
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.
687 lt_table_cause_from_name(char *name, int auto_create, int flags)
689 lt_cause_t *cause = NULL;
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);
699 if (cause == NULL && auto_create) {
700 char *cause_dup;
702 if (name[0] == '#') {
703 flags |= CAUSE_FLAG_HIDE_IN_SUMMARY;
706 cause_dup = lt_strdup(name);
707 cause = new_cause(cause_dup, flags);
708 g_hash_table_insert(cause_lookup, cause_dup, cause);
711 return (cause == NULL ? INVALID_CAUSE : cause->lt_c_cause_id);
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.
721 lt_table_cause_from_stack(const char *module_func, int *cause_id, int *priority)
723 lt_match_t *match;
725 g_assert(module_func != NULL && cause_id != NULL && priority != NULL);
727 if (symbol_lookup_table == NULL) {
728 return (0);
731 match = (lt_match_t *)
732 g_hash_table_lookup(symbol_lookup_table, module_func);
734 if (match == NULL) {
735 char *func = strchr(module_func, '`');
737 if (func != NULL) {
738 match = (lt_match_t *)
739 g_hash_table_lookup(symbol_lookup_table, func);
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);
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().
757 const char *
758 lt_table_get_cause_name(int cause_id)
760 lt_cause_t *cause;
762 if (cause_id < 0 || cause_id >= causes_array_len) {
763 return (NULL);
766 cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id);
768 if (cause == NULL) {
769 return (NULL);
770 } else {
771 return (cause->lt_c_name);
776 * Check cause flag.
777 * If CAUSE_ALL_FLAGS is passed in, all flags are returned.
780 lt_table_get_cause_flag(int cause_id, int flag)
782 lt_cause_t *cause;
784 if (cause_id < 0 || cause_id >= causes_array_len) {
785 return (0);
788 cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id);
790 if (cause == NULL) {
791 return (0);
792 } else {
793 return (cause->lt_c_flags & flag);
798 * Append macros to D script, if any.
801 lt_table_append_trans(FILE *fp)
803 if (dtrans != NULL) {
804 if (fwrite(dtrans, strlen(dtrans), 1, fp) != 1) {
805 return (-1);
809 return (0);
813 * Clean up function.
814 * Free the resources used for symbol table (symbols, causes etc.).
816 void
817 lt_table_deinit(void)
819 if (symbol_lookup_table != NULL) {
820 g_hash_table_destroy(symbol_lookup_table);
821 symbol_lookup_table = NULL;
824 if (cause_lookup != NULL) {
825 g_hash_table_destroy(cause_lookup);
826 cause_lookup = NULL;
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;
836 if (dtrans != NULL) {
837 g_free(dtrans);
838 dtrans = NULL;