Bug 1167899 - Profiler data should compute timing information from sample count, not timestamps in the samples. r=shu

This commit is contained in:
Jordan Santell 2015-06-08 18:16:18 -07:00
Родитель 93bf3a90af
Коммит db4860b1f2
28 изменённых файлов: 156 добавлений и 178 удалений

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

@ -23,21 +23,21 @@ loader.lazyRequireGetter(this, "FrameUtils",
* The raw thread object received from the backend. Contains samples,
* stackTable, frameTable, and stringTable.
* @param object options
* Additional supported options, @see ThreadNode.prototype.insert
* - number startTime [optional]
* - number endTime [optional]
* Additional supported options
* - number startTime
* - number endTime
* - boolean contentOnly [optional]
* - boolean invertTree [optional]
* - boolean flattenRecursion [optional]
*/
function ThreadNode(thread, options = {}) {
if (options.endTime == void 0 || options.startTime == void 0) {
throw new Error("ThreadNode requires both `startTime` and `endTime`.");
}
this.samples = 0;
this.duration = 0;
this.youngestFrameSamples = 0;
this.calls = [];
// Maps of frame to their self counts and duration.
this.selfCount = Object.create(null);
this.selfDuration = Object.create(null);
this.duration = options.endTime - options.startTime;
let { samples, stackTable, frameTable, stringTable, allocationsTable } = thread;
@ -74,8 +74,8 @@ ThreadNode.prototype = {
* index.
* @param object options
* Additional supported options
* - number startTime [optional]
* - number endTime [optional]
* - number startTime
* - number endTime
* - boolean contentOnly [optional]
* - boolean invertTree [optional]
*/
@ -120,9 +120,6 @@ ThreadNode.prototype = {
const InflatedFrame = FrameUtils.InflatedFrame;
const getOrAddInflatedFrame = FrameUtils.getOrAddInflatedFrame;
let selfCount = this.selfCount;
let selfDuration = this.selfDuration;
let samplesData = samples.data;
let stacksData = stackTable.data;
@ -130,8 +127,8 @@ ThreadNode.prototype = {
let inflatedFrameCache = FrameUtils.getInflatedFrameCache(frameTable);
let leafTable = Object.create(null);
let startTime = options.startTime || 0
let endTime = options.endTime || Infinity;
let startTime = options.startTime;
let endTime = options.endTime;
let flattenRecursion = options.flattenRecursion;
// Take the timestamp of the first sample as prevSampleTime. 0 is
@ -163,7 +160,6 @@ ThreadNode.prototype = {
continue;
}
let sampleDuration = sampleTime - prevSampleTime;
let stackIndex = sample[SAMPLE_STACK_SLOT];
let calls = this.calls;
let prevCalls = this.calls;
@ -223,22 +219,9 @@ ThreadNode.prototype = {
mutableFrameKeyOptions.isRoot = stackIndex === null;
let frameKey = inflatedFrame.getFrameKey(mutableFrameKeyOptions);
// Leaf frames are never skipped and require self count and duration
// bookkeeping.
if (isLeaf) {
// Tabulate self count and duration for the leaf frame. The frameKey
// is never empty for a leaf frame.
if (selfCount[frameKey] === undefined) {
selfCount[frameKey] = 0;
selfDuration[frameKey] = 0;
}
selfCount[frameKey]++;
selfDuration[frameKey] += sampleDuration;
} else {
// An empty frame key means this frame should be skipped.
if (frameKey === "") {
continue;
}
// An empty frame key means this frame should be skipped.
if (frameKey === "") {
continue;
}
// If we shouldn't flatten the current frame into the previous one, advance a
@ -250,18 +233,18 @@ ThreadNode.prototype = {
let frameNode = getOrAddFrameNode(calls, isLeaf, frameKey, inflatedFrame,
mutableFrameKeyOptions.isMetaCategoryOut,
leafTable);
frameNode._countSample(prevSampleTime, sampleTime, inflatedFrame.optimizations,
stringTable);
if (isLeaf) {
frameNode.youngestFrameSamples++;
}
frameNode.samples++;
frameNode._addOptimizations(inflatedFrame.optimizations, stringTable);
prevFrameKey = frameKey;
prevCalls = frameNode.calls;
isLeaf = mutableFrameKeyOptions.isLeaf = false;
}
this.duration += sampleDuration;
this.samples++;
prevSampleTime = sampleTime;
}
},
@ -347,7 +330,19 @@ ThreadNode.prototype = {
};
/**
* A function call node in a tree.
* A function call node in a tree. Represents a function call with a unique context,
* resulting in each FrameNode having its own row in the corresponding tree view.
* Take samples:
* A()->B()->C()
* A()->B()
* Q()->B()
*
* In inverted tree, A()->B()->C() would have one frame node, and A()->B() and
* Q()->B() would share a frame node.
* In an uninverted tree, A()->B()->C() and A()->B() would share a frame node,
* with Q()->B() having its own.
*
* In all cases, all the frame nodes originated from the same InflatedFrame.
*
* @param string frameKey
* The key associated with this frame. The key determines identity of
@ -372,8 +367,8 @@ function FrameNode(frameKey, { location, line, category, allocations, isContent
this.location = location;
this.line = line;
this.allocations = allocations;
this.youngestFrameSamples = 0;
this.samples = 0;
this.duration = 0;
this.calls = [];
this.isContent = !!isContent;
this._optimizations = null;
@ -384,22 +379,15 @@ function FrameNode(frameKey, { location, line, category, allocations, isContent
FrameNode.prototype = {
/**
* Count a sample as associated with this node.
* Take optimization data observed for this frame.
*
* @param number prevSampleTime
* The time when the immediate previous sample was sampled.
* @param number sampleTime
* The time when the current sample was sampled.
* @param object optimizationSite
* Any JIT optimization information attached to the current
* sample. Lazily inflated via stringTable.
* @param object stringTable
* The string table used to inflate the optimizationSite.
*/
_countSample: function (prevSampleTime, sampleTime, optimizationSite, stringTable) {
this.samples++;
this.duration += sampleTime - prevSampleTime;
_addOptimizations: function (optimizationSite, stringTable) {
// Simply accumulate optimization sites for now. Processing is done lazily
// by JITOptimizations, if optimization information is actually displayed.
if (optimizationSite) {
@ -424,7 +412,7 @@ FrameNode.prototype = {
}
this.samples += otherNode.samples;
this.duration += otherNode.duration;
this.youngestFrameSamples += otherNode.youngestFrameSamples;
if (otherNode._optimizations) {
let opts = this._optimizations;

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

@ -28,7 +28,7 @@ const DEFAULT_SORTING_PREDICATE = (frameA, frameB) => {
}
return dataA.selfPercentage < dataB.selfPercentage ? 1 : - 1;
}
return dataA.samples < dataB.samples ? 1 : -1;
return dataA.totalPercentage < dataB.totalPercentage ? 1 : -1;
};
const DEFAULT_AUTO_EXPAND_DEPTH = 3; // depth
@ -358,54 +358,28 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, {
// Leaf nodes in an inverted tree don't have to do anything special.
let isLeaf = this._level === 0;
let totalSamples = this.root.frame.samples;
let totalDuration = this.root.frame.duration;
if (this.inverted && !isLeaf && this.parent != null) {
let calleeData = this.parent.getDisplayedData();
// Percentage of time that this frame called the callee
// in this branch
let callerPercentage = this.frame.samples / calleeData.samples;
// Self duration, cost
if (this.visibleCells.selfDuration) {
data.selfDuration = this.frame.youngestFrameSamples / totalSamples * totalDuration;
}
if (this.visibleCells.selfPercentage) {
data.selfPercentage = this.frame.youngestFrameSamples / totalSamples * 100;
}
// Self/total duration.
if (this.visibleCells.duration) {
data.totalDuration = calleeData.totalDuration * callerPercentage;
}
if (this.visibleCells.selfDuration) {
data.selfDuration = 0;
}
// Total duration, cost
if (this.visibleCells.duration) {
data.totalDuration = this.frame.samples / totalSamples * totalDuration;
}
if (this.visibleCells.percentage) {
data.totalPercentage = this.frame.samples / totalSamples * 100;
}
// Self/total samples percentage.
if (this.visibleCells.percentage) {
data.totalPercentage = calleeData.totalPercentage * callerPercentage;
}
if (this.visibleCells.selfPercentage) {
data.selfPercentage = 0;
}
// Raw samples.
if (this.visibleCells.samples) {
data.samples = this.frame.samples;
}
} else {
// Self/total duration.
if (this.visibleCells.duration) {
data.totalDuration = this.frame.duration;
}
if (this.visibleCells.selfDuration) {
data.selfDuration = this.root.frame.selfDuration[this.frame.key];
}
// Self/total samples percentage.
if (this.visibleCells.percentage) {
data.totalPercentage = this.frame.samples / this.root.frame.samples * 100;
}
if (this.visibleCells.selfPercentage) {
data.selfPercentage = this.root.frame.selfCount[this.frame.key] / this.root.frame.samples * 100;
}
// Raw samples.
if (this.visibleCells.samples) {
data.samples = this.frame.samples;
}
// Raw samples.
if (this.visibleCells.samples) {
data.samples = this.frame.youngestFrameSamples;
}
// Self/total allocations count.

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

@ -127,8 +127,6 @@ const EVENTS = {
// Emitted by the OverviewView when a range has been selected in the graphs
OVERVIEW_RANGE_SELECTED: "Performance:UI:OverviewRangeSelected",
// Emitted by the OverviewView when a selection range has been removed
OVERVIEW_RANGE_CLEARED: "Performance:UI:OverviewRangeCleared",
// Emitted by the DetailsView when a subview is selected
DETAILS_VIEW_SELECTED: "Performance:UI:DetailsViewSelected",

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

@ -14,7 +14,7 @@ function* spawnTest() {
Services.prefs.setBoolPref(MEMORY_PREF, true);
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, $, DetailsView, JsCallTreeView, MemoryCallTreeView } = panel.panelWin;
let { EVENTS, $, DetailsView, OverviewView, JsCallTreeView, MemoryCallTreeView } = panel.panelWin;
yield DetailsView.selectView("js-calltree");
ok(DetailsView.isViewSelected(JsCallTreeView), "The call tree is now selected.");
@ -26,7 +26,7 @@ function* spawnTest() {
yield rendered;
// Mock the profile used so we can get a deterministic tree created
let threadNode = new ThreadNode(gProfile.threads[0]);
let threadNode = new ThreadNode(gProfile.threads[0], OverviewView.getTimeInterval());
JsCallTreeView._populateCallTree(threadNode);
JsCallTreeView.emit(EVENTS.JS_CALL_TREE_RENDERED);

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

@ -58,7 +58,7 @@ function* spawnTest() {
// Force a rerender
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
JsCallTreeView.render();
JsCallTreeView.render(OverviewView.getTimeInterval());
yield rendered;
is($("#jit-optimizations-view").hidden, true, "JIT Optimizations panel still hidden when rerendered");

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

@ -61,7 +61,7 @@ function* spawnTest() {
// Force a rerender
let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED);
JsCallTreeView.render();
JsCallTreeView.render(OverviewView.getTimeInterval());
yield rendered;
Services.prefs.setBoolPref(JIT_PREF, true);

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

@ -7,7 +7,7 @@
function* spawnTest() {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, PerformanceController, OverviewView } = panel.panelWin;
let startTime, endTime, params, _;
let params, _;
yield startRecording(panel);
@ -21,32 +21,49 @@ function* spawnTest() {
let graph = OverviewView.graphs.get("timeline");
let MAX = graph.width;
let duration = PerformanceController.getCurrentRecording().getDuration();
let selection = null;
// Throw out events that select everything, as this will occur on the
// first click
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, function handler (_, interval) {
if (interval.endTime !== duration) {
selection = interval;
OverviewView.off(handler);
}
});
// Select the first half of the graph
let results = onceSpread(OverviewView, EVENTS.OVERVIEW_RANGE_SELECTED);
// Select the first half of the graph
dragStart(graph, 0);
dragStop(graph, MAX / 2);
[_, { startTime, endTime }] = yield results;
yield waitUntil(() => selection);
let { startTime, endTime } = selection;
let mapStart = () => 0;
let mapEnd = () => PerformanceController.getCurrentRecording().getDuration();
let mapEnd = () => duration;
let actual = graph.getMappedSelection({ mapStart, mapEnd });
is(actual.min, 0, "graph selection starts at 0");
is(actual.max, duration/2, `graph selection ends at ${duration/2}`);
is(graph.hasSelection(), true,
"A selection exists on the graph.");
is(startTime, actual.min,
is(startTime, 0,
"OVERVIEW_RANGE_SELECTED fired with startTime value on click.");
is(endTime, actual.max,
is(endTime, duration / 2,
"OVERVIEW_RANGE_SELECTED fired with endTime value on click.");
// Listen to deselection
results = onceSpread(OverviewView, EVENTS.OVERVIEW_RANGE_CLEARED);
results = onceSpread(OverviewView, EVENTS.OVERVIEW_RANGE_SELECTED);
dropSelection(graph);
[_, params] = yield results;
is(graph.hasSelection(), false,
"A selection no longer on the graph.");
is(params, undefined,
"OVERVIEW_RANGE_CLEARED fired with no additional arguments.");
is(params.startTime, 0,
"OVERVIEW_RANGE_SELECTED fired with 0 as a startTime.");
is(params.endTime, duration,
"OVERVIEW_RANGE_SELECTED fired with max duration as endTime");
yield teardown(panel);
finish();

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

@ -40,7 +40,7 @@ function* spawnTest() {
ok(true, "Flamegraph rerenders after its corresponding pane is shown.");
rendered = once(JsFlameGraphView, EVENTS.JS_FLAMEGRAPH_RENDERED);
OverviewView.emit(EVENTS.OVERVIEW_RANGE_CLEARED);
OverviewView.emit(EVENTS.OVERVIEW_RANGE_SELECTED);
yield rendered;
ok(true, "Flamegraph rerenders when a range in the overview graph is removed.");

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

@ -10,7 +10,7 @@ function test() {
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
let { CallView } = devtools.require("devtools/performance/tree-view");
let threadNode = new ThreadNode(gThread);
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });
@ -31,7 +31,7 @@ function test() {
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 ms",
is(container.childNodes[0].childNodes[0].getAttribute("value"), "20 ms",
"The root node in the tree has the correct duration cell value.");
is(container.childNodes[0].childNodes[1].getAttribute("type"), "percentage",
@ -51,7 +51,7 @@ function test() {
is(container.childNodes[0].childNodes[4].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[4].getAttribute("value"), "0",
"The root node in the tree has the correct samples cell value.");
is(container.childNodes[0].childNodes[5].getAttribute("type"), "function",

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

@ -12,7 +12,7 @@ function test() {
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
let { CallView } = devtools.require("devtools/performance/tree-view");
let threadNode = new ThreadNode(gThread);
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });
@ -33,11 +33,11 @@ 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 ms",
is($$dur(0).getAttribute("value"), "20 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.");
is($$sampl(0).getAttribute("value"), "4",
is($$sampl(0).getAttribute("value"), "0",
"The root's samples cell displays the correct value.");
is($$fun(".call-tree-name")[0].getAttribute("value"), "(root)",
"The root's function cell displays the correct name.");
@ -59,11 +59,11 @@ 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 ms",
is($$dur(1).getAttribute("value"), "20 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.");
is($$sampl(1).getAttribute("value"), "4",
is($$sampl(1).getAttribute("value"), "0",
"The .A node's samples cell displays the correct value.");
is($fun(".call-tree-name", $$(".call-tree-item")[1]).getAttribute("value"), "A",
"The .A node's function cell displays the correct name.");
@ -88,11 +88,11 @@ 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 ms",
is($$dur(2).getAttribute("value"), "15 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.");
is($$sampl(2).getAttribute("value"), "3",
is($$sampl(2).getAttribute("value"), "0",
"The .A.B node's samples cell displays the correct value.");
is($fun(".call-tree-name", $$(".call-tree-item")[2]).getAttribute("value"), "B",
"The .A.B node's function cell displays the correct name.");
@ -107,11 +107,11 @@ function test() {
is($fun(".call-tree-category", $$(".call-tree-item")[2]).getAttribute("value"), "Styles",
"The .A.B node's function cell displays the correct category.");
is($$dur(3).getAttribute("value"), "7 ms",
is($$dur(3).getAttribute("value"), "5 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.");
is($$sampl(3).getAttribute("value"), "1",
is($$sampl(3).getAttribute("value"), "0",
"The .A.E node's samples cell displays the correct value.");
is($fun(".call-tree-name", $$(".call-tree-item")[3]).getAttribute("value"), "E",
"The .A.E node's function cell displays the correct name.");

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

@ -10,7 +10,7 @@ function test() {
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
let { CallView } = devtools.require("devtools/performance/tree-view");
let threadNode = new ThreadNode(gThread);
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });
@ -57,19 +57,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 ms",
is($$duration(0).getAttribute("value"), "20 ms",
"The root node's function cell displays the correct duration.");
is($$duration(1).getAttribute("value"), "15 ms",
is($$duration(1).getAttribute("value"), "20 ms",
"The .A node's function cell displays the correct duration.");
is($$duration(2).getAttribute("value"), "8 ms",
is($$duration(2).getAttribute("value"), "15 ms",
"The .A.B node's function cell displays the correct duration.");
is($$duration(3).getAttribute("value"), "3 ms",
is($$duration(3).getAttribute("value"), "10 ms",
"The .A.B.D node's function cell displays the correct duration.");
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 ms",
is($$duration(5).getAttribute("value"), "5 ms",
"The .A.E node's function cell displays the correct duration.");
is($$duration(6).getAttribute("value"), "7 ms",
is($$duration(6).getAttribute("value"), "5 ms",
"The .A.E.F node's function cell displays the correct duration.");
finish();

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

@ -12,7 +12,7 @@ function test() {
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
let { CallView } = devtools.require("devtools/performance/tree-view");
let threadNode = new ThreadNode(gThread);
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });

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

@ -10,7 +10,7 @@ function test() {
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
let { CallView } = devtools.require("devtools/performance/tree-view");
let threadNode = new ThreadNode(gThread);
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });

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

@ -10,7 +10,7 @@ function* spawnTest() {
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
let { CallView } = devtools.require("devtools/performance/tree-view");
let threadNode = new ThreadNode(gThread);
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });

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

@ -10,7 +10,7 @@ function* spawnTest() {
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
let { CallView } = devtools.require("devtools/performance/tree-view");
let threadNode = new ThreadNode(gThread);
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });

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

@ -26,7 +26,7 @@ function test() {
* - (JS)
*/
let threadNode = new ThreadNode(gThread, { contentOnly: true });
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 30, contentOnly: true });
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode, autoExpandDepth: 10 });

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

@ -12,7 +12,7 @@ let test = Task.async(function*() {
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
let { CallView } = devtools.require("devtools/performance/tree-view");
let threadNode = new ThreadNode(gSamples, { invertTree: true });
let threadNode = new ThreadNode(gSamples, { invertTree: true, startTime: 0, endTime: 10 });
let treeRoot = new CallView({ frame: threadNode, inverted: true, autoExpandDepth: 1 });
let container = document.createElement("vbox");

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

@ -10,7 +10,7 @@ function test() {
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
let { CallView } = devtools.require("devtools/performance/tree-view");
let threadNode = new ThreadNode(gThread, { invertTree: true });
let threadNode = new ThreadNode(gThread, { invertTree: true, startTime: 0, endTime: 50 });
let treeRoot = new CallView({ frame: threadNode, inverted: true, hidden: true });
let container = document.createElement("vbox");

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

@ -14,7 +14,7 @@ add_task(function test() {
// Create a root node from a given samples array.
let threadNode = new ThreadNode(gThread);
let threadNode = new ThreadNode(gThread, { startTime: 0, endTime: 20 });
let root = getFrameNodePath(threadNode, "(root)");
// Test the root node.
@ -22,8 +22,8 @@ add_task(function test() {
equal(threadNode.getInfo().nodeType, "Thread",
"The correct node type was retrieved for the root node.");
equal(root.duration, 20,
"The correct duration was calculated for the root node.");
equal(threadNode.duration, 20,
"The correct duration was calculated for the ThreadNode.");
equal(root.getInfo().functionName, "(root)",
"The correct function name was retrieved for the root node.");
equal(root.getInfo().categoryData.abbrev, "other",
@ -82,32 +82,38 @@ add_task(function test() {
equal(getFrameNodePath(root, "A > E > F").calls.length, 0,
"The correct number of child calls were calculated for the 'A > E > F' node.");
// Check the location, sample times, duration and samples of the root.
// Check the location, sample times, and samples of the root.
equal(getFrameNodePath(root, "A").location, "A",
"The 'A' node has the correct location.");
equal(getFrameNodePath(root, "A").duration, 20,
"The 'A' node has the correct duration in milliseconds.");
equal(getFrameNodePath(root, "A").youngestFrameSamples, 0,
"The 'A' has correct `youngestFrameSamples`");
equal(getFrameNodePath(root, "A").samples, 4,
"The 'A' node has the correct number of samples.");
"The 'A' has correct `samples`");
// A frame that is both a leaf and caught in another stack
equal(getFrameNodePath(root, "A > B > C").youngestFrameSamples, 1,
"The 'A > B > C' has correct `youngestFrameSamples`");
equal(getFrameNodePath(root, "A > B > C").samples, 2,
"The 'A > B > C' has correct `samples`");
// ...and the rightmost leaf.
equal(getFrameNodePath(root, "A > E > F").location, "F",
"The 'A > E > F' node has the correct location.");
equal(getFrameNodePath(root, "A > E > F").duration, 7,
"The 'A > E > F' node has the correct duration in milliseconds.");
equal(getFrameNodePath(root, "A > E > F").samples, 1,
"The 'A > E > F' node has the correct number of samples.");
equal(getFrameNodePath(root, "A > E > F").youngestFrameSamples, 1,
"The 'A > E > F' node has the correct number of youngestFrameSamples.");
// ...and the leftmost leaf.
equal(getFrameNodePath(root, "A > B > C > D > E > F > G").location, "G",
"The 'A > B > C > D > E > F > G' node has the correct location.");
equal(getFrameNodePath(root, "A > B > C > D > E > F > G").duration, 2,
"The 'A > B > C > D > E > F > G' node has the correct duration in milliseconds.");
equal(getFrameNodePath(root, "A > B > C > D > E > F > G").samples, 1,
"The 'A > B > C > D > E > F > G' node has the correct number of samples.");
equal(getFrameNodePath(root, "A > B > C > D > E > F > G").youngestFrameSamples, 1,
"The 'A > B > C > D > E > F > G' node has the correct number of youngestFrameSamples.");
});
let gThread = synthesizeProfileForTest([{

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

@ -14,12 +14,12 @@ add_task(function test() {
// Create a root node from a given samples array.
let root = getFrameNodePath(new ThreadNode(gThread), "(root)");
let thread = new ThreadNode(gThread, { startTime: 0, endTime: 10 });
let root = getFrameNodePath(thread, "(root)");
// Test the root node.
equal(root.duration, 5,
"The correct duration was calculated for the root node.");
// Test the ThreadNode, only node with a duration.
equal(thread.duration, 10,
"The correct duration was calculated for the ThreadNode.");
equal(root.calls.length, 1,
"The correct number of child calls were calculated for the root node.");

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

@ -20,12 +20,13 @@ add_task(function test() {
// exactly at 18.
let startTime = 5;
let endTime = 18;
let root = getFrameNodePath(new ThreadNode(gThread, { startTime, endTime }), "(root)");
let thread = new ThreadNode(gThread, { startTime, endTime });
let root = getFrameNodePath(thread, "(root)");
// Test the root node.
equal(root.duration, endTime - startTime,
"The correct duration was calculated for the root node.");
equal(thread.duration, endTime - startTime,
"The correct duration was calculated for the ThreadNode.");
equal(root.calls.length, 1,
"The correct number of child calls were calculated for the root node.");

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

@ -17,12 +17,12 @@ add_task(function test() {
let startTime = 5;
let endTime = 18;
let root = getFrameNodePath(new ThreadNode(gThread, { startTime: startTime, endTime: endTime, contentOnly: true }), "(root)");
let thread = new ThreadNode(gThread, { startTime, endTime, contentOnly: true });
let root = getFrameNodePath(thread, "(root)");
// Test the root node.
equal(root.duration, endTime - startTime,
"The correct duration was calculated for the root node.");
// Test the ThreadNode, only node which should have duration
equal(thread.duration, endTime - startTime,
"The correct duration was calculated for the root ThreadNode.");
equal(root.calls.length, 2,
"The correct number of child calls were calculated for the root node.");

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

@ -49,7 +49,7 @@ function run_test() {
add_task(function test() {
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
let root = new ThreadNode(gThread, { invertTree: true });
let root = new ThreadNode(gThread, { invertTree: true, startTime: 0, endTime: 4 });
equal(root.calls.length, 2,
"Should get the 2 youngest frames, not the 1 oldest frame");

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

@ -159,7 +159,7 @@ function run_test() {
add_task(function test() {
let { ThreadNode } = devtools.require("devtools/performance/tree-model");
let root = new ThreadNode(gThread);
let root = new ThreadNode(gThread, { startTime: 0, endTime: 4 });
let A = getFrameNodePath(root, "(root) > A");

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

@ -17,7 +17,7 @@ add_task(function test() {
// Create a root node from a given samples array.
let root = getFrameNodePath(new ThreadNode(gThread, { contentOnly: true }), "(root)");
let root = getFrameNodePath(new ThreadNode(gThread, { startTime: 5, endTime: 30, contentOnly: true }), "(root)");
/*
* should have a tree like:

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

@ -17,7 +17,7 @@ add_task(function test() {
// Create a root node from a given samples array.
let root = getFrameNodePath(new ThreadNode(gThread, { contentOnly: true }), "(root)");
let root = getFrameNodePath(new ThreadNode(gThread, { startTime: 5, endTime: 25, contentOnly: true }), "(root)");
/*
* should have a tree like:

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

@ -20,7 +20,6 @@ let DetailsSubview = {
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, this._onOverviewRangeChange);
OverviewView.on(EVENTS.OVERVIEW_RANGE_CLEARED, this._onOverviewRangeChange);
DetailsView.on(EVENTS.DETAILS_VIEW_SELECTED, this._onDetailsViewSelected);
},
@ -34,7 +33,6 @@ let DetailsSubview = {
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, this._onOverviewRangeChange);
OverviewView.off(EVENTS.OVERVIEW_RANGE_CLEARED, this._onOverviewRangeChange);
DetailsView.off(EVENTS.DETAILS_VIEW_SELECTED, this._onDetailsViewSelected);
},
@ -92,7 +90,7 @@ let DetailsSubview = {
return;
}
if (DetailsView.isViewSelected(this) || this.canUpdateWhileHidden) {
this.render();
this.render(OverviewView.getTimeInterval());
} else {
this.shouldUpdateWhenShown = true;
}

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

@ -165,8 +165,10 @@ let OverviewView = {
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).
if (!selection) {
// 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 };
@ -300,14 +302,8 @@ let OverviewView = {
if (this._stopSelectionChangeEventPropagation) {
return;
}
// If the range is smaller than a pixel (which can happen when performing
// a click on the graphs), treat this as a cleared selection.
let interval = this.getTimeInterval();
if (interval.endTime - interval.startTime < 1) {
this.emit(EVENTS.OVERVIEW_RANGE_CLEARED);
} else {
this.emit(EVENTS.OVERVIEW_RANGE_SELECTED, interval);
}
this.emit(EVENTS.OVERVIEW_RANGE_SELECTED, this.getTimeInterval());
},
_onGraphRendered: function (_, graphName) {