Merge mozilla-inbound to mozilla-central. a=merge
authorAndreea Pavel <apavel@mozilla.com>
Tue, 16 Jan 2018 11:50:13 +0200
changeset 720848 e4107773cffb1baefd5446666fce22c4d6eb0517
parent 720847 a6004a75d2d3e5fe04b4d9920cd2e90080784c59 (current diff)
parent 720837 8aec32412f04950fd53d81e8dc4a15d657f59378 (diff)
child 720849 69ff3aef574894dbae5821fe058281911e76457f
child 720865 0b8e18fe39c5e11b4c38ae09b92d4cea2c2c5b23
child 720867 a50b10be015bc14c035544fec23714352ba7aeef
child 720880 844df7e549f491279463751e2b103c826e1bff7e
child 720901 a1551e45c8503d29c9ca9e1d68af611dda36282d
child 720903 2a0b37dff813abef427554e6fd713700060cce75
child 720907 5af63bf541f16d39c15ff0202b4cf8ba8eaf2577
child 720910 aab99f736644ac82b477361564ce2d17d465ef4f
child 720921 9f56babd9d18f9f5bd1145b18ce34c1e2169cc9c
child 720937 0c27381005e883e99d00a3f713b1d2df121ed8af
child 720954 dbc4ca337b1851b87d99cb5acd0afbed1cdf959b
child 720956 1f87368a838de5768e7808aafa6df33b9ac15271
child 720958 9979cf8094d051b8e272037c2e982217e4bbdcdb
child 720960 a279790045eb91d96d20fca522e1b38c01d72a49
child 720961 bcfa0578223194f014214b240c43c4af0afbea2f
child 720968 88e197dc49e4d025c629a35067d3efeaf7bcc0b9
child 721176 7eafa6c999045a249d79849fff3b7d7a997adf1f
child 721293 f664a4ebc203ee269eca30b426fc24fa46da4639
child 721398 5dacd000828940ecf7d9b974a5435d196a394d0d
child 721540 64020cdd5eb59315e31678a52f2990d89658269e
child 721543 aa026a10e5d6de743ab7fa1bbd78e38b40ed55e1
child 721657 077e1f6eedff24b11b5e229b7a60d0278bbe4dde
child 721671 a7489fb1c04d628326147741a3f8b46fd4c8801b
child 721672 c04472a56b8d8c7b7c417321d616d1a1bf512518
child 721687 3fd12eb417c1c652958f025e63759fa3d44acfeb
child 722180 42635631d7d8f01181e757362c0f0f144221666d
child 722333 03edcbb8d13241ba5fe4ee97a8b3157f3fc6b751
child 722698 59164b4079d780adc9ef1cf95493fac3b825c259
child 723241 a6525ed16a32fcc025ecf7cc073d8200a79f6485
child 723542 e5c5f1c891e2a65e5d77cab5a377ebc2a27c635f
child 748271 60c7867bc2c4438fce9f8d814f8d92d08ac44358
push id95662
push userjdescottes@mozilla.com
push dateTue, 16 Jan 2018 10:32:41 +0000
reviewersmerge
milestone59.0a1
Merge mozilla-inbound to mozilla-central. a=merge
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -1337,19 +1337,16 @@ nsAccessibilityService::Init()
 
 #if defined(XP_WIN)
   // This information needs to be initialized before the observer fires.
   if (XRE_IsParentProcess()) {
     Compatibility::Init();
   }
 #endif // defined(XP_WIN)
 
-  static const char16_t kInitIndicator[] = { '1', 0 };
-  observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kInitIndicator);
-
   // Subscribe to EventListenerService.
   nsCOMPtr<nsIEventListenerService> eventListenerService =
     do_GetService("@mozilla.org/eventlistenerservice;1");
   if (!eventListenerService)
     return false;
 
   eventListenerService->AddListenerChangeListener(this);
 
@@ -1401,16 +1398,19 @@ nsAccessibilityService::Init()
 #endif
 
   // Now its safe to start platform accessibility.
   if (XRE_IsParentProcess())
     PlatformInit();
 
   statistics::A11yInitialized();
 
+  static const char16_t kInitIndicator[] = { '1', 0 };
+  observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kInitIndicator);
+
   return true;
 }
 
 void
 nsAccessibilityService::Shutdown()
 {
   // Application is going to be closed, shutdown accessibility and mark
   // accessibility service as shutdown to prevent calls of its methods.
@@ -1420,19 +1420,16 @@ nsAccessibilityService::Shutdown()
   MOZ_ASSERT(gConsumers, "Accessibility was shutdown already");
   UnsetConsumers(eXPCOM | eMainProcess | ePlatformAPI);
 
   // Remove observers.
   nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
   if (observerService) {
     observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
-
-    static const char16_t kShutdownIndicator[] = { '0', 0 };
-    observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kShutdownIndicator);
   }
 
   // Stop accessible document loader.
   DocManager::Shutdown();
 
   SelectionManager::Shutdown();
 
 #ifdef XP_WIN
@@ -1452,16 +1449,21 @@ nsAccessibilityService::Shutdown()
   NS_RELEASE(gApplicationAccessible);
   gApplicationAccessible = nullptr;
 
   NS_IF_RELEASE(gXPCApplicationAccessible);
   gXPCApplicationAccessible = nullptr;
 
   NS_RELEASE(gAccessibilityService);
   gAccessibilityService = nullptr;
+
+  if (observerService) {
+    static const char16_t kShutdownIndicator[] = { '0', 0 };
+    observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kShutdownIndicator);
+  }
 }
 
 already_AddRefed<Accessible>
 nsAccessibilityService::CreateAccessibleByType(nsIContent* aContent,
                                                DocAccessible* aDoc)
 {
   nsAutoString role;
   nsCoreUtils::XBLBindingRole(aContent, role);
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -513,20 +513,24 @@ Statistics::formatDetailedTotals() const
     char buffer[1024];
     SprintfLiteral(buffer, format, t(total), t(longest));
     return DuplicateString(buffer);
 }
 
 void
 Statistics::formatJsonSlice(size_t sliceNum, JSONPrinter& json) const
 {
+    /*
+     * We number each of the slice properties to keep the code in
+     * GCTelemetry.jsm in sync.  See MAX_SLICE_KEYS.
+     */
     json.beginObject();
-    formatJsonSliceDescription(sliceNum, slices_[sliceNum], json);
+    formatJsonSliceDescription(sliceNum, slices_[sliceNum], json); // # 1-11
 
-    json.beginObjectProperty("times");
+    json.beginObjectProperty("times"); // # 12
     formatJsonPhaseTimes(slices_[sliceNum].phaseTimes, json);
     json.endObject();
 
     json.endObject();
 }
 
 UniqueChars
 Statistics::renderJsonSlice(size_t sliceNum) const
@@ -566,113 +570,126 @@ Statistics::renderJsonMessage(uint64_t t
         return DuplicateString("{status:\"aborted\"}"); // May return nullptr
 
     Sprinter printer(nullptr, false);
     if (!printer.init())
         return UniqueChars(nullptr);
     JSONPrinter json(printer);
 
     json.beginObject();
-    json.property("status", "completed");
-    formatJsonDescription(timestamp, json);
+    json.property("status", "completed"); // JSON Key #1
+    formatJsonDescription(timestamp, json); // #2-22
 
     if (includeSlices) {
-        json.beginListProperty("slices_list");
+        json.beginListProperty("slices_list"); // #23
         for (unsigned i = 0; i < slices_.length(); i++)
             formatJsonSlice(i, json);
         json.endList();
     }
 
-    json.beginObjectProperty("totals");
+    json.beginObjectProperty("totals"); // #24
     formatJsonPhaseTimes(phaseTimes, json);
     json.endObject();
 
     json.endObject();
 
     return UniqueChars(printer.release());
 }
 
 void
 Statistics::formatJsonDescription(uint64_t timestamp, JSONPrinter& json) const
 {
     // If you change JSON properties here, please update:
-    // Telemetry ping code: toolkit/components/telemetry/GCTelemetry.jsm
-    // Telemetry documentation: toolkit/components/telemetry/docs/data/main-ping.rst
-    // Telemetry tests: toolkit/components/telemetry/tests/browser/browser_TelemetryGC.js
-    // Perf.html: https://github.com/devtools-html/perf.html
+    // Telemetry ping code:
+    //   toolkit/components/telemetry/GCTelemetry.jsm
+    // Telemetry documentation:
+    //   toolkit/components/telemetry/docs/data/main-ping.rst
+    // Telemetry tests:
+    //   toolkit/components/telemetry/tests/browser/browser_TelemetryGC.js,
+    //   toolkit/components/telemetry/tests/unit/test_TelemetryGC.js
+    // Perf.html:
+    //   https://github.com/devtools-html/perf.html
+    //
+    // Please also number each property to help correctly maintain the Telemetry ping code
 
-    json.property("timestamp", timestamp);
+    json.property("timestamp", timestamp); // # JSON Key #2
 
     TimeDuration total, longest;
     gcDuration(&total, &longest);
-    json.property("max_pause", longest, JSONPrinter::MILLISECONDS);
-    json.property("total_time", total, JSONPrinter::MILLISECONDS);
+    json.property("max_pause", longest, JSONPrinter::MILLISECONDS); // #3
+    json.property("total_time", total, JSONPrinter::MILLISECONDS); // #4
     // We might be able to omit reason if perf.html was able to retrive it
     // from the first slice.  But it doesn't do this yet.
-    json.property("reason", ExplainReason(slices_[0].reason));
-    json.property("zones_collected", zoneStats.collectedZoneCount);
-    json.property("total_zones", zoneStats.zoneCount);
-    json.property("total_compartments", zoneStats.compartmentCount);
-    json.property("minor_gcs", getCount(STAT_MINOR_GC));
+    json.property("reason", ExplainReason(slices_[0].reason)); // #5
+    json.property("zones_collected", zoneStats.collectedZoneCount); // #6
+    json.property("total_zones", zoneStats.zoneCount); // #7
+    json.property("total_compartments", zoneStats.compartmentCount); // #8
+    json.property("minor_gcs", getCount(STAT_MINOR_GC)); // #9
     uint32_t storebufferOverflows = getCount(STAT_STOREBUFFER_OVERFLOW);
     if (storebufferOverflows)
-        json.property("store_buffer_overflows", storebufferOverflows);
-    json.property("slices", slices_.length());
+        json.property("store_buffer_overflows", storebufferOverflows); // #10
+    json.property("slices", slices_.length()); // #11
 
     const double mmu20 = computeMMU(TimeDuration::FromMilliseconds(20));
     const double mmu50 = computeMMU(TimeDuration::FromMilliseconds(50));
-    json.property("mmu_20ms", int(mmu20 * 100));
-    json.property("mmu_50ms", int(mmu50 * 100));
+    json.property("mmu_20ms", int(mmu20 * 100)); // #12
+    json.property("mmu_50ms", int(mmu50 * 100)); // #13
 
     TimeDuration sccTotal, sccLongest;
     sccDurations(&sccTotal, &sccLongest);
-    json.property("scc_sweep_total", sccTotal, JSONPrinter::MILLISECONDS);
-    json.property("scc_sweep_max_pause", sccLongest, JSONPrinter::MILLISECONDS);
-
+    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_));
-    json.property("allocated_bytes", preBytes);
+        json.property("nonincremental_reason", ExplainAbortReason(nonincrementalReason_)); // #16
+    json.property("allocated_bytes", preBytes); // #17
     uint32_t addedChunks = getCount(STAT_NEW_CHUNK);
     if (addedChunks)
-        json.property("added_chunks", addedChunks);
+        json.property("added_chunks", addedChunks); // #18
     uint32_t removedChunks = getCount(STAT_DESTROY_CHUNK);
     if (removedChunks)
-        json.property("removed_chunks", removedChunks);
-    json.property("major_gc_number", startingMajorGCNumber);
-    json.property("minor_gc_number", startingMinorGCNumber);
-    json.property("slice_number", startingSliceNumber);
+        json.property("removed_chunks", removedChunks); // #19
+    json.property("major_gc_number", startingMajorGCNumber); // #20
+    json.property("minor_gc_number", startingMinorGCNumber); // #21
+    json.property("slice_number", startingSliceNumber); // #22
 }
 
 void
 Statistics::formatJsonSliceDescription(unsigned i, const SliceData& slice, JSONPrinter& json) const
 {
     // If you change JSON properties here, please update:
-    // Telemetry ping code: toolkit/components/telemetry/GCTelemetry.jsm
-    // Telemetry documentation: toolkit/components/telemetry/docs/data/main-ping.rst
-    // Telemetry tests: toolkit/components/telemetry/tests/browser/browser_TelemetryGC.js
-    // Perf.html: https://github.com/devtools-html/perf.html
+    // Telemetry ping code:
+    //   toolkit/components/telemetry/GCTelemetry.jsm
+    // Telemetry documentation:
+    //   toolkit/components/telemetry/docs/data/main-ping.rst
+    // Telemetry tests:
+    //   toolkit/components/telemetry/tests/browser/browser_TelemetryGC.js,
+    //   toolkit/components/telemetry/tests/unit/test_TelemetryGC.js
+    // Perf.html:
+    //   https://github.com/devtools-html/perf.html
+    //
     char budgetDescription[200];
     slice.budget.describe(budgetDescription, sizeof(budgetDescription) - 1);
     TimeStamp originTime = TimeStamp::ProcessCreation();
 
-    json.property("slice", i);
-    json.property("pause", slice.duration(), JSONPrinter::MILLISECONDS);
-    json.property("reason", ExplainReason(slice.reason));
-    json.property("initial_state", gc::StateName(slice.initialState));
-    json.property("final_state", gc::StateName(slice.finalState));
-    json.property("budget", budgetDescription);
-    json.property("major_gc_number", startingMajorGCNumber);
+    json.property("slice", i); // JSON Property #1
+    json.property("pause", slice.duration(), JSONPrinter::MILLISECONDS); // #2
+    json.property("reason", ExplainReason(slice.reason)); // #3
+    json.property("initial_state", gc::StateName(slice.initialState)); // #4
+    json.property("final_state", gc::StateName(slice.finalState)); // #5
+    json.property("budget", budgetDescription); // #6
+    json.property("major_gc_number", startingMajorGCNumber); // #7
     if (thresholdTriggered) {
-        json.floatProperty("trigger_amount", triggerAmount, 0);
-        json.floatProperty("trigger_threshold", triggerThreshold, 0);
+        json.floatProperty("trigger_amount", triggerAmount, 0); // #8
+        json.floatProperty("trigger_threshold", triggerThreshold, 0); // #9
     }
     int64_t numFaults = slice.endFaults - slice.startFaults;
     if (numFaults != 0)
-        json.property("page_faults", numFaults);
-    json.property("start_timestamp", slice.start - originTime, JSONPrinter::SECONDS);
+        json.property("page_faults", numFaults); // #10
+    json.property("start_timestamp", slice.start - originTime, JSONPrinter::SECONDS); // #11
 }
 
 void
 Statistics::formatJsonPhaseTimes(const PhaseTimeTable& phaseTimes, JSONPrinter& json) const
 {
     for (auto phase : AllPhases()) {
         TimeDuration ownTime = phaseTimes[phase];
         if (!ownTime.IsZero())
--- a/toolkit/components/telemetry/GCTelemetry.jsm
+++ b/toolkit/components/telemetry/GCTelemetry.jsm
@@ -18,16 +18,17 @@
  *
  * GCs from both the main process and all content processes are
  * recorded. The data is cleared for each new subsession.
  */
 
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/Log.jsm");
 
 this.EXPORTED_SYMBOLS = ["GCTelemetry"];
 
 // Names of processes where we record GCs.
 const PROCESS_NAMES = ["main", "content"];
 
 // Should be the time we started up in milliseconds since the epoch.
 const BASE_TIME = Date.now() - Services.telemetry.msSinceProcessStart();
@@ -109,28 +110,50 @@ class GCData {
 // If you adjust any of the constants here (slice limit, number of keys, etc.)
 // make sure to update the JSON schema at:
 // https://github.com/mozilla-services/mozilla-pipeline-schemas/blob/master/telemetry/main.schema.json
 // You should also adjust browser_TelemetryGC.js.
 const MAX_GC_KEYS = 24;
 const MAX_SLICES = 4;
 const MAX_SLICE_KEYS = 12;
 const MAX_PHASES = 65;
+const LOGGER_NAME = "Toolkit.Telemetry";
 
-function limitProperties(obj, count) {
-  // If there are too many properties, just delete them all. We don't
+function limitProperties(name, obj, count, log) {
+  log.trace("limitProperties");
+
+  // If there are too many properties delete all/most of them. We don't
   // expect this ever to happen.
-  if (Object.keys(obj).length > count) {
+  if (Object.keys(obj).length >= count) {
     for (let key of Object.keys(obj)) {
+      // If this is the main GC object then save some of the critical
+      // properties.
+      if (name === "data" && (
+          key === "max_pause" ||
+          key === "num_slices" ||
+          key === "slices_list" ||
+          key === "status" ||
+          key === "timestamp" ||
+          key === "total_time" ||
+          key === "totals")) {
+        continue;
+      }
+
       delete obj[key];
     }
+    log.warn("Number of properties exceeded in the GC telemetry " +
+        name + " ping");
   }
 }
 
-function limitSize(data) {
+/*
+ * Reduce the size of the object by limiting the number of slices or times
+ * etc.
+ */
+function limitSize(data, log) {
   // Store the number of slices so we know if we lost any at the end.
   data.num_slices = data.slices_list.length;
 
   data.slices_list.sort((a, b) => b.pause - a.pause);
 
   if (data.slices_list.length > MAX_SLICES) {
     // Make sure we always keep the first slice since it has the
     // reason the GC was started.
@@ -139,40 +162,43 @@ function limitSize(data) {
       data.slices_list[MAX_SLICES - 1] = data.slices_list[firstSliceIndex];
     }
 
     data.slices_list.length = MAX_SLICES;
   }
 
   data.slices_list.sort((a, b) => a.slice - b.slice);
 
-  limitProperties(data, MAX_GC_KEYS);
+  limitProperties("data", data, MAX_GC_KEYS, log);
 
   for (let slice of data.slices_list) {
-    limitProperties(slice, MAX_SLICE_KEYS);
-    limitProperties(slice.times, MAX_PHASES);
+    limitProperties("slice", slice, MAX_SLICE_KEYS, log);
+    limitProperties("slice.times", slice.times, MAX_PHASES, log);
   }
 
-  limitProperties(data.totals, MAX_PHASES);
+  limitProperties("data.totals", data.totals, MAX_PHASES, log);
 }
 
 let processData = new Map();
 for (let name of PROCESS_NAMES) {
   processData.set(name, new GCData(name));
 }
 
 var GCTelemetry = {
   initialized: false,
 
   init() {
     if (this.initialized) {
       return false;
     }
 
     this.initialized = true;
+    this._log = Log.repository.getLoggerWithMessagePrefix(
+      LOGGER_NAME, "GCTelemetry::");
+
     Services.obs.addObserver(this, "garbage-collection-statistics");
 
     if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
       Services.ppmm.addMessageListener("Telemetry:GCStatistics", this);
     }
 
     return true;
   },
@@ -186,19 +212,23 @@ var GCTelemetry = {
 
     if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
       Services.ppmm.removeMessageListener("Telemetry:GCStatistics", this);
     }
     this.initialized = false;
   },
 
   observe(subject, topic, arg) {
-    let data = JSON.parse(arg);
+    this.observeRaw(JSON.parse(arg));
+  },
 
-    limitSize(data);
+  // We expose this method so unit tests can call it, no need to test JSON
+  // parsing.
+  observeRaw(data) {
+    limitSize(data, this._log);
 
     if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
       processData.get("main").record(data);
     } else {
       Services.cpmm.sendAsyncMessage("Telemetry:GCStatistics", data);
     }
   },
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryGC.js
@@ -0,0 +1,144 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+"use strict";
+
+Cu.import("resource://gre/modules/GCTelemetry.jsm", this);
+
+function do_register_cleanup() {
+    GCTelemetry.shutdown();
+}
+
+/*
+ * These tests are very basic, my goal was to add enough testing to support
+ * a change made in Bug 1424760.  TODO Bug 1429635 for adding more extensive
+ * tests and deleting this comment.
+ */
+
+function run_test() {
+  // Test initialisation
+  Assert.ok(GCTelemetry.init(), "Initialize success");
+  Assert.ok(!GCTelemetry.init(), "Wont initialize twice");
+
+  // Test the basic success path.
+
+  // There are currently no entries
+  assert_num_entries(0, false);
+
+  // Add an entry
+  GCTelemetry.observeRaw(make_gc());
+
+  // Get it back.
+  assert_num_entries(1, false);
+  let entries1 = GCTelemetry.entries("main", false);
+  Assert.ok(entries1, "Got entries object");
+  Assert.ok(entries1.random, "Has random property");
+  Assert.ok(entries1.worst, "Has worst property");
+  let entry1 = entries1.worst[0];
+  Assert.ok(entry1, "Got worst entry");
+
+  // "true" will cause the entry to be clared.
+  assert_num_entries(1, true);
+  // There are currently no entries.
+  assert_num_entries(0, false);
+  Assert.equal(20, Object.keys(entry1).length);
+
+  // Test too many fields
+  let my_gc = make_gc();
+  for (let i = 0; i < 100; i++) {
+      my_gc["new_property_" + i] = "Data";
+  }
+
+  GCTelemetry.observeRaw(my_gc);
+  // Assert that it was recorded but has only 7 fields.
+  assert_num_entries(1, false);
+  let entries2 = GCTelemetry.entries("main", false);
+  Assert.ok(entries2, "Got entries object");
+  let entry2 = entries2.worst[0];
+  Assert.ok(entry2, "Got worst entry");
+  Assert.equal(7, Object.keys(entry2).length);
+}
+
+function assert_num_entries(expect, clear) {
+  let entries = GCTelemetry.entries("main", clear);
+  Assert.equal(expect, entries.worst.length, expect + " worst entries");
+  // Randomly sampled GCs are only recorded for content processes
+  Assert.equal(0, entries.random.length, expect + " random entries");
+}
+
+/*
+ * These events are not exactly well-formed, but good enough.  For example
+ * there's no guantee that all the pause times add up to total time, or
+ * that max_pause is correct.
+ */
+function make_gc() {
+  // Timestamps are in milliseconds since startup. All the times here
+  // are wall-clock times, which may not be monotonically increasing.
+  let timestamp = Math.random() * 1000000;
+
+  let gc = {
+    "status": "completed",
+    "timestamp": timestamp,
+    // All durations are in milliseconds.
+    "max_pause": Math.random() * 95 + 5,
+    "total_time": Math.random() * 500 + 500, // Sum of all slice times.
+    "zones_collected": 9,
+    "total_zones": 9,
+    "total_compartments": 309,
+    "minor_gcs": 44,
+    "store_buffer_overflows": 19,
+    "mmu_20ms": 0,
+    "mmu_50ms": 0,
+    "nonincremental_reason": "GCBytesTrigger",
+    "allocated_bytes": 38853696, // in bytes
+    "added_chunks": 54,
+    "removed_chunks": 12,
+    "slices": 15,
+    "slice_number": 218, // The first slice number for this GC event.
+    "slices_list": [
+      {
+        "slice": 218,  // The global index of this slice.
+        "pause": Math.random() * 2 + 28,
+        "reason": "SET_NEW_DOCUMENT",
+        "initial_state": "NotActive",
+        "final_state": "Mark",
+        "budget": "10ms",
+        "page_faults": 1,
+        "start_timestamp": timestamp + Math.random() * 50000,
+        "times": {
+          "wait_background_thread": 0.012,
+          "mark_discard_code": 2.845,
+          "purge": 0.723,
+          "mark": 9.831,
+          "mark_roots": 0.102,
+          "buffer_gray_roots": 3.095,
+          "mark_cross_compartment_wrappers": 0.039,
+          "mark_c_and_js_stacks": 0.005,
+          "mark_runtime_wide_data": 2.313,
+          "mark_embedding": 0.117,
+          "mark_compartments": 2.27,
+          "unmark": 1.063,
+          "minor_gcs_to_evict_nursery": 8.701,
+        }
+      }
+    ],
+    "totals": {
+      "wait_background_thread": 0.012,
+      "mark_discard_code": 2.845,
+      "purge": 0.723,
+      "mark": 9.831,
+      "mark_roots": 0.102,
+      "buffer_gray_roots": 3.095,
+      "mark_cross_compartment_wrappers": 0.039,
+      "mark_c_and_js_stacks": 0.005,
+      "mark_runtime_wide_data": 2.313,
+      "mark_embedding": 0.117,
+      "mark_compartments": 2.27,
+      "unmark": 1.063,
+      "minor_gcs_to_evict_nursery": 8.701,
+    }
+  };
+  return gc;
+}
+
--- a/toolkit/components/telemetry/tests/unit/xpcshell.ini
+++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini
@@ -69,8 +69,9 @@ skip-if = toolkit == 'android'
 [test_TelemetryCaptureStack.js]
 [test_TelemetryEvents.js]
 [test_ChildEvents.js]
 skip-if = os == "android" # Disabled due to crashes (see bug 1331366)
 [test_TelemetryModules.js]
 skip-if = (os == "android" && release_or_beta) # (see bug 1351197)
 [test_PingSender.js]
 skip-if = (os == "android") || (os == "linux" && bits == 32)
+[test_TelemetryGC.js]