Bug 1154115 - Fix devtools tests to use the new profiler JSON format. (r=jsantell)

This commit is contained in:
Shu-yu Guo 2015-05-11 14:16:44 -07:00
parent 2ad192eabb
commit 92f95889a0
42 changed files with 1290 additions and 679 deletions

View File

@ -376,6 +376,7 @@ function deflateThread(thread, uniqueStacks) {
stringTable: uniqueStacks.getStringTable()
};
}
exports.RecordingUtils.deflateThread = deflateThread;
function stackTableWithSchema(data) {
let slot = 0;
@ -444,6 +445,8 @@ UniqueStrings.prototype.getOrAddStringIndex = function(s) {
return index;
};
exports.RecordingUtils.UniqueStrings = UniqueStrings;
/**
* A helper class to deduplicate old-version profiles.
*
@ -564,3 +567,5 @@ UniqueStacks.prototype.getOrAddStackIndex = function(prefixIndex, frameIndex) {
UniqueStacks.prototype.getOrAddStringIndex = function(s) {
return this._uniqueStrings.getOrAddStringIndex(s);
};
exports.RecordingUtils.UniqueStacks = UniqueStacks;

View File

@ -129,6 +129,7 @@ support-files =
[browser_profiler_tree-view-06.js]
[browser_profiler_tree-view-07.js]
[browser_profiler_tree-view-08.js]
[browser_profiler-frame-utils-01.js]
[browser_timeline-blueprint.js]
[browser_timeline-filters.js]
[browser_timeline-waterfall-background.js]

View File

@ -10,7 +10,7 @@
function test() {
let { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
let output = RecordingUtils.getSamplesFromAllocations(TEST_DATA);
let output = RecordingUtils.getProfileThreadFromAllocations(TEST_DATA);
is(output.toSource(), EXPECTED_OUTPUT.toSource(), "The output is correct.");
finish();
@ -43,37 +43,60 @@ let TEST_DATA = {
counts: [11, 22, 33, 44]
};
let EXPECTED_OUTPUT = [{
time: 50,
frames: []
}, {
time: 100,
frames: []
}, {
time: 150,
frames: [{
location: "x (A:1:2)",
allocations: 22
}]
}, {
time: 200,
frames: [{
location: "x (A:1:2)",
allocations: 22
}, {
location: "y (B:3:4)",
allocations: 33
}]
}, {
time: 250,
frames: [{
location: "x (A:1:2)",
allocations: 22
}, {
location: "y (B:3:4)",
allocations: 33
}, {
location: "C:5:6",
allocations: 44
}]
}];
let EXPECTED_OUTPUT = {
name: "allocations",
samples: {
"schema": {
"stack": 0,
"time": 1,
"responsiveness": 2,
"rss": 3,
"uss": 4,
"frameNumber": 5,
"power": 6
},
data: [
[ 1, 150 ],
[ 2, 200 ],
[ 3, 250 ]
]
},
stackTable: {
"schema": {
"prefix": 0,
"frame": 1
},
"data": [
null,
[ null, 1 ], // x (A:1:2)
[ 1, 2 ], // x (A:1:2) > y (B:3:4)
[ 2, 3 ] // x (A:1:2) > y (B:3:4) > C:5:6
]
},
frameTable: {
"schema": {
"location": 0,
"implementation": 1,
"optimizations": 2,
"line": 3,
"category": 4
},
data: [
null,
[ 0 ],
[ 1 ],
[ 2 ]
]
},
"stringTable": [
"x (A:1:2)",
"y (B:3:4)",
"C:5:6"
],
"allocationsTable": [
11,
22,
33,
44
]
};

View File

@ -46,11 +46,12 @@ let test = Task.async(function*() {
for (let thread of profile.threads) {
info("Checking thread: " + thread.name);
for (let sample of thread.samples) {
for (let sample of thread.samples.data) {
sampleCount++;
if (sample.frames[0].location != "(root)") {
ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
let stack = getInflatedStackLocations(thread, sample);
if (stack[0] != "(root)") {
ok(false, "The sample " + stack.toSource() + " doesn't have a root node.");
}
}
}

View File

@ -45,11 +45,12 @@ let test = Task.async(function*() {
for (let thread of profile.threads) {
info("Checking thread: " + thread.name);
for (let sample of thread.samples) {
for (let sample of thread.samples.data) {
sampleCount++;
if (sample.frames[0].location != "(root)") {
ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
let stack = getInflatedStackLocations(thread, sample);
if (stack[0] != "(root)") {
ok(false, "The sample " + stack.toSource() + " doesn't have a root node.");
}
}
}

View File

@ -52,19 +52,20 @@ function spawnTest () {
yield front.stopRecording(secondRecording);
let secondRecordingProfile = secondRecording.getProfile();
let secondRecordingSamples = secondRecordingProfile.threads[0].samples;
let secondRecordingSamples = secondRecordingProfile.threads[0].samples.data;
isnot(secondRecording._profilerStartTime, 0,
"The profiling start time should not be 0 on the second recording.");
ok(secondRecording.getDuration() >= WAIT_TIME,
"The second recording duration is correct.");
info("Second profile's first sample time: " + secondRecordingSamples[0].time);
ok(secondRecordingSamples[0].time < secondRecordingStartTime,
const TIME_SLOT = secondRecordingProfile.threads[0].samples.schema.time;
info("Second profile's first sample time: " + secondRecordingSamples[0][TIME_SLOT]);
ok(secondRecordingSamples[0][TIME_SLOT] < secondRecordingStartTime,
"The second recorded sample times were normalized.");
ok(secondRecordingSamples[0].time > 0,
ok(secondRecordingSamples[0][TIME_SLOT] > 0,
"The second recorded sample times were normalized correctly.");
ok(!secondRecordingSamples.find(e => e.time + secondRecordingStartTime <= firstRecording.getDuration()),
ok(!secondRecordingSamples.find(e => e[TIME_SLOT] + secondRecordingStartTime <= firstRecording.getDuration()),
"There should be no samples from the first recording in the second one, " +
"even though the total number of frames did not overflow.");

View File

@ -37,18 +37,19 @@ function spawnTest () {
yield front.stopRecording(secondRecording);
let secondRecordingProfile = secondRecording.getProfile();
let secondRecordingSamples = secondRecordingProfile.threads[0].samples;
let secondRecordingSamples = secondRecordingProfile.threads[0].samples.data;
isnot(secondRecording._profilerStartTime, 0,
"The profiling start time should not be 0 on the second recording.");
ok(secondRecording.getDuration() >= WAIT_TIME,
"The second recording duration is correct.");
ok(secondRecordingSamples[0].time < secondRecordingStartTime,
const TIME_SLOT = secondRecordingProfile.threads[0].samples.schema.time;
ok(secondRecordingSamples[0][TIME_SLOT] < secondRecordingStartTime,
"The second recorded sample times were normalized.");
ok(secondRecordingSamples[0].time > 0,
ok(secondRecordingSamples[0][TIME_SLOT] > 0,
"The second recorded sample times were normalized correctly.");
ok(!secondRecordingSamples.find(e => e.time + secondRecordingStartTime <= firstRecording.getDuration()),
ok(!secondRecordingSamples.find(e => e[TIME_SLOT] + secondRecordingStartTime <= firstRecording.getDuration()),
"There should be no samples from the first recording in the second one, " +
"even though the total number of frames did not overflow.");

View File

@ -23,11 +23,12 @@ function spawnTest () {
for (let thread of profile.threads) {
info("Checking thread: " + thread.name);
for (let sample of thread.samples) {
for (let sample of thread.samples.data) {
sampleCount++;
if (sample.frames[0].location != "(root)") {
ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
let stack = getInflatedStackLocations(thread, sample);
if (stack[0] != "(root)") {
ok(false, "The sample " + stack.toSource() + " doesn't have a root node.");
}
}
}

View File

@ -4,7 +4,8 @@
/**
* Tests that the call tree up/down events work for js calltree and memory calltree.
*/
let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
const { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils")
function spawnTest () {
let focus = 0;
let focusEvent = () => focus++;
@ -24,7 +25,7 @@ function spawnTest () {
yield rendered;
// Mock the profile used so we can get a deterministic tree created
let threadNode = new ThreadNode(gSamples);
let threadNode = new ThreadNode(gProfile.threads[0]);
JsCallTreeView._populateCallTree(threadNode);
JsCallTreeView.emit(EVENTS.JS_CALL_TREE_RENDERED);
@ -44,36 +45,44 @@ function spawnTest () {
finish();
};
let gSamples = [{
time: 5,
frames: [
{ category: 8, location: "(root)" },
{ category: 8, location: "A (http://foo/bar/baz:12)" },
{ category: 16, location: "B (http://foo/bar/baz:34)" },
{ category: 32, location: "C (http://foo/bar/baz:56)" }
]
}, {
time: 5 + 1,
frames: [
{ category: 8, location: "(root)" },
{ category: 8, location: "A (http://foo/bar/baz:12)" },
{ category: 16, location: "B (http://foo/bar/baz:34)" },
{ category: 64, location: "D (http://foo/bar/baz:78)" }
]
}, {
time: 5 + 1 + 2,
frames: [
{ category: 8, location: "(root)" },
{ category: 8, location: "A (http://foo/bar/baz:12)" },
{ category: 16, location: "B (http://foo/bar/baz:34)" },
{ category: 64, location: "D (http://foo/bar/baz:78)" }
]
}, {
time: 5 + 1 + 2 + 7,
frames: [
{ category: 8, location: "(root)" },
{ category: 8, location: "A (http://foo/bar/baz:12)" },
{ category: 128, location: "E (http://foo/bar/baz:90)" },
{ category: 256, location: "F (http://foo/bar/baz:99)" }
]
}];
let gProfile = {
meta: { version: 2 },
threads: [{
samples: [{
time: 5,
frames: [
{ category: 8, location: "(root)" },
{ category: 8, location: "A (http://foo/bar/baz:12)" },
{ category: 16, location: "B (http://foo/bar/baz:34)" },
{ category: 32, location: "C (http://foo/bar/baz:56)" }
]
}, {
time: 5 + 1,
frames: [
{ category: 8, location: "(root)" },
{ category: 8, location: "A (http://foo/bar/baz:12)" },
{ category: 16, location: "B (http://foo/bar/baz:34)" },
{ category: 64, location: "D (http://foo/bar/baz:78)" }
]
}, {
time: 5 + 1 + 2,
frames: [
{ category: 8, location: "(root)" },
{ category: 8, location: "A (http://foo/bar/baz:12)" },
{ category: 16, location: "B (http://foo/bar/baz:34)" },
{ category: 64, location: "D (http://foo/bar/baz:78)" }
]
}, {
time: 5 + 1 + 2 + 7,
frames: [
{ category: 8, location: "(root)" },
{ category: 8, location: "A (http://foo/bar/baz:12)" },
{ category: 128, location: "E (http://foo/bar/baz:90)" },
{ category: 256, location: "F (http://foo/bar/baz:99)" }
]
}],
markers: []
}]
};
RecordingUtils.deflateProfile(gProfile);

View File

@ -7,64 +7,113 @@
* FrameNode, and the returning of that data is as expected.
*/
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
function test() {
let { JITOptimizations } = devtools.require("devtools/shared/profiler/jit");
let jit = new JITOptimizations(gOpts);
let rawSites = [];
rawSites.push(gRawSite2);
rawSites.push(gRawSite2);
rawSites.push(gRawSite1);
rawSites.push(gRawSite1);
rawSites.push(gRawSite2);
rawSites.push(gRawSite3);
jit.addOptimizationSite(1);
jit.addOptimizationSite(1);
jit.addOptimizationSite(0);
jit.addOptimizationSite(0);
jit.addOptimizationSite(1);
jit.addOptimizationSite(2);
let sites = jit.getOptimizationSites();
let jit = new JITOptimizations(rawSites, gStringTable.stringTable);
let sites = jit.optimizationSites;
let [first, second, third] = sites;
is(first.id, 1, "Ordered by samples count, descending");
is(first.id, 0, "site id is array index");
is(first.samples, 3, "first OptimizationSiteProfile has correct sample count");
is(first.data, gOpts[1], "includes OptimizationSite as reference under `data`");
is(second.id, 0, "Ordered by samples count, descending");
is(first.data.line, 34, "includes OptimizationSite as reference under `data`");
is(second.id, 1, "site id is array index");
is(second.samples, 2, "second OptimizationSiteProfile has correct sample count");
is(second.data, gOpts[0], "includes OptimizationSite as reference under `data`");
is(third.id, 2, "Ordered by samples count, descending");
is(second.data.line, 12, "includes OptimizationSite as reference under `data`");
is(third.id, 2, "site id is array index");
is(third.samples, 1, "third OptimizationSiteProfile has correct sample count");
is(third.data, gOpts[2], "includes OptimizationSite as reference under `data`");
is(third.data.line, 78, "includes OptimizationSite as reference under `data`");
finish();
}
let gOpts = [{
let gStringTable = new RecordingUtils.UniqueStrings();
function uniqStr(s) {
return gStringTable.getOrAddStringIndex(s);
}
let gRawSite1 = {
line: 12,
column: 2,
types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
{ keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
{ keyedBy: "primitive", location: "self-hosted" }
]}],
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "Inlined", strategy: "SomeGetter3" },
]
}, {
types: [{
mirType: uniqStr("Object"),
site: uniqStr("A (http://foo/bar/bar:12)"),
typeset: [{
keyedBy: uniqStr("constructor"),
name: uniqStr("Foo"),
location: uniqStr("A (http://foo/bar/baz:12)")
}, {
keyedBy: uniqStr("primitive"),
location: uniqStr("self-hosted")
}]
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("Inlined"), uniqStr("SomeGetter3")]
]
}
};
let gRawSite2 = {
line: 34,
types: [{ mirType: "Int32", site: "Receiver" }], // use no types
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "Failure3", strategy: "SomeGetter3" },
]
}, {
types: [{
mirType: uniqStr("Int32"),
site: uniqStr("Receiver")
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("Failure3"), uniqStr("SomeGetter3")]
]
}
};
let gRawSite3 = {
line: 78,
types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
{ keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
{ keyedBy: "primitive", location: "self-hosted" }
]}],
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "GenericSuccess", strategy: "SomeGetter3" },
]
}];
types: [{
mirType: uniqStr("Object"),
site: uniqStr("A (http://foo/bar/bar:12)"),
typeset: [{
keyedBy: uniqStr("constructor"),
name: uniqStr("Foo"),
location: uniqStr("A (http://foo/bar/baz:12)")
}, {
keyedBy: uniqStr("primitive"),
location: uniqStr("self-hosted")
}]
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("GenericSuccess"), uniqStr("SomeGetter3")]
]
}
};

View File

@ -6,19 +6,21 @@
* OptimizationSites methods work as expected.
*/
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
function test() {
let { JITOptimizations, OptimizationSite } = devtools.require("devtools/shared/profiler/jit");
let jit = new JITOptimizations(gOpts);
let rawSites = [];
rawSites.push(gRawSite2);
rawSites.push(gRawSite2);
rawSites.push(gRawSite1);
rawSites.push(gRawSite1);
rawSites.push(gRawSite2);
rawSites.push(gRawSite3);
jit.addOptimizationSite(1);
jit.addOptimizationSite(1);
jit.addOptimizationSite(0);
jit.addOptimizationSite(0);
jit.addOptimizationSite(1);
jit.addOptimizationSite(2);
let sites = jit.getOptimizationSites();
let jit = new JITOptimizations(rawSites, gStringTable.stringTable);
let sites = jit.optimizationSites;
let [first, second, third] = sites;
@ -40,38 +42,91 @@ function test() {
finish();
}
let gOpts = [{
let gStringTable = new RecordingUtils.UniqueStrings();
function uniqStr(s) {
return gStringTable.getOrAddStringIndex(s);
}
let gRawSite1 = {
line: 12,
column: 2,
types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
{ keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
{ keyedBy: "constructor", location: "A (http://foo/bar/baz:12)" }
]}, { mirType: "Int32", site: "A (http://foo/bar/bar:12)", types: [
{ keyedBy: "primitive", location: "self-hosted" }
]}],
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "Inlined", strategy: "SomeGetter3" },
]
}, {
types: [{
mirType: uniqStr("Object"),
site: uniqStr("A (http://foo/bar/bar:12)"),
typeset: [{
keyedBy: uniqStr("constructor"),
name: uniqStr("Foo"),
location: uniqStr("A (http://foo/bar/baz:12)")
}, {
keyedBy: uniqStr("constructor"),
location: uniqStr("A (http://foo/bar/baz:12)")
}]
}, {
mirType: uniqStr("Int32"),
site: uniqStr("A (http://foo/bar/bar:12)"),
typeset: [{
keyedBy: uniqStr("primitive"),
location: uniqStr("self-hosted")
}]
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("Inlined"), uniqStr("SomeGetter3")]
]
}
};
let gRawSite2 = {
line: 34,
types: [{ mirType: "Int32", site: "Receiver" }], // use no types
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
]
}, {
types: [{
mirType: uniqStr("Int32"),
site: uniqStr("Receiver")
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")]
]
}
};
let gRawSite3 = {
line: 78,
types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
{ keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
{ keyedBy: "primitive", location: "self-hosted" }
]}],
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "GenericSuccess", strategy: "SomeGetter3" },
]
}];
types: [{
mirType: uniqStr("Object"),
site: uniqStr("A (http://foo/bar/bar:12)"),
typeset: [{
keyedBy: uniqStr("constructor"),
name: uniqStr("Foo"),
location: uniqStr("A (http://foo/bar/baz:12)")
}, {
keyedBy: uniqStr("primitive"),
location: uniqStr("self-hosted")
}]
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("GenericSuccess"), uniqStr("SomeGetter3")]
]
}
};

View File

@ -6,6 +6,8 @@
* if on, and displays selected frames on focus.
*/
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
Services.prefs.setBoolPref(INVERT_PREF, false);
function spawnTest () {
@ -13,7 +15,7 @@ function spawnTest () {
let { EVENTS, $, $$, window, PerformanceController } = panel.panelWin;
let { OverviewView, DetailsView, JITOptimizationsView, JsCallTreeView, RecordingsView } = panel.panelWin;
let profilerData = { threads: [{samples: gSamples, optimizations: gOpts}] };
let profilerData = { threads: [gThread] }
is(Services.prefs.getBoolPref(JIT_PREF), false, "show JIT Optimizations pref off by default");
@ -29,8 +31,12 @@ function spawnTest () {
yield injectAndRenderProfilerData();
yield checkFrame(1, [0, 1]);
yield checkFrame(2, [2]);
// gRawSite1 and gRawSite2 are both optimizations on A, so they'll have
// indices in descending order of # of samples.
yield checkFrame(1, [{ i: 0, opt: gRawSite1 }, { i: 1, opt: gRawSite2 }]);
// gRawSite3 is the only optimization on B, so it'll have index 0.
yield checkFrame(2, [{ i: 0, opt: gRawSite3 }]);
yield checkFrame(3);
let select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
@ -64,7 +70,7 @@ function spawnTest () {
Services.prefs.setBoolPref(JIT_PREF, false);
}
function *checkFrame (frameIndex, expectedOptsIndex=[]) {
function *checkFrame (frameIndex, expectedOpts=[]) {
// Click the frame
let rendered = once(JITOptimizationsView, EVENTS.OPTIMIZATIONS_RENDERED);
mousedown(window, $$(".call-tree-item")[frameIndex]);
@ -73,7 +79,7 @@ function spawnTest () {
ok(true, "JITOptimizationsView rendered when enabling with the current frame node selected");
let isEmpty = $("#jit-optimizations-view").classList.contains("empty");
if (expectedOptsIndex.length === 0) {
if (expectedOpts.length === 0) {
ok(isEmpty, "JIT Optimizations view has an empty message when selecting a frame without opt data.");
return;
} else {
@ -82,7 +88,7 @@ function spawnTest () {
// Get the frame info for the first opt site, since all opt sites
// share the same frame info
let frameInfo = gOpts[expectedOptsIndex[0]]._testFrameInfo;
let frameInfo = expectedOpts[0].opt._testFrameInfo;
let { $headerName, $headerLine, $headerFile } = JITOptimizationsView;
ok(!$headerName.hidden, "header function name should be shown");
@ -94,13 +100,12 @@ function spawnTest () {
// Need the value of the optimizations in its array, as its
// an index used internally by the view to uniquely ID the opt
for (let i of expectedOptsIndex) {
let opt = gOpts[i];
for (let { i, opt } of expectedOpts) {
let { types: ionTypes, attempts } = opt;
// Check attempts
is($$(`.tree-widget-container li[data-id='["${i}","${i}-attempts"]'] .tree-widget-children .tree-widget-item`).length, attempts.length,
`found ${attempts.length} attempts`);
is($$(`.tree-widget-container li[data-id='["${i}","${i}-attempts"]'] .tree-widget-children .tree-widget-item`).length, attempts.data.length,
`found ${attempts.data.length} attempts`);
for (let j = 0; j < ionTypes.length; j++) {
ok($(`.tree-widget-container li[data-id='["${i}","${i}-types","${i}-types-${j}"]']`),
@ -109,7 +114,7 @@ function spawnTest () {
// The second and third optimization should display optimization failures.
let warningIcon = $(`.tree-widget-container li[data-id='["${i}"]'] .opt-icon[severity=warning]`);
if (i === 1 || i === 2) {
if (opt === gRawSite2 || opt === gRawSite3) {
ok(warningIcon, "did find a warning icon for all strategies failing.");
} else {
ok(!warningIcon, "did not find a warning icon for no successful strategies");
@ -118,68 +123,142 @@ function spawnTest () {
}
}
let gSamples = [{
time: 5,
frames: [
{ location: "(root)" },
{ location: "A (http://foo/bar/baz:12)", optsIndex: 0 },
{ location: "B (http://foo/bar/boo:34)", optsIndex: 2 },
{ location: "C (http://foo/bar/baz:56)" }
]
}, {
time: 5 + 1,
frames: [
{ location: "(root)" },
{ location: "A (http://foo/bar/baz:12)" },
{ location: "B (http://foo/bar/boo:34)" },
]
}, {
time: 5 + 1 + 2,
frames: [
{ location: "(root)" },
{ location: "A (http://foo/bar/baz:12)", optsIndex: 1 },
{ location: "B (http://foo/bar/boo:34)" },
]
}, {
time: 5 + 1 + 2 + 7,
frames: [
{ location: "(root)" },
{ location: "A (http://foo/bar/baz:12)", optsIndex: 0 },
{ location: "E (http://foo/bar/baz:90)" },
{ location: "F (http://foo/bar/baz:99)" }
]
}];
let gUniqueStacks = new RecordingUtils.UniqueStacks();
// Array of OptimizationSites
let gOpts = [{
function uniqStr(s) {
return gUniqueStacks.getOrAddStringIndex(s);
}
// Since deflateThread doesn't handle deflating optimization info, use
// placeholder names A_O1, B_O3, and A_O2, which will be used to manually
// splice deduped opts into the profile.
let gThread = RecordingUtils.deflateThread({
samples: [{
time: 0,
frames: [
{ location: "(root)" }
]
}, {
time: 5,
frames: [
{ location: "(root)" },
{ location: "A_O1" },
{ location: "B_O3" },
{ location: "C (http://foo/bar/baz:56)" }
]
}, {
time: 5 + 1,
frames: [
{ location: "(root)" },
{ location: "A (http://foo/bar/baz:12)" },
{ location: "B (http://foo/bar/boo:34)" },
]
}, {
time: 5 + 1 + 2,
frames: [
{ location: "(root)" },
{ location: "A_O2" },
{ location: "B (http://foo/bar/boo:34)" },
]
}, {
time: 5 + 1 + 2 + 7,
frames: [
{ location: "(root)" },
{ location: "A_O1" },
{ location: "E (http://foo/bar/baz:90)" },
{ location: "F (http://foo/bar/baz:99)" }
]
}],
markers: []
}, gUniqueStacks);
// 3 RawOptimizationSites
let gRawSite1 = {
_testFrameInfo: { name: "A", line: "12", file: "@baz" },
line: 12,
column: 2,
types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
{ keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
{ keyedBy: "primitive", location: "self-hosted" }
]}],
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "Inlined", strategy: "SomeGetter3" },
]
}, {
types: [{
mirType: uniqStr("Object"),
site: uniqStr("A (http://foo/bar/bar:12)"),
typeset: [{
keyedBy: uniqStr("constructor"),
name: uniqStr("Foo"),
location: uniqStr("A (http://foo/bar/baz:12)")
}, {
keyedBy: uniqStr("primitive"),
location: uniqStr("self-hosted")
}]
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("Inlined"), uniqStr("SomeGetter3")]
]
}
};
let gRawSite2 = {
_testFrameInfo: { name: "A", line: "12", file: "@baz" },
line: 12,
types: [{ mirType: "Int32", site: "Receiver" }], // use no types
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "Failure3", strategy: "SomeGetter3" },
]
}, {
types: [{
mirType: uniqStr("Int32"),
site: uniqStr("Receiver")
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("Failure3"), uniqStr("SomeGetter3")]
]
}
};
let gRawSite3 = {
_testFrameInfo: { name: "B", line: "34", file: "@boo" },
line: 34,
types: [{ mirType: "Int32", site: "Receiver" }], // use no types
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "Failure3", strategy: "SomeGetter3" },
]
}];
types: [{
mirType: uniqStr("Int32"),
site: uniqStr("Receiver")
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("Failure3"), uniqStr("SomeGetter3")]
]
}
};
gThread.frameTable.data.forEach((frame) => {
const LOCATION_SLOT = gThread.frameTable.schema.location;
const OPTIMIZATIONS_SLOT = gThread.frameTable.schema.optimizations;
let l = gThread.stringTable[frame[LOCATION_SLOT]];
switch (l) {
case "A_O1":
frame[LOCATION_SLOT] = uniqStr("A (http://foo/bar/baz:12)");
frame[OPTIMIZATIONS_SLOT] = gRawSite1;
break;
case "A_O2":
frame[LOCATION_SLOT] = uniqStr("A (http://foo/bar/baz:12)");
frame[OPTIMIZATIONS_SLOT] = gRawSite2;
break;
case "B_O3":
frame[LOCATION_SLOT] = uniqStr("B (http://foo/bar/boo:34)");
frame[OPTIMIZATIONS_SLOT] = gRawSite3;
break;
}
});

View File

@ -6,7 +6,9 @@
* for meta nodes when viewing "content only".
*/
let { CATEGORY_MASK } = devtools.require("devtools/shared/profiler/global");
const { CATEGORY_MASK } = devtools.require("devtools/shared/profiler/global");
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
Services.prefs.setBoolPref(INVERT_PREF, false);
Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
@ -15,7 +17,7 @@ function spawnTest () {
let { EVENTS, $, $$, window, PerformanceController } = panel.panelWin;
let { OverviewView, DetailsView, JITOptimizationsView, JsCallTreeView, RecordingsView } = panel.panelWin;
let profilerData = { threads: [{samples: gSamples, optimizations: gOpts}] };
let profilerData = { threads: [gThread] };
is(Services.prefs.getBoolPref(JIT_PREF), false, "show JIT Optimizations pref off by default");
@ -70,40 +72,94 @@ function spawnTest () {
}
}
let gSamples = [{
time: 5,
frames: [
{ location: "(root)" },
{ location: "A (http://foo/bar/baz:12)", optsIndex: 0 }
]
}, {
time: 5 + 1,
frames: [
{ location: "(root)" },
{ location: "A (http://foo/bar/baz:12)", optsIndex: 0 },
{ location: "JS", optsIndex: 1, category: CATEGORY_MASK("js") },
]
}];
let gUniqueStacks = new RecordingUtils.UniqueStacks();
// Array of OptimizationSites
let gOpts = [{
function uniqStr(s) {
return gUniqueStacks.getOrAddStringIndex(s);
}
let gThread = RecordingUtils.deflateThread({
samples: [{
time: 0,
frames: [
{ location: "(root)" }
]
}, {
time: 5,
frames: [
{ location: "(root)" },
{ location: "A (http://foo/bar/baz:12)" }
]
}, {
time: 5 + 1,
frames: [
{ location: "(root)" },
{ location: "A (http://foo/bar/baz:12)" },
{ location: "JS", category: CATEGORY_MASK("js") },
]
}],
markers: []
}, gUniqueStacks);
// 3 RawOptimizationSites
let gRawSite1 = {
line: 12,
column: 2,
types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
{ keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
{ keyedBy: "primitive", location: "self-hosted" }
]}],
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "Inlined", strategy: "SomeGetter3" },
]
}, {
types: [{
mirType: uniqStr("Object"),
site: uniqStr("A (http://foo/bar/bar:12)"),
typeset: [{
keyedBy: uniqStr("constructor"),
name: uniqStr("Foo"),
location: uniqStr("A (http://foo/bar/baz:12)")
}, {
keyedBy: uniqStr("primitive"),
location: uniqStr("self-hosted")
}]
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("Inlined"), uniqStr("SomeGetter3")]
]
}
};
let gRawSite2 = {
line: 22,
types: [{ mirType: "Int32", site: "Receiver" }], // use no types
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "Failure3", strategy: "SomeGetter3" },
]
}];
types: [{
mirType: uniqStr("Int32"),
site: uniqStr("Receiver")
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("Failure3"), uniqStr("SomeGetter3")]
]
}
};
gThread.frameTable.data.forEach((frame) => {
const LOCATION_SLOT = gThread.frameTable.schema.location;
const OPTIMIZATIONS_SLOT = gThread.frameTable.schema.optimizations;
let l = gThread.stringTable[frame[LOCATION_SLOT]];
switch (l) {
case "A (http://foo/bar/baz:12)":
frame[OPTIMIZATIONS_SLOT] = gRawSite1;
break;
case "JS":
frame[OPTIMIZATIONS_SLOT] = gRawSite2;
break;
}
});

View File

@ -18,10 +18,10 @@ function spawnTest () {
yield DetailsView.selectView("js-flamegraph");
yield rendered;
let samples1 = PerformanceController.getCurrentRecording().getProfile().threads[0].samples;
let rendering1 = FlameGraphUtils._cache.get(samples1);
let thread1 = PerformanceController.getCurrentRecording().getProfile().threads[0];
let rendering1 = FlameGraphUtils._cache.get(thread1);
ok(samples1,
ok(thread1,
"The samples were retrieved from the controller.");
ok(rendering1,
"The rendering data was cached.");
@ -32,10 +32,10 @@ function spawnTest () {
ok(true, "JsFlameGraphView rerendered when toggling flatten-tree-recursion.");
let samples2 = PerformanceController.getCurrentRecording().getProfile().threads[0].samples;
let rendering2 = FlameGraphUtils._cache.get(samples2);
let thread2 = PerformanceController.getCurrentRecording().getProfile().threads[0];
let rendering2 = FlameGraphUtils._cache.get(thread2);
is(samples1, samples2,
is(thread1, thread2,
"The same samples data should be retrieved from the controller (1).");
isnot(rendering1, rendering2,
"The rendering data should be different because other options were used (1).");
@ -46,10 +46,10 @@ function spawnTest () {
ok(true, "JsFlameGraphView rerendered when toggling back flatten-tree-recursion.");
let samples3 = PerformanceController.getCurrentRecording().getProfile().threads[0].samples;
let rendering3 = FlameGraphUtils._cache.get(samples3);
let thread3 = PerformanceController.getCurrentRecording().getProfile().threads[0];
let rendering3 = FlameGraphUtils._cache.get(thread3);
is(samples2, samples3,
is(thread2, thread3,
"The same samples data should be retrieved from the controller (2).");
isnot(rendering2, rendering3,
"The rendering data should be different because other options were used (2).");

View File

@ -21,13 +21,13 @@ function spawnTest () {
yield rendered;
let allocations1 = PerformanceController.getCurrentRecording().getAllocations();
let samples1 = RecordingUtils.getSamplesFromAllocations(allocations1);
let rendering1 = FlameGraphUtils._cache.get(samples1);
let thread1 = RecordingUtils.getProfileThreadFromAllocations(allocations1);
let rendering1 = FlameGraphUtils._cache.get(thread1);
ok(allocations1,
"The allocations were retrieved from the controller.");
ok(samples1,
"The samples were retrieved from the utility funcs.");
ok(thread1,
"The allocations profile was synthesized by the utility funcs.");
ok(rendering1,
"The rendering data was cached.");
@ -38,13 +38,13 @@ function spawnTest () {
ok(true, "MemoryFlameGraphView rerendered when toggling flatten-tree-recursion.");
let allocations2 = PerformanceController.getCurrentRecording().getAllocations();
let samples2 = RecordingUtils.getSamplesFromAllocations(allocations2);
let rendering2 = FlameGraphUtils._cache.get(samples2);
let thread2 = RecordingUtils.getProfileThreadFromAllocations(allocations2);
let rendering2 = FlameGraphUtils._cache.get(thread2);
is(allocations1, allocations2,
"The same allocations data should be retrieved from the controller (1).");
is(samples1, samples2,
"The same samples data should be retrieved from the utility funcs. (1).");
is(thread1, thread2,
"The same allocations profile should be retrieved from the utility funcs. (1).");
isnot(rendering1, rendering2,
"The rendering data should be different because other options were used (1).");
@ -55,13 +55,13 @@ function spawnTest () {
ok(true, "MemoryFlameGraphView rerendered when toggling back flatten-tree-recursion.");
let allocations3 = PerformanceController.getCurrentRecording().getAllocations();
let samples3 = RecordingUtils.getSamplesFromAllocations(allocations3);
let rendering3 = FlameGraphUtils._cache.get(samples3);
let thread3 = RecordingUtils.getProfileThreadFromAllocations(allocations3);
let rendering3 = FlameGraphUtils._cache.get(thread3);
is(allocations2, allocations3,
"The same allocations data should be retrieved from the controller (2).");
is(samples2, samples3,
"The same samples data should be retrieved from the utility funcs. (2).");
is(thread2, thread3,
"The same allocations profile should be retrieved from the utility funcs. (2).");
isnot(rendering2, rendering3,
"The rendering data should be different because other options were used (2).");

View File

@ -0,0 +1,95 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that frame-utils isContent and parseLocation work as intended
* when parsing over frames from the profiler.
*/
const CONTENT_LOCATIONS = [
"hello/<.world (https://foo/bar.js:123:987)",
"hello/<.world (http://foo/bar.js:123:987)",
"hello/<.world (http://foo/bar.js:123)",
"hello/<.world (http://foo/bar.js#baz:123:987)",
"hello/<.world (http://foo/#bar:123:987)",
"hello/<.world (http://foo/:123:987)",
"hello/<.world (app://myfxosapp/file.js:100:1)",
].map(argify);
const CHROME_LOCATIONS = [
{ location: "Startup::XRE_InitChildProcess", line: 456, column: 123 },
{ location: "chrome://browser/content/content.js", line: 456, column: 123 },
"setTimeout_timer (resource://gre/foo.js:123:434)",
"hello/<.world (jar:file://Users/mcurie/Dev/jetpacks.js)",
"hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)",
"EnterJIT",
].map(argify);
function test() {
const { isContent, parseLocation } = devtools.require("devtools/shared/profiler/frame-utils");
for (let frame of CONTENT_LOCATIONS) {
ok(isContent.apply(null, frameify(frame)), `${frame[0]} should be considered a content frame.`);
}
for (let frame of CHROME_LOCATIONS) {
ok(!isContent.apply(null, frameify(frame)), `${frame[0]} should not be considered a content frame.`);
}
// functionName, fileName, hostName, url, line, column
const FIELDS = ["functionName", "fileName", "hostName", "url", "line", "column"];
const PARSED_CONTENT = [
["hello/<.world", "bar.js", "foo", "https://foo/bar.js", 123, 987],
["hello/<.world", "bar.js", "foo", "http://foo/bar.js", 123, 987],
["hello/<.world", "bar.js", "foo", "http://foo/bar.js", 123, null],
["hello/<.world", "bar.js#baz", "foo", "http://foo/bar.js#baz", 123, 987],
["hello/<.world", "#bar", "foo", "http://foo/#bar", 123, 987],
["hello/<.world", "/", "foo", "http://foo/", 123, 987],
["hello/<.world", "file.js", "myfxosapp", "app://myfxosapp/file.js", 100, 1],
];
for (let i = 0; i < PARSED_CONTENT.length; i++) {
let parsed = parseLocation.apply(null, CONTENT_LOCATIONS[i]);
for (let j = 0; j < FIELDS.length; j++) {
is(parsed[FIELDS[j]], PARSED_CONTENT[i][j], `${CONTENT_LOCATIONS[i]} was parsed to correct ${FIELDS[j]}`);
}
}
const PARSED_CHROME = [
["Startup::XRE_InitChildProcess", null, null, null, 456, 123],
["chrome://browser/content/content.js", null, null, null, 456, 123],
["setTimeout_timer", "foo.js", null, "resource://gre/foo.js", 123, 434],
["hello/<.world (jar:file://Users/mcurie/Dev/jetpacks.js)", null, null, null, null, null],
["hello/<.world", "baz.js", "bar", "http://bar/baz.js", 123, 987],
["EnterJIT", null, null, null, null, null],
];
for (let i = 0; i < PARSED_CHROME.length; i++) {
let parsed = parseLocation.apply(null, CHROME_LOCATIONS[i]);
for (let j = 0; j < FIELDS.length; j++) {
is(parsed[FIELDS[j]], PARSED_CHROME[i][j], `${CHROME_LOCATIONS[i]} was parsed to correct ${FIELDS[j]}`);
}
}
finish();
}
/**
* Takes either a string or an object and turns it into an array that
* parseLocation.apply expects.
*/
function argify (val) {
if (typeof val === "string") {
return [val];
} else {
return [val.location, val.line, val.column];
}
}
/**
* Takes the result of argify and turns it into an array that can be passed to
* isContent.apply.
*/
function frameify(val) {
return [{ location: val[0] }];
}

View File

@ -9,10 +9,13 @@ function test() {
let { FrameNode } = devtools.require("devtools/shared/profiler/tree-model");
let { CATEGORY_OTHER } = devtools.require("devtools/shared/profiler/global");
let frame1 = new FrameNode({
let frame1 = new FrameNode("hello/<.world (http://foo/bar.js:123:987)", {
location: "hello/<.world (http://foo/bar.js:123:987)",
line: 456
});
line: 456,
isContent: FrameNode.isContent({
location: "hello/<.world (http://foo/bar.js:123:987)"
})
}, false);
is(frame1.getInfo().nodeType, "Frame",
"The first frame node has the correct type.");
@ -33,10 +36,13 @@ function test() {
is(frame1.getInfo().isContent, true,
"The first frame node has the correct content flag.");
let frame2 = new FrameNode({
let frame2 = new FrameNode("hello/<.world (http://foo/bar.js#baz:123:987)", {
location: "hello/<.world (http://foo/bar.js#baz:123:987)",
line: 456
});
line: 456,
isContent: FrameNode.isContent({
location: "hello/<.world (http://foo/bar.js#baz:123:987)"
})
}, false);
is(frame2.getInfo().nodeType, "Frame",
"The second frame node has the correct type.");
@ -57,10 +63,13 @@ function test() {
is(frame2.getInfo().isContent, true,
"The second frame node has the correct content flag.");
let frame3 = new FrameNode({
let frame3 = new FrameNode("hello/<.world (http://foo/#bar:123:987)", {
location: "hello/<.world (http://foo/#bar:123:987)",
line: 456
});
line: 456,
isContent: FrameNode.isContent({
location: "hello/<.world (http://foo/#bar:123:987)"
})
}, false);
is(frame3.getInfo().nodeType, "Frame",
"The third frame node has the correct type.");
@ -81,10 +90,13 @@ function test() {
is(frame3.getInfo().isContent, true,
"The third frame node has the correct content flag.");
let frame4 = new FrameNode({
let frame4 = new FrameNode("hello/<.world (http://foo/:123:987)", {
location: "hello/<.world (http://foo/:123:987)",
line: 456
});
line: 456,
isContent: FrameNode.isContent({
location: "hello/<.world (http://foo/:123:987)"
})
}, false);
is(frame4.getInfo().nodeType, "Frame",
"The fourth frame node has the correct type.");
@ -105,10 +117,13 @@ function test() {
is(frame4.getInfo().isContent, true,
"The fourth frame node has the correct content flag.");
let frame5 = new FrameNode({
let frame5 = new FrameNode("hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", {
location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)",
line: 456
});
line: 456,
isContent: FrameNode.isContent({
location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)"
})
}, false);
is(frame5.getInfo().nodeType, "Frame",
"The fifth frame node has the correct type.");
@ -129,12 +144,15 @@ function test() {
is(frame5.getInfo().isContent, false,
"The fifth frame node has the correct content flag.");
let frame6 = new FrameNode({
let frame6 = new FrameNode("Foo::Bar::Baz", {
location: "Foo::Bar::Baz",
line: 456,
column: 123,
category: CATEGORY_OTHER
});
category: CATEGORY_OTHER,
isContent: FrameNode.isContent({
location: "Foo::Bar::Baz",
category: CATEGORY_OTHER
})
}, false);
is(frame6.getInfo().nodeType, "Frame",
"The sixth frame node has the correct type.");
@ -148,16 +166,17 @@ function test() {
"The sixth frame node has the correct url.");
is(frame6.getInfo().line, 456,
"The sixth frame node has the correct line.");
is(frame6.getInfo().column, 123,
"The sixth frame node has the correct column.");
is(frame6.getInfo().categoryData.abbrev, "other",
"The sixth frame node has the correct category data.");
is(frame6.getInfo().isContent, false,
"The sixth frame node has the correct content flag.");
let frame7 = new FrameNode({
location: "EnterJIT"
});
let frame7 = new FrameNode("EnterJIT", {
location: "EnterJIT",
isContent: FrameNode.isContent({
location: "EnterJIT"
})
}, false);
is(frame7.getInfo().nodeType, "Frame",
"The seventh frame node has the correct type.");
@ -178,19 +197,19 @@ function test() {
is(frame7.getInfo().isContent, false,
"The seventh frame node has the correct content flag.");
let frame8 = new FrameNode({
let frame8 = new FrameNode("chrome://browser/content/content.js", {
location: "chrome://browser/content/content.js",
line: 456,
column: 123
});
}, false);
is(frame8.getInfo().hostName, null,
"The eighth frame node has the correct host name.");
let frame9 = new FrameNode({
let frame9 = new FrameNode("hello/<.world (resource://gre/foo.js:123:434)", {
location: "hello/<.world (resource://gre/foo.js:123:434)",
line: 456
});
}, false);
is(frame9.getInfo().hostName, null,
"The ninth frame node has the correct host name.");

View File

@ -6,170 +6,109 @@
*/
function test() {
let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
const { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
// Create a root node from a given samples array.
let root = new ThreadNode(gSamples);
let threadNode = new ThreadNode(gThread);
let root = getFrameNodePath(threadNode, "(root)");
// Test the root node.
is(root.duration, 18,
"The correct duration was calculated for the root node.");
is(root.getInfo().nodeType, "Thread",
is(threadNode.getInfo().nodeType, "Thread",
"The correct node type was retrieved for the root node.");
is(root.duration, 20,
"The correct duration was calculated for the root node.");
is(root.getInfo().functionName, "(root)",
"The correct function name was retrieved for the root node.");
is(root.getInfo().categoryData.toSource(), "({})",
"The correct empty category data was retrieved for the root node.");
is(Object.keys(root.calls).length, 1,
is(root.calls.length, 1,
"The correct number of child calls were calculated for the root node.");
is(Object.keys(root.calls)[0], "A",
ok(getFrameNodePath(root, "A"),
"The root node's only child call is correct.");
// Test all the descendant nodes.
is(Object.keys(root.calls.A.calls).length, 2,
"The correct number of child calls were calculated for the '.A' node.");
is(Object.keys(root.calls.A.calls)[0], "B",
"The '.A' node's first child call is correct.");
is(Object.keys(root.calls.A.calls)[1], "E",
"The '.A' node's second child call is correct.");
is(getFrameNodePath(root, "A").calls.length, 2,
"The correct number of child calls were calculated for the 'A' node.");
ok(getFrameNodePath(root, "A > B"),
"The 'A' node has a 'B' child call.");
ok(getFrameNodePath(root, "A > E"),
"The 'A' node has a 'E' child call.");
is(Object.keys(root.calls.A.calls.B.calls).length, 2,
"The correct number of child calls were calculated for the '.A.B' node.");
is(Object.keys(root.calls.A.calls.B.calls)[0], "C",
"The '.A.B' node's first child call is correct.");
is(Object.keys(root.calls.A.calls.B.calls)[1], "D",
"The '.A.B' node's second child call is correct.");
is(getFrameNodePath(root, "A > B").calls.length, 2,
"The correct number of child calls were calculated for the 'A > B' node.");
ok(getFrameNodePath(root, "A > B > C"),
"The 'A > B' node has a 'C' child call.");
ok(getFrameNodePath(root, "A > B > D"),
"The 'A > B' node has a 'D' child call.");
is(Object.keys(root.calls.A.calls.E.calls).length, 1,
"The correct number of child calls were calculated for the '.A.E' node.");
is(Object.keys(root.calls.A.calls.E.calls)[0], "F",
"The '.A.E' node's only child call is correct.");
is(getFrameNodePath(root, "A > E").calls.length, 1,
"The correct number of child calls were calculated for the 'A > E' node.");
ok(getFrameNodePath(root, "A > E > F"),
"The 'A > E' node has a 'F' child call.");
is(Object.keys(root.calls.A.calls.B.calls.C.calls).length, 0,
"The correct number of child calls were calculated for the '.A.B.C' node.");
is(Object.keys(root.calls.A.calls.B.calls.D.calls).length, 0,
"The correct number of child calls were calculated for the '.A.B.D' node.");
is(Object.keys(root.calls.A.calls.E.calls.F.calls).length, 0,
"The correct number of child calls were calculated for the '.A.E.F' node.");
is(getFrameNodePath(root, "A > B > C").calls.length, 1,
"The correct number of child calls were calculated for the 'A > B > C' node.");
ok(getFrameNodePath(root, "A > B > C > D"),
"The 'A > B > C' node has a 'D' child call.");
// Insert new nodes in the tree.
is(getFrameNodePath(root, "A > B > C > D").calls.length, 1,
"The correct number of child calls were calculated for the 'A > B > C > D' node.");
ok(getFrameNodePath(root, "A > B > C > D > E"),
"The 'A > B > C > D' node has a 'E' child call.");
root.insert({
time: 20,
frames: [
{ location: "(root)" },
{ location: "A" },
{ location: "B" },
{ location: "C" },
{ location: "D" },
{ location: "E" },
{ location: "F" },
{ location: "G" }
]
});
is(getFrameNodePath(root, "A > B > C > D > E").calls.length, 1,
"The correct number of child calls were calculated for the 'A > B > C > D > E' node.");
ok(getFrameNodePath(root, "A > B > C > D > E > F"),
"The 'A > B > C > D > E' node has a 'F' child call.");
// Retest the root node.
is(getFrameNodePath(root, "A > B > C > D > E > F").calls.length, 1,
"The correct number of child calls were calculated for the 'A > B > C > D > E > F' node.");
ok(getFrameNodePath(root, "A > B > C > D > E > F > G"),
"The 'A > B > C > D > E > F' node has a 'G' child call.");
is(root.duration, 20,
"The correct duration was recalculated for the root node.");
is(Object.keys(root.calls).length, 1,
"The correct number of child calls were calculated for the root node.");
is(Object.keys(root.calls)[0], "A",
"The root node's only child call is correct.");
// Retest all the descendant nodes.
is(Object.keys(root.calls.A.calls).length, 2,
"The correct number of child calls were calculated for the '.A' node.");
is(Object.keys(root.calls.A.calls)[0], "B",
"The '.A' node's first child call is correct.");
is(Object.keys(root.calls.A.calls)[1], "E",
"The '.A' node's second child call is correct.");
is(Object.keys(root.calls.A.calls.B.calls).length, 2,
"The correct number of child calls were calculated for the '.A.B' node.");
is(Object.keys(root.calls.A.calls.B.calls)[0], "C",
"The '.A.B' node's first child call is correct.");
is(Object.keys(root.calls.A.calls.B.calls)[1], "D",
"The '.A.B' node's second child call is correct.");
is(Object.keys(root.calls.A.calls.E.calls).length, 1,
"The correct number of child calls were calculated for the '.A.E' node.");
is(Object.keys(root.calls.A.calls.E.calls)[0], "F",
"The '.A.E' node's only child call is correct.");
is(Object.keys(root.calls.A.calls.B.calls.C.calls).length, 1,
"The correct number of child calls were calculated for the '.A.B.C' node.");
is(Object.keys(root.calls.A.calls.B.calls.C.calls)[0], "D",
"The '.A.B.C' node's only child call is correct.");
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls).length, 1,
"The correct number of child calls were calculated for the '.A.B.C.D' node.");
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls)[0], "E",
"The '.A.B.C.D' node's only child call is correct.");
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls).length, 1,
"The correct number of child calls were calculated for the '.A.B.C.D.E' node.");
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls)[0], "F",
"The '.A.B.C.D.E' node's only child call is correct.");
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls).length, 1,
"The correct number of child calls were calculated for the '.A.B.C.D.E.F' node.");
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls)[0], "G",
"The '.A.B.C.D.E.F' node's only child call is correct.");
is(Object.keys(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.calls).length, 0,
"The correct number of child calls were calculated for the '.A.B.D.E.F.G' node.");
is(Object.keys(root.calls.A.calls.B.calls.D.calls).length, 0,
"The correct number of child calls were calculated for the '.A.B.D' node.");
is(Object.keys(root.calls.A.calls.E.calls.F.calls).length, 0,
"The correct number of child calls were calculated for the '.A.E.F' node.");
is(getFrameNodePath(root, "A > B > C > D > E > F > G").calls.length, 0,
"The correct number of child calls were calculated for the 'A > B > C > D > E > F > G' node.");
is(getFrameNodePath(root, "A > B > D").calls.length, 0,
"The correct number of child calls were calculated for the 'A > B > D' node.");
is(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.
is(root.calls.A.location, "A",
"The '.A' node has the correct location.");
is(root.calls.A.sampleTimes.toSource(),
"[{start:5, end:10}, {start:11, end:17}, {start:18, end:25}, {start:20, end:22}]",
"The '.A' node has the correct sample times.");
is(root.calls.A.duration, 20,
"The '.A' node has the correct duration in milliseconds.");
is(root.calls.A.samples, 4,
"The '.A' node has the correct number of samples.");
is(getFrameNodePath(root, "A").location, "A",
"The 'A' node has the correct location.");
is(getFrameNodePath(root, "A").duration, 20,
"The 'A' node has the correct duration in milliseconds.");
is(getFrameNodePath(root, "A").samples, 4,
"The 'A' node has the correct number of samples.");
// ...and the rightmost leaf.
is(root.calls.A.calls.E.calls.F.location, "F",
"The '.A.E.F' node has the correct location.");
is(root.calls.A.calls.E.calls.F.sampleTimes.toSource(),
"[{start:18, end:25}]",
"The '.A.E.F' node has the correct sample times.");
is(root.calls.A.calls.E.calls.F.duration, 7,
"The '.A.E.F' node has the correct duration in milliseconds.");
is(root.calls.A.calls.E.calls.F.samples, 1,
"The '.A.E.F' node has the correct number of samples.");
is(getFrameNodePath(root, "A > E > F").location, "F",
"The 'A > E > F' node has the correct location.");
is(getFrameNodePath(root, "A > E > F").duration, 7,
"The 'A > E > F' node has the correct duration in milliseconds.");
is(getFrameNodePath(root, "A > E > F").samples, 1,
"The 'A > E > F' node has the correct number of samples.");
// ...and the leftmost leaf.
is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.location, "G",
"The '.A.B.C.D.E.F.G' node has the correct location.");
is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.sampleTimes.toSource(),
"[{start:20, end:22}]",
"The '.A.B.C.D.E.F.G' node has the correct sample times.");
is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.duration, 2,
"The '.A.B.C.D.E.F.G' node has the correct duration in milliseconds.");
is(root.calls.A.calls.B.calls.C.calls.D.calls.E.calls.F.calls.G.samples, 1,
"The '.A.B.C.D.E.F.G' node has the correct number of samples.");
is(getFrameNodePath(root, "A > B > C > D > E > F > G").location, "G",
"The 'A > B > C > D > E > F > G' node has the correct location.");
is(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.");
is(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.");
finish();
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ location: "(root)" },
@ -193,4 +132,16 @@ let gSamples = [{
{ location: "E" },
{ location: "F" }
]
}];
}, {
time: 20,
frames: [
{ location: "(root)" },
{ location: "A" },
{ location: "B" },
{ location: "C" },
{ location: "D" },
{ location: "E" },
{ location: "F" },
{ location: "G" }
]
}]);

View File

@ -10,37 +10,37 @@ function test() {
// Create a root node from a given samples array.
let root = new ThreadNode(gSamples);
let root = getFrameNodePath(new ThreadNode(gThread), "(root)");
// Test the root node.
is(root.duration, 5,
"The correct duration was calculated for the root node.");
is(Object.keys(root.calls).length, 1,
is(root.calls.length, 1,
"The correct number of child calls were calculated for the root node.");
is(Object.keys(root.calls)[0], "A",
ok(getFrameNodePath(root, "A"),
"The root node's only child call is correct.");
// Test all the descendant nodes.
is(Object.keys(root.calls.A.calls).length, 1,
"The correct number of child calls were calculated for the '.A' node.");
is(Object.keys(root.calls.A.calls)[0], "B",
"The '.A.B' node's only child call is correct.");
is(getFrameNodePath(root, "A").calls.length, 1,
"The correct number of child calls were calculated for the 'A' node.");
ok(getFrameNodePath(root, "A > B"),
"The 'A' node's only child call is correct.");
is(Object.keys(root.calls.A.calls.B.calls).length, 1,
"The correct number of child calls were calculated for the '.A.B' node.");
is(Object.keys(root.calls.A.calls.B.calls)[0], "C",
"The '.A.B' node's only child call is correct.");
is(getFrameNodePath(root, "A > B").calls.length, 1,
"The correct number of child calls were calculated for the 'A > B' node.");
ok(getFrameNodePath(root, "A > B > C"),
"The 'A > B' node's only child call is correct.");
is(Object.keys(root.calls.A.calls.B.calls.C.calls).length, 0,
"The correct number of child calls were calculated for the '.A.B.C' node.");
is(getFrameNodePath(root, "A > B > C").calls.length, 0,
"The correct number of child calls were calculated for the 'A > B > C' node.");
finish();
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ location: "(root)" },
@ -56,4 +56,4 @@ let gSamples = [{
{ location: "B" },
{ location: "D" }
]
}];
}]);

View File

@ -10,47 +10,52 @@ function test() {
let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
// Create a root node from a given samples array, filtering by time.
let root = new ThreadNode(gSamples, { startTime: 11, endTime: 18 });
//
// Filtering from 5 to 18 includes the 2nd and 3rd samples. The 2nd sample
// starts exactly on 5 and ends at 11. The 3rd sample starts at 11 and ends
// exactly at 18.
let startTime = 5;
let endTime = 18;
let root = getFrameNodePath(new ThreadNode(gThread, { startTime, endTime }), "(root)");
// Test the root node.
is(root.duration, 18,
is(root.duration, endTime - startTime,
"The correct duration was calculated for the root node.");
is(Object.keys(root.calls).length, 1,
is(root.calls.length, 1,
"The correct number of child calls were calculated for the root node.");
is(Object.keys(root.calls)[0], "A",
ok(getFrameNodePath(root, "A"),
"The root node's only child call is correct.");
// Test all the descendant nodes.
is(Object.keys(root.calls.A.calls).length, 2,
"The correct number of child calls were calculated for the '.A' node.");
is(Object.keys(root.calls.A.calls)[0], "B",
"The '.A' node's first child call is correct.");
is(Object.keys(root.calls.A.calls)[1], "E",
"The '.A' node's second child call is correct.");
is(getFrameNodePath(root, "A").calls.length, 2,
"The correct number of child calls were calculated for the 'A' node.");
ok(getFrameNodePath(root, "A > B"),
"The 'A' node has a 'B' child call.");
ok(getFrameNodePath(root, "A > E"),
"The 'A' node has a 'E' child call.");
is(Object.keys(root.calls.A.calls.B.calls).length, 1,
"The correct number of child calls were calculated for the '.A.B' node.");
is(Object.keys(root.calls.A.calls.B.calls)[0], "D",
"The '.A.B' node's only child call is correct.");
is(getFrameNodePath(root, "A > B").calls.length, 1,
"The correct number of child calls were calculated for the 'A > B' node.");
ok(getFrameNodePath(root, "A > B > D"),
"The 'A > B' node's only child call is correct.");
is(Object.keys(root.calls.A.calls.E.calls).length, 1,
"The correct number of child calls were calculated for the '.A.E' node.");
is(Object.keys(root.calls.A.calls.E.calls)[0], "F",
"The '.A.E' node's only child call is correct.");
is(getFrameNodePath(root, "A > E").calls.length, 1,
"The correct number of child calls were calculated for the 'A > E' node.");
ok(getFrameNodePath(root, "A > E > F"),
"The 'A > E' node's only child call is correct.");
is(Object.keys(root.calls.A.calls.B.calls.D.calls).length, 0,
"The correct number of child calls were calculated for the '.A.B.D' node.");
is(Object.keys(root.calls.A.calls.E.calls.F.calls).length, 0,
"The correct number of child calls were calculated for the '.A.E.F' node.");
is(getFrameNodePath(root, "A > B > D").calls.length, 0,
"The correct number of child calls were calculated for the 'A > B > D' node.");
is(getFrameNodePath(root, "A > E > F").calls.length, 0,
"The correct number of child calls were calculated for the 'A > E > F' node.");
finish();
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ location: "(root)" },
@ -83,4 +88,4 @@ let gSamples = [{
{ location: "C" },
{ location: "D" }
]
}];
}]);

View File

@ -11,44 +11,46 @@ function test() {
// Create a root node from a given samples array, filtering by time.
let root = new ThreadNode(gSamples, { startTime: 11, endTime: 18, contentOnly: true });
let startTime = 5;
let endTime = 18;
let root = getFrameNodePath(new ThreadNode(gThread, { startTime: startTime, endTime: endTime, contentOnly: true }), "(root)");
// Test the root node.
is(root.duration, 18,
is(root.duration, endTime - startTime,
"The correct duration was calculated for the root node.");
is(Object.keys(root.calls).length, 2,
is(root.calls.length, 2,
"The correct number of child calls were calculated for the root node.");
is(Object.keys(root.calls)[0], "http://D",
"The root node's first child call is correct.");
is(Object.keys(root.calls)[1], "http://A",
"The root node's second child call is correct.");
ok(getFrameNodePath(root, "http://D"),
"The root has a 'http://D' child call.");
ok(getFrameNodePath(root, "http://A"),
"The root has a 'http://A' child call.");
// Test all the descendant nodes.
is(Object.keys(root.calls["http://A"].calls).length, 1,
"The correct number of child calls were calculated for the '.A' node.");
is(Object.keys(root.calls["http://A"].calls)[0], "https://E",
"The '.A' node's only child call is correct.");
is(getFrameNodePath(root, "http://A").calls.length, 1,
"The correct number of child calls were calculated for the 'http://A' node.");
ok(getFrameNodePath(root, "http://A > https://E"),
"The 'http://A' node's only child call is correct.");
is(Object.keys(root.calls["http://A"].calls["https://E"].calls).length, 1,
"The correct number of child calls were calculated for the '.A.E' node.");
is(Object.keys(root.calls["http://A"].calls["https://E"].calls)[0], "file://F",
"The '.A.E' node's only child call is correct.");
is(getFrameNodePath(root, "http://A > https://E").calls.length, 1,
"The correct number of child calls were calculated for the 'http://A > http://E' node.");
ok(getFrameNodePath(root, "http://A > https://E > file://F"),
"The 'http://A > https://E' node's only child call is correct.");
is(Object.keys(root.calls["http://A"].calls["https://E"].calls["file://F"].calls).length, 1,
"The correct number of child calls were calculated for the '.A.E.F' node.");
is(Object.keys(root.calls["http://A"].calls["https://E"].calls["file://F"].calls)[0], "app://H",
"The '.A.E.F' node's only child call is correct.");
is(getFrameNodePath(root, "http://A > https://E > file://F").calls.length, 1,
"The correct number of child calls were calculated for the 'http://A > https://E >> file://F' node.");
ok(getFrameNodePath(root, "http://A > https://E > file://F > app://H"),
"The 'http://A > https://E >> file://F' node's only child call is correct.");
is(Object.keys(root.calls["http://D"].calls).length, 0,
"The correct number of child calls were calculated for the '.D' node.");
is(getFrameNodePath(root, "http://D").calls.length, 0,
"The correct number of child calls were calculated for the 'http://D' node.");
finish();
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ location: "(root)" },
@ -83,4 +85,4 @@ let gSamples = [{
{ location: "http://C" },
{ location: "http://D" }
]
}];
}]);

View File

@ -8,7 +8,7 @@
let time = 1;
let samples = [{
let gThread = synthesizeProfileForTest([{
time: time++,
frames: [
{ location: "(root)" },
@ -40,40 +40,40 @@ let samples = [{
{ location: "B" },
{ location: "F" }
]
}];
}]);
function test() {
let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
let root = new ThreadNode(samples, { invertTree: true });
let root = new ThreadNode(gThread, { invertTree: true });
is(Object.keys(root.calls).length, 2,
is(root.calls.length, 2,
"Should get the 2 youngest frames, not the 1 oldest frame");
let C = root.calls.C;
let C = getFrameNodePath(root, "C");
ok(C, "Should have C as a child of the root.");
is(Object.keys(C.calls).length, 3,
is(C.calls.length, 3,
"Should have 3 frames that called C.");
ok(C.calls.B, "B called C.");
ok(C.calls.D, "D called C.");
ok(C.calls.E, "E called C.");
ok(getFrameNodePath(C, "B"), "B called C.");
ok(getFrameNodePath(C, "D"), "D called C.");
ok(getFrameNodePath(C, "E"), "E called C.");
is(Object.keys(C.calls.B.calls).length, 1);
ok(C.calls.B.calls.A, "A called B called C");
is(Object.keys(C.calls.D.calls).length, 1);
ok(C.calls.D.calls.A, "A called D called C");
is(Object.keys(C.calls.E.calls).length, 1);
ok(C.calls.E.calls.A, "A called E called C");
is(getFrameNodePath(C, "B").calls.length, 1);
ok(getFrameNodePath(C, "B > A"), "A called B called C");
is(getFrameNodePath(C, "D").calls.length, 1);
ok(getFrameNodePath(C, "D > A"), "A called D called C");
is(getFrameNodePath(C, "E").calls.length, 1);
ok(getFrameNodePath(C, "E > A"), "A called E called C");
let F = root.calls.F;
let F = getFrameNodePath(root, "F");
ok(F, "Should have F as a child of the root.");
is(Object.keys(F.calls).length, 1);
ok(F.calls.B, "B called F");
is(F.calls.length, 1);
ok(getFrameNodePath(F, "B"), "B called F");
is(Object.keys(F.calls.B.calls).length, 1);
ok(F.calls.B.calls.A, "A called B called F");
is(getFrameNodePath(F, "B").calls.length, 1);
ok(getFrameNodePath(F, "B > A"), "A called B called F");
finish();
}

View File

@ -6,96 +6,174 @@
* the FrameNodes have the correct optimization data after iterating over samples.
*/
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
let gUniqueStacks = new RecordingUtils.UniqueStacks();
function uniqStr(s) {
return gUniqueStacks.getOrAddStringIndex(s);
}
let time = 1;
let samples = [{
time: time++,
frames: [
{ location: "(root)" },
{ location: "A", optsIndex: 0 },
{ location: "B" },
{ location: "C" }
]
}, {
time: time++,
frames: [
{ location: "(root)" },
{ location: "A", optsIndex: 0 },
{ location: "D" },
{ location: "C" }
]
}, {
time: time++,
frames: [
{ location: "(root)" },
{ location: "A", optsIndex: 1 },
{ location: "E", optsIndex: 2 },
{ location: "C" }
],
}, {
time: time++,
frames: [
{ location: "(root)" },
{ location: "A" },
{ location: "B" },
{ location: "F" }
]
}];
let gThread = RecordingUtils.deflateThread({
samples: [{
time: 0,
frames: [
{ location: "(root)" }
]
}, {
time: time++,
frames: [
{ location: "(root)" },
{ location: "A_O1" },
{ location: "B" },
{ location: "C" }
]
}, {
time: time++,
frames: [
{ location: "(root)" },
{ location: "A_O1" },
{ location: "D" },
{ location: "C" }
]
}, {
time: time++,
frames: [
{ location: "(root)" },
{ location: "A_O2" },
{ location: "E_O3" },
{ location: "C" }
],
}, {
time: time++,
frames: [
{ location: "(root)" },
{ location: "A" },
{ location: "B" },
{ location: "F" }
]
}],
markers: []
}, gUniqueStacks);
// Array of OptimizationSites
let gOpts = [{
// 3 OptimizationSites
let gRawSite1 = {
line: 12,
column: 2,
types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
{ keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
{ keyedBy: "primitive", location: "self-hosted" }
]}],
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "Inlined", strategy: "SomeGetter3" },
]
}, {
types: [{
mirType: uniqStr("Object"),
site: uniqStr("A (http://foo/bar/bar:12)"),
typeset: [{
keyedBy: uniqStr("constructor"),
name: uniqStr("Foo"),
location: uniqStr("A (http://foo/bar/baz:12)")
}, {
keyedBy: uniqStr("primitive"),
location: uniqStr("self-hosted")
}]
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("Inlined"), uniqStr("SomeGetter3")]
]
}
};
let gRawSite2 = {
line: 34,
types: [{ mirType: "Int32", site: "Receiver" }], // use no types
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "Failure3", strategy: "SomeGetter3" },
]
}, {
types: [{
mirType: uniqStr("Int32"),
site: uniqStr("Receiver")
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("Failure3"), uniqStr("SomeGetter3")]
]
}
};
let gRawSite3 = {
line: 78,
types: [{ mirType: "Object", site: "A (http://foo/bar/bar:12)", types: [
{ keyedBy: "constructor", name: "Foo", location: "A (http://foo/bar/baz:12)" },
{ keyedBy: "primitive", location: "self-hosted" }
]}],
attempts: [
{ outcome: "Failure1", strategy: "SomeGetter1" },
{ outcome: "Failure2", strategy: "SomeGetter2" },
{ outcome: "GenericSuccess", strategy: "SomeGetter3" },
]
}];
types: [{
mirType: uniqStr("Object"),
site: uniqStr("A (http://foo/bar/bar:12)"),
typeset: [{
keyedBy: uniqStr("constructor"),
name: uniqStr("Foo"),
location: uniqStr("A (http://foo/bar/baz:12)")
}, {
keyedBy: uniqStr("primitive"),
location: uniqStr("self-hosted")
}]
}],
attempts: {
schema: {
outcome: 0,
strategy: 1
},
data: [
[uniqStr("Failure1"), uniqStr("SomeGetter1")],
[uniqStr("Failure2"), uniqStr("SomeGetter2")],
[uniqStr("GenericSuccess"), uniqStr("SomeGetter3")]
]
}
};
gThread.frameTable.data.forEach((frame) => {
const LOCATION_SLOT = gThread.frameTable.schema.location;
const OPTIMIZATIONS_SLOT = gThread.frameTable.schema.optimizations;
let l = gThread.stringTable[frame[LOCATION_SLOT]];
switch (l) {
case "A_O1":
frame[LOCATION_SLOT] = uniqStr("A");
frame[OPTIMIZATIONS_SLOT] = gRawSite1;
break;
case "A_O2":
frame[LOCATION_SLOT] = uniqStr("A");
frame[OPTIMIZATIONS_SLOT] = gRawSite2;
break;
case "E_O3":
frame[LOCATION_SLOT] = uniqStr("E");
frame[OPTIMIZATIONS_SLOT] = gRawSite3;
break;
}
});
function test() {
let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
let root = new ThreadNode(samples, { optimizations: gOpts });
let root = new ThreadNode(gThread);
let A = root.calls.A;
let A = getFrameNodePath(root, "(root) > A");
let opts = A.getOptimizations();
let sites = opts.getOptimizationSites();
let sites = opts.optimizationSites;
is(sites.length, 2, "Frame A has two optimization sites.");
is(sites[0].samples, 2, "first opt site has 2 samples.");
is(sites[1].samples, 1, "second opt site has 1 sample.");
let E = A.calls.E;
let E = getFrameNodePath(A, "E");
opts = E.getOptimizations();
sites = opts.getOptimizationSites();
sites = opts.optimizationSites;
is(sites.length, 1, "Frame E has one optimization site.");
is(sites[0].samples, 1, "first opt site has 1 samples.");
let D = A.calls.D;
let D = getFrameNodePath(A, "D");
ok(!D.getOptimizations(),
"frames that do not have any opts data do not have JITOptimizations instances.");

View File

@ -13,7 +13,7 @@ function test() {
// Create a root node from a given samples array.
let root = new ThreadNode(gSamples, { contentOnly: true });
let root = getFrameNodePath(new ThreadNode(gThread, { contentOnly: true }), "(root)");
/*
* should have a tree like:
@ -26,29 +26,31 @@ function test() {
* - D
* - E
* - F
* - (JS)
* - (JS)
*/
// Test the root node.
is(Object.keys(root.calls).length, 2, "root has 2 children");
ok(root.calls[url("A")], "root has content child");
ok(root.calls["64"], "root has platform generalized child");
is(Object.keys(root.calls["64"].calls).length, 0, "platform generalized child is a leaf.");
is(root.calls.length, 2, "root has 2 children");
ok(getFrameNodePath(root, url("A")), "root has content child");
ok(getFrameNodePath(root, "64"), "root has platform generalized child");
is(getFrameNodePath(root, "64").calls.length, 0, "platform generalized child is a leaf.");
ok(root.calls[url("A")].calls["128"], "A has platform generalized child of another type");
is(Object.keys(root.calls[url("A")].calls["128"].calls).length, 0, "second generalized type is a leaf.");
ok(getFrameNodePath(root, `${url("A")} > 128`), "A has platform generalized child of another type");
is(getFrameNodePath(root, `${url("A")} > 128`).calls.length, 0, "second generalized type is a leaf.");
ok(root.calls[url("A")].calls[url("E")].calls[url("F")].calls["64"],
"a second leaf of the first generalized type exists deep in the tree.");
ok(root.calls[url("A")].calls["128"], "A has platform generalized child of another type");
ok(getFrameNodePath(root, `${url("A")} > ${url("E")} > ${url("F")} > 64`),
"a second leaf of the first generalized type exists deep in the tree.");
ok(getFrameNodePath(root, `${url("A")} > 128`), "A has platform generalized child of another type");
is(getFrameNodePath(root, "64").category,
getFrameNodePath(root, `${url("A")} > ${url("E")} > ${url("F")} > 64`).category,
"generalized frames of same type are duplicated in top-down view");
is(root.calls["64"].category, root.calls[url("A")].calls[url("E")].calls[url("F")].calls["64"].category,
"generalized frames of same type are duplicated in top-down view");
finish();
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ location: "(root)" },
@ -88,4 +90,4 @@ let gSamples = [{
{ location: "http://content/A" },
{ location: "contentZ", category: CATEGORY_MASK("gc", 1) },
]
}];
}]);

View File

@ -10,7 +10,9 @@ function test() {
let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
let threadNode = new ThreadNode(gSamples);
let threadNode = new ThreadNode(gThread);
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });
let container = document.createElement("vbox");
@ -60,7 +62,7 @@ function test() {
finish();
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ category: 8, location: "(root)" },
@ -92,4 +94,4 @@ let gSamples = [{
{ category: 128, location: "E (http://foo/bar/baz:90)" },
{ category: 256, location: "F (http://foo/bar/baz:99)" }
]
}];
}]);

View File

@ -12,7 +12,9 @@ function test() {
let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
let threadNode = new ThreadNode(gSamples);
let threadNode = new ThreadNode(gThread);
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });
let container = document.createElement("vbox");
@ -125,7 +127,7 @@ function test() {
finish();
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ category: CATEGORY_MASK('other'), location: "(root)" },
@ -157,4 +159,4 @@ let gSamples = [{
{ category: CATEGORY_MASK('gc', 2), location: "E (http://foo/bar/baz:90)" },
{ category: CATEGORY_MASK('network'), location: "F (http://foo/bar/baz:99)" }
]
}];
}]);

View File

@ -10,7 +10,9 @@ function test() {
let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
let threadNode = new ThreadNode(gSamples);
let threadNode = new ThreadNode(gThread);
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });
let container = document.createElement("vbox");
@ -73,7 +75,7 @@ function test() {
finish();
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ category: 8, location: "(root)" },
@ -105,5 +107,5 @@ let gSamples = [{
{ category: 128, location: "E (http://foo/bar/baz:90)" },
{ category: 256, location: "F (http://foo/bar/baz:99)" }
]
}];
}]);

View File

@ -12,7 +12,9 @@ function test() {
let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
let threadNode = new ThreadNode(gSamples);
let threadNode = new ThreadNode(gThread);
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });
let container = document.createElement("vbox");
@ -79,7 +81,7 @@ function test() {
finish();
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ category: CATEGORY_MASK('other'), location: "(root)" },
@ -111,4 +113,4 @@ let gSamples = [{
{ category: CATEGORY_MASK('gc', 2), location: "E (http://foo/bar/baz:90)" },
{ category: CATEGORY_MASK('network'), location: "F (http://foo/bar/baz:99)" }
]
}];
}]);

View File

@ -10,7 +10,9 @@ function test() {
let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
let threadNode = new ThreadNode(gSamples);
let threadNode = new ThreadNode(gThread);
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });
let container = document.createElement("vbox");
@ -31,7 +33,7 @@ function test() {
finish();
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ category: 8, location: "(root)" },
@ -63,4 +65,4 @@ let gSamples = [{
{ category: 128, location: "E (http://foo/bar/baz:90)" },
{ category: 256, location: "F (http://foo/bar/baz:99)" }
]
}];
}]);

View File

@ -10,7 +10,9 @@ function spawnTest () {
let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
let threadNode = new ThreadNode(gSamples);
let threadNode = new ThreadNode(gThread);
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });
let container = document.createElement("vbox");
@ -29,7 +31,7 @@ function spawnTest () {
finish();
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ category: 8, location: "(root)" },
@ -61,4 +63,4 @@ let gSamples = [{
{ category: 128, location: "E (http://foo/bar/baz:90)" },
{ category: 256, location: "F (http://foo/bar/baz:99)" }
]
}];
}]);

View File

@ -10,7 +10,9 @@ function spawnTest () {
let { ThreadNode } = devtools.require("devtools/shared/profiler/tree-model");
let { CallView } = devtools.require("devtools/shared/profiler/tree-view");
let threadNode = new ThreadNode(gSamples);
let threadNode = new ThreadNode(gThread);
// Don't display the synthesized (root) and the real (root) node twice.
threadNode.calls = threadNode.calls[0].calls;
let treeRoot = new CallView({ frame: threadNode });
let container = document.createElement("vbox");
@ -33,7 +35,7 @@ function spawnTest () {
"The .A.B.D node has the correct container node.");
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ category: 8, location: "(root)" },
@ -65,4 +67,4 @@ let gSamples = [{
{ category: 128, location: "E (http://foo/bar/baz:90)" },
{ category: 256, location: "F (http://foo/bar/baz:99)" }
]
}];
}]);

View File

@ -26,7 +26,9 @@ function test() {
* - (JS)
*/
let threadNode = new ThreadNode(gSamples, { contentOnly: true });
let threadNode = new ThreadNode(gThread, { 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 });
let container = document.createElement("vbox");
@ -61,7 +63,7 @@ function test() {
finish();
}
let gSamples = [{
let gThread = synthesizeProfileForTest([{
time: 5,
frames: [
{ location: "(root)" },
@ -101,4 +103,4 @@ let gSamples = [{
{ location: "http://content/A" },
{ location: "contentZ", category: CATEGORY_MASK("gc", 1) },
]
}];
}]);

View File

@ -517,3 +517,66 @@ function forceCC () {
SpecialPowers.DOMWindowUtils.garbageCollect();
SpecialPowers.DOMWindowUtils.garbageCollect();
}
/**
* Inflate a particular sample's stack and return an array of strings.
*/
function getInflatedStackLocations(thread, sample) {
let stackTable = thread.stackTable;
let frameTable = thread.frameTable;
let stringTable = thread.stringTable;
let SAMPLE_STACK_SLOT = thread.samples.schema.stack;
let STACK_PREFIX_SLOT = stackTable.schema.prefix;
let STACK_FRAME_SLOT = stackTable.schema.frame;
let FRAME_LOCATION_SLOT = frameTable.schema.location;
// Build the stack from the raw data and accumulate the locations in
// an array.
let stackIndex = sample[SAMPLE_STACK_SLOT];
let locations = [];
while (stackIndex !== null) {
let stackEntry = stackTable.data[stackIndex];
let frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]];
locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]);
stackIndex = stackEntry[STACK_PREFIX_SLOT];
}
// The profiler tree is inverted, so reverse the array.
return locations.reverse();
}
/**
* Get a path in a FrameNode call tree.
*/
function getFrameNodePath(root, path) {
let calls = root.calls;
let node;
for (let key of path.split(" > ")) {
node = calls.find((node) => node.key == key);
if (!node) {
break;
}
calls = node.calls;
}
return node;
}
/**
* Synthesize a profile for testing.
*/
function synthesizeProfileForTest(samples) {
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
samples.unshift({
time: 0,
frames: [
{ location: "(root)" }
]
});
let uniqueStacks = new RecordingUtils.UniqueStacks();
return RecordingUtils.deflateThread({
samples: samples,
markers: []
}, uniqueStacks);
}

View File

@ -5,6 +5,7 @@
// widget work properly in the flame graph.
let {FlameGraphUtils, FLAME_GRAPH_BLOCK_HEIGHT} = devtools.require("devtools/shared/widgets/FlameGraph");
let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
add_task(function*() {
yield promiseTab("about:blank");
@ -13,7 +14,7 @@ add_task(function*() {
});
function* performTest() {
let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA);
let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA);
ok(out, "Some data was outputted properly");
is(out.length, 10, "The outputted length is correct.");
@ -42,7 +43,7 @@ function* performTest() {
}
}
let TEST_DATA = [{
let TEST_DATA = synthesizeProfileForTest([{
frames: [{
location: "M"
}, {
@ -96,7 +97,7 @@ let TEST_DATA = [{
location: "Z"
}],
time: 500
}];
}]);
let EXPECTED_OUTPUT = [{
blocks: []

View File

@ -12,7 +12,7 @@ add_task(function*() {
});
function* performTest() {
let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, {
let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, {
flattenRecursion: true
});
@ -43,7 +43,7 @@ function* performTest() {
}
}
let TEST_DATA = [{
let TEST_DATA = synthesizeProfileForTest([{
frames: [{
location: "A"
}, {
@ -58,7 +58,7 @@ let TEST_DATA = [{
location: "C"
}],
time: 50,
}];
}]);
let EXPECTED_OUTPUT = [{
blocks: []
@ -89,7 +89,17 @@ let EXPECTED_OUTPUT = [{
text: "B"
}]
}, {
blocks: []
blocks: [{
srcData: {
startTime: 0,
rawLocation: "C"
},
x: 0,
y: FLAME_GRAPH_BLOCK_HEIGHT * 2,
width: 50,
height: FLAME_GRAPH_BLOCK_HEIGHT,
text: "C"
}]
}, {
blocks: []
}, {

View File

@ -13,8 +13,8 @@ add_task(function*() {
});
function* performTest() {
let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, {
filterFrames: FrameNode.isContent
let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, {
contentOnly: true
});
ok(out, "Some data was outputted properly");
@ -22,6 +22,8 @@ function* performTest() {
info("Got flame graph data:\n" + out.toSource() + "\n");
dump(JSON.stringify(out, undefined, 2));
for (let i = 0; i < out.length; i++) {
let found = out[i];
let expected = EXPECTED_OUTPUT[i];
@ -44,7 +46,7 @@ function* performTest() {
}
}
let TEST_DATA = [{
let TEST_DATA = synthesizeProfileForTest([{
frames: [{
location: "http://A"
}, {
@ -57,7 +59,7 @@ let TEST_DATA = [{
location: "resource://E"
}],
time: 50,
}];
}]);
let EXPECTED_OUTPUT = [{
blocks: []
@ -92,7 +94,17 @@ let EXPECTED_OUTPUT = [{
}, {
blocks: []
}, {
blocks: []
blocks: [{
srcData: {
startTime: 0,
rawLocation: "Gecko"
},
x: 0,
y: FLAME_GRAPH_BLOCK_HEIGHT * 3,
width: 50,
height: FLAME_GRAPH_BLOCK_HEIGHT,
text: "Gecko"
}]
}, {
blocks: []
}, {

View File

@ -13,10 +13,10 @@ add_task(function*() {
});
function* performTest() {
let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, {
let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, {
flattenRecursion: true,
filterFrames: FrameNode.isContent,
showIdleBlocks: "\m/"
contentOnly: true,
showIdleBlocks: '\m/'
});
ok(out, "Some data was outputted properly");
@ -46,7 +46,7 @@ function* performTest() {
}
}
let TEST_DATA = [{
let TEST_DATA = synthesizeProfileForTest([{
frames: [{
location: "http://A"
}, {
@ -66,11 +66,7 @@ let TEST_DATA = [{
}],
time: 50
}, {
frames: [{
location: "chrome://D"
}, {
location: "resource://E"
}],
frames: [],
time: 100
}, {
frames: [{
@ -81,7 +77,7 @@ let TEST_DATA = [{
location: "file://C",
}],
time: 150
}];
}]);
let EXPECTED_OUTPUT = [{
blocks: []
@ -118,6 +114,16 @@ let EXPECTED_OUTPUT = [{
width: 50,
height: FLAME_GRAPH_BLOCK_HEIGHT,
text: "http://A"
}, {
srcData: {
startTime: 0,
rawLocation: "file://C"
},
x: 100,
y: FLAME_GRAPH_BLOCK_HEIGHT * 2,
width: 50,
height: FLAME_GRAPH_BLOCK_HEIGHT,
text: "file://C"
}]
}, {
blocks: [{
@ -136,7 +142,17 @@ let EXPECTED_OUTPUT = [{
}, {
blocks: []
}, {
blocks: []
blocks: [{
srcData: {
startTime: 0,
rawLocation: "Gecko"
},
x: 0,
y: FLAME_GRAPH_BLOCK_HEIGHT * 3,
width: 50,
height: FLAME_GRAPH_BLOCK_HEIGHT,
text: "Gecko"
}]
}, {
blocks: []
}, {

View File

@ -12,19 +12,19 @@ add_task(function*() {
});
function* performTest() {
let out1 = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA);
let out2 = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA);
let out1 = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA);
let out2 = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA);
is(out1, out2, "The outputted data is identical.")
let out3 = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, { flattenRecursion: true });
let out3 = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, { flattenRecursion: true });
is(out2, out3, "The outputted data is still identical.");
FlameGraphUtils.removeFromCache(TEST_DATA);
let out4 = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, { flattenRecursion: true });
let out4 = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, { flattenRecursion: true });
isnot(out3, out4, "The outputted data is not identical anymore.");
}
let TEST_DATA = [{
let TEST_DATA = synthesizeProfileForTest([{
frames: [{
location: "A"
}, {
@ -39,4 +39,4 @@ let TEST_DATA = [{
location: "C"
}],
time: 50,
}];
}]);

View File

@ -13,7 +13,7 @@ add_task(function*() {
});
function* performTest() {
let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, {
let out = FlameGraphUtils.createFlameGraphDataFromThread(TEST_DATA, {
flattenRecursion: true
});
@ -44,14 +44,14 @@ function* performTest() {
}
}
let TEST_DATA = [{
let TEST_DATA = synthesizeProfileForTest([{
frames: [{
location: "A (http://path/to/file.js:10:5"
}, {
location: "B (http://path/to/file.js:100:5"
}],
time: 50,
}];
}]);
let EXPECTED_OUTPUT = [{
blocks: []
@ -74,7 +74,17 @@ let EXPECTED_OUTPUT = [{
text: "A (file.js:10)"
}]
}, {
blocks: []
blocks: [{
srcData: {
startTime: 0,
rawLocation: "B (http://path/to/file.js:100:5)"
},
x: 0,
y: FLAME_GRAPH_BLOCK_HEIGHT,
width: 50,
height: FLAME_GRAPH_BLOCK_HEIGHT,
text: "B (file.js:100)"
}]
}, {
blocks: []
}, {

View File

@ -244,6 +244,24 @@ function* openAndCloseToolbox(nbOfTimes, usageTime, toolId) {
}
}
/**
* Synthesize a profile for testing.
*/
function synthesizeProfileForTest(samples) {
const { RecordingUtils } = devtools.require("devtools/performance/recording-utils");
samples.unshift({
time: 0,
frames: []
});
let uniqueStacks = new RecordingUtils.UniqueStacks();
return RecordingUtils.deflateThread({
samples: samples,
markers: []
}, uniqueStacks);
}
/**
* Waits until a predicate returns true.
*

View File

@ -713,3 +713,34 @@ function reload(tabClient) {
tabClient._reload({}, deferred.resolve);
return deferred.promise;
}
/**
* Returns an array of stack location strings given a thread and a sample.
*
* @param object thread
* @param object sample
* @returns object
*/
function getInflatedStackLocations(thread, sample) {
let stackTable = thread.stackTable;
let frameTable = thread.frameTable;
let stringTable = thread.stringTable;
let SAMPLE_STACK_SLOT = thread.samples.schema.stack;
let STACK_PREFIX_SLOT = stackTable.schema.prefix;
let STACK_FRAME_SLOT = stackTable.schema.frame;
let FRAME_LOCATION_SLOT = frameTable.schema.location;
// Build the stack from the raw data and accumulate the locations in
// an array.
let stackIndex = sample[SAMPLE_STACK_SLOT];
let locations = [];
while (stackIndex !== null) {
let stackEntry = stackTable.data[stackIndex];
let frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]];
locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]);
stackIndex = stackEntry[STACK_PREFIX_SLOT];
}
// The profiler tree is inverted, so reverse the array.
return locations.reverse();
}

View File

@ -94,11 +94,11 @@ function test_data(client, actor, startTime, callback)
// Now check the samples. At least one sample is expected to
// have been in the busy wait above.
let loc = stack.name + " (" + stack.filename + ":" + funcLine + ")";
do_check_true(response.profile.threads[0].samples.some(sample => {
return typeof sample.frames == "object" &&
sample.frames.length != 0 &&
sample.frames.some(f => (f.location == loc));
let thread0 = response.profile.threads[0];
do_check_true(thread0.samples.data.some(sample => {
let frames = getInflatedStackLocations(thread0, sample);
return frames.length != 0 &&
frames.some(location => (location == loc));
}));
callback();