mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-08 20:58:24 +08:00
These were necessary during the migration towards TypeScript, but can be removed now that Closure is gone. R=jacktfranklin@chromium.org Bug: 1011811 Change-Id: Ie5ceb46fae590689762f7a55ebed8d4117b077b5 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2624712 Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org> Auto-Submit: Tim van der Lippe <tvanderlippe@chromium.org> Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
318 lines
9.4 KiB
JavaScript
318 lines
9.4 KiB
JavaScript
// 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 Bindings from '../bindings/bindings.js'; // eslint-disable-line no-unused-vars
|
|
import * as Common from '../common/common.js';
|
|
import * as SDK from '../sdk/sdk.js';
|
|
import * as TimelineModel from '../timeline_model/timeline_model.js';
|
|
|
|
import {TimelineUIUtils} from './TimelineUIUtils.js';
|
|
|
|
export class PerformanceModel extends Common.ObjectWrapper.ObjectWrapper {
|
|
constructor() {
|
|
super();
|
|
/** @type {?SDK.SDKModel.Target} */
|
|
this._mainTarget = null;
|
|
/** @type {?SDK.TracingModel.TracingModel} */
|
|
this._tracingModel = null;
|
|
/** @type {!Array<!TimelineModel.TimelineModelFilter.TimelineModelFilter>} */
|
|
this._filters = [];
|
|
|
|
this._timelineModel = new TimelineModel.TimelineModel.TimelineModelImpl();
|
|
this._frameModel = new TimelineModel.TimelineFrameModel.TimelineFrameModel(
|
|
event => TimelineUIUtils.eventStyle(event).category.name);
|
|
/** @type {?SDK.FilmStripModel.FilmStripModel} */
|
|
this._filmStripModel = null;
|
|
/** @type {!TimelineModel.TimelineIRModel.TimelineIRModel} */
|
|
this._irModel = new TimelineModel.TimelineIRModel.TimelineIRModel();
|
|
|
|
/** @type {!Window} */
|
|
this._window = {left: 0, right: Infinity};
|
|
|
|
/** @type {!Array<!{title: string, model: !SDK.TracingModel.TracingModel, timeOffset: number}>} */
|
|
this._extensionTracingModels = [];
|
|
/** @type {number|undefined} */
|
|
this._recordStartTime = undefined;
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.SDKModel.Target} target
|
|
*/
|
|
setMainTarget(target) {
|
|
this._mainTarget = target;
|
|
}
|
|
|
|
/**
|
|
* @return {?SDK.SDKModel.Target}
|
|
*/
|
|
mainTarget() {
|
|
return this._mainTarget;
|
|
}
|
|
|
|
/**
|
|
* @param {number} time
|
|
*/
|
|
setRecordStartTime(time) {
|
|
this._recordStartTime = time;
|
|
}
|
|
|
|
/**
|
|
* @return {number|undefined}
|
|
*/
|
|
recordStartTime() {
|
|
return this._recordStartTime;
|
|
}
|
|
|
|
/**
|
|
* @param {!Array<!TimelineModel.TimelineModelFilter.TimelineModelFilter>} filters
|
|
*/
|
|
setFilters(filters) {
|
|
this._filters = filters;
|
|
}
|
|
|
|
/**
|
|
* @return {!Array<!TimelineModel.TimelineModelFilter.TimelineModelFilter>}
|
|
*/
|
|
filters() {
|
|
return this._filters;
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.TracingModel.Event} event
|
|
* @return {boolean}
|
|
*/
|
|
isVisible(event) {
|
|
return this._filters.every(f => f.accept(event));
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.TracingModel.TracingModel} model
|
|
*/
|
|
setTracingModel(model) {
|
|
this._tracingModel = model;
|
|
this._timelineModel.setEvents(model);
|
|
|
|
let inputEvents = null;
|
|
let animationEvents = null;
|
|
for (const track of this._timelineModel.tracks()) {
|
|
if (track.type === TimelineModel.TimelineModel.TrackType.Input) {
|
|
inputEvents = track.asyncEvents;
|
|
}
|
|
if (track.type === TimelineModel.TimelineModel.TrackType.Animation) {
|
|
animationEvents = track.asyncEvents;
|
|
}
|
|
}
|
|
if (inputEvents || animationEvents) {
|
|
this._irModel.populate(inputEvents || [], animationEvents || []);
|
|
}
|
|
|
|
const mainTracks = this._timelineModel.tracks().filter(
|
|
track => track.type === TimelineModel.TimelineModel.TrackType.MainThread && track.forMainFrame &&
|
|
track.events.length);
|
|
const threadData = mainTracks.map(track => {
|
|
const event = track.events[0];
|
|
return {thread: event.thread, time: event.startTime};
|
|
});
|
|
this._frameModel.addTraceEvents(this._mainTarget, this._timelineModel.inspectedTargetEvents(), threadData);
|
|
|
|
for (const entry of this._extensionTracingModels) {
|
|
entry.model.adjustTime(
|
|
this._tracingModel.minimumRecordTime() + (entry.timeOffset / 1000) -
|
|
/** @type {number} */ (this._recordStartTime));
|
|
}
|
|
this._autoWindowTimes();
|
|
}
|
|
|
|
/**
|
|
* @param {string} title
|
|
* @param {!SDK.TracingModel.TracingModel} model
|
|
* @param {number} timeOffset
|
|
*/
|
|
addExtensionEvents(title, model, timeOffset) {
|
|
this._extensionTracingModels.push({model: model, title: title, timeOffset: timeOffset});
|
|
if (!this._tracingModel) {
|
|
return;
|
|
}
|
|
model.adjustTime(
|
|
this._tracingModel.minimumRecordTime() + (timeOffset / 1000) - /** @type {number} */ (this._recordStartTime));
|
|
this.dispatchEventToListeners(Events.ExtensionDataAdded);
|
|
}
|
|
|
|
/**
|
|
* @return {!SDK.TracingModel.TracingModel}
|
|
*/
|
|
tracingModel() {
|
|
if (!this._tracingModel) {
|
|
throw 'call setTracingModel before accessing PerformanceModel';
|
|
}
|
|
return this._tracingModel;
|
|
}
|
|
|
|
/**
|
|
* @return {!TimelineModel.TimelineModel.TimelineModelImpl}
|
|
*/
|
|
timelineModel() {
|
|
return this._timelineModel;
|
|
}
|
|
|
|
/**
|
|
* @return {!SDK.FilmStripModel.FilmStripModel} filmStripModel
|
|
*/
|
|
filmStripModel() {
|
|
if (this._filmStripModel) {
|
|
return this._filmStripModel;
|
|
}
|
|
if (!this._tracingModel) {
|
|
throw 'call setTracingModel before accessing PerformanceModel';
|
|
}
|
|
this._filmStripModel = new SDK.FilmStripModel.FilmStripModel(this._tracingModel);
|
|
return this._filmStripModel;
|
|
}
|
|
|
|
/**
|
|
* @return {!Array<!TimelineModel.TimelineFrameModel.TimelineFrame>} frames
|
|
*/
|
|
frames() {
|
|
return this._frameModel.frames();
|
|
}
|
|
|
|
/**
|
|
* @return {!TimelineModel.TimelineFrameModel.TimelineFrameModel} frames
|
|
*/
|
|
frameModel() {
|
|
return this._frameModel;
|
|
}
|
|
|
|
/**
|
|
* @return {!Array<!Common.SegmentedRange.Segment>}
|
|
*/
|
|
interactionRecords() {
|
|
return this._irModel.interactionRecords();
|
|
}
|
|
|
|
/**
|
|
* @return {!Array<!{title: string, model: !SDK.TracingModel.TracingModel}>}
|
|
*/
|
|
extensionInfo() {
|
|
return this._extensionTracingModels;
|
|
}
|
|
|
|
dispose() {
|
|
if (this._tracingModel) {
|
|
this._tracingModel.dispose();
|
|
}
|
|
for (const extensionEntry of this._extensionTracingModels) {
|
|
extensionEntry.model.dispose();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!TimelineModel.TimelineFrameModel.TimelineFrame} frame
|
|
* @return {?SDK.FilmStripModel.Frame}
|
|
*/
|
|
filmStripModelFrame(frame) {
|
|
// For idle frames, look at the state at the beginning of the frame.
|
|
const screenshotTime = frame.idle ? frame.startTime : frame.endTime;
|
|
const filmStripModel = /** @type {!SDK.FilmStripModel.FilmStripModel} */ (this._filmStripModel);
|
|
const filmStripFrame = filmStripModel.frameByTimestamp(screenshotTime);
|
|
return filmStripFrame && filmStripFrame.timestamp - frame.endTime < 10 ? filmStripFrame : null;
|
|
}
|
|
|
|
/**
|
|
* @param {!Common.StringOutputStream.OutputStream} stream
|
|
* @return {!Promise<?DOMError>}
|
|
*/
|
|
save(stream) {
|
|
if (!this._tracingModel) {
|
|
throw 'call setTracingModel before accessing PerformanceModel';
|
|
}
|
|
const backingStorage =
|
|
/** @type {!Bindings.TempFile.TempFileBackingStorage} */ (this._tracingModel.backingStorage());
|
|
return backingStorage.writeToStream(stream);
|
|
}
|
|
|
|
/**
|
|
* @param {!Window} window
|
|
* @param {boolean=} animate
|
|
*/
|
|
setWindow(window, animate) {
|
|
this._window = window;
|
|
this.dispatchEventToListeners(Events.WindowChanged, {window, animate});
|
|
}
|
|
|
|
/**
|
|
* @return {!Window}
|
|
*/
|
|
window() {
|
|
return this._window;
|
|
}
|
|
|
|
_autoWindowTimes() {
|
|
const timelineModel = this._timelineModel;
|
|
/** @type {!Array<!SDK.TracingModel.Event>} */
|
|
let tasks = [];
|
|
for (const track of timelineModel.tracks()) {
|
|
// Deliberately pick up last main frame's track.
|
|
if (track.type === TimelineModel.TimelineModel.TrackType.MainThread && track.forMainFrame) {
|
|
tasks = track.tasks;
|
|
}
|
|
}
|
|
if (!tasks.length) {
|
|
this.setWindow({left: timelineModel.minimumRecordTime(), right: timelineModel.maximumRecordTime()});
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* @param {number} startIndex
|
|
* @param {number} stopIndex
|
|
* @return {number}
|
|
*/
|
|
function findLowUtilizationRegion(startIndex, stopIndex) {
|
|
const threshold = 0.1;
|
|
let cutIndex = startIndex;
|
|
let cutTime = (tasks[cutIndex].startTime + /** @type {number} */ (tasks[cutIndex].endTime)) / 2;
|
|
let usedTime = 0;
|
|
const step = Math.sign(stopIndex - startIndex);
|
|
for (let i = startIndex; i !== stopIndex; i += step) {
|
|
const task = tasks[i];
|
|
const taskTime = (task.startTime + /** @type {number} */ (task.endTime)) / 2;
|
|
const interval = Math.abs(cutTime - taskTime);
|
|
if (usedTime < threshold * interval) {
|
|
cutIndex = i;
|
|
cutTime = taskTime;
|
|
usedTime = 0;
|
|
}
|
|
usedTime += /** @type {number} */ (task.duration);
|
|
}
|
|
return cutIndex;
|
|
}
|
|
const rightIndex = findLowUtilizationRegion(tasks.length - 1, 0);
|
|
const leftIndex = findLowUtilizationRegion(0, rightIndex);
|
|
let leftTime = tasks[leftIndex].startTime;
|
|
let rightTime = /** @type {number} */ (tasks[rightIndex].endTime);
|
|
const span = rightTime - leftTime;
|
|
const totalSpan = timelineModel.maximumRecordTime() - timelineModel.minimumRecordTime();
|
|
if (span < totalSpan * 0.1) {
|
|
leftTime = timelineModel.minimumRecordTime();
|
|
rightTime = timelineModel.maximumRecordTime();
|
|
} else {
|
|
leftTime = Math.max(leftTime - 0.05 * span, timelineModel.minimumRecordTime());
|
|
rightTime = Math.min(rightTime + 0.05 * span, timelineModel.maximumRecordTime());
|
|
}
|
|
this.setWindow({left: leftTime, right: rightTime});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @enum {symbol}
|
|
*/
|
|
export const Events = {
|
|
ExtensionDataAdded: Symbol('ExtensionDataAdded'),
|
|
WindowChanged: Symbol('WindowChanged')
|
|
};
|
|
|
|
/** @typedef {!{left: number, right: number}} */
|
|
// @ts-ignore typedef
|
|
export let Window;
|