Method | \n" 104 | "Run 1 (us) | \n" 105 | "Run 2 (us) | \n" 106 | "Diff (us) | \n" 107 | "Diff (%%) | \n" 108 | "1: # calls | \n" 109 | "2: # calls | \n" 110 | "
---|
Method | \n" 115 | "Exclusive | \n" 116 | "Inclusive | \n" 117 | "# calls | \n"; 118 | 119 | #define GRAPH_LABEL_VISITED 0x0001 120 | #define GRAPH_NODE_VISITED 0x0002 121 | 122 | /* 123 | * Values from the header of the data file. 124 | */ 125 | typedef struct DataHeader { 126 | unsigned int magic; 127 | short version; 128 | short offsetToData; 129 | long long startWhen; 130 | short recordSize; 131 | } DataHeader; 132 | 133 | /* 134 | * Entry from the thread list. 135 | */ 136 | typedef struct ThreadEntry { 137 | int threadId; 138 | const char *threadName; 139 | } ThreadEntry; 140 | 141 | struct MethodEntry; 142 | typedef struct TimedMethod { 143 | struct TimedMethod *next; 144 | uint64_t elapsedInclusive; 145 | int numCalls; 146 | struct MethodEntry *method; 147 | } TimedMethod; 148 | 149 | typedef struct ClassEntry { 150 | const char *className; 151 | uint64_t elapsedExclusive; 152 | int numMethods; 153 | struct MethodEntry **methods; 154 | /* list of methods in this class */ 155 | int numCalls[2]; /* 0=normal, 1=recursive */ 156 | } ClassEntry; 157 | 158 | typedef struct UniqueMethodEntry { 159 | uint64_t elapsedExclusive; 160 | int numMethods; 161 | struct MethodEntry **methods; 162 | /* list of methods with same name */ 163 | int numCalls[2]; /* 0=normal, 1=recursive */ 164 | } UniqueMethodEntry; 165 | 166 | /* 167 | * Entry from the method list. 168 | */ 169 | typedef struct MethodEntry { 170 | int64_t methodId; 171 | const char *className; 172 | const char *methodName; 173 | const char *signature; 174 | const char *fileName; 175 | int lineNum; 176 | uint64_t elapsedExclusive; 177 | uint64_t elapsedInclusive; 178 | uint64_t topExclusive; 179 | /* non-recursive exclusive time */ 180 | uint64_t recursiveInclusive; 181 | struct TimedMethod *parents[2]; 182 | /* 0=normal, 1=recursive */ 183 | struct TimedMethod *children[2]; 184 | /* 0=normal, 1=recursive */ 185 | int numCalls[2]; 186 | /* 0=normal, 1=recursive */ 187 | int index; 188 | /* used after sorting to number methods */ 189 | int recursiveEntries; 190 | /* number of entries on the stack */ 191 | int graphState; /* used when graphing to see if this method has been visited before */ 192 | } MethodEntry; 193 | 194 | /* 195 | * The parsed contents of the key file. 196 | */ 197 | typedef struct DataKeys { 198 | char *fileData; 199 | /* contents of the entire file */ 200 | long fileLen; 201 | int numThreads; 202 | ThreadEntry *threads; 203 | int numMethods; 204 | MethodEntry *methods; /* 2 extra methods: "toplevel" and "unknown" */ 205 | } DataKeys; 206 | 207 | #define TOPLEVEL_INDEX 0 208 | #define UNKNOWN_INDEX 1 209 | 210 | typedef struct StackEntry { 211 | MethodEntry *method; 212 | uint64_t entryTime; 213 | } StackEntry; 214 | 215 | typedef struct CallStack { 216 | int top; 217 | StackEntry calls[MAX_STACK_DEPTH]; 218 | uint64_t lastEventTime; 219 | uint64_t threadStartTime; 220 | } CallStack; 221 | 222 | typedef struct DiffEntry { 223 | MethodEntry *method1; 224 | MethodEntry *method2; 225 | int64_t differenceExclusive; 226 | int64_t differenceInclusive; 227 | double differenceExclusivePercentage; 228 | double differenceInclusivePercentage; 229 | } DiffEntry; 230 | 231 | // Global options 232 | typedef struct Options { 233 | const char *traceFileName; 234 | const char *diffFileName; 235 | const char *graphFileName; 236 | int keepDotFile; 237 | int dump; 238 | int outputHtml; 239 | const char *sortableUrl; 240 | int threshold; 241 | } Options; 242 | 243 | typedef struct TraceData { 244 | int numClasses; 245 | ClassEntry *classes; 246 | CallStack *stacks[MAX_THREADS]; 247 | int depth[MAX_THREADS]; 248 | int numUniqueMethods; 249 | UniqueMethodEntry *uniqueMethods; 250 | } TraceData; 251 | 252 | static Options gOptions; 253 | 254 | /* Escapes characters in the source string that are html special entities. 255 | * The escaped string is written to "dest" which must be large enough to 256 | * hold the result. A pointer to "dest" is returned. The characters and 257 | * their corresponding escape sequences are: 258 | * '<' < 259 | * '>' > 260 | * '&' & 261 | */ 262 | char *htmlEscape(const char *src, char *dest, int len) { 263 | char *destStart = dest; 264 | 265 | if (src == NULL) 266 | return NULL; 267 | 268 | int nbytes = 0; 269 | while (*src) { 270 | if (*src == '<') { 271 | nbytes += 4; 272 | if (nbytes >= len) 273 | break; 274 | *dest++ = '&'; 275 | *dest++ = 'l'; 276 | *dest++ = 't'; 277 | *dest++ = ';'; 278 | } else if (*src == '>') { 279 | nbytes += 4; 280 | if (nbytes >= len) 281 | break; 282 | *dest++ = '&'; 283 | *dest++ = 'g'; 284 | *dest++ = 't'; 285 | *dest++ = ';'; 286 | } else if (*src == '&') { 287 | nbytes += 5; 288 | if (nbytes >= len) 289 | break; 290 | *dest++ = '&'; 291 | *dest++ = 'a'; 292 | *dest++ = 'm'; 293 | *dest++ = 'p'; 294 | *dest++ = ';'; 295 | } else { 296 | nbytes += 1; 297 | if (nbytes >= len) 298 | break; 299 | *dest++ = *src; 300 | } 301 | src += 1; 302 | } 303 | if (nbytes >= len) { 304 | fprintf(stderr, "htmlEscape(): buffer overflow\n"); 305 | exit(1); 306 | } 307 | *dest = 0; 308 | 309 | return destStart; 310 | } 311 | 312 | /* Initializes a MethodEntry 313 | */ 314 | void initMethodEntry(MethodEntry *method, int64_t methodId, 315 | const char *className, const char *methodName, 316 | const char *signature, const char *fileName, 317 | const char *lineNumStr) { 318 | method->methodId = methodId; 319 | method->className = className; 320 | method->methodName = methodName; 321 | method->signature = signature; 322 | method->fileName = fileName; 323 | method->lineNum = (lineNumStr != NULL) ? atoi(lineNumStr) : -1; 324 | method->elapsedExclusive = 0; 325 | method->elapsedInclusive = 0; 326 | method->topExclusive = 0; 327 | method->recursiveInclusive = 0; 328 | method->parents[0] = NULL; 329 | method->parents[1] = NULL; 330 | method->children[0] = NULL; 331 | method->children[1] = NULL; 332 | method->numCalls[0] = 0; 333 | method->numCalls[1] = 0; 334 | method->index = 0; 335 | method->recursiveEntries = 0; 336 | } 337 | 338 | /* 339 | * This comparison function is called from qsort() to sort 340 | * methods into decreasing order of exclusive elapsed time. 341 | */ 342 | int compareElapsedExclusive(const void *a, const void *b) { 343 | uint64_t elapsed1, elapsed2; 344 | int result; 345 | 346 | const MethodEntry *methodA = *(const MethodEntry **) a; 347 | const MethodEntry *methodB = *(const MethodEntry **) b; 348 | elapsed1 = methodA->elapsedExclusive; 349 | elapsed2 = methodB->elapsedExclusive; 350 | if (elapsed1 < elapsed2) 351 | return 1; 352 | if (elapsed1 > elapsed2) 353 | return -1; 354 | 355 | /* If the elapsed times of two methods are equal, then sort them 356 | * into alphabetical order. 357 | */ 358 | result = strcmp(methodA->className, methodB->className); 359 | if (result == 0) { 360 | if (methodA->methodName == NULL || methodB->methodName == NULL) { 361 | int64_t idA = methodA->methodId; 362 | int64_t idB = methodB->methodId; 363 | if (idA < idB) 364 | return -1; 365 | if (idA > idB) 366 | return 1; 367 | return 0; 368 | } 369 | result = strcmp(methodA->methodName, methodB->methodName); 370 | if (result == 0) 371 | result = strcmp(methodA->signature, methodB->signature); 372 | } 373 | return result; 374 | } 375 | 376 | /* 377 | * This comparison function is called from qsort() to sort 378 | * methods into decreasing order of inclusive elapsed time. 379 | */ 380 | int compareElapsedInclusive(const void *a, const void *b) { 381 | const MethodEntry *methodA, *methodB; 382 | uint64_t elapsed1, elapsed2; 383 | int result; 384 | 385 | methodA = *(MethodEntry const **) a; 386 | methodB = *(MethodEntry const **) b; 387 | elapsed1 = methodA->elapsedInclusive; 388 | elapsed2 = methodB->elapsedInclusive; 389 | if (elapsed1 < elapsed2) 390 | return 1; 391 | if (elapsed1 > elapsed2) 392 | return -1; 393 | 394 | /* If the elapsed times of two methods are equal, then sort them 395 | * into alphabetical order. 396 | */ 397 | result = strcmp(methodA->className, methodB->className); 398 | if (result == 0) { 399 | if (methodA->methodName == NULL || methodB->methodName == NULL) { 400 | int64_t idA = methodA->methodId; 401 | int64_t idB = methodB->methodId; 402 | if (idA < idB) 403 | return -1; 404 | if (idA > idB) 405 | return 1; 406 | return 0; 407 | } 408 | result = strcmp(methodA->methodName, methodB->methodName); 409 | if (result == 0) 410 | result = strcmp(methodA->signature, methodB->signature); 411 | } 412 | return result; 413 | } 414 | 415 | /* 416 | * This comparison function is called from qsort() to sort 417 | * TimedMethods into decreasing order of inclusive elapsed time. 418 | */ 419 | int compareTimedMethod(const void *a, const void *b) { 420 | const TimedMethod *timedA, *timedB; 421 | uint64_t elapsed1, elapsed2; 422 | int result; 423 | 424 | timedA = (TimedMethod const *) a; 425 | timedB = (TimedMethod const *) b; 426 | elapsed1 = timedA->elapsedInclusive; 427 | elapsed2 = timedB->elapsedInclusive; 428 | if (elapsed1 < elapsed2) 429 | return 1; 430 | if (elapsed1 > elapsed2) 431 | return -1; 432 | 433 | /* If the elapsed times of two methods are equal, then sort them 434 | * into alphabetical order. 435 | */ 436 | MethodEntry *methodA = timedA->method; 437 | MethodEntry *methodB = timedB->method; 438 | result = strcmp(methodA->className, methodB->className); 439 | if (result == 0) { 440 | if (methodA->methodName == NULL || methodB->methodName == NULL) { 441 | int64_t idA = methodA->methodId; 442 | int64_t idB = methodB->methodId; 443 | if (idA < idB) 444 | return -1; 445 | if (idA > idB) 446 | return 1; 447 | return 0; 448 | } 449 | result = strcmp(methodA->methodName, methodB->methodName); 450 | if (result == 0) 451 | result = strcmp(methodA->signature, methodB->signature); 452 | } 453 | return result; 454 | } 455 | 456 | /* 457 | * This comparison function is called from qsort() to sort 458 | * MethodEntry pointers into alphabetical order of class names. 459 | */ 460 | int compareClassNames(const void *a, const void *b) { 461 | int result; 462 | 463 | const MethodEntry *methodA = *(const MethodEntry **) a; 464 | const MethodEntry *methodB = *(const MethodEntry **) b; 465 | result = strcmp(methodA->className, methodB->className); 466 | if (result == 0) { 467 | int64_t idA = methodA->methodId; 468 | int64_t idB = methodB->methodId; 469 | if (idA < idB) 470 | return -1; 471 | if (idA > idB) 472 | return 1; 473 | return 0; 474 | } 475 | return result; 476 | } 477 | 478 | /* 479 | * This comparison function is called from qsort() to sort 480 | * classes into decreasing order of exclusive elapsed time. 481 | */ 482 | int compareClassExclusive(const void *a, const void *b) { 483 | uint64_t elapsed1, elapsed2; 484 | int result; 485 | 486 | const ClassEntry *classA = *(const ClassEntry **) a; 487 | const ClassEntry *classB = *(const ClassEntry **) b; 488 | elapsed1 = classA->elapsedExclusive; 489 | elapsed2 = classB->elapsedExclusive; 490 | if (elapsed1 < elapsed2) 491 | return 1; 492 | if (elapsed1 > elapsed2) 493 | return -1; 494 | 495 | /* If the elapsed times of two classs are equal, then sort them 496 | * into alphabetical order. 497 | */ 498 | result = strcmp(classA->className, classB->className); 499 | if (result == 0) { 500 | /* Break ties with the first method id. This is probably not 501 | * needed. 502 | */ 503 | int64_t idA = classA->methods[0]->methodId; 504 | int64_t idB = classB->methods[0]->methodId; 505 | if (idA < idB) 506 | return -1; 507 | if (idA > idB) 508 | return 1; 509 | return 0; 510 | } 511 | return result; 512 | } 513 | 514 | /* 515 | * This comparison function is called from qsort() to sort 516 | * MethodEntry pointers into alphabetical order by method name, 517 | * then by class name. 518 | */ 519 | int compareMethodNames(const void *a, const void *b) { 520 | int result; 521 | 522 | const MethodEntry *methodA = *(const MethodEntry **) a; 523 | const MethodEntry *methodB = *(const MethodEntry **) b; 524 | if (methodA->methodName == NULL || methodB->methodName == NULL) { 525 | return compareClassNames(a, b); 526 | } 527 | result = strcmp(methodA->methodName, methodB->methodName); 528 | if (result == 0) { 529 | result = strcmp(methodA->className, methodB->className); 530 | if (result == 0) { 531 | int64_t idA = methodA->methodId; 532 | int64_t idB = methodB->methodId; 533 | if (idA < idB) 534 | return -1; 535 | if (idA > idB) 536 | return 1; 537 | return 0; 538 | } 539 | } 540 | return result; 541 | } 542 | 543 | /* 544 | * This comparison function is called from qsort() to sort 545 | * unique methods into decreasing order of exclusive elapsed time. 546 | */ 547 | int compareUniqueExclusive(const void *a, const void *b) { 548 | uint64_t elapsed1, elapsed2; 549 | int result; 550 | 551 | const UniqueMethodEntry *uniqueA = *(const UniqueMethodEntry **) a; 552 | const UniqueMethodEntry *uniqueB = *(const UniqueMethodEntry **) b; 553 | elapsed1 = uniqueA->elapsedExclusive; 554 | elapsed2 = uniqueB->elapsedExclusive; 555 | if (elapsed1 < elapsed2) 556 | return 1; 557 | if (elapsed1 > elapsed2) 558 | return -1; 559 | 560 | /* If the elapsed times of two methods are equal, then sort them 561 | * into alphabetical order. 562 | */ 563 | result = strcmp(uniqueA->methods[0]->className, 564 | uniqueB->methods[0]->className); 565 | if (result == 0) { 566 | int64_t idA = uniqueA->methods[0]->methodId; 567 | int64_t idB = uniqueB->methods[0]->methodId; 568 | if (idA < idB) 569 | return -1; 570 | if (idA > idB) 571 | return 1; 572 | return 0; 573 | } 574 | return result; 575 | } 576 | 577 | /* 578 | * Free a DataKeys struct. 579 | */ 580 | void freeDataKeys(DataKeys *pKeys) { 581 | if (pKeys == NULL) 582 | return; 583 | 584 | free(pKeys->fileData); 585 | free(pKeys->threads); 586 | free(pKeys->methods); 587 | free(pKeys); 588 | } 589 | 590 | /* 591 | * Find the offset to the next occurrence of the specified character. 592 | * 593 | * "data" should point somewhere within the current line. "len" is the 594 | * number of bytes left in the buffer. 595 | * 596 | * Returns -1 if we hit the end of the buffer. 597 | */ 598 | int findNextChar(const char *data, int len, char lookFor) { 599 | const char *start = data; 600 | 601 | while (len > 0) { 602 | if (*data == lookFor) 603 | return data - start; 604 | 605 | data++; 606 | len--; 607 | } 608 | 609 | return -1; 610 | } 611 | 612 | /* 613 | * Count the number of lines until the next token. 614 | * 615 | * Returns -1 if none found before EOF. 616 | */ 617 | int countLinesToToken(const char *data, int len) { 618 | int count = 0; 619 | int next; 620 | 621 | while (*data != TOKEN_CHAR) { 622 | next = findNextChar(data, len, '\n'); 623 | if (next < 0) 624 | return -1; 625 | count++; 626 | data += next + 1; 627 | len -= next + 1; 628 | } 629 | 630 | return count; 631 | } 632 | 633 | /* 634 | * Make sure we're at the start of the right section. 635 | * 636 | * Returns the length of the token line, or -1 if something is wrong. 637 | */ 638 | int checkToken(const char *data, int len, const char *cmpStr) { 639 | int cmpLen = strlen(cmpStr); 640 | int next; 641 | 642 | if (*data != TOKEN_CHAR) { 643 | fprintf(stderr, 644 | "ERROR: not at start of %s (found '%.10s')\n", cmpStr, data); 645 | return -1; 646 | } 647 | 648 | next = findNextChar(data, len, '\n'); 649 | if (next < cmpLen + 1) 650 | return -1; 651 | 652 | if (strncmp(data + 1, cmpStr, cmpLen) != 0) { 653 | fprintf(stderr, "ERROR: '%s' not found (got '%.7s')\n", cmpStr, data + 1); 654 | return -1; 655 | } 656 | 657 | return next + 1; 658 | } 659 | 660 | /* 661 | * Parse the "*version" section. 662 | */ 663 | long parseVersion(DataKeys *pKeys, long offset, int verbose) { 664 | char *data; 665 | char *dataEnd; 666 | int i, count, next; 667 | 668 | if (offset < 0) 669 | return -1; 670 | 671 | data = pKeys->fileData + offset; 672 | dataEnd = pKeys->fileData + pKeys->fileLen; 673 | next = checkToken(data, dataEnd - data, "version"); 674 | if (next <= 0) 675 | return -1; 676 | 677 | data += next; 678 | 679 | /* 680 | * Count the number of items in the "version" section. 681 | */ 682 | count = countLinesToToken(data, dataEnd - data); 683 | if (count <= 0) { 684 | fprintf(stderr, 685 | "ERROR: failed while reading version (found %d)\n", count); 686 | return -1; 687 | } 688 | 689 | /* find the end of the line */ 690 | next = findNextChar(data, dataEnd - data, '\n'); 691 | if (next < 0) 692 | return -1; 693 | 694 | data[next] = '\0'; 695 | versionNumber = strtoul(data, NULL, 0); 696 | if (verbose) 697 | printf("VERSION: %d\n", versionNumber); 698 | 699 | data += next + 1; 700 | 701 | /* skip over the rest of the stuff, which is "name=value" lines */ 702 | for (i = 1; i < count; i++) { 703 | next = findNextChar(data, dataEnd - data, '\n'); 704 | if (next < 0) 705 | return -1; 706 | //data[next] = '\0'; 707 | //printf("IGNORING: '%s'\n", data); 708 | data += next + 1; 709 | } 710 | 711 | return data - pKeys->fileData; 712 | } 713 | 714 | /* 715 | * Parse the "*threads" section. 716 | */ 717 | long parseThreads(DataKeys *pKeys, long offset) { 718 | char *data; 719 | char *dataEnd; 720 | int i, next, tab, count; 721 | 722 | if (offset < 0) 723 | return -1; 724 | 725 | data = pKeys->fileData + offset; 726 | dataEnd = pKeys->fileData + pKeys->fileLen; 727 | next = checkToken(data, dataEnd - data, "threads"); 728 | 729 | data += next; 730 | 731 | /* 732 | * Count the number of thread entries (one per line). 733 | */ 734 | count = countLinesToToken(data, dataEnd - data); 735 | if (count <= 0) { 736 | fprintf(stderr, 737 | "ERROR: failed while reading threads (found %d)\n", count); 738 | return -1; 739 | } 740 | 741 | //printf("+++ found %d threads\n", count); 742 | pKeys->threads = (ThreadEntry *) malloc(sizeof(ThreadEntry) * count); 743 | if (pKeys->threads == NULL) 744 | return -1; 745 | 746 | /* 747 | * Extract all entries. 748 | */ 749 | for (i = 0; i < count; i++) { 750 | next = findNextChar(data, dataEnd - data, '\n'); 751 | assert(next > 0); 752 | data[next] = '\0'; 753 | 754 | tab = findNextChar(data, next, '\t'); 755 | data[tab] = '\0'; 756 | 757 | pKeys->threads[i].threadId = atoi(data); 758 | pKeys->threads[i].threadName = data + tab + 1; 759 | 760 | data += next + 1; 761 | } 762 | 763 | pKeys->numThreads = count; 764 | return data - pKeys->fileData; 765 | } 766 | 767 | /* 768 | * Parse the "*methods" section. 769 | */ 770 | long parseMethods(DataKeys *pKeys, long offset) { 771 | char *data; 772 | char *dataEnd; 773 | int i, next, count; 774 | 775 | if (offset < 0) 776 | return -1; 777 | 778 | data = pKeys->fileData + offset; 779 | dataEnd = pKeys->fileData + pKeys->fileLen; 780 | next = checkToken(data, dataEnd - data, "methods"); 781 | if (next < 0) 782 | return -1; 783 | 784 | data += next; 785 | 786 | /* 787 | * Count the number of method entries (one per line). 788 | */ 789 | count = countLinesToToken(data, dataEnd - data); 790 | if (count <= 0) { 791 | fprintf(stderr, 792 | "ERROR: failed while reading methods (found %d)\n", count); 793 | return -1; 794 | } 795 | 796 | /* Reserve an extra method at location 0 for the "toplevel" method, 797 | * and another extra method for all other "unknown" methods. 798 | */ 799 | count += 2; 800 | pKeys->methods = (MethodEntry *) malloc(sizeof(MethodEntry) * count); 801 | if (pKeys->methods == NULL) 802 | return -1; 803 | initMethodEntry(&pKeys->methods[TOPLEVEL_INDEX], -2, "(toplevel)", 804 | NULL, NULL, NULL, NULL); 805 | initMethodEntry(&pKeys->methods[UNKNOWN_INDEX], -1, "(unknown)", 806 | NULL, NULL, NULL, NULL); 807 | 808 | /* 809 | * Extract all entries, starting with index 2. 810 | */ 811 | for (i = UNKNOWN_INDEX + 1; i < count; i++) { 812 | int tab1, tab2, tab3, tab4, tab5; 813 | int64_t id; 814 | char *endptr; 815 | 816 | next = findNextChar(data, dataEnd - data, '\n'); 817 | assert(next > 0); 818 | data[next] = '\0'; 819 | 820 | tab1 = findNextChar(data, next, '\t'); 821 | tab2 = findNextChar(data + (tab1 + 1), next - (tab1 + 1), '\t'); 822 | tab3 = findNextChar(data + (tab1 + tab2 + 2), next - (tab1 + tab2 + 2), '\t'); 823 | tab4 = findNextChar(data + (tab1 + tab2 + tab3 + 3), 824 | next - (tab1 + tab2 + tab3 + 3), '\t'); 825 | tab5 = findNextChar(data + (tab1 + tab2 + tab3 + tab4 + 4), 826 | next - (tab1 + tab2 + tab3 + tab4 + 4), '\t'); 827 | if (tab1 < 0) { 828 | fprintf(stderr, "ERROR: missing field on method line: '%s'\n", 829 | data); 830 | return -1; 831 | } 832 | assert(data[tab1] == '\t'); 833 | data[tab1] = '\0'; 834 | 835 | id = strtoul(data, &endptr, 0); 836 | if (*endptr != '\0') { 837 | fprintf(stderr, "ERROR: bad method ID '%s'\n", data); 838 | return -1; 839 | } 840 | 841 | // Allow files that specify just a function name, instead of requiring 842 | // "class \t method \t signature" 843 | if (tab2 > 0 && tab3 > 0) { 844 | tab2 += tab1 + 1; 845 | tab3 += tab2 + 1; 846 | assert(data[tab2] == '\t'); 847 | assert(data[tab3] == '\t'); 848 | data[tab2] = data[tab3] = '\0'; 849 | 850 | // This is starting to get awkward. Allow filename and line #. 851 | if (tab4 > 0 && tab5 > 0) { 852 | tab4 += tab3 + 1; 853 | tab5 += tab4 + 1; 854 | 855 | assert(data[tab4] == '\t'); 856 | assert(data[tab5] == '\t'); 857 | data[tab4] = data[tab5] = '\0'; 858 | 859 | initMethodEntry(&pKeys->methods[i], id, data + tab1 + 1, 860 | data + tab2 + 1, data + tab3 + 1, data + tab4 + 1, 861 | data + tab5 + 1); 862 | } else { 863 | initMethodEntry(&pKeys->methods[i], id, data + tab1 + 1, 864 | data + tab2 + 1, data + tab3 + 1, NULL, NULL); 865 | } 866 | } else { 867 | initMethodEntry(&pKeys->methods[i], id, data + tab1 + 1, 868 | NULL, NULL, NULL, NULL); 869 | } 870 | 871 | data += next + 1; 872 | } 873 | 874 | pKeys->numMethods = count; 875 | return data - pKeys->fileData; 876 | } 877 | 878 | /* 879 | * Parse the "*end" section. 880 | */ 881 | long parseEnd(DataKeys *pKeys, long offset) { 882 | char *data; 883 | char *dataEnd; 884 | int next; 885 | 886 | if (offset < 0) 887 | return -1; 888 | 889 | data = pKeys->fileData + offset; 890 | dataEnd = pKeys->fileData + pKeys->fileLen; 891 | next = checkToken(data, dataEnd - data, "end"); 892 | if (next < 0) 893 | return -1; 894 | 895 | data += next; 896 | 897 | return data - pKeys->fileData; 898 | } 899 | 900 | /* 901 | * Sort the thread list entries. 902 | */ 903 | static int compareThreads(const void *thread1, const void *thread2) { 904 | return ((const ThreadEntry *) thread1)->threadId - 905 | ((const ThreadEntry *) thread2)->threadId; 906 | } 907 | 908 | void sortThreadList(DataKeys *pKeys) { 909 | qsort(pKeys->threads, pKeys->numThreads, sizeof(pKeys->threads[0]), 910 | compareThreads); 911 | } 912 | 913 | /* 914 | * Sort the method list entries. 915 | */ 916 | static int compareMethods(const void *meth1, const void *meth2) { 917 | int64_t id1, id2; 918 | 919 | id1 = ((const MethodEntry *) meth1)->methodId; 920 | id2 = ((const MethodEntry *) meth2)->methodId; 921 | if (id1 < id2) 922 | return -1; 923 | if (id1 > id2) 924 | return 1; 925 | return 0; 926 | } 927 | 928 | void sortMethodList(DataKeys *pKeys) { 929 | qsort(pKeys->methods, pKeys->numMethods, sizeof(MethodEntry), 930 | compareMethods); 931 | } 932 | 933 | /* 934 | * Parse the key section, and return a copy of the parsed contents. 935 | */ 936 | DataKeys *parseKeys(FILE *fp, int verbose) { 937 | DataKeys *pKeys = NULL; 938 | long offset; 939 | int i; 940 | 941 | pKeys = (DataKeys *) calloc(1, sizeof(DataKeys)); 942 | if (pKeys == NULL) 943 | goto fail; 944 | 945 | /* 946 | * We load the entire file into memory. We do this, rather than memory- 947 | * mapping it, because we want to change some whitespace to NULs. 948 | */ 949 | if (fseek(fp, 0L, SEEK_END) != 0) { 950 | perror("fseek"); 951 | goto fail; 952 | } 953 | pKeys->fileLen = ftell(fp); 954 | if (pKeys->fileLen == 0) { 955 | fprintf(stderr, "Key file is empty.\n"); 956 | goto fail; 957 | } 958 | rewind(fp); 959 | 960 | pKeys->fileData = (char *) malloc(pKeys->fileLen); 961 | if (pKeys->fileData == NULL) { 962 | fprintf(stderr, "ERROR: unable to alloc %ld bytes\n", pKeys->fileLen); 963 | goto fail; 964 | } 965 | 966 | if (fread(pKeys->fileData, 1, pKeys->fileLen, fp) != (size_t) pKeys->fileLen) { 967 | fprintf(stderr, "ERROR: unable to read %ld bytes from trace file\n", 968 | pKeys->fileLen); 969 | goto fail; 970 | } 971 | 972 | offset = 0; 973 | 974 | offset = parseVersion(pKeys, offset, verbose); 975 | offset = parseThreads(pKeys, offset); 976 | offset = parseMethods(pKeys, offset); 977 | offset = parseEnd(pKeys, offset); 978 | if (offset < 0) 979 | goto fail; 980 | 981 | /* Reduce our allocation now that we know where the end of the key section is. */ 982 | pKeys->fileData = (char *) realloc(pKeys->fileData, offset); 983 | pKeys->fileLen = offset; 984 | /* Leave fp pointing to the beginning of the data section. */ 985 | fseek(fp, offset, SEEK_SET); 986 | 987 | sortThreadList(pKeys); 988 | sortMethodList(pKeys); 989 | 990 | /* 991 | * Dump list of threads. 992 | */ 993 | if (verbose) { 994 | printf("Threads (%d):\n", pKeys->numThreads); 995 | for (i = 0; i < pKeys->numThreads; i++) { 996 | printf("%2d %s\n", 997 | pKeys->threads[i].threadId, pKeys->threads[i].threadName); 998 | } 999 | } 1000 | 1001 | #if 0 1002 | /* 1003 | * Dump list of methods. 1004 | */ 1005 | if (verbose) { 1006 | printf("Methods (%d):\n", pKeys->numMethods); 1007 | for (i = 0; i < pKeys->numMethods; i++) { 1008 | printf("0x%08x %s : %s : %s\n", 1009 | pKeys->methods[i].methodId, pKeys->methods[i].className, 1010 | pKeys->methods[i].methodName, pKeys->methods[i].signature); 1011 | } 1012 | } 1013 | #endif 1014 | 1015 | return pKeys; 1016 | 1017 | fail: 1018 | freeDataKeys(pKeys); 1019 | return NULL; 1020 | } 1021 | 1022 | 1023 | /* 1024 | * Read values from the binary data file. 1025 | */ 1026 | 1027 | /* Make the return value "unsigned int" instead of "unsigned short" so that 1028 | * we can detect EOF. 1029 | */ 1030 | unsigned int read2LE(FILE *fp) { 1031 | unsigned int val; 1032 | 1033 | val = getc(fp); 1034 | val |= getc(fp) << 8; 1035 | return val; 1036 | } 1037 | 1038 | unsigned int read4LE(FILE *fp) { 1039 | unsigned int val; 1040 | 1041 | val = getc(fp); 1042 | val |= getc(fp) << 8; 1043 | val |= getc(fp) << 16; 1044 | val |= getc(fp) << 24; 1045 | return val; 1046 | } 1047 | 1048 | unsigned long long read8LE(FILE *fp) { 1049 | unsigned long long val; 1050 | 1051 | val = getc(fp); 1052 | val |= (unsigned long long) getc(fp) << 8; 1053 | val |= (unsigned long long) getc(fp) << 16; 1054 | val |= (unsigned long long) getc(fp) << 24; 1055 | val |= (unsigned long long) getc(fp) << 32; 1056 | val |= (unsigned long long) getc(fp) << 40; 1057 | val |= (unsigned long long) getc(fp) << 48; 1058 | val |= (unsigned long long) getc(fp) << 56; 1059 | return val; 1060 | } 1061 | 1062 | /* 1063 | * Parse the header of the data section. 1064 | * 1065 | * Returns with the file positioned at the start of the record data. 1066 | */ 1067 | int parseDataHeader(FILE *fp, DataHeader *pHeader) { 1068 | int bytesToRead; 1069 | 1070 | pHeader->magic = read4LE(fp); 1071 | pHeader->version = read2LE(fp); 1072 | pHeader->offsetToData = read2LE(fp); 1073 | pHeader->startWhen = read8LE(fp); 1074 | bytesToRead = pHeader->offsetToData - 16; 1075 | if (pHeader->version == 1) { 1076 | pHeader->recordSize = 9; 1077 | } else if (pHeader->version == 2) { 1078 | pHeader->recordSize = 10; 1079 | } else if (pHeader->version == 3) { 1080 | pHeader->recordSize = read2LE(fp); 1081 | bytesToRead -= 2; 1082 | } else { 1083 | fprintf(stderr, "Unsupported trace file version: %d\n", pHeader->version); 1084 | return -1; 1085 | } 1086 | 1087 | if (fseek(fp, bytesToRead, SEEK_CUR) != 0) { 1088 | return -1; 1089 | } 1090 | 1091 | return 0; 1092 | } 1093 | 1094 | /* 1095 | * Look up a method by it's method ID. 1096 | * 1097 | * Returns NULL if no matching method was found. 1098 | */ 1099 | MethodEntry *lookupMethod(DataKeys *pKeys, int64_t methodId) { 1100 | int hi, lo, mid; 1101 | int64_t id; 1102 | 1103 | lo = 0; 1104 | hi = pKeys->numMethods - 1; 1105 | 1106 | while (hi >= lo) { 1107 | mid = (hi + lo) / 2; 1108 | 1109 | id = pKeys->methods[mid].methodId; 1110 | if (id == methodId) /* match */ 1111 | return &pKeys->methods[mid]; 1112 | else if (id < methodId) /* too low */ 1113 | lo = mid + 1; 1114 | else /* too high */ 1115 | hi = mid - 1; 1116 | } 1117 | 1118 | return NULL; 1119 | } 1120 | 1121 | /* 1122 | * Reads the next data record, and assigns the data values to threadId, 1123 | * methodVal and elapsedTime. On end-of-file, the threadId, methodVal, 1124 | * and elapsedTime are unchanged. Returns 1 on end-of-file, otherwise 1125 | * returns 0. 1126 | */ 1127 | int readDataRecord(FILE *dataFp, DataHeader *dataHeader, 1128 | int *threadId, unsigned int *methodVal, uint64_t *elapsedTime) { 1129 | int id; 1130 | int bytesToRead; 1131 | 1132 | bytesToRead = dataHeader->recordSize; 1133 | if (dataHeader->version == 1) { 1134 | id = getc(dataFp); 1135 | bytesToRead -= 1; 1136 | } else { 1137 | id = read2LE(dataFp); 1138 | bytesToRead -= 2; 1139 | } 1140 | if (id == EOF) 1141 | return 1; 1142 | *threadId = id; 1143 | 1144 | *methodVal = read4LE(dataFp); 1145 | *elapsedTime = read4LE(dataFp); 1146 | bytesToRead -= 8; 1147 | 1148 | while (bytesToRead-- > 0) { 1149 | getc(dataFp); 1150 | } 1151 | 1152 | if (feof(dataFp)) { 1153 | fprintf(stderr, "WARNING: hit EOF mid-record\n"); 1154 | return 1; 1155 | } 1156 | return 0; 1157 | } 1158 | 1159 | /* 1160 | * Read the key file and use it to produce formatted output from the 1161 | * data file. 1162 | */ 1163 | void dumpTrace() { 1164 | static const char *actionStr[] = {"ent", "xit", "unr", "???"}; 1165 | MethodEntry bogusMethod = {0, "???", "???", "???", "???", -1, 0, 0, 0, 0, 1166 | {NULL, NULL}, {NULL, NULL}, {0, 0}, 0, 0, -1}; 1167 | char bogusBuf[80]; 1168 | char spaces[MAX_STACK_DEPTH + 1]; 1169 | FILE *dataFp = NULL; 1170 | DataHeader dataHeader; 1171 | DataKeys *pKeys = NULL; 1172 | int i; 1173 | TraceData traceData; 1174 | 1175 | //printf("Dumping '%s' '%s'\n", dataFileName, keyFileName); 1176 | 1177 | memset(spaces, '.', MAX_STACK_DEPTH); 1178 | spaces[MAX_STACK_DEPTH] = '\0'; 1179 | 1180 | for (i = 0; i < MAX_THREADS; i++) 1181 | traceData.depth[i] = 2; // adjust for return from start function 1182 | 1183 | dataFp = fopen(gOptions.traceFileName, "rb"); 1184 | if (dataFp == NULL) 1185 | goto bail; 1186 | 1187 | if ((pKeys = parseKeys(dataFp, 1)) == NULL) 1188 | goto bail; 1189 | 1190 | if (parseDataHeader(dataFp, &dataHeader) < 0) 1191 | goto bail; 1192 | 1193 | printf("Trace (threadID action usecs class.method signature):\n"); 1194 | 1195 | while (1) { 1196 | MethodEntry *method; 1197 | int threadId; 1198 | unsigned int methodVal; 1199 | uint64_t elapsedTime; 1200 | int action, printDepth; 1201 | int64_t methodId, lastEnter = 0; 1202 | int mismatch = 0; 1203 | char depthNote; 1204 | 1205 | /* 1206 | * Extract values from file. 1207 | */ 1208 | if (readDataRecord(dataFp, &dataHeader, &threadId, &methodVal, &elapsedTime)) 1209 | break; 1210 | 1211 | action = METHOD_ACTION(methodVal); 1212 | methodId = METHOD_ID(methodVal); 1213 | 1214 | /* 1215 | * Generate a line of output. 1216 | */ 1217 | if (action == METHOD_TRACE_ENTER) { 1218 | traceData.depth[threadId]++; 1219 | lastEnter = methodId; 1220 | } else { 1221 | /* quick test for mismatched adjacent enter/exit */ 1222 | if (lastEnter != 0 && lastEnter != methodId) 1223 | mismatch = 1; 1224 | } 1225 | 1226 | printDepth = traceData.depth[threadId]; 1227 | depthNote = ' '; 1228 | if (printDepth < 0) { 1229 | printDepth = 0; 1230 | depthNote = '-'; 1231 | } else if (printDepth > MAX_STACK_DEPTH) { 1232 | printDepth = MAX_STACK_DEPTH; 1233 | depthNote = '+'; 1234 | } 1235 | 1236 | method = lookupMethod(pKeys, methodId); 1237 | if (method == NULL) { 1238 | method = &bogusMethod; 1239 | sprintf(bogusBuf, "methodId: %#" PRIx64 "", methodId); 1240 | method->signature = bogusBuf; 1241 | } 1242 | 1243 | if (method->methodName) { 1244 | printf("%2d %s%c %8" PRIu64 "%c%s%s.%s %s\n", threadId, 1245 | actionStr[action], mismatch ? '!' : ' ', 1246 | elapsedTime, depthNote, 1247 | spaces + (MAX_STACK_DEPTH - printDepth), 1248 | method->className, method->methodName, method->signature); 1249 | } else { 1250 | printf("%2d %s%c %8" PRIu64 "%c%s%s\n", threadId, 1251 | actionStr[action], mismatch ? '!' : ' ', 1252 | elapsedTime, depthNote, 1253 | spaces + (MAX_STACK_DEPTH - printDepth), 1254 | method->className); 1255 | } 1256 | 1257 | if (action != METHOD_TRACE_ENTER) { 1258 | traceData.depth[threadId]--; /* METHOD_TRACE_EXIT or METHOD_TRACE_UNROLL */ 1259 | lastEnter = 0; 1260 | } 1261 | 1262 | mismatch = 0; 1263 | } 1264 | 1265 | bail: 1266 | if (dataFp != NULL) 1267 | fclose(dataFp); 1268 | if (pKeys != NULL) 1269 | freeDataKeys(pKeys); 1270 | } 1271 | 1272 | /* This routine adds the given time to the parent and child methods. 1273 | * This is called when the child routine exits, after the child has 1274 | * been popped from the stack. The elapsedTime parameter is the 1275 | * duration of the child routine, including time spent in called routines. 1276 | */ 1277 | void addInclusiveTime(MethodEntry *parent, MethodEntry *child, 1278 | uint64_t elapsedTime) { 1279 | TimedMethod *pTimed; 1280 | 1281 | #if 0 1282 | bool verbose = false; 1283 | if (strcmp(child->className, debugClassName) == 0) 1284 | verbose = true; 1285 | #endif 1286 | 1287 | int childIsRecursive = (child->recursiveEntries > 0); 1288 | int parentIsRecursive = (parent->recursiveEntries > 1); 1289 | 1290 | if (child->recursiveEntries == 0) { 1291 | child->elapsedInclusive += elapsedTime; 1292 | } else if (child->recursiveEntries == 1) { 1293 | child->recursiveInclusive += elapsedTime; 1294 | } 1295 | child->numCalls[childIsRecursive] += 1; 1296 | 1297 | #if 0 1298 | if (verbose) { 1299 | fprintf(stderr, 1300 | "%s %d elapsedTime: %lld eI: %lld, rI: %lld\n", 1301 | child->className, child->recursiveEntries, 1302 | elapsedTime, child->elapsedInclusive, 1303 | child->recursiveInclusive); 1304 | } 1305 | #endif 1306 | 1307 | /* Find the child method in the parent */ 1308 | TimedMethod *children = parent->children[parentIsRecursive]; 1309 | for (pTimed = children; pTimed; pTimed = pTimed->next) { 1310 | if (pTimed->method == child) { 1311 | pTimed->elapsedInclusive += elapsedTime; 1312 | pTimed->numCalls += 1; 1313 | break; 1314 | } 1315 | } 1316 | if (pTimed == NULL) { 1317 | /* Allocate a new TimedMethod */ 1318 | pTimed = (TimedMethod *) malloc(sizeof(TimedMethod)); 1319 | pTimed->elapsedInclusive = elapsedTime; 1320 | pTimed->numCalls = 1; 1321 | pTimed->method = child; 1322 | 1323 | /* Add it to the front of the list */ 1324 | pTimed->next = children; 1325 | parent->children[parentIsRecursive] = pTimed; 1326 | } 1327 | 1328 | /* Find the parent method in the child */ 1329 | TimedMethod *parents = child->parents[childIsRecursive]; 1330 | for (pTimed = parents; pTimed; pTimed = pTimed->next) { 1331 | if (pTimed->method == parent) { 1332 | pTimed->elapsedInclusive += elapsedTime; 1333 | pTimed->numCalls += 1; 1334 | break; 1335 | } 1336 | } 1337 | if (pTimed == NULL) { 1338 | /* Allocate a new TimedMethod */ 1339 | pTimed = (TimedMethod *) malloc(sizeof(TimedMethod)); 1340 | pTimed->elapsedInclusive = elapsedTime; 1341 | pTimed->numCalls = 1; 1342 | pTimed->method = parent; 1343 | 1344 | /* Add it to the front of the list */ 1345 | pTimed->next = parents; 1346 | child->parents[childIsRecursive] = pTimed; 1347 | } 1348 | 1349 | #if 0 1350 | if (verbose) { 1351 | fprintf(stderr, 1352 | " %s %d eI: %lld\n", 1353 | parent->className, parent->recursiveEntries, 1354 | pTimed->elapsedInclusive); 1355 | } 1356 | #endif 1357 | } 1358 | 1359 | /* Sorts a linked list and returns a newly allocated array containing 1360 | * the sorted entries. 1361 | */ 1362 | TimedMethod *sortTimedMethodList(TimedMethod *list, int *num) { 1363 | int ii; 1364 | TimedMethod *pTimed, *sorted; 1365 | 1366 | /* Count the elements */ 1367 | int num_entries = 0; 1368 | for (pTimed = list; pTimed; pTimed = pTimed->next) 1369 | num_entries += 1; 1370 | *num = num_entries; 1371 | if (num_entries == 0) 1372 | return NULL; 1373 | 1374 | /* Copy all the list elements to a new array and sort them */ 1375 | sorted = (TimedMethod *) malloc(sizeof(TimedMethod) * num_entries); 1376 | for (ii = 0, pTimed = list; pTimed; pTimed = pTimed->next, ++ii) 1377 | memcpy(&sorted[ii], pTimed, sizeof(TimedMethod)); 1378 | qsort(sorted, num_entries, sizeof(TimedMethod), compareTimedMethod); 1379 | 1380 | /* Fix up the "next" pointers so that they work. */ 1381 | for (ii = 0; ii < num_entries - 1; ++ii) 1382 | sorted[ii].next = &sorted[ii + 1]; 1383 | sorted[num_entries - 1].next = NULL; 1384 | 1385 | return sorted; 1386 | } 1387 | 1388 | /* Define flag values for printInclusiveMethod() */ 1389 | static const int kIsRecursive = 1; 1390 | 1391 | /* This prints the inclusive stats for all the parents or children of a 1392 | * method, depending on the list that is passed in. 1393 | */ 1394 | void printInclusiveMethod(MethodEntry *method, TimedMethod *list, int numCalls, 1395 | int flags) { 1396 | int num; 1397 | TimedMethod *pTimed; 1398 | char buf[80]; 1399 | char *anchor_close; 1400 | char *spaces = " "; /* 6 spaces */ 1401 | int num_spaces = strlen(spaces); 1402 | char *space_ptr = &spaces[num_spaces]; 1403 | char *className, *methodName, *signature; 1404 | char classBuf[HTML_BUFSIZE], methodBuf[HTML_BUFSIZE]; 1405 | char signatureBuf[HTML_BUFSIZE]; 1406 | 1407 | anchor_close = ""; 1408 | if (gOptions.outputHtml) 1409 | anchor_close = ""; 1410 | 1411 | TimedMethod *sorted = sortTimedMethodList(list, &num); 1412 | double methodTotal = method->elapsedInclusive; 1413 | for (pTimed = sorted; pTimed; pTimed = pTimed->next) { 1414 | MethodEntry *relative = pTimed->method; 1415 | className = (char *) (relative->className); 1416 | methodName = (char *) (relative->methodName); 1417 | signature = (char *) (relative->signature); 1418 | double per = 100.0 * pTimed->elapsedInclusive / methodTotal; 1419 | sprintf(buf, "[%d]", relative->index); 1420 | if (gOptions.outputHtml) { 1421 | int len = strlen(buf); 1422 | if (len > num_spaces) 1423 | len = num_spaces; 1424 | sprintf(buf, "[%d]", 1425 | relative->index, relative->index); 1426 | space_ptr = &spaces[len]; 1427 | className = htmlEscape(className, classBuf, HTML_BUFSIZE); 1428 | methodName = htmlEscape(methodName, methodBuf, HTML_BUFSIZE); 1429 | signature = htmlEscape(signature, signatureBuf, HTML_BUFSIZE); 1430 | } 1431 | int nCalls = numCalls; 1432 | if (nCalls == 0) 1433 | nCalls = relative->numCalls[0] + relative->numCalls[1]; 1434 | if (relative->methodName) { 1435 | if (flags & kIsRecursive) { 1436 | // Don't display percentages for recursive functions 1437 | printf("%6s %5s %6s %s%6s%s %6d/%-6d %9" PRIu64 " %s.%s %s\n", 1438 | "", "", "", 1439 | space_ptr, buf, anchor_close, 1440 | pTimed->numCalls, nCalls, 1441 | pTimed->elapsedInclusive, 1442 | className, methodName, signature); 1443 | } else { 1444 | printf("%6s %5s %5.1f%% %s%6s%s %6d/%-6d %9" PRIu64 1445 | " %s.%s %s\n", 1446 | "", "", per, 1447 | space_ptr, buf, anchor_close, 1448 | pTimed->numCalls, nCalls, 1449 | pTimed->elapsedInclusive, 1450 | className, methodName, signature); 1451 | } 1452 | } else { 1453 | if (flags & kIsRecursive) { 1454 | // Don't display percentages for recursive functions 1455 | printf("%6s %5s %6s %s%6s%s %6d/%-6d %9" PRIu64 " %s\n", 1456 | "", "", "", 1457 | space_ptr, buf, anchor_close, 1458 | pTimed->numCalls, nCalls, 1459 | pTimed->elapsedInclusive, 1460 | className); 1461 | } else { 1462 | printf("%6s %5s %5.1f%% %s%6s%s %6d/%-6d %9" PRIu64 " %s\n", 1463 | "", "", per, 1464 | space_ptr, buf, anchor_close, 1465 | pTimed->numCalls, nCalls, 1466 | pTimed->elapsedInclusive, 1467 | className); 1468 | } 1469 | } 1470 | } 1471 | } 1472 | 1473 | void countRecursiveEntries(CallStack *pStack, int top, MethodEntry *method) { 1474 | int ii; 1475 | 1476 | method->recursiveEntries = 0; 1477 | for (ii = 0; ii < top; ++ii) { 1478 | if (pStack->calls[ii].method == method) 1479 | method->recursiveEntries += 1; 1480 | } 1481 | } 1482 | 1483 | void stackDump(CallStack *pStack, int top) { 1484 | int ii; 1485 | 1486 | for (ii = 0; ii < top; ++ii) { 1487 | MethodEntry *method = pStack->calls[ii].method; 1488 | uint64_t entryTime = pStack->calls[ii].entryTime; 1489 | if (method->methodName) { 1490 | fprintf(stderr, " %2d: %8" PRIu64 " %s.%s %s\n", ii, entryTime, 1491 | method->className, method->methodName, method->signature); 1492 | } else { 1493 | fprintf(stderr, " %2d: %8" PRIu64 " %s\n", ii, entryTime, 1494 | method->className); 1495 | } 1496 | } 1497 | } 1498 | 1499 | void outputTableOfContents() { 1500 | printf("\n"); 1501 | printf("|||
---|---|---|---|---|---|---|
\n"); 2550 | 2551 | printf("%s.%s ", className, methodName); 2552 | if (gOptions.outputHtml) printf(" | "); 2553 | 2554 | printf("%" PRIu64 " ", method->elapsedExclusive); 2555 | if (gOptions.outputHtml) printf(" | "); 2556 | 2557 | printf("%" PRIu64 " ", method->elapsedInclusive); 2558 | if (gOptions.outputHtml) printf(" | "); 2559 | 2560 | printf("%d\n", method->numCalls[0]); 2561 | if (gOptions.outputHtml) printf(" | \n");
2562 | }
2563 |
2564 |
2565 | void createDiff(DataKeys *d1, uint64_t sum1 __unused,
2566 | DataKeys *d2, uint64_t sum2 __unused) {
2567 | MethodEntry **methods1 = parseMethodEntries(d1);
2568 | MethodEntry **methods2 = parseMethodEntries(d2);
2569 |
2570 | // sort and assign the indicies
2571 | int i;
2572 | qsort(methods1, d1->numMethods, sizeof(MethodEntry *), compareElapsedInclusive);
2573 | for (i = 0; i < d1->numMethods; ++i) {
2574 | methods1[i]->index = i;
2575 | }
2576 |
2577 | qsort(methods2, d2->numMethods, sizeof(MethodEntry *), compareElapsedInclusive);
2578 | for (i = 0; i < d2->numMethods; ++i) {
2579 | methods2[i]->index = i;
2580 | }
2581 |
2582 | int max = (d1->numMethods < d2->numMethods) ? d2->numMethods : d1->numMethods;
2583 | max++;
2584 | DiffEntry *diffs = (DiffEntry *) malloc(max * sizeof(DiffEntry));
2585 | memset(diffs, 0, max * sizeof(DiffEntry));
2586 | DiffEntry *ptr = diffs;
2587 |
2588 | // printf(" d1->numMethods: %d d1->numMethods: %d \n", d1->numMethods, d2->numMethods); 2589 | 2590 | int matches = 0; 2591 | 2592 | for (i = 0; i < d1->numMethods; i++) { 2593 | int match = findMatch(methods2, d2->numMethods, methods1[i]); 2594 | if (match >= 0) { 2595 | ptr->method1 = methods1[i]; 2596 | ptr->method2 = methods2[match]; 2597 | 2598 | uint64_t e1 = ptr->method1->elapsedExclusive; 2599 | uint64_t e2 = ptr->method2->elapsedExclusive; 2600 | if (e1 > 0) { 2601 | ptr->differenceExclusive = e2 - e1; 2602 | ptr->differenceExclusivePercentage = ((double) e2 / (double) e1) * 100.0; 2603 | } 2604 | 2605 | uint64_t i1 = ptr->method1->elapsedInclusive; 2606 | uint64_t i2 = ptr->method2->elapsedInclusive; 2607 | if (i1 > 0) { 2608 | ptr->differenceInclusive = i2 - i1; 2609 | ptr->differenceInclusivePercentage = ((double) i2 / (double) i1) * 100.0; 2610 | } 2611 | 2612 | // clear these out so we don't find them again and we know which ones 2613 | // we have left over 2614 | methods1[i] = NULL; 2615 | methods2[match] = NULL; 2616 | ptr++; 2617 | 2618 | matches++; 2619 | } 2620 | } 2621 | ptr->method1 = NULL; 2622 | ptr->method2 = NULL; 2623 | 2624 | qsort(diffs, matches, sizeof(DiffEntry), compareDiffEntriesExculsive); 2625 | ptr = diffs; 2626 | 2627 | if (gOptions.outputHtml) { 2628 | printf(htmlHeader, gOptions.sortableUrl); 2629 | printf(" Table of Contents\n"); 2630 | printf("\n"); 2634 | printf("Run 1: %s\n", gOptions.diffFileName); 2635 | printf("Run 2: %s \n", gOptions.traceFileName); 2636 | printf(" Exclusive\n"); 2637 | printf(tableHeader, "exclusive_table"); 2638 | } 2639 | 2640 | char classBuf[HTML_BUFSIZE]; 2641 | char methodBuf[HTML_BUFSIZE]; 2642 | char *className; 2643 | char *methodName; 2644 | 2645 | while (ptr->method1 != NULL && ptr->method2 != NULL) { 2646 | if (gOptions.outputHtml) printf(" | ||
\n"); 2647 | 2648 | className = htmlEscape(ptr->method1->className, classBuf, HTML_BUFSIZE); 2649 | methodName = htmlEscape(ptr->method1->methodName, methodBuf, HTML_BUFSIZE); 2650 | 2651 | printf("%s.%s ", className, methodName); 2652 | if (gOptions.outputHtml) printf(" | "); 2653 | 2654 | printf("%" PRIu64 " ", ptr->method1->elapsedExclusive); 2655 | if (gOptions.outputHtml) printf(" | "); 2656 | 2657 | printf("%" PRIu64 " ", ptr->method2->elapsedExclusive); 2658 | if (gOptions.outputHtml) printf(" | "); 2659 | 2660 | printf("%" PRIu64 " ", ptr->differenceExclusive); 2661 | if (gOptions.outputHtml) printf(" | "); 2662 | 2663 | printf("%.2f\n", ptr->differenceExclusivePercentage); 2664 | if (gOptions.outputHtml) printf(" | \n"); 2665 | 2666 | printf("%d\n", ptr->method1->numCalls[0]); 2667 | if (gOptions.outputHtml) printf(" | \n"); 2668 | 2669 | printf("%d\n", ptr->method2->numCalls[0]); 2670 | if (gOptions.outputHtml) printf(" |