diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index 381dbb09e604..6d5c8e5c9634 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -429,6 +429,9 @@ SystemCompartmentCount(JSRuntime *rt); extern JS_PUBLIC_API(size_t) UserCompartmentCount(JSRuntime *rt); +extern JS_PUBLIC_API(size_t) +PeakSizeOfTemporary(const JSRuntime *rt); + } // namespace JS #endif // js_MemoryMetrics_h diff --git a/js/src/ds/LifoAlloc.cpp b/js/src/ds/LifoAlloc.cpp index c8eb729fd18a..0b8e7d4c59f2 100644 --- a/js/src/ds/LifoAlloc.cpp +++ b/js/src/ds/LifoAlloc.cpp @@ -60,9 +60,16 @@ LifoAlloc::freeAll() while (first) { BumpChunk *victim = first; first = first->next(); + decrementCurSize(victim->computedSizeOfIncludingThis()); BumpChunk::delete_(victim); } first = latest = last = NULL; + + /* + * Nb: maintaining curSize_ correctly isn't easy. Fortunately, this is an + * excellent sanity check. + */ + JS_ASSERT(curSize_ == 0); } LifoAlloc::BumpChunk * @@ -105,6 +112,11 @@ LifoAlloc::getOrCreateChunk(size_t n) latest->setNext(newChunk); latest = last = newChunk; } + + size_t computedChunkSize = newChunk->computedSizeOfIncludingThis(); + JS_ASSERT(computedChunkSize == chunkSize); + incrementCurSize(computedChunkSize); + return newChunk; } @@ -118,8 +130,10 @@ LifoAlloc::transferFrom(LifoAlloc *other) if (!other->first) return; + incrementCurSize(other->curSize_); append(other->first, other->last); other->first = other->last = other->latest = NULL; + other->curSize_ = 0; } void @@ -131,14 +145,22 @@ LifoAlloc::transferUnusedFrom(LifoAlloc *other) if (other->markCount || !other->first) return; - /* - * Because of how getOrCreateChunk works, there may be unused chunks before - * |last|. We do not transfer those chunks. In most cases it is expected - * that last == first, so this should be a rare situation that, when it - * happens, should not recur. - */ + // Transfer all chunks *after* |latest|. if (other->latest->next()) { + if (other->latest == other->first) { + // We're transferring everything except the first chunk. + size_t delta = other->curSize_ - other->first->computedSizeOfIncludingThis(); + other->decrementCurSize(delta); + incrementCurSize(delta); + } else { + for (BumpChunk *chunk = other->latest->next(); chunk; chunk = chunk->next()) { + size_t size = chunk->computedSizeOfIncludingThis(); + incrementCurSize(size); + other->decrementCurSize(size); + } + } + append(other->latest->next(), other->last); other->latest->setNext(NULL); other->last = other->latest; diff --git a/js/src/ds/LifoAlloc.h b/js/src/ds/LifoAlloc.h index 4c4ef219c96a..a09f3944a7ab 100644 --- a/js/src/ds/LifoAlloc.h +++ b/js/src/ds/LifoAlloc.h @@ -94,10 +94,15 @@ class BumpChunk void setNext(BumpChunk *succ) { next_ = succ; } size_t used() const { return bump - bumpBase(); } + size_t sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf) { return mallocSizeOf(this); } + size_t computedSizeOfIncludingThis() { + return limit - headerBase(); + } + void resetBump() { setBump(headerBase() + sizeof(BumpChunk)); } @@ -165,11 +170,13 @@ class LifoAlloc BumpChunk *last; size_t markCount; size_t defaultChunkSize_; + size_t curSize_; + size_t peakSize_; void operator=(const LifoAlloc &) MOZ_DELETE; LifoAlloc(const LifoAlloc &) MOZ_DELETE; - /* + /* * Return a BumpChunk that can perform an allocation of at least size |n| * and add it to the chain appropriately. * @@ -183,6 +190,7 @@ class LifoAlloc first = latest = last = NULL; defaultChunkSize_ = defaultChunkSize; markCount = 0; + curSize_ = 0; } void append(BumpChunk *start, BumpChunk *end) { @@ -194,17 +202,39 @@ class LifoAlloc last = end; } + void incrementCurSize(size_t size) { + curSize_ += size; + if (curSize_ > peakSize_) + peakSize_ = curSize_; + } + void decrementCurSize(size_t size) { + MOZ_ASSERT(curSize_ >= size); + curSize_ -= size; + } + public: - explicit LifoAlloc(size_t defaultChunkSize) { reset(defaultChunkSize); } + explicit LifoAlloc(size_t defaultChunkSize) + : peakSize_(0) + { + reset(defaultChunkSize); + } /* Steal allocated chunks from |other|. */ void steal(LifoAlloc *other) { JS_ASSERT(!other->markCount); + + /* + * Copy everything from |other| to |this| except for |peakSize_|, which + * requires some care. + */ + size_t oldPeakSize = peakSize_; PodCopy((char *) this, (char *) other, sizeof(*this)); + peakSize_ = Max(oldPeakSize, curSize_); + other->reset(defaultChunkSize_); } - /* Append allocated chunks from |other|. They are removed from |other|. */ + /* Append all chunks from |other|. They are removed from |other|. */ void transferFrom(LifoAlloc *other); /* Append unused chunks from |other|. They are removed from |other|. */ @@ -249,12 +279,10 @@ class LifoAlloc JS_ALWAYS_INLINE bool ensureUnusedApproximate(size_t n) { size_t total = 0; - BumpChunk *chunk = latest; - while (chunk) { + for (BumpChunk *chunk = latest; chunk; chunk = chunk->next()) { total += chunk->unused(); if (total >= n) return true; - chunk = chunk->next(); } BumpChunk *latestBefore = latest; if (!getOrCreateChunk(n)) @@ -298,18 +326,15 @@ class LifoAlloc return; } - /* + /* * Find the chunk that contains |mark|, and make sure we don't pass * |latest| along the way -- we should be making the chain of active * chunks shorter, not longer! */ - BumpChunk *container = first; - while (true) { - if (container->contains(mark)) - break; + BumpChunk *container; + for (container = first; !container->contains(mark); container = container->next()) JS_ASSERT(container != latest); - container = container->next(); - } + latest = container; latest->release(mark); } @@ -324,25 +349,23 @@ class LifoAlloc /* Get the total "used" (occupied bytes) count for the arena chunks. */ size_t used() const { size_t accum = 0; - BumpChunk *it = first; - while (it) { - accum += it->used(); - if (it == latest) + for (BumpChunk *chunk = first; chunk; chunk = chunk->next()) { + accum += chunk->used(); + if (chunk == latest) break; - it = it->next(); } return accum; } /* Get the total size of the arena chunks (including unused space). */ size_t sizeOfExcludingThis(JSMallocSizeOfFun mallocSizeOf) const { - size_t accum = 0; - BumpChunk *it = first; - while (it) { - accum += it->sizeOfIncludingThis(mallocSizeOf); - it = it->next(); - } - return accum; + size_t n = 0; + for (BumpChunk *chunk = first; chunk; chunk = chunk->next()) + n += chunk->sizeOfIncludingThis(mallocSizeOf); + + /* While we're here, let's sanity check curSize_. */ + MOZ_ASSERT(curSize_ == n); + return n; } /* Like sizeOfExcludingThis(), but includes the size of the LifoAlloc itself. */ @@ -350,6 +373,12 @@ class LifoAlloc return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); } + /* + * Get the peak size of the arena chunks (including unused space and + * bookkeeping space). + */ + size_t peakSizeOfExcludingThis() const { return peakSize_; } + /* Doesn't perform construction; useful for lazily-initialized POD types. */ template JS_ALWAYS_INLINE diff --git a/js/src/jsmemorymetrics.cpp b/js/src/jsmemorymetrics.cpp index 4175bc8dc42b..0ab84944ada1 100644 --- a/js/src/jsmemorymetrics.cpp +++ b/js/src/jsmemorymetrics.cpp @@ -392,3 +392,10 @@ JS::UserCompartmentCount(JSRuntime *rt) } return n; } + +JS_PUBLIC_API(size_t) +JS::PeakSizeOfTemporary(const JSRuntime *rt) +{ + return rt->tempLifoAlloc.peakSizeOfExcludingThis(); +} + diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 37c4431d48e0..acedb35d6375 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -1412,6 +1412,7 @@ NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSGCHeap, nsIMemoryReporter::UNITS_BYTES, GetGCChunkTotalBytes, "Memory used by the garbage-collected JavaScript heap.") + static int64_t GetJSSystemCompartmentCount() { @@ -1451,6 +1452,22 @@ NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSUserCompartmentCount, "listed under 'js' if a garbage collection occurs at an inopportune time, " "but such cases should be rare.") +static int64_t +GetJSMainRuntimeTemporaryPeakSize() +{ + return JS::PeakSizeOfTemporary(nsXPConnect::GetRuntimeInstance()->GetJSRuntime()); +} + +// This is also a single reporter so it can be used by telemetry. +NS_MEMORY_REPORTER_IMPLEMENT(JSMainRuntimeTemporaryPeak, + "js-main-runtime-temporary-peak", + KIND_OTHER, + nsIMemoryReporter::UNITS_BYTES, + GetJSMainRuntimeTemporaryPeakSize, + "The peak size of the transient storage in the main JSRuntime (the " + "current size of which is reported as " + "'explicit/js-non-window/runtime/temporary')."); + // The REPORT* macros do an unconditional report. The ZCREPORT* macros are for // compartments and zones; they aggregate any entries smaller than // SUNDRIES_THRESHOLD into "gc-heap/sundries" and "other-sundries" entries for @@ -2666,6 +2683,7 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect) NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSGCHeap)); NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSSystemCompartmentCount)); NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSUserCompartmentCount)); + NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(JSMainRuntimeTemporaryPeak)); NS_RegisterMemoryMultiReporter(new JSCompartmentsMultiReporter); mJSHolders.Init(512); diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 0b569b7fff0f..2eab7a6b5e45 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -210,6 +210,14 @@ "extended_statistics_ok": true, "description": "Total JavaScript compartments used for web pages" }, + "MEMORY_JS_MAIN_RUNTIME_TEMPORARY_PEAK": { + "kind": "exponential", + "low": 1024, + "high": "16 * 1024 * 1024", + "n_buckets": 200, + "extended_statistics_ok": true, + "description": "Peak memory used by the main JSRuntime to store transient data (KB)" + }, "MEMORY_JS_GC_HEAP": { "kind": "exponential", "low": 1024, diff --git a/toolkit/components/telemetry/TelemetryPing.js b/toolkit/components/telemetry/TelemetryPing.js index a87335ce230d..6bb1bc7051f9 100644 --- a/toolkit/components/telemetry/TelemetryPing.js +++ b/toolkit/components/telemetry/TelemetryPing.js @@ -59,6 +59,7 @@ const MEM_HISTOGRAMS = { "js-gc-heap": "MEMORY_JS_GC_HEAP", "js-compartments/system": "MEMORY_JS_COMPARTMENTS_SYSTEM", "js-compartments/user": "MEMORY_JS_COMPARTMENTS_USER", + "js-main-runtime-temporary-peak": "MEMORY_JS_MAIN_RUNTIME_TEMPORARY_PEAK", "explicit": "MEMORY_EXPLICIT", "resident-fast": "MEMORY_RESIDENT", "vsize": "MEMORY_VSIZE",