1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 /* 18 * Process dmtrace output. 19 * 20 * This is the wrong way to go about it -- C is a clumsy language for 21 * shuffling data around. It'll do for a first pass. 22 */ 23 #include "profile.h" // from VM header 24 25 #include <assert.h> 26 #include <errno.h> 27 #include <inttypes.h> 28 #include <stdint.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <time.h> 33 #include <unistd.h> 34 35 /* Version number in the key file. 36 * Version 1 uses one byte for the thread id. 37 * Version 2 uses two bytes for the thread ids. 38 * Version 3 encodes the record size and adds an optional extra timestamp field. 39 */ 40 int32_t versionNumber; 41 42 /* arbitrarily limit indentation */ 43 #define MAX_STACK_DEPTH 10000 44 45 /* thread list in key file is not reliable, so just max out */ 46 #define MAX_THREADS 32768 47 48 /* Size of temporary buffers for escaping html strings */ 49 #define HTML_BUFSIZE 10240 50 51 const char* htmlHeader = 52 "<html>\n<head>\n<script type=\"text/javascript\" " 53 "src=\"%ssortable.js\"></script>\n" 54 "<script langugage=\"javascript\">\n" 55 "function toggle(item) {\n" 56 " obj=document.getElementById(item);\n" 57 " visible=(obj.style.display!=\"none\" && obj.style.display!=\"\");\n" 58 " key=document.getElementById(\"x\" + item);\n" 59 " if (visible) {\n" 60 " obj.style.display=\"none\";\n" 61 " key.innerHTML=\"+\";\n" 62 " } else {\n" 63 " obj.style.display=\"block\";\n" 64 " key.innerHTML=\"-\";\n" 65 " }\n" 66 "}\n" 67 "function onMouseOver(obj) {\n" 68 " obj.style.background=\"lightblue\";\n" 69 "}\n" 70 "function onMouseOut(obj) {\n" 71 " obj.style.background=\"white\";\n" 72 "}\n" 73 "</script>\n" 74 "<style type=\"text/css\">\n" 75 "div { font-family: courier; font-size: 13 }\n" 76 "div.parent { margin-left: 15; display: none }\n" 77 "div.leaf { margin-left: 10 }\n" 78 "div.header { margin-left: 10 }\n" 79 "div.link { margin-left: 10; cursor: move }\n" 80 "span.parent { padding-right: 10; }\n" 81 "span.leaf { padding-right: 10; }\n" 82 "a img { border: 0;}\n" 83 "table.sortable th { border-width: 0px 1px 1px 1px; background-color: " 84 "#ccc;}\n" 85 "a { text-decoration: none; }\n" 86 "a:hover { text-decoration: underline; }\n" 87 "table.sortable th, table.sortable td { text-align: left;}" 88 "table.sortable tr.odd td { background-color: #ddd; }\n" 89 "table.sortable tr.even td { background-color: #fff; }\n" 90 "</style>\n" 91 "</head><body>\n\n"; 92 93 const char* htmlFooter = "\n</body>\n</html>\n"; 94 const char* profileSeparator = 95 "======================================================================"; 96 97 const char* tableHeader = 98 "<table class='sortable' id='%s'><tr>\n" 99 "<th>Method</th>\n" 100 "<th>Run 1 (us)</th>\n" 101 "<th>Run 2 (us)</th>\n" 102 "<th>Diff (us)</th>\n" 103 "<th>Diff (%%)</th>\n" 104 "<th>1: # calls</th>\n" 105 "<th>2: # calls</th>\n" 106 "</tr>\n"; 107 108 const char* tableHeaderMissing = 109 "<table class='sortable' id='%s'>\n" 110 "<th>Method</th>\n" 111 "<th>Exclusive</th>\n" 112 "<th>Inclusive</th>\n" 113 "<th># calls</th>\n"; 114 115 #define GRAPH_LABEL_VISITED 0x0001 116 #define GRAPH_NODE_VISITED 0x0002 117 118 /* 119 * Values from the header of the data file. 120 */ 121 typedef struct DataHeader { 122 uint32_t magic; 123 int16_t version; 124 int16_t offsetToData; 125 int64_t startWhen; 126 int16_t recordSize; 127 } DataHeader; 128 129 /* 130 * Entry from the thread list. 131 */ 132 typedef struct ThreadEntry { 133 int32_t threadId; 134 const char* threadName; 135 } ThreadEntry; 136 137 struct MethodEntry; 138 typedef struct TimedMethod { 139 struct TimedMethod* next; 140 uint64_t elapsedInclusive; 141 int32_t numCalls; 142 struct MethodEntry* method; 143 } TimedMethod; 144 145 typedef struct ClassEntry { 146 const char* className; 147 uint64_t elapsedExclusive; 148 int32_t numMethods; 149 struct MethodEntry** methods; /* list of methods in this class */ 150 int32_t numCalls[2]; /* 0=normal, 1=recursive */ 151 } ClassEntry; 152 153 typedef struct UniqueMethodEntry { 154 uint64_t elapsedExclusive; 155 int32_t numMethods; 156 struct MethodEntry** methods; /* list of methods with same name */ 157 int32_t numCalls[2]; /* 0=normal, 1=recursive */ 158 } UniqueMethodEntry; 159 160 /* 161 * Entry from the method list. 162 */ 163 typedef struct MethodEntry { 164 int64_t methodId; 165 const char* className; 166 const char* methodName; 167 const char* signature; 168 const char* fileName; 169 int32_t lineNum; 170 uint64_t elapsedExclusive; 171 uint64_t elapsedInclusive; 172 uint64_t topExclusive; /* non-recursive exclusive time */ 173 uint64_t recursiveInclusive; 174 struct TimedMethod* parents[2]; /* 0=normal, 1=recursive */ 175 struct TimedMethod* children[2]; /* 0=normal, 1=recursive */ 176 int32_t numCalls[2]; /* 0=normal, 1=recursive */ 177 int32_t index; /* used after sorting to number methods */ 178 int32_t recursiveEntries; /* number of entries on the stack */ 179 int32_t graphState; /* used when graphing to see if this method has been visited before */ 180 } MethodEntry; 181 182 /* 183 * The parsed contents of the key file. 184 */ 185 typedef struct DataKeys { 186 char* fileData; /* contents of the entire file */ 187 int64_t fileLen; 188 int32_t numThreads; 189 ThreadEntry* threads; 190 int32_t numMethods; 191 MethodEntry* methods; /* 2 extra methods: "toplevel" and "unknown" */ 192 } DataKeys; 193 194 #define TOPLEVEL_INDEX 0 195 #define UNKNOWN_INDEX 1 196 197 typedef struct StackEntry { 198 MethodEntry* method; 199 uint64_t entryTime; 200 } StackEntry; 201 202 typedef struct CallStack { 203 int32_t top; 204 StackEntry calls[MAX_STACK_DEPTH]; 205 uint64_t lastEventTime; 206 uint64_t threadStartTime; 207 } CallStack; 208 209 typedef struct DiffEntry { 210 MethodEntry* method1; 211 MethodEntry* method2; 212 int64_t differenceExclusive; 213 int64_t differenceInclusive; 214 double differenceExclusivePercentage; 215 double differenceInclusivePercentage; 216 } DiffEntry; 217 218 // Global options 219 typedef struct Options { 220 const char* traceFileName; 221 const char* diffFileName; 222 const char* graphFileName; 223 int32_t keepDotFile; 224 int32_t dump; 225 int32_t outputHtml; 226 const char* sortableUrl; 227 int32_t threshold; 228 } Options; 229 230 typedef struct TraceData { 231 int32_t numClasses; 232 ClassEntry* classes; 233 CallStack* stacks[MAX_THREADS]; 234 int32_t depth[MAX_THREADS]; 235 int32_t numUniqueMethods; 236 UniqueMethodEntry* uniqueMethods; 237 } TraceData; 238 239 static Options gOptions; 240 241 /* Escapes characters in the source string that are html special entities. 242 * The escaped string is written to "dest" which must be large enough to 243 * hold the result. A pointer to "dest" is returned. The characters and 244 * their corresponding escape sequences are: 245 * '<' < 246 * '>' > 247 * '&' & 248 */ 249 char* htmlEscape(const char* src, char* dest, int32_t len) { 250 char* destStart = dest; 251 252 if (src == nullptr) return nullptr; 253 254 int32_t nbytes = 0; 255 while (*src) { 256 if (*src == '<') { 257 nbytes += 4; 258 if (nbytes >= len) break; 259 *dest++ = '&'; 260 *dest++ = 'l'; 261 *dest++ = 't'; 262 *dest++ = ';'; 263 } else if (*src == '>') { 264 nbytes += 4; 265 if (nbytes >= len) break; 266 *dest++ = '&'; 267 *dest++ = 'g'; 268 *dest++ = 't'; 269 *dest++ = ';'; 270 } else if (*src == '&') { 271 nbytes += 5; 272 if (nbytes >= len) break; 273 *dest++ = '&'; 274 *dest++ = 'a'; 275 *dest++ = 'm'; 276 *dest++ = 'p'; 277 *dest++ = ';'; 278 } else { 279 nbytes += 1; 280 if (nbytes >= len) break; 281 *dest++ = *src; 282 } 283 src += 1; 284 } 285 if (nbytes >= len) { 286 fprintf(stderr, "htmlEscape(): buffer overflow\n"); 287 exit(1); 288 } 289 *dest = 0; 290 291 return destStart; 292 } 293 294 /* Initializes a MethodEntry 295 */ 296 void initMethodEntry(MethodEntry* method, int64_t methodId, const char* className, 297 const char* methodName, const char* signature, const char* fileName, 298 const char* lineNumStr) { 299 method->methodId = methodId; 300 method->className = className; 301 method->methodName = methodName; 302 method->signature = signature; 303 method->fileName = fileName; 304 method->lineNum = (lineNumStr != nullptr) ? atoi(lineNumStr) : -1; 305 method->elapsedExclusive = 0; 306 method->elapsedInclusive = 0; 307 method->topExclusive = 0; 308 method->recursiveInclusive = 0; 309 method->parents[0] = nullptr; 310 method->parents[1] = nullptr; 311 method->children[0] = nullptr; 312 method->children[1] = nullptr; 313 method->numCalls[0] = 0; 314 method->numCalls[1] = 0; 315 method->index = 0; 316 method->recursiveEntries = 0; 317 } 318 319 /* 320 * This comparison function is called from qsort() to sort 321 * methods into decreasing order of exclusive elapsed time. 322 */ 323 int32_t compareElapsedExclusive(const void* a, const void* b) { 324 const MethodEntry* methodA = *(const MethodEntry**) a; 325 const MethodEntry* methodB = *(const MethodEntry**) b; 326 uint64_t elapsed1 = methodA->elapsedExclusive; 327 uint64_t elapsed2 = methodB->elapsedExclusive; 328 if (elapsed1 < elapsed2) return 1; 329 if (elapsed1 > elapsed2) return -1; 330 331 /* If the elapsed times of two methods are equal, then sort them 332 * into alphabetical order. 333 */ 334 int32_t result = strcmp(methodA->className, methodB->className); 335 if (result == 0) { 336 if (methodA->methodName == nullptr || methodB->methodName == nullptr) { 337 int64_t idA = methodA->methodId; 338 int64_t idB = methodB->methodId; 339 if (idA < idB) return -1; 340 if (idA > idB) return 1; 341 return 0; 342 } 343 result = strcmp(methodA->methodName, methodB->methodName); 344 if (result == 0) result = strcmp(methodA->signature, methodB->signature); 345 } 346 return result; 347 } 348 349 /* 350 * This comparison function is called from qsort() to sort 351 * methods into decreasing order of inclusive elapsed time. 352 */ 353 int32_t compareElapsedInclusive(const void* a, const void* b) { 354 const MethodEntry* methodA = *(MethodEntry const**) a; 355 const MethodEntry* methodB = *(MethodEntry const**) b; 356 uint64_t elapsed1 = methodA->elapsedInclusive; 357 uint64_t elapsed2 = methodB->elapsedInclusive; 358 if (elapsed1 < elapsed2) return 1; 359 if (elapsed1 > elapsed2) return -1; 360 361 /* If the elapsed times of two methods are equal, then sort them 362 * into alphabetical order. 363 */ 364 int32_t result = strcmp(methodA->className, methodB->className); 365 if (result == 0) { 366 if (methodA->methodName == nullptr || methodB->methodName == nullptr) { 367 int64_t idA = methodA->methodId; 368 int64_t idB = methodB->methodId; 369 if (idA < idB) return -1; 370 if (idA > idB) return 1; 371 return 0; 372 } 373 result = strcmp(methodA->methodName, methodB->methodName); 374 if (result == 0) result = strcmp(methodA->signature, methodB->signature); 375 } 376 return result; 377 } 378 379 /* 380 * This comparison function is called from qsort() to sort 381 * TimedMethods into decreasing order of inclusive elapsed time. 382 */ 383 int32_t compareTimedMethod(const void* a, const void* b) { 384 const TimedMethod* timedA = (TimedMethod const*) a; 385 const TimedMethod* timedB = (TimedMethod const*) b; 386 uint64_t elapsed1 = timedA->elapsedInclusive; 387 uint64_t elapsed2 = timedB->elapsedInclusive; 388 if (elapsed1 < elapsed2) return 1; 389 if (elapsed1 > elapsed2) return -1; 390 391 /* If the elapsed times of two methods are equal, then sort them 392 * into alphabetical order. 393 */ 394 MethodEntry* methodA = timedA->method; 395 MethodEntry* methodB = timedB->method; 396 int32_t result = strcmp(methodA->className, methodB->className); 397 if (result == 0) { 398 if (methodA->methodName == nullptr || methodB->methodName == nullptr) { 399 int64_t idA = methodA->methodId; 400 int64_t idB = methodB->methodId; 401 if (idA < idB) return -1; 402 if (idA > idB) return 1; 403 return 0; 404 } 405 result = strcmp(methodA->methodName, methodB->methodName); 406 if (result == 0) result = strcmp(methodA->signature, methodB->signature); 407 } 408 return result; 409 } 410 411 /* 412 * This comparison function is called from qsort() to sort 413 * MethodEntry pointers into alphabetical order of class names. 414 */ 415 int32_t compareClassNames(const void* a, const void* b) { 416 const MethodEntry* methodA = *(const MethodEntry**) a; 417 const MethodEntry* methodB = *(const MethodEntry**) b; 418 int32_t result = strcmp(methodA->className, methodB->className); 419 if (result == 0) { 420 int64_t idA = methodA->methodId; 421 int64_t idB = methodB->methodId; 422 if (idA < idB) return -1; 423 if (idA > idB) return 1; 424 return 0; 425 } 426 return result; 427 } 428 429 /* 430 * This comparison function is called from qsort() to sort 431 * classes into decreasing order of exclusive elapsed time. 432 */ 433 int32_t compareClassExclusive(const void* a, const void* b) { 434 const ClassEntry* classA = *(const ClassEntry**) a; 435 const ClassEntry* classB = *(const ClassEntry**) b; 436 uint64_t elapsed1 = classA->elapsedExclusive; 437 uint64_t elapsed2 = classB->elapsedExclusive; 438 if (elapsed1 < elapsed2) return 1; 439 if (elapsed1 > elapsed2) return -1; 440 441 /* If the elapsed times of two classs are equal, then sort them 442 * into alphabetical order. 443 */ 444 int32_t result = strcmp(classA->className, classB->className); 445 if (result == 0) { 446 /* Break ties with the first method id. This is probably not 447 * needed. 448 */ 449 int64_t idA = classA->methods[0]->methodId; 450 int64_t idB = classB->methods[0]->methodId; 451 if (idA < idB) return -1; 452 if (idA > idB) return 1; 453 return 0; 454 } 455 return result; 456 } 457 458 /* 459 * This comparison function is called from qsort() to sort 460 * MethodEntry pointers into alphabetical order by method name, 461 * then by class name. 462 */ 463 int32_t compareMethodNames(const void* a, const void* b) { 464 const MethodEntry* methodA = *(const MethodEntry**) a; 465 const MethodEntry* methodB = *(const MethodEntry**) b; 466 if (methodA->methodName == nullptr || methodB->methodName == nullptr) { 467 return compareClassNames(a, b); 468 } 469 int32_t result = strcmp(methodA->methodName, methodB->methodName); 470 if (result == 0) { 471 result = strcmp(methodA->className, methodB->className); 472 if (result == 0) { 473 int64_t idA = methodA->methodId; 474 int64_t idB = methodB->methodId; 475 if (idA < idB) return -1; 476 if (idA > idB) return 1; 477 return 0; 478 } 479 } 480 return result; 481 } 482 483 /* 484 * This comparison function is called from qsort() to sort 485 * unique methods into decreasing order of exclusive elapsed time. 486 */ 487 int32_t compareUniqueExclusive(const void* a, const void* b) { 488 const UniqueMethodEntry* uniqueA = *(const UniqueMethodEntry**) a; 489 const UniqueMethodEntry* uniqueB = *(const UniqueMethodEntry**) b; 490 uint64_t elapsed1 = uniqueA->elapsedExclusive; 491 uint64_t elapsed2 = uniqueB->elapsedExclusive; 492 if (elapsed1 < elapsed2) return 1; 493 if (elapsed1 > elapsed2) return -1; 494 495 /* If the elapsed times of two methods are equal, then sort them 496 * into alphabetical order. 497 */ 498 int32_t result = strcmp(uniqueA->methods[0]->className, uniqueB->methods[0]->className); 499 if (result == 0) { 500 int64_t idA = uniqueA->methods[0]->methodId; 501 int64_t idB = uniqueB->methods[0]->methodId; 502 if (idA < idB) return -1; 503 if (idA > idB) return 1; 504 return 0; 505 } 506 return result; 507 } 508 509 /* 510 * Free a DataKeys struct. 511 */ 512 void freeDataKeys(DataKeys* pKeys) { 513 if (pKeys == nullptr) return; 514 515 delete[] pKeys->fileData; 516 delete[] pKeys->threads; 517 delete[] pKeys->methods; 518 delete pKeys; 519 } 520 521 /* 522 * Find the offset to the next occurrence of the specified character. 523 * 524 * "data" should point somewhere within the current line. "len" is the 525 * number of bytes left in the buffer. 526 * 527 * Returns -1 if we hit the end of the buffer. 528 */ 529 int32_t findNextChar(const char* data, int32_t len, char lookFor) { 530 const char* start = data; 531 532 while (len > 0) { 533 if (*data == lookFor) return data - start; 534 535 data++; 536 len--; 537 } 538 539 return -1; 540 } 541 542 /* 543 * Count the number of lines until the next token. 544 * 545 * Returns -1 if none found before EOF. 546 */ 547 int32_t countLinesToToken(const char* data, int32_t len) { 548 int32_t count = 0; 549 int32_t next; 550 551 while (*data != TOKEN_CHAR) { 552 next = findNextChar(data, len, '\n'); 553 if (next < 0) return -1; 554 count++; 555 data += next + 1; 556 len -= next + 1; 557 } 558 559 return count; 560 } 561 562 /* 563 * Make sure we're at the start of the right section. 564 * 565 * Returns the length of the token line, or -1 if something is wrong. 566 */ 567 int32_t checkToken(const char* data, int32_t len, const char* cmpStr) { 568 int32_t cmpLen = strlen(cmpStr); 569 int32_t next; 570 571 if (*data != TOKEN_CHAR) { 572 fprintf(stderr, "ERROR: not at start of %s (found '%.10s')\n", cmpStr, data); 573 return -1; 574 } 575 576 next = findNextChar(data, len, '\n'); 577 if (next < cmpLen + 1) return -1; 578 579 if (strncmp(data + 1, cmpStr, cmpLen) != 0) { 580 fprintf(stderr, "ERROR: '%s' not found (got '%.7s')\n", cmpStr, data + 1); 581 return -1; 582 } 583 584 return next + 1; 585 } 586 587 /* 588 * Parse the "*version" section. 589 */ 590 int64_t parseVersion(DataKeys* pKeys, int64_t offset, int32_t verbose) { 591 if (offset < 0) return -1; 592 593 char* data = pKeys->fileData + offset; 594 char* dataEnd = pKeys->fileData + pKeys->fileLen; 595 int32_t next = checkToken(data, dataEnd - data, "version"); 596 if (next <= 0) return -1; 597 598 data += next; 599 600 /* 601 * Count the number of items in the "version" section. 602 */ 603 int32_t count = countLinesToToken(data, dataEnd - data); 604 if (count <= 0) { 605 fprintf(stderr, "ERROR: failed while reading version (found %d)\n", count); 606 return -1; 607 } 608 609 /* find the end of the line */ 610 next = findNextChar(data, dataEnd - data, '\n'); 611 if (next < 0) return -1; 612 613 data[next] = '\0'; 614 versionNumber = strtoul(data, nullptr, 0); 615 if (verbose) printf("VERSION: %d\n", versionNumber); 616 617 data += next + 1; 618 619 /* skip over the rest of the stuff, which is "name=value" lines */ 620 for (int32_t i = 1; i < count; i++) { 621 next = findNextChar(data, dataEnd - data, '\n'); 622 if (next < 0) return -1; 623 // data[next] = '\0'; 624 // printf("IGNORING: '%s'\n", data); 625 data += next + 1; 626 } 627 628 return data - pKeys->fileData; 629 } 630 631 /* 632 * Parse the "*threads" section. 633 */ 634 int64_t parseThreads(DataKeys* pKeys, int64_t offset) { 635 if (offset < 0) return -1; 636 637 char* data = pKeys->fileData + offset; 638 char* dataEnd = pKeys->fileData + pKeys->fileLen; 639 int32_t next = checkToken(data, dataEnd - data, "threads"); 640 641 data += next; 642 643 /* 644 * Count the number of thread entries (one per line). 645 */ 646 int32_t count = countLinesToToken(data, dataEnd - data); 647 if (count <= 0) { 648 fprintf(stderr, "ERROR: failed while reading threads (found %d)\n", count); 649 return -1; 650 } 651 652 // printf("+++ found %d threads\n", count); 653 pKeys->threads = new ThreadEntry[count]; 654 if (pKeys->threads == nullptr) return -1; 655 656 /* 657 * Extract all entries. 658 */ 659 for (int32_t i = 0; i < count; i++) { 660 next = findNextChar(data, dataEnd - data, '\n'); 661 assert(next > 0); 662 data[next] = '\0'; 663 664 int32_t tab = findNextChar(data, next, '\t'); 665 data[tab] = '\0'; 666 667 pKeys->threads[i].threadId = atoi(data); 668 pKeys->threads[i].threadName = data + tab + 1; 669 670 data += next + 1; 671 } 672 673 pKeys->numThreads = count; 674 return data - pKeys->fileData; 675 } 676 677 /* 678 * Parse the "*methods" section. 679 */ 680 int64_t parseMethods(DataKeys* pKeys, int64_t offset) { 681 if (offset < 0) return -1; 682 683 char* data = pKeys->fileData + offset; 684 char* dataEnd = pKeys->fileData + pKeys->fileLen; 685 int32_t next = checkToken(data, dataEnd - data, "methods"); 686 if (next < 0) return -1; 687 688 data += next; 689 690 /* 691 * Count the number of method entries (one per line). 692 */ 693 int32_t count = countLinesToToken(data, dataEnd - data); 694 if (count <= 0) { 695 fprintf(stderr, "ERROR: failed while reading methods (found %d)\n", count); 696 return -1; 697 } 698 699 /* Reserve an extra method at location 0 for the "toplevel" method, 700 * and another extra method for all other "unknown" methods. 701 */ 702 count += 2; 703 pKeys->methods = new MethodEntry[count]; 704 if (pKeys->methods == nullptr) return -1; 705 initMethodEntry(&pKeys->methods[TOPLEVEL_INDEX], -2, "(toplevel)", nullptr, nullptr, 706 nullptr, nullptr); 707 initMethodEntry(&pKeys->methods[UNKNOWN_INDEX], -1, "(unknown)", nullptr, nullptr, 708 nullptr, nullptr); 709 710 /* 711 * Extract all entries, starting with index 2. 712 */ 713 for (int32_t i = UNKNOWN_INDEX + 1; i < count; i++) { 714 next = findNextChar(data, dataEnd - data, '\n'); 715 assert(next > 0); 716 data[next] = '\0'; 717 718 int32_t tab1 = findNextChar(data, next, '\t'); 719 int32_t tab2 = findNextChar(data + (tab1 + 1), next - (tab1 + 1), '\t'); 720 int32_t tab3 = findNextChar(data + (tab1 + tab2 + 2), next - (tab1 + tab2 + 2), '\t'); 721 int32_t tab4 = findNextChar(data + (tab1 + tab2 + tab3 + 3), 722 next - (tab1 + tab2 + tab3 + 3), '\t'); 723 int32_t tab5 = findNextChar(data + (tab1 + tab2 + tab3 + tab4 + 4), 724 next - (tab1 + tab2 + tab3 + tab4 + 4), '\t'); 725 if (tab1 < 0) { 726 fprintf(stderr, "ERROR: missing field on method line: '%s'\n", data); 727 return -1; 728 } 729 assert(data[tab1] == '\t'); 730 data[tab1] = '\0'; 731 732 char* endptr; 733 int64_t id = strtoul(data, &endptr, 0); 734 if (*endptr != '\0') { 735 fprintf(stderr, "ERROR: bad method ID '%s'\n", data); 736 return -1; 737 } 738 739 // Allow files that specify just a function name, instead of requiring 740 // "class \t method \t signature" 741 if (tab2 > 0 && tab3 > 0) { 742 tab2 += tab1 + 1; 743 tab3 += tab2 + 1; 744 assert(data[tab2] == '\t'); 745 assert(data[tab3] == '\t'); 746 data[tab2] = data[tab3] = '\0'; 747 748 // This is starting to get awkward. Allow filename and line #. 749 if (tab4 > 0 && tab5 > 0) { 750 tab4 += tab3 + 1; 751 tab5 += tab4 + 1; 752 753 assert(data[tab4] == '\t'); 754 assert(data[tab5] == '\t'); 755 data[tab4] = data[tab5] = '\0'; 756 757 initMethodEntry(&pKeys->methods[i], id, data + tab1 + 1, 758 data + tab2 + 1, data + tab3 + 1, data + tab4 + 1, 759 data + tab5 + 1); 760 } else { 761 initMethodEntry(&pKeys->methods[i], id, data + tab1 + 1, 762 data + tab2 + 1, data + tab3 + 1, nullptr, nullptr); 763 } 764 } else { 765 initMethodEntry(&pKeys->methods[i], id, data + tab1 + 1, nullptr, nullptr, nullptr, 766 nullptr); 767 } 768 769 data += next + 1; 770 } 771 772 pKeys->numMethods = count; 773 return data - pKeys->fileData; 774 } 775 776 /* 777 * Parse the "*end" section. 778 */ 779 int64_t parseEnd(DataKeys* pKeys, int64_t offset) { 780 if (offset < 0) return -1; 781 782 char* data = pKeys->fileData + offset; 783 char* dataEnd = pKeys->fileData + pKeys->fileLen; 784 int32_t next = checkToken(data, dataEnd - data, "end"); 785 if (next < 0) return -1; 786 787 data += next; 788 789 return data - pKeys->fileData; 790 } 791 792 /* 793 * Sort the thread list entries. 794 */ 795 static int32_t compareThreads(const void* thread1, const void* thread2) { 796 return ((const ThreadEntry*) thread1)->threadId - 797 ((const ThreadEntry*) thread2)->threadId; 798 } 799 800 void sortThreadList(DataKeys* pKeys) { 801 qsort(pKeys->threads, pKeys->numThreads, sizeof(pKeys->threads[0]), compareThreads); 802 } 803 804 /* 805 * Sort the method list entries. 806 */ 807 static int32_t compareMethods(const void* meth1, const void* meth2) { 808 int64_t id1 = ((const MethodEntry*) meth1)->methodId; 809 int64_t id2 = ((const MethodEntry*) meth2)->methodId; 810 if (id1 < id2) return -1; 811 if (id1 > id2) return 1; 812 return 0; 813 } 814 815 void sortMethodList(DataKeys* pKeys) { 816 qsort(pKeys->methods, pKeys->numMethods, sizeof(MethodEntry), compareMethods); 817 } 818 819 /* 820 * Parse the key section, and return a copy of the parsed contents. 821 */ 822 DataKeys* parseKeys(FILE* fp, int32_t verbose) { 823 int64_t offset; 824 DataKeys* pKeys = new DataKeys(); 825 if (pKeys == nullptr) return nullptr; 826 memset(pKeys, 0, sizeof(DataKeys)); 827 828 /* 829 * We load the entire file into memory. We do this, rather than memory- 830 * mapping it, because we want to change some whitespace to NULs. 831 */ 832 if (fseek(fp, 0L, SEEK_END) != 0) { 833 perror("fseek"); 834 freeDataKeys(pKeys); 835 return nullptr; 836 } 837 pKeys->fileLen = ftell(fp); 838 if (pKeys->fileLen == 0) { 839 fprintf(stderr, "Key file is empty.\n"); 840 freeDataKeys(pKeys); 841 return nullptr; 842 } 843 rewind(fp); 844 845 pKeys->fileData = new char[pKeys->fileLen]; 846 if (pKeys->fileData == nullptr) { 847 fprintf(stderr, "ERROR: unable to alloc %" PRIu64 " bytes\n", pKeys->fileLen); 848 freeDataKeys(pKeys); 849 return nullptr; 850 } 851 852 if (fread(pKeys->fileData, 1, pKeys->fileLen, fp) != (size_t)pKeys->fileLen) { 853 fprintf(stderr, "ERROR: unable to read %" PRIu64 " bytes from trace file\n", pKeys->fileLen); 854 freeDataKeys(pKeys); 855 return nullptr; 856 } 857 858 offset = 0; 859 offset = parseVersion(pKeys, offset, verbose); 860 offset = parseThreads(pKeys, offset); 861 offset = parseMethods(pKeys, offset); 862 offset = parseEnd(pKeys, offset); 863 if (offset < 0) { 864 freeDataKeys(pKeys); 865 return nullptr; 866 } 867 868 /* 869 * Although it is tempting to reduce our allocation now that we know where the 870 * end of the key section is, there is a pitfall. The method names and 871 * signatures in the method list contain pointers into the fileData area. 872 * Realloc or free will result in corruption. 873 */ 874 875 /* Leave fp pointing to the beginning of the data section. */ 876 fseek(fp, offset, SEEK_SET); 877 878 sortThreadList(pKeys); 879 sortMethodList(pKeys); 880 881 /* 882 * Dump list of threads. 883 */ 884 if (verbose) { 885 printf("Threads (%d):\n", pKeys->numThreads); 886 for (int32_t i = 0; i < pKeys->numThreads; i++) { 887 printf("%2d %s\n", pKeys->threads[i].threadId, pKeys->threads[i].threadName); 888 } 889 } 890 891 #if 0 892 /* 893 * Dump list of methods. 894 */ 895 if (verbose) { 896 printf("Methods (%d):\n", pKeys->numMethods); 897 for (int32_t i = 0; i < pKeys->numMethods; i++) { 898 printf("0x%08x %s : %s : %s\n", 899 pKeys->methods[i].methodId, pKeys->methods[i].className, 900 pKeys->methods[i].methodName, pKeys->methods[i].signature); 901 } 902 } 903 #endif 904 905 return pKeys; 906 } 907 908 /* 909 * Read values from the binary data file. 910 */ 911 912 /* 913 * Make the return value "uint32_t" instead of "uint16_t" so that we can detect EOF. 914 */ 915 uint32_t read2LE(FILE* fp) { 916 uint32_t val = getc(fp); 917 val |= getc(fp) << 8; 918 return val; 919 } 920 uint32_t read4LE(FILE* fp) { 921 uint32_t val = getc(fp); 922 val |= getc(fp) << 8; 923 val |= getc(fp) << 16; 924 val |= getc(fp) << 24; 925 return val; 926 } 927 uint64_t read8LE(FILE* fp) { 928 uint64_t val = getc(fp); 929 val |= (uint64_t) getc(fp) << 8; 930 val |= (uint64_t) getc(fp) << 16; 931 val |= (uint64_t) getc(fp) << 24; 932 val |= (uint64_t) getc(fp) << 32; 933 val |= (uint64_t) getc(fp) << 40; 934 val |= (uint64_t) getc(fp) << 48; 935 val |= (uint64_t) getc(fp) << 56; 936 return val; 937 } 938 939 /* 940 * Parse the header of the data section. 941 * 942 * Returns with the file positioned at the start of the record data. 943 */ 944 int32_t parseDataHeader(FILE* fp, DataHeader* pHeader) { 945 pHeader->magic = read4LE(fp); 946 pHeader->version = read2LE(fp); 947 pHeader->offsetToData = read2LE(fp); 948 pHeader->startWhen = read8LE(fp); 949 int32_t bytesToRead = pHeader->offsetToData - 16; 950 if (pHeader->version == 1) { 951 pHeader->recordSize = 9; 952 } else if (pHeader->version == 2) { 953 pHeader->recordSize = 10; 954 } else if (pHeader->version == 3) { 955 pHeader->recordSize = read2LE(fp); 956 bytesToRead -= 2; 957 } else { 958 fprintf(stderr, "Unsupported trace file version: %d\n", pHeader->version); 959 return -1; 960 } 961 962 if (fseek(fp, bytesToRead, SEEK_CUR) != 0) { 963 return -1; 964 } 965 966 return 0; 967 } 968 969 /* 970 * Look up a method by it's method ID. 971 * 972 * Returns nullptr if no matching method was found. 973 */ 974 MethodEntry* lookupMethod(DataKeys* pKeys, int64_t methodId) { 975 int32_t lo = 0; 976 int32_t hi = pKeys->numMethods - 1; 977 978 while (hi >= lo) { 979 int32_t mid = (hi + lo) / 2; 980 981 int64_t id = pKeys->methods[mid].methodId; 982 if (id == methodId) /* match */ 983 return &pKeys->methods[mid]; 984 else if (id < methodId) /* too low */ 985 lo = mid + 1; 986 else /* too high */ 987 hi = mid - 1; 988 } 989 990 return nullptr; 991 } 992 993 /* 994 * Reads the next data record, and assigns the data values to threadId, 995 * methodVal and elapsedTime. On end-of-file, the threadId, methodVal, 996 * and elapsedTime are unchanged. Returns 1 on end-of-file, otherwise 997 * returns 0. 998 */ 999 int32_t readDataRecord(FILE* dataFp, DataHeader* dataHeader, int32_t* threadId, 1000 uint32_t* methodVal, uint64_t* elapsedTime) { 1001 int32_t id; 1002 int32_t bytesToRead = dataHeader->recordSize; 1003 if (dataHeader->version == 1) { 1004 id = getc(dataFp); 1005 bytesToRead -= 1; 1006 } else { 1007 id = read2LE(dataFp); 1008 bytesToRead -= 2; 1009 } 1010 if (id == EOF) return 1; 1011 *threadId = id; 1012 1013 *methodVal = read4LE(dataFp); 1014 *elapsedTime = read4LE(dataFp); 1015 bytesToRead -= 8; 1016 1017 while (bytesToRead-- > 0) { 1018 getc(dataFp); 1019 } 1020 1021 if (feof(dataFp)) { 1022 fprintf(stderr, "WARNING: hit EOF mid-record\n"); 1023 return 1; 1024 } 1025 return 0; 1026 } 1027 1028 /* 1029 * Read the key file and use it to produce formatted output from the 1030 * data file. 1031 */ 1032 void dumpTrace() { 1033 static const char* actionStr[] = {"ent", "xit", "unr", "???"}; 1034 MethodEntry bogusMethod = { 1035 0, "???", "???", "???", "???", -1, 0, 0, 1036 0, 0, {nullptr, nullptr}, {nullptr, nullptr}, {0, 0}, 0, 0, -1}; 1037 char bogusBuf[80]; 1038 TraceData traceData; 1039 1040 // printf("Dumping '%s' '%s'\n", dataFileName, keyFileName); 1041 1042 char spaces[MAX_STACK_DEPTH + 1]; 1043 memset(spaces, '.', MAX_STACK_DEPTH); 1044 spaces[MAX_STACK_DEPTH] = '\0'; 1045 1046 for (int32_t i = 0; i < MAX_THREADS; i++) 1047 traceData.depth[i] = 2; // adjust for return from start function 1048 1049 FILE* dataFp = fopen(gOptions.traceFileName, "rb"); 1050 if (dataFp == nullptr) return; 1051 1052 DataKeys* pKeys = parseKeys(dataFp, 1); 1053 if (pKeys == nullptr) { 1054 fclose(dataFp); 1055 return; 1056 } 1057 1058 DataHeader dataHeader; 1059 if (parseDataHeader(dataFp, &dataHeader) < 0) { 1060 fclose(dataFp); 1061 freeDataKeys(pKeys); 1062 return; 1063 } 1064 1065 printf("Trace (threadID action usecs class.method signature):\n"); 1066 1067 while (1) { 1068 /* 1069 * Extract values from file. 1070 */ 1071 int32_t threadId; 1072 uint32_t methodVal; 1073 uint64_t elapsedTime; 1074 if (readDataRecord(dataFp, &dataHeader, &threadId, &methodVal, &elapsedTime)) 1075 break; 1076 1077 int32_t action = METHOD_ACTION(methodVal); 1078 int64_t methodId = METHOD_ID(methodVal); 1079 1080 /* 1081 * Generate a line of output. 1082 */ 1083 int64_t lastEnter = 0; 1084 int32_t mismatch = 0; 1085 if (action == METHOD_TRACE_ENTER) { 1086 traceData.depth[threadId]++; 1087 lastEnter = methodId; 1088 } else { 1089 /* quick test for mismatched adjacent enter/exit */ 1090 if (lastEnter != 0 && lastEnter != methodId) mismatch = 1; 1091 } 1092 1093 int32_t printDepth = traceData.depth[threadId]; 1094 char depthNote = ' '; 1095 if (printDepth < 0) { 1096 printDepth = 0; 1097 depthNote = '-'; 1098 } else if (printDepth > MAX_STACK_DEPTH) { 1099 printDepth = MAX_STACK_DEPTH; 1100 depthNote = '+'; 1101 } 1102 1103 MethodEntry* method = lookupMethod(pKeys, methodId); 1104 if (method == nullptr) { 1105 method = &bogusMethod; 1106 sprintf(bogusBuf, "methodId: %#" PRIx64 "", methodId); 1107 method->signature = bogusBuf; 1108 } 1109 1110 if (method->methodName) { 1111 printf("%2d %s%c %8" PRIu64 "%c%s%s.%s %s\n", threadId, actionStr[action], 1112 mismatch ? '!' : ' ', elapsedTime, depthNote, 1113 spaces + (MAX_STACK_DEPTH - printDepth), method->className, 1114 method->methodName, method->signature); 1115 } else { 1116 printf("%2d %s%c %8" PRIu64 "%c%s%s\n", threadId, actionStr[action], 1117 mismatch ? '!' : ' ', elapsedTime, depthNote, 1118 spaces + (MAX_STACK_DEPTH - printDepth), method->className); 1119 } 1120 1121 if (action != METHOD_TRACE_ENTER) { 1122 traceData.depth[threadId]--; /* METHOD_TRACE_EXIT or METHOD_TRACE_UNROLL */ 1123 lastEnter = 0; 1124 } 1125 1126 mismatch = 0; 1127 } 1128 1129 fclose(dataFp); 1130 freeDataKeys(pKeys); 1131 } 1132 1133 /* This routine adds the given time to the parent and child methods. 1134 * This is called when the child routine exits, after the child has 1135 * been popped from the stack. The elapsedTime parameter is the 1136 * duration of the child routine, including time spent in called routines. 1137 */ 1138 void addInclusiveTime(MethodEntry* parent, MethodEntry* child, uint64_t elapsedTime) { 1139 #if 0 1140 bool verbose = false; 1141 if (strcmp(child->className, debugClassName) == 0) 1142 verbose = true; 1143 #endif 1144 1145 int32_t childIsRecursive = (child->recursiveEntries > 0); 1146 int32_t parentIsRecursive = (parent->recursiveEntries > 1); 1147 1148 if (child->recursiveEntries == 0) { 1149 child->elapsedInclusive += elapsedTime; 1150 } else if (child->recursiveEntries == 1) { 1151 child->recursiveInclusive += elapsedTime; 1152 } 1153 child->numCalls[childIsRecursive] += 1; 1154 1155 #if 0 1156 if (verbose) { 1157 fprintf(stderr, 1158 "%s %d elapsedTime: %lld eI: %lld, rI: %lld\n", 1159 child->className, child->recursiveEntries, 1160 elapsedTime, child->elapsedInclusive, 1161 child->recursiveInclusive); 1162 } 1163 #endif 1164 1165 /* Find the child method in the parent */ 1166 TimedMethod* pTimed; 1167 TimedMethod* children = parent->children[parentIsRecursive]; 1168 for (pTimed = children; pTimed; pTimed = pTimed->next) { 1169 if (pTimed->method == child) { 1170 pTimed->elapsedInclusive += elapsedTime; 1171 pTimed->numCalls += 1; 1172 break; 1173 } 1174 } 1175 if (pTimed == nullptr) { 1176 /* Allocate a new TimedMethod */ 1177 pTimed = new TimedMethod(); 1178 pTimed->elapsedInclusive = elapsedTime; 1179 pTimed->numCalls = 1; 1180 pTimed->method = child; 1181 1182 /* Add it to the front of the list */ 1183 pTimed->next = children; 1184 parent->children[parentIsRecursive] = pTimed; 1185 } 1186 1187 /* Find the parent method in the child */ 1188 TimedMethod* parents = child->parents[childIsRecursive]; 1189 for (pTimed = parents; pTimed; pTimed = pTimed->next) { 1190 if (pTimed->method == parent) { 1191 pTimed->elapsedInclusive += elapsedTime; 1192 pTimed->numCalls += 1; 1193 break; 1194 } 1195 } 1196 if (pTimed == nullptr) { 1197 /* Allocate a new TimedMethod */ 1198 pTimed = new TimedMethod(); 1199 pTimed->elapsedInclusive = elapsedTime; 1200 pTimed->numCalls = 1; 1201 pTimed->method = parent; 1202 1203 /* Add it to the front of the list */ 1204 pTimed->next = parents; 1205 child->parents[childIsRecursive] = pTimed; 1206 } 1207 1208 #if 0 1209 if (verbose) { 1210 fprintf(stderr, 1211 " %s %d eI: %lld\n", 1212 parent->className, parent->recursiveEntries, 1213 pTimed->elapsedInclusive); 1214 } 1215 #endif 1216 } 1217 1218 /* Sorts a linked list and returns a newly allocated array containing 1219 * the sorted entries. 1220 */ 1221 TimedMethod* sortTimedMethodList(TimedMethod* list, int32_t* num) { 1222 /* Count the elements */ 1223 TimedMethod* pTimed; 1224 int32_t num_entries = 0; 1225 for (pTimed = list; pTimed; pTimed = pTimed->next) num_entries += 1; 1226 *num = num_entries; 1227 if (num_entries == 0) return nullptr; 1228 1229 /* Copy all the list elements to a new array and sort them */ 1230 int32_t ii; 1231 TimedMethod* sorted = new TimedMethod[num_entries]; 1232 for (ii = 0, pTimed = list; pTimed; pTimed = pTimed->next, ++ii) 1233 memcpy(&sorted[ii], pTimed, sizeof(TimedMethod)); 1234 qsort(sorted, num_entries, sizeof(TimedMethod), compareTimedMethod); 1235 1236 /* Fix up the "next" pointers so that they work. */ 1237 for (ii = 0; ii < num_entries - 1; ++ii) sorted[ii].next = &sorted[ii + 1]; 1238 sorted[num_entries - 1].next = nullptr; 1239 1240 return sorted; 1241 } 1242 1243 /* Define flag values for printInclusiveMethod() */ 1244 static const int32_t kIsRecursive = 1; 1245 1246 /* This prints the inclusive stats for all the parents or children of a 1247 * method, depending on the list that is passed in. 1248 */ 1249 void printInclusiveMethod(MethodEntry* method, TimedMethod* list, int32_t numCalls, int32_t flags) { 1250 char buf[80]; 1251 const char* anchor_close = ""; 1252 const char* spaces = " "; /* 6 spaces */ 1253 int32_t num_spaces = strlen(spaces); 1254 const char* space_ptr = &spaces[num_spaces]; 1255 char classBuf[HTML_BUFSIZE], methodBuf[HTML_BUFSIZE]; 1256 char signatureBuf[HTML_BUFSIZE]; 1257 1258 if (gOptions.outputHtml) anchor_close = "</a>"; 1259 1260 int32_t num; 1261 TimedMethod* sorted = sortTimedMethodList(list, &num); 1262 double methodTotal = method->elapsedInclusive; 1263 for (TimedMethod* pTimed = sorted; pTimed; pTimed = pTimed->next) { 1264 MethodEntry* relative = pTimed->method; 1265 const char* className = relative->className; 1266 const char* methodName = relative->methodName; 1267 const char* signature = relative->signature; 1268 double per = 100.0 * pTimed->elapsedInclusive / methodTotal; 1269 sprintf(buf, "[%d]", relative->index); 1270 if (gOptions.outputHtml) { 1271 int32_t len = strlen(buf); 1272 if (len > num_spaces) len = num_spaces; 1273 sprintf(buf, "<a href=\"#m%d\">[%d]", relative->index, relative->index); 1274 space_ptr = &spaces[len]; 1275 className = htmlEscape(className, classBuf, HTML_BUFSIZE); 1276 methodName = htmlEscape(methodName, methodBuf, HTML_BUFSIZE); 1277 signature = htmlEscape(signature, signatureBuf, HTML_BUFSIZE); 1278 } 1279 int32_t nCalls = numCalls; 1280 if (nCalls == 0) nCalls = relative->numCalls[0] + relative->numCalls[1]; 1281 if (relative->methodName) { 1282 if (flags & kIsRecursive) { 1283 // Don't display percentages for recursive functions 1284 printf("%6s %5s %6s %s%6s%s %6d/%-6d %9" PRIu64 " %s.%s %s\n", "", "", 1285 "", space_ptr, buf, anchor_close, pTimed->numCalls, nCalls, 1286 pTimed->elapsedInclusive, className, methodName, signature); 1287 } else { 1288 printf("%6s %5s %5.1f%% %s%6s%s %6d/%-6d %9" PRIu64 " %s.%s %s\n", "", 1289 "", per, space_ptr, buf, anchor_close, pTimed->numCalls, nCalls, 1290 pTimed->elapsedInclusive, className, methodName, signature); 1291 } 1292 } else { 1293 if (flags & kIsRecursive) { 1294 // Don't display percentages for recursive functions 1295 printf("%6s %5s %6s %s%6s%s %6d/%-6d %9" PRIu64 " %s\n", "", "", "", 1296 space_ptr, buf, anchor_close, pTimed->numCalls, nCalls, 1297 pTimed->elapsedInclusive, className); 1298 } else { 1299 printf("%6s %5s %5.1f%% %s%6s%s %6d/%-6d %9" PRIu64 " %s\n", "", "", 1300 per, space_ptr, buf, anchor_close, pTimed->numCalls, nCalls, 1301 pTimed->elapsedInclusive, className); 1302 } 1303 } 1304 } 1305 } 1306 1307 void countRecursiveEntries(CallStack* pStack, int32_t top, MethodEntry* method) { 1308 method->recursiveEntries = 0; 1309 for (int32_t ii = 0; ii < top; ++ii) { 1310 if (pStack->calls[ii].method == method) method->recursiveEntries += 1; 1311 } 1312 } 1313 1314 void stackDump(CallStack* pStack, int32_t top) { 1315 for (int32_t ii = 0; ii < top; ++ii) { 1316 MethodEntry* method = pStack->calls[ii].method; 1317 uint64_t entryTime = pStack->calls[ii].entryTime; 1318 if (method->methodName) { 1319 fprintf(stderr, " %2d: %8" PRIu64 " %s.%s %s\n", ii, entryTime, 1320 method->className, method->methodName, method->signature); 1321 } else { 1322 fprintf(stderr, " %2d: %8" PRIu64 " %s\n", ii, entryTime, method->className); 1323 } 1324 } 1325 } 1326 1327 void outputTableOfContents() { 1328 printf("<a name=\"contents\"></a>\n"); 1329 printf("<h2>Table of Contents</h2>\n"); 1330 printf("<ul>\n"); 1331 printf(" <li><a href=\"#exclusive\">Exclusive profile</a></li>\n"); 1332 printf(" <li><a href=\"#inclusive\">Inclusive profile</a></li>\n"); 1333 printf(" <li><a href=\"#class\">Class/method profile</a></li>\n"); 1334 printf(" <li><a href=\"#method\">Method/class profile</a></li>\n"); 1335 printf("</ul>\n\n"); 1336 } 1337 1338 void outputNavigationBar() { 1339 printf("<a href=\"#contents\">[Top]</a>\n"); 1340 printf("<a href=\"#exclusive\">[Exclusive]</a>\n"); 1341 printf("<a href=\"#inclusive\">[Inclusive]</a>\n"); 1342 printf("<a href=\"#class\">[Class]</a>\n"); 1343 printf("<a href=\"#method\">[Method]</a>\n"); 1344 printf("<br><br>\n"); 1345 } 1346 1347 void printExclusiveProfile(MethodEntry** pMethods, int32_t numMethods, uint64_t sumThreadTime) { 1348 char classBuf[HTML_BUFSIZE], methodBuf[HTML_BUFSIZE]; 1349 char signatureBuf[HTML_BUFSIZE]; 1350 const char* anchor_close = ""; 1351 char anchor_buf[80]; 1352 anchor_buf[0] = 0; 1353 if (gOptions.outputHtml) { 1354 anchor_close = "</a>"; 1355 printf("<a name=\"exclusive\"></a>\n"); 1356 printf("<hr>\n"); 1357 outputNavigationBar(); 1358 } else { 1359 printf("\n%s\n", profileSeparator); 1360 } 1361 1362 /* First, sort the methods into decreasing order of inclusive 1363 * elapsed time so that we can assign the method indices. 1364 */ 1365 qsort(pMethods, numMethods, sizeof(MethodEntry*), compareElapsedInclusive); 1366 1367 for (int32_t ii = 0; ii < numMethods; ++ii) pMethods[ii]->index = ii; 1368 1369 /* Sort the methods into decreasing order of exclusive elapsed time. */ 1370 qsort(pMethods, numMethods, sizeof(MethodEntry*), compareElapsedExclusive); 1371 1372 printf("Total cycles: %" PRIu64 "\n\n", sumThreadTime); 1373 if (gOptions.outputHtml) { 1374 printf("<br><br>\n"); 1375 } 1376 printf("Exclusive elapsed times for each method, not including time spent in\n"); 1377 printf("children, sorted by exclusive time.\n\n"); 1378 if (gOptions.outputHtml) { 1379 printf("<br><br>\n<pre>\n"); 1380 } 1381 1382 printf(" Usecs self %% sum %% Method\n"); 1383 1384 double sum = 0; 1385 double total = sumThreadTime; 1386 for (int32_t ii = 0; ii < numMethods; ++ii) { 1387 MethodEntry* method = pMethods[ii]; 1388 /* Don't show methods with zero cycles */ 1389 if (method->elapsedExclusive == 0) break; 1390 const char* className = method->className; 1391 const char* methodName = method->methodName; 1392 const char* signature = method->signature; 1393 sum += method->elapsedExclusive; 1394 double per = 100.0 * method->elapsedExclusive / total; 1395 double sum_per = 100.0 * sum / total; 1396 if (gOptions.outputHtml) { 1397 sprintf(anchor_buf, "<a href=\"#m%d\">", method->index); 1398 className = htmlEscape(className, classBuf, HTML_BUFSIZE); 1399 methodName = htmlEscape(methodName, methodBuf, HTML_BUFSIZE); 1400 signature = htmlEscape(signature, signatureBuf, HTML_BUFSIZE); 1401 } 1402 if (method->methodName) { 1403 printf("%9" PRIu64 " %6.2f %6.2f %s[%d]%s %s.%s %s\n", 1404 method->elapsedExclusive, per, sum_per, anchor_buf, method->index, 1405 anchor_close, className, methodName, signature); 1406 } else { 1407 printf("%9" PRIu64 " %6.2f %6.2f %s[%d]%s %s\n", 1408 method->elapsedExclusive, per, sum_per, anchor_buf, method->index, 1409 anchor_close, className); 1410 } 1411 } 1412 if (gOptions.outputHtml) { 1413 printf("</pre>\n"); 1414 } 1415 } 1416 1417 /* check to make sure that the child method meets the threshold of the parent */ 1418 int32_t checkThreshold(MethodEntry* parent, MethodEntry* child) { 1419 double parentTime = parent->elapsedInclusive; 1420 double childTime = child->elapsedInclusive; 1421 int64_t percentage = (childTime / parentTime) * 100.0; 1422 return (percentage < gOptions.threshold) ? 0 : 1; 1423 } 1424 1425 void createLabels(FILE* file, MethodEntry* method) { 1426 fprintf(file, 1427 "node%d[label = \"[%d] %s.%s (%" PRIu64 ", %" PRIu64 ", %d)\"]\n", 1428 method->index, method->index, method->className, method->methodName, 1429 method->elapsedInclusive / 1000, method->elapsedExclusive / 1000, 1430 method->numCalls[0]); 1431 1432 method->graphState = GRAPH_LABEL_VISITED; 1433 1434 for (TimedMethod* child = method->children[0]; child; child = child->next) { 1435 MethodEntry* childMethod = child->method; 1436 1437 if ((childMethod->graphState & GRAPH_LABEL_VISITED) == 0 && 1438 checkThreshold(method, childMethod)) { 1439 createLabels(file, child->method); 1440 } 1441 } 1442 } 1443 1444 void createLinks(FILE* file, MethodEntry* method) { 1445 method->graphState |= GRAPH_NODE_VISITED; 1446 1447 for (TimedMethod* child = method->children[0]; child; child = child->next) { 1448 MethodEntry* childMethod = child->method; 1449 if (checkThreshold(method, child->method)) { 1450 fprintf(file, "node%d -> node%d\n", method->index, child->method->index); 1451 // only visit children that haven't been visited before 1452 if ((childMethod->graphState & GRAPH_NODE_VISITED) == 0) { 1453 createLinks(file, child->method); 1454 } 1455 } 1456 } 1457 } 1458 1459 void createInclusiveProfileGraphNew(DataKeys* dataKeys) { 1460 // create a temporary file in /tmp 1461 char path[FILENAME_MAX]; 1462 if (gOptions.keepDotFile) { 1463 snprintf(path, FILENAME_MAX, "%s.dot", gOptions.graphFileName); 1464 } else { 1465 snprintf(path, FILENAME_MAX, "dot-%d-%d.dot", (int32_t)time(nullptr), rand()); 1466 } 1467 1468 FILE* file = fopen(path, "w+"); 1469 1470 fprintf(file, "digraph g {\nnode [shape = record,height=.1];\n"); 1471 1472 createLabels(file, dataKeys->methods); 1473 createLinks(file, dataKeys->methods); 1474 1475 fprintf(file, "}"); 1476 fclose(file); 1477 1478 // now that we have the dot file generate the image 1479 char command[1024]; 1480 snprintf(command, 1024, "dot -Tpng -o \"%s\" \"%s\"", gOptions.graphFileName, path); 1481 1482 system(command); 1483 1484 if (!gOptions.keepDotFile) { 1485 remove(path); 1486 } 1487 } 1488 1489 void printInclusiveProfile(MethodEntry** pMethods, int32_t numMethods, uint64_t sumThreadTime) { 1490 char classBuf[HTML_BUFSIZE], methodBuf[HTML_BUFSIZE]; 1491 char signatureBuf[HTML_BUFSIZE]; 1492 char anchor_buf[80]; 1493 const char* anchor_close = ""; 1494 anchor_buf[0] = 0; 1495 if (gOptions.outputHtml) { 1496 anchor_close = "</a>"; 1497 printf("<a name=\"inclusive\"></a>\n"); 1498 printf("<hr>\n"); 1499 outputNavigationBar(); 1500 } else { 1501 printf("\n%s\n", profileSeparator); 1502 } 1503 1504 /* Sort the methods into decreasing order of inclusive elapsed time. */ 1505 qsort(pMethods, numMethods, sizeof(MethodEntry*), compareElapsedInclusive); 1506 1507 printf("\nInclusive elapsed times for each method and its parents and children,\n"); 1508 printf("sorted by inclusive time.\n\n"); 1509 1510 if (gOptions.outputHtml) { 1511 printf("<br><br>\n<pre>\n"); 1512 } 1513 1514 printf("index %%/total %%/self index calls usecs name\n"); 1515 1516 double total = sumThreadTime; 1517 for (int32_t ii = 0; ii < numMethods; ++ii) { 1518 char buf[40]; 1519 1520 MethodEntry* method = pMethods[ii]; 1521 /* Don't show methods with zero cycles */ 1522 if (method->elapsedInclusive == 0) break; 1523 1524 const char* className = method->className; 1525 const char* methodName = method->methodName; 1526 const char* signature = method->signature; 1527 1528 if (gOptions.outputHtml) { 1529 printf("<a name=\"m%d\"></a>", method->index); 1530 className = htmlEscape(className, classBuf, HTML_BUFSIZE); 1531 methodName = htmlEscape(methodName, methodBuf, HTML_BUFSIZE); 1532 signature = htmlEscape(signature, signatureBuf, HTML_BUFSIZE); 1533 } 1534 printf("----------------------------------------------------\n"); 1535 1536 /* Sort and print the parents */ 1537 int32_t numCalls = method->numCalls[0] + method->numCalls[1]; 1538 printInclusiveMethod(method, method->parents[0], numCalls, 0); 1539 if (method->parents[1]) { 1540 printf(" +++++++++++++++++++++++++\n"); 1541 printInclusiveMethod(method, method->parents[1], numCalls, kIsRecursive); 1542 } 1543 1544 double per = 100.0 * method->elapsedInclusive / total; 1545 sprintf(buf, "[%d]", ii); 1546 if (method->methodName) { 1547 printf("%-6s %5.1f%% %5s %6s %6d+%-6d %9" PRIu64 " %s.%s %s\n", buf, 1548 per, "", "", method->numCalls[0], method->numCalls[1], 1549 method->elapsedInclusive, className, methodName, signature); 1550 } else { 1551 printf("%-6s %5.1f%% %5s %6s %6d+%-6d %9" PRIu64 " %s\n", buf, per, "", 1552 "", method->numCalls[0], method->numCalls[1], 1553 method->elapsedInclusive, className); 1554 } 1555 double excl_per = 100.0 * method->topExclusive / method->elapsedInclusive; 1556 printf("%6s %5s %5.1f%% %6s %6s %6s %9" PRIu64 "\n", "", "", excl_per, 1557 "excl", "", "", method->topExclusive); 1558 1559 /* Sort and print the children */ 1560 printInclusiveMethod(method, method->children[0], 0, 0); 1561 if (method->children[1]) { 1562 printf(" +++++++++++++++++++++++++\n"); 1563 printInclusiveMethod(method, method->children[1], 0, kIsRecursive); 1564 } 1565 } 1566 if (gOptions.outputHtml) { 1567 printf("</pre>\n"); 1568 } 1569 } 1570 1571 void createClassList(TraceData* traceData, MethodEntry** pMethods, int32_t numMethods) { 1572 /* Sort the methods into alphabetical order to find the unique class 1573 * names. 1574 */ 1575 qsort(pMethods, numMethods, sizeof(MethodEntry*), compareClassNames); 1576 1577 /* Count the number of unique class names. */ 1578 const char* currentClassName = ""; 1579 const char* firstClassName = nullptr; 1580 traceData->numClasses = 0; 1581 for (int32_t ii = 0; ii < numMethods; ++ii) { 1582 if (pMethods[ii]->methodName == nullptr) { 1583 continue; 1584 } 1585 if (strcmp(pMethods[ii]->className, currentClassName) != 0) { 1586 // Remember the first one 1587 if (firstClassName == nullptr) { 1588 firstClassName = pMethods[ii]->className; 1589 } 1590 traceData->numClasses += 1; 1591 currentClassName = pMethods[ii]->className; 1592 } 1593 } 1594 1595 if (traceData->numClasses == 0) { 1596 traceData->classes = nullptr; 1597 return; 1598 } 1599 1600 /* Allocate space for all of the unique class names */ 1601 traceData->classes = new ClassEntry[traceData->numClasses]; 1602 1603 /* Initialize the classes array */ 1604 memset(traceData->classes, 0, sizeof(ClassEntry) * traceData->numClasses); 1605 ClassEntry* pClass = traceData->classes; 1606 pClass->className = currentClassName = firstClassName; 1607 int32_t prevNumMethods = 0; 1608 for (int32_t ii = 0; ii < numMethods; ++ii) { 1609 if (pMethods[ii]->methodName == nullptr) { 1610 continue; 1611 } 1612 if (strcmp(pMethods[ii]->className, currentClassName) != 0) { 1613 pClass->numMethods = prevNumMethods; 1614 (++pClass)->className = currentClassName = pMethods[ii]->className; 1615 prevNumMethods = 0; 1616 } 1617 prevNumMethods += 1; 1618 } 1619 pClass->numMethods = prevNumMethods; 1620 1621 /* Create the array of MethodEntry pointers for each class */ 1622 pClass = nullptr; 1623 currentClassName = ""; 1624 int32_t nextMethod = 0; 1625 for (int32_t ii = 0; ii < numMethods; ++ii) { 1626 if (pMethods[ii]->methodName == nullptr) { 1627 continue; 1628 } 1629 if (strcmp(pMethods[ii]->className, currentClassName) != 0) { 1630 currentClassName = pMethods[ii]->className; 1631 if (pClass == nullptr) 1632 pClass = traceData->classes; 1633 else 1634 pClass++; 1635 /* Allocate space for the methods array */ 1636 pClass->methods = new MethodEntry*[pClass->numMethods]; 1637 nextMethod = 0; 1638 } 1639 pClass->methods[nextMethod++] = pMethods[ii]; 1640 } 1641 } 1642 1643 /* Prints a number of html non-breaking spaces according so that the length 1644 * of the string "buf" is at least "width" characters wide. If width is 1645 * negative, then trailing spaces are added instead of leading spaces. 1646 */ 1647 void printHtmlField(char* buf, int32_t width) { 1648 int32_t leadingSpaces = 1; 1649 if (width < 0) { 1650 width = -width; 1651 leadingSpaces = 0; 1652 } 1653 int32_t len = strlen(buf); 1654 int32_t numSpaces = width - len; 1655 if (numSpaces <= 0) { 1656 printf("%s", buf); 1657 return; 1658 } 1659 if (leadingSpaces == 0) printf("%s", buf); 1660 for (int32_t ii = 0; ii < numSpaces; ++ii) printf(" "); 1661 if (leadingSpaces == 1) printf("%s", buf); 1662 } 1663 1664 void printClassProfiles(TraceData* traceData, uint64_t sumThreadTime) { 1665 char classBuf[HTML_BUFSIZE]; 1666 char methodBuf[HTML_BUFSIZE]; 1667 char signatureBuf[HTML_BUFSIZE]; 1668 1669 if (gOptions.outputHtml) { 1670 printf("<a name=\"class\"></a>\n"); 1671 printf("<hr>\n"); 1672 outputNavigationBar(); 1673 } else { 1674 printf("\n%s\n", profileSeparator); 1675 } 1676 1677 if (traceData->numClasses == 0) { 1678 printf("\nNo classes.\n"); 1679 if (gOptions.outputHtml) { 1680 printf("<br><br>\n"); 1681 } 1682 return; 1683 } 1684 1685 printf("\nExclusive elapsed time for each class, summed over all the methods\n"); 1686 printf("in the class.\n\n"); 1687 if (gOptions.outputHtml) { 1688 printf("<br><br>\n"); 1689 } 1690 1691 /* For each class, sum the exclusive times in all of the methods 1692 * in that class. Also sum the number of method calls. Also 1693 * sort the methods so the most expensive appear at the top. 1694 */ 1695 ClassEntry* pClass = traceData->classes; 1696 for (int32_t ii = 0; ii < traceData->numClasses; ++ii, ++pClass) { 1697 // printf("%s %d methods\n", pClass->className, pClass->numMethods); 1698 int32_t numMethods = pClass->numMethods; 1699 for (int32_t jj = 0; jj < numMethods; ++jj) { 1700 MethodEntry* method = pClass->methods[jj]; 1701 pClass->elapsedExclusive += method->elapsedExclusive; 1702 pClass->numCalls[0] += method->numCalls[0]; 1703 pClass->numCalls[1] += method->numCalls[1]; 1704 } 1705 1706 /* Sort the methods into decreasing order of exclusive time */ 1707 qsort(pClass->methods, numMethods, sizeof(MethodEntry*), compareElapsedExclusive); 1708 } 1709 1710 /* Allocate an array of pointers to the classes for more efficient sorting. */ 1711 ClassEntry** pClasses = new ClassEntry*[traceData->numClasses]; 1712 for (int32_t ii = 0; ii < traceData->numClasses; ++ii) 1713 pClasses[ii] = &traceData->classes[ii]; 1714 1715 /* Sort the classes into decreasing order of exclusive time */ 1716 qsort(pClasses, traceData->numClasses, sizeof(ClassEntry*), compareClassExclusive); 1717 1718 if (gOptions.outputHtml) { 1719 printf( 1720 "<div class=\"header\"><span " 1721 "class=\"parent\"> </span> "); 1722 printf("Cycles %%/total Cumul.%% Calls+Recur Class</div>\n"); 1723 } else { 1724 printf(" Cycles %%/total Cumul.%% Calls+Recur Class\n"); 1725 } 1726 1727 double sum = 0; 1728 double total = sumThreadTime; 1729 for (int32_t ii = 0; ii < traceData->numClasses; ++ii) { 1730 /* Skip classes with zero cycles */ 1731 pClass = pClasses[ii]; 1732 if (pClass->elapsedExclusive == 0) break; 1733 1734 sum += pClass->elapsedExclusive; 1735 double per = 100.0 * pClass->elapsedExclusive / total; 1736 double sum_per = 100.0 * sum / total; 1737 const char* className = pClass->className; 1738 if (gOptions.outputHtml) { 1739 char buf[80]; 1740 1741 className = htmlEscape(className, classBuf, HTML_BUFSIZE); 1742 printf( 1743 "<div class=\"link\" onClick=\"javascript:toggle('d%d')\" " 1744 "onMouseOver=\"javascript:onMouseOver(this)\" " 1745 "onMouseOut=\"javascript:onMouseOut(this)\"><span class=\"parent\" " 1746 "id=\"xd%d\">+</span>", 1747 ii, ii); 1748 sprintf(buf, "%" PRIu64, pClass->elapsedExclusive); 1749 printHtmlField(buf, 9); 1750 printf(" "); 1751 sprintf(buf, "%.1f", per); 1752 printHtmlField(buf, 7); 1753 printf(" "); 1754 sprintf(buf, "%.1f", sum_per); 1755 printHtmlField(buf, 7); 1756 printf(" "); 1757 sprintf(buf, "%d", pClass->numCalls[0]); 1758 printHtmlField(buf, 6); 1759 printf("+"); 1760 sprintf(buf, "%d", pClass->numCalls[1]); 1761 printHtmlField(buf, -6); 1762 printf(" "); 1763 printf("%s", className); 1764 printf("</div>\n"); 1765 printf("<div class=\"parent\" id=\"d%d\">\n", ii); 1766 } else { 1767 printf("---------------------------------------------\n"); 1768 printf("%9" PRIu64 " %7.1f %7.1f %6d+%-6d %s\n", pClass->elapsedExclusive, 1769 per, sum_per, pClass->numCalls[0], pClass->numCalls[1], className); 1770 } 1771 1772 int32_t numMethods = pClass->numMethods; 1773 double classExclusive = pClass->elapsedExclusive; 1774 double sumMethods = 0; 1775 for (int32_t jj = 0; jj < numMethods; ++jj) { 1776 MethodEntry* method = pClass->methods[jj]; 1777 const char* methodName = method->methodName; 1778 const char* signature = method->signature; 1779 per = 100.0 * method->elapsedExclusive / classExclusive; 1780 sumMethods += method->elapsedExclusive; 1781 sum_per = 100.0 * sumMethods / classExclusive; 1782 if (gOptions.outputHtml) { 1783 char buf[80]; 1784 1785 methodName = htmlEscape(methodName, methodBuf, HTML_BUFSIZE); 1786 signature = htmlEscape(signature, signatureBuf, HTML_BUFSIZE); 1787 printf("<div class=\"leaf\"><span class=\"leaf\"> </span>"); 1788 sprintf(buf, "%" PRIu64, method->elapsedExclusive); 1789 printHtmlField(buf, 9); 1790 printf(" "); 1791 sprintf(buf, "%" PRIu64, method->elapsedInclusive); 1792 printHtmlField(buf, 9); 1793 printf(" "); 1794 sprintf(buf, "%.1f", per); 1795 printHtmlField(buf, 7); 1796 printf(" "); 1797 sprintf(buf, "%.1f", sum_per); 1798 printHtmlField(buf, 7); 1799 printf(" "); 1800 sprintf(buf, "%d", method->numCalls[0]); 1801 printHtmlField(buf, 6); 1802 printf("+"); 1803 sprintf(buf, "%d", method->numCalls[1]); 1804 printHtmlField(buf, -6); 1805 printf(" "); 1806 printf("<a href=\"#m%d\">[%d]</a> %s %s", method->index, 1807 method->index, methodName, signature); 1808 printf("</div>\n"); 1809 } else { 1810 printf("%9" PRIu64 " %9" PRIu64 " %7.1f %7.1f %6d+%-6d [%d] %s %s\n", 1811 method->elapsedExclusive, method->elapsedInclusive, per, sum_per, 1812 method->numCalls[0], method->numCalls[1], method->index, 1813 methodName, signature); 1814 } 1815 } 1816 if (gOptions.outputHtml) { 1817 printf("</div>\n"); 1818 } 1819 } 1820 } 1821 1822 void createUniqueMethodList(TraceData* traceData, MethodEntry** pMethods, int32_t numMethods) { 1823 /* Sort the methods into alphabetical order of method names 1824 * to find the unique method names. 1825 */ 1826 qsort(pMethods, numMethods, sizeof(MethodEntry*), compareMethodNames); 1827 1828 /* Count the number of unique method names, ignoring class and signature. */ 1829 const char* currentMethodName = ""; 1830 traceData->numUniqueMethods = 0; 1831 for (int32_t ii = 0; ii < numMethods; ++ii) { 1832 if (pMethods[ii]->methodName == nullptr) continue; 1833 if (strcmp(pMethods[ii]->methodName, currentMethodName) != 0) { 1834 traceData->numUniqueMethods += 1; 1835 currentMethodName = pMethods[ii]->methodName; 1836 } 1837 } 1838 if (traceData->numUniqueMethods == 0) return; 1839 1840 /* Allocate space for pointers to all of the unique methods */ 1841 traceData->uniqueMethods = new UniqueMethodEntry[traceData->numUniqueMethods]; 1842 1843 /* Initialize the uniqueMethods array */ 1844 memset(traceData->uniqueMethods, 0, sizeof(UniqueMethodEntry) * traceData->numUniqueMethods); 1845 UniqueMethodEntry* pUnique = traceData->uniqueMethods; 1846 currentMethodName = nullptr; 1847 int32_t prevNumMethods = 0; 1848 for (int32_t ii = 0; ii < numMethods; ++ii) { 1849 if (pMethods[ii]->methodName == nullptr) continue; 1850 if (currentMethodName == nullptr) currentMethodName = pMethods[ii]->methodName; 1851 if (strcmp(pMethods[ii]->methodName, currentMethodName) != 0) { 1852 currentMethodName = pMethods[ii]->methodName; 1853 pUnique->numMethods = prevNumMethods; 1854 pUnique++; 1855 prevNumMethods = 0; 1856 } 1857 prevNumMethods += 1; 1858 } 1859 pUnique->numMethods = prevNumMethods; 1860 1861 /* Create the array of MethodEntry pointers for each unique method */ 1862 pUnique = nullptr; 1863 currentMethodName = ""; 1864 int32_t nextMethod = 0; 1865 for (int32_t ii = 0; ii < numMethods; ++ii) { 1866 if (pMethods[ii]->methodName == nullptr) continue; 1867 if (strcmp(pMethods[ii]->methodName, currentMethodName) != 0) { 1868 currentMethodName = pMethods[ii]->methodName; 1869 if (pUnique == nullptr) 1870 pUnique = traceData->uniqueMethods; 1871 else 1872 pUnique++; 1873 /* Allocate space for the methods array */ 1874 pUnique->methods = new MethodEntry*[pUnique->numMethods]; 1875 nextMethod = 0; 1876 } 1877 pUnique->methods[nextMethod++] = pMethods[ii]; 1878 } 1879 } 1880 1881 void printMethodProfiles(TraceData* traceData, uint64_t sumThreadTime) { 1882 char classBuf[HTML_BUFSIZE], methodBuf[HTML_BUFSIZE]; 1883 char signatureBuf[HTML_BUFSIZE]; 1884 1885 if (traceData->numUniqueMethods == 0) return; 1886 1887 if (gOptions.outputHtml) { 1888 printf("<a name=\"method\"></a>\n"); 1889 printf("<hr>\n"); 1890 outputNavigationBar(); 1891 } else { 1892 printf("\n%s\n", profileSeparator); 1893 } 1894 1895 printf("\nExclusive elapsed time for each method, summed over all the classes\n"); 1896 printf("that contain a method with the same name.\n\n"); 1897 if (gOptions.outputHtml) { 1898 printf("<br><br>\n"); 1899 } 1900 1901 /* For each unique method, sum the exclusive times in all of the methods 1902 * with the same name. Also sum the number of method calls. Also 1903 * sort the methods so the most expensive appear at the top. 1904 */ 1905 UniqueMethodEntry* pUnique = traceData->uniqueMethods; 1906 for (int32_t ii = 0; ii < traceData->numUniqueMethods; ++ii, ++pUnique) { 1907 int32_t numMethods = pUnique->numMethods; 1908 for (int32_t jj = 0; jj < numMethods; ++jj) { 1909 MethodEntry* method = pUnique->methods[jj]; 1910 pUnique->elapsedExclusive += method->elapsedExclusive; 1911 pUnique->numCalls[0] += method->numCalls[0]; 1912 pUnique->numCalls[1] += method->numCalls[1]; 1913 } 1914 1915 /* Sort the methods into decreasing order of exclusive time */ 1916 qsort(pUnique->methods, numMethods, sizeof(MethodEntry*), compareElapsedExclusive); 1917 } 1918 1919 /* Allocate an array of pointers to the methods for more efficient sorting. */ 1920 UniqueMethodEntry** pUniqueMethods = new UniqueMethodEntry*[traceData->numUniqueMethods]; 1921 for (int32_t ii = 0; ii < traceData->numUniqueMethods; ++ii) 1922 pUniqueMethods[ii] = &traceData->uniqueMethods[ii]; 1923 1924 /* Sort the methods into decreasing order of exclusive time */ 1925 qsort(pUniqueMethods, traceData->numUniqueMethods, sizeof(UniqueMethodEntry*), 1926 compareUniqueExclusive); 1927 1928 if (gOptions.outputHtml) { 1929 printf( 1930 "<div class=\"header\"><span " 1931 "class=\"parent\"> </span> "); 1932 printf("Cycles %%/total Cumul.%% Calls+Recur Method</div>\n"); 1933 } else { 1934 printf(" Cycles %%/total Cumul.%% Calls+Recur Method\n"); 1935 } 1936 1937 double sum = 0; 1938 double total = sumThreadTime; 1939 for (int32_t ii = 0; ii < traceData->numUniqueMethods; ++ii) { 1940 /* Skip methods with zero cycles */ 1941 pUnique = pUniqueMethods[ii]; 1942 if (pUnique->elapsedExclusive == 0) break; 1943 1944 sum += pUnique->elapsedExclusive; 1945 double per = 100.0 * pUnique->elapsedExclusive / total; 1946 double sum_per = 100.0 * sum / total; 1947 const char* methodName = pUnique->methods[0]->methodName; 1948 if (gOptions.outputHtml) { 1949 char buf[80]; 1950 1951 methodName = htmlEscape(methodName, methodBuf, HTML_BUFSIZE); 1952 printf( 1953 "<div class=\"link\" onClick=\"javascript:toggle('e%d')\" " 1954 "onMouseOver=\"javascript:onMouseOver(this)\" " 1955 "onMouseOut=\"javascript:onMouseOut(this)\"><span class=\"parent\" " 1956 "id=\"xe%d\">+</span>", 1957 ii, ii); 1958 sprintf(buf, "%" PRIu64, pUnique->elapsedExclusive); 1959 printHtmlField(buf, 9); 1960 printf(" "); 1961 sprintf(buf, "%.1f", per); 1962 printHtmlField(buf, 7); 1963 printf(" "); 1964 sprintf(buf, "%.1f", sum_per); 1965 printHtmlField(buf, 7); 1966 printf(" "); 1967 sprintf(buf, "%d", pUnique->numCalls[0]); 1968 printHtmlField(buf, 6); 1969 printf("+"); 1970 sprintf(buf, "%d", pUnique->numCalls[1]); 1971 printHtmlField(buf, -6); 1972 printf(" "); 1973 printf("%s", methodName); 1974 printf("</div>\n"); 1975 printf("<div class=\"parent\" id=\"e%d\">\n", ii); 1976 } else { 1977 printf("---------------------------------------------\n"); 1978 printf("%9" PRIu64 " %7.1f %7.1f %6d+%-6d %s\n", 1979 pUnique->elapsedExclusive, per, sum_per, pUnique->numCalls[0], 1980 pUnique->numCalls[1], methodName); 1981 } 1982 int32_t numMethods = pUnique->numMethods; 1983 double methodExclusive = pUnique->elapsedExclusive; 1984 double sumMethods = 0; 1985 for (int32_t jj = 0; jj < numMethods; ++jj) { 1986 MethodEntry* method = pUnique->methods[jj]; 1987 const char* className = method->className; 1988 const char* signature = method->signature; 1989 per = 100.0 * method->elapsedExclusive / methodExclusive; 1990 sumMethods += method->elapsedExclusive; 1991 sum_per = 100.0 * sumMethods / methodExclusive; 1992 if (gOptions.outputHtml) { 1993 char buf[80]; 1994 1995 className = htmlEscape(className, classBuf, HTML_BUFSIZE); 1996 signature = htmlEscape(signature, signatureBuf, HTML_BUFSIZE); 1997 printf("<div class=\"leaf\"><span class=\"leaf\"> </span>"); 1998 sprintf(buf, "%" PRIu64, method->elapsedExclusive); 1999 printHtmlField(buf, 9); 2000 printf(" "); 2001 sprintf(buf, "%" PRIu64, method->elapsedInclusive); 2002 printHtmlField(buf, 9); 2003 printf(" "); 2004 sprintf(buf, "%.1f", per); 2005 printHtmlField(buf, 7); 2006 printf(" "); 2007 sprintf(buf, "%.1f", sum_per); 2008 printHtmlField(buf, 7); 2009 printf(" "); 2010 sprintf(buf, "%d", method->numCalls[0]); 2011 printHtmlField(buf, 6); 2012 printf("+"); 2013 sprintf(buf, "%d", method->numCalls[1]); 2014 printHtmlField(buf, -6); 2015 printf(" "); 2016 printf("<a href=\"#m%d\">[%d]</a> %s.%s %s", method->index, 2017 method->index, className, methodName, signature); 2018 printf("</div>\n"); 2019 } else { 2020 printf("%9" PRIu64 " %9" PRIu64 " %7.1f %7.1f %6d+%-6d [%d] %s.%s %s\n", 2021 method->elapsedExclusive, method->elapsedInclusive, per, sum_per, 2022 method->numCalls[0], method->numCalls[1], method->index, 2023 className, methodName, signature); 2024 } 2025 } 2026 if (gOptions.outputHtml) { 2027 printf("</div>\n"); 2028 } 2029 } 2030 } 2031 2032 /* 2033 * Read the key and data files and return the MethodEntries for those files 2034 */ 2035 DataKeys* parseDataKeys(TraceData* traceData, const char* traceFileName, uint64_t* threadTime) { 2036 MethodEntry* caller; 2037 2038 FILE* dataFp = fopen(traceFileName, "rb"); 2039 if (dataFp == nullptr) return nullptr; 2040 2041 DataKeys* dataKeys = parseKeys(dataFp, 0); 2042 if (dataKeys == nullptr) { 2043 fclose(dataFp); 2044 return nullptr; 2045 } 2046 2047 DataHeader dataHeader; 2048 if (parseDataHeader(dataFp, &dataHeader) < 0) { 2049 fclose(dataFp); 2050 return dataKeys; 2051 } 2052 2053 #if 0 2054 FILE* dumpStream = fopen("debug", "w"); 2055 #endif 2056 while (1) { 2057 /* 2058 * Extract values from file. 2059 */ 2060 int32_t threadId; 2061 uint32_t methodVal; 2062 uint64_t currentTime; 2063 if (readDataRecord(dataFp, &dataHeader, &threadId, &methodVal, ¤tTime)) 2064 break; 2065 2066 int32_t action = METHOD_ACTION(methodVal); 2067 int64_t methodId = METHOD_ID(methodVal); 2068 2069 /* Get the call stack for this thread */ 2070 CallStack* pStack = traceData->stacks[threadId]; 2071 2072 /* If there is no call stack yet for this thread, then allocate one */ 2073 if (pStack == nullptr) { 2074 pStack = new CallStack(); 2075 pStack->top = 0; 2076 pStack->lastEventTime = currentTime; 2077 pStack->threadStartTime = currentTime; 2078 traceData->stacks[threadId] = pStack; 2079 } 2080 2081 /* Lookup the current method */ 2082 MethodEntry* method = lookupMethod(dataKeys, methodId); 2083 if (method == nullptr) method = &dataKeys->methods[UNKNOWN_INDEX]; 2084 2085 #if 0 2086 if (method->methodName) { 2087 fprintf(dumpStream, "%2d %-8llu %d %8llu r %d c %d %s.%s %s\n", 2088 threadId, currentTime, action, pStack->threadStartTime, 2089 method->recursiveEntries, 2090 pStack->top, method->className, method->methodName, 2091 method->signature); 2092 } else { 2093 fprintf(dumpStream, "%2d %-8llu %d %8llu r %d c %d %s\n", 2094 threadId, currentTime, action, pStack->threadStartTime, 2095 method->recursiveEntries, 2096 pStack->top, method->className); 2097 } 2098 #endif 2099 2100 if (action == METHOD_TRACE_ENTER) { 2101 /* This is a method entry */ 2102 if (pStack->top >= MAX_STACK_DEPTH) { 2103 fprintf(stderr, "Stack overflow (exceeded %d frames)\n", 2104 MAX_STACK_DEPTH); 2105 exit(1); 2106 } 2107 2108 /* Get the caller method */ 2109 if (pStack->top >= 1) 2110 caller = pStack->calls[pStack->top - 1].method; 2111 else 2112 caller = &dataKeys->methods[TOPLEVEL_INDEX]; 2113 countRecursiveEntries(pStack, pStack->top, caller); 2114 caller->elapsedExclusive += currentTime - pStack->lastEventTime; 2115 #if 0 2116 if (caller->elapsedExclusive > 10000000) 2117 fprintf(dumpStream, "%llu current %llu last %llu diff %llu\n", 2118 caller->elapsedExclusive, currentTime, 2119 pStack->lastEventTime, 2120 currentTime - pStack->lastEventTime); 2121 #endif 2122 if (caller->recursiveEntries <= 1) { 2123 caller->topExclusive += currentTime - pStack->lastEventTime; 2124 } 2125 2126 /* Push the method on the stack for this thread */ 2127 pStack->calls[pStack->top].method = method; 2128 pStack->calls[pStack->top++].entryTime = currentTime; 2129 } else { 2130 /* This is a method exit */ 2131 uint64_t entryTime = 0; 2132 2133 /* Pop the method off the stack for this thread */ 2134 if (pStack->top > 0) { 2135 pStack->top -= 1; 2136 entryTime = pStack->calls[pStack->top].entryTime; 2137 if (method != pStack->calls[pStack->top].method) { 2138 if (method->methodName) { 2139 fprintf(stderr, "Exit from method %s.%s %s does not match stack:\n", 2140 method->className, method->methodName, method->signature); 2141 } else { 2142 fprintf(stderr, "Exit from method %s does not match stack:\n", 2143 method->className); 2144 } 2145 stackDump(pStack, pStack->top + 1); 2146 exit(1); 2147 } 2148 } 2149 2150 /* Get the caller method */ 2151 if (pStack->top >= 1) 2152 caller = pStack->calls[pStack->top - 1].method; 2153 else 2154 caller = &dataKeys->methods[TOPLEVEL_INDEX]; 2155 countRecursiveEntries(pStack, pStack->top, caller); 2156 countRecursiveEntries(pStack, pStack->top, method); 2157 uint64_t elapsed = currentTime - entryTime; 2158 addInclusiveTime(caller, method, elapsed); 2159 method->elapsedExclusive += currentTime - pStack->lastEventTime; 2160 if (method->recursiveEntries == 0) { 2161 method->topExclusive += currentTime - pStack->lastEventTime; 2162 } 2163 } 2164 /* Remember the time of the last entry or exit event */ 2165 pStack->lastEventTime = currentTime; 2166 } 2167 2168 /* If we have calls on the stack when the trace ends, then clean 2169 * up the stack and add time to the callers by pretending that we 2170 * are exiting from their methods now. 2171 */ 2172 uint64_t sumThreadTime = 0; 2173 for (int32_t threadId = 0; threadId < MAX_THREADS; ++threadId) { 2174 CallStack* pStack = traceData->stacks[threadId]; 2175 2176 /* If this thread never existed, then continue with next thread */ 2177 if (pStack == nullptr) continue; 2178 2179 /* Also, add up the time taken by all of the threads */ 2180 sumThreadTime += pStack->lastEventTime - pStack->threadStartTime; 2181 2182 for (int32_t ii = 0; ii < pStack->top; ++ii) { 2183 if (ii == 0) 2184 caller = &dataKeys->methods[TOPLEVEL_INDEX]; 2185 else 2186 caller = pStack->calls[ii - 1].method; 2187 MethodEntry* method = pStack->calls[ii].method; 2188 countRecursiveEntries(pStack, ii, caller); 2189 countRecursiveEntries(pStack, ii, method); 2190 2191 uint64_t entryTime = pStack->calls[ii].entryTime; 2192 uint64_t elapsed = pStack->lastEventTime - entryTime; 2193 addInclusiveTime(caller, method, elapsed); 2194 } 2195 } 2196 caller = &dataKeys->methods[TOPLEVEL_INDEX]; 2197 caller->elapsedInclusive = sumThreadTime; 2198 2199 #if 0 2200 fclose(dumpStream); 2201 #endif 2202 2203 if (threadTime != nullptr) { 2204 *threadTime = sumThreadTime; 2205 } 2206 2207 fclose(dataFp); 2208 return dataKeys; 2209 } 2210 2211 MethodEntry** parseMethodEntries(DataKeys* dataKeys) { 2212 /* Create a new array of pointers to the methods and sort the pointers 2213 * instead of the actual MethodEntry structs. We need to do this 2214 * because there are other lists that contain pointers to the 2215 * MethodEntry structs. 2216 */ 2217 MethodEntry** pMethods = new MethodEntry*[dataKeys->numMethods]; 2218 for (int32_t ii = 0; ii < dataKeys->numMethods; ++ii) { 2219 MethodEntry* entry = &dataKeys->methods[ii]; 2220 pMethods[ii] = entry; 2221 } 2222 2223 return pMethods; 2224 } 2225 2226 /* 2227 * Produce a function profile from the following methods 2228 */ 2229 void profileTrace(TraceData* traceData, MethodEntry** pMethods, int32_t numMethods, 2230 uint64_t sumThreadTime) { 2231 /* Print the html header, if necessary */ 2232 if (gOptions.outputHtml) { 2233 printf(htmlHeader, gOptions.sortableUrl); 2234 outputTableOfContents(); 2235 } 2236 2237 printExclusiveProfile(pMethods, numMethods, sumThreadTime); 2238 printInclusiveProfile(pMethods, numMethods, sumThreadTime); 2239 2240 createClassList(traceData, pMethods, numMethods); 2241 printClassProfiles(traceData, sumThreadTime); 2242 2243 createUniqueMethodList(traceData, pMethods, numMethods); 2244 printMethodProfiles(traceData, sumThreadTime); 2245 2246 if (gOptions.outputHtml) { 2247 printf("%s", htmlFooter); 2248 } 2249 } 2250 2251 int32_t compareMethodNamesForDiff(const void* a, const void* b) { 2252 const MethodEntry* methodA = *(const MethodEntry**) a; 2253 const MethodEntry* methodB = *(const MethodEntry**) b; 2254 if (methodA->methodName == nullptr || methodB->methodName == nullptr) { 2255 return compareClassNames(a, b); 2256 } 2257 int32_t result = strcmp(methodA->methodName, methodB->methodName); 2258 if (result == 0) { 2259 result = strcmp(methodA->signature, methodB->signature); 2260 if (result == 0) { 2261 return strcmp(methodA->className, methodB->className); 2262 } 2263 } 2264 return result; 2265 } 2266 2267 int32_t findMatch(MethodEntry** methods, int32_t size, MethodEntry* matchThis) { 2268 for (int32_t i = 0; i < size; i++) { 2269 MethodEntry* method = methods[i]; 2270 2271 if (method != nullptr && !compareMethodNamesForDiff(&method, &matchThis)) { 2272 // printf("%s.%s == %s.%s<br>\n", matchThis->className, matchThis->methodName, 2273 // method->className, method->methodName); 2274 2275 return i; 2276 // if (!compareMethodNames(&method, &matchThis)) return i; 2277 } 2278 } 2279 2280 return -1; 2281 } 2282 2283 int32_t compareDiffEntriesExculsive(const void* a, const void* b) { 2284 const DiffEntry* entryA = (const DiffEntry*) a; 2285 const DiffEntry* entryB = (const DiffEntry*) b; 2286 2287 if (entryA->differenceExclusive < entryB->differenceExclusive) { 2288 return 1; 2289 } else if (entryA->differenceExclusive > entryB->differenceExclusive) { 2290 return -1; 2291 } 2292 2293 return 0; 2294 } 2295 2296 int32_t compareDiffEntriesInculsive(const void* a, const void* b) { 2297 const DiffEntry* entryA = (const DiffEntry*) a; 2298 const DiffEntry* entryB = (const DiffEntry*) b; 2299 2300 if (entryA->differenceInclusive < entryB->differenceInclusive) { 2301 return 1; 2302 } else if (entryA->differenceInclusive > entryB->differenceInclusive) { 2303 return -1; 2304 } 2305 2306 return 0; 2307 } 2308 2309 void printMissingMethod(MethodEntry* method) { 2310 char classBuf[HTML_BUFSIZE]; 2311 char methodBuf[HTML_BUFSIZE]; 2312 2313 char* className = htmlEscape(method->className, classBuf, HTML_BUFSIZE); 2314 char* methodName = htmlEscape(method->methodName, methodBuf, HTML_BUFSIZE); 2315 2316 if (gOptions.outputHtml) printf("<tr><td>\n"); 2317 2318 printf("%s.%s ", className, methodName); 2319 if (gOptions.outputHtml) printf("</td><td>"); 2320 2321 printf("%" PRIu64 " ", method->elapsedExclusive); 2322 if (gOptions.outputHtml) printf("</td><td>"); 2323 2324 printf("%" PRIu64 " ", method->elapsedInclusive); 2325 if (gOptions.outputHtml) printf("</td><td>"); 2326 2327 printf("%d\n", method->numCalls[0]); 2328 if (gOptions.outputHtml) printf("</td><td>\n"); 2329 } 2330 2331 void createDiff(DataKeys* d1, DataKeys* d2) { 2332 MethodEntry** methods1 = parseMethodEntries(d1); 2333 MethodEntry** methods2 = parseMethodEntries(d2); 2334 2335 // sort and assign the indicies 2336 qsort(methods1, d1->numMethods, sizeof(MethodEntry*), compareElapsedInclusive); 2337 for (int32_t i = 0; i < d1->numMethods; ++i) { 2338 methods1[i]->index = i; 2339 } 2340 2341 qsort(methods2, d2->numMethods, sizeof(MethodEntry*), compareElapsedInclusive); 2342 for (int32_t i = 0; i < d2->numMethods; ++i) { 2343 methods2[i]->index = i; 2344 } 2345 2346 int32_t max = (d1->numMethods < d2->numMethods) ? d2->numMethods : d1->numMethods; 2347 max++; 2348 DiffEntry* diffs = new DiffEntry[max]; 2349 memset(diffs, 0, max * sizeof(DiffEntry)); 2350 DiffEntry* ptr = diffs; 2351 2352 // printf("<br>d1->numMethods: %d d1->numMethods: %d<br>\n", 2353 // d1->numMethods, d2->numMethods); 2354 2355 int32_t matches = 0; 2356 2357 for (int32_t i = 0; i < d1->numMethods; i++) { 2358 int32_t match = findMatch(methods2, d2->numMethods, methods1[i]); 2359 if (match >= 0) { 2360 ptr->method1 = methods1[i]; 2361 ptr->method2 = methods2[match]; 2362 2363 uint64_t e1 = ptr->method1->elapsedExclusive; 2364 uint64_t e2 = ptr->method2->elapsedExclusive; 2365 if (e1 > 0) { 2366 ptr->differenceExclusive = e2 - e1; 2367 ptr->differenceExclusivePercentage = (static_cast<double>(e2) / 2368 static_cast<double>(e1)) * 100.0; 2369 } 2370 2371 uint64_t i1 = ptr->method1->elapsedInclusive; 2372 uint64_t i2 = ptr->method2->elapsedInclusive; 2373 if (i1 > 0) { 2374 ptr->differenceInclusive = i2 - i1; 2375 ptr->differenceInclusivePercentage = (static_cast<double>(i2) / 2376 static_cast<double>(i1)) * 100.0; 2377 } 2378 2379 // clear these out so we don't find them again and we know which ones 2380 // we have left over 2381 methods1[i] = nullptr; 2382 methods2[match] = nullptr; 2383 ptr++; 2384 2385 matches++; 2386 } 2387 } 2388 ptr->method1 = nullptr; 2389 ptr->method2 = nullptr; 2390 2391 qsort(diffs, matches, sizeof(DiffEntry), compareDiffEntriesExculsive); 2392 ptr = diffs; 2393 2394 if (gOptions.outputHtml) { 2395 printf(htmlHeader, gOptions.sortableUrl); 2396 printf("<h3>Table of Contents</h3>\n"); 2397 printf("<ul>\n"); 2398 printf("<li><a href='#exclusive'>Exclusive</a>\n"); 2399 printf("<li><a href='#inclusive'>Inclusive</a>\n"); 2400 printf("</ul>\n"); 2401 printf("Run 1: %s<br>\n", gOptions.diffFileName); 2402 printf("Run 2: %s<br>\n", gOptions.traceFileName); 2403 printf("<a name=\"exclusive\"></a><h3 id=\"exclusive\">Exclusive</h3>\n"); 2404 printf(tableHeader, "exclusive_table"); 2405 } 2406 2407 char classBuf[HTML_BUFSIZE]; 2408 char methodBuf[HTML_BUFSIZE]; 2409 while (ptr->method1 != nullptr && ptr->method2 != nullptr) { 2410 if (gOptions.outputHtml) printf("<tr><td>\n"); 2411 2412 char* className = htmlEscape(ptr->method1->className, classBuf, HTML_BUFSIZE); 2413 char* methodName = htmlEscape(ptr->method1->methodName, methodBuf, HTML_BUFSIZE); 2414 2415 printf("%s.%s ", className, methodName); 2416 if (gOptions.outputHtml) printf("</td><td>"); 2417 2418 printf("%" PRIu64 " ", ptr->method1->elapsedExclusive); 2419 if (gOptions.outputHtml) printf("</td><td>"); 2420 2421 printf("%" PRIu64 " ", ptr->method2->elapsedExclusive); 2422 if (gOptions.outputHtml) printf("</td><td>"); 2423 2424 printf("%" PRIu64 " ", ptr->differenceExclusive); 2425 if (gOptions.outputHtml) printf("</td><td>"); 2426 2427 printf("%.2f\n", ptr->differenceExclusivePercentage); 2428 if (gOptions.outputHtml) printf("</td><td>\n"); 2429 2430 printf("%d\n", ptr->method1->numCalls[0]); 2431 if (gOptions.outputHtml) printf("</td><td>\n"); 2432 2433 printf("%d\n", ptr->method2->numCalls[0]); 2434 if (gOptions.outputHtml) printf("</td></tr>\n"); 2435 2436 ptr++; 2437 } 2438 2439 if (gOptions.outputHtml) printf("</table>\n"); 2440 2441 if (gOptions.outputHtml) { 2442 printf(htmlHeader, gOptions.sortableUrl); 2443 printf("Run 1: %s<br>\n", gOptions.diffFileName); 2444 printf("Run 2: %s<br>\n", gOptions.traceFileName); 2445 printf("<a name=\"inclusive\"></a><h3 id=\"inculisve\">Inclusive</h3>\n"); 2446 printf(tableHeader, "inclusive_table"); 2447 } 2448 2449 qsort(diffs, matches, sizeof(DiffEntry), compareDiffEntriesInculsive); 2450 ptr = diffs; 2451 2452 while (ptr->method1 != nullptr && ptr->method2 != nullptr) { 2453 if (gOptions.outputHtml) printf("<tr><td>\n"); 2454 2455 char* className = htmlEscape(ptr->method1->className, classBuf, HTML_BUFSIZE); 2456 char* methodName = htmlEscape(ptr->method1->methodName, methodBuf, HTML_BUFSIZE); 2457 2458 printf("%s.%s ", className, methodName); 2459 if (gOptions.outputHtml) printf("</td><td>"); 2460 2461 printf("%" PRIu64 " ", ptr->method1->elapsedInclusive); 2462 if (gOptions.outputHtml) printf("</td><td>"); 2463 2464 printf("%" PRIu64 " ", ptr->method2->elapsedInclusive); 2465 if (gOptions.outputHtml) printf("</td><td>"); 2466 2467 printf("%" PRIu64 " ", ptr->differenceInclusive); 2468 if (gOptions.outputHtml) printf("</td><td>"); 2469 2470 printf("%.2f\n", ptr->differenceInclusivePercentage); 2471 if (gOptions.outputHtml) printf("</td><td>\n"); 2472 2473 printf("%d\n", ptr->method1->numCalls[0]); 2474 if (gOptions.outputHtml) printf("</td><td>\n"); 2475 2476 printf("%d\n", ptr->method2->numCalls[0]); 2477 if (gOptions.outputHtml) printf("</td></tr>\n"); 2478 2479 ptr++; 2480 } 2481 2482 if (gOptions.outputHtml) { 2483 printf("</table>\n"); 2484 printf("<h3>Run 1 methods not found in Run 2</h3>"); 2485 printf(tableHeaderMissing, "?"); 2486 } 2487 2488 for (int32_t i = 0; i < d1->numMethods; ++i) { 2489 if (methods1[i] != nullptr) { 2490 printMissingMethod(methods1[i]); 2491 } 2492 } 2493 2494 if (gOptions.outputHtml) { 2495 printf("</table>\n"); 2496 printf("<h3>Run 2 methods not found in Run 1</h3>"); 2497 printf(tableHeaderMissing, "?"); 2498 } 2499 2500 for (int32_t i = 0; i < d2->numMethods; ++i) { 2501 if (methods2[i] != nullptr) { 2502 printMissingMethod(methods2[i]); 2503 } 2504 } 2505 2506 if (gOptions.outputHtml) printf("</body></html\n"); 2507 } 2508 2509 int32_t usage(const char* program) { 2510 fprintf(stderr, "Copyright (C) 2006 The Android Open Source Project\n\n"); 2511 fprintf(stderr, 2512 "usage: %s [-ho] [-s sortable] [-d trace-file-name] [-g outfile] " 2513 "trace-file-name\n", 2514 program); 2515 fprintf(stderr, " -d trace-file-name - Diff with this trace\n"); 2516 fprintf(stderr, " -g outfile - Write graph to 'outfile'\n"); 2517 fprintf(stderr, 2518 " -k - When writing a graph, keep the intermediate " 2519 "DOT file\n"); 2520 fprintf(stderr, " -h - Turn on HTML output\n"); 2521 fprintf( 2522 stderr, 2523 " -o - Dump the dmtrace file instead of profiling\n"); 2524 fprintf(stderr, 2525 " -s - URL base to where the sortable javascript " 2526 "file\n"); 2527 fprintf(stderr, 2528 " -t threshold - Threshold percentage for including nodes in " 2529 "the graph\n"); 2530 return 2; 2531 } 2532 2533 // Returns true if there was an error 2534 int32_t parseOptions(int32_t argc, char** argv) { 2535 while (1) { 2536 int32_t opt = getopt(argc, argv, "d:hg:kos:t:"); 2537 if (opt == -1) break; 2538 switch (opt) { 2539 case 'd': 2540 gOptions.diffFileName = optarg; 2541 break; 2542 case 'g': 2543 gOptions.graphFileName = optarg; 2544 break; 2545 case 'k': 2546 gOptions.keepDotFile = 1; 2547 break; 2548 case 'h': 2549 gOptions.outputHtml = 1; 2550 break; 2551 case 'o': 2552 gOptions.dump = 1; 2553 break; 2554 case 's': 2555 gOptions.sortableUrl = optarg; 2556 break; 2557 case 't': 2558 gOptions.threshold = atoi(optarg); 2559 break; 2560 default: 2561 return 1; 2562 } 2563 } 2564 return 0; 2565 } 2566 2567 /* 2568 * Parse args. 2569 */ 2570 int32_t main(int32_t argc, char** argv) { 2571 gOptions.threshold = -1; 2572 2573 // Parse the options 2574 if (parseOptions(argc, argv) || argc - optind != 1) return usage(argv[0]); 2575 2576 gOptions.traceFileName = argv[optind]; 2577 2578 if (gOptions.threshold < 0 || 100 <= gOptions.threshold) { 2579 gOptions.threshold = 20; 2580 } 2581 2582 if (gOptions.dump) { 2583 dumpTrace(); 2584 return 0; 2585 } 2586 2587 uint64_t sumThreadTime = 0; 2588 2589 TraceData data1; 2590 DataKeys* dataKeys = parseDataKeys(&data1, gOptions.traceFileName, &sumThreadTime); 2591 if (dataKeys == nullptr) { 2592 fprintf(stderr, "Cannot read \"%s\".\n", gOptions.traceFileName); 2593 exit(1); 2594 } 2595 2596 if (gOptions.diffFileName != nullptr) { 2597 uint64_t sum2; 2598 TraceData data2; 2599 DataKeys* d2 = parseDataKeys(&data2, gOptions.diffFileName, &sum2); 2600 if (d2 == nullptr) { 2601 fprintf(stderr, "Cannot read \"%s\".\n", gOptions.diffFileName); 2602 exit(1); 2603 } 2604 2605 createDiff(d2, dataKeys); 2606 2607 freeDataKeys(d2); 2608 } else { 2609 MethodEntry** methods = parseMethodEntries(dataKeys); 2610 profileTrace(&data1, methods, dataKeys->numMethods, sumThreadTime); 2611 if (gOptions.graphFileName != nullptr) { 2612 createInclusiveProfileGraphNew(dataKeys); 2613 } 2614 delete[] methods; 2615 } 2616 2617 freeDataKeys(dataKeys); 2618 2619 return 0; 2620 } 2621