| // Copyright 2017 The Chromium Authors. All |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview using private properties isn't a Closure violation in tests. |
| * @suppress {accessControls} |
| */ |
| |
| PerformanceTestRunner.timelinePropertyFormatters = { |
| children: 'formatAsTypeName', |
| endTime: 'formatAsTypeName', |
| requestId: 'formatAsTypeName', |
| startTime: '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.createTracingModel = function(events) { |
| const model = new SDK.TracingModel(new Bindings.TempFileBackingStorage('tracing')); |
| model.addEvents(events); |
| model.tracingComplete(); |
| return model; |
| }; |
| |
| PerformanceTestRunner.tracingModel = function() { |
| return UI.panels.timeline._performanceModel.tracingModel(); |
| }; |
| |
| PerformanceTestRunner.invokeWithTracing = function(functionName, callback, additionalCategories, enableJSSampling) { |
| let categories = '-*,disabled-by-default-devtools.timeline*,devtools.timeline,blink.user_timing,' + |
| SDK.TracingModel.LegacyTopLevelEventCategory; |
| |
| if (additionalCategories) { |
| categories += ',' + additionalCategories; |
| } |
| |
| const timelinePanel = UI.panels.timeline; |
| const timelineController = PerformanceTestRunner.createTimelineController(); |
| timelinePanel._timelineController = timelineController; |
| timelineController._startRecordingWithCategories(categories, enableJSSampling).then(tracingStarted); |
| |
| function tracingStarted() { |
| timelinePanel._recordingStarted(); |
| TestRunner.callFunctionInPageAsync(functionName).then(onPageActionsDone); |
| } |
| |
| function onPageActionsDone() { |
| PerformanceTestRunner.runWhenTimelineIsReady(callback); |
| timelineController.stopRecording(); |
| } |
| }; |
| |
| PerformanceTestRunner.performanceModel = function() { |
| return UI.panels.timeline._performanceModel; |
| }; |
| |
| PerformanceTestRunner.timelineModel = function() { |
| return PerformanceTestRunner.performanceModel().timelineModel(); |
| }; |
| |
| PerformanceTestRunner.timelineFrameModel = function() { |
| return PerformanceTestRunner.performanceModel().frameModel(); |
| }; |
| |
| PerformanceTestRunner.createPerformanceModelWithEvents = function(events) { |
| const tracingModel = new SDK.TracingModel(new Bindings.TempFileBackingStorage('tracing')); |
| tracingModel.addEvents(events); |
| tracingModel.tracingComplete(); |
| const performanceModel = new Timeline.PerformanceModel(); |
| performanceModel.setTracingModel(tracingModel); |
| UI.panels.timeline._performanceModel = performanceModel; |
| UI.panels.timeline._applyFilters(performanceModel); |
| return performanceModel; |
| }; |
| |
| PerformanceTestRunner.createTimelineController = function() { |
| const controller = new Timeline.TimelineController(SDK.targetManager.mainTarget(), UI.panels.timeline); |
| controller._tracingManager = TestRunner.tracingManager; |
| return controller; |
| }; |
| |
| PerformanceTestRunner.runWhenTimelineIsReady = function(callback) { |
| TestRunner.addSniffer(UI.panels.timeline, 'loadingComplete', () => callback()); |
| }; |
| |
| PerformanceTestRunner.startTimeline = function() { |
| const panel = UI.panels.timeline; |
| panel._toggleRecording(); |
| return TestRunner.addSnifferPromise(panel, '_recordingStarted'); |
| }; |
| |
| PerformanceTestRunner.stopTimeline = function() { |
| return new Promise(resolve => { |
| PerformanceTestRunner.runWhenTimelineIsReady(resolve); |
| UI.panels.timeline._toggleRecording(); |
| }); |
| }; |
| |
| 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); |
| PerformanceTestRunner.printTimelineRecordsWithDetails(typeName); |
| if (includeTimeStamps) { |
| TestRunner.addResult('Timestamp records: '); |
| PerformanceTestRunner.printTimestampRecords(typeName); |
| } |
| TestRunner.completeTest(); |
| }; |
| |
| PerformanceTestRunner.printTimelineRecords = function(name) { |
| for (const event of PerformanceTestRunner.timelineModel().inspectedTargetEvents()) { |
| if (event.name === name) { |
| PerformanceTestRunner.printTraceEventProperties(event); |
| } |
| } |
| }; |
| |
| PerformanceTestRunner.printTimelineRecordsWithDetails = function(name) { |
| for (const event of PerformanceTestRunner.timelineModel().inspectedTargetEvents()) { |
| if (name === event.name) { |
| PerformanceTestRunner.printTraceEventPropertiesWithDetails(event); |
| } |
| } |
| }; |
| |
| PerformanceTestRunner.walkTimelineEventTree = function(callback) { |
| const view = new Timeline.EventsTimelineTreeView(UI.panels.timeline._filters, null); |
| view.setModel(PerformanceTestRunner.performanceModel(), PerformanceTestRunner.mainTrack()); |
| const selection = Timeline.TimelineSelection.fromRange( |
| PerformanceTestRunner.timelineModel().minimumRecordTime(), |
| PerformanceTestRunner.timelineModel().maximumRecordTime()); |
| view.updateContents(selection); |
| PerformanceTestRunner.walkTimelineEventTreeUnderNode(callback, view._currentTree, 0); |
| }; |
| |
| PerformanceTestRunner.walkTimelineEventTreeUnderNode = function(callback, root, level) { |
| const event = root.event; |
| |
| if (event) { |
| callback(event, level, root); |
| } |
| |
| for (const child of root.children().values()) { |
| PerformanceTestRunner.walkTimelineEventTreeUnderNode(callback, child, (level || 0) + 1); |
| } |
| }; |
| |
| PerformanceTestRunner.printTimestampRecords = function(typeName) { |
| const dividers = PerformanceTestRunner.timelineModel().timeMarkerEvents(); |
| |
| for (const event of dividers) { |
| if (event.name === typeName) { |
| PerformanceTestRunner.printTraceEventProperties(event); |
| } |
| } |
| }; |
| |
| PerformanceTestRunner.forAllEvents = function(events, callback) { |
| const eventStack = []; |
| |
| for (const event of events) { |
| while (eventStack.length && eventStack.peekLast().endTime <= event.startTime) { |
| eventStack.pop(); |
| } |
| |
| 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: frameId, |
| stackTrace: TimelineModel.TimelineData.forEvent(traceEvent).stackTrace, |
| 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 = function(event) { |
| PerformanceTestRunner.printTraceEventProperties(event); |
| const details = Timeline.TimelineUIUtils.buildDetailsTextForTraceEvent( |
| event, SDK.targetManager.mainTarget(), new Components.Linkifier()); |
| TestRunner.addResult(`Text details for ${event.name}: ${details}`); |
| |
| if (TimelineModel.TimelineData.forEvent(event).warning) { |
| TestRunner.addResult(`${event.name} has a warning`); |
| } |
| }; |
| |
| PerformanceTestRunner.mainTrack = function() { |
| let mainTrack; |
| for (const track of PerformanceTestRunner.timelineModel().tracks()) { |
| if (track.type === TimelineModel.TimelineModel.TrackType.MainThread && track.forMainFrame) { |
| mainTrack = track; |
| } |
| } |
| return mainTrack; |
| }; |
| |
| PerformanceTestRunner.mainTrackEvents = function() { |
| return PerformanceTestRunner.mainTrack().events; |
| }; |
| |
| PerformanceTestRunner.findTimelineEvent = function(name, index) { |
| return PerformanceTestRunner.mainTrackEvents().filter(e => e.name === name)[index || 0]; |
| }; |
| |
| 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.dumpInvalidations = function(recordType, index, comment) { |
| const event = PerformanceTestRunner.findTimelineEvent(recordType, index || 0); |
| |
| TestRunner.addArray( |
| TimelineModel.InvalidationTracker.invalidationEventsFor(event), PerformanceTestRunner.InvalidationFormatters, '', |
| comment); |
| }; |
| |
| PerformanceTestRunner.dumpFlameChartProvider = function(provider, includeGroups) { |
| const includeGroupsSet = includeGroups && new Set(includeGroups); |
| const timelineData = provider.timelineData(); |
| const stackDepth = provider.maxStackDepth(); |
| const entriesByLevel = new Platform.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 = UI.panels.timeline._flameChart._mainDataProvider; |
| TestRunner.addResult('Timeline Flame Chart'); |
| PerformanceTestRunner.dumpFlameChartProvider(provider, includeGroups); |
| }; |
| |
| PerformanceTestRunner.loadTimeline = function(timelineData) { |
| const promise = new Promise(fulfill => PerformanceTestRunner.runWhenTimelineIsReady(fulfill)); |
| |
| UI.panels.timeline._loadFromFile(new Blob([timelineData], {type: 'text/plain'})); |
| |
| return promise; |
| }; |
| |
| 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.capturePixelsAsyncThen(callback); |
| else |
| window.requestAnimationFrame(callback); |
| |
| return promise; |
| } |
| `); |