|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "leaky.h" |
|
7 #include "intcnt.h" |
|
8 |
|
9 #include <sys/types.h> |
|
10 #include <sys/mman.h> |
|
11 #include <sys/stat.h> |
|
12 #include <fcntl.h> |
|
13 #include <unistd.h> |
|
14 #include <string.h> |
|
15 #ifndef NTO |
|
16 #include <getopt.h> |
|
17 #endif |
|
18 #include <assert.h> |
|
19 #include <stdlib.h> |
|
20 #include <stdio.h> |
|
21 |
|
22 #ifdef NTO |
|
23 #include <mem.h> |
|
24 #endif |
|
25 |
|
26 #ifndef FALSE |
|
27 #define FALSE 0 |
|
28 #endif |
|
29 #ifndef TRUE |
|
30 #define TRUE 1 |
|
31 #endif |
|
32 |
|
33 static const u_int DefaultBuckets = 10007; // arbitrary, but prime |
|
34 static const u_int MaxBuckets = 1000003; // arbitrary, but prime |
|
35 |
|
36 //---------------------------------------------------------------------- |
|
37 |
|
38 int main(int argc, char** argv) |
|
39 { |
|
40 leaky* l = new leaky; |
|
41 |
|
42 l->initialize(argc, argv); |
|
43 l->outputfd = stdout; |
|
44 |
|
45 for (int i = 0; i < l->numLogFiles; i++) { |
|
46 if (l->output_dir || l->numLogFiles > 1) { |
|
47 char name[2048]; // XXX fix |
|
48 if (l->output_dir) |
|
49 snprintf(name,sizeof(name),"%s/%s.html",l->output_dir,argv[l->logFileIndex + i]); |
|
50 else |
|
51 snprintf(name,sizeof(name),"%s.html",argv[l->logFileIndex + i]); |
|
52 |
|
53 fprintf(stderr,"opening %s\n",name); |
|
54 l->outputfd = fopen(name,"w"); |
|
55 // if an error we won't process the file |
|
56 } |
|
57 if (l->outputfd) { // paranoia |
|
58 l->open(argv[l->logFileIndex + i]); |
|
59 |
|
60 if (l->outputfd != stderr) { |
|
61 fclose(l->outputfd); |
|
62 l->outputfd = nullptr; |
|
63 } |
|
64 } |
|
65 } |
|
66 |
|
67 return 0; |
|
68 } |
|
69 |
|
70 char * |
|
71 htmlify(const char *in) |
|
72 { |
|
73 const char *p = in; |
|
74 char *out, *q; |
|
75 int n = 0; |
|
76 size_t newlen; |
|
77 |
|
78 // Count the number of '<' and '>' in the input. |
|
79 while ((p = strpbrk(p, "<>"))) |
|
80 { |
|
81 ++n; |
|
82 ++p; |
|
83 } |
|
84 |
|
85 // Knowing the number of '<' and '>', we can calculate the space |
|
86 // needed for the output string. |
|
87 newlen = strlen(in) + n * 3 + 1; |
|
88 out = new char[newlen]; |
|
89 |
|
90 // Copy the input to the output, with substitutions. |
|
91 p = in; |
|
92 q = out; |
|
93 do |
|
94 { |
|
95 if (*p == '<') |
|
96 { |
|
97 strcpy(q, "<"); |
|
98 q += 4; |
|
99 } |
|
100 else if (*p == '>') |
|
101 { |
|
102 strcpy(q, ">"); |
|
103 q += 4; |
|
104 } |
|
105 else |
|
106 { |
|
107 *q++ = *p; |
|
108 } |
|
109 p++; |
|
110 } while (*p); |
|
111 *q = '\0'; |
|
112 |
|
113 return out; |
|
114 } |
|
115 |
|
116 leaky::leaky() |
|
117 { |
|
118 applicationName = nullptr; |
|
119 progFile = nullptr; |
|
120 |
|
121 quiet = true; |
|
122 showAddress = false; |
|
123 showThreads = false; |
|
124 stackDepth = 100000; |
|
125 onlyThread = 0; |
|
126 cleo = false; |
|
127 |
|
128 mappedLogFile = -1; |
|
129 firstLogEntry = lastLogEntry = 0; |
|
130 |
|
131 sfd = -1; |
|
132 externalSymbols = 0; |
|
133 usefulSymbols = 0; |
|
134 numExternalSymbols = 0; |
|
135 lowestSymbolAddr = 0; |
|
136 highestSymbolAddr = 0; |
|
137 |
|
138 loadMap = nullptr; |
|
139 |
|
140 collect_last = false; |
|
141 collect_start = -1; |
|
142 collect_end = -1; |
|
143 } |
|
144 |
|
145 leaky::~leaky() |
|
146 { |
|
147 } |
|
148 |
|
149 void leaky::usageError() |
|
150 { |
|
151 fprintf(stderr, "Usage: %s [-v] [-t] [-e exclude] [-i include] [-s stackdepth] [--last] [--all] [--start n [--end m]] [--cleo] [--output-dir dir] prog log [log2 ...]\n", (char*) applicationName); |
|
152 fprintf(stderr, |
|
153 "\t-v: verbose\n" |
|
154 "\t-t | --threads: split threads\n" |
|
155 "\t--only-thread n: only profile thread N\n" |
|
156 "\t-i include-id: stack must include specified id\n" |
|
157 "\t-e exclude-id: stack must NOT include specified id\n" |
|
158 "\t-s stackdepth: Limit depth looked at from captured stack frames\n" |
|
159 "\t--last: only profile the last capture section\n" |
|
160 "\t--start n [--end m]: profile n to m (or end) capture sections\n" |
|
161 "\t--cleo: format output for 'cleopatra' display\n" |
|
162 "\t--output-dir dir: write output files to dir\n" |
|
163 "\tIf there's one log, output goes to stdout unless --output-dir is set\n" |
|
164 "\tIf there are more than one log, output files will be named with .html added\n" |
|
165 ); |
|
166 exit(-1); |
|
167 } |
|
168 |
|
169 static struct option longopts[] = { |
|
170 { "threads", 0, nullptr, 't' }, |
|
171 { "only-thread", 1, nullptr, 'T' }, |
|
172 { "last", 0, nullptr, 'l' }, |
|
173 { "start", 1, nullptr, 'x' }, |
|
174 { "end", 1, nullptr, 'n' }, |
|
175 { "cleo",0, nullptr, 'c' }, |
|
176 { "output-dir", 1, nullptr, 'd' }, |
|
177 { nullptr, 0, nullptr, 0 }, |
|
178 }; |
|
179 |
|
180 void leaky::initialize(int argc, char** argv) |
|
181 { |
|
182 applicationName = argv[0]; |
|
183 applicationName = strrchr(applicationName, '/'); |
|
184 if (!applicationName) { |
|
185 applicationName = argv[0]; |
|
186 } else { |
|
187 applicationName++; |
|
188 } |
|
189 |
|
190 int arg; |
|
191 int errflg = 0; |
|
192 int longindex = 0; |
|
193 |
|
194 onlyThread = 0; |
|
195 output_dir = nullptr; |
|
196 cleo = false; |
|
197 |
|
198 // XXX tons of cruft here left over from tracemalloc |
|
199 // XXX The -- options shouldn't need short versions, or they should be documented |
|
200 while (((arg = getopt_long(argc, argv, "adEe:gh:i:r:Rs:tT:qvx:ln:",longopts,&longindex)) != -1)) { |
|
201 switch (arg) { |
|
202 case '?': |
|
203 default: |
|
204 fprintf(stderr,"error: unknown option %c\n",optopt); |
|
205 errflg++; |
|
206 break; |
|
207 case 'a': |
|
208 break; |
|
209 case 'A': // not implemented |
|
210 showAddress = true; |
|
211 break; |
|
212 case 'c': |
|
213 cleo = true; |
|
214 break; |
|
215 case 'd': |
|
216 output_dir = optarg; // reference to an argv pointer |
|
217 break; |
|
218 case 'R': |
|
219 break; |
|
220 case 'e': |
|
221 exclusions.add(optarg); |
|
222 break; |
|
223 case 'g': |
|
224 break; |
|
225 case 'r': // not implemented |
|
226 roots.add(optarg); |
|
227 if (!includes.IsEmpty()) { |
|
228 errflg++; |
|
229 } |
|
230 break; |
|
231 case 'i': |
|
232 includes.add(optarg); |
|
233 if (!roots.IsEmpty()) { |
|
234 errflg++; |
|
235 } |
|
236 break; |
|
237 case 'h': |
|
238 break; |
|
239 case 's': |
|
240 stackDepth = atoi(optarg); |
|
241 if (stackDepth < 2) { |
|
242 stackDepth = 2; |
|
243 } |
|
244 break; |
|
245 case 'x': |
|
246 // --start |
|
247 collect_start = atoi(optarg); |
|
248 break; |
|
249 case 'n': |
|
250 // --end |
|
251 collect_end = atoi(optarg); |
|
252 break; |
|
253 case 'l': |
|
254 // --last |
|
255 collect_last = true; |
|
256 break; |
|
257 case 'q': |
|
258 break; |
|
259 case 'v': |
|
260 quiet = !quiet; |
|
261 break; |
|
262 case 't': |
|
263 showThreads = true; |
|
264 break; |
|
265 case 'T': |
|
266 showThreads = true; |
|
267 onlyThread = atoi(optarg); |
|
268 break; |
|
269 } |
|
270 } |
|
271 if (errflg || ((argc - optind) < 2)) { |
|
272 usageError(); |
|
273 } |
|
274 progFile = argv[optind++]; |
|
275 logFileIndex = optind; |
|
276 numLogFiles = argc - optind; |
|
277 if (!quiet) |
|
278 fprintf(stderr,"numlogfiles = %d\n",numLogFiles); |
|
279 } |
|
280 |
|
281 static void* mapFile(int fd, u_int flags, off_t* sz) |
|
282 { |
|
283 struct stat sb; |
|
284 if (fstat(fd, &sb) < 0) { |
|
285 perror("fstat"); |
|
286 exit(-1); |
|
287 } |
|
288 void* base = mmap(0, (int)sb.st_size, flags, MAP_PRIVATE, fd, 0); |
|
289 if (!base) { |
|
290 perror("mmap"); |
|
291 exit(-1); |
|
292 } |
|
293 *sz = sb.st_size; |
|
294 return base; |
|
295 } |
|
296 |
|
297 void leaky::LoadMap() |
|
298 { |
|
299 malloc_map_entry mme; |
|
300 char name[1000]; |
|
301 |
|
302 if (!loadMap) { |
|
303 // all files use the same map |
|
304 int fd = ::open(M_MAPFILE, O_RDONLY); |
|
305 if (fd < 0) { |
|
306 perror("open: " M_MAPFILE); |
|
307 exit(-1); |
|
308 } |
|
309 for (;;) { |
|
310 int nb = read(fd, &mme, sizeof(mme)); |
|
311 if (nb != sizeof(mme)) break; |
|
312 nb = read(fd, name, mme.nameLen); |
|
313 if (nb != (int)mme.nameLen) break; |
|
314 name[mme.nameLen] = 0; |
|
315 if (!quiet) { |
|
316 fprintf(stderr,"%s @ %lx\n", name, mme.address); |
|
317 } |
|
318 |
|
319 LoadMapEntry* lme = new LoadMapEntry; |
|
320 lme->address = mme.address; |
|
321 lme->name = strdup(name); |
|
322 lme->next = loadMap; |
|
323 loadMap = lme; |
|
324 } |
|
325 close(fd); |
|
326 } |
|
327 } |
|
328 |
|
329 void leaky::open(char *logFile) |
|
330 { |
|
331 int threadArray[100]; // should auto-expand |
|
332 int last_thread = -1; |
|
333 int numThreads = 0; |
|
334 int section = -1; |
|
335 bool collecting = false; |
|
336 |
|
337 LoadMap(); |
|
338 |
|
339 setupSymbols(progFile); |
|
340 |
|
341 // open up the log file |
|
342 if (mappedLogFile) |
|
343 ::close(mappedLogFile); |
|
344 |
|
345 mappedLogFile = ::open(logFile, O_RDONLY); |
|
346 if (mappedLogFile < 0) { |
|
347 perror("open"); |
|
348 exit(-1); |
|
349 } |
|
350 off_t size; |
|
351 firstLogEntry = (malloc_log_entry*) mapFile(mappedLogFile, PROT_READ, &size); |
|
352 lastLogEntry = (malloc_log_entry*)((char*)firstLogEntry + size); |
|
353 |
|
354 if (!collect_last || collect_start < 0) { |
|
355 collecting = true; |
|
356 } |
|
357 |
|
358 // First, restrict it to the capture sections specified (all, last, start/end) |
|
359 // This loop walks through all the call stacks we recorded |
|
360 for (malloc_log_entry* lep=firstLogEntry; |
|
361 lep < lastLogEntry; |
|
362 lep = reinterpret_cast<malloc_log_entry*>(&lep->pcs[lep->numpcs])) { |
|
363 |
|
364 if (lep->flags & JP_FIRST_AFTER_PAUSE) { |
|
365 section++; |
|
366 if (collect_last) { |
|
367 firstLogEntry = lep; |
|
368 numThreads = 0; |
|
369 collecting = true; |
|
370 } |
|
371 if (collect_start == section) { |
|
372 collecting = true; |
|
373 firstLogEntry = lep; |
|
374 } |
|
375 if (collect_end == section) { |
|
376 collecting = false; |
|
377 lastLogEntry = lep; |
|
378 } |
|
379 if (!quiet) |
|
380 fprintf(stderr,"New section %d: first=%p, last=%p, collecting=%d\n", |
|
381 section,(void*)firstLogEntry,(void*)lastLogEntry,collecting); |
|
382 } |
|
383 |
|
384 // Capture thread info at the same time |
|
385 |
|
386 // Find all the threads captured |
|
387 |
|
388 // pthread/linux docs say the signal can be delivered to any thread in |
|
389 // the process. In practice, it appears in Linux that it's always |
|
390 // delivered to the thread that called setitimer(), and each thread can |
|
391 // have a separate itimer. There's a support library for gprof that |
|
392 // overlays pthread_create() to set timers in any threads you spawn. |
|
393 if (showThreads && collecting) { |
|
394 if (lep->thread != last_thread) |
|
395 { |
|
396 int i; |
|
397 for (i=0; i<numThreads; i++) |
|
398 { |
|
399 if (lep->thread == threadArray[i]) |
|
400 break; |
|
401 } |
|
402 if (i == numThreads && |
|
403 i < (int) (sizeof(threadArray)/sizeof(threadArray[0]))) |
|
404 { |
|
405 threadArray[i] = lep->thread; |
|
406 numThreads++; |
|
407 if (!quiet) |
|
408 fprintf(stderr,"new thread %d\n",lep->thread); |
|
409 } |
|
410 } |
|
411 } |
|
412 } |
|
413 if (!quiet) |
|
414 fprintf(stderr,"Done collecting: sections %d: first=%p, last=%p, numThreads=%d\n", |
|
415 section,(void*)firstLogEntry,(void*)lastLogEntry,numThreads); |
|
416 |
|
417 if (!cleo) { |
|
418 fprintf(outputfd,"<html><head><title>Jprof Profile Report</title></head><body>\n"); |
|
419 fprintf(outputfd,"<h1><center>Jprof Profile Report</center></h1>\n"); |
|
420 } |
|
421 |
|
422 if (showThreads) |
|
423 { |
|
424 fprintf(stderr,"Num threads %d\n",numThreads); |
|
425 |
|
426 if (!cleo) { |
|
427 fprintf(outputfd,"<hr>Threads:<p><pre>\n"); |
|
428 for (int i=0; i<numThreads; i++) |
|
429 { |
|
430 fprintf(outputfd," <a href=\"#thread_%d\">%d</a> ", |
|
431 threadArray[i],threadArray[i]); |
|
432 if ((i+1)%10 == 0) |
|
433 fprintf(outputfd,"<br>\n"); |
|
434 } |
|
435 fprintf(outputfd,"</pre>"); |
|
436 } |
|
437 |
|
438 for (int i=0; i<numThreads; i++) |
|
439 { |
|
440 if (!onlyThread || onlyThread == threadArray[i]) |
|
441 analyze(threadArray[i]); |
|
442 } |
|
443 } |
|
444 else |
|
445 { |
|
446 analyze(0); |
|
447 } |
|
448 |
|
449 if (!cleo) |
|
450 fprintf(outputfd,"</pre></body></html>\n"); |
|
451 } |
|
452 |
|
453 //---------------------------------------------------------------------- |
|
454 |
|
455 |
|
456 static int symbolOrder(void const* a, void const* b) |
|
457 { |
|
458 Symbol const** ap = (Symbol const **)a; |
|
459 Symbol const** bp = (Symbol const **)b; |
|
460 return (*ap)->address == (*bp)->address ? 0 : |
|
461 ((*ap)->address > (*bp)->address ? 1 : -1); |
|
462 } |
|
463 |
|
464 void leaky::ReadSharedLibrarySymbols() |
|
465 { |
|
466 LoadMapEntry* lme = loadMap; |
|
467 while (nullptr != lme) { |
|
468 ReadSymbols(lme->name, lme->address); |
|
469 lme = lme->next; |
|
470 } |
|
471 } |
|
472 |
|
473 void leaky::setupSymbols(const char *fileName) |
|
474 { |
|
475 if (usefulSymbols == 0) { |
|
476 // only read once! |
|
477 |
|
478 // Read in symbols from the program |
|
479 ReadSymbols(fileName, 0); |
|
480 |
|
481 // Read in symbols from the .so's |
|
482 ReadSharedLibrarySymbols(); |
|
483 |
|
484 if (!quiet) { |
|
485 fprintf(stderr,"A total of %d symbols were loaded\n", usefulSymbols); |
|
486 } |
|
487 |
|
488 // Now sort them |
|
489 qsort(externalSymbols, usefulSymbols, sizeof(Symbol *), symbolOrder); |
|
490 lowestSymbolAddr = externalSymbols[0]->address; |
|
491 highestSymbolAddr = externalSymbols[usefulSymbols-1]->address; |
|
492 } |
|
493 } |
|
494 |
|
495 // Binary search the table, looking for a symbol that covers this |
|
496 // address. |
|
497 int leaky::findSymbolIndex(u_long addr) |
|
498 { |
|
499 u_int base = 0; |
|
500 u_int limit = usefulSymbols - 1; |
|
501 Symbol** end = &externalSymbols[limit]; |
|
502 while (base <= limit) { |
|
503 u_int midPoint = (base + limit)>>1; |
|
504 Symbol** sp = &externalSymbols[midPoint]; |
|
505 if (addr < (*sp)->address) { |
|
506 if (midPoint == 0) { |
|
507 return -1; |
|
508 } |
|
509 limit = midPoint - 1; |
|
510 } else { |
|
511 if (sp+1 < end) { |
|
512 if (addr < (*(sp+1))->address) { |
|
513 return midPoint; |
|
514 } |
|
515 } else { |
|
516 return midPoint; |
|
517 } |
|
518 base = midPoint + 1; |
|
519 } |
|
520 } |
|
521 return -1; |
|
522 } |
|
523 |
|
524 Symbol* leaky::findSymbol(u_long addr) |
|
525 { |
|
526 int idx = findSymbolIndex(addr); |
|
527 |
|
528 if(idx<0) { |
|
529 return nullptr; |
|
530 } else { |
|
531 return externalSymbols[idx]; |
|
532 } |
|
533 } |
|
534 |
|
535 //---------------------------------------------------------------------- |
|
536 |
|
537 bool leaky::excluded(malloc_log_entry* lep) |
|
538 { |
|
539 if (exclusions.IsEmpty()) { |
|
540 return false; |
|
541 } |
|
542 |
|
543 char** pcp = &lep->pcs[0]; |
|
544 u_int n = lep->numpcs; |
|
545 for (u_int i = 0; i < n; i++, pcp++) { |
|
546 Symbol* sp = findSymbol((u_long) *pcp); |
|
547 if (sp && exclusions.contains(sp->name)) { |
|
548 return true; |
|
549 } |
|
550 } |
|
551 return false; |
|
552 } |
|
553 |
|
554 bool leaky::included(malloc_log_entry* lep) |
|
555 { |
|
556 if (includes.IsEmpty()) { |
|
557 return true; |
|
558 } |
|
559 |
|
560 char** pcp = &lep->pcs[0]; |
|
561 u_int n = lep->numpcs; |
|
562 for (u_int i = 0; i < n; i++, pcp++) { |
|
563 Symbol* sp = findSymbol((u_long) *pcp); |
|
564 if (sp && includes.contains(sp->name)) { |
|
565 return true; |
|
566 } |
|
567 } |
|
568 return false; |
|
569 } |
|
570 |
|
571 //---------------------------------------------------------------------- |
|
572 |
|
573 void leaky::displayStackTrace(FILE* out, malloc_log_entry* lep) |
|
574 { |
|
575 char** pcp = &lep->pcs[0]; |
|
576 u_int n = (lep->numpcs < stackDepth) ? lep->numpcs : stackDepth; |
|
577 for (u_int i = 0; i < n; i++, pcp++) { |
|
578 u_long addr = (u_long) *pcp; |
|
579 Symbol* sp = findSymbol(addr); |
|
580 if (sp) { |
|
581 fputs(sp->name, out); |
|
582 if (showAddress) { |
|
583 fprintf(out, "[%p]", (char*)addr); |
|
584 } |
|
585 } |
|
586 else { |
|
587 fprintf(out, "<%p>", (char*)addr); |
|
588 } |
|
589 fputc(' ', out); |
|
590 } |
|
591 fputc('\n', out); |
|
592 } |
|
593 |
|
594 void leaky::dumpEntryToLog(malloc_log_entry* lep) |
|
595 { |
|
596 printf("%ld\t", lep->delTime); |
|
597 printf(" --> "); |
|
598 displayStackTrace(outputfd, lep); |
|
599 } |
|
600 |
|
601 void leaky::generateReportHTML(FILE *fp, int *countArray, int count, int thread) |
|
602 { |
|
603 fprintf(fp,"<center>"); |
|
604 if (showThreads) |
|
605 { |
|
606 fprintf(fp,"<hr><A NAME=thread_%d><b>Thread: %d</b></A><p>", |
|
607 thread,thread); |
|
608 } |
|
609 fprintf(fp,"<A href=#flat_%d>flat</A><b> | </b><A href=#hier_%d>hierarchical</A>", |
|
610 thread,thread); |
|
611 fprintf(fp,"</center><P><P><P>\n"); |
|
612 |
|
613 int totalTimerHits = count; |
|
614 int *rankingTable = new int[usefulSymbols]; |
|
615 |
|
616 for(int cnt=usefulSymbols; --cnt>=0; rankingTable[cnt]=cnt); |
|
617 |
|
618 // Drat. I would use ::qsort() but I would need a global variable and my |
|
619 // intro-pascal professor threatened to flunk anyone who used globals. |
|
620 // She damaged me for life :-) (That was 1986. See how much influence |
|
621 // she had. I don't remember her name but I always feel guilty about globals) |
|
622 |
|
623 // Shell Sort. 581130733 is the max 31 bit value of h = 3h+1 |
|
624 int mx, i, h; |
|
625 for(mx=usefulSymbols/9, h=581130733; h>0; h/=3) { |
|
626 if(h<mx) { |
|
627 for(i = h-1; i<usefulSymbols; i++) { |
|
628 int j, tmp=rankingTable[i], val = countArray[tmp]; |
|
629 for(j = i; (j>=h) && (countArray[rankingTable[j-h]]<val); j-=h) { |
|
630 rankingTable[j] = rankingTable[j-h]; |
|
631 } |
|
632 rankingTable[j] = tmp; |
|
633 } |
|
634 } |
|
635 } |
|
636 |
|
637 // Ok, We are sorted now. Let's go through the table until we get to |
|
638 // functions that were never called. Right now we don't do much inside |
|
639 // this loop. Later we can get callers and callees into it like gprof |
|
640 // does |
|
641 fprintf(fp, |
|
642 "<h2><A NAME=hier_%d></A><center><a href=\"http://mxr.mozilla.org/mozilla-central/source/tools/jprof/README.html#hier\">Hierarchical Profile</a></center></h2><hr>\n", |
|
643 thread); |
|
644 fprintf(fp, "<pre>\n"); |
|
645 fprintf(fp, "%6s %6s %4s %s\n", |
|
646 "index", "Count", "Hits", "Function Name"); |
|
647 |
|
648 for(i=0; i<usefulSymbols && countArray[rankingTable[i]]>0; i++) { |
|
649 Symbol **sp=&externalSymbols[rankingTable[i]]; |
|
650 |
|
651 (*sp)->cntP.printReport(fp, this, rankingTable[i], totalTimerHits); |
|
652 |
|
653 char *symname = htmlify((*sp)->name); |
|
654 fprintf(fp, "%6d %6d (%3.1f%%)%s <a name=%d>%8d (%3.1f%%)</a>%s <b>%s</b>\n", |
|
655 rankingTable[i], |
|
656 (*sp)->timerHit, ((*sp)->timerHit*1000/totalTimerHits)/10.0, |
|
657 ((*sp)->timerHit*1000/totalTimerHits)/10.0 >= 10.0 ? "" : " ", |
|
658 rankingTable[i], countArray[rankingTable[i]], |
|
659 (countArray[rankingTable[i]]*1000/totalTimerHits)/10.0, |
|
660 (countArray[rankingTable[i]]*1000/totalTimerHits)/10.0 >= 10.0 ? "" : " ", |
|
661 symname); |
|
662 delete [] symname; |
|
663 |
|
664 (*sp)->cntC.printReport(fp, this, rankingTable[i], totalTimerHits); |
|
665 |
|
666 fprintf(fp, "<hr>\n"); |
|
667 } |
|
668 fprintf(fp,"</pre>\n"); |
|
669 |
|
670 // OK, Now we want to print the flat profile. To do this we resort on |
|
671 // the hit count. |
|
672 |
|
673 // Cut-N-Paste Shell sort from above. The Ranking Table has already been |
|
674 // populated, so we do not have to reinitialize it. |
|
675 for(mx=usefulSymbols/9, h=581130733; h>0; h/=3) { |
|
676 if(h<mx) { |
|
677 for(i = h-1; i<usefulSymbols; i++) { |
|
678 int j, tmp=rankingTable[i], val = externalSymbols[tmp]->timerHit; |
|
679 for(j = i; |
|
680 (j>=h) && (externalSymbols[rankingTable[j-h]]->timerHit<val); j-=h) { |
|
681 rankingTable[j] = rankingTable[j-h]; |
|
682 } |
|
683 rankingTable[j] = tmp; |
|
684 } |
|
685 } |
|
686 } |
|
687 |
|
688 // Pre-count up total counter hits, to get a percentage. |
|
689 // I wanted the total before walking the list, if this |
|
690 // double-pass over externalSymbols gets slow we can |
|
691 // do single-pass and print this out after the loop finishes. |
|
692 totalTimerHits = 0; |
|
693 for(i=0; |
|
694 i<usefulSymbols && externalSymbols[rankingTable[i]]->timerHit>0; i++) { |
|
695 Symbol **sp=&externalSymbols[rankingTable[i]]; |
|
696 totalTimerHits += (*sp)->timerHit; |
|
697 } |
|
698 if (totalTimerHits == 0) |
|
699 totalTimerHits = 1; |
|
700 |
|
701 if (totalTimerHits != count) |
|
702 fprintf(stderr,"Hit count mismatch: count=%d; totalTimerHits=%d", |
|
703 count,totalTimerHits); |
|
704 |
|
705 fprintf(fp,"<h2><A NAME=flat_%d></A><center><a href=\"http://mxr.mozilla.org/mozilla-central/source/tools/jprof/README.html#flat\">Flat Profile</a></center></h2><br>\n", |
|
706 thread); |
|
707 fprintf(fp, "<pre>\n"); |
|
708 |
|
709 fprintf(fp, "Total hit count: %d\n", totalTimerHits); |
|
710 fprintf(fp, "Count %%Total Function Name\n"); |
|
711 // Now loop for as long as we have timer hits |
|
712 for(i=0; |
|
713 i<usefulSymbols && externalSymbols[rankingTable[i]]->timerHit>0; i++) { |
|
714 |
|
715 Symbol **sp=&externalSymbols[rankingTable[i]]; |
|
716 |
|
717 char *symname = htmlify((*sp)->name); |
|
718 fprintf(fp, "<a href=\"#%d\">%3d %-2.1f %s</a>\n", |
|
719 rankingTable[i], (*sp)->timerHit, |
|
720 ((float)(*sp)->timerHit/(float)totalTimerHits)*100.0, symname); |
|
721 delete [] symname; |
|
722 } |
|
723 } |
|
724 |
|
725 void leaky::analyze(int thread) |
|
726 { |
|
727 int *countArray = new int[usefulSymbols]; |
|
728 int *flagArray = new int[usefulSymbols]; |
|
729 |
|
730 //Zero our function call counter |
|
731 memset(countArray, 0, sizeof(countArray[0])*usefulSymbols); |
|
732 |
|
733 // reset hit counts |
|
734 for(int i=0; i<usefulSymbols; i++) { |
|
735 externalSymbols[i]->timerHit = 0; |
|
736 externalSymbols[i]->regClear(); |
|
737 } |
|
738 |
|
739 // The flag array is used to prevent counting symbols multiple times |
|
740 // if functions are called recursively. In order to keep from having |
|
741 // to zero it on each pass through the loop, we mark it with the value |
|
742 // of stacks on each trip through the loop. This means we can determine |
|
743 // if we have seen this symbol for this stack trace w/o having to reset |
|
744 // from the prior stacktrace. |
|
745 memset(flagArray, -1, sizeof(flagArray[0])*usefulSymbols); |
|
746 |
|
747 if (cleo) |
|
748 fprintf(outputfd,"m-Start\n"); |
|
749 |
|
750 // This loop walks through all the call stacks we recorded |
|
751 // --last, --start and --end can restrict it, as can excludes/includes |
|
752 stacks = 0; |
|
753 for(malloc_log_entry* lep=firstLogEntry; |
|
754 lep < lastLogEntry; |
|
755 lep = reinterpret_cast<malloc_log_entry*>(&lep->pcs[lep->numpcs])) { |
|
756 |
|
757 if ((thread != 0 && lep->thread != thread) || |
|
758 excluded(lep) || !included(lep)) |
|
759 { |
|
760 continue; |
|
761 } |
|
762 |
|
763 ++stacks; // How many stack frames did we collect |
|
764 |
|
765 u_int n = (lep->numpcs < stackDepth) ? lep->numpcs : stackDepth; |
|
766 char** pcp = &lep->pcs[n-1]; |
|
767 int idx=-1, parrentIdx=-1; // Init idx incase n==0 |
|
768 if (cleo) { |
|
769 // This loop walks through every symbol in the call stack. By walking it |
|
770 // backwards we know who called the function when we get there. |
|
771 char type = 's'; |
|
772 for (int i=n-1; i>=0; --i, --pcp) { |
|
773 idx = findSymbolIndex(reinterpret_cast<u_long>(*pcp)); |
|
774 |
|
775 if(idx>=0) { |
|
776 // Skip over bogus __restore_rt frames that realtime profiling |
|
777 // can introduce. |
|
778 if (i > 0 && !strcmp(externalSymbols[idx]->name, "__restore_rt")) { |
|
779 --pcp; |
|
780 --i; |
|
781 idx = findSymbolIndex(reinterpret_cast<u_long>(*pcp)); |
|
782 if (idx < 0) { |
|
783 continue; |
|
784 } |
|
785 } |
|
786 Symbol **sp=&externalSymbols[idx]; |
|
787 char *symname = htmlify((*sp)->name); |
|
788 fprintf(outputfd,"%c-%s\n",type,symname); |
|
789 delete [] symname; |
|
790 } |
|
791 // else can't find symbol - ignore |
|
792 type = 'c'; |
|
793 } |
|
794 } else { |
|
795 // This loop walks through every symbol in the call stack. By walking it |
|
796 // backwards we know who called the function when we get there. |
|
797 for (int i=n-1; i>=0; --i, --pcp) { |
|
798 idx = findSymbolIndex(reinterpret_cast<u_long>(*pcp)); |
|
799 |
|
800 if(idx>=0) { |
|
801 // Skip over bogus __restore_rt frames that realtime profiling |
|
802 // can introduce. |
|
803 if (i > 0 && !strcmp(externalSymbols[idx]->name, "__restore_rt")) { |
|
804 --pcp; |
|
805 --i; |
|
806 idx = findSymbolIndex(reinterpret_cast<u_long>(*pcp)); |
|
807 if (idx < 0) { |
|
808 continue; |
|
809 } |
|
810 } |
|
811 |
|
812 // If we have not seen this symbol before count it and mark it as seen |
|
813 if(flagArray[idx]!=stacks && ((flagArray[idx]=stacks) || true)) { |
|
814 ++countArray[idx]; |
|
815 } |
|
816 |
|
817 // We know who we are and we know who our parrent is. Count this |
|
818 if(parrentIdx>=0) { |
|
819 externalSymbols[parrentIdx]->regChild(idx); |
|
820 externalSymbols[idx]->regParrent(parrentIdx); |
|
821 } |
|
822 // inside if() so an unknown in the middle of a stack won't break |
|
823 // the link! |
|
824 parrentIdx=idx; |
|
825 } |
|
826 } |
|
827 |
|
828 // idx should be the function that we were in when we received the signal. |
|
829 if(idx>=0) { |
|
830 ++externalSymbols[idx]->timerHit; |
|
831 } |
|
832 |
|
833 } |
|
834 } |
|
835 if (!cleo) |
|
836 generateReportHTML(outputfd, countArray, stacks, thread); |
|
837 } |
|
838 |
|
839 void FunctionCount::printReport(FILE *fp, leaky *lk, int parent, int total) |
|
840 { |
|
841 const char *fmt = " <A href=\"#%d\">%8d (%3.1f%%)%s %s</A>%s\n"; |
|
842 |
|
843 int nmax, tmax=((~0U)>>1); |
|
844 |
|
845 do { |
|
846 nmax=0; |
|
847 for(int j=getSize(); --j>=0;) { |
|
848 int cnt = getCount(j); |
|
849 if(cnt==tmax) { |
|
850 int idx = getIndex(j); |
|
851 char *symname = htmlify(lk->indexToName(idx)); |
|
852 fprintf(fp, fmt, idx, getCount(j), |
|
853 getCount(j)*100.0/total, |
|
854 getCount(j)*100.0/total >= 10.0 ? "" : " ", |
|
855 symname, |
|
856 parent == idx ? " (self)" : ""); |
|
857 delete [] symname; |
|
858 } else if(cnt<tmax && cnt>nmax) { |
|
859 nmax=cnt; |
|
860 } |
|
861 } |
|
862 } while((tmax=nmax)>0); |
|
863 } |