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 */
htmlEscape(const char * src,char * dest,int32_t len)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 */
initMethodEntry(MethodEntry * method,int64_t methodId,const char * className,const char * methodName,const char * signature,const char * fileName,const char * lineNumStr)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 */
compareElapsedExclusive(const void * a,const void * b)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 */
compareElapsedInclusive(const void * a,const void * b)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 */
compareTimedMethod(const void * a,const void * b)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 */
compareClassNames(const void * a,const void * b)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 */
compareClassExclusive(const void * a,const void * b)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 */
compareMethodNames(const void * a,const void * b)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 */
compareUniqueExclusive(const void * a,const void * b)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 */
freeDataKeys(DataKeys * pKeys)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 */
findNextChar(const char * data,int32_t len,char lookFor)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 */
countLinesToToken(const char * data,int32_t len)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 */
checkToken(const char * data,int32_t len,const char * cmpStr)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 */
parseVersion(DataKeys * pKeys,int64_t offset,int32_t verbose)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 */
parseThreads(DataKeys * pKeys,int64_t offset)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 */
parseMethods(DataKeys * pKeys,int64_t offset)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 */
parseEnd(DataKeys * pKeys,int64_t offset)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 */
compareThreads(const void * thread1,const void * thread2)795 static int32_t compareThreads(const void* thread1, const void* thread2) {
796 return ((const ThreadEntry*) thread1)->threadId -
797 ((const ThreadEntry*) thread2)->threadId;
798 }
799
sortThreadList(DataKeys * pKeys)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 */
compareMethods(const void * meth1,const void * meth2)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
sortMethodList(DataKeys * pKeys)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 */
parseKeys(FILE * fp,int32_t verbose)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 */
read2LE(FILE * fp)915 uint32_t read2LE(FILE* fp) {
916 uint32_t val = getc(fp);
917 val |= getc(fp) << 8;
918 return val;
919 }
read4LE(FILE * fp)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 }
read8LE(FILE * fp)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 */
parseDataHeader(FILE * fp,DataHeader * pHeader)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 */
lookupMethod(DataKeys * pKeys,int64_t methodId)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 */
readDataRecord(FILE * dataFp,DataHeader * dataHeader,int32_t * threadId,uint32_t * methodVal,uint64_t * elapsedTime)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 */
dumpTrace()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 */
addInclusiveTime(MethodEntry * parent,MethodEntry * child,uint64_t elapsedTime)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 */
sortTimedMethodList(TimedMethod * list,int32_t * num)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 */
printInclusiveMethod(MethodEntry * method,TimedMethod * list,int32_t numCalls,int32_t flags)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
countRecursiveEntries(CallStack * pStack,int32_t top,MethodEntry * method)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
stackDump(CallStack * pStack,int32_t top)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
outputTableOfContents()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
outputNavigationBar()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
printExclusiveProfile(MethodEntry ** pMethods,int32_t numMethods,uint64_t sumThreadTime)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 */
checkThreshold(MethodEntry * parent,MethodEntry * child)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
createLabels(FILE * file,MethodEntry * method)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
createLinks(FILE * file,MethodEntry * method)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
createInclusiveProfileGraphNew(DataKeys * dataKeys)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
printInclusiveProfile(MethodEntry ** pMethods,int32_t numMethods,uint64_t sumThreadTime)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
createClassList(TraceData * traceData,MethodEntry ** pMethods,int32_t numMethods)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 */
printHtmlField(char * buf,int32_t width)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
printClassProfiles(TraceData * traceData,uint64_t sumThreadTime)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
createUniqueMethodList(TraceData * traceData,MethodEntry ** pMethods,int32_t numMethods)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
printMethodProfiles(TraceData * traceData,uint64_t sumThreadTime)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 */
parseDataKeys(TraceData * traceData,const char * traceFileName,uint64_t * threadTime)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
parseMethodEntries(DataKeys * dataKeys)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 */
profileTrace(TraceData * traceData,MethodEntry ** pMethods,int32_t numMethods,uint64_t sumThreadTime)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
compareMethodNamesForDiff(const void * a,const void * b)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
findMatch(MethodEntry ** methods,int32_t size,MethodEntry * matchThis)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
compareDiffEntriesExculsive(const void * a,const void * b)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
compareDiffEntriesInculsive(const void * a,const void * b)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
printMissingMethod(MethodEntry * method)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
createDiff(DataKeys * d1,DataKeys * d2)2331 void createDiff(DataKeys* d1, DataKeys* d2) {
2332 MethodEntry** methods1 = parseMethodEntries(d1);
2333 MethodEntry** methods2 = parseMethodEntries(d2);
2334
2335 // sort and assign the indices
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
usage(const char * program)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
parseOptions(int32_t argc,char ** argv)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 */
main(int32_t argc,char ** argv)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