Bug 1481812 - Refactor memory gathering into its own module r=chutten
authorJan-Erik Rediger <jrediger@mozilla.com>
Fri, 12 Oct 2018 13:51:24 +0000
changeset 499741 801c33dcde58caa5a8be251eb1b0f9597cd26777
parent 499740 bb057b527690f626ad617828044c96cd113d4122
child 499742 f1a8f5f2079797d054575d5b8877a5e4654b7650
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschutten
bugs1481812
milestone64.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 1481812 - Refactor memory gathering into its own module r=chutten This avoids loading the remaining parts of TelemetrySession in a content process. This saves around 10 kb of memory. Differential Revision: https://phabricator.services.mozilla.com/D8378
browser/base/content/test/performance/browser_startup_content.js
toolkit/components/telemetry/app/TelemetryController.jsm
toolkit/components/telemetry/moz.build
toolkit/components/telemetry/other/MemoryTelemetry.jsm
toolkit/components/telemetry/pings/TelemetrySession.jsm
toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
--- a/browser/base/content/test/performance/browser_startup_content.js
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -59,17 +59,17 @@ const whitelist = {
     "resource://gre/modules/Readerable.jsm",
     "resource://gre/modules/WebProgressChild.jsm",
 
     // Pocket
     "chrome://pocket/content/AboutPocket.jsm",
 
     // Telemetry
     "resource://gre/modules/TelemetryController.jsm", // bug 1470339
-    "resource://gre/modules/TelemetrySession.jsm", // bug 1470339
+    "resource://gre/modules/MemoryTelemetry.jsm", // bug 1481812
     "resource://gre/modules/TelemetryUtils.jsm", // bug 1470339
 
     // Extensions
     "resource://gre/modules/ExtensionUtils.jsm",
     "resource://gre/modules/MessageChannel.jsm",
   ]),
 };
 
--- a/toolkit/components/telemetry/app/TelemetryController.jsm
+++ b/toolkit/components/telemetry/app/TelemetryController.jsm
@@ -52,16 +52,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   ClientID: "resource://gre/modules/ClientID.jsm",
   AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
   TelemetryStorage: "resource://gre/modules/TelemetryStorage.jsm",
   TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
   TelemetryArchive: "resource://gre/modules/TelemetryArchive.jsm",
   TelemetrySession: "resource://gre/modules/TelemetrySession.jsm",
+  MemoryTelemetry: "resource://gre/modules/MemoryTelemetry.jsm",
   TelemetrySend: "resource://gre/modules/TelemetrySend.jsm",
   TelemetryReportingPolicy: "resource://gre/modules/TelemetryReportingPolicy.jsm",
   TelemetryModules: "resource://gre/modules/ModulesPing.jsm",
   UpdatePing: "resource://gre/modules/UpdatePing.jsm",
   TelemetryHealthPing: "resource://gre/modules/HealthPing.jsm",
   TelemetryEventPing: "resource://gre/modules/EventPing.jsm",
   OS: "resource://gre/modules/osfile.jsm",
 });
@@ -626,16 +627,17 @@ var Impl = {
       return Promise.resolve();
     }
 
     this._attachObservers();
 
     // Perform a lightweight, early initialization for the component, just registering
     // a few observers and initializing the session.
     TelemetrySession.earlyInit(this._testMode);
+    MemoryTelemetry.earlyInit(this._testMode);
 
     // Annotate crash reports so that we get pings for startup crashes
     TelemetrySend.earlyInit();
 
     // For very short session durations, we may never load the client
     // id from disk.
     // We try to cache it in prefs to avoid this, even though this may
     // lead to some stale client ids.
@@ -667,16 +669,17 @@ var Impl = {
           this._log.trace("Upload disabled, but got a valid client ID. Setting canary client ID.");
           this._clientID = await ClientID.setClientID(TelemetryUtils.knownClientID);
         }
 
         await TelemetrySend.setup(this._testMode);
 
         // Perform TelemetrySession delayed init.
         await TelemetrySession.delayedInit();
+        await MemoryTelemetry.delayedInit();
 
         if (Services.prefs.getBoolPref(TelemetryUtils.Preferences.NewProfilePingEnabled, false) &&
             !TelemetrySession.newProfilePingSent) {
           // Kick off the scheduling of the new-profile ping.
           this.scheduleNewProfilePing();
         }
 
         // Purge the pings archive by removing outdated pings. We don't wait for
@@ -723,17 +726,17 @@ var Impl = {
     this._testMode = testing;
 
     // We call |enableTelemetryRecording| here to make sure that Telemetry.canRecord* flags
     // are in sync between chrome and content processes.
     if (!this.enableTelemetryRecording()) {
       this._log.trace("setupContentTelemetry - Content process recording disabled.");
       return;
     }
-    TelemetrySession.setupContent(testing);
+    MemoryTelemetry.setupContent(testing);
   },
 
   // Do proper shutdown waiting and cleanup.
   async _cleanupOnShutdown() {
     if (!this._initialized) {
       return;
     }
 
@@ -756,16 +759,17 @@ var Impl = {
 
       // Stop any ping sending.
       await TelemetrySend.shutdown();
 
       // Send latest data.
       await TelemetryHealthPing.shutdown();
 
       await TelemetrySession.shutdown();
+      await MemoryTelemetry.shutdown();
 
       // First wait for clients processing shutdown.
       await this._shutdownBarrier.wait();
 
       // ... and wait for any outstanding async ping activity.
       await this._connectionsBarrier.wait();
 
       // Perform final shutdown operations.
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -93,16 +93,17 @@ EXTRA_JS_MODULES += [
     'app/TelemetryEnvironment.jsm',
     'app/TelemetryReportingPolicy.jsm',
     'app/TelemetrySend.jsm',
     'app/TelemetryStopwatch.jsm',
     'app/TelemetryStorage.jsm',
     'app/TelemetryTimestamps.jsm',
     'app/TelemetryUtils.jsm',
     'other/GCTelemetry.jsm',
+    'other/MemoryTelemetry.jsm',
     'other/UITelemetry.jsm',
     'pings/EventPing.jsm',
     'pings/HealthPing.jsm',
     'pings/ModulesPing.jsm',
     'pings/TelemetrySession.jsm',
     'pings/UpdatePing.jsm',
 ]
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/other/MemoryTelemetry.jsm
@@ -0,0 +1,540 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/Log.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm", this);
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
+ChromeUtils.import("resource://gre/modules/DeferredTask.jsm", this);
+ChromeUtils.import("resource://gre/modules/Timer.jsm");
+ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this);
+ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  GCTelemetry: "resource://gre/modules/GCTelemetry.jsm",
+});
+
+const Utils = TelemetryUtils;
+
+const LOGGER_NAME = "Toolkit.Telemetry";
+const LOGGER_PREFIX = "MemoryTelemetry" + (Utils.isContentProcess ? "#content::" : "::");
+
+const MESSAGE_TELEMETRY_USS = "Telemetry:USS";
+const MESSAGE_TELEMETRY_GET_CHILD_USS = "Telemetry:GetChildUSS";
+
+// Do not gather data more than once a minute (ms)
+const TELEMETRY_INTERVAL = 60 * 1000;
+// Delay before intializing telemetry (ms)
+const TELEMETRY_DELAY = Services.prefs.getIntPref("toolkit.telemetry.initDelay", 60) * 1000;
+// Delay before initializing telemetry if we're testing (ms)
+const TELEMETRY_TEST_DELAY = 1;
+
+const TOPIC_CYCLE_COLLECTOR_BEGIN = "cycle-collector-begin";
+
+// How long to wait in millis for all the child memory reports to come in
+const TOTAL_MEMORY_COLLECTOR_TIMEOUT = 200;
+
+var gLastMemoryPoll = null;
+
+XPCOMUtils.defineLazyServiceGetters(this, {
+  Telemetry: ["@mozilla.org/base/telemetry;1", "nsITelemetry"],
+});
+
+var EXPORTED_SYMBOLS = ["MemoryTelemetry"];
+
+var MemoryTelemetry = Object.freeze({
+  /**
+   * Pull values from about:memory into corresponding histograms
+   */
+  gatherMemory() {
+    return Impl.gatherMemory();
+  },
+
+  /**
+   * Triggers shutdown of the module.
+   */
+  shutdown() {
+    return Impl.shutdown();
+  },
+
+  /**
+   * Sets up components used in the content process.
+   */
+  setupContent(testing = false) {
+    return Impl.setupContent(testing);
+  },
+
+  /**
+   * Lightweight init function, called as soon as Firefox starts.
+   */
+  earlyInit(aTesting = false) {
+    return Impl.earlyInit(aTesting);
+  },
+
+  /**
+   * Does the "heavy" Telemetry initialization later on, so we
+   * don't impact startup performance.
+   * @return {Promise} Resolved when the initialization completes.
+   */
+  delayedInit() {
+    return Impl.delayedInit();
+  },
+});
+
+var Impl = {
+  _initialized: false,
+  _logger: null,
+  _prevValues: {},
+  // A task performing delayed initialization of the chrome process
+  _delayedInitTask: null,
+  // Need a timeout in case children are tardy in giving back their memory reports.
+  _totalMemoryTimeout: undefined,
+  _testing: false,
+  // An accumulator of total memory across all processes. Only valid once the final child reports.
+  _totalMemory: null,
+  // A Set of outstanding USS report ids
+  _childrenToHearFrom: null,
+  // monotonically-increasing id for USS reports
+  _nextTotalMemoryId: 1,
+  _USSFromChildProcesses: null,
+  // Keep track of the active observers
+  _observedTopics: new Set(),
+
+  addObserver(aTopic) {
+    Services.obs.addObserver(this, aTopic);
+    this._observedTopics.add(aTopic);
+  },
+
+  removeObserver(aTopic) {
+    Services.obs.removeObserver(this, aTopic);
+    this._observedTopics.delete(aTopic);
+  },
+
+  get _log() {
+    if (!this._logger) {
+      this._logger = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
+    }
+    return this._logger;
+  },
+
+  /**
+   * Pull values from about:memory into corresponding histograms
+   */
+  gatherMemory: function gatherMemory() {
+    let mgr;
+    try {
+      mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
+            getService(Ci.nsIMemoryReporterManager);
+    } catch (e) {
+      // OK to skip memory reporters in xpcshell
+      return;
+    }
+
+    let histogram = Telemetry.getHistogramById("TELEMETRY_MEMORY_REPORTER_MS");
+    let startTime = new Date();
+
+    // Get memory measurements from distinguished amount attributes.  We used
+    // to measure "explicit" too, but it could cause hangs, and the data was
+    // always really noisy anyway.  See bug 859657.
+    //
+    // test_TelemetrySession.js relies on some of these histograms being
+    // here.  If you remove any of the following histograms from here, you'll
+    // have to modify test_TelemetrySession.js:
+    //
+    //   * MEMORY_JS_GC_HEAP, and
+    //   * MEMORY_JS_COMPARTMENTS_SYSTEM.
+    //
+    // The distinguished amount attribute names don't match the telemetry id
+    // names in some cases due to a combination of (a) historical reasons, and
+    // (b) the fact that we can't change telemetry id names without breaking
+    // data continuity.
+    //
+    let boundHandleMemoryReport = this.handleMemoryReport.bind(this);
+    let h = (id, units, amountName) => {
+      try {
+        // If mgr[amountName] throws an exception, just move on -- some amounts
+        // aren't available on all platforms.  But if the attribute simply
+        // isn't present, that indicates the distinguished amounts have changed
+        // and this file hasn't been updated appropriately.
+        let amount = mgr[amountName];
+        if (amount === undefined) {
+          this._log.error("gatherMemory - telemetry accessed an unknown distinguished amount");
+        }
+        boundHandleMemoryReport(id, units, amount);
+      } catch (e) {
+      }
+    };
+    let b = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_BYTES, n);
+    let c = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_COUNT, n);
+    let cc = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE, n);
+    let p = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_PERCENTAGE, n);
+
+    // GHOST_WINDOWS is opt-out as of Firefox 55
+    c("GHOST_WINDOWS", "ghostWindows");
+
+    if (!Telemetry.canRecordExtended) {
+      return;
+    }
+
+    b("MEMORY_VSIZE", "vsize");
+    if (!Services.appinfo.is64Bit || AppConstants.platform !== "win") {
+      b("MEMORY_VSIZE_MAX_CONTIGUOUS", "vsizeMaxContiguous");
+    }
+    b("MEMORY_RESIDENT_FAST", "residentFast");
+    b("MEMORY_UNIQUE", "residentUnique");
+    p("MEMORY_HEAP_OVERHEAD_FRACTION", "heapOverheadFraction");
+    b("MEMORY_JS_GC_HEAP", "JSMainRuntimeGCHeap");
+    c("MEMORY_JS_COMPARTMENTS_SYSTEM", "JSMainRuntimeRealmsSystem");
+    c("MEMORY_JS_COMPARTMENTS_USER", "JSMainRuntimeRealmsUser");
+    b("MEMORY_IMAGES_CONTENT_USED_UNCOMPRESSED", "imagesContentUsedUncompressed");
+    b("MEMORY_STORAGE_SQLITE", "storageSQLite");
+    cc("LOW_MEMORY_EVENTS_VIRTUAL", "lowMemoryEventsVirtual");
+    cc("LOW_MEMORY_EVENTS_COMMIT_SPACE", "lowMemoryEventsCommitSpace");
+    cc("LOW_MEMORY_EVENTS_PHYSICAL", "lowMemoryEventsPhysical");
+    cc("PAGE_FAULTS_HARD", "pageFaultsHard");
+
+    try {
+      mgr.getHeapAllocatedAsync(heapAllocated => {
+        boundHandleMemoryReport("MEMORY_HEAP_ALLOCATED",
+                                Ci.nsIMemoryReporter.UNITS_BYTES,
+                                heapAllocated);
+      });
+    } catch (e) {
+    }
+
+    if (!Utils.isContentProcess && !this._totalMemoryTimeout) {
+      // Only the chrome process should gather total memory
+      // total = parent RSS + sum(child USS)
+      this._totalMemory = mgr.residentFast;
+      if (Services.ppmm.childCount > 1) {
+        // Do not report If we time out waiting for the children to call
+        this._totalMemoryTimeout = setTimeout(
+          () => {
+            this._totalMemoryTimeout = undefined;
+            this._childrenToHearFrom.clear();
+          },
+          TOTAL_MEMORY_COLLECTOR_TIMEOUT);
+        this._USSFromChildProcesses = [];
+        this._childrenToHearFrom = new Set();
+        for (let i = 1; i < Services.ppmm.childCount; i++) {
+          let child = Services.ppmm.getChildAt(i);
+          try {
+            child.sendAsyncMessage(MESSAGE_TELEMETRY_GET_CHILD_USS, {id: this._nextTotalMemoryId});
+            this._childrenToHearFrom.add(this._nextTotalMemoryId);
+            this._nextTotalMemoryId++;
+          } catch (ex) {
+            // If a content process has just crashed, then attempting to send it
+            // an async message will throw an exception.
+            Cu.reportError(ex);
+          }
+        }
+      } else {
+        boundHandleMemoryReport(
+          "MEMORY_TOTAL",
+          Ci.nsIMemoryReporter.UNITS_BYTES,
+          this._totalMemory);
+      }
+    }
+
+    histogram.add(new Date() - startTime);
+  },
+
+  handleMemoryReport(id, units, amount, key) {
+    let val;
+    if (units == Ci.nsIMemoryReporter.UNITS_BYTES) {
+      val = Math.floor(amount / 1024);
+    } else if (units == Ci.nsIMemoryReporter.UNITS_PERCENTAGE) {
+      // UNITS_PERCENTAGE amounts are 100x greater than their raw value.
+      val = Math.floor(amount / 100);
+    } else if (units == Ci.nsIMemoryReporter.UNITS_COUNT) {
+      val = amount;
+    } else if (units == Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE) {
+      // If the reporter gives us a cumulative count, we'll report the
+      // difference in its value between now and our previous ping.
+
+      if (!(id in this._prevValues)) {
+        // If this is the first time we're reading this reporter, store its
+        // current value but don't report it in the telemetry ping, so we
+        // ignore the effect startup had on the reporter.
+        this._prevValues[id] = amount;
+        return;
+      }
+
+      val = amount - this._prevValues[id];
+      this._prevValues[id] = amount;
+    } else {
+      this._log.error("handleMemoryReport - Can't handle memory reporter with units " + units);
+      return;
+    }
+
+    if (key) {
+      Telemetry.getKeyedHistogramById(id).add(key, val);
+      return;
+    }
+
+    Telemetry.getHistogramById(id).add(val);
+  },
+
+  attachObservers: function attachObservers() {
+    if (!this._initialized)
+      return;
+    if (Telemetry.canRecordExtended) {
+      this.addObserver(TOPIC_CYCLE_COLLECTOR_BEGIN);
+    }
+  },
+
+
+  /**
+   * Lightweight init function, called as soon as Firefox starts.
+   */
+  earlyInit(testing) {
+    this._log.trace("earlyInit");
+
+    this._initStarted = true;
+    this._testing = testing;
+
+    if (this._initialized && !testing) {
+      this._log.error("earlyInit - already initialized");
+      return;
+    }
+
+    if (!Telemetry.canRecordBase && !testing) {
+      this._log.config("earlyInit - Telemetry recording is disabled, skipping Chrome process setup.");
+      return;
+    }
+
+    Services.ppmm.addMessageListener(MESSAGE_TELEMETRY_USS, this);
+  },
+
+  /**
+   * Does the "heavy" Telemetry initialization later on, so we
+   * don't impact startup performance.
+   * @return {Promise} Resolved when the initialization completes.
+   */
+  delayedInit() {
+    this._log.trace("delayedInit");
+
+    this._delayedInitTask = (async () => {
+      try {
+        this._initialized = true;
+
+        this.attachObservers();
+        this.gatherMemory();
+
+        if (Telemetry.canRecordExtended) {
+          GCTelemetry.init();
+        }
+
+        this._delayedInitTask = null;
+      } catch (e) {
+        this._delayedInitTask = null;
+        throw e;
+      }
+    })();
+
+    return this._delayedInitTask;
+  },
+
+  /**
+   * Initializes telemetry for a content process.
+   */
+  setupContent: function setupContent(testing) {
+    this._log.trace("setupContent");
+    this._testing = testing;
+
+    if (!Telemetry.canRecordBase) {
+      this._log.trace("setupContent - base recording is disabled, not initializing");
+      return;
+    }
+
+    this.addObserver("content-child-shutdown");
+    Services.cpmm.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_USS, this);
+
+    let delayedTask = new DeferredTask(() => {
+      this._initialized = true;
+
+      this.attachObservers();
+      this.gatherMemory();
+
+      if (Telemetry.canRecordExtended) {
+        GCTelemetry.init();
+      }
+    }, testing ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY,
+    testing ? 0 : undefined);
+
+    delayedTask.arm();
+  },
+
+  getOpenTabsCount: function getOpenTabsCount() {
+    let tabCount = 0;
+
+    for (let win of Services.wm.getEnumerator("navigator:browser")) {
+      tabCount += win.gBrowser.tabs.length;
+    }
+
+    return tabCount;
+  },
+
+  receiveMessage: function receiveMessage(message) {
+    this._log.trace("receiveMessage - Message name " + message.name);
+    switch (message.name) {
+    case MESSAGE_TELEMETRY_USS:
+    {
+      // In parent process, receive the USS report from the child
+      if (this._totalMemoryTimeout && this._childrenToHearFrom.delete(message.data.id)) {
+        let uss = message.data.bytes;
+        this._totalMemory += uss;
+        this._USSFromChildProcesses.push(uss);
+        if (this._childrenToHearFrom.size == 0) {
+          clearTimeout(this._totalMemoryTimeout);
+          this._totalMemoryTimeout = undefined;
+          this.handleMemoryReport(
+            "MEMORY_TOTAL",
+            Ci.nsIMemoryReporter.UNITS_BYTES,
+            this._totalMemory);
+
+          let length = this._USSFromChildProcesses.length;
+          if (length > 1) {
+            // Mean of the USS of all the content processes.
+            let mean = this._USSFromChildProcesses.reduce((a, b) => a + b, 0) / length;
+            // Absolute error of USS for each content process, normalized by the mean (*100 to get it in percentage).
+            // 20% means for a content process that it is using 20% more or 20% less than the mean.
+            let diffs = this._USSFromChildProcesses.map(value => Math.floor(Math.abs(value - mean) * 100 / mean));
+            let tabsCount = this.getOpenTabsCount();
+            let key;
+            if (tabsCount < 11) {
+              key = "0 - 10 tabs";
+            } else if (tabsCount < 501) {
+              key = "11 - 500 tabs";
+            } else {
+              key = "more tabs";
+            }
+
+            diffs.forEach(value => {
+              this.handleMemoryReport(
+              "MEMORY_DISTRIBUTION_AMONG_CONTENT",
+              Ci.nsIMemoryReporter.UNITS_COUNT,
+              value,
+              key);
+            });
+
+            // This notification is for testing only.
+            Services.obs.notifyObservers(null, "gather-memory-telemetry-finished");
+          }
+          this._USSFromChildProcesses = undefined;
+        }
+      } else {
+        this._log.trace("Child USS report was missed");
+      }
+      break;
+    }
+    case MESSAGE_TELEMETRY_GET_CHILD_USS:
+    {
+      // In child process, send the requested USS report
+      this.sendContentProcessUSS(message.data.id);
+      break;
+    }
+    default:
+      throw new Error("Telemetry.receiveMessage: bad message name");
+    }
+  },
+
+  sendContentProcessUSS: function sendContentProcessUSS(aMessageId) {
+    this._log.trace("sendContentProcessUSS");
+
+    let mgr;
+    try {
+      mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
+            getService(Ci.nsIMemoryReporterManager);
+    } catch (e) {
+      // OK to skip memory reporters in xpcshell
+      return;
+    }
+
+    Services.cpmm.sendAsyncMessage(
+      MESSAGE_TELEMETRY_USS,
+      {bytes: mgr.residentUnique, id: aMessageId}
+    );
+  },
+
+  /**
+   * Do some shutdown work that is common to all process types.
+   */
+  uninstall() {
+    for (let topic of this._observedTopics) {
+      try {
+        // Tests may flip Telemetry.canRecordExtended on and off. It can be the case
+        // that the observer TOPIC_CYCLE_COLLECTOR_BEGIN was not added.
+        this.removeObserver(topic);
+      } catch (e) {
+        this._log.warn("uninstall - Failed to remove " + topic, e);
+      }
+    }
+
+    GCTelemetry.shutdown();
+  },
+
+  observe(aSubject, aTopic, aData) {
+    // Prevent the cycle collector begin topic from cluttering the log.
+    if (aTopic != TOPIC_CYCLE_COLLECTOR_BEGIN) {
+      this._log.trace("observe - " + aTopic + " notified.");
+    }
+
+    switch (aTopic) {
+    case "content-child-shutdown":
+      // content-child-shutdown is only registered for content processes.
+      this.uninstall();
+      Telemetry.flushBatchedChildTelemetry();
+      break;
+    case TOPIC_CYCLE_COLLECTOR_BEGIN:
+      let now = new Date();
+      if (!gLastMemoryPoll
+          || (TELEMETRY_INTERVAL <= now - gLastMemoryPoll)) {
+        gLastMemoryPoll = now;
+
+        this._log.trace("Dispatching idle gatherMemory task");
+        Services.tm.idleDispatchToMainThread(() => {
+          this._log.trace("Running idle gatherMemory task");
+          this.gatherMemory();
+          return true;
+        });
+      }
+      break;
+    }
+    return undefined;
+  },
+
+  shutdown() {
+    this._log.trace("shutdown");
+
+    let cleanup = () => {
+      this.uninstall();
+
+      this._initStarted = false;
+      this._initialized = false;
+    };
+
+    // We can be in one the following states here:
+    // 1) delayedInit was never called
+    // or it was called and
+    //   2) _delayedInitTask is running now.
+    //   3) _delayedInitTask finished running already.
+
+    // This handles 1).
+    if (!this._initStarted) {
+      return Promise.resolve();
+    }
+
+    // This handles 3).
+    if (!this._delayedInitTask) {
+      // We already ran the delayed initialization.
+      return cleanup();
+    }
+
+    // This handles 2).
+    return this._delayedInitTask.then(cleanup);
+  },
+};
--- a/toolkit/components/telemetry/pings/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/pings/TelemetrySession.jsm
@@ -3,25 +3,25 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Log.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
-ChromeUtils.import("resource://gre/modules/DeferredTask.jsm", this);
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
 ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this);
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm",
   TelemetryController: "resource://gre/modules/TelemetryController.jsm",
   TelemetryStorage: "resource://gre/modules/TelemetryStorage.jsm",
+  MemoryTelemetry: "resource://gre/modules/MemoryTelemetry.jsm",
   UITelemetry: "resource://gre/modules/UITelemetry.jsm",
   GCTelemetry: "resource://gre/modules/GCTelemetry.jsm",
   TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
   TelemetryReportingPolicy: "resource://gre/modules/TelemetryReportingPolicy.jsm",
 });
 
 const Utils = TelemetryUtils;
 
@@ -43,29 +43,20 @@ const REASON_SHUTDOWN = "shutdown";
 
 const ENVIRONMENT_CHANGE_LISTENER = "TelemetrySession::onEnvironmentChange";
 
 const MIN_SUBSESSION_LENGTH_MS = Services.prefs.getIntPref("toolkit.telemetry.minSubsessionLength", 5 * 60) * 1000;
 
 const LOGGER_NAME = "Toolkit.Telemetry";
 const LOGGER_PREFIX = "TelemetrySession" + (Utils.isContentProcess ? "#content::" : "::");
 
-const MESSAGE_TELEMETRY_USS = "Telemetry:USS";
-const MESSAGE_TELEMETRY_GET_CHILD_USS = "Telemetry:GetChildUSS";
-
 // Whether the FHR/Telemetry unification features are enabled.
 // Changing this pref requires a restart.
 const IS_UNIFIED_TELEMETRY = Services.prefs.getBoolPref(TelemetryUtils.Preferences.Unified, false);
 
-// Do not gather data more than once a minute (ms)
-const TELEMETRY_INTERVAL = 60 * 1000;
-// Delay before intializing telemetry (ms)
-const TELEMETRY_DELAY = Services.prefs.getIntPref("toolkit.telemetry.initDelay", 60) * 1000;
-// Delay before initializing telemetry if we're testing (ms)
-const TELEMETRY_TEST_DELAY = 1;
 // Execute a scheduler tick every 5 minutes.
 const SCHEDULER_TICK_INTERVAL_MS = Services.prefs.getIntPref("toolkit.telemetry.scheduler.tickInterval", 5 * 60) * 1000;
 // When user is idle, execute a scheduler tick every 60 minutes.
 const SCHEDULER_TICK_IDLE_INTERVAL_MS = Services.prefs.getIntPref("toolkit.telemetry.scheduler.idleTickInterval", 60 * 60) * 1000;
 
 // The tolerance we have when checking if it's midnight (15 minutes).
 const SCHEDULER_MIDNIGHT_TOLERANCE_MS = 15 * 60 * 1000;
 
@@ -77,26 +68,19 @@ const SCHEDULER_TICK_MAX_IDLE_DELAY_MS =
 // On idle-daily a gather-telemetry notification is fired, during it probes can
 // start asynchronous tasks to gather data.
 const IDLE_TIMEOUT_SECONDS = Services.prefs.getIntPref("toolkit.telemetry.idleTimeout", 5 * 60);
 
 // The frequency at which we persist session data to the disk to prevent data loss
 // in case of aborted sessions (currently 5 minutes).
 const ABORTED_SESSION_UPDATE_INTERVAL_MS = 5 * 60 * 1000;
 
-const TOPIC_CYCLE_COLLECTOR_BEGIN = "cycle-collector-begin";
-
-// How long to wait in millis for all the child memory reports to come in
-const TOTAL_MEMORY_COLLECTOR_TIMEOUT = 200;
-
 // Control whether Telemetry data should be encrypted with Prio.
 const PRIO_ENABLED_PREF = "prio.enabled";
 
-var gLastMemoryPoll = null;
-
 var gWasDebuggerAttached = false;
 
 XPCOMUtils.defineLazyServiceGetters(this, {
   Telemetry: ["@mozilla.org/base/telemetry;1", "nsITelemetry"],
   idleService: ["@mozilla.org/widget/idleservice;1", "nsIIdleService"],
 });
 
 function generateUUID() {
@@ -600,22 +584,16 @@ var TelemetrySession = Object.freeze({
   },
   /**
    * Triggers shutdown of the module.
    */
   shutdown() {
     return Impl.shutdownChromeProcess();
   },
   /**
-   * Sets up components used in the content process.
-   */
-  setupContent(testing = false) {
-    return Impl.setupContentProcess(testing);
-  },
-  /**
    * Used only for testing purposes.
    */
   testUninstall() {
     try {
       Impl.uninstall();
     } catch (ex) {
       // Ignore errors
     }
@@ -657,17 +635,16 @@ var TelemetrySession = Object.freeze({
   get newProfilePingSent() {
     return Impl._newProfilePingSent;
   },
 });
 
 var Impl = {
   _initialized: false,
   _logger: null,
-  _prevValues: {},
   _slowSQLStartup: {},
   // The activity state for the user. If false, don't count the next
   // active tick. Otherwise, increment the active ticks as usual.
   _isUserActive: true,
   _startupIO: {},
   // The previous build ID, if this is the first run with a new build.
   // Null if this is the first run, or the previous build ID is unknown.
   _previousBuildId: null,
@@ -691,26 +668,18 @@ var Impl = {
   // length measurements.
   _subsessionStartTimeMonotonic: 0,
   // The active ticks counted when the subsession starts
   _subsessionStartActiveTicks: 0,
   // Active ticks in the whole session.
   _sessionActiveTicks: 0,
   // A task performing delayed initialization of the chrome process
   _delayedInitTask: null,
-  // Need a timeout in case children are tardy in giving back their memory reports.
-  _totalMemoryTimeout: undefined,
   _testing: false,
   // An accumulator of total memory across all processes. Only valid once the final child reports.
-  _totalMemory: null,
-  // A Set of outstanding USS report ids
-  _childrenToHearFrom: null,
-  // monotonically-increasing id for USS reports
-  _nextTotalMemoryId: 1,
-  _USSFromChildProcesses: null,
   _lastEnvironmentChangeDate: 0,
   // We save whether the "new-profile" ping was sent yet, to
   // survive profile refresh and migrations.
   _newProfilePingSent: false,
   // Keep track of the active observers
   _observedTopics: new Set(),
 
   addObserver(aTopic) {
@@ -925,174 +894,16 @@ var Impl = {
     let flashVersion = this.getFlashVersion();
     if (flashVersion)
       ret.flashVersion = flashVersion;
 
     return ret;
   },
 
   /**
-   * Pull values from about:memory into corresponding histograms
-   */
-  gatherMemory: function gatherMemory() {
-    let mgr;
-    try {
-      mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
-            getService(Ci.nsIMemoryReporterManager);
-    } catch (e) {
-      // OK to skip memory reporters in xpcshell
-      return;
-    }
-
-    let histogram = Telemetry.getHistogramById("TELEMETRY_MEMORY_REPORTER_MS");
-    let startTime = new Date();
-
-    // Get memory measurements from distinguished amount attributes.  We used
-    // to measure "explicit" too, but it could cause hangs, and the data was
-    // always really noisy anyway.  See bug 859657.
-    //
-    // test_TelemetryController.js relies on some of these histograms being
-    // here.  If you remove any of the following histograms from here, you'll
-    // have to modify test_TelemetryController.js:
-    //
-    //   * MEMORY_JS_GC_HEAP, and
-    //   * MEMORY_JS_COMPARTMENTS_SYSTEM.
-    //
-    // The distinguished amount attribute names don't match the telemetry id
-    // names in some cases due to a combination of (a) historical reasons, and
-    // (b) the fact that we can't change telemetry id names without breaking
-    // data continuity.
-    //
-    let boundHandleMemoryReport = this.handleMemoryReport.bind(this);
-    let h = (id, units, amountName) => {
-      try {
-        // If mgr[amountName] throws an exception, just move on -- some amounts
-        // aren't available on all platforms.  But if the attribute simply
-        // isn't present, that indicates the distinguished amounts have changed
-        // and this file hasn't been updated appropriately.
-        let amount = mgr[amountName];
-        if (amount === undefined) {
-          this._log.error("gatherMemory - telemetry accessed an unknown distinguished amount");
-        }
-        boundHandleMemoryReport(id, units, amount);
-      } catch (e) {
-      }
-    };
-    let b = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_BYTES, n);
-    let c = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_COUNT, n);
-    let cc = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE, n);
-    let p = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_PERCENTAGE, n);
-
-    // GHOST_WINDOWS is opt-out as of Firefox 55
-    c("GHOST_WINDOWS", "ghostWindows");
-
-    if (!Telemetry.canRecordExtended) {
-      return;
-    }
-
-    b("MEMORY_VSIZE", "vsize");
-    if (!Services.appinfo.is64Bit || AppConstants.platform !== "win") {
-      b("MEMORY_VSIZE_MAX_CONTIGUOUS", "vsizeMaxContiguous");
-    }
-    b("MEMORY_RESIDENT_FAST", "residentFast");
-    b("MEMORY_UNIQUE", "residentUnique");
-    p("MEMORY_HEAP_OVERHEAD_FRACTION", "heapOverheadFraction");
-    b("MEMORY_JS_GC_HEAP", "JSMainRuntimeGCHeap");
-    c("MEMORY_JS_COMPARTMENTS_SYSTEM", "JSMainRuntimeRealmsSystem");
-    c("MEMORY_JS_COMPARTMENTS_USER", "JSMainRuntimeRealmsUser");
-    b("MEMORY_IMAGES_CONTENT_USED_UNCOMPRESSED", "imagesContentUsedUncompressed");
-    b("MEMORY_STORAGE_SQLITE", "storageSQLite");
-    cc("LOW_MEMORY_EVENTS_VIRTUAL", "lowMemoryEventsVirtual");
-    cc("LOW_MEMORY_EVENTS_COMMIT_SPACE", "lowMemoryEventsCommitSpace");
-    cc("LOW_MEMORY_EVENTS_PHYSICAL", "lowMemoryEventsPhysical");
-    cc("PAGE_FAULTS_HARD", "pageFaultsHard");
-
-    try {
-      mgr.getHeapAllocatedAsync(heapAllocated => {
-        boundHandleMemoryReport("MEMORY_HEAP_ALLOCATED",
-                                Ci.nsIMemoryReporter.UNITS_BYTES,
-                                heapAllocated);
-      });
-    } catch (e) {
-    }
-
-    if (!Utils.isContentProcess && !this._totalMemoryTimeout) {
-      // Only the chrome process should gather total memory
-      // total = parent RSS + sum(child USS)
-      this._totalMemory = mgr.residentFast;
-      if (Services.ppmm.childCount > 1) {
-        // Do not report If we time out waiting for the children to call
-        this._totalMemoryTimeout = setTimeout(
-          () => {
-            this._totalMemoryTimeout = undefined;
-            this._childrenToHearFrom.clear();
-          },
-          TOTAL_MEMORY_COLLECTOR_TIMEOUT);
-        this._USSFromChildProcesses = [];
-        this._childrenToHearFrom = new Set();
-        for (let i = 1; i < Services.ppmm.childCount; i++) {
-          let child = Services.ppmm.getChildAt(i);
-          try {
-            child.sendAsyncMessage(MESSAGE_TELEMETRY_GET_CHILD_USS, {id: this._nextTotalMemoryId});
-            this._childrenToHearFrom.add(this._nextTotalMemoryId);
-            this._nextTotalMemoryId++;
-          } catch (ex) {
-            // If a content process has just crashed, then attempting to send it
-            // an async message will throw an exception.
-            Cu.reportError(ex);
-          }
-        }
-      } else {
-        boundHandleMemoryReport(
-          "MEMORY_TOTAL",
-          Ci.nsIMemoryReporter.UNITS_BYTES,
-          this._totalMemory);
-      }
-    }
-
-    histogram.add(new Date() - startTime);
-  },
-
-  handleMemoryReport(id, units, amount, key) {
-    let val;
-    if (units == Ci.nsIMemoryReporter.UNITS_BYTES) {
-      val = Math.floor(amount / 1024);
-    } else if (units == Ci.nsIMemoryReporter.UNITS_PERCENTAGE) {
-      // UNITS_PERCENTAGE amounts are 100x greater than their raw value.
-      val = Math.floor(amount / 100);
-    } else if (units == Ci.nsIMemoryReporter.UNITS_COUNT) {
-      val = amount;
-    } else if (units == Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE) {
-      // If the reporter gives us a cumulative count, we'll report the
-      // difference in its value between now and our previous ping.
-
-      if (!(id in this._prevValues)) {
-        // If this is the first time we're reading this reporter, store its
-        // current value but don't report it in the telemetry ping, so we
-        // ignore the effect startup had on the reporter.
-        this._prevValues[id] = amount;
-        return;
-      }
-
-      val = amount - this._prevValues[id];
-      this._prevValues[id] = amount;
-    } else {
-      this._log.error("handleMemoryReport - Can't handle memory reporter with units " + units);
-      return;
-    }
-
-    if (key) {
-      Telemetry.getKeyedHistogramById(id).add(key, val);
-      return;
-    }
-
-    Telemetry.getHistogramById(id).add(val);
-  },
-
-  /**
    * Get the current session's payload using the provided
    * simpleMeasurements and info, which are typically obtained by a call
    * to |this.getSimpleMeasurements| and |this.getMetadata|,
    * respectively.
    */
   assemblePayloadWithMeasurements(simpleMeasurements, info, reason, clearSubsession) {
     const isSubsession = IS_UNIFIED_TELEMETRY && !this._isClassicReason(reason);
     clearSubsession = IS_UNIFIED_TELEMETRY && clearSubsession;
@@ -1260,17 +1071,17 @@ var Impl = {
   },
 
   /**
    * Send data to the server. Record success/send-time in histograms
    */
   send: function send(reason) {
     this._log.trace("send - Reason " + reason);
     // populate histograms one last time
-    this.gatherMemory();
+    MemoryTelemetry.gatherMemory();
 
     const isSubsession = !this._isClassicReason(reason);
     let payload = this.getSessionPayload(reason, isSubsession);
     let options = {
       addClientId: true,
       addEnvironment: true,
     };
     return TelemetryController.submitExternalPing(getPingType(payload), payload, options);
@@ -1287,26 +1098,16 @@ var Impl = {
     }
     this.addObserver("xul-window-visible");
 
     // Attach the active-ticks related observers.
     this.addObserver("user-interaction-active");
     this.addObserver("user-interaction-inactive");
   },
 
-  attachObservers: function attachObservers() {
-    if (!this._initialized)
-      return;
-    this.addObserver("idle-daily");
-    if (Telemetry.canRecordExtended) {
-      this.addObserver(TOPIC_CYCLE_COLLECTOR_BEGIN);
-    }
-  },
-
-
   /**
    * Lightweight init function, called as soon as Firefox starts.
    */
   earlyInit(testing) {
     this._log.trace("earlyInit");
 
     this._initStarted = true;
     this._testing = testing;
@@ -1337,18 +1138,16 @@ var Impl = {
     let thisBuildID = Services.appinfo.appBuildID;
     // If there is no previousBuildId preference, we send null to the server.
     if (previousBuildId != thisBuildID) {
       this._previousBuildId = previousBuildId;
       Services.prefs.setStringPref(TelemetryUtils.Preferences.PreviousBuildID, thisBuildID);
     }
 
     this.attachEarlyObservers();
-
-    Services.ppmm.addMessageListener(MESSAGE_TELEMETRY_USS, this);
   },
 
   /**
    * Does the "heavy" Telemetry initialization later on, so we
    * don't impact startup performance.
    * @return {Promise} Resolved when the initialization completes.
    */
   delayedInit() {
@@ -1358,22 +1157,18 @@ var Impl = {
       try {
         this._initialized = true;
 
         await this._loadSessionData();
         // Update the session data to keep track of new subsessions created before
         // the initialization.
         await TelemetryStorage.saveSessionData(this._getSessionDataObject());
 
-        this.attachObservers();
-        this.gatherMemory();
-
-        if (Telemetry.canRecordExtended) {
-          GCTelemetry.init();
-        }
+        this.addObserver("idle-daily");
+        MemoryTelemetry.gatherMemory();
 
         Telemetry.asyncFetchTelemetryData(function() {});
 
         if (IS_UNIFIED_TELEMETRY) {
           // Check for a previously written aborted session ping.
           await TelemetryController.checkAbortedSessionPing();
 
           // Write the first aborted-session ping as early as possible. Just do that
@@ -1399,156 +1194,34 @@ var Impl = {
         this._delayedInitTask = null;
         throw e;
       }
     })();
 
     return this._delayedInitTask;
   },
 
-  getOpenTabsCount: function getOpenTabsCount() {
-    let tabCount = 0;
-
-    for (let win of Services.wm.getEnumerator("navigator:browser")) {
-      tabCount += win.gBrowser.tabs.length;
-    }
-
-    return tabCount;
-  },
-
-  /**
-   * Initializes telemetry for a content process.
-   */
-  setupContentProcess: function setupContentProcess(testing) {
-    this._log.trace("setupContentProcess");
-    this._testing = testing;
-
-    if (!Telemetry.canRecordBase) {
-      this._log.trace("setupContentProcess - base recording is disabled, not initializing");
-      return;
-    }
-
-    this.addObserver("content-child-shutdown");
-    Services.cpmm.addMessageListener(MESSAGE_TELEMETRY_GET_CHILD_USS, this);
-
-    let delayedTask = new DeferredTask(() => {
-      this._initialized = true;
-
-      this.attachObservers();
-      this.gatherMemory();
-
-      if (Telemetry.canRecordExtended) {
-        GCTelemetry.init();
-      }
-    }, testing ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY,
-    testing ? 0 : undefined);
-
-    delayedTask.arm();
-  },
-
   getFlashVersion: function getFlashVersion() {
     let host = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
     let tags = host.getPluginTags();
 
     for (let i = 0; i < tags.length; i++) {
       if (tags[i].name == "Shockwave Flash")
         return tags[i].version;
     }
 
     return null;
   },
 
-  receiveMessage: function receiveMessage(message) {
-    this._log.trace("receiveMessage - Message name " + message.name);
-    switch (message.name) {
-    case MESSAGE_TELEMETRY_USS:
-    {
-      // In parent process, receive the USS report from the child
-      if (this._totalMemoryTimeout && this._childrenToHearFrom.delete(message.data.id)) {
-        let uss = message.data.bytes;
-        this._totalMemory += uss;
-        this._USSFromChildProcesses.push(uss);
-        if (this._childrenToHearFrom.size == 0) {
-          clearTimeout(this._totalMemoryTimeout);
-          this._totalMemoryTimeout = undefined;
-          this.handleMemoryReport(
-            "MEMORY_TOTAL",
-            Ci.nsIMemoryReporter.UNITS_BYTES,
-            this._totalMemory);
-
-          let length = this._USSFromChildProcesses.length;
-          if (length > 1) {
-            // Mean of the USS of all the content processes.
-            let mean = this._USSFromChildProcesses.reduce((a, b) => a + b, 0) / length;
-            // Absolute error of USS for each content process, normalized by the mean (*100 to get it in percentage).
-            // 20% means for a content process that it is using 20% more or 20% less than the mean.
-            let diffs = this._USSFromChildProcesses.map(value => Math.floor(Math.abs(value - mean) * 100 / mean));
-            let tabsCount = this.getOpenTabsCount();
-            let key;
-            if (tabsCount < 11) {
-              key = "0 - 10 tabs";
-            } else if (tabsCount < 501) {
-              key = "11 - 500 tabs";
-            } else {
-              key = "more tabs";
-            }
-
-            diffs.forEach(value => {
-              this.handleMemoryReport(
-              "MEMORY_DISTRIBUTION_AMONG_CONTENT",
-              Ci.nsIMemoryReporter.UNITS_COUNT,
-              value,
-              key);
-            });
-
-            // This notification is for testing only.
-            Services.obs.notifyObservers(null, "gather-memory-telemetry-finished");
-          }
-          this._USSFromChildProcesses = undefined;
-        }
-      } else {
-        this._log.trace("Child USS report was missed");
-      }
-      break;
-    }
-    case MESSAGE_TELEMETRY_GET_CHILD_USS:
-    {
-      // In child process, send the requested USS report
-      this.sendContentProcessUSS(message.data.id);
-      break;
-    }
-    default:
-      throw new Error("Telemetry.receiveMessage: bad message name");
-    }
-  },
-
-  sendContentProcessUSS: function sendContentProcessUSS(aMessageId) {
-    this._log.trace("sendContentProcessUSS");
-
-    let mgr;
-    try {
-      mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
-            getService(Ci.nsIMemoryReporterManager);
-    } catch (e) {
-      // OK to skip memory reporters in xpcshell
-      return;
-    }
-
-    Services.cpmm.sendAsyncMessage(
-      MESSAGE_TELEMETRY_USS,
-      {bytes: mgr.residentUnique, id: aMessageId}
-    );
-  },
-
-   /**
-    * On Desktop: Save the "shutdown" ping to disk.
-    * On Android: Save the "saved-session" ping to disk.
-    * This needs to be called after TelemetrySend shuts down otherwise pings
-    * would be sent instead of getting persisted to disk.
-    */
+  /**
+   * On Desktop: Save the "shutdown" ping to disk.
+   * On Android: Save the "saved-session" ping to disk.
+   * This needs to be called after TelemetrySend shuts down otherwise pings
+   * would be sent instead of getting persisted to disk.
+   */
   saveShutdownPings() {
     this._log.trace("saveShutdownPings");
 
     // We append the promises to this list and wait
     // on all pings to be saved after kicking off their collection.
     let p = [];
 
     if (IS_UNIFIED_TELEMETRY) {
@@ -1621,29 +1294,27 @@ var Impl = {
       try {
         // Tests may flip Telemetry.canRecordExtended on and off. It can be the case
         // that the observer TOPIC_CYCLE_COLLECTOR_BEGIN was not added.
         this.removeObserver(topic);
       } catch (e) {
         this._log.warn("uninstall - Failed to remove " + topic, e);
       }
     }
-
-    GCTelemetry.shutdown();
   },
 
   getPayload: function getPayload(reason, clearSubsession) {
     this._log.trace("getPayload - clearSubsession: " + clearSubsession);
     reason = reason || REASON_GATHER_PAYLOAD;
     // This function returns the current Telemetry payload to the caller.
     // We only gather startup info once.
     if (Object.keys(this._slowSQLStartup).length == 0) {
       this._slowSQLStartup = Telemetry.slowSQL;
     }
-    this.gatherMemory();
+    MemoryTelemetry.gatherMemory();
     return this.getSessionPayload(reason, clearSubsession);
   },
 
   gatherStartup: function gatherStartup() {
     this._log.trace("gatherStartup");
     let counters = processInfo.getCounters();
     if (counters) {
       [this._startupIO.startupSessionRestoreReadBytes,
@@ -1674,41 +1345,19 @@ var Impl = {
       Telemetry.scalarAdd("browser.engagement.active_ticks", 1);
     }
   },
 
   /**
    * This observer drives telemetry.
    */
   observe(aSubject, aTopic, aData) {
-    // Prevent the cycle collector begin topic from cluttering the log.
-    if (aTopic != TOPIC_CYCLE_COLLECTOR_BEGIN) {
-      this._log.trace("observe - " + aTopic + " notified.");
-    }
+    this._log.trace("observe - " + aTopic + " notified.");
 
     switch (aTopic) {
-    case "content-child-shutdown":
-      // content-child-shutdown is only registered for content processes.
-      this.uninstall();
-      Telemetry.flushBatchedChildTelemetry();
-      break;
-    case TOPIC_CYCLE_COLLECTOR_BEGIN:
-      let now = new Date();
-      if (!gLastMemoryPoll
-          || (TELEMETRY_INTERVAL <= now - gLastMemoryPoll)) {
-        gLastMemoryPoll = now;
-
-        this._log.trace("Dispatching idle gatherMemory task");
-        Services.tm.idleDispatchToMainThread(() => {
-          this._log.trace("Running idle gatherMemory task");
-          this.gatherMemory();
-          return true;
-        });
-      }
-      break;
     case "xul-window-visible":
       this.removeObserver("xul-window-visible");
       var counters = processInfo.getCounters();
       if (counters) {
         [this._startupIO.startupWindowVisibleReadBytes,
           this._startupIO.startupWindowVisibleWriteBytes] = counters;
       }
       break;
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -356,17 +356,17 @@ function checkPayload(payload, reason, s
     Assert.equal(uneval(tc), uneval(expected_tc));
   }
 
   // The ping should include data from memory reporters.  We can't check that
   // this data is correct, because we can't control the values returned by the
   // memory reporters.  But we can at least check that the data is there.
   //
   // It's important to check for the presence of reporters with a mix of units,
-  // because TelemetryController has separate logic for each one.  But we can't
+  // because MemoryTelemetry has separate logic for each one.  But we can't
   // currently check UNITS_COUNT_CUMULATIVE or UNITS_PERCENTAGE because
   // Telemetry doesn't touch a memory reporter with these units that's
   // available on all platforms.
 
   Assert.ok("MEMORY_JS_GC_HEAP" in payload.histograms); // UNITS_BYTES
   Assert.ok("MEMORY_JS_COMPARTMENTS_SYSTEM" in payload.histograms); // UNITS_COUNT
 
   Assert.ok(("mainThread" in payload.slowSQL) &&