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