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 cause_id; 39 int flags; 40 char *name; 41 } lt_cause_t; 42 43 /* 44 * Structure that represents a matched cause. 45 */ 46 typedef struct { 47 int priority; 48 int cause_id; 49 } lt_match_t; 50 51 /* All lt_cause_t that are created. */ 52 static GPtrArray *causes_array = NULL; 53 static int causes_array_len = 0; 54 /* 55 * This hash table maps a symbol to a cause entry. 56 * key type is "char *" and value type is "lt_match_t *". 57 */ 58 static GHashTable *symbol_lookup_table = NULL; 59 /* 60 * This hash table maps a cause name to an cause id. 61 * Note only cause names that are found in D script is put in this table. 62 * key type is "char *" and value type is "int" (which is cause_id). 63 */ 64 static GHashTable *named_causes = NULL; 65 66 /* 67 * Help function to free one lt_cause_t structure. 68 */ 69 /* ARGSUSED */ 70 static void 71 free_cause(lt_cause_t *cause, void *user) 72 { 73 g_assert(cause != NULL && cause->name != NULL); 74 75 free(cause->name); 76 free(cause); 77 } 78 79 /* 80 * Add a cause. 81 * Note this function takes ownership of char *name. 82 */ 83 static lt_cause_t * 84 new_cause(char *name, int flags) 85 { 86 lt_cause_t *entry; 87 88 g_assert(name != NULL); 89 90 entry = (lt_cause_t *)lt_malloc(sizeof (lt_cause_t)); 91 entry->flags = flags; 92 entry->name = name; 93 entry->cause_id = causes_array_len; 94 95 g_ptr_array_add(causes_array, entry); 96 ++causes_array_len; 97 98 return (entry); 99 } 100 101 /* 102 * Set a cause to "disabled" state. 103 */ 104 static void 105 disable_cause(char *cause_str, GHashTable *cause_table) 106 { 107 lt_cause_t *cause; 108 109 cause = (lt_cause_t *)g_hash_table_lookup(cause_table, cause_str); 110 if (cause != NULL) { 111 cause->flags |= CAUSE_FLAG_DISABLED; 112 } 113 } 114 115 /* 116 * Helper functions that reads a line from a char * array. 117 */ 118 static int 119 read_line_from_mem(const char *mem, int mem_len, char *line, int line_len, 120 int *index) 121 { 122 g_assert(mem != NULL && line != NULL && index != NULL); 123 124 if (line_len <= 0 || mem_len <= 0) { 125 return (0); 126 } 127 if (*index >= mem_len) { 128 return (0); 129 } 130 131 while (line_len > 1 && *index < mem_len) { 132 *line = mem[(*index)++]; 133 --line_len; 134 ++line; 135 if (*(line-1) == '\r' || *(line-1) == '\n') { 136 break; 137 } 138 } 139 *line = 0; 140 141 return (1); 142 } 143 144 /* 145 * The main loop that parses the translation rules one line at a time, 146 * and construct latencytop lookup data structure from it. 147 */ 148 static int 149 parse_config(const char *work, int work_len) 150 { 151 char line[256]; 152 int len; 153 char *begin, *end, *tmp; 154 int priority = 0; 155 char *match; 156 char *match_dup; 157 char *cause_str; 158 lt_cause_t *cause; 159 lt_match_t *match_entry; 160 int current = 0; 161 GHashTable *cause_lookup; 162 GSequence *cmd_disable; 163 164 cause_lookup = g_hash_table_new(g_str_hash, g_str_equal); 165 lt_check_null(cause_lookup); 166 167 cmd_disable = g_sequence_new((GDestroyNotify)free); 168 lt_check_null(cmd_disable); 169 170 while (read_line_from_mem(work, work_len, line, sizeof (line), 171 ¤t)) { 172 len = strlen(line); 173 if (line[len-1] != '\n' && line[len-1] != '\r' && 174 current < work_len) { 175 lt_display_error("Configuration line too long.\n"); 176 goto err; 177 } 178 179 begin = line; 180 while (isspace(*begin)) { 181 ++begin; 182 } 183 if (*begin == '\0') { 184 /* empty line, ignore */ 185 continue; 186 } 187 188 /* Delete trailing spaces. */ 189 end = begin + strlen(begin) - 1; 190 while (isspace(*end)) { 191 --end; 192 } 193 end[1] = 0; 194 195 if (*begin == '#') { 196 continue; 197 } else if (*begin == ';') { 198 char old_chr = 0; 199 /* special command */ 200 /* ; disable_cause FSFlush Daemon */ 201 /* ^ */ 202 ++begin; 203 204 while (isspace(*begin)) { 205 ++begin; 206 } 207 /* ; disable_cause FSFlush Daemon */ 208 /* ^ */ 209 if (*begin == '\0') { 210 continue; 211 } 212 213 for (tmp = begin; 214 *tmp != '\0' && !isspace(*tmp); 215 ++tmp) { 216 } 217 old_chr = *tmp; 218 *tmp = 0; 219 220 if (strcmp("disable_cause", begin) == 0) { 221 if (old_chr == '\0') { 222 /* Must have an argument */ 223 lt_display_error( 224 "Invalid command format: %s\n", 225 begin); 226 goto err; 227 } 228 229 begin = tmp+1; 230 while (isspace(*begin)) { 231 ++begin; 232 } 233 234 g_sequence_append(cmd_disable, 235 lt_strdup(begin)); 236 } else { 237 *tmp = old_chr; 238 lt_display_error( 239 "Unknown command: %s\n", begin); 240 goto err; 241 } 242 continue; 243 } 244 245 g_assert(*begin != '#' && *begin != ';'); 246 247 /* 10 genunix`indir Syscall indir */ 248 /* ^ */ 249 priority = strtol(begin, &tmp, 10); 250 if (tmp == begin || priority == 0) { 251 lt_display_error( 252 "Invalid configuration line: %s\n", line); 253 goto err; 254 } 255 begin = tmp; 256 257 /* 10 genunix`indir Syscall indir */ 258 /* ^ */ 259 while (isspace(*begin)) { 260 ++begin; 261 } 262 if (*begin == 0) { 263 lt_display_error( 264 "Invalid configuration line: %s\n", line); 265 goto err; 266 } 267 268 /* 10 genunix`indir Syscall indir */ 269 /* -----^ */ 270 for (tmp = begin; 271 *tmp != '\0' && !isspace(*tmp); 272 ++tmp) { 273 } 274 if (*tmp == '\0') { 275 lt_display_error( 276 "Invalid configuration line: %s\n", line); 277 goto err; 278 } 279 *tmp = 0; 280 match = begin; 281 282 /* Check if we have mapped this function before. */ 283 match_entry = (lt_match_t *) 284 g_hash_table_lookup(symbol_lookup_table, match); 285 if (match_entry != NULL && 286 HIGHER_PRIORITY(match_entry->priority, priority)) { 287 /* We already have a higher entry. Ignore this. */ 288 continue; 289 } 290 291 begin = tmp+1; 292 293 /* 10 genunix`indir Syscall indir */ 294 /* -------------------------------------^ */ 295 while (isspace(*begin)) { 296 ++begin; 297 } 298 if (*begin == 0) { 299 lt_display_error( 300 "Invalid configuration line: %s\n", line); 301 goto err; 302 } 303 cause_str = begin; 304 305 /* Check if we have mapped this cause before. */ 306 cause = (lt_cause_t *) 307 g_hash_table_lookup(cause_lookup, cause_str); 308 if (cause == NULL) { 309 char *cause_dup = lt_strdup(cause_str); 310 cause = new_cause(cause_dup, 0); 311 g_hash_table_insert(cause_lookup, cause_dup, cause); 312 } 313 314 match_entry = (lt_match_t *)lt_malloc(sizeof (lt_match_t)); 315 g_assert(NULL != match_entry); 316 match_entry->priority = priority; 317 match_entry->cause_id = cause->cause_id; 318 match_dup = lt_strdup(match); 319 320 g_hash_table_insert(symbol_lookup_table, match_dup, 321 match_entry); 322 } 323 324 g_sequence_foreach(cmd_disable, (GFunc)disable_cause, cause_lookup); 325 g_sequence_free(cmd_disable); 326 g_hash_table_destroy(cause_lookup); 327 328 return (0); 329 330 err: 331 g_sequence_free(cmd_disable); 332 g_hash_table_destroy(cause_lookup); 333 334 return (-1); 335 } 336 337 /* 338 * Init function, called when latencytop starts. 339 * It loads the translation rules from a file. 340 * A configuration file defines some causes and symbols matching these causes. 341 */ 342 int 343 lt_table_init(void) 344 { 345 char *config_loaded = NULL; 346 int config_loaded_len = 0; 347 const char *work = NULL; 348 int work_len = 0; 349 lt_cause_t *cause; 350 351 #ifdef EMBED_CONFIGS 352 work = (char *)latencytop_trans; 353 work_len = latencytop_trans_len; 354 #endif 355 356 if (g_config.config_name != NULL) { 357 FILE *fp; 358 359 fp = fopen(g_config.config_name, "r"); 360 if (NULL == fp) { 361 lt_display_error( 362 "Unable to open configuration file.\n"); 363 return (-1); 364 } 365 366 (void) fseek(fp, 0, SEEK_END); 367 config_loaded_len = (int)ftell(fp); 368 config_loaded = (char *)lt_malloc(config_loaded_len); 369 (void) fseek(fp, 0, SEEK_SET); 370 371 if (fread(config_loaded, config_loaded_len, 1, fp) == 0) { 372 lt_display_error( 373 "Unable to read configuration file.\n"); 374 (void) fclose(fp); 375 free(config_loaded); 376 return (-1); 377 } 378 379 (void) fclose(fp); 380 (void) printf("Loaded configuration from %s\n", 381 g_config.config_name); 382 383 work = config_loaded; 384 work_len = config_loaded_len; 385 } 386 387 g_assert(work != NULL && work_len != 0); 388 389 lt_table_deinit(); 390 causes_array = g_ptr_array_new(); 391 lt_check_null(causes_array); 392 393 /* 0 is not used, to keep a place for bugs etc. */ 394 cause = new_cause(lt_strdup("Nothing"), CAUSE_FLAG_DISABLED); 395 g_assert(cause->cause_id == INVALID_CAUSE); 396 397 symbol_lookup_table = g_hash_table_new_full( 398 g_str_hash, g_str_equal, 399 (GDestroyNotify)free, (GDestroyNotify)free); 400 lt_check_null(symbol_lookup_table); 401 402 if (parse_config(work, work_len) != 0) { 403 return (-1); 404 } 405 406 if (config_loaded != NULL) { 407 free(config_loaded); 408 } 409 410 return (0); 411 } 412 413 /* 414 * Some causes, such as "lock spinning", does not have stack trace. 415 * Instead, their names are explicitly specified in DTrace script. 416 * This function will resolve such causes, and dynamically add them 417 * to the global tables when first met (lazy initialization). 418 * auto_create: set to TRUE will create the entry if it is not found. 419 * Returns cause_id of the cause. 420 */ 421 int 422 lt_table_lookup_named_cause(char *name, int auto_create) 423 { 424 int cause_id = INVALID_CAUSE; 425 426 if (named_causes == NULL) { 427 named_causes = g_hash_table_new_full( 428 g_str_hash, g_str_equal, (GDestroyNotify)free, NULL); 429 lt_check_null(named_causes); 430 } else { 431 cause_id = LT_POINTER_TO_INT(g_hash_table_lookup( 432 named_causes, name)); 433 } 434 435 if (cause_id == INVALID_CAUSE && auto_create) { 436 int flags = CAUSE_FLAG_SPECIAL; 437 lt_cause_t *cause; 438 439 if (name[0] == '#') { 440 flags |= CAUSE_FLAG_HIDE_IN_SUMMARY; 441 } 442 443 cause = new_cause(lt_strdup(name), flags); 444 if (cause == NULL) { 445 return (INVALID_CAUSE); 446 } 447 cause_id = cause->cause_id; 448 449 g_hash_table_insert(named_causes, lt_strdup(name), 450 LT_INT_TO_POINTER(cause_id)); 451 } 452 453 return (cause_id); 454 } 455 456 /* 457 * Try to map a symbol on stack to a known cause. 458 * module_func has the format "module_name`function_name". 459 * cause_id and priority will be set if a cause is found. 460 * Returns 1 if found, 0 if not found. 461 */ 462 int 463 lt_table_lookup_cause(const char *module_func, int *cause_id, int *priority) 464 { 465 lt_match_t *match; 466 467 g_assert(module_func != NULL && cause_id != NULL && priority != NULL); 468 469 if (symbol_lookup_table == NULL) { 470 return (0); 471 } 472 473 match = (lt_match_t *) 474 g_hash_table_lookup(symbol_lookup_table, module_func); 475 if (match == NULL) { 476 char *func = strchr(module_func, '`'); 477 478 if (func != NULL) { 479 match = (lt_match_t *) 480 g_hash_table_lookup(symbol_lookup_table, func); 481 } 482 } 483 484 if (match == NULL) { 485 return (0); 486 } else { 487 *cause_id = match->cause_id; 488 *priority = match->priority; 489 return (1); 490 } 491 } 492 493 /* 494 * Get the display name of a cause. Cause_id must be valid, 495 * which is usually return from lt_table_lookup_cause() or 496 * lt_table_lookup_named_cause(). 497 */ 498 const char * 499 lt_table_get_cause_name(int cause_id) 500 { 501 lt_cause_t *cause; 502 503 if (cause_id < 0 || cause_id >= causes_array_len) { 504 return (NULL); 505 } 506 507 cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id); 508 if (cause == NULL) { 509 return (NULL); 510 } else { 511 return (cause->name); 512 } 513 } 514 515 /* 516 * Check a cause's flag, e.g. if it has CAUSE_FLAG_DISABLED. 517 * Use CAUSE_ALL_FLAGS to get all flags at once. 518 */ 519 int 520 lt_table_get_cause_flag(int cause_id, int flag) 521 { 522 lt_cause_t *cause; 523 524 if (cause_id < 0 || cause_id >= causes_array_len) { 525 return (0); 526 } 527 cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id); 528 529 if (cause == NULL) { 530 return (0); 531 } else { 532 return (cause->flags & flag); 533 } 534 } 535 536 /* 537 * Clean up function. 538 * Free the resource used for symbol table. E.g. symbols, causes. 539 */ 540 void 541 lt_table_deinit(void) 542 { 543 if (symbol_lookup_table != NULL) { 544 g_hash_table_destroy(symbol_lookup_table); 545 symbol_lookup_table = NULL; 546 } 547 548 if (named_causes != NULL) { 549 g_hash_table_destroy(named_causes); 550 named_causes = NULL; 551 } 552 553 if (causes_array != NULL) { 554 g_ptr_array_foreach(causes_array, (GFunc)free_cause, NULL); 555 g_ptr_array_free(causes_array, TRUE); 556 causes_array = NULL; 557 } 558 559 causes_array_len = 0; 560 } 561