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: 2 -*- */ |
michael@0 | 2 | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
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 | #include "DMD.h" |
michael@0 | 8 | |
michael@0 | 9 | #include <ctype.h> |
michael@0 | 10 | #include <errno.h> |
michael@0 | 11 | #include <limits.h> |
michael@0 | 12 | #include <stdarg.h> |
michael@0 | 13 | #include <stdio.h> |
michael@0 | 14 | #include <stdlib.h> |
michael@0 | 15 | #include <string.h> |
michael@0 | 16 | |
michael@0 | 17 | #ifdef XP_WIN |
michael@0 | 18 | #if defined(MOZ_OPTIMIZE) && !defined(MOZ_PROFILING) |
michael@0 | 19 | #error "Optimized, DMD-enabled builds on Windows must be built with --enable-profiling" |
michael@0 | 20 | #endif |
michael@0 | 21 | #include <windows.h> |
michael@0 | 22 | #include <process.h> |
michael@0 | 23 | #else |
michael@0 | 24 | #include <unistd.h> |
michael@0 | 25 | #endif |
michael@0 | 26 | |
michael@0 | 27 | #ifdef ANDROID |
michael@0 | 28 | #include <android/log.h> |
michael@0 | 29 | #endif |
michael@0 | 30 | |
michael@0 | 31 | #include "nscore.h" |
michael@0 | 32 | #include "nsStackWalk.h" |
michael@0 | 33 | |
michael@0 | 34 | #include "js/HashTable.h" |
michael@0 | 35 | #include "js/Vector.h" |
michael@0 | 36 | |
michael@0 | 37 | #include "mozilla/Assertions.h" |
michael@0 | 38 | #include "mozilla/HashFunctions.h" |
michael@0 | 39 | #include "mozilla/Likely.h" |
michael@0 | 40 | #include "mozilla/MemoryReporting.h" |
michael@0 | 41 | |
michael@0 | 42 | // MOZ_REPLACE_ONLY_MEMALIGN saves us from having to define |
michael@0 | 43 | // replace_{posix_memalign,aligned_alloc,valloc}. It requires defining |
michael@0 | 44 | // PAGE_SIZE. Nb: sysconf() is expensive, but it's only used for (the obsolete |
michael@0 | 45 | // and rarely used) valloc. |
michael@0 | 46 | #define MOZ_REPLACE_ONLY_MEMALIGN 1 |
michael@0 | 47 | #ifdef XP_WIN |
michael@0 | 48 | #define PAGE_SIZE GetPageSize() |
michael@0 | 49 | static long GetPageSize() |
michael@0 | 50 | { |
michael@0 | 51 | SYSTEM_INFO si; |
michael@0 | 52 | GetSystemInfo(&si); |
michael@0 | 53 | return si.dwPageSize; |
michael@0 | 54 | } |
michael@0 | 55 | #else |
michael@0 | 56 | #define PAGE_SIZE sysconf(_SC_PAGESIZE) |
michael@0 | 57 | #endif |
michael@0 | 58 | #include "replace_malloc.h" |
michael@0 | 59 | #undef MOZ_REPLACE_ONLY_MEMALIGN |
michael@0 | 60 | #undef PAGE_SIZE |
michael@0 | 61 | |
michael@0 | 62 | namespace mozilla { |
michael@0 | 63 | namespace dmd { |
michael@0 | 64 | |
michael@0 | 65 | //--------------------------------------------------------------------------- |
michael@0 | 66 | // Utilities |
michael@0 | 67 | //--------------------------------------------------------------------------- |
michael@0 | 68 | |
michael@0 | 69 | #ifndef DISALLOW_COPY_AND_ASSIGN |
michael@0 | 70 | #define DISALLOW_COPY_AND_ASSIGN(T) \ |
michael@0 | 71 | T(const T&); \ |
michael@0 | 72 | void operator=(const T&) |
michael@0 | 73 | #endif |
michael@0 | 74 | |
michael@0 | 75 | static const malloc_table_t* gMallocTable = nullptr; |
michael@0 | 76 | |
michael@0 | 77 | // This enables/disables DMD. |
michael@0 | 78 | static bool gIsDMDRunning = false; |
michael@0 | 79 | |
michael@0 | 80 | // This provides infallible allocations (they abort on OOM). We use it for all |
michael@0 | 81 | // of DMD's own allocations, which fall into the following three cases. |
michael@0 | 82 | // - Direct allocations (the easy case). |
michael@0 | 83 | // - Indirect allocations in js::{Vector,HashSet,HashMap} -- this class serves |
michael@0 | 84 | // as their AllocPolicy. |
michael@0 | 85 | // - Other indirect allocations (e.g. NS_StackWalk) -- see the comments on |
michael@0 | 86 | // Thread::mBlockIntercepts and in replace_malloc for how these work. |
michael@0 | 87 | // |
michael@0 | 88 | class InfallibleAllocPolicy |
michael@0 | 89 | { |
michael@0 | 90 | static void ExitOnFailure(const void* aP); |
michael@0 | 91 | |
michael@0 | 92 | public: |
michael@0 | 93 | static void* malloc_(size_t aSize) |
michael@0 | 94 | { |
michael@0 | 95 | void* p = gMallocTable->malloc(aSize); |
michael@0 | 96 | ExitOnFailure(p); |
michael@0 | 97 | return p; |
michael@0 | 98 | } |
michael@0 | 99 | |
michael@0 | 100 | static void* calloc_(size_t aSize) |
michael@0 | 101 | { |
michael@0 | 102 | void* p = gMallocTable->calloc(1, aSize); |
michael@0 | 103 | ExitOnFailure(p); |
michael@0 | 104 | return p; |
michael@0 | 105 | } |
michael@0 | 106 | |
michael@0 | 107 | // This realloc_ is the one we use for direct reallocs within DMD. |
michael@0 | 108 | static void* realloc_(void* aPtr, size_t aNewSize) |
michael@0 | 109 | { |
michael@0 | 110 | void* p = gMallocTable->realloc(aPtr, aNewSize); |
michael@0 | 111 | ExitOnFailure(p); |
michael@0 | 112 | return p; |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | // This realloc_ is required for this to be a JS container AllocPolicy. |
michael@0 | 116 | static void* realloc_(void* aPtr, size_t aOldSize, size_t aNewSize) |
michael@0 | 117 | { |
michael@0 | 118 | return InfallibleAllocPolicy::realloc_(aPtr, aNewSize); |
michael@0 | 119 | } |
michael@0 | 120 | |
michael@0 | 121 | static void* memalign_(size_t aAlignment, size_t aSize) |
michael@0 | 122 | { |
michael@0 | 123 | void* p = gMallocTable->memalign(aAlignment, aSize); |
michael@0 | 124 | ExitOnFailure(p); |
michael@0 | 125 | return p; |
michael@0 | 126 | } |
michael@0 | 127 | |
michael@0 | 128 | static void free_(void* aPtr) { gMallocTable->free(aPtr); } |
michael@0 | 129 | |
michael@0 | 130 | static char* strdup_(const char* aStr) |
michael@0 | 131 | { |
michael@0 | 132 | char* s = (char*) InfallibleAllocPolicy::malloc_(strlen(aStr) + 1); |
michael@0 | 133 | strcpy(s, aStr); |
michael@0 | 134 | return s; |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | template <class T> |
michael@0 | 138 | static T* new_() |
michael@0 | 139 | { |
michael@0 | 140 | void* mem = malloc_(sizeof(T)); |
michael@0 | 141 | ExitOnFailure(mem); |
michael@0 | 142 | return new (mem) T; |
michael@0 | 143 | } |
michael@0 | 144 | |
michael@0 | 145 | template <class T, typename P1> |
michael@0 | 146 | static T* new_(P1 p1) |
michael@0 | 147 | { |
michael@0 | 148 | void* mem = malloc_(sizeof(T)); |
michael@0 | 149 | ExitOnFailure(mem); |
michael@0 | 150 | return new (mem) T(p1); |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | template <class T> |
michael@0 | 154 | static void delete_(T *p) |
michael@0 | 155 | { |
michael@0 | 156 | if (p) { |
michael@0 | 157 | p->~T(); |
michael@0 | 158 | InfallibleAllocPolicy::free_(p); |
michael@0 | 159 | } |
michael@0 | 160 | } |
michael@0 | 161 | |
michael@0 | 162 | static void reportAllocOverflow() { ExitOnFailure(nullptr); } |
michael@0 | 163 | }; |
michael@0 | 164 | |
michael@0 | 165 | // This is only needed because of the |const void*| vs |void*| arg mismatch. |
michael@0 | 166 | static size_t |
michael@0 | 167 | MallocSizeOf(const void* aPtr) |
michael@0 | 168 | { |
michael@0 | 169 | return gMallocTable->malloc_usable_size(const_cast<void*>(aPtr)); |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | static void |
michael@0 | 173 | StatusMsg(const char* aFmt, ...) |
michael@0 | 174 | { |
michael@0 | 175 | va_list ap; |
michael@0 | 176 | va_start(ap, aFmt); |
michael@0 | 177 | #ifdef ANDROID |
michael@0 | 178 | __android_log_vprint(ANDROID_LOG_INFO, "DMD", aFmt, ap); |
michael@0 | 179 | #else |
michael@0 | 180 | // The +64 is easily enough for the "DMD[<pid>] " prefix and the NUL. |
michael@0 | 181 | char* fmt = (char*) InfallibleAllocPolicy::malloc_(strlen(aFmt) + 64); |
michael@0 | 182 | sprintf(fmt, "DMD[%d] %s", getpid(), aFmt); |
michael@0 | 183 | vfprintf(stderr, fmt, ap); |
michael@0 | 184 | InfallibleAllocPolicy::free_(fmt); |
michael@0 | 185 | #endif |
michael@0 | 186 | va_end(ap); |
michael@0 | 187 | } |
michael@0 | 188 | |
michael@0 | 189 | /* static */ void |
michael@0 | 190 | InfallibleAllocPolicy::ExitOnFailure(const void* aP) |
michael@0 | 191 | { |
michael@0 | 192 | if (!aP) { |
michael@0 | 193 | StatusMsg("out of memory; aborting\n"); |
michael@0 | 194 | MOZ_CRASH(); |
michael@0 | 195 | } |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | void |
michael@0 | 199 | Writer::Write(const char* aFmt, ...) const |
michael@0 | 200 | { |
michael@0 | 201 | va_list ap; |
michael@0 | 202 | va_start(ap, aFmt); |
michael@0 | 203 | mWriterFun(mWriteState, aFmt, ap); |
michael@0 | 204 | va_end(ap); |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | #define W(...) aWriter.Write(__VA_ARGS__); |
michael@0 | 208 | |
michael@0 | 209 | #define WriteTitle(...) \ |
michael@0 | 210 | W("------------------------------------------------------------------\n"); \ |
michael@0 | 211 | W(__VA_ARGS__); \ |
michael@0 | 212 | W("------------------------------------------------------------------\n\n"); |
michael@0 | 213 | |
michael@0 | 214 | MOZ_EXPORT void |
michael@0 | 215 | FpWrite(void* aWriteState, const char* aFmt, va_list aAp) |
michael@0 | 216 | { |
michael@0 | 217 | FILE* fp = static_cast<FILE*>(aWriteState); |
michael@0 | 218 | vfprintf(fp, aFmt, aAp); |
michael@0 | 219 | } |
michael@0 | 220 | |
michael@0 | 221 | static double |
michael@0 | 222 | Percent(size_t part, size_t whole) |
michael@0 | 223 | { |
michael@0 | 224 | return (whole == 0) ? 0 : 100 * (double)part / whole; |
michael@0 | 225 | } |
michael@0 | 226 | |
michael@0 | 227 | // Commifies the number and prepends a '~' if requested. Best used with |
michael@0 | 228 | // |kBufLen| and |gBuf[1234]|, because they should be big enough for any number |
michael@0 | 229 | // we'll see. |
michael@0 | 230 | static char* |
michael@0 | 231 | Show(size_t n, char* buf, size_t buflen, bool addTilde = false) |
michael@0 | 232 | { |
michael@0 | 233 | int nc = 0, i = 0, lasti = buflen - 2; |
michael@0 | 234 | buf[lasti + 1] = '\0'; |
michael@0 | 235 | if (n == 0) { |
michael@0 | 236 | buf[lasti - i] = '0'; |
michael@0 | 237 | i++; |
michael@0 | 238 | } else { |
michael@0 | 239 | while (n > 0) { |
michael@0 | 240 | if (((i - nc) % 3) == 0 && i != 0) { |
michael@0 | 241 | buf[lasti - i] = ','; |
michael@0 | 242 | i++; |
michael@0 | 243 | nc++; |
michael@0 | 244 | } |
michael@0 | 245 | buf[lasti - i] = static_cast<char>((n % 10) + '0'); |
michael@0 | 246 | i++; |
michael@0 | 247 | n /= 10; |
michael@0 | 248 | } |
michael@0 | 249 | } |
michael@0 | 250 | int firstCharIndex = lasti - i + 1; |
michael@0 | 251 | |
michael@0 | 252 | if (addTilde) { |
michael@0 | 253 | firstCharIndex--; |
michael@0 | 254 | buf[firstCharIndex] = '~'; |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | MOZ_ASSERT(firstCharIndex >= 0); |
michael@0 | 258 | return &buf[firstCharIndex]; |
michael@0 | 259 | } |
michael@0 | 260 | |
michael@0 | 261 | static const char* |
michael@0 | 262 | Plural(size_t aN) |
michael@0 | 263 | { |
michael@0 | 264 | return aN == 1 ? "" : "s"; |
michael@0 | 265 | } |
michael@0 | 266 | |
michael@0 | 267 | // Used by calls to Show(). |
michael@0 | 268 | static const size_t kBufLen = 64; |
michael@0 | 269 | static char gBuf1[kBufLen]; |
michael@0 | 270 | static char gBuf2[kBufLen]; |
michael@0 | 271 | static char gBuf3[kBufLen]; |
michael@0 | 272 | static char gBuf4[kBufLen]; |
michael@0 | 273 | |
michael@0 | 274 | //--------------------------------------------------------------------------- |
michael@0 | 275 | // Options (Part 1) |
michael@0 | 276 | //--------------------------------------------------------------------------- |
michael@0 | 277 | |
michael@0 | 278 | class Options |
michael@0 | 279 | { |
michael@0 | 280 | template <typename T> |
michael@0 | 281 | struct NumOption |
michael@0 | 282 | { |
michael@0 | 283 | const T mDefault; |
michael@0 | 284 | const T mMax; |
michael@0 | 285 | T mActual; |
michael@0 | 286 | NumOption(T aDefault, T aMax) |
michael@0 | 287 | : mDefault(aDefault), mMax(aMax), mActual(aDefault) |
michael@0 | 288 | {} |
michael@0 | 289 | }; |
michael@0 | 290 | |
michael@0 | 291 | enum Mode { |
michael@0 | 292 | Normal, // run normally |
michael@0 | 293 | Test, // do some basic correctness tests |
michael@0 | 294 | Stress // do some performance stress tests |
michael@0 | 295 | }; |
michael@0 | 296 | |
michael@0 | 297 | char* mDMDEnvVar; // a saved copy, for printing during Dump() |
michael@0 | 298 | |
michael@0 | 299 | NumOption<size_t> mSampleBelowSize; |
michael@0 | 300 | NumOption<uint32_t> mMaxFrames; |
michael@0 | 301 | NumOption<uint32_t> mMaxRecords; |
michael@0 | 302 | Mode mMode; |
michael@0 | 303 | |
michael@0 | 304 | void BadArg(const char* aArg); |
michael@0 | 305 | static const char* ValueIfMatch(const char* aArg, const char* aOptionName); |
michael@0 | 306 | static bool GetLong(const char* aArg, const char* aOptionName, |
michael@0 | 307 | long aMin, long aMax, long* aN); |
michael@0 | 308 | |
michael@0 | 309 | public: |
michael@0 | 310 | Options(const char* aDMDEnvVar); |
michael@0 | 311 | |
michael@0 | 312 | const char* DMDEnvVar() const { return mDMDEnvVar; } |
michael@0 | 313 | |
michael@0 | 314 | size_t SampleBelowSize() const { return mSampleBelowSize.mActual; } |
michael@0 | 315 | size_t MaxFrames() const { return mMaxFrames.mActual; } |
michael@0 | 316 | size_t MaxRecords() const { return mMaxRecords.mActual; } |
michael@0 | 317 | |
michael@0 | 318 | void SetSampleBelowSize(size_t aN) { mSampleBelowSize.mActual = aN; } |
michael@0 | 319 | |
michael@0 | 320 | bool IsTestMode() const { return mMode == Test; } |
michael@0 | 321 | bool IsStressMode() const { return mMode == Stress; } |
michael@0 | 322 | }; |
michael@0 | 323 | |
michael@0 | 324 | static Options *gOptions; |
michael@0 | 325 | |
michael@0 | 326 | //--------------------------------------------------------------------------- |
michael@0 | 327 | // The global lock |
michael@0 | 328 | //--------------------------------------------------------------------------- |
michael@0 | 329 | |
michael@0 | 330 | // MutexBase implements the platform-specific parts of a mutex. |
michael@0 | 331 | |
michael@0 | 332 | #ifdef XP_WIN |
michael@0 | 333 | |
michael@0 | 334 | class MutexBase |
michael@0 | 335 | { |
michael@0 | 336 | CRITICAL_SECTION mCS; |
michael@0 | 337 | |
michael@0 | 338 | DISALLOW_COPY_AND_ASSIGN(MutexBase); |
michael@0 | 339 | |
michael@0 | 340 | public: |
michael@0 | 341 | MutexBase() |
michael@0 | 342 | { |
michael@0 | 343 | InitializeCriticalSection(&mCS); |
michael@0 | 344 | } |
michael@0 | 345 | |
michael@0 | 346 | ~MutexBase() |
michael@0 | 347 | { |
michael@0 | 348 | DeleteCriticalSection(&mCS); |
michael@0 | 349 | } |
michael@0 | 350 | |
michael@0 | 351 | void Lock() |
michael@0 | 352 | { |
michael@0 | 353 | EnterCriticalSection(&mCS); |
michael@0 | 354 | } |
michael@0 | 355 | |
michael@0 | 356 | void Unlock() |
michael@0 | 357 | { |
michael@0 | 358 | LeaveCriticalSection(&mCS); |
michael@0 | 359 | } |
michael@0 | 360 | }; |
michael@0 | 361 | |
michael@0 | 362 | #else |
michael@0 | 363 | |
michael@0 | 364 | #include <pthread.h> |
michael@0 | 365 | #include <sys/types.h> |
michael@0 | 366 | |
michael@0 | 367 | class MutexBase |
michael@0 | 368 | { |
michael@0 | 369 | pthread_mutex_t mMutex; |
michael@0 | 370 | |
michael@0 | 371 | DISALLOW_COPY_AND_ASSIGN(MutexBase); |
michael@0 | 372 | |
michael@0 | 373 | public: |
michael@0 | 374 | MutexBase() |
michael@0 | 375 | { |
michael@0 | 376 | pthread_mutex_init(&mMutex, nullptr); |
michael@0 | 377 | } |
michael@0 | 378 | |
michael@0 | 379 | void Lock() |
michael@0 | 380 | { |
michael@0 | 381 | pthread_mutex_lock(&mMutex); |
michael@0 | 382 | } |
michael@0 | 383 | |
michael@0 | 384 | void Unlock() |
michael@0 | 385 | { |
michael@0 | 386 | pthread_mutex_unlock(&mMutex); |
michael@0 | 387 | } |
michael@0 | 388 | }; |
michael@0 | 389 | |
michael@0 | 390 | #endif |
michael@0 | 391 | |
michael@0 | 392 | class Mutex : private MutexBase |
michael@0 | 393 | { |
michael@0 | 394 | bool mIsLocked; |
michael@0 | 395 | |
michael@0 | 396 | DISALLOW_COPY_AND_ASSIGN(Mutex); |
michael@0 | 397 | |
michael@0 | 398 | public: |
michael@0 | 399 | Mutex() |
michael@0 | 400 | : mIsLocked(false) |
michael@0 | 401 | {} |
michael@0 | 402 | |
michael@0 | 403 | void Lock() |
michael@0 | 404 | { |
michael@0 | 405 | MutexBase::Lock(); |
michael@0 | 406 | MOZ_ASSERT(!mIsLocked); |
michael@0 | 407 | mIsLocked = true; |
michael@0 | 408 | } |
michael@0 | 409 | |
michael@0 | 410 | void Unlock() |
michael@0 | 411 | { |
michael@0 | 412 | MOZ_ASSERT(mIsLocked); |
michael@0 | 413 | mIsLocked = false; |
michael@0 | 414 | MutexBase::Unlock(); |
michael@0 | 415 | } |
michael@0 | 416 | |
michael@0 | 417 | bool IsLocked() |
michael@0 | 418 | { |
michael@0 | 419 | return mIsLocked; |
michael@0 | 420 | } |
michael@0 | 421 | }; |
michael@0 | 422 | |
michael@0 | 423 | // This lock must be held while manipulating global state, such as |
michael@0 | 424 | // gStackTraceTable, gBlockTable, etc. |
michael@0 | 425 | static Mutex* gStateLock = nullptr; |
michael@0 | 426 | |
michael@0 | 427 | class AutoLockState |
michael@0 | 428 | { |
michael@0 | 429 | DISALLOW_COPY_AND_ASSIGN(AutoLockState); |
michael@0 | 430 | |
michael@0 | 431 | public: |
michael@0 | 432 | AutoLockState() |
michael@0 | 433 | { |
michael@0 | 434 | gStateLock->Lock(); |
michael@0 | 435 | } |
michael@0 | 436 | ~AutoLockState() |
michael@0 | 437 | { |
michael@0 | 438 | gStateLock->Unlock(); |
michael@0 | 439 | } |
michael@0 | 440 | }; |
michael@0 | 441 | |
michael@0 | 442 | class AutoUnlockState |
michael@0 | 443 | { |
michael@0 | 444 | DISALLOW_COPY_AND_ASSIGN(AutoUnlockState); |
michael@0 | 445 | |
michael@0 | 446 | public: |
michael@0 | 447 | AutoUnlockState() |
michael@0 | 448 | { |
michael@0 | 449 | gStateLock->Unlock(); |
michael@0 | 450 | } |
michael@0 | 451 | ~AutoUnlockState() |
michael@0 | 452 | { |
michael@0 | 453 | gStateLock->Lock(); |
michael@0 | 454 | } |
michael@0 | 455 | }; |
michael@0 | 456 | |
michael@0 | 457 | //--------------------------------------------------------------------------- |
michael@0 | 458 | // Thread-local storage and blocking of intercepts |
michael@0 | 459 | //--------------------------------------------------------------------------- |
michael@0 | 460 | |
michael@0 | 461 | #ifdef XP_WIN |
michael@0 | 462 | |
michael@0 | 463 | #define DMD_TLS_INDEX_TYPE DWORD |
michael@0 | 464 | #define DMD_CREATE_TLS_INDEX(i_) do { \ |
michael@0 | 465 | (i_) = TlsAlloc(); \ |
michael@0 | 466 | } while (0) |
michael@0 | 467 | #define DMD_DESTROY_TLS_INDEX(i_) TlsFree((i_)) |
michael@0 | 468 | #define DMD_GET_TLS_DATA(i_) TlsGetValue((i_)) |
michael@0 | 469 | #define DMD_SET_TLS_DATA(i_, v_) TlsSetValue((i_), (v_)) |
michael@0 | 470 | |
michael@0 | 471 | #else |
michael@0 | 472 | |
michael@0 | 473 | #include <pthread.h> |
michael@0 | 474 | |
michael@0 | 475 | #define DMD_TLS_INDEX_TYPE pthread_key_t |
michael@0 | 476 | #define DMD_CREATE_TLS_INDEX(i_) pthread_key_create(&(i_), nullptr) |
michael@0 | 477 | #define DMD_DESTROY_TLS_INDEX(i_) pthread_key_delete((i_)) |
michael@0 | 478 | #define DMD_GET_TLS_DATA(i_) pthread_getspecific((i_)) |
michael@0 | 479 | #define DMD_SET_TLS_DATA(i_, v_) pthread_setspecific((i_), (v_)) |
michael@0 | 480 | |
michael@0 | 481 | #endif |
michael@0 | 482 | |
michael@0 | 483 | static DMD_TLS_INDEX_TYPE gTlsIndex; |
michael@0 | 484 | |
michael@0 | 485 | class Thread |
michael@0 | 486 | { |
michael@0 | 487 | // Required for allocation via InfallibleAllocPolicy::new_. |
michael@0 | 488 | friend class InfallibleAllocPolicy; |
michael@0 | 489 | |
michael@0 | 490 | // When true, this blocks intercepts, which allows malloc interception |
michael@0 | 491 | // functions to themselves call malloc. (Nb: for direct calls to malloc we |
michael@0 | 492 | // can just use InfallibleAllocPolicy::{malloc_,new_}, but we sometimes |
michael@0 | 493 | // indirectly call vanilla malloc via functions like NS_StackWalk.) |
michael@0 | 494 | bool mBlockIntercepts; |
michael@0 | 495 | |
michael@0 | 496 | Thread() |
michael@0 | 497 | : mBlockIntercepts(false) |
michael@0 | 498 | {} |
michael@0 | 499 | |
michael@0 | 500 | DISALLOW_COPY_AND_ASSIGN(Thread); |
michael@0 | 501 | |
michael@0 | 502 | public: |
michael@0 | 503 | static Thread* Fetch(); |
michael@0 | 504 | |
michael@0 | 505 | bool BlockIntercepts() |
michael@0 | 506 | { |
michael@0 | 507 | MOZ_ASSERT(!mBlockIntercepts); |
michael@0 | 508 | return mBlockIntercepts = true; |
michael@0 | 509 | } |
michael@0 | 510 | |
michael@0 | 511 | bool UnblockIntercepts() |
michael@0 | 512 | { |
michael@0 | 513 | MOZ_ASSERT(mBlockIntercepts); |
michael@0 | 514 | return mBlockIntercepts = false; |
michael@0 | 515 | } |
michael@0 | 516 | |
michael@0 | 517 | bool InterceptsAreBlocked() const |
michael@0 | 518 | { |
michael@0 | 519 | return mBlockIntercepts; |
michael@0 | 520 | } |
michael@0 | 521 | }; |
michael@0 | 522 | |
michael@0 | 523 | /* static */ Thread* |
michael@0 | 524 | Thread::Fetch() |
michael@0 | 525 | { |
michael@0 | 526 | Thread* t = static_cast<Thread*>(DMD_GET_TLS_DATA(gTlsIndex)); |
michael@0 | 527 | |
michael@0 | 528 | if (MOZ_UNLIKELY(!t)) { |
michael@0 | 529 | // This memory is never freed, even if the thread dies. It's a leak, but |
michael@0 | 530 | // only a tiny one. |
michael@0 | 531 | t = InfallibleAllocPolicy::new_<Thread>(); |
michael@0 | 532 | DMD_SET_TLS_DATA(gTlsIndex, t); |
michael@0 | 533 | } |
michael@0 | 534 | |
michael@0 | 535 | return t; |
michael@0 | 536 | } |
michael@0 | 537 | |
michael@0 | 538 | // An object of this class must be created (on the stack) before running any |
michael@0 | 539 | // code that might allocate. |
michael@0 | 540 | class AutoBlockIntercepts |
michael@0 | 541 | { |
michael@0 | 542 | Thread* const mT; |
michael@0 | 543 | |
michael@0 | 544 | DISALLOW_COPY_AND_ASSIGN(AutoBlockIntercepts); |
michael@0 | 545 | |
michael@0 | 546 | public: |
michael@0 | 547 | AutoBlockIntercepts(Thread* aT) |
michael@0 | 548 | : mT(aT) |
michael@0 | 549 | { |
michael@0 | 550 | mT->BlockIntercepts(); |
michael@0 | 551 | } |
michael@0 | 552 | ~AutoBlockIntercepts() |
michael@0 | 553 | { |
michael@0 | 554 | MOZ_ASSERT(mT->InterceptsAreBlocked()); |
michael@0 | 555 | mT->UnblockIntercepts(); |
michael@0 | 556 | } |
michael@0 | 557 | }; |
michael@0 | 558 | |
michael@0 | 559 | //--------------------------------------------------------------------------- |
michael@0 | 560 | // Location service |
michael@0 | 561 | //--------------------------------------------------------------------------- |
michael@0 | 562 | |
michael@0 | 563 | // This class is used to print details about code locations. |
michael@0 | 564 | class LocationService |
michael@0 | 565 | { |
michael@0 | 566 | // WriteLocation() is the key function in this class. It's basically a |
michael@0 | 567 | // wrapper around NS_DescribeCodeAddress. |
michael@0 | 568 | // |
michael@0 | 569 | // However, NS_DescribeCodeAddress is very slow on some platforms, and we |
michael@0 | 570 | // have lots of repeated (i.e. same PC) calls to it. So we do some caching |
michael@0 | 571 | // of results. Each cached result includes two strings (|mFunction| and |
michael@0 | 572 | // |mLibrary|), so we also optimize them for space in the following ways. |
michael@0 | 573 | // |
michael@0 | 574 | // - The number of distinct library names is small, e.g. a few dozen. There |
michael@0 | 575 | // is lots of repetition, especially of libxul. So we intern them in their |
michael@0 | 576 | // own table, which saves space over duplicating them for each cache entry. |
michael@0 | 577 | // |
michael@0 | 578 | // - The number of distinct function names is much higher, so we duplicate |
michael@0 | 579 | // them in each cache entry. That's more space-efficient than interning |
michael@0 | 580 | // because entries containing single-occurrence function names are quickly |
michael@0 | 581 | // overwritten, and their copies released. In addition, empty function |
michael@0 | 582 | // names are common, so we use nullptr to represent them compactly. |
michael@0 | 583 | |
michael@0 | 584 | struct StringHasher |
michael@0 | 585 | { |
michael@0 | 586 | typedef const char* Lookup; |
michael@0 | 587 | |
michael@0 | 588 | static uint32_t hash(const char* const& aS) |
michael@0 | 589 | { |
michael@0 | 590 | return HashString(aS); |
michael@0 | 591 | } |
michael@0 | 592 | |
michael@0 | 593 | static bool match(const char* const& aA, const char* const& aB) |
michael@0 | 594 | { |
michael@0 | 595 | return strcmp(aA, aB) == 0; |
michael@0 | 596 | } |
michael@0 | 597 | }; |
michael@0 | 598 | |
michael@0 | 599 | typedef js::HashSet<const char*, StringHasher, InfallibleAllocPolicy> |
michael@0 | 600 | StringTable; |
michael@0 | 601 | |
michael@0 | 602 | StringTable mLibraryStrings; |
michael@0 | 603 | |
michael@0 | 604 | struct Entry |
michael@0 | 605 | { |
michael@0 | 606 | const void* mPc; |
michael@0 | 607 | char* mFunction; // owned by the Entry; may be null |
michael@0 | 608 | const char* mLibrary; // owned by mLibraryStrings; never null |
michael@0 | 609 | // in a non-empty entry is in use |
michael@0 | 610 | ptrdiff_t mLOffset; |
michael@0 | 611 | char* mFileName; // owned by the Entry; may be null |
michael@0 | 612 | uint32_t mLineNo:31; |
michael@0 | 613 | uint32_t mInUse:1; // is the entry used? |
michael@0 | 614 | |
michael@0 | 615 | Entry() |
michael@0 | 616 | : mPc(0), mFunction(nullptr), mLibrary(nullptr), mLOffset(0), mFileName(nullptr), mLineNo(0), mInUse(0) |
michael@0 | 617 | {} |
michael@0 | 618 | |
michael@0 | 619 | ~Entry() |
michael@0 | 620 | { |
michael@0 | 621 | // We don't free mLibrary because it's externally owned. |
michael@0 | 622 | InfallibleAllocPolicy::free_(mFunction); |
michael@0 | 623 | InfallibleAllocPolicy::free_(mFileName); |
michael@0 | 624 | } |
michael@0 | 625 | |
michael@0 | 626 | void Replace(const void* aPc, const char* aFunction, |
michael@0 | 627 | const char* aLibrary, ptrdiff_t aLOffset, |
michael@0 | 628 | const char* aFileName, unsigned long aLineNo) |
michael@0 | 629 | { |
michael@0 | 630 | mPc = aPc; |
michael@0 | 631 | |
michael@0 | 632 | // Convert "" to nullptr. Otherwise, make a copy of the name. |
michael@0 | 633 | InfallibleAllocPolicy::free_(mFunction); |
michael@0 | 634 | mFunction = |
michael@0 | 635 | !aFunction[0] ? nullptr : InfallibleAllocPolicy::strdup_(aFunction); |
michael@0 | 636 | InfallibleAllocPolicy::free_(mFileName); |
michael@0 | 637 | mFileName = |
michael@0 | 638 | !aFileName[0] ? nullptr : InfallibleAllocPolicy::strdup_(aFileName); |
michael@0 | 639 | |
michael@0 | 640 | |
michael@0 | 641 | mLibrary = aLibrary; |
michael@0 | 642 | mLOffset = aLOffset; |
michael@0 | 643 | mLineNo = aLineNo; |
michael@0 | 644 | |
michael@0 | 645 | mInUse = 1; |
michael@0 | 646 | } |
michael@0 | 647 | |
michael@0 | 648 | size_t SizeOfExcludingThis() { |
michael@0 | 649 | // Don't measure mLibrary because it's externally owned. |
michael@0 | 650 | return MallocSizeOf(mFunction) + MallocSizeOf(mFileName); |
michael@0 | 651 | } |
michael@0 | 652 | }; |
michael@0 | 653 | |
michael@0 | 654 | // A direct-mapped cache. When doing a dump just after starting desktop |
michael@0 | 655 | // Firefox (which is similar to dumping after a longer-running session, |
michael@0 | 656 | // thanks to the limit on how many records we dump), a cache with 2^24 |
michael@0 | 657 | // entries (which approximates an infinite-entry cache) has a ~91% hit rate. |
michael@0 | 658 | // A cache with 2^12 entries has a ~83% hit rate, and takes up ~85 KiB (on |
michael@0 | 659 | // 32-bit platforms) or ~150 KiB (on 64-bit platforms). |
michael@0 | 660 | static const size_t kNumEntries = 1 << 12; |
michael@0 | 661 | static const size_t kMask = kNumEntries - 1; |
michael@0 | 662 | Entry mEntries[kNumEntries]; |
michael@0 | 663 | |
michael@0 | 664 | size_t mNumCacheHits; |
michael@0 | 665 | size_t mNumCacheMisses; |
michael@0 | 666 | |
michael@0 | 667 | public: |
michael@0 | 668 | LocationService() |
michael@0 | 669 | : mEntries(), mNumCacheHits(0), mNumCacheMisses(0) |
michael@0 | 670 | { |
michael@0 | 671 | (void)mLibraryStrings.init(64); |
michael@0 | 672 | } |
michael@0 | 673 | |
michael@0 | 674 | void WriteLocation(const Writer& aWriter, const void* aPc) |
michael@0 | 675 | { |
michael@0 | 676 | MOZ_ASSERT(gStateLock->IsLocked()); |
michael@0 | 677 | |
michael@0 | 678 | uint32_t index = HashGeneric(aPc) & kMask; |
michael@0 | 679 | MOZ_ASSERT(index < kNumEntries); |
michael@0 | 680 | Entry& entry = mEntries[index]; |
michael@0 | 681 | |
michael@0 | 682 | if (!entry.mInUse || entry.mPc != aPc) { |
michael@0 | 683 | mNumCacheMisses++; |
michael@0 | 684 | |
michael@0 | 685 | // NS_DescribeCodeAddress can (on Linux) acquire a lock inside |
michael@0 | 686 | // the shared library loader. Another thread might call malloc |
michael@0 | 687 | // while holding that lock (when loading a shared library). So |
michael@0 | 688 | // we have to exit gStateLock around this call. For details, see |
michael@0 | 689 | // https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3 |
michael@0 | 690 | nsCodeAddressDetails details; |
michael@0 | 691 | { |
michael@0 | 692 | AutoUnlockState unlock; |
michael@0 | 693 | (void)NS_DescribeCodeAddress(const_cast<void*>(aPc), &details); |
michael@0 | 694 | } |
michael@0 | 695 | |
michael@0 | 696 | // Intern the library name. |
michael@0 | 697 | const char* library = nullptr; |
michael@0 | 698 | StringTable::AddPtr p = mLibraryStrings.lookupForAdd(details.library); |
michael@0 | 699 | if (!p) { |
michael@0 | 700 | library = InfallibleAllocPolicy::strdup_(details.library); |
michael@0 | 701 | (void)mLibraryStrings.add(p, library); |
michael@0 | 702 | } else { |
michael@0 | 703 | library = *p; |
michael@0 | 704 | } |
michael@0 | 705 | |
michael@0 | 706 | entry.Replace(aPc, details.function, library, details.loffset, details.filename, details.lineno); |
michael@0 | 707 | |
michael@0 | 708 | } else { |
michael@0 | 709 | mNumCacheHits++; |
michael@0 | 710 | } |
michael@0 | 711 | |
michael@0 | 712 | MOZ_ASSERT(entry.mPc == aPc); |
michael@0 | 713 | |
michael@0 | 714 | uintptr_t entryPc = (uintptr_t)(entry.mPc); |
michael@0 | 715 | // Sometimes we get nothing useful. Just print "???" for the entire entry |
michael@0 | 716 | // so that fix-linux-stack.pl doesn't complain about an empty filename. |
michael@0 | 717 | if (!entry.mFunction && !entry.mLibrary[0] && entry.mLOffset == 0) { |
michael@0 | 718 | W(" ??? 0x%x\n", entryPc); |
michael@0 | 719 | } else { |
michael@0 | 720 | // Use "???" for unknown functions. |
michael@0 | 721 | const char* entryFunction = entry.mFunction ? entry.mFunction : "???"; |
michael@0 | 722 | if (entry.mFileName) { |
michael@0 | 723 | // On Windows we can get the filename and line number at runtime. |
michael@0 | 724 | W(" %s (%s:%lu) 0x%x\n", |
michael@0 | 725 | entryFunction, entry.mFileName, entry.mLineNo, entryPc); |
michael@0 | 726 | } else { |
michael@0 | 727 | // On Linux and Mac we cannot get the filename and line number at |
michael@0 | 728 | // runtime, so we print the offset in a form that fix-linux-stack.pl and |
michael@0 | 729 | // fix_macosx_stack.py can post-process. |
michael@0 | 730 | W(" %s[%s +0x%X] 0x%x\n", |
michael@0 | 731 | entryFunction, entry.mLibrary, entry.mLOffset, entryPc); |
michael@0 | 732 | } |
michael@0 | 733 | } |
michael@0 | 734 | } |
michael@0 | 735 | |
michael@0 | 736 | size_t SizeOfIncludingThis() |
michael@0 | 737 | { |
michael@0 | 738 | size_t n = MallocSizeOf(this); |
michael@0 | 739 | for (uint32_t i = 0; i < kNumEntries; i++) { |
michael@0 | 740 | n += mEntries[i].SizeOfExcludingThis(); |
michael@0 | 741 | } |
michael@0 | 742 | |
michael@0 | 743 | n += mLibraryStrings.sizeOfExcludingThis(MallocSizeOf); |
michael@0 | 744 | for (StringTable::Range r = mLibraryStrings.all(); |
michael@0 | 745 | !r.empty(); |
michael@0 | 746 | r.popFront()) { |
michael@0 | 747 | n += MallocSizeOf(r.front()); |
michael@0 | 748 | } |
michael@0 | 749 | |
michael@0 | 750 | return n; |
michael@0 | 751 | } |
michael@0 | 752 | |
michael@0 | 753 | size_t CacheCapacity() const { return kNumEntries; } |
michael@0 | 754 | |
michael@0 | 755 | size_t CacheCount() const |
michael@0 | 756 | { |
michael@0 | 757 | size_t n = 0; |
michael@0 | 758 | for (size_t i = 0; i < kNumEntries; i++) { |
michael@0 | 759 | if (mEntries[i].mInUse) { |
michael@0 | 760 | n++; |
michael@0 | 761 | } |
michael@0 | 762 | } |
michael@0 | 763 | return n; |
michael@0 | 764 | } |
michael@0 | 765 | |
michael@0 | 766 | size_t NumCacheHits() const { return mNumCacheHits; } |
michael@0 | 767 | size_t NumCacheMisses() const { return mNumCacheMisses; } |
michael@0 | 768 | }; |
michael@0 | 769 | |
michael@0 | 770 | //--------------------------------------------------------------------------- |
michael@0 | 771 | // Stack traces |
michael@0 | 772 | //--------------------------------------------------------------------------- |
michael@0 | 773 | |
michael@0 | 774 | class StackTrace |
michael@0 | 775 | { |
michael@0 | 776 | public: |
michael@0 | 777 | static const uint32_t MaxFrames = 24; |
michael@0 | 778 | |
michael@0 | 779 | private: |
michael@0 | 780 | uint32_t mLength; // The number of PCs. |
michael@0 | 781 | void* mPcs[MaxFrames]; // The PCs themselves. If --max-frames is less |
michael@0 | 782 | // than 24, this array is bigger than necessary, |
michael@0 | 783 | // but that case is unusual. |
michael@0 | 784 | |
michael@0 | 785 | public: |
michael@0 | 786 | StackTrace() : mLength(0) {} |
michael@0 | 787 | |
michael@0 | 788 | uint32_t Length() const { return mLength; } |
michael@0 | 789 | void* Pc(uint32_t i) const { MOZ_ASSERT(i < mLength); return mPcs[i]; } |
michael@0 | 790 | |
michael@0 | 791 | uint32_t Size() const { return mLength * sizeof(mPcs[0]); } |
michael@0 | 792 | |
michael@0 | 793 | // The stack trace returned by this function is interned in gStackTraceTable, |
michael@0 | 794 | // and so is immortal and unmovable. |
michael@0 | 795 | static const StackTrace* Get(Thread* aT); |
michael@0 | 796 | |
michael@0 | 797 | void Sort() |
michael@0 | 798 | { |
michael@0 | 799 | qsort(mPcs, mLength, sizeof(mPcs[0]), StackTrace::QsortCmp); |
michael@0 | 800 | } |
michael@0 | 801 | |
michael@0 | 802 | void Print(const Writer& aWriter, LocationService* aLocService) const; |
michael@0 | 803 | |
michael@0 | 804 | // Hash policy. |
michael@0 | 805 | |
michael@0 | 806 | typedef StackTrace* Lookup; |
michael@0 | 807 | |
michael@0 | 808 | static uint32_t hash(const StackTrace* const& aSt) |
michael@0 | 809 | { |
michael@0 | 810 | return mozilla::HashBytes(aSt->mPcs, aSt->Size()); |
michael@0 | 811 | } |
michael@0 | 812 | |
michael@0 | 813 | static bool match(const StackTrace* const& aA, |
michael@0 | 814 | const StackTrace* const& aB) |
michael@0 | 815 | { |
michael@0 | 816 | return aA->mLength == aB->mLength && |
michael@0 | 817 | memcmp(aA->mPcs, aB->mPcs, aA->Size()) == 0; |
michael@0 | 818 | } |
michael@0 | 819 | |
michael@0 | 820 | private: |
michael@0 | 821 | static void StackWalkCallback(void* aPc, void* aSp, void* aClosure) |
michael@0 | 822 | { |
michael@0 | 823 | StackTrace* st = (StackTrace*) aClosure; |
michael@0 | 824 | MOZ_ASSERT(st->mLength < MaxFrames); |
michael@0 | 825 | st->mPcs[st->mLength] = aPc; |
michael@0 | 826 | st->mLength++; |
michael@0 | 827 | } |
michael@0 | 828 | |
michael@0 | 829 | static int QsortCmp(const void* aA, const void* aB) |
michael@0 | 830 | { |
michael@0 | 831 | const void* const a = *static_cast<const void* const*>(aA); |
michael@0 | 832 | const void* const b = *static_cast<const void* const*>(aB); |
michael@0 | 833 | if (a < b) return -1; |
michael@0 | 834 | if (a > b) return 1; |
michael@0 | 835 | return 0; |
michael@0 | 836 | } |
michael@0 | 837 | }; |
michael@0 | 838 | |
michael@0 | 839 | typedef js::HashSet<StackTrace*, StackTrace, InfallibleAllocPolicy> |
michael@0 | 840 | StackTraceTable; |
michael@0 | 841 | static StackTraceTable* gStackTraceTable = nullptr; |
michael@0 | 842 | |
michael@0 | 843 | // We won't GC the stack trace table until it this many elements. |
michael@0 | 844 | static uint32_t gGCStackTraceTableWhenSizeExceeds = 4 * 1024; |
michael@0 | 845 | |
michael@0 | 846 | void |
michael@0 | 847 | StackTrace::Print(const Writer& aWriter, LocationService* aLocService) const |
michael@0 | 848 | { |
michael@0 | 849 | if (mLength == 0) { |
michael@0 | 850 | W(" (empty)\n"); // StackTrace::Get() must have failed |
michael@0 | 851 | return; |
michael@0 | 852 | } |
michael@0 | 853 | |
michael@0 | 854 | for (uint32_t i = 0; i < mLength; i++) { |
michael@0 | 855 | aLocService->WriteLocation(aWriter, Pc(i)); |
michael@0 | 856 | } |
michael@0 | 857 | } |
michael@0 | 858 | |
michael@0 | 859 | /* static */ const StackTrace* |
michael@0 | 860 | StackTrace::Get(Thread* aT) |
michael@0 | 861 | { |
michael@0 | 862 | MOZ_ASSERT(gStateLock->IsLocked()); |
michael@0 | 863 | MOZ_ASSERT(aT->InterceptsAreBlocked()); |
michael@0 | 864 | |
michael@0 | 865 | // On Windows, NS_StackWalk can acquire a lock from the shared library |
michael@0 | 866 | // loader. Another thread might call malloc while holding that lock (when |
michael@0 | 867 | // loading a shared library). So we can't be in gStateLock during the call |
michael@0 | 868 | // to NS_StackWalk. For details, see |
michael@0 | 869 | // https://bugzilla.mozilla.org/show_bug.cgi?id=374829#c8 |
michael@0 | 870 | // On Linux, something similar can happen; see bug 824340. |
michael@0 | 871 | // So let's just release it on all platforms. |
michael@0 | 872 | nsresult rv; |
michael@0 | 873 | StackTrace tmp; |
michael@0 | 874 | { |
michael@0 | 875 | AutoUnlockState unlock; |
michael@0 | 876 | uint32_t skipFrames = 2; |
michael@0 | 877 | rv = NS_StackWalk(StackWalkCallback, skipFrames, |
michael@0 | 878 | gOptions->MaxFrames(), &tmp, 0, nullptr); |
michael@0 | 879 | } |
michael@0 | 880 | |
michael@0 | 881 | if (rv == NS_OK) { |
michael@0 | 882 | // Handle the common case first. All is ok. Nothing to do. |
michael@0 | 883 | } else if (rv == NS_ERROR_NOT_IMPLEMENTED || rv == NS_ERROR_FAILURE) { |
michael@0 | 884 | tmp.mLength = 0; |
michael@0 | 885 | } else if (rv == NS_ERROR_UNEXPECTED) { |
michael@0 | 886 | // XXX: This |rv| only happens on Mac, and it indicates that we're handling |
michael@0 | 887 | // a call to malloc that happened inside a mutex-handling function. Any |
michael@0 | 888 | // attempt to create a semaphore (which can happen in printf) could |
michael@0 | 889 | // deadlock. |
michael@0 | 890 | // |
michael@0 | 891 | // However, the most complex thing DMD does after Get() returns is to put |
michael@0 | 892 | // something in a hash table, which might call |
michael@0 | 893 | // InfallibleAllocPolicy::malloc_. I'm not yet sure if this needs special |
michael@0 | 894 | // handling, hence the forced abort. Sorry. If you hit this, please file |
michael@0 | 895 | // a bug and CC nnethercote. |
michael@0 | 896 | MOZ_CRASH(); |
michael@0 | 897 | } else { |
michael@0 | 898 | MOZ_CRASH(); // should be impossible |
michael@0 | 899 | } |
michael@0 | 900 | |
michael@0 | 901 | StackTraceTable::AddPtr p = gStackTraceTable->lookupForAdd(&tmp); |
michael@0 | 902 | if (!p) { |
michael@0 | 903 | StackTrace* stnew = InfallibleAllocPolicy::new_<StackTrace>(tmp); |
michael@0 | 904 | (void)gStackTraceTable->add(p, stnew); |
michael@0 | 905 | } |
michael@0 | 906 | return *p; |
michael@0 | 907 | } |
michael@0 | 908 | |
michael@0 | 909 | //--------------------------------------------------------------------------- |
michael@0 | 910 | // Heap blocks |
michael@0 | 911 | //--------------------------------------------------------------------------- |
michael@0 | 912 | |
michael@0 | 913 | // This class combines a 2-byte-aligned pointer (i.e. one whose bottom bit |
michael@0 | 914 | // is zero) with a 1-bit tag. |
michael@0 | 915 | // |
michael@0 | 916 | // |T| is the pointer type, e.g. |int*|, not the pointed-to type. This makes |
michael@0 | 917 | // is easier to have const pointers, e.g. |TaggedPtr<const int*>|. |
michael@0 | 918 | template <typename T> |
michael@0 | 919 | class TaggedPtr |
michael@0 | 920 | { |
michael@0 | 921 | union |
michael@0 | 922 | { |
michael@0 | 923 | T mPtr; |
michael@0 | 924 | uintptr_t mUint; |
michael@0 | 925 | }; |
michael@0 | 926 | |
michael@0 | 927 | static const uintptr_t kTagMask = uintptr_t(0x1); |
michael@0 | 928 | static const uintptr_t kPtrMask = ~kTagMask; |
michael@0 | 929 | |
michael@0 | 930 | static bool IsTwoByteAligned(T aPtr) |
michael@0 | 931 | { |
michael@0 | 932 | return (uintptr_t(aPtr) & kTagMask) == 0; |
michael@0 | 933 | } |
michael@0 | 934 | |
michael@0 | 935 | public: |
michael@0 | 936 | TaggedPtr() |
michael@0 | 937 | : mPtr(nullptr) |
michael@0 | 938 | {} |
michael@0 | 939 | |
michael@0 | 940 | TaggedPtr(T aPtr, bool aBool) |
michael@0 | 941 | : mPtr(aPtr) |
michael@0 | 942 | { |
michael@0 | 943 | MOZ_ASSERT(IsTwoByteAligned(aPtr)); |
michael@0 | 944 | uintptr_t tag = uintptr_t(aBool); |
michael@0 | 945 | MOZ_ASSERT(tag <= kTagMask); |
michael@0 | 946 | mUint |= (tag & kTagMask); |
michael@0 | 947 | } |
michael@0 | 948 | |
michael@0 | 949 | void Set(T aPtr, bool aBool) |
michael@0 | 950 | { |
michael@0 | 951 | MOZ_ASSERT(IsTwoByteAligned(aPtr)); |
michael@0 | 952 | mPtr = aPtr; |
michael@0 | 953 | uintptr_t tag = uintptr_t(aBool); |
michael@0 | 954 | MOZ_ASSERT(tag <= kTagMask); |
michael@0 | 955 | mUint |= (tag & kTagMask); |
michael@0 | 956 | } |
michael@0 | 957 | |
michael@0 | 958 | T Ptr() const { return reinterpret_cast<T>(mUint & kPtrMask); } |
michael@0 | 959 | |
michael@0 | 960 | bool Tag() const { return bool(mUint & kTagMask); } |
michael@0 | 961 | }; |
michael@0 | 962 | |
michael@0 | 963 | // A live heap block. |
michael@0 | 964 | class Block |
michael@0 | 965 | { |
michael@0 | 966 | const void* mPtr; |
michael@0 | 967 | const size_t mReqSize; // size requested |
michael@0 | 968 | |
michael@0 | 969 | // Ptr: |mAllocStackTrace| - stack trace where this block was allocated. |
michael@0 | 970 | // Tag bit 0: |mSampled| - was this block sampled? (if so, slop == 0). |
michael@0 | 971 | TaggedPtr<const StackTrace* const> |
michael@0 | 972 | mAllocStackTrace_mSampled; |
michael@0 | 973 | |
michael@0 | 974 | // This array has two elements because we record at most two reports of a |
michael@0 | 975 | // block. |
michael@0 | 976 | // - Ptr: |mReportStackTrace| - stack trace where this block was reported. |
michael@0 | 977 | // nullptr if not reported. |
michael@0 | 978 | // - Tag bit 0: |mReportedOnAlloc| - was the block reported immediately on |
michael@0 | 979 | // allocation? If so, DMD must not clear the report at the end of Dump(). |
michael@0 | 980 | // Only relevant if |mReportStackTrace| is non-nullptr. |
michael@0 | 981 | // |
michael@0 | 982 | // |mPtr| is used as the key in BlockTable, so it's ok for this member |
michael@0 | 983 | // to be |mutable|. |
michael@0 | 984 | mutable TaggedPtr<const StackTrace*> mReportStackTrace_mReportedOnAlloc[2]; |
michael@0 | 985 | |
michael@0 | 986 | public: |
michael@0 | 987 | Block(const void* aPtr, size_t aReqSize, const StackTrace* aAllocStackTrace, |
michael@0 | 988 | bool aSampled) |
michael@0 | 989 | : mPtr(aPtr), |
michael@0 | 990 | mReqSize(aReqSize), |
michael@0 | 991 | mAllocStackTrace_mSampled(aAllocStackTrace, aSampled), |
michael@0 | 992 | mReportStackTrace_mReportedOnAlloc() // all fields get zeroed |
michael@0 | 993 | { |
michael@0 | 994 | MOZ_ASSERT(aAllocStackTrace); |
michael@0 | 995 | } |
michael@0 | 996 | |
michael@0 | 997 | size_t ReqSize() const { return mReqSize; } |
michael@0 | 998 | |
michael@0 | 999 | // Sampled blocks always have zero slop. |
michael@0 | 1000 | size_t SlopSize() const |
michael@0 | 1001 | { |
michael@0 | 1002 | return IsSampled() ? 0 : MallocSizeOf(mPtr) - mReqSize; |
michael@0 | 1003 | } |
michael@0 | 1004 | |
michael@0 | 1005 | size_t UsableSize() const |
michael@0 | 1006 | { |
michael@0 | 1007 | return IsSampled() ? mReqSize : MallocSizeOf(mPtr); |
michael@0 | 1008 | } |
michael@0 | 1009 | |
michael@0 | 1010 | bool IsSampled() const |
michael@0 | 1011 | { |
michael@0 | 1012 | return mAllocStackTrace_mSampled.Tag(); |
michael@0 | 1013 | } |
michael@0 | 1014 | |
michael@0 | 1015 | const StackTrace* AllocStackTrace() const |
michael@0 | 1016 | { |
michael@0 | 1017 | return mAllocStackTrace_mSampled.Ptr(); |
michael@0 | 1018 | } |
michael@0 | 1019 | |
michael@0 | 1020 | const StackTrace* ReportStackTrace1() const { |
michael@0 | 1021 | return mReportStackTrace_mReportedOnAlloc[0].Ptr(); |
michael@0 | 1022 | } |
michael@0 | 1023 | |
michael@0 | 1024 | const StackTrace* ReportStackTrace2() const { |
michael@0 | 1025 | return mReportStackTrace_mReportedOnAlloc[1].Ptr(); |
michael@0 | 1026 | } |
michael@0 | 1027 | |
michael@0 | 1028 | bool ReportedOnAlloc1() const { |
michael@0 | 1029 | return mReportStackTrace_mReportedOnAlloc[0].Tag(); |
michael@0 | 1030 | } |
michael@0 | 1031 | |
michael@0 | 1032 | bool ReportedOnAlloc2() const { |
michael@0 | 1033 | return mReportStackTrace_mReportedOnAlloc[1].Tag(); |
michael@0 | 1034 | } |
michael@0 | 1035 | |
michael@0 | 1036 | uint32_t NumReports() const { |
michael@0 | 1037 | if (ReportStackTrace2()) { |
michael@0 | 1038 | MOZ_ASSERT(ReportStackTrace1()); |
michael@0 | 1039 | return 2; |
michael@0 | 1040 | } |
michael@0 | 1041 | if (ReportStackTrace1()) { |
michael@0 | 1042 | return 1; |
michael@0 | 1043 | } |
michael@0 | 1044 | return 0; |
michael@0 | 1045 | } |
michael@0 | 1046 | |
michael@0 | 1047 | // This is |const| thanks to the |mutable| fields above. |
michael@0 | 1048 | void Report(Thread* aT, bool aReportedOnAlloc) const |
michael@0 | 1049 | { |
michael@0 | 1050 | // We don't bother recording reports after the 2nd one. |
michael@0 | 1051 | uint32_t numReports = NumReports(); |
michael@0 | 1052 | if (numReports < 2) { |
michael@0 | 1053 | mReportStackTrace_mReportedOnAlloc[numReports].Set(StackTrace::Get(aT), |
michael@0 | 1054 | aReportedOnAlloc); |
michael@0 | 1055 | } |
michael@0 | 1056 | } |
michael@0 | 1057 | |
michael@0 | 1058 | void UnreportIfNotReportedOnAlloc() const |
michael@0 | 1059 | { |
michael@0 | 1060 | if (!ReportedOnAlloc1() && !ReportedOnAlloc2()) { |
michael@0 | 1061 | mReportStackTrace_mReportedOnAlloc[0].Set(nullptr, 0); |
michael@0 | 1062 | mReportStackTrace_mReportedOnAlloc[1].Set(nullptr, 0); |
michael@0 | 1063 | |
michael@0 | 1064 | } else if (!ReportedOnAlloc1() && ReportedOnAlloc2()) { |
michael@0 | 1065 | // Shift the 2nd report down to the 1st one. |
michael@0 | 1066 | mReportStackTrace_mReportedOnAlloc[0] = |
michael@0 | 1067 | mReportStackTrace_mReportedOnAlloc[1]; |
michael@0 | 1068 | mReportStackTrace_mReportedOnAlloc[1].Set(nullptr, 0); |
michael@0 | 1069 | |
michael@0 | 1070 | } else if (ReportedOnAlloc1() && !ReportedOnAlloc2()) { |
michael@0 | 1071 | mReportStackTrace_mReportedOnAlloc[1].Set(nullptr, 0); |
michael@0 | 1072 | } |
michael@0 | 1073 | } |
michael@0 | 1074 | |
michael@0 | 1075 | // Hash policy. |
michael@0 | 1076 | |
michael@0 | 1077 | typedef const void* Lookup; |
michael@0 | 1078 | |
michael@0 | 1079 | static uint32_t hash(const void* const& aPtr) |
michael@0 | 1080 | { |
michael@0 | 1081 | return mozilla::HashGeneric(aPtr); |
michael@0 | 1082 | } |
michael@0 | 1083 | |
michael@0 | 1084 | static bool match(const Block& aB, const void* const& aPtr) |
michael@0 | 1085 | { |
michael@0 | 1086 | return aB.mPtr == aPtr; |
michael@0 | 1087 | } |
michael@0 | 1088 | }; |
michael@0 | 1089 | |
michael@0 | 1090 | typedef js::HashSet<Block, Block, InfallibleAllocPolicy> BlockTable; |
michael@0 | 1091 | static BlockTable* gBlockTable = nullptr; |
michael@0 | 1092 | |
michael@0 | 1093 | typedef js::HashSet<const StackTrace*, js::DefaultHasher<const StackTrace*>, |
michael@0 | 1094 | InfallibleAllocPolicy> |
michael@0 | 1095 | StackTraceSet; |
michael@0 | 1096 | |
michael@0 | 1097 | // Add a pointer to each live stack trace into the given StackTraceSet. (A |
michael@0 | 1098 | // stack trace is live if it's used by one of the live blocks.) |
michael@0 | 1099 | static void |
michael@0 | 1100 | GatherUsedStackTraces(StackTraceSet& aStackTraces) |
michael@0 | 1101 | { |
michael@0 | 1102 | MOZ_ASSERT(gStateLock->IsLocked()); |
michael@0 | 1103 | MOZ_ASSERT(Thread::Fetch()->InterceptsAreBlocked()); |
michael@0 | 1104 | |
michael@0 | 1105 | aStackTraces.finish(); |
michael@0 | 1106 | aStackTraces.init(1024); |
michael@0 | 1107 | |
michael@0 | 1108 | for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) { |
michael@0 | 1109 | const Block& b = r.front(); |
michael@0 | 1110 | aStackTraces.put(b.AllocStackTrace()); |
michael@0 | 1111 | aStackTraces.put(b.ReportStackTrace1()); |
michael@0 | 1112 | aStackTraces.put(b.ReportStackTrace2()); |
michael@0 | 1113 | } |
michael@0 | 1114 | |
michael@0 | 1115 | // Any of the stack traces added above may have been null. For the sake of |
michael@0 | 1116 | // cleanliness, don't leave the null pointer in the set. |
michael@0 | 1117 | aStackTraces.remove(nullptr); |
michael@0 | 1118 | } |
michael@0 | 1119 | |
michael@0 | 1120 | // Delete stack traces that we aren't using, and compact our hashtable. |
michael@0 | 1121 | static void |
michael@0 | 1122 | GCStackTraces() |
michael@0 | 1123 | { |
michael@0 | 1124 | MOZ_ASSERT(gStateLock->IsLocked()); |
michael@0 | 1125 | MOZ_ASSERT(Thread::Fetch()->InterceptsAreBlocked()); |
michael@0 | 1126 | |
michael@0 | 1127 | StackTraceSet usedStackTraces; |
michael@0 | 1128 | GatherUsedStackTraces(usedStackTraces); |
michael@0 | 1129 | |
michael@0 | 1130 | // Delete all unused stack traces from gStackTraceTable. The Enum destructor |
michael@0 | 1131 | // will automatically rehash and compact the table. |
michael@0 | 1132 | for (StackTraceTable::Enum e(*gStackTraceTable); |
michael@0 | 1133 | !e.empty(); |
michael@0 | 1134 | e.popFront()) { |
michael@0 | 1135 | StackTrace* const& st = e.front(); |
michael@0 | 1136 | |
michael@0 | 1137 | if (!usedStackTraces.has(st)) { |
michael@0 | 1138 | e.removeFront(); |
michael@0 | 1139 | InfallibleAllocPolicy::delete_(st); |
michael@0 | 1140 | } |
michael@0 | 1141 | } |
michael@0 | 1142 | |
michael@0 | 1143 | // Schedule a GC when we have twice as many stack traces as we had right after |
michael@0 | 1144 | // this GC finished. |
michael@0 | 1145 | gGCStackTraceTableWhenSizeExceeds = 2 * gStackTraceTable->count(); |
michael@0 | 1146 | } |
michael@0 | 1147 | |
michael@0 | 1148 | //--------------------------------------------------------------------------- |
michael@0 | 1149 | // malloc/free callbacks |
michael@0 | 1150 | //--------------------------------------------------------------------------- |
michael@0 | 1151 | |
michael@0 | 1152 | static size_t gSmallBlockActualSizeCounter = 0; |
michael@0 | 1153 | |
michael@0 | 1154 | static void |
michael@0 | 1155 | AllocCallback(void* aPtr, size_t aReqSize, Thread* aT) |
michael@0 | 1156 | { |
michael@0 | 1157 | MOZ_ASSERT(gIsDMDRunning); |
michael@0 | 1158 | |
michael@0 | 1159 | if (!aPtr) { |
michael@0 | 1160 | return; |
michael@0 | 1161 | } |
michael@0 | 1162 | |
michael@0 | 1163 | AutoLockState lock; |
michael@0 | 1164 | AutoBlockIntercepts block(aT); |
michael@0 | 1165 | |
michael@0 | 1166 | size_t actualSize = gMallocTable->malloc_usable_size(aPtr); |
michael@0 | 1167 | size_t sampleBelowSize = gOptions->SampleBelowSize(); |
michael@0 | 1168 | |
michael@0 | 1169 | if (actualSize < sampleBelowSize) { |
michael@0 | 1170 | // If this allocation is smaller than the sample-below size, increment the |
michael@0 | 1171 | // cumulative counter. Then, if that counter now exceeds the sample size, |
michael@0 | 1172 | // blame this allocation for |sampleBelowSize| bytes. This precludes the |
michael@0 | 1173 | // measurement of slop. |
michael@0 | 1174 | gSmallBlockActualSizeCounter += actualSize; |
michael@0 | 1175 | if (gSmallBlockActualSizeCounter >= sampleBelowSize) { |
michael@0 | 1176 | gSmallBlockActualSizeCounter -= sampleBelowSize; |
michael@0 | 1177 | |
michael@0 | 1178 | Block b(aPtr, sampleBelowSize, StackTrace::Get(aT), /* sampled */ true); |
michael@0 | 1179 | (void)gBlockTable->putNew(aPtr, b); |
michael@0 | 1180 | } |
michael@0 | 1181 | } else { |
michael@0 | 1182 | // If this block size is larger than the sample size, record it exactly. |
michael@0 | 1183 | Block b(aPtr, aReqSize, StackTrace::Get(aT), /* sampled */ false); |
michael@0 | 1184 | (void)gBlockTable->putNew(aPtr, b); |
michael@0 | 1185 | } |
michael@0 | 1186 | } |
michael@0 | 1187 | |
michael@0 | 1188 | static void |
michael@0 | 1189 | FreeCallback(void* aPtr, Thread* aT) |
michael@0 | 1190 | { |
michael@0 | 1191 | MOZ_ASSERT(gIsDMDRunning); |
michael@0 | 1192 | |
michael@0 | 1193 | if (!aPtr) { |
michael@0 | 1194 | return; |
michael@0 | 1195 | } |
michael@0 | 1196 | |
michael@0 | 1197 | AutoLockState lock; |
michael@0 | 1198 | AutoBlockIntercepts block(aT); |
michael@0 | 1199 | |
michael@0 | 1200 | gBlockTable->remove(aPtr); |
michael@0 | 1201 | |
michael@0 | 1202 | if (gStackTraceTable->count() > gGCStackTraceTableWhenSizeExceeds) { |
michael@0 | 1203 | GCStackTraces(); |
michael@0 | 1204 | } |
michael@0 | 1205 | } |
michael@0 | 1206 | |
michael@0 | 1207 | //--------------------------------------------------------------------------- |
michael@0 | 1208 | // malloc/free interception |
michael@0 | 1209 | //--------------------------------------------------------------------------- |
michael@0 | 1210 | |
michael@0 | 1211 | static void Init(const malloc_table_t* aMallocTable); |
michael@0 | 1212 | |
michael@0 | 1213 | } // namespace dmd |
michael@0 | 1214 | } // namespace mozilla |
michael@0 | 1215 | |
michael@0 | 1216 | void |
michael@0 | 1217 | replace_init(const malloc_table_t* aMallocTable) |
michael@0 | 1218 | { |
michael@0 | 1219 | mozilla::dmd::Init(aMallocTable); |
michael@0 | 1220 | } |
michael@0 | 1221 | |
michael@0 | 1222 | void* |
michael@0 | 1223 | replace_malloc(size_t aSize) |
michael@0 | 1224 | { |
michael@0 | 1225 | using namespace mozilla::dmd; |
michael@0 | 1226 | |
michael@0 | 1227 | if (!gIsDMDRunning) { |
michael@0 | 1228 | // DMD hasn't started up, either because it wasn't enabled by the user, or |
michael@0 | 1229 | // we're still in Init() and something has indirectly called malloc. Do a |
michael@0 | 1230 | // vanilla malloc. (In the latter case, if it fails we'll crash. But |
michael@0 | 1231 | // OOM is highly unlikely so early on.) |
michael@0 | 1232 | return gMallocTable->malloc(aSize); |
michael@0 | 1233 | } |
michael@0 | 1234 | |
michael@0 | 1235 | Thread* t = Thread::Fetch(); |
michael@0 | 1236 | if (t->InterceptsAreBlocked()) { |
michael@0 | 1237 | // Intercepts are blocked, which means this must be a call to malloc |
michael@0 | 1238 | // triggered indirectly by DMD (e.g. via NS_StackWalk). Be infallible. |
michael@0 | 1239 | return InfallibleAllocPolicy::malloc_(aSize); |
michael@0 | 1240 | } |
michael@0 | 1241 | |
michael@0 | 1242 | // This must be a call to malloc from outside DMD. Intercept it. |
michael@0 | 1243 | void* ptr = gMallocTable->malloc(aSize); |
michael@0 | 1244 | AllocCallback(ptr, aSize, t); |
michael@0 | 1245 | return ptr; |
michael@0 | 1246 | } |
michael@0 | 1247 | |
michael@0 | 1248 | void* |
michael@0 | 1249 | replace_calloc(size_t aCount, size_t aSize) |
michael@0 | 1250 | { |
michael@0 | 1251 | using namespace mozilla::dmd; |
michael@0 | 1252 | |
michael@0 | 1253 | if (!gIsDMDRunning) { |
michael@0 | 1254 | return gMallocTable->calloc(aCount, aSize); |
michael@0 | 1255 | } |
michael@0 | 1256 | |
michael@0 | 1257 | Thread* t = Thread::Fetch(); |
michael@0 | 1258 | if (t->InterceptsAreBlocked()) { |
michael@0 | 1259 | return InfallibleAllocPolicy::calloc_(aCount * aSize); |
michael@0 | 1260 | } |
michael@0 | 1261 | |
michael@0 | 1262 | void* ptr = gMallocTable->calloc(aCount, aSize); |
michael@0 | 1263 | AllocCallback(ptr, aCount * aSize, t); |
michael@0 | 1264 | return ptr; |
michael@0 | 1265 | } |
michael@0 | 1266 | |
michael@0 | 1267 | void* |
michael@0 | 1268 | replace_realloc(void* aOldPtr, size_t aSize) |
michael@0 | 1269 | { |
michael@0 | 1270 | using namespace mozilla::dmd; |
michael@0 | 1271 | |
michael@0 | 1272 | if (!gIsDMDRunning) { |
michael@0 | 1273 | return gMallocTable->realloc(aOldPtr, aSize); |
michael@0 | 1274 | } |
michael@0 | 1275 | |
michael@0 | 1276 | Thread* t = Thread::Fetch(); |
michael@0 | 1277 | if (t->InterceptsAreBlocked()) { |
michael@0 | 1278 | return InfallibleAllocPolicy::realloc_(aOldPtr, aSize); |
michael@0 | 1279 | } |
michael@0 | 1280 | |
michael@0 | 1281 | // If |aOldPtr| is nullptr, the call is equivalent to |malloc(aSize)|. |
michael@0 | 1282 | if (!aOldPtr) { |
michael@0 | 1283 | return replace_malloc(aSize); |
michael@0 | 1284 | } |
michael@0 | 1285 | |
michael@0 | 1286 | // Be very careful here! Must remove the block from the table before doing |
michael@0 | 1287 | // the realloc to avoid races, just like in replace_free(). |
michael@0 | 1288 | // Nb: This does an unnecessary hashtable remove+add if the block doesn't |
michael@0 | 1289 | // move, but doing better isn't worth the effort. |
michael@0 | 1290 | FreeCallback(aOldPtr, t); |
michael@0 | 1291 | void* ptr = gMallocTable->realloc(aOldPtr, aSize); |
michael@0 | 1292 | if (ptr) { |
michael@0 | 1293 | AllocCallback(ptr, aSize, t); |
michael@0 | 1294 | } else { |
michael@0 | 1295 | // If realloc fails, we re-insert the old pointer. It will look like it |
michael@0 | 1296 | // was allocated for the first time here, which is untrue, and the slop |
michael@0 | 1297 | // bytes will be zero, which may be untrue. But this case is rare and |
michael@0 | 1298 | // doing better isn't worth the effort. |
michael@0 | 1299 | AllocCallback(aOldPtr, gMallocTable->malloc_usable_size(aOldPtr), t); |
michael@0 | 1300 | } |
michael@0 | 1301 | return ptr; |
michael@0 | 1302 | } |
michael@0 | 1303 | |
michael@0 | 1304 | void* |
michael@0 | 1305 | replace_memalign(size_t aAlignment, size_t aSize) |
michael@0 | 1306 | { |
michael@0 | 1307 | using namespace mozilla::dmd; |
michael@0 | 1308 | |
michael@0 | 1309 | if (!gIsDMDRunning) { |
michael@0 | 1310 | return gMallocTable->memalign(aAlignment, aSize); |
michael@0 | 1311 | } |
michael@0 | 1312 | |
michael@0 | 1313 | Thread* t = Thread::Fetch(); |
michael@0 | 1314 | if (t->InterceptsAreBlocked()) { |
michael@0 | 1315 | return InfallibleAllocPolicy::memalign_(aAlignment, aSize); |
michael@0 | 1316 | } |
michael@0 | 1317 | |
michael@0 | 1318 | void* ptr = gMallocTable->memalign(aAlignment, aSize); |
michael@0 | 1319 | AllocCallback(ptr, aSize, t); |
michael@0 | 1320 | return ptr; |
michael@0 | 1321 | } |
michael@0 | 1322 | |
michael@0 | 1323 | void |
michael@0 | 1324 | replace_free(void* aPtr) |
michael@0 | 1325 | { |
michael@0 | 1326 | using namespace mozilla::dmd; |
michael@0 | 1327 | |
michael@0 | 1328 | if (!gIsDMDRunning) { |
michael@0 | 1329 | gMallocTable->free(aPtr); |
michael@0 | 1330 | return; |
michael@0 | 1331 | } |
michael@0 | 1332 | |
michael@0 | 1333 | Thread* t = Thread::Fetch(); |
michael@0 | 1334 | if (t->InterceptsAreBlocked()) { |
michael@0 | 1335 | return InfallibleAllocPolicy::free_(aPtr); |
michael@0 | 1336 | } |
michael@0 | 1337 | |
michael@0 | 1338 | // Do the actual free after updating the table. Otherwise, another thread |
michael@0 | 1339 | // could call malloc and get the freed block and update the table, and then |
michael@0 | 1340 | // our update here would remove the newly-malloc'd block. |
michael@0 | 1341 | FreeCallback(aPtr, t); |
michael@0 | 1342 | gMallocTable->free(aPtr); |
michael@0 | 1343 | } |
michael@0 | 1344 | |
michael@0 | 1345 | namespace mozilla { |
michael@0 | 1346 | namespace dmd { |
michael@0 | 1347 | |
michael@0 | 1348 | //--------------------------------------------------------------------------- |
michael@0 | 1349 | // Stack trace records |
michael@0 | 1350 | //--------------------------------------------------------------------------- |
michael@0 | 1351 | |
michael@0 | 1352 | class TraceRecordKey |
michael@0 | 1353 | { |
michael@0 | 1354 | public: |
michael@0 | 1355 | const StackTrace* const mAllocStackTrace; // never null |
michael@0 | 1356 | protected: |
michael@0 | 1357 | const StackTrace* const mReportStackTrace1; // nullptr if unreported |
michael@0 | 1358 | const StackTrace* const mReportStackTrace2; // nullptr if not 2x-reported |
michael@0 | 1359 | |
michael@0 | 1360 | public: |
michael@0 | 1361 | TraceRecordKey(const Block& aB) |
michael@0 | 1362 | : mAllocStackTrace(aB.AllocStackTrace()), |
michael@0 | 1363 | mReportStackTrace1(aB.ReportStackTrace1()), |
michael@0 | 1364 | mReportStackTrace2(aB.ReportStackTrace2()) |
michael@0 | 1365 | { |
michael@0 | 1366 | MOZ_ASSERT(mAllocStackTrace); |
michael@0 | 1367 | } |
michael@0 | 1368 | |
michael@0 | 1369 | // Hash policy. |
michael@0 | 1370 | |
michael@0 | 1371 | typedef TraceRecordKey Lookup; |
michael@0 | 1372 | |
michael@0 | 1373 | static uint32_t hash(const TraceRecordKey& aKey) |
michael@0 | 1374 | { |
michael@0 | 1375 | return mozilla::HashGeneric(aKey.mAllocStackTrace, |
michael@0 | 1376 | aKey.mReportStackTrace1, |
michael@0 | 1377 | aKey.mReportStackTrace2); |
michael@0 | 1378 | } |
michael@0 | 1379 | |
michael@0 | 1380 | static bool match(const TraceRecordKey& aA, const TraceRecordKey& aB) |
michael@0 | 1381 | { |
michael@0 | 1382 | return aA.mAllocStackTrace == aB.mAllocStackTrace && |
michael@0 | 1383 | aA.mReportStackTrace1 == aB.mReportStackTrace1 && |
michael@0 | 1384 | aA.mReportStackTrace2 == aB.mReportStackTrace2; |
michael@0 | 1385 | } |
michael@0 | 1386 | }; |
michael@0 | 1387 | |
michael@0 | 1388 | class RecordSize |
michael@0 | 1389 | { |
michael@0 | 1390 | static const size_t kReqBits = sizeof(size_t) * 8 - 1; // 31 or 63 |
michael@0 | 1391 | |
michael@0 | 1392 | size_t mReq; // size requested |
michael@0 | 1393 | size_t mSlop:kReqBits; // slop bytes |
michael@0 | 1394 | size_t mSampled:1; // were one or more blocks contributing to this |
michael@0 | 1395 | // RecordSize sampled? |
michael@0 | 1396 | public: |
michael@0 | 1397 | RecordSize() |
michael@0 | 1398 | : mReq(0), |
michael@0 | 1399 | mSlop(0), |
michael@0 | 1400 | mSampled(false) |
michael@0 | 1401 | {} |
michael@0 | 1402 | |
michael@0 | 1403 | size_t Req() const { return mReq; } |
michael@0 | 1404 | size_t Slop() const { return mSlop; } |
michael@0 | 1405 | size_t Usable() const { return mReq + mSlop; } |
michael@0 | 1406 | |
michael@0 | 1407 | bool IsSampled() const { return mSampled; } |
michael@0 | 1408 | |
michael@0 | 1409 | void Add(const Block& aB) |
michael@0 | 1410 | { |
michael@0 | 1411 | mReq += aB.ReqSize(); |
michael@0 | 1412 | mSlop += aB.SlopSize(); |
michael@0 | 1413 | mSampled = mSampled || aB.IsSampled(); |
michael@0 | 1414 | } |
michael@0 | 1415 | |
michael@0 | 1416 | void Add(const RecordSize& aRecordSize) |
michael@0 | 1417 | { |
michael@0 | 1418 | mReq += aRecordSize.Req(); |
michael@0 | 1419 | mSlop += aRecordSize.Slop(); |
michael@0 | 1420 | mSampled = mSampled || aRecordSize.IsSampled(); |
michael@0 | 1421 | } |
michael@0 | 1422 | |
michael@0 | 1423 | static int Cmp(const RecordSize& aA, const RecordSize& aB) |
michael@0 | 1424 | { |
michael@0 | 1425 | // Primary sort: put bigger usable sizes first. |
michael@0 | 1426 | if (aA.Usable() > aB.Usable()) return -1; |
michael@0 | 1427 | if (aA.Usable() < aB.Usable()) return 1; |
michael@0 | 1428 | |
michael@0 | 1429 | // Secondary sort: put bigger requested sizes first. |
michael@0 | 1430 | if (aA.Req() > aB.Req()) return -1; |
michael@0 | 1431 | if (aA.Req() < aB.Req()) return 1; |
michael@0 | 1432 | |
michael@0 | 1433 | // Tertiary sort: put non-sampled records before sampled records. |
michael@0 | 1434 | if (!aA.mSampled && aB.mSampled) return -1; |
michael@0 | 1435 | if ( aA.mSampled && !aB.mSampled) return 1; |
michael@0 | 1436 | |
michael@0 | 1437 | return 0; |
michael@0 | 1438 | } |
michael@0 | 1439 | }; |
michael@0 | 1440 | |
michael@0 | 1441 | // A collection of one or more heap blocks with a common TraceRecordKey. |
michael@0 | 1442 | class TraceRecord : public TraceRecordKey |
michael@0 | 1443 | { |
michael@0 | 1444 | // The TraceRecordKey base class serves as the key in TraceRecordTables. |
michael@0 | 1445 | // These two fields constitute the value, so it's ok for them to be |
michael@0 | 1446 | // |mutable|. |
michael@0 | 1447 | mutable uint32_t mNumBlocks; // number of blocks with this TraceRecordKey |
michael@0 | 1448 | mutable RecordSize mRecordSize; // combined size of those blocks |
michael@0 | 1449 | |
michael@0 | 1450 | public: |
michael@0 | 1451 | explicit TraceRecord(const TraceRecordKey& aKey) |
michael@0 | 1452 | : TraceRecordKey(aKey), |
michael@0 | 1453 | mNumBlocks(0), |
michael@0 | 1454 | mRecordSize() |
michael@0 | 1455 | {} |
michael@0 | 1456 | |
michael@0 | 1457 | uint32_t NumBlocks() const { return mNumBlocks; } |
michael@0 | 1458 | |
michael@0 | 1459 | const RecordSize& GetRecordSize() const { return mRecordSize; } |
michael@0 | 1460 | |
michael@0 | 1461 | // This is |const| thanks to the |mutable| fields above. |
michael@0 | 1462 | void Add(const Block& aB) const |
michael@0 | 1463 | { |
michael@0 | 1464 | mNumBlocks++; |
michael@0 | 1465 | mRecordSize.Add(aB); |
michael@0 | 1466 | } |
michael@0 | 1467 | |
michael@0 | 1468 | // For PrintSortedRecords. |
michael@0 | 1469 | static const char* const kRecordKind; |
michael@0 | 1470 | static bool recordsOverlap() { return false; } |
michael@0 | 1471 | |
michael@0 | 1472 | void Print(const Writer& aWriter, LocationService* aLocService, |
michael@0 | 1473 | uint32_t aM, uint32_t aN, const char* aStr, const char* astr, |
michael@0 | 1474 | size_t aCategoryUsableSize, size_t aCumulativeUsableSize, |
michael@0 | 1475 | size_t aTotalUsableSize) const; |
michael@0 | 1476 | |
michael@0 | 1477 | static int QsortCmp(const void* aA, const void* aB) |
michael@0 | 1478 | { |
michael@0 | 1479 | const TraceRecord* const a = *static_cast<const TraceRecord* const*>(aA); |
michael@0 | 1480 | const TraceRecord* const b = *static_cast<const TraceRecord* const*>(aB); |
michael@0 | 1481 | |
michael@0 | 1482 | return RecordSize::Cmp(a->mRecordSize, b->mRecordSize); |
michael@0 | 1483 | } |
michael@0 | 1484 | }; |
michael@0 | 1485 | |
michael@0 | 1486 | const char* const TraceRecord::kRecordKind = "trace"; |
michael@0 | 1487 | |
michael@0 | 1488 | typedef js::HashSet<TraceRecord, TraceRecord, InfallibleAllocPolicy> |
michael@0 | 1489 | TraceRecordTable; |
michael@0 | 1490 | |
michael@0 | 1491 | void |
michael@0 | 1492 | TraceRecord::Print(const Writer& aWriter, LocationService* aLocService, |
michael@0 | 1493 | uint32_t aM, uint32_t aN, const char* aStr, const char* astr, |
michael@0 | 1494 | size_t aCategoryUsableSize, size_t aCumulativeUsableSize, |
michael@0 | 1495 | size_t aTotalUsableSize) const |
michael@0 | 1496 | { |
michael@0 | 1497 | bool showTilde = mRecordSize.IsSampled(); |
michael@0 | 1498 | |
michael@0 | 1499 | W("%s: %s block%s in stack trace record %s of %s\n", |
michael@0 | 1500 | aStr, |
michael@0 | 1501 | Show(mNumBlocks, gBuf1, kBufLen, showTilde), Plural(mNumBlocks), |
michael@0 | 1502 | Show(aM, gBuf2, kBufLen), |
michael@0 | 1503 | Show(aN, gBuf3, kBufLen)); |
michael@0 | 1504 | |
michael@0 | 1505 | W(" %s bytes (%s requested / %s slop)\n", |
michael@0 | 1506 | Show(mRecordSize.Usable(), gBuf1, kBufLen, showTilde), |
michael@0 | 1507 | Show(mRecordSize.Req(), gBuf2, kBufLen, showTilde), |
michael@0 | 1508 | Show(mRecordSize.Slop(), gBuf3, kBufLen, showTilde)); |
michael@0 | 1509 | |
michael@0 | 1510 | W(" %4.2f%% of the heap (%4.2f%% cumulative); " |
michael@0 | 1511 | " %4.2f%% of %s (%4.2f%% cumulative)\n", |
michael@0 | 1512 | Percent(mRecordSize.Usable(), aTotalUsableSize), |
michael@0 | 1513 | Percent(aCumulativeUsableSize, aTotalUsableSize), |
michael@0 | 1514 | Percent(mRecordSize.Usable(), aCategoryUsableSize), |
michael@0 | 1515 | astr, |
michael@0 | 1516 | Percent(aCumulativeUsableSize, aCategoryUsableSize)); |
michael@0 | 1517 | |
michael@0 | 1518 | W(" Allocated at\n"); |
michael@0 | 1519 | mAllocStackTrace->Print(aWriter, aLocService); |
michael@0 | 1520 | |
michael@0 | 1521 | if (mReportStackTrace1) { |
michael@0 | 1522 | W("\n Reported at\n"); |
michael@0 | 1523 | mReportStackTrace1->Print(aWriter, aLocService); |
michael@0 | 1524 | } |
michael@0 | 1525 | if (mReportStackTrace2) { |
michael@0 | 1526 | W("\n Reported again at\n"); |
michael@0 | 1527 | mReportStackTrace2->Print(aWriter, aLocService); |
michael@0 | 1528 | } |
michael@0 | 1529 | |
michael@0 | 1530 | W("\n"); |
michael@0 | 1531 | } |
michael@0 | 1532 | |
michael@0 | 1533 | //--------------------------------------------------------------------------- |
michael@0 | 1534 | // Stack frame records |
michael@0 | 1535 | //--------------------------------------------------------------------------- |
michael@0 | 1536 | |
michael@0 | 1537 | // A collection of one or more stack frames (from heap block allocation stack |
michael@0 | 1538 | // traces) with a common PC. |
michael@0 | 1539 | class FrameRecord |
michael@0 | 1540 | { |
michael@0 | 1541 | // mPc is used as the key in FrameRecordTable, and the other members |
michael@0 | 1542 | // constitute the value, so it's ok for them to be |mutable|. |
michael@0 | 1543 | const void* const mPc; |
michael@0 | 1544 | mutable size_t mNumBlocks; |
michael@0 | 1545 | mutable size_t mNumTraceRecords; |
michael@0 | 1546 | mutable RecordSize mRecordSize; |
michael@0 | 1547 | |
michael@0 | 1548 | public: |
michael@0 | 1549 | explicit FrameRecord(const void* aPc) |
michael@0 | 1550 | : mPc(aPc), |
michael@0 | 1551 | mNumBlocks(0), |
michael@0 | 1552 | mNumTraceRecords(0), |
michael@0 | 1553 | mRecordSize() |
michael@0 | 1554 | {} |
michael@0 | 1555 | |
michael@0 | 1556 | const RecordSize& GetRecordSize() const { return mRecordSize; } |
michael@0 | 1557 | |
michael@0 | 1558 | // This is |const| thanks to the |mutable| fields above. |
michael@0 | 1559 | void Add(const TraceRecord& aTr) const |
michael@0 | 1560 | { |
michael@0 | 1561 | mNumBlocks += aTr.NumBlocks(); |
michael@0 | 1562 | mNumTraceRecords++; |
michael@0 | 1563 | mRecordSize.Add(aTr.GetRecordSize()); |
michael@0 | 1564 | } |
michael@0 | 1565 | |
michael@0 | 1566 | void Print(const Writer& aWriter, LocationService* aLocService, |
michael@0 | 1567 | uint32_t aM, uint32_t aN, const char* aStr, const char* astr, |
michael@0 | 1568 | size_t aCategoryUsableSize, size_t aCumulativeUsableSize, |
michael@0 | 1569 | size_t aTotalUsableSize) const; |
michael@0 | 1570 | |
michael@0 | 1571 | static int QsortCmp(const void* aA, const void* aB) |
michael@0 | 1572 | { |
michael@0 | 1573 | const FrameRecord* const a = *static_cast<const FrameRecord* const*>(aA); |
michael@0 | 1574 | const FrameRecord* const b = *static_cast<const FrameRecord* const*>(aB); |
michael@0 | 1575 | |
michael@0 | 1576 | return RecordSize::Cmp(a->mRecordSize, b->mRecordSize); |
michael@0 | 1577 | } |
michael@0 | 1578 | |
michael@0 | 1579 | // For PrintSortedRecords. |
michael@0 | 1580 | static const char* const kRecordKind; |
michael@0 | 1581 | static bool recordsOverlap() { return true; } |
michael@0 | 1582 | |
michael@0 | 1583 | // Hash policy. |
michael@0 | 1584 | |
michael@0 | 1585 | typedef const void* Lookup; |
michael@0 | 1586 | |
michael@0 | 1587 | static uint32_t hash(const void* const& aPc) |
michael@0 | 1588 | { |
michael@0 | 1589 | return mozilla::HashGeneric(aPc); |
michael@0 | 1590 | } |
michael@0 | 1591 | |
michael@0 | 1592 | static bool match(const FrameRecord& aFr, const void* const& aPc) |
michael@0 | 1593 | { |
michael@0 | 1594 | return aFr.mPc == aPc; |
michael@0 | 1595 | } |
michael@0 | 1596 | }; |
michael@0 | 1597 | |
michael@0 | 1598 | const char* const FrameRecord::kRecordKind = "frame"; |
michael@0 | 1599 | |
michael@0 | 1600 | typedef js::HashSet<FrameRecord, FrameRecord, InfallibleAllocPolicy> |
michael@0 | 1601 | FrameRecordTable; |
michael@0 | 1602 | |
michael@0 | 1603 | void |
michael@0 | 1604 | FrameRecord::Print(const Writer& aWriter, LocationService* aLocService, |
michael@0 | 1605 | uint32_t aM, uint32_t aN, const char* aStr, const char* astr, |
michael@0 | 1606 | size_t aCategoryUsableSize, size_t aCumulativeUsableSize, |
michael@0 | 1607 | size_t aTotalUsableSize) const |
michael@0 | 1608 | { |
michael@0 | 1609 | (void)aCumulativeUsableSize; |
michael@0 | 1610 | |
michael@0 | 1611 | bool showTilde = mRecordSize.IsSampled(); |
michael@0 | 1612 | |
michael@0 | 1613 | W("%s: %s block%s from %s stack trace record%s in stack frame record %s of %s\n", |
michael@0 | 1614 | aStr, |
michael@0 | 1615 | Show(mNumBlocks, gBuf1, kBufLen, showTilde), Plural(mNumBlocks), |
michael@0 | 1616 | Show(mNumTraceRecords, gBuf2, kBufLen, showTilde), Plural(mNumTraceRecords), |
michael@0 | 1617 | Show(aM, gBuf3, kBufLen), |
michael@0 | 1618 | Show(aN, gBuf4, kBufLen)); |
michael@0 | 1619 | |
michael@0 | 1620 | W(" %s bytes (%s requested / %s slop)\n", |
michael@0 | 1621 | Show(mRecordSize.Usable(), gBuf1, kBufLen, showTilde), |
michael@0 | 1622 | Show(mRecordSize.Req(), gBuf2, kBufLen, showTilde), |
michael@0 | 1623 | Show(mRecordSize.Slop(), gBuf3, kBufLen, showTilde)); |
michael@0 | 1624 | |
michael@0 | 1625 | W(" %4.2f%% of the heap; %4.2f%% of %s\n", |
michael@0 | 1626 | Percent(mRecordSize.Usable(), aTotalUsableSize), |
michael@0 | 1627 | Percent(mRecordSize.Usable(), aCategoryUsableSize), |
michael@0 | 1628 | astr); |
michael@0 | 1629 | |
michael@0 | 1630 | W(" PC is\n"); |
michael@0 | 1631 | aLocService->WriteLocation(aWriter, mPc); |
michael@0 | 1632 | W("\n"); |
michael@0 | 1633 | } |
michael@0 | 1634 | |
michael@0 | 1635 | //--------------------------------------------------------------------------- |
michael@0 | 1636 | // Options (Part 2) |
michael@0 | 1637 | //--------------------------------------------------------------------------- |
michael@0 | 1638 | |
michael@0 | 1639 | // Given an |aOptionName| like "foo", succeed if |aArg| has the form "foo=blah" |
michael@0 | 1640 | // (where "blah" is non-empty) and return the pointer to "blah". |aArg| can |
michael@0 | 1641 | // have leading space chars (but not other whitespace). |
michael@0 | 1642 | const char* |
michael@0 | 1643 | Options::ValueIfMatch(const char* aArg, const char* aOptionName) |
michael@0 | 1644 | { |
michael@0 | 1645 | MOZ_ASSERT(!isspace(*aArg)); // any leading whitespace should not remain |
michael@0 | 1646 | size_t optionLen = strlen(aOptionName); |
michael@0 | 1647 | if (strncmp(aArg, aOptionName, optionLen) == 0 && aArg[optionLen] == '=' && |
michael@0 | 1648 | aArg[optionLen + 1]) { |
michael@0 | 1649 | return aArg + optionLen + 1; |
michael@0 | 1650 | } |
michael@0 | 1651 | return nullptr; |
michael@0 | 1652 | } |
michael@0 | 1653 | |
michael@0 | 1654 | // Extracts a |long| value for an option from an argument. It must be within |
michael@0 | 1655 | // the range |aMin..aMax| (inclusive). |
michael@0 | 1656 | bool |
michael@0 | 1657 | Options::GetLong(const char* aArg, const char* aOptionName, |
michael@0 | 1658 | long aMin, long aMax, long* aN) |
michael@0 | 1659 | { |
michael@0 | 1660 | if (const char* optionValue = ValueIfMatch(aArg, aOptionName)) { |
michael@0 | 1661 | char* endPtr; |
michael@0 | 1662 | *aN = strtol(optionValue, &endPtr, /* base */ 10); |
michael@0 | 1663 | if (!*endPtr && aMin <= *aN && *aN <= aMax && |
michael@0 | 1664 | *aN != LONG_MIN && *aN != LONG_MAX) { |
michael@0 | 1665 | return true; |
michael@0 | 1666 | } |
michael@0 | 1667 | } |
michael@0 | 1668 | return false; |
michael@0 | 1669 | } |
michael@0 | 1670 | |
michael@0 | 1671 | // The sample-below default is a prime number close to 4096. |
michael@0 | 1672 | // - Why that size? Because it's *much* faster but only moderately less precise |
michael@0 | 1673 | // than a size of 1. |
michael@0 | 1674 | // - Why prime? Because it makes our sampling more random. If we used a size |
michael@0 | 1675 | // of 4096, for example, then our alloc counter would only take on even |
michael@0 | 1676 | // values, because jemalloc always rounds up requests sizes. In contrast, a |
michael@0 | 1677 | // prime size will explore all possible values of the alloc counter. |
michael@0 | 1678 | // |
michael@0 | 1679 | Options::Options(const char* aDMDEnvVar) |
michael@0 | 1680 | : mDMDEnvVar(InfallibleAllocPolicy::strdup_(aDMDEnvVar)), |
michael@0 | 1681 | mSampleBelowSize(4093, 100 * 100 * 1000), |
michael@0 | 1682 | mMaxFrames(StackTrace::MaxFrames, StackTrace::MaxFrames), |
michael@0 | 1683 | mMaxRecords(1000, 1000000), |
michael@0 | 1684 | mMode(Normal) |
michael@0 | 1685 | { |
michael@0 | 1686 | char* e = mDMDEnvVar; |
michael@0 | 1687 | if (strcmp(e, "1") != 0) { |
michael@0 | 1688 | bool isEnd = false; |
michael@0 | 1689 | while (!isEnd) { |
michael@0 | 1690 | // Consume leading whitespace. |
michael@0 | 1691 | while (isspace(*e)) { |
michael@0 | 1692 | e++; |
michael@0 | 1693 | } |
michael@0 | 1694 | |
michael@0 | 1695 | // Save the start of the arg. |
michael@0 | 1696 | const char* arg = e; |
michael@0 | 1697 | |
michael@0 | 1698 | // Find the first char after the arg, and temporarily change it to '\0' |
michael@0 | 1699 | // to isolate the arg. |
michael@0 | 1700 | while (!isspace(*e) && *e != '\0') { |
michael@0 | 1701 | e++; |
michael@0 | 1702 | } |
michael@0 | 1703 | char replacedChar = *e; |
michael@0 | 1704 | isEnd = replacedChar == '\0'; |
michael@0 | 1705 | *e = '\0'; |
michael@0 | 1706 | |
michael@0 | 1707 | // Handle arg |
michael@0 | 1708 | long myLong; |
michael@0 | 1709 | if (GetLong(arg, "--sample-below", 1, mSampleBelowSize.mMax, &myLong)) { |
michael@0 | 1710 | mSampleBelowSize.mActual = myLong; |
michael@0 | 1711 | |
michael@0 | 1712 | } else if (GetLong(arg, "--max-frames", 1, mMaxFrames.mMax, &myLong)) { |
michael@0 | 1713 | mMaxFrames.mActual = myLong; |
michael@0 | 1714 | |
michael@0 | 1715 | } else if (GetLong(arg, "--max-records", 1, mMaxRecords.mMax, &myLong)) { |
michael@0 | 1716 | mMaxRecords.mActual = myLong; |
michael@0 | 1717 | |
michael@0 | 1718 | } else if (strcmp(arg, "--mode=normal") == 0) { |
michael@0 | 1719 | mMode = Options::Normal; |
michael@0 | 1720 | } else if (strcmp(arg, "--mode=test") == 0) { |
michael@0 | 1721 | mMode = Options::Test; |
michael@0 | 1722 | } else if (strcmp(arg, "--mode=stress") == 0) { |
michael@0 | 1723 | mMode = Options::Stress; |
michael@0 | 1724 | |
michael@0 | 1725 | } else if (strcmp(arg, "") == 0) { |
michael@0 | 1726 | // This can only happen if there is trailing whitespace. Ignore. |
michael@0 | 1727 | MOZ_ASSERT(isEnd); |
michael@0 | 1728 | |
michael@0 | 1729 | } else { |
michael@0 | 1730 | BadArg(arg); |
michael@0 | 1731 | } |
michael@0 | 1732 | |
michael@0 | 1733 | // Undo the temporary isolation. |
michael@0 | 1734 | *e = replacedChar; |
michael@0 | 1735 | } |
michael@0 | 1736 | } |
michael@0 | 1737 | } |
michael@0 | 1738 | |
michael@0 | 1739 | void |
michael@0 | 1740 | Options::BadArg(const char* aArg) |
michael@0 | 1741 | { |
michael@0 | 1742 | StatusMsg("\n"); |
michael@0 | 1743 | StatusMsg("Bad entry in the $DMD environment variable: '%s'.\n", aArg); |
michael@0 | 1744 | StatusMsg("\n"); |
michael@0 | 1745 | StatusMsg("Valid values of $DMD are:\n"); |
michael@0 | 1746 | StatusMsg("- undefined or \"\" or \"0\", which disables DMD, or\n"); |
michael@0 | 1747 | StatusMsg("- \"1\", which enables it with the default options, or\n"); |
michael@0 | 1748 | StatusMsg("- a whitespace-separated list of |--option=val| entries, which\n"); |
michael@0 | 1749 | StatusMsg(" enables it with non-default options.\n"); |
michael@0 | 1750 | StatusMsg("\n"); |
michael@0 | 1751 | StatusMsg("The following options are allowed; defaults are shown in [].\n"); |
michael@0 | 1752 | StatusMsg(" --sample-below=<1..%d> Sample blocks smaller than this [%d]\n", |
michael@0 | 1753 | int(mSampleBelowSize.mMax), |
michael@0 | 1754 | int(mSampleBelowSize.mDefault)); |
michael@0 | 1755 | StatusMsg(" (prime numbers are recommended)\n"); |
michael@0 | 1756 | StatusMsg(" --max-frames=<1..%d> Max. depth of stack traces [%d]\n", |
michael@0 | 1757 | int(mMaxFrames.mMax), |
michael@0 | 1758 | int(mMaxFrames.mDefault)); |
michael@0 | 1759 | StatusMsg(" --max-records=<1..%u> Max. number of records printed [%u]\n", |
michael@0 | 1760 | mMaxRecords.mMax, |
michael@0 | 1761 | mMaxRecords.mDefault); |
michael@0 | 1762 | StatusMsg(" --mode=<normal|test|stress> Mode of operation [normal]\n"); |
michael@0 | 1763 | StatusMsg("\n"); |
michael@0 | 1764 | exit(1); |
michael@0 | 1765 | } |
michael@0 | 1766 | |
michael@0 | 1767 | //--------------------------------------------------------------------------- |
michael@0 | 1768 | // DMD start-up |
michael@0 | 1769 | //--------------------------------------------------------------------------- |
michael@0 | 1770 | |
michael@0 | 1771 | #ifdef XP_MACOSX |
michael@0 | 1772 | static void |
michael@0 | 1773 | NopStackWalkCallback(void* aPc, void* aSp, void* aClosure) |
michael@0 | 1774 | { |
michael@0 | 1775 | } |
michael@0 | 1776 | #endif |
michael@0 | 1777 | |
michael@0 | 1778 | // Note that fopen() can allocate. |
michael@0 | 1779 | static FILE* |
michael@0 | 1780 | OpenOutputFile(const char* aFilename) |
michael@0 | 1781 | { |
michael@0 | 1782 | FILE* fp = fopen(aFilename, "w"); |
michael@0 | 1783 | if (!fp) { |
michael@0 | 1784 | StatusMsg("can't create %s file: %s\n", aFilename, strerror(errno)); |
michael@0 | 1785 | exit(1); |
michael@0 | 1786 | } |
michael@0 | 1787 | return fp; |
michael@0 | 1788 | } |
michael@0 | 1789 | |
michael@0 | 1790 | static void RunTestMode(FILE* fp); |
michael@0 | 1791 | static void RunStressMode(FILE* fp); |
michael@0 | 1792 | |
michael@0 | 1793 | // WARNING: this function runs *very* early -- before all static initializers |
michael@0 | 1794 | // have run. For this reason, non-scalar globals such as gStateLock and |
michael@0 | 1795 | // gStackTraceTable are allocated dynamically (so we can guarantee their |
michael@0 | 1796 | // construction in this function) rather than statically. |
michael@0 | 1797 | static void |
michael@0 | 1798 | Init(const malloc_table_t* aMallocTable) |
michael@0 | 1799 | { |
michael@0 | 1800 | MOZ_ASSERT(!gIsDMDRunning); |
michael@0 | 1801 | |
michael@0 | 1802 | gMallocTable = aMallocTable; |
michael@0 | 1803 | |
michael@0 | 1804 | // DMD is controlled by the |DMD| environment variable. |
michael@0 | 1805 | // - If it's unset or empty or "0", DMD doesn't run. |
michael@0 | 1806 | // - Otherwise, the contents dictate DMD's behaviour. |
michael@0 | 1807 | |
michael@0 | 1808 | char* e = getenv("DMD"); |
michael@0 | 1809 | StatusMsg("$DMD = '%s'\n", e); |
michael@0 | 1810 | |
michael@0 | 1811 | if (!e || strcmp(e, "") == 0 || strcmp(e, "0") == 0) { |
michael@0 | 1812 | StatusMsg("DMD is not enabled\n"); |
michael@0 | 1813 | return; |
michael@0 | 1814 | } |
michael@0 | 1815 | |
michael@0 | 1816 | // Parse $DMD env var. |
michael@0 | 1817 | gOptions = InfallibleAllocPolicy::new_<Options>(e); |
michael@0 | 1818 | |
michael@0 | 1819 | StatusMsg("DMD is enabled\n"); |
michael@0 | 1820 | |
michael@0 | 1821 | #ifdef XP_MACOSX |
michael@0 | 1822 | // On Mac OS X we need to call StackWalkInitCriticalAddress() very early |
michael@0 | 1823 | // (prior to the creation of any mutexes, apparently) otherwise we can get |
michael@0 | 1824 | // hangs when getting stack traces (bug 821577). But |
michael@0 | 1825 | // StackWalkInitCriticalAddress() isn't exported from xpcom/, so instead we |
michael@0 | 1826 | // just call NS_StackWalk, because that calls StackWalkInitCriticalAddress(). |
michael@0 | 1827 | // See the comment above StackWalkInitCriticalAddress() for more details. |
michael@0 | 1828 | (void)NS_StackWalk(NopStackWalkCallback, /* skipFrames */ 0, |
michael@0 | 1829 | /* maxFrames */ 1, nullptr, 0, nullptr); |
michael@0 | 1830 | #endif |
michael@0 | 1831 | |
michael@0 | 1832 | gStateLock = InfallibleAllocPolicy::new_<Mutex>(); |
michael@0 | 1833 | |
michael@0 | 1834 | gSmallBlockActualSizeCounter = 0; |
michael@0 | 1835 | |
michael@0 | 1836 | DMD_CREATE_TLS_INDEX(gTlsIndex); |
michael@0 | 1837 | |
michael@0 | 1838 | { |
michael@0 | 1839 | AutoLockState lock; |
michael@0 | 1840 | |
michael@0 | 1841 | gStackTraceTable = InfallibleAllocPolicy::new_<StackTraceTable>(); |
michael@0 | 1842 | gStackTraceTable->init(8192); |
michael@0 | 1843 | |
michael@0 | 1844 | gBlockTable = InfallibleAllocPolicy::new_<BlockTable>(); |
michael@0 | 1845 | gBlockTable->init(8192); |
michael@0 | 1846 | } |
michael@0 | 1847 | |
michael@0 | 1848 | if (gOptions->IsTestMode()) { |
michael@0 | 1849 | // OpenOutputFile() can allocate. So do this before setting |
michael@0 | 1850 | // gIsDMDRunning so those allocations don't show up in our results. Once |
michael@0 | 1851 | // gIsDMDRunning is set we are intercepting malloc et al. in earnest. |
michael@0 | 1852 | FILE* fp = OpenOutputFile("test.dmd"); |
michael@0 | 1853 | gIsDMDRunning = true; |
michael@0 | 1854 | |
michael@0 | 1855 | StatusMsg("running test mode...\n"); |
michael@0 | 1856 | RunTestMode(fp); |
michael@0 | 1857 | StatusMsg("finished test mode\n"); |
michael@0 | 1858 | fclose(fp); |
michael@0 | 1859 | exit(0); |
michael@0 | 1860 | } |
michael@0 | 1861 | |
michael@0 | 1862 | if (gOptions->IsStressMode()) { |
michael@0 | 1863 | FILE* fp = OpenOutputFile("stress.dmd"); |
michael@0 | 1864 | gIsDMDRunning = true; |
michael@0 | 1865 | |
michael@0 | 1866 | StatusMsg("running stress mode...\n"); |
michael@0 | 1867 | RunStressMode(fp); |
michael@0 | 1868 | StatusMsg("finished stress mode\n"); |
michael@0 | 1869 | fclose(fp); |
michael@0 | 1870 | exit(0); |
michael@0 | 1871 | } |
michael@0 | 1872 | |
michael@0 | 1873 | gIsDMDRunning = true; |
michael@0 | 1874 | } |
michael@0 | 1875 | |
michael@0 | 1876 | //--------------------------------------------------------------------------- |
michael@0 | 1877 | // DMD reporting and unreporting |
michael@0 | 1878 | //--------------------------------------------------------------------------- |
michael@0 | 1879 | |
michael@0 | 1880 | static void |
michael@0 | 1881 | ReportHelper(const void* aPtr, bool aReportedOnAlloc) |
michael@0 | 1882 | { |
michael@0 | 1883 | if (!gIsDMDRunning || !aPtr) { |
michael@0 | 1884 | return; |
michael@0 | 1885 | } |
michael@0 | 1886 | |
michael@0 | 1887 | Thread* t = Thread::Fetch(); |
michael@0 | 1888 | |
michael@0 | 1889 | AutoBlockIntercepts block(t); |
michael@0 | 1890 | AutoLockState lock; |
michael@0 | 1891 | |
michael@0 | 1892 | if (BlockTable::Ptr p = gBlockTable->lookup(aPtr)) { |
michael@0 | 1893 | p->Report(t, aReportedOnAlloc); |
michael@0 | 1894 | } else { |
michael@0 | 1895 | // We have no record of the block. Do nothing. Either: |
michael@0 | 1896 | // - We're sampling and we skipped this block. This is likely. |
michael@0 | 1897 | // - It's a bogus pointer. This is unlikely because Report() is almost |
michael@0 | 1898 | // always called in conjunction with a malloc_size_of-style function. |
michael@0 | 1899 | } |
michael@0 | 1900 | } |
michael@0 | 1901 | |
michael@0 | 1902 | MOZ_EXPORT void |
michael@0 | 1903 | Report(const void* aPtr) |
michael@0 | 1904 | { |
michael@0 | 1905 | ReportHelper(aPtr, /* onAlloc */ false); |
michael@0 | 1906 | } |
michael@0 | 1907 | |
michael@0 | 1908 | MOZ_EXPORT void |
michael@0 | 1909 | ReportOnAlloc(const void* aPtr) |
michael@0 | 1910 | { |
michael@0 | 1911 | ReportHelper(aPtr, /* onAlloc */ true); |
michael@0 | 1912 | } |
michael@0 | 1913 | |
michael@0 | 1914 | //--------------------------------------------------------------------------- |
michael@0 | 1915 | // DMD output |
michael@0 | 1916 | //--------------------------------------------------------------------------- |
michael@0 | 1917 | |
michael@0 | 1918 | // This works for both TraceRecords and StackFrameRecords. |
michael@0 | 1919 | template <class Record> |
michael@0 | 1920 | static void |
michael@0 | 1921 | PrintSortedRecords(const Writer& aWriter, LocationService* aLocService, |
michael@0 | 1922 | const char* aStr, const char* astr, |
michael@0 | 1923 | const js::HashSet<Record, Record, InfallibleAllocPolicy>& |
michael@0 | 1924 | aRecordTable, |
michael@0 | 1925 | size_t aCategoryUsableSize, size_t aTotalUsableSize) |
michael@0 | 1926 | { |
michael@0 | 1927 | const char* kind = Record::kRecordKind; |
michael@0 | 1928 | StatusMsg(" creating and sorting %s stack %s record array...\n", astr, kind); |
michael@0 | 1929 | |
michael@0 | 1930 | // Convert the table into a sorted array. |
michael@0 | 1931 | js::Vector<const Record*, 0, InfallibleAllocPolicy> recordArray; |
michael@0 | 1932 | recordArray.reserve(aRecordTable.count()); |
michael@0 | 1933 | typedef js::HashSet<Record, Record, InfallibleAllocPolicy> RecordTable; |
michael@0 | 1934 | for (typename RecordTable::Range r = aRecordTable.all(); |
michael@0 | 1935 | !r.empty(); |
michael@0 | 1936 | r.popFront()) { |
michael@0 | 1937 | recordArray.infallibleAppend(&r.front()); |
michael@0 | 1938 | } |
michael@0 | 1939 | qsort(recordArray.begin(), recordArray.length(), sizeof(recordArray[0]), |
michael@0 | 1940 | Record::QsortCmp); |
michael@0 | 1941 | |
michael@0 | 1942 | WriteTitle("%s stack %s records\n", aStr, kind); |
michael@0 | 1943 | |
michael@0 | 1944 | if (recordArray.length() == 0) { |
michael@0 | 1945 | W("(none)\n\n"); |
michael@0 | 1946 | return; |
michael@0 | 1947 | } |
michael@0 | 1948 | |
michael@0 | 1949 | StatusMsg(" printing %s stack %s record array...\n", astr, kind); |
michael@0 | 1950 | size_t cumulativeUsableSize = 0; |
michael@0 | 1951 | |
michael@0 | 1952 | // Limit the number of records printed, because fix-linux-stack.pl is too |
michael@0 | 1953 | // damn slow. Note that we don't break out of this loop because we need to |
michael@0 | 1954 | // keep adding to |cumulativeUsableSize|. |
michael@0 | 1955 | uint32_t numRecords = recordArray.length(); |
michael@0 | 1956 | uint32_t maxRecords = gOptions->MaxRecords(); |
michael@0 | 1957 | for (uint32_t i = 0; i < numRecords; i++) { |
michael@0 | 1958 | const Record* r = recordArray[i]; |
michael@0 | 1959 | cumulativeUsableSize += r->GetRecordSize().Usable(); |
michael@0 | 1960 | if (i < maxRecords) { |
michael@0 | 1961 | r->Print(aWriter, aLocService, i+1, numRecords, aStr, astr, |
michael@0 | 1962 | aCategoryUsableSize, cumulativeUsableSize, aTotalUsableSize); |
michael@0 | 1963 | } else if (i == maxRecords) { |
michael@0 | 1964 | W("%s: stopping after %s stack %s records\n\n", aStr, |
michael@0 | 1965 | Show(maxRecords, gBuf1, kBufLen), kind); |
michael@0 | 1966 | } |
michael@0 | 1967 | } |
michael@0 | 1968 | |
michael@0 | 1969 | // This holds for TraceRecords, but not for FrameRecords. |
michael@0 | 1970 | MOZ_ASSERT_IF(!Record::recordsOverlap(), |
michael@0 | 1971 | aCategoryUsableSize == cumulativeUsableSize); |
michael@0 | 1972 | } |
michael@0 | 1973 | |
michael@0 | 1974 | static void |
michael@0 | 1975 | PrintSortedTraceAndFrameRecords(const Writer& aWriter, |
michael@0 | 1976 | LocationService* aLocService, |
michael@0 | 1977 | const char* aStr, const char* astr, |
michael@0 | 1978 | const TraceRecordTable& aTraceRecordTable, |
michael@0 | 1979 | size_t aCategoryUsableSize, |
michael@0 | 1980 | size_t aTotalUsableSize) |
michael@0 | 1981 | { |
michael@0 | 1982 | PrintSortedRecords(aWriter, aLocService, aStr, astr, aTraceRecordTable, |
michael@0 | 1983 | aCategoryUsableSize, aTotalUsableSize); |
michael@0 | 1984 | |
michael@0 | 1985 | FrameRecordTable frameRecordTable; |
michael@0 | 1986 | (void)frameRecordTable.init(2048); |
michael@0 | 1987 | for (TraceRecordTable::Range r = aTraceRecordTable.all(); |
michael@0 | 1988 | !r.empty(); |
michael@0 | 1989 | r.popFront()) { |
michael@0 | 1990 | const TraceRecord& tr = r.front(); |
michael@0 | 1991 | const StackTrace* st = tr.mAllocStackTrace; |
michael@0 | 1992 | |
michael@0 | 1993 | // A single PC can appear multiple times in a stack trace. We ignore |
michael@0 | 1994 | // duplicates by first sorting and then ignoring adjacent duplicates. |
michael@0 | 1995 | StackTrace sorted(*st); |
michael@0 | 1996 | sorted.Sort(); // sorts the copy, not the original |
michael@0 | 1997 | void* prevPc = (void*)intptr_t(-1); |
michael@0 | 1998 | for (uint32_t i = 0; i < sorted.Length(); i++) { |
michael@0 | 1999 | void* pc = sorted.Pc(i); |
michael@0 | 2000 | if (pc == prevPc) { |
michael@0 | 2001 | continue; // ignore duplicate |
michael@0 | 2002 | } |
michael@0 | 2003 | prevPc = pc; |
michael@0 | 2004 | |
michael@0 | 2005 | FrameRecordTable::AddPtr p = frameRecordTable.lookupForAdd(pc); |
michael@0 | 2006 | if (!p) { |
michael@0 | 2007 | FrameRecord fr(pc); |
michael@0 | 2008 | (void)frameRecordTable.add(p, fr); |
michael@0 | 2009 | } |
michael@0 | 2010 | p->Add(tr); |
michael@0 | 2011 | } |
michael@0 | 2012 | } |
michael@0 | 2013 | |
michael@0 | 2014 | PrintSortedRecords(aWriter, aLocService, aStr, astr, frameRecordTable, |
michael@0 | 2015 | aCategoryUsableSize, aTotalUsableSize); |
michael@0 | 2016 | } |
michael@0 | 2017 | |
michael@0 | 2018 | // Note that, unlike most SizeOf* functions, this function does not take a |
michael@0 | 2019 | // |mozilla::MallocSizeOf| argument. That's because those arguments are |
michael@0 | 2020 | // primarily to aid DMD track heap blocks... but DMD deliberately doesn't track |
michael@0 | 2021 | // heap blocks it allocated for itself! |
michael@0 | 2022 | // |
michael@0 | 2023 | // SizeOfInternal should be called while you're holding the state lock and |
michael@0 | 2024 | // while intercepts are blocked; SizeOf acquires the lock and blocks |
michael@0 | 2025 | // intercepts. |
michael@0 | 2026 | |
michael@0 | 2027 | static void |
michael@0 | 2028 | SizeOfInternal(Sizes* aSizes) |
michael@0 | 2029 | { |
michael@0 | 2030 | MOZ_ASSERT(gStateLock->IsLocked()); |
michael@0 | 2031 | MOZ_ASSERT(Thread::Fetch()->InterceptsAreBlocked()); |
michael@0 | 2032 | |
michael@0 | 2033 | aSizes->Clear(); |
michael@0 | 2034 | |
michael@0 | 2035 | if (!gIsDMDRunning) { |
michael@0 | 2036 | return; |
michael@0 | 2037 | } |
michael@0 | 2038 | |
michael@0 | 2039 | StackTraceSet usedStackTraces; |
michael@0 | 2040 | GatherUsedStackTraces(usedStackTraces); |
michael@0 | 2041 | |
michael@0 | 2042 | for (StackTraceTable::Range r = gStackTraceTable->all(); |
michael@0 | 2043 | !r.empty(); |
michael@0 | 2044 | r.popFront()) { |
michael@0 | 2045 | StackTrace* const& st = r.front(); |
michael@0 | 2046 | |
michael@0 | 2047 | if (usedStackTraces.has(st)) { |
michael@0 | 2048 | aSizes->mStackTracesUsed += MallocSizeOf(st); |
michael@0 | 2049 | } else { |
michael@0 | 2050 | aSizes->mStackTracesUnused += MallocSizeOf(st); |
michael@0 | 2051 | } |
michael@0 | 2052 | } |
michael@0 | 2053 | |
michael@0 | 2054 | aSizes->mStackTraceTable = |
michael@0 | 2055 | gStackTraceTable->sizeOfIncludingThis(MallocSizeOf); |
michael@0 | 2056 | |
michael@0 | 2057 | aSizes->mBlockTable = gBlockTable->sizeOfIncludingThis(MallocSizeOf); |
michael@0 | 2058 | } |
michael@0 | 2059 | |
michael@0 | 2060 | MOZ_EXPORT void |
michael@0 | 2061 | SizeOf(Sizes* aSizes) |
michael@0 | 2062 | { |
michael@0 | 2063 | aSizes->Clear(); |
michael@0 | 2064 | |
michael@0 | 2065 | if (!gIsDMDRunning) { |
michael@0 | 2066 | return; |
michael@0 | 2067 | } |
michael@0 | 2068 | |
michael@0 | 2069 | AutoBlockIntercepts block(Thread::Fetch()); |
michael@0 | 2070 | AutoLockState lock; |
michael@0 | 2071 | SizeOfInternal(aSizes); |
michael@0 | 2072 | } |
michael@0 | 2073 | |
michael@0 | 2074 | void |
michael@0 | 2075 | ClearReportsInternal() |
michael@0 | 2076 | { |
michael@0 | 2077 | MOZ_ASSERT(gStateLock->IsLocked()); |
michael@0 | 2078 | |
michael@0 | 2079 | // Unreport all blocks that were marked reported by a memory reporter. This |
michael@0 | 2080 | // excludes those that were reported on allocation, because they need to keep |
michael@0 | 2081 | // their reported marking. |
michael@0 | 2082 | for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) { |
michael@0 | 2083 | r.front().UnreportIfNotReportedOnAlloc(); |
michael@0 | 2084 | } |
michael@0 | 2085 | } |
michael@0 | 2086 | |
michael@0 | 2087 | MOZ_EXPORT void |
michael@0 | 2088 | ClearReports() |
michael@0 | 2089 | { |
michael@0 | 2090 | if (!gIsDMDRunning) { |
michael@0 | 2091 | return; |
michael@0 | 2092 | } |
michael@0 | 2093 | |
michael@0 | 2094 | AutoLockState lock; |
michael@0 | 2095 | ClearReportsInternal(); |
michael@0 | 2096 | } |
michael@0 | 2097 | |
michael@0 | 2098 | MOZ_EXPORT bool |
michael@0 | 2099 | IsEnabled() |
michael@0 | 2100 | { |
michael@0 | 2101 | return gIsDMDRunning; |
michael@0 | 2102 | } |
michael@0 | 2103 | |
michael@0 | 2104 | MOZ_EXPORT void |
michael@0 | 2105 | Dump(Writer aWriter) |
michael@0 | 2106 | { |
michael@0 | 2107 | if (!gIsDMDRunning) { |
michael@0 | 2108 | const char* msg = "cannot Dump(); DMD was not enabled at startup\n"; |
michael@0 | 2109 | StatusMsg("%s", msg); |
michael@0 | 2110 | W("%s", msg); |
michael@0 | 2111 | return; |
michael@0 | 2112 | } |
michael@0 | 2113 | |
michael@0 | 2114 | AutoBlockIntercepts block(Thread::Fetch()); |
michael@0 | 2115 | AutoLockState lock; |
michael@0 | 2116 | |
michael@0 | 2117 | static int dumpCount = 1; |
michael@0 | 2118 | StatusMsg("Dump %d {\n", dumpCount++); |
michael@0 | 2119 | |
michael@0 | 2120 | StatusMsg(" gathering stack trace records...\n"); |
michael@0 | 2121 | |
michael@0 | 2122 | TraceRecordTable unreportedTraceRecordTable; |
michael@0 | 2123 | (void)unreportedTraceRecordTable.init(1024); |
michael@0 | 2124 | size_t unreportedUsableSize = 0; |
michael@0 | 2125 | size_t unreportedNumBlocks = 0; |
michael@0 | 2126 | |
michael@0 | 2127 | TraceRecordTable onceReportedTraceRecordTable; |
michael@0 | 2128 | (void)onceReportedTraceRecordTable.init(1024); |
michael@0 | 2129 | size_t onceReportedUsableSize = 0; |
michael@0 | 2130 | size_t onceReportedNumBlocks = 0; |
michael@0 | 2131 | |
michael@0 | 2132 | TraceRecordTable twiceReportedTraceRecordTable; |
michael@0 | 2133 | (void)twiceReportedTraceRecordTable.init(0); |
michael@0 | 2134 | size_t twiceReportedUsableSize = 0; |
michael@0 | 2135 | size_t twiceReportedNumBlocks = 0; |
michael@0 | 2136 | |
michael@0 | 2137 | bool anyBlocksSampled = false; |
michael@0 | 2138 | |
michael@0 | 2139 | for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) { |
michael@0 | 2140 | const Block& b = r.front(); |
michael@0 | 2141 | |
michael@0 | 2142 | TraceRecordTable* table; |
michael@0 | 2143 | uint32_t numReports = b.NumReports(); |
michael@0 | 2144 | if (numReports == 0) { |
michael@0 | 2145 | unreportedUsableSize += b.UsableSize(); |
michael@0 | 2146 | unreportedNumBlocks++; |
michael@0 | 2147 | table = &unreportedTraceRecordTable; |
michael@0 | 2148 | } else if (numReports == 1) { |
michael@0 | 2149 | onceReportedUsableSize += b.UsableSize(); |
michael@0 | 2150 | onceReportedNumBlocks++; |
michael@0 | 2151 | table = &onceReportedTraceRecordTable; |
michael@0 | 2152 | } else { |
michael@0 | 2153 | MOZ_ASSERT(numReports == 2); |
michael@0 | 2154 | twiceReportedUsableSize += b.UsableSize(); |
michael@0 | 2155 | twiceReportedNumBlocks++; |
michael@0 | 2156 | table = &twiceReportedTraceRecordTable; |
michael@0 | 2157 | } |
michael@0 | 2158 | TraceRecordKey key(b); |
michael@0 | 2159 | TraceRecordTable::AddPtr p = table->lookupForAdd(key); |
michael@0 | 2160 | if (!p) { |
michael@0 | 2161 | TraceRecord tr(b); |
michael@0 | 2162 | (void)table->add(p, tr); |
michael@0 | 2163 | } |
michael@0 | 2164 | p->Add(b); |
michael@0 | 2165 | |
michael@0 | 2166 | anyBlocksSampled = anyBlocksSampled || b.IsSampled(); |
michael@0 | 2167 | } |
michael@0 | 2168 | size_t totalUsableSize = |
michael@0 | 2169 | unreportedUsableSize + onceReportedUsableSize + twiceReportedUsableSize; |
michael@0 | 2170 | size_t totalNumBlocks = |
michael@0 | 2171 | unreportedNumBlocks + onceReportedNumBlocks + twiceReportedNumBlocks; |
michael@0 | 2172 | |
michael@0 | 2173 | WriteTitle("Invocation\n"); |
michael@0 | 2174 | W("$DMD = '%s'\n", gOptions->DMDEnvVar()); |
michael@0 | 2175 | W("Sample-below size = %lld\n\n", |
michael@0 | 2176 | (long long)(gOptions->SampleBelowSize())); |
michael@0 | 2177 | |
michael@0 | 2178 | // Allocate this on the heap instead of the stack because it's fairly large. |
michael@0 | 2179 | LocationService* locService = InfallibleAllocPolicy::new_<LocationService>(); |
michael@0 | 2180 | |
michael@0 | 2181 | PrintSortedRecords(aWriter, locService, "Twice-reported", "twice-reported", |
michael@0 | 2182 | twiceReportedTraceRecordTable, twiceReportedUsableSize, |
michael@0 | 2183 | totalUsableSize); |
michael@0 | 2184 | |
michael@0 | 2185 | PrintSortedTraceAndFrameRecords(aWriter, locService, |
michael@0 | 2186 | "Unreported", "unreported", |
michael@0 | 2187 | unreportedTraceRecordTable, |
michael@0 | 2188 | unreportedUsableSize, totalUsableSize); |
michael@0 | 2189 | |
michael@0 | 2190 | PrintSortedTraceAndFrameRecords(aWriter, locService, |
michael@0 | 2191 | "Once-reported", "once-reported", |
michael@0 | 2192 | onceReportedTraceRecordTable, |
michael@0 | 2193 | onceReportedUsableSize, totalUsableSize); |
michael@0 | 2194 | |
michael@0 | 2195 | bool showTilde = anyBlocksSampled; |
michael@0 | 2196 | WriteTitle("Summary\n"); |
michael@0 | 2197 | |
michael@0 | 2198 | W("Total: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n", |
michael@0 | 2199 | Show(totalUsableSize, gBuf1, kBufLen, showTilde), |
michael@0 | 2200 | 100.0, |
michael@0 | 2201 | Show(totalNumBlocks, gBuf2, kBufLen, showTilde), |
michael@0 | 2202 | 100.0); |
michael@0 | 2203 | |
michael@0 | 2204 | W("Unreported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n", |
michael@0 | 2205 | Show(unreportedUsableSize, gBuf1, kBufLen, showTilde), |
michael@0 | 2206 | Percent(unreportedUsableSize, totalUsableSize), |
michael@0 | 2207 | Show(unreportedNumBlocks, gBuf2, kBufLen, showTilde), |
michael@0 | 2208 | Percent(unreportedNumBlocks, totalNumBlocks)); |
michael@0 | 2209 | |
michael@0 | 2210 | W("Once-reported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n", |
michael@0 | 2211 | Show(onceReportedUsableSize, gBuf1, kBufLen, showTilde), |
michael@0 | 2212 | Percent(onceReportedUsableSize, totalUsableSize), |
michael@0 | 2213 | Show(onceReportedNumBlocks, gBuf2, kBufLen, showTilde), |
michael@0 | 2214 | Percent(onceReportedNumBlocks, totalNumBlocks)); |
michael@0 | 2215 | |
michael@0 | 2216 | W("Twice-reported: %12s bytes (%6.2f%%) in %7s blocks (%6.2f%%)\n", |
michael@0 | 2217 | Show(twiceReportedUsableSize, gBuf1, kBufLen, showTilde), |
michael@0 | 2218 | Percent(twiceReportedUsableSize, totalUsableSize), |
michael@0 | 2219 | Show(twiceReportedNumBlocks, gBuf2, kBufLen, showTilde), |
michael@0 | 2220 | Percent(twiceReportedNumBlocks, totalNumBlocks)); |
michael@0 | 2221 | |
michael@0 | 2222 | W("\n"); |
michael@0 | 2223 | |
michael@0 | 2224 | // Stats are non-deterministic, so don't show them in test mode. |
michael@0 | 2225 | if (!gOptions->IsTestMode()) { |
michael@0 | 2226 | Sizes sizes; |
michael@0 | 2227 | SizeOfInternal(&sizes); |
michael@0 | 2228 | |
michael@0 | 2229 | WriteTitle("Execution measurements\n"); |
michael@0 | 2230 | |
michael@0 | 2231 | W("Data structures that persist after Dump() ends:\n"); |
michael@0 | 2232 | |
michael@0 | 2233 | W(" Used stack traces: %10s bytes\n", |
michael@0 | 2234 | Show(sizes.mStackTracesUsed, gBuf1, kBufLen)); |
michael@0 | 2235 | |
michael@0 | 2236 | W(" Unused stack traces: %10s bytes\n", |
michael@0 | 2237 | Show(sizes.mStackTracesUnused, gBuf1, kBufLen)); |
michael@0 | 2238 | |
michael@0 | 2239 | W(" Stack trace table: %10s bytes (%s entries, %s used)\n", |
michael@0 | 2240 | Show(sizes.mStackTraceTable, gBuf1, kBufLen), |
michael@0 | 2241 | Show(gStackTraceTable->capacity(), gBuf2, kBufLen), |
michael@0 | 2242 | Show(gStackTraceTable->count(), gBuf3, kBufLen)); |
michael@0 | 2243 | |
michael@0 | 2244 | W(" Block table: %10s bytes (%s entries, %s used)\n", |
michael@0 | 2245 | Show(sizes.mBlockTable, gBuf1, kBufLen), |
michael@0 | 2246 | Show(gBlockTable->capacity(), gBuf2, kBufLen), |
michael@0 | 2247 | Show(gBlockTable->count(), gBuf3, kBufLen)); |
michael@0 | 2248 | |
michael@0 | 2249 | W("\nData structures that are destroyed after Dump() ends:\n"); |
michael@0 | 2250 | |
michael@0 | 2251 | size_t unreportedSize = |
michael@0 | 2252 | unreportedTraceRecordTable.sizeOfIncludingThis(MallocSizeOf); |
michael@0 | 2253 | W(" Unreported table: %10s bytes (%s entries, %s used)\n", |
michael@0 | 2254 | Show(unreportedSize, gBuf1, kBufLen), |
michael@0 | 2255 | Show(unreportedTraceRecordTable.capacity(), gBuf2, kBufLen), |
michael@0 | 2256 | Show(unreportedTraceRecordTable.count(), gBuf3, kBufLen)); |
michael@0 | 2257 | |
michael@0 | 2258 | size_t onceReportedSize = |
michael@0 | 2259 | onceReportedTraceRecordTable.sizeOfIncludingThis(MallocSizeOf); |
michael@0 | 2260 | W(" Once-reported table: %10s bytes (%s entries, %s used)\n", |
michael@0 | 2261 | Show(onceReportedSize, gBuf1, kBufLen), |
michael@0 | 2262 | Show(onceReportedTraceRecordTable.capacity(), gBuf2, kBufLen), |
michael@0 | 2263 | Show(onceReportedTraceRecordTable.count(), gBuf3, kBufLen)); |
michael@0 | 2264 | |
michael@0 | 2265 | size_t twiceReportedSize = |
michael@0 | 2266 | twiceReportedTraceRecordTable.sizeOfIncludingThis(MallocSizeOf); |
michael@0 | 2267 | W(" Twice-reported table: %10s bytes (%s entries, %s used)\n", |
michael@0 | 2268 | Show(twiceReportedSize, gBuf1, kBufLen), |
michael@0 | 2269 | Show(twiceReportedTraceRecordTable.capacity(), gBuf2, kBufLen), |
michael@0 | 2270 | Show(twiceReportedTraceRecordTable.count(), gBuf3, kBufLen)); |
michael@0 | 2271 | |
michael@0 | 2272 | W(" Location service: %10s bytes\n", |
michael@0 | 2273 | Show(locService->SizeOfIncludingThis(), gBuf1, kBufLen)); |
michael@0 | 2274 | |
michael@0 | 2275 | W("\nCounts:\n"); |
michael@0 | 2276 | |
michael@0 | 2277 | size_t hits = locService->NumCacheHits(); |
michael@0 | 2278 | size_t misses = locService->NumCacheMisses(); |
michael@0 | 2279 | size_t requests = hits + misses; |
michael@0 | 2280 | W(" Location service: %10s requests\n", |
michael@0 | 2281 | Show(requests, gBuf1, kBufLen)); |
michael@0 | 2282 | |
michael@0 | 2283 | size_t count = locService->CacheCount(); |
michael@0 | 2284 | size_t capacity = locService->CacheCapacity(); |
michael@0 | 2285 | W(" Location service cache: %4.1f%% hit rate, %.1f%% occupancy at end\n", |
michael@0 | 2286 | Percent(hits, requests), Percent(count, capacity)); |
michael@0 | 2287 | |
michael@0 | 2288 | W("\n"); |
michael@0 | 2289 | } |
michael@0 | 2290 | |
michael@0 | 2291 | InfallibleAllocPolicy::delete_(locService); |
michael@0 | 2292 | |
michael@0 | 2293 | ClearReportsInternal(); // Use internal version, we already have the lock. |
michael@0 | 2294 | |
michael@0 | 2295 | StatusMsg("}\n"); |
michael@0 | 2296 | } |
michael@0 | 2297 | |
michael@0 | 2298 | //--------------------------------------------------------------------------- |
michael@0 | 2299 | // Testing |
michael@0 | 2300 | //--------------------------------------------------------------------------- |
michael@0 | 2301 | |
michael@0 | 2302 | // This function checks that heap blocks that have the same stack trace but |
michael@0 | 2303 | // different (or no) reporters get aggregated separately. |
michael@0 | 2304 | void foo() |
michael@0 | 2305 | { |
michael@0 | 2306 | char* a[6]; |
michael@0 | 2307 | for (int i = 0; i < 6; i++) { |
michael@0 | 2308 | a[i] = (char*) malloc(128 - 16*i); |
michael@0 | 2309 | } |
michael@0 | 2310 | |
michael@0 | 2311 | for (int i = 0; i <= 1; i++) |
michael@0 | 2312 | Report(a[i]); // reported |
michael@0 | 2313 | Report(a[2]); // reported |
michael@0 | 2314 | Report(a[3]); // reported |
michael@0 | 2315 | // a[4], a[5] unreported |
michael@0 | 2316 | } |
michael@0 | 2317 | |
michael@0 | 2318 | // This stops otherwise-unused variables from being optimized away. |
michael@0 | 2319 | static void |
michael@0 | 2320 | UseItOrLoseIt(void* a) |
michael@0 | 2321 | { |
michael@0 | 2322 | char buf[64]; |
michael@0 | 2323 | sprintf(buf, "%p\n", a); |
michael@0 | 2324 | fwrite(buf, 1, strlen(buf) + 1, stderr); |
michael@0 | 2325 | } |
michael@0 | 2326 | |
michael@0 | 2327 | // The output from this should be compared against test-expected.dmd. It's |
michael@0 | 2328 | // been tested on Linux64, and probably will give different results on other |
michael@0 | 2329 | // platforms. |
michael@0 | 2330 | static void |
michael@0 | 2331 | RunTestMode(FILE* fp) |
michael@0 | 2332 | { |
michael@0 | 2333 | Writer writer(FpWrite, fp); |
michael@0 | 2334 | |
michael@0 | 2335 | // The first part of this test requires sampling to be disabled. |
michael@0 | 2336 | gOptions->SetSampleBelowSize(1); |
michael@0 | 2337 | |
michael@0 | 2338 | // Dump 1. Zero for everything. |
michael@0 | 2339 | Dump(writer); |
michael@0 | 2340 | |
michael@0 | 2341 | // Dump 2: 1 freed, 9 out of 10 unreported. |
michael@0 | 2342 | // Dump 3: still present and unreported. |
michael@0 | 2343 | int i; |
michael@0 | 2344 | char* a; |
michael@0 | 2345 | for (i = 0; i < 10; i++) { |
michael@0 | 2346 | a = (char*) malloc(100); |
michael@0 | 2347 | UseItOrLoseIt(a); |
michael@0 | 2348 | } |
michael@0 | 2349 | free(a); |
michael@0 | 2350 | |
michael@0 | 2351 | // Min-sized block. |
michael@0 | 2352 | // Dump 2: reported. |
michael@0 | 2353 | // Dump 3: thrice-reported. |
michael@0 | 2354 | char* a2 = (char*) malloc(0); |
michael@0 | 2355 | Report(a2); |
michael@0 | 2356 | |
michael@0 | 2357 | // Operator new[]. |
michael@0 | 2358 | // Dump 2: reported. |
michael@0 | 2359 | // Dump 3: reportedness carries over, due to ReportOnAlloc. |
michael@0 | 2360 | char* b = new char[10]; |
michael@0 | 2361 | ReportOnAlloc(b); |
michael@0 | 2362 | |
michael@0 | 2363 | // ReportOnAlloc, then freed. |
michael@0 | 2364 | // Dump 2: freed, irrelevant. |
michael@0 | 2365 | // Dump 3: freed, irrelevant. |
michael@0 | 2366 | char* b2 = new char; |
michael@0 | 2367 | ReportOnAlloc(b2); |
michael@0 | 2368 | free(b2); |
michael@0 | 2369 | |
michael@0 | 2370 | // Dump 2: reported 4 times. |
michael@0 | 2371 | // Dump 3: freed, irrelevant. |
michael@0 | 2372 | char* c = (char*) calloc(10, 3); |
michael@0 | 2373 | Report(c); |
michael@0 | 2374 | for (int i = 0; i < 3; i++) { |
michael@0 | 2375 | Report(c); |
michael@0 | 2376 | } |
michael@0 | 2377 | |
michael@0 | 2378 | // Dump 2: ignored. |
michael@0 | 2379 | // Dump 3: irrelevant. |
michael@0 | 2380 | Report((void*)(intptr_t)i); |
michael@0 | 2381 | |
michael@0 | 2382 | // jemalloc rounds this up to 8192. |
michael@0 | 2383 | // Dump 2: reported. |
michael@0 | 2384 | // Dump 3: freed. |
michael@0 | 2385 | char* e = (char*) malloc(4096); |
michael@0 | 2386 | e = (char*) realloc(e, 4097); |
michael@0 | 2387 | Report(e); |
michael@0 | 2388 | |
michael@0 | 2389 | // First realloc is like malloc; second realloc is shrinking. |
michael@0 | 2390 | // Dump 2: reported. |
michael@0 | 2391 | // Dump 3: re-reported. |
michael@0 | 2392 | char* e2 = (char*) realloc(nullptr, 1024); |
michael@0 | 2393 | e2 = (char*) realloc(e2, 512); |
michael@0 | 2394 | Report(e2); |
michael@0 | 2395 | |
michael@0 | 2396 | // First realloc is like malloc; second realloc creates a min-sized block. |
michael@0 | 2397 | // XXX: on Windows, second realloc frees the block. |
michael@0 | 2398 | // Dump 2: reported. |
michael@0 | 2399 | // Dump 3: freed, irrelevant. |
michael@0 | 2400 | char* e3 = (char*) realloc(nullptr, 1023); |
michael@0 | 2401 | //e3 = (char*) realloc(e3, 0); |
michael@0 | 2402 | MOZ_ASSERT(e3); |
michael@0 | 2403 | Report(e3); |
michael@0 | 2404 | |
michael@0 | 2405 | // Dump 2: freed, irrelevant. |
michael@0 | 2406 | // Dump 3: freed, irrelevant. |
michael@0 | 2407 | char* f = (char*) malloc(64); |
michael@0 | 2408 | free(f); |
michael@0 | 2409 | |
michael@0 | 2410 | // Dump 2: ignored. |
michael@0 | 2411 | // Dump 3: irrelevant. |
michael@0 | 2412 | Report((void*)(intptr_t)0x0); |
michael@0 | 2413 | |
michael@0 | 2414 | // Dump 2: mixture of reported and unreported. |
michael@0 | 2415 | // Dump 3: all unreported. |
michael@0 | 2416 | foo(); |
michael@0 | 2417 | foo(); |
michael@0 | 2418 | |
michael@0 | 2419 | // Dump 2: twice-reported. |
michael@0 | 2420 | // Dump 3: twice-reported. |
michael@0 | 2421 | char* g1 = (char*) malloc(77); |
michael@0 | 2422 | ReportOnAlloc(g1); |
michael@0 | 2423 | ReportOnAlloc(g1); |
michael@0 | 2424 | |
michael@0 | 2425 | // Dump 2: twice-reported. |
michael@0 | 2426 | // Dump 3: once-reported. |
michael@0 | 2427 | char* g2 = (char*) malloc(78); |
michael@0 | 2428 | Report(g2); |
michael@0 | 2429 | ReportOnAlloc(g2); |
michael@0 | 2430 | |
michael@0 | 2431 | // Dump 2: twice-reported. |
michael@0 | 2432 | // Dump 3: once-reported. |
michael@0 | 2433 | char* g3 = (char*) malloc(79); |
michael@0 | 2434 | ReportOnAlloc(g3); |
michael@0 | 2435 | Report(g3); |
michael@0 | 2436 | |
michael@0 | 2437 | // All the odd-ball ones. |
michael@0 | 2438 | // Dump 2: all unreported. |
michael@0 | 2439 | // Dump 3: all freed, irrelevant. |
michael@0 | 2440 | // XXX: no memalign on Mac |
michael@0 | 2441 | //void* x = memalign(64, 65); // rounds up to 128 |
michael@0 | 2442 | //UseItOrLoseIt(x); |
michael@0 | 2443 | // XXX: posix_memalign doesn't work on B2G |
michael@0 | 2444 | //void* y; |
michael@0 | 2445 | //posix_memalign(&y, 128, 129); // rounds up to 256 |
michael@0 | 2446 | //UseItOrLoseIt(y); |
michael@0 | 2447 | // XXX: valloc doesn't work on Windows. |
michael@0 | 2448 | //void* z = valloc(1); // rounds up to 4096 |
michael@0 | 2449 | //UseItOrLoseIt(z); |
michael@0 | 2450 | //aligned_alloc(64, 256); // XXX: C11 only |
michael@0 | 2451 | |
michael@0 | 2452 | // Dump 2. |
michael@0 | 2453 | Dump(writer); |
michael@0 | 2454 | |
michael@0 | 2455 | //--------- |
michael@0 | 2456 | |
michael@0 | 2457 | Report(a2); |
michael@0 | 2458 | Report(a2); |
michael@0 | 2459 | free(c); |
michael@0 | 2460 | free(e); |
michael@0 | 2461 | Report(e2); |
michael@0 | 2462 | free(e3); |
michael@0 | 2463 | //free(x); |
michael@0 | 2464 | //free(y); |
michael@0 | 2465 | //free(z); |
michael@0 | 2466 | |
michael@0 | 2467 | // Dump 3. |
michael@0 | 2468 | Dump(writer); |
michael@0 | 2469 | |
michael@0 | 2470 | //--------- |
michael@0 | 2471 | |
michael@0 | 2472 | // Clear all knowledge of existing blocks to give us a clean slate. |
michael@0 | 2473 | gBlockTable->clear(); |
michael@0 | 2474 | |
michael@0 | 2475 | gOptions->SetSampleBelowSize(128); |
michael@0 | 2476 | |
michael@0 | 2477 | char* s; |
michael@0 | 2478 | |
michael@0 | 2479 | // This equals the sample size, and so is reported exactly. It should be |
michael@0 | 2480 | // listed before records of the same size that are sampled. |
michael@0 | 2481 | s = (char*) malloc(128); |
michael@0 | 2482 | UseItOrLoseIt(s); |
michael@0 | 2483 | |
michael@0 | 2484 | // This exceeds the sample size, and so is reported exactly. |
michael@0 | 2485 | s = (char*) malloc(144); |
michael@0 | 2486 | UseItOrLoseIt(s); |
michael@0 | 2487 | |
michael@0 | 2488 | // These together constitute exactly one sample. |
michael@0 | 2489 | for (int i = 0; i < 16; i++) { |
michael@0 | 2490 | s = (char*) malloc(8); |
michael@0 | 2491 | UseItOrLoseIt(s); |
michael@0 | 2492 | } |
michael@0 | 2493 | MOZ_ASSERT(gSmallBlockActualSizeCounter == 0); |
michael@0 | 2494 | |
michael@0 | 2495 | // These fall 8 bytes short of a full sample. |
michael@0 | 2496 | for (int i = 0; i < 15; i++) { |
michael@0 | 2497 | s = (char*) malloc(8); |
michael@0 | 2498 | UseItOrLoseIt(s); |
michael@0 | 2499 | } |
michael@0 | 2500 | MOZ_ASSERT(gSmallBlockActualSizeCounter == 120); |
michael@0 | 2501 | |
michael@0 | 2502 | // This exceeds the sample size, and so is recorded exactly. |
michael@0 | 2503 | s = (char*) malloc(256); |
michael@0 | 2504 | UseItOrLoseIt(s); |
michael@0 | 2505 | MOZ_ASSERT(gSmallBlockActualSizeCounter == 120); |
michael@0 | 2506 | |
michael@0 | 2507 | // This gets more than to a full sample from the |i < 15| loop above. |
michael@0 | 2508 | s = (char*) malloc(96); |
michael@0 | 2509 | UseItOrLoseIt(s); |
michael@0 | 2510 | MOZ_ASSERT(gSmallBlockActualSizeCounter == 88); |
michael@0 | 2511 | |
michael@0 | 2512 | // This gets to another full sample. |
michael@0 | 2513 | for (int i = 0; i < 5; i++) { |
michael@0 | 2514 | s = (char*) malloc(8); |
michael@0 | 2515 | UseItOrLoseIt(s); |
michael@0 | 2516 | } |
michael@0 | 2517 | MOZ_ASSERT(gSmallBlockActualSizeCounter == 0); |
michael@0 | 2518 | |
michael@0 | 2519 | // This allocates 16, 32, ..., 128 bytes, which results in a stack trace |
michael@0 | 2520 | // record that contains a mix of sample and non-sampled blocks, and so should |
michael@0 | 2521 | // be printed with '~' signs. |
michael@0 | 2522 | for (int i = 1; i <= 8; i++) { |
michael@0 | 2523 | s = (char*) malloc(i * 16); |
michael@0 | 2524 | UseItOrLoseIt(s); |
michael@0 | 2525 | } |
michael@0 | 2526 | MOZ_ASSERT(gSmallBlockActualSizeCounter == 64); |
michael@0 | 2527 | |
michael@0 | 2528 | // At the end we're 64 bytes into the current sample so we report ~1,424 |
michael@0 | 2529 | // bytes of allocation overall, which is 64 less than the real value 1,488. |
michael@0 | 2530 | |
michael@0 | 2531 | // Dump 4. |
michael@0 | 2532 | Dump(writer); |
michael@0 | 2533 | } |
michael@0 | 2534 | |
michael@0 | 2535 | //--------------------------------------------------------------------------- |
michael@0 | 2536 | // Stress testing microbenchmark |
michael@0 | 2537 | //--------------------------------------------------------------------------- |
michael@0 | 2538 | |
michael@0 | 2539 | // This stops otherwise-unused variables from being optimized away. |
michael@0 | 2540 | static void |
michael@0 | 2541 | UseItOrLoseIt2(void* a) |
michael@0 | 2542 | { |
michael@0 | 2543 | if (a == (void*)0x42) { |
michael@0 | 2544 | printf("UseItOrLoseIt2\n"); |
michael@0 | 2545 | } |
michael@0 | 2546 | } |
michael@0 | 2547 | |
michael@0 | 2548 | MOZ_NEVER_INLINE static void |
michael@0 | 2549 | stress5() |
michael@0 | 2550 | { |
michael@0 | 2551 | for (int i = 0; i < 10; i++) { |
michael@0 | 2552 | void* x = malloc(64); |
michael@0 | 2553 | UseItOrLoseIt2(x); |
michael@0 | 2554 | if (i & 1) { |
michael@0 | 2555 | free(x); |
michael@0 | 2556 | } |
michael@0 | 2557 | } |
michael@0 | 2558 | } |
michael@0 | 2559 | |
michael@0 | 2560 | MOZ_NEVER_INLINE static void |
michael@0 | 2561 | stress4() |
michael@0 | 2562 | { |
michael@0 | 2563 | stress5(); stress5(); stress5(); stress5(); stress5(); |
michael@0 | 2564 | stress5(); stress5(); stress5(); stress5(); stress5(); |
michael@0 | 2565 | } |
michael@0 | 2566 | |
michael@0 | 2567 | MOZ_NEVER_INLINE static void |
michael@0 | 2568 | stress3() |
michael@0 | 2569 | { |
michael@0 | 2570 | for (int i = 0; i < 10; i++) { |
michael@0 | 2571 | stress4(); |
michael@0 | 2572 | } |
michael@0 | 2573 | } |
michael@0 | 2574 | |
michael@0 | 2575 | MOZ_NEVER_INLINE static void |
michael@0 | 2576 | stress2() |
michael@0 | 2577 | { |
michael@0 | 2578 | stress3(); stress3(); stress3(); stress3(); stress3(); |
michael@0 | 2579 | stress3(); stress3(); stress3(); stress3(); stress3(); |
michael@0 | 2580 | } |
michael@0 | 2581 | |
michael@0 | 2582 | MOZ_NEVER_INLINE static void |
michael@0 | 2583 | stress1() |
michael@0 | 2584 | { |
michael@0 | 2585 | for (int i = 0; i < 10; i++) { |
michael@0 | 2586 | stress2(); |
michael@0 | 2587 | } |
michael@0 | 2588 | } |
michael@0 | 2589 | |
michael@0 | 2590 | // This stress test does lots of allocations and frees, which is where most of |
michael@0 | 2591 | // DMD's overhead occurs. It allocates 1,000,000 64-byte blocks, spread evenly |
michael@0 | 2592 | // across 1,000 distinct stack traces. It frees every second one immediately |
michael@0 | 2593 | // after allocating it. |
michael@0 | 2594 | // |
michael@0 | 2595 | // It's highly artificial, but it's deterministic and easy to run. It can be |
michael@0 | 2596 | // timed under different conditions to glean performance data. |
michael@0 | 2597 | static void |
michael@0 | 2598 | RunStressMode(FILE* fp) |
michael@0 | 2599 | { |
michael@0 | 2600 | Writer writer(FpWrite, fp); |
michael@0 | 2601 | |
michael@0 | 2602 | // Disable sampling for maximum stress. |
michael@0 | 2603 | gOptions->SetSampleBelowSize(1); |
michael@0 | 2604 | |
michael@0 | 2605 | stress1(); stress1(); stress1(); stress1(); stress1(); |
michael@0 | 2606 | stress1(); stress1(); stress1(); stress1(); stress1(); |
michael@0 | 2607 | |
michael@0 | 2608 | Dump(writer); |
michael@0 | 2609 | } |
michael@0 | 2610 | |
michael@0 | 2611 | } // namespace dmd |
michael@0 | 2612 | } // namespace mozilla |