mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 01:35:35 +00:00
424 lines
14 KiB
JavaScript
424 lines
14 KiB
JavaScript
/* 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/. */
|
|
/* import-globals-from ../performance-controller.js */
|
|
/* import-globals-from ../performance-view.js */
|
|
"use strict";
|
|
|
|
// No sense updating the overview more often than receiving data from the
|
|
// backend. Make sure this isn't lower than DEFAULT_TIMELINE_DATA_PULL_TIMEOUT
|
|
// in devtools/server/actors/timeline.js
|
|
|
|
// The following units are in milliseconds.
|
|
const OVERVIEW_UPDATE_INTERVAL = 200;
|
|
const FRAMERATE_GRAPH_LOW_RES_INTERVAL = 100;
|
|
const FRAMERATE_GRAPH_HIGH_RES_INTERVAL = 16;
|
|
const GRAPH_REQUIREMENTS = {
|
|
timeline: {
|
|
features: ["withMarkers"]
|
|
},
|
|
framerate: {
|
|
features: ["withTicks"]
|
|
},
|
|
memory: {
|
|
features: ["withMemory"]
|
|
},
|
|
};
|
|
|
|
/**
|
|
* View handler for the overview panel's time view, displaying
|
|
* framerate, timeline and memory over time.
|
|
*/
|
|
var OverviewView = {
|
|
|
|
/**
|
|
* How frequently we attempt to render the graphs. Overridden
|
|
* in tests.
|
|
*/
|
|
OVERVIEW_UPDATE_INTERVAL: OVERVIEW_UPDATE_INTERVAL,
|
|
|
|
/**
|
|
* Sets up the view with event binding.
|
|
*/
|
|
initialize: function () {
|
|
this.graphs = new GraphsController({
|
|
root: $("#overview-pane"),
|
|
getFilter: () => PerformanceController.getPref("hidden-markers"),
|
|
getTheme: () => PerformanceController.getTheme(),
|
|
});
|
|
|
|
// If no timeline support, shut it all down.
|
|
if (!PerformanceController.getTraits().features.withMarkers) {
|
|
this.disable();
|
|
return;
|
|
}
|
|
|
|
// Store info on multiprocess support.
|
|
this._multiprocessData = PerformanceController.getMultiprocessStatus();
|
|
|
|
this._onRecordingStateChange = this._onRecordingStateChange.bind(this);
|
|
this._onRecordingSelected = this._onRecordingSelected.bind(this);
|
|
this._onRecordingTick = this._onRecordingTick.bind(this);
|
|
this._onGraphSelecting = this._onGraphSelecting.bind(this);
|
|
this._onGraphRendered = this._onGraphRendered.bind(this);
|
|
this._onPrefChanged = this._onPrefChanged.bind(this);
|
|
this._onThemeChanged = this._onThemeChanged.bind(this);
|
|
|
|
// Toggle the initial visibility of memory and framerate graph containers
|
|
// based off of prefs.
|
|
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
|
PerformanceController.on(EVENTS.THEME_CHANGED, this._onThemeChanged);
|
|
PerformanceController.on(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange);
|
|
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
|
this.graphs.on("selecting", this._onGraphSelecting);
|
|
this.graphs.on("rendered", this._onGraphRendered);
|
|
},
|
|
|
|
/**
|
|
* Unbinds events.
|
|
*/
|
|
destroy: Task.async(function* () {
|
|
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
|
PerformanceController.off(EVENTS.THEME_CHANGED, this._onThemeChanged);
|
|
PerformanceController.off(EVENTS.RECORDING_STATE_CHANGE,
|
|
this._onRecordingStateChange);
|
|
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
|
this.graphs.off("selecting", this._onGraphSelecting);
|
|
this.graphs.off("rendered", this._onGraphRendered);
|
|
yield this.graphs.destroy();
|
|
}),
|
|
|
|
/**
|
|
* Returns true if any of the overview graphs have mouse dragging active,
|
|
* false otherwise.
|
|
*/
|
|
get isMouseActive() {
|
|
// Fetch all graphs currently stored in the GraphsController.
|
|
// These graphs are not necessarily active, but will not have
|
|
// an active mouse, in that case.
|
|
return !!this.graphs.getWidgets().some(e => e.isMouseActive);
|
|
},
|
|
|
|
/**
|
|
* Disabled in the event we're using a Timeline mock, so we'll have no
|
|
* timeline, ticks or memory data to show, so just block rendering and hide
|
|
* the panel.
|
|
*/
|
|
disable: function () {
|
|
this._disabled = true;
|
|
this.graphs.disableAll();
|
|
},
|
|
|
|
/**
|
|
* Returns the disabled status.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
isDisabled: function () {
|
|
return this._disabled;
|
|
},
|
|
|
|
/**
|
|
* Sets the time interval selection for all graphs in this overview.
|
|
*
|
|
* @param object interval
|
|
* The { startTime, endTime }, in milliseconds.
|
|
*/
|
|
setTimeInterval: function (interval, options = {}) {
|
|
let recording = PerformanceController.getCurrentRecording();
|
|
if (recording == null) {
|
|
throw new Error("A recording should be available in order to set the selection.");
|
|
}
|
|
if (this.isDisabled()) {
|
|
return;
|
|
}
|
|
let mapStart = () => 0;
|
|
let mapEnd = () => recording.getDuration();
|
|
let selection = { start: interval.startTime, end: interval.endTime };
|
|
this._stopSelectionChangeEventPropagation = options.stopPropagation;
|
|
this.graphs.setMappedSelection(selection, { mapStart, mapEnd });
|
|
this._stopSelectionChangeEventPropagation = false;
|
|
},
|
|
|
|
/**
|
|
* Gets the time interval selection for all graphs in this overview.
|
|
*
|
|
* @return object
|
|
* The { startTime, endTime }, in milliseconds.
|
|
*/
|
|
getTimeInterval: function () {
|
|
let recording = PerformanceController.getCurrentRecording();
|
|
if (recording == null) {
|
|
throw new Error("A recording should be available in order to get the selection.");
|
|
}
|
|
if (this.isDisabled()) {
|
|
return { startTime: 0, endTime: recording.getDuration() };
|
|
}
|
|
let mapStart = () => 0;
|
|
let mapEnd = () => recording.getDuration();
|
|
let selection = this.graphs.getMappedSelection({ mapStart, mapEnd });
|
|
// If no selection returned, this means the overview graphs have not been rendered
|
|
// yet, so act as if we have no selection (the full recording). Also
|
|
// if the selection range distance is tiny, assume the range was cleared or just
|
|
// clicked, and we do not have a range.
|
|
if (!selection || (selection.max - selection.min) < 1) {
|
|
return { startTime: 0, endTime: recording.getDuration() };
|
|
}
|
|
return { startTime: selection.min, endTime: selection.max };
|
|
},
|
|
|
|
/**
|
|
* Method for handling all the set up for rendering the overview graphs.
|
|
*
|
|
* @param number resolution
|
|
* The fps graph resolution. @see Graphs.js
|
|
*/
|
|
render: Task.async(function* (resolution) {
|
|
if (this.isDisabled()) {
|
|
return;
|
|
}
|
|
|
|
let recording = PerformanceController.getCurrentRecording();
|
|
yield this.graphs.render(recording.getAllData(), resolution);
|
|
|
|
// Finished rendering all graphs in this overview.
|
|
this.emit(EVENTS.UI_OVERVIEW_RENDERED, resolution);
|
|
}),
|
|
|
|
/**
|
|
* Called at most every OVERVIEW_UPDATE_INTERVAL milliseconds
|
|
* and uses data fetched from the controller to render
|
|
* data into all the corresponding overview graphs.
|
|
*/
|
|
_onRecordingTick: Task.async(function* () {
|
|
yield this.render(FRAMERATE_GRAPH_LOW_RES_INTERVAL);
|
|
this._prepareNextTick();
|
|
}),
|
|
|
|
/**
|
|
* Called to refresh the timer to keep firing _onRecordingTick.
|
|
*/
|
|
_prepareNextTick: function () {
|
|
// Check here to see if there's still a _timeoutId, incase
|
|
// `stop` was called before the _prepareNextTick call was executed.
|
|
if (this.isRendering()) {
|
|
this._timeoutId = setTimeout(this._onRecordingTick, this.OVERVIEW_UPDATE_INTERVAL);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Called when recording state changes.
|
|
*/
|
|
_onRecordingStateChange: OverviewViewOnStateChange(Task.async(
|
|
function* (_, state, recording) {
|
|
if (state !== "recording-stopped") {
|
|
return;
|
|
}
|
|
// Check to see if the recording that just stopped is the current recording.
|
|
// If it is, render the high-res graphs. For manual recordings, it will also
|
|
// be the current recording, but profiles generated by `console.profile` can stop
|
|
// while having another profile selected -- in this case, OverviewView should keep
|
|
// rendering the current recording.
|
|
if (recording !== PerformanceController.getCurrentRecording()) {
|
|
return;
|
|
}
|
|
this.render(FRAMERATE_GRAPH_HIGH_RES_INTERVAL);
|
|
yield this._checkSelection(recording);
|
|
})),
|
|
|
|
/**
|
|
* Called when a new recording is selected.
|
|
*/
|
|
_onRecordingSelected: OverviewViewOnStateChange(Task.async(function* (_, recording) {
|
|
this._setGraphVisibilityFromRecordingFeatures(recording);
|
|
|
|
// If this recording is complete, render the high res graph
|
|
if (recording.isCompleted()) {
|
|
yield this.render(FRAMERATE_GRAPH_HIGH_RES_INTERVAL);
|
|
}
|
|
yield this._checkSelection(recording);
|
|
this.graphs.dropSelection();
|
|
})),
|
|
|
|
/**
|
|
* Start the polling for rendering the overview graph.
|
|
*/
|
|
_startPolling: function () {
|
|
this._timeoutId = setTimeout(this._onRecordingTick, this.OVERVIEW_UPDATE_INTERVAL);
|
|
},
|
|
|
|
/**
|
|
* Stop the polling for rendering the overview graph.
|
|
*/
|
|
_stopPolling: function () {
|
|
clearTimeout(this._timeoutId);
|
|
this._timeoutId = null;
|
|
},
|
|
|
|
/**
|
|
* Whether or not the overview view is in a state of polling rendering.
|
|
*/
|
|
isRendering: function () {
|
|
return !!this._timeoutId;
|
|
},
|
|
|
|
/**
|
|
* Makes sure the selection is enabled or disabled in all the graphs,
|
|
* based on whether a recording currently exists and is not in progress.
|
|
*/
|
|
_checkSelection: Task.async(function* (recording) {
|
|
let isEnabled = recording ? recording.isCompleted() : false;
|
|
yield this.graphs.selectionEnabled(isEnabled);
|
|
}),
|
|
|
|
/**
|
|
* Fired when the graph selection has changed. Called by
|
|
* mouseup and scroll events.
|
|
*/
|
|
_onGraphSelecting: function () {
|
|
if (this._stopSelectionChangeEventPropagation) {
|
|
return;
|
|
}
|
|
|
|
this.emit(EVENTS.UI_OVERVIEW_RANGE_SELECTED, this.getTimeInterval());
|
|
},
|
|
|
|
_onGraphRendered: function (_, graphName) {
|
|
switch (graphName) {
|
|
case "timeline":
|
|
this.emit(EVENTS.UI_MARKERS_GRAPH_RENDERED);
|
|
break;
|
|
case "memory":
|
|
this.emit(EVENTS.UI_MEMORY_GRAPH_RENDERED);
|
|
break;
|
|
case "framerate":
|
|
this.emit(EVENTS.UI_FRAMERATE_GRAPH_RENDERED);
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Called whenever a preference in `devtools.performance.ui.` changes.
|
|
* Does not care about the enabling of memory/framerate graphs,
|
|
* because those will set values on a recording model, and
|
|
* the graphs will render based on the existence.
|
|
*/
|
|
_onPrefChanged: Task.async(function* (_, prefName, prefValue) {
|
|
switch (prefName) {
|
|
case "hidden-markers": {
|
|
let graph = yield this.graphs.isAvailable("timeline");
|
|
if (graph) {
|
|
let filter = PerformanceController.getPref("hidden-markers");
|
|
graph.setFilter(filter);
|
|
graph.refresh({ force: true });
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}),
|
|
|
|
_setGraphVisibilityFromRecordingFeatures: function (recording) {
|
|
for (let [graphName, requirements] of Object.entries(GRAPH_REQUIREMENTS)) {
|
|
this.graphs.enable(graphName,
|
|
PerformanceController.isFeatureSupported(requirements.features));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Fetch the multiprocess status and if e10s is not currently on, disable
|
|
* realtime rendering.
|
|
*
|
|
* @return {boolean}
|
|
*/
|
|
isRealtimeRenderingEnabled: function () {
|
|
return this._multiprocessData.enabled;
|
|
},
|
|
|
|
/**
|
|
* Show the graphs overview panel when a recording is finished
|
|
* when non-realtime graphs are enabled. Also set the graph visibility
|
|
* so the performance graphs know which graphs to render.
|
|
*
|
|
* @param {RecordingModel} recording
|
|
*/
|
|
_showGraphsPanel: function (recording) {
|
|
this._setGraphVisibilityFromRecordingFeatures(recording);
|
|
$("#overview-pane").classList.remove("hidden");
|
|
},
|
|
|
|
/**
|
|
* Hide the graphs container completely.
|
|
*/
|
|
_hideGraphsPanel: function () {
|
|
$("#overview-pane").classList.add("hidden");
|
|
},
|
|
|
|
/**
|
|
* Called when `devtools.theme` changes.
|
|
*/
|
|
_onThemeChanged: function (_, theme) {
|
|
this.graphs.setTheme({ theme, redraw: true });
|
|
},
|
|
|
|
toString: () => "[object OverviewView]"
|
|
};
|
|
|
|
/**
|
|
* Utility that can wrap a method of OverviewView that
|
|
* handles a recording state change like when a recording is starting,
|
|
* stopping, or about to start/stop, and determines whether or not
|
|
* the polling for rendering the overview graphs needs to start or stop.
|
|
* Must be called with the OverviewView context.
|
|
*
|
|
* @param {function?} fn
|
|
* @return {function}
|
|
*/
|
|
function OverviewViewOnStateChange(fn) {
|
|
return function _onRecordingStateChange(eventName, recording) {
|
|
// Normalize arguments for the RECORDING_STATE_CHANGE event,
|
|
// as it also has a `state` argument.
|
|
if (typeof recording === "string") {
|
|
recording = arguments[2];
|
|
}
|
|
|
|
let currentRecording = PerformanceController.getCurrentRecording();
|
|
|
|
// All these methods require a recording to exist selected and
|
|
// from the event name, since there is a delay between starting
|
|
// a recording and changing the selection.
|
|
if (!currentRecording || !recording) {
|
|
// If no recording (this can occur when having a console.profile recording, and
|
|
// we do not stop it from the backend), and we are still rendering updates,
|
|
// stop that.
|
|
if (this.isRendering()) {
|
|
this._stopPolling();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If realtime rendering is not enabed (e10s not on), then
|
|
// show the disabled message, or the full graphs if the recording is completed
|
|
if (!this.isRealtimeRenderingEnabled()) {
|
|
if (recording.isRecording()) {
|
|
this._hideGraphsPanel();
|
|
// Abort, as we do not want to change polling status.
|
|
return;
|
|
}
|
|
this._showGraphsPanel(recording);
|
|
}
|
|
|
|
if (this.isRendering() && !currentRecording.isRecording()) {
|
|
this._stopPolling();
|
|
} else if (currentRecording.isRecording() && !this.isRendering()) {
|
|
this._startPolling();
|
|
}
|
|
|
|
if (fn) {
|
|
fn.apply(this, arguments);
|
|
}
|
|
};
|
|
}
|
|
|
|
// Decorates the OverviewView as an EventEmitter
|
|
EventEmitter.decorate(OverviewView);
|