Merge mozilla-inbound to mozilla-central a=merge
authorAndreea Pavel <apavel@mozilla.com>
Sat, 13 Jul 2019 00:45:18 +0300
changeset 482623 f1d381735279b97f3f130e4fd54d3356bfad486c
parent 482622 cd685b4cff6dbbfd5eed7dc27c1e169995c93653 (current diff)
parent 482531 1fbc711925ba8c05f021f40aad329483413d38f6 (diff)
child 482624 91fee5b67b691f56997a3f27d13d63f7dd57dbb6
child 482672 761072d343054f84b52f4efd01637ca3d2ed998f
push id113677
push userapavel@mozilla.com
push dateFri, 12 Jul 2019 21:57:56 +0000
treeherdermozilla-inbound@91fee5b67b69 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone70.0a1
first release with
nightly linux32
f1d381735279 / 70.0a1 / 20190712214601 / files
nightly linux64
f1d381735279 / 70.0a1 / 20190712214601 / files
nightly mac
f1d381735279 / 70.0a1 / 20190712214601 / files
nightly win32
f1d381735279 / 70.0a1 / 20190712214601 / files
nightly win64
f1d381735279 / 70.0a1 / 20190712214601 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central a=merge
js/src/jsfriendapi.h
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1948,21 +1948,16 @@ void nsJSContext::RunNextCollectorTimer(
 
 // static
 void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell,
                                              JS::GCReason aReason) {
   if (!aDocShell || !XRE_IsContentProcess()) {
     return;
   }
 
-  // Only do this if there is an incremental collection active.
-  if (!sInterSliceGCRunner && !sICCRunner) {
-    return;
-  }
-
   nsCOMPtr<nsIDocShellTreeItem> root;
   aDocShell->GetSameTypeRootTreeItem(getter_AddRefs(root));
   if (root == aDocShell) {
     // We don't want to run collectors when loading the top level page.
     return;
   }
 
   Document* rootDocument = root->GetDocument();
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -969,16 +969,19 @@ void Chunk::updateChunkListAfterFree(JSR
     decommitAllArenas();
     MOZ_ASSERT(info.numArenasFreeCommitted == 0);
     rt->gc.recycleChunk(this, lock);
   }
 }
 
 void GCRuntime::releaseArena(Arena* arena, const AutoLockGC& lock) {
   arena->zone->zoneSize.removeGCArena();
+  if (arena->zone->wasGCStarted()) {
+    stats().recordFreedArena();
+  }
   arena->chunk()->releaseArena(rt, arena, lock);
 }
 
 GCRuntime::GCRuntime(JSRuntime* rt)
     : rt(rt),
       systemZone(nullptr),
       atomsZone(nullptr),
       stats_(rt),
@@ -4554,16 +4557,17 @@ bool GCRuntime::beginMarkPhase(JS::GCRea
   gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK);
   traceRuntimeForMajorGC(gcmarker, session);
 
   if (isIncremental) {
     markCompartments();
   }
 
   updateMallocCountersOnGCStart();
+  stats().measureInitialHeapSize();
 
   /*
    * Process any queued source compressions during the start of a major
    * GC.
    */
   {
     AutoLockHelperThreadState helperLock;
     HelperThreadState().startHandlingCompressionTasks(helperLock);
@@ -8321,16 +8325,19 @@ void GCRuntime::mergeRealms(Realm* sourc
     MOZ_ASSERT(r.get() == source);
   }
 
   // Merge the allocator, stats and UIDs in source's zone into target's zone.
   target->zone()->arenas.adoptArenas(&source->zone()->arenas,
                                      targetZoneIsCollecting);
   target->zone()->addTenuredAllocsSinceMinorGC(
       source->zone()->getAndResetTenuredAllocsSinceMinorGC());
+  if (targetZoneIsCollecting) {
+    stats().adoptHeapSizeDuringIncrementalGC(source->zone());
+  }
   target->zone()->zoneSize.adopt(source->zone()->zoneSize);
   target->zone()->adoptUniqueIds(source->zone());
   target->zone()->adoptMallocBytes(source->zone());
 
   // Merge other info in source's zone into target's zone.
   target->zone()->types.typeLifoAlloc().transferFrom(
       &source->zone()->types.typeLifoAlloc());
   MOZ_RELEASE_ASSERT(source->zone()->types.sweepTypeLifoAlloc.ref().isEmpty());
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -20,16 +20,18 @@
 #include "dbg/Debugger.h"
 #include "gc/GC.h"
 #include "gc/Memory.h"
 #include "util/Text.h"
 #include "vm/HelperThreads.h"
 #include "vm/Runtime.h"
 #include "vm/Time.h"
 
+#include "gc/PrivateIterators-inl.h"
+
 using namespace js;
 using namespace js::gc;
 using namespace js::gcstats;
 
 using mozilla::DebugOnly;
 using mozilla::EnumeratedArray;
 using mozilla::TimeDuration;
 using mozilla::TimeStamp;
@@ -330,17 +332,17 @@ UniqueChars Statistics::formatCompactSum
 
   SprintfLiteral(buffer,
                  "Zones: %d of %d (-%d); Compartments: %d of %d (-%d); "
                  "HeapSize: %.3f MiB; "
                  "HeapChange (abs): %+d (%d); ",
                  zoneStats.collectedZoneCount, zoneStats.zoneCount,
                  zoneStats.sweptZoneCount, zoneStats.collectedCompartmentCount,
                  zoneStats.compartmentCount, zoneStats.sweptCompartmentCount,
-                 double(preHeapSize) / bytesPerMiB,
+                 double(preTotalHeapBytes) / bytesPerMiB,
                  counts[COUNT_NEW_CHUNK] - counts[COUNT_DESTROY_CHUNK],
                  counts[COUNT_NEW_CHUNK] + counts[COUNT_DESTROY_CHUNK]);
   if (!fragments.append(DuplicateString(buffer))) {
     return UniqueChars(nullptr);
   }
 
   MOZ_ASSERT_IF(counts[COUNT_ARENA_RELOCATED], gckind == GC_SHRINK);
   if (gckind == GC_SHRINK) {
@@ -452,17 +454,17 @@ UniqueChars Statistics::formatDetailedDe
       buffer, format, ExplainInvocationKind(gckind),
       ExplainGCReason(slices_[0].reason), nonincremental() ? "no - " : "yes",
       nonincremental() ? ExplainAbortReason(nonincrementalReason_) : "",
       zoneStats.collectedZoneCount, zoneStats.zoneCount,
       zoneStats.sweptZoneCount, zoneStats.collectedCompartmentCount,
       zoneStats.compartmentCount, zoneStats.sweptCompartmentCount,
       getCount(COUNT_MINOR_GC), getCount(COUNT_STOREBUFFER_OVERFLOW),
       mmu20 * 100., mmu50 * 100., t(sccTotal), t(sccLongest),
-      double(preHeapSize) / bytesPerMiB,
+      double(preTotalHeapBytes) / bytesPerMiB,
       getCount(COUNT_NEW_CHUNK) - getCount(COUNT_DESTROY_CHUNK),
       getCount(COUNT_NEW_CHUNK) + getCount(COUNT_DESTROY_CHUNK),
       double(ArenaSize * getCount(COUNT_ARENA_RELOCATED)) / bytesPerMiB,
       thresholdBuffer);
 
   return DuplicateString(buffer);
 }
 
@@ -679,19 +681,19 @@ void Statistics::formatJsonDescription(u
   json.property("scc_sweep_total", sccTotal, JSONPrinter::MILLISECONDS);  // #14
   json.property("scc_sweep_max_pause", sccLongest,
                 JSONPrinter::MILLISECONDS);  // #15
 
   if (nonincrementalReason_ != AbortReason::None) {
     json.property("nonincremental_reason",
                   ExplainAbortReason(nonincrementalReason_));  // #16
   }
-  json.property("allocated_bytes", preHeapSize);  // #17
+  json.property("allocated_bytes", preTotalHeapBytes);  // #17
   if (use == Statistics::JSONUse::PROFILER) {
-    json.property("post_heap_size", postHeapSize);
+    json.property("post_heap_size", postTotalHeapBytes);
   }
 
   uint32_t addedChunks = getCount(COUNT_NEW_CHUNK);
   if (addedChunks) {
     json.property("added_chunks", addedChunks);  // #18
   }
   uint32_t removedChunks = getCount(COUNT_DESTROY_CHUNK);
   if (removedChunks) {
@@ -749,18 +751,20 @@ void Statistics::formatJsonPhaseTimes(co
 }
 
 Statistics::Statistics(JSRuntime* rt)
     : runtime(rt),
       gcTimerFile(nullptr),
       gcDebugFile(nullptr),
       nonincrementalReason_(gc::AbortReason::None),
       allocsSinceMinorGC({0, 0}),
-      preHeapSize(0),
-      postHeapSize(0),
+      preTotalHeapBytes(0),
+      postTotalHeapBytes(0),
+      preCollectedHeapBytes(0),
+      heapBytesFreed(0),
       thresholdTriggered(false),
       triggerAmount(0.0),
       triggerThreshold(0.0),
       startingMinorGCNumber(0),
       startingMajorGCNumber(0),
       startingSliceNumber(0),
       maxPauseInInterval(0),
       sliceCallback(nullptr),
@@ -982,29 +986,52 @@ void Statistics::printStats() {
 void Statistics::beginGC(JSGCInvocationKind kind,
                          const TimeStamp& currentTime) {
   slices_.clearAndFree();
   sccTimes.clearAndFree();
   gckind = kind;
   nonincrementalReason_ = gc::AbortReason::None;
 
   GCRuntime& gc = runtime->gc;
-  preHeapSize = gc.heapSize.gcBytes();
+  preTotalHeapBytes = gc.heapSize.gcBytes();
+
+  preCollectedHeapBytes = 0;
+  heapBytesFreed = 0;
+
   startingMajorGCNumber = gc.majorGCCount();
   startingSliceNumber = gc.gcNumber();
+
   if (gc.lastGCTime()) {
     timeSinceLastGC = currentTime - gc.lastGCTime();
   }
 }
 
+void Statistics::measureInitialHeapSize() {
+  MOZ_ASSERT(preCollectedHeapBytes == 0);
+  for (GCZonesIter zone(runtime, WithAtoms); !zone.done(); zone.next()) {
+    preCollectedHeapBytes += zone->zoneSize.gcBytes();
+  }
+}
+
+void Statistics::adoptHeapSizeDuringIncrementalGC(Zone* mergedZone) {
+  // A zone is being merged into a zone that's currently being collected so we
+  // need to adjust our record of the total size of heap for collected zones.
+  MOZ_ASSERT(runtime->gc.isIncrementalGCInProgress());
+  preCollectedHeapBytes += mergedZone->zoneSize.gcBytes();
+}
+
 void Statistics::endGC() {
-  TimeDuration sccTotal, sccLongest;
-  sccDurations(&sccTotal, &sccLongest);
-  postHeapSize = runtime->gc.heapSize.gcBytes();
+  postTotalHeapBytes = runtime->gc.heapSize.gcBytes();
+
+  sendGCTelemetry();
 
+  thresholdTriggered = false;
+}
+
+void Statistics::sendGCTelemetry() {
   runtime->addTelemetry(JS_TELEMETRY_GC_IS_ZONE_GC,
                         !zoneStats.isFullCollection());
   TimeDuration markTotal = SumPhase(PhaseKind::MARK, phaseTimes);
   TimeDuration markRootsTotal = SumPhase(PhaseKind::MARK_ROOTS, phaseTimes);
   double markTime = t(markTotal);
   size_t markCount = runtime->gc.marker.getMarkCount();
   double markRate = markCount / markTime;
   runtime->addTelemetry(JS_TELEMETRY_GC_MARK_MS, markTime);
@@ -1033,16 +1060,19 @@ void Statistics::endGC() {
   runtime->addTelemetry(JS_TELEMETRY_GC_RESET, lastSlice.wasReset());
   if (lastSlice.wasReset()) {
     runtime->addTelemetry(JS_TELEMETRY_GC_RESET_REASON,
                           uint32_t(lastSlice.resetReason));
   }
 
   runtime->addTelemetry(JS_TELEMETRY_GC_INCREMENTAL_DISABLED,
                         !runtime->gc.isIncrementalGCAllowed());
+
+  TimeDuration sccTotal, sccLongest;
+  sccDurations(&sccTotal, &sccLongest);
   runtime->addTelemetry(JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS, t(sccTotal));
   runtime->addTelemetry(JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS, t(sccLongest));
 
   TimeDuration total, longest;
   gcDuration(&total, &longest);
 
   runtime->addTelemetry(JS_TELEMETRY_GC_MS, t(total));
   runtime->addTelemetry(JS_TELEMETRY_GC_MAX_PAUSE_MS_2, t(longest));
@@ -1055,17 +1085,22 @@ void Statistics::endGC() {
   if (!runtime->parentRuntime && timeSinceLastGC) {
     runtime->addTelemetry(JS_TELEMETRY_GC_TIME_BETWEEN_S,
                           timeSinceLastGC.ToSeconds());
     if (!nonincremental()) {
       runtime->addTelemetry(JS_TELEMETRY_GC_SLICE_COUNT, slices_.length());
     }
   }
 
-  thresholdTriggered = false;
+  MOZ_ASSERT(preCollectedHeapBytes >= heapBytesFreed);
+  size_t bytesSurvived = preCollectedHeapBytes - heapBytesFreed;
+  double survialRate =
+      100.0 * double(bytesSurvived) / double(preCollectedHeapBytes);
+  runtime->addTelemetry(JS_TELEMETRY_GC_TENURED_SURVIVAL_RATE,
+                        uint32_t(survialRate));
 }
 
 void Statistics::beginNurseryCollection(JS::GCReason reason) {
   count(COUNT_MINOR_GC);
   startingMinorGCNumber = runtime->gc.minorGCCount();
   if (nurseryCollectionCallback) {
     (*nurseryCollectionCallback)(
         runtime->mainContextFromOwnThread(),
@@ -1133,49 +1168,18 @@ void Statistics::endSlice() {
 
   if (!aborted) {
     auto& slice = slices_.back();
     slice.end = ReallyNow();
     slice.endFaults = GetPageFaultCount();
     slice.finalState = runtime->gc.state();
 
     writeLogMessage("end slice");
-    TimeDuration sliceTime = slice.end - slice.start;
-    runtime->addTelemetry(JS_TELEMETRY_GC_SLICE_MS, t(sliceTime));
 
-    if (slice.budget.isTimeBudget()) {
-      int64_t budget_ms = slice.budget.timeBudget.budget;
-      runtime->addTelemetry(JS_TELEMETRY_GC_BUDGET_MS, budget_ms);
-      if (IsCurrentlyAnimating(runtime->lastAnimationTime, slice.end)) {
-        runtime->addTelemetry(JS_TELEMETRY_GC_ANIMATION_MS, t(sliceTime));
-      }
-
-      // Record any phase that goes 1.5 times or 5ms over its budget.
-      double longSliceThreshold = std::min(1.5 * budget_ms, budget_ms + 5.0);
-      if (sliceTime.ToMilliseconds() > longSliceThreshold) {
-        PhaseKind longest = LongestPhaseSelfTimeInMajorGC(slice.phaseTimes);
-        reportLongestPhaseInMajorGC(longest, JS_TELEMETRY_GC_SLOW_PHASE);
-
-        // If the longest phase was waiting for parallel tasks then
-        // record the longest task.
-        if (longest == PhaseKind::JOIN_PARALLEL_TASKS) {
-          PhaseKind longestParallel =
-              LongestPhaseSelfTimeInMajorGC(slice.parallelTimes);
-          reportLongestPhaseInMajorGC(longestParallel,
-                                      JS_TELEMETRY_GC_SLOW_TASK);
-        }
-      }
-
-      // Record how long we went over budget.
-      int64_t overrun = sliceTime.ToMicroseconds() - (1000 * budget_ms);
-      if (overrun > 0) {
-        runtime->addTelemetry(JS_TELEMETRY_GC_BUDGET_OVERRUN,
-                              uint32_t(overrun));
-      }
-    }
+    sendSliceTelemetry(slice);
 
     sliceCount_++;
   }
 
   bool last = !runtime->gc.isIncrementalGCInProgress();
   if (last) {
     if (gcTimerFile) {
       printStats();
@@ -1231,16 +1235,50 @@ void Statistics::endSlice() {
 
     phaseStartTimes[Phase::MUTATOR] = mutatorStartTime;
     phaseTimes[Phase::MUTATOR] = mutatorTime;
   }
 
   aborted = false;
 }
 
+void Statistics::sendSliceTelemetry(const SliceData& slice) {
+  TimeDuration sliceTime = slice.end - slice.start;
+  runtime->addTelemetry(JS_TELEMETRY_GC_SLICE_MS, t(sliceTime));
+
+  if (slice.budget.isTimeBudget()) {
+    int64_t budget_ms = slice.budget.timeBudget.budget;
+    runtime->addTelemetry(JS_TELEMETRY_GC_BUDGET_MS, budget_ms);
+    if (IsCurrentlyAnimating(runtime->lastAnimationTime, slice.end)) {
+      runtime->addTelemetry(JS_TELEMETRY_GC_ANIMATION_MS, t(sliceTime));
+    }
+
+    // Record any phase that goes 1.5 times or 5ms over its budget.
+    double longSliceThreshold = std::min(1.5 * budget_ms, budget_ms + 5.0);
+    if (sliceTime.ToMilliseconds() > longSliceThreshold) {
+      PhaseKind longest = LongestPhaseSelfTimeInMajorGC(slice.phaseTimes);
+      reportLongestPhaseInMajorGC(longest, JS_TELEMETRY_GC_SLOW_PHASE);
+
+      // If the longest phase was waiting for parallel tasks then
+      // record the longest task.
+      if (longest == PhaseKind::JOIN_PARALLEL_TASKS) {
+        PhaseKind longestParallel =
+            LongestPhaseSelfTimeInMajorGC(slice.parallelTimes);
+        reportLongestPhaseInMajorGC(longestParallel, JS_TELEMETRY_GC_SLOW_TASK);
+      }
+    }
+
+    // Record how long we went over budget.
+    int64_t overrun = int64_t(sliceTime.ToMicroseconds()) - (1000 * budget_ms);
+    if (overrun > 0) {
+      runtime->addTelemetry(JS_TELEMETRY_GC_BUDGET_OVERRUN, uint32_t(overrun));
+    }
+  }
+}
+
 void Statistics::reportLongestPhaseInMajorGC(PhaseKind longest,
                                              int telemetryId) {
   if (longest != PhaseKind::NONE) {
     uint8_t bucket = phaseKinds[longest].telemetryBucket;
     runtime->addTelemetry(telemetryId, bucket);
   }
 }
 
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -175,16 +175,20 @@ struct Statistics {
 
   void reset(gc::AbortReason reason) {
     MOZ_ASSERT(reason != gc::AbortReason::None);
     if (!aborted) {
       slices_.back().resetReason = reason;
     }
   }
 
+  void measureInitialHeapSize();
+  void adoptHeapSizeDuringIncrementalGC(Zone* mergedZone);
+  void recordFreedArena() { heapBytesFreed += gc::ArenaSize; }
+
   void nonincremental(gc::AbortReason reason) {
     MOZ_ASSERT(reason != gc::AbortReason::None);
     nonincrementalReason_ = reason;
     writeLogMessage("Non-incremental reason: %s", nonincrementalReason());
   }
 
   bool nonincremental() const {
     return nonincrementalReason_ != gc::AbortReason::None;
@@ -352,19 +356,28 @@ struct Statistics {
    * These events cannot be kept in the above array, we need to take their
    * address.
    */
   struct {
     uint32_t nursery;
     uint32_t tenured;
   } allocsSinceMinorGC;
 
-  /* Heap size before and after the GC ran. */
-  size_t preHeapSize;
-  size_t postHeapSize;
+  /* Total GC heap size before and after the GC ran. */
+  size_t preTotalHeapBytes;
+  size_t postTotalHeapBytes;
+
+  /* GC heap size for collected zones before GC ran. */
+  size_t preCollectedHeapBytes;
+
+  /* Total GC heap memory freed during collection. */
+  using AtomicSizeCounter =
+      mozilla::Atomic<size_t, mozilla::Relaxed,
+                      mozilla::recordreplay::Behavior::DontPreserve>;
+  AtomicSizeCounter heapBytesFreed;
 
   /* If the GC was triggered by exceeding some threshold, record the
    * threshold and the value that exceeded it. */
   bool thresholdTriggered;
   double triggerAmount;
   double triggerThreshold;
 
   /* GC numbers as of the beginning of the collection. */
@@ -420,16 +433,19 @@ struct Statistics {
   uint64_t sliceCount_;
 
   Phase currentPhase() const;
   Phase lookupChildPhase(PhaseKind phaseKind) const;
 
   void beginGC(JSGCInvocationKind kind, const TimeStamp& currentTime);
   void endGC();
 
+  void sendGCTelemetry();
+  void sendSliceTelemetry(const SliceData& slice);
+
   void recordPhaseBegin(Phase phase);
   void recordPhaseEnd(Phase phase);
 
   void gcDuration(TimeDuration* total, TimeDuration* maxPause) const;
   void sccDurations(TimeDuration* total, TimeDuration* maxPause) const;
   void printStats();
 
   void reportLongestPhaseInMajorGC(PhaseKind longest, int telemetryId);
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -162,16 +162,17 @@ enum {
   JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS,
   JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS,
   JS_TELEMETRY_GC_MINOR_REASON,
   JS_TELEMETRY_GC_MINOR_REASON_LONG,
   JS_TELEMETRY_GC_MINOR_US,
   JS_TELEMETRY_GC_NURSERY_BYTES,
   JS_TELEMETRY_GC_PRETENURE_COUNT,
   JS_TELEMETRY_GC_NURSERY_PROMOTION_RATE,
+  JS_TELEMETRY_GC_TENURED_SURVIVAL_RATE,
   JS_TELEMETRY_GC_MARK_RATE,
   JS_TELEMETRY_GC_TIME_BETWEEN_S,
   JS_TELEMETRY_GC_TIME_BETWEEN_SLICES_MS,
   JS_TELEMETRY_GC_SLICE_COUNT,
   JS_TELEMETRY_PRIVILEGED_PARSER_COMPILE_LAZY_AFTER_MS,
   JS_TELEMETRY_WEB_PARSER_COMPILE_LAZY_AFTER_MS,
   JS_TELEMETRY_DEPRECATED_ARRAY_GENERICS,
   JS_TELEMETRY_END
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2746,16 +2746,19 @@ static void AccumulateTelemetryCallback(
       Telemetry::Accumulate(Telemetry::GC_NURSERY_BYTES_2, sample);
       break;
     case JS_TELEMETRY_GC_PRETENURE_COUNT:
       Telemetry::Accumulate(Telemetry::GC_PRETENURE_COUNT, sample);
       break;
     case JS_TELEMETRY_GC_NURSERY_PROMOTION_RATE:
       Telemetry::Accumulate(Telemetry::GC_NURSERY_PROMOTION_RATE, sample);
       break;
+    case JS_TELEMETRY_GC_TENURED_SURVIVAL_RATE:
+      Telemetry::Accumulate(Telemetry::GC_TENURED_SURVIVAL_RATE, sample);
+      break;
     case JS_TELEMETRY_GC_MARK_RATE:
       Telemetry::Accumulate(Telemetry::GC_MARK_RATE, sample);
       break;
     case JS_TELEMETRY_GC_TIME_BETWEEN_S:
       Telemetry::Accumulate(Telemetry::GC_TIME_BETWEEN_S, sample);
       break;
     case JS_TELEMETRY_GC_TIME_BETWEEN_SLICES_MS:
       Telemetry::Accumulate(Telemetry::GC_TIME_BETWEEN_SLICES_MS, sample);
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -904,16 +904,27 @@
     "alert_emails": ["dev-telemetry-gc-alerts@mozilla.org", "jcoppeard@mozilla.com"],
     "expires_in_version": "never",
     "kind": "linear",
     "high": 100,
     "n_buckets": 50,
     "bug_numbers": [1485299],
     "description": "The percentage of nursery objects that were promoted to tenured heap."
   },
+  "GC_TENURED_SURVIVAL_RATE": {
+    "record_in_processes": ["main", "content"],
+    "alert_emails": ["dev-telemetry-gc-alerts@mozilla.org", "jcoppeard@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "linear",
+    "high": 100,
+    "n_buckets": 50,
+    "releaseChannelCollection": "opt-out",
+    "bug_numbers": [1563755],
+    "description": "The percentage of tenured GC things that survived a collection."
+  },
   "GC_MARK_RATE": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["dev-telemetry-gc-alerts@mozilla.org", "jcoppeard@mozilla.com"],
     "expires_in_version": "never",
     "kind": "linear",
     "low": 1000,
     "high": 1000000,
     "n_buckets": 100,