memory/replace/dmd/DMD.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

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

mercurial