UNPKG

chrome-devtools-frontend

Version:
367 lines (299 loc) • 12.5 kB
// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Platform from '../../core/platform/platform.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as Trace from '../../models/trace/trace.js'; import * as Timeline from '../../panels/timeline/timeline.js'; import * as Components from '../../ui/legacy/components/utils/utils.js'; import * as UI from '../../ui/legacy/legacy.js'; /** * @fileoverview using private properties isn't a Closure violation in tests. */ self.PerformanceTestRunner = self.PerformanceTestRunner || {}; PerformanceTestRunner.timelinePropertyFormatters = { children: 'formatAsTypeName', endTime: 'formatAsTypeName', requestId: 'formatAsTypeName', startTime: 'formatAsTypeName', responseTime: 'formatAsTypeName', stackTrace: 'formatAsTypeName', url: 'formatAsURL', fileName: 'formatAsURL', scriptName: 'formatAsTypeName', scriptId: 'formatAsTypeName', usedHeapSizeDelta: 'skip', id: 'formatAsTypeName', timerId: 'formatAsTypeName', layerId: 'formatAsTypeName', frameId: 'formatAsTypeName', frame: 'formatAsTypeName', page: 'formatAsTypeName', encodedDataLength: 'formatAsTypeName', identifier: 'formatAsTypeName', clip: 'formatAsTypeName', root: 'formatAsTypeName', backendNodeId: 'formatAsTypeName', nodeId: 'formatAsTypeName', rootNode: 'formatAsTypeName', finishTime: 'formatAsTypeName', thread: 'formatAsTypeName', allottedMilliseconds: 'formatAsTypeName', timedOut: 'formatAsTypeName', networkTime: 'formatAsTypeName', timing: 'formatAsTypeName', streamed: 'formatAsTypeName', producedCacheSize: 'formatAsTypeName', consumedCacheSize: 'formatAsTypeName' }; PerformanceTestRunner.InvalidationFormatters = { tracingEvent: 'skip', cause: 'formatAsInvalidationCause', frame: 'skip', invalidatedSelectorId: 'skip', invalidationList: 'skip', invalidationSet: 'skip', linkedRecalcStyleEvent: 'skip', linkedLayoutEvent: 'skip', nodeId: 'skip', paintId: 'skip', startTime: 'skip' }; TestRunner.formatters.formatAsInvalidationCause = function(cause) { if (!cause) { return '<undefined>'; } let stackTrace; if (cause.stackTrace && cause.stackTrace.length) { stackTrace = TestRunner.formatters.formatAsURL(cause.stackTrace[0].url) + ':' + (cause.stackTrace[0].lineNumber + 1); } return '{reason: ' + cause.reason + ', stackTrace: ' + stackTrace + '}'; }; PerformanceTestRunner.invokeWithTracing = function(functionName, callback, additionalCategories, enableJSSampling) { let categories = '-*,disabled-by-default-devtools.timeline*,devtools.timeline,blink.user_timing,toplevel'; if (additionalCategories) { categories += ',' + additionalCategories; } const timelinePanel = Timeline.TimelinePanel.TimelinePanel.instance(); const timelineController = PerformanceTestRunner.createTimelineController(); timelinePanel.timelineController = timelineController; timelineController.startRecordingWithCategories(categories, enableJSSampling).then(tracingStarted); function tracingStarted() { timelinePanel.recordingStarted(); TestRunner.callFunctionInPageAsync(functionName).then(onPageActionsDone); } function onPageActionsDone() { timelineController.stopRecording().then(callback); } }; PerformanceTestRunner.performanceModel = function() { return Timeline.TimelinePanel.TimelinePanel.instance().performanceModel; }; PerformanceTestRunner.traceEngineParsedTrace = function() { return Timeline.TimelinePanel.TimelinePanel.instance().getParsedTraceForLayoutTests(); }; PerformanceTestRunner.traceEngineRawEvents = function() { return Timeline.TimelinePanel.TimelinePanel.instance().getTraceEngineRawTraceEventsForLayoutTests(); }; // NOTE: if you are here and trying to use this method, please think first if // you can instead add a unit test to the DevTools repository. That is // preferred to layout tests, if possible. PerformanceTestRunner.createTraceEngineDataFromEvents = async function(events) { const model = Trace.TraceModel.Model.createWithAllHandlers(Trace.Types.Configuration.defaults()); await model.parse(events); // Model only has one trace, so we can hardcode 0 here to get the latest // result. return model.parsedTrace(0); }; PerformanceTestRunner.createTimelineController = function() { const controller = new Timeline.TimelineController.TimelineController( SDK.TargetManager.TargetManager.instance().primaryPageTarget(), Timeline.TimelinePanel.TimelinePanel.instance()); controller.tracingManager = TestRunner.tracingManager; return controller; }; PerformanceTestRunner.runWhenTimelineIsReady = function(callback) { TestRunner.addSniffer(Timeline.TimelinePanel.TimelinePanel.instance(), 'loadingCompleteForTest', () => callback()); }; PerformanceTestRunner.startTimeline = function() { const panel = Timeline.TimelinePanel.TimelinePanel.instance(); panel.toggleRecording(); return TestRunner.addSnifferPromise(panel, 'recordingStarted'); }; PerformanceTestRunner.stopTimeline = async function() { await Timeline.TimelinePanel.TimelinePanel.instance().toggleRecording(); }; PerformanceTestRunner.runPerfTraceWithReload = async function() { await PerformanceTestRunner.startTimeline(); await TestRunner.reloadPagePromise(); await PerformanceTestRunner.stopTimeline(); }; PerformanceTestRunner.getTimelineWidget = async function() { return await UI.ViewManager.ViewManager.instance().view('timeline').widget(); }; PerformanceTestRunner.getNetworkFlameChartElement = async function() { const widget = await PerformanceTestRunner.getTimelineWidget(); return widget.flameChart.networkFlameChart.contentElement; }; PerformanceTestRunner.getMainFlameChartElement = async function() { const widget = await PerformanceTestRunner.getTimelineWidget(); return widget.flameChart.mainFlameChart.contentElement; }; PerformanceTestRunner.evaluateWithTimeline = async function(actions) { await PerformanceTestRunner.startTimeline(); await TestRunner.evaluateInPageAnonymously(actions); await PerformanceTestRunner.stopTimeline(); }; PerformanceTestRunner.invokeAsyncWithTimeline = async function(functionName) { await PerformanceTestRunner.startTimeline(); await TestRunner.callFunctionInPageAsync(functionName); await PerformanceTestRunner.stopTimeline(); }; PerformanceTestRunner.performActionsAndPrint = async function(actions, typeName, includeTimeStamps) { await PerformanceTestRunner.evaluateWithTimeline(actions); await PerformanceTestRunner.printTimelineRecordsWithDetails(typeName); if (includeTimeStamps) { TestRunner.addResult('Timestamp records: '); PerformanceTestRunner.printTimestampRecords(typeName); } TestRunner.completeTest(); }; PerformanceTestRunner.walkTimelineEventTreeUnderNode = async function(callback, root, level) { const event = root.event; if (event) { await callback(event, level, root); } for (const child of root.children().values()) { await PerformanceTestRunner.walkTimelineEventTreeUnderNode(callback, child, (level || 0) + 1); } }; PerformanceTestRunner.forAllEvents = async function(events, callback) { const eventStack = []; for (const event of events) { while (eventStack.length && eventStack[eventStack.length - 1].endTime <= event.startTime) { eventStack.pop(); } await callback(event, eventStack); if (event.endTime) { eventStack.push(event); } } }; PerformanceTestRunner.printTraceEventProperties = function(traceEvent) { TestRunner.addResult(traceEvent.name + ' Properties:'); const data = traceEvent.args['beginData'] || traceEvent.args['data']; const frameId = data && data['frame']; const object = { data: traceEvent.args['data'] || traceEvent.args, endTime: traceEvent.endTime || traceEvent.startTime, frameId, startTime: traceEvent.startTime, type: traceEvent.name }; for (const field in object) { if (object[field] === null || object[field] === undefined) { delete object[field]; } } TestRunner.addObject(object, PerformanceTestRunner.timelinePropertyFormatters); }; PerformanceTestRunner.printTraceEventPropertiesWithDetails = async function(event) { PerformanceTestRunner.printTraceEventProperties(event); const details = await Timeline.TimelineUIUtils.TimelineUIUtils.buildDetailsTextForTraceEvent( event, SDK.TargetManager.TargetManager.instance().primaryPageTarget(), new Components.Linkifier.Linkifier()); TestRunner.waitForPendingLiveLocationUpdates(); TestRunner.addResult(`Text details for ${event.name}: ${details}`); }; PerformanceTestRunner.findChildEvent = function(events, parentIndex, name) { const endTime = events[parentIndex].endTime; for (let i = parentIndex + 1; i < events.length && (!events[i].endTime || events[i].endTime <= endTime); ++i) { if (events[i].name === name) { return events[i]; } } return null; }; PerformanceTestRunner.dumpFrame = function(frame) { const fieldsToDump = [ 'cpuTime', 'duration', 'startTime', 'endTime', 'id', 'mainThreadFrameId', 'timeByCategory', 'other', 'scripting', 'painting', 'rendering', 'committedFrom', 'idle' ]; function formatFields(object) { const result = {}; for (const key in object) { if (fieldsToDump.indexOf(key) < 0) { continue; } let value = object[key]; if (typeof value === 'number') { value = Number(value.toFixed(7)); } else if (typeof value === 'object' && value) { value = formatFields(value); } result[key] = value; } return result; } TestRunner.addObject(formatFields(frame)); }; PerformanceTestRunner.dumpFlameChartProvider = function(provider, includeGroups) { const includeGroupsSet = includeGroups && new Set(includeGroups); const timelineData = provider.timelineData(); const stackDepth = provider.maxStackDepth(); const entriesByLevel = new Platform.MapUtilities.Multimap(); for (let i = 0; i < timelineData.entryLevels.length; ++i) { entriesByLevel.set(timelineData.entryLevels[i], i); } for (let groupIndex = 0; groupIndex < timelineData.groups.length; ++groupIndex) { const group = timelineData.groups[groupIndex]; if (includeGroupsSet && !includeGroupsSet.has(group.name)) { continue; } const maxLevel = (groupIndex + 1 < timelineData.groups.length ? timelineData.groups[groupIndex + 1].startLevel : stackDepth); TestRunner.addResult(`Group: ${group.name}`); for (let level = group.startLevel; level < maxLevel; ++level) { TestRunner.addResult(`Level ${level - group.startLevel}`); const entries = entriesByLevel.get(level); for (const index of entries) { const title = provider.entryTitle(index); const color = provider.entryColor(index); TestRunner.addResult(`${title} (${color})`); } } } }; PerformanceTestRunner.dumpTimelineFlameChart = function(includeGroups) { const provider = Timeline.TimelinePanel.TimelinePanel.instance().flameChart.mainDataProvider; TestRunner.addResult('Timeline Flame Chart'); PerformanceTestRunner.dumpFlameChartProvider(provider, includeGroups); }; PerformanceTestRunner.loadTimeline = async function(timelineData) { await Timeline.TimelinePanel.TimelinePanel.instance().loadFromFile(new Blob([timelineData], {type: 'text/plain'})); }; TestRunner.deprecatedInitAsync(` function wrapCallFunctionForTimeline(f) { let script = document.createElement('script'); script.textContent = '(' + f.toString() + ')()\\n//# sourceURL=wrapCallFunctionForTimeline.js'; document.body.appendChild(script); } function generateFrames(count) { let promise = Promise.resolve(); for (let i = count; i > 0; --i) promise = promise.then(changeBackgroundAndWaitForFrame.bind(null, i)); return promise; function changeBackgroundAndWaitForFrame(i) { document.body.style.backgroundColor = (i & 1 ? 'rgb(200, 200, 200)' : 'rgb(240, 240, 240)'); return waitForFrame(); } } function waitForFrame() { let callback; let promise = new Promise(fulfill => callback = fulfill); if (window.testRunner) testRunner.updateAllLifecyclePhasesAndCompositeThen(callback); else window.requestAnimationFrame(callback); return promise; } `);