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 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 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 * 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 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 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 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 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 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 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 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 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 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 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 * 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 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 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 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