Bug 1322560 - Add APIs for retrieving major GC timing info
☠☠ backed out by 7bf6fa55b2c7 ☠ ☠
authorSteve Fink <sfink@mozilla.com>
Wed, 26 Apr 2017 13:48:09 -0700
changeset 355583 4dee851a0d45655260ace1c01eb4dd9e9905942f
parent 355582 86277c75f5f7c5320214b5b99d2ca70af027693c
child 355584 5fe280e53d4f474f5f16ff834e0b9cf55745d746
push id89703
push usersfink@mozilla.com
push dateFri, 28 Apr 2017 18:04:28 +0000
treeherdermozilla-inbound@2cd1662bcc5b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1322560
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1322560 - Add APIs for retrieving major GC timing info
js/public/GCAPI.h
js/src/gc/Statistics.cpp
js/src/gc/Statistics.h
js/src/jsgc.cpp
js/src/vm/Debugger.cpp
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -8,16 +8,17 @@
 #define js_GCAPI_h
 
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Vector.h"
 
 #include "js/GCAnnotations.h"
 #include "js/HeapAPI.h"
 #include "js/UniquePtr.h"
+#include "js/Utility.h"
 
 namespace js {
 namespace gc {
 class GCRuntime;
 } // namespace gc
 namespace gcstats {
 struct Statistics;
 } // namespace gcstats
@@ -340,16 +341,24 @@ struct JS_PUBLIC_API(GCDescription) {
 
     GCDescription(bool isZone, JSGCInvocationKind kind, gcreason::Reason reason)
       : isZone_(isZone), invocationKind_(kind), reason_(reason) {}
 
     char16_t* formatSliceMessage(JSContext* cx) const;
     char16_t* formatSummaryMessage(JSContext* cx) const;
     char16_t* formatJSON(JSContext* cx, uint64_t timestamp) const;
 
+    mozilla::TimeStamp startTime(JSContext* cx) const;
+    mozilla::TimeStamp endTime(JSContext* cx) const;
+    mozilla::TimeStamp lastSliceStart(JSContext* cx) const;
+    mozilla::TimeStamp lastSliceEnd(JSContext* cx) const;
+
+    JS::UniqueChars sliceToJSON(JSContext* cx) const;
+    JS::UniqueChars summaryToJSON(JSContext* cx) const;
+
     JS::dbg::GarbageCollectionEvent::Ptr toGCEvent(JSContext* cx) const;
 };
 
 typedef void
 (* GCSliceCallback)(JSContext* cx, GCProgress progress, const GCDescription& desc);
 
 /**
  * The GC slice callback is called at the beginning and end of each slice. This
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -298,20 +298,20 @@ struct AllPhaseIterator {
         return phases[current].parent == PHASE_MULTI_PARENTS;
     }
 };
 
 void
 Statistics::gcDuration(TimeDuration* total, TimeDuration* maxPause) const
 {
     *total = *maxPause = 0;
-    for (const SliceData* slice = slices.begin(); slice != slices.end(); slice++) {
-        *total += slice->duration();
-        if (slice->duration() > *maxPause)
-            *maxPause = slice->duration();
+    for (auto& slice : slices_) {
+        *total += slice.duration();
+        if (slice.duration() > *maxPause)
+            *maxPause = slice.duration();
     }
     if (*maxPause > maxPauseInInterval)
         maxPauseInInterval = *maxPause;
 }
 
 void
 Statistics::sccDurations(TimeDuration* total, TimeDuration* maxPause) const
 {
@@ -374,37 +374,37 @@ SumChildTimes(size_t phaseSlot, Phase ph
     }
     return total;
 }
 
 UniqueChars
 Statistics::formatCompactSliceMessage() const
 {
     // Skip if we OOM'ed.
-    if (slices.length() == 0)
+    if (slices_.length() == 0)
         return UniqueChars(nullptr);
 
-    const size_t index = slices.length() - 1;
-    const SliceData& slice = slices[index];
+    const size_t index = slices_.length() - 1;
+    const SliceData& slice = slices_.back();
 
     char budgetDescription[200];
     slice.budget.describe(budgetDescription, sizeof(budgetDescription) - 1);
 
     const char* format =
         "GC Slice %u - Pause: %.3fms of %s budget (@ %.3fms); Reason: %s; Reset: %s%s; Times: ";
     char buffer[1024];
     SprintfLiteral(buffer, format, index,
-                   t(slice.duration()), budgetDescription, t(slice.start - slices[0].start),
+                   t(slice.duration()), budgetDescription, t(slice.start - slices_[0].start),
                    ExplainReason(slice.reason),
                    slice.wasReset() ? "yes - " : "no",
                    slice.wasReset() ? ExplainAbortReason(slice.resetReason) : "");
 
     FragmentVector fragments;
     if (!fragments.append(DuplicateString(buffer)) ||
-        !fragments.append(formatCompactSlicePhaseTimes(slices[index].phaseTimes)))
+        !fragments.append(formatCompactSlicePhaseTimes(slices_[index].phaseTimes)))
     {
         return UniqueChars(nullptr);
     }
     return Join(fragments);
 }
 
 UniqueChars
 Statistics::formatCompactSummaryMessage() const
@@ -493,21 +493,21 @@ Statistics::formatCompactSlicePhaseTimes
 UniqueChars
 Statistics::formatDetailedMessage() const
 {
     FragmentVector fragments;
 
     if (!fragments.append(formatDetailedDescription()))
         return UniqueChars(nullptr);
 
-    if (slices.length() > 1) {
-        for (unsigned i = 0; i < slices.length(); i++) {
-            if (!fragments.append(formatDetailedSliceDescription(i, slices[i])))
+    if (!slices_.empty()) {
+        for (unsigned i = 0; i < slices_.length(); i++) {
+            if (!fragments.append(formatDetailedSliceDescription(i, slices_[i])))
                 return UniqueChars(nullptr);
-            if (!fragments.append(formatDetailedPhaseTimes(slices[i].phaseTimes)))
+            if (!fragments.append(formatDetailedPhaseTimes(slices_[i].phaseTimes)))
                 return UniqueChars(nullptr);
         }
     }
     if (!fragments.append(formatDetailedTotals()))
         return UniqueChars(nullptr);
     if (!fragments.append(formatDetailedPhaseTimes(phaseTimes)))
         return UniqueChars(nullptr);
 
@@ -538,17 +538,17 @@ Statistics::formatDetailedDescription() 
   SCC Sweep Total (MaxPause): %.3fms (%.3fms)\n\
   HeapSize: %.3f MiB\n\
   Chunk Delta (magnitude): %+d  (%d)\n\
   Arenas Relocated: %.3f MiB\n\
 ";
     char buffer[1024];
     SprintfLiteral(buffer, format,
                    ExplainInvocationKind(gckind),
-                   ExplainReason(slices[0].reason),
+                   ExplainReason(slices_[0].reason),
                    nonincremental() ? "no - " : "yes",
                    nonincremental() ? ExplainAbortReason(nonincrementalReason_) : "",
                    zoneStats.collectedZoneCount, zoneStats.zoneCount, zoneStats.sweptZoneCount,
                    zoneStats.collectedCompartmentCount, zoneStats.compartmentCount,
                    zoneStats.sweptCompartmentCount,
                    counts[STAT_MINOR_GC],
                    counts[STAT_STOREBUFFER_OVERFLOW],
                    mmu20 * 100., mmu50 * 100.,
@@ -576,17 +576,17 @@ Statistics::formatDetailedSliceDescripti
     Pause: %.3fms of %s budget (@ %.3fms)\n\
 ";
     char buffer[1024];
     SprintfLiteral(buffer, format, i, ExplainReason(slice.reason),
                    slice.wasReset() ? "yes - " : "no",
                    slice.wasReset() ? ExplainAbortReason(slice.resetReason) : "",
                    gc::StateName(slice.initialState), gc::StateName(slice.finalState),
                    uint64_t(slice.endFaults - slice.startFaults),
-                   t(slice.duration()), budgetDescription, t(slice.start - slices[0].start));
+                   t(slice.duration()), budgetDescription, t(slice.start - slices_[0].start));
     return DuplicateString(buffer);
 }
 
 UniqueChars
 Statistics::formatDetailedPhaseTimes(const PhaseTimeTable& phaseTimes) const
 {
     static const char* LevelToIndent[] = { "", "  ", "    ", "      " };
     static const TimeDuration MaxUnaccountedChildTime = TimeDuration::FromMicroseconds(50);
@@ -633,42 +633,62 @@ Statistics::formatDetailedTotals() const
     Max Pause: %.3fms\n\
 ";
     char buffer[1024];
     SprintfLiteral(buffer, format, t(total), t(longest));
     return DuplicateString(buffer);
 }
 
 UniqueChars
-Statistics::formatJsonMessage(uint64_t timestamp) const
+Statistics::formatJsonSlice(size_t sliceNum) const
 {
-    MOZ_ASSERT(!aborted);
-
     FragmentVector fragments;
 
     if (!fragments.append(DuplicateString("{")) ||
-        !fragments.append(formatJsonDescription(timestamp)) ||
-        !fragments.append(DuplicateString("\"slices\":[")))
+        !fragments.append(formatJsonSliceDescription(sliceNum, slices_[sliceNum])) ||
+        !fragments.append(DuplicateString("\"times\":{")) ||
+        !fragments.append(formatJsonPhaseTimes(slices_[sliceNum].phaseTimes)) ||
+        !fragments.append(DuplicateString("}}")))
     {
         return UniqueChars(nullptr);
     }
 
-    for (unsigned i = 0; i < slices.length(); i++) {
-        if (!fragments.append(DuplicateString("{")) ||
-            !fragments.append(formatJsonSliceDescription(i, slices[i])) ||
-            !fragments.append(DuplicateString("\"times\":{")) ||
-            !fragments.append(formatJsonPhaseTimes(slices[i].phaseTimes)) ||
-            !fragments.append(DuplicateString("}}")) ||
-            (i < (slices.length() - 1) && !fragments.append(DuplicateString(","))))
-        {
-            return UniqueChars(nullptr);
-        }
+    return Join(fragments);
+}
+
+UniqueChars
+Statistics::formatJsonMessage(uint64_t timestamp, bool includeSlices) const
+{
+    if (aborted)
+        return DuplicateString("{status:\"aborted\"}"); // May return nullptr
+
+    FragmentVector fragments;
+
+    if (!fragments.append(DuplicateString("{")) ||
+        !fragments.append(formatJsonDescription(timestamp)))
+    {
+        return UniqueChars(nullptr);
     }
 
-    if (!fragments.append(DuplicateString("],\"totals\":{")) ||
+    if (includeSlices) {
+        if (!fragments.append(DuplicateString("\"slices\":[")))
+            return UniqueChars(nullptr);
+
+        for (unsigned i = 0; i < slices_.length(); i++) {
+            if (!fragments.append(formatJsonSlice(i)))
+                return UniqueChars(nullptr);
+            if ((i < (slices_.length() - 1) && !fragments.append(DuplicateString(","))))
+                return UniqueChars(nullptr);
+        }
+
+        if (!fragments.append(DuplicateString("],")))
+            return UniqueChars(nullptr);
+    }
+
+    if (!fragments.append(DuplicateString("\"totals\":{")) ||
         !fragments.append(formatJsonPhaseTimes(phaseTimes)) ||
         !fragments.append(DuplicateString("}}")))
     {
         return UniqueChars(nullptr);
     }
 
     return Join(fragments);
 }
@@ -698,16 +718,17 @@ Statistics::formatJsonDescription(uint64
 
     const double mmu20 = computeMMU(TimeDuration::FromMilliseconds(20));
     const double mmu50 = computeMMU(TimeDuration::FromMilliseconds(50));
 
     const char *format =
         "\"timestamp\":%llu,"
         "\"max_pause\":%llu.%03llu,"
         "\"total_time\":%llu.%03llu,"
+        "\"reason\":\"%s\","
         "\"zones_collected\":%d,"
         "\"total_zones\":%d,"
         "\"total_compartments\":%d,"
         "\"minor_gcs\":%d,"
         "\"store_buffer_overflows\":%d,"
         "\"mmu_20ms\":%d,"
         "\"mmu_50ms\":%d,"
         "\"scc_sweep_total\":%llu.%03llu,"
@@ -716,16 +737,17 @@ Statistics::formatJsonDescription(uint64
         "\"allocated\":%u,"
         "\"added_chunks\":%d,"
         "\"removed_chunks\":%d,";
     char buffer[1024];
     SprintfLiteral(buffer, format,
                    (unsigned long long)timestamp,
                    longestParts.quot, longestParts.rem,
                    totalParts.quot, totalParts.rem,
+                   ExplainReason(slices_[0].reason),
                    zoneStats.collectedZoneCount,
                    zoneStats.zoneCount,
                    zoneStats.compartmentCount,
                    counts[STAT_MINOR_GC],
                    counts[STAT_STOREBUFFER_OVERFLOW],
                    int(mmu20 * 100),
                    int(mmu50 * 100),
                    sccTotalParts.quot, sccTotalParts.rem,
@@ -737,17 +759,17 @@ Statistics::formatJsonDescription(uint64
     return DuplicateString(buffer);
 }
 
 UniqueChars
 Statistics::formatJsonSliceDescription(unsigned i, const SliceData& slice) const
 {
     TimeDuration duration = slice.duration();
     lldiv_t durationParts = SplitDurationMS(duration);
-    TimeDuration when = slice.start - slices[0].start;
+    TimeDuration when = slice.start - slices_[0].start;
     lldiv_t whenParts = SplitDurationMS(when);
     char budgetDescription[200];
     slice.budget.describe(budgetDescription, sizeof(budgetDescription) - 1);
     int64_t pageFaults = slice.endFaults - slice.startFaults;
     TimeStamp originTime = TimeStamp::ProcessCreation();
 
     const char* format =
         "\"slice\":%d,"
@@ -1034,17 +1056,17 @@ Statistics::printStats()
         }
     }
     fflush(fp);
 }
 
 void
 Statistics::beginGC(JSGCInvocationKind kind)
 {
-    slices.clearAndFree();
+    slices_.clearAndFree();
     sccTimes.clearAndFree();
     gckind = kind;
     nonincrementalReason_ = gc::AbortReason::None;
 
     preBytes = runtime->gc.usage.gcBytes();
 }
 
 void
@@ -1120,21 +1142,21 @@ Statistics::beginSlice(const ZoneGCStats
                        SliceBudget budget, JS::gcreason::Reason reason)
 {
     this->zoneStats = zoneStats;
 
     bool first = !runtime->gc.isIncrementalGCInProgress();
     if (first)
         beginGC(gckind);
 
-    if (!slices.emplaceBack(budget,
-                            reason,
-                            TimeStamp::Now(),
-                            GetPageFaultCount(),
-                            runtime->gc.state()))
+    if (!slices_.emplaceBack(budget,
+                             reason,
+                             TimeStamp::Now(),
+                             GetPageFaultCount(),
+                             runtime->gc.state()))
     {
         // If we are OOM, set a flag to indicate we have missing slice data.
         aborted = true;
         return;
     }
 
     runtime->addTelemetry(JS_TELEMETRY_GC_REASON, reason);
 
@@ -1145,56 +1167,56 @@ Statistics::beginSlice(const ZoneGCStats
                          first ? JS::GC_CYCLE_BEGIN : JS::GC_SLICE_BEGIN,
                          JS::GCDescription(!wasFullGC, gckind, reason));
 }
 
 void
 Statistics::endSlice()
 {
     if (!aborted) {
-        slices.back().end = TimeStamp::Now();
-        slices.back().endFaults = GetPageFaultCount();
-        slices.back().finalState = runtime->gc.state();
+        slices_.back().end = TimeStamp::Now();
+        slices_.back().endFaults = GetPageFaultCount();
+        slices_.back().finalState = runtime->gc.state();
 
-        TimeDuration sliceTime = slices.back().end - slices.back().start;
+        TimeDuration sliceTime = slices_.back().end - slices_.back().start;
         runtime->addTelemetry(JS_TELEMETRY_GC_SLICE_MS, t(sliceTime));
-        runtime->addTelemetry(JS_TELEMETRY_GC_RESET, slices.back().wasReset());
-        if (slices.back().wasReset())
-            runtime->addTelemetry(JS_TELEMETRY_GC_RESET_REASON, uint32_t(slices.back().resetReason));
+        runtime->addTelemetry(JS_TELEMETRY_GC_RESET, slices_.back().wasReset());
+        if (slices_.back().wasReset())
+            runtime->addTelemetry(JS_TELEMETRY_GC_RESET_REASON, uint32_t(slices_.back().resetReason));
 
-        if (slices.back().budget.isTimeBudget()) {
-            int64_t budget_ms = slices.back().budget.timeBudget.budget;
+        if (slices_.back().budget.isTimeBudget()) {
+            int64_t budget_ms = slices_.back().budget.timeBudget.budget;
             runtime->addTelemetry(JS_TELEMETRY_GC_BUDGET_MS, budget_ms);
             if (budget_ms == runtime->gc.defaultSliceBudget())
                 runtime->addTelemetry(JS_TELEMETRY_GC_ANIMATION_MS, t(sliceTime));
 
             // Record any phase that goes more than 2x over its budget.
             if (sliceTime.ToMilliseconds() > 2 * budget_ms) {
-                Phase longest = LongestPhaseSelfTime(slices.back().phaseTimes);
+                Phase longest = LongestPhaseSelfTime(slices_.back().phaseTimes);
                 runtime->addTelemetry(JS_TELEMETRY_GC_SLOW_PHASE, phases[longest].telemetryBucket);
             }
         }
 
         sliceCount_++;
     }
 
     bool last = !runtime->gc.isIncrementalGCInProgress();
     if (last)
         endGC();
 
-    if (enableProfiling_ && !aborted && slices.back().duration() >= profileThreshold_)
+    if (enableProfiling_ && !aborted && slices_.back().duration() >= profileThreshold_)
         printSliceProfile();
 
     // Slice callbacks should only fire for the outermost level.
     if (!aborted) {
         bool wasFullGC = zoneStats.isCollectingAllZones();
         if (sliceCallback)
             (*sliceCallback)(TlsContext.get(),
                              last ? JS::GC_CYCLE_END : JS::GC_SLICE_END,
-                             JS::GCDescription(!wasFullGC, gckind, slices.back().reason));
+                             JS::GCDescription(!wasFullGC, gckind, slices_.back().reason));
     }
 
     // Do this after the slice callback since it uses these values.
     if (last) {
         for (auto& count : counts)
             count = 0;
 
         // Clear the timers at the end of a GC because we accumulate time in
@@ -1308,18 +1330,18 @@ Statistics::recordPhaseEnd(Phase phase)
     TimeStamp now = TimeStamp::Now();
 
     if (phase == PHASE_MUTATOR)
         timedGCStart = now;
 
     phaseNestingDepth--;
 
     TimeDuration t = now - phaseStartTimes[phase];
-    if (!slices.empty())
-        slices.back().phaseTimes[activeDagSlot][phase] += t;
+    if (!slices_.empty())
+        slices_.back().phaseTimes[activeDagSlot][phase] += t;
     phaseTimes[activeDagSlot][phase] += t;
     phaseStartTimes[phase] = TimeStamp();
 }
 
 void
 Statistics::endPhase(Phase phase)
 {
     recordPhaseEnd(phase);
@@ -1333,18 +1355,18 @@ Statistics::endPhase(Phase phase)
         resumePhases();
 }
 
 void
 Statistics::endParallelPhase(Phase phase, const GCParallelTask* task)
 {
     phaseNestingDepth--;
 
-    if (!slices.empty())
-        slices.back().phaseTimes[PHASE_DAG_NONE][phase] += task->duration();
+    if (!slices_.empty())
+        slices_.back().phaseTimes[PHASE_DAG_NONE][phase] += task->duration();
     phaseTimes[PHASE_DAG_NONE][phase] += task->duration();
     phaseStartTimes[phase] = TimeStamp();
 }
 
 TimeStamp
 Statistics::beginSCC()
 {
     return TimeStamp::Now();
@@ -1366,36 +1388,38 @@ Statistics::endSCC(unsigned scc, TimeSta
  * that means that, for any 50ms window of time, at least 80% of the window is
  * devoted to the mutator. In other words, the GC is running for at most 20% of
  * the window, or 10ms. The GC can run multiple slices during the 50ms window
  * as long as the total time it spends is at most 10ms.
  */
 double
 Statistics::computeMMU(TimeDuration window) const
 {
-    MOZ_ASSERT(!slices.empty());
+    MOZ_ASSERT(!slices_.empty());
 
-    TimeDuration gc = slices[0].end - slices[0].start;
+    TimeDuration gc = slices_[0].end - slices_[0].start;
     TimeDuration gcMax = gc;
 
     if (gc >= window)
         return 0.0;
 
     int startIndex = 0;
-    for (size_t endIndex = 1; endIndex < slices.length(); endIndex++) {
-        gc += slices[endIndex].end - slices[endIndex].start;
+    for (size_t endIndex = 1; endIndex < slices_.length(); endIndex++) {
+        auto& startSlice = slices_[startIndex];
+        auto& endSlice = slices_[endIndex];
+        gc += endSlice.end - endSlice.start;
 
-        while (slices[endIndex].end - slices[startIndex].end >= window) {
-            gc -= slices[startIndex].end - slices[startIndex].start;
+        while (endSlice.end - startSlice.end >= window) {
+            gc -= startSlice.end - startSlice.start;
             startIndex++;
         }
 
         TimeDuration cur = gc;
-        if (slices[endIndex].end - slices[startIndex].start > window)
-            cur -= (slices[endIndex].end - slices[startIndex].start - window);
+        if (endSlice.end - startSlice.start > window)
+            cur -= (endSlice.end - startSlice.start - window);
         if (cur > gcMax)
             gcMax = cur;
     }
 
     return double((window - gcMax) / window);
 }
 
 void
@@ -1434,17 +1458,17 @@ Statistics::printProfileTimes(const Prof
     for (auto time : times)
         fprintf(stderr, " %6" PRIi64, static_cast<int64_t>(time.ToMilliseconds()));
     fprintf(stderr, "\n");
 }
 
 void
 Statistics::printSliceProfile()
 {
-    const SliceData& slice = slices.back();
+    const SliceData& slice = slices_.back();
 
     maybePrintProfileHeaders();
 
     fprintf(stderr, "MajorGC: %20s %1d -> %1d      ",
             ExplainReason(slice.reason), int(slice.initialState), int(slice.finalState));
 
     ProfileDurations times;
     times[ProfileKey::Total] = slice.duration();
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -240,17 +240,17 @@ struct Statistics
 
     // Note when we sweep a zone or compartment.
     void sweptZone() { ++zoneStats.sweptZoneCount; }
     void sweptCompartment() { ++zoneStats.sweptCompartmentCount; }
 
     void reset(gc::AbortReason reason) {
         MOZ_ASSERT(reason != gc::AbortReason::None);
         if (!aborted)
-            slices.back().resetReason = reason;
+            slices_.back().resetReason = reason;
     }
 
     void nonincremental(gc::AbortReason reason) {
         MOZ_ASSERT(reason != gc::AbortReason::None);
         nonincrementalReason_ = reason;
     }
 
     bool nonincremental() const {
@@ -269,17 +269,17 @@ struct Statistics
     void beginNurseryCollection(JS::gcreason::Reason reason);
     void endNurseryCollection(JS::gcreason::Reason reason);
 
     TimeStamp beginSCC();
     void endSCC(unsigned scc, TimeStamp start);
 
     UniqueChars formatCompactSliceMessage() const;
     UniqueChars formatCompactSummaryMessage() const;
-    UniqueChars formatJsonMessage(uint64_t timestamp) const;
+    UniqueChars formatJsonMessage(uint64_t timestamp, bool includeSlices = true) const;
     UniqueChars formatDetailedMessage() const;
 
     JS::GCSliceCallback setSliceCallback(JS::GCSliceCallback callback);
     JS::GCNurseryCollectionCallback setNurseryCollectionCallback(
         JS::GCNurseryCollectionCallback callback);
 
     TimeDuration clearMaxGCPauseAccumulator();
     TimeDuration getMaxGCPauseSinceClear();
@@ -314,43 +314,54 @@ struct Statistics
         size_t startFaults, endFaults;
         PhaseTimeTable phaseTimes;
 
         TimeDuration duration() const { return end - start; }
         bool wasReset() const { return resetReason != gc::AbortReason::None; }
     };
 
     typedef Vector<SliceData, 8, SystemAllocPolicy> SliceDataVector;
-    typedef SliceDataVector::ConstRange SliceRange;
+
+    const SliceDataVector& slices() const { return slices_; }
 
-    SliceRange sliceRange() const { return slices.all(); }
-    size_t slicesLength() const { return slices.length(); }
+    TimeStamp start() const {
+        MOZ_ASSERT(phaseStartTimes[PHASE_GC_BEGIN]);
+        return slices_[0].start;
+    }
+
+    TimeStamp end() const {
+        MOZ_ASSERT(phaseStartTimes[PHASE_GC_BEGIN]);
+        return slices_.back().end;
+    }
 
     // Occasionally print header lines for profiling information.
     void maybePrintProfileHeaders();
 
     // Print header line for profile times.
     void printProfileHeader();
 
     // Print total profile times on shutdown.
     void printTotalProfileTimes();
 
+    // Return JSON for the timings of just the given slice.
+    UniqueChars formatJsonSlice(size_t sliceNum) const;
+
   private:
     JSRuntime* runtime;
 
     /* File pointer used for MOZ_GCTIMER output. */
     FILE* fp;
 
     ZoneGCStats zoneStats;
 
     JSGCInvocationKind gckind;
 
     gc::AbortReason nonincrementalReason_;
 
-    SliceDataVector slices;
+    SliceDataVector slices_;
 
     /* Most recent time when the given phase started. */
     EnumeratedArray<Phase, PHASE_LIMIT, TimeStamp> phaseStartTimes;
 
     /* Bookkeeping for GC timings when timingMutator is true */
     TimeStamp timedGCStart;
     TimeDuration timedGCTime;
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -252,16 +252,17 @@
 using namespace js;
 using namespace js::gc;
 
 using mozilla::ArrayLength;
 using mozilla::Get;
 using mozilla::HashCodeScrambler;
 using mozilla::Maybe;
 using mozilla::Swap;
+using mozilla::TimeStamp;
 
 using JS::AutoGCRooter;
 
 /* Increase the IGC marking slice time if we are in highFrequencyGC mode. */
 static const int IGC_MARK_SLICE_MULTIPLIER = 2;
 
 const AllocKind gc::slotsToThingKind[] = {
     /*  0 */ AllocKind::OBJECT0,  AllocKind::OBJECT2,  AllocKind::OBJECT2,  AllocKind::OBJECT4,
@@ -7621,16 +7622,54 @@ JS::GCDescription::formatJSON(JSContext*
     if (!out)
         return nullptr;
     out.get()[nchars] = 0;
 
     CopyAndInflateChars(out.get(), cstr.get(), nchars);
     return out.release();
 }
 
+TimeStamp
+JS::GCDescription::startTime(JSContext* cx) const
+{
+    return cx->runtime()->gc.stats().start();
+}
+
+TimeStamp
+JS::GCDescription::endTime(JSContext* cx) const
+{
+    return cx->runtime()->gc.stats().end();
+}
+
+TimeStamp
+JS::GCDescription::lastSliceStart(JSContext* cx) const
+{
+    return cx->runtime()->gc.stats().slices().back().start;
+}
+
+TimeStamp
+JS::GCDescription::lastSliceEnd(JSContext* cx) const
+{
+    return cx->runtime()->gc.stats().slices().back().end;
+}
+
+JS::UniqueChars
+JS::GCDescription::sliceToJSON(JSContext* cx) const
+{
+    size_t slices = cx->runtime()->gc.stats().slices().length();
+    MOZ_ASSERT(slices > 0);
+    return cx->runtime()->gc.stats().formatJsonSlice(slices - 1);
+}
+
+JS::UniqueChars
+JS::GCDescription::summaryToJSON(JSContext* cx) const
+{
+    return cx->runtime()->gc.stats().formatJsonMessage(0, false);
+}
+
 JS_PUBLIC_API(JS::GCSliceCallback)
 JS::SetGCSliceCallback(JSContext* cx, GCSliceCallback callback)
 {
     return cx->runtime()->gc.setSliceCallback(callback);
 }
 
 JS_PUBLIC_API(JS::DoCycleCollectionCallback)
 JS::SetDoCycleCollectionCallback(JSContext* cx, JS::DoCycleCollectionCallback callback)
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -11734,31 +11734,31 @@ namespace dbg {
 GarbageCollectionEvent::Create(JSRuntime* rt, ::js::gcstats::Statistics& stats, uint64_t gcNumber)
 {
     auto data = rt->make_unique<GarbageCollectionEvent>(gcNumber);
     if (!data)
         return nullptr;
 
     data->nonincrementalReason = stats.nonincrementalReason();
 
-    for (auto range = stats.sliceRange(); !range.empty(); range.popFront()) {
+    for (auto& slice : stats.slices()) {
         if (!data->reason) {
             // There is only one GC reason for the whole cycle, but for legacy
             // reasons this data is stored and replicated on each slice. Each
             // slice used to have its own GCReason, but now they are all the
             // same.
-            data->reason = gcreason::ExplainReason(range.front().reason);
+            data->reason = gcreason::ExplainReason(slice.reason);
             MOZ_ASSERT(data->reason);
         }
 
         if (!data->collections.growBy(1))
             return nullptr;
 
-        data->collections.back().startTimestamp = range.front().start;
-        data->collections.back().endTimestamp = range.front().end;
+        data->collections.back().startTimestamp = slice.start;
+        data->collections.back().endTimestamp = slice.end;
     }
 
     return data;
 }
 
 static bool
 DefineStringProperty(JSContext* cx, HandleObject obj, PropertyName* propName, const char* strVal)
 {