Bug 1476775 - Part 2: Change the profiler usage in devtools after API change r=julienw,gregtatum
authorNazım Can Altınova <canaltinova@gmail.com>
Fri, 23 Nov 2018 16:10:08 +0000
changeset 507145 bed93ebb313b1fb65bfbce423c7c09ac277c26e9
parent 507144 9f21792d4ae6515aa1e12503b530757e884d4ff0
child 507146 b599964cc3ee9182a50a4ed38996cef6fe87c13a
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjulienw, gregtatum
bugs1476775
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1476775 - Part 2: Change the profiler usage in devtools after API change r=julienw,gregtatum Depends on D6267 Differential Revision: https://phabricator.services.mozilla.com/D6268
devtools/client/performance-new/browser.js
devtools/client/performance-new/components/Settings.js
devtools/client/performance-new/initializer.js
devtools/client/performance-new/panel.js
devtools/client/performance-new/store/actions.js
devtools/client/performance-new/store/reducers.js
devtools/client/performance-new/store/selectors.js
devtools/client/performance-new/test/chrome/chrome.ini
devtools/client/performance-new/test/chrome/head.js
devtools/client/performance-new/test/chrome/test_perf-settings-duration.html
devtools/client/performance-new/utils.js
devtools/client/performance/modules/global.js
devtools/client/webconsole/test/chrome/test_render_perf.html
devtools/client/webide/content/webide.js
devtools/server/actors/perf.js
devtools/server/actors/root.js
devtools/server/tests/browser/browser_perf-04.js
devtools/shared/moz.build
devtools/shared/performance-new/common.js
devtools/shared/performance-new/moz.build
devtools/shared/specs/perf.js
--- a/devtools/client/performance-new/browser.js
+++ b/devtools/client/performance-new/browser.js
@@ -115,24 +115,29 @@ async function _getIntPref(preferenceFro
  * is helpful for configuring for remote targets like Android phones that may require
  * different features or configurations.
  *
  * @param {PreferenceFront} preferenceFront
  * @param {object} defaultSettings See the getRecordingSettings selector for the shape
  *                                 of the object and how it gets defined.
  */
 async function getRecordingPreferences(preferenceFront, defaultSettings = {}) {
-  const [ entries, interval, features, threads ] = await Promise.all([
+  const [ entries, duration, interval, features, threads ] = await Promise.all([
     _getIntPref(
       preferenceFront,
       `devtools.performance.recording.entries`,
       defaultSettings.entries
     ),
     _getIntPref(
       preferenceFront,
+      `devtools.performance.recording.window-length`,
+      defaultSettings.duration
+    ),
+    _getIntPref(
+      preferenceFront,
       `devtools.performance.recording.interval`,
       defaultSettings.interval
     ),
     _getArrayOfStringsPref(
       preferenceFront,
       `devtools.performance.recording.features`,
       defaultSettings.features
     ),
@@ -140,17 +145,17 @@ async function getRecordingPreferences(p
       preferenceFront,
       `devtools.performance.recording.threads`,
       defaultSettings.threads
     ),
   ]);
 
   // The pref stores the value in usec.
   const newInterval = interval / 1000;
-  return { entries, interval: newInterval, features, threads };
+  return { entries, duration, interval: newInterval, features, threads };
 }
 
 /**
  * Take the recording settings, as defined by the getRecordingSettings selector, and
  * persist them to preferences.
  *
  * @param {PreferenceFront} preferenceFront
  * @param {object} defaultSettings See the getRecordingSettings selector for the shape
@@ -158,16 +163,20 @@ async function getRecordingPreferences(p
  */
 async function setRecordingPreferences(preferenceFront, settings) {
   await Promise.all([
     preferenceFront.setIntPref(
       `devtools.performance.recording.entries`,
       settings.entries
     ),
     preferenceFront.setIntPref(
+      `devtools.performance.recording.window-length`,
+      settings.duration
+    ),
+    preferenceFront.setIntPref(
       `devtools.performance.recording.interval`,
       // The pref stores the value in usec.
       settings.interval * 1000
     ),
     preferenceFront.setCharPref(
       `devtools.performance.recording.features`,
       JSON.stringify(settings.features)
     ),
--- a/devtools/client/performance-new/components/Settings.js
+++ b/devtools/client/performance-new/components/Settings.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 const { PureComponent, createFactory } = require("devtools/client/shared/vendor/react");
 const { div, details, summary, label, input, span, h2, section } = require("devtools/client/shared/vendor/react-dom-factories");
 const Range = createFactory(require("devtools/client/performance-new/components/Range"));
-const { makeExponentialScale, formatFileSize, calculateOverhead } = require("devtools/client/performance-new/utils");
+const { makeExponentialScale, formatFileSize, calculateOverhead, INFINITE_WINDOW_LENGTH } = require("devtools/client/performance-new/utils");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const actions = require("devtools/client/performance-new/store/actions");
 const selectors = require("devtools/client/performance-new/store/selectors");
 
 // sizeof(double) + sizeof(char)
 // http://searchfox.org/mozilla-central/rev/e8835f52eff29772a57dca7bcc86a9a312a23729/tools/profiler/core/ProfileEntry.h#73
 const PROFILE_ENTRY_SIZE = 9;
@@ -154,23 +154,26 @@ const featureCheckboxes = [
  * This component manages the settings for recording a performance profile.
  */
 class Settings extends PureComponent {
   static get propTypes() {
     return {
       // StateProps
       interval: PropTypes.number.isRequired,
       entries: PropTypes.number.isRequired,
+      duration: PropTypes.number.isRequired,
       features: PropTypes.array.isRequired,
       threads: PropTypes.array.isRequired,
       threadsString: PropTypes.string.isRequired,
+      actorVersion: PropTypes.string.isRequired,
 
       // DispatchProps
       changeInterval: PropTypes.func.isRequired,
       changeEntries: PropTypes.func.isRequired,
+      changeDuration: PropTypes.func.isRequired,
       changeFeatures: PropTypes.func.isRequired,
       changeThreads: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.state = {
@@ -181,16 +184,17 @@ class Settings extends PureComponent {
     this._handleThreadCheckboxChange = this._handleThreadCheckboxChange.bind(this);
     this._handleFeaturesCheckboxChange = this._handleFeaturesCheckboxChange.bind(this);
     this._setThreadTextFromInput = this._setThreadTextFromInput.bind(this);
     this._handleThreadTextCleanup = this._handleThreadTextCleanup.bind(this);
     this._renderThreadsColumns = this._renderThreadsColumns.bind(this);
 
     this._intervalExponentialScale = makeExponentialScale(0.01, 100);
     this._entriesExponentialScale = makeExponentialScale(100000, 100000000);
+    this._durationExponentialScale = makeExponentialScale(1, INFINITE_WINDOW_LENGTH);
   }
 
   _renderNotches() {
     const { interval, entries, features } = this.props;
     const overhead = calculateOverhead(interval, entries, features);
     const notchCount = 22;
     const notches = [];
     for (let i = 0; i < notchCount; i++) {
@@ -381,18 +385,30 @@ class Settings extends PureComponent {
       Range({
         label: "Sampling interval:",
         value: this.props.interval,
         id: "perf-range-interval",
         scale: this._intervalExponentialScale,
         display: _intervalTextDisplay,
         onChange: this.props.changeInterval,
       }),
+      // Firefox 65 introduced a duration-based buffer with actorVersion 1.
+      // We are hiding the duration range if the actor is older. Fx65+
+      this.props.actorVersion > 0
+        ? Range({
+          label: "Window length:",
+          value: this.props.duration,
+          id: "perf-range-duration",
+          scale: this._durationExponentialScale,
+          display: _durationTextDisplay,
+          onChange: this.props.changeDuration,
+        })
+        : null,
       Range({
-        label: "Buffer size:",
+        label: "Max buffer size:",
         value: this.props.entries,
         id: "perf-range-entries",
         scale: this._entriesExponentialScale,
         display: _entriesTextDisplay,
         onChange: this.props.changeEntries,
       }),
       this._renderThreads(),
       this._renderFeatures()
@@ -428,26 +444,38 @@ function _intervalTextDisplay(value) {
  * Format the entries number for display.
  * @param {number} value
  * @return {string}
  */
 function _entriesTextDisplay(value) {
   return formatFileSize(value * PROFILE_ENTRY_SIZE);
 }
 
+/**
+ * Format the duration number for display.
+ * @param {number} value
+ * @return {string}
+ */
+function _durationTextDisplay(value) {
+  return value === INFINITE_WINDOW_LENGTH ? `∞` : `${value} sec`;
+}
+
 function mapStateToProps(state) {
   return {
     interval: selectors.getInterval(state),
     entries: selectors.getEntries(state),
+    duration: selectors.getDuration(state),
     features: selectors.getFeatures(state),
     threads: selectors.getThreads(state),
     threadsString: selectors.getThreadsString(state),
+    actorVersion: selectors.getActorVersion(state),
   };
 }
 
 const mapDispatchToProps = {
   changeInterval: actions.changeInterval,
   changeEntries: actions.changeEntries,
+  changeDuration: actions.changeDuration,
   changeFeatures: actions.changeFeatures,
   changeThreads: actions.changeThreads,
 };
 
 module.exports = connect(mapStateToProps, mapDispatchToProps)(Settings);
--- a/devtools/client/performance-new/initializer.js
+++ b/devtools/client/performance-new/initializer.js
@@ -25,25 +25,28 @@ const {
   setRecordingPreferences,
 } = require("devtools/client/performance-new/browser");
 
 /**
  * Initialize the panel by creating a redux store, and render the root component.
  *
  * @param perfFront - The Perf actor's front. Used to start and stop recordings.
  * @param preferenceFront - Used to get the recording preferences from the device.
+ * @param actorVersion {(number|undefined)} - The Perf actor's version.
  */
-async function gInit(perfFront, preferenceFront) {
+async function gInit(perfFront, preferenceFront, actorVersion) {
   const store = createStore(reducers);
+  actorVersion = actorVersion || 0;
 
   // Do some initialization, especially with privileged things that are part of the
   // the browser.
   store.dispatch(actions.initializeStore({
     perfFront,
     receiveProfile,
+    actorVersion,
     // Pull the default recording settings from the reducer, and update them according
     // to what's in the target's preferences. This way the preferences are stored
     // on the target. This could be useful for something like Android where you might
     // want to tweak the settings.
     recordingSettingsFromPreferences: await getRecordingPreferences(
       preferenceFront,
       selectors.getRecordingSettings(store.getState())
     ),
--- a/devtools/client/performance-new/panel.js
+++ b/devtools/client/performance-new/panel.js
@@ -22,25 +22,26 @@ class PerformancePanel {
       this._opening = this._doOpen();
     }
     return this._opening;
   }
 
   async _doOpen() {
     this.panelWin.gToolbox = this.toolbox;
     this.panelWin.gTarget = this.target;
+    const actorVersion = this.target.getTrait("perfActorVersion");
 
     const [perfFront, preferenceFront] = await Promise.all([
       this.target.client.mainRoot.getFront("perf"),
       this.target.client.mainRoot.getFront("preference"),
     ]);
 
     this.isReady = true;
     this.emit("ready");
-    this.panelWin.gInit(perfFront, preferenceFront);
+    this.panelWin.gInit(perfFront, preferenceFront, actorVersion);
     return this;
   }
 
   // DevToolPanel API:
 
   get target() {
     return this.toolbox.target;
   }
--- a/devtools/client/performance-new/store/actions.js
+++ b/devtools/client/performance-new/store/actions.js
@@ -4,17 +4,17 @@
 "use strict";
 
 const selectors = require("devtools/client/performance-new/store/selectors");
 const { recordingState: {
   AVAILABLE_TO_RECORD,
   REQUEST_TO_START_RECORDING,
   REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER,
   REQUEST_TO_STOP_PROFILER,
-}} = require("devtools/client/performance-new/utils");
+}, INFINITE_WINDOW_LENGTH } = require("devtools/client/performance-new/utils");
 
 /**
  * The recording state manages the current state of the recording panel.
  * @param {string} state - A valid state in `recordingState`.
  * @param {object} options
  */
 const changeRecordingState = exports.changeRecordingState =
   (state, options = { didRecordingUnexpectedlyStopped: false }) => ({
@@ -68,16 +68,25 @@ exports.changeInterval = interval => _di
  * @param {number} entries
  */
 exports.changeEntries = entries => _dispatchAndUpdatePreferences({
   type: "CHANGE_ENTRIES",
   entries,
 });
 
 /**
+ * Updates the recording settings for the duration.
+ * @param {number} duration in seconds
+ */
+exports.changeDuration = duration => _dispatchAndUpdatePreferences({
+  type: "CHANGE_DURATION",
+  duration,
+});
+
+/**
  * Updates the recording settings for the features.
  * @param {object} features
  */
 exports.changeFeatures = features => _dispatchAndUpdatePreferences({
   type: "CHANGE_FEATURES",
   features,
 });
 
@@ -102,16 +111,27 @@ exports.initializeStore = values => ({
 
 /**
  * Start a new recording with the perfFront and update the internal recording state.
  */
 exports.startRecording = () => {
   return (dispatch, getState) => {
     const recordingSettings = selectors.getRecordingSettings(getState());
     const perfFront = selectors.getPerfFront(getState());
+    // We should pass 0 to startProfiler call if the window length should be infinite.
+    if (recordingSettings.duration === INFINITE_WINDOW_LENGTH) {
+      recordingSettings.duration = 0;
+    }
+    // Firefox 65 introduced a duration-based buffer with actorVersion 1.
+    // We should delete the duration parameter if the profiled Firefox is older than
+    // version 1. This cannot happen inside the devtools panel but it may happen
+    // when profiling an older Firefox with remote debugging. Fx65+
+    if (selectors.getActorVersion(getState()) < 1) {
+      delete recordingSettings.duration;
+    }
     perfFront.startProfiler(recordingSettings);
     dispatch(changeRecordingState(REQUEST_TO_START_RECORDING));
   };
 };
 
 /**
  * Returns a function getDebugPathFor(debugName, breakpadId) => string which
  * resolves a (debugName, breakpadId) pair to the library's debugPath, i.e.
--- a/devtools/client/performance-new/store/reducers.js
+++ b/devtools/client/performance-new/store/reducers.js
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 const { combineReducers } = require("devtools/client/shared/vendor/redux");
 
 const { recordingState: {
   NOT_YET_KNOWN,
 }} = require("devtools/client/performance-new/utils");
-
+const { DEFAULT_WINDOW_LENGTH } = require("devtools/shared/performance-new/common");
 /**
  * The current state of the recording.
  * @param state - A recordingState key.
  */
 function recordingState(state = NOT_YET_KNOWN, action) {
   switch (action.type) {
     case "CHANGE_RECORDING_STATE":
       return action.state;
@@ -80,16 +80,31 @@ function entries(state = 10000000, actio
     case "INITIALIZE_STORE":
       return action.recordingSettingsFromPreferences.entries;
     default:
       return state;
   }
 }
 
 /**
+ * The window length of profiler's circular buffer. Defaults to 20 sec.
+ * @param {number} state
+ */
+function duration(state = DEFAULT_WINDOW_LENGTH, action) {
+  switch (action.type) {
+    case "CHANGE_DURATION":
+      return action.duration;
+    case "INITIALIZE_STORE":
+      return action.recordingSettingsFromPreferences.duration;
+    default:
+      return state;
+  }
+}
+
+/**
  * The features that are enabled for the profiler.
  * @param {array} state
  */
 function features(state = ["js", "stackwalk", "responsiveness"], action) {
   switch (action.type) {
     case "CHANGE_FEATURES":
       return action.features;
     case "INITIALIZE_STORE":
@@ -133,18 +148,33 @@ function initializedValues(state = null,
         receiveProfile: action.receiveProfile,
         setRecordingPreferences: action.setRecordingPreferences,
       };
     default:
       return state;
   }
 }
 
+/**
+ * The current actor version
+ * @param {number} state
+ */
+function actorVersion(state = 0, action) {
+  switch (action.type) {
+    case "INITIALIZE_STORE":
+      return action.actorVersion;
+    default:
+      return state;
+  }
+}
+
 module.exports = combineReducers({
   recordingState,
   recordingUnexpectedlyStopped,
   isSupportedPlatform,
   interval,
   entries,
+  duration,
   features,
   threads,
   initializedValues,
+  actorVersion,
 });
--- a/devtools/client/performance-new/store/selectors.js
+++ b/devtools/client/performance-new/store/selectors.js
@@ -3,23 +3,26 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const getRecordingState = state => state.recordingState;
 const getRecordingUnexpectedlyStopped = state => state.recordingUnexpectedlyStopped;
 const getIsSupportedPlatform = state => state.isSupportedPlatform;
 const getInterval = state => state.interval;
 const getEntries = state => state.entries;
+const getDuration = state => state.duration;
 const getFeatures = state => state.features;
 const getThreads = state => state.threads;
 const getThreadsString = state => getThreads(state).join(",");
+const getActorVersion = state => state.actorVersion;
 
 const getRecordingSettings = state => {
   return {
     entries: getEntries(state),
+    duration: getDuration(state),
     interval: getInterval(state),
     features: getFeatures(state),
     threads: getThreads(state),
   };
 };
 
 const getInitializedValues = state => {
   const values = state.initializedValues;
@@ -35,17 +38,19 @@ const getSetRecordingPreferencesFn =
   state => getInitializedValues(state).setRecordingPreferences;
 
 module.exports = {
   getRecordingState,
   getRecordingUnexpectedlyStopped,
   getIsSupportedPlatform,
   getInterval,
   getEntries,
+  getDuration,
   getFeatures,
   getThreads,
   getThreadsString,
   getRecordingSettings,
   getInitializedValues,
   getPerfFront,
   getReceiveProfileFn,
   getSetRecordingPreferencesFn,
+  getActorVersion,
 };
--- a/devtools/client/performance-new/test/chrome/chrome.ini
+++ b/devtools/client/performance-new/test/chrome/chrome.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 support-files =
   head.js
 
+[test_perf-settings-duration.html]
 [test_perf-settings-entries.html]
 [test_perf-settings-features.html]
 [test_perf-settings-interval.html]
 [test_perf-settings-threads.html]
 [test_perf-state-01.html]
 [test_perf-state-02.html]
 [test_perf-state-03.html]
 [test_perf-state-04.html]
--- a/devtools/client/performance-new/test/chrome/head.js
+++ b/devtools/client/performance-new/test/chrome/head.js
@@ -185,16 +185,17 @@ function createPerfComponent() {
   function recordingPreferencesMock(settings) {
     recordingPreferencesCalls.push(settings);
   }
 
   function mountComponent() {
     store.dispatch(actions.initializeStore({
       perfFront: perfFrontMock,
       receiveProfile: receiveProfileMock,
+      actorVersion: 1,
       recordingSettingsFromPreferences: selectors.getRecordingSettings(store.getState()),
       setRecordingPreferences: recordingPreferencesMock,
     }));
 
     return ReactDOM.render(
       React.createElement(
         ReactRedux.Provider,
         { store },
new file mode 100644
--- /dev/null
+++ b/devtools/client/performance-new/test/chrome/test_perf-settings-duration.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!-- 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/. -->
+<head>
+  <meta charset="utf-8">
+  <title>Perf component test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+  <div id="container"></div>
+
+  <pre id="test">
+    <script src="head.js" type="application/javascript"></script>
+    <script type="application/javascript">
+      "use strict";
+
+      /**
+       * Test that the duration setting can be changed.
+       */
+      addPerfTest(async () => {
+        const {
+          perfFrontMock,
+          mountAndInitializeComponent,
+          selectors,
+          getState,
+          recordingPreferencesCalls,
+        } = createPerfComponent();
+
+        await mountAndInitializeComponent();
+
+        is(selectors.getDuration(getState()), 20,
+          "The duration starts out with the default");
+        is(recordingPreferencesCalls.length, 0,
+          "No calls have been made to set preferences");
+
+        const inputValue = 75;
+        const scaledValue = 90;
+        const input = document.querySelector("#perf-range-duration");
+        setReactFriendlyInputValue(input, inputValue);
+
+        is(selectors.getDuration(getState()), scaledValue,
+          "The duration was changed according to a logarithmic scale.");
+        is(recordingPreferencesCalls[0].duration, scaledValue,
+          "The preference was recorded.");
+
+        // Start the profiler by clicking the start button, and flushing the async
+        // calls out to the mock perf front.
+        document.querySelector("button").click();
+        await perfFrontMock._flushAsyncQueue();
+
+        is(perfFrontMock._startProfilerCalls.length, 1,
+          "Start profiler was called once");
+        is(perfFrontMock._startProfilerCalls[0].duration, scaledValue,
+          "Start profiler was called with the correct duration");
+      });
+    </script>
+  </pre>
+</body>
+</html>
--- a/devtools/client/performance-new/utils.js
+++ b/devtools/client/performance-new/utils.js
@@ -20,16 +20,24 @@ const recordingState = {
   // Some other code with access to the profiler started it.
   OTHER_IS_RECORDING: "other-is-recording",
   // Profiling is not available when in private browsing mode.
   LOCKED_BY_PRIVATE_BROWSING: "locked-by-private-browsing",
 };
 
 const UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
 
+// Window Length accepts a numerical value between 1-N. We also need to put an
+// infinite number at the end of the window length slider. Therefore, the max
+// value pretends like it's infinite in the slider.
+// The maximum value of window length is 300 seconds. And because of the working
+// mechanism of `makeExponentialScale` the next value is 400, so we are treating
+// 400 as infinity.
+const INFINITE_WINDOW_LENGTH = 400;
+
 /**
  * Linearly interpolate between values.
  * https://en.wikipedia.org/wiki/Linear_interpolation
  *
  * @param {number} frac - Value ranged 0 - 1 to interpolate between the range
  *                        start and range end.
  * @param {number} rangeState - The value to start from.
  * @param {number} rangeEnd - The value to interpolate to.
@@ -175,9 +183,10 @@ function calculateOverhead(interval, buf
 }
 
 module.exports = {
   formatFileSize,
   makeExponentialScale,
   scaleRangeWithClamping,
   calculateOverhead,
   recordingState,
+  INFINITE_WINDOW_LENGTH,
 };
--- a/devtools/client/performance/modules/global.js
+++ b/devtools/client/performance/modules/global.js
@@ -25,12 +25,13 @@ exports.L10N = new MultiLocalizationHelp
  */
 exports.PREFS = new PrefsHelper("devtools.performance", {
   "show-triggers-for-gc-types": ["Char", "ui.show-triggers-for-gc-types"],
   "show-platform-data": ["Bool", "ui.show-platform-data"],
   "hidden-markers": ["Json", "timeline.hidden-markers"],
   "memory-sample-probability": ["Float", "memory.sample-probability"],
   "memory-max-log-length": ["Int", "memory.max-log-length"],
   "profiler-buffer-size": ["Int", "profiler.buffer-size"],
+  "profiler-window-length": ["Int", "profiler.window-length-seconds"],
   "profiler-sample-frequency": ["Int", "profiler.sample-frequency-hz"],
   // TODO: re-enable once we flame charts via bug 1148663.
   "enable-memory-flame": ["Bool", "ui.enable-memory-flame"],
 });
--- a/devtools/client/webconsole/test/chrome/test_render_perf.html
+++ b/devtools/client/webconsole/test/chrome/test_render_perf.html
@@ -177,27 +177,29 @@ window.onload = async function() {
     null,
     document,
   );
   wrapper.init();
 
   // From https://github.com/devtools-html/perf.html/blob/b73eb73df04c7df51464fa50eeadef3dc7f5d4e2/docs/gecko-profile-format.md#L21
   const settings = {
     entries: 100000000,
+    duration: 20,
     interval: 1,
     features: ["js"],
     threads: ["GeckoMain"],
   };
   Services.profiler.StartProfiler(
     settings.entries,
     settings.interval,
     settings.features,
     settings.features.length,
     settings.threads,
-    settings.threads.length
+    settings.threads.length,
+    settings.duration
   );
   info("Profiler has started");
 
   await wait(500);
 
   await testBulkLogging(wrapper);
 
   await wait(500);
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -878,17 +878,20 @@ var Cmds = {
     UI.selectDeckPanel("devicepreferences");
   },
 
   showPerformancePanel: function() {
     UI.selectDeckPanel("performance");
     const iframe = document.getElementById("deck-panel-performance");
 
     iframe.addEventListener("DOMContentLoaded", () => {
-      iframe.contentWindow.gInit(AppManager.perfFront, AppManager.preferenceFront);
+      const actorVersion = AppManager.connection.client.mainRoot.traits.perfActorVersion;
+      iframe.contentWindow.gInit(AppManager.perfFront,
+                                 AppManager.preferenceFront,
+                                 actorVersion);
     }, { once: true });
   },
 
   async play() {
     let busy;
     switch (AppManager.selectedProject.type) {
       case "packaged":
         busy = UI.busyWithProgressUntil(AppManager.installAndRunProject(),
--- a/devtools/server/actors/perf.js
+++ b/devtools/server/actors/perf.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const protocol = require("devtools/shared/protocol");
 const { ActorClassWithSpec, Actor } = protocol;
 const { perfSpec } = require("devtools/shared/specs/perf");
+const { DEFAULT_WINDOW_LENGTH } = require("devtools/shared/performance-new/common");
 const { Ci } = require("chrome");
 const Services = require("Services");
 
 loader.lazyImporter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 // Some platforms are built without the Gecko Profiler.
 const IS_SUPPORTED_PLATFORM = "nsIProfiler" in Ci;
@@ -49,31 +50,34 @@ exports.PerfActor = ActorClassWithSpec(p
     if (!IS_SUPPORTED_PLATFORM) {
       return false;
     }
 
     // For a quick implementation, decide on some default values. These may need
     // to be tweaked or made configurable as needed.
     const settings = {
       entries: options.entries || 1000000,
+      duration: options.duration !== undefined
+        ? options.duration : DEFAULT_WINDOW_LENGTH,
       interval: options.interval || 1,
       features: options.features ||
         ["js", "stackwalk", "responsiveness", "threads", "leaf"],
       threads: options.threads || ["GeckoMain", "Compositor"],
     };
 
     try {
       // This can throw an error if the profiler is in the wrong state.
       Services.profiler.StartProfiler(
         settings.entries,
         settings.interval,
         settings.features,
         settings.features.length,
         settings.threads,
-        settings.threads.length
+        settings.threads.length,
+        settings.duration
       );
     } catch (e) {
       // In case any errors get triggered, bailout with a false.
       return false;
     }
 
     return true;
   },
@@ -148,16 +152,16 @@ exports.PerfActor = ActorClassWithSpec(p
           this.emit("profile-locked-by-private-browsing");
         }
         break;
       case "last-pb-context-exited":
         this.emit("profile-unlocked-from-private-browsing");
         break;
       case "profiler-started":
         const param = subject.QueryInterface(Ci.nsIProfilerStartParams);
-        this.emit(topic, param.entries, param.interval, param.features);
+        this.emit(topic, param.entries, param.interval, param.features, param.duration);
         break;
       case "profiler-stopped":
         this.emit(topic);
         break;
     }
   },
 });
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -160,16 +160,22 @@ RootActor.prototype = {
     // fully equipped to handle heap snapshots for the memory tool. Fx44+
     heapSnapshots: true,
     // Whether or not the timeline actor can emit DOMContentLoaded and Load
     // markers, currently in use by the network monitor. Fx45+
     documentLoadingMarkers: true,
     // Whether or not the webextension addon actor have to be connected
     // to retrieve the extension child process target actors.
     webExtensionAddonConnect: true,
+    // Version of perf actor. Fx65+
+    // Version 1 - Firefox 65: Introduces a duration-based buffer. With that change
+    // Services.profiler.StartProfiler method accepts an additional parameter called
+    // `window-length`. This is an optional parameter but it will throw an error if the
+    // profiled Firefox doesn't accept it.
+    perfActorVersion: 1,
   },
 
   /**
    * Return a 'hello' packet as specified by the Remote Debugging Protocol.
    */
   sayHello: function() {
     return {
       from: this.actorID,
--- a/devtools/server/tests/browser/browser_perf-04.js
+++ b/devtools/server/tests/browser/browser_perf-04.js
@@ -12,24 +12,25 @@ add_task(async function() {
   // Assert the initial state.
   is(await front.isSupportedPlatform(), true,
     "This test only runs on supported platforms.");
   is(await front.isLockedForPrivateBrowsing(), false,
     "The browser is not in private browsing mode.");
   is(await front.isActive(), false,
     "The profiler is not active yet.");
 
-  front.once("profiler-started", (entries, interval, features) => {
+  front.once("profiler-started", (entries, interval, features, duration) => {
     is(entries, 1000, "Should apply entries by startProfiler");
     is(interval, 0.1, "Should apply interval by startProfiler");
     is(features, 0x202, "Should apply features by startProfiler");
+    is(duration, 2, "Should apply duration by startProfiler");
   });
 
   // Start the profiler.
-  await front.startProfiler({ entries: 1000, interval: 0.1,
+  await front.startProfiler({ entries: 1000, duration: 2, interval: 0.1,
                               features: ["js", "stackwalk"] });
 
   is(await front.isActive(), true, "The profiler is active.");
 
   // clean up
   await front.stopProfilerAndDiscardProfile();
   await front.destroy();
   await client.close();
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -16,16 +16,17 @@ DIRS += [
     'fronts',
     'heapsnapshot',
     'inspector',
     'jsbeautify',
     'layout',
     'locales',
     'node-properties',
     'performance',
+    'performance-new',
     'platform',
     'protocol',
     'pretty-fast',
     'qrcode',
     'screenshot',
     'security',
     'sourcemap',
     'sprintfjs',
new file mode 100644
--- /dev/null
+++ b/devtools/shared/performance-new/common.js
@@ -0,0 +1,12 @@
+/* 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";
+
+/*
+ * Default window length in seconds.
+ */
+const DEFAULT_WINDOW_LENGTH = 20;
+
+exports.DEFAULT_WINDOW_LENGTH = DEFAULT_WINDOW_LENGTH;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/performance-new/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+    'common.js',
+)
--- a/devtools/shared/specs/perf.js
+++ b/devtools/shared/specs/perf.js
@@ -9,32 +9,34 @@ const perfDescription = {
   typeName: "perf",
 
   events: {
     "profiler-started": {
       type: "profiler-started",
       entries: Arg(0, "number"),
       interval: Arg(1, "number"),
       features: Arg(2, "number"),
+      duration: Arg(3, "nullable:number"),
     },
     "profiler-stopped": {
       type: "profiler-stopped",
     },
     "profile-locked-by-private-browsing": {
       type: "profile-locked-by-private-browsing",
     },
     "profile-unlocked-from-private-browsing": {
       type: "profile-unlocked-from-private-browsing",
     },
   },
 
   methods: {
     startProfiler: {
       request: {
         entries: Option(0, "number"),
+        duration: Option(0, "nullable:number"),
         interval: Option(0, "number"),
         features: Option(0, "array:string"),
         threads: Option(0, "array:string"),
       },
       response: { value: RetVal("boolean") },
     },
 
     /**