mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-10 21:54:47 +08:00
Fixed: 1324090 Change-Id: I7eed96829032e45addc9960abf3cb7d538ab12ce Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3635178 Commit-Queue: Jack Franklin <jacktfranklin@chromium.org> Reviewed-by: Andres Olivares <andoli@chromium.org>
262 lines
9.3 KiB
TypeScript
262 lines
9.3 KiB
TypeScript
// 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 Common from '../../core/common/common.js';
|
|
import * as SDK from '../../core/sdk/sdk.js';
|
|
import type * as Bindings from '../../models/bindings/bindings.js';
|
|
import * as TimelineModel from '../../models/timeline_model/timeline_model.js';
|
|
|
|
import {TimelineUIUtils} from './TimelineUIUtils.js';
|
|
|
|
export class PerformanceModel extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
|
|
private mainTargetInternal: SDK.Target.Target|null;
|
|
private tracingModelInternal: SDK.TracingModel.TracingModel|null;
|
|
private filtersInternal: TimelineModel.TimelineModelFilter.TimelineModelFilter[];
|
|
private readonly timelineModelInternal: TimelineModel.TimelineModel.TimelineModelImpl;
|
|
private readonly frameModelInternal: TimelineModel.TimelineFrameModel.TimelineFrameModel;
|
|
private filmStripModelInternal: SDK.FilmStripModel.FilmStripModel|null;
|
|
private readonly irModel: TimelineModel.TimelineIRModel.TimelineIRModel;
|
|
private windowInternal: Window;
|
|
private readonly extensionTracingModels: {
|
|
title: string,
|
|
model: SDK.TracingModel.TracingModel,
|
|
timeOffset: number,
|
|
}[];
|
|
private recordStartTimeInternal?: number;
|
|
|
|
constructor() {
|
|
super();
|
|
this.mainTargetInternal = null;
|
|
this.tracingModelInternal = null;
|
|
this.filtersInternal = [];
|
|
|
|
this.timelineModelInternal = new TimelineModel.TimelineModel.TimelineModelImpl();
|
|
this.frameModelInternal = new TimelineModel.TimelineFrameModel.TimelineFrameModel(
|
|
event => TimelineUIUtils.eventStyle(event).category.name);
|
|
this.filmStripModelInternal = null;
|
|
this.irModel = new TimelineModel.TimelineIRModel.TimelineIRModel();
|
|
|
|
this.windowInternal = {left: 0, right: Infinity};
|
|
|
|
this.extensionTracingModels = [];
|
|
this.recordStartTimeInternal = undefined;
|
|
}
|
|
|
|
setMainTarget(target: SDK.Target.Target): void {
|
|
this.mainTargetInternal = target;
|
|
}
|
|
|
|
mainTarget(): SDK.Target.Target|null {
|
|
return this.mainTargetInternal;
|
|
}
|
|
|
|
setRecordStartTime(time: number): void {
|
|
this.recordStartTimeInternal = time;
|
|
}
|
|
|
|
recordStartTime(): number|undefined {
|
|
return this.recordStartTimeInternal;
|
|
}
|
|
|
|
setFilters(filters: TimelineModel.TimelineModelFilter.TimelineModelFilter[]): void {
|
|
this.filtersInternal = filters;
|
|
}
|
|
|
|
filters(): TimelineModel.TimelineModelFilter.TimelineModelFilter[] {
|
|
return this.filtersInternal;
|
|
}
|
|
|
|
isVisible(event: SDK.TracingModel.Event): boolean {
|
|
return this.filtersInternal.every(f => f.accept(event));
|
|
}
|
|
|
|
setTracingModel(model: SDK.TracingModel.TracingModel): void {
|
|
this.tracingModelInternal = model;
|
|
this.timelineModelInternal.setEvents(model);
|
|
|
|
let animationEvents: SDK.TracingModel.AsyncEvent[]|null = null;
|
|
for (const track of this.timelineModelInternal.tracks()) {
|
|
if (track.type === TimelineModel.TimelineModel.TrackType.Animation) {
|
|
animationEvents = track.asyncEvents;
|
|
}
|
|
}
|
|
if (animationEvents) {
|
|
this.irModel.populate([], animationEvents || []);
|
|
}
|
|
|
|
const mainTracks = this.timelineModelInternal.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.frameModelInternal.addTraceEvents(
|
|
this.mainTargetInternal, this.timelineModelInternal.inspectedTargetEvents(), threadData);
|
|
|
|
for (const entry of this.extensionTracingModels) {
|
|
entry.model.adjustTime(
|
|
this.tracingModelInternal.minimumRecordTime() + (entry.timeOffset / 1000) -
|
|
(this.recordStartTimeInternal as number));
|
|
}
|
|
this.autoWindowTimes();
|
|
}
|
|
|
|
addExtensionEvents(title: string, model: SDK.TracingModel.TracingModel, timeOffset: number): void {
|
|
this.extensionTracingModels.push({model: model, title: title, timeOffset: timeOffset});
|
|
if (!this.tracingModelInternal) {
|
|
return;
|
|
}
|
|
model.adjustTime(
|
|
this.tracingModelInternal.minimumRecordTime() + (timeOffset / 1000) - (this.recordStartTimeInternal as number));
|
|
this.dispatchEventToListeners(Events.ExtensionDataAdded);
|
|
}
|
|
|
|
tracingModel(): SDK.TracingModel.TracingModel {
|
|
if (!this.tracingModelInternal) {
|
|
throw 'call setTracingModel before accessing PerformanceModel';
|
|
}
|
|
return this.tracingModelInternal;
|
|
}
|
|
|
|
timelineModel(): TimelineModel.TimelineModel.TimelineModelImpl {
|
|
return this.timelineModelInternal;
|
|
}
|
|
|
|
filmStripModel(): SDK.FilmStripModel.FilmStripModel {
|
|
if (this.filmStripModelInternal) {
|
|
return this.filmStripModelInternal;
|
|
}
|
|
if (!this.tracingModelInternal) {
|
|
throw 'call setTracingModel before accessing PerformanceModel';
|
|
}
|
|
this.filmStripModelInternal = new SDK.FilmStripModel.FilmStripModel(this.tracingModelInternal);
|
|
return this.filmStripModelInternal;
|
|
}
|
|
|
|
frames(): TimelineModel.TimelineFrameModel.TimelineFrame[] {
|
|
return this.frameModelInternal.getFrames();
|
|
}
|
|
|
|
frameModel(): TimelineModel.TimelineFrameModel.TimelineFrameModel {
|
|
return this.frameModelInternal;
|
|
}
|
|
|
|
interactionRecords(): Common.SegmentedRange.Segment<TimelineModel.TimelineIRModel.Phases>[] {
|
|
return this.irModel.interactionRecords();
|
|
}
|
|
|
|
extensionInfo(): {
|
|
title: string,
|
|
model: SDK.TracingModel.TracingModel,
|
|
}[] {
|
|
return this.extensionTracingModels;
|
|
}
|
|
|
|
dispose(): void {
|
|
if (this.tracingModelInternal) {
|
|
this.tracingModelInternal.dispose();
|
|
}
|
|
for (const extensionEntry of this.extensionTracingModels) {
|
|
extensionEntry.model.dispose();
|
|
}
|
|
}
|
|
|
|
filmStripModelFrame(frame: TimelineModel.TimelineFrameModel.TimelineFrame): SDK.FilmStripModel.Frame|null {
|
|
// For idle frames, look at the state at the beginning of the frame.
|
|
const screenshotTime = frame.idle ? frame.startTime : frame.endTime;
|
|
const filmStripModel = (this.filmStripModelInternal as SDK.FilmStripModel.FilmStripModel);
|
|
const filmStripFrame = filmStripModel.frameByTimestamp(screenshotTime);
|
|
return filmStripFrame && filmStripFrame.timestamp - frame.endTime < 10 ? filmStripFrame : null;
|
|
}
|
|
|
|
save(stream: Common.StringOutputStream.OutputStream): Promise<DOMError|null> {
|
|
if (!this.tracingModelInternal) {
|
|
throw 'call setTracingModel before accessing PerformanceModel';
|
|
}
|
|
const backingStorage = (this.tracingModelInternal.backingStorage() as Bindings.TempFile.TempFileBackingStorage);
|
|
return backingStorage.writeToStream(stream);
|
|
}
|
|
|
|
setWindow(window: Window, animate?: boolean): void {
|
|
this.windowInternal = window;
|
|
this.dispatchEventToListeners(Events.WindowChanged, {window, animate});
|
|
}
|
|
|
|
window(): Window {
|
|
return this.windowInternal;
|
|
}
|
|
|
|
private autoWindowTimes(): void {
|
|
const timelineModel = this.timelineModelInternal;
|
|
let tasks: SDK.TracingModel.Event[] = [];
|
|
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;
|
|
}
|
|
|
|
function findLowUtilizationRegion(startIndex: number, stopIndex: number): number {
|
|
const threshold = 0.1;
|
|
let cutIndex = startIndex;
|
|
let cutTime = (tasks[cutIndex].startTime + (tasks[cutIndex].endTime as number)) / 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 + (task.endTime as number)) / 2;
|
|
const interval = Math.abs(cutTime - taskTime);
|
|
if (usedTime < threshold * interval) {
|
|
cutIndex = i;
|
|
cutTime = taskTime;
|
|
usedTime = 0;
|
|
}
|
|
usedTime += (task.duration as number);
|
|
}
|
|
return cutIndex;
|
|
}
|
|
const rightIndex = findLowUtilizationRegion(tasks.length - 1, 0);
|
|
const leftIndex = findLowUtilizationRegion(0, rightIndex);
|
|
let leftTime: number = tasks[leftIndex].startTime;
|
|
let rightTime: number = (tasks[rightIndex].endTime as number);
|
|
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});
|
|
}
|
|
}
|
|
|
|
// TODO(crbug.com/1167717): Make this a const enum again
|
|
// eslint-disable-next-line rulesdir/const_enum
|
|
export enum Events {
|
|
ExtensionDataAdded = 'ExtensionDataAdded',
|
|
WindowChanged = 'WindowChanged',
|
|
}
|
|
|
|
export interface WindowChangedEvent {
|
|
window: Window;
|
|
animate: boolean|undefined;
|
|
}
|
|
|
|
export type EventTypes = {
|
|
[Events.ExtensionDataAdded]: void,
|
|
[Events.WindowChanged]: WindowChangedEvent,
|
|
};
|
|
|
|
export interface Window {
|
|
left: number;
|
|
right: number;
|
|
}
|