Bug 1077461 - Build call tree for memory allocations view, r=jsantell

--HG--
rename : browser/devtools/performance/test/browser_perf-options-invert-call-tree.js => browser/devtools/performance/test/browser_perf-options-invert-call-tree-01.js
rename : browser/devtools/performance/views/details-call-tree.js => browser/devtools/performance/views/details-js-call-tree.js
rename : browser/devtools/performance/views/details-flamegraph.js => browser/devtools/performance/views/details-js-flamegraph.js
This commit is contained in:
Victor Porof 2015-01-28 14:30:32 -05:00
Родитель 4e7fdc601c
Коммит 8799c47957
43 изменённых файлов: 920 добавлений и 268 удалений

Просмотреть файл

@ -99,9 +99,11 @@ browser.jar:
content/browser/devtools/performance/views/toolbar.js (performance/views/toolbar.js)
content/browser/devtools/performance/views/details.js (performance/views/details.js)
content/browser/devtools/performance/views/details-subview.js (performance/views/details-abstract-subview.js)
content/browser/devtools/performance/views/details-call-tree.js (performance/views/details-call-tree.js)
content/browser/devtools/performance/views/details-waterfall.js (performance/views/details-waterfall.js)
content/browser/devtools/performance/views/details-flamegraph.js (performance/views/details-flamegraph.js)
content/browser/devtools/performance/views/details-js-call-tree.js (performance/views/details-js-call-tree.js)
content/browser/devtools/performance/views/details-js-flamegraph.js (performance/views/details-js-flamegraph.js)
content/browser/devtools/performance/views/details-memory-call-tree.js (performance/views/details-memory-call-tree.js)
content/browser/devtools/performance/views/details-memory-flamegraph.js (performance/views/details-memory-flamegraph.js)
content/browser/devtools/performance/views/recordings.js (performance/views/recordings.js)
#endif
content/browser/devtools/responsivedesign/resize-commands.js (responsivedesign/resize-commands.js)

Просмотреть файл

@ -13,12 +13,23 @@ loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
loader.lazyRequireGetter(this, "TimelineFront",
"devtools/server/actors/timeline", true);
loader.lazyRequireGetter(this, "MemoryFront",
"devtools/server/actors/memory", true);
loader.lazyRequireGetter(this, "DevToolsUtils",
"devtools/toolkit/DevToolsUtils");
loader.lazyImporter(this, "gDevTools",
"resource:///modules/devtools/gDevTools.jsm");
loader.lazyImporter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
loader.lazyImporter(this, "clearTimeout",
"resource://gre/modules/Timer.jsm");
// How often do we pull allocation sites from the memory actor.
const DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT = 200; // ms
/**
* A cache of all PerformanceActorsConnection instances.
* The keys are Target objects.
@ -44,6 +55,25 @@ SharedPerformanceActors.forTarget = function(target) {
return instance;
};
/**
* A dummy front decorated with the provided methods.
*
* @param array blueprint
* A list of [funcName, retVal] describing the class.
*/
function MockedFront(blueprint) {
EventEmitter.decorate(this);
for (let [funcName, retVal] of blueprint) {
this[funcName] = (x => x).bind(this, retVal);
}
}
MockedFront.prototype = {
initialize: function() {},
destroy: function() {}
};
/**
* A connection to underlying actors (profiler, memory, framerate, etc.)
* shared by all tools in a target.
@ -67,7 +97,7 @@ function PerformanceActorsConnection(target) {
PerformanceActorsConnection.prototype = {
/**
* Initializes a connection to the profiler and other miscellaneous actors.
* If already open, nothing happens.
* If in the process of opening, or already open, nothing happens.
*
* @return object
* A promise that is resolved once the connection is established.
@ -80,11 +110,13 @@ PerformanceActorsConnection.prototype = {
// Local debugging needs to make the target remote.
yield this._target.makeRemote();
// Sets `this._profiler`
// 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.
yield this._connectProfilerActor();
// Sets or shims `this._timeline`
yield this._connectTimelineActor();
yield this._connectMemoryActor();
this._connected = true;
@ -94,10 +126,10 @@ PerformanceActorsConnection.prototype = {
/**
* Destroys this connection.
*/
destroy: function () {
this._disconnectActors();
destroy: Task.async(function*() {
yield this._disconnectActors();
this._connected = false;
},
}),
/**
* Initializes a connection to the profiler actor.
@ -126,46 +158,51 @@ PerformanceActorsConnection.prototype = {
/**
* Initializes a connection to a timeline actor.
* TODO: use framework level feature detection from bug 1069673
*/
_connectTimelineActor: function() {
// Only initialize the timeline front if the respective actor is available.
// Older Gecko versions don't have an existing implementation, in which case
// all the methods we need can be easily mocked.
//
// If the timeline actor exists, all underlying actors (memory, framerate) exist,
// with the expected methods and behaviour. If using the Performance tool,
// and timeline actor does not exist (FxOS devices < Gecko 35),
// then just use the mocked actor and do not display timeline data.
//
// TODO use framework level feature detection from bug 1069673
if (this._target.form && this._target.form.timelineActor) {
this._timeline = new TimelineFront(this._target.client, this._target.form);
} else {
this._timeline = {
start: () => 0,
stop: () => 0,
isRecording: () => false,
on: () => null,
off: () => null,
once: () => promise.reject(),
destroy: () => null
};
this._timeline = new MockedFront([
["start", 0],
["stop", 0]
]);
}
},
/**
* Initializes a connection to a memory actor.
* TODO: use framework level feature detection from bug 1069673
*/
_connectMemoryActor: function() {
if (this._target.form && this._target.form.memoryActor) {
this._memory = new MemoryFront(this._target.client, this._target.form);
} else {
this._memory = new MockedFront([
["attach"],
["detach"],
["startRecordingAllocations", 0],
["stopRecordingAllocations", 0],
["getAllocations"]
]);
}
},
/**
* Closes the connections to non-profiler actors.
*/
_disconnectActors: function () {
this._timeline.destroy();
},
_disconnectActors: Task.async(function* () {
yield this._timeline.destroy();
yield this._memory.destroy();
}),
/**
* Sends the request over the remote debugging protocol to the
* specified actor.
*
* @param string actor
* The designated actor. Currently supported: "profiler", "timeline".
* Currently supported: "profiler", "timeline", "memory".
* @param string method
* Method to call on the backend.
* @param any args [optional]
@ -188,6 +225,11 @@ PerformanceActorsConnection.prototype = {
if (actor == "timeline") {
return this._timeline[method].apply(this._timeline, args);
}
// Handle requests to the memory actor.
if (actor == "memory") {
return this._memory[method].apply(this._memory, args);
}
}
};
@ -208,69 +250,142 @@ function PerformanceFront(connection) {
connection._timeline.on("frames", (delta, frames) => this.emit("frames", delta, frames));
connection._timeline.on("memory", (delta, measurement) => this.emit("memory", delta, measurement));
connection._timeline.on("ticks", (delta, timestamps) => this.emit("ticks", delta, timestamps));
this._pullAllocationSites = this._pullAllocationSites.bind(this);
this._sitesPullTimeout = 0;
}
PerformanceFront.prototype = {
/**
* Manually begins a recording session.
*
* @param object timelineOptions
* An options object to pass to the timeline front. Supported
* properties are `withTicks` and `withMemory`.
* @param object options
* An options object to pass to the actors. Supported properties are
* `withTicks`, `withMemory` and `withAllocations`.
* @return object
* A promise that is resolved once recording has started.
*/
startRecording: Task.async(function*(timelineOptions = {}) {
let profilerStatus = yield this._request("profiler", "isActive");
let profilerStartTime;
startRecording: Task.async(function*(options = {}) {
// All actors are started asynchronously over the remote debugging protocol.
// Get the corresponding start times from each one of them.
let profilerStartTime = yield this._startProfiler();
let timelineStartTime = yield this._startTimeline(options);
let memoryStartTime = yield this._startMemory(options);
// 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.
if (!profilerStatus.isActive) {
// Extend the profiler options so that protocol.js doesn't modify the original.
let profilerOptions = extend({}, this._customProfilerOptions);
yield this._request("profiler", "startProfiler", profilerOptions);
profilerStartTime = 0;
this.emit("profiler-activated");
} else {
profilerStartTime = profilerStatus.currentTime;
this.emit("profiler-already-active");
}
// The timeline actor is target-dependent, so just make sure it's recording.
// It won't, however, be available in older Geckos (FF < 35).
let timelineStartTime = yield this._request("timeline", "start", timelineOptions);
// Return the start times from the two actors. They will be used to
// synchronize the profiler and timeline data.
return {
profilerStartTime,
timelineStartTime
timelineStartTime,
memoryStartTime
};
}),
/**
* Manually ends the current recording session.
*
* @param object options
* @see PerformanceFront.prototype.startRecording
* @return object
* A promise that is resolved once recording has stopped,
* with the profiler and timeline data.
* with the profiler and memory data, along with all the end times.
*/
stopRecording: Task.async(function*() {
let timelineEndTime = yield this._request("timeline", "stop");
stopRecording: Task.async(function*(options = {}) {
let memoryEndTime = yield this._stopMemory(options);
let timelineEndTime = yield this._stopTimeline(options);
let profilerData = yield this._request("profiler", "getProfile");
// Return the end times from the two actors. They will be used to
// synchronize the profiler and timeline data.
return {
// 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,
memoryEndTime: memoryEndTime
};
}),
/**
* Starts the profiler actor, if necessary.
*/
_startProfiler: Task.async(function *() {
// 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 profilerStatus = yield this._request("profiler", "isActive");
if (profilerStatus.isActive) {
this.emit("profiler-already-active");
return profilerStatus.currentTime;
}
// Extend the profiler options so that protocol.js doesn't modify the original.
let profilerOptions = extend({}, this._customProfilerOptions);
yield this._request("profiler", "startProfiler", profilerOptions);
this.emit("profiler-activated");
return 0;
}),
/**
* Starts the timeline actor.
*/
_startTimeline: Task.async(function *(options) {
// The timeline actor is target-dependent, so just make sure it's recording.
// It won't, however, be available in older Geckos (FF < 35).
return (yield this._request("timeline", "start", options));
}),
/**
* Stops the timeline actor.
*/
_stopTimeline: Task.async(function *(options) {
return (yield this._request("timeline", "stop"));
}),
/**
* Starts the timeline actor, if necessary.
*/
_startMemory: Task.async(function *(options) {
if (!options.withAllocations) {
return 0;
}
yield this._request("memory", "attach");
let memoryStartTime = yield this._request("memory", "startRecordingAllocations");
yield this._pullAllocationSites();
return memoryStartTime;
}),
/**
* Stops the timeline actor, if necessary.
*/
_stopMemory: Task.async(function *(options) {
if (!options.withAllocations) {
return 0;
}
clearTimeout(this._sitesPullTimeout);
let memoryEndTime = yield this._request("memory", "stopRecordingAllocations");
yield this._request("memory", "detach");
return memoryEndTime;
}),
/**
* At regular intervals, pull allocations from the memory actor, and forward
* them to consumers.
*/
_pullAllocationSites: Task.async(function *() {
let memoryData = yield this._request("memory", "getAllocations");
this.emit("allocations", {
sites: memoryData.allocations,
timestamps: memoryData.allocationsTimestamps,
frames: memoryData.frames,
counts: memoryData.counts
});
let delay = DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT;
this._sitesPullTimeout = setTimeout(this._pullAllocationSites, delay);
}),
/**
* Overrides the options sent to the built-in profiler module when activating,
* such as the maximum entries count, the sampling interval etc.
@ -285,7 +400,8 @@ PerformanceFront.prototype = {
};
/**
* A collection of small wrappers promisifying functions invoking callbacks.
* Returns a promise resolved with a listing of all the tabs in the
* provided thread client.
*/
function listTabs(client) {
let deferred = promise.defer();

Просмотреть файл

@ -131,7 +131,7 @@ function convertLegacyData (legacyData) {
let { profilerData, ticksData, recordingDuration } = legacyData;
// The `profilerData` and `ticksData` stay, but the previously unrecorded
// fields just are empty arrays.
// fields just are empty arrays or objects.
let data = {
label: profilerData.profilerLabel,
duration: recordingDuration,
@ -139,6 +139,7 @@ function convertLegacyData (legacyData) {
frames: [],
memory: [],
ticks: ticksData,
allocations: { sites: [], timestamps: [], frames: [], counts: [] },
profile: profilerData.profile
};

Просмотреть файл

@ -28,14 +28,16 @@ RecordingModel.prototype = {
_recording: false,
_profilerStartTime: 0,
_timelineStartTime: 0,
_memoryStartTime: 0,
// Serializable fields, necessary and sufficient for import and export.
_label: "",
_duration: 0,
_markers: null,
_frames: null,
_ticks: null,
_memory: null,
_ticks: null,
_allocations: null,
_profile: null,
/**
@ -54,6 +56,7 @@ RecordingModel.prototype = {
this._frames = recordingData.frames;
this._memory = recordingData.memory;
this._ticks = recordingData.ticks;
this._allocations = recordingData.allocations;
this._profile = recordingData.profile;
}),
@ -72,8 +75,7 @@ RecordingModel.prototype = {
* Starts recording with the PerformanceFront.
*
* @param object options
* An options object to pass to the timeline front. Supported
* properties are `withTicks` and `withMemory`.
* @see PerformanceFront.prototype.startRecording
*/
startRecording: Task.async(function *(options = {}) {
// Times must come from the actor in order to be self-consistent.
@ -85,19 +87,24 @@ RecordingModel.prototype = {
let info = yield this._front.startRecording(options);
this._profilerStartTime = info.profilerStartTime;
this._timelineStartTime = info.timelineStartTime;
this._memoryStartTime = info.memoryStartTime;
this._recording = true;
this._markers = [];
this._frames = [];
this._memory = [];
this._ticks = [];
this._allocations = { sites: [], timestamps: [], frames: [], counts: [] };
}),
/**
* Stops recording with the PerformanceFront.
*
* @param object options
* @see RecordingModel.prototype.startRecording
*/
stopRecording: Task.async(function *() {
let info = yield this._front.stopRecording();
stopRecording: Task.async(function *(options) {
let info = yield this._front.stopRecording(options);
this._profile = info.profile;
this._duration = info.profilerEndTime - this._profilerStartTime;
this._recording = false;
@ -168,6 +175,14 @@ RecordingModel.prototype = {
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
@ -186,8 +201,9 @@ RecordingModel.prototype = {
let frames = this.getFrames();
let memory = this.getMemory();
let ticks = this.getTicks();
let allocations = this.getAllocations();
let profile = this.getProfile();
return { label, duration, markers, frames, memory, ticks, profile };
return { label, duration, markers, frames, memory, ticks, allocations, profile };
},
/**
@ -209,35 +225,50 @@ RecordingModel.prototype = {
}
switch (eventName) {
// Accumulate markers into an array. Furthermore, timestamps do not
// have a zero epoch, so offset all of them by the timeline's start time.
case "markers":
// Accumulate timeline markers into an array. Furthermore, the timestamps
// do not have a zero epoch, so offset all of them by the start time.
case "markers": {
let [markers] = data;
RecordingUtils.offsetMarkerTimes(markers, this._timelineStartTime);
Array.prototype.push.apply(this._markers, markers);
break;
}
// Accumulate stack frames into an array.
case "frames":
case "frames": {
let [, frames] = data;
Array.prototype.push.apply(this._frames, frames);
break;
// Accumulate memory measurements into an array. Furthermore, the
// timestamp does not have a zero epoch, so offset it.
case "memory":
}
// Accumulate memory measurements into an array. Furthermore, the timestamp
// does not have a zero epoch, so offset it by the actor's start time.
case "memory": {
let [currentTime, measurement] = data;
this._memory.push({
delta: currentTime - this._timelineStartTime,
value: measurement.total / 1024 / 1024
});
break;
}
// Save the accumulated refresh driver ticks.
case "ticks":
case "ticks": {
let [, timestamps] = data;
this._ticks = timestamps;
break;
}
// Accumulate allocation sites into an array. Furthermore, the timestamps
// do not have a zero epoch, and are microseconds instead of milliseconds,
// so offset all of them by the start time, also converting from µs to ms.
case "allocations": {
let [{ sites, timestamps, frames, counts }] = data;
let timeOffset = this._memoryStartTime * 1000;
let timeScale = 1000;
RecordingUtils.offsetAndScaleTimestamps(timestamps, timeOffset, timeScale);
Array.prototype.push.apply(this._allocations.sites, sites);
Array.prototype.push.apply(this._allocations.timestamps, timestamps);
Array.prototype.push.apply(this._allocations.frames, frames);
Array.prototype.push.apply(this._allocations.counts, counts);
break;
}
}
}
};

Просмотреть файл

@ -58,6 +58,24 @@ exports.RecordingUtils.offsetMarkerTimes = function(markers, timeOffset) {
}
}
/**
* Offsets and scales all the timestamps in the provided array by the
* specified time and scale factor.
*
* @param array array
* A list of timestamps received from the backend.
* @param number timeOffset
* The amount of time to offset by (in milliseconds).
* @param number timeScale
* The factor to scale by, after offsetting.
*/
exports.RecordingUtils.offsetAndScaleTimestamps = function(timestamps, timeOffset, timeScale) {
for (let i = 0, len = timestamps.length; i < len; i++) {
timestamps[i] -= timeOffset;
timestamps[i] /= timeScale;
}
}
/**
* Converts allocation data from the memory actor to something that follows
* the same structure as the samples data received from the profiler.

Просмотреть файл

@ -58,7 +58,7 @@ PerformancePanel.prototype = {
}
// Destroy the connection to ensure packet handlers are removed from client.
this._connection.destroy();
yield this._connection.destroy();
yield this.panelWin.shutdownPerformance();
this.emit("destroyed");

Просмотреть файл

@ -21,6 +21,8 @@ devtools.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
"devtools/timeline/global", true);
devtools.lazyRequireGetter(this, "L10N",
"devtools/profiler/global", true);
devtools.lazyRequireGetter(this, "RecordingUtils",
"devtools/performance/recording-utils", true);
devtools.lazyRequireGetter(this, "RecordingModel",
"devtools/performance/recording-model", true);
devtools.lazyRequireGetter(this, "MarkersOverview",
@ -98,14 +100,20 @@ const EVENTS = {
// Emitted by the DetailsView when a subview is selected
DETAILS_VIEW_SELECTED: "Performance:UI:DetailsViewSelected",
// Emitted by the CallTreeView when a call tree has been rendered
CALL_TREE_RENDERED: "Performance:UI:CallTreeRendered",
// Emitted by the WaterfallView when it has been rendered
WATERFALL_RENDERED: "Performance:UI:WaterfallRendered",
// Emitted by the FlameGraphView when it has been rendered
FLAMEGRAPH_RENDERED: "Performance:UI:FlameGraphRendered",
// 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",
@ -184,10 +192,11 @@ let PerformanceController = {
RecordingsView.on(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
RecordingsView.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
gFront.on("ticks", this._onTimelineData); // framerate
gFront.on("markers", this._onTimelineData); // timeline markers
gFront.on("frames", this._onTimelineData); // stack frames
gFront.on("memory", this._onTimelineData); // memory measurements
gFront.on("ticks", this._onTimelineData); // framerate
gFront.on("allocations", this._onTimelineData); // memory allocations
}),
/**
@ -201,10 +210,11 @@ let PerformanceController = {
RecordingsView.off(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
RecordingsView.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
gFront.off("ticks", this._onTimelineData);
gFront.off("markers", this._onTimelineData);
gFront.off("frames", this._onTimelineData);
gFront.off("memory", this._onTimelineData);
gFront.off("ticks", this._onTimelineData);
gFront.off("allocations", this._onTimelineData);
},
/**
@ -223,7 +233,11 @@ let PerformanceController = {
let recording = this._createRecording();
this.emit(EVENTS.RECORDING_WILL_START, recording);
yield recording.startRecording({ withTicks: true, withMemory: true });
yield recording.startRecording({
withTicks: true,
withMemory: true,
withAllocations: true
});
this.emit(EVENTS.RECORDING_STARTED, recording);
this.setCurrentRecording(recording);
@ -237,7 +251,9 @@ let PerformanceController = {
let recording = this._getLatestRecording();
this.emit(EVENTS.RECORDING_WILL_STOP, recording);
yield recording.stopRecording();
yield recording.stopRecording({
withAllocations: true
});
this.emit(EVENTS.RECORDING_STOPPED, recording);
}),
@ -318,7 +334,7 @@ let PerformanceController = {
* Fired whenever the PerformanceFront emits markers, memory or ticks.
*/
_onTimelineData: function (...data) {
this._recordings.forEach(profile => profile.addTimelineData.apply(profile, data));
this._recordings.forEach(e => e.addTimelineData.apply(e, data));
this.emit(EVENTS.TIMELINE_DATA, ...data);
},

Просмотреть файл

@ -20,9 +20,11 @@
<script type="application/javascript" src="performance/views/overview.js"/>
<script type="application/javascript" src="performance/views/toolbar.js"/>
<script type="application/javascript" src="performance/views/details-subview.js"/>
<script type="application/javascript" src="performance/views/details-call-tree.js"/>
<script type="application/javascript" src="performance/views/details-waterfall.js"/>
<script type="application/javascript" src="performance/views/details-flamegraph.js"/>
<script type="application/javascript" src="performance/views/details-js-call-tree.js"/>
<script type="application/javascript" src="performance/views/details-js-flamegraph.js"/>
<script type="application/javascript" src="performance/views/details-memory-call-tree.js"/>
<script type="application/javascript" src="performance/views/details-memory-flamegraph.js"/>
<script type="application/javascript" src="performance/views/details.js"/>
<script type="application/javascript" src="performance/views/recordings.js"/>
@ -60,14 +62,25 @@
<toolbar id="performance-toolbar" class="devtools-toolbar">
<hbox id="performance-toolbar-controls-detail-views" class="devtools-toolbarbutton-group">
<toolbarbutton id="select-waterfall-view"
class="devtools-toolbarbutton"
class="devtools-toolbarbutton devtools-button"
label="&profilerUI.toolbar.waterfall;"
data-view="waterfall" />
<toolbarbutton id="select-calltree-view"
class="devtools-toolbarbutton"
data-view="calltree" />
<toolbarbutton id="select-flamegraph-view"
class="devtools-toolbarbutton"
data-view="flamegraph" />
<toolbarbutton id="select-js-calltree-view"
class="devtools-toolbarbutton devtools-button"
label="&profilerUI.toolbar.js-calltree;"
data-view="js-calltree" />
<toolbarbutton id="select-js-flamegraph-view"
class="devtools-toolbarbutton devtools-button"
label="&profilerUI.toolbar.js-flamegraph;"
data-view="js-flamegraph" />
<toolbarbutton id="select-memory-calltree-view"
class="devtools-toolbarbutton devtools-button"
label="&profilerUI.toolbar.memory-calltree;"
data-view="memory-calltree" />
<toolbarbutton id="select-memory-flamegraph-view"
class="devtools-toolbarbutton devtools-button"
label="&profilerUI.toolbar.memory-flamegraph;"
data-view="memory-flamegraph" />
</hbox>
<spacer flex="1"></spacer>
<hbox id="performance-toolbar-control-options" class="devtools-toolbarbutton-group">
@ -94,12 +107,12 @@
height="150"/>
</hbox>
<vbox id="calltree-view" flex="1">
<vbox id="js-calltree-view" flex="1">
<hbox class="call-tree-headers-container">
<label class="plain call-tree-header"
type="duration"
crop="end"
value="&profilerUI.table.totalDuration;"/>
value="&profilerUI.table.totalDuration2;"/>
<label class="plain call-tree-header"
type="percentage"
crop="end"
@ -107,7 +120,7 @@
<label class="plain call-tree-header"
type="self-duration"
crop="end"
value="&profilerUI.table.selfDuration;"/>
value="&profilerUI.table.selfDuration2;"/>
<label class="plain call-tree-header"
type="self-percentage"
crop="end"
@ -123,9 +136,53 @@
</hbox>
<vbox class="call-tree-cells-container" flex="1"/>
</vbox>
<hbox id="flamegraph-view" flex="1">
<hbox id="js-flamegraph-view" flex="1">
</hbox>
<vbox id="memory-calltree-view" flex="1">
<hbox class="call-tree-headers-container">
<label class="plain call-tree-header"
type="duration"
crop="end"
value="&profilerUI.table.totalDuration2;"/>
<label class="plain call-tree-header"
type="percentage"
crop="end"
value="&profilerUI.table.totalPercentage;"/>
<label class="plain call-tree-header"
type="allocations"
crop="end"
value="&profilerUI.table.totalAlloc;"/>
<label class="plain call-tree-header"
type="self-duration"
crop="end"
value="&profilerUI.table.selfDuration2;"/>
<label class="plain call-tree-header"
type="self-percentage"
crop="end"
value="&profilerUI.table.selfPercentage;"/>
<label class="plain call-tree-header"
type="self-allocations"
crop="end"
value="&profilerUI.table.selfAlloc;"/>
<label class="plain call-tree-header"
type="samples"
crop="end"
value="&profilerUI.table.samples;"/>
<label class="plain call-tree-header"
type="function"
crop="end"
value="&profilerUI.table.function;"/>
</hbox>
<vbox class="call-tree-cells-container" flex="1"/>
</vbox>
<hbox id="memory-flamegraph-view" flex="1">
<!-- TODO: bug 1077461 -->
</hbox>
</deck>
</vbox>
</hbox>
</window>

Просмотреть файл

@ -14,6 +14,8 @@ support-files =
[browser_perf-data-samples.js]
[browser_perf-details-calltree-render.js]
[browser_perf-details-flamegraph-render.js]
[browser_perf-details-memory-calltree-render.js]
[browser_perf-details-memory-flamegraph-render.js]
[browser_perf-details-waterfall-render.js]
[browser_perf-details-01.js]
[browser_perf-details-02.js]
@ -28,7 +30,8 @@ support-files =
[browser_perf-front.js]
[browser_perf-jump-to-debugger-01.js]
[browser_perf-jump-to-debugger-02.js]
[browser_perf-options-invert-call-tree.js]
[browser_perf-options-invert-call-tree-01.js]
[browser_perf-options-invert-call-tree-02.js]
[browser_perf-overview-render-01.js]
[browser_perf-overview-render-02.js]
[browser_perf-overview-render-03.js]

Просмотреть файл

@ -13,10 +13,17 @@ function spawnTest () {
// Select calltree view
let viewChanged = onceSpread(DetailsView, EVENTS.DETAILS_VIEW_SELECTED);
command($("toolbarbutton[data-view='calltree']"));
command($("toolbarbutton[data-view='js-calltree']"));
let [_, viewName] = yield viewChanged;
is(viewName, "calltree", "DETAILS_VIEW_SELECTED fired with view name");
checkViews(DetailsView, doc, "calltree");
is(viewName, "js-calltree", "DETAILS_VIEW_SELECTED fired with view name");
checkViews(DetailsView, doc, "js-calltree");
// Select flamegraph view
viewChanged = onceSpread(DetailsView, EVENTS.DETAILS_VIEW_SELECTED);
command($("toolbarbutton[data-view='js-flamegraph']"));
[_, viewName] = yield viewChanged;
is(viewName, "js-flamegraph", "DETAILS_VIEW_SELECTED fired with view name");
checkViews(DetailsView, doc, "js-flamegraph");
// Select waterfall view
viewChanged = onceSpread(DetailsView, EVENTS.DETAILS_VIEW_SELECTED);
@ -25,13 +32,6 @@ function spawnTest () {
is(viewName, "waterfall", "DETAILS_VIEW_SELECTED fired with view name");
checkViews(DetailsView, doc, "waterfall");
// Select flamegraph view
viewChanged = onceSpread(DetailsView, EVENTS.DETAILS_VIEW_SELECTED);
command($("toolbarbutton[data-view='flamegraph']"));
[_, viewName] = yield viewChanged;
is(viewName, "flamegraph", "DETAILS_VIEW_SELECTED fired with view name");
checkViews(DetailsView, doc, "flamegraph");
yield teardown(panel);
finish();
}

Просмотреть файл

@ -7,25 +7,25 @@
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView } = panel.panelWin;
let { WaterfallView, CallTreeView, FlameGraphView } = panel.panelWin;
let { WaterfallView, JsCallTreeView, JsFlameGraphView } = panel.panelWin;
ok(DetailsView.isViewSelected(WaterfallView),
"The waterfall view is selected by default in the details view.");
let selected = DetailsView.whenViewSelected(CallTreeView);
let selected = DetailsView.whenViewSelected(JsCallTreeView);
let notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
DetailsView.selectView("calltree");
DetailsView.selectView("js-calltree");
yield Promise.all([selected, notified]);
ok(DetailsView.isViewSelected(CallTreeView),
ok(DetailsView.isViewSelected(JsCallTreeView),
"The waterfall view is now selected in the details view.");
selected = DetailsView.whenViewSelected(FlameGraphView);
selected = DetailsView.whenViewSelected(JsFlameGraphView);
notified = DetailsView.once(EVENTS.DETAILS_VIEW_SELECTED);
DetailsView.selectView("flamegraph");
DetailsView.selectView("js-flamegraph");
yield Promise.all([selected, notified]);
ok(DetailsView.isViewSelected(FlameGraphView),
ok(DetailsView.isViewSelected(JsFlameGraphView),
"The flamegraph view is now selected in the details view.");
selected = DetailsView.whenViewSelected(WaterfallView);

Просмотреть файл

@ -6,28 +6,28 @@
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView, CallTreeView } = panel.panelWin;
let { EVENTS, DetailsView, JsCallTreeView } = panel.panelWin;
DetailsView.selectView("calltree");
ok(DetailsView.isViewSelected(CallTreeView), "The call tree is now selected.");
DetailsView.selectView("js-calltree");
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
yield startRecording(panel);
yield busyWait(100);
let rendered = once(CallTreeView, EVENTS.CALL_TREE_RENDERED);
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield rendered;
ok(true, "CallTreeView rendered after recording is stopped.");
ok(true, "JsCallTreeView rendered after recording is stopped.");
yield startRecording(panel);
yield busyWait(100);
rendered = once(CallTreeView, EVENTS.CALL_TREE_RENDERED);
rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield rendered;
ok(true, "CallTreeView rendered again after recording completed a second time.");
ok(true, "JsCallTreeView rendered again after recording completed a second time.");
yield teardown(panel);
finish();

Просмотреть файл

@ -6,28 +6,28 @@
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView, FlameGraphView } = panel.panelWin;
let { EVENTS, DetailsView, JsFlameGraphView } = panel.panelWin;
DetailsView.selectView("flamegraph");
ok(DetailsView.isViewSelected(FlameGraphView), "The flamegraph is now selected.");
DetailsView.selectView("js-flamegraph");
ok(DetailsView.isViewSelected(JsFlameGraphView), "The flamegraph is now selected.");
yield startRecording(panel);
yield busyWait(100);
let rendered = once(FlameGraphView, EVENTS.FLAMEGRAPH_RENDERED);
let rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
yield rendered;
ok(true, "FlameGraphView rendered after recording is stopped.");
ok(true, "JsFlameGraphView rendered after recording is stopped.");
yield startRecording(panel);
yield busyWait(100);
rendered = once(FlameGraphView, EVENTS.FLAMEGRAPH_RENDERED);
rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
yield rendered;
ok(true, "FlameGraphView rendered again after recording completed a second time.");
ok(true, "JsFlameGraphView rendered again after recording completed a second time.");
yield teardown(panel);
finish();

Просмотреть файл

@ -0,0 +1,34 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the memory call tree view renders content after recording.
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView, MemoryCallTreeView } = panel.panelWin;
DetailsView.selectView("memory-calltree");
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
yield startRecording(panel);
yield busyWait(100);
let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield rendered;
ok(true, "MemoryCallTreeView rendered after recording is stopped.");
yield startRecording(panel);
yield busyWait(100);
rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield rendered;
ok(true, "MemoryCallTreeView rendered again after recording completed a second time.");
yield teardown(panel);
finish();
}

Просмотреть файл

@ -0,0 +1,34 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the memory flamegraph view renders content after recording.
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView, MemoryFlameGraphView } = panel.panelWin;
DetailsView.selectView("memory-flamegraph");
ok(DetailsView.isViewSelected(MemoryFlameGraphView), "The flamegraph is now selected.");
yield startRecording(panel);
yield busyWait(100);
let rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
yield rendered;
ok(true, "MemoryFlameGraphView rendered after recording is stopped.");
yield startRecording(panel);
yield busyWait(100);
rendered = once(MemoryFlameGraphView, EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
yield stopRecording(panel);
yield rendered;
ok(true, "MemoryFlameGraphView rendered again after recording completed a second time.");
yield teardown(panel);
finish();
}

Просмотреть файл

@ -10,26 +10,44 @@ let WAIT_TIME = 1000;
function spawnTest () {
let { target, front } = yield initBackend(SIMPLE_URL);
let { profilerStartTime, timelineStartTime } = yield front.startRecording();
let {
profilerStartTime,
timelineStartTime,
memoryStartTime
} = yield front.startRecording({
withAllocations: true
});
ok(typeof profilerStartTime === "number",
"The front.startRecording() emits a profiler start time.");
ok(typeof timelineStartTime === "number",
"The front.startRecording() emits a timeline start time.");
ok(typeof memoryStartTime === "number",
"The front.startRecording() emits a memory start time.");
yield busyWait(WAIT_TIME);
let { profilerEndTime, timelineEndTime } = yield front.stopRecording();
let {
profilerEndTime,
timelineEndTime,
memoryEndTime
} = yield front.stopRecording({
withAllocations: true
});
ok(typeof profilerEndTime === "number",
"The front.stopRecording() emits a profiler end time.");
ok(typeof timelineEndTime === "number",
"The front.stopRecording() emits a timeline end time.");
ok(typeof memoryEndTime === "number",
"The front.stopRecording() emits a memory end time.");
ok(profilerEndTime > profilerStartTime,
"The profilerEndTime is after profilerStartTime.");
ok(timelineEndTime > timelineStartTime,
"The timelineEndTime is after timelineStartTime.");
ok(memoryEndTime > memoryStartTime,
"The memoryEndTime is after memoryStartTime.");
yield removeTab(target.tab);
finish();

Просмотреть файл

@ -0,0 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const INVERT_PREF = "devtools.performance.ui.invert-call-tree";
/**
* Tests that the js call tree view is re-rendered after the
* "invert-call-tree" pref is changed.
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView, JsCallTreeView } = panel.panelWin;
Services.prefs.setBoolPref(INVERT_PREF, true);
DetailsView.selectView("js-calltree");
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
yield startRecording(panel);
yield busyWait(100);
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield rendered;
rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
Services.prefs.setBoolPref(INVERT_PREF, false);
yield rendered;
ok(true, "JsCallTreeView rerendered when toggling invert-call-tree.");
rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
Services.prefs.setBoolPref(INVERT_PREF, true);
yield rendered;
ok(true, "JsCallTreeView rerendered when toggling back invert-call-tree.");
yield teardown(panel);
finish();
}

Просмотреть файл

@ -0,0 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const INVERT_PREF = "devtools.performance.ui.invert-call-tree";
/**
* Tests that the memory call tree view is re-rendered after the
* "invert-call-tree" pref is changed.
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView, MemoryCallTreeView } = panel.panelWin;
Services.prefs.setBoolPref(INVERT_PREF, true);
DetailsView.selectView("memory-calltree");
ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
yield startRecording(panel);
yield busyWait(100);
let rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
yield stopRecording(panel);
yield rendered;
rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
Services.prefs.setBoolPref(INVERT_PREF, false);
yield rendered;
ok(true, "MemoryCallTreeView rerendered when toggling invert-call-tree.");
rendered = once(MemoryCallTreeView, EVENTS.MEMORY_CALL_TREE_RENDERED);
Services.prefs.setBoolPref(INVERT_PREF, true);
yield rendered;
ok(true, "MemoryCallTreeView rerendered when toggling back invert-call-tree.");
yield teardown(panel);
finish();
}

Просмотреть файл

@ -1,39 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const INVERT_PREF = "devtools.performance.ui.invert-call-tree";
/**
* Tests that the call tree view renders after recording.
*/
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, DetailsView, CallTreeView } = panel.panelWin;
Services.prefs.setBoolPref(INVERT_PREF, true);
DetailsView.selectView("calltree");
ok(DetailsView.isViewSelected(CallTreeView), "The call tree is now selected.");
yield startRecording(panel);
yield busyWait(100);
let rendered = once(CallTreeView, EVENTS.CALL_TREE_RENDERED);
yield stopRecording(panel);
yield rendered;
rendered = once(CallTreeView, EVENTS.CALL_TREE_RENDERED);
Services.prefs.setBoolPref(INVERT_PREF, false);
yield rendered;
ok(true, "CallTreeView rerendered when toggling invert-call-tree.");
rendered = once(CallTreeView, EVENTS.CALL_TREE_RENDERED);
Services.prefs.setBoolPref(INVERT_PREF, true);
yield rendered;
ok(true, "CallTreeView rerendered when toggling back invert-call-tree.");
yield teardown(panel);
finish();
}

Просмотреть файл

@ -53,9 +53,9 @@ function spawnTest () {
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, fail);
let secondInterval = OverviewView.getTimeInterval();
is(secondInterval.startTime, 30,
is(Math.round(secondInterval.startTime), 30,
"The interval's start time was properly set again.");
is(secondInterval.endTime, 40,
is(Math.round(secondInterval.endTime), 40,
"The interval's end time was properly set again.");
yield teardown(panel);

Просмотреть файл

@ -7,14 +7,14 @@
function spawnTest () {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, PerformanceController, OverviewView, DetailsView } = panel.panelWin;
let { WaterfallView, CallTreeView, FlameGraphView } = panel.panelWin;
let { WaterfallView, JsCallTreeView, JsFlameGraphView } = panel.panelWin;
let updatedWaterfall = 0;
let updatedCallTree = 0;
let updatedFlameGraph = 0;
WaterfallView.on(EVENTS.WATERFALL_RENDERED, () => updatedWaterfall++);
CallTreeView.on(EVENTS.CALL_TREE_RENDERED, () => updatedCallTree++);
FlameGraphView.on(EVENTS.FLAMEGRAPH_RENDERED, () => updatedFlameGraph++);
JsCallTreeView.on(EVENTS.JS_CALL_TREE_RENDERED, () => updatedCallTree++);
JsFlameGraphView.on(EVENTS.JS_FLAMEGRAPH_RENDERED, () => updatedFlameGraph++);
yield startRecording(panel);
yield busyWait(100);
@ -26,23 +26,23 @@ function spawnTest () {
yield rendered;
ok(true, "Waterfall rerenders when a range in the overview graph is selected.");
rendered = once(CallTreeView, EVENTS.CALL_TREE_RENDERED);
DetailsView.selectView("calltree");
rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
DetailsView.selectView("js-calltree");
yield rendered;
ok(true, "Call tree rerenders after its corresponding pane is shown.");
rendered = once(FlameGraphView, EVENTS.FLAMEGRAPH_RENDERED);
DetailsView.selectView("flamegraph");
rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
DetailsView.selectView("js-flamegraph");
yield rendered;
ok(true, "Flamegraph rerenders after its corresponding pane is shown.");
rendered = once(FlameGraphView, EVENTS.FLAMEGRAPH_RENDERED);
rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
OverviewView.emit(EVENTS.OVERVIEW_RANGE_CLEARED);
yield rendered;
ok(true, "Flamegraph rerenders when a range in the overview graph is removed.");
rendered = once(CallTreeView, EVENTS.CALL_TREE_RENDERED);
DetailsView.selectView("calltree");
rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
DetailsView.selectView("js-calltree");
yield rendered;
ok(true, "Call tree rerenders after its corresponding pane is shown.");
@ -52,8 +52,8 @@ function spawnTest () {
ok(true, "Waterfall rerenders after its corresponding pane is shown.");
is(updatedWaterfall, 3, "WaterfallView rerendered 3 times.");
is(updatedCallTree, 2, "CallTreeView rerendered 2 times.");
is(updatedFlameGraph, 2, "FlameGraphView rerendered 2 times.");
is(updatedCallTree, 2, "JsCallTreeView rerendered 2 times.");
is(updatedFlameGraph, 2, "JsFlameGraphView rerendered 2 times.");
yield teardown(panel);
finish();

Просмотреть файл

@ -56,8 +56,10 @@ let test = Task.async(function*() {
"The impored data is identical to the original data (4).");
is(importedData.ticks.toSource(), originalData.ticks.toSource(),
"The impored data is identical to the original data (5).");
is(importedData.profile.toSource(), originalData.profile.toSource(),
is(importedData.allocations.toSource(), originalData.allocations.toSource(),
"The impored data is identical to the original data (6).");
is(importedData.profile.toSource(), originalData.profile.toSource(),
"The impored data is identical to the original data (7).");
yield teardown(panel);
finish();

Просмотреть файл

@ -54,16 +54,18 @@ let test = Task.async(function*() {
"The imported legacy data was successfully converted for the current tool (1).");
is(importedData.duration, data.duration,
"The imported legacy data was successfully converted for the current tool (2).");
is(importedData.markers.toSource(), [].toSource(),
is(importedData.markers.toSource(), data.markers.toSource(),
"The imported legacy data was successfully converted for the current tool (3).");
is(importedData.frames.toSource(), [].toSource(),
is(importedData.frames.toSource(), data.frames.toSource(),
"The imported legacy data was successfully converted for the current tool (4).");
is(importedData.memory.toSource(), [].toSource(),
is(importedData.memory.toSource(), data.memory.toSource(),
"The imported legacy data was successfully converted for the current tool (5).");
is(importedData.ticks.toSource(), data.ticks.toSource(),
"The imported legacy data was successfully converted for the current tool (6).");
is(importedData.profile.toSource(), data.profile.toSource(),
is(importedData.allocations.toSource(), data.allocations.toSource(),
"The imported legacy data was successfully converted for the current tool (7).");
is(importedData.profile.toSource(), data.profile.toSource(),
"The imported legacy data was successfully converted for the current tool (8).");
yield teardown(panel);
finish();

Просмотреть файл

@ -298,9 +298,9 @@ function waitForWidgetsRendered(panel) {
let {
EVENTS,
OverviewView,
CallTreeView,
WaterfallView,
FlameGraphView
JsCallTreeView,
JsFlameGraphView
} = panel.panelWin;
return Promise.all([
@ -309,8 +309,8 @@ function waitForWidgetsRendered(panel) {
once(OverviewView, EVENTS.FRAMERATE_GRAPH_RENDERED),
once(OverviewView, EVENTS.OVERVIEW_RENDERED),
once(WaterfallView, EVENTS.WATERFALL_RENDERED),
once(CallTreeView, EVENTS.CALL_TREE_RENDERED),
once(FlameGraphView, EVENTS.FLAMEGRAPH_RENDERED)
once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED),
once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED)
]);
}

Просмотреть файл

@ -6,7 +6,7 @@
/**
* CallTree view containing profiler call tree, controlled by DetailsView.
*/
let CallTreeView = Heritage.extend(DetailsSubview, {
let JsCallTreeView = Heritage.extend(DetailsSubview, {
rangeChangeDebounceTime: 50, // ms
/**
@ -43,7 +43,7 @@ let CallTreeView = Heritage.extend(DetailsSubview, {
let profile = recording.getProfile();
let threadNode = this._prepareCallTree(profile, interval, options);
this._populateCallTree(threadNode, options);
this.emit(EVENTS.CALL_TREE_RENDERED);
this.emit(EVENTS.JS_CALL_TREE_RENDERED);
},
/**
@ -94,10 +94,13 @@ let CallTreeView = Heritage.extend(DetailsSubview, {
root.on("link", this._onLink);
// Clear out other call trees.
let container = $(".call-tree-cells-container");
let container = $("#js-calltree-view > .call-tree-cells-container");
container.innerHTML = "";
root.attachTo(container);
// Profiler data does not contain memory allocations information.
root.toggleAllocations(false);
// When platform data isn't shown, hide the cateogry labels, since they're
// only available for C++ frames.
let contentOnly = !Prefs.showPlatformData;

Просмотреть файл

@ -7,14 +7,14 @@
* FlameGraph view containing a pyramid-like visualization of a profile,
* controlled by DetailsView.
*/
let FlameGraphView = Heritage.extend(DetailsSubview, {
let JsFlameGraphView = Heritage.extend(DetailsSubview, {
/**
* Sets up the view with event binding.
*/
initialize: Task.async(function* () {
DetailsSubview.initialize.call(this);
this.graph = new FlameGraph($("#flamegraph-view"));
this.graph = new FlameGraph($("#js-flamegraph-view"));
this.graph.timelineTickUnits = L10N.getStr("graphs.ms");
yield this.graph.ready();
@ -61,7 +61,7 @@ let FlameGraphView = Heritage.extend(DetailsSubview, {
}
});
this.emit(EVENTS.FLAMEGRAPH_RENDERED);
this.emit(EVENTS.JS_FLAMEGRAPH_RENDERED);
},
/**

Просмотреть файл

@ -0,0 +1,124 @@
/* 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";
/**
* CallTree view containing memory allocation sites, controlled by DetailsView.
*/
let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
rangeChangeDebounceTime: 100, // ms
/**
* Sets up the view with event binding.
*/
initialize: function () {
DetailsSubview.initialize.call(this);
this._cache = new WeakMap();
this._onPrefChanged = this._onPrefChanged.bind(this);
this._onLink = this._onLink.bind(this);
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
},
/**
* Unbinds events.
*/
destroy: function () {
DetailsSubview.destroy.call(this);
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
},
/**
* Method for handling all the set up for rendering a new call tree.
*
* @param object interval [optional]
* The { startTime, endTime }, in milliseconds.
* @param object options [optional]
* Additional options for new the call tree.
*/
render: function (interval={}, options={}) {
let recording = PerformanceController.getCurrentRecording();
let allocations = recording.getAllocations();
let threadNode = this._prepareCallTree(allocations, interval, options);
this._populateCallTree(threadNode, options);
this.emit(EVENTS.MEMORY_CALL_TREE_RENDERED);
},
/**
* Fired on the "link" event for the call tree in this container.
*/
_onLink: function (_, treeItem) {
let { url, line } = treeItem.frame.getInfo();
viewSourceInDebugger(url, line).then(
() => this.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER),
() => this.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER));
},
/**
* Called when the recording is stopped and prepares data to
* populate the call tree.
*/
_prepareCallTree: function (allocations, { startTime, endTime }, options) {
let cached = this._cache.get(allocations);
if (cached) {
var samples = cached;
} else {
var samples = RecordingUtils.getSamplesFromAllocations(allocations);
this._cache.set(allocations, samples);
}
let contentOnly = !Prefs.showPlatformData;
let invertTree = PerformanceController.getPref("invert-call-tree");
let threadNode = new ThreadNode(samples,
{ startTime, endTime, contentOnly, invertTree });
// If we have an empty profile (no samples), then don't invert the tree, as
// it would hide the root node and a completely blank call tree space can be
// mis-interpreted as an error.
options.inverted = invertTree && threadNode.samples > 0;
return threadNode;
},
/**
* Renders the call tree.
*/
_populateCallTree: function (frameNode, options={}) {
let root = new CallView({
frame: frameNode,
inverted: options.inverted,
// Root nodes are hidden in inverted call trees.
hidden: options.inverted,
// Memory call trees should be sorted by allocations.
sortingPredicate: (a, b) => a.frame.allocations < b.frame.allocations ? 1 : -1,
// Call trees should only auto-expand when not inverted. Passing undefined
// will default to the CALL_TREE_AUTO_EXPAND depth.
autoExpandDepth: options.inverted ? 0 : undefined,
});
// Bind events.
root.on("link", this._onLink);
// Clear out other call trees.
let container = $("#memory-calltree-view > .call-tree-cells-container");
container.innerHTML = "";
root.attachTo(container);
// Memory allocation samples don't contain cateogry labels.
root.toggleCategories(false);
},
/**
* Called when a preference under "devtools.performance.ui." is changed.
*/
_onPrefChanged: function (_, prefName, value) {
if (prefName === "invert-call-tree") {
this.render(OverviewView.getTimeInterval());
}
}
});

Просмотреть файл

@ -0,0 +1,36 @@
/* 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";
/**
* FlameGraph view containing a pyramid-like visualization of memory allocation
* sites, controlled by DetailsView.
*
* TODO: bug 1077469
*/
let MemoryFlameGraphView = Heritage.extend(DetailsSubview, {
/**
* Sets up the view with event binding.
*/
initialize: Task.async(function* () {
DetailsSubview.initialize.call(this);
}),
/**
* Unbinds events.
*/
destroy: function () {
DetailsSubview.destroy.call(this);
},
/**
* Method for handling all the set up for rendering a new flamegraph.
*
* @param object interval [optional]
* The { startTime, endTime }, in milliseconds.
*/
render: function (interval={}) {
this.emit(EVENTS.MEMORY_FLAMEGRAPH_RENDERED);
}
});

Просмотреть файл

@ -14,9 +14,11 @@ let DetailsView = {
* Name to node+object mapping of subviews.
*/
components: {
waterfall: { id: "waterfall-view", view: WaterfallView },
calltree: { id: "calltree-view", view: CallTreeView },
flamegraph: { id: "flamegraph-view", view: FlameGraphView }
"waterfall": { id: "waterfall-view", view: WaterfallView },
"js-calltree": { id: "js-calltree-view", view: JsCallTreeView },
"js-flamegraph": { id: "js-flamegraph-view", view: JsFlameGraphView },
"memory-calltree": { id: "memory-calltree-view", view: MemoryCallTreeView },
"memory-flamegraph": { id: "memory-flamegraph-view", view: MemoryFlameGraphView }
},
/**

Просмотреть файл

@ -112,7 +112,7 @@
<label class="plain call-tree-header"
type="duration"
crop="end"
value="&profilerUI.table.totalDuration;"/>
value="&profilerUI.table.totalDuration2;"/>
<label class="plain call-tree-header"
type="percentage"
crop="end"
@ -120,7 +120,7 @@
<label class="plain call-tree-header"
type="self-duration"
crop="end"
value="&profilerUI.table.selfDuration;"/>
value="&profilerUI.table.selfDuration2;"/>
<label class="plain call-tree-header"
type="self-percentage"
crop="end"

Просмотреть файл

@ -22,14 +22,14 @@ function test() {
is(container.childNodes[0].className, "call-tree-item",
"The root node in the tree has the correct class name.");
is(container.childNodes[0].childNodes.length, 6,
is(container.childNodes[0].childNodes.length, 8,
"The root node in the tree has the correct number of children.");
is(container.childNodes[0].querySelectorAll(".call-tree-cell").length, 6,
is(container.childNodes[0].querySelectorAll(".call-tree-cell").length, 8,
"The root node in the tree has only 'call-tree-cell' children.");
is(container.childNodes[0].childNodes[0].getAttribute("type"), "duration",
"The root node in the tree has a duration cell.");
is(container.childNodes[0].childNodes[0].getAttribute("value"), "15",
is(container.childNodes[0].childNodes[0].getAttribute("value"), "15 ms",
"The root node in the tree has the correct duration cell value.");
is(container.childNodes[0].childNodes[1].getAttribute("type"), "percentage",
@ -37,24 +37,34 @@ function test() {
is(container.childNodes[0].childNodes[1].getAttribute("value"), "100%",
"The root node in the tree has the correct percentage cell value.");
is(container.childNodes[0].childNodes[2].getAttribute("type"), "self-duration",
is(container.childNodes[0].childNodes[2].getAttribute("type"), "allocations",
"The root node in the tree has a self-duration cell.");
is(container.childNodes[0].childNodes[2].getAttribute("value"), "0",
"The root node in the tree has the correct self-duration cell value.");
is(container.childNodes[0].childNodes[3].getAttribute("type"), "self-percentage",
is(container.childNodes[0].childNodes[3].getAttribute("type"), "self-duration",
"The root node in the tree has a self-duration cell.");
is(container.childNodes[0].childNodes[3].getAttribute("value"), "0 ms",
"The root node in the tree has the correct self-duration cell value.");
is(container.childNodes[0].childNodes[4].getAttribute("type"), "self-percentage",
"The root node in the tree has a self-percentage cell.");
is(container.childNodes[0].childNodes[3].getAttribute("value"), "0%",
is(container.childNodes[0].childNodes[4].getAttribute("value"), "0%",
"The root node in the tree has the correct self-percentage cell value.");
is(container.childNodes[0].childNodes[4].getAttribute("type"), "samples",
is(container.childNodes[0].childNodes[5].getAttribute("type"), "self-allocations",
"The root node in the tree has a self-percentage cell.");
is(container.childNodes[0].childNodes[5].getAttribute("value"), "0",
"The root node in the tree has the correct self-percentage cell value.");
is(container.childNodes[0].childNodes[6].getAttribute("type"), "samples",
"The root node in the tree has an samples cell.");
is(container.childNodes[0].childNodes[4].getAttribute("value"), "4",
is(container.childNodes[0].childNodes[6].getAttribute("value"), "4",
"The root node in the tree has the correct samples cell value.");
is(container.childNodes[0].childNodes[5].getAttribute("type"), "function",
is(container.childNodes[0].childNodes[7].getAttribute("type"), "function",
"The root node in the tree has a function cell.");
is(container.childNodes[0].childNodes[5].style.MozMarginStart, "0px",
is(container.childNodes[0].childNodes[7].style.MozMarginStart, "0px",
"The root node in the tree has the correct indentation.");
finish();

Просмотреть файл

@ -27,7 +27,7 @@ function test() {
is(container.childNodes[0].className, "call-tree-item",
"The root node in the tree has the correct class name.");
is($$dur(0).getAttribute("value"), "15",
is($$dur(0).getAttribute("value"), "15 ms",
"The root's duration cell displays the correct value.");
is($$perc(0).getAttribute("value"), "100%",
"The root's percentage cell displays the correct value.");
@ -53,7 +53,7 @@ function test() {
is(container.childNodes[1].className, "call-tree-item",
"The .A node in the tree has the correct class name.");
is($$dur(1).getAttribute("value"), "15",
is($$dur(1).getAttribute("value"), "15 ms",
"The .A node's duration cell displays the correct value.");
is($$perc(1).getAttribute("value"), "100%",
"The .A node's percentage cell displays the correct value.");
@ -82,7 +82,7 @@ function test() {
is(container.childNodes[3].className, "call-tree-item",
"The .E node in the tree has the correct class name.");
is($$dur(2).getAttribute("value"), "8",
is($$dur(2).getAttribute("value"), "8 ms",
"The .A.B node's duration cell displays the correct value.");
is($$perc(2).getAttribute("value"), "75%",
"The .A.B node's percentage cell displays the correct value.");
@ -101,7 +101,7 @@ function test() {
is($$fun(".call-tree-category")[2].getAttribute("value"), "Styles",
"The .A.B node's function cell displays the correct category.");
is($$dur(3).getAttribute("value"), "7",
is($$dur(3).getAttribute("value"), "7 ms",
"The .A.E node's duration cell displays the correct value.");
is($$perc(3).getAttribute("value"), "25%",
"The .A.E node's percentage cell displays the correct value.");

Просмотреть файл

@ -55,19 +55,19 @@ function test() {
is($$name(6).getAttribute("value"), "F",
"The .A.E.F node's function cell displays the correct name.");
is($$duration(0).getAttribute("value"), "15",
is($$duration(0).getAttribute("value"), "15 ms",
"The root node's function cell displays the correct duration.");
is($$duration(1).getAttribute("value"), "15",
is($$duration(1).getAttribute("value"), "15 ms",
"The .A node's function cell displays the correct duration.");
is($$duration(2).getAttribute("value"), "8",
is($$duration(2).getAttribute("value"), "8 ms",
"The .A.B node's function cell displays the correct duration.");
is($$duration(3).getAttribute("value"), "3",
is($$duration(3).getAttribute("value"), "3 ms",
"The .A.B.D node's function cell displays the correct duration.");
is($$duration(4).getAttribute("value"), "5",
is($$duration(4).getAttribute("value"), "5 ms",
"The .A.B.C node's function cell displays the correct duration.");
is($$duration(5).getAttribute("value"), "7",
is($$duration(5).getAttribute("value"), "7 ms",
"The .A.E node's function cell displays the correct duration.");
is($$duration(6).getAttribute("value"), "7",
is($$duration(6).getAttribute("value"), "7 ms",
"The .A.E.F node's function cell displays the correct duration.");
finish();

Просмотреть файл

@ -42,22 +42,26 @@ function test() {
ok(!A.target.querySelector(".call-tree-category").hidden,
"The .A.B.D node's category label cell should not be hidden.");
is(D.target.childNodes.length, 6,
is(D.target.childNodes.length, 8,
"The number of columns displayed for tree items is correct.");
is(D.target.childNodes[0].getAttribute("type"), "duration",
"The first column displayed for tree items is correct.");
is(D.target.childNodes[1].getAttribute("type"), "percentage",
"The third column displayed for tree items is correct.");
is(D.target.childNodes[2].getAttribute("type"), "self-duration",
is(D.target.childNodes[2].getAttribute("type"), "allocations",
"The second column displayed for tree items is correct.");
is(D.target.childNodes[3].getAttribute("type"), "self-percentage",
is(D.target.childNodes[3].getAttribute("type"), "self-duration",
"The second column displayed for tree items is correct.");
is(D.target.childNodes[4].getAttribute("type"), "self-percentage",
"The fourth column displayed for tree items is correct.");
is(D.target.childNodes[4].getAttribute("type"), "samples",
is(D.target.childNodes[5].getAttribute("type"), "self-allocations",
"The fourth column displayed for tree items is correct.");
is(D.target.childNodes[6].getAttribute("type"), "samples",
"The fifth column displayed for tree items is correct.");
is(D.target.childNodes[5].getAttribute("type"), "function",
is(D.target.childNodes[7].getAttribute("type"), "function",
"The sixth column displayed for tree items is correct.");
let functionCell = D.target.childNodes[5];
let functionCell = D.target.childNodes[7];
is(functionCell.childNodes.length, 9,
"The number of columns displayed for function cells is correct.");

Просмотреть файл

@ -509,6 +509,7 @@ let ProfileView = {
let contentOnly = !Prefs.showPlatformData;
callTreeRoot.toggleCategories(!contentOnly);
callTreeRoot.toggleAllocations(false);
this._callTreeRootByPanel.set(panel, callTreeRoot);
},

Просмотреть файл

@ -140,12 +140,15 @@ ThreadNode.prototype = {
* The column number inside the source containing this function call.
* @param number category
* The category type of this function call ("js", "graphics" etc.).
* @param number allocations
* The number of memory allocations performed in this frame.
*/
function FrameNode({ location, line, column, category }) {
function FrameNode({ location, line, column, category, allocations }) {
this.location = location;
this.line = line;
this.column = column;
this.category = category;
this.allocations = allocations || 0;
this.sampleTimes = [];
this.samples = 0;
this.duration = 0;

Просмотреть файл

@ -13,10 +13,13 @@ loader.lazyImporter(this, "Heritage",
loader.lazyImporter(this, "AbstractTreeItem",
"resource:///modules/devtools/AbstractTreeItem.jsm");
const MILLISECOND_UNITS = L10N.getStr("table.ms");
const PERCENTAGE_UNITS = L10N.getStr("table.percentage");
const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
const ZOOM_BUTTON_TOOLTIP = L10N.getStr("table.zoom.tooltiptext");
const CALL_TREE_INDENTATION = 16; // px
const CALL_TREE_AUTO_EXPAND = 3; // depth
const CALL_TREE_INDENTATION = 16; // px
const DEFAULT_SORTING_PREDICATE = (a, b) => a.frame.samples < b.frame.samples ? 1 : -1;
const clamp = (val, min, max) => Math.max(min, Math.min(max, val));
const sum = vals => vals.reduce((a, b) => a + b, 0);
@ -37,10 +40,6 @@ exports.CallView = CallView;
* Every instance of a `CallView` represents a row in the call tree. The same
* parent node is used for all rows.
*
* @param number autoExpandDepth [optional]
* The depth to which the tree should automatically expand. Defualts to
* the caller's autoExpandDepth if a caller exists, otherwise defaults to
* CALL_TREE_AUTO_EXPAND.
* @param CallView caller
* The CallView considered the "caller" frame. This instance will be
* represent the "callee". Should be null for root nodes.
@ -54,9 +53,17 @@ exports.CallView = CallView;
* @param boolean inverted [optional]
* Whether the call tree has been inverted (bottom up, rather than
* top-down). Defaults to false.
* @param function sortingPredicate [optional]
* The predicate used to sort the tree items when created. Defaults to
* the caller's sortingPredicate if a caller exists, otherwise defaults
* to DEFAULT_SORTING_PREDICATE. The two passed arguments are FrameNodes.
* @param number autoExpandDepth [optional]
* The depth to which the tree should automatically expand. Defualts to
* the caller's `autoExpandDepth` if a caller exists, otherwise defaults
* to CALL_TREE_AUTO_EXPAND.
*/
function CallView({ autoExpandDepth, caller, frame, level, hidden, inverted }) {
// Assume no indentation if the this tree item's level is not specified.
function CallView({ caller, frame, level, hidden, inverted, sortingPredicate, autoExpandDepth }) {
// Assume no indentation if this tree item's level is not specified.
level = level || 0;
// Don't increase indentation if this tree item is hidden.
@ -66,6 +73,11 @@ function CallView({ autoExpandDepth, caller, frame, level, hidden, inverted }) {
AbstractTreeItem.call(this, { parent: caller, level });
this.sortingPredicate = sortingPredicate != null
? sortingPredicate
: caller ? caller.sortingPredicate
: DEFAULT_SORTING_PREDICATE
this.autoExpandDepth = autoExpandDepth != null
? autoExpandDepth
: caller ? caller.autoExpandDepth
@ -95,18 +107,23 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
let selfPercentage;
let selfDuration;
let totalAllocations;
if (!this._getChildCalls().length) {
selfPercentage = framePercentage;
selfDuration = this.frame.duration;
totalAllocations = this.frame.allocations;
} else {
let childrenPercentage = sum(
[this._getPercentage(c.samples) for (c of this._getChildCalls())]);
let childrenDuration = sum(
[c.duration for (c of this._getChildCalls())]);
let childrenAllocations = sum(
[c.allocations for (c of this._getChildCalls())]);
selfPercentage = clamp(framePercentage - childrenPercentage, 0, 100);
selfDuration = this.frame.duration - childrenDuration;
totalAllocations = this.frame.allocations + childrenAllocations;
if (this.inverted) {
selfPercentage = framePercentage - selfPercentage;
@ -118,6 +135,8 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
let selfDurationCell = this._createTimeCell(selfDuration, true);
let percentageCell = this._createExecutionCell(framePercentage);
let selfPercentageCell = this._createExecutionCell(selfPercentage, true);
let allocationsCell = this._createAllocationsCell(totalAllocations);
let selfAllocationsCell = this._createAllocationsCell(this.frame.allocations, true);
let samplesCell = this._createSamplesCell(this.frame.samples);
let functionCell = this._createFunctionCell(arrowNode, frameInfo, this.level);
@ -138,8 +157,10 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
targetNode.appendChild(durationCell);
targetNode.appendChild(percentageCell);
targetNode.appendChild(allocationsCell);
targetNode.appendChild(selfDurationCell);
targetNode.appendChild(selfPercentageCell);
targetNode.appendChild(selfAllocationsCell);
targetNode.appendChild(samplesCell);
targetNode.appendChild(functionCell);
@ -177,8 +198,9 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
}));
}
// Sort the "callees" asc. by samples, before inserting them in the tree.
children.sort((a, b) => a.frame.samples < b.frame.samples ? 1 : -1);
// Sort the "callees" asc. by samples, before inserting them in the tree,
// if no other sorting predicate was specified on this on the root item.
children.sort(this.sortingPredicate);
},
/**
@ -190,7 +212,7 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
cell.className = "plain call-tree-cell";
cell.setAttribute("type", isSelf ? "self-duration" : "duration");
cell.setAttribute("crop", "end");
cell.setAttribute("value", L10N.numberWithDecimals(duration, 2));
cell.setAttribute("value", L10N.numberWithDecimals(duration, 2) + " " + MILLISECOND_UNITS);
return cell;
},
_createExecutionCell: function(percentage, isSelf = false) {
@ -198,7 +220,15 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
cell.className = "plain call-tree-cell";
cell.setAttribute("type", isSelf ? "self-percentage" : "percentage");
cell.setAttribute("crop", "end");
cell.setAttribute("value", L10N.numberWithDecimals(percentage, 2) + "%");
cell.setAttribute("value", L10N.numberWithDecimals(percentage, 2) + PERCENTAGE_UNITS);
return cell;
},
_createAllocationsCell: function(count, isSelf = false) {
let cell = this.document.createElement("label");
cell.className = "plain call-tree-cell";
cell.setAttribute("type", isSelf ? "self-allocations" : "allocations");
cell.setAttribute("crop", "end");
cell.setAttribute("value", count || 0);
return cell;
},
_createSamplesCell: function(count) {
@ -271,6 +301,18 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
return cell;
},
/**
* Toggles the allocations information hidden or visible.
* @param boolean visible
*/
toggleAllocations: function(visible) {
if (!visible) {
this.container.setAttribute("allocations-hidden", "");
} else {
this.container.removeAttribute("allocations-hidden");
}
},
/**
* Toggles the category information hidden or visible.
* @param boolean visible

Просмотреть файл

@ -41,13 +41,23 @@
- on a button that remvoes all the recordings. -->
<!ENTITY profilerUI.clearButton "Clear">
<!-- LOCALIZATION NOTE (profilerUI.toolbar.*): These strings are displayed
- in the toolbar on buttons that select which view is currently shown. -->
<!ENTITY profilerUI.toolbar.waterfall "Timeline">
<!ENTITY profilerUI.toolbar.js-calltree "JavaScript">
<!ENTITY profilerUI.toolbar.memory-calltree "Memory">
<!ENTITY profilerUI.toolbar.js-flamegraph "JS Flame Chart">
<!ENTITY profilerUI.toolbar.memory-flamegraph "Memory Flame Chart">
<!-- LOCALIZATION NOTE (profilerUI.table.*): These strings are displayed
- in the call tree headers for a recording. -->
<!ENTITY profilerUI.table.totalDuration "Total Time (ms)">
<!ENTITY profilerUI.table.selfDuration "Self Time (ms)">
<!ENTITY profilerUI.table.totalDuration2 "Total Time">
<!ENTITY profilerUI.table.selfDuration2 "Self Time">
<!ENTITY profilerUI.table.totalPercentage "Total Cost">
<!ENTITY profilerUI.table.selfPercentage "Self Cost">
<!ENTITY profilerUI.table.samples "Samples">
<!ENTITY profilerUI.table.totalAlloc "Total Allocations">
<!ENTITY profilerUI.table.selfAlloc "Self Allocations">
<!ENTITY profilerUI.table.function "Function">
<!-- LOCALIZATION NOTE (profilerUI.newtab.tooltiptext): The tooltiptext shown

Просмотреть файл

@ -83,6 +83,14 @@ category.graphics=Graphics
category.storage=Storage
category.events=Input & Events
# LOCALIZATION NOTE (graphs.ms):
# This string is displayed in the call tree after units of time in milliseconds.
table.ms=ms
# LOCALIZATION NOTE (graphs.ms):
# This string is displayed in the call tree after units representing percentages.
table.percentage=%
# LOCALIZATION NOTE (table.root):
# This string is displayed in the call tree for the root node.
table.root=(root)

Просмотреть файл

@ -28,9 +28,9 @@
</g>
<g id="details-call-tree">
<rect x="0px" y="3px" width="16px" height="2px" rx="1" ry="1"/>
<rect x="3px" y="6px" width="7px" height="2px" rx="1" ry="1"/>
<rect x="6px" y="9px" width="6px" height="2px" rx="1" ry="1"/>
<rect x="9px" y="12px" width="5px" height="2px" rx="1" ry="1"/>
<rect x="0px" y="6px" width="8px" height="2px" rx="1" ry="1"/>
<rect x="0px" y="9px" width="11px" height="2px" rx="1" ry="1"/>
<rect x="0px" y="12px" width="6px" height="2px" rx="1" ry="1"/>
</g>
<g id="details-flamegraph">
<rect x="0px" y="3px" width="16px" height="2px" rx="1" ry="1"/>

До

Ширина:  |  Высота:  |  Размер: 1.8 KiB

После

Ширина:  |  Высота:  |  Размер: 1.8 KiB

Просмотреть файл

@ -25,7 +25,14 @@
-moz-border-end-color: var(--theme-splitter-color);
}
/* Overview Panel */
#performance-toolbar-controls-detail-views > toolbarbutton {
min-width: 0;
}
#performance-toolbar-controls-detail-views .toolbarbutton-text {
-moz-padding-start: 4px;
-moz-padding-end: 8px;
}
#record-button {
list-style-image: url(profiler-stopwatch.svg);
@ -45,11 +52,13 @@
list-style-image: url(performance-icons.svg#details-waterfall);
}
#select-calltree-view {
#select-js-calltree-view,
#select-memory-calltree-view {
list-style-image: url(performance-icons.svg#details-call-tree);
}
#select-flamegraph-view {
#select-js-flamegraph-view,
#select-memory-flamegraph-view {
list-style-image: url(performance-icons.svg#details-flamegraph);
}
@ -61,27 +70,42 @@
overflow: auto;
}
.call-tree-cells-container[allocations-hidden] .call-tree-cell[type="allocations"],
.call-tree-cells-container[allocations-hidden] .call-tree-cell[type="self-allocations"],
.call-tree-cells-container[categories-hidden] .call-tree-category {
display: none;
}
.call-tree-header {
font-size: 90%;
padding-top: 2px !important;
padding-bottom: 2px !important;
}
.call-tree-header[type="duration"],
.call-tree-cell[type="duration"],
.call-tree-header[type="self-duration"],
.call-tree-cell[type="self-duration"] {
width: 9em;
width: 6vw;
}
.call-tree-header[type="percentage"],
.call-tree-cell[type="percentage"],
.call-tree-header[type="self-percentage"],
.call-tree-cell[type="self-percentage"] {
width: 6em;
width: 5vw;
}
.call-tree-header[type="samples"],
.call-tree-cell[type="samples"] {
width: 5em;
width: 4vw;
}
.call-tree-header[type="allocations"],
.call-tree-cell[type="allocations"],
.call-tree-header[type="self-allocations"],
.call-tree-cell[type="self-allocations"] {
width: 7vw;
}
.call-tree-header[type="function"],

Просмотреть файл

@ -220,6 +220,8 @@
overflow: auto;
}
.call-tree-cells-container[allocations-hidden] .call-tree-cell[type="allocations"],
.call-tree-cells-container[allocations-hidden] .call-tree-cell[type="self-allocations"],
.call-tree-cells-container[categories-hidden] .call-tree-category {
display: none;
}

Просмотреть файл

@ -165,11 +165,15 @@ let MemoryActor = protocol.ActorClass({
? options.probability
: 1.0;
this.dbg.memory.trackingAllocationSites = true;
return Date.now();
}), {
request: {
options: Arg(0, "nullable:AllocationsRecordingOptions")
},
response: {}
response: {
value: RetVal(0, "number")
}
}),
/**
@ -178,9 +182,13 @@ let MemoryActor = protocol.ActorClass({
stopRecordingAllocations: method(expectState("attached", function() {
this.dbg.memory.trackingAllocationSites = false;
this._clearFrames();
return Date.now();
}), {
request: {},
response: {}
response: {
value: RetVal(0, "number")
}
}),
/**