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 489635 801c33dcde58caa5a8be251eb1b0f9597cd26777
parent 489634 bb057b527690f626ad617828044c96cd113d4122
child 489636 f1a8f5f2079797d054575d5b8877a5e4654b7650
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewerschutten
bugs1481812
milestone64.0a1
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) &&