Bug 1196047 - Move most of shared/performance to client or server. r=jsantell
authorJ. Ryan Stinnett <jryans@gmail.com>
Wed, 07 Oct 2015 16:02:47 -0500
changeset 300068 4d1f1902c41061590e7786e035eecd42835c0b67
parent 300067 9003dfe561498e6da0910e26232697412aba990f
child 300069 c53c4223a7a0189659caddbef3b91579d73065f9
push id6276
push usernalexander@mozilla.com
push dateSat, 10 Oct 2015 21:29:21 +0000
reviewersjsantell
bugs1196047
milestone44.0a1
Bug 1196047 - Move most of shared/performance to client or server. r=jsantell
devtools/client/performance/legacy/actors.js
devtools/client/performance/legacy/compatibility.js
devtools/client/performance/legacy/front.js
devtools/client/performance/legacy/moz.build
devtools/client/performance/legacy/recording.js
devtools/client/performance/modules/io.js
devtools/client/performance/modules/moz.build
devtools/client/performance/moz.build
devtools/client/performance/performance-controller.js
devtools/client/performance/test/browser.ini
devtools/client/performance/test/browser_perf-legacy-front-07.js
devtools/client/performance/test/browser_perf-legacy-front-08.js
devtools/client/performance/test/browser_perf-legacy-front-09.js
devtools/client/performance/test/head.js
devtools/client/performance/test/unit/head.js
devtools/client/performance/test/unit/test_perf-utils-allocations-to-samples.js
devtools/client/performance/test/unit/test_tree-model-allocations-01.js
devtools/client/performance/test/unit/test_tree-model-allocations-02.js
devtools/client/shared/test/head.js
devtools/server/actors/performance-recording.js
devtools/server/actors/performance.js
devtools/server/moz.build
devtools/server/performance/moz.build
devtools/server/performance/recorder.js
devtools/server/tests/browser/browser.ini
devtools/server/tests/browser/browser_perf-legacy-front-01.js
devtools/server/tests/browser/browser_perf-legacy-front-02.js
devtools/server/tests/browser/browser_perf-legacy-front-03.js
devtools/shared/performance/io.js
devtools/shared/performance/legacy/actors.js
devtools/shared/performance/legacy/compatibility.js
devtools/shared/performance/legacy/front.js
devtools/shared/performance/legacy/moz.build
devtools/shared/performance/legacy/recording.js
devtools/shared/performance/moz.build
devtools/shared/performance/recorder.js
devtools/shared/performance/recording-utils.js
devtools/shared/performance/test/test_perf-utils-allocations-to-samples.js
devtools/shared/performance/utils.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/legacy/actors.js
@@ -0,0 +1,254 @@
+/* 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";
+
+const { Task } = require("resource://gre/modules/Task.jsm");
+
+loader.lazyRequireGetter(this, "promise");
+loader.lazyRequireGetter(this, "EventEmitter",
+  "devtools/shared/event-emitter");
+loader.lazyRequireGetter(this, "Poller",
+  "devtools/client/shared/poller", true);
+
+loader.lazyRequireGetter(this, "CompatUtils",
+  "devtools/client/performance/legacy/compatibility");
+loader.lazyRequireGetter(this, "RecordingUtils",
+  "devtools/shared/performance/recording-utils");
+loader.lazyRequireGetter(this, "TimelineFront",
+  "devtools/server/actors/timeline", true);
+loader.lazyRequireGetter(this, "ProfilerFront",
+  "devtools/server/actors/profiler", true);
+
+// how often do we check the status of the profiler's circular buffer
+const PROFILER_CHECK_TIMER = 5000; // ms
+
+const TIMELINE_ACTOR_METHODS = [
+  "start", "stop",
+];
+
+const PROFILER_ACTOR_METHODS = [
+  "startProfiler", "getStartOptions", "stopProfiler",
+  "registerEventNotifications", "unregisterEventNotifications"
+];
+
+/**
+ * Constructor for a facade around an underlying ProfilerFront.
+ */
+function LegacyProfilerFront (target) {
+  this._target = target;
+  this._onProfilerEvent = this._onProfilerEvent.bind(this);
+  this._checkProfilerStatus = this._checkProfilerStatus.bind(this);
+  this._PROFILER_CHECK_TIMER = this._target.TEST_MOCK_PROFILER_CHECK_TIMER || PROFILER_CHECK_TIMER;
+
+  EventEmitter.decorate(this);
+}
+
+LegacyProfilerFront.prototype = {
+  EVENTS: ["console-api-profiler", "profiler-stopped"],
+
+  // Connects to the targets underlying real ProfilerFront.
+  connect: Task.async(function*() {
+    let target = this._target;
+    this._front = new ProfilerFront(target.client, target.form);
+
+    // Fetch and store information about the SPS profiler and
+    // server profiler.
+    this.traits = {};
+    this.traits.filterable = target.getTrait("profilerDataFilterable");
+
+    // Directly register to event notifications when connected
+    // to hook into `console.profile|profileEnd` calls.
+    yield this.registerEventNotifications({ events: this.EVENTS });
+    target.client.addListener("eventNotification", this._onProfilerEvent);
+  }),
+
+  /**
+   * Unregisters events for the underlying profiler actor.
+   */
+  destroy: Task.async(function *() {
+    if (this._poller) {
+      yield this._poller.destroy();
+    }
+    yield this.unregisterEventNotifications({ events: this.EVENTS });
+    this._target.client.removeListener("eventNotification", this._onProfilerEvent);
+  }),
+
+  /**
+   * Starts the profiler actor, if necessary.
+   *
+   * @option {number?} bufferSize
+   * @option {number?} sampleFrequency
+   */
+  start: Task.async(function *(options={}) {
+    // Check for poller status even if the profiler is already active --
+    // profiler can be activated via `console.profile` or another source, like
+    // the Gecko Profiler.
+    if (!this._poller) {
+      this._poller = new Poller(this._checkProfilerStatus, this._PROFILER_CHECK_TIMER, false);
+    }
+    if (!this._poller.isPolling()) {
+      this._poller.on();
+    }
+
+    // Start the profiler only if it wasn't already active. The built-in
+    // nsIPerformance module will be kept recording, because it's the same instance
+    // for all targets and interacts with the whole platform, so we don't want
+    // to affect other clients by stopping (or restarting) it.
+    let { isActive, currentTime, position, generation, totalSize } = yield this.getStatus();
+
+    if (isActive) {
+      return { startTime: currentTime, position, generation, totalSize };
+    }
+
+    // Translate options from the recording model into profiler-specific
+    // options for the nsIProfiler
+    let profilerOptions = {
+      entries: options.bufferSize,
+      interval: options.sampleFrequency ? (1000 / (options.sampleFrequency * 1000)) : void 0
+    };
+
+    let startInfo = yield this.startProfiler(profilerOptions);
+    let startTime = 0;
+    if ('currentTime' in startInfo) {
+      startTime = startInfo.currentTime;
+    }
+
+    return { startTime, position, generation, totalSize };
+  }),
+
+  /**
+   * Indicates the end of a recording -- does not actually stop the profiler
+   * (stopProfiler does that), but notes that we no longer need to poll
+   * for buffer status.
+   */
+  stop: Task.async(function *() {
+    yield this._poller.off();
+  }),
+
+  /**
+   * Wrapper around `profiler.isActive()` to take profiler status data and emit.
+   */
+  getStatus: Task.async(function *() {
+    let data = yield (CompatUtils.callFrontMethod("isActive").call(this));
+    // If no data, the last poll for `isActive()` was wrapping up, and the target.client
+    // is now null, so we no longer have data, so just abort here.
+    if (!data) {
+      return;
+    }
+
+    // If TEST_PROFILER_FILTER_STATUS defined (via array of fields), filter
+    // out any field from isActive, used only in tests. Used to filter out
+    // buffer status fields to simulate older geckos.
+    if (this._target.TEST_PROFILER_FILTER_STATUS) {
+      data = Object.keys(data).reduce((acc, prop) => {
+        if (this._target.TEST_PROFILER_FILTER_STATUS.indexOf(prop) === -1) {
+          acc[prop] = data[prop];
+        }
+        return acc;
+      }, {});
+    }
+
+    this.emit("profiler-status", data);
+    return data;
+  }),
+
+  /**
+   * Returns profile data from now since `startTime`.
+   */
+  getProfile: Task.async(function *(options) {
+    let profilerData = yield (CompatUtils.callFrontMethod("getProfile").call(this, options));
+    // If the backend is not deduped, dedupe it ourselves, as rest of the code
+    // expects a deduped profile.
+    if (profilerData.profile.meta.version === 2) {
+      RecordingUtils.deflateProfile(profilerData.profile);
+    }
+
+    // If the backend does not support filtering by start and endtime on platform (< Fx40),
+    // do it on the client (much slower).
+    if (!this.traits.filterable) {
+      RecordingUtils.filterSamples(profilerData.profile, options.startTime || 0);
+    }
+
+    return profilerData;
+  }),
+
+  /**
+   * Invoked whenever a registered event was emitted by the profiler actor.
+   *
+   * @param object response
+   *        The data received from the backend.
+   */
+  _onProfilerEvent: function (_, { topic, subject, details }) {
+    if (topic === "console-api-profiler") {
+      if (subject.action === "profile") {
+        this.emit("console-profile-start", details);
+      } else if (subject.action === "profileEnd") {
+        this.emit("console-profile-stop", details);
+      }
+    } else if (topic === "profiler-stopped") {
+      this.emit("profiler-stopped");
+    }
+  },
+
+  _checkProfilerStatus: Task.async(function *() {
+    // Calling `getStatus()` will emit the "profiler-status" on its own
+    yield this.getStatus();
+  }),
+
+  toString: () => "[object LegacyProfilerFront]"
+};
+
+/**
+ * Constructor for a facade around an underlying TimelineFront.
+ */
+function LegacyTimelineFront (target) {
+  this._target = target;
+  EventEmitter.decorate(this);
+}
+
+LegacyTimelineFront.prototype = {
+  EVENTS: ["markers", "frames", "ticks"],
+
+  connect: Task.async(function*() {
+    let supported = yield CompatUtils.timelineActorSupported(this._target);
+    this._front = supported ?
+                  new TimelineFront(this._target.client, this._target.form) :
+                  new CompatUtils.MockTimelineFront();
+
+    this.IS_MOCK = !supported;
+
+    // Binds underlying actor events and consolidates them to a `timeline-data`
+    // exposed event.
+    this.EVENTS.forEach(type => {
+      let handler = this[`_on${type}`] = this._onTimelineData.bind(this, type);
+      this._front.on(type, handler);
+    });
+  }),
+
+  /**
+   * Override actor's destroy, so we can unregister listeners before
+   * destroying the underlying actor.
+   */
+  destroy: Task.async(function *() {
+    this.EVENTS.forEach(type => this._front.off(type, this[`_on${type}`]));
+    yield this._front.destroy();
+  }),
+
+  /**
+   * An aggregate of all events (markers, frames, ticks) and exposes
+   * to PerformanceActorsConnection as a single event.
+   */
+  _onTimelineData: function (type, ...data) {
+    this.emit("timeline-data", type, ...data);
+  },
+
+  toString: () => "[object LegacyTimelineFront]"
+};
+
+// Bind all the methods that directly proxy to the actor
+PROFILER_ACTOR_METHODS.forEach(m => LegacyProfilerFront.prototype[m] = CompatUtils.callFrontMethod(m));
+TIMELINE_ACTOR_METHODS.forEach(m => LegacyTimelineFront.prototype[m] = CompatUtils.callFrontMethod(m));
+
+exports.LegacyProfilerFront = LegacyProfilerFront;
+exports.LegacyTimelineFront = LegacyTimelineFront;
rename from devtools/shared/performance/legacy/compatibility.js
rename to devtools/client/performance/legacy/compatibility.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/legacy/front.js
@@ -0,0 +1,479 @@
+/* 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";
+
+const { Cu } = require("chrome");
+const { Task } = require("resource://gre/modules/Task.jsm");
+
+loader.lazyRequireGetter(this, "Services");
+loader.lazyRequireGetter(this, "promise");
+loader.lazyRequireGetter(this, "extend",
+  "sdk/util/object", true);
+
+loader.lazyRequireGetter(this, "Actors",
+  "devtools/client/performance/legacy/actors");
+loader.lazyRequireGetter(this, "LegacyPerformanceRecording",
+  "devtools/client/performance/legacy/recording", true);
+loader.lazyRequireGetter(this, "importRecording",
+  "devtools/client/performance/legacy/recording", true);
+loader.lazyRequireGetter(this, "normalizePerformanceFeatures",
+  "devtools/shared/performance/recording-utils", true);
+loader.lazyRequireGetter(this, "DevToolsUtils",
+  "devtools/shared/DevToolsUtils");
+loader.lazyRequireGetter(this, "getDeviceFront",
+  "devtools/server/actors/device", true);
+loader.lazyRequireGetter(this, "getSystemInfo",
+  "devtools/shared/system", true);
+loader.lazyRequireGetter(this, "events",
+  "sdk/event/core");
+loader.lazyRequireGetter(this, "EventTarget",
+  "sdk/event/target", true);
+loader.lazyRequireGetter(this, "Class",
+  "sdk/core/heritage", true);
+
+/**
+ * A connection to underlying actors (profiler, framerate, etc.)
+ * shared by all tools in a target.
+ */
+const LegacyPerformanceFront = Class({
+  extends: EventTarget,
+
+  LEGACY_FRONT: true,
+
+  traits: {
+    features: {
+      withMarkers: true,
+      withTicks: true,
+      withMemory: false,
+      withAllocations: false,
+      withJITOptimizations: false,
+    },
+  },
+
+  initialize: function (target) {
+    let { form, client } = target;
+    this._target = target;
+    this._form = form;
+    this._client = client;
+    this._pendingConsoleRecordings = [];
+    this._sitesPullTimeout = 0;
+    this._recordings = [];
+
+    this._pipeToFront = this._pipeToFront.bind(this);
+    this._onTimelineData = this._onTimelineData.bind(this);
+    this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this);
+    this._onConsoleProfileStop = this._onConsoleProfileStop.bind(this);
+    this._onProfilerStatus = this._onProfilerStatus.bind(this);
+    this._onProfilerUnexpectedlyStopped = this._onProfilerUnexpectedlyStopped.bind(this);
+  },
+
+  /**
+   * Initializes a connection to the profiler and other miscellaneous actors.
+   * If in the process of opening, or already open, nothing happens.
+   *
+   * @return object
+   *         A promise that is resolved once the connection is established.
+   */
+  connect: Task.async(function*() {
+    if (this._connecting) {
+      return this._connecting.promise;
+    }
+
+    // Create a promise that gets resolved upon connecting, so that
+    // other attempts to open the connection use the same resolution promise
+    this._connecting = promise.defer();
+
+    // Sets `this._profiler`, `this._timeline`.
+    // Only initialize the timeline fronts if the respective actors
+    // are available. Older Gecko versions don't have existing implementations,
+    // in which case all the methods we need can be easily mocked.
+    yield this._connectActors();
+    yield this._registerListeners();
+
+    this._connecting.resolve();
+  }),
+
+  /**
+   * Destroys this connection.
+   */
+  destroy: Task.async(function*() {
+    if (this._connecting) {
+      yield this._connecting.promise;
+    } else {
+      return;
+    }
+
+    yield this._unregisterListeners();
+    yield this._disconnectActors();
+
+    this._connecting = null;
+    this._profiler = null;
+    this._timeline = null;
+    this._client = null;
+    this._form = null;
+    this._target = this._target;
+  }),
+
+  /**
+   * Initializes fronts and connects to the underlying actors using the facades
+   * found in ./actors.js.
+   */
+  _connectActors: Task.async(function*() {
+    this._profiler = new Actors.LegacyProfilerFront(this._target);
+    this._timeline = new Actors.LegacyTimelineFront(this._target);
+
+    yield promise.all([
+      this._profiler.connect(),
+      this._timeline.connect()
+    ]);
+
+    // If mocked timeline, update the traits
+    this.traits.features.withMarkers = !this._timeline.IS_MOCK;
+    this.traits.features.withTicks = !this._timeline.IS_MOCK;
+  }),
+
+  /**
+   * Registers listeners on events from the underlying
+   * actors, so the connection can handle them.
+   */
+  _registerListeners: function () {
+    this._timeline.on("timeline-data", this._onTimelineData);
+    this._profiler.on("console-profile-start", this._onConsoleProfileStart);
+    this._profiler.on("console-profile-stop", this._onConsoleProfileStop);
+    this._profiler.on("profiler-stopped", this._onProfilerUnexpectedlyStopped);
+    this._profiler.on("profiler-status", this._onProfilerStatus);
+  },
+
+  /**
+   * Unregisters listeners on events on the underlying actors.
+   */
+  _unregisterListeners: function () {
+    this._timeline.off("timeline-data", this._onTimelineData);
+    this._profiler.off("console-profile-start", this._onConsoleProfileStart);
+    this._profiler.off("console-profile-stop", this._onConsoleProfileStop);
+    this._profiler.off("profiler-stopped", this._onProfilerUnexpectedlyStopped);
+    this._profiler.off("profiler-status", this._onProfilerStatus);
+  },
+
+  /**
+   * Closes the connections to non-profiler actors.
+   */
+  _disconnectActors: Task.async(function* () {
+    yield promise.all([
+      this._profiler.destroy(),
+      this._timeline.destroy(),
+    ]);
+  }),
+
+  /**
+   * Invoked whenever `console.profile` is called.
+   *
+   * @param string profileLabel
+   *        The provided string argument if available; undefined otherwise.
+   * @param number currentTime
+   *        The time (in milliseconds) when the call was made, relative to when
+   *        the nsIProfiler module was started.
+   */
+  _onConsoleProfileStart: Task.async(function *(_, { profileLabel, currentTime: startTime }) {
+    let recordings = this._recordings;
+
+    // Abort if a profile with this label already exists.
+    if (recordings.find(e => e.getLabel() === profileLabel)) {
+      return;
+    }
+
+    events.emit(this, "console-profile-start");
+
+    yield this.startRecording(extend({}, getLegacyPerformanceRecordingPrefs(), {
+      console: true,
+      label: profileLabel
+    }));
+  }),
+
+  /**
+   * Invoked whenever `console.profileEnd` is called.
+   *
+   * @param string profileLabel
+   *        The provided string argument if available; undefined otherwise.
+   * @param number currentTime
+   *        The time (in milliseconds) when the call was made, relative to when
+   *        the nsIProfiler module was started.
+   */
+  _onConsoleProfileStop: Task.async(function *(_, data) {
+    // If no data, abort; can occur if profiler isn't running and we get a surprise
+    // call to console.profileEnd()
+    if (!data) {
+      return;
+    }
+    let { profileLabel, currentTime: endTime } = data;
+
+    let pending = this._recordings.filter(r => r.isConsole() && r.isRecording());
+    if (pending.length === 0) {
+      return;
+    }
+
+    let model;
+    // Try to find the corresponding `console.profile` call if
+    // a label was used in profileEnd(). If no matches, abort.
+    if (profileLabel) {
+      model = pending.find(e => e.getLabel() === profileLabel);
+    }
+    // If no label supplied, pop off the most recent pending console recording
+    else {
+      model = pending[pending.length - 1];
+    }
+
+    // If `profileEnd()` was called with a label, and there are no matching
+    // sessions, abort.
+    if (!model) {
+      Cu.reportError("console.profileEnd() called with label that does not match a recording.");
+      return;
+    }
+
+    yield this.stopRecording(model);
+  }),
+
+ /**
+  * TODO handle bug 1144438
+  */
+  _onProfilerUnexpectedlyStopped: function () {
+    Cu.reportError("Profiler unexpectedly stopped.", arguments);
+  },
+
+  /**
+   * Called whenever there is timeline data of any of the following types:
+   * - markers
+   * - frames
+   * - ticks
+   *
+   * Populate our internal store of recordings for all currently recording sessions.
+   */
+  _onTimelineData: function (_, ...data) {
+    this._recordings.forEach(e => e._addTimelineData.apply(e, data));
+    events.emit(this, "timeline-data", ...data);
+  },
+
+  /**
+   * Called whenever the underlying profiler polls its current status.
+   */
+  _onProfilerStatus: function (_, data) {
+    // If no data emitted (whether from an older actor being destroyed
+    // from a previous test, or the server does not support it), just ignore.
+    if (!data || data.position === void 0) {
+      return;
+    }
+
+    this._currentBufferStatus = data;
+    events.emit(this, "profiler-status", data);
+  },
+
+  /**
+   * Begins a recording session
+   *
+   * @param object options
+   *        An options object to pass to the actors. Supported properties are
+   *        `withTicks`, `withMemory` and `withAllocations`, `probability`, and `maxLogLength`.
+   * @return object
+   *         A promise that is resolved once recording has started.
+   */
+  startRecording: Task.async(function*(options = {}) {
+    let model = new LegacyPerformanceRecording(normalizePerformanceFeatures(options, this.traits.features));
+
+    // All actors are started asynchronously over the remote debugging protocol.
+    // Get the corresponding start times from each one of them.
+    // The timeline actors are target-dependent, so start those as well,
+    // even though these are mocked in older Geckos (FF < 35)
+    let profilerStart = this._profiler.start(options);
+    let timelineStart = this._timeline.start(options);
+
+    let { startTime, position, generation, totalSize } = yield profilerStart;
+    let timelineStartTime = yield timelineStart;
+
+    let data = {
+      profilerStartTime: startTime, timelineStartTime,
+      generation, position, totalSize
+    };
+
+    // Signify to the model that the recording has started,
+    // populate with data and store the recording model here.
+    model._populate(data);
+    this._recordings.push(model);
+
+    events.emit(this, "recording-started", model);
+    return model;
+  }),
+
+  /**
+   * Manually ends the recording session for the corresponding LegacyPerformanceRecording.
+   *
+   * @param LegacyPerformanceRecording model
+   *        The corresponding LegacyPerformanceRecording that belongs to the recording session wished to stop.
+   * @return LegacyPerformanceRecording
+   *         Returns the same model, populated with the profiling data.
+   */
+  stopRecording: Task.async(function*(model) {
+    // If model isn't in the LegacyPerformanceFront internal store,
+    // then do nothing.
+    if (this._recordings.indexOf(model) === -1) {
+      return;
+    }
+
+    // Flag the recording as no longer recording, so that `model.isRecording()`
+    // is false. Do this before we fetch all the data, and then subsequently
+    // the recording can be considered "completed".
+    let endTime = Date.now();
+    model._onStoppingRecording(endTime);
+    events.emit(this, "recording-stopping", model);
+
+    // Currently there are two ways profiles stop recording. Either manually in the
+    // performance tool, or via console.profileEnd. Once a recording is done,
+    // we want to deliver the model to the performance tool (either as a return
+    // from the LegacyPerformanceFront or via `console-profile-stop` event) and then
+    // remove it from the internal store.
+    //
+    // In the case where a console.profile is generated via the console (so the tools are
+    // open), we initialize the Performance tool so it can listen to those events.
+    this._recordings.splice(this._recordings.indexOf(model), 1);
+
+    let config = model.getConfiguration();
+    let startTime = model._getProfilerStartTime();
+    let profilerData = yield this._profiler.getProfile({ startTime });
+    let timelineEndTime = Date.now();
+
+    // Only if there are no more sessions recording do we stop
+    // the underlying timeline actors. If we're still recording,
+    // juse use Date.now() for the timeline end times, as those
+    // are only used in tests.
+    if (!this.isRecording()) {
+      // This doesn't stop the profiler, just turns off polling for
+      // events, and also turns off events on timeline actors.
+      yield this._profiler.stop();
+      timelineEndTime = yield this._timeline.stop(config);
+    }
+
+    let systemDeferred = promise.defer();
+    this._client.listTabs(form => {
+      systemDeferred.resolve(getDeviceFront(this._client, form).getDescription());
+    });
+    let systemHost = yield systemDeferred.promise;
+    let systemClient = yield getSystemInfo();
+
+    // Set the results on the LegacyPerformanceRecording itself.
+    model._onStopRecording({
+      // Data available only at the end of a recording.
+      profile: profilerData.profile,
+
+      // End times for all the actors.
+      profilerEndTime: profilerData.currentTime,
+      timelineEndTime: timelineEndTime,
+      systemHost,
+      systemClient,
+    });
+
+    events.emit(this, "recording-stopped", model);
+    return model;
+  }),
+
+  /**
+   * Creates a recording object when given a nsILocalFile.
+   *
+   * @param {nsILocalFile} file
+   *        The file to import the data from.
+   * @return {Promise<LegacyPerformanceRecording>}
+   */
+  importRecording: function (file) {
+    return importRecording(file);
+  },
+
+  /**
+   * Checks all currently stored recording models and returns a boolean
+   * if there is a session currently being recorded.
+   *
+   * @return Boolean
+   */
+  isRecording: function () {
+    return this._recordings.some(recording => recording.isRecording());
+  },
+
+  /**
+   * Pass in a PerformanceRecording and get a normalized value from 0 to 1 of how much
+   * of this recording's lifetime remains without being overwritten.
+   *
+   * @param {PerformanceRecording} recording
+   * @return {number?}
+   */
+  getBufferUsageForRecording: function (recording) {
+    if (!recording.isRecording() || !this._currentBufferStatus) {
+      return null;
+    }
+    let { position: currentPosition, totalSize, generation: currentGeneration } = this._currentBufferStatus;
+    let { position: origPosition, generation: origGeneration } = recording.getStartingBufferStatus();
+
+    let normalizedCurrent = (totalSize * (currentGeneration - origGeneration)) + currentPosition;
+    let percent = (normalizedCurrent - origPosition) / totalSize;
+    return percent > 1 ? 1 : percent;
+  },
+
+  /**
+   * Returns the configurations set on underlying components, used in tests.
+   * Returns an object with `probability`, `maxLogLength` for allocations, and
+   * `entries` and `interval` for profiler.
+   *
+   * @return {object}
+   */
+  getConfiguration: Task.async(function *() {
+    let profilerConfig = yield this._request("profiler", "getStartOptions");
+    return profilerConfig;
+  }),
+
+  /**
+   * An event from an underlying actor that we just want
+   * to pipe to the front itself.
+   */
+  _pipeToFront: function (eventName, ...args) {
+    events.emit(this, eventName, ...args);
+  },
+
+  /**
+   * Helper method to interface with the underlying actors directly.
+   * Used only in tests.
+   */
+  _request: function (actorName, method, ...args) {
+    if (!DevToolsUtils.testing) {
+      throw new Error("LegacyPerformanceFront._request may only be used in tests.");
+    }
+    let actor = this[`_${actorName}`];
+    return actor[method].apply(actor, args);
+  },
+
+  /**
+   * Sets how often the "profiler-status" event should be emitted.
+   * Used in tests.
+   */
+  setProfilerStatusInterval: function (n) {
+    if (this._profiler._poller) {
+      this._profiler._poller._wait = n;
+    }
+    this._profiler._PROFILER_CHECK_TIMER = n;
+  },
+
+  toString: () => "[object LegacyPerformanceFront]"
+});
+
+/**
+ * Creates an object of configurations based off of preferences for a LegacyPerformanceRecording.
+ */
+function getLegacyPerformanceRecordingPrefs () {
+  return {
+    withMarkers: true,
+    withMemory: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
+    withTicks: Services.prefs.getBoolPref("devtools.performance.ui.enable-framerate"),
+    withAllocations: Services.prefs.getBoolPref("devtools.performance.ui.enable-allocations"),
+    withJITOptimizations: Services.prefs.getBoolPref("devtools.performance.ui.enable-jit-optimizations"),
+    allocationsSampleProbability: +Services.prefs.getCharPref("devtools.performance.memory.sample-probability"),
+    allocationsMaxLogLength: Services.prefs.getIntPref("devtools.performance.memory.max-log-length")
+  };
+}
+
+exports.LegacyPerformanceFront = LegacyPerformanceFront;
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/legacy/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+    'actors.js',
+    'compatibility.js',
+    'front.js',
+    'recording.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/legacy/recording.js
@@ -0,0 +1,172 @@
+/* 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";
+
+const { Cc, Ci, Cu, Cr } = require("chrome");
+const { Task } = require("resource://gre/modules/Task.jsm");
+
+loader.lazyRequireGetter(this, "PerformanceIO",
+  "devtools/client/performance/modules/io");
+loader.lazyRequireGetter(this, "RecordingUtils",
+  "devtools/shared/performance/recording-utils");
+loader.lazyRequireGetter(this, "PerformanceRecordingCommon",
+  "devtools/shared/performance/recording-common", true);
+loader.lazyRequireGetter(this, "merge", "sdk/util/object", true);
+
+/**
+ * Model for a wholistic profile, containing the duration, profiling data,
+ * frames data, timeline (marker, tick, memory) data, and methods to mark
+ * a recording as 'in progress' or 'finished'.
+ */
+const LegacyPerformanceRecording = function (options={}) {
+  this._label = options.label || "";
+  this._console = options.console || false;
+
+  this._configuration = {
+    withMarkers: options.withMarkers || false,
+    withTicks: options.withTicks || false,
+    withMemory: options.withMemory || false,
+    withAllocations: options.withAllocations || false,
+    withJITOptimizations: options.withJITOptimizations || false,
+    allocationsSampleProbability: options.allocationsSampleProbability || 0,
+    allocationsMaxLogLength: options.allocationsMaxLogLength || 0,
+    bufferSize: options.bufferSize || 0,
+    sampleFrequency: options.sampleFrequency || 1
+  };
+};
+
+LegacyPerformanceRecording.prototype = merge({
+  _profilerStartTime: 0,
+  _timelineStartTime: 0,
+  _memoryStartTime: 0,
+
+  /**
+   * Saves the current recording to a file.
+   *
+   * @param nsILocalFile file
+   *        The file to stream the data into.
+   */
+  exportRecording: Task.async(function *(file) {
+    let recordingData = this.getAllData();
+    yield PerformanceIO.saveRecordingToFile(recordingData, file);
+  }),
+
+  /**
+   * Sets up the instance with data from the PerformanceFront when
+   * starting a recording. Should only be called by PerformanceFront.
+   */
+  _populate: function (info) {
+    // Times must come from the actor in order to be self-consistent.
+    // However, we also want to update the view with the elapsed time
+    // even when the actor is not generating data. To do this we get
+    // the local time and use it to compute a reasonable elapsed time.
+    this._localStartTime = Date.now();
+
+    this._profilerStartTime = info.profilerStartTime;
+    this._timelineStartTime = info.timelineStartTime;
+    this._memoryStartTime = info.memoryStartTime;
+    this._startingBufferStatus = {
+      position: info.position,
+      totalSize: info.totalSize,
+      generation: info.generation
+    };
+
+    this._recording = true;
+
+    this._systemHost = {};
+    this._systemClient = {};
+    this._markers = [];
+    this._frames = [];
+    this._memory = [];
+    this._ticks = [];
+    this._allocations = { sites: [], timestamps: [], frames: [], sizes: [] };
+  },
+
+  /**
+   * Called when the signal was sent to the front to no longer record more
+   * data, and begin fetching the data. There's some delay during fetching,
+   * even though the recording is stopped, the model is not yet completed until
+   * all the data is fetched.
+   */
+  _onStoppingRecording: function (endTime) {
+    this._duration = endTime - this._localStartTime;
+    this._recording = false;
+  },
+
+  /**
+   * Sets results available from stopping a recording from PerformanceFront.
+   * Should only be called by PerformanceFront.
+   */
+  _onStopRecording: Task.async(function *({ profilerEndTime, profile, systemClient, systemHost }) {
+    // Update the duration with the accurate profilerEndTime, so we don't have
+    // samples outside of the approximate duration set in `_onStoppingRecording`.
+    this._duration = profilerEndTime - this._profilerStartTime;
+    this._profile = profile;
+    this._completed = true;
+
+    // We filter out all samples that fall out of current profile's range
+    // since the profiler is continuously running. Because of this, sample
+    // times are not guaranteed to have a zero epoch, so offset the
+    // timestamps.
+    RecordingUtils.offsetSampleTimes(this._profile, this._profilerStartTime);
+
+    // Markers need to be sorted ascending by time, to be properly displayed
+    // in a waterfall view.
+    this._markers = this._markers.sort((a, b) => (a.start > b.start));
+
+    this._systemHost = systemHost;
+    this._systemClient = systemClient;
+  }),
+
+  /**
+   * Gets the profile's start time.
+   * @return number
+   */
+  _getProfilerStartTime: function () {
+    return this._profilerStartTime;
+  },
+
+  /**
+   * Fired whenever the PerformanceFront emits markers, memory or ticks.
+   */
+  _addTimelineData: function (eventName, ...data) {
+    // If this model isn't currently recording,
+    // ignore the timeline data.
+    if (!this.isRecording()) {
+      return;
+    }
+
+    let config = this.getConfiguration();
+
+    switch (eventName) {
+      // Accumulate timeline markers into an array. Furthermore, the timestamps
+      // do not have a zero epoch, so offset all of them by the start time.
+      case "markers": {
+        if (!config.withMarkers) { break; }
+        let [markers] = data;
+        RecordingUtils.offsetMarkerTimes(markers, this._timelineStartTime);
+        RecordingUtils.pushAll(this._markers, markers);
+        break;
+      }
+      // Accumulate stack frames into an array.
+      case "frames": {
+        if (!config.withMarkers) { break; }
+        let [, frames] = data;
+        RecordingUtils.pushAll(this._frames, frames);
+        break;
+      }
+      // Save the accumulated refresh driver ticks.
+      case "ticks": {
+        if (!config.withTicks) { break; }
+        let [, timestamps] = data;
+        this._ticks = timestamps;
+        break;
+      }
+    }
+  },
+
+  toString: () => "[object LegacyPerformanceRecording]"
+}, PerformanceRecordingCommon);
+
+exports.LegacyPerformanceRecording = LegacyPerformanceRecording;
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/modules/io.js
@@ -0,0 +1,166 @@
+/* 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";
+
+const { Cc, Ci, Cu, Cr } = require("chrome");
+
+loader.lazyRequireGetter(this, "Services");
+loader.lazyRequireGetter(this, "promise");
+loader.lazyRequireGetter(this, "RecordingUtils",
+  "devtools/shared/performance/recording-utils");
+
+loader.lazyImporter(this, "FileUtils",
+  "resource://gre/modules/FileUtils.jsm");
+loader.lazyImporter(this, "NetUtil",
+  "resource://gre/modules/NetUtil.jsm");
+
+// This identifier string is used to tentatively ascertain whether or not
+// a JSON loaded from disk is actually something generated by this tool.
+// It isn't, of course, a definitive verification, but a Good Enoughâ„¢
+// approximation before continuing the import. Don't localize this.
+const PERF_TOOL_SERIALIZER_IDENTIFIER = "Recorded Performance Data";
+const PERF_TOOL_SERIALIZER_LEGACY_VERSION = 1;
+const PERF_TOOL_SERIALIZER_CURRENT_VERSION = 2;
+
+/**
+ * Helpers for importing/exporting JSON.
+ */
+
+/**
+ * Gets a nsIScriptableUnicodeConverter instance with a default UTF-8 charset.
+ * @return object
+ */
+function getUnicodeConverter () {
+  let className = "@mozilla.org/intl/scriptableunicodeconverter";
+  let converter = Cc[className].createInstance(Ci.nsIScriptableUnicodeConverter);
+  converter.charset = "UTF-8";
+  return converter;
+}
+
+/**
+ * Saves a recording as JSON to a file. The provided data is assumed to be
+ * acyclical, so that it can be properly serialized.
+ *
+ * @param object recordingData
+ *        The recording data to stream as JSON.
+ * @param nsILocalFile file
+ *        The file to stream the data into.
+ * @return object
+ *         A promise that is resolved once streaming finishes, or rejected
+ *         if there was an error.
+ */
+function saveRecordingToFile (recordingData, file) {
+  let deferred = promise.defer();
+
+  recordingData.fileType = PERF_TOOL_SERIALIZER_IDENTIFIER;
+  recordingData.version = PERF_TOOL_SERIALIZER_CURRENT_VERSION;
+
+  let string = JSON.stringify(recordingData);
+  let inputStream = this.getUnicodeConverter().convertToInputStream(string);
+  let outputStream = FileUtils.openSafeFileOutputStream(file);
+
+  NetUtil.asyncCopy(inputStream, outputStream, deferred.resolve);
+  return deferred.promise;
+}
+
+/**
+ * Loads a recording stored as JSON from a file.
+ *
+ * @param nsILocalFile file
+ *        The file to import the data from.
+ * @return object
+ *         A promise that is resolved once importing finishes, or rejected
+ *         if there was an error.
+ */
+function loadRecordingFromFile (file) {
+  let deferred = promise.defer();
+
+  let channel = NetUtil.newChannel({
+    uri: NetUtil.newURI(file),
+    loadUsingSystemPrincipal: true});
+
+  channel.contentType = "text/plain";
+
+  NetUtil.asyncFetch(channel, (inputStream, status) => {
+    try {
+      let string = NetUtil.readInputStreamToString(inputStream, inputStream.available());
+      var recordingData = JSON.parse(string);
+    } catch (e) {
+      deferred.reject(new Error("Could not read recording data file."));
+      return;
+    }
+    if (recordingData.fileType != PERF_TOOL_SERIALIZER_IDENTIFIER) {
+      deferred.reject(new Error("Unrecognized recording data file."));
+      return;
+    }
+    if (!isValidSerializerVersion(recordingData.version)) {
+      deferred.reject(new Error("Unsupported recording data file version."));
+      return;
+    }
+    if (recordingData.version === PERF_TOOL_SERIALIZER_LEGACY_VERSION) {
+      recordingData = convertLegacyData(recordingData);
+    }
+    if (recordingData.profile.meta.version === 2) {
+      RecordingUtils.deflateProfile(recordingData.profile);
+    }
+    deferred.resolve(recordingData);
+  });
+
+  return deferred.promise;
+}
+
+/**
+ * Returns a boolean indicating whether or not the passed in `version`
+ * is supported by this serializer.
+ *
+ * @param number version
+ * @return boolean
+ */
+function isValidSerializerVersion (version) {
+  return !!~[
+    PERF_TOOL_SERIALIZER_LEGACY_VERSION,
+    PERF_TOOL_SERIALIZER_CURRENT_VERSION
+  ].indexOf(version);
+}
+
+/**
+ * Takes recording data (with version `1`, from the original profiler tool), and
+ * massages the data to be line with the current performance tool's property names
+ * and values.
+ *
+ * @param object legacyData
+ * @return object
+ */
+function convertLegacyData (legacyData) {
+  let { profilerData, ticksData, recordingDuration } = legacyData;
+
+  // The `profilerData` and `ticksData` stay, but the previously unrecorded
+  // fields just are empty arrays or objects.
+  let data = {
+    label: profilerData.profilerLabel,
+    duration: recordingDuration,
+    markers: [],
+    frames: [],
+    memory: [],
+    ticks: ticksData,
+    allocations: { sites: [], timestamps: [], frames: [], sizes: [] },
+    profile: profilerData.profile,
+    // Fake a configuration object here if there's tick data,
+    // so that it can be rendered
+    configuration: {
+      withTicks: !!ticksData.length,
+      withMarkers: false,
+      withMemory: false,
+      withAllocations: false
+    },
+    systemHost: {},
+    systemClient: {},
+  };
+
+  return data;
+}
+
+exports.getUnicodeConverter = getUnicodeConverter;
+exports.saveRecordingToFile = saveRecordingToFile;
+exports.loadRecordingFromFile = loadRecordingFromFile;
--- a/devtools/client/performance/modules/moz.build
+++ b/devtools/client/performance/modules/moz.build
@@ -5,10 +5,11 @@
 
 DIRS += [
     'logic',
     'widgets',
 ]
 
 DevToolsModules(
     'global.js',
+    'io.js',
     'markers.js',
 )
--- a/devtools/client/performance/moz.build
+++ b/devtools/client/performance/moz.build
@@ -1,14 +1,15 @@
 # vim: set filetype=python:
 # 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/.
 
 DIRS += [
+    'legacy',
     'modules',
 ]
 
 DevToolsModules(
     'events.js',
     'panel.js'
 )
 
--- a/devtools/client/performance/performance-controller.js
+++ b/devtools/client/performance/performance-controller.js
@@ -30,17 +30,17 @@ loader.lazyRequireGetter(this, "system",
 
 loader.lazyRequireGetter(this, "L10N",
   "devtools/client/performance/modules/global", true);
 loader.lazyRequireGetter(this, "PerformanceTelemetry",
   "devtools/client/performance/modules/logic/telemetry", true);
 loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
   "devtools/client/performance/modules/markers", true);
 loader.lazyRequireGetter(this, "RecordingUtils",
-  "devtools/shared/performance/utils");
+  "devtools/shared/performance/recording-utils");
 loader.lazyRequireGetter(this, "GraphsController",
   "devtools/client/performance/modules/widgets/graphs", true);
 loader.lazyRequireGetter(this, "OptimizationsGraph",
   "devtools/client/performance/modules/widgets/graphs", true);
 loader.lazyRequireGetter(this, "WaterfallHeader",
   "devtools/client/performance/modules/widgets/waterfall-ticks", true);
 loader.lazyRequireGetter(this, "MarkerView",
   "devtools/client/performance/modules/widgets/marker-view", true);
--- a/devtools/client/performance/test/browser.ini
+++ b/devtools/client/performance/test/browser.ini
@@ -42,16 +42,19 @@ skip-if = true # Bug 1161817
 [browser_perf-jit-view-01.js]
 [browser_perf-jit-view-02.js]
 [browser_perf-legacy-front-01.js]
 [browser_perf-legacy-front-02.js]
 [browser_perf-legacy-front-03.js]
 [browser_perf-legacy-front-04.js]
 [browser_perf-legacy-front-05.js]
 [browser_perf-legacy-front-06.js]
+[browser_perf-legacy-front-07.js]
+[browser_perf-legacy-front-08.js]
+[browser_perf-legacy-front-09.js]
 [browser_perf-loading-01.js]
 [browser_perf-loading-02.js]
 [browser_perf-marker-details-01.js]
 skip-if = os == 'linux' # Bug 1172120
 [browser_perf-options-01.js]
 [browser_perf-options-02.js]
 [browser_perf-options-03.js]
 [browser_perf-options-invert-call-tree-01.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/test/browser_perf-legacy-front-07.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test basic functionality of PerformanceFront with mock memory and timeline actors.
+ */
+
+var WAIT_TIME = 100;
+
+const { LegacyPerformanceFront } = require("devtools/client/performance/legacy/front");
+
+function* spawnTest() {
+  let tab = yield getTab(SIMPLE_URL);
+  let target = TargetFactory.forTab(tab);
+  yield target.makeRemote();
+
+  merge(target, {
+    TEST_MOCK_TIMELINE_ACTOR: true,
+    TEST_PERFORMANCE_LEGACY_FRONT: true,
+  });
+
+  let front = new LegacyPerformanceFront(target);
+  yield front.connect();
+
+  ok(front.LEGACY_FRONT, true, "Using legacy front");
+  front.on("timeline-data", () => ok(false, "There should not be any timeline-data events when mocked"));
+
+  let recording = yield front.startRecording({
+    withTicks: true,
+    withMarkers: true,
+    withMemory: true,
+    withAllocations: true,
+  });
+
+  is(recording.getConfiguration().withMarkers, false, "overrides withMarkers based off of actor support");
+  is(recording.getConfiguration().withTicks, false, "overrides withTicks based off of actor support");
+  is(recording.getConfiguration().withMemory, false, "overrides withMemory based off of actor support");
+  is(recording.getConfiguration().withAllocations, false, "overrides withAllocations based off of actor support");
+
+  yield busyWait(WAIT_TIME);
+
+  yield front.stopRecording(recording);
+
+  ok(typeof recording.getDuration() === "number",
+    "The front.stopRecording() allows recording to get a duration.");
+  ok(recording.getDuration() >= 0, "duration is a positive number");
+  isEmptyArray(recording.getMarkers(), "markers");
+  isEmptyArray(recording.getTicks(), "ticks");
+  isEmptyArray(recording.getMemory(), "memory");
+  isEmptyArray(recording.getAllocations().sites, "allocations.sites");
+  isEmptyArray(recording.getAllocations().timestamps, "allocations.timestamps");
+  isEmptyArray(recording.getAllocations().frames, "allocations.frames");
+  ok(recording.getProfile().threads[0].samples.data.length, "profile data has some samples");
+  checkSystemInfo(recording, "Host");
+  checkSystemInfo(recording, "Client");
+
+  yield front.destroy();
+  gBrowser.removeCurrentTab();
+}
+
+function isEmptyArray (array, name) {
+  ok(Array.isArray(array), `${name} is an array`);
+  ok(array.length === 0, `${name} is empty`);
+}
+
+function getTab (url) {
+  let tab = gBrowser.selectedTab = gBrowser.addTab();
+  let loaded = once(gBrowser.selectedBrowser, "load", true);
+
+  content.location = url;
+  return loaded.then(() => {
+    return new Promise(resolve => {
+      let isBlank = url == "about:blank";
+      waitForFocus(() => resolve(tab), content, isBlank);
+    });
+  });
+}
+
+function checkSystemInfo (recording, type) {
+  let data = recording[`get${type}SystemInfo`]();
+  for (let field of ["appid", "apptype", "vendor", "name", "version"]) {
+    ok(data[field], `get${type}SystemInfo() has ${field} property`);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/test/browser_perf-legacy-front-08.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test basic functionality of PerformanceFront without a mock Timeline actor.
+ */
+
+var WAIT_TIME = 100;
+
+const { LegacyPerformanceFront } = require("devtools/client/performance/legacy/front");
+
+function* spawnTest() {
+  let tab = yield getTab(SIMPLE_URL);
+  let target = TargetFactory.forTab(tab);
+  yield target.makeRemote();
+
+  merge(target, {
+    TEST_PERFORMANCE_LEGACY_FRONT: true,
+  });
+
+  let front = new LegacyPerformanceFront(target);
+  yield front.connect();
+
+  ok(front.LEGACY_FRONT, true, "Using legacy front");
+
+  let recording = yield front.startRecording({
+    withTicks: true,
+    withMarkers: true,
+    withMemory: true,
+    withAllocations: true,
+  });
+
+  is(recording.getConfiguration().withMarkers, true, "allows withMarkers based off of actor support");
+  is(recording.getConfiguration().withTicks, true, "allows withTicks based off of actor support");
+  is(recording.getConfiguration().withMemory, false, "overrides withMemory based off of actor support");
+  is(recording.getConfiguration().withAllocations, false, "overrides withAllocations based off of actor support");
+
+  yield waitUntil(() => recording.getMarkers().length);
+  yield waitUntil(() => recording.getTicks().length);
+
+  yield front.stopRecording(recording);
+
+  ok(recording.getMarkers().length, "we have several markers");
+  ok(recording.getTicks().length, "we have several ticks");
+
+  ok(typeof recording.getDuration() === "number",
+    "The front.stopRecording() allows recording to get a duration.");
+  ok(recording.getDuration() >= 0, "duration is a positive number");
+  isEmptyArray(recording.getMemory(), "memory");
+  isEmptyArray(recording.getAllocations().sites, "allocations.sites");
+  isEmptyArray(recording.getAllocations().timestamps, "allocations.timestamps");
+  isEmptyArray(recording.getAllocations().frames, "allocations.frames");
+  ok(recording.getProfile().threads[0].samples.data.length, "profile data has some samples");
+
+  yield front.destroy();
+  gBrowser.removeCurrentTab();
+}
+
+function isEmptyArray (array, name) {
+  ok(Array.isArray(array), `${name} is an array`);
+  ok(array.length === 0, `${name} is empty`);
+}
+
+function getTab (url) {
+  let tab = gBrowser.selectedTab = gBrowser.addTab();
+  let loaded = once(gBrowser.selectedBrowser, "load", true);
+
+  content.location = url;
+  return loaded.then(() => {
+    return new Promise(resolve => {
+      let isBlank = url == "about:blank";
+      waitForFocus(() => resolve(tab), content, isBlank);
+    });
+  });
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance/test/browser_perf-legacy-front-09.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that when using an older server (< Fx40) where the profiler actor does not
+ * have the `getBufferInfo` method that nothing breaks and RecordingModels have null
+ * `getBufferUsage()` values.
+ */
+
+const { LegacyPerformanceFront } = require("devtools/client/performance/legacy/front");
+
+function* spawnTest() {
+  let tab = yield getTab(SIMPLE_URL);
+  let target = TargetFactory.forTab(tab);
+  yield target.makeRemote();
+
+  merge(target, {
+    TEST_PROFILER_FILTER_STATUS: ["position", "totalSize", "generation"],
+    TEST_PERFORMANCE_LEGACY_FRONT: true,
+  });
+
+  let front = new LegacyPerformanceFront(target);
+  yield front.connect();
+  front.setProfilerStatusInterval(10);
+
+  front.on("profiler-status", () => ok(false, "profiler-status should not be called when not supported"));
+  let model = yield front.startRecording();
+
+  yield busyWait(100);
+  is(front.getBufferUsageForRecording(model), null, "buffer usage for recording should be null");
+
+  yield front.stopRecording(model);
+  yield front.destroy();
+  gBrowser.removeCurrentTab();
+}
+
+function getTab (url) {
+  let tab = gBrowser.selectedTab = gBrowser.addTab();
+  let loaded = once(gBrowser.selectedBrowser, "load", true);
+
+  content.location = url;
+  return loaded.then(() => {
+    return new Promise(resolve => {
+      let isBlank = url == "about:blank";
+      waitForFocus(() => resolve(tab), content, isBlank);
+    });
+  });
+}
--- a/devtools/client/performance/test/head.js
+++ b/devtools/client/performance/test/head.js
@@ -11,17 +11,17 @@ var { require } = Cu.import("resource://
 var { gDevTools } = Cu.import("resource:///modules/devtools/client/framework/gDevTools.jsm", {});
 var { console } = require("resource://gre/modules/devtools/shared/Console.jsm");
 var { TargetFactory } = require("devtools/client/framework/target");
 var Promise = require("promise");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var { DebuggerServer } = require("devtools/server/main");
 var { merge } = require("sdk/util/object");
 var { createPerformanceFront } = require("devtools/server/actors/performance");
-var RecordingUtils = require("devtools/shared/performance/utils");
+var RecordingUtils = require("devtools/shared/performance/recording-utils");
 var {
   PMM_loadFrameScripts, PMM_isProfilerActive, PMM_stopProfiler,
   sendProfilerCommand, consoleMethod
 } = require("devtools/shared/performance/process-communication");
 
 var mm = null;
 
 const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js"
--- a/devtools/client/performance/test/unit/head.js
+++ b/devtools/client/performance/test/unit/head.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 var { require } = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
 var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
 var { console } = require("resource://gre/modules/devtools/shared/Console.jsm");
-const RecordingUtils = require("devtools/shared/performance/utils");
+const RecordingUtils = require("devtools/shared/performance/recording-utils");
 
 const PLATFORM_DATA_PREF = "devtools.performance.ui.show-platform-data";
 
 /**
  * Get a path in a FrameNode call tree.
  */
 function getFrameNodePath(root, path) {
   let calls = root.calls;
--- a/devtools/client/performance/test/unit/test_perf-utils-allocations-to-samples.js
+++ b/devtools/client/performance/test/unit/test_perf-utils-allocations-to-samples.js
@@ -7,17 +7,17 @@
  * received from the profiler.
  */
 
 function run_test() {
   run_next_test();
 }
 
 add_task(function () {
-  const { getProfileThreadFromAllocations } = require("devtools/shared/performance/utils");
+  const { getProfileThreadFromAllocations } = require("devtools/shared/performance/recording-utils");
   let output = getProfileThreadFromAllocations(TEST_DATA);
   equal(output.toSource(), EXPECTED_OUTPUT.toSource(), "The output is correct.");
 });
 
 var TEST_DATA = {
   sites: [0, 0, 1, 2, 3],
   timestamps: [50, 100, 150, 200, 250],
   sizes: [0, 0, 100, 200, 300],
--- a/devtools/client/performance/test/unit/test_tree-model-allocations-01.js
+++ b/devtools/client/performance/test/unit/test_tree-model-allocations-01.js
@@ -7,17 +7,17 @@
  */
 
 function run_test() {
   run_next_test();
 }
 
 add_task(function () {
   let { ThreadNode } = require("devtools/client/performance/modules/logic/tree-model");
-  const { getProfileThreadFromAllocations } = require("devtools/shared/performance/utils");
+  const { getProfileThreadFromAllocations } = require("devtools/shared/performance/recording-utils");
   let allocationData = getProfileThreadFromAllocations(TEST_DATA);
   let thread = new ThreadNode(allocationData, { startTime: 0, endTime: 1000 });
 
   /**
    * Values are in order according to:
    * +-------------+------------+-------------+-------------+------------------------------+
    * | Self Bytes  | Self Count | Total Bytes | Total Count | Function                     |
    * +-------------+------------+-------------+-------------+------------------------------+
--- a/devtools/client/performance/test/unit/test_tree-model-allocations-02.js
+++ b/devtools/client/performance/test/unit/test_tree-model-allocations-02.js
@@ -7,17 +7,17 @@
  */
 
 function run_test() {
   run_next_test();
 }
 
 add_task(function () {
   let { ThreadNode } = require("devtools/client/performance/modules/logic/tree-model");
-  const { getProfileThreadFromAllocations } = require("devtools/shared/performance/utils");
+  const { getProfileThreadFromAllocations } = require("devtools/shared/performance/recording-utils");
   let allocationData = getProfileThreadFromAllocations(TEST_DATA);
   let thread = new ThreadNode(allocationData, { invertTree: true, startTime: 0, endTime: 1000 });
 
   /**
    * Values are in order according to:
    * +-------------+------------+-------------+-------------+------------------------------+
    * | Self Bytes  | Self Count | Total Bytes | Total Count | Function                     |
    * +-------------+------------+-------------+-------------+------------------------------+
--- a/devtools/client/shared/test/head.js
+++ b/devtools/client/shared/test/head.js
@@ -246,17 +246,17 @@ function* openAndCloseToolbox(nbOfTimes,
     yield gDevTools.closeToolbox(target);
   }
 }
 
 /**
  * Synthesize a profile for testing.
  */
 function synthesizeProfileForTest(samples) {
-  const RecordingUtils = require("devtools/shared/performance/utils");
+  const RecordingUtils = require("devtools/shared/performance/recording-utils");
 
   samples.unshift({
     time: 0,
     frames: []
   });
 
   let uniqueStacks = new RecordingUtils.UniqueStacks();
   return RecordingUtils.deflateThread({
--- a/devtools/server/actors/performance-recording.js
+++ b/devtools/server/actors/performance-recording.js
@@ -7,19 +7,19 @@
 const { Cu } = require("chrome");
 const protocol = require("devtools/server/protocol");
 const { custom, method, RetVal, Arg, Option, types, preEvent } = protocol;
 const { actorBridge } = require("devtools/server/actors/common");
 
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "merge", "sdk/util/object", true);
 loader.lazyRequireGetter(this, "PerformanceIO",
-  "devtools/shared/performance/io");
+  "devtools/client/performance/modules/io");
 loader.lazyRequireGetter(this, "RecordingUtils",
-  "devtools/shared/performance/utils");
+  "devtools/shared/performance/recording-utils");
 loader.lazyRequireGetter(this, "PerformanceRecordingCommon",
   "devtools/shared/performance/recording-common", true);
 
 /**
  * This actor wraps the Performance module at devtools/shared/shared/performance.js
  * and provides RDP definitions.
  *
  * @see devtools/shared/shared/performance.js for documentation.
--- a/devtools/server/actors/performance.js
+++ b/devtools/server/actors/performance.js
@@ -10,23 +10,23 @@ const { Task } = require("resource://gre
 const { Actor, custom, method, RetVal, Arg, Option, types, preEvent } = protocol;
 const { actorBridge } = require("devtools/server/actors/common");
 const { PerformanceRecordingActor, PerformanceRecordingFront } = require("devtools/server/actors/performance-recording");
 
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "extend", "sdk/util/object", true);
 
 loader.lazyRequireGetter(this, "PerformanceRecorder",
-  "devtools/shared/performance/recorder", true);
+  "devtools/server/performance/recorder", true);
 loader.lazyRequireGetter(this, "PerformanceIO",
-  "devtools/shared/performance/io");
+  "devtools/client/performance/modules/io");
 loader.lazyRequireGetter(this, "normalizePerformanceFeatures",
-  "devtools/shared/performance/utils", true);
+  "devtools/shared/performance/recording-utils", true);
 loader.lazyRequireGetter(this, "LegacyPerformanceFront",
-  "devtools/shared/performance/legacy/front", true);
+  "devtools/client/performance/legacy/front", true);
 loader.lazyRequireGetter(this, "getSystemInfo",
   "devtools/shared/system", true);
 
 const PIPE_TO_FRONT_EVENTS = new Set([
   "recording-started", "recording-stopping", "recording-stopped",
   "profiler-status", "timeline-data", "console-profile-start"
 ]);
 
--- a/devtools/server/moz.build
+++ b/devtools/server/moz.build
@@ -3,16 +3,17 @@
 # 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/.
 
 include('../templates.mozbuild')
 
 DIRS += [
     'actors',
+    'performance',
     'shims',
 ]
 
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 
 XPIDL_SOURCES += [
new file mode 100644
--- /dev/null
+++ b/devtools/server/performance/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+    'recorder.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/server/performance/recorder.js
@@ -0,0 +1,490 @@
+/* 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";
+
+const { Cc, Ci, Cu, Cr } = require("chrome");
+const { Task } = require("resource://gre/modules/Task.jsm");
+
+loader.lazyRequireGetter(this, "Services");
+loader.lazyRequireGetter(this, "promise");
+loader.lazyRequireGetter(this, "extend",
+  "sdk/util/object", true);
+loader.lazyRequireGetter(this, "Class",
+  "sdk/core/heritage", true);
+loader.lazyRequireGetter(this, "EventTarget",
+  "sdk/event/target", true);
+loader.lazyRequireGetter(this, "events",
+  "sdk/event/core");
+
+loader.lazyRequireGetter(this, "Memory",
+  "devtools/shared/shared/memory", true);
+loader.lazyRequireGetter(this, "Timeline",
+  "devtools/shared/shared/timeline", true);
+loader.lazyRequireGetter(this, "Profiler",
+  "devtools/shared/shared/profiler", true);
+loader.lazyRequireGetter(this, "PerformanceRecordingActor",
+  "devtools/server/actors/performance-recording", true);
+loader.lazyRequireGetter(this, "PerformanceRecordingFront",
+  "devtools/server/actors/performance-recording", true);
+loader.lazyRequireGetter(this, "mapRecordingOptions",
+  "devtools/shared/performance/recording-utils", true);
+loader.lazyRequireGetter(this, "DevToolsUtils",
+  "devtools/shared/DevToolsUtils");
+loader.lazyRequireGetter(this, "getSystemInfo",
+  "devtools/shared/system", true);
+
+const PROFILER_EVENTS = [
+  "console-api-profiler",
+  "profiler-started",
+  "profiler-stopped",
+  "profiler-status"
+];
+
+// Max time in milliseconds for the allocations event to occur, which will
+// occur on every GC, or at least as often as DRAIN_ALLOCATIONS_TIMEOUT.
+const DRAIN_ALLOCATIONS_TIMEOUT = 2000;
+
+/**
+ * A connection to underlying actors (profiler, memory, framerate, etc.)
+ * shared by all tools in a target.
+ *
+ * @param Target target
+ *        The target owning this connection.
+ */
+const PerformanceRecorder = exports.PerformanceRecorder = Class({
+  extends: EventTarget,
+
+  initialize: function (conn, tabActor) {
+    this.conn = conn;
+    this.tabActor = tabActor;
+
+    this._pendingConsoleRecordings = [];
+    this._recordings = [];
+
+    this._onTimelineData = this._onTimelineData.bind(this);
+    this._onProfilerEvent = this._onProfilerEvent.bind(this);
+  },
+
+  /**
+   * Initializes a connection to the profiler and other miscellaneous actors.
+   * If in the process of opening, or already open, nothing happens.
+   *
+   * @param {Object} options.systemClient
+   *        Metadata about the client's system to attach to the recording models.
+   *
+   * @return object
+   *         A promise that is resolved once the connection is established.
+   */
+  connect: function (options) {
+    if (this._connected) {
+      return;
+    }
+
+    // Sets `this._profiler`, `this._timeline` and `this._memory`.
+    // Only initialize the timeline and memory fronts if the respective actors
+    // are available. Older Gecko versions don't have existing implementations,
+    // in which case all the methods we need can be easily mocked.
+    this._connectComponents();
+    this._registerListeners();
+
+    this._systemClient = options.systemClient;
+
+    this._connected = true;
+  },
+
+  /**
+   * Destroys this connection.
+   */
+  destroy: function () {
+    this._unregisterListeners();
+    this._disconnectComponents();
+
+    this._connected = null;
+    this._profiler = null;
+    this._timeline = null;
+    this._memory = null;
+    this._target = null;
+    this._client = null;
+  },
+
+  /**
+   * Initializes fronts and connects to the underlying actors using the facades
+   * found in ./actors.js.
+   */
+  _connectComponents: function () {
+    this._profiler = new Profiler(this.tabActor);
+    this._memory = new Memory(this.tabActor);
+    this._timeline = new Timeline(this.tabActor);
+    this._profiler.registerEventNotifications({ events: PROFILER_EVENTS });
+  },
+
+  /**
+   * Registers listeners on events from the underlying
+   * actors, so the connection can handle them.
+   */
+  _registerListeners: function () {
+    this._timeline.on("*", this._onTimelineData);
+    this._memory.on("*", this._onTimelineData);
+    this._profiler.on("*", this._onProfilerEvent);
+  },
+
+  /**
+   * Unregisters listeners on events on the underlying actors.
+   */
+  _unregisterListeners: function () {
+    this._timeline.off("*", this._onTimelineData);
+    this._memory.off("*", this._onTimelineData);
+    this._profiler.off("*", this._onProfilerEvent);
+  },
+
+  /**
+   * Closes the connections to non-profiler actors.
+   */
+  _disconnectComponents: function () {
+    this._profiler.unregisterEventNotifications({ events: PROFILER_EVENTS });
+    this._profiler.destroy();
+    this._timeline.destroy();
+    this._memory.destroy();
+  },
+
+  _onProfilerEvent: function (topic, data) {
+    if (topic === "console-api-profiler") {
+      if (data.subject.action === "profile") {
+        this._onConsoleProfileStart(data.details);
+      } else if (data.subject.action === "profileEnd") {
+        this._onConsoleProfileEnd(data.details);
+      }
+    } else if (topic === "profiler-stopped") {
+      this._onProfilerUnexpectedlyStopped();
+    } else if (topic === "profiler-status") {
+      events.emit(this, "profiler-status", data);
+    }
+  },
+
+  /**
+   * Invoked whenever `console.profile` is called.
+   *
+   * @param string profileLabel
+   *        The provided string argument if available; undefined otherwise.
+   * @param number currentTime
+   *        The time (in milliseconds) when the call was made, relative to when
+   *        the nsIProfiler module was started.
+   */
+  _onConsoleProfileStart: Task.async(function *({ profileLabel, currentTime: startTime }) {
+    let recordings = this._recordings;
+
+    // Abort if a profile with this label already exists.
+    if (recordings.find(e => e.getLabel() === profileLabel)) {
+      return;
+    }
+
+    // Immediately emit this so the client can start setting things up,
+    // expecting a recording very soon.
+    events.emit(this, "console-profile-start");
+
+    let model = yield this.startRecording(extend({}, getPerformanceRecordingPrefs(), {
+      console: true,
+      label: profileLabel
+    }));
+  }),
+
+  /**
+   * Invoked whenever `console.profileEnd` is called.
+   *
+   * @param string profileLabel
+   *        The provided string argument if available; undefined otherwise.
+   * @param number currentTime
+   *        The time (in milliseconds) when the call was made, relative to when
+   *        the nsIProfiler module was started.
+   */
+  _onConsoleProfileEnd: Task.async(function *(data) {
+    // If no data, abort; can occur if profiler isn't running and we get a surprise
+    // call to console.profileEnd()
+    if (!data) {
+      return;
+    }
+    let { profileLabel, currentTime: endTime } = data;
+
+    let pending = this._recordings.filter(r => r.isConsole() && r.isRecording());
+    if (pending.length === 0) {
+      return;
+    }
+
+    let model;
+    // Try to find the corresponding `console.profile` call if
+    // a label was used in profileEnd(). If no matches, abort.
+    if (profileLabel) {
+      model = pending.find(e => e.getLabel() === profileLabel);
+    }
+    // If no label supplied, pop off the most recent pending console recording
+    else {
+      model = pending[pending.length - 1];
+    }
+
+    // If `profileEnd()` was called with a label, and there are no matching
+    // sessions, abort.
+    if (!model) {
+      Cu.reportError("console.profileEnd() called with label that does not match a recording.");
+      return;
+    }
+
+    yield this.stopRecording(model);
+  }),
+
+ /**
+  * TODO handle bug 1144438
+  */
+  _onProfilerUnexpectedlyStopped: function () {
+    Cu.reportError("Profiler unexpectedly stopped.", arguments);
+  },
+
+  /**
+   * Called whenever there is timeline data of any of the following types:
+   * - markers
+   * - frames
+   * - memory
+   * - ticks
+   * - allocations
+   */
+  _onTimelineData: function (eventName, ...data) {
+    let eventData = Object.create(null);
+
+    switch (eventName) {
+      case "markers": {
+        eventData = { markers: data[0], endTime: data[1] };
+        break;
+      }
+      case "ticks": {
+        eventData = { delta: data[0], timestamps: data[1] };
+        break;
+      }
+      case "memory": {
+        eventData = { delta: data[0], measurement: data[1] };
+        break;
+      }
+      case "frames": {
+        eventData = { delta: data[0], frames: data[1] };
+        break;
+      }
+      case "allocations": {
+        eventData = data[0];
+        break;
+      }
+    }
+
+    // Filter by only recordings that are currently recording;
+    // TODO should filter by recordings that have realtimeMarkers enabled.
+    let activeRecordings = this._recordings.filter(r => r.isRecording());
+
+    if (activeRecordings.length) {
+      events.emit(this, "timeline-data", eventName, eventData, activeRecordings);
+    }
+  },
+
+  /**
+   * Checks whether or not recording is currently supported. At the moment,
+   * this is only influenced by private browsing mode and the profiler.
+   */
+  canCurrentlyRecord: function() {
+    let success = true;
+    let reasons = [];
+
+    if (!Profiler.canProfile()) {
+      success = false,
+      reasons.push("profiler-unavailable");
+    }
+
+    // Check other factors that will affect the possibility of successfully
+    // starting a recording here.
+
+    return { success, reasons };
+  },
+
+  /**
+   * Begins a recording session
+   *
+   * @param boolean options.withMarkers
+   * @param boolean options.withJITOptimizations
+   * @param boolean options.withTicks
+   * @param boolean options.withMemory
+   * @param boolean options.withAllocations
+   * @param boolean options.allocationsSampleProbability
+   * @param boolean options.allocationsMaxLogLength
+   * @param boolean options.bufferSize
+   * @param boolean options.sampleFrequency
+   * @param boolean options.console
+   * @param string options.label
+   * @param boolean options.realtimeMarkers
+   * @return object
+   *         A promise that is resolved once recording has started.
+   */
+  startRecording: Task.async(function*(options) {
+    let profilerStart, timelineStart, memoryStart;
+
+    profilerStart = Task.spawn(function *() {
+      let data = yield this._profiler.isActive();
+      if (data.isActive) {
+        return data;
+      }
+      let startData = yield this._profiler.start(mapRecordingOptions("profiler", options));
+
+      // If no current time is exposed from starting, set it to 0 -- this is an
+      // older Gecko that does not return its starting time, and uses an epoch based
+      // on the profiler's start time.
+      if (startData.currentTime == null) {
+        startData.currentTime = 0;
+      }
+      return startData;
+    }.bind(this));
+
+    // Timeline will almost always be on if using the DevTools, but using component
+    // independently could result in no timeline.
+    if (options.withMarkers || options.withTicks || options.withMemory) {
+      timelineStart = this._timeline.start(mapRecordingOptions("timeline", options));
+    }
+
+    if (options.withAllocations) {
+      if (this._memory.getState() === "detached") {
+        this._memory.attach();
+      }
+      memoryStart = this._memory.startRecordingAllocations(extend(mapRecordingOptions("memory", options), {
+        drainAllocationsTimeout: DRAIN_ALLOCATIONS_TIMEOUT
+      }));
+    }
+
+    let [profilerStartData, timelineStartData, memoryStartData] = yield promise.all([
+      profilerStart, timelineStart, memoryStart
+    ]);
+
+    let data = Object.create(null);
+    // Filter out start times that are not actually used (0 or undefined), and
+    // find the earliest time since all sources use same epoch.
+    let startTimes = [profilerStartData.currentTime, memoryStartData, timelineStartData].filter(Boolean);
+    data.startTime = Math.min(...startTimes);
+    data.position = profilerStartData.position;
+    data.generation = profilerStartData.generation;
+    data.totalSize = profilerStartData.totalSize;
+
+    data.systemClient = this._systemClient;
+    data.systemHost = yield getSystemInfo();
+
+    let model = new PerformanceRecordingActor(this.conn, options, data);
+    this._recordings.push(model);
+
+    events.emit(this, "recording-started", model);
+    return model;
+  }),
+
+  /**
+   * Manually ends the recording session for the corresponding PerformanceRecording.
+   *
+   * @param PerformanceRecording model
+   *        The corresponding PerformanceRecording that belongs to the recording session wished to stop.
+   * @return PerformanceRecording
+   *         Returns the same model, populated with the profiling data.
+   */
+  stopRecording: Task.async(function *(model) {
+    // If model isn't in the Recorder's internal store,
+    // then do nothing, like if this was a console.profileEnd
+    // from a different target.
+    if (this._recordings.indexOf(model) === -1) {
+      return model;
+    }
+
+    // Flag the recording as no longer recording, so that `model.isRecording()`
+    // is false. Do this before we fetch all the data, and then subsequently
+    // the recording can be considered "completed".
+    let endTime = Date.now();
+    events.emit(this, "recording-stopping", model);
+
+    // Currently there are two ways profiles stop recording. Either manually in the
+    // performance tool, or via console.profileEnd. Once a recording is done,
+    // we want to deliver the model to the performance tool (either as a return
+    // from the PerformanceFront or via `console-profile-stop` event) and then
+    // remove it from the internal store.
+    //
+    // In the case where a console.profile is generated via the console (so the tools are
+    // open), we initialize the Performance tool so it can listen to those events.
+    this._recordings.splice(this._recordings.indexOf(model), 1);
+
+    let startTime = model._startTime;
+    let profilerData = this._profiler.getProfile({ startTime });
+
+    // Only if there are no more sessions recording do we stop
+    // the underlying memory and timeline actors. If we're still recording,
+    // juse use Date.now() for the memory and timeline end times, as those
+    // are only used in tests.
+    if (!this.isRecording()) {
+      // Check to see if memory is recording, so we only stop recording
+      // if necessary (otherwise if the memory component is not attached, this will fail)
+      if (this._memory.isRecordingAllocations()) {
+        this._memory.stopRecordingAllocations();
+      }
+      this._timeline.stop();
+    }
+
+    let recordingData = {
+      // Data available only at the end of a recording.
+      profile: profilerData.profile,
+      // End times for all the actors.
+      duration: profilerData.currentTime - startTime,
+    };
+
+    events.emit(this, "recording-stopped", model, recordingData);
+    return model;
+  }),
+
+  /**
+   * Checks all currently stored recording handles and returns a boolean
+   * if there is a session currently being recorded.
+   *
+   * @return Boolean
+   */
+  isRecording: function () {
+    return this._recordings.some(h => h.isRecording());
+  },
+
+  /**
+   * Returns all current recordings.
+   */
+  getRecordings: function () {
+    return this._recordings;
+  },
+
+  /**
+   * Sets how often the "profiler-status" event should be emitted.
+   * Used in tests.
+   */
+  setProfilerStatusInterval: function (n) {
+    this._profiler.setProfilerStatusInterval(n);
+  },
+
+  /**
+   * Returns the configurations set on underlying components, used in tests.
+   * Returns an object with `probability`, `maxLogLength` for allocations, and
+   * `features`, `threadFilters`, `entries` and `interval` for profiler.
+   *
+   * @return {object}
+   */
+  getConfiguration: function () {
+    return extend({}, this._memory.getAllocationsSettings(), this._profiler.getStartOptions());
+  },
+
+  toString: () => "[object PerformanceRecorder]"
+});
+
+/**
+ * Creates an object of configurations based off of preferences for a PerformanceRecording.
+ */
+function getPerformanceRecordingPrefs () {
+  return {
+    withMarkers: true,
+    withMemory: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
+    withTicks: Services.prefs.getBoolPref("devtools.performance.ui.enable-framerate"),
+    withAllocations: Services.prefs.getBoolPref("devtools.performance.ui.enable-allocations"),
+    withJITOptimizations: Services.prefs.getBoolPref("devtools.performance.ui.enable-jit-optimizations"),
+    allocationsSampleProbability: +Services.prefs.getCharPref("devtools.performance.memory.sample-probability"),
+    allocationsMaxLogLength: Services.prefs.getIntPref("devtools.performance.memory.max-log-length")
+  };
+}
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -53,22 +53,16 @@ skip-if = e10s # Bug 1183605 - devtools/
 [browser_markers-gc.js]
 [browser_markers-parse-html.js]
 [browser_markers-styles.js]
 [browser_markers-timestamp.js]
 [browser_navigateEvents.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-allocation-data.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
-[browser_perf-legacy-front-01.js]
-skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
-[browser_perf-legacy-front-02.js]
-skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
-[browser_perf-legacy-front-03.js]
-skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-profiler-01.js]
 [browser_perf-profiler-02.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-profiler-03.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-realtime-markers.js]
 [browser_perf-recording-actor-01.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
deleted file mode 100644
--- a/devtools/server/tests/browser/browser_perf-legacy-front-01.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Test basic functionality of PerformanceFront with mock memory and timeline actors.
- */
-
-var WAIT_TIME = 100;
-
-const { TargetFactory } = require("devtools/client/framework/target");
-const { LegacyPerformanceFront } = require("devtools/shared/performance/legacy/front");
-const { merge } = require("sdk/util/object");
-
-add_task(function*() {
-  let tab = yield getTab(MAIN_DOMAIN + "doc_perf.html");
-  let target = TargetFactory.forTab(tab);
-  yield target.makeRemote();
-
-  merge(target, {
-    TEST_MOCK_TIMELINE_ACTOR: true,
-    TEST_PERFORMANCE_LEGACY_FRONT: true,
-  });
-
-  let front = new LegacyPerformanceFront(target);
-  yield front.connect();
-
-  ok(front.LEGACY_FRONT, true, "Using legacy front");
-  front.on("timeline-data", () => ok(false, "There should not be any timeline-data events when mocked"));
-
-  let recording = yield front.startRecording({
-    withTicks: true,
-    withMarkers: true,
-    withMemory: true,
-    withAllocations: true,
-  });
-
-  is(recording.getConfiguration().withMarkers, false, "overrides withMarkers based off of actor support");
-  is(recording.getConfiguration().withTicks, false, "overrides withTicks based off of actor support");
-  is(recording.getConfiguration().withMemory, false, "overrides withMemory based off of actor support");
-  is(recording.getConfiguration().withAllocations, false, "overrides withAllocations based off of actor support");
-
-  yield busyWait(WAIT_TIME);
-
-  yield front.stopRecording(recording);
-
-  ok(typeof recording.getDuration() === "number",
-    "The front.stopRecording() allows recording to get a duration.");
-  ok(recording.getDuration() >= 0, "duration is a positive number");
-  isEmptyArray(recording.getMarkers(), "markers");
-  isEmptyArray(recording.getTicks(), "ticks");
-  isEmptyArray(recording.getMemory(), "memory");
-  isEmptyArray(recording.getAllocations().sites, "allocations.sites");
-  isEmptyArray(recording.getAllocations().timestamps, "allocations.timestamps");
-  isEmptyArray(recording.getAllocations().frames, "allocations.frames");
-  ok(recording.getProfile().threads[0].samples.data.length, "profile data has some samples");
-  checkSystemInfo(recording, "Host");
-  checkSystemInfo(recording, "Client");
-
-  yield front.destroy();
-  yield closeDebuggerClient(target.client);
-  gBrowser.removeCurrentTab();
-});
-
-function isEmptyArray (array, name) {
-  ok(Array.isArray(array), `${name} is an array`);
-  ok(array.length === 0, `${name} is empty`);
-}
-
-function getTab (url) {
-  let tab = gBrowser.selectedTab = gBrowser.addTab();
-  let loaded = once(gBrowser.selectedBrowser, "load", true);
-
-  content.location = url;
-  return loaded.then(() => {
-    return new Promise(resolve => {
-      let isBlank = url == "about:blank";
-      waitForFocus(() => resolve(tab), content, isBlank);
-    });
-  });
-}
-
-function checkSystemInfo (recording, type) {
-  let data = recording[`get${type}SystemInfo`]();
-  for (let field of ["appid", "apptype", "vendor", "name", "version"]) {
-    ok(data[field], `get${type}SystemInfo() has ${field} property`);
-  }
-}
deleted file mode 100644
--- a/devtools/server/tests/browser/browser_perf-legacy-front-02.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Test basic functionality of PerformanceFront without a mock Timeline actor.
- */
-
-var WAIT_TIME = 100;
-
-const { TargetFactory } = require("devtools/client/framework/target");
-const { LegacyPerformanceFront } = require("devtools/shared/performance/legacy/front");
-const { merge } = require("sdk/util/object");
-
-add_task(function*() {
-  let tab = yield getTab(MAIN_DOMAIN + "doc_perf.html");
-  let target = TargetFactory.forTab(tab);
-  yield target.makeRemote();
-
-  merge(target, {
-    TEST_PERFORMANCE_LEGACY_FRONT: true,
-  });
-
-  let front = new LegacyPerformanceFront(target);
-  yield front.connect();
-
-  ok(front.LEGACY_FRONT, true, "Using legacy front");
-
-  let recording = yield front.startRecording({
-    withTicks: true,
-    withMarkers: true,
-    withMemory: true,
-    withAllocations: true,
-  });
-
-  is(recording.getConfiguration().withMarkers, true, "allows withMarkers based off of actor support");
-  is(recording.getConfiguration().withTicks, true, "allows withTicks based off of actor support");
-  is(recording.getConfiguration().withMemory, false, "overrides withMemory based off of actor support");
-  is(recording.getConfiguration().withAllocations, false, "overrides withAllocations based off of actor support");
-
-  yield waitUntil(() => recording.getMarkers().length);
-  yield waitUntil(() => recording.getTicks().length);
-
-  yield front.stopRecording(recording);
-
-  ok(recording.getMarkers().length, "we have several markers");
-  ok(recording.getTicks().length, "we have several ticks");
-
-  ok(typeof recording.getDuration() === "number",
-    "The front.stopRecording() allows recording to get a duration.");
-  ok(recording.getDuration() >= 0, "duration is a positive number");
-  isEmptyArray(recording.getMemory(), "memory");
-  isEmptyArray(recording.getAllocations().sites, "allocations.sites");
-  isEmptyArray(recording.getAllocations().timestamps, "allocations.timestamps");
-  isEmptyArray(recording.getAllocations().frames, "allocations.frames");
-  ok(recording.getProfile().threads[0].samples.data.length, "profile data has some samples");
-
-  yield front.destroy();
-  yield closeDebuggerClient(target.client);
-  gBrowser.removeCurrentTab();
-});
-
-function isEmptyArray (array, name) {
-  ok(Array.isArray(array), `${name} is an array`);
-  ok(array.length === 0, `${name} is empty`);
-}
-
-function getTab (url) {
-  let tab = gBrowser.selectedTab = gBrowser.addTab();
-  let loaded = once(gBrowser.selectedBrowser, "load", true);
-
-  content.location = url;
-  return loaded.then(() => {
-    return new Promise(resolve => {
-      let isBlank = url == "about:blank";
-      waitForFocus(() => resolve(tab), content, isBlank);
-    });
-  });
-}
deleted file mode 100644
--- a/devtools/server/tests/browser/browser_perf-legacy-front-03.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests that when using an older server (< Fx40) where the profiler actor does not
- * have the `getBufferInfo` method that nothing breaks and RecordingModels have null
- * `getBufferUsage()` values.
- */
-
-const { TargetFactory } = require("devtools/client/framework/target");
-const { LegacyPerformanceFront } = require("devtools/shared/performance/legacy/front");
-const { merge } = require("sdk/util/object");
-
-add_task(function*() {
-  let tab = yield getTab(MAIN_DOMAIN + "doc_perf.html");
-  let target = TargetFactory.forTab(tab);
-  yield target.makeRemote();
-
-  merge(target, {
-    TEST_PROFILER_FILTER_STATUS: ["position", "totalSize", "generation"],
-    TEST_PERFORMANCE_LEGACY_FRONT: true,
-  });
-
-  let front = new LegacyPerformanceFront(target);
-  yield front.connect();
-  front.setProfilerStatusInterval(10);
-
-  front.on("profiler-status", () => ok(false, "profiler-status should not be called when not supported"));
-  let model = yield front.startRecording();
-
-  yield busyWait(100);
-  is(front.getBufferUsageForRecording(model), null, "buffer usage for recording should be null");
-
-  yield front.stopRecording(model);
-  yield front.destroy();
-  yield closeDebuggerClient(target.client);
-  gBrowser.removeCurrentTab();
-});
-
-function getTab (url) {
-  let tab = gBrowser.selectedTab = gBrowser.addTab();
-  let loaded = once(gBrowser.selectedBrowser, "load", true);
-
-  content.location = url;
-  return loaded.then(() => {
-    return new Promise(resolve => {
-      let isBlank = url == "about:blank";
-      waitForFocus(() => resolve(tab), content, isBlank);
-    });
-  });
-}
deleted file mode 100644
--- a/devtools/shared/performance/io.js
+++ /dev/null
@@ -1,166 +0,0 @@
-/* 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";
-
-const { Cc, Ci, Cu, Cr } = require("chrome");
-
-loader.lazyRequireGetter(this, "Services");
-loader.lazyRequireGetter(this, "promise");
-loader.lazyRequireGetter(this, "RecordingUtils",
-  "devtools/shared/performance/utils");
-
-loader.lazyImporter(this, "FileUtils",
-  "resource://gre/modules/FileUtils.jsm");
-loader.lazyImporter(this, "NetUtil",
-  "resource://gre/modules/NetUtil.jsm");
-
-// This identifier string is used to tentatively ascertain whether or not
-// a JSON loaded from disk is actually something generated by this tool.
-// It isn't, of course, a definitive verification, but a Good Enoughâ„¢
-// approximation before continuing the import. Don't localize this.
-const PERF_TOOL_SERIALIZER_IDENTIFIER = "Recorded Performance Data";
-const PERF_TOOL_SERIALIZER_LEGACY_VERSION = 1;
-const PERF_TOOL_SERIALIZER_CURRENT_VERSION = 2;
-
-/**
- * Helpers for importing/exporting JSON.
- */
-
-/**
- * Gets a nsIScriptableUnicodeConverter instance with a default UTF-8 charset.
- * @return object
- */
-function getUnicodeConverter () {
-  let className = "@mozilla.org/intl/scriptableunicodeconverter";
-  let converter = Cc[className].createInstance(Ci.nsIScriptableUnicodeConverter);
-  converter.charset = "UTF-8";
-  return converter;
-}
-
-/**
- * Saves a recording as JSON to a file. The provided data is assumed to be
- * acyclical, so that it can be properly serialized.
- *
- * @param object recordingData
- *        The recording data to stream as JSON.
- * @param nsILocalFile file
- *        The file to stream the data into.
- * @return object
- *         A promise that is resolved once streaming finishes, or rejected
- *         if there was an error.
- */
-function saveRecordingToFile (recordingData, file) {
-  let deferred = promise.defer();
-
-  recordingData.fileType = PERF_TOOL_SERIALIZER_IDENTIFIER;
-  recordingData.version = PERF_TOOL_SERIALIZER_CURRENT_VERSION;
-
-  let string = JSON.stringify(recordingData);
-  let inputStream = this.getUnicodeConverter().convertToInputStream(string);
-  let outputStream = FileUtils.openSafeFileOutputStream(file);
-
-  NetUtil.asyncCopy(inputStream, outputStream, deferred.resolve);
-  return deferred.promise;
-}
-
-/**
- * Loads a recording stored as JSON from a file.
- *
- * @param nsILocalFile file
- *        The file to import the data from.
- * @return object
- *         A promise that is resolved once importing finishes, or rejected
- *         if there was an error.
- */
-function loadRecordingFromFile (file) {
-  let deferred = promise.defer();
-
-  let channel = NetUtil.newChannel({
-    uri: NetUtil.newURI(file),
-    loadUsingSystemPrincipal: true});
-
-  channel.contentType = "text/plain";
-
-  NetUtil.asyncFetch(channel, (inputStream, status) => {
-    try {
-      let string = NetUtil.readInputStreamToString(inputStream, inputStream.available());
-      var recordingData = JSON.parse(string);
-    } catch (e) {
-      deferred.reject(new Error("Could not read recording data file."));
-      return;
-    }
-    if (recordingData.fileType != PERF_TOOL_SERIALIZER_IDENTIFIER) {
-      deferred.reject(new Error("Unrecognized recording data file."));
-      return;
-    }
-    if (!isValidSerializerVersion(recordingData.version)) {
-      deferred.reject(new Error("Unsupported recording data file version."));
-      return;
-    }
-    if (recordingData.version === PERF_TOOL_SERIALIZER_LEGACY_VERSION) {
-      recordingData = convertLegacyData(recordingData);
-    }
-    if (recordingData.profile.meta.version === 2) {
-      RecordingUtils.deflateProfile(recordingData.profile);
-    }
-    deferred.resolve(recordingData);
-  });
-
-  return deferred.promise;
-}
-
-/**
- * Returns a boolean indicating whether or not the passed in `version`
- * is supported by this serializer.
- *
- * @param number version
- * @return boolean
- */
-function isValidSerializerVersion (version) {
-  return !!~[
-    PERF_TOOL_SERIALIZER_LEGACY_VERSION,
-    PERF_TOOL_SERIALIZER_CURRENT_VERSION
-  ].indexOf(version);
-}
-
-/**
- * Takes recording data (with version `1`, from the original profiler tool), and
- * massages the data to be line with the current performance tool's property names
- * and values.
- *
- * @param object legacyData
- * @return object
- */
-function convertLegacyData (legacyData) {
-  let { profilerData, ticksData, recordingDuration } = legacyData;
-
-  // The `profilerData` and `ticksData` stay, but the previously unrecorded
-  // fields just are empty arrays or objects.
-  let data = {
-    label: profilerData.profilerLabel,
-    duration: recordingDuration,
-    markers: [],
-    frames: [],
-    memory: [],
-    ticks: ticksData,
-    allocations: { sites: [], timestamps: [], frames: [], sizes: [] },
-    profile: profilerData.profile,
-    // Fake a configuration object here if there's tick data,
-    // so that it can be rendered
-    configuration: {
-      withTicks: !!ticksData.length,
-      withMarkers: false,
-      withMemory: false,
-      withAllocations: false
-    },
-    systemHost: {},
-    systemClient: {},
-  };
-
-  return data;
-}
-
-exports.getUnicodeConverter = getUnicodeConverter;
-exports.saveRecordingToFile = saveRecordingToFile;
-exports.loadRecordingFromFile = loadRecordingFromFile;
deleted file mode 100644
--- a/devtools/shared/performance/legacy/actors.js
+++ /dev/null
@@ -1,254 +0,0 @@
-/* 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";
-
-const { Task } = require("resource://gre/modules/Task.jsm");
-
-loader.lazyRequireGetter(this, "promise");
-loader.lazyRequireGetter(this, "EventEmitter",
-  "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "Poller",
-  "devtools/client/shared/poller", true);
-
-loader.lazyRequireGetter(this, "CompatUtils",
-  "devtools/shared/performance/legacy/compatibility");
-loader.lazyRequireGetter(this, "RecordingUtils",
-  "devtools/shared/performance/utils");
-loader.lazyRequireGetter(this, "TimelineFront",
-  "devtools/server/actors/timeline", true);
-loader.lazyRequireGetter(this, "ProfilerFront",
-  "devtools/server/actors/profiler", true);
-
-// how often do we check the status of the profiler's circular buffer
-const PROFILER_CHECK_TIMER = 5000; // ms
-
-const TIMELINE_ACTOR_METHODS = [
-  "start", "stop",
-];
-
-const PROFILER_ACTOR_METHODS = [
-  "startProfiler", "getStartOptions", "stopProfiler",
-  "registerEventNotifications", "unregisterEventNotifications"
-];
-
-/**
- * Constructor for a facade around an underlying ProfilerFront.
- */
-function LegacyProfilerFront (target) {
-  this._target = target;
-  this._onProfilerEvent = this._onProfilerEvent.bind(this);
-  this._checkProfilerStatus = this._checkProfilerStatus.bind(this);
-  this._PROFILER_CHECK_TIMER = this._target.TEST_MOCK_PROFILER_CHECK_TIMER || PROFILER_CHECK_TIMER;
-
-  EventEmitter.decorate(this);
-}
-
-LegacyProfilerFront.prototype = {
-  EVENTS: ["console-api-profiler", "profiler-stopped"],
-
-  // Connects to the targets underlying real ProfilerFront.
-  connect: Task.async(function*() {
-    let target = this._target;
-    this._front = new ProfilerFront(target.client, target.form);
-
-    // Fetch and store information about the SPS profiler and
-    // server profiler.
-    this.traits = {};
-    this.traits.filterable = target.getTrait("profilerDataFilterable");
-
-    // Directly register to event notifications when connected
-    // to hook into `console.profile|profileEnd` calls.
-    yield this.registerEventNotifications({ events: this.EVENTS });
-    target.client.addListener("eventNotification", this._onProfilerEvent);
-  }),
-
-  /**
-   * Unregisters events for the underlying profiler actor.
-   */
-  destroy: Task.async(function *() {
-    if (this._poller) {
-      yield this._poller.destroy();
-    }
-    yield this.unregisterEventNotifications({ events: this.EVENTS });
-    this._target.client.removeListener("eventNotification", this._onProfilerEvent);
-  }),
-
-  /**
-   * Starts the profiler actor, if necessary.
-   *
-   * @option {number?} bufferSize
-   * @option {number?} sampleFrequency
-   */
-  start: Task.async(function *(options={}) {
-    // Check for poller status even if the profiler is already active --
-    // profiler can be activated via `console.profile` or another source, like
-    // the Gecko Profiler.
-    if (!this._poller) {
-      this._poller = new Poller(this._checkProfilerStatus, this._PROFILER_CHECK_TIMER, false);
-    }
-    if (!this._poller.isPolling()) {
-      this._poller.on();
-    }
-
-    // Start the profiler only if it wasn't already active. The built-in
-    // nsIPerformance module will be kept recording, because it's the same instance
-    // for all targets and interacts with the whole platform, so we don't want
-    // to affect other clients by stopping (or restarting) it.
-    let { isActive, currentTime, position, generation, totalSize } = yield this.getStatus();
-
-    if (isActive) {
-      return { startTime: currentTime, position, generation, totalSize };
-    }
-
-    // Translate options from the recording model into profiler-specific
-    // options for the nsIProfiler
-    let profilerOptions = {
-      entries: options.bufferSize,
-      interval: options.sampleFrequency ? (1000 / (options.sampleFrequency * 1000)) : void 0
-    };
-
-    let startInfo = yield this.startProfiler(profilerOptions);
-    let startTime = 0;
-    if ('currentTime' in startInfo) {
-      startTime = startInfo.currentTime;
-    }
-
-    return { startTime, position, generation, totalSize };
-  }),
-
-  /**
-   * Indicates the end of a recording -- does not actually stop the profiler
-   * (stopProfiler does that), but notes that we no longer need to poll
-   * for buffer status.
-   */
-  stop: Task.async(function *() {
-    yield this._poller.off();
-  }),
-
-  /**
-   * Wrapper around `profiler.isActive()` to take profiler status data and emit.
-   */
-  getStatus: Task.async(function *() {
-    let data = yield (CompatUtils.callFrontMethod("isActive").call(this));
-    // If no data, the last poll for `isActive()` was wrapping up, and the target.client
-    // is now null, so we no longer have data, so just abort here.
-    if (!data) {
-      return;
-    }
-
-    // If TEST_PROFILER_FILTER_STATUS defined (via array of fields), filter
-    // out any field from isActive, used only in tests. Used to filter out
-    // buffer status fields to simulate older geckos.
-    if (this._target.TEST_PROFILER_FILTER_STATUS) {
-      data = Object.keys(data).reduce((acc, prop) => {
-        if (this._target.TEST_PROFILER_FILTER_STATUS.indexOf(prop) === -1) {
-          acc[prop] = data[prop];
-        }
-        return acc;
-      }, {});
-    }
-
-    this.emit("profiler-status", data);
-    return data;
-  }),
-
-  /**
-   * Returns profile data from now since `startTime`.
-   */
-  getProfile: Task.async(function *(options) {
-    let profilerData = yield (CompatUtils.callFrontMethod("getProfile").call(this, options));
-    // If the backend is not deduped, dedupe it ourselves, as rest of the code
-    // expects a deduped profile.
-    if (profilerData.profile.meta.version === 2) {
-      RecordingUtils.deflateProfile(profilerData.profile);
-    }
-
-    // If the backend does not support filtering by start and endtime on platform (< Fx40),
-    // do it on the client (much slower).
-    if (!this.traits.filterable) {
-      RecordingUtils.filterSamples(profilerData.profile, options.startTime || 0);
-    }
-
-    return profilerData;
-  }),
-
-  /**
-   * Invoked whenever a registered event was emitted by the profiler actor.
-   *
-   * @param object response
-   *        The data received from the backend.
-   */
-  _onProfilerEvent: function (_, { topic, subject, details }) {
-    if (topic === "console-api-profiler") {
-      if (subject.action === "profile") {
-        this.emit("console-profile-start", details);
-      } else if (subject.action === "profileEnd") {
-        this.emit("console-profile-stop", details);
-      }
-    } else if (topic === "profiler-stopped") {
-      this.emit("profiler-stopped");
-    }
-  },
-
-  _checkProfilerStatus: Task.async(function *() {
-    // Calling `getStatus()` will emit the "profiler-status" on its own
-    yield this.getStatus();
-  }),
-
-  toString: () => "[object LegacyProfilerFront]"
-};
-
-/**
- * Constructor for a facade around an underlying TimelineFront.
- */
-function LegacyTimelineFront (target) {
-  this._target = target;
-  EventEmitter.decorate(this);
-}
-
-LegacyTimelineFront.prototype = {
-  EVENTS: ["markers", "frames", "ticks"],
-
-  connect: Task.async(function*() {
-    let supported = yield CompatUtils.timelineActorSupported(this._target);
-    this._front = supported ?
-                  new TimelineFront(this._target.client, this._target.form) :
-                  new CompatUtils.MockTimelineFront();
-
-    this.IS_MOCK = !supported;
-
-    // Binds underlying actor events and consolidates them to a `timeline-data`
-    // exposed event.
-    this.EVENTS.forEach(type => {
-      let handler = this[`_on${type}`] = this._onTimelineData.bind(this, type);
-      this._front.on(type, handler);
-    });
-  }),
-
-  /**
-   * Override actor's destroy, so we can unregister listeners before
-   * destroying the underlying actor.
-   */
-  destroy: Task.async(function *() {
-    this.EVENTS.forEach(type => this._front.off(type, this[`_on${type}`]));
-    yield this._front.destroy();
-  }),
-
-  /**
-   * An aggregate of all events (markers, frames, ticks) and exposes
-   * to PerformanceActorsConnection as a single event.
-   */
-  _onTimelineData: function (type, ...data) {
-    this.emit("timeline-data", type, ...data);
-  },
-
-  toString: () => "[object LegacyTimelineFront]"
-};
-
-// Bind all the methods that directly proxy to the actor
-PROFILER_ACTOR_METHODS.forEach(m => LegacyProfilerFront.prototype[m] = CompatUtils.callFrontMethod(m));
-TIMELINE_ACTOR_METHODS.forEach(m => LegacyTimelineFront.prototype[m] = CompatUtils.callFrontMethod(m));
-
-exports.LegacyProfilerFront = LegacyProfilerFront;
-exports.LegacyTimelineFront = LegacyTimelineFront;
deleted file mode 100644
--- a/devtools/shared/performance/legacy/front.js
+++ /dev/null
@@ -1,479 +0,0 @@
-/* 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";
-
-const { Cu } = require("chrome");
-const { Task } = require("resource://gre/modules/Task.jsm");
-
-loader.lazyRequireGetter(this, "Services");
-loader.lazyRequireGetter(this, "promise");
-loader.lazyRequireGetter(this, "extend",
-  "sdk/util/object", true);
-
-loader.lazyRequireGetter(this, "Actors",
-  "devtools/shared/performance/legacy/actors");
-loader.lazyRequireGetter(this, "LegacyPerformanceRecording",
-  "devtools/shared/performance/legacy/recording", true);
-loader.lazyRequireGetter(this, "importRecording",
-  "devtools/shared/performance/legacy/recording", true);
-loader.lazyRequireGetter(this, "normalizePerformanceFeatures",
-  "devtools/shared/performance/utils", true);
-loader.lazyRequireGetter(this, "DevToolsUtils",
-  "devtools/shared/DevToolsUtils");
-loader.lazyRequireGetter(this, "getDeviceFront",
-  "devtools/server/actors/device", true);
-loader.lazyRequireGetter(this, "getSystemInfo",
-  "devtools/shared/system", true);
-loader.lazyRequireGetter(this, "events",
-  "sdk/event/core");
-loader.lazyRequireGetter(this, "EventTarget",
-  "sdk/event/target", true);
-loader.lazyRequireGetter(this, "Class",
-  "sdk/core/heritage", true);
-
-/**
- * A connection to underlying actors (profiler, framerate, etc.)
- * shared by all tools in a target.
- */
-const LegacyPerformanceFront = Class({
-  extends: EventTarget,
-
-  LEGACY_FRONT: true,
-
-  traits: {
-    features: {
-      withMarkers: true,
-      withTicks: true,
-      withMemory: false,
-      withAllocations: false,
-      withJITOptimizations: false,
-    },
-  },
-
-  initialize: function (target) {
-    let { form, client } = target;
-    this._target = target;
-    this._form = form;
-    this._client = client;
-    this._pendingConsoleRecordings = [];
-    this._sitesPullTimeout = 0;
-    this._recordings = [];
-
-    this._pipeToFront = this._pipeToFront.bind(this);
-    this._onTimelineData = this._onTimelineData.bind(this);
-    this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this);
-    this._onConsoleProfileStop = this._onConsoleProfileStop.bind(this);
-    this._onProfilerStatus = this._onProfilerStatus.bind(this);
-    this._onProfilerUnexpectedlyStopped = this._onProfilerUnexpectedlyStopped.bind(this);
-  },
-
-  /**
-   * Initializes a connection to the profiler and other miscellaneous actors.
-   * If in the process of opening, or already open, nothing happens.
-   *
-   * @return object
-   *         A promise that is resolved once the connection is established.
-   */
-  connect: Task.async(function*() {
-    if (this._connecting) {
-      return this._connecting.promise;
-    }
-
-    // Create a promise that gets resolved upon connecting, so that
-    // other attempts to open the connection use the same resolution promise
-    this._connecting = promise.defer();
-
-    // Sets `this._profiler`, `this._timeline`.
-    // Only initialize the timeline fronts if the respective actors
-    // are available. Older Gecko versions don't have existing implementations,
-    // in which case all the methods we need can be easily mocked.
-    yield this._connectActors();
-    yield this._registerListeners();
-
-    this._connecting.resolve();
-  }),
-
-  /**
-   * Destroys this connection.
-   */
-  destroy: Task.async(function*() {
-    if (this._connecting) {
-      yield this._connecting.promise;
-    } else {
-      return;
-    }
-
-    yield this._unregisterListeners();
-    yield this._disconnectActors();
-
-    this._connecting = null;
-    this._profiler = null;
-    this._timeline = null;
-    this._client = null;
-    this._form = null;
-    this._target = this._target;
-  }),
-
-  /**
-   * Initializes fronts and connects to the underlying actors using the facades
-   * found in ./actors.js.
-   */
-  _connectActors: Task.async(function*() {
-    this._profiler = new Actors.LegacyProfilerFront(this._target);
-    this._timeline = new Actors.LegacyTimelineFront(this._target);
-
-    yield promise.all([
-      this._profiler.connect(),
-      this._timeline.connect()
-    ]);
-
-    // If mocked timeline, update the traits
-    this.traits.features.withMarkers = !this._timeline.IS_MOCK;
-    this.traits.features.withTicks = !this._timeline.IS_MOCK;
-  }),
-
-  /**
-   * Registers listeners on events from the underlying
-   * actors, so the connection can handle them.
-   */
-  _registerListeners: function () {
-    this._timeline.on("timeline-data", this._onTimelineData);
-    this._profiler.on("console-profile-start", this._onConsoleProfileStart);
-    this._profiler.on("console-profile-stop", this._onConsoleProfileStop);
-    this._profiler.on("profiler-stopped", this._onProfilerUnexpectedlyStopped);
-    this._profiler.on("profiler-status", this._onProfilerStatus);
-  },
-
-  /**
-   * Unregisters listeners on events on the underlying actors.
-   */
-  _unregisterListeners: function () {
-    this._timeline.off("timeline-data", this._onTimelineData);
-    this._profiler.off("console-profile-start", this._onConsoleProfileStart);
-    this._profiler.off("console-profile-stop", this._onConsoleProfileStop);
-    this._profiler.off("profiler-stopped", this._onProfilerUnexpectedlyStopped);
-    this._profiler.off("profiler-status", this._onProfilerStatus);
-  },
-
-  /**
-   * Closes the connections to non-profiler actors.
-   */
-  _disconnectActors: Task.async(function* () {
-    yield promise.all([
-      this._profiler.destroy(),
-      this._timeline.destroy(),
-    ]);
-  }),
-
-  /**
-   * Invoked whenever `console.profile` is called.
-   *
-   * @param string profileLabel
-   *        The provided string argument if available; undefined otherwise.
-   * @param number currentTime
-   *        The time (in milliseconds) when the call was made, relative to when
-   *        the nsIProfiler module was started.
-   */
-  _onConsoleProfileStart: Task.async(function *(_, { profileLabel, currentTime: startTime }) {
-    let recordings = this._recordings;
-
-    // Abort if a profile with this label already exists.
-    if (recordings.find(e => e.getLabel() === profileLabel)) {
-      return;
-    }
-
-    events.emit(this, "console-profile-start");
-
-    yield this.startRecording(extend({}, getLegacyPerformanceRecordingPrefs(), {
-      console: true,
-      label: profileLabel
-    }));
-  }),
-
-  /**
-   * Invoked whenever `console.profileEnd` is called.
-   *
-   * @param string profileLabel
-   *        The provided string argument if available; undefined otherwise.
-   * @param number currentTime
-   *        The time (in milliseconds) when the call was made, relative to when
-   *        the nsIProfiler module was started.
-   */
-  _onConsoleProfileStop: Task.async(function *(_, data) {
-    // If no data, abort; can occur if profiler isn't running and we get a surprise
-    // call to console.profileEnd()
-    if (!data) {
-      return;
-    }
-    let { profileLabel, currentTime: endTime } = data;
-
-    let pending = this._recordings.filter(r => r.isConsole() && r.isRecording());
-    if (pending.length === 0) {
-      return;
-    }
-
-    let model;
-    // Try to find the corresponding `console.profile` call if
-    // a label was used in profileEnd(). If no matches, abort.
-    if (profileLabel) {
-      model = pending.find(e => e.getLabel() === profileLabel);
-    }
-    // If no label supplied, pop off the most recent pending console recording
-    else {
-      model = pending[pending.length - 1];
-    }
-
-    // If `profileEnd()` was called with a label, and there are no matching
-    // sessions, abort.
-    if (!model) {
-      Cu.reportError("console.profileEnd() called with label that does not match a recording.");
-      return;
-    }
-
-    yield this.stopRecording(model);
-  }),
-
- /**
-  * TODO handle bug 1144438
-  */
-  _onProfilerUnexpectedlyStopped: function () {
-    Cu.reportError("Profiler unexpectedly stopped.", arguments);
-  },
-
-  /**
-   * Called whenever there is timeline data of any of the following types:
-   * - markers
-   * - frames
-   * - ticks
-   *
-   * Populate our internal store of recordings for all currently recording sessions.
-   */
-  _onTimelineData: function (_, ...data) {
-    this._recordings.forEach(e => e._addTimelineData.apply(e, data));
-    events.emit(this, "timeline-data", ...data);
-  },
-
-  /**
-   * Called whenever the underlying profiler polls its current status.
-   */
-  _onProfilerStatus: function (_, data) {
-    // If no data emitted (whether from an older actor being destroyed
-    // from a previous test, or the server does not support it), just ignore.
-    if (!data || data.position === void 0) {
-      return;
-    }
-
-    this._currentBufferStatus = data;
-    events.emit(this, "profiler-status", data);
-  },
-
-  /**
-   * Begins a recording session
-   *
-   * @param object options
-   *        An options object to pass to the actors. Supported properties are
-   *        `withTicks`, `withMemory` and `withAllocations`, `probability`, and `maxLogLength`.
-   * @return object
-   *         A promise that is resolved once recording has started.
-   */
-  startRecording: Task.async(function*(options = {}) {
-    let model = new LegacyPerformanceRecording(normalizePerformanceFeatures(options, this.traits.features));
-
-    // All actors are started asynchronously over the remote debugging protocol.
-    // Get the corresponding start times from each one of them.
-    // The timeline actors are target-dependent, so start those as well,
-    // even though these are mocked in older Geckos (FF < 35)
-    let profilerStart = this._profiler.start(options);
-    let timelineStart = this._timeline.start(options);
-
-    let { startTime, position, generation, totalSize } = yield profilerStart;
-    let timelineStartTime = yield timelineStart;
-
-    let data = {
-      profilerStartTime: startTime, timelineStartTime,
-      generation, position, totalSize
-    };
-
-    // Signify to the model that the recording has started,
-    // populate with data and store the recording model here.
-    model._populate(data);
-    this._recordings.push(model);
-
-    events.emit(this, "recording-started", model);
-    return model;
-  }),
-
-  /**
-   * Manually ends the recording session for the corresponding LegacyPerformanceRecording.
-   *
-   * @param LegacyPerformanceRecording model
-   *        The corresponding LegacyPerformanceRecording that belongs to the recording session wished to stop.
-   * @return LegacyPerformanceRecording
-   *         Returns the same model, populated with the profiling data.
-   */
-  stopRecording: Task.async(function*(model) {
-    // If model isn't in the LegacyPerformanceFront internal store,
-    // then do nothing.
-    if (this._recordings.indexOf(model) === -1) {
-      return;
-    }
-
-    // Flag the recording as no longer recording, so that `model.isRecording()`
-    // is false. Do this before we fetch all the data, and then subsequently
-    // the recording can be considered "completed".
-    let endTime = Date.now();
-    model._onStoppingRecording(endTime);
-    events.emit(this, "recording-stopping", model);
-
-    // Currently there are two ways profiles stop recording. Either manually in the
-    // performance tool, or via console.profileEnd. Once a recording is done,
-    // we want to deliver the model to the performance tool (either as a return
-    // from the LegacyPerformanceFront or via `console-profile-stop` event) and then
-    // remove it from the internal store.
-    //
-    // In the case where a console.profile is generated via the console (so the tools are
-    // open), we initialize the Performance tool so it can listen to those events.
-    this._recordings.splice(this._recordings.indexOf(model), 1);
-
-    let config = model.getConfiguration();
-    let startTime = model._getProfilerStartTime();
-    let profilerData = yield this._profiler.getProfile({ startTime });
-    let timelineEndTime = Date.now();
-
-    // Only if there are no more sessions recording do we stop
-    // the underlying timeline actors. If we're still recording,
-    // juse use Date.now() for the timeline end times, as those
-    // are only used in tests.
-    if (!this.isRecording()) {
-      // This doesn't stop the profiler, just turns off polling for
-      // events, and also turns off events on timeline actors.
-      yield this._profiler.stop();
-      timelineEndTime = yield this._timeline.stop(config);
-    }
-
-    let systemDeferred = promise.defer();
-    this._client.listTabs(form => {
-      systemDeferred.resolve(getDeviceFront(this._client, form).getDescription());
-    });
-    let systemHost = yield systemDeferred.promise;
-    let systemClient = yield getSystemInfo();
-
-    // Set the results on the LegacyPerformanceRecording itself.
-    model._onStopRecording({
-      // Data available only at the end of a recording.
-      profile: profilerData.profile,
-
-      // End times for all the actors.
-      profilerEndTime: profilerData.currentTime,
-      timelineEndTime: timelineEndTime,
-      systemHost,
-      systemClient,
-    });
-
-    events.emit(this, "recording-stopped", model);
-    return model;
-  }),
-
-  /**
-   * Creates a recording object when given a nsILocalFile.
-   *
-   * @param {nsILocalFile} file
-   *        The file to import the data from.
-   * @return {Promise<LegacyPerformanceRecording>}
-   */
-  importRecording: function (file) {
-    return importRecording(file);
-  },
-
-  /**
-   * Checks all currently stored recording models and returns a boolean
-   * if there is a session currently being recorded.
-   *
-   * @return Boolean
-   */
-  isRecording: function () {
-    return this._recordings.some(recording => recording.isRecording());
-  },
-
-  /**
-   * Pass in a PerformanceRecording and get a normalized value from 0 to 1 of how much
-   * of this recording's lifetime remains without being overwritten.
-   *
-   * @param {PerformanceRecording} recording
-   * @return {number?}
-   */
-  getBufferUsageForRecording: function (recording) {
-    if (!recording.isRecording() || !this._currentBufferStatus) {
-      return null;
-    }
-    let { position: currentPosition, totalSize, generation: currentGeneration } = this._currentBufferStatus;
-    let { position: origPosition, generation: origGeneration } = recording.getStartingBufferStatus();
-
-    let normalizedCurrent = (totalSize * (currentGeneration - origGeneration)) + currentPosition;
-    let percent = (normalizedCurrent - origPosition) / totalSize;
-    return percent > 1 ? 1 : percent;
-  },
-
-  /**
-   * Returns the configurations set on underlying components, used in tests.
-   * Returns an object with `probability`, `maxLogLength` for allocations, and
-   * `entries` and `interval` for profiler.
-   *
-   * @return {object}
-   */
-  getConfiguration: Task.async(function *() {
-    let profilerConfig = yield this._request("profiler", "getStartOptions");
-    return profilerConfig;
-  }),
-
-  /**
-   * An event from an underlying actor that we just want
-   * to pipe to the front itself.
-   */
-  _pipeToFront: function (eventName, ...args) {
-    events.emit(this, eventName, ...args);
-  },
-
-  /**
-   * Helper method to interface with the underlying actors directly.
-   * Used only in tests.
-   */
-  _request: function (actorName, method, ...args) {
-    if (!DevToolsUtils.testing) {
-      throw new Error("LegacyPerformanceFront._request may only be used in tests.");
-    }
-    let actor = this[`_${actorName}`];
-    return actor[method].apply(actor, args);
-  },
-
-  /**
-   * Sets how often the "profiler-status" event should be emitted.
-   * Used in tests.
-   */
-  setProfilerStatusInterval: function (n) {
-    if (this._profiler._poller) {
-      this._profiler._poller._wait = n;
-    }
-    this._profiler._PROFILER_CHECK_TIMER = n;
-  },
-
-  toString: () => "[object LegacyPerformanceFront]"
-});
-
-/**
- * Creates an object of configurations based off of preferences for a LegacyPerformanceRecording.
- */
-function getLegacyPerformanceRecordingPrefs () {
-  return {
-    withMarkers: true,
-    withMemory: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
-    withTicks: Services.prefs.getBoolPref("devtools.performance.ui.enable-framerate"),
-    withAllocations: Services.prefs.getBoolPref("devtools.performance.ui.enable-allocations"),
-    withJITOptimizations: Services.prefs.getBoolPref("devtools.performance.ui.enable-jit-optimizations"),
-    allocationsSampleProbability: +Services.prefs.getCharPref("devtools.performance.memory.sample-probability"),
-    allocationsMaxLogLength: Services.prefs.getIntPref("devtools.performance.memory.max-log-length")
-  };
-}
-
-exports.LegacyPerformanceFront = LegacyPerformanceFront;
deleted file mode 100644
--- a/devtools/shared/performance/legacy/moz.build
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-DevToolsModules(
-  'actors.js',
-  'compatibility.js',
-  'front.js',
-  'recording.js',
-)
deleted file mode 100644
--- a/devtools/shared/performance/legacy/recording.js
+++ /dev/null
@@ -1,172 +0,0 @@
-/* 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";
-
-const { Cc, Ci, Cu, Cr } = require("chrome");
-const { Task } = require("resource://gre/modules/Task.jsm");
-
-loader.lazyRequireGetter(this, "PerformanceIO",
-  "devtools/shared/performance/io");
-loader.lazyRequireGetter(this, "RecordingUtils",
-  "devtools/shared/performance/utils");
-loader.lazyRequireGetter(this, "PerformanceRecordingCommon",
-  "devtools/shared/performance/recording-common", true);
-loader.lazyRequireGetter(this, "merge", "sdk/util/object", true);
-
-/**
- * Model for a wholistic profile, containing the duration, profiling data,
- * frames data, timeline (marker, tick, memory) data, and methods to mark
- * a recording as 'in progress' or 'finished'.
- */
-const LegacyPerformanceRecording = function (options={}) {
-  this._label = options.label || "";
-  this._console = options.console || false;
-
-  this._configuration = {
-    withMarkers: options.withMarkers || false,
-    withTicks: options.withTicks || false,
-    withMemory: options.withMemory || false,
-    withAllocations: options.withAllocations || false,
-    withJITOptimizations: options.withJITOptimizations || false,
-    allocationsSampleProbability: options.allocationsSampleProbability || 0,
-    allocationsMaxLogLength: options.allocationsMaxLogLength || 0,
-    bufferSize: options.bufferSize || 0,
-    sampleFrequency: options.sampleFrequency || 1
-  };
-};
-
-LegacyPerformanceRecording.prototype = merge({
-  _profilerStartTime: 0,
-  _timelineStartTime: 0,
-  _memoryStartTime: 0,
-
-  /**
-   * Saves the current recording to a file.
-   *
-   * @param nsILocalFile file
-   *        The file to stream the data into.
-   */
-  exportRecording: Task.async(function *(file) {
-    let recordingData = this.getAllData();
-    yield PerformanceIO.saveRecordingToFile(recordingData, file);
-  }),
-
-  /**
-   * Sets up the instance with data from the PerformanceFront when
-   * starting a recording. Should only be called by PerformanceFront.
-   */
-  _populate: function (info) {
-    // Times must come from the actor in order to be self-consistent.
-    // However, we also want to update the view with the elapsed time
-    // even when the actor is not generating data. To do this we get
-    // the local time and use it to compute a reasonable elapsed time.
-    this._localStartTime = Date.now();
-
-    this._profilerStartTime = info.profilerStartTime;
-    this._timelineStartTime = info.timelineStartTime;
-    this._memoryStartTime = info.memoryStartTime;
-    this._startingBufferStatus = {
-      position: info.position,
-      totalSize: info.totalSize,
-      generation: info.generation
-    };
-
-    this._recording = true;
-
-    this._systemHost = {};
-    this._systemClient = {};
-    this._markers = [];
-    this._frames = [];
-    this._memory = [];
-    this._ticks = [];
-    this._allocations = { sites: [], timestamps: [], frames: [], sizes: [] };
-  },
-
-  /**
-   * Called when the signal was sent to the front to no longer record more
-   * data, and begin fetching the data. There's some delay during fetching,
-   * even though the recording is stopped, the model is not yet completed until
-   * all the data is fetched.
-   */
-  _onStoppingRecording: function (endTime) {
-    this._duration = endTime - this._localStartTime;
-    this._recording = false;
-  },
-
-  /**
-   * Sets results available from stopping a recording from PerformanceFront.
-   * Should only be called by PerformanceFront.
-   */
-  _onStopRecording: Task.async(function *({ profilerEndTime, profile, systemClient, systemHost }) {
-    // Update the duration with the accurate profilerEndTime, so we don't have
-    // samples outside of the approximate duration set in `_onStoppingRecording`.
-    this._duration = profilerEndTime - this._profilerStartTime;
-    this._profile = profile;
-    this._completed = true;
-
-    // We filter out all samples that fall out of current profile's range
-    // since the profiler is continuously running. Because of this, sample
-    // times are not guaranteed to have a zero epoch, so offset the
-    // timestamps.
-    RecordingUtils.offsetSampleTimes(this._profile, this._profilerStartTime);
-
-    // Markers need to be sorted ascending by time, to be properly displayed
-    // in a waterfall view.
-    this._markers = this._markers.sort((a, b) => (a.start > b.start));
-
-    this._systemHost = systemHost;
-    this._systemClient = systemClient;
-  }),
-
-  /**
-   * Gets the profile's start time.
-   * @return number
-   */
-  _getProfilerStartTime: function () {
-    return this._profilerStartTime;
-  },
-
-  /**
-   * Fired whenever the PerformanceFront emits markers, memory or ticks.
-   */
-  _addTimelineData: function (eventName, ...data) {
-    // If this model isn't currently recording,
-    // ignore the timeline data.
-    if (!this.isRecording()) {
-      return;
-    }
-
-    let config = this.getConfiguration();
-
-    switch (eventName) {
-      // Accumulate timeline markers into an array. Furthermore, the timestamps
-      // do not have a zero epoch, so offset all of them by the start time.
-      case "markers": {
-        if (!config.withMarkers) { break; }
-        let [markers] = data;
-        RecordingUtils.offsetMarkerTimes(markers, this._timelineStartTime);
-        RecordingUtils.pushAll(this._markers, markers);
-        break;
-      }
-      // Accumulate stack frames into an array.
-      case "frames": {
-        if (!config.withMarkers) { break; }
-        let [, frames] = data;
-        RecordingUtils.pushAll(this._frames, frames);
-        break;
-      }
-      // Save the accumulated refresh driver ticks.
-      case "ticks": {
-        if (!config.withTicks) { break; }
-        let [, timestamps] = data;
-        this._ticks = timestamps;
-        break;
-      }
-    }
-  },
-
-  toString: () => "[object LegacyPerformanceRecording]"
-}, PerformanceRecordingCommon);
-
-exports.LegacyPerformanceRecording = LegacyPerformanceRecording;
--- a/devtools/shared/performance/moz.build
+++ b/devtools/shared/performance/moz.build
@@ -1,19 +1,13 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-DIRS += [
-    'legacy',
-]
-
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
 
 DevToolsModules(
-  'io.js',
-  'process-communication.js',
-  'recorder.js',
-  'recording-common.js',
-  'utils.js',
+    'process-communication.js',
+    'recording-common.js',
+    'recording-utils.js',
 )
deleted file mode 100644
--- a/devtools/shared/performance/recorder.js
+++ /dev/null
@@ -1,490 +0,0 @@
-/* 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";
-
-const { Cc, Ci, Cu, Cr } = require("chrome");
-const { Task } = require("resource://gre/modules/Task.jsm");
-
-loader.lazyRequireGetter(this, "Services");
-loader.lazyRequireGetter(this, "promise");
-loader.lazyRequireGetter(this, "extend",
-  "sdk/util/object", true);
-loader.lazyRequireGetter(this, "Class",
-  "sdk/core/heritage", true);
-loader.lazyRequireGetter(this, "EventTarget",
-  "sdk/event/target", true);
-loader.lazyRequireGetter(this, "events",
-  "sdk/event/core");
-
-loader.lazyRequireGetter(this, "Memory",
-  "devtools/shared/shared/memory", true);
-loader.lazyRequireGetter(this, "Timeline",
-  "devtools/shared/shared/timeline", true);
-loader.lazyRequireGetter(this, "Profiler",
-  "devtools/shared/shared/profiler", true);
-loader.lazyRequireGetter(this, "PerformanceRecordingActor",
-  "devtools/server/actors/performance-recording", true);
-loader.lazyRequireGetter(this, "PerformanceRecordingFront",
-  "devtools/server/actors/performance-recording", true);
-loader.lazyRequireGetter(this, "mapRecordingOptions",
-  "devtools/shared/performance/utils", true);
-loader.lazyRequireGetter(this, "DevToolsUtils",
-  "devtools/shared/DevToolsUtils");
-loader.lazyRequireGetter(this, "getSystemInfo",
-  "devtools/shared/system", true);
-
-const PROFILER_EVENTS = [
-  "console-api-profiler",
-  "profiler-started",
-  "profiler-stopped",
-  "profiler-status"
-];
-
-// Max time in milliseconds for the allocations event to occur, which will
-// occur on every GC, or at least as often as DRAIN_ALLOCATIONS_TIMEOUT.
-const DRAIN_ALLOCATIONS_TIMEOUT = 2000;
-
-/**
- * A connection to underlying actors (profiler, memory, framerate, etc.)
- * shared by all tools in a target.
- *
- * @param Target target
- *        The target owning this connection.
- */
-const PerformanceRecorder = exports.PerformanceRecorder = Class({
-  extends: EventTarget,
-
-  initialize: function (conn, tabActor) {
-    this.conn = conn;
-    this.tabActor = tabActor;
-
-    this._pendingConsoleRecordings = [];
-    this._recordings = [];
-
-    this._onTimelineData = this._onTimelineData.bind(this);
-    this._onProfilerEvent = this._onProfilerEvent.bind(this);
-  },
-
-  /**
-   * Initializes a connection to the profiler and other miscellaneous actors.
-   * If in the process of opening, or already open, nothing happens.
-   *
-   * @param {Object} options.systemClient
-   *        Metadata about the client's system to attach to the recording models.
-   *
-   * @return object
-   *         A promise that is resolved once the connection is established.
-   */
-  connect: function (options) {
-    if (this._connected) {
-      return;
-    }
-
-    // Sets `this._profiler`, `this._timeline` and `this._memory`.
-    // Only initialize the timeline and memory fronts if the respective actors
-    // are available. Older Gecko versions don't have existing implementations,
-    // in which case all the methods we need can be easily mocked.
-    this._connectComponents();
-    this._registerListeners();
-
-    this._systemClient = options.systemClient;
-
-    this._connected = true;
-  },
-
-  /**
-   * Destroys this connection.
-   */
-  destroy: function () {
-    this._unregisterListeners();
-    this._disconnectComponents();
-
-    this._connected = null;
-    this._profiler = null;
-    this._timeline = null;
-    this._memory = null;
-    this._target = null;
-    this._client = null;
-  },
-
-  /**
-   * Initializes fronts and connects to the underlying actors using the facades
-   * found in ./actors.js.
-   */
-  _connectComponents: function () {
-    this._profiler = new Profiler(this.tabActor);
-    this._memory = new Memory(this.tabActor);
-    this._timeline = new Timeline(this.tabActor);
-    this._profiler.registerEventNotifications({ events: PROFILER_EVENTS });
-  },
-
-  /**
-   * Registers listeners on events from the underlying
-   * actors, so the connection can handle them.
-   */
-  _registerListeners: function () {
-    this._timeline.on("*", this._onTimelineData);
-    this._memory.on("*", this._onTimelineData);
-    this._profiler.on("*", this._onProfilerEvent);
-  },
-
-  /**
-   * Unregisters listeners on events on the underlying actors.
-   */
-  _unregisterListeners: function () {
-    this._timeline.off("*", this._onTimelineData);
-    this._memory.off("*", this._onTimelineData);
-    this._profiler.off("*", this._onProfilerEvent);
-  },
-
-  /**
-   * Closes the connections to non-profiler actors.
-   */
-  _disconnectComponents: function () {
-    this._profiler.unregisterEventNotifications({ events: PROFILER_EVENTS });
-    this._profiler.destroy();
-    this._timeline.destroy();
-    this._memory.destroy();
-  },
-
-  _onProfilerEvent: function (topic, data) {
-    if (topic === "console-api-profiler") {
-      if (data.subject.action === "profile") {
-        this._onConsoleProfileStart(data.details);
-      } else if (data.subject.action === "profileEnd") {
-        this._onConsoleProfileEnd(data.details);
-      }
-    } else if (topic === "profiler-stopped") {
-      this._onProfilerUnexpectedlyStopped();
-    } else if (topic === "profiler-status") {
-      events.emit(this, "profiler-status", data);
-    }
-  },
-
-  /**
-   * Invoked whenever `console.profile` is called.
-   *
-   * @param string profileLabel
-   *        The provided string argument if available; undefined otherwise.
-   * @param number currentTime
-   *        The time (in milliseconds) when the call was made, relative to when
-   *        the nsIProfiler module was started.
-   */
-  _onConsoleProfileStart: Task.async(function *({ profileLabel, currentTime: startTime }) {
-    let recordings = this._recordings;
-
-    // Abort if a profile with this label already exists.
-    if (recordings.find(e => e.getLabel() === profileLabel)) {
-      return;
-    }
-
-    // Immediately emit this so the client can start setting things up,
-    // expecting a recording very soon.
-    events.emit(this, "console-profile-start");
-
-    let model = yield this.startRecording(extend({}, getPerformanceRecordingPrefs(), {
-      console: true,
-      label: profileLabel
-    }));
-  }),
-
-  /**
-   * Invoked whenever `console.profileEnd` is called.
-   *
-   * @param string profileLabel
-   *        The provided string argument if available; undefined otherwise.
-   * @param number currentTime
-   *        The time (in milliseconds) when the call was made, relative to when
-   *        the nsIProfiler module was started.
-   */
-  _onConsoleProfileEnd: Task.async(function *(data) {
-    // If no data, abort; can occur if profiler isn't running and we get a surprise
-    // call to console.profileEnd()
-    if (!data) {
-      return;
-    }
-    let { profileLabel, currentTime: endTime } = data;
-
-    let pending = this._recordings.filter(r => r.isConsole() && r.isRecording());
-    if (pending.length === 0) {
-      return;
-    }
-
-    let model;
-    // Try to find the corresponding `console.profile` call if
-    // a label was used in profileEnd(). If no matches, abort.
-    if (profileLabel) {
-      model = pending.find(e => e.getLabel() === profileLabel);
-    }
-    // If no label supplied, pop off the most recent pending console recording
-    else {
-      model = pending[pending.length - 1];
-    }
-
-    // If `profileEnd()` was called with a label, and there are no matching
-    // sessions, abort.
-    if (!model) {
-      Cu.reportError("console.profileEnd() called with label that does not match a recording.");
-      return;
-    }
-
-    yield this.stopRecording(model);
-  }),
-
- /**
-  * TODO handle bug 1144438
-  */
-  _onProfilerUnexpectedlyStopped: function () {
-    Cu.reportError("Profiler unexpectedly stopped.", arguments);
-  },
-
-  /**
-   * Called whenever there is timeline data of any of the following types:
-   * - markers
-   * - frames
-   * - memory
-   * - ticks
-   * - allocations
-   */
-  _onTimelineData: function (eventName, ...data) {
-    let eventData = Object.create(null);
-
-    switch (eventName) {
-      case "markers": {
-        eventData = { markers: data[0], endTime: data[1] };
-        break;
-      }
-      case "ticks": {
-        eventData = { delta: data[0], timestamps: data[1] };
-        break;
-      }
-      case "memory": {
-        eventData = { delta: data[0], measurement: data[1] };
-        break;
-      }
-      case "frames": {
-        eventData = { delta: data[0], frames: data[1] };
-        break;
-      }
-      case "allocations": {
-        eventData = data[0];
-        break;
-      }
-    }
-
-    // Filter by only recordings that are currently recording;
-    // TODO should filter by recordings that have realtimeMarkers enabled.
-    let activeRecordings = this._recordings.filter(r => r.isRecording());
-
-    if (activeRecordings.length) {
-      events.emit(this, "timeline-data", eventName, eventData, activeRecordings);
-    }
-  },
-
-  /**
-   * Checks whether or not recording is currently supported. At the moment,
-   * this is only influenced by private browsing mode and the profiler.
-   */
-  canCurrentlyRecord: function() {
-    let success = true;
-    let reasons = [];
-
-    if (!Profiler.canProfile()) {
-      success = false,
-      reasons.push("profiler-unavailable");
-    }
-
-    // Check other factors that will affect the possibility of successfully
-    // starting a recording here.
-
-    return { success, reasons };
-  },
-
-  /**
-   * Begins a recording session
-   *
-   * @param boolean options.withMarkers
-   * @param boolean options.withJITOptimizations
-   * @param boolean options.withTicks
-   * @param boolean options.withMemory
-   * @param boolean options.withAllocations
-   * @param boolean options.allocationsSampleProbability
-   * @param boolean options.allocationsMaxLogLength
-   * @param boolean options.bufferSize
-   * @param boolean options.sampleFrequency
-   * @param boolean options.console
-   * @param string options.label
-   * @param boolean options.realtimeMarkers
-   * @return object
-   *         A promise that is resolved once recording has started.
-   */
-  startRecording: Task.async(function*(options) {
-    let profilerStart, timelineStart, memoryStart;
-
-    profilerStart = Task.spawn(function *() {
-      let data = yield this._profiler.isActive();
-      if (data.isActive) {
-        return data;
-      }
-      let startData = yield this._profiler.start(mapRecordingOptions("profiler", options));
-
-      // If no current time is exposed from starting, set it to 0 -- this is an
-      // older Gecko that does not return its starting time, and uses an epoch based
-      // on the profiler's start time.
-      if (startData.currentTime == null) {
-        startData.currentTime = 0;
-      }
-      return startData;
-    }.bind(this));
-
-    // Timeline will almost always be on if using the DevTools, but using component
-    // independently could result in no timeline.
-    if (options.withMarkers || options.withTicks || options.withMemory) {
-      timelineStart = this._timeline.start(mapRecordingOptions("timeline", options));
-    }
-
-    if (options.withAllocations) {
-      if (this._memory.getState() === "detached") {
-        this._memory.attach();
-      }
-      memoryStart = this._memory.startRecordingAllocations(extend(mapRecordingOptions("memory", options), {
-        drainAllocationsTimeout: DRAIN_ALLOCATIONS_TIMEOUT
-      }));
-    }
-
-    let [profilerStartData, timelineStartData, memoryStartData] = yield promise.all([
-      profilerStart, timelineStart, memoryStart
-    ]);
-
-    let data = Object.create(null);
-    // Filter out start times that are not actually used (0 or undefined), and
-    // find the earliest time since all sources use same epoch.
-    let startTimes = [profilerStartData.currentTime, memoryStartData, timelineStartData].filter(Boolean);
-    data.startTime = Math.min(...startTimes);
-    data.position = profilerStartData.position;
-    data.generation = profilerStartData.generation;
-    data.totalSize = profilerStartData.totalSize;
-
-    data.systemClient = this._systemClient;
-    data.systemHost = yield getSystemInfo();
-
-    let model = new PerformanceRecordingActor(this.conn, options, data);
-    this._recordings.push(model);
-
-    events.emit(this, "recording-started", model);
-    return model;
-  }),
-
-  /**
-   * Manually ends the recording session for the corresponding PerformanceRecording.
-   *
-   * @param PerformanceRecording model
-   *        The corresponding PerformanceRecording that belongs to the recording session wished to stop.
-   * @return PerformanceRecording
-   *         Returns the same model, populated with the profiling data.
-   */
-  stopRecording: Task.async(function *(model) {
-    // If model isn't in the Recorder's internal store,
-    // then do nothing, like if this was a console.profileEnd
-    // from a different target.
-    if (this._recordings.indexOf(model) === -1) {
-      return model;
-    }
-
-    // Flag the recording as no longer recording, so that `model.isRecording()`
-    // is false. Do this before we fetch all the data, and then subsequently
-    // the recording can be considered "completed".
-    let endTime = Date.now();
-    events.emit(this, "recording-stopping", model);
-
-    // Currently there are two ways profiles stop recording. Either manually in the
-    // performance tool, or via console.profileEnd. Once a recording is done,
-    // we want to deliver the model to the performance tool (either as a return
-    // from the PerformanceFront or via `console-profile-stop` event) and then
-    // remove it from the internal store.
-    //
-    // In the case where a console.profile is generated via the console (so the tools are
-    // open), we initialize the Performance tool so it can listen to those events.
-    this._recordings.splice(this._recordings.indexOf(model), 1);
-
-    let startTime = model._startTime;
-    let profilerData = this._profiler.getProfile({ startTime });
-
-    // Only if there are no more sessions recording do we stop
-    // the underlying memory and timeline actors. If we're still recording,
-    // juse use Date.now() for the memory and timeline end times, as those
-    // are only used in tests.
-    if (!this.isRecording()) {
-      // Check to see if memory is recording, so we only stop recording
-      // if necessary (otherwise if the memory component is not attached, this will fail)
-      if (this._memory.isRecordingAllocations()) {
-        this._memory.stopRecordingAllocations();
-      }
-      this._timeline.stop();
-    }
-
-    let recordingData = {
-      // Data available only at the end of a recording.
-      profile: profilerData.profile,
-      // End times for all the actors.
-      duration: profilerData.currentTime - startTime,
-    };
-
-    events.emit(this, "recording-stopped", model, recordingData);
-    return model;
-  }),
-
-  /**
-   * Checks all currently stored recording handles and returns a boolean
-   * if there is a session currently being recorded.
-   *
-   * @return Boolean
-   */
-  isRecording: function () {
-    return this._recordings.some(h => h.isRecording());
-  },
-
-  /**
-   * Returns all current recordings.
-   */
-  getRecordings: function () {
-    return this._recordings;
-  },
-
-  /**
-   * Sets how often the "profiler-status" event should be emitted.
-   * Used in tests.
-   */
-  setProfilerStatusInterval: function (n) {
-    this._profiler.setProfilerStatusInterval(n);
-  },
-
-  /**
-   * Returns the configurations set on underlying components, used in tests.
-   * Returns an object with `probability`, `maxLogLength` for allocations, and
-   * `features`, `threadFilters`, `entries` and `interval` for profiler.
-   *
-   * @return {object}
-   */
-  getConfiguration: function () {
-    return extend({}, this._memory.getAllocationsSettings(), this._profiler.getStartOptions());
-  },
-
-  toString: () => "[object PerformanceRecorder]"
-});
-
-/**
- * Creates an object of configurations based off of preferences for a PerformanceRecording.
- */
-function getPerformanceRecordingPrefs () {
-  return {
-    withMarkers: true,
-    withMemory: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
-    withTicks: Services.prefs.getBoolPref("devtools.performance.ui.enable-framerate"),
-    withAllocations: Services.prefs.getBoolPref("devtools.performance.ui.enable-allocations"),
-    withJITOptimizations: Services.prefs.getBoolPref("devtools.performance.ui.enable-jit-optimizations"),
-    allocationsSampleProbability: +Services.prefs.getCharPref("devtools.performance.memory.sample-probability"),
-    allocationsMaxLogLength: Services.prefs.getIntPref("devtools.performance.memory.max-log-length")
-  };
-}
rename from devtools/shared/performance/utils.js
rename to devtools/shared/performance/recording-utils.js
--- a/devtools/shared/performance/test/test_perf-utils-allocations-to-samples.js
+++ b/devtools/shared/performance/test/test_perf-utils-allocations-to-samples.js
@@ -7,17 +7,17 @@
  * received from the profiler.
  */
 
 function run_test() {
   run_next_test();
 }
 
 add_task(function () {
-  const { getProfileThreadFromAllocations } = require("devtools/shared/performance/utils");
+  const { getProfileThreadFromAllocations } = require("devtools/shared/performance/recording-utils");
   let output = getProfileThreadFromAllocations(TEST_DATA);
   equal(output.toSource(), EXPECTED_OUTPUT.toSource(), "The output is correct.");
 });
 
 var TEST_DATA = {
   sites: [0, 0, 1, 2, 3],
   timestamps: [50, 100, 150, 200, 250],
   sizes: [0, 0, 100, 200, 300],