Merge f-t to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 12 Sep 2015 15:59:00 -0700
changeset 296491 68718290640b0ca195344fa5cc4c55fbc260e19e
parent 296485 0bdadeda24894e4bee22d50fc613f1482a34e3eb (current diff)
parent 296490 51a06ae4082e537d008b1f4d70b59ea2aa54f7e9 (diff)
child 296496 f7fcd1276a89af8b72e5a29848e35d035a8e5ef8
child 296504 3a21445ed124bfcabb21de30611e765374bb31f6
child 296524 ea98c139736ed8450252eb7d00d35a7b4c3e454d
push id962
push userjlund@mozilla.com
push dateFri, 04 Dec 2015 23:28:54 +0000
treeherdermozilla-release@23a2d286e80f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone43.0a1
first release with
nightly linux32
68718290640b / 43.0a1 / 20150913030204 / files
nightly linux64
68718290640b / 43.0a1 / 20150913030204 / files
nightly mac
68718290640b / 43.0a1 / 20150913030204 / files
nightly win32
68718290640b / 43.0a1 / 20150913030204 / files
nightly win64
68718290640b / 43.0a1 / 20150913030204 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge f-t to m-c, a=merge
--- a/addon-sdk/source/lib/node/os.js
+++ b/addon-sdk/source/lib/node/os.js
@@ -6,23 +6,37 @@
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { Cc, Ci } = require('chrome');
 const system = require('../sdk/system');
 const runtime = require('../sdk/system/runtime');
-const oscpu = Cc["@mozilla.org/network/protocol;1?name=http"]
-                 .getService(Ci.nsIHttpProtocolHandler).oscpu;
-const hostname = Cc["@mozilla.org/network/dns-service;1"]
-                 .getService(Ci.nsIDNSService).myHostName;
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 const isWindows = system.platform === 'win32';
 const endianness = ((new Uint32Array((new Uint8Array([1,2,3,4])).buffer))[0] === 0x04030201) ? 'LE' : 'BE';
 
+XPCOMUtils.defineLazyGetter(this, "oscpu", () => {
+  try {
+    return Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;
+  } catch (e) {
+    return "";
+  }
+});
+
+XPCOMUtils.defineLazyGetter(this, "hostname", () => {
+  try {
+    // On some platforms (Linux according to try), this service does not exist and fails.
+    return Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService).myHostName;
+  } catch (e) {
+    return "";
+  }
+});
+
 /**
  * Returns a path to a temp directory
  */
 exports.tmpdir = () => system.pathFor('TmpD');
 
 /**
  * Returns the endianness of the architecture: either 'LE' or 'BE'
  */
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/events.js
@@ -0,0 +1,106 @@
+/* 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";
+
+module.exports = {
+  // Fired by the PerformanceController and OptionsView when a pref changes.
+  PREF_CHANGED: "Performance:PrefChanged",
+
+  // Fired by the PerformanceController when the devtools theme changes.
+  THEME_CHANGED: "Performance:ThemeChanged",
+
+  // Emitted by the PerformanceView when the state (display mode) changes,
+  // for example when switching between "empty", "recording" or "recorded".
+  // This causes certain panels to be hidden or visible.
+  UI_STATE_CHANGED: "Performance:UI:StateChanged",
+
+  // Emitted by the PerformanceView on clear button click
+  UI_CLEAR_RECORDINGS: "Performance:UI:ClearRecordings",
+
+  // Emitted by the PerformanceView on record button click
+  UI_START_RECORDING: "Performance:UI:StartRecording",
+  UI_STOP_RECORDING: "Performance:UI:StopRecording",
+
+  // Emitted by the PerformanceView on import button click
+  UI_IMPORT_RECORDING: "Performance:UI:ImportRecording",
+  // Emitted by the RecordingsView on export button click
+  UI_EXPORT_RECORDING: "Performance:UI:ExportRecording",
+
+  // When a new recording is being tracked in the panel.
+  NEW_RECORDING: "Performance:NewRecording",
+
+  // When a recording is started or stopped or stopping via the PerformanceController
+  RECORDING_STATE_CHANGE: "Performance:RecordingStateChange",
+
+  // Emitted by the PerformanceController or RecordingView
+  // when a recording model is selected
+  RECORDING_SELECTED: "Performance:RecordingSelected",
+
+  // When recordings have been cleared out
+  RECORDINGS_CLEARED: "Performance:RecordingsCleared",
+
+  // When a recording is exported via the PerformanceController
+  RECORDING_EXPORTED: "Performance:RecordingExported",
+
+  // Emitted by the PerformanceController when a recording is imported.
+  // Unless you're interested in specifically imported recordings, like in tests
+  // or telemetry, you should probably use the normal RECORDING_STATE_CHANGE in the UI.
+  RECORDING_IMPORTED: "Performance:RecordingImported",
+
+  // When the front has updated information on the profiler's circular buffer
+  PROFILER_STATUS_UPDATED: "Performance:BufferUpdated",
+
+  // When the PerformanceView updates the display of the buffer status
+  UI_BUFFER_STATUS_UPDATED: "Performance:UI:BufferUpdated",
+
+  // Emitted by the OptimizationsListView when it renders new optimization
+  // data and clears the optimization data
+  OPTIMIZATIONS_RESET: "Performance:UI:OptimizationsReset",
+  OPTIMIZATIONS_RENDERED: "Performance:UI:OptimizationsRendered",
+
+  // Emitted by the OverviewView when more data has been rendered
+  OVERVIEW_RENDERED: "Performance:UI:OverviewRendered",
+  FRAMERATE_GRAPH_RENDERED: "Performance:UI:OverviewFramerateRendered",
+  MARKERS_GRAPH_RENDERED: "Performance:UI:OverviewMarkersRendered",
+  MEMORY_GRAPH_RENDERED: "Performance:UI:OverviewMemoryRendered",
+
+  // Emitted by the OverviewView when a range has been selected in the graphs
+  OVERVIEW_RANGE_SELECTED: "Performance:UI:OverviewRangeSelected",
+
+  // Emitted by the DetailsView when a subview is selected
+  DETAILS_VIEW_SELECTED: "Performance:UI:DetailsViewSelected",
+
+  // Emitted by the WaterfallView when it has been rendered
+  WATERFALL_RENDERED: "Performance:UI:WaterfallRendered",
+
+  // Emitted by the JsCallTreeView when a call tree has been rendered
+  JS_CALL_TREE_RENDERED: "Performance:UI:JsCallTreeRendered",
+
+  // Emitted by the JsFlameGraphView when it has been rendered
+  JS_FLAMEGRAPH_RENDERED: "Performance:UI:JsFlameGraphRendered",
+
+  // Emitted by the MemoryCallTreeView when a call tree has been rendered
+  MEMORY_CALL_TREE_RENDERED: "Performance:UI:MemoryCallTreeRendered",
+
+  // Emitted by the MemoryFlameGraphView when it has been rendered
+  MEMORY_FLAMEGRAPH_RENDERED: "Performance:UI:MemoryFlameGraphRendered",
+
+  // When a source is shown in the JavaScript Debugger at a specific location.
+  SOURCE_SHOWN_IN_JS_DEBUGGER: "Performance:UI:SourceShownInJsDebugger",
+  SOURCE_NOT_FOUND_IN_JS_DEBUGGER: "Performance:UI:SourceNotFoundInJsDebugger",
+
+  // These are short hands for the RECORDING_STATE_CHANGE event to make refactoring
+  // tests easier and in rare cases (telemetry). UI components should use
+  // RECORDING_STATE_CHANGE in almost all cases,
+  RECORDING_STARTED: "Performance:RecordingStarted",
+  RECORDING_WILL_STOP: "Performance:RecordingWillStop",
+  RECORDING_STOPPED: "Performance:RecordingStopped",
+
+  // Fired by the PerformanceController when `populateWithRecordings` is finished.
+  RECORDINGS_SEEDED: "Performance:RecordingsSeeded",
+
+  // Emitted by the PerformanceController when `PerformanceController.stopRecording()`
+  // is completed; used in tests, to know when a manual UI click is finished.
+  CONTROLLER_STOPPED_RECORDING: "Performance:Controller:StoppedRecording",
+};
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/modules/logic/telemetry.js
@@ -0,0 +1,122 @@
+/* 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";
+
+loader.lazyRequireGetter(this, "Telemetry",
+  "devtools/shared/telemetry");
+loader.lazyRequireGetter(this, "Services",
+  "resource://gre/modules/Services.jsm", true);
+loader.lazyRequireGetter(this, "DevToolsUtils",
+  "devtools/toolkit/DevToolsUtils");
+loader.lazyRequireGetter(this, "EVENTS",
+  "devtools/performance/events");
+
+const EVENT_MAP_FLAGS = new Map([
+  [EVENTS.RECORDING_IMPORTED, "DEVTOOLS_PERFTOOLS_RECORDING_IMPORT_FLAG"],
+  [EVENTS.RECORDING_EXPORTED, "DEVTOOLS_PERFTOOLS_RECORDING_EXPORT_FLAG"],
+]);
+
+const RECORDING_FEATURES = [
+  "withMarkers", "withTicks", "withMemory", "withAllocations", "withJITOptimizations"
+];
+
+const SELECTED_VIEW_HISTOGRAM_NAME = "DEVTOOLS_PERFTOOLS_SELECTED_VIEW_MS";
+
+function PerformanceTelemetry (emitter) {
+  this._emitter = emitter;
+  this._telemetry = new Telemetry();
+  this.onFlagEvent = this.onFlagEvent.bind(this);
+  this.onRecordingStopped = this.onRecordingStopped.bind(this);
+  this.onViewSelected = this.onViewSelected.bind(this);
+
+  for (let [event] of EVENT_MAP_FLAGS) {
+    this._emitter.on(event, this.onFlagEvent);
+  }
+
+  this._emitter.on(EVENTS.RECORDING_STOPPED, this.onRecordingStopped);
+  this._emitter.on(EVENTS.DETAILS_VIEW_SELECTED, this.onViewSelected);
+
+  if (DevToolsUtils.testing) {
+    this.recordLogs();
+  }
+}
+
+PerformanceTelemetry.prototype.destroy = function () {
+  if (this._previousView) {
+    this._telemetry.stopTimer(SELECTED_VIEW_HISTOGRAM_NAME, this._previousView);
+  }
+
+  this._telemetry.destroy();
+  for (let [event] of EVENT_MAP_FLAGS) {
+    this._emitter.off(event, this.onFlagEvent);
+  }
+  this._emitter.off(EVENTS.RECORDING_STOPPED, this.onRecordingStopped);
+  this._emitter.off(EVENTS.DETAILS_VIEW_SELECTED, this.onViewSelected);
+  this._emitter = null;
+};
+
+PerformanceTelemetry.prototype.onFlagEvent = function (eventName, ...data) {
+  this._telemetry.log(EVENT_MAP_FLAGS.get(eventName), true);
+};
+
+PerformanceTelemetry.prototype.onRecordingStopped = function (_, model) {
+  if (model.isConsole()) {
+    this._telemetry.log("DEVTOOLS_PERFTOOLS_CONSOLE_RECORDING_COUNT", true);
+  } else {
+    this._telemetry.log("DEVTOOLS_PERFTOOLS_RECORDING_COUNT", true);
+  }
+
+  this._telemetry.log("DEVTOOLS_PERFTOOLS_RECORDING_DURATION_MS", model.getDuration());
+
+  let config = model.getConfiguration();
+  for (let k in config) {
+    if (RECORDING_FEATURES.indexOf(k) !== -1) {
+      this._telemetry.logKeyed("DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", k, config[k]);
+    }
+  }
+};
+
+PerformanceTelemetry.prototype.onViewSelected = function (_, viewName) {
+  if (this._previousView) {
+    this._telemetry.stopTimer(SELECTED_VIEW_HISTOGRAM_NAME, this._previousView);
+  }
+  this._previousView = viewName;
+  this._telemetry.startTimer(SELECTED_VIEW_HISTOGRAM_NAME);
+};
+
+/**
+ * Utility to record histogram calls to this instance.
+ * Should only be used in testing mode; throws otherwise.
+ */
+PerformanceTelemetry.prototype.recordLogs = function () {
+  if (!DevToolsUtils.testing) {
+    throw new Error("Can only record telemetry logs in tests.");
+  }
+
+  let originalLog = this._telemetry.log;
+  let originalLogKeyed = this._telemetry.logKeyed;
+  this._log = {};
+
+  this._telemetry.log = (function (histo, data) {
+    let results = this._log[histo] = this._log[histo] || [];
+    results.push(data);
+    originalLog(histo, data);
+  }).bind(this);
+
+  this._telemetry.logKeyed = (function (histo, key, data) {
+    let results = this._log[histo] = this._log[histo] || [];
+    results.push([key, data]);
+    originalLogKeyed(histo, key, data);
+  }).bind(this);
+};
+
+PerformanceTelemetry.prototype.getLogs = function () {
+  if (!DevToolsUtils.testing) {
+    throw new Error("Can only get telemetry logs in tests.");
+  }
+
+  return this._log;
+};
+
+exports.PerformanceTelemetry = PerformanceTelemetry;
--- a/browser/devtools/performance/moz.build
+++ b/browser/devtools/performance/moz.build
@@ -1,18 +1,20 @@
 # 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/.
 
 EXTRA_JS_MODULES.devtools.performance += [
+    'events.js',
     'modules/global.js',
     'modules/logic/frame-utils.js',
     'modules/logic/jit.js',
     'modules/logic/marker-utils.js',
+    'modules/logic/telemetry.js',
     'modules/logic/tree-model.js',
     'modules/logic/waterfall-utils.js',
     'modules/markers.js',
     'modules/widgets/graphs.js',
     'modules/widgets/marker-details.js',
     'modules/widgets/marker-view.js',
     'modules/widgets/markers-overview.js',
     'modules/widgets/tree-view.js',
--- a/browser/devtools/performance/performance-controller.js
+++ b/browser/devtools/performance/performance-controller.js
@@ -4,29 +4,34 @@
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 const { loader, require } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 
 const { Task } = require("resource://gre/modules/Task.jsm");
 const { Heritage, ViewHelpers, WidgetMethods } = require("resource:///modules/devtools/ViewHelpers.jsm");
 
+// Events emitted by various objects in the panel.
+const EVENTS = require("devtools/performance/events");
+
 loader.lazyRequireGetter(this, "Services");
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
 loader.lazyRequireGetter(this, "DevToolsUtils",
   "devtools/toolkit/DevToolsUtils");
 loader.lazyRequireGetter(this, "system",
   "devtools/toolkit/shared/system");
 
 // Logic modules
 
 loader.lazyRequireGetter(this, "L10N",
   "devtools/performance/global", true);
+loader.lazyRequireGetter(this, "PerformanceTelemetry",
+  "devtools/performance/telemetry", true);
 loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
   "devtools/performance/markers", true);
 loader.lazyRequireGetter(this, "RecordingUtils",
   "devtools/toolkit/performance/utils");
 loader.lazyRequireGetter(this, "GraphsController",
   "devtools/performance/graphs", true);
 loader.lazyRequireGetter(this, "WaterfallHeader",
   "devtools/performance/waterfall-ticks", true);
@@ -64,118 +69,16 @@ loader.lazyImporter(this, "setNamedTimeo
   "resource:///modules/devtools/ViewHelpers.jsm");
 loader.lazyImporter(this, "clearNamedTimeout",
   "resource:///modules/devtools/ViewHelpers.jsm");
 loader.lazyImporter(this, "PluralForm",
   "resource://gre/modules/PluralForm.jsm");
 
 const BRANCH_NAME = "devtools.performance.ui.";
 
-// Events emitted by various objects in the panel.
-const EVENTS = {
-  // Fired by the PerformanceController and OptionsView when a pref changes.
-  PREF_CHANGED: "Performance:PrefChanged",
-
-  // Fired by the PerformanceController when the devtools theme changes.
-  THEME_CHANGED: "Performance:ThemeChanged",
-
-  // Emitted by the PerformanceView when the state (display mode) changes,
-  // for example when switching between "empty", "recording" or "recorded".
-  // This causes certain panels to be hidden or visible.
-  UI_STATE_CHANGED: "Performance:UI:StateChanged",
-
-  // Emitted by the PerformanceView on clear button click
-  UI_CLEAR_RECORDINGS: "Performance:UI:ClearRecordings",
-
-  // Emitted by the PerformanceView on record button click
-  UI_START_RECORDING: "Performance:UI:StartRecording",
-  UI_STOP_RECORDING: "Performance:UI:StopRecording",
-
-  // Emitted by the PerformanceView on import button click
-  UI_IMPORT_RECORDING: "Performance:UI:ImportRecording",
-  // Emitted by the RecordingsView on export button click
-  UI_EXPORT_RECORDING: "Performance:UI:ExportRecording",
-
-  // When a new recording is being tracked in the panel.
-  NEW_RECORDING: "Performance:NewRecording",
-
-  // When a recording is started or stopped or stopping via the PerformanceController
-  RECORDING_STATE_CHANGE: "Performance:RecordingStateChange",
-
-  // Emitted by the PerformanceController or RecordingView
-  // when a recording model is selected
-  RECORDING_SELECTED: "Performance:RecordingSelected",
-
-  // When recordings have been cleared out
-  RECORDINGS_CLEARED: "Performance:RecordingsCleared",
-
-  // When a recording is exported via the PerformanceController
-  RECORDING_EXPORTED: "Performance:RecordingExported",
-
-  // When the front has updated information on the profiler's circular buffer
-  PROFILER_STATUS_UPDATED: "Performance:BufferUpdated",
-
-  // When the PerformanceView updates the display of the buffer status
-  UI_BUFFER_STATUS_UPDATED: "Performance:UI:BufferUpdated",
-
-  // Emitted by the OptimizationsListView when it renders new optimization
-  // data and clears the optimization data
-  OPTIMIZATIONS_RESET: "Performance:UI:OptimizationsReset",
-  OPTIMIZATIONS_RENDERED: "Performance:UI:OptimizationsRendered",
-
-  // Emitted by the OverviewView when more data has been rendered
-  OVERVIEW_RENDERED: "Performance:UI:OverviewRendered",
-  FRAMERATE_GRAPH_RENDERED: "Performance:UI:OverviewFramerateRendered",
-  MARKERS_GRAPH_RENDERED: "Performance:UI:OverviewMarkersRendered",
-  MEMORY_GRAPH_RENDERED: "Performance:UI:OverviewMemoryRendered",
-
-  // Emitted by the OverviewView when a range has been selected in the graphs
-  OVERVIEW_RANGE_SELECTED: "Performance:UI:OverviewRangeSelected",
-
-  // Emitted by the DetailsView when a subview is selected
-  DETAILS_VIEW_SELECTED: "Performance:UI:DetailsViewSelected",
-
-  // Emitted by the WaterfallView when it has been rendered
-  WATERFALL_RENDERED: "Performance:UI:WaterfallRendered",
-
-  // Emitted by the JsCallTreeView when a call tree has been rendered
-  JS_CALL_TREE_RENDERED: "Performance:UI:JsCallTreeRendered",
-
-  // Emitted by the JsFlameGraphView when it has been rendered
-  JS_FLAMEGRAPH_RENDERED: "Performance:UI:JsFlameGraphRendered",
-
-  // Emitted by the MemoryCallTreeView when a call tree has been rendered
-  MEMORY_CALL_TREE_RENDERED: "Performance:UI:MemoryCallTreeRendered",
-
-  // Emitted by the MemoryFlameGraphView when it has been rendered
-  MEMORY_FLAMEGRAPH_RENDERED: "Performance:UI:MemoryFlameGraphRendered",
-
-  // When a source is shown in the JavaScript Debugger at a specific location.
-  SOURCE_SHOWN_IN_JS_DEBUGGER: "Performance:UI:SourceShownInJsDebugger",
-  SOURCE_NOT_FOUND_IN_JS_DEBUGGER: "Performance:UI:SourceNotFoundInJsDebugger",
-
-  // These are short hands for the RECORDING_STATE_CHANGE event to make refactoring
-  // tests easier. UI components should use RECORDING_STATE_CHANGE, and these are
-  // deprecated for test usage only.
-  RECORDING_STARTED: "Performance:RecordingStarted",
-  RECORDING_WILL_STOP: "Performance:RecordingWillStop",
-  RECORDING_STOPPED: "Performance:RecordingStopped",
-
-  // Fired by the PerformanceController when `populateWithRecordings` is finished.
-  RECORDINGS_SEEDED: "Performance:RecordingsSeeded",
-
-  // Emitted by the PerformanceController when `PerformanceController.stopRecording()`
-  // is completed; used in tests, to know when a manual UI click is finished.
-  CONTROLLER_STOPPED_RECORDING: "Performance:Controller:StoppedRecording",
-
-  // Emitted by the PerformanceController when a recording is imported. Used
-  // only in tests. Should use the normal RECORDING_STATE_CHANGE in the UI.
-  RECORDING_IMPORTED: "Performance:ImportedRecording",
-};
-
 /**
  * The current target, toolbox and PerformanceFront, set by this tool's host.
  */
 let gToolbox, gTarget, gFront;
 
 /**
  * Initializes the profiler controller and views.
  */
@@ -200,59 +103,64 @@ let PerformanceController = {
   _recordings: [],
   _currentRecording: null,
 
   /**
    * Listen for events emitted by the current tab target and
    * main UI events.
    */
   initialize: Task.async(function* () {
+    this._telemetry = new PerformanceTelemetry(this);
     this.startRecording = this.startRecording.bind(this);
     this.stopRecording = this.stopRecording.bind(this);
     this.importRecording = this.importRecording.bind(this);
     this.exportRecording = this.exportRecording.bind(this);
     this.clearRecordings = this.clearRecordings.bind(this);
     this._onRecordingSelectFromView = this._onRecordingSelectFromView.bind(this);
     this._onPrefChanged = this._onPrefChanged.bind(this);
     this._onThemeChanged = this._onThemeChanged.bind(this);
     this._onFrontEvent = this._onFrontEvent.bind(this);
+    this._pipe = this._pipe.bind(this);
 
     // Store data regarding if e10s is enabled.
     this._e10s = Services.appinfo.browserTabsRemoteAutostart;
     this._setMultiprocessAttributes();
 
     this._prefs = require("devtools/performance/global").PREFS;
     this._prefs.on("pref-changed", this._onPrefChanged);
 
     gFront.on("*", this._onFrontEvent);
     ToolbarView.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
     PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
     PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
     PerformanceView.on(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
     PerformanceView.on(EVENTS.UI_CLEAR_RECORDINGS, this.clearRecordings);
     RecordingsView.on(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
     RecordingsView.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
+    DetailsView.on(EVENTS.DETAILS_VIEW_SELECTED, this._pipe);
 
     gDevTools.on("pref-changed", this._onThemeChanged);
   }),
 
   /**
    * Remove events handled by the PerformanceController
    */
   destroy: function() {
+    this._telemetry.destroy();
     this._prefs.off("pref-changed", this._onPrefChanged);
 
     gFront.off("*", this._onFrontEvent);
     ToolbarView.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
     PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
     PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
     PerformanceView.off(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
     PerformanceView.off(EVENTS.UI_CLEAR_RECORDINGS, this.clearRecordings);
     RecordingsView.off(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
     RecordingsView.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
+    DetailsView.off(EVENTS.DETAILS_VIEW_SELECTED, this._pipe);
 
     gDevTools.off("pref-changed", this._onThemeChanged);
   },
 
   /**
    * Returns the current devtools theme.
    */
   getTheme: function () {
@@ -367,22 +275,17 @@ let PerformanceController = {
    *
    * @param nsILocalFile file
    *        The file to import the data from.
    */
   importRecording: Task.async(function*(_, file) {
     let recording = yield gFront.importRecording(file);
     this._addNewRecording(recording);
 
-    // Only emit in tests for legacy purposes for shorthand --
-    // other things in UI should handle the generic NEW_RECORDING
-    // event to handle lazy recordings.
-    if (DevToolsUtils.testing) {
-      this.emit(EVENTS.RECORDING_IMPORTED, recording);
-    }
+    this.emit(EVENTS.RECORDING_IMPORTED, recording);
   }),
 
   /**
    * Sets the currently active PerformanceRecording. Should rarely be called directly,
    * as RecordingsView handles this when manually selected a recording item. Exceptions
    * are when clearing the view.
    * @param PerformanceRecording recording
    */
@@ -486,31 +389,29 @@ let PerformanceController = {
    */
   _onRecordingStateChange: function (state, model) {
     this._addNewRecording(model);
 
     this.emit(EVENTS.RECORDING_STATE_CHANGE, state, model);
 
     // Emit the state specific events for tests that I'm too
     // lazy and frusterated to change right now. These events
-    // should only be used in tests, as the rest of the UI should
-    // react to general RECORDING_STATE_CHANGE events and NEW_RECORDING
-    // events to handle lazy recordings.
-    if (DevToolsUtils.testing) {
-      switch (state) {
-        case "recording-started":
-          this.emit(EVENTS.RECORDING_STARTED, model);
-          break;
-        case "recording-stopping":
-          this.emit(EVENTS.RECORDING_WILL_STOP, model);
-          break;
-        case "recording-stopped":
-          this.emit(EVENTS.RECORDING_STOPPED, model);
-          break;
-      }
+    // should only be used in tests and specific rare cases (telemetry),
+    // as the rest of the UI should react to general RECORDING_STATE_CHANGE
+    // events and NEW_RECORDING events to handle lazy recordings.
+    switch (state) {
+      case "recording-started":
+        this.emit(EVENTS.RECORDING_STARTED, model);
+        break;
+      case "recording-stopping":
+        this.emit(EVENTS.RECORDING_WILL_STOP, model);
+        break;
+      case "recording-stopped":
+        this.emit(EVENTS.RECORDING_STOPPED, model);
+        break;
     }
   },
 
   /**
    * Takes a recording and returns a value between 0 and 1 indicating how much
    * of the buffer is used.
    */
   getBufferUsageForRecording: function (recording) {
@@ -630,16 +531,23 @@ let PerformanceController = {
       $("#performance-view").setAttribute("e10s", "disabled");
     }
     // Could be a chance where the directive goes away yet e10s is still on
     else if (!enabled && !supported) {
       $("#performance-view").setAttribute("e10s", "unsupported");
     }
   },
 
+  /**
+   * Pipes an event from some source to the PerformanceController.
+   */
+  _pipe: function (eventName, ...data) {
+    this.emit(eventName, ...data);
+  },
+
   toString: () => "[object PerformanceController]"
 };
 
 /**
  * Convenient way of emitting events from the controller.
  */
 EventEmitter.decorate(PerformanceController);
 
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -77,17 +77,17 @@ skip-if = os == 'linux' # Bug 1172120
 [browser_perf-overview-render-03.js]
 [browser_perf-overview-render-04.js]
 skip-if = os == 'linux' # bug 1186322
 [browser_perf-overview-selection-01.js]
 [browser_perf-overview-selection-02.js]
 [browser_perf-overview-selection-03.js]
 [browser_perf-overview-time-interval.js]
 [browser_perf-states.js]
-skip-if = os == 'linux' # bug 1203888
+skip-if = debug # bug 1203888
 [browser_perf-refresh.js]
 [browser_perf-ui-recording.js]
 skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-notices-01.js]
 skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-notices-02.js]
 skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-notices-03.js]
@@ -114,16 +114,17 @@ skip-if = os == 'linux' # bug 1186322
 skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-selected-02.js]
 skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-selected-03.js]
 skip-if = os == 'linux' # bug 1186322
 [browser_perf-recording-selected-04.js]
 skip-if = os == 'linux' || debug # bug 1186322 for Linux, bug 1203895 for leaks
 [browser_perf-theme-toggle-01.js]
+[browser_perf-telemetry.js]
 [browser_profiler_tree-abstract-01.js]
 [browser_profiler_tree-abstract-02.js]
 [browser_profiler_tree-abstract-03.js]
 [browser_profiler_tree-abstract-04.js]
 [browser_profiler_tree-view-01.js]
 [browser_profiler_tree-view-02.js]
 [browser_profiler_tree-view-03.js]
 [browser_profiler_tree-view-04.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-telemetry.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the performance telemetry module records events at appropriate times.
+ */
+
+function* spawnTest() {
+  PMM_loadFrameScripts(gBrowser);
+  let { panel } = yield initPerformance(SIMPLE_URL);
+  let { EVENTS, PerformanceController, OverviewView, DetailsView, WaterfallView, JsCallTreeView, JsFlameGraphView } = panel.panelWin;
+
+  Services.prefs.setBoolPref(MEMORY_PREF, false);
+  let DURATION = "DEVTOOLS_PERFTOOLS_RECORDING_DURATION_MS";
+  let COUNT = "DEVTOOLS_PERFTOOLS_RECORDING_COUNT";
+  let CONSOLE_COUNT = "DEVTOOLS_PERFTOOLS_CONSOLE_RECORDING_COUNT";
+  let FEATURES = "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED";
+  let VIEWS = "DEVTOOLS_PERFTOOLS_SELECTED_VIEW_MS";
+  let EXPORTED = "DEVTOOLS_PERFTOOLS_RECORDING_EXPORT_FLAG";
+  let IMPORTED = "DEVTOOLS_PERFTOOLS_RECORDING_IMPORT_FLAG";
+
+  let telemetry = PerformanceController._telemetry;
+  let logs = telemetry.getLogs();
+
+  yield startRecording(panel);
+  yield stopRecording(panel);
+
+  Services.prefs.setBoolPref(MEMORY_PREF, true);
+
+  yield startRecording(panel);
+  yield stopRecording(panel);
+
+  is(logs[DURATION].length, 2, `two entry for ${DURATION}`);
+  ok(logs[DURATION].every(d => typeof d === "number"), `every ${DURATION} entry is a number`);
+  is(logs[COUNT].length, 2, `two entry for ${COUNT}`);
+  is(logs[CONSOLE_COUNT], void 0, `no entries for ${CONSOLE_COUNT}`);
+  is(logs[FEATURES].length, 10, `two recordings worth of entries for ${FEATURES}`);
+
+  ok(logs[FEATURES].find(r => r[0] === "withMemory" && r[1] === true), "one feature entry for memory enabled");
+  ok(logs[FEATURES].find(r => r[0] === "withMemory" && r[1] === false), "one feature entry for memory disabled");
+
+  let calltreeRendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
+  let flamegraphRendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
+
+  // Go through some views to check later
+  DetailsView.selectView("js-calltree");
+  yield calltreeRendered;
+  DetailsView.selectView("js-flamegraph");
+  yield flamegraphRendered;
+
+  let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
+  file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+  let exported = once(PerformanceController, EVENTS.RECORDING_EXPORTED);
+  yield PerformanceController.exportRecording("", PerformanceController.getCurrentRecording(), file);
+  yield exported;
+
+  ok(logs[EXPORTED], `a telemetry entry for ${EXPORTED} exists after exporting`);
+
+  let imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
+  yield PerformanceController.importRecording(null, file);
+  yield imported;
+
+  ok(logs[IMPORTED], `a telemetry entry for ${IMPORTED} exists after importing`);
+
+  yield consoleProfile(panel.panelWin, "rust");
+  yield consoleProfileEnd(panel.panelWin, "rust");
+
+  info("Performed a console recording.");
+
+  is(logs[DURATION].length, 3, `three entry for ${DURATION}`);
+  ok(logs[DURATION].every(d => typeof d === "number"), `every ${DURATION} entry is a number`);
+  is(logs[COUNT].length, 2, `two entry for ${COUNT}`);
+  is(logs[CONSOLE_COUNT].length, 1, `one entry for ${CONSOLE_COUNT}`);
+  is(logs[FEATURES].length, 15, `two recordings worth of entries for ${FEATURES}`);
+
+  yield teardown(panel);
+
+  // Check views after destruction to ensure `js-flamegraph` gets called with a time
+  // during destruction
+  ok(logs[VIEWS].find(r => r[0] === "waterfall" && typeof r[1] === "number"), `${VIEWS} for waterfall view and time.`);
+  ok(logs[VIEWS].find(r => r[0] === "js-calltree" && typeof r[1] === "number"), `${VIEWS} for js-calltree view and time.`);
+  ok(logs[VIEWS].find(r => r[0] === "js-flamegraph" && typeof r[1] === "number"), `${VIEWS} for js-flamegraph view and time.`);
+
+  finish();
+};
--- a/browser/devtools/shared/telemetry.js
+++ b/browser/devtools/shared/telemetry.js
@@ -271,22 +271,28 @@ Telemetry.prototype = {
     this._timers.set(histogramId, new Date());
   },
 
   /**
    * Stop the timer and log elasped time for a timing-based histogram entry.
    *
    * @param String histogramId
    *        Histogram in which the data is to be stored.
+   * @param String key [optional]
+   *        Optional key for a keyed histogram.
    */
-  stopTimer: function(histogramId) {
+  stopTimer: function(histogramId, key) {
     let startTime = this._timers.get(histogramId);
     if (startTime) {
       let time = (new Date() - startTime) / 1000;
-      this.log(histogramId, time);
+      if (!key) {
+        this.log(histogramId, time);
+      } else {
+        this.logKeyed(histogramId, key, time);
+      }
       this._timers.delete(histogramId);
     }
   },
 
   /**
    * Log a value to a histogram.
    *
    * @param  {String} histogramId
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -7271,16 +7271,57 @@
   },
   "DEVTOOLS_HEAP_SNAPSHOT_EDGE_COUNT": {
     "expires_in_version": "never",
     "kind": "linear",
     "high": "10000000",
     "n_buckets": "10000",
     "description": "The number of edges serialized into a heap snapshot."
   },
+  "DEVTOOLS_PERFTOOLS_RECORDING_COUNT": {
+    "expires_in_version": "never",
+    "kind": "count",
+    "description": "Incremented whenever a performance tool recording is completed."
+  },
+  "DEVTOOLS_PERFTOOLS_CONSOLE_RECORDING_COUNT": {
+    "expires_in_version": "never",
+    "kind": "count",
+    "description": "Incremented whenever a performance tool recording is completed that was initiated via console.profile."
+  },
+  "DEVTOOLS_PERFTOOLS_RECORDING_IMPORT_FLAG": {
+    "expires_in_version": "never",
+    "kind": "flag",
+    "description": "When a user imports a recording in the performance tool."
+  },
+  "DEVTOOLS_PERFTOOLS_RECORDING_EXPORT_FLAG": {
+    "expires_in_version": "never",
+    "kind": "flag",
+    "description": "When a user imports a recording in the performance tool."
+  },
+  "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "keyed": true,
+    "description": "When a user starts a recording with specific recording options, keyed by feature name (withMarkers, withAllocations, etc.)."
+  },
+  "DEVTOOLS_PERFTOOLS_RECORDING_DURATION_MS": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "600000",
+    "n_buckets": 20,
+    "description": "The length of a duration in MS of a performance tool recording."
+  },
+  "DEVTOOLS_PERFTOOLS_SELECTED_VIEW_MS": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "keyed": true,
+    "high": "600000",
+    "n_buckets": 20,
+    "description": "The amount of time spent in a specific performance tool view, keyed by view name (waterfall, js-calltree, js-flamegraph, etc)."
+  },
   "BROWSER_IS_USER_DEFAULT": {
     "expires_in_version": "never",
     "kind": "boolean",
     "releaseChannelCollection": "opt-out",
     "description": "The result of the startup default desktop browser check."
   },
   "BROWSER_IS_USER_DEFAULT_ERROR": {
     "expires_in_version": "never",
--- a/toolkit/devtools/performance/io.js
+++ b/toolkit/devtools/performance/io.js
@@ -148,17 +148,19 @@ function convertLegacyData (legacyData) 
     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/toolkit/devtools/performance/legacy/front.js
+++ b/toolkit/devtools/performance/legacy/front.js
@@ -16,16 +16,20 @@ loader.lazyRequireGetter(this, "Actors",
 loader.lazyRequireGetter(this, "LegacyPerformanceRecording",
   "devtools/toolkit/performance/legacy/recording", true);
 loader.lazyRequireGetter(this, "importRecording",
   "devtools/toolkit/performance/legacy/recording", true);
 loader.lazyRequireGetter(this, "normalizePerformanceFeatures",
   "devtools/toolkit/performance/utils", true);
 loader.lazyRequireGetter(this, "DevToolsUtils",
   "devtools/toolkit/DevToolsUtils");
+loader.lazyRequireGetter(this, "getDeviceFront",
+  "devtools/toolkit/server/actors/device", true);
+loader.lazyRequireGetter(this, "getSystemInfo",
+  "devtools/toolkit/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);
 
 /**
@@ -328,39 +332,48 @@ const LegacyPerformanceFront = Class({
     // 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 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
+      timelineEndTime: timelineEndTime,
+      systemHost,
+      systemClient,
     });
 
     events.emit(this, "recording-stopped", model);
     return model;
   }),
 
   /**
    * Creates a recording object when given a nsILocalFile.
--- a/toolkit/devtools/performance/legacy/recording.js
+++ b/toolkit/devtools/performance/legacy/recording.js
@@ -5,16 +5,19 @@
 
 const { Cc, Ci, Cu, Cr } = require("chrome");
 const { Task } = require("resource://gre/modules/Task.jsm");
 
 loader.lazyRequireGetter(this, "PerformanceIO",
   "devtools/toolkit/performance/io");
 loader.lazyRequireGetter(this, "RecordingUtils",
   "devtools/toolkit/performance/utils");
+loader.lazyRequireGetter(this, "PerformanceRecordingCommon",
+  "devtools/toolkit/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 || "";
@@ -28,59 +31,20 @@ const LegacyPerformanceRecording = funct
     withJITOptimizations: options.withJITOptimizations || false,
     allocationsSampleProbability: options.allocationsSampleProbability || 0,
     allocationsMaxLogLength: options.allocationsMaxLogLength || 0,
     bufferSize: options.bufferSize || 0,
     sampleFrequency: options.sampleFrequency || 1
   };
 };
 
-LegacyPerformanceRecording.prototype = {
-  // Private fields, only needed when a recording is started or stopped.
-  _console: false,
-  _imported: false,
-  _recording: false,
-  _completed: false,
+LegacyPerformanceRecording.prototype = merge({
   _profilerStartTime: 0,
   _timelineStartTime: 0,
   _memoryStartTime: 0,
-  _configuration: {},
-  _startingBufferStatus: null,
-  _bufferPercent: null,
-
-  // Serializable fields, necessary and sufficient for import and export.
-  _label: "",
-  _duration: 0,
-  _markers: null,
-  _frames: null,
-  _memory: null,
-  _ticks: null,
-  _allocations: null,
-  _profile: null,
-
-  /**
-   * Loads a recording from a file.
-   *
-   * @param nsILocalFile file
-   *        The file to import the data form.
-   */
-  importRecording: Task.async(function *(file) {
-    let recordingData = yield PerformanceIO.loadRecordingFromFile(file);
-
-    this._imported = true;
-    this._label = recordingData.label || "";
-    this._duration = recordingData.duration;
-    this._markers = recordingData.markers;
-    this._frames = recordingData.frames;
-    this._memory = recordingData.memory;
-    this._ticks = recordingData.ticks;
-    this._allocations = recordingData.allocations;
-    this._profile = recordingData.profile;
-    this._configuration = recordingData.configuration || {};
-  }),
 
   /**
    * Saves the current recording to a file.
    *
    * @param nsILocalFile file
    *        The file to stream the data into.
    */
   exportRecording: Task.async(function *(file) {
@@ -102,21 +66,21 @@ LegacyPerformanceRecording.prototype = {
     this._profilerStartTime = info.profilerStartTime;
     this._timelineStartTime = info.timelineStartTime;
     this._memoryStartTime = info.memoryStartTime;
     this._startingBufferStatus = {
       position: info.position,
       totalSize: info.totalSize,
       generation: info.generation
     };
-    // initialize the _bufferPercent if the server supports it.
-    this._bufferPercent = info.position !== void 0 ? 0 : null;
 
     this._recording = true;
 
+    this._systemHost = {};
+    this._systemClient = {};
     this._markers = [];
     this._frames = [];
     this._memory = [];
     this._ticks = [];
     this._allocations = { sites: [], timestamps: [], frames: [], sizes: [] };
   },
 
   /**
@@ -129,191 +93,46 @@ LegacyPerformanceRecording.prototype = {
     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 }) {
+  _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 () {
+  _getProfilerStartTime: function () {
     return this._profilerStartTime;
   },
 
   /**
-   * Gets the profile's label, from `console.profile(LABEL)`.
-   * @return string
-   */
-  getLabel: function () {
-    return this._label;
-  },
-
-  /**
-   * Gets duration of this recording, in milliseconds.
-   * @return number
-   */
-  getDuration: function () {
-    // Compute an approximate ending time for the current recording if it is
-    // still in progress. This is needed to ensure that the view updates even
-    // when new data is not being generated.
-    if (this._recording) {
-      return Date.now() - this._localStartTime;
-    } else {
-      return this._duration;
-    }
-  },
-
-  /**
-   * Returns configuration object of specifying whether the recording
-   * was started withTicks, withMemory and withAllocations, and other configurations.
-   * @return object
-   */
-  getConfiguration: function () {
-    return this._configuration;
-  },
-
-  /**
-   * Gets the accumulated markers in the current recording.
-   * @return array
-   */
-  getMarkers: function() {
-    return this._markers;
-  },
-
-  /**
-   * Gets the accumulated stack frames in the current recording.
-   * @return array
-   */
-  getFrames: function() {
-    return this._frames;
-  },
-
-  /**
-   * Gets the accumulated memory measurements in this recording.
-   * @return array
-   */
-  getMemory: function() {
-    return this._memory;
-  },
-
-  /**
-   * Gets the accumulated refresh driver ticks in this recording.
-   * @return array
-   */
-  getTicks: function() {
-    return this._ticks;
-  },
-
-  /**
-   * Gets the memory allocations data in this recording.
-   * @return array
-   */
-  getAllocations: function() {
-    return this._allocations;
-  },
-
-  /**
-   * Gets the profiler data in this recording.
-   * @return array
-   */
-  getProfile: function() {
-    return this._profile;
-  },
-
-  /**
-   * Gets all the data in this recording.
-   */
-  getAllData: function() {
-    let label = this.getLabel();
-    let duration = this.getDuration();
-    let markers = this.getMarkers();
-    let frames = this.getFrames();
-    let memory = this.getMemory();
-    let ticks = this.getTicks();
-    let allocations = this.getAllocations();
-    let profile = this.getProfile();
-    let configuration = this.getConfiguration();
-    return { label, duration, markers, frames, memory, ticks, allocations, profile, configuration };
-  },
-
-  /**
-   * Returns a boolean indicating whether or not this recording model
-   * was imported via file.
-   */
-  isImported: function () {
-    return this._imported;
-  },
-
-  /**
-   * Returns a boolean indicating whether or not this recording model
-   * was started via a `console.profile` call.
-   */
-  isConsole: function () {
-    return this._console;
-  },
-
-  /**
-   * Returns a boolean indicating whether or not this recording model
-   * has finished recording.
-   * There is some delay in fetching data between when the recording stops, and
-   * when the recording is considered completed once it has all the profiler and timeline data.
-   */
-  isCompleted: function () {
-    return this._completed || this.isImported();
-  },
-
-  /**
-   * Returns a boolean indicating whether or not this recording model
-   * is recording.
-   * A model may no longer be recording, yet still not have the profiler data. In that
-   * case, use `isCompleted()`.
-   */
-  isRecording: function () {
-    return this._recording;
-  },
-
-  /**
-   * Returns a boolean indicating if this recording is no longer recording, but
-   * not yet completed.
-   */
-  isFinalizing: function () {
-    return !this.isRecording() && !this.isCompleted();
-  },
-
-  /**
-   * Returns the position, generation and totalSize of the profiler
-   * when this recording was started.
-   */
-  getStartingBufferStatus: function () {
-    return this._startingBufferStatus;
-  },
-
-  /**
    * 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;
     }
@@ -343,11 +162,11 @@ LegacyPerformanceRecording.prototype = {
         let [, timestamps] = data;
         this._ticks = timestamps;
         break;
       }
     }
   },
 
   toString: () => "[object LegacyPerformanceRecording]"
-};
+}, PerformanceRecordingCommon);
 
 exports.LegacyPerformanceRecording = LegacyPerformanceRecording;
--- a/toolkit/devtools/performance/moz.build
+++ b/toolkit/devtools/performance/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
 
 EXTRA_JS_MODULES.devtools.performance += [
   'io.js',
   'process-communication.js',
   'recorder.js',
+  'recording-common.js',
   'utils.js',
 ]
 
 EXTRA_JS_MODULES.devtools.performance.legacy += [
   'legacy/actors.js',
   'legacy/compatibility.js',
   'legacy/front.js',
   'legacy/recording.js',
--- a/toolkit/devtools/performance/recorder.js
+++ b/toolkit/devtools/performance/recorder.js
@@ -20,23 +20,24 @@ loader.lazyRequireGetter(this, "events",
 loader.lazyRequireGetter(this, "Memory",
   "devtools/toolkit/shared/memory", true);
 loader.lazyRequireGetter(this, "Timeline",
   "devtools/toolkit/shared/timeline", true);
 loader.lazyRequireGetter(this, "Profiler",
   "devtools/toolkit/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/toolkit/performance/utils", true);
 loader.lazyRequireGetter(this, "DevToolsUtils",
   "devtools/toolkit/DevToolsUtils");
+loader.lazyRequireGetter(this, "getSystemInfo",
+  "devtools/toolkit/shared/system", true);
 
 const PROFILER_EVENTS = [
   "console-api-profiler",
   "profiler-started",
   "profiler-stopped",
   "profiler-status"
 ];
 
@@ -64,31 +65,36 @@ const PerformanceRecorder = exports.Perf
     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 () {
+  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();
@@ -336,16 +342,19 @@ const PerformanceRecorder = exports.Perf
     // 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;
   }),
 
   /**
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/performance/recording-common.js
@@ -0,0 +1,97 @@
+/* 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";
+
+/**
+ * A mixin to be used for PerformanceRecordingActor, PerformanceRecordingFront,
+ * and LegacyPerformanceRecording for helper methods to access data.
+ */
+
+const PerformanceRecordingCommon = exports.PerformanceRecordingCommon = {
+  // Private fields, only needed when a recording is started or stopped.
+  _console: false,
+  _imported: false,
+  _recording: false,
+  _completed: false,
+  _configuration: {},
+  _startingBufferStatus: null,
+  _localStartTime: 0,
+
+  // Serializable fields, necessary and sufficient for import and export.
+  _label: "",
+  _duration: 0,
+  _markers: null,
+  _frames: null,
+  _memory: null,
+  _ticks: null,
+  _allocations: null,
+  _profile: null,
+  _systemHost: null,
+  _systemClient: null,
+
+  /**
+   * Helper methods for returning the status of the recording.
+   * These methods should be consistent on both the front and actor.
+   */
+  isRecording: function () { return this._recording; },
+  isCompleted: function () { return this._completed || this.isImported(); },
+  isFinalizing: function () { return !this.isRecording() && !this.isCompleted(); },
+  isConsole: function () { return this._console; },
+  isImported: function () { return this._imported; },
+
+  /**
+   * Helper methods for returning configuration for the recording.
+   * These methods should be consistent on both the front and actor.
+   */
+  getConfiguration: function () { return this._configuration; },
+  getLabel: function () { return this._label; },
+
+  /**
+   * Gets duration of this recording, in milliseconds.
+   * @return number
+   */
+  getDuration: function () {
+    // Compute an approximate ending time for the current recording if it is
+    // still in progress. This is needed to ensure that the view updates even
+    // when new data is not being generated. If recording is completed, use
+    // the duration from the profiler; if between recording and being finalized,
+    // use the last estimated duration.
+    if (this.isRecording()) {
+      return this._estimatedDuration = Date.now() - this._localStartTime;
+    } else {
+      return this._duration || this._estimatedDuration || 0;
+    }
+  },
+
+  /**
+   * Helper methods for returning recording data.
+   * These methods should be consistent on both the front and actor.
+   */
+  getMarkers: function() { return this._markers; },
+  getFrames: function() { return this._frames; },
+  getMemory: function() { return this._memory; },
+  getTicks: function() { return this._ticks; },
+  getAllocations: function() { return this._allocations; },
+  getProfile: function() { return this._profile; },
+  getHostSystemInfo: function() { return this._systemHost; },
+  getClientSystemInfo: function() { return this._systemClient; },
+  getStartingBufferStatus: function() { return this._startingBufferStatus; },
+
+  getAllData: function () {
+    let label = this.getLabel();
+    let duration = this.getDuration();
+    let markers = this.getMarkers();
+    let frames = this.getFrames();
+    let memory = this.getMemory();
+    let ticks = this.getTicks();
+    let allocations = this.getAllocations();
+    let profile = this.getProfile();
+    let configuration = this.getConfiguration();
+    let systemHost = this.getHostSystemInfo();
+    let systemClient = this.getClientSystemInfo();
+
+    return { label, duration, markers, frames, memory, ticks, allocations, profile, configuration, systemHost, systemClient };
+  },
+};
--- a/toolkit/devtools/server/actors/performance-recording.js
+++ b/toolkit/devtools/server/actors/performance-recording.js
@@ -10,82 +10,18 @@ const { custom, method, RetVal, Arg, Opt
 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/toolkit/performance/io");
 loader.lazyRequireGetter(this, "RecordingUtils",
   "devtools/toolkit/performance/utils");
-
-/**
- * A set of functions used by both the front and actor to access
- * internal properties.
- */
-const PerformanceRecordingCommon = {
-  // Private fields, only needed when a recording is started or stopped.
-  _console: false,
-  _imported: false,
-  _recording: false,
-  _completed: false,
-  _configuration: {},
-  _startingBufferStatus: null,
-  _localStartTime: null,
-
-  // Serializable fields, necessary and sufficient for import and export.
-  _label: "",
-  _duration: 0,
-  _markers: null,
-  _frames: null,
-  _memory: null,
-  _ticks: null,
-  _allocations: null,
-  _profile: null,
-
-  /**
-   * Helper methods for returning the status of the recording.
-   * These methods should be consistent on both the front and actor.
-   */
-  isRecording: function () { return this._recording; },
-  isCompleted: function () { return this._completed || this.isImported(); },
-  isFinalizing: function () { return !this.isRecording() && !this.isCompleted(); },
-  isConsole: function () { return this._console; },
-  isImported: function () { return this._imported; },
-
-  /**
-   * Helper methods for returning configuration for the recording.
-   * These methods should be consistent on both the front and actor.
-   */
-  getConfiguration: function () { return this._configuration; },
-  getLabel: function () { return this._label; },
-
-  /**
-   * Helper methods for returning recording data.
-   * These methods should be consistent on both the front and actor.
-   */
-  getMarkers: function() { return this._markers; },
-  getFrames: function() { return this._frames; },
-  getMemory: function() { return this._memory; },
-  getTicks: function() { return this._ticks; },
-  getAllocations: function() { return this._allocations; },
-  getProfile: function() { return this._profile; },
-
-  getAllData: function () {
-    let label = this.getLabel();
-    let duration = this.getDuration();
-    let markers = this.getMarkers();
-    let frames = this.getFrames();
-    let memory = this.getMemory();
-    let ticks = this.getTicks();
-    let allocations = this.getAllocations();
-    let profile = this.getProfile();
-    let configuration = this.getConfiguration();
-    return { label, duration, markers, frames, memory, ticks, allocations, profile, configuration };
-  },
-};
+loader.lazyRequireGetter(this, "PerformanceRecordingCommon",
+  "devtools/toolkit/performance/recording-common", true);
 
 /**
  * This actor wraps the Performance module at toolkit/devtools/shared/performance.js
  * and provides RDP definitions.
  *
  * @see toolkit/devtools/shared/performance.js for documentation.
  */
 let PerformanceRecordingActor = exports.PerformanceRecordingActor = protocol.ActorClass(merge({
@@ -106,19 +42,22 @@ let PerformanceRecordingActor = exports.
       localStartTime: this._localStartTime,
       recording: this._recording,
       completed: this._completed,
       duration: this._duration,
     };
 
     // Only send profiler data once it exists and it has
     // not yet been sent
-    if (this._profile && !this._sentProfilerData) {
-      form.profile = this._profile;
-      this._sentProfilerData = true;
+    if (this._profile && !this._sentFinalizedData) {
+      form.finalizedData = true;
+      form.profile = this.getProfile();
+      form.systemHost = this.getHostSystemInfo();
+      form.systemClient = this.getClientSystemInfo();
+      this._sentFinalizedData = true;
     }
 
     return form;
   },
 
   /**
    * @param {object} conn
    * @param {object} options
@@ -159,16 +98,19 @@ let PerformanceRecordingActor = exports.
       };
 
       this._recording = true;
       this._markers = [];
       this._frames = [];
       this._memory = [];
       this._ticks = [];
       this._allocations = { sites: [], timestamps: [], frames: [], sizes: [] };
+
+      this._systemHost = meta.systemHost || {};
+      this._systemClient = meta.systemClient || {};
     }
   },
 
   destroy: function() {
     protocol.Actor.prototype.destroy.call(this);
   },
 
   /**
@@ -228,18 +170,20 @@ let PerformanceRecordingFront = exports.
     this._console = form.console;
     this._label = form.label;
     this._startTime = form.startTime;
     this._localStartTime = form.localStartTime;
     this._recording = form.recording;
     this._completed = form.completed;
     this._duration = form.duration;
 
-    if (form.profile) {
+    if (form.finalizedData) {
       this._profile = form.profile;
+      this._systemHost = form.systemHost;
+      this._systemClient = form.systemClient;
     }
 
     // Sort again on the client side if we're using realtime markers and the recording
     // just finished. This is because GC/Compositing markers can come into the array out of order with
     // the other markers, leading to strange collapsing in waterfall view.
     if (this._completed && !this._markersSorted) {
       this._markers = this._markers.sort((a, b) => (a.start > b.start));
       this._markersSorted = true;
@@ -266,43 +210,16 @@ let PerformanceRecordingFront = exports.
    *        The file to stream the data into.
    */
   exportRecording: function (file) {
     let recordingData = this.getAllData();
     return PerformanceIO.saveRecordingToFile(recordingData, file);
   },
 
   /**
-   * Returns the position, generation, and totalSize of the profiler
-   * when this recording was started.
-   *
-   * @return {object}
-   */
-  getStartingBufferStatus: function () {
-    return this._form.startingBufferStatus;
-  },
-
-  /**
-   * Gets duration of this recording, in milliseconds.
-   * @return number
-   */
-  getDuration: function () {
-    // Compute an approximate ending time for the current recording if it is
-    // still in progress. This is needed to ensure that the view updates even
-    // when new data is not being generated. If recording is completed, use
-    // the duration from the profiler; if between recording and being finalized,
-    // use the last estimated duration.
-    if (this.isRecording()) {
-      return this._estimatedDuration = Date.now() - this._localStartTime;
-    } else {
-      return this._duration || this._estimatedDuration || 0;
-    }
-  },
-
-  /**
    * Fired whenever the PerformanceFront emits markers, memory or ticks.
    */
   _addTimelineData: function (eventName, data) {
     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.
--- a/toolkit/devtools/server/actors/performance.js
+++ b/toolkit/devtools/server/actors/performance.js
@@ -17,16 +17,18 @@ loader.lazyRequireGetter(this, "extend",
 loader.lazyRequireGetter(this, "PerformanceRecorder",
   "devtools/toolkit/performance/recorder", true);
 loader.lazyRequireGetter(this, "PerformanceIO",
   "devtools/toolkit/performance/io");
 loader.lazyRequireGetter(this, "normalizePerformanceFeatures",
   "devtools/toolkit/performance/utils", true);
 loader.lazyRequireGetter(this, "LegacyPerformanceFront",
   "devtools/toolkit/performance/legacy/front", true);
+loader.lazyRequireGetter(this, "getSystemInfo",
+  "devtools/toolkit/shared/system", true);
 
 const PIPE_TO_FRONT_EVENTS = new Set([
   "recording-started", "recording-stopping", "recording-stopped",
   "profiler-status", "timeline-data", "console-profile-start"
 ]);
 
 const RECORDING_STATE_CHANGE_EVENTS = new Set([
   "recording-started", "recording-stopping", "recording-stopped"
@@ -92,20 +94,23 @@ let PerformanceActor = exports.Performan
   },
 
   destroy: function () {
     events.off(this.bridge, "*", this._onRecorderEvent);
     this.bridge.destroy();
     protocol.Actor.prototype.destroy.call(this);
   },
 
-  connect: method(function () {
-    this.bridge.connect();
-    return this.traits;
-  }, { response: RetVal("json") }),
+  connect: method(function (config) {
+    this.bridge.connect({ systemClient: config.systemClient });
+    return { traits: this.traits };
+  }, {
+    request: { options: Arg(0, "nullable:json") },
+    response: RetVal("json")
+  }),
 
   startRecording: method(Task.async(function *(options={}) {
     let normalizedOptions = normalizePerformanceFeatures(options, this.traits.features);
     let recording = yield this.bridge.startRecording(normalizedOptions);
 
     this.manage(recording);
 
     return recording;
@@ -177,19 +182,27 @@ const PerformanceFront = exports.Perform
     this.actorID = form.performanceActor;
     this.manage(this);
   },
 
   destroy: function () {
     protocol.Front.prototype.destroy.call(this);
   },
 
-  connect: custom(function () {
-    return this._connect().then(traits => this._traits = traits);
-  }, {
+  /**
+   * Conenct to the server, and handle once-off tasks like storing traits
+   * or system info.
+   */
+  connect: custom(Task.async(function *() {
+    let systemClient = yield getSystemInfo();
+    let { traits } = yield this._connect({ systemClient });
+    this._traits = traits;
+
+    return this._traits;
+  }), {
     impl: "_connect"
   }),
 
   get traits() {
     if (!this._traits) {
       Cu.reportError("Cannot access traits of PerformanceFront before calling `connect()`.");
     }
     return this._traits;
@@ -234,16 +247,18 @@ const PerformanceFront = exports.Perform
       model._duration = recordingData.duration;
       model._markers = recordingData.markers;
       model._frames = recordingData.frames;
       model._memory = recordingData.memory;
       model._ticks = recordingData.ticks;
       model._allocations = recordingData.allocations;
       model._profile = recordingData.profile;
       model._configuration = recordingData.configuration || {};
+      model._systemHost = recordingData.systemHost;
+      model._systemClient = recordingData.systemClient;
       return model;
     });
   },
 
   /**
    * Store profiler status when the position has been update so we can
    * calculate recording's buffer percentage usage after emitting the event.
    */
--- a/toolkit/devtools/server/tests/browser/browser_perf-legacy-front-01.js
+++ b/toolkit/devtools/server/tests/browser/browser_perf-legacy-front-01.js
@@ -48,16 +48,18 @@ add_task(function*() {
   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`);
@@ -71,8 +73,15 @@ function getTab (url) {
   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`);
+  }
+}
--- a/toolkit/devtools/server/tests/browser/browser_perf-recording-actor-01.js
+++ b/toolkit/devtools/server/tests/browser/browser_perf-recording-actor-01.js
@@ -44,23 +44,36 @@ add_task(function*() {
     ok(!rec.isCompleted(), "recording is not yet completed on 'recording-stopping'");
     ok(rec.isFinalizing(), "recording is considered finalizing between 'recording-stopping' and 'recording-stopped'");
   }
 
   yield stopped;
   ok(!rec.isRecording(), "on 'recording-stopped', model is still no longer recording");
   ok(rec.isCompleted(), "on 'recording-stopped', model is considered 'complete'");
 
+  checkSystemInfo(rec, "Host");
+  checkSystemInfo(rec, "Client");
+
   // Export and import a rec, and ensure it has the correct state.
   let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
   file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
   yield rec.exportRecording(file);
 
   let importedModel = yield front.importRecording(file);
 
   ok(importedModel.isCompleted(), "All imported recordings should be completed");
   ok(!importedModel.isRecording(), "All imported recordings should not be recording");
   ok(importedModel.isImported(), "All imported recordings should be considerd imported");
 
+  checkSystemInfo(importedModel, "Host");
+  checkSystemInfo(importedModel, "Client");
+
   yield front.destroy();
   yield closeDebuggerClient(client);
   gBrowser.removeCurrentTab();
 });
+
+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`);
+  }
+}
--- a/toolkit/devtools/shared/system.js
+++ b/toolkit/devtools/shared/system.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const { Task } = require("resource://gre/modules/Task.jsm");
 
 loader.lazyRequireGetter(this, "Services");
 loader.lazyRequireGetter(this, "promise");
-loader.lazyRequireGetter(this, "OS", "resource://gre/modules/commonjs/node/os");
+loader.lazyRequireGetter(this, "OS", "resource://gre/modules/commonjs/node/os.js");
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "AppConstants",
   "resource://gre/modules/AppConstants.jsm", true);
 loader.lazyGetter(this, "screenManager", () => {
   return Cc["@mozilla.org/gfx/screenmanager;1"].getService(Ci.nsIScreenManager);
 });
 loader.lazyGetter(this, "oscpu", () => {
   return Cc["@mozilla.org/network/protocol;1?name=http"]
@@ -159,28 +159,33 @@ function *getSystemInfo() {
     height,
     brandName,
   };
 
   CACHED_INFO = info;
   return info;
 }
 
-function getProfileLocation() {
-  let profd = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
-  let profservice = Cc["@mozilla.org/toolkit/profile-service;1"].getService(Ci.nsIToolkitProfileService);
-  var profiles = profservice.profiles;
-  while (profiles.hasMoreElements()) {
-    let profile = profiles.getNext().QueryInterface(Ci.nsIToolkitProfile);
-    if (profile.rootDir.path == profd.path) {
-      return profile = profile.name;
+function getProfileLocation () {
+  // In child processes, we cannot access the profile location.
+  try {
+    let profd = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
+    let profservice = Cc["@mozilla.org/toolkit/profile-service;1"].getService(Ci.nsIToolkitProfileService);
+    var profiles = profservice.profiles;
+    while (profiles.hasMoreElements()) {
+      let profile = profiles.getNext().QueryInterface(Ci.nsIToolkitProfile);
+      if (profile.rootDir.path == profd.path) {
+        return profile = profile.name;
+      }
     }
+
+    return profd.leafName;
+  } catch (e) {
+    return "";
   }
-
-  return profd.leafName;
 }
 
 function getAppIniString(section, key) {
   let inifile = Services.dirsvc.get("GreD", Ci.nsIFile);
   inifile.append("application.ini");
 
   if (!inifile.exists()) {
     inifile = Services.dirsvc.get("CurProcD", Ci.nsIFile);