/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2008-2009, Intel Corporation. * All Rights Reserved. */ #include #include #include #include #include #include "latencytop.h" /* * Structure that holds detail of a cause. */ typedef struct { int lt_c_cause_id; int lt_c_flags; char *lt_c_name; } lt_cause_t; /* * Structure that represents a matched cause. */ typedef struct { int lt_mt_priority; int lt_mt_cause_id; } lt_match_t; /* All lt_cause_t that are created. */ static GHashTable *cause_lookup = NULL; static GPtrArray *causes_array = NULL; static int causes_array_len = 0; /* * This hash table maps a symbol to a cause. * key is of type "char *" and value is of type "lt_match_t *". */ static GHashTable *symbol_lookup_table = NULL; /* * The dtrace translation rules we get from the script */ char *dtrans = NULL; /* * These structures are only used inside .trans parser. */ typedef struct { int lt_dm_priority; char *lt_dm_macro; } lt_dmacro_t; typedef struct { GSequence *lt_pr_cmd_disable; GHashTable *lt_pr_dmacro; } lt_parser_t; /* ARGSUSED */ static void free_cause(lt_cause_t *cause, void *user) { g_assert(cause != NULL && cause->lt_c_name != NULL); free(cause->lt_c_name); free(cause); } static void free_dmacro(lt_dmacro_t *d) { g_assert(d->lt_dm_macro != NULL); free(d->lt_dm_macro); free(d); } /* * Add a cause. */ static lt_cause_t * new_cause(char *name, int flags) { lt_cause_t *entry; g_assert(name != NULL); entry = (lt_cause_t *)lt_malloc(sizeof (lt_cause_t)); entry->lt_c_flags = flags; entry->lt_c_name = name; entry->lt_c_cause_id = causes_array_len; g_ptr_array_add(causes_array, entry); ++causes_array_len; return (entry); } /* * Set a cause to "disabled" state. */ static void disable_cause(char *cause_str, GHashTable *cause_table) { lt_cause_t *cause; cause = (lt_cause_t *)g_hash_table_lookup(cause_table, cause_str); if (cause != NULL) { cause->lt_c_flags |= CAUSE_FLAG_DISABLED; } } /* * Helper functions that reads a line from a character array. */ static int read_line_from_mem(const char *mem, int mem_len, char *line, int line_len, int *index) { g_assert(mem != NULL && line != NULL && index != NULL); if (line_len <= 0 || mem_len <= 0) { return (0); } if (*index >= mem_len) { return (0); } while (line_len > 1 && *index < mem_len) { *line = mem[(*index)++]; --line_len; ++line; if (*(line-1) == '\r' || *(line-1) == '\n') { break; } } *line = '\0'; return (1); } /* * Parse special command from configuration file. Special command * has the following format : * disable_cause */ static int parse_config_cmd(char *begin, lt_parser_t *parser) { char *tmp; char old_chr = 0; /* * disable_cause FSFlush Daemon * ^ */ if (*begin == '\0') { return (0); } for (tmp = begin; *tmp != '\0' && !isspace(*tmp); ++tmp) { } old_chr = *tmp; *tmp = '\0'; if (strcmp("disable_cause", begin) == 0) { if (old_chr == '\0') { /* Must have an argument */ lt_display_error( "Invalid command format: %s\n", begin); return (-1); } begin = tmp+1; while (isspace(*begin)) { ++begin; } g_sequence_append(parser->lt_pr_cmd_disable, lt_strdup(begin)); } else { *tmp = old_chr; lt_display_error( "Unknown command: %s\n", begin); return (-1); } return (0); } /* * Parse symbol translation from configuration file. Symbol translation * has the following format : * * * * Finally check if that cause has already been mapped. */ static int parse_sym_trans(char *begin) { int priority = 0; char *match; char *match_dup; char *cause_str; lt_cause_t *cause; lt_match_t *match_entry; char *tmp; /* * 10 genunix`pread Syscall pread * ^ */ priority = strtol(begin, &tmp, 10); if (tmp == begin || priority == 0) { return (-1); } begin = tmp; /* * 10 genunix`pread Syscall pread * --^ */ if (!isspace(*begin)) { /* At least one space char after */ return (-1); } while (isspace(*begin)) { ++begin; } if (*begin == 0) { return (-1); } /* * 10 genunix`pread Syscall pread * -----^ */ for (tmp = begin; *tmp != '\0' && !isspace(*tmp); ++tmp) { } if (*tmp == '\0') { return (-1); } *tmp = '\0'; match = begin; /* Check if we have mapped this function before. */ match_entry = (lt_match_t *) g_hash_table_lookup(symbol_lookup_table, match); if (match_entry != NULL && HIGHER_PRIORITY(match_entry->lt_mt_priority, priority)) { /* We already have a higher entry. Ignore this. */ return (0); } begin = tmp + 1; /* * 10 genunix`pread Syscall pread * -------------------------------------^ */ while (isspace(*begin)) { ++begin; } if (*begin == 0) { return (-1); } cause_str = begin; /* Check if we have mapped this cause before. */ cause = (lt_cause_t *) g_hash_table_lookup(cause_lookup, cause_str); if (cause == NULL) { char *cause_dup = lt_strdup(cause_str); cause = new_cause(cause_dup, 0); g_hash_table_insert(cause_lookup, cause_dup, cause); } match_entry = (lt_match_t *)lt_malloc(sizeof (lt_match_t)); match_entry->lt_mt_priority = priority; match_entry->lt_mt_cause_id = cause->lt_c_cause_id; match_dup = lt_strdup(match); g_hash_table_insert(symbol_lookup_table, match_dup, match_entry); return (0); } /* * Parse D macro. D macros have the following format : * * * * Finally check if that cause has already been mapped. */ static int parse_dmacro(char *begin, lt_parser_t *parser) { int priority = 0; char *entryprobe; char *returnprobe; char *cause_str; char buf[512]; char probepair[512]; char *tmp = NULL; lt_cause_t *cause; lt_dmacro_t *dmacro; /* * 10 syscall::pread:entry syscall::pread:return Syscall pread * ^ */ priority = strtol(begin, &tmp, 10); if (tmp == begin || priority == 0) { return (-1); } begin = tmp; /* * 10 syscall::pread:entry syscall::pread:return Syscall pread * --^ */ while (isspace(*begin)) { ++begin; } if (*begin == 0) { return (-1); } /* * 10 syscall::pread:entry syscall::pread:return Syscall pread * -----^ */ for (tmp = begin; *tmp != '\0' && !isspace(*tmp); ++tmp) { } if (*tmp == '\0') { return (-1); } *tmp = '\0'; entryprobe = begin; begin = tmp + 1; while (isspace(*begin)) { ++begin; } /* * 10 syscall::pread:entry syscall::pread:return Syscall pread * -----------------------------^ */ for (tmp = begin; *tmp != '\0' && !isspace(*tmp); ++tmp) { } if (*tmp == '\0') { return (-1); } *tmp = '\0'; returnprobe = begin; begin = tmp + 1; while (isspace(*begin)) { ++begin; } /* * 10 syscall::pread:entry syscall::pread:return Syscall pread * -----------------------------------------------------^ */ if (*begin == 0) { return (-1); } cause_str = begin; dmacro = NULL; /* Check if we have mapped this cause before. */ cause = (lt_cause_t *) g_hash_table_lookup(cause_lookup, cause_str); if (cause == NULL) { char *cause_dup = lt_strdup(cause_str); cause = new_cause(cause_dup, 0); g_hash_table_insert(cause_lookup, cause_dup, cause); } (void) snprintf(buf, sizeof (buf), "\nTRANSLATE(%s, %s, \"%s\", %d)\n", entryprobe, returnprobe, cause_str, priority); (void) snprintf(probepair, sizeof (probepair), "%s %s", entryprobe, returnprobe); g_assert(cause != NULL); g_assert(parser->lt_pr_dmacro != NULL); dmacro = g_hash_table_lookup(parser->lt_pr_dmacro, probepair); if (dmacro == NULL) { dmacro = (lt_dmacro_t *)lt_malloc(sizeof (lt_dmacro_t)); dmacro->lt_dm_priority = priority; dmacro->lt_dm_macro = lt_strdup(buf); g_hash_table_insert(parser->lt_pr_dmacro, lt_strdup(probepair), dmacro); } else if (dmacro->lt_dm_priority < priority) { free(dmacro->lt_dm_macro); dmacro->lt_dm_priority = priority; dmacro->lt_dm_macro = lt_strdup(buf); } return (0); } /* * Helper function to collect TRANSLATE() macros. */ /* ARGSUSED */ static void genscript(void *key, lt_dmacro_t *dmacro, GString *str) { g_string_append(str, dmacro->lt_dm_macro); } /* * Main logic that parses translation rules one line at a time, * and creates a lookup table from it. The syntax for the translation * is as follows : * * # <--- comment * D <--- D macro * S <--- Symbols * disable_cause <--- special command */ static int parse_config(const char *work, int work_len) { char line[256]; int len; char *begin, *end; int current = 0; lt_parser_t parser; int ret = 0; char flag; GString *script; cause_lookup = g_hash_table_new(g_str_hash, g_str_equal); lt_check_null(cause_lookup); parser.lt_pr_cmd_disable = g_sequence_new((GDestroyNotify)free); lt_check_null(parser.lt_pr_cmd_disable); parser.lt_pr_dmacro = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)free, (GDestroyNotify)free_dmacro); lt_check_null(parser.lt_pr_dmacro); while (read_line_from_mem(work, work_len, line, sizeof (line), ¤t)) { len = strlen(line); if (line[len-1] != '\n' && line[len-1] != '\r' && current < work_len) { lt_display_error("Configuration line too long.\n"); goto err; } begin = line; while (isspace(*begin)) { ++begin; } if (*begin == '\0') { /* Ignore empty line */ continue; } /* Delete trailing spaces. */ end = begin + strlen(begin) - 1; while (isspace(*end)) { --end; } end[1] = '\0'; flag = *begin; ++begin; switch (flag) { case '#': ret = 0; break; case ';': ret = parse_config_cmd(begin, &parser); break; case 'D': case 'd': if (!isspace(*begin)) { lt_display_error( "No space after flag char: %s\n", line); } while (isspace(*begin)) { ++begin; } ret = parse_dmacro(begin, &parser); break; case 'S': case 's': if (!isspace(*begin)) { lt_display_error( "No space after flag char: %s\n", line); } while (isspace(*begin)) { ++begin; } ret = parse_sym_trans(begin); break; default: ret = -1; break; } if (ret != 0) { lt_display_error( "Invalid configuration line: %s\n", line); goto err; } } script = g_string_new(NULL); g_hash_table_foreach(parser.lt_pr_dmacro, (GHFunc)genscript, script); dtrans = g_string_free(script, FALSE); if (dtrans != NULL && strlen(dtrans) == 0) { free(dtrans); dtrans = NULL; } g_sequence_foreach(parser.lt_pr_cmd_disable, (GFunc)disable_cause, cause_lookup); g_sequence_free(parser.lt_pr_cmd_disable); return (0); err: g_sequence_free(parser.lt_pr_cmd_disable); g_hash_table_destroy(parser.lt_pr_dmacro); return (-1); } /* * Init function, called when latencytop starts. * It loads translation rules from the configuration file. The configuration * file defines some causes and symbols that match those causes. */ int lt_table_init(void) { char *config_loaded = NULL; int config_loaded_len = 0; const char *work = NULL; int work_len = 0; lt_cause_t *cause; #ifdef EMBED_CONFIGS work = &latencytop_trans_start; work_len = (int)(&latencytop_trans_end - &latencytop_trans_start); #endif if (g_config.lt_cfg_config_name != NULL) { FILE *fp; fp = fopen(g_config.lt_cfg_config_name, "r"); if (NULL == fp) { lt_display_error( "Unable to open configuration file.\n"); return (-1); } (void) fseek(fp, 0, SEEK_END); config_loaded_len = (int)ftell(fp); config_loaded = (char *)lt_malloc(config_loaded_len); (void) fseek(fp, 0, SEEK_SET); /* A zero-byte translation is valid */ if (config_loaded_len != 0 && fread(config_loaded, config_loaded_len, 1, fp) == 0) { lt_display_error( "Unable to read configuration file.\n"); (void) fclose(fp); free(config_loaded); return (-1); } (void) fclose(fp); (void) printf("Loaded configuration from %s\n", g_config.lt_cfg_config_name); work = config_loaded; work_len = config_loaded_len; } lt_table_deinit(); causes_array = g_ptr_array_new(); lt_check_null(causes_array); /* 0 is not used, but it is kept as a place for bugs etc. */ cause = new_cause(lt_strdup("Nothing"), CAUSE_FLAG_DISABLED); g_assert(cause->lt_c_cause_id == INVALID_CAUSE); symbol_lookup_table = g_hash_table_new_full( g_str_hash, g_str_equal, (GDestroyNotify)free, (GDestroyNotify)free); lt_check_null(symbol_lookup_table); if (work_len != 0 && parse_config(work, work_len) != 0) { return (-1); } if (config_loaded != NULL) { free(config_loaded); } return (0); } /* * Some causes, such as "lock spinning", do not have stack trace. Names * of such causes are explicitly specified in the D script. * This function resolves such causes and dynamically adds them * to the global tables when they are found first. If auto_create is set * to TRUE, the entry will be created if it is not found. * Return cause_id of the cause. */ int lt_table_cause_from_name(char *name, int auto_create, int flags) { lt_cause_t *cause = NULL; if (cause_lookup == NULL) { cause_lookup = g_hash_table_new(g_str_hash, g_str_equal); lt_check_null(cause_lookup); } else { cause = (lt_cause_t *) g_hash_table_lookup(cause_lookup, name); } if (cause == NULL && auto_create) { char *cause_dup; if (name[0] == '#') { flags |= CAUSE_FLAG_HIDE_IN_SUMMARY; } cause_dup = lt_strdup(name); cause = new_cause(cause_dup, flags); g_hash_table_insert(cause_lookup, cause_dup, cause); } return (cause == NULL ? INVALID_CAUSE : cause->lt_c_cause_id); } /* * Try to map a symbol on stack to a known cause. * module_func has the format "module_name`function_name". * cause_id and priority will be set if a cause is found. * If cause is found return 1, otherwise return 0. */ int lt_table_cause_from_stack(const char *module_func, int *cause_id, int *priority) { lt_match_t *match; g_assert(module_func != NULL && cause_id != NULL && priority != NULL); if (symbol_lookup_table == NULL) { return (0); } match = (lt_match_t *) g_hash_table_lookup(symbol_lookup_table, module_func); if (match == NULL) { char *func = strchr(module_func, '`'); if (func != NULL) { match = (lt_match_t *) g_hash_table_lookup(symbol_lookup_table, func); } } if (match == NULL) { return (0); } else { *cause_id = match->lt_mt_cause_id; *priority = match->lt_mt_priority; return (1); } } /* * Get the display name of a cause. cause_id must be valid, * it is usually returned from lt_table_cause_from_stack() or * lt_table_cause_from_name(). */ const char * lt_table_get_cause_name(int cause_id) { lt_cause_t *cause; if (cause_id < 0 || cause_id >= causes_array_len) { return (NULL); } cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id); if (cause == NULL) { return (NULL); } else { return (cause->lt_c_name); } } /* * Check cause flag. * If CAUSE_ALL_FLAGS is passed in, all flags are returned. */ int lt_table_get_cause_flag(int cause_id, int flag) { lt_cause_t *cause; if (cause_id < 0 || cause_id >= causes_array_len) { return (0); } cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id); if (cause == NULL) { return (0); } else { return (cause->lt_c_flags & flag); } } /* * Append macros to D script, if any. */ int lt_table_append_trans(FILE *fp) { if (dtrans != NULL) { if (fwrite(dtrans, strlen(dtrans), 1, fp) != 1) { return (-1); } } return (0); } /* * Clean up function. * Free the resources used for symbol table (symbols, causes etc.). */ void lt_table_deinit(void) { if (symbol_lookup_table != NULL) { g_hash_table_destroy(symbol_lookup_table); symbol_lookup_table = NULL; } if (cause_lookup != NULL) { g_hash_table_destroy(cause_lookup); cause_lookup = NULL; } if (causes_array != NULL) { g_ptr_array_foreach(causes_array, (GFunc)free_cause, NULL); g_ptr_array_free(causes_array, TRUE); causes_array = NULL; causes_array_len = 0; } if (dtrans != NULL) { g_free(dtrans); dtrans = NULL; } }