|
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 /* |
|
8 ** spacetrace.c |
|
9 ** |
|
10 ** SpaceTrace is meant to take the output of trace-malloc and present |
|
11 ** a picture of allocations over the run of the application. |
|
12 */ |
|
13 |
|
14 /* |
|
15 ** Required include files. |
|
16 */ |
|
17 #include "spacetrace.h" |
|
18 |
|
19 #include <ctype.h> |
|
20 #include <math.h> |
|
21 #include <string.h> |
|
22 #include <time.h> |
|
23 #if defined(XP_WIN32) |
|
24 #include <malloc.h> /* _heapMin */ |
|
25 #endif |
|
26 |
|
27 #if defined(HAVE_BOUTELL_GD) |
|
28 /* |
|
29 ** See http://www.boutell.com/gd for the GD graphics library. |
|
30 ** Ports for many platorms exist. |
|
31 ** Your box may already have the lib (mine did, redhat 7.1 workstation). |
|
32 */ |
|
33 #include <gd.h> |
|
34 #include <gdfontt.h> |
|
35 #include <gdfonts.h> |
|
36 #include <gdfontmb.h> |
|
37 #endif /* HAVE_BOUTELL_GD */ |
|
38 |
|
39 #include "nsQuickSort.h" |
|
40 /* |
|
41 ** strcasecmp API please. |
|
42 */ |
|
43 #if defined(_MSC_VER) |
|
44 #define strcasecmp _stricmp |
|
45 #define strncasecmp _strnicmp |
|
46 #endif |
|
47 |
|
48 /* |
|
49 ** the globals variables. happy joy. |
|
50 */ |
|
51 STGlobals globals; |
|
52 |
|
53 /* |
|
54 ** have the heap cleanup at opportune times, if possible. |
|
55 */ |
|
56 void |
|
57 heapCompact(void) |
|
58 { |
|
59 #if defined(XP_WIN32) |
|
60 _heapmin(); |
|
61 #endif |
|
62 } |
|
63 |
|
64 #define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) \ |
|
65 PR_fprintf(PR_STDOUT, "--%s\nDisabled by default.\n%s\n", #option_name, option_help); |
|
66 #define ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help) \ |
|
67 PR_fprintf(PR_STDOUT, "--%s=<value>\nDefault value is \"%s\".\n%s\n", #option_name, default_value, option_help); |
|
68 #define ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \ |
|
69 PR_fprintf(PR_STDOUT, "--%s=<value>\nUp to %u occurrences allowed.\n%s\n", #option_name, array_size, option_help); |
|
70 #define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \ |
|
71 PR_fprintf(PR_STDOUT, "--%s=<value>\nUnlimited occurrences allowed.\n%s\n", #option_name, option_help); |
|
72 #define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \ |
|
73 PR_fprintf(PR_STDOUT, "--%s=<value>\nDefault value is %u.\n%s\n", #option_name, default_value, option_help); |
|
74 #define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \ |
|
75 PR_fprintf(PR_STDOUT, "--%s=<value>\nDefault value is %llu.\n%s\n", #option_name, default_value, option_help); |
|
76 |
|
77 /* |
|
78 ** showHelp |
|
79 ** |
|
80 ** Give simple command line help. |
|
81 ** Returns !0 if the help was showed. |
|
82 */ |
|
83 int |
|
84 showHelp(void) |
|
85 { |
|
86 int retval = 0; |
|
87 |
|
88 if (PR_FALSE != globals.mCommandLineOptions.mHelp) { |
|
89 PR_fprintf(PR_STDOUT, "Usage:\t%s [OPTION]... [-|filename]\n\n", |
|
90 globals.mProgramName); |
|
91 |
|
92 |
|
93 #include "stoptions.h" |
|
94 |
|
95 /* |
|
96 ** Showed something. |
|
97 */ |
|
98 retval = __LINE__; |
|
99 } |
|
100 |
|
101 return retval; |
|
102 } |
|
103 |
|
104 /* |
|
105 ** ticks2xsec |
|
106 ** |
|
107 ** Convert platform specific ticks to second units |
|
108 ** Returns 0 on success. |
|
109 */ |
|
110 uint32_t |
|
111 ticks2xsec(tmreader * aReader, uint32_t aTicks, uint32_t aResolution) |
|
112 { |
|
113 return (uint32_t)((aResolution * aTicks)/aReader->ticksPerSec); |
|
114 } |
|
115 |
|
116 #define ticks2msec(reader, ticks) ticks2xsec((reader), (ticks), 1000) |
|
117 #define ticks2usec(reader, ticks) ticks2xsec((reader), (ticks), 1000000) |
|
118 |
|
119 /* |
|
120 ** initOptions |
|
121 ** |
|
122 ** Determine global settings for the application. |
|
123 ** Returns 0 on success. |
|
124 */ |
|
125 int |
|
126 initOptions(int aArgCount, char **aArgArray) |
|
127 { |
|
128 int retval = 0; |
|
129 int traverse = 0; |
|
130 |
|
131 /* |
|
132 ** Set the initial global default options. |
|
133 */ |
|
134 #define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) globals.mCommandLineOptions.m##option_name = PR_FALSE; |
|
135 #define ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help) PR_snprintf(globals.mCommandLineOptions.m##option_name, sizeof(globals.mCommandLineOptions.m##option_name), "%s", default_value); |
|
136 #define ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) { int loop; for(loop = 0; loop < array_size; loop++) { globals.mCommandLineOptions.m##option_name[loop][0] = '\0'; } } |
|
137 #define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) globals.mCommandLineOptions.m##option_name = NULL; globals.mCommandLineOptions.m##option_name##Count = 0; |
|
138 #define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) globals.mCommandLineOptions.m##option_name = default_value * multiplier; |
|
139 #define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) { uint64_t def64 = default_value; uint64_t mul64 = multiplier; globals.mCommandLineOptions.m##option_name##64 = def64 * mul64; } |
|
140 |
|
141 #include "stoptions.h" |
|
142 |
|
143 /* |
|
144 ** Go through all arguments. |
|
145 ** Two dashes lead off an option. |
|
146 ** Any single dash leads off help, unless it is a lone dash (stdin). |
|
147 ** Anything else will be attempted as a file to be processed. |
|
148 */ |
|
149 for (traverse = 1; traverse < aArgCount; traverse++) { |
|
150 if ('-' == aArgArray[traverse][0] && '-' == aArgArray[traverse][1]) { |
|
151 const char *option = &aArgArray[traverse][2]; |
|
152 |
|
153 /* |
|
154 ** Initial if(0) needed to make "else if"s valid. |
|
155 */ |
|
156 if (0) { |
|
157 } |
|
158 |
|
159 #define ST_CMD_OPTION_BOOL(option_name, option_genre, option_help) \ |
|
160 else if(0 == strcasecmp(option, #option_name)) \ |
|
161 { \ |
|
162 globals.mCommandLineOptions.m##option_name = PR_TRUE; \ |
|
163 } |
|
164 #define ST_CMD_OPTION_STRING(option_name, option_genre, default_value, option_help) \ |
|
165 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \ |
|
166 { \ |
|
167 PR_snprintf(globals.mCommandLineOptions.m##option_name, sizeof(globals.mCommandLineOptions.m##option_name), "%s", option + strlen(#option_name "=")); \ |
|
168 } |
|
169 #define ST_CMD_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \ |
|
170 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \ |
|
171 { \ |
|
172 int arrLoop = 0; \ |
|
173 \ |
|
174 for(arrLoop = 0; arrLoop < array_size; arrLoop++) \ |
|
175 { \ |
|
176 if('\0' == globals.mCommandLineOptions.m##option_name[arrLoop][0]) \ |
|
177 { \ |
|
178 break; \ |
|
179 } \ |
|
180 }\ |
|
181 \ |
|
182 if(arrLoop != array_size) \ |
|
183 { \ |
|
184 PR_snprintf(globals.mCommandLineOptions.m##option_name[arrLoop], sizeof(globals.mCommandLineOptions.m##option_name[arrLoop]), "%s", option + strlen(#option_name "=")); \ |
|
185 } \ |
|
186 else \ |
|
187 { \ |
|
188 REPORT_ERROR_MSG(__LINE__, option); \ |
|
189 retval = __LINE__; \ |
|
190 globals.mCommandLineOptions.mHelp = PR_TRUE; \ |
|
191 } \ |
|
192 } |
|
193 #define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \ |
|
194 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \ |
|
195 { \ |
|
196 const char** expand = NULL; \ |
|
197 \ |
|
198 expand = (const char**)realloc((void*)globals.mCommandLineOptions.m##option_name, sizeof(const char*) * (globals.mCommandLineOptions.m##option_name##Count + 1)); \ |
|
199 if(NULL != expand) \ |
|
200 { \ |
|
201 globals.mCommandLineOptions.m##option_name = expand; \ |
|
202 globals.mCommandLineOptions.m##option_name[globals.mCommandLineOptions.m##option_name##Count] = option + strlen(#option_name "="); \ |
|
203 globals.mCommandLineOptions.m##option_name##Count++; \ |
|
204 } \ |
|
205 else \ |
|
206 { \ |
|
207 retval = __LINE__; \ |
|
208 globals.mCommandLineOptions.mHelp = PR_TRUE; \ |
|
209 } \ |
|
210 } |
|
211 #define ST_CMD_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \ |
|
212 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \ |
|
213 { \ |
|
214 int32_t scanRes = 0; \ |
|
215 \ |
|
216 scanRes = PR_sscanf(option + strlen(#option_name "="), "%u", &globals.mCommandLineOptions.m##option_name); \ |
|
217 if(1 != scanRes) \ |
|
218 { \ |
|
219 REPORT_ERROR_MSG(__LINE__, option); \ |
|
220 retval = __LINE__; \ |
|
221 globals.mCommandLineOptions.mHelp = PR_TRUE; \ |
|
222 } \ |
|
223 } |
|
224 #define ST_CMD_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \ |
|
225 else if(0 == strncasecmp(option, #option_name "=", strlen(#option_name "="))) \ |
|
226 { \ |
|
227 int32_t scanRes = 0; \ |
|
228 \ |
|
229 scanRes = PR_sscanf(option + strlen(#option_name "="), "%llu", &globals.mCommandLineOptions.m##option_name##64); \ |
|
230 if(1 != scanRes) \ |
|
231 { \ |
|
232 REPORT_ERROR_MSG(__LINE__, option); \ |
|
233 retval = __LINE__; \ |
|
234 globals.mCommandLineOptions.mHelp = PR_TRUE; \ |
|
235 } \ |
|
236 } |
|
237 |
|
238 #include "stoptions.h" |
|
239 |
|
240 /* |
|
241 ** If no match on options, this else will get hit. |
|
242 */ |
|
243 else { |
|
244 REPORT_ERROR_MSG(__LINE__, option); |
|
245 retval = __LINE__; |
|
246 globals.mCommandLineOptions.mHelp = PR_TRUE; |
|
247 } |
|
248 } |
|
249 else if ('-' == aArgArray[traverse][0] |
|
250 && '\0' != aArgArray[traverse][1]) { |
|
251 /* |
|
252 ** Show help, bad/legacy option. |
|
253 */ |
|
254 REPORT_ERROR_MSG(__LINE__, aArgArray[traverse]); |
|
255 retval = __LINE__; |
|
256 globals.mCommandLineOptions.mHelp = PR_TRUE; |
|
257 } |
|
258 else { |
|
259 /* |
|
260 ** Default is same as FileName option, the file to process. |
|
261 */ |
|
262 PR_snprintf(globals.mCommandLineOptions.mFileName, |
|
263 sizeof(globals.mCommandLineOptions.mFileName), "%s", |
|
264 aArgArray[traverse]); |
|
265 } |
|
266 } |
|
267 |
|
268 /* |
|
269 ** initialize the categories |
|
270 */ |
|
271 initCategories(&globals); |
|
272 |
|
273 return retval; |
|
274 } |
|
275 |
|
276 #if ST_WANT_GRAPHS |
|
277 /* |
|
278 ** createGraph |
|
279 ** |
|
280 ** Create a GD image with the common properties of a graph. |
|
281 ** Upon return, you normally allocate legend colors, |
|
282 ** draw your graph inside the region |
|
283 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGH-STGD_MARGIN, |
|
284 ** and then call drawGraph to format the surrounding information. |
|
285 ** |
|
286 ** You should use the normal GD image release function, gdImageDestroy |
|
287 ** when done with it. |
|
288 ** |
|
289 ** Image attributes: |
|
290 ** STGD_WIDTHxSTGD_HEIGHT |
|
291 ** trasparent (white) background |
|
292 ** incremental display |
|
293 */ |
|
294 gdImagePtr |
|
295 createGraph(int *aTransparencyColor) |
|
296 { |
|
297 gdImagePtr retval = NULL; |
|
298 |
|
299 if (NULL != aTransparencyColor) { |
|
300 *aTransparencyColor = -1; |
|
301 |
|
302 retval = gdImageCreate(STGD_WIDTH, STGD_HEIGHT); |
|
303 if (NULL != retval) { |
|
304 /* |
|
305 ** Background color (first one). |
|
306 */ |
|
307 *aTransparencyColor = gdImageColorAllocate(retval, 255, 255, 255); |
|
308 if (-1 != *aTransparencyColor) { |
|
309 /* |
|
310 ** As transparency. |
|
311 */ |
|
312 gdImageColorTransparent(retval, *aTransparencyColor); |
|
313 } |
|
314 |
|
315 /* |
|
316 ** And to set interlacing. |
|
317 */ |
|
318 gdImageInterlace(retval, 1); |
|
319 } |
|
320 else { |
|
321 REPORT_ERROR(__LINE__, gdImageCreate); |
|
322 } |
|
323 } |
|
324 else { |
|
325 REPORT_ERROR(__LINE__, createGraph); |
|
326 } |
|
327 |
|
328 return retval; |
|
329 } |
|
330 #endif /* ST_WANT_GRAPHS */ |
|
331 |
|
332 #if ST_WANT_GRAPHS |
|
333 /* |
|
334 ** drawGraph |
|
335 ** |
|
336 ** This function mainly exists to simplify putitng all the pretty lace |
|
337 ** around a home made graph. |
|
338 */ |
|
339 void |
|
340 drawGraph(gdImagePtr aImage, int aColor, |
|
341 const char *aGraphTitle, |
|
342 const char *aXAxisTitle, |
|
343 const char *aYAxisTitle, |
|
344 uint32_t aXMarkCount, |
|
345 uint32_t * aXMarkPercents, |
|
346 const char **aXMarkTexts, |
|
347 uint32_t aYMarkCount, |
|
348 uint32_t * aYMarkPercents, |
|
349 const char **aYMarkTexts, |
|
350 uint32_t aLegendCount, |
|
351 int *aLegendColors, const char **aLegendTexts) |
|
352 { |
|
353 if (NULL != aImage && NULL != aGraphTitle && |
|
354 NULL != aXAxisTitle && NULL != aYAxisTitle && |
|
355 (0 == aXMarkCount || (NULL != aXMarkPercents && NULL != aXMarkTexts)) |
|
356 && (0 == aYMarkCount |
|
357 || (NULL != aYMarkPercents && NULL != aYMarkTexts)) |
|
358 && (0 == aLegendCount |
|
359 || (NULL != aLegendColors && NULL != aLegendTexts))) { |
|
360 int margin = 1; |
|
361 uint32_t traverse = 0; |
|
362 uint32_t target = 0; |
|
363 const int markSize = 2; |
|
364 int x1 = 0; |
|
365 int y1 = 0; |
|
366 int x2 = 0; |
|
367 int y2 = 0; |
|
368 time_t theTimeT = time(NULL); |
|
369 char *theTime = ctime(&theTimeT); |
|
370 const char *logo = "SpaceTrace"; |
|
371 gdFontPtr titleFont = gdFontMediumBold; |
|
372 gdFontPtr markFont = gdFontTiny; |
|
373 gdFontPtr dateFont = gdFontTiny; |
|
374 gdFontPtr axisFont = gdFontSmall; |
|
375 gdFontPtr legendFont = gdFontTiny; |
|
376 gdFontPtr logoFont = gdFontTiny; |
|
377 |
|
378 /* |
|
379 ** Fixup the color. |
|
380 ** Black by default. |
|
381 */ |
|
382 if (-1 == aColor) { |
|
383 aColor = gdImageColorAllocate(aImage, 0, 0, 0); |
|
384 } |
|
385 if (-1 == aColor) { |
|
386 aColor = gdImageColorClosest(aImage, 0, 0, 0); |
|
387 } |
|
388 |
|
389 /* |
|
390 ** Output the box. |
|
391 */ |
|
392 x1 = STGD_MARGIN - margin; |
|
393 y1 = STGD_MARGIN - margin; |
|
394 x2 = STGD_WIDTH - x1; |
|
395 y2 = STGD_HEIGHT - y1; |
|
396 gdImageRectangle(aImage, x1, y1, x2, y2, aColor); |
|
397 margin++; |
|
398 |
|
399 /* |
|
400 ** Need to make small markings on the graph to indicate where the |
|
401 ** labels line up exactly. |
|
402 ** While we're at it, draw the label text. |
|
403 */ |
|
404 for (traverse = 0; traverse < aXMarkCount; traverse++) { |
|
405 target = |
|
406 ((STGD_WIDTH - |
|
407 (STGD_MARGIN * 2)) * aXMarkPercents[traverse]) / 100; |
|
408 |
|
409 x1 = STGD_MARGIN + target; |
|
410 y1 = STGD_MARGIN - margin; |
|
411 x2 = x1; |
|
412 y2 = y1 - markSize; |
|
413 gdImageLine(aImage, x1, y1, x2, y2, aColor); |
|
414 |
|
415 y1 = STGD_HEIGHT - y1; |
|
416 y2 = STGD_HEIGHT - y2; |
|
417 gdImageLine(aImage, x1, y1, x2, y2, aColor); |
|
418 |
|
419 if (NULL != aXMarkTexts[traverse]) { |
|
420 x1 = STGD_MARGIN + target - (markFont->h / 2); |
|
421 y1 = STGD_HEIGHT - STGD_MARGIN + margin + markSize + |
|
422 (strlen(aXMarkTexts[traverse]) * markFont->w); |
|
423 gdImageStringUp(aImage, markFont, x1, y1, |
|
424 (unsigned char *) aXMarkTexts[traverse], |
|
425 aColor); |
|
426 } |
|
427 } |
|
428 for (traverse = 0; traverse < aYMarkCount; traverse++) { |
|
429 target = |
|
430 ((STGD_HEIGHT - (STGD_MARGIN * 2)) * (100 - |
|
431 aYMarkPercents |
|
432 [traverse])) / 100; |
|
433 |
|
434 x1 = STGD_MARGIN - margin; |
|
435 y1 = STGD_MARGIN + target; |
|
436 x2 = x1 - markSize; |
|
437 y2 = y1; |
|
438 gdImageLine(aImage, x1, y1, x2, y2, aColor); |
|
439 |
|
440 x1 = STGD_WIDTH - x1; |
|
441 x2 = STGD_WIDTH - x2; |
|
442 gdImageLine(aImage, x1, y1, x2, y2, aColor); |
|
443 |
|
444 if (NULL != aYMarkTexts[traverse]) { |
|
445 x1 = STGD_MARGIN - margin - markSize - |
|
446 (strlen(aYMarkTexts[traverse]) * markFont->w); |
|
447 y1 = STGD_MARGIN + target - (markFont->h / 2); |
|
448 gdImageString(aImage, markFont, x1, y1, |
|
449 (unsigned char *) aYMarkTexts[traverse], |
|
450 aColor); |
|
451 } |
|
452 } |
|
453 margin += markSize; |
|
454 |
|
455 /* |
|
456 ** Title will be centered above the image. |
|
457 */ |
|
458 x1 = (STGD_WIDTH / 2) - ((strlen(aGraphTitle) * titleFont->w) / 2); |
|
459 y1 = ((STGD_MARGIN - margin) / 2) - (titleFont->h / 2); |
|
460 gdImageString(aImage, titleFont, x1, y1, |
|
461 (unsigned char *) aGraphTitle, aColor); |
|
462 |
|
463 /* |
|
464 ** Upper left will be the date. |
|
465 */ |
|
466 x1 = 0; |
|
467 y1 = 0; |
|
468 traverse = strlen(theTime) - 1; |
|
469 if (isspace(theTime[traverse])) { |
|
470 theTime[traverse] = '\0'; |
|
471 } |
|
472 gdImageString(aImage, dateFont, x1, y1, (unsigned char *) theTime, |
|
473 aColor); |
|
474 |
|
475 /* |
|
476 ** Lower right will be the logo. |
|
477 */ |
|
478 x1 = STGD_WIDTH - (strlen(logo) * logoFont->w); |
|
479 y1 = STGD_HEIGHT - logoFont->h; |
|
480 gdImageString(aImage, logoFont, x1, y1, (unsigned char *) logo, |
|
481 aColor); |
|
482 |
|
483 /* |
|
484 ** X and Y axis titles |
|
485 */ |
|
486 x1 = (STGD_WIDTH / 2) - ((strlen(aXAxisTitle) * axisFont->w) / 2); |
|
487 y1 = STGD_HEIGHT - axisFont->h; |
|
488 gdImageString(aImage, axisFont, x1, y1, (unsigned char *) aXAxisTitle, |
|
489 aColor); |
|
490 x1 = 0; |
|
491 y1 = (STGD_HEIGHT / 2) + ((strlen(aYAxisTitle) * axisFont->w) / 2); |
|
492 gdImageStringUp(aImage, axisFont, x1, y1, |
|
493 (unsigned char *) aYAxisTitle, aColor); |
|
494 |
|
495 /* |
|
496 ** The legend. |
|
497 ** Centered on the right hand side, going up. |
|
498 */ |
|
499 x1 = STGD_WIDTH - STGD_MARGIN + margin + |
|
500 (aLegendCount * legendFont->h) / 2; |
|
501 x2 = STGD_WIDTH - (aLegendCount * legendFont->h); |
|
502 if (x1 > x2) { |
|
503 x1 = x2; |
|
504 } |
|
505 |
|
506 y1 = 0; |
|
507 for (traverse = 0; traverse < aLegendCount; traverse++) { |
|
508 y2 = (STGD_HEIGHT / 2) + |
|
509 ((strlen(aLegendTexts[traverse]) * legendFont->w) / 2); |
|
510 if (y2 > y1) { |
|
511 y1 = y2; |
|
512 } |
|
513 } |
|
514 for (traverse = 0; traverse < aLegendCount; traverse++) { |
|
515 gdImageStringUp(aImage, legendFont, x1, y1, |
|
516 (unsigned char *) aLegendTexts[traverse], |
|
517 aLegendColors[traverse]); |
|
518 x1 += legendFont->h; |
|
519 } |
|
520 } |
|
521 } |
|
522 |
|
523 #endif /* ST_WANT_GRAPHS */ |
|
524 |
|
525 #if defined(HAVE_BOUTELL_GD) |
|
526 /* |
|
527 ** pngSink |
|
528 ** |
|
529 ** GD callback, used to write out the png. |
|
530 */ |
|
531 int |
|
532 pngSink(void *aContext, const char *aBuffer, int aLen) |
|
533 { |
|
534 return PR_Write((PRFileDesc *) aContext, aBuffer, aLen); |
|
535 } |
|
536 #endif /* HAVE_BOUTELL_GD */ |
|
537 |
|
538 /* |
|
539 ** FormatNumber |
|
540 ** |
|
541 ** Formats a number with thousands separator. Don't free the result. Returns |
|
542 ** static data. |
|
543 */ |
|
544 char * |
|
545 FormatNumber(int32_t num) |
|
546 { |
|
547 static char buf[64]; |
|
548 char tmpbuf[64]; |
|
549 int len = 0; |
|
550 int bufindex = sizeof(buf) - 1; |
|
551 int mod3; |
|
552 |
|
553 PR_snprintf(tmpbuf, sizeof(tmpbuf), "%d", num); |
|
554 |
|
555 /* now insert the thousands separator */ |
|
556 mod3 = 0; |
|
557 len = strlen(tmpbuf); |
|
558 while (len >= 0) { |
|
559 if (tmpbuf[len] >= '0' && tmpbuf[len] <= '9') { |
|
560 if (mod3 == 3) { |
|
561 buf[bufindex--] = ','; |
|
562 mod3 = 0; |
|
563 } |
|
564 mod3++; |
|
565 } |
|
566 buf[bufindex--] = tmpbuf[len--]; |
|
567 } |
|
568 return buf + bufindex + 1; |
|
569 } |
|
570 |
|
571 /* |
|
572 ** actualByteSize |
|
573 ** |
|
574 ** Apply alignment and overhead to size to figure out actual byte size |
|
575 */ |
|
576 uint32_t |
|
577 actualByteSize(STOptions * inOptions, uint32_t retval) |
|
578 { |
|
579 /* |
|
580 ** Need to bump the result by our alignment and overhead. |
|
581 ** The idea here is that an allocation actually costs you more than you |
|
582 ** thought. |
|
583 ** |
|
584 ** The msvcrt malloc has an alignment of 16 with an overhead of 8. |
|
585 ** The win32 HeapAlloc has an alignment of 8 with an overhead of 8. |
|
586 */ |
|
587 if (0 != retval) { |
|
588 uint32_t eval = 0; |
|
589 uint32_t over = 0; |
|
590 |
|
591 eval = retval - 1; |
|
592 if (0 != inOptions->mAlignBy) { |
|
593 over = eval % inOptions->mAlignBy; |
|
594 } |
|
595 retval = eval + inOptions->mOverhead + inOptions->mAlignBy - over; |
|
596 } |
|
597 |
|
598 return retval; |
|
599 } |
|
600 |
|
601 /* |
|
602 ** byteSize |
|
603 ** |
|
604 ** Figuring the byte size of an allocation. |
|
605 ** Might expand in the future to report size at a given time. |
|
606 ** For now, just use last relevant event. |
|
607 */ |
|
608 uint32_t |
|
609 byteSize(STOptions * inOptions, STAllocation * aAlloc) |
|
610 { |
|
611 uint32_t retval = 0; |
|
612 |
|
613 if (NULL != aAlloc && 0 != aAlloc->mEventCount) { |
|
614 uint32_t index = aAlloc->mEventCount; |
|
615 |
|
616 /* |
|
617 ** Generally, the size is the last event's size. |
|
618 */ |
|
619 do { |
|
620 index--; |
|
621 retval = aAlloc->mEvents[index].mHeapSize; |
|
622 } |
|
623 while (0 == retval && 0 != index); |
|
624 } |
|
625 return actualByteSize(inOptions, retval); |
|
626 } |
|
627 |
|
628 |
|
629 /* |
|
630 ** recalculateAllocationCost |
|
631 ** |
|
632 ** Given an allocation, does a recalculation of Cost - weight, heapcount etc. |
|
633 ** and does the right thing to propagate the cost upwards. |
|
634 */ |
|
635 int |
|
636 recalculateAllocationCost(STOptions * inOptions, STContext * inContext, |
|
637 STRun * aRun, STAllocation * aAllocation, |
|
638 PRBool updateParent) |
|
639 { |
|
640 /* |
|
641 ** Now, see if they desire a callsite update. |
|
642 ** As mentioned previously, we decide if the run desires us to |
|
643 ** manipulate the callsite data only if its stamp is set. |
|
644 ** We change all callsites and parent callsites to have that |
|
645 ** stamp as well, so as to mark them as being relevant to |
|
646 ** the current run in question. |
|
647 */ |
|
648 if (NULL != inContext && 0 != aRun->mStats[inContext->mIndex].mStamp) { |
|
649 uint32_t timeval = |
|
650 aAllocation->mMaxTimeval - aAllocation->mMinTimeval; |
|
651 uint32_t size = byteSize(inOptions, aAllocation); |
|
652 uint32_t heapCost = aAllocation->mHeapRuntimeCost; |
|
653 uint64_t timeval64 = timeval; |
|
654 uint64_t size64 = size; |
|
655 uint64_t weight64 = timeval64 * size64; |
|
656 |
|
657 /* |
|
658 ** First, update this run. |
|
659 */ |
|
660 aRun->mStats[inContext->mIndex].mCompositeCount++; |
|
661 aRun->mStats[inContext->mIndex].mHeapRuntimeCost += heapCost; |
|
662 aRun->mStats[inContext->mIndex].mSize += size; |
|
663 aRun->mStats[inContext->mIndex].mTimeval64 += timeval64; |
|
664 aRun->mStats[inContext->mIndex].mWeight64 += weight64; |
|
665 |
|
666 /* |
|
667 ** Use the first event of the allocation to update the parent |
|
668 ** callsites. |
|
669 ** This has positive effect of not updating realloc callsites |
|
670 ** with the same data over and over again. |
|
671 */ |
|
672 if (updateParent && 0 < aAllocation->mEventCount) { |
|
673 tmcallsite *callsite = aAllocation->mEvents[0].mCallsite; |
|
674 STRun *callsiteRun = NULL; |
|
675 |
|
676 /* |
|
677 ** Go up parents till we drop. |
|
678 */ |
|
679 while (NULL != callsite && NULL != callsite->method) { |
|
680 callsiteRun = CALLSITE_RUN(callsite); |
|
681 if (NULL != callsiteRun) { |
|
682 /* |
|
683 ** Do we init it? |
|
684 */ |
|
685 if (callsiteRun->mStats[inContext->mIndex].mStamp != |
|
686 aRun->mStats[inContext->mIndex].mStamp) { |
|
687 memset(&callsiteRun->mStats[inContext->mIndex], 0, |
|
688 sizeof(STCallsiteStats)); |
|
689 callsiteRun->mStats[inContext->mIndex].mStamp = |
|
690 aRun->mStats[inContext->mIndex].mStamp; |
|
691 } |
|
692 |
|
693 /* |
|
694 ** Add the values. |
|
695 ** Note that if the allocation was ever realloced, |
|
696 ** we are actually recording the final size. |
|
697 ** Also, the composite count does not include |
|
698 ** calls to realloc (or free for that matter), |
|
699 ** but rather is simply a count of actual heap |
|
700 ** allocation objects, from which someone will |
|
701 ** draw conclusions regarding number of malloc |
|
702 ** and free calls. |
|
703 ** It is possible to generate the exact number |
|
704 ** of calls to free/malloc/realloc should the |
|
705 ** absolute need arise to count them individually, |
|
706 ** but I fear it will take mucho memory and this |
|
707 ** is perhaps good enough for now. |
|
708 */ |
|
709 callsiteRun->mStats[inContext->mIndex].mCompositeCount++; |
|
710 callsiteRun->mStats[inContext->mIndex].mHeapRuntimeCost += |
|
711 heapCost; |
|
712 callsiteRun->mStats[inContext->mIndex].mSize += size; |
|
713 callsiteRun->mStats[inContext->mIndex].mTimeval64 += |
|
714 timeval64; |
|
715 callsiteRun->mStats[inContext->mIndex].mWeight64 += |
|
716 weight64; |
|
717 } |
|
718 |
|
719 callsite = callsite->parent; |
|
720 } |
|
721 } |
|
722 } |
|
723 |
|
724 return 0; |
|
725 } |
|
726 |
|
727 |
|
728 /* |
|
729 ** appendAllocation |
|
730 ** |
|
731 ** Given a run, append the allocation to it. |
|
732 ** No DUP checks are done. |
|
733 ** Also, we might want to update the parent callsites with stats. |
|
734 ** We decide to do this heavy duty work only if the run we are appending |
|
735 ** to has a non ZERO mStats[].mStamp, meaning that it is asking to track |
|
736 ** such information when it was created. |
|
737 ** Returns !0 on success. |
|
738 */ |
|
739 int |
|
740 appendAllocation(STOptions * inOptions, STContext * inContext, |
|
741 STRun * aRun, STAllocation * aAllocation) |
|
742 { |
|
743 int retval = 0; |
|
744 |
|
745 if (NULL != aRun && NULL != aAllocation && NULL != inOptions) { |
|
746 STAllocation **expand = NULL; |
|
747 |
|
748 /* |
|
749 ** Expand the size of the array if needed. |
|
750 */ |
|
751 expand = (STAllocation **) realloc(aRun->mAllocations, |
|
752 sizeof(STAllocation *) * |
|
753 (aRun->mAllocationCount + 1)); |
|
754 if (NULL != expand) { |
|
755 /* |
|
756 ** Reassign in case of pointer move. |
|
757 */ |
|
758 aRun->mAllocations = expand; |
|
759 |
|
760 /* |
|
761 ** Stick the allocation in. |
|
762 */ |
|
763 aRun->mAllocations[aRun->mAllocationCount] = aAllocation; |
|
764 |
|
765 /* |
|
766 ** If this is the global run, we need to let the allocation |
|
767 ** track the index back to us. |
|
768 */ |
|
769 if (&globals.mRun == aRun) { |
|
770 aAllocation->mRunIndex = aRun->mAllocationCount; |
|
771 } |
|
772 |
|
773 /* |
|
774 ** Increase the count. |
|
775 */ |
|
776 aRun->mAllocationCount++; |
|
777 |
|
778 /* |
|
779 ** We're good. |
|
780 */ |
|
781 retval = __LINE__; |
|
782 |
|
783 /* |
|
784 ** update allocation cost |
|
785 */ |
|
786 recalculateAllocationCost(inOptions, inContext, aRun, aAllocation, |
|
787 PR_TRUE); |
|
788 } |
|
789 else { |
|
790 REPORT_ERROR(__LINE__, appendAllocation); |
|
791 } |
|
792 } |
|
793 else { |
|
794 REPORT_ERROR(__LINE__, appendAllocation); |
|
795 } |
|
796 |
|
797 return retval; |
|
798 } |
|
799 |
|
800 /* |
|
801 ** hasCallsiteMatch |
|
802 ** |
|
803 ** Determine if the callsite or the other callsites has the matching text. |
|
804 ** |
|
805 ** Returns 0 if there is no match. |
|
806 */ |
|
807 int |
|
808 hasCallsiteMatch(tmcallsite * aCallsite, const char *aMatch, int aDirection) |
|
809 { |
|
810 int retval = 0; |
|
811 |
|
812 if (NULL != aCallsite && NULL != aCallsite->method && |
|
813 NULL != aMatch && '\0' != *aMatch) { |
|
814 const char *methodName = NULL; |
|
815 |
|
816 do { |
|
817 methodName = tmmethodnode_name(aCallsite->method); |
|
818 if (NULL != methodName && NULL != strstr(methodName, aMatch)) { |
|
819 /* |
|
820 ** Contains the text. |
|
821 */ |
|
822 retval = __LINE__; |
|
823 break; |
|
824 } |
|
825 else { |
|
826 switch (aDirection) { |
|
827 case ST_FOLLOW_SIBLINGS: |
|
828 aCallsite = aCallsite->siblings; |
|
829 break; |
|
830 case ST_FOLLOW_PARENTS: |
|
831 aCallsite = aCallsite->parent; |
|
832 break; |
|
833 default: |
|
834 aCallsite = NULL; |
|
835 REPORT_ERROR(__LINE__, hasCallsiteMatch); |
|
836 break; |
|
837 } |
|
838 } |
|
839 } |
|
840 while (NULL != aCallsite && NULL != aCallsite->method); |
|
841 } |
|
842 else { |
|
843 REPORT_ERROR(__LINE__, hasCallsiteMatch); |
|
844 } |
|
845 |
|
846 return retval; |
|
847 } |
|
848 |
|
849 /* |
|
850 ** harvestRun |
|
851 ** |
|
852 ** Provide a simply way to go over a run, and yield the relevant allocations. |
|
853 ** The restrictions are easily set via the options page or the command |
|
854 ** line switches. |
|
855 ** |
|
856 ** On any match, add the allocation to the provided run. |
|
857 ** |
|
858 ** This makes it much easier for all the code to respect the options in |
|
859 ** force. |
|
860 ** |
|
861 ** Returns !0 on error, though aOutRun may contain a partial data set. |
|
862 */ |
|
863 int |
|
864 harvestRun(const STRun * aInRun, STRun * aOutRun, |
|
865 STOptions * aOptions, STContext * inContext) |
|
866 { |
|
867 int retval = 0; |
|
868 |
|
869 #if defined(DEBUG_dp) |
|
870 PRIntervalTime start = PR_IntervalNow(); |
|
871 |
|
872 fprintf(stderr, "DEBUG: harvesting run...\n"); |
|
873 #endif |
|
874 |
|
875 if (NULL != aInRun && NULL != aOutRun && aInRun != aOutRun |
|
876 && NULL != aOptions && NULL != inContext) { |
|
877 uint32_t traverse = 0; |
|
878 STAllocation *current = NULL; |
|
879 |
|
880 for (traverse = 0; |
|
881 0 == retval && traverse < aInRun->mAllocationCount; traverse++) { |
|
882 current = aInRun->mAllocations[traverse]; |
|
883 if (NULL != current) { |
|
884 uint32_t lifetime = 0; |
|
885 uint32_t bytesize = 0; |
|
886 uint64_t weight64 = 0; |
|
887 uint64_t bytesize64 = 0; |
|
888 uint64_t lifetime64 = 0; |
|
889 int appendRes = 0; |
|
890 int looper = 0; |
|
891 PRBool matched = PR_FALSE; |
|
892 |
|
893 /* |
|
894 ** Use this as an opportune time to fixup a memory |
|
895 ** leaked timeval, so as to not completely skew |
|
896 ** the weights. |
|
897 */ |
|
898 if (ST_TIMEVAL_MAX == current->mMaxTimeval) { |
|
899 current->mMaxTimeval = globals.mMaxTimeval; |
|
900 } |
|
901 |
|
902 /* |
|
903 ** Check allocation timeval restrictions. |
|
904 ** We have to slide the recorded timevals to be zero |
|
905 ** based, so that the comparisons make sense. |
|
906 */ |
|
907 if ((aOptions->mAllocationTimevalMin > |
|
908 (current->mMinTimeval - globals.mMinTimeval)) || |
|
909 (aOptions->mAllocationTimevalMax < |
|
910 (current->mMinTimeval - globals.mMinTimeval))) { |
|
911 continue; |
|
912 } |
|
913 |
|
914 /* |
|
915 ** Check timeval restrictions. |
|
916 ** We have to slide the recorded timevals to be zero |
|
917 ** based, so that the comparisons make sense. |
|
918 */ |
|
919 if ((aOptions->mTimevalMin > |
|
920 (current->mMinTimeval - globals.mMinTimeval)) || |
|
921 (aOptions->mTimevalMax < |
|
922 (current->mMinTimeval - globals.mMinTimeval))) { |
|
923 continue; |
|
924 } |
|
925 |
|
926 /* |
|
927 ** Check lifetime restrictions. |
|
928 */ |
|
929 lifetime = current->mMaxTimeval - current->mMinTimeval; |
|
930 if ((lifetime < aOptions->mLifetimeMin) || |
|
931 (lifetime > aOptions->mLifetimeMax)) { |
|
932 continue; |
|
933 } |
|
934 |
|
935 /* |
|
936 ** Check byte size restrictions. |
|
937 */ |
|
938 bytesize = byteSize(aOptions, current); |
|
939 if ((bytesize < aOptions->mSizeMin) || |
|
940 (bytesize > aOptions->mSizeMax)) { |
|
941 continue; |
|
942 } |
|
943 |
|
944 /* |
|
945 ** Check weight restrictions. |
|
946 */ |
|
947 weight64 = (uint64_t)(bytesize * lifetime); |
|
948 if (weight64 < aOptions->mWeightMin64 || |
|
949 weight64 > aOptions->mWeightMax64) { |
|
950 continue; |
|
951 } |
|
952 |
|
953 /* |
|
954 ** Possibly restrict the callsite by text. |
|
955 ** Do this last, as it is a heavier check. |
|
956 ** |
|
957 ** One day, we may need to expand the logic to check for |
|
958 ** events beyond the initial allocation event. |
|
959 */ |
|
960 for (looper = 0; ST_SUBSTRING_MATCH_MAX > looper; looper++) { |
|
961 if ('\0' != aOptions->mRestrictText[looper][0]) { |
|
962 if (0 == |
|
963 hasCallsiteMatch(current->mEvents[0].mCallsite, |
|
964 aOptions->mRestrictText[looper], |
|
965 ST_FOLLOW_PARENTS)) { |
|
966 break; |
|
967 } |
|
968 } |
|
969 else { |
|
970 matched = PR_TRUE; |
|
971 break; |
|
972 } |
|
973 } |
|
974 if (ST_SUBSTRING_MATCH_MAX == looper) { |
|
975 matched = PR_TRUE; |
|
976 } |
|
977 if (PR_FALSE == matched) { |
|
978 continue; |
|
979 } |
|
980 |
|
981 /* |
|
982 ** You get here, we add to the run. |
|
983 */ |
|
984 appendRes = |
|
985 appendAllocation(aOptions, inContext, aOutRun, current); |
|
986 if (0 == appendRes) { |
|
987 retval = __LINE__; |
|
988 REPORT_ERROR(__LINE__, appendAllocation); |
|
989 } |
|
990 } |
|
991 } |
|
992 } |
|
993 |
|
994 #if defined(DEBUG_dp) |
|
995 fprintf(stderr, "DEBUG: harvesting ends: %dms [%d allocations]\n", |
|
996 PR_IntervalToMilliseconds(PR_IntervalNow() - start), |
|
997 aInRun->mAllocationCount); |
|
998 #endif |
|
999 return retval; |
|
1000 } |
|
1001 |
|
1002 /* |
|
1003 ** recalculateRunCost |
|
1004 ** |
|
1005 ** Goes over all allocations of a run and recalculates and propagates |
|
1006 ** the allocation costs - weight, heapcount, size |
|
1007 */ |
|
1008 int |
|
1009 recalculateRunCost(STOptions * inOptions, STContext * inContext, STRun * aRun) |
|
1010 { |
|
1011 uint32_t traverse = 0; |
|
1012 STAllocation *current = NULL; |
|
1013 |
|
1014 #if defined(DEBUG_dp) |
|
1015 PRIntervalTime start = PR_IntervalNow(); |
|
1016 |
|
1017 fprintf(stderr, "DEBUG: recalculateRunCost...\n"); |
|
1018 #endif |
|
1019 |
|
1020 if (NULL == aRun) |
|
1021 return -1; |
|
1022 |
|
1023 /* reset stats of this run to 0 to begin recalculation */ |
|
1024 memset(&aRun->mStats[inContext->mIndex], 0, sizeof(STCallsiteStats)); |
|
1025 |
|
1026 /* reset timestamp to force propogation of cost */ |
|
1027 aRun->mStats[inContext->mIndex].mStamp = PR_IntervalNow(); |
|
1028 |
|
1029 for (traverse = 0; traverse < aRun->mAllocationCount; traverse++) { |
|
1030 current = aRun->mAllocations[traverse]; |
|
1031 if (NULL != current) { |
|
1032 recalculateAllocationCost(inOptions, inContext, aRun, current, |
|
1033 PR_TRUE); |
|
1034 } |
|
1035 } |
|
1036 |
|
1037 #if defined(DEBUG_dp) |
|
1038 fprintf(stderr, "DEBUG: recalculateRunCost ends: %dms [%d allocations]\n", |
|
1039 PR_IntervalToMilliseconds(PR_IntervalNow() - start), |
|
1040 aRun->mAllocationCount); |
|
1041 #endif |
|
1042 |
|
1043 return 0; |
|
1044 } |
|
1045 |
|
1046 |
|
1047 /* |
|
1048 ** compareAllocations |
|
1049 ** |
|
1050 ** qsort callback. |
|
1051 ** Compare the allocations as specified by the options. |
|
1052 */ |
|
1053 int |
|
1054 compareAllocations(const void *aAlloc1, const void *aAlloc2, void *aContext) |
|
1055 { |
|
1056 int retval = 0; |
|
1057 STOptions *inOptions = (STOptions *) aContext; |
|
1058 |
|
1059 if (NULL != aAlloc1 && NULL != aAlloc2 && NULL != inOptions) { |
|
1060 STAllocation *alloc1 = *((STAllocation **) aAlloc1); |
|
1061 STAllocation *alloc2 = *((STAllocation **) aAlloc2); |
|
1062 |
|
1063 if (NULL != alloc1 && NULL != alloc2) { |
|
1064 /* |
|
1065 ** Logic determined by pref/option. |
|
1066 */ |
|
1067 switch (inOptions->mOrderBy) { |
|
1068 case ST_COUNT: |
|
1069 /* |
|
1070 ** "By count" on a single allocation means nothing, |
|
1071 ** fall through to weight. |
|
1072 */ |
|
1073 case ST_WEIGHT: |
|
1074 { |
|
1075 uint64_t weight164 = 0; |
|
1076 uint64_t weight264 = 0; |
|
1077 uint64_t bytesize164 = 0; |
|
1078 uint64_t bytesize264 = 0; |
|
1079 uint64_t timeval164 = 0; |
|
1080 uint64_t timeval264 = 0; |
|
1081 |
|
1082 bytesize164 = byteSize(inOptions, alloc1); |
|
1083 timeval164 = alloc1->mMaxTimeval - alloc1->mMinTimeval; |
|
1084 weight164 = bytesize164 * timeval164; |
|
1085 bytesize264 = byteSize(inOptions, alloc2); |
|
1086 timeval264 = alloc2->mMaxTimeval - alloc2->mMinTimeval; |
|
1087 weight264 = bytesize264 * timeval264; |
|
1088 |
|
1089 if (weight164 < weight264) { |
|
1090 retval = __LINE__; |
|
1091 } |
|
1092 else if (weight164 > weight264) { |
|
1093 retval = -__LINE__; |
|
1094 } |
|
1095 } |
|
1096 break; |
|
1097 |
|
1098 case ST_SIZE: |
|
1099 { |
|
1100 uint32_t size1 = byteSize(inOptions, alloc1); |
|
1101 uint32_t size2 = byteSize(inOptions, alloc2); |
|
1102 |
|
1103 if (size1 < size2) { |
|
1104 retval = __LINE__; |
|
1105 } |
|
1106 else if (size1 > size2) { |
|
1107 retval = -__LINE__; |
|
1108 } |
|
1109 } |
|
1110 break; |
|
1111 |
|
1112 case ST_TIMEVAL: |
|
1113 { |
|
1114 uint32_t timeval1 = |
|
1115 (alloc1->mMaxTimeval - alloc1->mMinTimeval); |
|
1116 uint32_t timeval2 = |
|
1117 (alloc2->mMaxTimeval - alloc2->mMinTimeval); |
|
1118 |
|
1119 if (timeval1 < timeval2) { |
|
1120 retval = __LINE__; |
|
1121 } |
|
1122 else if (timeval1 > timeval2) { |
|
1123 retval = -__LINE__; |
|
1124 } |
|
1125 } |
|
1126 break; |
|
1127 |
|
1128 case ST_HEAPCOST: |
|
1129 { |
|
1130 uint32_t cost1 = alloc1->mHeapRuntimeCost; |
|
1131 uint32_t cost2 = alloc2->mHeapRuntimeCost; |
|
1132 |
|
1133 if (cost1 < cost2) { |
|
1134 retval = __LINE__; |
|
1135 } |
|
1136 else if (cost1 > cost2) { |
|
1137 retval = -__LINE__; |
|
1138 } |
|
1139 } |
|
1140 break; |
|
1141 |
|
1142 default: |
|
1143 { |
|
1144 REPORT_ERROR(__LINE__, compareAllocations); |
|
1145 } |
|
1146 break; |
|
1147 } |
|
1148 } |
|
1149 } |
|
1150 |
|
1151 return retval; |
|
1152 } |
|
1153 |
|
1154 /* |
|
1155 ** sortRun |
|
1156 ** |
|
1157 ** Given a run, sort it in the manner specified by the options. |
|
1158 ** Returns !0 on failure. |
|
1159 */ |
|
1160 int |
|
1161 sortRun(STOptions * inOptions, STRun * aRun) |
|
1162 { |
|
1163 int retval = 0; |
|
1164 |
|
1165 if (NULL != aRun && NULL != inOptions) { |
|
1166 if (NULL != aRun->mAllocations && 0 < aRun->mAllocationCount) { |
|
1167 NS_QuickSort(aRun->mAllocations, aRun->mAllocationCount, |
|
1168 sizeof(STAllocation *), compareAllocations, |
|
1169 inOptions); |
|
1170 } |
|
1171 } |
|
1172 else { |
|
1173 retval = __LINE__; |
|
1174 REPORT_ERROR(__LINE__, sortRun); |
|
1175 } |
|
1176 |
|
1177 return retval; |
|
1178 } |
|
1179 |
|
1180 /* |
|
1181 ** createRun |
|
1182 ** |
|
1183 ** Returns a newly allocated run, properly initialized. |
|
1184 ** Must call freeRun() with the new STRun. |
|
1185 ** |
|
1186 ** ONLY PASS IN A NON_ZERO STAMP IF YOU KNOW WHAT YOU ARE DOING!!! |
|
1187 ** A non zero stamp in a run has side effects all over the |
|
1188 ** callsites of the allocations added to the run and their |
|
1189 ** parents. |
|
1190 ** |
|
1191 ** Returns NULL on failure. |
|
1192 */ |
|
1193 STRun * |
|
1194 createRun(STContext * inContext, uint32_t aStamp) |
|
1195 { |
|
1196 STRun *retval = NULL; |
|
1197 |
|
1198 retval = (STRun *) calloc(1, sizeof(STRun)); |
|
1199 if (NULL != retval) { |
|
1200 retval->mStats = |
|
1201 (STCallsiteStats *) calloc(globals.mCommandLineOptions.mContexts, |
|
1202 sizeof(STCallsiteStats)); |
|
1203 if (NULL != retval->mStats) { |
|
1204 if (NULL != inContext) { |
|
1205 retval->mStats[inContext->mIndex].mStamp = aStamp; |
|
1206 } |
|
1207 } |
|
1208 else { |
|
1209 free(retval); |
|
1210 retval = NULL; |
|
1211 } |
|
1212 } |
|
1213 |
|
1214 return retval; |
|
1215 } |
|
1216 |
|
1217 /* |
|
1218 ** freeRun |
|
1219 ** |
|
1220 ** Free off the run and the associated data. |
|
1221 */ |
|
1222 void |
|
1223 freeRun(STRun * aRun) |
|
1224 { |
|
1225 if (NULL != aRun) { |
|
1226 if (NULL != aRun->mAllocations) { |
|
1227 /* |
|
1228 ** We do not free the allocations themselves. |
|
1229 ** They are likely pointed to by at least 2 other existing |
|
1230 ** runs. |
|
1231 */ |
|
1232 free(aRun->mAllocations); |
|
1233 aRun->mAllocations = NULL; |
|
1234 } |
|
1235 |
|
1236 if (NULL != aRun->mStats) { |
|
1237 free(aRun->mStats); |
|
1238 aRun->mStats = NULL; |
|
1239 } |
|
1240 |
|
1241 free(aRun); |
|
1242 aRun = NULL; |
|
1243 } |
|
1244 } |
|
1245 |
|
1246 /* |
|
1247 ** createRunFromGlobal |
|
1248 ** |
|
1249 ** Harvest the global run, then sort it. |
|
1250 ** Returns NULL on failure. |
|
1251 ** Must call freeRun() with the new STRun. |
|
1252 */ |
|
1253 STRun * |
|
1254 createRunFromGlobal(STOptions * inOptions, STContext * inContext) |
|
1255 { |
|
1256 STRun *retval = NULL; |
|
1257 |
|
1258 if (NULL != inOptions && NULL != inContext) { |
|
1259 /* |
|
1260 ** We stamp the run. |
|
1261 ** As things are appended to it, it realizes that it should stamp the |
|
1262 ** callsite backtrace with the information as well. |
|
1263 ** In this manner, we can provide meaningful callsite data. |
|
1264 */ |
|
1265 retval = createRun(inContext, PR_IntervalNow()); |
|
1266 |
|
1267 if (NULL != retval) { |
|
1268 STCategoryNode *node = NULL; |
|
1269 int failure = 0; |
|
1270 int harvestRes = |
|
1271 harvestRun(&globals.mRun, retval, inOptions, inContext); |
|
1272 if (0 == harvestRes) { |
|
1273 int sortRes = sortRun(inOptions, retval); |
|
1274 |
|
1275 if (0 != sortRes) { |
|
1276 failure = __LINE__; |
|
1277 } |
|
1278 } |
|
1279 else { |
|
1280 failure = __LINE__; |
|
1281 } |
|
1282 |
|
1283 |
|
1284 if (0 != failure) { |
|
1285 freeRun(retval); |
|
1286 retval = NULL; |
|
1287 |
|
1288 REPORT_ERROR(failure, createRunFromGlobal); |
|
1289 } |
|
1290 |
|
1291 /* |
|
1292 ** Categorize the run. |
|
1293 */ |
|
1294 failure = categorizeRun(inOptions, inContext, retval, &globals); |
|
1295 if (0 != failure) { |
|
1296 REPORT_ERROR(__LINE__, categorizeRun); |
|
1297 } |
|
1298 |
|
1299 /* |
|
1300 ** if we are focussing on a category, return that run instead of |
|
1301 ** the harvested run. Make sure to recalculate cost. |
|
1302 */ |
|
1303 node = findCategoryNode(inOptions->mCategoryName, &globals); |
|
1304 if (node) { |
|
1305 /* Recalculate cost of run */ |
|
1306 recalculateRunCost(inOptions, inContext, |
|
1307 node->runs[inContext->mIndex]); |
|
1308 |
|
1309 retval = node->runs[inContext->mIndex]; |
|
1310 } |
|
1311 } |
|
1312 } |
|
1313 else { |
|
1314 REPORT_ERROR(__LINE__, createRunFromGlobal); |
|
1315 } |
|
1316 |
|
1317 return retval; |
|
1318 } |
|
1319 |
|
1320 /* |
|
1321 ** getLiveAllocationByHeapID |
|
1322 ** |
|
1323 ** Go through a run and find the right heap ID. |
|
1324 ** At the time of the call to this function, the allocation must be LIVE, |
|
1325 ** meaning that it can not be freed. |
|
1326 ** Go through the run backwards, in hopes of finding it near the end. |
|
1327 ** |
|
1328 ** Returns the allocation on success, otherwise NULL. |
|
1329 */ |
|
1330 STAllocation * |
|
1331 getLiveAllocationByHeapID(STRun * aRun, uint32_t aHeapID) |
|
1332 { |
|
1333 STAllocation *retval = NULL; |
|
1334 |
|
1335 if (NULL != aRun && 0 != aHeapID) { |
|
1336 uint32_t traverse = aRun->mAllocationCount; |
|
1337 STAllocation *eval = NULL; |
|
1338 |
|
1339 /* |
|
1340 ** Go through in reverse order. |
|
1341 ** Stop when we have a return value. |
|
1342 */ |
|
1343 while (0 < traverse && NULL == retval) { |
|
1344 /* |
|
1345 ** Back up one to align with zero based index. |
|
1346 */ |
|
1347 traverse--; |
|
1348 |
|
1349 /* |
|
1350 ** Take the pointer math out of further operations. |
|
1351 */ |
|
1352 eval = aRun->mAllocations[traverse]; |
|
1353 |
|
1354 /* |
|
1355 ** Take a look at the events in reverse order. |
|
1356 ** Basically the last event must NOT be a free. |
|
1357 ** The last event must NOT be a realloc of size zero (free). |
|
1358 ** Otherwise, try to match up the heapID of the event. |
|
1359 */ |
|
1360 if (0 != eval->mEventCount) { |
|
1361 STAllocEvent *event = eval->mEvents + (eval->mEventCount - 1); |
|
1362 |
|
1363 switch (event->mEventType) { |
|
1364 case TM_EVENT_FREE: |
|
1365 { |
|
1366 /* |
|
1367 ** No freed allocation can match. |
|
1368 */ |
|
1369 } |
|
1370 break; |
|
1371 |
|
1372 case TM_EVENT_REALLOC: |
|
1373 case TM_EVENT_CALLOC: |
|
1374 case TM_EVENT_MALLOC: |
|
1375 { |
|
1376 /* |
|
1377 ** Heap IDs must match. |
|
1378 */ |
|
1379 if (aHeapID == event->mHeapID) { |
|
1380 retval = eval; |
|
1381 } |
|
1382 } |
|
1383 break; |
|
1384 |
|
1385 default: |
|
1386 { |
|
1387 REPORT_ERROR(__LINE__, getAllocationByHeapID); |
|
1388 } |
|
1389 break; |
|
1390 } |
|
1391 } |
|
1392 } |
|
1393 } |
|
1394 else { |
|
1395 REPORT_ERROR(__LINE__, getAllocationByHeapID); |
|
1396 } |
|
1397 |
|
1398 return retval; |
|
1399 } |
|
1400 |
|
1401 /* |
|
1402 ** appendEvent |
|
1403 ** |
|
1404 ** Given an allocation, append a new event to its lifetime. |
|
1405 ** Returns the new event on success, otherwise NULL. |
|
1406 */ |
|
1407 STAllocEvent * |
|
1408 appendEvent(STAllocation * aAllocation, uint32_t aTimeval, char aEventType, |
|
1409 uint32_t aHeapID, uint32_t aHeapSize, tmcallsite * aCallsite) |
|
1410 { |
|
1411 STAllocEvent *retval = NULL; |
|
1412 |
|
1413 if (NULL != aAllocation && NULL != aCallsite) { |
|
1414 STAllocEvent *expand = NULL; |
|
1415 |
|
1416 /* |
|
1417 ** Expand the allocation's event array. |
|
1418 */ |
|
1419 expand = |
|
1420 (STAllocEvent *) realloc(aAllocation->mEvents, |
|
1421 sizeof(STAllocEvent) * |
|
1422 (aAllocation->mEventCount + 1)); |
|
1423 if (NULL != expand) { |
|
1424 /* |
|
1425 ** Reassign in case of pointer move. |
|
1426 */ |
|
1427 aAllocation->mEvents = expand; |
|
1428 |
|
1429 /* |
|
1430 ** Remove the pointer math from rest of code. |
|
1431 */ |
|
1432 retval = aAllocation->mEvents + aAllocation->mEventCount; |
|
1433 |
|
1434 /* |
|
1435 ** Increase event array count. |
|
1436 */ |
|
1437 aAllocation->mEventCount++; |
|
1438 |
|
1439 /* |
|
1440 ** Fill in the event. |
|
1441 */ |
|
1442 retval->mTimeval = aTimeval; |
|
1443 retval->mEventType = aEventType; |
|
1444 retval->mHeapID = aHeapID; |
|
1445 retval->mHeapSize = aHeapSize; |
|
1446 retval->mCallsite = aCallsite; |
|
1447 |
|
1448 /* |
|
1449 ** Allocation may need to update idea of lifetime. |
|
1450 ** See allocationTracker to see mMinTimeval inited to ST_TIMEVAL_MAX. |
|
1451 */ |
|
1452 if (aAllocation->mMinTimeval > aTimeval) { |
|
1453 aAllocation->mMinTimeval = aTimeval; |
|
1454 } |
|
1455 |
|
1456 /* |
|
1457 ** This a free event? |
|
1458 ** Can only set max timeval on a free. |
|
1459 ** Otherwise, mMaxTimeval remains ST_TIMEVAL_MAX. |
|
1460 ** Set in allocationTracker. |
|
1461 */ |
|
1462 if (TM_EVENT_FREE == aEventType) { |
|
1463 aAllocation->mMaxTimeval = aTimeval; |
|
1464 } |
|
1465 } |
|
1466 else { |
|
1467 REPORT_ERROR(__LINE__, appendEvent); |
|
1468 } |
|
1469 } |
|
1470 else { |
|
1471 REPORT_ERROR(__LINE__, appendEvent); |
|
1472 } |
|
1473 |
|
1474 return retval; |
|
1475 } |
|
1476 |
|
1477 /* |
|
1478 ** hasAllocation |
|
1479 ** |
|
1480 ** Determine if a given run has an allocation. |
|
1481 ** This is really nothing more than a pointer comparison loop. |
|
1482 ** Returns !0 if the run has the allocation. |
|
1483 */ |
|
1484 int |
|
1485 hasAllocation(STRun * aRun, STAllocation * aTestFor) |
|
1486 { |
|
1487 int retval = 0; |
|
1488 |
|
1489 if (NULL != aRun && NULL != aTestFor) { |
|
1490 uint32_t traverse = aRun->mAllocationCount; |
|
1491 |
|
1492 /* |
|
1493 ** Go through reverse, in the hopes it exists nearer the end. |
|
1494 */ |
|
1495 while (0 < traverse) { |
|
1496 /* |
|
1497 ** Back up. |
|
1498 */ |
|
1499 traverse--; |
|
1500 |
|
1501 if (aTestFor == aRun->mAllocations[traverse]) { |
|
1502 retval = __LINE__; |
|
1503 break; |
|
1504 } |
|
1505 } |
|
1506 } |
|
1507 else { |
|
1508 REPORT_ERROR(__LINE__, hasAllocation); |
|
1509 } |
|
1510 |
|
1511 return retval; |
|
1512 } |
|
1513 |
|
1514 /* |
|
1515 ** allocationTracker |
|
1516 ** |
|
1517 ** Important to keep track of all allocations unique so as to determine |
|
1518 ** their lifetimes. |
|
1519 ** |
|
1520 ** Returns a pointer to the allocation on success. |
|
1521 ** Return NULL on failure. |
|
1522 */ |
|
1523 STAllocation * |
|
1524 allocationTracker(uint32_t aTimeval, char aType, uint32_t aHeapRuntimeCost, |
|
1525 tmcallsite * aCallsite, uint32_t aHeapID, uint32_t aSize, |
|
1526 tmcallsite * aOldCallsite, uint32_t aOldHeapID, |
|
1527 uint32_t aOldSize) |
|
1528 { |
|
1529 STAllocation *retval = NULL; |
|
1530 static int compactor = 1; |
|
1531 const int frequency = 10000; |
|
1532 uint32_t actualSize, actualOldSize = 0; |
|
1533 |
|
1534 actualSize = actualByteSize(&globals.mCommandLineOptions, aSize); |
|
1535 if (aOldSize) |
|
1536 actualOldSize = |
|
1537 actualByteSize(&globals.mCommandLineOptions, aOldSize); |
|
1538 |
|
1539 if (NULL != aCallsite) { |
|
1540 int newAllocation = 0; |
|
1541 tmcallsite *searchCallsite = NULL; |
|
1542 uint32_t searchHeapID = 0; |
|
1543 STAllocation *allocation = NULL; |
|
1544 |
|
1545 /* |
|
1546 ** Global operation ID increases. |
|
1547 */ |
|
1548 globals.mOperationCount++; |
|
1549 |
|
1550 /* |
|
1551 ** Fix up the timevals if needed. |
|
1552 */ |
|
1553 if (aTimeval < globals.mMinTimeval) { |
|
1554 globals.mMinTimeval = aTimeval; |
|
1555 } |
|
1556 if (aTimeval > globals.mMaxTimeval) { |
|
1557 globals.mMaxTimeval = aTimeval; |
|
1558 } |
|
1559 |
|
1560 switch (aType) { |
|
1561 case TM_EVENT_FREE: |
|
1562 { |
|
1563 /* |
|
1564 ** Update the global counter. |
|
1565 */ |
|
1566 globals.mFreeCount++; |
|
1567 |
|
1568 /* |
|
1569 ** Update our peak memory used counter |
|
1570 */ |
|
1571 globals.mMemoryUsed -= actualSize; |
|
1572 |
|
1573 /* |
|
1574 ** Not a new allocation, will need to search passed in site |
|
1575 ** for the original allocation. |
|
1576 */ |
|
1577 searchCallsite = aCallsite; |
|
1578 searchHeapID = aHeapID; |
|
1579 } |
|
1580 break; |
|
1581 |
|
1582 case TM_EVENT_MALLOC: |
|
1583 { |
|
1584 /* |
|
1585 ** Update the global counter. |
|
1586 */ |
|
1587 globals.mMallocCount++; |
|
1588 |
|
1589 /* |
|
1590 ** Update our peak memory used counter |
|
1591 */ |
|
1592 globals.mMemoryUsed += actualSize; |
|
1593 if (globals.mMemoryUsed > globals.mPeakMemoryUsed) { |
|
1594 globals.mPeakMemoryUsed = globals.mMemoryUsed; |
|
1595 } |
|
1596 |
|
1597 /* |
|
1598 ** This will be a new allocation. |
|
1599 */ |
|
1600 newAllocation = __LINE__; |
|
1601 } |
|
1602 break; |
|
1603 |
|
1604 case TM_EVENT_CALLOC: |
|
1605 { |
|
1606 /* |
|
1607 ** Update the global counter. |
|
1608 */ |
|
1609 globals.mCallocCount++; |
|
1610 |
|
1611 /* |
|
1612 ** Update our peak memory used counter |
|
1613 */ |
|
1614 globals.mMemoryUsed += actualSize; |
|
1615 if (globals.mMemoryUsed > globals.mPeakMemoryUsed) { |
|
1616 globals.mPeakMemoryUsed = globals.mMemoryUsed; |
|
1617 } |
|
1618 |
|
1619 /* |
|
1620 ** This will be a new allocation. |
|
1621 */ |
|
1622 newAllocation = __LINE__; |
|
1623 } |
|
1624 break; |
|
1625 |
|
1626 case TM_EVENT_REALLOC: |
|
1627 { |
|
1628 /* |
|
1629 ** Update the global counter. |
|
1630 */ |
|
1631 globals.mReallocCount++; |
|
1632 |
|
1633 /* |
|
1634 ** Update our peak memory used counter |
|
1635 */ |
|
1636 globals.mMemoryUsed += actualSize - actualOldSize; |
|
1637 if (globals.mMemoryUsed > globals.mPeakMemoryUsed) { |
|
1638 globals.mPeakMemoryUsed = globals.mMemoryUsed; |
|
1639 } |
|
1640 |
|
1641 /* |
|
1642 ** This might be a new allocation. |
|
1643 */ |
|
1644 if (NULL == aOldCallsite) { |
|
1645 newAllocation = __LINE__; |
|
1646 } |
|
1647 else { |
|
1648 /* |
|
1649 ** Need to search for the original callsite for the |
|
1650 ** index to the allocation. |
|
1651 */ |
|
1652 searchCallsite = aOldCallsite; |
|
1653 searchHeapID = aOldHeapID; |
|
1654 } |
|
1655 } |
|
1656 break; |
|
1657 |
|
1658 default: |
|
1659 { |
|
1660 REPORT_ERROR(__LINE__, allocationTracker); |
|
1661 } |
|
1662 break; |
|
1663 } |
|
1664 |
|
1665 /* |
|
1666 ** We are either modifying an existing allocation or we are creating |
|
1667 ** a new one. |
|
1668 */ |
|
1669 if (0 != newAllocation) { |
|
1670 allocation = (STAllocation *) calloc(1, sizeof(STAllocation)); |
|
1671 if (NULL != allocation) { |
|
1672 /* |
|
1673 ** Fixup the min timeval so if logic later will just work. |
|
1674 */ |
|
1675 allocation->mMinTimeval = ST_TIMEVAL_MAX; |
|
1676 allocation->mMaxTimeval = ST_TIMEVAL_MAX; |
|
1677 } |
|
1678 } |
|
1679 else if (NULL != searchCallsite |
|
1680 && NULL != CALLSITE_RUN(searchCallsite) |
|
1681 && 0 != searchHeapID) { |
|
1682 /* |
|
1683 ** We know what to search for, and we reduce what we search |
|
1684 ** by only looking for those allocations at a known callsite. |
|
1685 */ |
|
1686 allocation = |
|
1687 getLiveAllocationByHeapID(CALLSITE_RUN(searchCallsite), |
|
1688 searchHeapID); |
|
1689 } |
|
1690 else { |
|
1691 REPORT_ERROR(__LINE__, allocationTracker); |
|
1692 } |
|
1693 |
|
1694 if (NULL != allocation) { |
|
1695 STAllocEvent *appendResult = NULL; |
|
1696 |
|
1697 /* |
|
1698 ** Record the amount of time this allocation event took. |
|
1699 */ |
|
1700 allocation->mHeapRuntimeCost += aHeapRuntimeCost; |
|
1701 |
|
1702 /* |
|
1703 ** Now that we have an allocation, we need to make sure it has |
|
1704 ** the proper event. |
|
1705 */ |
|
1706 appendResult = |
|
1707 appendEvent(allocation, aTimeval, aType, aHeapID, aSize, |
|
1708 aCallsite); |
|
1709 if (NULL != appendResult) { |
|
1710 if (0 != newAllocation) { |
|
1711 int runAppendResult = 0; |
|
1712 int callsiteAppendResult = 0; |
|
1713 |
|
1714 /* |
|
1715 ** A new allocation needs to be added to the global run. |
|
1716 ** A new allocation needs to be added to the callsite. |
|
1717 */ |
|
1718 runAppendResult = |
|
1719 appendAllocation(&globals.mCommandLineOptions, NULL, |
|
1720 &globals.mRun, allocation); |
|
1721 callsiteAppendResult = |
|
1722 appendAllocation(&globals.mCommandLineOptions, NULL, |
|
1723 CALLSITE_RUN(aCallsite), allocation); |
|
1724 if (0 != runAppendResult && 0 != callsiteAppendResult) { |
|
1725 /* |
|
1726 ** Success. |
|
1727 */ |
|
1728 retval = allocation; |
|
1729 } |
|
1730 else { |
|
1731 REPORT_ERROR(__LINE__, appendAllocation); |
|
1732 } |
|
1733 } |
|
1734 else { |
|
1735 /* |
|
1736 ** An existing allocation, if a realloc situation, |
|
1737 ** may need to be added to the new callsite. |
|
1738 ** This can only occur if the new and old callsites |
|
1739 ** differ. |
|
1740 ** Even then, a brute force check will need to be made |
|
1741 ** to ensure the allocation was not added twice; |
|
1742 ** consider a realloc scenario where two different |
|
1743 ** call stacks bump the allocation back and forth. |
|
1744 */ |
|
1745 if (aCallsite != searchCallsite) { |
|
1746 int found = 0; |
|
1747 |
|
1748 found = |
|
1749 hasAllocation(CALLSITE_RUN(aCallsite), |
|
1750 allocation); |
|
1751 if (0 == found) { |
|
1752 int appendResult = 0; |
|
1753 |
|
1754 appendResult = |
|
1755 appendAllocation(&globals.mCommandLineOptions, |
|
1756 NULL, |
|
1757 CALLSITE_RUN(aCallsite), |
|
1758 allocation); |
|
1759 if (0 != appendResult) { |
|
1760 /* |
|
1761 ** Success. |
|
1762 */ |
|
1763 retval = allocation; |
|
1764 } |
|
1765 else { |
|
1766 REPORT_ERROR(__LINE__, appendAllocation); |
|
1767 } |
|
1768 } |
|
1769 else { |
|
1770 /* |
|
1771 ** Already there. |
|
1772 */ |
|
1773 retval = allocation; |
|
1774 } |
|
1775 } |
|
1776 else { |
|
1777 /* |
|
1778 ** Success. |
|
1779 */ |
|
1780 retval = allocation; |
|
1781 } |
|
1782 } |
|
1783 } |
|
1784 else { |
|
1785 REPORT_ERROR(__LINE__, appendEvent); |
|
1786 } |
|
1787 } |
|
1788 else { |
|
1789 REPORT_ERROR(__LINE__, allocationTracker); |
|
1790 } |
|
1791 } |
|
1792 else { |
|
1793 REPORT_ERROR(__LINE__, allocationTracker); |
|
1794 } |
|
1795 |
|
1796 /* |
|
1797 ** Compact the heap a bit if you can. |
|
1798 */ |
|
1799 compactor++; |
|
1800 if (0 == (compactor % frequency)) { |
|
1801 heapCompact(); |
|
1802 } |
|
1803 |
|
1804 return retval; |
|
1805 } |
|
1806 |
|
1807 /* |
|
1808 ** trackEvent |
|
1809 ** |
|
1810 ** An allocation event has dropped in on us. |
|
1811 ** We need to do the right thing and track it. |
|
1812 */ |
|
1813 void |
|
1814 trackEvent(uint32_t aTimeval, char aType, uint32_t aHeapRuntimeCost, |
|
1815 tmcallsite * aCallsite, uint32_t aHeapID, uint32_t aSize, |
|
1816 tmcallsite * aOldCallsite, uint32_t aOldHeapID, uint32_t aOldSize) |
|
1817 { |
|
1818 if (NULL != aCallsite) { |
|
1819 /* |
|
1820 ** Verify the old callsite just in case. |
|
1821 */ |
|
1822 if (NULL != CALLSITE_RUN(aCallsite) |
|
1823 && (NULL == aOldCallsite || NULL != CALLSITE_RUN(aOldCallsite))) { |
|
1824 STAllocation *allocation = NULL; |
|
1825 |
|
1826 /* |
|
1827 ** Add to the allocation tracking code. |
|
1828 */ |
|
1829 allocation = |
|
1830 allocationTracker(aTimeval, aType, aHeapRuntimeCost, |
|
1831 aCallsite, aHeapID, aSize, aOldCallsite, |
|
1832 aOldHeapID, aOldSize); |
|
1833 |
|
1834 if (NULL == allocation) { |
|
1835 REPORT_ERROR(__LINE__, allocationTracker); |
|
1836 } |
|
1837 } |
|
1838 else { |
|
1839 REPORT_ERROR(__LINE__, trackEvent); |
|
1840 } |
|
1841 } |
|
1842 else { |
|
1843 REPORT_ERROR(__LINE__, trackEvent); |
|
1844 } |
|
1845 } |
|
1846 |
|
1847 /* |
|
1848 ** tmEventHandler |
|
1849 ** |
|
1850 ** Callback from the tmreader_eventloop function. |
|
1851 ** Simply tries to sort out what we desire to know. |
|
1852 */ |
|
1853 |
|
1854 static const char spinner_chars[] = { '/', '-', '\\', '|' }; |
|
1855 |
|
1856 #define SPINNER_UPDATE_FREQUENCY 4096 |
|
1857 #define SPINNER_CHAR_COUNT (sizeof(spinner_chars) / sizeof(spinner_chars[0])) |
|
1858 #define SPINNER_CHAR(_x) spinner_chars[(_x / SPINNER_UPDATE_FREQUENCY) % SPINNER_CHAR_COUNT] |
|
1859 |
|
1860 void |
|
1861 tmEventHandler(tmreader * aReader, tmevent * aEvent) |
|
1862 { |
|
1863 static int event_count = 0; /* for spinner */ |
|
1864 if ((event_count++ % SPINNER_UPDATE_FREQUENCY) == 0) |
|
1865 printf("\rReading... %c", SPINNER_CHAR(event_count)); |
|
1866 |
|
1867 if (NULL != aReader && NULL != aEvent) { |
|
1868 switch (aEvent->type) { |
|
1869 /* |
|
1870 ** Events we ignore. |
|
1871 */ |
|
1872 case TM_EVENT_LIBRARY: |
|
1873 case TM_EVENT_METHOD: |
|
1874 case TM_EVENT_STATS: |
|
1875 case TM_EVENT_TIMESTAMP: |
|
1876 case TM_EVENT_FILENAME: |
|
1877 break; |
|
1878 |
|
1879 /* |
|
1880 ** Allocation events need to be tracked. |
|
1881 */ |
|
1882 case TM_EVENT_MALLOC: |
|
1883 case TM_EVENT_CALLOC: |
|
1884 case TM_EVENT_REALLOC: |
|
1885 case TM_EVENT_FREE: |
|
1886 { |
|
1887 uint32_t oldptr = 0; |
|
1888 uint32_t oldsize = 0; |
|
1889 tmcallsite *callsite = NULL; |
|
1890 tmcallsite *oldcallsite = NULL; |
|
1891 |
|
1892 if (TM_EVENT_REALLOC == aEvent->type) { |
|
1893 /* |
|
1894 ** Only care about old arguments if there were any. |
|
1895 */ |
|
1896 if (0 != aEvent->u.alloc.oldserial) { |
|
1897 oldptr = aEvent->u.alloc.oldptr; |
|
1898 oldsize = aEvent->u.alloc.oldsize; |
|
1899 oldcallsite = |
|
1900 tmreader_callsite(aReader, |
|
1901 aEvent->u.alloc.oldserial); |
|
1902 if (NULL == oldcallsite) { |
|
1903 REPORT_ERROR(__LINE__, tmreader_callsite); |
|
1904 } |
|
1905 } |
|
1906 } |
|
1907 |
|
1908 callsite = tmreader_callsite(aReader, aEvent->serial); |
|
1909 if (NULL != callsite) { |
|
1910 /* |
|
1911 ** Verify a callsite run is there. |
|
1912 ** If not, we are ignoring this callsite. |
|
1913 */ |
|
1914 if (NULL != CALLSITE_RUN(callsite)) { |
|
1915 char eventType = aEvent->type; |
|
1916 uint32_t eventSize = aEvent->u.alloc.size; |
|
1917 |
|
1918 /* |
|
1919 ** Play a nasty trick on reallocs of size zero. |
|
1920 ** They are to become free events, adjust the size accordingly. |
|
1921 ** This allows me to avoid all types of special case code. |
|
1922 */ |
|
1923 if (0 == aEvent->u.alloc.size |
|
1924 && TM_EVENT_REALLOC == aEvent->type) { |
|
1925 eventType = TM_EVENT_FREE; |
|
1926 if (0 != aEvent->u.alloc.oldserial) { |
|
1927 eventSize = aEvent->u.alloc.oldsize; |
|
1928 } |
|
1929 } |
|
1930 trackEvent(ticks2msec |
|
1931 (aReader, aEvent->u.alloc.interval), |
|
1932 eventType, ticks2usec(aReader, |
|
1933 aEvent->u.alloc. |
|
1934 cost), callsite, |
|
1935 aEvent->u.alloc.ptr, eventSize, |
|
1936 oldcallsite, oldptr, oldsize); |
|
1937 } |
|
1938 } |
|
1939 else { |
|
1940 REPORT_ERROR(__LINE__, tmreader_callsite); |
|
1941 } |
|
1942 } |
|
1943 break; |
|
1944 |
|
1945 /* |
|
1946 ** Callsite, set up the callsite run if it does not exist. |
|
1947 */ |
|
1948 case TM_EVENT_CALLSITE: |
|
1949 { |
|
1950 tmcallsite *callsite = |
|
1951 tmreader_callsite(aReader, aEvent->serial); |
|
1952 |
|
1953 if (NULL != callsite) { |
|
1954 if (NULL == CALLSITE_RUN(callsite)) { |
|
1955 int createrun = __LINE__; |
|
1956 |
|
1957 #if defined(MOZILLA_CLIENT) |
|
1958 /* |
|
1959 ** For a mozilla spacetrace, ignore this particular |
|
1960 ** callsite as it is just noise, and causes us to |
|
1961 ** use a lot of memory. |
|
1962 ** |
|
1963 ** This callsite is present on the linux build, |
|
1964 ** not sure if the other platforms have it. |
|
1965 */ |
|
1966 if (0 != |
|
1967 hasCallsiteMatch(callsite, "g_main_is_running", |
|
1968 ST_FOLLOW_PARENTS)) { |
|
1969 createrun = 0; |
|
1970 } |
|
1971 #endif /* MOZILLA_CLIENT */ |
|
1972 |
|
1973 if (0 != createrun) { |
|
1974 callsite->data = createRun(NULL, 0); |
|
1975 } |
|
1976 } |
|
1977 } |
|
1978 else { |
|
1979 REPORT_ERROR(__LINE__, tmreader_callsite); |
|
1980 } |
|
1981 } |
|
1982 break; |
|
1983 |
|
1984 /* |
|
1985 ** Unhandled events should not be allowed. |
|
1986 */ |
|
1987 default: |
|
1988 { |
|
1989 REPORT_ERROR(__LINE__, tmEventHandler); |
|
1990 } |
|
1991 break; |
|
1992 } |
|
1993 } |
|
1994 } |
|
1995 |
|
1996 /* |
|
1997 ** optionGetDataOut |
|
1998 ** |
|
1999 ** Output option get data. |
|
2000 */ |
|
2001 void |
|
2002 optionGetDataOut(PRFileDesc * inFD, STOptions * inOptions) |
|
2003 { |
|
2004 if (NULL != inFD && NULL != inOptions) { |
|
2005 int mark = 0; |
|
2006 |
|
2007 #define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \ |
|
2008 PR_fprintf(inFD, "%s%s=%d", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name); |
|
2009 #define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \ |
|
2010 PR_fprintf(inFD, "%s%s=%s", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name); |
|
2011 #define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \ |
|
2012 { \ |
|
2013 uint32_t loop = 0; \ |
|
2014 \ |
|
2015 for(loop = 0; loop < array_size; loop++) \ |
|
2016 { \ |
|
2017 PR_fprintf(inFD, "%s%s=%s", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name[loop]); \ |
|
2018 } \ |
|
2019 } |
|
2020 #define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */ |
|
2021 #define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \ |
|
2022 PR_fprintf(inFD, "%s%s=%u", (0 == mark++) ? "?" : "&", #option_name, inOptions->m##option_name / multiplier); |
|
2023 #define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \ |
|
2024 { \ |
|
2025 uint64_t def64 = default_value; \ |
|
2026 uint64_t mul64 = multiplier; \ |
|
2027 uint64_t div64; \ |
|
2028 \ |
|
2029 div64 = inOptions->m##option_name##64 / mul64; \ |
|
2030 PR_fprintf(inFD, "%s%s=%llu", (0 == mark++) ? "?" : "&", #option_name, div64); \ |
|
2031 } |
|
2032 |
|
2033 #include "stoptions.h" |
|
2034 } |
|
2035 } |
|
2036 |
|
2037 /* |
|
2038 ** htmlAnchor |
|
2039 ** |
|
2040 ** Output an HTML anchor, or just the text depending on the mode. |
|
2041 */ |
|
2042 void |
|
2043 htmlAnchor(STRequest * inRequest, |
|
2044 const char *aHref, |
|
2045 const char *aText, |
|
2046 const char *aTarget, const char *aClass, STOptions * inOptions) |
|
2047 { |
|
2048 if (NULL != aHref && '\0' != *aHref && NULL != aText && '\0' != *aText) { |
|
2049 int anchorLive = 1; |
|
2050 |
|
2051 /* |
|
2052 ** In batch mode, we need to verify the anchor is live. |
|
2053 */ |
|
2054 if (0 != inRequest->mOptions.mBatchRequestCount) { |
|
2055 uint32_t loop = 0; |
|
2056 int comparison = 1; |
|
2057 |
|
2058 for (loop = 0; loop < inRequest->mOptions.mBatchRequestCount; |
|
2059 loop++) { |
|
2060 comparison = |
|
2061 strcmp(aHref, inRequest->mOptions.mBatchRequest[loop]); |
|
2062 if (0 == comparison) { |
|
2063 break; |
|
2064 } |
|
2065 } |
|
2066 |
|
2067 /* |
|
2068 ** Did we find it? |
|
2069 */ |
|
2070 if (0 == comparison) { |
|
2071 anchorLive = 0; |
|
2072 } |
|
2073 } |
|
2074 |
|
2075 /* |
|
2076 ** In any mode, don't make an href to the current page. |
|
2077 */ |
|
2078 if (0 != anchorLive && NULL != inRequest->mGetFileName) { |
|
2079 if (0 == strcmp(aHref, inRequest->mGetFileName)) { |
|
2080 anchorLive = 0; |
|
2081 } |
|
2082 } |
|
2083 |
|
2084 /* |
|
2085 ** Do the right thing. |
|
2086 */ |
|
2087 if (0 != anchorLive) { |
|
2088 PR_fprintf(inRequest->mFD, "<a class=\"%s\" ", aClass); |
|
2089 if (NULL != aTarget && '\0' != *aTarget) { |
|
2090 PR_fprintf(inRequest->mFD, "target=\"%s\" ", aTarget); |
|
2091 } |
|
2092 PR_fprintf(inRequest->mFD, "href=\"./%s", aHref); |
|
2093 |
|
2094 /* |
|
2095 ** The options, if desired, get appended as form data. |
|
2096 */ |
|
2097 optionGetDataOut(inRequest->mFD, inOptions); |
|
2098 |
|
2099 PR_fprintf(inRequest->mFD, "\">%s</a>\n", aText); |
|
2100 } |
|
2101 else { |
|
2102 PR_fprintf(inRequest->mFD, "<span class=\"%s\">%s</span>\n", |
|
2103 aClass, aText); |
|
2104 } |
|
2105 } |
|
2106 else { |
|
2107 REPORT_ERROR(__LINE__, htmlAnchor); |
|
2108 } |
|
2109 } |
|
2110 |
|
2111 /* |
|
2112 ** htmlAllocationAnchor |
|
2113 ** |
|
2114 ** Output an html achor that will resolve to the allocation in question. |
|
2115 */ |
|
2116 void |
|
2117 htmlAllocationAnchor(STRequest * inRequest, STAllocation * aAllocation, |
|
2118 const char *aText) |
|
2119 { |
|
2120 if (NULL != aAllocation && NULL != aText && '\0' != *aText) { |
|
2121 char buffer[128]; |
|
2122 |
|
2123 /* |
|
2124 ** This is a total hack. |
|
2125 ** The filename contains the index of the allocation in globals.mRun. |
|
2126 ** Safer than using the raw pointer value.... |
|
2127 */ |
|
2128 PR_snprintf(buffer, sizeof(buffer), "allocation_%u.html", |
|
2129 aAllocation->mRunIndex); |
|
2130 |
|
2131 htmlAnchor(inRequest, buffer, aText, NULL, "allocation", |
|
2132 &inRequest->mOptions); |
|
2133 } |
|
2134 else { |
|
2135 REPORT_ERROR(__LINE__, htmlAllocationAnchor); |
|
2136 } |
|
2137 } |
|
2138 |
|
2139 /* |
|
2140 ** resolveSourceFile |
|
2141 ** |
|
2142 ** Easy way to get a readable/short name. |
|
2143 ** NULL if not present, not resolvable. |
|
2144 */ |
|
2145 const char * |
|
2146 resolveSourceFile(tmmethodnode * aMethod) |
|
2147 { |
|
2148 const char *retval = NULL; |
|
2149 |
|
2150 if (NULL != aMethod) { |
|
2151 const char *methodSays = NULL; |
|
2152 |
|
2153 methodSays = aMethod->sourcefile; |
|
2154 |
|
2155 if (NULL != methodSays && '\0' != methodSays[0] |
|
2156 && 0 != strcmp("noname", methodSays)) { |
|
2157 retval = strrchr(methodSays, '/'); |
|
2158 if (NULL != retval) { |
|
2159 retval++; |
|
2160 } |
|
2161 else { |
|
2162 retval = methodSays; |
|
2163 } |
|
2164 } |
|
2165 } |
|
2166 |
|
2167 return retval; |
|
2168 } |
|
2169 |
|
2170 /* |
|
2171 ** htmlCallsiteAnchor |
|
2172 ** |
|
2173 ** Output an html anchor that will resolve to the callsite in question. |
|
2174 ** If no text is provided, we provide our own. |
|
2175 ** |
|
2176 ** RealName determines whether or not we crawl our parents until the point |
|
2177 ** we no longer match stats. |
|
2178 */ |
|
2179 void |
|
2180 htmlCallsiteAnchor(STRequest * inRequest, tmcallsite * aCallsite, |
|
2181 const char *aText, int aRealName) |
|
2182 { |
|
2183 if (NULL != aCallsite) { |
|
2184 char textBuf[512]; |
|
2185 char hrefBuf[128]; |
|
2186 tmcallsite *namesite = aCallsite; |
|
2187 |
|
2188 /* |
|
2189 ** Should we use a different name? |
|
2190 */ |
|
2191 if (0 == aRealName && NULL != namesite->parent |
|
2192 && NULL != namesite->parent->method) { |
|
2193 STRun *myRun = NULL; |
|
2194 STRun *upRun = NULL; |
|
2195 |
|
2196 do { |
|
2197 myRun = CALLSITE_RUN(namesite); |
|
2198 upRun = CALLSITE_RUN(namesite->parent); |
|
2199 |
|
2200 if (0 != |
|
2201 memcmp(&myRun->mStats[inRequest->mContext->mIndex], |
|
2202 &upRun->mStats[inRequest->mContext->mIndex], |
|
2203 sizeof(STCallsiteStats))) { |
|
2204 /* |
|
2205 ** Doesn't match, stop. |
|
2206 */ |
|
2207 break; |
|
2208 } |
|
2209 else { |
|
2210 /* |
|
2211 ** Matches, keep going up. |
|
2212 */ |
|
2213 namesite = namesite->parent; |
|
2214 } |
|
2215 } |
|
2216 while (NULL != namesite->parent |
|
2217 && NULL != namesite->parent->method); |
|
2218 } |
|
2219 |
|
2220 /* |
|
2221 ** If no text, provide our own. |
|
2222 */ |
|
2223 if (NULL == aText || '\0' == *aText) { |
|
2224 const char *methodName = NULL; |
|
2225 const char *sourceFile = NULL; |
|
2226 |
|
2227 if (NULL != namesite->method) { |
|
2228 methodName = tmmethodnode_name(namesite->method); |
|
2229 } |
|
2230 else { |
|
2231 methodName = "==NONAME=="; |
|
2232 } |
|
2233 |
|
2234 /* |
|
2235 ** Decide which format to use to identify the callsite. |
|
2236 ** If we can detect availability, hook up the filename with lxr information. |
|
2237 */ |
|
2238 sourceFile = resolveSourceFile(namesite->method); |
|
2239 if (NULL != sourceFile |
|
2240 && 0 == strncmp("mozilla/", namesite->method->sourcefile, |
|
2241 8)) { |
|
2242 char lxrHREFBuf[512]; |
|
2243 |
|
2244 PR_snprintf(lxrHREFBuf, sizeof(lxrHREFBuf), |
|
2245 " [<a href=\"http://lxr.mozilla.org/mozilla/source/%s#%u\" class=\"lxr\" target=\"_st_lxr\">%s:%u</a>]", |
|
2246 namesite->method->sourcefile + 8, |
|
2247 namesite->method->linenumber, sourceFile, |
|
2248 namesite->method->linenumber); |
|
2249 PR_snprintf(textBuf, sizeof(textBuf), |
|
2250 "<span class=\"source mozilla-source\">%s</span>%s", |
|
2251 methodName, lxrHREFBuf); |
|
2252 } |
|
2253 else if (NULL != sourceFile) { |
|
2254 PR_snprintf(textBuf, sizeof(textBuf), |
|
2255 "<span class=\"source external-source\">%s [<span class=\"source-extra\">%s:%u</span>]</span>", |
|
2256 methodName, sourceFile, |
|
2257 namesite->method->linenumber); |
|
2258 } |
|
2259 else { |
|
2260 PR_snprintf(textBuf, sizeof(textBuf), |
|
2261 "<span class=\"source binary-source\">%s [<span class=\"source-extra\">+%u(%u)</span>]</span>", |
|
2262 methodName, namesite->offset, |
|
2263 (uint32_t) namesite->entry.key); |
|
2264 } |
|
2265 |
|
2266 aText = textBuf; |
|
2267 } |
|
2268 |
|
2269 PR_snprintf(hrefBuf, sizeof(hrefBuf), "callsite_%u.html", |
|
2270 (uint32_t) aCallsite->entry.key); |
|
2271 |
|
2272 htmlAnchor(inRequest, hrefBuf, aText, NULL, "callsite", |
|
2273 &inRequest->mOptions); |
|
2274 } |
|
2275 else { |
|
2276 REPORT_ERROR(__LINE__, htmlCallsiteAnchor); |
|
2277 } |
|
2278 } |
|
2279 |
|
2280 /* |
|
2281 ** htmlHeader |
|
2282 ** |
|
2283 ** Output a standard header in the report files. |
|
2284 */ |
|
2285 void |
|
2286 htmlHeader(STRequest * inRequest, const char *aTitle) |
|
2287 { |
|
2288 PR_fprintf(inRequest->mFD, |
|
2289 "<html>\n" |
|
2290 "<head>\n" |
|
2291 "<title>%s</title>\n" |
|
2292 "<link rel=\"stylesheet\" href=\"spacetrace.css\" type=\"text/css\"" |
|
2293 "</head>\n" |
|
2294 "<body>\n" |
|
2295 "<div class=spacetrace-header>\n" |
|
2296 "<span class=spacetrace-title>Spacetrace</span>" |
|
2297 "<span class=navigate>\n" |
|
2298 "<span class=\"category-title header-text\">Category:</span>\n" |
|
2299 "<span class=\"current-category\">%s</span>\n", |
|
2300 aTitle, inRequest->mOptions.mCategoryName); |
|
2301 |
|
2302 PR_fprintf(inRequest->mFD, "<span class=\"header-item\">"); |
|
2303 htmlAnchor(inRequest, "index.html", "Index", NULL, "header-menuitem", |
|
2304 &inRequest->mOptions); |
|
2305 PR_fprintf(inRequest->mFD, "</span>\n"); |
|
2306 |
|
2307 PR_fprintf(inRequest->mFD, "<span class=\"header-item\">"); |
|
2308 htmlAnchor(inRequest, "options.html", "Options", NULL, "header-menuitem", |
|
2309 &inRequest->mOptions); |
|
2310 PR_fprintf(inRequest->mFD, "</span>\n"); |
|
2311 |
|
2312 PR_fprintf(inRequest->mFD, "</span>\n"); /* class=navigate */ |
|
2313 |
|
2314 PR_fprintf(inRequest->mFD, |
|
2315 "</div>\n\n<div class=\"header-separator\"></div>\n\n"); |
|
2316 } |
|
2317 |
|
2318 /* |
|
2319 ** htmlFooter |
|
2320 ** |
|
2321 ** Output a standard footer in the report file. |
|
2322 */ |
|
2323 void |
|
2324 htmlFooter(STRequest * inRequest) |
|
2325 { |
|
2326 PR_fprintf(inRequest->mFD, |
|
2327 "<div class=\"footer-separator\"></div>\n\n" |
|
2328 "<div class=\"footer\">\n" |
|
2329 "<span class=\"footer-text\">SpaceTrace</span>\n" |
|
2330 "</div>\n\n" "</body>\n" "</html>\n"); |
|
2331 } |
|
2332 |
|
2333 /* |
|
2334 ** htmlNotFound |
|
2335 ** |
|
2336 ** Not found message. |
|
2337 */ |
|
2338 void |
|
2339 htmlNotFound(STRequest * inRequest) |
|
2340 { |
|
2341 htmlHeader(inRequest, "File Not Found"); |
|
2342 PR_fprintf(inRequest->mFD, "File Not Found\n"); |
|
2343 htmlFooter(inRequest); |
|
2344 } |
|
2345 |
|
2346 void |
|
2347 htmlStartTable(STRequest* inRequest, |
|
2348 const char* table_class, |
|
2349 const char* id, |
|
2350 const char* caption, |
|
2351 const char * const headers[], uint32_t header_length) |
|
2352 { |
|
2353 uint32_t i; |
|
2354 |
|
2355 PR_fprintf(inRequest->mFD, |
|
2356 "<div id=\"%s\"><table class=\"data %s\">\n" |
|
2357 " <caption>%s</caption>" |
|
2358 " <thead>\n" |
|
2359 " <tr class=\"row-header\">\n", id, |
|
2360 table_class ? table_class : "", |
|
2361 caption); |
|
2362 |
|
2363 for (i=0; i< header_length; i++) |
|
2364 PR_fprintf(inRequest->mFD, |
|
2365 " <th>%s</th>\n", headers[i]); |
|
2366 |
|
2367 PR_fprintf(inRequest->mFD, " </tr> </thead> <tbody>\n"); |
|
2368 } |
|
2369 |
|
2370 /* |
|
2371 ** callsiteArrayFromCallsite |
|
2372 ** |
|
2373 ** Simply return an array of the callsites divulged from the site passed in, |
|
2374 ** including the site passed in. |
|
2375 ** Do not worry about dups, or the order of the items. |
|
2376 ** |
|
2377 ** Returns the number of items in the array. |
|
2378 ** If the same as aExistingCount, then nothing happened. |
|
2379 */ |
|
2380 uint32_t |
|
2381 callsiteArrayFromCallsite(tmcallsite *** aArray, uint32_t aExistingCount, |
|
2382 tmcallsite * aSite, int aFollow) |
|
2383 { |
|
2384 uint32_t retval = 0; |
|
2385 |
|
2386 if (NULL != aArray && NULL != aSite) { |
|
2387 tmcallsite **expand = NULL; |
|
2388 |
|
2389 /* |
|
2390 ** If we have an existing count, we just keep expanding this. |
|
2391 */ |
|
2392 retval = aExistingCount; |
|
2393 |
|
2394 /* |
|
2395 ** Go through every allocation. |
|
2396 */ |
|
2397 do { |
|
2398 /* |
|
2399 ** expand the array. |
|
2400 */ |
|
2401 expand = |
|
2402 (tmcallsite **) realloc(*aArray, |
|
2403 sizeof(tmcallsite *) * (retval + 1)); |
|
2404 if (NULL != expand) { |
|
2405 /* |
|
2406 ** Set the callsite in case of pointer move. |
|
2407 */ |
|
2408 *aArray = expand; |
|
2409 |
|
2410 /* |
|
2411 ** Assign the value. |
|
2412 */ |
|
2413 (*aArray)[retval] = aSite; |
|
2414 retval++; |
|
2415 } |
|
2416 else { |
|
2417 REPORT_ERROR(__LINE__, realloc); |
|
2418 break; |
|
2419 } |
|
2420 |
|
2421 |
|
2422 /* |
|
2423 ** What do we follow? |
|
2424 */ |
|
2425 switch (aFollow) { |
|
2426 case ST_FOLLOW_SIBLINGS: |
|
2427 aSite = aSite->siblings; |
|
2428 break; |
|
2429 case ST_FOLLOW_PARENTS: |
|
2430 aSite = aSite->parent; |
|
2431 break; |
|
2432 default: |
|
2433 aSite = NULL; |
|
2434 REPORT_ERROR(__LINE__, callsiteArrayFromCallsite); |
|
2435 break; |
|
2436 } |
|
2437 } |
|
2438 while (NULL != aSite && NULL != aSite->method); |
|
2439 } |
|
2440 |
|
2441 return retval; |
|
2442 } |
|
2443 |
|
2444 /* |
|
2445 ** callsiteArrayFromRun |
|
2446 ** |
|
2447 ** Simply return an array of the callsites from the run allocations. |
|
2448 ** We only pay attention to callsites that were not free callsites. |
|
2449 ** Do not worry about dups, or the order of the items. |
|
2450 ** |
|
2451 ** Returns the number of items in the array. |
|
2452 ** If the same as aExistingCount, then nothing happened. |
|
2453 */ |
|
2454 uint32_t |
|
2455 callsiteArrayFromRun(tmcallsite *** aArray, uint32_t aExistingCount, |
|
2456 STRun * aRun) |
|
2457 { |
|
2458 uint32_t retval = 0; |
|
2459 |
|
2460 if (NULL != aArray && NULL != aRun && 0 < aRun->mAllocationCount) { |
|
2461 uint32_t allocLoop = 0; |
|
2462 uint32_t eventLoop = 0; |
|
2463 int stopLoops = 0; |
|
2464 |
|
2465 /* |
|
2466 ** If we have an existing count, we just keep expanding this. |
|
2467 */ |
|
2468 retval = aExistingCount; |
|
2469 |
|
2470 /* |
|
2471 ** Go through every allocation. |
|
2472 */ |
|
2473 for (allocLoop = 0; |
|
2474 0 == stopLoops && allocLoop < aRun->mAllocationCount; |
|
2475 allocLoop++) { |
|
2476 /* |
|
2477 ** Go through every event. |
|
2478 */ |
|
2479 for (eventLoop = 0; |
|
2480 0 == stopLoops |
|
2481 && eventLoop < aRun->mAllocations[allocLoop]->mEventCount; |
|
2482 eventLoop++) { |
|
2483 /* |
|
2484 ** Skip the free events. |
|
2485 */ |
|
2486 if (TM_EVENT_FREE != |
|
2487 aRun->mAllocations[allocLoop]->mEvents[eventLoop]. |
|
2488 mEventType) { |
|
2489 tmcallsite **expand = NULL; |
|
2490 |
|
2491 /* |
|
2492 ** expand the array. |
|
2493 */ |
|
2494 expand = |
|
2495 (tmcallsite **) realloc(*aArray, |
|
2496 sizeof(tmcallsite *) * |
|
2497 (retval + 1)); |
|
2498 if (NULL != expand) { |
|
2499 /* |
|
2500 ** Set the callsite in case of pointer move. |
|
2501 */ |
|
2502 *aArray = expand; |
|
2503 |
|
2504 /* |
|
2505 ** Assign the value. |
|
2506 */ |
|
2507 (*aArray)[retval] = |
|
2508 aRun->mAllocations[allocLoop]->mEvents[eventLoop]. |
|
2509 mCallsite; |
|
2510 retval++; |
|
2511 } |
|
2512 else { |
|
2513 REPORT_ERROR(__LINE__, realloc); |
|
2514 stopLoops = __LINE__; |
|
2515 } |
|
2516 } |
|
2517 } |
|
2518 } |
|
2519 } |
|
2520 |
|
2521 return retval; |
|
2522 } |
|
2523 |
|
2524 /* |
|
2525 ** getDataPRUint* |
|
2526 ** |
|
2527 ** Helper to avoid cut and paste code. |
|
2528 ** Failure to find aCheckFor does not mean failure. |
|
2529 ** In case of dups, specify an index on non "1" to get others. |
|
2530 ** Do not touch storage space unless a find is made. |
|
2531 ** Returns !0 on failure. |
|
2532 */ |
|
2533 int |
|
2534 getDataPRUint32Base(const FormData * aGetData, const char *aCheckFor, |
|
2535 int inIndex, void *aStoreResult, uint32_t aBits) |
|
2536 { |
|
2537 int retval = 0; |
|
2538 |
|
2539 if (NULL != aGetData && NULL != aCheckFor && 0 != inIndex |
|
2540 && NULL != aStoreResult) { |
|
2541 unsigned finder = 0; |
|
2542 |
|
2543 /* |
|
2544 ** Loop over the names, looking for an exact string match. |
|
2545 ** Skip over initial finds, decrementing inIndex, until "1". |
|
2546 */ |
|
2547 for (finder = 0; finder < aGetData->mNVCount; finder++) { |
|
2548 if (0 == strcmp(aCheckFor, aGetData->mNArray[finder])) { |
|
2549 inIndex--; |
|
2550 |
|
2551 if (0 == inIndex) { |
|
2552 int32_t scanRes = 0; |
|
2553 |
|
2554 if (64 == aBits) { |
|
2555 scanRes = |
|
2556 PR_sscanf(aGetData->mVArray[finder], "%llu", |
|
2557 aStoreResult); |
|
2558 } |
|
2559 else { |
|
2560 scanRes = |
|
2561 PR_sscanf(aGetData->mVArray[finder], "%u", |
|
2562 aStoreResult); |
|
2563 } |
|
2564 if (1 != scanRes) { |
|
2565 retval = __LINE__; |
|
2566 REPORT_ERROR(__LINE__, PR_sscanf); |
|
2567 } |
|
2568 break; |
|
2569 } |
|
2570 } |
|
2571 } |
|
2572 } |
|
2573 else { |
|
2574 retval = __LINE__; |
|
2575 REPORT_ERROR(__LINE__, getDataPRUint32Base); |
|
2576 } |
|
2577 |
|
2578 return retval; |
|
2579 } |
|
2580 |
|
2581 int |
|
2582 getDataPRUint32(const FormData * aGetData, const char *aCheckFor, int inIndex, |
|
2583 uint32_t * aStoreResult, uint32_t aConversion) |
|
2584 { |
|
2585 int retval = 0; |
|
2586 |
|
2587 retval = |
|
2588 getDataPRUint32Base(aGetData, aCheckFor, inIndex, aStoreResult, 32); |
|
2589 *aStoreResult *= aConversion; |
|
2590 |
|
2591 return retval; |
|
2592 } |
|
2593 |
|
2594 int |
|
2595 getDataPRUint64(const FormData * aGetData, const char *aCheckFor, int inIndex, |
|
2596 uint64_t * aStoreResult64, uint64_t aConversion64) |
|
2597 { |
|
2598 int retval = 0; |
|
2599 uint64_t value64 = 0; |
|
2600 |
|
2601 retval = getDataPRUint32Base(aGetData, aCheckFor, inIndex, &value64, 64); |
|
2602 *aStoreResult64 = value64 * aConversion64; |
|
2603 |
|
2604 return retval; |
|
2605 } |
|
2606 |
|
2607 /* |
|
2608 ** getDataString |
|
2609 ** |
|
2610 ** Pull out the string data, if specified. |
|
2611 ** In case of dups, specify an index on non "1" to get others. |
|
2612 ** Do not touch storage space unless a find is made. |
|
2613 ** Return !0 on failure. |
|
2614 */ |
|
2615 int |
|
2616 getDataString(const FormData * aGetData, const char *aCheckFor, int inIndex, |
|
2617 char *aStoreResult, int inStoreResultLength) |
|
2618 { |
|
2619 int retval = 0; |
|
2620 |
|
2621 if (NULL != aGetData && NULL != aCheckFor && 0 != inIndex |
|
2622 && NULL != aStoreResult && 0 != inStoreResultLength) { |
|
2623 unsigned finder = 0; |
|
2624 |
|
2625 /* |
|
2626 ** Loop over the names, looking for an exact string match. |
|
2627 ** Skip over initial finds, decrementing inIndex, until "1". |
|
2628 */ |
|
2629 for (finder = 0; finder < aGetData->mNVCount; finder++) { |
|
2630 if (0 == strcmp(aCheckFor, aGetData->mNArray[finder])) { |
|
2631 inIndex--; |
|
2632 |
|
2633 if (0 == inIndex) { |
|
2634 PR_snprintf(aStoreResult, inStoreResultLength, "%s", |
|
2635 aGetData->mVArray[finder]); |
|
2636 break; |
|
2637 } |
|
2638 } |
|
2639 } |
|
2640 } |
|
2641 else { |
|
2642 retval = __LINE__; |
|
2643 REPORT_ERROR(__LINE__, getDataPRUint32); |
|
2644 } |
|
2645 |
|
2646 return retval; |
|
2647 } |
|
2648 |
|
2649 /* |
|
2650 ** displayTopAllocations |
|
2651 ** |
|
2652 ** Present the top allocations. |
|
2653 ** The run must be passed in, and it must be pre-sorted. |
|
2654 ** |
|
2655 ** Returns !0 on failure. |
|
2656 */ |
|
2657 int |
|
2658 displayTopAllocations(STRequest * inRequest, STRun * aRun, |
|
2659 const char* id, |
|
2660 const char* caption, |
|
2661 int aWantCallsite) |
|
2662 { |
|
2663 int retval = 0; |
|
2664 |
|
2665 if (NULL != aRun) { |
|
2666 if (0 < aRun->mAllocationCount) { |
|
2667 uint32_t loop = 0; |
|
2668 STAllocation *current = NULL; |
|
2669 |
|
2670 static const char* const headers[] = { |
|
2671 "Rank", "Index", "Byte Size", "Lifespan (sec)", |
|
2672 "Weight", "Heap Op (sec)" |
|
2673 }; |
|
2674 |
|
2675 static const char* const headers_callsite[] = { |
|
2676 "Rank", "Index", "Byte Size", "Lifespan (sec)", |
|
2677 "Weight", "Heap Op (sec)", "Origin Callsite" |
|
2678 }; |
|
2679 |
|
2680 if (aWantCallsite) |
|
2681 htmlStartTable(inRequest, NULL, id, |
|
2682 caption, |
|
2683 headers_callsite, |
|
2684 sizeof(headers_callsite) / sizeof(headers_callsite[0])); |
|
2685 else |
|
2686 htmlStartTable(inRequest, NULL, id, caption, |
|
2687 headers, |
|
2688 sizeof(headers) / sizeof(headers[0])); |
|
2689 /* |
|
2690 ** Loop over the items, up to some limit or until the end. |
|
2691 */ |
|
2692 for (loop = 0; |
|
2693 loop < inRequest->mOptions.mListItemMax |
|
2694 && loop < aRun->mAllocationCount; loop++) { |
|
2695 current = aRun->mAllocations[loop]; |
|
2696 if (NULL != current) { |
|
2697 uint32_t lifespan = |
|
2698 current->mMaxTimeval - current->mMinTimeval; |
|
2699 uint32_t size = byteSize(&inRequest->mOptions, current); |
|
2700 uint32_t heapCost = current->mHeapRuntimeCost; |
|
2701 uint64_t weight64 = 0; |
|
2702 char buffer[32]; |
|
2703 |
|
2704 weight64 =(uint64_t)(size * lifespan); |
|
2705 |
|
2706 PR_fprintf(inRequest->mFD, "<tr>\n"); |
|
2707 |
|
2708 /* |
|
2709 ** Rank. |
|
2710 */ |
|
2711 PR_fprintf(inRequest->mFD, "<td align=right>%u</td>\n", |
|
2712 loop + 1); |
|
2713 |
|
2714 /* |
|
2715 ** Index. |
|
2716 */ |
|
2717 PR_snprintf(buffer, sizeof(buffer), "%u", |
|
2718 current->mRunIndex); |
|
2719 PR_fprintf(inRequest->mFD, "<td align=right>\n"); |
|
2720 htmlAllocationAnchor(inRequest, current, buffer); |
|
2721 PR_fprintf(inRequest->mFD, "</td>\n"); |
|
2722 |
|
2723 /* |
|
2724 ** Byte Size. |
|
2725 */ |
|
2726 PR_fprintf(inRequest->mFD, "<td align=right>%u</td>\n", |
|
2727 size); |
|
2728 |
|
2729 /* |
|
2730 ** Lifespan. |
|
2731 */ |
|
2732 PR_fprintf(inRequest->mFD, |
|
2733 "<td align=right>" ST_TIMEVAL_FORMAT "</td>\n", |
|
2734 ST_TIMEVAL_PRINTABLE(lifespan)); |
|
2735 |
|
2736 /* |
|
2737 ** Weight. |
|
2738 */ |
|
2739 PR_fprintf(inRequest->mFD, "<td align=right>%llu</td>\n", |
|
2740 weight64); |
|
2741 |
|
2742 /* |
|
2743 ** Heap operation cost. |
|
2744 */ |
|
2745 PR_fprintf(inRequest->mFD, |
|
2746 "<td align=right>" ST_MICROVAL_FORMAT |
|
2747 "</td>\n", ST_MICROVAL_PRINTABLE(heapCost)); |
|
2748 |
|
2749 if (0 != aWantCallsite) { |
|
2750 /* |
|
2751 ** Callsite. |
|
2752 */ |
|
2753 PR_fprintf(inRequest->mFD, "<td>"); |
|
2754 htmlCallsiteAnchor(inRequest, |
|
2755 current->mEvents[0].mCallsite, |
|
2756 NULL, 0); |
|
2757 PR_fprintf(inRequest->mFD, "</td>\n"); |
|
2758 } |
|
2759 |
|
2760 PR_fprintf(inRequest->mFD, "</tr>\n"); |
|
2761 } |
|
2762 } |
|
2763 |
|
2764 PR_fprintf(inRequest->mFD, "</tbody>\n</table></div>\n\n"); |
|
2765 } |
|
2766 } |
|
2767 else { |
|
2768 retval = __LINE__; |
|
2769 REPORT_ERROR(__LINE__, displayTopAllocations); |
|
2770 } |
|
2771 |
|
2772 return retval; |
|
2773 } |
|
2774 |
|
2775 /* |
|
2776 ** displayMemoryLeaks |
|
2777 ** |
|
2778 ** Present the top memory leaks. |
|
2779 ** The run must be passed in, and it must be pre-sorted. |
|
2780 ** |
|
2781 ** Returns !0 on failure. |
|
2782 */ |
|
2783 int |
|
2784 displayMemoryLeaks(STRequest * inRequest, STRun * aRun) |
|
2785 { |
|
2786 int retval = 0; |
|
2787 |
|
2788 if (NULL != aRun) { |
|
2789 uint32_t loop = 0; |
|
2790 uint32_t displayed = 0; |
|
2791 STAllocation *current = NULL; |
|
2792 |
|
2793 static const char * headers[] = { |
|
2794 "Rank", "Index", "Byte Size", "Lifespan (sec)", |
|
2795 "Weight", "Heap Op (sec)", "Origin Callsite" |
|
2796 }; |
|
2797 |
|
2798 htmlStartTable(inRequest, NULL, "memory-leaks", "Memory Leaks", headers, |
|
2799 sizeof(headers) / sizeof(headers[0])); |
|
2800 |
|
2801 /* |
|
2802 ** Loop over all of the items, or until we've displayed enough. |
|
2803 */ |
|
2804 for (loop = 0; |
|
2805 displayed < inRequest->mOptions.mListItemMax |
|
2806 && loop < aRun->mAllocationCount; loop++) { |
|
2807 current = aRun->mAllocations[loop]; |
|
2808 if (NULL != current && 0 != current->mEventCount) { |
|
2809 /* |
|
2810 ** In order to be a leak, the last event of its life must |
|
2811 ** NOT be a free operation. |
|
2812 ** |
|
2813 ** A free operation is just that, a free. |
|
2814 */ |
|
2815 if (TM_EVENT_FREE != |
|
2816 current->mEvents[current->mEventCount - 1].mEventType) { |
|
2817 uint32_t lifespan = |
|
2818 current->mMaxTimeval - current->mMinTimeval; |
|
2819 uint32_t size = byteSize(&inRequest->mOptions, current); |
|
2820 uint32_t heapCost = current->mHeapRuntimeCost; |
|
2821 uint64_t weight64 = 0; |
|
2822 char buffer[32]; |
|
2823 |
|
2824 weight64 =(uint64_t)(size * lifespan); |
|
2825 |
|
2826 /* |
|
2827 ** One more shown. |
|
2828 */ |
|
2829 displayed++; |
|
2830 |
|
2831 PR_fprintf(inRequest->mFD, "<tr>\n"); |
|
2832 |
|
2833 /* |
|
2834 ** Rank. |
|
2835 */ |
|
2836 PR_fprintf(inRequest->mFD, "<td align=right>%u</td>\n", |
|
2837 displayed); |
|
2838 |
|
2839 /* |
|
2840 ** Index. |
|
2841 */ |
|
2842 PR_snprintf(buffer, sizeof(buffer), "%u", |
|
2843 current->mRunIndex); |
|
2844 PR_fprintf(inRequest->mFD, "<td align=right>\n"); |
|
2845 htmlAllocationAnchor(inRequest, current, buffer); |
|
2846 PR_fprintf(inRequest->mFD, "</td>\n"); |
|
2847 |
|
2848 /* |
|
2849 ** Byte Size. |
|
2850 */ |
|
2851 PR_fprintf(inRequest->mFD, "<td align=right>%u</td>\n", |
|
2852 size); |
|
2853 |
|
2854 /* |
|
2855 ** Lifespan. |
|
2856 */ |
|
2857 PR_fprintf(inRequest->mFD, |
|
2858 "<td align=right>" ST_TIMEVAL_FORMAT "</td>\n", |
|
2859 ST_TIMEVAL_PRINTABLE(lifespan)); |
|
2860 |
|
2861 /* |
|
2862 ** Weight. |
|
2863 */ |
|
2864 PR_fprintf(inRequest->mFD, "<td align=right>%llu</td>\n", |
|
2865 weight64); |
|
2866 |
|
2867 /* |
|
2868 ** Heap Operation Seconds. |
|
2869 */ |
|
2870 PR_fprintf(inRequest->mFD, |
|
2871 "<td align=right>" ST_MICROVAL_FORMAT |
|
2872 "</td>\n", ST_MICROVAL_PRINTABLE(heapCost)); |
|
2873 |
|
2874 /* |
|
2875 ** Callsite. |
|
2876 */ |
|
2877 PR_fprintf(inRequest->mFD, "<td>"); |
|
2878 htmlCallsiteAnchor(inRequest, |
|
2879 current->mEvents[0].mCallsite, NULL, |
|
2880 0); |
|
2881 PR_fprintf(inRequest->mFD, "</td>\n"); |
|
2882 |
|
2883 PR_fprintf(inRequest->mFD, "</tr>\n"); |
|
2884 } |
|
2885 } |
|
2886 } |
|
2887 |
|
2888 PR_fprintf(inRequest->mFD, "</tbody></table></div>\n\n"); |
|
2889 } |
|
2890 else { |
|
2891 retval = __LINE__; |
|
2892 REPORT_ERROR(__LINE__, displayMemoryLeaks); |
|
2893 } |
|
2894 |
|
2895 return retval; |
|
2896 } |
|
2897 |
|
2898 /* |
|
2899 ** displayCallsites |
|
2900 ** |
|
2901 ** Display a table of callsites. |
|
2902 ** If the stamp is non zero, then must match that stamp. |
|
2903 ** If the stamp is zero, then must match the global sorted run stamp. |
|
2904 ** Return !0 on error. |
|
2905 */ |
|
2906 int |
|
2907 displayCallsites(STRequest * inRequest, tmcallsite * aCallsite, int aFollow, |
|
2908 uint32_t aStamp, |
|
2909 const char* id, |
|
2910 const char* caption, |
|
2911 int aRealNames) |
|
2912 { |
|
2913 int retval = 0; |
|
2914 |
|
2915 if (NULL != aCallsite && NULL != aCallsite->method) { |
|
2916 int headerDisplayed = 0; |
|
2917 STRun *run = NULL; |
|
2918 |
|
2919 /* |
|
2920 ** Correct the stamp if need be. |
|
2921 */ |
|
2922 if (0 == aStamp && NULL != inRequest->mContext->mSortedRun) { |
|
2923 aStamp = |
|
2924 inRequest->mContext->mSortedRun->mStats[inRequest->mContext-> |
|
2925 mIndex].mStamp; |
|
2926 } |
|
2927 |
|
2928 /* |
|
2929 ** Loop over the callsites looking for a stamp match. |
|
2930 ** A stamp guarantees there is something interesting to look at too. |
|
2931 ** If found, output it. |
|
2932 */ |
|
2933 while (NULL != aCallsite && NULL != aCallsite->method) { |
|
2934 run = CALLSITE_RUN(aCallsite); |
|
2935 if (NULL != run) { |
|
2936 if (aStamp == run->mStats[inRequest->mContext->mIndex].mStamp) { |
|
2937 /* |
|
2938 ** We got a header? |
|
2939 */ |
|
2940 if (0 == headerDisplayed) { |
|
2941 |
|
2942 static const char* const headers[] = { |
|
2943 "Callsite", |
|
2944 "<abbr title=\"Composite Size\">C. Size</abbr>", |
|
2945 "<abbr title=\"Composite Seconds\">C. Seconds</abbr>", |
|
2946 "<abbr title=\"Composite Weight\">C. Weight</abbr>", |
|
2947 "<abbr title=\"Heap Object Count\">H.O. Count</abbr>", |
|
2948 "<abbr title=\"Composite Heap Operation Seconds\">C.H. Operation (sec)</abbr>" |
|
2949 }; |
|
2950 headerDisplayed = __LINE__; |
|
2951 htmlStartTable(inRequest, NULL, id, caption, headers, |
|
2952 sizeof(headers)/sizeof(headers[0])); |
|
2953 } |
|
2954 |
|
2955 /* |
|
2956 ** Output the information. |
|
2957 */ |
|
2958 PR_fprintf(inRequest->mFD, "<tr>\n"); |
|
2959 |
|
2960 /* |
|
2961 ** Method name. |
|
2962 */ |
|
2963 PR_fprintf(inRequest->mFD, "<td>"); |
|
2964 htmlCallsiteAnchor(inRequest, aCallsite, NULL, |
|
2965 aRealNames); |
|
2966 PR_fprintf(inRequest->mFD, "</td>"); |
|
2967 |
|
2968 /* |
|
2969 ** Byte Size. |
|
2970 */ |
|
2971 PR_fprintf(inRequest->mFD, |
|
2972 "<td valign=top align=right>%u</td>\n", |
|
2973 run->mStats[inRequest->mContext->mIndex]. |
|
2974 mSize); |
|
2975 |
|
2976 /* |
|
2977 ** Seconds. |
|
2978 */ |
|
2979 PR_fprintf(inRequest->mFD, |
|
2980 "<td valign=top align=right>" ST_TIMEVAL_FORMAT |
|
2981 "</td>\n", |
|
2982 ST_TIMEVAL_PRINTABLE64(run-> |
|
2983 mStats[inRequest-> |
|
2984 mContext-> |
|
2985 mIndex]. |
|
2986 mTimeval64)); |
|
2987 |
|
2988 /* |
|
2989 ** Weight. |
|
2990 */ |
|
2991 PR_fprintf(inRequest->mFD, |
|
2992 "<td valign=top align=right>%llu</td>\n", |
|
2993 run->mStats[inRequest->mContext->mIndex]. |
|
2994 mWeight64); |
|
2995 |
|
2996 /* |
|
2997 ** Allocation object count. |
|
2998 */ |
|
2999 PR_fprintf(inRequest->mFD, |
|
3000 "<td valign=top align=right>%u</td>\n", |
|
3001 run->mStats[inRequest->mContext->mIndex]. |
|
3002 mCompositeCount); |
|
3003 |
|
3004 /* |
|
3005 ** Heap Operation Seconds. |
|
3006 */ |
|
3007 PR_fprintf(inRequest->mFD, |
|
3008 "<td valign=top align=right>" |
|
3009 ST_MICROVAL_FORMAT "</td>\n", |
|
3010 ST_MICROVAL_PRINTABLE(run-> |
|
3011 mStats[inRequest-> |
|
3012 mContext->mIndex]. |
|
3013 mHeapRuntimeCost)); |
|
3014 |
|
3015 PR_fprintf(inRequest->mFD, "</tr>\n"); |
|
3016 } |
|
3017 } |
|
3018 else { |
|
3019 retval = __LINE__; |
|
3020 REPORT_ERROR(__LINE__, displayCallsites); |
|
3021 break; |
|
3022 } |
|
3023 |
|
3024 /* |
|
3025 ** What do we follow? |
|
3026 */ |
|
3027 switch (aFollow) { |
|
3028 case ST_FOLLOW_SIBLINGS: |
|
3029 aCallsite = aCallsite->siblings; |
|
3030 break; |
|
3031 case ST_FOLLOW_PARENTS: |
|
3032 aCallsite = aCallsite->parent; |
|
3033 break; |
|
3034 default: |
|
3035 aCallsite = NULL; |
|
3036 retval = __LINE__; |
|
3037 REPORT_ERROR(__LINE__, displayCallsites); |
|
3038 break; |
|
3039 } |
|
3040 } |
|
3041 |
|
3042 /* |
|
3043 ** Terminate the table if we should. |
|
3044 */ |
|
3045 if (0 != headerDisplayed) { |
|
3046 PR_fprintf(inRequest->mFD, "</tbody></table></div>\n\n"); |
|
3047 } |
|
3048 } |
|
3049 else { |
|
3050 retval = __LINE__; |
|
3051 REPORT_ERROR(__LINE__, displayCallsites); |
|
3052 } |
|
3053 |
|
3054 return retval; |
|
3055 } |
|
3056 |
|
3057 /* |
|
3058 ** displayAllocationDetails |
|
3059 ** |
|
3060 ** Report what we know about the allocation. |
|
3061 ** |
|
3062 ** Returns !0 on error. |
|
3063 */ |
|
3064 int |
|
3065 displayAllocationDetails(STRequest * inRequest, STAllocation * aAllocation) |
|
3066 { |
|
3067 int retval = 0; |
|
3068 |
|
3069 if (NULL != aAllocation) { |
|
3070 uint32_t traverse = 0; |
|
3071 uint32_t bytesize = byteSize(&inRequest->mOptions, aAllocation); |
|
3072 uint32_t timeval = |
|
3073 aAllocation->mMaxTimeval - aAllocation->mMinTimeval; |
|
3074 uint32_t heapCost = aAllocation->mHeapRuntimeCost; |
|
3075 uint64_t weight64 = 0; |
|
3076 uint32_t cacheval = 0; |
|
3077 int displayRes = 0; |
|
3078 |
|
3079 weight64 = (uint64_t)(bytesize * timeval); |
|
3080 |
|
3081 PR_fprintf(inRequest->mFD, "<p>Allocation %u Details:</p>\n", |
|
3082 aAllocation->mRunIndex); |
|
3083 |
|
3084 PR_fprintf(inRequest->mFD, "<div id=\"allocation-details\"><table class=\"data summary\">\n"); |
|
3085 PR_fprintf(inRequest->mFD, |
|
3086 "<tr><td align=left>Final Size:</td><td align=right>%u</td></tr>\n", |
|
3087 bytesize); |
|
3088 PR_fprintf(inRequest->mFD, |
|
3089 "<tr><td align=left>Lifespan Seconds:</td><td align=right>" |
|
3090 ST_TIMEVAL_FORMAT "</td></tr>\n", |
|
3091 ST_TIMEVAL_PRINTABLE(timeval)); |
|
3092 PR_fprintf(inRequest->mFD, |
|
3093 "<tr><td align=left>Weight:</td><td align=right>%llu</td></tr>\n", |
|
3094 weight64); |
|
3095 PR_fprintf(inRequest->mFD, |
|
3096 "<tr><td align=left>Heap Operation Seconds:</td><td align=right>" |
|
3097 ST_MICROVAL_FORMAT "</td></tr>\n", |
|
3098 ST_MICROVAL_PRINTABLE(heapCost)); |
|
3099 PR_fprintf(inRequest->mFD, "</table></div>\n"); |
|
3100 |
|
3101 /* |
|
3102 ** The events. |
|
3103 */ |
|
3104 |
|
3105 { |
|
3106 static const char* const headers[] = { |
|
3107 "Operation", "Size", "Seconds", "" |
|
3108 }; |
|
3109 |
|
3110 char caption[100]; |
|
3111 PR_snprintf(caption, sizeof(caption), "%u Life Event(s)", |
|
3112 aAllocation->mEventCount); |
|
3113 htmlStartTable(inRequest, NULL, "allocation-details", caption, headers, |
|
3114 sizeof(headers) / sizeof(headers[0])); |
|
3115 } |
|
3116 |
|
3117 for (traverse = 0; |
|
3118 traverse < aAllocation->mEventCount |
|
3119 && traverse < inRequest->mOptions.mListItemMax; traverse++) { |
|
3120 PR_fprintf(inRequest->mFD, "<tr>\n"); |
|
3121 |
|
3122 /* |
|
3123 ** count. |
|
3124 */ |
|
3125 PR_fprintf(inRequest->mFD, |
|
3126 "<td valign=top align=right>%u.</td>\n", traverse + 1); |
|
3127 |
|
3128 /* |
|
3129 ** Operation. |
|
3130 */ |
|
3131 PR_fprintf(inRequest->mFD, "<td valign=top>"); |
|
3132 switch (aAllocation->mEvents[traverse].mEventType) { |
|
3133 case TM_EVENT_CALLOC: |
|
3134 PR_fprintf(inRequest->mFD, "calloc"); |
|
3135 break; |
|
3136 case TM_EVENT_FREE: |
|
3137 PR_fprintf(inRequest->mFD, "free"); |
|
3138 break; |
|
3139 case TM_EVENT_MALLOC: |
|
3140 PR_fprintf(inRequest->mFD, "malloc"); |
|
3141 break; |
|
3142 case TM_EVENT_REALLOC: |
|
3143 PR_fprintf(inRequest->mFD, "realloc"); |
|
3144 break; |
|
3145 default: |
|
3146 retval = __LINE__; |
|
3147 REPORT_ERROR(__LINE__, displayAllocationDetails); |
|
3148 break; |
|
3149 } |
|
3150 PR_fprintf(inRequest->mFD, "</td>"); |
|
3151 |
|
3152 /* |
|
3153 ** Size. |
|
3154 */ |
|
3155 PR_fprintf(inRequest->mFD, "<td valign=top align=right>%u</td>\n", |
|
3156 aAllocation->mEvents[traverse].mHeapSize); |
|
3157 |
|
3158 /* |
|
3159 ** Timeval. |
|
3160 */ |
|
3161 cacheval = |
|
3162 aAllocation->mEvents[traverse].mTimeval - globals.mMinTimeval; |
|
3163 PR_fprintf(inRequest->mFD, |
|
3164 "<td valign=top align=right>" ST_TIMEVAL_FORMAT |
|
3165 "</td>\n", ST_TIMEVAL_PRINTABLE(cacheval)); |
|
3166 |
|
3167 /* |
|
3168 ** Callsite backtrace. |
|
3169 ** Only relevant backtrace is for event 0 for now until |
|
3170 ** trace-malloc outputs proper callsites for all others. |
|
3171 */ |
|
3172 PR_fprintf(inRequest->mFD, "<td valign=top>\n"); |
|
3173 if (0 == traverse) { |
|
3174 displayRes = |
|
3175 displayCallsites(inRequest, |
|
3176 aAllocation->mEvents[traverse].mCallsite, |
|
3177 ST_FOLLOW_PARENTS, 0, "event-stack", "", __LINE__); |
|
3178 if (0 != displayRes) { |
|
3179 retval = __LINE__; |
|
3180 REPORT_ERROR(__LINE__, displayCallsite); |
|
3181 } |
|
3182 } |
|
3183 PR_fprintf(inRequest->mFD, "</td>\n"); |
|
3184 |
|
3185 PR_fprintf(inRequest->mFD, "</tr>\n"); |
|
3186 } |
|
3187 PR_fprintf(inRequest->mFD, "</table></div>\n"); |
|
3188 } |
|
3189 else { |
|
3190 retval = __LINE__; |
|
3191 REPORT_ERROR(__LINE__, displayAllocationDetails); |
|
3192 } |
|
3193 |
|
3194 return retval; |
|
3195 } |
|
3196 |
|
3197 /* |
|
3198 ** compareCallsites |
|
3199 ** |
|
3200 ** qsort callback. |
|
3201 ** Compare the callsites as specified by the options. |
|
3202 ** There must be NO equal callsites, unless they really are duplicates, |
|
3203 ** this is so that a duplicate detector loop can |
|
3204 ** simply skip sorted items until the callsite is different. |
|
3205 */ |
|
3206 int |
|
3207 compareCallsites(const void *aSite1, const void *aSite2, void *aContext) |
|
3208 { |
|
3209 int retval = 0; |
|
3210 STRequest *inRequest = (STRequest *) aContext; |
|
3211 |
|
3212 if (NULL != aSite1 && NULL != aSite2) { |
|
3213 tmcallsite *site1 = *((tmcallsite **) aSite1); |
|
3214 tmcallsite *site2 = *((tmcallsite **) aSite2); |
|
3215 |
|
3216 if (NULL != site1 && NULL != site2) { |
|
3217 STRun *run1 = CALLSITE_RUN(site1); |
|
3218 STRun *run2 = CALLSITE_RUN(site2); |
|
3219 |
|
3220 if (NULL != run1 && NULL != run2) { |
|
3221 STCallsiteStats *stats1 = |
|
3222 &(run1->mStats[inRequest->mContext->mIndex]); |
|
3223 STCallsiteStats *stats2 = |
|
3224 &(run2->mStats[inRequest->mContext->mIndex]); |
|
3225 |
|
3226 /* |
|
3227 ** Logic determined by pref/option. |
|
3228 */ |
|
3229 switch (inRequest->mOptions.mOrderBy) { |
|
3230 case ST_WEIGHT: |
|
3231 { |
|
3232 uint64_t weight164 = stats1->mWeight64; |
|
3233 uint64_t weight264 = stats2->mWeight64; |
|
3234 |
|
3235 if (weight164 < weight264) { |
|
3236 retval = __LINE__; |
|
3237 } |
|
3238 else if (weight164 > weight264) { |
|
3239 retval = -__LINE__; |
|
3240 } |
|
3241 } |
|
3242 break; |
|
3243 |
|
3244 case ST_SIZE: |
|
3245 { |
|
3246 uint32_t size1 = stats1->mSize; |
|
3247 uint32_t size2 = stats2->mSize; |
|
3248 |
|
3249 if (size1 < size2) { |
|
3250 retval = __LINE__; |
|
3251 } |
|
3252 else if (size1 > size2) { |
|
3253 retval = -__LINE__; |
|
3254 } |
|
3255 } |
|
3256 break; |
|
3257 |
|
3258 case ST_TIMEVAL: |
|
3259 { |
|
3260 uint64_t timeval164 = stats1->mTimeval64; |
|
3261 uint64_t timeval264 = stats2->mTimeval64; |
|
3262 |
|
3263 if (timeval164 < timeval264) { |
|
3264 retval = __LINE__; |
|
3265 } |
|
3266 else if (timeval164 > timeval264) { |
|
3267 retval = -__LINE__; |
|
3268 } |
|
3269 } |
|
3270 break; |
|
3271 |
|
3272 case ST_COUNT: |
|
3273 { |
|
3274 uint32_t count1 = stats1->mCompositeCount; |
|
3275 uint32_t count2 = stats2->mCompositeCount; |
|
3276 |
|
3277 if (count1 < count2) { |
|
3278 retval = __LINE__; |
|
3279 } |
|
3280 else if (count1 > count2) { |
|
3281 retval = -__LINE__; |
|
3282 } |
|
3283 } |
|
3284 break; |
|
3285 |
|
3286 case ST_HEAPCOST: |
|
3287 { |
|
3288 uint32_t cost1 = stats1->mHeapRuntimeCost; |
|
3289 uint32_t cost2 = stats2->mHeapRuntimeCost; |
|
3290 |
|
3291 if (cost1 < cost2) { |
|
3292 retval = __LINE__; |
|
3293 } |
|
3294 else if (cost1 > cost2) { |
|
3295 retval = -__LINE__; |
|
3296 } |
|
3297 } |
|
3298 break; |
|
3299 |
|
3300 default: |
|
3301 { |
|
3302 REPORT_ERROR(__LINE__, compareAllocations); |
|
3303 } |
|
3304 break; |
|
3305 } |
|
3306 |
|
3307 /* |
|
3308 ** If the return value is still zero, do a pointer compare. |
|
3309 ** This makes sure we return zero, only iff the same object. |
|
3310 */ |
|
3311 if (0 == retval) { |
|
3312 if (stats1 < stats2) { |
|
3313 retval = __LINE__; |
|
3314 } |
|
3315 else if (stats1 > stats2) { |
|
3316 retval = -__LINE__; |
|
3317 } |
|
3318 } |
|
3319 } |
|
3320 } |
|
3321 } |
|
3322 |
|
3323 return retval; |
|
3324 } |
|
3325 |
|
3326 /* |
|
3327 ** displayTopCallsites |
|
3328 ** |
|
3329 ** Given a list of callsites, sort it, and output skipping dups. |
|
3330 ** The passed in callsite array is side effected, as in that it will come |
|
3331 ** back sorted. This function will not release the array. |
|
3332 ** |
|
3333 ** Note: If the stamp passed in is non zero, then all callsites must match. |
|
3334 ** If the stamp is zero, all callsites must match global sorted run stamp. |
|
3335 ** |
|
3336 ** Returns !0 on error. |
|
3337 */ |
|
3338 int |
|
3339 displayTopCallsites(STRequest * inRequest, tmcallsite ** aCallsites, |
|
3340 uint32_t aCallsiteCount, uint32_t aStamp, |
|
3341 const char* id, |
|
3342 const char* caption, |
|
3343 int aRealName) |
|
3344 { |
|
3345 int retval = 0; |
|
3346 |
|
3347 if (NULL != aCallsites && 0 < aCallsiteCount) { |
|
3348 uint32_t traverse = 0; |
|
3349 STRun *run = NULL; |
|
3350 tmcallsite *site = NULL; |
|
3351 int headerDisplayed = 0; |
|
3352 uint32_t displayed = 0; |
|
3353 |
|
3354 /* |
|
3355 ** Fixup the stamp. |
|
3356 */ |
|
3357 if (0 == aStamp && NULL != inRequest->mContext->mSortedRun) { |
|
3358 aStamp = |
|
3359 inRequest->mContext->mSortedRun->mStats[inRequest->mContext-> |
|
3360 mIndex].mStamp; |
|
3361 } |
|
3362 |
|
3363 /* |
|
3364 ** Sort the things. |
|
3365 */ |
|
3366 NS_QuickSort(aCallsites, aCallsiteCount, sizeof(tmcallsite *), |
|
3367 compareCallsites, inRequest); |
|
3368 |
|
3369 /* |
|
3370 ** Time for output. |
|
3371 */ |
|
3372 for (traverse = 0; |
|
3373 traverse < aCallsiteCount |
|
3374 && inRequest->mOptions.mListItemMax > displayed; traverse++) { |
|
3375 site = aCallsites[traverse]; |
|
3376 run = CALLSITE_RUN(site); |
|
3377 |
|
3378 /* |
|
3379 ** Only if the same stamp.... |
|
3380 */ |
|
3381 if (aStamp == run->mStats[inRequest->mContext->mIndex].mStamp) { |
|
3382 /* |
|
3383 ** We got a header yet? |
|
3384 */ |
|
3385 if (0 == headerDisplayed) { |
|
3386 static const char* const headers[] = { |
|
3387 "Rank", |
|
3388 "Callsite", |
|
3389 "<abbr title=\"Composite Size\">Size</abbr>", |
|
3390 "<abbr title=\"Composite Seconds\">Seconds</abbr>", |
|
3391 "<abbr title=\"Composite Weight\">Weight</abbr>", |
|
3392 "<abbr title=\"Heap Object Count\">Object Count</abbr>", |
|
3393 "<abbr title=\"Composite Heap Operation Seconds\">C.H. Operation (sec)</abbr>" |
|
3394 }; |
|
3395 headerDisplayed = __LINE__; |
|
3396 |
|
3397 htmlStartTable(inRequest, NULL, id, caption, headers, |
|
3398 sizeof(headers) / sizeof(headers[0])); |
|
3399 } |
|
3400 |
|
3401 displayed++; |
|
3402 |
|
3403 PR_fprintf(inRequest->mFD, "<tr>\n"); |
|
3404 |
|
3405 /* |
|
3406 ** Rank. |
|
3407 */ |
|
3408 PR_fprintf(inRequest->mFD, |
|
3409 "<td align=right valign=top>%u</td>\n", displayed); |
|
3410 |
|
3411 /* |
|
3412 ** Method. |
|
3413 */ |
|
3414 PR_fprintf(inRequest->mFD, "<td>"); |
|
3415 htmlCallsiteAnchor(inRequest, site, NULL, aRealName); |
|
3416 PR_fprintf(inRequest->mFD, "</td>\n"); |
|
3417 |
|
3418 /* |
|
3419 ** Size. |
|
3420 */ |
|
3421 PR_fprintf(inRequest->mFD, |
|
3422 "<td align=right valign=top>%u</td>\n", |
|
3423 run->mStats[inRequest->mContext->mIndex].mSize); |
|
3424 |
|
3425 /* |
|
3426 ** Timeval. |
|
3427 */ |
|
3428 PR_fprintf(inRequest->mFD, |
|
3429 "<td align=right valign=top>" ST_TIMEVAL_FORMAT |
|
3430 "</td>\n", |
|
3431 ST_TIMEVAL_PRINTABLE64(run-> |
|
3432 mStats[inRequest->mContext-> |
|
3433 mIndex].mTimeval64)); |
|
3434 |
|
3435 /* |
|
3436 ** Weight. |
|
3437 */ |
|
3438 PR_fprintf(inRequest->mFD, |
|
3439 "<td align=right valign=top>%llu</td>\n", |
|
3440 run->mStats[inRequest->mContext->mIndex]. |
|
3441 mWeight64); |
|
3442 |
|
3443 /* |
|
3444 ** Allocation object count. |
|
3445 */ |
|
3446 PR_fprintf(inRequest->mFD, |
|
3447 "<td align=right valign=top>%u</td>\n", |
|
3448 run->mStats[inRequest->mContext->mIndex]. |
|
3449 mCompositeCount); |
|
3450 |
|
3451 /* |
|
3452 ** Heap operation seconds. |
|
3453 */ |
|
3454 PR_fprintf(inRequest->mFD, |
|
3455 "<td align=right valign=top>" ST_MICROVAL_FORMAT |
|
3456 "</td>\n", |
|
3457 ST_MICROVAL_PRINTABLE(run-> |
|
3458 mStats[inRequest->mContext-> |
|
3459 mIndex]. |
|
3460 mHeapRuntimeCost)); |
|
3461 |
|
3462 PR_fprintf(inRequest->mFD, "</tr>\n"); |
|
3463 |
|
3464 |
|
3465 if (inRequest->mOptions.mListItemMax > displayed) { |
|
3466 /* |
|
3467 ** Skip any dups. |
|
3468 */ |
|
3469 while (((traverse + 1) < aCallsiteCount) |
|
3470 && (site == aCallsites[traverse + 1])) { |
|
3471 traverse++; |
|
3472 } |
|
3473 } |
|
3474 } |
|
3475 } |
|
3476 |
|
3477 /* |
|
3478 ** We need to terminate anything? |
|
3479 */ |
|
3480 if (0 != headerDisplayed) { |
|
3481 PR_fprintf(inRequest->mFD, "</table></div>\n"); |
|
3482 } |
|
3483 } |
|
3484 else { |
|
3485 retval = __LINE__; |
|
3486 REPORT_ERROR(__LINE__, displayTopCallsites); |
|
3487 } |
|
3488 |
|
3489 return retval; |
|
3490 } |
|
3491 |
|
3492 /* |
|
3493 ** displayCallsiteDetails |
|
3494 ** |
|
3495 ** The callsite specific report. |
|
3496 ** Try to report what we know. |
|
3497 ** This one hits a little harder than the rest. |
|
3498 ** |
|
3499 ** Returns !0 on error. |
|
3500 */ |
|
3501 int |
|
3502 displayCallsiteDetails(STRequest * inRequest, tmcallsite * aCallsite) |
|
3503 { |
|
3504 int retval = 0; |
|
3505 |
|
3506 if (NULL != aCallsite && NULL != aCallsite->method) { |
|
3507 STRun *sortedRun = NULL; |
|
3508 STRun *thisRun = CALLSITE_RUN(aCallsite); |
|
3509 const char *sourceFile = NULL; |
|
3510 |
|
3511 sourceFile = resolveSourceFile(aCallsite->method); |
|
3512 |
|
3513 PR_fprintf(inRequest->mFD, "<div class=\"callsite-header\">\n"); |
|
3514 if (sourceFile) { |
|
3515 PR_fprintf(inRequest->mFD, "<b>%s</b>", |
|
3516 tmmethodnode_name(aCallsite->method)); |
|
3517 PR_fprintf(inRequest->mFD, |
|
3518 " [<a href=\"http://lxr.mozilla.org/mozilla/source/%s#%u\" class=\"lxr\" target=\"_st_lxr\">%s:%u</a>]", |
|
3519 aCallsite->method->sourcefile, |
|
3520 aCallsite->method->linenumber, sourceFile, |
|
3521 aCallsite->method->linenumber); |
|
3522 } |
|
3523 else { |
|
3524 PR_fprintf(inRequest->mFD, |
|
3525 "<p><b>%s</b>+%u(%u) Callsite Details:</p>\n", |
|
3526 tmmethodnode_name(aCallsite->method), |
|
3527 aCallsite->offset, (uint32_t) aCallsite->entry.key); |
|
3528 } |
|
3529 |
|
3530 PR_fprintf(inRequest->mFD, "</div>\n\n"); |
|
3531 PR_fprintf(inRequest->mFD, "<div id=\"callsite-details\"><table class=\"data summary\">\n"); |
|
3532 PR_fprintf(inRequest->mFD, |
|
3533 "<tr><td>Composite Byte Size:</td><td align=right>%u</td></tr>\n", |
|
3534 thisRun->mStats[inRequest->mContext->mIndex].mSize); |
|
3535 PR_fprintf(inRequest->mFD, |
|
3536 "<tr><td>Composite Seconds:</td><td align=right>" |
|
3537 ST_TIMEVAL_FORMAT "</td></tr>\n", |
|
3538 ST_TIMEVAL_PRINTABLE64(thisRun-> |
|
3539 mStats[inRequest->mContext->mIndex]. |
|
3540 mTimeval64)); |
|
3541 PR_fprintf(inRequest->mFD, |
|
3542 "<tr><td>Composite Weight:</td><td align=right>%llu</td></tr>\n", |
|
3543 thisRun->mStats[inRequest->mContext->mIndex].mWeight64); |
|
3544 PR_fprintf(inRequest->mFD, |
|
3545 "<tr><td>Heap Object Count:</td><td align=right>%u</td></tr>\n", |
|
3546 thisRun->mStats[inRequest->mContext->mIndex]. |
|
3547 mCompositeCount); |
|
3548 PR_fprintf(inRequest->mFD, |
|
3549 "<tr><td>Heap Operation Seconds:</td><td align=right>" |
|
3550 ST_MICROVAL_FORMAT "</td></tr>\n", |
|
3551 ST_MICROVAL_PRINTABLE(thisRun-> |
|
3552 mStats[inRequest->mContext->mIndex]. |
|
3553 mHeapRuntimeCost)); |
|
3554 PR_fprintf(inRequest->mFD, "</table></div>\n\n"); |
|
3555 |
|
3556 /* |
|
3557 ** Kids (callsites we call): |
|
3558 */ |
|
3559 if (NULL != aCallsite->kids && NULL != aCallsite->kids->method) { |
|
3560 int displayRes = 0; |
|
3561 uint32_t siteCount = 0; |
|
3562 tmcallsite **sites = NULL; |
|
3563 |
|
3564 /* |
|
3565 ** Collect the kid sibling callsites. |
|
3566 ** Doing it this way sorts them for relevance. |
|
3567 */ |
|
3568 siteCount = |
|
3569 callsiteArrayFromCallsite(&sites, 0, aCallsite->kids, |
|
3570 ST_FOLLOW_SIBLINGS); |
|
3571 if (0 != siteCount && NULL != sites) { |
|
3572 /* |
|
3573 ** Got something to show. |
|
3574 */ |
|
3575 displayRes = |
|
3576 displayTopCallsites(inRequest, sites, siteCount, 0, |
|
3577 "callsites", |
|
3578 "Children Callsites", |
|
3579 __LINE__); |
|
3580 if (0 != displayRes) { |
|
3581 retval = __LINE__; |
|
3582 REPORT_ERROR(__LINE__, displayTopCallsites); |
|
3583 } |
|
3584 |
|
3585 /* |
|
3586 ** Done with array. |
|
3587 */ |
|
3588 free(sites); |
|
3589 sites = NULL; |
|
3590 } |
|
3591 } |
|
3592 |
|
3593 /* |
|
3594 ** Parents (those who call us): |
|
3595 */ |
|
3596 if (NULL != aCallsite->parent && NULL != aCallsite->parent->method) { |
|
3597 int displayRes = 0; |
|
3598 |
|
3599 displayRes = |
|
3600 displayCallsites(inRequest, aCallsite->parent, |
|
3601 ST_FOLLOW_PARENTS, 0, "caller-stack", "Caller stack", |
|
3602 __LINE__); |
|
3603 if (0 != displayRes) { |
|
3604 retval = __LINE__; |
|
3605 REPORT_ERROR(__LINE__, displayCallsites); |
|
3606 } |
|
3607 } |
|
3608 |
|
3609 /* |
|
3610 ** Allocations we did. |
|
3611 ** Simply harvest our own run. |
|
3612 */ |
|
3613 sortedRun = createRun(inRequest->mContext, 0); |
|
3614 if (NULL != sortedRun) { |
|
3615 int harvestRes = 0; |
|
3616 |
|
3617 harvestRes = |
|
3618 harvestRun(CALLSITE_RUN(aCallsite), sortedRun, |
|
3619 &inRequest->mOptions, inRequest->mContext); |
|
3620 if (0 == harvestRes) { |
|
3621 if (0 != sortedRun->mAllocationCount) { |
|
3622 int sortRes = 0; |
|
3623 |
|
3624 sortRes = sortRun(&inRequest->mOptions, sortedRun); |
|
3625 if (0 == sortRes) { |
|
3626 int displayRes = 0; |
|
3627 |
|
3628 displayRes = |
|
3629 displayTopAllocations(inRequest, sortedRun, |
|
3630 "allocations", |
|
3631 "Allocations", |
|
3632 0); |
|
3633 if (0 != displayRes) { |
|
3634 retval = __LINE__; |
|
3635 REPORT_ERROR(__LINE__, displayTopAllocations); |
|
3636 } |
|
3637 } |
|
3638 else { |
|
3639 retval = __LINE__; |
|
3640 REPORT_ERROR(__LINE__, sortRun); |
|
3641 } |
|
3642 } |
|
3643 } |
|
3644 else { |
|
3645 retval = __LINE__; |
|
3646 REPORT_ERROR(__LINE__, harvestRun); |
|
3647 } |
|
3648 |
|
3649 /* |
|
3650 ** Done with the run. |
|
3651 */ |
|
3652 freeRun(sortedRun); |
|
3653 sortedRun = NULL; |
|
3654 } |
|
3655 else { |
|
3656 retval = __LINE__; |
|
3657 REPORT_ERROR(__LINE__, createRun); |
|
3658 } |
|
3659 } |
|
3660 else { |
|
3661 retval = __LINE__; |
|
3662 REPORT_ERROR(__LINE__, displayCallsiteDetails); |
|
3663 } |
|
3664 |
|
3665 return retval; |
|
3666 } |
|
3667 |
|
3668 #if ST_WANT_GRAPHS |
|
3669 /* |
|
3670 ** graphFootprint |
|
3671 ** |
|
3672 ** Output a PNG graph of the memory usage of the run. |
|
3673 ** |
|
3674 ** Draw the graph within these boundaries. |
|
3675 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN |
|
3676 ** |
|
3677 ** Returns !0 on failure. |
|
3678 */ |
|
3679 int |
|
3680 graphFootprint(STRequest * inRequest, STRun * aRun) |
|
3681 { |
|
3682 int retval = 0; |
|
3683 |
|
3684 if (NULL != aRun) { |
|
3685 uint32_t *YData = NULL; |
|
3686 uint32_t YDataArray[STGD_SPACE_X]; |
|
3687 uint32_t traverse = 0; |
|
3688 uint32_t timeval = 0; |
|
3689 uint32_t loop = 0; |
|
3690 PRBool underLock = PR_FALSE; |
|
3691 |
|
3692 /* |
|
3693 ** Decide if this is custom or we should use the cache. |
|
3694 */ |
|
3695 if (aRun == inRequest->mContext->mSortedRun) { |
|
3696 YData = inRequest->mContext->mFootprintYData; |
|
3697 underLock = PR_TRUE; |
|
3698 } |
|
3699 else { |
|
3700 YData = YDataArray; |
|
3701 } |
|
3702 |
|
3703 /* |
|
3704 ** Protect the shared data so that only one client has access to it |
|
3705 ** at any given time. |
|
3706 */ |
|
3707 if (PR_FALSE != underLock) { |
|
3708 PR_Lock(inRequest->mContext->mImageLock); |
|
3709 } |
|
3710 |
|
3711 /* |
|
3712 ** Only do the computations if we aren't cached already. |
|
3713 */ |
|
3714 if (YData != inRequest->mContext->mFootprintYData |
|
3715 || PR_FALSE == inRequest->mContext->mFootprintCached) { |
|
3716 memset(YData, 0, sizeof(uint32_t) * STGD_SPACE_X); |
|
3717 |
|
3718 /* |
|
3719 ** Initialize our Y data. |
|
3720 ** Pretty brutal loop here.... |
|
3721 */ |
|
3722 for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X; |
|
3723 traverse++) { |
|
3724 /* |
|
3725 ** Compute what timeval this Y data lands in. |
|
3726 */ |
|
3727 timeval = |
|
3728 ((traverse * |
|
3729 (globals.mMaxTimeval - |
|
3730 globals.mMinTimeval)) / STGD_SPACE_X) + |
|
3731 globals.mMinTimeval; |
|
3732 |
|
3733 /* |
|
3734 ** Loop over the run. |
|
3735 ** Should an allocation contain said Timeval, we're good. |
|
3736 */ |
|
3737 for (loop = 0; loop < aRun->mAllocationCount; loop++) { |
|
3738 if (timeval >= aRun->mAllocations[loop]->mMinTimeval |
|
3739 && timeval <= aRun->mAllocations[loop]->mMaxTimeval) { |
|
3740 YData[traverse] += |
|
3741 byteSize(&inRequest->mOptions, |
|
3742 aRun->mAllocations[loop]); |
|
3743 } |
|
3744 } |
|
3745 } |
|
3746 |
|
3747 /* |
|
3748 ** Did we cache this? |
|
3749 */ |
|
3750 if (YData == inRequest->mContext->mFootprintYData) { |
|
3751 inRequest->mContext->mFootprintCached = PR_TRUE; |
|
3752 } |
|
3753 } |
|
3754 |
|
3755 /* |
|
3756 ** Done with the lock. |
|
3757 */ |
|
3758 if (PR_FALSE != underLock) { |
|
3759 PR_Unlock(inRequest->mContext->mImageLock); |
|
3760 } |
|
3761 |
|
3762 if (0 == retval) { |
|
3763 uint32_t minMemory = (uint32_t) - 1; |
|
3764 uint32_t maxMemory = 0; |
|
3765 int transparent = 0; |
|
3766 gdImagePtr graph = NULL; |
|
3767 |
|
3768 /* |
|
3769 ** Go through and find the minimum and maximum sizes. |
|
3770 */ |
|
3771 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { |
|
3772 if (YData[traverse] < minMemory) { |
|
3773 minMemory = YData[traverse]; |
|
3774 } |
|
3775 if (YData[traverse] > maxMemory) { |
|
3776 maxMemory = YData[traverse]; |
|
3777 } |
|
3778 } |
|
3779 |
|
3780 /* |
|
3781 ** We can now draw the graph. |
|
3782 */ |
|
3783 graph = createGraph(&transparent); |
|
3784 if (NULL != graph) { |
|
3785 gdSink theSink; |
|
3786 int red = 0; |
|
3787 int x1 = 0; |
|
3788 int y1 = 0; |
|
3789 int x2 = 0; |
|
3790 int y2 = 0; |
|
3791 uint32_t percents[11] = |
|
3792 { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; |
|
3793 char *timevals[11]; |
|
3794 char *bytes[11]; |
|
3795 char timevalSpace[11][32]; |
|
3796 char byteSpace[11][32]; |
|
3797 int legendColors[1]; |
|
3798 const char *legends[1] = { "Memory in Use" }; |
|
3799 uint32_t cached = 0; |
|
3800 |
|
3801 /* |
|
3802 ** Figure out what the labels will say. |
|
3803 */ |
|
3804 for (traverse = 0; traverse < 11; traverse++) { |
|
3805 timevals[traverse] = timevalSpace[traverse]; |
|
3806 bytes[traverse] = byteSpace[traverse]; |
|
3807 |
|
3808 cached = |
|
3809 ((globals.mMaxTimeval - |
|
3810 globals.mMinTimeval) * percents[traverse]) / 100; |
|
3811 PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT, |
|
3812 ST_TIMEVAL_PRINTABLE(cached)); |
|
3813 PR_snprintf(bytes[traverse], 32, "%u", |
|
3814 ((maxMemory - |
|
3815 minMemory) * percents[traverse]) / 100); |
|
3816 } |
|
3817 |
|
3818 red = gdImageColorAllocate(graph, 255, 0, 0); |
|
3819 legendColors[0] = red; |
|
3820 |
|
3821 drawGraph(graph, -1, "Memory Footprint Over Time", "Seconds", |
|
3822 "Bytes", 11, percents, (const char **) timevals, 11, |
|
3823 percents, (const char **) bytes, 1, legendColors, |
|
3824 legends); |
|
3825 |
|
3826 if (maxMemory != minMemory) { |
|
3827 int64_t in64 = 0; |
|
3828 int64_t ydata64 = 0; |
|
3829 int64_t spacey64 = 0; |
|
3830 int64_t mem64 = 0; |
|
3831 int32_t in32 = 0; |
|
3832 |
|
3833 /* |
|
3834 ** Go through our Y data and mark it up. |
|
3835 */ |
|
3836 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { |
|
3837 x1 = traverse + STGD_MARGIN; |
|
3838 y1 = STGD_HEIGHT - STGD_MARGIN; |
|
3839 |
|
3840 /* |
|
3841 ** Need to do this math in 64 bits. |
|
3842 */ |
|
3843 ydata64 = (int64_t)YData[traverse]; |
|
3844 spacey64 = (int64_t)STGD_SPACE_Y; |
|
3845 mem64 = (int64_t)(maxMemory - minMemory); |
|
3846 |
|
3847 in64 = ydata64 * spacey64; |
|
3848 in64 /= mem64; |
|
3849 in32 = int32_t(in64); |
|
3850 |
|
3851 x2 = x1; |
|
3852 y2 = y1 - in32; |
|
3853 |
|
3854 gdImageLine(graph, x1, y1, x2, y2, red); |
|
3855 } |
|
3856 } |
|
3857 |
|
3858 |
|
3859 theSink.context = inRequest->mFD; |
|
3860 theSink.sink = pngSink; |
|
3861 gdImagePngToSink(graph, &theSink); |
|
3862 |
|
3863 gdImageDestroy(graph); |
|
3864 } |
|
3865 else { |
|
3866 retval = __LINE__; |
|
3867 REPORT_ERROR(__LINE__, createGraph); |
|
3868 } |
|
3869 } |
|
3870 } |
|
3871 else { |
|
3872 retval = __LINE__; |
|
3873 REPORT_ERROR(__LINE__, graphFootprint); |
|
3874 } |
|
3875 |
|
3876 return retval; |
|
3877 } |
|
3878 #endif /* ST_WANT_GRAPHS */ |
|
3879 |
|
3880 #if ST_WANT_GRAPHS |
|
3881 /* |
|
3882 ** graphTimeval |
|
3883 ** |
|
3884 ** Output a PNG graph of when the memory is allocated. |
|
3885 ** |
|
3886 ** Draw the graph within these boundaries. |
|
3887 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN |
|
3888 ** |
|
3889 ** Returns !0 on failure. |
|
3890 */ |
|
3891 int |
|
3892 graphTimeval(STRequest * inRequest, STRun * aRun) |
|
3893 { |
|
3894 int retval = 0; |
|
3895 |
|
3896 if (NULL != aRun) { |
|
3897 uint32_t *YData = NULL; |
|
3898 uint32_t YDataArray[STGD_SPACE_X]; |
|
3899 uint32_t traverse = 0; |
|
3900 uint32_t timeval = globals.mMinTimeval; |
|
3901 uint32_t loop = 0; |
|
3902 PRBool underLock = PR_FALSE; |
|
3903 |
|
3904 /* |
|
3905 ** Decide if this is custom or we should use the global cache. |
|
3906 */ |
|
3907 if (aRun == inRequest->mContext->mSortedRun) { |
|
3908 YData = inRequest->mContext->mTimevalYData; |
|
3909 underLock = PR_TRUE; |
|
3910 } |
|
3911 else { |
|
3912 YData = YDataArray; |
|
3913 } |
|
3914 |
|
3915 /* |
|
3916 ** Protect the shared data so that only one client has access to it |
|
3917 ** at any given time. |
|
3918 */ |
|
3919 if (PR_FALSE != underLock) { |
|
3920 PR_Lock(inRequest->mContext->mImageLock); |
|
3921 } |
|
3922 |
|
3923 /* |
|
3924 ** Only do the computations if we aren't cached already. |
|
3925 */ |
|
3926 if (YData != inRequest->mContext->mTimevalYData |
|
3927 || PR_FALSE == inRequest->mContext->mTimevalCached) { |
|
3928 uint32_t prevTimeval = 0; |
|
3929 |
|
3930 memset(YData, 0, sizeof(uint32_t) * STGD_SPACE_X); |
|
3931 |
|
3932 /* |
|
3933 ** Initialize our Y data. |
|
3934 ** Pretty brutal loop here.... |
|
3935 */ |
|
3936 for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X; |
|
3937 traverse++) { |
|
3938 /* |
|
3939 ** Compute what timeval this Y data lands in. |
|
3940 */ |
|
3941 prevTimeval = timeval; |
|
3942 timeval = |
|
3943 ((traverse * |
|
3944 (globals.mMaxTimeval - |
|
3945 globals.mMinTimeval)) / STGD_SPACE_X) + |
|
3946 globals.mMinTimeval; |
|
3947 |
|
3948 /* |
|
3949 ** Loop over the run. |
|
3950 ** Should an allocation have been allocated between |
|
3951 ** prevTimeval and timeval.... |
|
3952 */ |
|
3953 for (loop = 0; loop < aRun->mAllocationCount; loop++) { |
|
3954 if (prevTimeval < aRun->mAllocations[loop]->mMinTimeval |
|
3955 && timeval >= aRun->mAllocations[loop]->mMinTimeval) { |
|
3956 YData[traverse] += |
|
3957 byteSize(&inRequest->mOptions, |
|
3958 aRun->mAllocations[loop]); |
|
3959 } |
|
3960 } |
|
3961 } |
|
3962 |
|
3963 /* |
|
3964 ** Did we cache this? |
|
3965 */ |
|
3966 if (YData == inRequest->mContext->mTimevalYData) { |
|
3967 inRequest->mContext->mTimevalCached = PR_TRUE; |
|
3968 } |
|
3969 } |
|
3970 |
|
3971 /* |
|
3972 ** Done with the lock. |
|
3973 */ |
|
3974 if (PR_FALSE != underLock) { |
|
3975 PR_Unlock(inRequest->mContext->mImageLock); |
|
3976 } |
|
3977 |
|
3978 if (0 == retval) { |
|
3979 uint32_t minMemory = (uint32_t) - 1; |
|
3980 uint32_t maxMemory = 0; |
|
3981 int transparent = 0; |
|
3982 gdImagePtr graph = NULL; |
|
3983 |
|
3984 /* |
|
3985 ** Go through and find the minimum and maximum sizes. |
|
3986 */ |
|
3987 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { |
|
3988 if (YData[traverse] < minMemory) { |
|
3989 minMemory = YData[traverse]; |
|
3990 } |
|
3991 if (YData[traverse] > maxMemory) { |
|
3992 maxMemory = YData[traverse]; |
|
3993 } |
|
3994 } |
|
3995 |
|
3996 /* |
|
3997 ** We can now draw the graph. |
|
3998 */ |
|
3999 graph = createGraph(&transparent); |
|
4000 if (NULL != graph) { |
|
4001 gdSink theSink; |
|
4002 int red = 0; |
|
4003 int x1 = 0; |
|
4004 int y1 = 0; |
|
4005 int x2 = 0; |
|
4006 int y2 = 0; |
|
4007 uint32_t percents[11] = |
|
4008 { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; |
|
4009 char *timevals[11]; |
|
4010 char *bytes[11]; |
|
4011 char timevalSpace[11][32]; |
|
4012 char byteSpace[11][32]; |
|
4013 int legendColors[1]; |
|
4014 const char *legends[1] = { "Memory Allocated" }; |
|
4015 uint32_t cached = 0; |
|
4016 |
|
4017 /* |
|
4018 ** Figure out what the labels will say. |
|
4019 */ |
|
4020 for (traverse = 0; traverse < 11; traverse++) { |
|
4021 timevals[traverse] = timevalSpace[traverse]; |
|
4022 bytes[traverse] = byteSpace[traverse]; |
|
4023 |
|
4024 cached = |
|
4025 ((globals.mMaxTimeval - |
|
4026 globals.mMinTimeval) * percents[traverse]) / 100; |
|
4027 PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT, |
|
4028 ST_TIMEVAL_PRINTABLE(cached)); |
|
4029 PR_snprintf(bytes[traverse], 32, "%u", |
|
4030 ((maxMemory - |
|
4031 minMemory) * percents[traverse]) / 100); |
|
4032 } |
|
4033 |
|
4034 red = gdImageColorAllocate(graph, 255, 0, 0); |
|
4035 legendColors[0] = red; |
|
4036 |
|
4037 drawGraph(graph, -1, "Allocation Times", "Seconds", "Bytes", |
|
4038 11, percents, (const char **) timevals, 11, |
|
4039 percents, (const char **) bytes, 1, legendColors, |
|
4040 legends); |
|
4041 |
|
4042 if (maxMemory != minMemory) { |
|
4043 int64_t in64 = 0; |
|
4044 int64_t ydata64 = 0; |
|
4045 int64_t spacey64 = 0; |
|
4046 int64_t mem64 = 0; |
|
4047 int32_t in32 = 0; |
|
4048 |
|
4049 /* |
|
4050 ** Go through our Y data and mark it up. |
|
4051 */ |
|
4052 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { |
|
4053 x1 = traverse + STGD_MARGIN; |
|
4054 y1 = STGD_HEIGHT - STGD_MARGIN; |
|
4055 |
|
4056 /* |
|
4057 ** Need to do this math in 64 bits. |
|
4058 */ |
|
4059 ydata64 = (int64_t)YData[traverse]; |
|
4060 spacey64 = (int64_t)STGD_SPACE_Y; |
|
4061 mem64 = (int64_t)(maxMemory - minMemory); |
|
4062 |
|
4063 in64 = ydata64 * spacey64; |
|
4064 in64 /= mem64; |
|
4065 in32 = int32_t(in64); |
|
4066 |
|
4067 x2 = x1; |
|
4068 y2 = y1 - in32; |
|
4069 |
|
4070 gdImageLine(graph, x1, y1, x2, y2, red); |
|
4071 } |
|
4072 } |
|
4073 |
|
4074 |
|
4075 theSink.context = inRequest->mFD; |
|
4076 theSink.sink = pngSink; |
|
4077 gdImagePngToSink(graph, &theSink); |
|
4078 |
|
4079 gdImageDestroy(graph); |
|
4080 } |
|
4081 else { |
|
4082 retval = __LINE__; |
|
4083 REPORT_ERROR(__LINE__, createGraph); |
|
4084 } |
|
4085 } |
|
4086 } |
|
4087 else { |
|
4088 retval = __LINE__; |
|
4089 REPORT_ERROR(__LINE__, graphTimeval); |
|
4090 } |
|
4091 |
|
4092 return retval; |
|
4093 } |
|
4094 #endif /* ST_WANT_GRAPHS */ |
|
4095 |
|
4096 #if ST_WANT_GRAPHS |
|
4097 /* |
|
4098 ** graphLifespan |
|
4099 ** |
|
4100 ** Output a PNG graph of how long memory lived. |
|
4101 ** |
|
4102 ** Draw the graph within these boundaries. |
|
4103 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN |
|
4104 ** |
|
4105 ** Returns !0 on failure. |
|
4106 */ |
|
4107 int |
|
4108 graphLifespan(STRequest * inRequest, STRun * aRun) |
|
4109 { |
|
4110 int retval = 0; |
|
4111 |
|
4112 if (NULL != aRun) { |
|
4113 uint32_t *YData = NULL; |
|
4114 uint32_t YDataArray[STGD_SPACE_X]; |
|
4115 uint32_t traverse = 0; |
|
4116 uint32_t timeval = 0; |
|
4117 uint32_t loop = 0; |
|
4118 PRBool underLock = PR_FALSE; |
|
4119 |
|
4120 /* |
|
4121 ** Decide if this is custom or we should use the global cache. |
|
4122 */ |
|
4123 if (aRun == inRequest->mContext->mSortedRun) { |
|
4124 YData = inRequest->mContext->mLifespanYData; |
|
4125 underLock = PR_TRUE; |
|
4126 } |
|
4127 else { |
|
4128 YData = YDataArray; |
|
4129 } |
|
4130 |
|
4131 /* |
|
4132 ** Protect the shared data so that only one client has access to it |
|
4133 ** at any given time. |
|
4134 */ |
|
4135 if (PR_FALSE != underLock) { |
|
4136 PR_Lock(inRequest->mContext->mImageLock); |
|
4137 } |
|
4138 |
|
4139 /* |
|
4140 ** Only do the computations if we aren't cached already. |
|
4141 */ |
|
4142 if (YData != inRequest->mContext->mLifespanYData |
|
4143 || PR_FALSE == inRequest->mContext->mLifespanCached) { |
|
4144 uint32_t prevTimeval = 0; |
|
4145 uint32_t lifespan = 0; |
|
4146 |
|
4147 memset(YData, 0, sizeof(uint32_t) * STGD_SPACE_X); |
|
4148 |
|
4149 /* |
|
4150 ** Initialize our Y data. |
|
4151 ** Pretty brutal loop here.... |
|
4152 */ |
|
4153 for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X; |
|
4154 traverse++) { |
|
4155 /* |
|
4156 ** Compute what timeval this Y data lands in. |
|
4157 */ |
|
4158 prevTimeval = timeval; |
|
4159 timeval = |
|
4160 (traverse * (globals.mMaxTimeval - globals.mMinTimeval)) / |
|
4161 STGD_SPACE_X; |
|
4162 |
|
4163 /* |
|
4164 ** Loop over the run. |
|
4165 ** Should an allocation have lived between |
|
4166 ** prevTimeval and timeval.... |
|
4167 */ |
|
4168 for (loop = 0; loop < aRun->mAllocationCount; loop++) { |
|
4169 lifespan = |
|
4170 aRun->mAllocations[loop]->mMaxTimeval - |
|
4171 aRun->mAllocations[loop]->mMinTimeval; |
|
4172 |
|
4173 if (prevTimeval < lifespan && timeval >= lifespan) { |
|
4174 YData[traverse] += |
|
4175 byteSize(&inRequest->mOptions, |
|
4176 aRun->mAllocations[loop]); |
|
4177 } |
|
4178 } |
|
4179 } |
|
4180 |
|
4181 /* |
|
4182 ** Did we cache this? |
|
4183 */ |
|
4184 if (YData == inRequest->mContext->mLifespanYData) { |
|
4185 inRequest->mContext->mLifespanCached = PR_TRUE; |
|
4186 } |
|
4187 } |
|
4188 |
|
4189 /* |
|
4190 ** Done with the lock. |
|
4191 */ |
|
4192 if (PR_FALSE != underLock) { |
|
4193 PR_Unlock(inRequest->mContext->mImageLock); |
|
4194 } |
|
4195 |
|
4196 if (0 == retval) { |
|
4197 uint32_t minMemory = (uint32_t) - 1; |
|
4198 uint32_t maxMemory = 0; |
|
4199 int transparent = 0; |
|
4200 gdImagePtr graph = NULL; |
|
4201 |
|
4202 /* |
|
4203 ** Go through and find the minimum and maximum sizes. |
|
4204 */ |
|
4205 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { |
|
4206 if (YData[traverse] < minMemory) { |
|
4207 minMemory = YData[traverse]; |
|
4208 } |
|
4209 if (YData[traverse] > maxMemory) { |
|
4210 maxMemory = YData[traverse]; |
|
4211 } |
|
4212 } |
|
4213 |
|
4214 /* |
|
4215 ** We can now draw the graph. |
|
4216 */ |
|
4217 graph = createGraph(&transparent); |
|
4218 if (NULL != graph) { |
|
4219 gdSink theSink; |
|
4220 int red = 0; |
|
4221 int x1 = 0; |
|
4222 int y1 = 0; |
|
4223 int x2 = 0; |
|
4224 int y2 = 0; |
|
4225 uint32_t percents[11] = |
|
4226 { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; |
|
4227 char *timevals[11]; |
|
4228 char *bytes[11]; |
|
4229 char timevalSpace[11][32]; |
|
4230 char byteSpace[11][32]; |
|
4231 int legendColors[1]; |
|
4232 const char *legends[1] = { "Live Memory" }; |
|
4233 uint32_t cached = 0; |
|
4234 |
|
4235 /* |
|
4236 ** Figure out what the labels will say. |
|
4237 */ |
|
4238 for (traverse = 0; traverse < 11; traverse++) { |
|
4239 timevals[traverse] = timevalSpace[traverse]; |
|
4240 bytes[traverse] = byteSpace[traverse]; |
|
4241 |
|
4242 cached = |
|
4243 ((globals.mMaxTimeval - |
|
4244 globals.mMinTimeval) * percents[traverse]) / 100; |
|
4245 PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT, |
|
4246 ST_TIMEVAL_PRINTABLE(cached)); |
|
4247 PR_snprintf(bytes[traverse], 32, "%u", |
|
4248 ((maxMemory - |
|
4249 minMemory) * percents[traverse]) / 100); |
|
4250 } |
|
4251 |
|
4252 red = gdImageColorAllocate(graph, 255, 0, 0); |
|
4253 legendColors[0] = red; |
|
4254 |
|
4255 drawGraph(graph, -1, "Allocation Lifespans", "Lifespan", |
|
4256 "Bytes", 11, percents, (const char **) timevals, 11, |
|
4257 percents, (const char **) bytes, 1, legendColors, |
|
4258 legends); |
|
4259 |
|
4260 if (maxMemory != minMemory) { |
|
4261 int64_t in64 = 0; |
|
4262 int64_t ydata64 = 0; |
|
4263 int64_t spacey64 = 0; |
|
4264 int64_t mem64 = 0; |
|
4265 int32_t in32 = 0; |
|
4266 |
|
4267 /* |
|
4268 ** Go through our Y data and mark it up. |
|
4269 */ |
|
4270 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { |
|
4271 x1 = traverse + STGD_MARGIN; |
|
4272 y1 = STGD_HEIGHT - STGD_MARGIN; |
|
4273 |
|
4274 /* |
|
4275 ** Need to do this math in 64 bits. |
|
4276 */ |
|
4277 ydata64 = (int64_t)YData[traverse]; |
|
4278 spacey64 = (int64_t)STGD_SPACE_Y; |
|
4279 mem64 = (int64_t)(maxMemory - minMemory); |
|
4280 |
|
4281 in64 = ydata64 * spacey64; |
|
4282 in64 /= mem64; |
|
4283 in32 = int32_t(in64); |
|
4284 |
|
4285 x2 = x1; |
|
4286 y2 = y1 - in32; |
|
4287 |
|
4288 gdImageLine(graph, x1, y1, x2, y2, red); |
|
4289 } |
|
4290 } |
|
4291 |
|
4292 |
|
4293 theSink.context = inRequest->mFD; |
|
4294 theSink.sink = pngSink; |
|
4295 gdImagePngToSink(graph, &theSink); |
|
4296 |
|
4297 gdImageDestroy(graph); |
|
4298 } |
|
4299 else { |
|
4300 retval = __LINE__; |
|
4301 REPORT_ERROR(__LINE__, createGraph); |
|
4302 } |
|
4303 } |
|
4304 } |
|
4305 else { |
|
4306 retval = __LINE__; |
|
4307 REPORT_ERROR(__LINE__, graphLifespan); |
|
4308 } |
|
4309 |
|
4310 return retval; |
|
4311 } |
|
4312 #endif /* ST_WANT_GRAPHS */ |
|
4313 |
|
4314 #if ST_WANT_GRAPHS |
|
4315 /* |
|
4316 ** graphWeight |
|
4317 ** |
|
4318 ** Output a PNG graph of Allocations by Weight |
|
4319 ** |
|
4320 ** Draw the graph within these boundaries. |
|
4321 ** STGD_MARGIN,STGD_MARGIN,STGD_WIDTH-STGD_MARGIN,STGD_HEIGHT-STGD_MARGIN |
|
4322 ** |
|
4323 ** Returns !0 on failure. |
|
4324 */ |
|
4325 int |
|
4326 graphWeight(STRequest * inRequest, STRun * aRun) |
|
4327 { |
|
4328 int retval = 0; |
|
4329 |
|
4330 if (NULL != aRun) { |
|
4331 uint64_t *YData64 = NULL; |
|
4332 uint64_t YDataArray64[STGD_SPACE_X]; |
|
4333 uint32_t traverse = 0; |
|
4334 uint32_t timeval = globals.mMinTimeval; |
|
4335 uint32_t loop = 0; |
|
4336 PRBool underLock = PR_FALSE; |
|
4337 |
|
4338 /* |
|
4339 ** Decide if this is custom or we should use the global cache. |
|
4340 */ |
|
4341 if (aRun == inRequest->mContext->mSortedRun) { |
|
4342 YData64 = inRequest->mContext->mWeightYData64; |
|
4343 underLock = PR_TRUE; |
|
4344 } |
|
4345 else { |
|
4346 YData64 = YDataArray64; |
|
4347 } |
|
4348 |
|
4349 /* |
|
4350 ** Protect the shared data so that only one client has access to it |
|
4351 ** at any given time. |
|
4352 */ |
|
4353 if (PR_FALSE != underLock) { |
|
4354 PR_Lock(inRequest->mContext->mImageLock); |
|
4355 } |
|
4356 |
|
4357 /* |
|
4358 ** Only do the computations if we aren't cached already. |
|
4359 */ |
|
4360 if (YData64 != inRequest->mContext->mWeightYData64 |
|
4361 || PR_FALSE == inRequest->mContext->mWeightCached) { |
|
4362 uint32_t prevTimeval = 0; |
|
4363 |
|
4364 memset(YData64, 0, sizeof(uint64_t) * STGD_SPACE_X); |
|
4365 |
|
4366 /* |
|
4367 ** Initialize our Y data. |
|
4368 ** Pretty brutal loop here.... |
|
4369 */ |
|
4370 for (traverse = 0; 0 == retval && traverse < STGD_SPACE_X; |
|
4371 traverse++) { |
|
4372 /* |
|
4373 ** Compute what timeval this Y data lands in. |
|
4374 */ |
|
4375 prevTimeval = timeval; |
|
4376 timeval = |
|
4377 ((traverse * |
|
4378 (globals.mMaxTimeval - |
|
4379 globals.mMinTimeval)) / STGD_SPACE_X) + |
|
4380 globals.mMinTimeval; |
|
4381 |
|
4382 /* |
|
4383 ** Loop over the run. |
|
4384 ** Should an allocation have been allocated between |
|
4385 ** prevTimeval and timeval.... |
|
4386 */ |
|
4387 for (loop = 0; loop < aRun->mAllocationCount; loop++) { |
|
4388 if (prevTimeval < aRun->mAllocations[loop]->mMinTimeval |
|
4389 && timeval >= aRun->mAllocations[loop]->mMinTimeval) { |
|
4390 uint64_t size64 = 0; |
|
4391 uint64_t lifespan64 = 0; |
|
4392 uint64_t weight64 = 0; |
|
4393 |
|
4394 size64 = byteSize(&inRequest->mOptions, |
|
4395 aRun->mAllocations[loop]); |
|
4396 lifespan64 = aRun->mAllocations[loop]->mMaxTimeval - |
|
4397 aRun->mAllocations[loop]->mMinTimeval; |
|
4398 weight64 = size64 * lifespan64; |
|
4399 |
|
4400 YData64[traverse] += weight64; |
|
4401 } |
|
4402 } |
|
4403 } |
|
4404 |
|
4405 /* |
|
4406 ** Did we cache this? |
|
4407 */ |
|
4408 if (YData64 == inRequest->mContext->mWeightYData64) { |
|
4409 inRequest->mContext->mWeightCached = PR_TRUE; |
|
4410 } |
|
4411 } |
|
4412 |
|
4413 /* |
|
4414 ** Done with the lock. |
|
4415 */ |
|
4416 if (PR_FALSE != underLock) { |
|
4417 PR_Unlock(inRequest->mContext->mImageLock); |
|
4418 } |
|
4419 |
|
4420 if (0 == retval) { |
|
4421 uint64_t minWeight64 = (0xFFFFFFFFLL << 32) + 0xFFFFFFFFLL; |
|
4422 uint64_t maxWeight64 = 0; |
|
4423 int transparent = 0; |
|
4424 gdImagePtr graph = NULL; |
|
4425 |
|
4426 /* |
|
4427 ** Go through and find the minimum and maximum weights. |
|
4428 */ |
|
4429 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { |
|
4430 if (YData64[traverse] < minWeight64) { |
|
4431 minWeight64 = YData64[traverse]; |
|
4432 } |
|
4433 if (YData64[traverse] > maxWeight64) { |
|
4434 maxWeight64 = YData64[traverse]; |
|
4435 } |
|
4436 } |
|
4437 |
|
4438 /* |
|
4439 ** We can now draw the graph. |
|
4440 */ |
|
4441 graph = createGraph(&transparent); |
|
4442 if (NULL != graph) { |
|
4443 gdSink theSink; |
|
4444 int red = 0; |
|
4445 int x1 = 0; |
|
4446 int y1 = 0; |
|
4447 int x2 = 0; |
|
4448 int y2 = 0; |
|
4449 uint32_t percents[11] = |
|
4450 { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }; |
|
4451 char *timevals[11]; |
|
4452 char *bytes[11]; |
|
4453 char timevalSpace[11][32]; |
|
4454 char byteSpace[11][32]; |
|
4455 int legendColors[1]; |
|
4456 const char *legends[1] = { "Memory Weight" }; |
|
4457 uint64_t percent64 = 0; |
|
4458 uint64_t result64 = 0; |
|
4459 |
|
4460 uint32_t cached = 0; |
|
4461 uint64_t hundred64 = 100; |
|
4462 |
|
4463 /* |
|
4464 ** Figure out what the labels will say. |
|
4465 */ |
|
4466 for (traverse = 0; traverse < 11; traverse++) { |
|
4467 timevals[traverse] = timevalSpace[traverse]; |
|
4468 bytes[traverse] = byteSpace[traverse]; |
|
4469 |
|
4470 cached = |
|
4471 ((globals.mMaxTimeval - |
|
4472 globals.mMinTimeval) * percents[traverse]) / 100; |
|
4473 PR_snprintf(timevals[traverse], 32, ST_TIMEVAL_FORMAT, |
|
4474 ST_TIMEVAL_PRINTABLE(cached)); |
|
4475 |
|
4476 result64 = (maxWeight64 - minWeight64) * percents[traverse]; |
|
4477 result64 /= hundred64; |
|
4478 PR_snprintf(bytes[traverse], 32, "%llu", result64); |
|
4479 } |
|
4480 |
|
4481 red = gdImageColorAllocate(graph, 255, 0, 0); |
|
4482 legendColors[0] = red; |
|
4483 |
|
4484 drawGraph(graph, -1, "Allocation Weights", "Seconds", |
|
4485 "Weight", 11, percents, (const char **) timevals, |
|
4486 11, percents, (const char **) bytes, 1, |
|
4487 legendColors, legends); |
|
4488 |
|
4489 if (maxWeight64 != minWeight64) { |
|
4490 int64_t in64 = 0; |
|
4491 int64_t spacey64 = 0; |
|
4492 int64_t weight64 = 0; |
|
4493 int32_t in32 = 0; |
|
4494 |
|
4495 /* |
|
4496 ** Go through our Y data and mark it up. |
|
4497 */ |
|
4498 for (traverse = 0; traverse < STGD_SPACE_X; traverse++) { |
|
4499 x1 = traverse + STGD_MARGIN; |
|
4500 y1 = STGD_HEIGHT - STGD_MARGIN; |
|
4501 |
|
4502 /* |
|
4503 ** Need to do this math in 64 bits. |
|
4504 */ |
|
4505 spacey64 = (int64_t)STGD_SPACE_Y; |
|
4506 weight64 = maxWeight64 - minWeight64; |
|
4507 |
|
4508 in64 = YData64[traverse] * spacey64; |
|
4509 in64 /= weight64; |
|
4510 in32 = int32_t(in64); |
|
4511 |
|
4512 x2 = x1; |
|
4513 y2 = y1 - in32; |
|
4514 |
|
4515 gdImageLine(graph, x1, y1, x2, y2, red); |
|
4516 } |
|
4517 } |
|
4518 |
|
4519 |
|
4520 theSink.context = inRequest->mFD; |
|
4521 theSink.sink = pngSink; |
|
4522 gdImagePngToSink(graph, &theSink); |
|
4523 |
|
4524 gdImageDestroy(graph); |
|
4525 } |
|
4526 else { |
|
4527 retval = __LINE__; |
|
4528 REPORT_ERROR(__LINE__, createGraph); |
|
4529 } |
|
4530 } |
|
4531 } |
|
4532 else { |
|
4533 retval = __LINE__; |
|
4534 REPORT_ERROR(__LINE__, graphWeight); |
|
4535 } |
|
4536 |
|
4537 return retval; |
|
4538 } |
|
4539 #endif /* ST_WANT_GRAPHS */ |
|
4540 |
|
4541 #define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \ |
|
4542 { \ |
|
4543 uint32_t convert = (uint32_t)outOptions->m##option_name; \ |
|
4544 \ |
|
4545 getDataPRUint32(inFormData, #option_name, 1, &convert, 1); \ |
|
4546 outOptions->m##option_name = (PRBool)convert; \ |
|
4547 } |
|
4548 #define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \ |
|
4549 getDataString(inFormData, #option_name, 1, outOptions->m##option_name, sizeof(outOptions->m##option_name)); |
|
4550 #define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \ |
|
4551 { \ |
|
4552 uint32_t loop = 0; \ |
|
4553 uint32_t found = 0; \ |
|
4554 char buffer[ST_OPTION_STRING_MAX]; \ |
|
4555 \ |
|
4556 for(loop = 0; loop < array_size; loop++) \ |
|
4557 { \ |
|
4558 buffer[0] = '\0'; \ |
|
4559 getDataString(inFormData, #option_name, (loop + 1), buffer, sizeof(buffer)); \ |
|
4560 \ |
|
4561 if('\0' != buffer[0]) \ |
|
4562 { \ |
|
4563 PR_snprintf(outOptions->m##option_name[found], sizeof(outOptions->m##option_name[found]), "%s", buffer); \ |
|
4564 found++; \ |
|
4565 } \ |
|
4566 } \ |
|
4567 \ |
|
4568 for(; found < array_size; found++) \ |
|
4569 { \ |
|
4570 outOptions->m##option_name[found][0] = '\0'; \ |
|
4571 } \ |
|
4572 } |
|
4573 #define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */ |
|
4574 #define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \ |
|
4575 getDataPRUint32(inFormData, #option_name, 1, &outOptions->m##option_name, multiplier); |
|
4576 #define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \ |
|
4577 { \ |
|
4578 uint64_t mul64 = multiplier; \ |
|
4579 \ |
|
4580 getDataPRUint64(inFormData, #option_name, 1, &outOptions->m##option_name##64, mul64); \ |
|
4581 } |
|
4582 /* |
|
4583 ** fillOptions |
|
4584 ** |
|
4585 ** Given an appropriate hexcaped string, distill the option values |
|
4586 ** and fill the given STOption struct. |
|
4587 ** |
|
4588 ** Note that the options passed in are not touched UNLESS there is |
|
4589 ** a replacement found in the form data. |
|
4590 */ |
|
4591 void |
|
4592 fillOptions(STOptions * outOptions, const FormData * inFormData) |
|
4593 { |
|
4594 if (NULL != outOptions && NULL != inFormData) { |
|
4595 |
|
4596 #include "stoptions.h" |
|
4597 |
|
4598 /* |
|
4599 ** Special sanity check here for some options that need data validation. |
|
4600 */ |
|
4601 if (!outOptions->mCategoryName[0] |
|
4602 || !findCategoryNode(outOptions->mCategoryName, &globals)) { |
|
4603 PR_snprintf(outOptions->mCategoryName, |
|
4604 sizeof(outOptions->mCategoryName), "%s", |
|
4605 ST_ROOT_CATEGORY_NAME); |
|
4606 } |
|
4607 } |
|
4608 } |
|
4609 |
|
4610 |
|
4611 void |
|
4612 displayOptionString(STRequest * inRequest, |
|
4613 const char *option_name, |
|
4614 const char *option_genre, |
|
4615 const char *default_value, |
|
4616 const char *option_help, const char *value) |
|
4617 { |
|
4618 #if 0 |
|
4619 PR_fprintf(inRequest->mFD, "<input type=submit value=%s>\n", option_name); |
|
4620 #endif |
|
4621 PR_fprintf(inRequest->mFD, "<div class=option-box>\n"); |
|
4622 PR_fprintf(inRequest->mFD, "<p class=option-name>%s</p>\n", option_name); |
|
4623 PR_fprintf(inRequest->mFD, |
|
4624 "<input type=text name=\"%s\" value=\"%s\">\n", |
|
4625 option_name, value); |
|
4626 PR_fprintf(inRequest->mFD, |
|
4627 "<p class=option-default>Default value is \"%s\".</p>\n<p class=option-help>%s</p>\n", |
|
4628 default_value, option_help); |
|
4629 PR_fprintf(inRequest->mFD, "</div>\n"); |
|
4630 } |
|
4631 |
|
4632 static void |
|
4633 displayOptionStringArray(STRequest * inRequest, |
|
4634 const char *option_name, |
|
4635 const char *option_genre, |
|
4636 uint32_t array_size, |
|
4637 const char *option_help, const char values[5] |
|
4638 [ST_OPTION_STRING_MAX]) |
|
4639 { |
|
4640 /* values should not be a fixed length! */ |
|
4641 PR_ASSERT(array_size == 5); |
|
4642 #if 0 |
|
4643 PR_fprintf(inRequest->mFD, "<input type=submit value=%s>\n", option_name); |
|
4644 #endif |
|
4645 PR_fprintf(inRequest->mFD, "<div class=\"option-box\">\n"); |
|
4646 PR_fprintf(inRequest->mFD, "<p class=option-name>%s</p>\n", option_name); { |
|
4647 uint32_t loop = 0; |
|
4648 |
|
4649 for (loop = 0; loop < array_size; loop++) { |
|
4650 PR_fprintf(inRequest->mFD, |
|
4651 "<input type=text name=\"%s\" value=\"%s\"><br>\n", |
|
4652 option_name, values[loop]); |
|
4653 } |
|
4654 } |
|
4655 PR_fprintf(inRequest->mFD, |
|
4656 "<p class=option-default>Up to %u occurrences allowed.</p>\n<p class=option-help>%s</p>\n", |
|
4657 array_size, option_help); |
|
4658 PR_fprintf(inRequest->mFD, "</div>\n"); |
|
4659 } |
|
4660 |
|
4661 static void |
|
4662 displayOptionInt(STRequest * inRequest, |
|
4663 const char *option_name, |
|
4664 const char *option_genre, |
|
4665 uint32_t default_value, |
|
4666 uint32_t multiplier, const char *option_help, uint32_t value) |
|
4667 { |
|
4668 #if 0 |
|
4669 PR_fprintf(inRequest->mFD, "<input type=submit value=%s>\n", option_name); |
|
4670 #endif |
|
4671 PR_fprintf(inRequest->mFD, "<div class=\"option-box\">\n"); |
|
4672 PR_fprintf(inRequest->mFD, "<p class=option-name>%s</p>\n", option_name); |
|
4673 PR_fprintf(inRequest->mFD, |
|
4674 "<input type=text name=%s value=%u>\n", option_name, |
|
4675 value / multiplier); |
|
4676 PR_fprintf(inRequest->mFD, |
|
4677 "<p class=option-default>Default value is %u.</p>\n<p class=option-help>%s</p>\n", |
|
4678 default_value, option_help); |
|
4679 PR_fprintf(inRequest->mFD, "</div>\n"); |
|
4680 } |
|
4681 |
|
4682 static void |
|
4683 displayOptionInt64(STRequest * inRequest, |
|
4684 const char *option_name, |
|
4685 const char *option_genre, |
|
4686 uint64_t default_value, |
|
4687 uint64_t multiplier, |
|
4688 const char *option_help, uint64_t value) |
|
4689 { |
|
4690 #if 0 |
|
4691 PR_fprintf(inRequest->mFD, "<input type=submit value=%s>\n", option_name); |
|
4692 #endif |
|
4693 PR_fprintf(inRequest->mFD, "<div class=\"option-box\">\n"); |
|
4694 PR_fprintf(inRequest->mFD, "<p class=option-name>%s</p>\n", option_name); { |
|
4695 uint64_t def64 = default_value; |
|
4696 uint64_t mul64 = multiplier; |
|
4697 uint64_t div64; |
|
4698 |
|
4699 div64 = value / mul64; |
|
4700 PR_fprintf(inRequest->mFD, |
|
4701 "<input type=text name=%s value=%llu>\n", |
|
4702 option_name, div64); |
|
4703 PR_fprintf(inRequest->mFD, |
|
4704 "<p class=option-default>Default value is %llu.</p>\n<p class=option-help>%s</p>\n", |
|
4705 def64, option_help); |
|
4706 } |
|
4707 PR_fprintf(inRequest->mFD, "</div>\n"); |
|
4708 } |
|
4709 |
|
4710 /* |
|
4711 ** displaySettings |
|
4712 ** |
|
4713 ** Present the settings for change during execution. |
|
4714 */ |
|
4715 void |
|
4716 displaySettings(STRequest * inRequest) |
|
4717 { |
|
4718 int applyRes = 0; |
|
4719 |
|
4720 /* |
|
4721 ** We've got a form to create. |
|
4722 */ |
|
4723 PR_fprintf(inRequest->mFD, "<form method=get action=\"./index.html\">\n"); |
|
4724 /* |
|
4725 ** Respect newlines in help text. |
|
4726 */ |
|
4727 #if 0 |
|
4728 PR_fprintf(inRequest->mFD, "<pre>\n"); |
|
4729 #endif |
|
4730 #define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \ |
|
4731 displayOptionBool(option_name, option_genre, option_help) |
|
4732 #define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \ |
|
4733 displayOptionString(inRequest, #option_name, #option_genre, default_value, option_help, inRequest->mOptions.m##option_name); |
|
4734 #define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \ |
|
4735 displayOptionStringArray(inRequest, #option_name, #option_genre, array_size, option_help, inRequest->mOptions.m##option_name); |
|
4736 #define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */ |
|
4737 #define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \ |
|
4738 displayOptionInt(inRequest, #option_name, #option_genre, default_value, multiplier, option_help, inRequest->mOptions.m##option_name); |
|
4739 #define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \ |
|
4740 displayOptionInt64(inRequest, #option_name, #option_genre, default_value, multiplier, option_help, inRequest->mOptions.m##option_name##64); |
|
4741 #include "stoptions.h" |
|
4742 /* |
|
4743 ** Give a submit/reset button, obligatory. |
|
4744 ** Done respecting newlines in help text. |
|
4745 */ |
|
4746 PR_fprintf(inRequest->mFD, |
|
4747 "<input type=submit value=\"Save Options\"> <input type=reset>\n"); |
|
4748 #if 0 |
|
4749 PR_fprintf(inRequest->mFD, "</pre>\n"); |
|
4750 #endif |
|
4751 /* |
|
4752 ** Done with form. |
|
4753 */ |
|
4754 PR_fprintf(inRequest->mFD, "</form>\n"); |
|
4755 } |
|
4756 |
|
4757 int |
|
4758 handleLocalFile(STRequest * inRequest, const char *aFilename) |
|
4759 { |
|
4760 static const char *const local_files[] = { |
|
4761 "spacetrace.css", |
|
4762 }; |
|
4763 static const size_t local_file_count = |
|
4764 sizeof(local_files) / sizeof(local_files[0]); |
|
4765 size_t i; |
|
4766 |
|
4767 for (i = 0; i < local_file_count; i++) { |
|
4768 if (0 == strcmp(local_files[i], aFilename)) |
|
4769 return 1; |
|
4770 } |
|
4771 return 0; |
|
4772 } |
|
4773 |
|
4774 /* |
|
4775 ** displayFile |
|
4776 ** |
|
4777 ** reads a file from disk, and streams it to the request |
|
4778 */ |
|
4779 int |
|
4780 displayFile(STRequest * inRequest, const char *aFilename) |
|
4781 { |
|
4782 PRFileDesc *inFd; |
|
4783 const char *filepath = |
|
4784 PR_smprintf("res%c%s", PR_GetDirectorySeparator(), aFilename); |
|
4785 char buffer[2048]; |
|
4786 int32_t readRes; |
|
4787 |
|
4788 inFd = PR_Open(filepath, PR_RDONLY, PR_IRUSR); |
|
4789 if (!inFd) |
|
4790 return -1; |
|
4791 while ((readRes = PR_Read(inFd, buffer, sizeof(buffer))) > 0) { |
|
4792 PR_Write(inRequest->mFD, buffer, readRes); |
|
4793 } |
|
4794 if (readRes != 0) |
|
4795 return -1; |
|
4796 PR_Close(inFd); |
|
4797 return 0; |
|
4798 } |
|
4799 |
|
4800 /* |
|
4801 ** displayIndex |
|
4802 ** |
|
4803 ** Present a list of the reports you can drill down into. |
|
4804 ** Returns !0 on failure. |
|
4805 */ |
|
4806 int |
|
4807 displayIndex(STRequest * inRequest) |
|
4808 { |
|
4809 int retval = 0; |
|
4810 STOptions *options = &inRequest->mOptions; |
|
4811 |
|
4812 /* |
|
4813 ** Present reports in a list format. |
|
4814 */ |
|
4815 PR_fprintf(inRequest->mFD, "<ul>"); |
|
4816 PR_fprintf(inRequest->mFD, "\n<li>"); |
|
4817 htmlAnchor(inRequest, "root_callsites.html", "Root Callsites", |
|
4818 NULL, "mainmenu", options); |
|
4819 PR_fprintf(inRequest->mFD, "\n<li>"); |
|
4820 htmlAnchor(inRequest, "categories_summary.html", |
|
4821 "Categories Report", NULL, "mainmenu", options); |
|
4822 PR_fprintf(inRequest->mFD, "\n<li>"); |
|
4823 htmlAnchor(inRequest, "top_callsites.html", |
|
4824 "Top Callsites Report", NULL, "mainmenu", options); |
|
4825 PR_fprintf(inRequest->mFD, "\n<li>"); |
|
4826 htmlAnchor(inRequest, "top_allocations.html", |
|
4827 "Top Allocations Report", NULL, "mainmenu", options); |
|
4828 PR_fprintf(inRequest->mFD, "\n<li>"); |
|
4829 htmlAnchor(inRequest, "memory_leaks.html", |
|
4830 "Memory Leak Report", NULL, "mainmenu", options); |
|
4831 #if ST_WANT_GRAPHS |
|
4832 PR_fprintf(inRequest->mFD, "\n<li>Graphs"); |
|
4833 PR_fprintf(inRequest->mFD, "<ul>"); |
|
4834 PR_fprintf(inRequest->mFD, "\n<li>"); |
|
4835 htmlAnchor(inRequest, "footprint_graph.html", "Footprint", |
|
4836 NULL, "mainmenu graph", options); |
|
4837 PR_fprintf(inRequest->mFD, "\n<li>"); |
|
4838 htmlAnchor(inRequest, "lifespan_graph.html", |
|
4839 "Allocation Lifespans", NULL, "mainmenu graph", options); |
|
4840 PR_fprintf(inRequest->mFD, "\n<li>"); |
|
4841 htmlAnchor(inRequest, "times_graph.html", "Allocation Times", |
|
4842 NULL, "mainmenu graph", options); |
|
4843 PR_fprintf(inRequest->mFD, "\n<li>"); |
|
4844 htmlAnchor(inRequest, "weight_graph.html", |
|
4845 "Allocation Weights", NULL, "mainmenu graph", options); |
|
4846 PR_fprintf(inRequest->mFD, "\n</ul>\n"); |
|
4847 #endif /* ST_WANT_GRAPHS */ |
|
4848 PR_fprintf(inRequest->mFD, "\n</ul>\n"); |
|
4849 return retval; |
|
4850 } |
|
4851 |
|
4852 /* |
|
4853 ** initRequestOptions |
|
4854 ** |
|
4855 ** Given the request, set the options that are specific to the request. |
|
4856 ** These can generally be determined in the following manner: |
|
4857 ** Copy over global options. |
|
4858 ** If getData present, attempt to use options therein. |
|
4859 */ |
|
4860 void |
|
4861 initRequestOptions(STRequest * inRequest) |
|
4862 { |
|
4863 if (NULL != inRequest) { |
|
4864 /* |
|
4865 ** Copy of global options. |
|
4866 */ |
|
4867 memcpy(&inRequest->mOptions, &globals.mCommandLineOptions, |
|
4868 sizeof(globals.mCommandLineOptions)); |
|
4869 /* |
|
4870 ** Decide what will override global options if anything. |
|
4871 */ |
|
4872 if (NULL != inRequest->mGetData) { |
|
4873 fillOptions(&inRequest->mOptions, inRequest->mGetData); |
|
4874 } |
|
4875 } |
|
4876 } |
|
4877 |
|
4878 STContext * |
|
4879 contextLookup(STOptions * inOptions) |
|
4880 /* |
|
4881 ** Lookup a context that matches the options. |
|
4882 ** The lookup may block, especially if the context needs to be created. |
|
4883 ** Callers of this API must eventually call contextRelease with the |
|
4884 ** return value; failure to do so will cause this applications |
|
4885 ** to eventually not work as advertised. |
|
4886 ** |
|
4887 ** inOptions The options determine which context is relevant. |
|
4888 ** returns The fully completed context on success. |
|
4889 ** The context is read only in practice, so please do not |
|
4890 ** write to it or anything it points to. |
|
4891 ** NULL on failure. |
|
4892 */ |
|
4893 { |
|
4894 STContext *retval = NULL; |
|
4895 STContextCache *inCache = &globals.mContextCache; |
|
4896 |
|
4897 if (NULL != inOptions && NULL != inCache) { |
|
4898 uint32_t loop = 0; |
|
4899 STContext *categoryException = NULL; |
|
4900 PRBool newContext = PR_FALSE; |
|
4901 PRBool evictContext = PR_FALSE; |
|
4902 PRBool changeCategoryContext = PR_FALSE; |
|
4903 |
|
4904 /* |
|
4905 ** Own the context cache while we are in here. |
|
4906 */ |
|
4907 PR_Lock(inCache->mLock); |
|
4908 /* |
|
4909 ** Loop until successful. |
|
4910 ** Waiting on the condition variable makes sure we don't hog the |
|
4911 ** lock below. |
|
4912 */ |
|
4913 while (1) { |
|
4914 /* |
|
4915 ** Go over the cache items. |
|
4916 ** At this point we are looking for a cache hit, with multiple |
|
4917 ** readers. |
|
4918 */ |
|
4919 for (loop = 0; loop < inCache->mItemCount; loop++) { |
|
4920 /* |
|
4921 ** Must be in use. |
|
4922 */ |
|
4923 if (PR_FALSE != inCache->mItems[loop].mInUse) { |
|
4924 int delta[(STOptionGenre) MaxGenres]; |
|
4925 |
|
4926 /* |
|
4927 ** Compare the relevant options, figure out if different |
|
4928 ** in any genre that we care about. |
|
4929 */ |
|
4930 memset(&delta, 0, sizeof(delta)); |
|
4931 #define ST_WEB_OPTION_BOOL(option_name, option_genre, option_help) \ |
|
4932 if(inOptions->m##option_name != inCache->mItems[loop].mOptions.m##option_name) \ |
|
4933 { \ |
|
4934 delta[(STOptionGenre)option_genre]++; \ |
|
4935 } |
|
4936 #define ST_WEB_OPTION_STRING(option_name, option_genre, default_value, option_help) \ |
|
4937 if(0 != strcmp(inOptions->m##option_name, inCache->mItems[loop].mOptions.m##option_name)) \ |
|
4938 { \ |
|
4939 delta[(STOptionGenre)option_genre]++; \ |
|
4940 } |
|
4941 #define ST_WEB_OPTION_STRING_ARRAY(option_name, option_genre, array_size, option_help) \ |
|
4942 { \ |
|
4943 uint32_t macro_loop = 0; \ |
|
4944 \ |
|
4945 for(macro_loop = 0; macro_loop < array_size; macro_loop++) \ |
|
4946 { \ |
|
4947 if(0 != strcmp(inOptions->m##option_name[macro_loop], inCache->mItems[loop].mOptions.m##option_name[macro_loop])) \ |
|
4948 { \ |
|
4949 break; \ |
|
4950 } \ |
|
4951 } \ |
|
4952 \ |
|
4953 if(macro_loop != array_size) \ |
|
4954 { \ |
|
4955 delta[(STOptionGenre)option_genre]++; \ |
|
4956 } \ |
|
4957 } |
|
4958 #define ST_WEB_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) /* no implementation */ |
|
4959 #define ST_WEB_OPTION_UINT32(option_name, option_genre, default_value, multiplier, option_help) \ |
|
4960 if(inOptions->m##option_name != inCache->mItems[loop].mOptions.m##option_name) \ |
|
4961 { \ |
|
4962 delta[(STOptionGenre)option_genre]++; \ |
|
4963 } |
|
4964 #define ST_WEB_OPTION_UINT64(option_name, option_genre, default_value, multiplier, option_help) \ |
|
4965 if(inOptions->m##option_name##64 != inCache->mItems[loop].mOptions.m##option_name##64) \ |
|
4966 { \ |
|
4967 delta[(STOptionGenre)option_genre]++; \ |
|
4968 } |
|
4969 #include "stoptions.h" |
|
4970 /* |
|
4971 ** If there is no genre out of alignment, we accept this as the context. |
|
4972 */ |
|
4973 if (0 == delta[CategoryGenre] && |
|
4974 0 == delta[DataSortGenre] && |
|
4975 0 == delta[DataSetGenre] && 0 == delta[DataSizeGenre] |
|
4976 ) { |
|
4977 retval = &inCache->mItems[loop].mContext; |
|
4978 break; |
|
4979 } |
|
4980 |
|
4981 /* |
|
4982 ** A special exception to the rule here. |
|
4983 ** If all that is different is the category genre, and there |
|
4984 ** is no one looking at the context (zero ref count), |
|
4985 ** then there is some magic we can perform. |
|
4986 */ |
|
4987 if (NULL == retval && |
|
4988 0 == inCache->mItems[loop].mReferenceCount && |
|
4989 0 != delta[CategoryGenre] && |
|
4990 0 == delta[DataSortGenre] && |
|
4991 0 == delta[DataSetGenre] && 0 == delta[DataSizeGenre] |
|
4992 ) { |
|
4993 categoryException = &inCache->mItems[loop].mContext; |
|
4994 } |
|
4995 } |
|
4996 } |
|
4997 |
|
4998 /* |
|
4999 ** Pick up our category exception if relevant. |
|
5000 */ |
|
5001 if (NULL == retval && NULL != categoryException) { |
|
5002 retval = categoryException; |
|
5003 categoryException = NULL; |
|
5004 changeCategoryContext = PR_TRUE; |
|
5005 } |
|
5006 |
|
5007 /* |
|
5008 ** If we don't have a cache hit, then we need to check for an empty |
|
5009 ** spot to take over. |
|
5010 */ |
|
5011 if (NULL == retval) { |
|
5012 for (loop = 0; loop < inCache->mItemCount; loop++) { |
|
5013 /* |
|
5014 ** Must NOT be in use, then it will be the context. |
|
5015 */ |
|
5016 if (PR_FALSE == inCache->mItems[loop].mInUse) { |
|
5017 retval = &inCache->mItems[loop].mContext; |
|
5018 newContext = PR_TRUE; |
|
5019 break; |
|
5020 } |
|
5021 } |
|
5022 } |
|
5023 |
|
5024 /* |
|
5025 ** If we still don't have a return value, then we need to see if |
|
5026 ** there are any old items with zero ref counts that we |
|
5027 ** can take over. |
|
5028 */ |
|
5029 if (NULL == retval) { |
|
5030 for (loop = 0; loop < inCache->mItemCount; loop++) { |
|
5031 /* |
|
5032 ** Must be in use. |
|
5033 */ |
|
5034 if (PR_FALSE != inCache->mItems[loop].mInUse) { |
|
5035 /* |
|
5036 ** Must have a ref count of zero. |
|
5037 */ |
|
5038 if (0 == inCache->mItems[loop].mReferenceCount) { |
|
5039 /* |
|
5040 ** Must be older than any other we know of. |
|
5041 */ |
|
5042 if (NULL != retval) { |
|
5043 if (inCache->mItems[loop].mLastAccessed < |
|
5044 inCache->mItems[retval->mIndex]. |
|
5045 mLastAccessed) { |
|
5046 retval = &inCache->mItems[loop].mContext; |
|
5047 } |
|
5048 } |
|
5049 else { |
|
5050 retval = &inCache->mItems[loop].mContext; |
|
5051 } |
|
5052 } |
|
5053 } |
|
5054 } |
|
5055 |
|
5056 if (NULL != retval) { |
|
5057 evictContext = PR_TRUE; |
|
5058 } |
|
5059 } |
|
5060 |
|
5061 /* |
|
5062 ** If we still don't have a return value, then we can not avoid |
|
5063 ** waiting around until someone gives us the chance. |
|
5064 ** The chance, in specific, comes when a cache item reference |
|
5065 ** count returns to zero, upon which we can try to take |
|
5066 ** it over again. |
|
5067 */ |
|
5068 if (NULL == retval) { |
|
5069 /* |
|
5070 ** This has the side effect of release the context lock. |
|
5071 ** This is a good thing so that other clients can continue |
|
5072 ** to connect and hopefully have cache hits. |
|
5073 ** If they do not have cache hits, then we will end up |
|
5074 ** with a bunch of waiters here.... |
|
5075 */ |
|
5076 PR_WaitCondVar(inCache->mCacheMiss, PR_INTERVAL_NO_TIMEOUT); |
|
5077 } |
|
5078 |
|
5079 /* |
|
5080 ** If we have a return value, improve the reference count here. |
|
5081 */ |
|
5082 if (NULL != retval) { |
|
5083 /* |
|
5084 ** Decide if there are any changes to be made. |
|
5085 ** Do as little as possible, then fall through the context |
|
5086 ** cache lock to finish up. |
|
5087 ** This way, lengthy init operations will not block |
|
5088 ** other clients, only matches to this context. |
|
5089 */ |
|
5090 if (PR_FALSE != newContext || |
|
5091 PR_FALSE != evictContext || |
|
5092 PR_FALSE != changeCategoryContext) { |
|
5093 /* |
|
5094 ** Overwrite the prefs for this context. |
|
5095 ** They are changing. |
|
5096 */ |
|
5097 memcpy(&inCache->mItems[retval->mIndex].mOptions, |
|
5098 inOptions, |
|
5099 sizeof(inCache->mItems[retval->mIndex].mOptions)); |
|
5100 /* |
|
5101 ** As we are going to be changing the context, we need to write lock it. |
|
5102 ** This makes sure no readers are allowed while we are making our changes. |
|
5103 */ |
|
5104 PR_RWLock_Wlock(retval->mRWLock); |
|
5105 } |
|
5106 |
|
5107 /* |
|
5108 ** NOTE, ref count gets incremented here, inside content |
|
5109 ** cache lock so it can not be flushed once lock |
|
5110 ** released. |
|
5111 */ |
|
5112 inCache->mItems[retval->mIndex].mInUse = PR_TRUE; |
|
5113 inCache->mItems[retval->mIndex].mReferenceCount++; |
|
5114 /* |
|
5115 ** That's all folks. |
|
5116 */ |
|
5117 break; |
|
5118 } |
|
5119 |
|
5120 } /* while(1), try again */ |
|
5121 |
|
5122 /* |
|
5123 ** Done with context cache. |
|
5124 */ |
|
5125 PR_Unlock(inCache->mLock); |
|
5126 /* |
|
5127 ** Now that the context cached is free to continue accepting other |
|
5128 ** requests, we have a little more work to do. |
|
5129 */ |
|
5130 if (NULL != retval) { |
|
5131 PRBool unlock = PR_FALSE; |
|
5132 |
|
5133 /* |
|
5134 ** If evicting, we need to free off the old stuff. |
|
5135 */ |
|
5136 if (PR_FALSE != evictContext) { |
|
5137 unlock = PR_TRUE; |
|
5138 /* |
|
5139 ** We do not free the sorted run. |
|
5140 ** The category code takes care of this. |
|
5141 */ |
|
5142 retval->mSortedRun = NULL; |
|
5143 #if ST_WANT_GRAPHS |
|
5144 /* |
|
5145 ** There is no need to |
|
5146 ** PR_Lock(retval->mImageLock) |
|
5147 ** We are already under write lock for the entire structure. |
|
5148 */ |
|
5149 retval->mFootprintCached = PR_FALSE; |
|
5150 retval->mTimevalCached = PR_FALSE; |
|
5151 retval->mLifespanCached = PR_FALSE; |
|
5152 retval->mWeightCached = PR_FALSE; |
|
5153 #endif |
|
5154 } |
|
5155 |
|
5156 /* |
|
5157 ** If new or recently evicted, we need to fully init. |
|
5158 */ |
|
5159 if (PR_FALSE != newContext || PR_FALSE != evictContext) { |
|
5160 unlock = PR_TRUE; |
|
5161 retval->mSortedRun = |
|
5162 createRunFromGlobal(&inCache->mItems[retval->mIndex]. |
|
5163 mOptions, |
|
5164 &inCache->mItems[retval->mIndex]. |
|
5165 mContext); |
|
5166 } |
|
5167 |
|
5168 /* |
|
5169 ** If changing category, we need to do some sneaky stuff. |
|
5170 */ |
|
5171 if (PR_FALSE != changeCategoryContext) { |
|
5172 STCategoryNode *node = NULL; |
|
5173 |
|
5174 unlock = PR_TRUE; |
|
5175 /* |
|
5176 ** Just a category change. We don't need to harvest. Just find the |
|
5177 ** right node and set the cache.mSortedRun. We need to recompute |
|
5178 ** cost though. But that is cheap. |
|
5179 */ |
|
5180 node = |
|
5181 findCategoryNode(inCache->mItems[retval->mIndex].mOptions. |
|
5182 mCategoryName, &globals); |
|
5183 if (node) { |
|
5184 /* Recalculate cost of run */ |
|
5185 recalculateRunCost(&inCache->mItems[retval->mIndex]. |
|
5186 mOptions, retval, |
|
5187 node->runs[retval->mIndex]); |
|
5188 retval->mSortedRun = node->runs[retval->mIndex]; |
|
5189 } |
|
5190 |
|
5191 #if ST_WANT_GRAPHS |
|
5192 /* |
|
5193 ** There is no need to |
|
5194 ** PR_Lock(retval->mImageLock) |
|
5195 ** We are already under write lock for the entire structure. |
|
5196 */ |
|
5197 retval->mFootprintCached = PR_FALSE; |
|
5198 retval->mTimevalCached = PR_FALSE; |
|
5199 retval->mLifespanCached = PR_FALSE; |
|
5200 retval->mWeightCached = PR_FALSE; |
|
5201 #endif |
|
5202 } |
|
5203 |
|
5204 /* |
|
5205 ** Release the write lock if we took one to make changes. |
|
5206 */ |
|
5207 if (PR_FALSE != unlock) { |
|
5208 PR_RWLock_Unlock(retval->mRWLock); |
|
5209 } |
|
5210 |
|
5211 /* |
|
5212 ** Last thing possible, take a read lock on our return value. |
|
5213 ** This will cause us to block if the context is not fully |
|
5214 ** initialized in another thread holding the write lock. |
|
5215 */ |
|
5216 PR_RWLock_Rlock(retval->mRWLock); |
|
5217 } |
|
5218 } |
|
5219 |
|
5220 return retval; |
|
5221 } |
|
5222 |
|
5223 void |
|
5224 contextRelease(STContext * inContext) |
|
5225 /* |
|
5226 ** After a successful call to contextLookup, one should call this API when |
|
5227 ** done with the context. |
|
5228 ** This effectively removes the usage of the client on a cached item. |
|
5229 */ |
|
5230 { |
|
5231 STContextCache *inCache = &globals.mContextCache; |
|
5232 |
|
5233 if (NULL != inContext && NULL != inCache) { |
|
5234 /* |
|
5235 ** Own the context cache while in here. |
|
5236 */ |
|
5237 PR_Lock(inCache->mLock); |
|
5238 /* |
|
5239 ** Give up the read lock on the context. |
|
5240 */ |
|
5241 PR_RWLock_Unlock(inContext->mRWLock); |
|
5242 /* |
|
5243 ** Decrement the reference count on the context. |
|
5244 ** If it was the last reference, notify that a new item is |
|
5245 ** available for eviction. |
|
5246 ** A waiting thread will wake up and eat it. |
|
5247 ** Also set when it was last accessed so the oldest unused item |
|
5248 ** can be targeted for eviction. |
|
5249 */ |
|
5250 inCache->mItems[inContext->mIndex].mReferenceCount--; |
|
5251 if (0 == inCache->mItems[inContext->mIndex].mReferenceCount) { |
|
5252 PR_NotifyCondVar(inCache->mCacheMiss); |
|
5253 inCache->mItems[inContext->mIndex].mLastAccessed = |
|
5254 PR_IntervalNow(); |
|
5255 } |
|
5256 |
|
5257 /* |
|
5258 ** Done with context cache. |
|
5259 */ |
|
5260 PR_Unlock(inCache->mLock); |
|
5261 } |
|
5262 } |
|
5263 |
|
5264 |
|
5265 /* |
|
5266 ** handleRequest |
|
5267 ** |
|
5268 ** Based on what file they are asking for, perform some processing. |
|
5269 ** Output the results to aFD. |
|
5270 ** |
|
5271 ** Returns !0 on error. |
|
5272 */ |
|
5273 int |
|
5274 handleRequest(tmreader * aTMR, PRFileDesc * aFD, |
|
5275 const char *aFileName, const FormData * aGetData) |
|
5276 { |
|
5277 int retval = 0; |
|
5278 |
|
5279 if (NULL != aTMR && NULL != aFD && NULL != aFileName |
|
5280 && '\0' != *aFileName) { |
|
5281 STRequest request; |
|
5282 |
|
5283 /* |
|
5284 ** Init the request. |
|
5285 */ |
|
5286 memset(&request, 0, sizeof(request)); |
|
5287 request.mFD = aFD; |
|
5288 request.mGetFileName = aFileName; |
|
5289 request.mGetData = aGetData; |
|
5290 /* |
|
5291 ** Set local options for this request. |
|
5292 */ |
|
5293 initRequestOptions(&request); |
|
5294 /* |
|
5295 ** Get our cached context for this client. |
|
5296 ** Simply based on the options. |
|
5297 */ |
|
5298 request.mContext = contextLookup(&request.mOptions); |
|
5299 if (NULL != request.mContext) { |
|
5300 /* |
|
5301 ** Attempt to find the file of interest. |
|
5302 */ |
|
5303 if (handleLocalFile(&request, aFileName)) { |
|
5304 displayFile(&request, aFileName); |
|
5305 } |
|
5306 else if (0 == strcmp("index.html", aFileName)) { |
|
5307 int displayRes = 0; |
|
5308 |
|
5309 htmlHeader(&request, "SpaceTrace Index"); |
|
5310 displayRes = displayIndex(&request); |
|
5311 if (0 != displayRes) { |
|
5312 retval = __LINE__; |
|
5313 REPORT_ERROR(__LINE__, displayIndex); |
|
5314 } |
|
5315 |
|
5316 htmlFooter(&request); |
|
5317 } |
|
5318 else if (0 == strcmp("settings.html", aFileName) || |
|
5319 0 == strcmp("options.html", aFileName)) { |
|
5320 htmlHeader(&request, "SpaceTrace Options"); |
|
5321 displaySettings(&request); |
|
5322 htmlFooter(&request); |
|
5323 } |
|
5324 else if (0 == strcmp("top_allocations.html", aFileName)) { |
|
5325 int displayRes = 0; |
|
5326 |
|
5327 htmlHeader(&request, "SpaceTrace Top Allocations Report"); |
|
5328 displayRes = |
|
5329 displayTopAllocations(&request, |
|
5330 request.mContext->mSortedRun, |
|
5331 "top-allocations", |
|
5332 "SpaceTrace Top Allocations Report", |
|
5333 1); |
|
5334 if (0 != displayRes) { |
|
5335 retval = __LINE__; |
|
5336 REPORT_ERROR(__LINE__, displayTopAllocations); |
|
5337 } |
|
5338 |
|
5339 htmlFooter(&request); |
|
5340 } |
|
5341 else if (0 == strcmp("top_callsites.html", aFileName)) { |
|
5342 int displayRes = 0; |
|
5343 tmcallsite **array = NULL; |
|
5344 uint32_t arrayCount = 0; |
|
5345 |
|
5346 /* |
|
5347 ** Display header after we figure out if we are going to focus |
|
5348 ** on a category. |
|
5349 */ |
|
5350 htmlHeader(&request, "SpaceTrace Top Callsites Report"); |
|
5351 if (NULL != request.mContext->mSortedRun |
|
5352 && 0 < request.mContext->mSortedRun->mAllocationCount) { |
|
5353 arrayCount = |
|
5354 callsiteArrayFromRun(&array, 0, |
|
5355 request.mContext->mSortedRun); |
|
5356 if (0 != arrayCount && NULL != array) { |
|
5357 displayRes = |
|
5358 displayTopCallsites(&request, array, arrayCount, |
|
5359 0, |
|
5360 "top-callsites", |
|
5361 "Top Callsites Report", |
|
5362 0); |
|
5363 if (0 != displayRes) { |
|
5364 retval = __LINE__; |
|
5365 REPORT_ERROR(__LINE__, displayTopCallsites); |
|
5366 } |
|
5367 |
|
5368 /* |
|
5369 ** Done with the array. |
|
5370 */ |
|
5371 free(array); |
|
5372 array = NULL; |
|
5373 } |
|
5374 } |
|
5375 else { |
|
5376 retval = __LINE__; |
|
5377 REPORT_ERROR(__LINE__, handleRequest); |
|
5378 } |
|
5379 |
|
5380 htmlFooter(&request); |
|
5381 } |
|
5382 else if (0 == strcmp("memory_leaks.html", aFileName)) { |
|
5383 int displayRes = 0; |
|
5384 |
|
5385 htmlHeader(&request, "SpaceTrace Memory Leaks Report"); |
|
5386 displayRes = |
|
5387 displayMemoryLeaks(&request, |
|
5388 request.mContext->mSortedRun); |
|
5389 if (0 != displayRes) { |
|
5390 retval = __LINE__; |
|
5391 REPORT_ERROR(__LINE__, displayMemoryLeaks); |
|
5392 } |
|
5393 |
|
5394 htmlFooter(&request); |
|
5395 } |
|
5396 else if (0 == strncmp("allocation_", aFileName, 11)) { |
|
5397 int scanRes = 0; |
|
5398 uint32_t allocationIndex = 0; |
|
5399 |
|
5400 /* |
|
5401 ** Oh, what a hack.... |
|
5402 ** The index to the allocation structure in the global run |
|
5403 ** is in the filename. Better than the pointer value.... |
|
5404 */ |
|
5405 scanRes = PR_sscanf(aFileName + 11, "%u", &allocationIndex); |
|
5406 if (1 == scanRes |
|
5407 && globals.mRun.mAllocationCount > allocationIndex |
|
5408 && NULL != globals.mRun.mAllocations[allocationIndex]) { |
|
5409 STAllocation *allocation = |
|
5410 globals.mRun.mAllocations[allocationIndex]; |
|
5411 char buffer[128]; |
|
5412 int displayRes = 0; |
|
5413 |
|
5414 PR_snprintf(buffer, sizeof(buffer), |
|
5415 "SpaceTrace Allocation %u Details Report", |
|
5416 allocationIndex); |
|
5417 htmlHeader(&request, buffer); |
|
5418 displayRes = |
|
5419 displayAllocationDetails(&request, allocation); |
|
5420 if (0 != displayRes) { |
|
5421 retval = __LINE__; |
|
5422 REPORT_ERROR(__LINE__, displayAllocationDetails); |
|
5423 } |
|
5424 |
|
5425 htmlFooter(&request); |
|
5426 } |
|
5427 else { |
|
5428 htmlNotFound(&request); |
|
5429 } |
|
5430 } |
|
5431 else if (0 == strncmp("callsite_", aFileName, 9)) { |
|
5432 int scanRes = 0; |
|
5433 uint32_t callsiteSerial = 0; |
|
5434 tmcallsite *resolved = NULL; |
|
5435 |
|
5436 /* |
|
5437 ** Oh, what a hack.... |
|
5438 ** The serial(key) to the callsite structure in the hash table |
|
5439 ** is in the filename. Better than the pointer value.... |
|
5440 */ |
|
5441 scanRes = PR_sscanf(aFileName + 9, "%u", &callsiteSerial); |
|
5442 if (1 == scanRes && 0 != callsiteSerial |
|
5443 && NULL != (resolved = |
|
5444 tmreader_callsite(aTMR, callsiteSerial))) { |
|
5445 char buffer[128]; |
|
5446 int displayRes = 0; |
|
5447 |
|
5448 PR_snprintf(buffer, sizeof(buffer), |
|
5449 "SpaceTrace Callsite %u Details Report", |
|
5450 callsiteSerial); |
|
5451 htmlHeader(&request, buffer); |
|
5452 displayRes = displayCallsiteDetails(&request, resolved); |
|
5453 if (0 != displayRes) { |
|
5454 retval = __LINE__; |
|
5455 REPORT_ERROR(__LINE__, displayAllocationDetails); |
|
5456 } |
|
5457 |
|
5458 htmlFooter(&request); |
|
5459 } |
|
5460 else { |
|
5461 htmlNotFound(&request); |
|
5462 } |
|
5463 } |
|
5464 else if (0 == strcmp("root_callsites.html", aFileName)) { |
|
5465 int displayRes = 0; |
|
5466 |
|
5467 htmlHeader(&request, "SpaceTrace Root Callsites"); |
|
5468 displayRes = |
|
5469 displayCallsites(&request, aTMR->calltree_root.kids, |
|
5470 ST_FOLLOW_SIBLINGS, 0, |
|
5471 "callsites-root", |
|
5472 "SpaceTrace Root Callsites", |
|
5473 __LINE__); |
|
5474 if (0 != displayRes) { |
|
5475 retval = __LINE__; |
|
5476 REPORT_ERROR(__LINE__, displayCallsites); |
|
5477 } |
|
5478 |
|
5479 htmlFooter(&request); |
|
5480 } |
|
5481 #if ST_WANT_GRAPHS |
|
5482 else if (0 == strcmp("footprint_graph.html", aFileName)) { |
|
5483 int displayRes = 0; |
|
5484 |
|
5485 htmlHeader(&request, "SpaceTrace Memory Footprint Report"); |
|
5486 PR_fprintf(request.mFD, "<div align=center>\n"); |
|
5487 PR_fprintf(request.mFD, "<img src=\"./footprint.png"); |
|
5488 optionGetDataOut(request.mFD, &request.mOptions); |
|
5489 PR_fprintf(request.mFD, "\">\n"); |
|
5490 PR_fprintf(request.mFD, "</div>\n"); |
|
5491 htmlFooter(&request); |
|
5492 } |
|
5493 #endif /* ST_WANT_GRAPHS */ |
|
5494 #if ST_WANT_GRAPHS |
|
5495 else if (0 == strcmp("times_graph.html", aFileName)) { |
|
5496 int displayRes = 0; |
|
5497 |
|
5498 htmlHeader(&request, "SpaceTrace Allocation Times Report"); |
|
5499 PR_fprintf(request.mFD, "<div align=center>\n"); |
|
5500 PR_fprintf(request.mFD, "<img src=\"./times.png"); |
|
5501 optionGetDataOut(request.mFD, &request.mOptions); |
|
5502 PR_fprintf(request.mFD, "\">\n"); |
|
5503 PR_fprintf(request.mFD, "</div>\n"); |
|
5504 htmlFooter(&request); |
|
5505 } |
|
5506 #endif /* ST_WANT_GRAPHS */ |
|
5507 #if ST_WANT_GRAPHS |
|
5508 else if (0 == strcmp("lifespan_graph.html", aFileName)) { |
|
5509 int displayRes = 0; |
|
5510 |
|
5511 htmlHeader(&request, |
|
5512 "SpaceTrace Allocation Lifespans Report"); |
|
5513 PR_fprintf(request.mFD, "<div align=center>\n"); |
|
5514 PR_fprintf(request.mFD, "<img src=\"./lifespan.png"); |
|
5515 optionGetDataOut(request.mFD, &request.mOptions); |
|
5516 PR_fprintf(request.mFD, "\">\n"); |
|
5517 PR_fprintf(request.mFD, "</div>\n"); |
|
5518 htmlFooter(&request); |
|
5519 } |
|
5520 #endif /* ST_WANT_GRAPHS */ |
|
5521 #if ST_WANT_GRAPHS |
|
5522 else if (0 == strcmp("weight_graph.html", aFileName)) { |
|
5523 int displayRes = 0; |
|
5524 |
|
5525 htmlHeader(&request, "SpaceTrace Allocation Weights Report"); |
|
5526 PR_fprintf(request.mFD, "<div align=center>\n"); |
|
5527 PR_fprintf(request.mFD, "<img src=\"./weight.png"); |
|
5528 optionGetDataOut(request.mFD, &request.mOptions); |
|
5529 PR_fprintf(request.mFD, "\">\n"); |
|
5530 PR_fprintf(request.mFD, "</div>\n"); |
|
5531 htmlFooter(&request); |
|
5532 } |
|
5533 #endif /* ST_WANT_GRAPHS */ |
|
5534 #if ST_WANT_GRAPHS |
|
5535 else if (0 == strcmp("footprint.png", aFileName)) { |
|
5536 int graphRes = 0; |
|
5537 |
|
5538 graphRes = |
|
5539 graphFootprint(&request, request.mContext->mSortedRun); |
|
5540 if (0 != graphRes) { |
|
5541 retval = __LINE__; |
|
5542 REPORT_ERROR(__LINE__, graphFootprint); |
|
5543 } |
|
5544 } |
|
5545 #endif /* ST_WANT_GRAPHS */ |
|
5546 #if ST_WANT_GRAPHS |
|
5547 else if (0 == strcmp("times.png", aFileName)) { |
|
5548 int graphRes = 0; |
|
5549 |
|
5550 graphRes = |
|
5551 graphTimeval(&request, request.mContext->mSortedRun); |
|
5552 if (0 != graphRes) { |
|
5553 retval = __LINE__; |
|
5554 REPORT_ERROR(__LINE__, graphTimeval); |
|
5555 } |
|
5556 } |
|
5557 #endif /* ST_WANT_GRAPHS */ |
|
5558 #if ST_WANT_GRAPHS |
|
5559 else if (0 == strcmp("lifespan.png", aFileName)) { |
|
5560 int graphRes = 0; |
|
5561 |
|
5562 graphRes = |
|
5563 graphLifespan(&request, request.mContext->mSortedRun); |
|
5564 if (0 != graphRes) { |
|
5565 retval = __LINE__; |
|
5566 REPORT_ERROR(__LINE__, graphLifespan); |
|
5567 } |
|
5568 } |
|
5569 #endif /* ST_WANT_GRAPHS */ |
|
5570 #if ST_WANT_GRAPHS |
|
5571 else if (0 == strcmp("weight.png", aFileName)) { |
|
5572 int graphRes = 0; |
|
5573 |
|
5574 graphRes = |
|
5575 graphWeight(&request, request.mContext->mSortedRun); |
|
5576 if (0 != graphRes) { |
|
5577 retval = __LINE__; |
|
5578 REPORT_ERROR(__LINE__, graphWeight); |
|
5579 } |
|
5580 } |
|
5581 #endif /* ST_WANT_GRAPHS */ |
|
5582 else if (0 == strcmp("categories_summary.html", aFileName)) { |
|
5583 int displayRes = 0; |
|
5584 |
|
5585 htmlHeader(&request, "Category Report"); |
|
5586 displayRes = |
|
5587 displayCategoryReport(&request, &globals.mCategoryRoot, |
|
5588 1); |
|
5589 if (0 != displayRes) { |
|
5590 retval = __LINE__; |
|
5591 REPORT_ERROR(__LINE__, displayMemoryLeaks); |
|
5592 } |
|
5593 |
|
5594 htmlFooter(&request); |
|
5595 } |
|
5596 else { |
|
5597 htmlNotFound(&request); |
|
5598 } |
|
5599 |
|
5600 /* |
|
5601 ** Release the context we obtained earlier. |
|
5602 */ |
|
5603 contextRelease(request.mContext); |
|
5604 request.mContext = NULL; |
|
5605 } |
|
5606 else { |
|
5607 retval = __LINE__; |
|
5608 REPORT_ERROR(__LINE__, contextObtain); |
|
5609 } |
|
5610 } |
|
5611 else { |
|
5612 retval = __LINE__; |
|
5613 REPORT_ERROR(__LINE__, handleRequest); |
|
5614 } |
|
5615 |
|
5616 /* |
|
5617 ** Compact a little if you can after each request. |
|
5618 */ |
|
5619 heapCompact(); |
|
5620 return retval; |
|
5621 } |
|
5622 |
|
5623 /* |
|
5624 ** handleClient |
|
5625 ** |
|
5626 ** main() of the new client thread. |
|
5627 ** Read the fd for the request. |
|
5628 ** Output the results. |
|
5629 */ |
|
5630 void |
|
5631 handleClient(void *inArg) |
|
5632 { |
|
5633 PRFileDesc *aFD = NULL; |
|
5634 |
|
5635 aFD = (PRFileDesc *) inArg; |
|
5636 if (NULL != aFD) { |
|
5637 PRStatus closeRes = PR_SUCCESS; |
|
5638 char aBuffer[2048]; |
|
5639 int32_t readRes = 0; |
|
5640 |
|
5641 readRes = PR_Read(aFD, aBuffer, sizeof(aBuffer)); |
|
5642 if (0 <= readRes) { |
|
5643 const char *sanityCheck = "GET /"; |
|
5644 |
|
5645 if (0 == strncmp(sanityCheck, aBuffer, 5)) { |
|
5646 char *eourl = NULL; |
|
5647 char *start = &aBuffer[5]; |
|
5648 char *getData = NULL; |
|
5649 int realFun = 0; |
|
5650 const char *crlf = "\015\012"; |
|
5651 char *eoline = NULL; |
|
5652 FormData *fdGet = NULL; |
|
5653 |
|
5654 /* |
|
5655 ** Truncate the line if possible. |
|
5656 ** Only want first one. |
|
5657 */ |
|
5658 eoline = strstr(aBuffer, crlf); |
|
5659 if (NULL != eoline) { |
|
5660 *eoline = '\0'; |
|
5661 } |
|
5662 |
|
5663 /* |
|
5664 ** Find the whitespace. |
|
5665 ** That is either end of line or the " HTTP/1.x" suffix. |
|
5666 ** We do not care. |
|
5667 */ |
|
5668 for (eourl = start; 0 == isspace(*eourl) && '\0' != *eourl; |
|
5669 eourl++) { |
|
5670 /* |
|
5671 ** No body. |
|
5672 */ |
|
5673 } |
|
5674 |
|
5675 /* |
|
5676 ** Cap it off. |
|
5677 ** Convert empty '/' to index.html. |
|
5678 */ |
|
5679 *eourl = '\0'; |
|
5680 if ('\0' == *start) { |
|
5681 strcpy(start, "index.html"); |
|
5682 } |
|
5683 |
|
5684 /* |
|
5685 ** Have we got any GET form data? |
|
5686 */ |
|
5687 getData = strchr(start, '?'); |
|
5688 if (NULL != getData) { |
|
5689 /* |
|
5690 ** Whack it off. |
|
5691 */ |
|
5692 *getData = '\0'; |
|
5693 getData++; |
|
5694 } |
|
5695 |
|
5696 /* |
|
5697 ** Convert get data into a more useful format. |
|
5698 */ |
|
5699 fdGet = FormData_Create(getData); |
|
5700 /* |
|
5701 ** This is totally a hack, but oh well.... |
|
5702 ** |
|
5703 ** Send that the request was OK, regardless. |
|
5704 ** |
|
5705 ** If we have any get data, then it is a set of options |
|
5706 ** we attempt to apply. |
|
5707 ** |
|
5708 ** Other code will tell the user they were wrong or if |
|
5709 ** there was an error. |
|
5710 ** If the filename contains a ".png", then send the image |
|
5711 ** mime type, otherwise, say it is text/html. |
|
5712 */ |
|
5713 PR_fprintf(aFD, "HTTP/1.1 200 OK%s", crlf); |
|
5714 PR_fprintf(aFD, "Server: %s%s", |
|
5715 "$Id: spacetrace.c,v 1.54 2006/11/01 23:02:17 timeless%mozdev.org Exp $", |
|
5716 crlf); |
|
5717 PR_fprintf(aFD, "Content-type: "); |
|
5718 if (NULL != strstr(start, ".png")) { |
|
5719 PR_fprintf(aFD, "image/png"); |
|
5720 } |
|
5721 else if (NULL != strstr(start, ".jpg")) { |
|
5722 PR_fprintf(aFD, "image/jpeg"); |
|
5723 } |
|
5724 else if (NULL != strstr(start, ".txt")) { |
|
5725 PR_fprintf(aFD, "text/plain"); |
|
5726 } |
|
5727 else if (NULL != strstr(start, ".css")) { |
|
5728 PR_fprintf(aFD, "text/css"); |
|
5729 } |
|
5730 else { |
|
5731 PR_fprintf(aFD, "text/html"); |
|
5732 } |
|
5733 PR_fprintf(aFD, crlf); |
|
5734 /* |
|
5735 ** One more to separate headers from content. |
|
5736 */ |
|
5737 PR_fprintf(aFD, crlf); |
|
5738 /* |
|
5739 ** Ready for the real fun. |
|
5740 */ |
|
5741 realFun = handleRequest(globals.mTMR, aFD, start, fdGet); |
|
5742 if (0 != realFun) { |
|
5743 REPORT_ERROR(__LINE__, handleRequest); |
|
5744 } |
|
5745 |
|
5746 /* |
|
5747 ** Free off get data if around. |
|
5748 */ |
|
5749 FormData_Destroy(fdGet); |
|
5750 fdGet = NULL; |
|
5751 } |
|
5752 else { |
|
5753 REPORT_ERROR(__LINE__, handleClient); |
|
5754 } |
|
5755 } |
|
5756 else { |
|
5757 REPORT_ERROR(__LINE__, lineReader); |
|
5758 } |
|
5759 |
|
5760 /* |
|
5761 ** Done with the connection. |
|
5762 */ |
|
5763 closeRes = PR_Close(aFD); |
|
5764 if (PR_SUCCESS != closeRes) { |
|
5765 REPORT_ERROR(__LINE__, PR_Close); |
|
5766 } |
|
5767 } |
|
5768 else { |
|
5769 REPORT_ERROR(__LINE__, handleClient); |
|
5770 } |
|
5771 } |
|
5772 |
|
5773 /* |
|
5774 ** serverMode |
|
5775 ** |
|
5776 ** List on a port as a httpd. |
|
5777 ** Output results interactively on demand. |
|
5778 ** |
|
5779 ** Returns !0 on error. |
|
5780 */ |
|
5781 int |
|
5782 serverMode(void) |
|
5783 { |
|
5784 int retval = 0; |
|
5785 PRFileDesc *socket = NULL; |
|
5786 |
|
5787 /* |
|
5788 ** Create a socket. |
|
5789 */ |
|
5790 socket = PR_NewTCPSocket(); |
|
5791 if (NULL != socket) { |
|
5792 PRStatus closeRes = PR_SUCCESS; |
|
5793 PRNetAddr bindAddr; |
|
5794 PRStatus bindRes = PR_SUCCESS; |
|
5795 |
|
5796 /* |
|
5797 ** Bind it to an interface/port. |
|
5798 ** Any interface. |
|
5799 */ |
|
5800 bindAddr.inet.family = PR_AF_INET; |
|
5801 bindAddr.inet.port = |
|
5802 PR_htons((uint16_t) globals.mCommandLineOptions.mHttpdPort); |
|
5803 bindAddr.inet.ip = PR_htonl(PR_INADDR_ANY); |
|
5804 bindRes = PR_Bind(socket, &bindAddr); |
|
5805 if (PR_SUCCESS == bindRes) { |
|
5806 PRStatus listenRes = PR_SUCCESS; |
|
5807 const int backlog = 0x20; |
|
5808 |
|
5809 /* |
|
5810 ** Start listening for clients. |
|
5811 ** Give a decent backlog, some of our processing will take |
|
5812 ** a bit. |
|
5813 */ |
|
5814 listenRes = PR_Listen(socket, backlog); |
|
5815 if (PR_SUCCESS == listenRes) { |
|
5816 PRFileDesc *connection = NULL; |
|
5817 int failureSum = 0; |
|
5818 char message[80]; |
|
5819 |
|
5820 /* |
|
5821 ** Output a little message saying we are receiving. |
|
5822 */ |
|
5823 PR_snprintf(message, sizeof(message), |
|
5824 "server accepting connections at http://localhost:%u/", |
|
5825 globals.mCommandLineOptions.mHttpdPort); |
|
5826 REPORT_INFO(message); |
|
5827 PR_fprintf(PR_STDOUT, "Peak memory used: %s bytes\n", |
|
5828 FormatNumber(globals.mPeakMemoryUsed)); |
|
5829 PR_fprintf(PR_STDOUT, "Allocations : %s total\n", |
|
5830 FormatNumber(globals.mMallocCount + |
|
5831 globals.mCallocCount + |
|
5832 globals.mReallocCount), |
|
5833 FormatNumber(globals.mFreeCount)); |
|
5834 PR_fprintf(PR_STDOUT, "Breakdown : %s malloc\n", |
|
5835 FormatNumber(globals.mMallocCount)); |
|
5836 PR_fprintf(PR_STDOUT, " %s calloc\n", |
|
5837 FormatNumber(globals.mCallocCount)); |
|
5838 PR_fprintf(PR_STDOUT, " %s realloc\n", |
|
5839 FormatNumber(globals.mReallocCount)); |
|
5840 PR_fprintf(PR_STDOUT, " %s free\n", |
|
5841 FormatNumber(globals.mFreeCount)); |
|
5842 PR_fprintf(PR_STDOUT, "Leaks : %s\n", |
|
5843 FormatNumber((globals.mMallocCount + |
|
5844 globals.mCallocCount + |
|
5845 globals.mReallocCount) - |
|
5846 globals.mFreeCount)); |
|
5847 /* |
|
5848 ** Keep accepting until we know otherwise. |
|
5849 ** |
|
5850 ** We do a thread per connection. |
|
5851 ** Up to the thread to close the connection when done. |
|
5852 ** |
|
5853 ** This is known by me to be suboptimal, and I would rather |
|
5854 ** do a thread pool if it ever becomes a resource issue. |
|
5855 ** Any issues would simply point to a need to get |
|
5856 ** more machines or a beefier machine to handle the |
|
5857 ** requests, as well as a need to do thread pooling and |
|
5858 ** avoid thread creation overhead. |
|
5859 ** The threads are not tracked, except possibly by NSPR |
|
5860 ** itself and PR_Cleanup will wait on them all to exit as |
|
5861 ** user threads so our shared data is valid. |
|
5862 */ |
|
5863 while (0 == retval) { |
|
5864 connection = |
|
5865 PR_Accept(socket, NULL, PR_INTERVAL_NO_TIMEOUT); |
|
5866 if (NULL != connection) { |
|
5867 PRThread *clientThread = NULL; |
|
5868 |
|
5869 /* |
|
5870 ** Thread per connection. |
|
5871 */ |
|
5872 clientThread = PR_CreateThread(PR_USER_THREAD, /* PR_Cleanup sync */ |
|
5873 handleClient, (void *) connection, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, /* IO enabled */ |
|
5874 PR_UNJOINABLE_THREAD, |
|
5875 0); |
|
5876 if (NULL == clientThread) { |
|
5877 PRStatus closeRes = PR_SUCCESS; |
|
5878 |
|
5879 failureSum += __LINE__; |
|
5880 REPORT_ERROR(__LINE__, PR_Accept); |
|
5881 /* |
|
5882 ** Close the connection as well, no service |
|
5883 */ |
|
5884 closeRes = PR_Close(connection); |
|
5885 if (PR_FAILURE == closeRes) { |
|
5886 REPORT_ERROR(__LINE__, PR_Close); |
|
5887 } |
|
5888 } |
|
5889 } |
|
5890 else { |
|
5891 failureSum += __LINE__; |
|
5892 REPORT_ERROR(__LINE__, PR_Accept); |
|
5893 } |
|
5894 } |
|
5895 |
|
5896 if (0 != failureSum) { |
|
5897 retval = __LINE__; |
|
5898 } |
|
5899 |
|
5900 /* |
|
5901 ** Output a little message saying it is all over. |
|
5902 */ |
|
5903 REPORT_INFO("server no longer accepting connections...."); |
|
5904 } |
|
5905 else { |
|
5906 retval = __LINE__; |
|
5907 REPORT_ERROR(__LINE__, PR_Listen); |
|
5908 } |
|
5909 } |
|
5910 else { |
|
5911 retval = __LINE__; |
|
5912 REPORT_ERROR(__LINE__, PR_Bind); |
|
5913 } |
|
5914 |
|
5915 /* |
|
5916 ** Done with socket. |
|
5917 */ |
|
5918 closeRes = PR_Close(socket); |
|
5919 if (PR_SUCCESS != closeRes) { |
|
5920 retval = __LINE__; |
|
5921 REPORT_ERROR(__LINE__, PR_Close); |
|
5922 } |
|
5923 socket = NULL; |
|
5924 } |
|
5925 else { |
|
5926 retval = __LINE__; |
|
5927 REPORT_ERROR(__LINE__, PR_NewTCPSocket); |
|
5928 } |
|
5929 |
|
5930 return retval; |
|
5931 } |
|
5932 |
|
5933 /* |
|
5934 ** batchMode |
|
5935 ** |
|
5936 ** Perform whatever batch requests we were asked to do. |
|
5937 */ |
|
5938 int |
|
5939 batchMode(void) |
|
5940 { |
|
5941 int retval = 0; |
|
5942 |
|
5943 if (0 != globals.mCommandLineOptions.mBatchRequestCount) { |
|
5944 uint32_t loop = 0; |
|
5945 int failureSum = 0; |
|
5946 int handleRes = 0; |
|
5947 char aFileName[1024]; |
|
5948 uint32_t sprintfRes = 0; |
|
5949 |
|
5950 /* |
|
5951 ** Go through and process the various files requested. |
|
5952 ** We do not stop on failure, as it is too costly to rerun the |
|
5953 ** batch job. |
|
5954 */ |
|
5955 for (loop = 0; |
|
5956 loop < globals.mCommandLineOptions.mBatchRequestCount; loop++) { |
|
5957 sprintfRes = |
|
5958 PR_snprintf(aFileName, sizeof(aFileName), "%s%c%s", |
|
5959 globals.mCommandLineOptions.mOutputDir, |
|
5960 PR_GetDirectorySeparator(), |
|
5961 globals.mCommandLineOptions.mBatchRequest[loop]); |
|
5962 if ((uint32_t) - 1 != sprintfRes) { |
|
5963 PRFileDesc *outFile = NULL; |
|
5964 |
|
5965 outFile = PR_Open(aFileName, ST_FLAGS, ST_PERMS); |
|
5966 if (NULL != outFile) { |
|
5967 PRStatus closeRes = PR_SUCCESS; |
|
5968 |
|
5969 handleRes = |
|
5970 handleRequest(globals.mTMR, outFile, |
|
5971 globals.mCommandLineOptions. |
|
5972 mBatchRequest[loop], NULL); |
|
5973 if (0 != handleRes) { |
|
5974 failureSum += __LINE__; |
|
5975 REPORT_ERROR(__LINE__, handleRequest); |
|
5976 } |
|
5977 |
|
5978 closeRes = PR_Close(outFile); |
|
5979 if (PR_SUCCESS != closeRes) { |
|
5980 failureSum += __LINE__; |
|
5981 REPORT_ERROR(__LINE__, PR_Close); |
|
5982 } |
|
5983 } |
|
5984 else { |
|
5985 failureSum += __LINE__; |
|
5986 REPORT_ERROR(__LINE__, PR_Open); |
|
5987 } |
|
5988 } |
|
5989 else { |
|
5990 failureSum += __LINE__; |
|
5991 REPORT_ERROR(__LINE__, PR_snprintf); |
|
5992 } |
|
5993 } |
|
5994 |
|
5995 if (0 != failureSum) { |
|
5996 retval = __LINE__; |
|
5997 } |
|
5998 } |
|
5999 else { |
|
6000 retval = __LINE__; |
|
6001 REPORT_ERROR(__LINE__, outputReports); |
|
6002 } |
|
6003 |
|
6004 return retval; |
|
6005 } |
|
6006 |
|
6007 /* |
|
6008 ** doRun |
|
6009 ** |
|
6010 ** Perform the actual processing this program requires. |
|
6011 ** Returns !0 on failure. |
|
6012 */ |
|
6013 int |
|
6014 doRun(void) |
|
6015 { |
|
6016 int retval = 0; |
|
6017 |
|
6018 /* |
|
6019 ** Create the new trace-malloc reader. |
|
6020 */ |
|
6021 globals.mTMR = tmreader_new(globals.mProgramName, NULL); |
|
6022 if (NULL != globals.mTMR) { |
|
6023 int tmResult = 0; |
|
6024 int outputResult = 0; |
|
6025 |
|
6026 #if defined(DEBUG_dp) |
|
6027 PRIntervalTime start = PR_IntervalNow(); |
|
6028 |
|
6029 fprintf(stderr, "DEBUG: reading tracemalloc data...\n"); |
|
6030 #endif |
|
6031 tmResult = |
|
6032 tmreader_eventloop(globals.mTMR, |
|
6033 globals.mCommandLineOptions.mFileName, |
|
6034 tmEventHandler); |
|
6035 printf("\rReading... Done.\n"); |
|
6036 #if defined(DEBUG_dp) |
|
6037 fprintf(stderr, |
|
6038 "DEBUG: reading tracemalloc data ends: %dms [%d allocations]\n", |
|
6039 PR_IntervalToMilliseconds(PR_IntervalNow() - start), |
|
6040 globals.mRun.mAllocationCount); |
|
6041 #endif |
|
6042 if (0 == tmResult) { |
|
6043 REPORT_ERROR(__LINE__, tmreader_eventloop); |
|
6044 retval = __LINE__; |
|
6045 } |
|
6046 |
|
6047 if (0 == retval) { |
|
6048 /* |
|
6049 ** Decide if we're going into batch mode or server mode. |
|
6050 */ |
|
6051 if (0 != globals.mCommandLineOptions.mBatchRequestCount) { |
|
6052 /* |
|
6053 ** Output in one big step while everything still exists. |
|
6054 */ |
|
6055 outputResult = batchMode(); |
|
6056 if (0 != outputResult) { |
|
6057 REPORT_ERROR(__LINE__, batchMode); |
|
6058 retval = __LINE__; |
|
6059 } |
|
6060 } |
|
6061 else { |
|
6062 int serverRes = 0; |
|
6063 |
|
6064 /* |
|
6065 ** httpd time. |
|
6066 */ |
|
6067 serverRes = serverMode(); |
|
6068 if (0 != serverRes) { |
|
6069 REPORT_ERROR(__LINE__, serverMode); |
|
6070 retval = __LINE__; |
|
6071 } |
|
6072 } |
|
6073 |
|
6074 /* |
|
6075 ** Clear our categorization tree |
|
6076 */ |
|
6077 freeCategories(&globals); |
|
6078 } |
|
6079 } |
|
6080 else { |
|
6081 REPORT_ERROR(__LINE__, tmreader_new); |
|
6082 retval = __LINE__; |
|
6083 } |
|
6084 |
|
6085 return retval; |
|
6086 } |
|
6087 |
|
6088 int |
|
6089 initCaches(void) |
|
6090 /* |
|
6091 ** Initialize the global caches. |
|
6092 ** More involved since we have to allocated/create some objects. |
|
6093 ** |
|
6094 ** returns Zero if all is well. |
|
6095 ** Non-zero on error. |
|
6096 */ |
|
6097 { |
|
6098 int retval = 0; |
|
6099 STContextCache *inCache = &globals.mContextCache; |
|
6100 |
|
6101 if (NULL != inCache && 0 != globals.mCommandLineOptions.mContexts) { |
|
6102 inCache->mLock = PR_NewLock(); |
|
6103 if (NULL != inCache->mLock) { |
|
6104 inCache->mCacheMiss = PR_NewCondVar(inCache->mLock); |
|
6105 if (NULL != inCache->mCacheMiss) { |
|
6106 inCache->mItems = |
|
6107 (STContextCacheItem *) calloc(globals.mCommandLineOptions. |
|
6108 mContexts, |
|
6109 sizeof(STContextCacheItem)); |
|
6110 if (NULL != inCache->mItems) { |
|
6111 uint32_t loop = 0; |
|
6112 char buffer[64]; |
|
6113 |
|
6114 inCache->mItemCount = |
|
6115 globals.mCommandLineOptions.mContexts; |
|
6116 /* |
|
6117 ** Init each item as needed. |
|
6118 */ |
|
6119 for (loop = 0; loop < inCache->mItemCount; loop++) { |
|
6120 inCache->mItems[loop].mContext.mIndex = loop; |
|
6121 PR_snprintf(buffer, sizeof(buffer), |
|
6122 "Context Item %d RW Lock", loop); |
|
6123 inCache->mItems[loop].mContext.mRWLock = |
|
6124 PR_NewRWLock(PR_RWLOCK_RANK_NONE, buffer); |
|
6125 if (NULL == inCache->mItems[loop].mContext.mRWLock) { |
|
6126 break; |
|
6127 } |
|
6128 #if ST_WANT_GRAPHS |
|
6129 inCache->mItems[loop].mContext.mImageLock = |
|
6130 PR_NewLock(); |
|
6131 if (NULL == inCache->mItems[loop].mContext.mImageLock) { |
|
6132 break; |
|
6133 } |
|
6134 #endif |
|
6135 } |
|
6136 |
|
6137 if (loop != inCache->mItemCount) { |
|
6138 retval = __LINE__; |
|
6139 REPORT_ERROR(__LINE__, initCaches); |
|
6140 } |
|
6141 } |
|
6142 else { |
|
6143 retval = __LINE__; |
|
6144 REPORT_ERROR(__LINE__, calloc); |
|
6145 } |
|
6146 } |
|
6147 else { |
|
6148 retval = __LINE__; |
|
6149 REPORT_ERROR(__LINE__, PR_NewCondVar); |
|
6150 } |
|
6151 } |
|
6152 else { |
|
6153 retval = __LINE__; |
|
6154 REPORT_ERROR(__LINE__, PR_NewLock); |
|
6155 } |
|
6156 } |
|
6157 else { |
|
6158 retval = __LINE__; |
|
6159 REPORT_ERROR(__LINE__, initCaches); |
|
6160 } |
|
6161 |
|
6162 return retval; |
|
6163 } |
|
6164 |
|
6165 int |
|
6166 destroyCaches(void) |
|
6167 /* |
|
6168 ** Clean up any global caches we have laying around. |
|
6169 ** |
|
6170 ** returns Zero if all is well. |
|
6171 ** Non-zero on error. |
|
6172 */ |
|
6173 { |
|
6174 int retval = 0; |
|
6175 STContextCache *inCache = &globals.mContextCache; |
|
6176 |
|
6177 if (NULL != inCache) { |
|
6178 uint32_t loop = 0; |
|
6179 |
|
6180 /* |
|
6181 ** Uninit item data one by one. |
|
6182 */ |
|
6183 for (loop = 0; loop < inCache->mItemCount; loop++) { |
|
6184 if (NULL != inCache->mItems[loop].mContext.mRWLock) { |
|
6185 PR_DestroyRWLock(inCache->mItems[loop].mContext.mRWLock); |
|
6186 inCache->mItems[loop].mContext.mRWLock = NULL; |
|
6187 } |
|
6188 #if ST_WANT_GRAPHS |
|
6189 if (NULL != inCache->mItems[loop].mContext.mImageLock) { |
|
6190 PR_DestroyLock(inCache->mItems[loop].mContext.mImageLock); |
|
6191 inCache->mItems[loop].mContext.mImageLock = NULL; |
|
6192 } |
|
6193 #endif |
|
6194 } |
|
6195 |
|
6196 inCache->mItemCount = 0; |
|
6197 if (NULL != inCache->mItems) { |
|
6198 free(inCache->mItems); |
|
6199 inCache->mItems = NULL; |
|
6200 } |
|
6201 |
|
6202 if (NULL != inCache->mCacheMiss) { |
|
6203 PR_DestroyCondVar(inCache->mCacheMiss); |
|
6204 inCache->mCacheMiss = NULL; |
|
6205 } |
|
6206 |
|
6207 if (NULL != inCache->mLock) { |
|
6208 PR_DestroyLock(inCache->mLock); |
|
6209 inCache->mLock = NULL; |
|
6210 } |
|
6211 } |
|
6212 else { |
|
6213 retval = __LINE__; |
|
6214 REPORT_ERROR(__LINE__, destroyCaches); |
|
6215 } |
|
6216 |
|
6217 return retval; |
|
6218 } |
|
6219 |
|
6220 /* |
|
6221 ** main |
|
6222 ** |
|
6223 ** Process entry and exit. |
|
6224 */ |
|
6225 int |
|
6226 main(int aArgCount, char **aArgArray) |
|
6227 { |
|
6228 int retval = 0; |
|
6229 int optionsResult = 0; |
|
6230 PRStatus prResult = PR_SUCCESS; |
|
6231 int showedHelp = 0; |
|
6232 int looper = 0; |
|
6233 int cacheResult = 0; |
|
6234 |
|
6235 /* |
|
6236 ** NSPR init. |
|
6237 */ |
|
6238 PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); |
|
6239 /* |
|
6240 ** Initialize globals |
|
6241 */ |
|
6242 memset(&globals, 0, sizeof(globals)); |
|
6243 /* |
|
6244 ** Set the program name. |
|
6245 */ |
|
6246 globals.mProgramName = aArgArray[0]; |
|
6247 /* |
|
6248 ** Set the minimum timeval really high so other code |
|
6249 ** that checks the timeval will get it right. |
|
6250 */ |
|
6251 globals.mMinTimeval = ST_TIMEVAL_MAX; |
|
6252 /* |
|
6253 ** Handle initializing options. |
|
6254 */ |
|
6255 optionsResult = initOptions(aArgCount, aArgArray); |
|
6256 if (0 != optionsResult) { |
|
6257 REPORT_ERROR(optionsResult, initOptions); |
|
6258 retval = __LINE__; |
|
6259 } |
|
6260 |
|
6261 /* |
|
6262 ** Initialize our caches. |
|
6263 */ |
|
6264 cacheResult = initCaches(); |
|
6265 if (0 != cacheResult) { |
|
6266 retval = __LINE__; |
|
6267 REPORT_ERROR(__LINE__, initCaches); |
|
6268 } |
|
6269 |
|
6270 /* |
|
6271 ** Small alloc code init. |
|
6272 */ |
|
6273 globals.mCategoryRoot.runs = |
|
6274 (STRun **) calloc(globals.mCommandLineOptions.mContexts, |
|
6275 sizeof(STRun *)); |
|
6276 if (NULL == globals.mCategoryRoot.runs) { |
|
6277 retval = __LINE__; |
|
6278 REPORT_ERROR(__LINE__, calloc); |
|
6279 } |
|
6280 |
|
6281 /* |
|
6282 ** Show help on usage if need be. |
|
6283 */ |
|
6284 showedHelp = showHelp(); |
|
6285 /* |
|
6286 ** Only perform the run if everything is checking out. |
|
6287 */ |
|
6288 if (0 == showedHelp && 0 == retval) { |
|
6289 int runResult = 0; |
|
6290 |
|
6291 runResult = doRun(); |
|
6292 if (0 != runResult) { |
|
6293 REPORT_ERROR(runResult, doRun); |
|
6294 retval = __LINE__; |
|
6295 } |
|
6296 } |
|
6297 |
|
6298 if (0 != retval) { |
|
6299 REPORT_ERROR(retval, main); |
|
6300 } |
|
6301 |
|
6302 /* |
|
6303 ** Have NSPR join all client threads we started. |
|
6304 */ |
|
6305 prResult = PR_Cleanup(); |
|
6306 if (PR_SUCCESS != prResult) { |
|
6307 REPORT_ERROR(retval, PR_Cleanup); |
|
6308 retval = __LINE__; |
|
6309 } |
|
6310 /* |
|
6311 ** All threads are joined/done by this line. |
|
6312 */ |
|
6313 |
|
6314 /* |
|
6315 ** Options allocated a little. |
|
6316 */ |
|
6317 #define ST_CMD_OPTION_STRING_PTR_ARRAY(option_name, option_genre, option_help) \ |
|
6318 if(NULL != globals.mCommandLineOptions.m##option_name) \ |
|
6319 { \ |
|
6320 free((void*)globals.mCommandLineOptions.m##option_name); \ |
|
6321 globals.mCommandLineOptions.m##option_name = NULL; \ |
|
6322 globals.mCommandLineOptions.m##option_name##Count = 0; \ |
|
6323 } |
|
6324 #include "stoptions.h" |
|
6325 |
|
6326 /* |
|
6327 ** globals has a small modification to clear up. |
|
6328 */ |
|
6329 if (NULL != globals.mCategoryRoot.runs) { |
|
6330 free(globals.mCategoryRoot.runs); |
|
6331 globals.mCategoryRoot.runs = NULL; |
|
6332 } |
|
6333 |
|
6334 /* |
|
6335 ** Blow away our caches. |
|
6336 */ |
|
6337 cacheResult = destroyCaches(); |
|
6338 if (0 != cacheResult) { |
|
6339 retval = __LINE__; |
|
6340 REPORT_ERROR(__LINE__, initCaches); |
|
6341 } |
|
6342 |
|
6343 /* |
|
6344 ** We are safe to kill our tmreader data. |
|
6345 */ |
|
6346 if (NULL != globals.mTMR) { |
|
6347 tmreader_destroy(globals.mTMR); |
|
6348 globals.mTMR = NULL; |
|
6349 } |
|
6350 |
|
6351 return retval; |
|
6352 } |