/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ requestLongerTimeout(2); /** * Tests that the JIT Optimizations view renders optimization data * if on, and displays selected frames on focus. */ Services.prefs.setBoolPref(INVERT_PREF, false); function* spawnTest() { let { panel } = yield initPerformance(SIMPLE_URL); let { EVENTS, $, $$, window, PerformanceController } = panel.panelWin; let { OverviewView, DetailsView, OptimizationsListView, JsCallTreeView, RecordingsView } = panel.panelWin; let profilerData = { threads: [gThread] } is(Services.prefs.getBoolPref(JIT_PREF), false, "record JIT Optimizations pref off by default"); Services.prefs.setBoolPref(JIT_PREF, true); is(Services.prefs.getBoolPref(JIT_PREF), true, "toggle on record JIT Optimizations"); // Make two recordings, so we have one to switch to later, as the // second one will have fake sample data yield startRecording(panel); yield stopRecording(panel); yield startRecording(panel); yield stopRecording(panel); yield DetailsView.selectView("js-calltree"); yield injectAndRenderProfilerData(); // A is never a leaf, so it's optimizations should not be shown. yield checkFrame(1); // gRawSite2 and gRawSite3 are both optimizations on B, so they'll have // indices in descending order of # of samples. yield checkFrame(2, [{ i: 0, opt: gRawSite2 }, { i: 1, opt: gRawSite3 }]); // Leaf node (C) with no optimizations should not display any opts. yield checkFrame(3); let select = once(PerformanceController, EVENTS.RECORDING_SELECTED); let reset = once(OptimizationsListView, EVENTS.OPTIMIZATIONS_RESET); RecordingsView.selectedIndex = 0; yield Promise.all([select, reset]); ok(true, "JITOptimizations view correctly reset when switching recordings."); yield teardown(panel); finish(); function *injectAndRenderProfilerData() { // Get current recording and inject our mock data info("Injecting mock profile data"); let recording = PerformanceController.getCurrentRecording(); recording._profile = profilerData; // Force a rerender let rendered = once(JsCallTreeView, EVENTS.JS_CALL_TREE_RENDERED); JsCallTreeView.render(OverviewView.getTimeInterval()); yield rendered; is($("#jit-optimizations-view").hidden, false, "JIT Optimizations should be visible when pref is on"); ok($("#jit-optimizations-view").classList.contains("empty"), "JIT Optimizations view has empty message when no frames selected."); } function *checkFrame (frameIndex, expectedOpts=[]) { info(`Checking frame ${frameIndex}`); // Click the frame let rendered = once(OptimizationsListView, EVENTS.OPTIMIZATIONS_RENDERED); mousedown(window, $$(".call-tree-item")[frameIndex]); yield rendered; ok(true, "OptimizationsListView rendered when enabling with the current frame node selected"); let isEmpty = $("#jit-optimizations-view").classList.contains("empty"); if (expectedOpts.length === 0) { ok(isEmpty, "JIT Optimizations view has an empty message when selecting a frame without opt data."); return; } else { ok(!isEmpty, "JIT Optimizations view has no empty message."); } // Get the frame info for the first opt site, since all opt sites // share the same frame info let frameInfo = expectedOpts[0].opt._testFrameInfo; let { $headerName, $headerLine, $headerFile } = OptimizationsListView; ok(!$headerName.hidden, "header function name should be shown"); ok(!$headerLine.hidden, "header line should be shown"); ok(!$headerFile.hidden, "header file should be shown"); is($headerName.textContent, frameInfo.name, "correct header function name."); is($headerLine.textContent, frameInfo.line, "correct header line"); is($headerFile.textContent, frameInfo.file, "correct header file"); // 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, 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.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}"]']`), "found an ion type row"); } // The second and third optimization should display optimization failures. let warningIcon = $(`.tree-widget-container li[data-id='["${i}"]'] .opt-icon[severity=warning]`); if (opt === gRawSite3 || opt === gRawSite1) { ok(warningIcon, "did find a warning icon for all strategies failing."); } else { ok(!warningIcon, "did not find a warning icon for no successful strategies"); } } } } var gUniqueStacks = new RecordingUtils.UniqueStacks(); function uniqStr(s) { return gUniqueStacks.getOrAddStringIndex(s); } // Since deflateThread doesn't handle deflating optimization info, use // placeholder names A_O1, B_O2, and B_O3, which will be used to manually // splice deduped opts into the profile. var gThread = RecordingUtils.deflateThread({ samples: [{ time: 0, frames: [ { location: "(root)" } ] }, { time: 5, frames: [ { location: "(root)" }, { location: "A_O1" }, { location: "B_O2" }, { location: "C (http://foo/bar/baz:56)" } ] }, { time: 5 + 1, frames: [ { location: "(root)" }, { location: "A (http://foo/bar/baz:12)" }, { location: "B_O2" }, ] }, { time: 5 + 1 + 2, frames: [ { location: "(root)" }, { location: "A_O1" }, { location: "B_O3" }, ] }, { 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 var gRawSite1 = { _testFrameInfo: { name: "A", line: "12", file: "@baz" }, line: 12, column: 2, 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("Failure3"), uniqStr("SomeGetter3")] ] } }; var gRawSite2 = { _testFrameInfo: { name: "B", line: "10", file: "@boo" }, line: 40, types: [{ mirType: uniqStr("Int32"), site: uniqStr("Receiver") }], attempts: { schema: { outcome: 0, strategy: 1 }, data: [ [uniqStr("Failure1"), uniqStr("SomeGetter1")], [uniqStr("Failure2"), uniqStr("SomeGetter2")], [uniqStr("Inlined"), uniqStr("SomeGetter3")] ] } }; var gRawSite3 = { _testFrameInfo: { name: "B", line: "10", file: "@boo" }, line: 34, 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 "B_O2": frame[LOCATION_SLOT] = uniqStr("B (http://foo/bar/boo:10)"); frame[OPTIMIZATIONS_SLOT] = gRawSite2; break; case "B_O3": frame[LOCATION_SLOT] = uniqStr("B (http://foo/bar/boo:10)"); frame[OPTIMIZATIONS_SLOT] = gRawSite3; break; } });