Bug 1160900 - Display buffer status while recording a profile. r=vp
authorJordan Santell <jsantell@mozilla.com>
Fri, 08 May 2015 16:19:58 -0700
changeset 243182 1fcc6c6ddf0fec244088c05c0b3f028b66847d72
parent 243181 2cc54ae7afa57fa286260fcde5fb08c3e43a7971
child 243183 0a10db771035830da18951918c7839ea5b78069c
push id12857
push userjsantell@mozilla.com
push dateSun, 10 May 2015 00:46:06 +0000
treeherderfx-team@1fcc6c6ddf0f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvp
bugs1160900
milestone40.0a1
Bug 1160900 - Display buffer status while recording a profile. r=vp
browser/devtools/performance/modules/actors.js
browser/devtools/performance/modules/front.js
browser/devtools/performance/modules/recording-model.js
browser/devtools/performance/performance-controller.js
browser/devtools/performance/performance-view.js
browser/devtools/performance/performance.xul
browser/devtools/performance/test/browser.ini
browser/devtools/performance/test/browser_perf-compatibility-06.js
browser/devtools/performance/test/browser_perf-compatibility-07.js
browser/devtools/performance/test/browser_perf-recording-model-02.js
browser/devtools/performance/test/browser_perf-recording-notices-03.js
browser/devtools/performance/test/browser_perf-recording-notices-04.js
browser/devtools/performance/test/head.js
browser/themes/shared/devtools/performance.inc.css
--- a/browser/devtools/performance/modules/actors.js
+++ b/browser/devtools/performance/modules/actors.js
@@ -21,40 +21,40 @@ loader.lazyRequireGetter(this, "MemoryFr
   "devtools/server/actors/memory", true);
 loader.lazyRequireGetter(this, "Poller",
   "devtools/shared/poller", true);
 
 // how often do we pull allocation sites from the memory actor
 const ALLOCATION_SITE_POLL_TIMER = 200; // ms
 
 // how often do we check the status of the profiler's circular buffer
-const BUFFER_CHECK_TIMER = 5000; // ms
+const PROFILER_CHECK_TIMER = 5000; // ms
 
 const MEMORY_ACTOR_METHODS = [
   "attach", "detach", "getState", "getAllocationsSettings",
   "getAllocations", "startRecordingAllocations", "stopRecordingAllocations"
 ];
 
 const TIMELINE_ACTOR_METHODS = [
   "start", "stop",
 ];
 
 const PROFILER_ACTOR_METHODS = [
-  "isActive", "startProfiler", "getStartOptions", "stopProfiler",
+  "startProfiler", "getStartOptions", "stopProfiler",
   "registerEventNotifications", "unregisterEventNotifications"
 ];
 
 /**
  * Constructor for a facade around an underlying ProfilerFront.
  */
 function ProfilerFrontFacade (target) {
   this._target = target;
   this._onProfilerEvent = this._onProfilerEvent.bind(this);
-  this._checkBufferStatus = this._checkBufferStatus.bind(this);
-  this._BUFFER_CHECK_TIMER = this._target.TEST_MOCK_BUFFER_CHECK_TIMER || BUFFER_CHECK_TIMER;
+  this._checkProfilerStatus = this._checkProfilerStatus.bind(this);
+  this._PROFILER_CHECK_TIMER = this._target.TEST_MOCK_PROFILER_CHECK_TIMER || PROFILER_CHECK_TIMER;
 
   EventEmitter.decorate(this);
 }
 
 ProfilerFrontFacade.prototype = {
   EVENTS: ["console-api-profiler", "profiler-stopped"],
 
   // Connects to the targets underlying real ProfilerFront.
@@ -92,27 +92,28 @@ ProfilerFrontFacade.prototype = {
    * @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._checkBufferStatus, this._BUFFER_CHECK_TIMER, false);
+      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.isActive();
+    let { isActive, currentTime, position, generation, totalSize } = yield this.getStatus();
+
     if (isActive) {
       this.emit("profiler-already-active");
       return { startTime: currentTime, position, generation, totalSize };
     }
 
     // Translate options from the recording model into profiler-specific
     // options for the nsIProfiler
     let profilerOptions = {
@@ -131,16 +132,43 @@ ProfilerFrontFacade.prototype = {
    * (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 (actorCompatibilityBridge("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 (actorCompatibilityBridge("getProfile").call(this, options));
     // 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);
@@ -162,18 +190,19 @@ ProfilerFrontFacade.prototype = {
       } else if (subject.action === "profileEnd") {
         this.emit("console-profile-end", details);
       }
     } else if (topic === "profiler-stopped") {
       this.emit("profiler-stopped");
     }
   },
 
-  _checkBufferStatus: Task.async(function *() {
-    this.emit("buffer-status", (yield this.isActive()));
+  _checkProfilerStatus: Task.async(function *() {
+    // Calling `getStatus()` will emit the "profiler-status" on its own
+    yield this.getStatus();
   }),
 
   toString: () => "[object ProfilerFrontFacade]"
 };
 
 // Bind all the methods that directly proxy to the actor
 PROFILER_ACTOR_METHODS.forEach(method => ProfilerFrontFacade.prototype[method] = actorCompatibilityBridge(method));
 exports.ProfilerFront = ProfilerFrontFacade;
--- a/browser/devtools/performance/modules/front.js
+++ b/browser/devtools/performance/modules/front.js
@@ -20,17 +20,17 @@ loader.lazyImporter(this, "gDevTools",
   "resource:///modules/devtools/gDevTools.jsm");
 loader.lazyImporter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 
 // Events to pipe from PerformanceActorsConnection to the PerformanceFront
 const CONNECTION_PIPE_EVENTS = [
   "timeline-data", "profiler-already-active", "profiler-activated",
   "recording-starting", "recording-started", "recording-stopping", "recording-stopped",
-  "buffer-status"
+  "profiler-status"
 ];
 
 /**
  * A cache of all PerformanceActorsConnection instances.
  * The keys are Target objects.
  */
 let SharedPerformanceActors = new WeakMap();
 
@@ -71,17 +71,17 @@ function PerformanceActorsConnection(tar
   this._pendingConsoleRecordings = [];
   this._sitesPullTimeout = 0;
   this._recordings = [];
 
   this._pipeToConnection = this._pipeToConnection.bind(this);
   this._onTimelineData = this._onTimelineData.bind(this);
   this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this);
   this._onConsoleProfileEnd = this._onConsoleProfileEnd.bind(this);
-  this._onBufferStatus = this._onBufferStatus.bind(this);
+  this._onProfilerStatus = this._onProfilerStatus.bind(this);
   this._onProfilerUnexpectedlyStopped = this._onProfilerUnexpectedlyStopped.bind(this);
 
   Services.obs.notifyObservers(null, "performance-actors-connection-created", null);
 }
 
 PerformanceActorsConnection.prototype = {
 
   // Properties set based off of server actor support
@@ -166,31 +166,31 @@ PerformanceActorsConnection.prototype = 
   _registerListeners: function () {
     this._timeline.on("timeline-data", this._onTimelineData);
     this._memory.on("timeline-data", this._onTimelineData);
     this._profiler.on("console-profile-start", this._onConsoleProfileStart);
     this._profiler.on("console-profile-end", this._onConsoleProfileEnd);
     this._profiler.on("profiler-stopped", this._onProfilerUnexpectedlyStopped);
     this._profiler.on("profiler-already-active", this._pipeToConnection);
     this._profiler.on("profiler-activated", this._pipeToConnection);
-    this._profiler.on("buffer-status", this._onBufferStatus);
+    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._memory.off("timeline-data", this._onTimelineData);
     this._profiler.off("console-profile-start", this._onConsoleProfileStart);
     this._profiler.off("console-profile-end", this._onConsoleProfileEnd);
     this._profiler.off("profiler-stopped", this._onProfilerUnexpectedlyStopped);
     this._profiler.off("profiler-already-active", this._pipeToConnection);
     this._profiler.off("profiler-activated", this._pipeToConnection);
-    this._profiler.off("buffer-status", this._onBufferStatus);
+    this._profiler.off("profiler-status", this._onProfilerStatus);
   },
 
   /**
    * Closes the connections to non-profiler actors.
    */
   _disconnectActors: Task.async(function* () {
     yield Promise.all([
       this._profiler.destroy(),
@@ -289,28 +289,30 @@ PerformanceActorsConnection.prototype = 
    * Populate our internal store of recordings for all currently recording sessions.
    */
   _onTimelineData: function (_, ...data) {
     this._recordings.forEach(e => e._addTimelineData.apply(e, data));
     this.emit("timeline-data", ...data);
   },
 
   /**
-   * Called whenever the underlying profiler polls its buffer status.
+   * Called whenever the underlying profiler polls its current status.
    */
-  _onBufferStatus: function (_, data) {
-    // If no buffer data emitted (whether from an older actor being destroyed
+  _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.
-    // Also check for a value of buffer status (`position`) to see if it's
-    // because of an unsupported server.
-    if (!data || data.position === void 0) {
+    if (!data) {
       return;
     }
-    this._recordings.forEach(e => e._addBufferStatusData.call(e, data));
-    this.emit("buffer-status", data);
+    // Check for a value of buffer status (`position`) to see if the server
+    // supports buffer status -- apply to the recording models if so.
+    if (data.position !== void 0) {
+      this._recordings.forEach(e => e._addBufferStatusData.call(e, data));
+    }
+    this.emit("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`.
--- a/browser/devtools/performance/modules/recording-model.js
+++ b/browser/devtools/performance/modules/recording-model.js
@@ -102,16 +102,18 @@ RecordingModel.prototype = {
     this._profilerStartTime = info.profilerStartTime;
     this._timelineStartTime = info.timelineStartTime;
     this._memoryStartTime = info.memoryStartTime;
     this._originalBufferStatus = {
       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._markers = [];
     this._frames = [];
     this._memory = [];
     this._ticks = [];
     this._allocations = { sites: [], timestamps: [], frames: [], counts: [] };
--- a/browser/devtools/performance/performance-controller.js
+++ b/browser/devtools/performance/performance-controller.js
@@ -92,16 +92,21 @@ const EVENTS = {
 
   // When recordings have been cleared out
   RECORDINGS_CLEARED: "Performance:RecordingsCleared",
 
   // When a recording is imported or exported via the PerformanceController
   RECORDING_IMPORTED: "Performance:RecordingImported",
   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 JITOptimizationsView 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",
@@ -179,16 +184,17 @@ let PerformanceController = {
     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._onRecordingStateChange = this._onRecordingStateChange.bind(this);
+    this._onProfilerStatusUpdated = this._onProfilerStatusUpdated.bind(this);
 
     // All boolean prefs should be handled via the OptionsView in the
     // ToolbarView, so that they may be accessible via the "gear" menu.
     // Every other pref should be registered here.
     this._nonBooleanPrefs = new ViewHelpers.Prefs("devtools.performance", {
       "hidden-markers": ["Json", "timeline.hidden-markers"],
       "memory-sample-probability": ["Float", "memory.sample-probability"],
       "memory-max-log-length": ["Int", "memory.max-log-length"],
@@ -198,16 +204,17 @@ let PerformanceController = {
 
     this._nonBooleanPrefs.registerObserver();
     this._nonBooleanPrefs.on("pref-changed", this._onPrefChanged);
 
     gFront.on("recording-starting", this._onRecordingStateChange);
     gFront.on("recording-started", this._onRecordingStateChange);
     gFront.on("recording-stopping", this._onRecordingStateChange);
     gFront.on("recording-stopped", this._onRecordingStateChange);
+    gFront.on("profiler-status", this._onProfilerStatusUpdated);
     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);
 
@@ -220,16 +227,17 @@ let PerformanceController = {
   destroy: function() {
     this._nonBooleanPrefs.unregisterObserver();
     this._nonBooleanPrefs.off("pref-changed", this._onPrefChanged);
 
     gFront.off("recording-starting", this._onRecordingStateChange);
     gFront.off("recording-started", this._onRecordingStateChange);
     gFront.off("recording-stopping", this._onRecordingStateChange);
     gFront.off("recording-stopped", this._onRecordingStateChange);
+    gFront.on("profiler-status", this._onProfilerStatusUpdated);
     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);
 
@@ -426,16 +434,23 @@ let PerformanceController = {
     if (data.pref !== "devtools.theme") {
       return;
     }
 
     this.emit(EVENTS.THEME_CHANGED, data.newValue);
   },
 
   /**
+   * Emitted when the front updates RecordingModel's buffer status.
+   */
+  _onProfilerStatusUpdated: function (_, data) {
+    this.emit(EVENTS.PROFILER_STATUS_UPDATED, data);
+  },
+
+  /**
    * Fired when a recording model changes state.
    *
    * @param {string} state
    * @param {RecordingModel} model
    */
   _onRecordingStateChange: function (state, model) {
     switch (state) {
       // Fired when a RecordingModel was just created from the front
--- a/browser/devtools/performance/performance-view.js
+++ b/browser/devtools/performance/performance-view.js
@@ -4,16 +4,19 @@
 "use strict";
 
 /**
  * Master view handler for the performance tool.
  */
 let PerformanceView = {
 
   _state: null,
+  // Set to true if the front emits a "buffer-status" event, indicating
+  // that the server has support for determining buffer status.
+  _bufferStatusSupported: false,
 
   // Mapping of state to selectors for different panes
   // of the main profiler view. Used in `PerformanceView.setState()`
   states: {
     empty: [
       { deck: "#performance-view", pane: "#empty-notice" }
     ],
     recording: [
@@ -41,27 +44,29 @@ let PerformanceView = {
     this._onRecordButtonClick = this._onRecordButtonClick.bind(this);
     this._onImportButtonClick = this._onImportButtonClick.bind(this);
     this._onClearButtonClick = this._onClearButtonClick.bind(this);
     this._lockRecordButtons = this._lockRecordButtons.bind(this);
     this._unlockRecordButtons = this._unlockRecordButtons.bind(this);
     this._onRecordingSelected = this._onRecordingSelected.bind(this);
     this._onRecordingStopped = this._onRecordingStopped.bind(this);
     this._onRecordingStarted = this._onRecordingStarted.bind(this);
+    this._onProfilerStatusUpdated = this._onProfilerStatusUpdated.bind(this);
 
     for (let button of $$(".record-button")) {
       button.addEventListener("click", this._onRecordButtonClick);
     }
     this._importButton.addEventListener("click", this._onImportButtonClick);
     this._clearButton.addEventListener("click", this._onClearButtonClick);
 
     // Bind to controller events to unlock the record button
     PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
     PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
     PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
+    PerformanceController.on(EVENTS.PROFILER_STATUS_UPDATED, this._onProfilerStatusUpdated);
 
     this.setState("empty");
 
     // Initialize the ToolbarView first, because other views may need access
     // to the OptionsView via the controller, to read prefs.
     yield ToolbarView.initialize();
     yield RecordingsView.initialize();
     yield OverviewView.initialize();
@@ -76,16 +81,17 @@ let PerformanceView = {
       button.removeEventListener("click", this._onRecordButtonClick);
     }
     this._importButton.removeEventListener("click", this._onImportButtonClick);
     this._clearButton.removeEventListener("click", this._onClearButtonClick);
 
     PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
     PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
     PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
+    PerformanceController.off(EVENTS.PROFILER_STATUS_UPDATED, this._onProfilerStatusUpdated);
 
     yield ToolbarView.destroy();
     yield RecordingsView.destroy();
     yield OverviewView.destroy();
     yield DetailsView.destroy();
   }),
 
   /**
@@ -110,27 +116,64 @@ let PerformanceView = {
       label = label ? `"${label}"` : "";
 
       let startCommand = $(".console-profile-recording-notice .console-profile-command");
       let stopCommand = $(".console-profile-stop-notice .console-profile-command");
 
       startCommand.value = `console.profile(${label})`;
       stopCommand.value = `console.profileEnd(${label})`;
     }
+
+    this.updateBufferStatus();
     this.emit(EVENTS.UI_STATE_CHANGED, state);
   },
 
   /**
    * Returns the state of the PerformanceView.
    */
   getState: function () {
     return this._state;
   },
 
   /**
+   * Updates the displayed buffer status.
+   */
+  updateBufferStatus: function () {
+    // If we've never seen a "buffer-status" event from the front, ignore
+    // and keep the buffer elements hidden.
+    if (!this._bufferStatusSupported) {
+      return;
+    }
+
+    let recording = PerformanceController.getCurrentRecording();
+    if (!recording || !recording.isRecording()) {
+      return;
+    }
+
+    let bufferUsage = recording.getBufferUsage();
+
+    // Normalize to a percentage value
+    let percent = Math.floor(bufferUsage * 100);
+
+    let $container = $("#details-pane-container");
+    let $bufferLabel = $(".buffer-status-message", $container.selectedPanel);
+
+    // Be a little flexible on the buffer status, although not sure how
+    // this could happen, as RecordingModel clamps.
+    if (percent >= 99) {
+      $container.setAttribute("buffer-status", "full");
+    } else {
+      $container.setAttribute("buffer-status", "in-progress");
+    }
+
+    $bufferLabel.value = L10N.getFormatStr("profiler.bufferStatus", percent);
+    this.emit(EVENTS.UI_BUFFER_UPDATED, percent);
+  },
+
+  /**
    * Adds the `locked` attribute on the record button. This prevents it
    * from being clicked while recording is started or stopped.
    */
   _lockRecordButtons: function () {
     for (let button of $$(".record-button")) {
       button.setAttribute("locked", "true");
     }
   },
@@ -148,16 +191,19 @@ let PerformanceView = {
    * When a recording has started.
    */
   _onRecordingStarted: function (_, recording) {
     // A stopped recording can be from `console.profileEnd` -- only unlock
     // the button if it's the main recording that was started via UI.
     if (!recording.isConsole()) {
       this._unlockRecordButtons();
     }
+    if (recording.isRecording()) {
+      this.updateBufferStatus();
+    }
   },
 
   /**
    * When a recording is complete.
    */
   _onRecordingStopped: function (_, recording) {
     // A stopped recording can be from `console.profileEnd` -- only unlock
     // the button if it's the main recording that was started via UI.
@@ -222,15 +268,38 @@ let PerformanceView = {
       this.setState("console-recording");
     } else if (recording.isRecording()) {
       this.setState("recording");
     } else {
       this.setState("recorded");
     }
   },
 
+  /**
+   * Fired when the controller has updated information on the buffer's status.
+   * Update the buffer status display if shown.
+   */
+  _onProfilerStatusUpdated: function (_, data) {
+    // We only care about buffer status here, so check to see
+    // if it has position.
+    if (!data || data.position === void 0) {
+      return;
+    }
+    // If this is our first buffer event, set the status and add a class
+    if (!this._bufferStatusSupported) {
+      this._bufferStatusSupported = true;
+      $("#details-pane-container").setAttribute("buffer-status", "in-progress");
+    }
+
+    if (!this.getState("recording") && !this.getState("console-recording")) {
+      return;
+    }
+
+    this.updateBufferStatus();
+  },
+
   toString: () => "[object PerformanceView]"
 };
 
 /**
  * Convenient way of emitting events from the view.
  */
 EventEmitter.decorate(PerformanceView);
--- a/browser/devtools/performance/performance.xul
+++ b/browser/devtools/performance/performance.xul
@@ -137,37 +137,45 @@
       </toolbar>
 
       <deck id="performance-view" flex="1">
         <hbox id="empty-notice"
               class="notice-container"
               align="center"
               pack="center"
               flex="1">
-          <hbox class="devtools-toolbarbutton-group">
+          <hbox class="devtools-toolbarbutton-group"
+                pack="center">
             <toolbarbutton class="record-button"
                            label="&profilerUI.startRecording;" />
           </hbox>
         </hbox>
         <vbox id="performance-view-content" flex="1">
           <vbox id="overview-pane">
             <hbox id="markers-overview"/>
             <hbox id="memory-overview"/>
             <hbox id="time-framerate"/>
           </vbox>
           <deck id="details-pane-container" flex="1">
             <hbox id="recording-notice"
                   class="notice-container"
                   align="center"
                   pack="center"
                   flex="1">
-              <hbox class="devtools-toolbarbutton-group">
-                <toolbarbutton class="record-button"
-                               label="&profilerUI.stopRecording;" />
-              </hbox>
+              <vbox>
+                <hbox class="devtools-toolbarbutton-group"
+                      pack="center">
+                  <toolbarbutton class="record-button"
+                                 label="&profilerUI.stopRecording;" />
+                </hbox>
+                <label class="buffer-status-message"
+                       tooltiptext="&profilerUI.bufferStatusTooltip;"/>
+                <label class="buffer-status-message-full"
+                       value="&profilerUI.bufferStatusFull;"/>
+              </vbox>
             </hbox>
             <hbox id="console-recording-notice"
                   class="notice-container"
                   align="center"
                   pack="center"
                   flex="1">
                   <vbox flex="1" align="center">
                     <hbox class="console-profile-recording-notice">
@@ -175,16 +183,20 @@
                       <label class="console-profile-command" />
                       <label value="&profilerUI.console.recordingNoticeEnd;" />
                     </hbox>
                     <hbox class="console-profile-stop-notice">
                       <label value="&profilerUI.console.stopCommandStart;" />
                       <label class="console-profile-command" />
                       <label value="&profilerUI.console.stopCommandEnd;" />
                     </hbox>
+                    <label class="buffer-status-message"
+                           tooltiptext="&profilerUI.bufferStatusTooltip;"/>
+                    <label class="buffer-status-message-full"
+                           value="&profilerUI.bufferStatusFull;"/>
                   </vbox>
             </hbox>
             <deck id="details-pane" flex="1">
               <hbox id="waterfall-view" flex="1">
                 <vbox id="waterfall-breakdown" flex="1" class="devtools-main-content" />
                 <splitter class="devtools-side-splitter"/>
                 <vbox id="waterfall-details"
                       class="theme-sidebar"
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -15,16 +15,17 @@ support-files =
 [browser_markers-parse-html.js]
 [browser_perf-allocations-to-samples.js]
 [browser_perf-compatibility-01.js]
 [browser_perf-compatibility-02.js]
 [browser_perf-compatibility-03.js]
 [browser_perf-compatibility-04.js]
 [browser_perf-compatibility-05.js]
 [browser_perf-compatibility-06.js]
+[browser_perf-compatibility-07.js]
 [browser_perf-clear-01.js]
 [browser_perf-clear-02.js]
 [browser_perf-columns-js-calltree.js]
 [browser_perf-columns-memory-calltree.js]
 [browser_perf-console-record-01.js]
 [browser_perf-console-record-02.js]
 [browser_perf-console-record-03.js]
 [browser_perf-console-record-04.js]
@@ -89,16 +90,18 @@ support-files =
 [browser_perf-shared-connection-03.js]
 [browser_perf-states.js]
 [browser_perf-refresh.js]
 [browser_perf-ui-recording.js]
 [browser_perf-recording-model-01.js]
 [browser_perf-recording-model-02.js]
 [browser_perf-recording-notices-01.js]
 [browser_perf-recording-notices-02.js]
+[browser_perf-recording-notices-03.js]
+[browser_perf-recording-notices-04.js]
 [browser_perf_recordings-io-01.js]
 [browser_perf_recordings-io-02.js]
 [browser_perf_recordings-io-03.js]
 [browser_perf_recordings-io-04.js]
 [browser_perf-range-changed-render.js]
 [browser_perf-recording-selected-01.js]
 [browser_perf-recording-selected-02.js]
 [browser_perf-recording-selected-03.js]
--- a/browser/devtools/performance/test/browser_perf-compatibility-06.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-06.js
@@ -3,44 +3,30 @@
 
 /**
  * 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.
  */
 
 function spawnTest () {
-  let { target, front } = yield initBackend(SIMPLE_URL, { TEST_MOCK_BUFFER_CHECK_TIMER: 10 });
-  let frontBufferStatusCalled = false;
+  let { target, front } = yield initBackend(SIMPLE_URL, {
+    TEST_MOCK_PROFILER_CHECK_TIMER: 10,
+    TEST_PROFILER_FILTER_STATUS: ["position", "totalSize", "generation"]
+  });
 
-  // Explicitly override the profiler's `isActive` method, where
-  // all the buffer info is retrieved, and delete the buffer properties.
-  let isActive = front._connection._profiler.isActive;
-  front._connection._profiler.isActive = function () {
-    return isActive.apply(front._connection._profiler, arguments).then(res => {
-      return { isActive: res.isActive, currentTime: res.currentTime };
-    });
-  };
-
-  front.on("buffer-status", () => frontBufferStatusCalled = true);
   let model = yield front.startRecording();
-  let [_, stats] = yield onceSpread(front._connection._profiler, "buffer-status");
-  is(stats.generation, void 0, "buffer-status has void `generation`");
-  is(stats.totalSize, void 0, "buffer-status has void `totalSize`");
-  is(stats.position, void 0, "buffer-status has void `position`");
-
   let count = 0;
   while (count < 5) {
-    let [_, stats] = yield onceSpread(front._connection._profiler, "buffer-status");
-    is(stats.generation, void 0, "buffer-status has void `generation`");
-    is(stats.totalSize, void 0, "buffer-status has void `totalSize`");
-    is(stats.position, void 0, "buffer-status has void `position`");
+    let [_, stats] = yield onceSpread(front._connection._profiler, "profiler-status");
+    is(stats.generation, void 0, "profiler-status has void `generation`");
+    is(stats.totalSize, void 0, "profiler-status has void `totalSize`");
+    is(stats.position, void 0, "profiler-status has void `position`");
     count++;
   }
 
   is(model.getBufferUsage(), null, "model should have `null` for its buffer usage");
   yield front.stopRecording(model);
   is(model.getBufferUsage(), null, "after recording, model should still have `null` for its buffer usage");
-  ok(!frontBufferStatusCalled, "the front should never emit a buffer-status event when not supported.");
 
   yield removeTab(target.tab);
   finish();
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-compatibility-07.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that recording notices does not display any buffer
+ * status on servers that do not support buffer statuses.
+ */
+function spawnTest () {
+  let { panel } = yield initPerformance(SIMPLE_URL, void 0, {
+    TEST_MOCK_PROFILER_CHECK_TIMER: 10,
+    TEST_PROFILER_FILTER_STATUS: ["position", "totalSize", "generation"]
+  });
+  let { gFront: front, EVENTS, $, PerformanceController, PerformanceView, RecordingsView } = panel.panelWin;
+
+  yield startRecording(panel);
+
+  yield once(front._connection._profiler, "profiler-status");
+  ok(!$("#details-pane-container").getAttribute("buffer-status"),
+    "container does not have [buffer-status] attribute when not supported");
+
+  yield once(front._connection._profiler, "profiler-status");
+  ok(!$("#details-pane-container").getAttribute("buffer-status"),
+    "container does not have [buffer-status] attribute when not supported");
+
+  yield stopRecording(panel);
+
+  yield teardown(panel);
+  finish();
+}
--- a/browser/devtools/performance/test/browser_perf-recording-model-02.js
+++ b/browser/devtools/performance/test/browser_perf-recording-model-02.js
@@ -3,36 +3,38 @@
 
 /**
  * Test that buffer status is correctly updated in recording models.
  */
 
 let BUFFER_SIZE = 20000;
 
 function spawnTest () {
-  let { target, front } = yield initBackend(SIMPLE_URL, { TEST_MOCK_BUFFER_CHECK_TIMER: 10 });
+  let { target, front } = yield initBackend(SIMPLE_URL, { TEST_MOCK_PROFILER_CHECK_TIMER: 10 });
   let config = { bufferSize: BUFFER_SIZE };
 
   let model = yield front.startRecording(config);
-  let [_, stats] = yield onceSpread(front, "buffer-status");
-  is(stats.totalSize, BUFFER_SIZE, `buffer-status event has correct totalSize: ${stats.totalSize}`);
-  ok(stats.position < BUFFER_SIZE, `buffer-status event has correct position: ${stats.position}`);
-  is(stats.generation, 0, `buffer-status event has correct generation: ${stats.generation}`);
+  let [_, stats] = yield onceSpread(front, "profiler-status");
+  is(stats.totalSize, BUFFER_SIZE, `profiler-status event has correct totalSize: ${stats.totalSize}`);
+  ok(stats.position < BUFFER_SIZE, `profiler-status event has correct position: ${stats.position}`);
+  is(stats.generation, 0, `profiler-status event has correct generation: ${stats.generation}`);
+  ok(stats.isActive, `profiler-status event is isActive`);
+  is(typeof stats.currentTime, "number", `profiler-status event has currentTime`);
 
   // Halt once more for a buffer status to ensure we're beyond 0
-  yield once(front, "buffer-status");
+  yield once(front, "profiler-status");
 
   let lastBufferStatus = 0;
   let checkCount = 0;
   while (lastBufferStatus < 1) {
     let currentBufferStatus = model.getBufferUsage();
-    ok(currentBufferStatus > lastBufferStatus, `buffer is more filled than before: ${currentBufferStatus}`);
+    ok(currentBufferStatus > lastBufferStatus, `buffer is more filled than before: ${currentBufferStatus} > ${lastBufferStatus}`);
     lastBufferStatus = currentBufferStatus;
     checkCount++;
-    yield once(front, "buffer-status");
+    yield once(front, "profiler-status");
   }
 
   ok(checkCount >= 1, "atleast 1 event were fired until the buffer was filled");
   is(lastBufferStatus, 1, "buffer usage cannot surpass 100%");
   yield front.stopRecording(model);
 
   is(model.getBufferUsage(), null, "getBufferUsage() should be null when no longer recording.");
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-recording-notices-03.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that recording notices display buffer status when available,
+ * and can switch between different recordings with the correct buffer information
+ * displayed.
+ */
+function spawnTest () {
+  loadFrameScripts();
+  // Keep it large, but still get to 1% relatively quick
+  Services.prefs.setIntPref(PROFILER_BUFFER_SIZE_PREF, 1000000);
+  let { panel } = yield initPerformance(SIMPLE_URL, void 0, { TEST_MOCK_PROFILER_CHECK_TIMER: 10 });
+  let { EVENTS, $, PerformanceController, PerformanceView, RecordingsView } = panel.panelWin;
+
+  yield startRecording(panel);
+
+  let percent = 0;
+  while (percent === 0) {
+    [,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_UPDATED);
+  }
+
+  let bufferUsage = PerformanceController.getCurrentRecording().getBufferUsage();
+  is($("#details-pane-container").getAttribute("buffer-status"), "in-progress",
+    "container has [buffer-status=in-progress]");
+  ok($("#recording-notice .buffer-status-message").value.indexOf(percent + "%") !== -1,
+    "buffer status text has correct percentage");
+
+  // Start a console profile
+  yield consoleProfile(panel.panelWin, "rust");
+
+  percent = 0;
+  while (percent <= (Math.floor(bufferUsage * 100))) {
+    [,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_UPDATED);
+  }
+
+  ok(percent > Math.floor(bufferUsage * 100), "buffer percentage increased in display");
+  bufferUsage = PerformanceController.getCurrentRecording().getBufferUsage();
+
+  is($("#details-pane-container").getAttribute("buffer-status"), "in-progress",
+    "container has [buffer-status=in-progress]");
+  ok($("#recording-notice .buffer-status-message").value.indexOf(percent + "%") !== -1,
+    "buffer status text has correct percentage");
+
+  RecordingsView.selectedIndex = 1;
+  percent = 0;
+  while (percent === 0) {
+    [,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_UPDATED);
+  }
+
+  ok(percent < Math.floor(bufferUsage * 100), "percentage updated for newly selected recording");
+  is($("#details-pane-container").getAttribute("buffer-status"), "in-progress",
+    "container has [buffer-status=in-progress]");
+  ok($("#console-recording-notice .buffer-status-message").value.indexOf(percent + "%") !== -1,
+    "buffer status text has correct percentage for console recording");
+
+  yield consoleProfileEnd(panel.panelWin, "rust");
+  RecordingsView.selectedIndex = 0;
+
+  percent = 0;
+  while (percent <= (Math.floor(bufferUsage * 100))) {
+    [,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_UPDATED);
+  }
+  ok(percent > Math.floor(bufferUsage * 100), "percentage increased for original recording");
+  is($("#details-pane-container").getAttribute("buffer-status"), "in-progress",
+    "container has [buffer-status=in-progress]");
+  ok($("#recording-notice .buffer-status-message").value.indexOf(percent + "%") !== -1,
+    "buffer status text has correct percentage");
+
+  yield stopRecording(panel);
+
+  yield teardown(panel);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-recording-notices-04.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that when a recording overlaps the circular buffer, that
+ * a class is assigned to the recording notices.
+ */
+function spawnTest () {
+  // Make sure the profiler module is stopped so we can set a new buffer limit
+  nsIProfilerModule.StopProfiler();
+  Services.prefs.setIntPref(PROFILER_BUFFER_SIZE_PREF, 1000);
+
+  let { panel } = yield initPerformance(SIMPLE_URL, void 0, { TEST_MOCK_PROFILER_CHECK_TIMER: 10 });
+  let { EVENTS, $, PerformanceController, PerformanceView } = panel.panelWin;
+
+  yield startRecording(panel);
+
+  let percent = 0;
+  while (percent < 100) {
+    [,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_UPDATED);
+  }
+
+  let bufferUsage = PerformanceController.getCurrentRecording().getBufferUsage();
+  ok(bufferUsage, 1, "Buffer is full for this recording");
+  ok($("#details-pane-container").getAttribute("buffer-status"), "full",
+    "container has [buffer-status=full]");
+
+  yield stopRecording(panel);
+
+  yield teardown(panel);
+  finish();
+}
--- a/browser/devtools/performance/test/head.js
+++ b/browser/devtools/performance/test/head.js
@@ -190,44 +190,47 @@ function initBackend(aUrl, targetOps={})
 
     yield target.makeRemote();
 
     // Attach addition options to `target`. This is used to force mock fronts
     // to smokescreen test different servers where memory or timeline actors
     // may not exist. Possible options that will actually work:
     // TEST_MOCK_MEMORY_ACTOR = true
     // TEST_MOCK_TIMELINE_ACTOR = true
-    // TEST_MOCK_BUFFER_CHECK_TIMER = number
+    // TEST_MOCK_PROFILER_CHECK_TIMER = number
+    // TEST_PROFILER_FILTER_STATUS = array
     merge(target, targetOps);
 
     let connection = getPerformanceActorsConnection(target);
     yield connection.open();
 
     let front = new PerformanceFront(connection);
     return { target, front };
   });
 }
 
-function initPerformance(aUrl, selectedTool="performance", targetOps={}) {
+function initPerformance(aUrl, tool="performance", targetOps={}) {
   info("Initializing a performance pane.");
 
   return Task.spawn(function*() {
     let tab = yield addTab(aUrl);
     let target = TargetFactory.forTab(tab);
 
     yield target.makeRemote();
 
     // Attach addition options to `target`. This is used to force mock fronts
     // to smokescreen test different servers where memory or timeline actors
     // may not exist. Possible options that will actually work:
     // TEST_MOCK_MEMORY_ACTOR = true
     // TEST_MOCK_TIMELINE_ACTOR = true
+    // TEST_MOCK_PROFILER_CHECK_TIMER = number
+    // TEST_PROFILER_FILTER_STATUS = array
     merge(target, targetOps);
 
-    let toolbox = yield gDevTools.showToolbox(target, selectedTool);
+    let toolbox = yield gDevTools.showToolbox(target, tool);
     let panel = toolbox.getCurrentPanel();
     return { target, panel, toolbox };
   });
 }
 
 /**
  * Initializes a webconsole panel. Returns a target, panel and toolbox reference.
  * Also returns a console property that allows calls to `profile` and `profileEnd`.
--- a/browser/themes/shared/devtools/performance.inc.css
+++ b/browser/themes/shared/devtools/performance.inc.css
@@ -102,16 +102,36 @@
 }
 
 #performance-view toolbarbutton.record-button[checked],
 #performance-view toolbarbutton.record-button[checked] {
   color: var(--theme-selection-color);
   background: var(--theme-selection-background);
 }
 
+#details-pane-container .buffer-status-message,
+#details-pane-container .buffer-status-message-full {
+  display: none;
+}
+
+#details-pane-container[buffer-status="in-progress"] .buffer-status-message {
+  display: block;
+  opacity: 0.5;
+}
+
+#details-pane-container[buffer-status="full"] .buffer-status-message {
+  display: block;
+  color: var(--theme-highlight-red);
+  font-weight: bold;
+  opacity: 1;
+}
+
+#details-pane-container[buffer-status="full"] .buffer-status-message-full {
+  display: block;
+}
 
 /* Overview Panel */
 
 #main-record-button {
   list-style-image: url(profiler-stopwatch.svg);
 }
 
 #main-record-button[checked] {