diff --git a/js/public/HeapAPI.h b/js/public/HeapAPI.h index 79fa6232c794..969067e34033 100644 --- a/js/public/HeapAPI.h +++ b/js/public/HeapAPI.h @@ -383,6 +383,14 @@ IsIncrementalBarrierNeededOnTenuredGCThing(JS::shadow::Runtime *rt, const JS::GC return JS::shadow::Zone::asShadowZone(zone)->needsIncrementalBarrier(); } +/* + * Create an object providing access to the garbage collector's internal notion + * of the current state of memory (both GC heap memory and GCthing-controlled + * malloc memory. + */ +extern JS_PUBLIC_API(JSObject *) +NewMemoryInfoObject(JSContext *cx); + } /* namespace gc */ } /* namespace js */ diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index 104ff8a16eac..6941e621b65f 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -752,6 +752,7 @@ class GCRuntime void removeBlackRootsTracer(JSTraceDataOp traceOp, void *data); void setMaxMallocBytes(size_t value); + int32_t getMallocBytes() const { return mallocBytesUntilGC; } void resetMallocBytes(); bool isTooMuchMalloc() const { return mallocBytesUntilGC <= 0; } void updateMallocCounter(JS::Zone *zone, size_t nbytes); @@ -781,6 +782,12 @@ class GCRuntime uint64_t gcNumber() { return number; } void incGcNumber() { ++number; } + uint64_t minorGCCount() { return minorGCNumber; } + void incMinorGcNumber() { ++minorGCNumber; } + + uint64_t majorGCCount() { return majorGCNumber; } + void incMajorGcNumber() { ++majorGCNumber; } + bool isIncrementalGc() { return isIncremental; } bool isFullGc() { return isFull; } @@ -1062,6 +1069,9 @@ class GCRuntime /* Perform full GC if rt->keepAtoms() becomes false. */ bool fullGCForAtomsRequested_; + /* Incremented at the start of every minor GC. */ + uint64_t minorGCNumber; + /* Incremented at the start of every major GC. */ uint64_t majorGCNumber; diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index 251f023e6764..f14174cc1c4d 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -807,6 +807,8 @@ js::Nursery::collect(JSRuntime *rt, JS::gcreason::Reason reason, ObjectGroupList return; } + rt->gc.incMinorGcNumber(); + rt->gc.stats.count(gcstats::STAT_MINOR_GC); TraceMinorGCStart(); diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index 8b72823714f7..f042c6e841fc 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -43,7 +43,7 @@ class ZoneHeapThreshold double gcHeapGrowthFactor() const { return gcHeapGrowthFactor_; } size_t gcTriggerBytes() const { return gcTriggerBytes_; } - bool isCloseToAllocTrigger(const js::gc::HeapUsage& usage, bool highFrequencyGC) const; + double allocTrigger(bool highFrequencyGC) const; void updateAfterGC(size_t lastBytes, JSGCInvocationKind gckind, const GCSchedulingTunables &tunables, const GCSchedulingState &state); diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 3ac22f8dba50..3e79cdc61bc4 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -5257,7 +5257,6 @@ GetSavedFrameParent(JSContext *cx, HandleObject savedFrame, MutableHandleObject extern JS_PUBLIC_API(bool) StringifySavedFrameStack(JSContext *cx, HandleObject stack, MutableHandleString stringp); - } /* namespace JS */ #endif /* jsapi_h */ diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index d7c601a5351b..7237a4836e42 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1068,6 +1068,7 @@ GCRuntime::GCRuntime(JSRuntime *rt) : majorGCTriggerReason(JS::gcreason::NO_REASON), minorGCTriggerReason(JS::gcreason::NO_REASON), fullGCForAtomsRequested_(false), + minorGCNumber(0), majorGCNumber(0), jitReleaseNumber(0), number(0), @@ -1660,11 +1661,10 @@ GCRuntime::onTooMuchMalloc() mallocGCTriggered = triggerGC(JS::gcreason::TOO_MUCH_MALLOC); } -bool -ZoneHeapThreshold::isCloseToAllocTrigger(const js::gc::HeapUsage& usage, bool highFrequencyGC) const +double +ZoneHeapThreshold::allocTrigger(bool highFrequencyGC) const { - double factor = highFrequencyGC ? 0.85 : 0.9; - return usage.gcBytes() >= factor * gcTriggerBytes(); + return (highFrequencyGC ? 0.85 : 0.9) * gcTriggerBytes(); } /* static */ double @@ -3071,7 +3071,7 @@ GCRuntime::maybeGC(Zone *zone) return true; if (zone->usage.gcBytes() > 1024 * 1024 && - zone->threshold.isCloseToAllocTrigger(zone->usage, schedulingState.inHighFrequencyGCMode()) && + zone->usage.gcBytes() >= zone->threshold.allocTrigger(schedulingState.inHighFrequencyGCMode()) && !isIncrementalGCInProgress() && !isBackgroundSweeping()) { @@ -5911,7 +5911,7 @@ GCRuntime::gcCycle(bool incremental, SliceBudget &budget, JS::gcreason::Reason r number++; if (!isIncrementalGCInProgress()) - majorGCNumber++; + incMajorGcNumber(); // It's ok if threads other than the main thread have suppressGC set, as // they are operating on zones which will not be collected from here. @@ -6013,7 +6013,7 @@ GCRuntime::scanZonesBeforeGC() zone->scheduleGC(); /* This is a heuristic to reduce the total number of collections. */ - if (zone->threshold.isCloseToAllocTrigger(zone->usage, schedulingState.inHighFrequencyGCMode())) + if (zone->usage.gcBytes() >= zone->threshold.allocTrigger(schedulingState.inHighFrequencyGCMode())) zone->scheduleGC(); zoneStats.zoneCount++; @@ -7091,3 +7091,202 @@ JS::IsGenerationalGCEnabled(JSRuntime *rt) { return rt->gc.isGenerationalGCEnabled(); } + +namespace js { +namespace gc { +namespace MemInfo { + +static bool +GCBytesGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->runtime()->gc.usage.gcBytes())); + return true; +} + +static bool +GCMaxBytesGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->runtime()->gc.tunables.gcMaxBytes())); + return true; +} + +static bool +MallocBytesGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->runtime()->gc.getMallocBytes())); + return true; +} + +static bool +MaxMallocGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->runtime()->gc.maxMallocBytesAllocated())); + return true; +} + +static bool +GCHighFreqGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setBoolean(cx->runtime()->gc.schedulingState.inHighFrequencyGCMode()); + return true; +} + +static bool +GCNumberGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->runtime()->gc.gcNumber())); + return true; +} + +static bool +MajorGCCountGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->runtime()->gc.majorGCCount())); + return true; +} + +static bool +MinorGCCountGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->runtime()->gc.minorGCCount())); + return true; +} + +static bool +ZoneGCBytesGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->zone()->usage.gcBytes())); + return true; +} + +static bool +ZoneGCTriggerBytesGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->zone()->threshold.gcTriggerBytes())); + return true; +} + +static bool +ZoneGCAllocTriggerGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->zone()->threshold.allocTrigger(cx->runtime()->gc.schedulingState.inHighFrequencyGCMode()))); + return true; +} + +static bool +ZoneMallocBytesGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->zone()->gcMallocBytes)); + return true; +} + +static bool +ZoneMaxMallocGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->zone()->gcMaxMallocBytes)); + return true; +} + +static bool +ZoneGCDelayBytesGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->zone()->gcDelayBytes)); + return true; +} + +static bool +ZoneGCHeapGrowthFactorGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(cx->zone()->threshold.gcHeapGrowthFactor()); + return true; +} + +static bool +ZoneGCNumberGetter(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setNumber(double(cx->zone()->gcNumber())); + return true; +} + +} /* namespace MemInfo */ + +JSObject * +NewMemoryInfoObject(JSContext *cx) +{ + RootedObject obj(cx, JS_NewObject(cx, nullptr)); + + using namespace MemInfo; + struct { + const char *name; + JSNative getter; + } getters[] = { + { "gcBytes", GCBytesGetter }, + { "gcMaxBytes", GCMaxBytesGetter }, + { "mallocBytesRemaining", MallocBytesGetter }, + { "maxMalloc", MaxMallocGetter }, + { "gcIsHighFrequencyMode", GCHighFreqGetter }, + { "gcNumber", GCNumberGetter }, + { "majorGCCount", MajorGCCountGetter }, + { "minorGCCount", MinorGCCountGetter } + }; + + for (size_t i = 0; i < mozilla::ArrayLength(getters); i++) { + if (!JS_DefineProperty(cx, obj, getters[i].name, UndefinedHandleValue, + JSPROP_READONLY | JSPROP_SHARED | JSPROP_ENUMERATE, + getters[i].getter, nullptr)) + { + return nullptr; + } + } + + RootedObject zoneObj(cx, JS_NewObject(cx, nullptr)); + if (!zoneObj) + return nullptr; + + if (!JS_DefineProperty(cx, obj, "zone", zoneObj, JSPROP_ENUMERATE)) + return nullptr; + + struct { + const char *name; + JSNative getter; + } zoneGetters[] = { + { "gcBytes", ZoneGCBytesGetter }, + { "gcTriggerBytes", ZoneGCTriggerBytesGetter }, + { "gcAllocTrigger", ZoneGCAllocTriggerGetter }, + { "mallocBytesRemaining", ZoneMallocBytesGetter }, + { "maxMalloc", ZoneMaxMallocGetter }, + { "delayBytes", ZoneGCDelayBytesGetter }, + { "heapGrowthFactor", ZoneGCHeapGrowthFactorGetter }, + { "gcNumber", ZoneGCNumberGetter } + }; + + for (size_t i = 0; i < mozilla::ArrayLength(zoneGetters); i++) { + if (!JS_DefineProperty(cx, zoneObj, zoneGetters[i].name, UndefinedHandleValue, + JSPROP_READONLY | JSPROP_SHARED | JSPROP_ENUMERATE, + zoneGetters[i].getter, nullptr)) + { + return nullptr; + } + } + + return obj; +} + +} /* namespace gc */ +} /* namespace js */ diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 44b02ea4e4b0..86c263bb5da8 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -1395,6 +1395,9 @@ class ZoneList ZoneList &operator=(const ZoneList &other) = delete; }; +JSObject * +NewMemoryStatisticsObject(JSContext *cx); + } /* namespace gc */ #ifdef DEBUG diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 8ebf391de28b..f99da70bdb96 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -5643,6 +5643,22 @@ NewGlobalObject(JSContext *cx, JS::CompartmentOptions &options, return nullptr; } + RootedObject performanceObj(cx, JS_NewObject(cx, nullptr)); + if (!performanceObj) + return nullptr; + RootedObject mozMemoryObj(cx, JS_NewObject(cx, nullptr)); + if (!mozMemoryObj) + return nullptr; + RootedObject gcObj(cx, gc::NewMemoryInfoObject(cx)); + if (!gcObj) + return nullptr; + if (!JS_DefineProperty(cx, glob, "performance", performanceObj, JSPROP_ENUMERATE)) + return nullptr; + if (!JS_DefineProperty(cx, performanceObj, "mozMemory", mozMemoryObj, JSPROP_ENUMERATE)) + return nullptr; + if (!JS_DefineProperty(cx, mozMemoryObj, "gc", gcObj, JSPROP_ENUMERATE)) + return nullptr; + /* Initialize FakeDOMObject. */ static const js::DOMCallbacks DOMcallbacks = { InstanceClassHasProtoAtDepth