Merge Android 14 QPR1

Merged-In: Ic7232e2b9b8090ec108a2e8a945d07b52389e26a
Bug: 315507370
Change-Id: I53fabecd504cb3aaf6b1f9c7a9335eacd4309b28
This commit is contained in:
Xin Li
2023-12-08 13:13:08 -08:00
112 changed files with 2688 additions and 1431 deletions

View File

@@ -2,7 +2,7 @@ Pkg.Desc=Android SDK Platform ${PLATFORM_VERSION}
Pkg.UserSrc=false
Platform.Version=${PLATFORM_VERSION}
Platform.CodeName=
Pkg.Revision=1
Pkg.Revision=2
AndroidVersion.ApiLevel=${PLATFORM_SDK_VERSION}
AndroidVersion.CodeName=${PLATFORM_VERSION_CODENAME}
AndroidVersion.ExtensionLevel=${PLATFORM_SDK_EXTENSION_VERSION}

View File

@@ -30,7 +30,7 @@ exports.config = {
args: ['--headless', '--disable-gpu', '--window-size=1280x1024'],
},
},
chromeDriver: './node_modules/webdriver-manager/selenium/chromedriver_113.0.5672.63',
chromeDriver: './node_modules/webdriver-manager/selenium/chromedriver_114.0.5735.90',
allScriptsTimeout: 10000,
getPageTimeout: 10000,

View File

@@ -58,6 +58,7 @@ import {ViewerScreenRecordingComponent} from 'viewers/viewer_screen_recording/vi
import {ViewerSurfaceFlingerComponent} from 'viewers/viewer_surface_flinger/viewer_surface_flinger_component';
import {ViewerTransactionsComponent} from 'viewers/viewer_transactions/viewer_transactions_component';
import {ViewerTransitionsComponent} from 'viewers/viewer_transitions/viewer_transitions_component';
import {ViewerViewCaptureComponent} from 'viewers/viewer_view_capture/viewer_view_capture_component';
import {ViewerWindowManagerComponent} from 'viewers/viewer_window_manager/viewer_window_manager_component';
import {AdbProxyComponent} from './components/adb_proxy_component';
import {AppComponent} from './components/app_component';
@@ -88,6 +89,7 @@ import {WebAdbComponent} from './components/web_adb_component';
ViewerTransactionsComponent,
ViewerScreenRecordingComponent,
ViewerTransitionsComponent,
ViewerViewCaptureComponent,
CollectTracesComponent,
UploadTracesComponent,
AdbProxyComponent,

View File

@@ -32,8 +32,8 @@ import {FileUtils} from 'common/file_utils';
import {PersistentStore} from 'common/persistent_store';
import {CrossToolProtocol} from 'cross_tool/cross_tool_protocol';
import {TraceDataListener} from 'interfaces/trace_data_listener';
import {LoadedTrace} from 'trace/loaded_trace';
import {Timestamp} from 'trace/timestamp';
import {Trace} from 'trace/trace';
import {TraceType} from 'trace/trace_type';
import {proxyClient, ProxyState} from 'trace_collection/proxy_client';
import {ViewerInputMethodComponent} from 'viewers/components/viewer_input_method_component';
@@ -43,6 +43,7 @@ import {ViewerScreenRecordingComponent} from 'viewers/viewer_screen_recording/vi
import {ViewerSurfaceFlingerComponent} from 'viewers/viewer_surface_flinger/viewer_surface_flinger_component';
import {ViewerTransactionsComponent} from 'viewers/viewer_transactions/viewer_transactions_component';
import {ViewerTransitionsComponent} from 'viewers/viewer_transitions/viewer_transitions_component';
import {ViewerViewCaptureComponent} from 'viewers/viewer_view_capture/viewer_view_capture_component';
import {ViewerWindowManagerComponent} from 'viewers/viewer_window_manager/viewer_window_manager_component';
import {CollectTracesComponent} from './collect_traces_component';
import {SnackBarOpener} from './snack_bar_opener';
@@ -61,7 +62,7 @@ import {UploadTracesComponent} from './upload_traces_component';
<div class="spacer">
<mat-icon
*ngIf="activeTrace"
*ngIf="dataLoaded && activeTrace"
class="icon"
[matTooltip]="TRACE_INFO[activeTrace.type].name"
[style]="{color: TRACE_INFO[activeTrace.type].color, marginRight: '0.5rem'}">
@@ -213,7 +214,7 @@ export class AppComponent implements TraceDataListener {
isDarkModeOn!: boolean;
dataLoaded = false;
activeView?: View;
activeTrace?: LoadedTrace;
activeTrace?: Trace<object>;
activeTraceFileInfo = '';
collapsedTimelineHeight = 0;
@ViewChild(UploadTracesComponent) uploadTracesComponent?: UploadTracesComponent;
@@ -284,6 +285,12 @@ export class AppComponent implements TraceDataListener {
createCustomElement(ViewerTransitionsComponent, {injector})
);
}
if (!customElements.get('viewer-view-capture')) {
customElements.define(
'viewer-view-capture',
createCustomElement(ViewerViewCaptureComponent, {injector})
);
}
}
ngAfterViewInit() {
@@ -302,7 +309,7 @@ export class AppComponent implements TraceDataListener {
}
getLoadedTraceTypes(): TraceType[] {
return this.tracePipeline.getLoadedTraces().map((trace) => trace.type);
return this.tracePipeline.getTraces().mapTrace((trace) => trace.type);
}
onTraceDataLoaded(viewers: Viewer[]) {
@@ -338,11 +345,11 @@ export class AppComponent implements TraceDataListener {
document.body.removeChild(a);
}
onActiveViewChanged(view: View) {
async onActiveViewChanged(view: View) {
this.activeView = view;
this.activeTrace = this.getActiveTrace(view);
this.activeTraceFileInfo = this.makeActiveTraceFileInfo(view);
this.timelineData.setActiveViewTraceTypes(view.dependencies);
await this.mediator.onWinscopeActiveViewChanged(view);
}
goToLink(url: string) {
@@ -356,13 +363,17 @@ export class AppComponent implements TraceDataListener {
return '';
}
return `${trace.descriptors.join(', ')}`;
return `${trace.getDescriptors().join(', ')}`;
}
private getActiveTrace(view: View): LoadedTrace | undefined {
return this.tracePipeline
.getLoadedTraces()
.find((trace) => trace.type === view.dependencies[0]);
private getActiveTrace(view: View): Trace<object> | undefined {
let activeTrace: Trace<object> | undefined;
this.tracePipeline.getTraces().forEachTrace((trace) => {
if (trace.type === view.dependencies[0]) {
activeTrace = trace;
}
});
return activeTrace;
}
private async makeTraceFilesForDownload(): Promise<File[]> {

View File

@@ -37,7 +37,7 @@ import {SingleTimelineComponent} from './single_timeline_component';
template: `
<div id="expanded-timeline-wrapper" #expandedTimelineWrapper>
<div
*ngFor="let trace of getTraces(); trackBy: trackTraceBySelectedTimestamp"
*ngFor="let trace of this.timelineData.getTraces(); trackBy: trackTraceBySelectedTimestamp"
class="timeline">
<div class="icon-wrapper">
<mat-icon
@@ -148,14 +148,6 @@ export class ExpandedTimelineComponent {
TRACE_INFO = TRACE_INFO;
getTraces(): Array<Trace<{}>> {
const traces = new Array<Trace<{}>>();
this.timelineData.getTraces().forEachTrace((trace) => {
traces.push(trace);
});
return traces;
}
@HostListener('window:resize', ['$event'])
onResize(event: Event) {
this.resizeCanvases();

View File

@@ -107,9 +107,11 @@ export class MiniTimelineComponent {
private getTracesToShow(): Traces {
const traces = new Traces();
this.selectedTraces.forEach((type) => {
traces.setTrace(type, assertDefined(this.timelineData.getTraces().getTrace(type)));
});
this.selectedTraces
.filter((type) => this.timelineData.getTraces().getTrace(type) !== undefined)
.forEach((type) => {
traces.setTrace(type, assertDefined(this.timelineData.getTraces().getTrace(type)));
});
return traces;
}

View File

@@ -30,8 +30,14 @@ import {FormControl, FormGroup, Validators} from '@angular/forms';
import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
import {TimelineData} from 'app/timeline_data';
import {TRACE_INFO} from 'app/trace_info';
import {assertDefined} from 'common/assert_utils';
import {FunctionUtils} from 'common/function_utils';
import {StringUtils} from 'common/string_utils';
import {TimeUtils} from 'common/time_utils';
import {
OnTracePositionUpdate,
TracePositionUpdateEmitter,
} from 'interfaces/trace_position_update_emitter';
import {TracePositionUpdateListener} from 'interfaces/trace_position_update_listener';
import {ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType} from 'trace/timestamp';
import {TracePosition} from 'trace/trace_position';
@@ -287,7 +293,7 @@ import {MiniTimelineComponent} from './mini_timeline_component';
`,
],
})
export class TimelineComponent implements TracePositionUpdateListener {
export class TimelineComponent implements TracePositionUpdateEmitter, TracePositionUpdateListener {
readonly TOGGLE_BUTTON_CLASS: string = 'button-toggle-expansion';
readonly MAX_SELECTED_TRACES = 3;
@@ -358,6 +364,8 @@ export class TimelineComponent implements TracePositionUpdateListener {
TRACE_INFO = TRACE_INFO;
private onTracePositionUpdateCallback: OnTracePositionUpdate = FunctionUtils.DO_NOTHING_ASYNC;
constructor(
@Inject(DomSanitizer) private sanitizer: DomSanitizer,
@Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef
@@ -381,17 +389,21 @@ export class TimelineComponent implements TracePositionUpdateListener {
this.collapsedTimelineSizeChanged.emit(height);
}
setOnTracePositionUpdate(callback: OnTracePositionUpdate) {
this.onTracePositionUpdateCallback = callback;
}
getVideoCurrentTime() {
return this.timelineData.searchCorrespondingScreenRecordingTimeSeconds(
this.getCurrentTracePosition()
);
}
private seekTimestamp: Timestamp | undefined;
private seekTracePosition?: TracePosition;
getCurrentTracePosition(): TracePosition {
if (this.seekTimestamp !== undefined) {
return TracePosition.fromTimestamp(this.seekTimestamp);
if (this.seekTracePosition) {
return this.seekTracePosition;
}
const position = this.timelineData.getCurrentPosition();
@@ -411,8 +423,9 @@ export class TimelineComponent implements TracePositionUpdateListener {
this.changeDetectorRef.detectChanges();
}
updatePosition(position: TracePosition) {
async updatePosition(position: TracePosition) {
this.timelineData.setPosition(position);
await this.onTracePositionUpdateCallback(position);
}
usingRealtime(): boolean {
@@ -420,7 +433,11 @@ export class TimelineComponent implements TracePositionUpdateListener {
}
updateSeekTimestamp(timestamp: Timestamp | undefined) {
this.seekTimestamp = timestamp;
if (timestamp) {
this.seekTracePosition = TracePosition.fromTimestamp(timestamp);
} else {
this.seekTracePosition = undefined;
}
this.updateTimeInputValuesToCurrentTimestamp();
}
@@ -464,11 +481,11 @@ export class TimelineComponent implements TracePositionUpdateListener {
}
@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
async handleKeyboardEvent(event: KeyboardEvent) {
if (event.key === 'ArrowLeft') {
this.moveToPreviousEntry();
await this.moveToPreviousEntry();
} else if (event.key === 'ArrowRight') {
this.moveToNextEntry();
await this.moveToNextEntry();
}
}
@@ -476,6 +493,9 @@ export class TimelineComponent implements TracePositionUpdateListener {
if (!this.internalActiveTrace) {
return false;
}
if (this.timelineData.getTraces().getTrace(this.internalActiveTrace) === undefined) {
return false;
}
return this.timelineData.getPreviousEntryFor(this.internalActiveTrace) !== undefined;
}
@@ -483,45 +503,50 @@ export class TimelineComponent implements TracePositionUpdateListener {
if (!this.internalActiveTrace) {
return false;
}
if (this.timelineData.getTraces().getTrace(this.internalActiveTrace) === undefined) {
return false;
}
return this.timelineData.getNextEntryFor(this.internalActiveTrace) !== undefined;
}
moveToPreviousEntry() {
async moveToPreviousEntry() {
if (!this.internalActiveTrace) {
return;
}
this.timelineData.moveToPreviousEntryFor(this.internalActiveTrace);
await this.onTracePositionUpdateCallback(assertDefined(this.timelineData.getCurrentPosition()));
}
moveToNextEntry() {
async moveToNextEntry() {
if (!this.internalActiveTrace) {
return;
}
this.timelineData.moveToNextEntryFor(this.internalActiveTrace);
await this.onTracePositionUpdateCallback(assertDefined(this.timelineData.getCurrentPosition()));
}
humanElapsedTimeInputChange(event: Event) {
async humanElapsedTimeInputChange(event: Event) {
if (event.type !== 'change') {
return;
}
const target = event.target as HTMLInputElement;
const timestamp = TimeUtils.parseHumanElapsed(target.value);
this.timelineData.setPosition(TracePosition.fromTimestamp(timestamp));
await this.updatePosition(TracePosition.fromTimestamp(timestamp));
this.updateTimeInputValuesToCurrentTimestamp();
}
humanRealTimeInputChanged(event: Event) {
async humanRealTimeInputChanged(event: Event) {
if (event.type !== 'change') {
return;
}
const target = event.target as HTMLInputElement;
const timestamp = TimeUtils.parseHumanReal(target.value);
this.timelineData.setPosition(TracePosition.fromTimestamp(timestamp));
await this.updatePosition(TracePosition.fromTimestamp(timestamp));
this.updateTimeInputValuesToCurrentTimestamp();
}
nanosecondsInputTimeChange(event: Event) {
async nanosecondsInputTimeChange(event: Event) {
if (event.type !== 'change') {
return;
}
@@ -531,7 +556,7 @@ export class TimelineComponent implements TracePositionUpdateListener {
this.timelineData.getTimestampType()!,
StringUtils.parseBigIntStrippingUnit(target.value)
);
this.timelineData.setPosition(TracePosition.fromTimestamp(timestamp));
await this.updatePosition(TracePosition.fromTimestamp(timestamp));
this.updateTimeInputValuesToCurrentTimestamp();
}
}

View File

@@ -14,10 +14,20 @@
* limitations under the License.
*/
import {
OnTracePositionUpdate,
TracePositionUpdateEmitter,
} from 'interfaces/trace_position_update_emitter';
import {TracePositionUpdateListener} from 'interfaces/trace_position_update_listener';
import {TracePosition} from 'trace/trace_position';
export class TimelineComponentStub implements TracePositionUpdateListener {
export class TimelineComponentStub
implements TracePositionUpdateEmitter, TracePositionUpdateListener
{
setOnTracePositionUpdate(callback: OnTracePositionUpdate) {
// do nothing
}
onTracePositionUpdate(position: TracePosition) {
// do nothing
}

View File

@@ -25,7 +25,7 @@ import {
import {TRACE_INFO} from 'app/trace_info';
import {TracePipeline} from 'app/trace_pipeline';
import {ProgressListener} from 'interfaces/progress_listener';
import {LoadedTrace} from 'trace/loaded_trace';
import {Trace} from 'trace/trace';
import {LoadProgressComponent} from './load_progress_component';
@Component({
@@ -57,15 +57,15 @@ import {LoadProgressComponent} from './load_progress_component';
</load-progress>
<mat-list
*ngIf="!isLoadingFiles && this.tracePipeline.getLoadedTraces().length > 0"
*ngIf="!isLoadingFiles && this.tracePipeline.getTraces().getSize() > 0"
class="uploaded-files">
<mat-list-item *ngFor="let trace of this.tracePipeline.getLoadedTraces()">
<mat-list-item *ngFor="let trace of this.tracePipeline.getTraces()">
<mat-icon matListIcon>
{{ TRACE_INFO[trace.type].icon }}
</mat-icon>
<p matLine>{{ TRACE_INFO[trace.type].name }}</p>
<p matLine *ngFor="let descriptor of trace.descriptors">{{ descriptor }}</p>
<p matLine *ngFor="let descriptor of trace.getDescriptors()">{{ descriptor }}</p>
<button color="primary" mat-icon-button (click)="onRemoveTrace($event, trace)">
<mat-icon>close</mat-icon>
@@ -73,9 +73,7 @@ import {LoadProgressComponent} from './load_progress_component';
</mat-list-item>
</mat-list>
<div
*ngIf="!isLoadingFiles && tracePipeline.getLoadedTraces().length === 0"
class="drop-info">
<div *ngIf="!isLoadingFiles && tracePipeline.getTraces().getSize() === 0" class="drop-info">
<p class="mat-body-3 icon">
<mat-icon inline fontIcon="upload"></mat-icon>
</p>
@@ -84,7 +82,7 @@ import {LoadProgressComponent} from './load_progress_component';
</mat-card-content>
<div
*ngIf="!isLoadingFiles && tracePipeline.getLoadedTraces().length > 0"
*ngIf="!isLoadingFiles && tracePipeline.getTraces().getSize() > 0"
class="trace-actions-container">
<button
color="primary"
@@ -238,10 +236,10 @@ export class UploadTracesComponent implements ProgressListener {
this.filesUploaded.emit(Array.from(droppedFiles));
}
onRemoveTrace(event: MouseEvent, trace: LoadedTrace) {
onRemoveTrace(event: MouseEvent, trace: Trace<object>) {
event.preventDefault();
event.stopPropagation();
this.tracePipeline.removeTraceFile(trace.type);
this.tracePipeline.removeTrace(trace);
this.onOperationFinished();
}

View File

@@ -22,17 +22,19 @@ import {RemoteTimestampReceiver} from 'interfaces/remote_timestamp_receiver';
import {RemoteTimestampSender} from 'interfaces/remote_timestamp_sender';
import {Runnable} from 'interfaces/runnable';
import {TraceDataListener} from 'interfaces/trace_data_listener';
import {TracePositionUpdateEmitter} from 'interfaces/trace_position_update_emitter';
import {TracePositionUpdateListener} from 'interfaces/trace_position_update_listener';
import {UserNotificationListener} from 'interfaces/user_notification_listener';
import {Timestamp, TimestampType} from 'trace/timestamp';
import {TraceFile} from 'trace/trace_file';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {Viewer} from 'viewers/viewer';
import {View, Viewer} from 'viewers/viewer';
import {ViewerFactory} from 'viewers/viewer_factory';
import {TimelineData} from './timeline_data';
import {TracePipeline} from './trace_pipeline';
type TimelineComponentInterface = TracePositionUpdateListener & TracePositionUpdateEmitter;
type CrossToolProtocolInterface = RemoteBugreportReceiver &
RemoteTimestampReceiver &
RemoteTimestampSender;
@@ -43,7 +45,7 @@ export class Mediator {
private crossToolProtocol: CrossToolProtocolInterface;
private uploadTracesComponent?: ProgressListener;
private collectTracesComponent?: ProgressListener;
private timelineComponent?: TracePositionUpdateListener;
private timelineComponent?: TimelineComponentInterface;
private appComponent: TraceDataListener;
private userNotificationListener: UserNotificationListener;
private storage: Storage;
@@ -73,18 +75,14 @@ export class Mediator {
this.userNotificationListener = userNotificationListener;
this.storage = storage;
this.timelineData.setOnTracePositionUpdate((position) => {
this.onWinscopeTracePositionUpdate(position);
});
this.crossToolProtocol.setOnBugreportReceived(
async (bugreport: File, timestamp?: Timestamp) => {
await this.onRemoteBugreportReceived(bugreport, timestamp);
}
);
this.crossToolProtocol.setOnTimestampReceived((timestamp: Timestamp) => {
this.onRemoteTimestampReceived(timestamp);
this.crossToolProtocol.setOnTimestampReceived(async (timestamp: Timestamp) => {
await this.onRemoteTimestampReceived(timestamp);
});
this.abtChromeExtensionProtocol.setOnBuganizerAttachmentsDownloadStart(() => {
@@ -106,8 +104,11 @@ export class Mediator {
this.collectTracesComponent = collectTracesComponent;
}
setTimelineComponent(timelineComponent: TracePositionUpdateListener | undefined) {
setTimelineComponent(timelineComponent: TimelineComponentInterface | undefined) {
this.timelineComponent = timelineComponent;
this.timelineComponent?.setOnTracePositionUpdate(async (position) => {
await this.onTimelineTracePositionUpdate(position);
});
}
onWinscopeInitialized() {
@@ -133,23 +134,46 @@ export class Mediator {
await this.processLoadedTraceFiles();
}
onWinscopeTracePositionUpdate(position: TracePosition) {
this.executeIgnoringRecursiveTimestampNotifications(() => {
this.updateViewersTracePosition(position);
async onWinscopeActiveViewChanged(view: View) {
this.timelineData.setActiveViewTraceTypes(view.dependencies);
await this.propagateTracePosition(this.timelineData.getCurrentPosition());
}
const timestamp = position.timestamp;
if (timestamp.getType() !== TimestampType.REAL) {
console.warn(
'Cannot propagate timestamp change to remote tool.' +
` Remote tool expects timestamp type ${TimestampType.REAL},` +
` but Winscope wants to notify timestamp type ${timestamp.getType()}.`
);
} else {
this.crossToolProtocol.sendTimestamp(timestamp);
}
async onTimelineTracePositionUpdate(position: TracePosition) {
await this.propagateTracePosition(position);
}
this.timelineComponent?.onTracePositionUpdate(position);
private async propagateTracePosition(
position?: TracePosition,
omitCrossToolProtocol: boolean = false
) {
if (!position) {
return;
}
//TODO (b/289478304): update only visible viewers (1 tab viewer + overlay viewers)
const promises = this.viewers.map((viewer) => {
return viewer.onTracePositionUpdate(position);
});
await Promise.all(promises);
this.timelineComponent?.onTracePositionUpdate(position);
if (omitCrossToolProtocol) {
return;
}
const timestamp = position.timestamp;
if (timestamp.getType() !== TimestampType.REAL) {
console.warn(
'Cannot propagate timestamp change to remote tool.' +
` Remote tool expects timestamp type ${TimestampType.REAL},` +
` but Winscope wants to notify timestamp type ${timestamp.getType()}.`
);
return;
}
this.crossToolProtocol.sendTimestamp(timestamp);
}
private onBuganizerAttachmentsDownloadStart() {
@@ -167,38 +191,30 @@ export class Mediator {
this.currentProgressListener = this.uploadTracesComponent;
await this.processRemoteFilesReceived([bugreport]);
if (timestamp !== undefined) {
this.onRemoteTimestampReceived(timestamp);
await this.onRemoteTimestampReceived(timestamp);
}
}
private onRemoteTimestampReceived(timestamp: Timestamp) {
this.executeIgnoringRecursiveTimestampNotifications(() => {
this.lastRemoteToolTimestampReceived = timestamp;
private async onRemoteTimestampReceived(timestamp: Timestamp) {
this.lastRemoteToolTimestampReceived = timestamp;
if (!this.isTraceDataVisualized) {
return; // apply timestamp later when traces are visualized
}
if (!this.isTraceDataVisualized) {
return; // apply timestamp later when traces are visualized
}
if (this.timelineData.getTimestampType() !== timestamp.getType()) {
console.warn(
'Cannot apply new timestamp received from remote tool.' +
` Remote tool notified timestamp type ${timestamp.getType()},` +
` but Winscope is accepting timestamp type ${this.timelineData.getTimestampType()}.`
);
return;
}
if (this.timelineData.getTimestampType() !== timestamp.getType()) {
console.warn(
'Cannot apply new timestamp received from remote tool.' +
` Remote tool notified timestamp type ${timestamp.getType()},` +
` but Winscope is accepting timestamp type ${this.timelineData.getTimestampType()}.`
);
return;
}
if (
this.timelineData.getCurrentPosition()?.timestamp.getValueNs() === timestamp.getValueNs()
) {
return; // no timestamp change
}
const position = TracePosition.fromTimestamp(timestamp);
this.timelineData.setPosition(position);
const position = TracePosition.fromTimestamp(timestamp);
this.updateViewersTracePosition(position);
this.timelineData.setPosition(position);
this.timelineComponent?.onTracePositionUpdate(position); //TODO: is this redundant?
});
await this.propagateTracePosition(this.timelineData.getCurrentPosition(), true);
}
private async processRemoteFilesReceived(files: File[]) {
@@ -234,23 +250,23 @@ export class Mediator {
// allow the UI to update before making the main thread very busy
await new Promise<void>((resolve) => setTimeout(resolve, 10));
this.tracePipeline.buildTraces();
await this.tracePipeline.buildTraces();
this.currentProgressListener?.onOperationFinished();
this.timelineData.initialize(
this.tracePipeline.getTraces(),
this.tracePipeline.getScreenRecordingVideo()
await this.tracePipeline.getScreenRecordingVideo()
);
this.createViewers();
await this.createViewers();
this.appComponent.onTraceDataLoaded(this.viewers);
this.isTraceDataVisualized = true;
if (this.lastRemoteToolTimestampReceived !== undefined) {
this.onRemoteTimestampReceived(this.lastRemoteToolTimestampReceived);
await this.onRemoteTimestampReceived(this.lastRemoteToolTimestampReceived);
}
}
private createViewers() {
private async createViewers() {
const traces = this.tracePipeline.getTraces();
const traceTypes = new Set<TraceType>();
traces.forEachTrace((trace) => {
@@ -258,26 +274,17 @@ export class Mediator {
});
this.viewers = new ViewerFactory().createViewers(traceTypes, traces, this.storage);
// Update the viewers as soon as they are created
const position = this.timelineData.getCurrentPosition();
if (position) {
this.onWinscopeTracePositionUpdate(position);
}
// Set position as soon as the viewers are created
await this.propagateTracePosition(this.timelineData.getCurrentPosition(), true);
}
private updateViewersTracePosition(position: TracePosition) {
this.viewers.forEach((viewer) => {
viewer.onTracePositionUpdate(position);
});
}
private executeIgnoringRecursiveTimestampNotifications(op: () => void) {
private async executeIgnoringRecursiveTimestampNotifications(op: () => Promise<void>) {
if (this.isChangingCurrentTimestamp) {
return;
}
this.isChangingCurrentTimestamp = true;
try {
op();
await op();
} finally {
this.isChangingCurrentTimestamp = false;
}

View File

@@ -61,7 +61,6 @@ describe('Mediator', () => {
});
beforeEach(async () => {
timelineComponent = new TimelineComponentStub();
tracePipeline = new TracePipeline();
timelineData = new TimelineData();
abtChromeExtensionProtocol = new AbtChromeExtensionProtocolStub();
@@ -94,6 +93,8 @@ describe('Mediator', () => {
spyOn(timelineData, 'initialize').and.callThrough(),
spyOn(appComponent, 'onTraceDataLoaded'),
spyOn(viewerStub, 'onTracePositionUpdate'),
spyOn(timelineComponent, 'onTracePositionUpdate'),
spyOn(crossToolProtocol, 'sendTimestamp'),
];
await mediator.onWinscopeFilesUploaded(inputFiles);
@@ -113,8 +114,11 @@ describe('Mediator', () => {
expect(uploadTracesComponent.onOperationFinished).toHaveBeenCalled();
expect(timelineData.initialize).toHaveBeenCalledTimes(1);
expect(appComponent.onTraceDataLoaded).toHaveBeenCalledOnceWith([viewerStub]);
// notifies viewer about current timestamp on creation
// propagates trace position on viewers creation
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
});
it('handles collected traces from Winscope', async () => {
@@ -124,6 +128,8 @@ describe('Mediator', () => {
spyOn(timelineData, 'initialize').and.callThrough(),
spyOn(appComponent, 'onTraceDataLoaded'),
spyOn(viewerStub, 'onTracePositionUpdate'),
spyOn(timelineComponent, 'onTracePositionUpdate'),
spyOn(crossToolProtocol, 'sendTimestamp'),
];
await mediator.onWinscopeFilesCollected(inputFiles);
@@ -132,8 +138,11 @@ describe('Mediator', () => {
expect(collectTracesComponent.onOperationFinished).toHaveBeenCalled();
expect(timelineData.initialize).toHaveBeenCalledTimes(1);
expect(appComponent.onTraceDataLoaded).toHaveBeenCalledOnceWith([viewerStub]);
// notifies viewer about current timestamp on creation
// propagates trace position on viewers creation
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
});
//TODO: test "bugreport data from cross-tool protocol" when FileUtils is fully compatible with
@@ -160,7 +169,7 @@ describe('Mediator', () => {
expect(uploadTracesComponent.onOperationFinished).toHaveBeenCalledTimes(1);
});
it('propagates trace position update from timeline data', async () => {
it('propagates trace position update from timeline component', async () => {
await loadTraceFiles();
await mediator.onWinscopeViewTracesRequest();
@@ -171,20 +180,14 @@ describe('Mediator', () => {
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
// notify timestamp
timelineData.setPosition(POSITION_10);
// notify position
await mediator.onTimelineTracePositionUpdate(POSITION_10);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(1);
// notify same timestamp again (ignored, no timestamp change)
timelineData.setPosition(POSITION_10);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(1);
// notify another timestamp
timelineData.setPosition(POSITION_11);
// notify position
await mediator.onTimelineTracePositionUpdate(POSITION_11);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(2);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(2);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(2);
@@ -205,12 +208,7 @@ describe('Mediator', () => {
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
// receive same timestamp again (ignored, no timestamp change)
await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
// receive another
// receive timestamp
await crossToolProtocol.onTimestampReceived(TIMESTAMP_11);
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(2);
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(2);

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import {FunctionUtils} from 'common/function_utils';
import {assertDefined} from 'common/assert_utils';
import {TimeUtils} from 'common/time_utils';
import {ScreenRecordingUtils} from 'trace/screen_recording_utils';
import {Timestamp, TimestampType} from 'trace/timestamp';
@@ -23,13 +23,12 @@ import {Traces} from 'trace/traces';
import {TraceEntryFinder} from 'trace/trace_entry_finder';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {assertDefined} from '../common/assert_utils';
export type TracePositionCallbackType = (position: TracePosition) => void;
export interface TimeRange {
from: Timestamp;
to: Timestamp;
}
const INVALID_TIMESTAMP = 0n
export class TimelineData {
private traces = new Traces();
@@ -40,25 +39,29 @@ export class TimelineData {
private explicitlySetPosition?: TracePosition;
private explicitlySetSelection?: TimeRange;
private activeViewTraceTypes: TraceType[] = []; // dependencies of current active view
private onTracePositionUpdate: TracePositionCallbackType = FunctionUtils.DO_NOTHING;
initialize(traces: Traces, screenRecordingVideo: Blob | undefined) {
this.clear();
this.traces = traces;
this.traces = new Traces();
traces.forEachTrace((trace, type) => {
if (type === TraceType.WINDOW_MANAGER) {
// Filter out WindowManager dumps with no timestamp from timeline
if (
trace.lengthEntries === 1 &&
trace.getEntry(0).getTimestamp().getValueNs() === INVALID_TIMESTAMP
) {
return;
}
}
this.traces.setTrace(type, trace);
});
this.screenRecordingVideo = screenRecordingVideo;
this.firstEntry = this.findFirstEntry();
this.lastEntry = this.findLastEntry();
this.timestampType = this.firstEntry?.getTimestamp().getType();
const position = this.getCurrentPosition();
if (position) {
this.onTracePositionUpdate(position);
}
}
setOnTracePositionUpdate(callback: TracePositionCallbackType) {
this.onTracePositionUpdate = callback;
}
getCurrentPosition(): TracePosition | undefined {
@@ -90,15 +93,11 @@ export class TimelineData {
}
}
this.applyOperationAndNotifyIfCurrentPositionChanged(() => {
this.explicitlySetPosition = position;
});
this.explicitlySetPosition = position;
}
setActiveViewTraceTypes(types: TraceType[]) {
this.applyOperationAndNotifyIfCurrentPositionChanged(() => {
this.activeViewTraceTypes = types;
});
this.activeViewTraceTypes = types;
}
getTimestampType(): TimestampType | undefined {
@@ -219,16 +218,14 @@ export class TimelineData {
}
clear() {
this.applyOperationAndNotifyIfCurrentPositionChanged(() => {
this.traces = new Traces();
this.firstEntry = undefined;
this.lastEntry = undefined;
this.explicitlySetPosition = undefined;
this.timestampType = undefined;
this.explicitlySetSelection = undefined;
this.screenRecordingVideo = undefined;
this.activeViewTraceTypes = [];
});
this.traces = new Traces();
this.firstEntry = undefined;
this.lastEntry = undefined;
this.explicitlySetPosition = undefined;
this.timestampType = undefined;
this.explicitlySetSelection = undefined;
this.screenRecordingVideo = undefined;
this.activeViewTraceTypes = [];
}
private findFirstEntry(): TraceEntry<{}> | undefined {
@@ -265,6 +262,7 @@ export class TimelineData {
private getFirstEntryOfActiveViewTraces(): TraceEntry<{}> | undefined {
const activeEntries = this.activeViewTraceTypes
.filter((it) => this.traces.getTrace(it) !== undefined)
.map((traceType) => assertDefined(this.traces.getTrace(traceType)))
.filter((trace) => trace.lengthEntries > 0)
.map((trace) => trace.getEntry(0))
@@ -276,13 +274,4 @@ export class TimelineData {
}
return activeEntries[0];
}
private applyOperationAndNotifyIfCurrentPositionChanged(op: () => void) {
const prevPosition = this.getCurrentPosition();
op();
const currentPosition = this.getCurrentPosition();
if (currentPosition && (!prevPosition || !currentPosition.isEqual(prevPosition))) {
this.onTracePositionUpdate(currentPosition);
}
}
}

View File

@@ -20,16 +20,8 @@ import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {TimelineData} from './timeline_data';
class TracePositionUpdateListener {
onTracePositionUpdate(position: TracePosition) {
// do nothing
}
}
describe('TimelineData', () => {
let timelineData: TimelineData;
const positionUpdateListener = new TracePositionUpdateListener();
const timestamp10 = new Timestamp(TimestampType.REAL, 10n);
const timestamp11 = new Timestamp(TimestampType.REAL, 11n);
@@ -47,9 +39,6 @@ describe('TimelineData', () => {
beforeEach(() => {
timelineData = new TimelineData();
timelineData.setOnTracePositionUpdate((position) => {
positionUpdateListener.onTracePositionUpdate(position);
});
});
it('can be initialized', () => {
@@ -59,6 +48,20 @@ describe('TimelineData', () => {
expect(timelineData.getCurrentPosition()).toBeDefined();
});
it('ignores dumps with no timestamp', () => {
expect(timelineData.getCurrentPosition()).toBeUndefined();
const traces = new TracesBuilder()
.setTimestamps(TraceType.SURFACE_FLINGER, [timestamp10, timestamp11])
.setTimestamps(TraceType.WINDOW_MANAGER, [new Timestamp(TimestampType.REAL, 0n)])
.build();
timelineData.initialize(traces, undefined);
expect(timelineData.getTraces().getTrace(TraceType.WINDOW_MANAGER)).toBeUndefined();
expect(timelineData.getFullTimeRange().from).toBe(timestamp10);
expect(timelineData.getFullTimeRange().to).toBe(timestamp11);
});
it('uses first entry by default', () => {
timelineData.initialize(traces, undefined);
expect(timelineData.getCurrentPosition()).toEqual(position10);
@@ -95,30 +98,6 @@ describe('TimelineData', () => {
expect(timelineData.getCurrentPosition()).toEqual(position10);
});
it('executes callback on position update', () => {
spyOn(positionUpdateListener, 'onTracePositionUpdate');
expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(0);
timelineData.initialize(traces, undefined);
expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(1);
timelineData.setActiveViewTraceTypes([TraceType.WINDOW_MANAGER]);
expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(2);
});
it("doesn't execute callback when position doesn't change", () => {
timelineData.initialize(traces, undefined);
spyOn(positionUpdateListener, 'onTracePositionUpdate');
expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(0);
timelineData.setActiveViewTraceTypes([TraceType.SURFACE_FLINGER]);
expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(0);
timelineData.setActiveViewTraceTypes([TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER]);
expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(0);
});
it('hasTimestamps()', () => {
expect(timelineData.hasTimestamps()).toBeFalse();

View File

@@ -108,6 +108,13 @@ export const TRACE_INFO: TraceInfoMap = {
color: '#137333',
downloadArchiveDir: 'launcher',
},
// TODO: Choose ViewCapture icon, color, title name, and download archive directory
[TraceType.VIEW_CAPTURE]: {
name: 'View Capture',
icon: LAUNCHER_ICON,
color: '#137333',
downloadArchiveDir: 'launcher',
},
[TraceType.INPUT_METHOD_CLIENTS]: {
name: 'IME Clients',
icon: IME_ICON,

View File

@@ -16,10 +16,8 @@
import {FunctionUtils, OnProgressUpdateType} from 'common/function_utils';
import {ParserError, ParserFactory} from 'parsers/parser_factory';
import {TracesParserCujs} from 'parsers/traces_parser_cujs';
import {TracesParserTransitions} from 'parsers/traces_parser_transitions';
import {TracesParserFactory} from 'parsers/traces_parser_factory';
import {FrameMapper} from 'trace/frame_mapper';
import {LoadedTrace} from 'trace/loaded_trace';
import {Parser} from 'trace/parser';
import {TimestampType} from 'trace/timestamp';
import {Trace} from 'trace/trace';
@@ -29,9 +27,10 @@ import {TraceType} from 'trace/trace_type';
class TracePipeline {
private parserFactory = new ParserFactory();
private tracesParserFactory = new TracesParserFactory();
private parsers: Array<Parser<object>> = [];
private files = new Map<TraceType, TraceFile>();
private traces?: Traces;
private traces = new Traces();
private commonTimestampType?: TimestampType;
async loadTraceFiles(
@@ -39,81 +38,69 @@ class TracePipeline {
onLoadProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING
): Promise<ParserError[]> {
traceFiles = await this.filterBugreportFilesIfNeeded(traceFiles);
const [parsers, parserErrors] = await this.parserFactory.createParsers(
const [fileAndParsers, parserErrors] = await this.parserFactory.createParsers(
traceFiles,
onLoadProgressUpdate
);
this.parsers = parsers.map((it) => it.parser);
const tracesParsers = [
new TracesParserTransitions(this.parsers),
new TracesParserCujs(this.parsers),
];
for (const tracesParser of tracesParsers) {
if (tracesParser.canProvideEntries()) {
this.parsers.push(tracesParser);
}
for (const fileAndParser of fileAndParsers) {
this.files.set(fileAndParser.parser.getTraceType(), fileAndParser.file);
}
for (const parser of parsers) {
this.files.set(parser.parser.getTraceType(), parser.file);
}
const newParsers = fileAndParsers.map((it) => it.parser);
this.parsers = this.parsers.concat(newParsers);
if (this.parsers.some((it) => it.getTraceType() === TraceType.TRANSITION)) {
this.parsers = this.parsers.filter(
(it) =>
it.getTraceType() !== TraceType.WM_TRANSITION &&
it.getTraceType() !== TraceType.SHELL_TRANSITION
);
const tracesParsers = await this.tracesParserFactory.createParsers(this.parsers);
const allParsers = this.parsers.concat(tracesParsers);
this.traces = new Traces();
allParsers.forEach((parser) => {
const trace = Trace.newUninitializedTrace(parser);
this.traces?.setTrace(parser.getTraceType(), trace);
});
const hasTransitionTrace = this.traces
.mapTrace((trace) => trace.type)
.some((type) => type === TraceType.TRANSITION);
if (hasTransitionTrace) {
this.traces.deleteTrace(TraceType.WM_TRANSITION);
this.traces.deleteTrace(TraceType.SHELL_TRANSITION);
}
return parserErrors;
}
removeTraceFile(type: TraceType) {
this.parsers = this.parsers.filter((parser) => parser.getTraceType() !== type);
removeTrace(trace: Trace<object>) {
this.parsers = this.parsers.filter((parser) => parser.getTraceType() !== trace.type);
this.traces.deleteTrace(trace.type);
}
getLoadedFiles(): Map<TraceType, TraceFile> {
return this.files;
}
getLoadedTraces(): LoadedTrace[] {
return this.parsers.map(
(parser: Parser<object>) => new LoadedTrace(parser.getDescriptors(), parser.getTraceType())
);
}
buildTraces() {
async buildTraces() {
const commonTimestampType = this.getCommonTimestampType();
this.traces = new Traces();
this.parsers.forEach((parser) => {
const trace = Trace.newUninitializedTrace(parser);
trace.init(commonTimestampType);
this.traces?.setTrace(parser.getTraceType(), trace);
});
new FrameMapper(this.traces).computeMapping();
this.traces.forEachTrace((trace) => trace.init(commonTimestampType));
await new FrameMapper(this.traces).computeMapping();
}
getTraces(): Traces {
this.checkTracesWereBuilt();
return this.traces!;
return this.traces;
}
getScreenRecordingVideo(): undefined | Blob {
async getScreenRecordingVideo(): Promise<undefined | Blob> {
const screenRecording = this.getTraces().getTrace(TraceType.SCREEN_RECORDING);
if (!screenRecording || screenRecording.lengthEntries === 0) {
return undefined;
}
return screenRecording.getEntry(0).getValue().videoData;
return (await screenRecording.getEntry(0).getValue()).videoData;
}
clear() {
this.parserFactory = new ParserFactory();
this.parsers = [];
this.traces = undefined;
this.traces = new Traces();
this.commonTimestampType = undefined;
this.files = new Map<TraceType, TraceFile>();
}
@@ -130,9 +117,13 @@ class TracePipeline {
return files;
}
const BUGREPORT_TRACE_DIRS = ['FS/data/misc/wmtrace/', 'FS/data/misc/perfetto-traces/'];
const isFileWithinBugreportTraceDir = (file: TraceFile) => {
for (const traceDir of BUGREPORT_TRACE_DIRS) {
const BUGREPORT_FILES_ALLOWLIST = [
'FS/data/misc/wmtrace/',
'FS/data/misc/perfetto-traces/',
'proto/window_CRITICAL.proto',
];
const isFileAllowlisted = (file: TraceFile) => {
for (const traceDir of BUGREPORT_FILES_ALLOWLIST) {
if (file.file.name.startsWith(traceDir)) {
return true;
}
@@ -144,7 +135,7 @@ class TracePipeline {
file.parentArchive === bugreportMainEntry.parentArchive;
return files.filter((file) => {
return isFileWithinBugreportTraceDir(file) || !fileBelongsToBugreport(file);
return isFileAllowlisted(file) || !fileBelongsToBugreport(file);
});
}
@@ -163,14 +154,6 @@ class TracePipeline {
throw Error('Failed to find common timestamp type across all traces');
}
private checkTracesWereBuilt() {
if (!this.traces) {
throw new Error(
`Can't access traces before building them. Did you forget to call '${this.buildTraces.name}'?`
);
}
}
}
export {TracePipeline};

View File

@@ -14,6 +14,7 @@
* limitations under the License.
*/
import {assertDefined} from 'common/assert_utils';
import {TracesUtils} from 'test/unit/traces_utils';
import {UnitTestUtils} from 'test/unit/utils';
import {TraceFile} from 'trace/trace_file';
@@ -21,26 +22,47 @@ import {TraceType} from 'trace/trace_type';
import {TracePipeline} from './trace_pipeline';
describe('TracePipeline', () => {
let validSfTraceFile: TraceFile;
let validWmTraceFile: TraceFile;
let tracePipeline: TracePipeline;
beforeEach(async () => {
validSfTraceFile = new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb')
);
validWmTraceFile = new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/WindowManager.pb')
);
tracePipeline = new TracePipeline();
});
it('can load valid trace files', async () => {
expect(tracePipeline.getLoadedTraces().length).toEqual(0);
expect(tracePipeline.getTraces().getSize()).toEqual(0);
await loadValidSfWmTraces();
expect(tracePipeline.getLoadedTraces().length).toEqual(2);
expect(tracePipeline.getTraces().getSize()).toEqual(2);
const traceEntries = TracesUtils.extractEntries(tracePipeline.getTraces());
const traceEntries = await TracesUtils.extractEntries(tracePipeline.getTraces());
expect(traceEntries.get(TraceType.WINDOW_MANAGER)?.length).toBeGreaterThan(0);
expect(traceEntries.get(TraceType.SURFACE_FLINGER)?.length).toBeGreaterThan(0);
});
it('can load a new file without dropping already-loaded traces', async () => {
expect(tracePipeline.getTraces().getSize()).toEqual(0);
await tracePipeline.loadTraceFiles([validSfTraceFile]);
expect(tracePipeline.getTraces().getSize()).toEqual(1);
await tracePipeline.loadTraceFiles([validWmTraceFile]);
expect(tracePipeline.getTraces().getSize()).toEqual(2);
await tracePipeline.loadTraceFiles([validWmTraceFile]); // ignored (duplicated)
expect(tracePipeline.getTraces().getSize()).toEqual(2);
});
it('can load bugreport and ignores non-trace dirs', async () => {
expect(tracePipeline.getLoadedTraces().length).toEqual(0);
expect(tracePipeline.getTraces().getSize()).toEqual(0);
// Could be any file, we just need an instance of File to be used as a fake bugreport archive
const bugreportArchive = await UnitTestUtils.getFixtureFile(
@@ -76,7 +98,14 @@ describe('TracePipeline', () => {
new TraceFile(
await UnitTestUtils.getFixtureFile(
'traces/elapsed_and_real_timestamp/WindowManager.pb',
'FS/data/misc/ignored-dir/window_manager.bp'
'proto/window_CRITICAL.proto'
),
bugreportArchive
),
new TraceFile(
await UnitTestUtils.getFixtureFile(
'traces/elapsed_and_real_timestamp/wm_transition_trace.pb',
'FS/data/misc/ignored-dir/wm_transition_trace.bp'
),
bugreportArchive
),
@@ -98,13 +127,14 @@ describe('TracePipeline', () => {
const mergedFiles = bugreportFiles.concat([plainTraceFile]);
const errors = await tracePipeline.loadTraceFiles(mergedFiles);
expect(errors.length).toEqual(0);
tracePipeline.buildTraces();
await tracePipeline.buildTraces();
const traces = tracePipeline.getTraces();
expect(traces.getTrace(TraceType.SURFACE_FLINGER)).toBeDefined();
expect(traces.getTrace(TraceType.TRANSACTIONS)).toBeDefined();
expect(traces.getTrace(TraceType.WINDOW_MANAGER)).toBeUndefined(); // ignored
expect(traces.getTrace(TraceType.WM_TRANSITION)).toBeUndefined(); // ignored
expect(traces.getTrace(TraceType.INPUT_METHOD_CLIENTS)).toBeDefined();
expect(traces.getTrace(TraceType.WINDOW_MANAGER)).toBeDefined();
});
it('is robust to invalid trace files', async () => {
@@ -113,20 +143,20 @@ describe('TracePipeline', () => {
];
const errors = await tracePipeline.loadTraceFiles(invalidTraceFiles);
tracePipeline.buildTraces();
await tracePipeline.buildTraces();
expect(errors.length).toEqual(1);
expect(tracePipeline.getLoadedTraces().length).toEqual(0);
expect(tracePipeline.getTraces().getSize()).toEqual(0);
});
it('is robust to mixed valid and invalid trace files', async () => {
expect(tracePipeline.getLoadedTraces().length).toEqual(0);
expect(tracePipeline.getTraces().getSize()).toEqual(0);
const files = [
new TraceFile(await UnitTestUtils.getFixtureFile('winscope_homepage.png')),
new TraceFile(await UnitTestUtils.getFixtureFile('traces/dump_WindowManager.pb')),
];
const errors = await tracePipeline.loadTraceFiles(files);
tracePipeline.buildTraces();
expect(tracePipeline.getLoadedTraces().length).toEqual(1);
await tracePipeline.buildTraces();
expect(tracePipeline.getTraces().getSize()).toEqual(1);
expect(errors.length).toEqual(1);
});
@@ -136,37 +166,41 @@ describe('TracePipeline', () => {
];
const errors = await tracePipeline.loadTraceFiles(traceFilesWithNoEntries);
tracePipeline.buildTraces();
await tracePipeline.buildTraces();
expect(errors.length).toEqual(0);
expect(tracePipeline.getLoadedTraces().length).toEqual(1);
expect(tracePipeline.getTraces().getSize()).toEqual(1);
});
it('can remove traces', async () => {
await loadValidSfWmTraces();
expect(tracePipeline.getLoadedTraces().length).toEqual(2);
expect(tracePipeline.getTraces().getSize()).toEqual(2);
tracePipeline.removeTraceFile(TraceType.SURFACE_FLINGER);
tracePipeline.buildTraces();
expect(tracePipeline.getLoadedTraces().length).toEqual(1);
const sfTrace = assertDefined(tracePipeline.getTraces().getTrace(TraceType.SURFACE_FLINGER));
const wmTrace = assertDefined(tracePipeline.getTraces().getTrace(TraceType.WINDOW_MANAGER));
tracePipeline.removeTraceFile(TraceType.WINDOW_MANAGER);
tracePipeline.buildTraces();
expect(tracePipeline.getLoadedTraces().length).toEqual(0);
tracePipeline.removeTrace(sfTrace);
await tracePipeline.buildTraces();
expect(tracePipeline.getTraces().getSize()).toEqual(1);
tracePipeline.removeTrace(wmTrace);
await tracePipeline.buildTraces();
expect(tracePipeline.getTraces().getSize()).toEqual(0);
});
it('gets loaded trace files', async () => {
it('gets loaded traces', async () => {
await loadValidSfWmTraces();
const files = tracePipeline.getLoadedTraces();
expect(files.length).toEqual(2);
expect(files[0].descriptors).toBeTruthy();
expect(files[0].descriptors.length).toBeGreaterThan(0);
const traces = tracePipeline.getTraces();
expect(traces.getSize()).toEqual(2);
const actualTraceTypes = new Set(files.map((file) => file.type));
const actualTraceTypes = new Set(traces.mapTrace((trace) => trace.type));
const expectedTraceTypes = new Set([TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER]);
expect(actualTraceTypes).toEqual(expectedTraceTypes);
const sfTrace = assertDefined(traces.getTrace(TraceType.SURFACE_FLINGER));
expect(sfTrace.getDescriptors().length).toBeGreaterThan(0);
});
it('builds traces', async () => {
@@ -186,49 +220,25 @@ describe('TracePipeline', () => {
),
];
await tracePipeline.loadTraceFiles(traceFiles);
tracePipeline.buildTraces();
await tracePipeline.buildTraces();
const video = tracePipeline.getScreenRecordingVideo();
const video = await tracePipeline.getScreenRecordingVideo();
expect(video).toBeDefined();
expect(video!.size).toBeGreaterThan(0);
});
it('can be cleared', async () => {
await loadValidSfWmTraces();
expect(tracePipeline.getLoadedTraces().length).toBeGreaterThan(0);
expect(tracePipeline.getTraces().getSize()).toBeGreaterThan(0);
tracePipeline.clear();
expect(tracePipeline.getLoadedTraces().length).toEqual(0);
expect(() => {
tracePipeline.getTraces();
}).toThrow();
expect(() => {
tracePipeline.getScreenRecordingVideo();
}).toThrow();
});
it('throws if accessed before traces are built', async () => {
expect(() => {
tracePipeline.getTraces();
}).toThrow();
expect(() => {
tracePipeline.getScreenRecordingVideo();
}).toThrow();
expect(tracePipeline.getTraces().getSize()).toEqual(0);
});
const loadValidSfWmTraces = async () => {
const traceFiles = [
new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb')
),
new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/WindowManager.pb')
),
];
const traceFiles = [validSfTraceFile, validWmTraceFile];
const errors = await tracePipeline.loadTraceFiles(traceFiles);
expect(errors.length).toEqual(0);
tracePipeline.buildTraces();
await tracePipeline.buildTraces();
};
});

View File

@@ -31,7 +31,7 @@ export class CrossToolProtocol
{
private remoteTool?: RemoteTool;
private onBugreportReceived: OnBugreportReceived = FunctionUtils.DO_NOTHING_ASYNC;
private onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING;
private onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING_ASYNC;
constructor() {
window.addEventListener('message', async (event) => {
@@ -89,10 +89,12 @@ export class CrossToolProtocol
case MessageType.BUGREPORT:
console.log('Cross-tool protocol received bugreport message:', message);
await this.onMessageBugreportReceived(message as MessageBugReport);
console.log('Cross-tool protocol processes bugreport message:', message);
break;
case MessageType.TIMESTAMP:
console.log('Cross-tool protocol received timestamp message:', message);
this.onMessageTimestampReceived(message as MessageTimestamp);
await this.onMessageTimestampReceived(message as MessageTimestamp);
console.log('Cross-tool protocol processed timestamp message:', message);
break;
case MessageType.FILES:
console.log('Cross-tool protocol received unexpected files message', message);
@@ -106,11 +108,11 @@ export class CrossToolProtocol
private async onMessageBugreportReceived(message: MessageBugReport) {
const timestamp =
message.timestampNs !== undefined ? new RealTimestamp(message.timestampNs) : undefined;
this.onBugreportReceived(message.file, timestamp);
await this.onBugreportReceived(message.file, timestamp);
}
private onMessageTimestampReceived(message: MessageTimestamp) {
private async onMessageTimestampReceived(message: MessageTimestamp) {
const timestamp = new RealTimestamp(message.timestampNs);
this.onTimestampReceived(timestamp);
await this.onTimestampReceived(timestamp);
}
}

View File

@@ -24,7 +24,7 @@ export class CrossToolProtocolStub
implements RemoteBugreportReceiver, RemoteTimestampReceiver, RemoteTimestampSender
{
onBugreportReceived: OnBugreportReceived = FunctionUtils.DO_NOTHING_ASYNC;
onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING;
onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING_ASYNC;
setOnBugreportReceived(callback: OnBugreportReceived) {
this.onBugreportReceived = callback;

View File

@@ -16,7 +16,7 @@
import {RealTimestamp} from 'trace/timestamp';
export type OnTimestampReceived = (timestamp: RealTimestamp) => void;
export type OnTimestampReceived = (timestamp: RealTimestamp) => Promise<void>;
export interface RemoteTimestampReceiver {
setOnTimestampReceived(callback: OnTimestampReceived): void;

View File

@@ -14,8 +14,10 @@
* limitations under the License.
*/
import {TraceType} from './trace_type';
import {TracePosition} from 'trace/trace_position';
export class LoadedTrace {
constructor(public descriptors: string[], public type: TraceType) {}
export type OnTracePositionUpdate = (position: TracePosition) => Promise<void>;
export interface TracePositionUpdateEmitter {
setOnTracePositionUpdate(callback: OnTracePositionUpdate): void;
}

View File

@@ -20,7 +20,7 @@ import {Timestamp, TimestampType} from 'trace/timestamp';
import {TraceFile} from 'trace/trace_file';
import {TraceType} from 'trace/trace_type';
abstract class AbstractParser<T extends object = object> implements Parser<object> {
abstract class AbstractParser<T extends object = object> implements Parser<T> {
protected traceFile: TraceFile;
protected decodedEntries: any[] = [];
private timestamps: Map<TimestampType, Timestamp[]> = new Map<TimestampType, Timestamp[]>();
@@ -78,8 +78,9 @@ abstract class AbstractParser<T extends object = object> implements Parser<objec
return this.timestamps.get(type);
}
getEntry(index: number, timestampType: TimestampType): T {
return this.processDecodedEntry(index, timestampType, this.decodedEntries[index]);
getEntry(index: number, timestampType: TimestampType): Promise<T> {
const entry = this.processDecodedEntry(index, timestampType, this.decodedEntries[index]);
return Promise.resolve(entry);
}
// Add default values to the proto objects.

View File

@@ -16,42 +16,33 @@
import {Parser} from 'trace/parser';
import {Timestamp, TimestampType} from 'trace/timestamp';
import {TraceFile} from 'trace/trace_file';
import {TraceType} from 'trace/trace_type';
export abstract class AbstractTracesParser<T> implements Parser<T> {
constructor(readonly parsers: Array<Parser<object>>) {}
private timestampsSet: boolean = false;
private timestamps: Map<TimestampType, Timestamp[]> = new Map<TimestampType, Timestamp[]>();
getTraceFile(): TraceFile {
throw new Error('Method not implemented.');
}
abstract canProvideEntries(): boolean;
abstract parse(): Promise<void>;
abstract getDescriptors(): string[];
abstract getTraceType(): TraceType;
abstract getEntry(index: number, timestampType: TimestampType): T;
abstract getEntry(index: number, timestampType: TimestampType): Promise<T>;
abstract getLengthEntries(): number;
getTimestamps(type: TimestampType): Timestamp[] | undefined {
this.setTimestamps();
return this.timestamps.get(type);
}
private setTimestamps() {
if (this.timestampsSet) {
return;
}
protected async parseTimestamps() {
for (const type of [TimestampType.ELAPSED, TimestampType.REAL]) {
const timestamps: Timestamp[] = [];
let areTimestampsValid = true;
for (let index = 0; index < this.getLengthEntries(); index++) {
const entry = this.getEntry(index, type);
const entry = await this.getEntry(index, type);
const timestamp = this.getTimestamp(type, entry);
if (timestamp === undefined) {
areTimestampsValid = false;
@@ -68,8 +59,5 @@ export abstract class AbstractTracesParser<T> implements Parser<T> {
this.timestampsSet = true;
}
abstract getTimestamp(type: TimestampType, decodedEntry: any): undefined | Timestamp;
private timestampsSet: boolean = false;
private timestamps: Map<TimestampType, Timestamp[]> = new Map<TimestampType, Timestamp[]>();
protected abstract getTimestamp(type: TimestampType, decodedEntry: any): undefined | Timestamp;
}

View File

@@ -48,11 +48,9 @@ describe('ParserAccessibility', () => {
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
});
it('retrieves trace entry', () => {
const timestamp = new Timestamp(TimestampType.REAL, 1659107089100562784n);
expect(BigInt(parser.getEntry(1, TimestampType.REAL).elapsedRealtimeNanos)).toEqual(
14499599656n
);
it('retrieves trace entry', async () => {
const entry = await parser.getEntry(1, TimestampType.REAL);
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(14499599656n);
});
});

View File

@@ -53,17 +53,12 @@ describe('Parser', () => {
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
});
it('retrieves trace entries', () => {
expect(
BigInt(parser.getEntry(0, TimestampType.REAL)!.timestamp.unixNanos.toString())
).toEqual(1659107089075566202n);
expect(
BigInt(
parser
.getEntry(parser.getLengthEntries() - 1, TimestampType.REAL)!
.timestamp.unixNanos.toString()
)
).toEqual(1659107091700249187n);
it('retrieves trace entries', async () => {
let entry = await parser.getEntry(0, TimestampType.REAL);
expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(1659107089075566202n);
entry = await parser.getEntry(parser.getLengthEntries() - 1, TimestampType.REAL);
expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(1659107091700249187n);
});
});
@@ -83,17 +78,12 @@ describe('Parser', () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)).toEqual(expected);
});
it('retrieves trace entries', () => {
expect(
BigInt(parser.getEntry(0, TimestampType.ELAPSED)!.timestamp.elapsedNanos.toString())
).toEqual(850254319343n);
expect(
BigInt(
parser
.getEntry(parser.getLengthEntries() - 1, TimestampType.ELAPSED)!
.timestamp.elapsedNanos.toString()
)
).toEqual(850782750048n);
it('retrieves trace entries', async () => {
let entry = await parser.getEntry(0, TimestampType.ELAPSED);
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(850254319343n);
entry = await parser.getEntry(parser.getLengthEntries() - 1, TimestampType.ELAPSED);
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(850782750048n);
});
});
});

View File

@@ -51,8 +51,8 @@ describe('ParserEventLog', () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)).toEqual(undefined);
});
it('contains parsed jank CUJ events', () => {
const entry = parser.getEntry(18, TimestampType.REAL);
expect(entry instanceof CujEvent).toBeTrue();
it('contains parsed jank CUJ events', async () => {
const entry = await parser.getEntry(18, TimestampType.REAL);
expect(entry).toBeInstanceOf(CujEvent);
});
});

View File

@@ -30,6 +30,7 @@ import {ParserSurfaceFlinger} from './parser_surface_flinger';
import {ParserTransactions} from './parser_transactions';
import {ParserTransitionsShell} from './parser_transitions_shell';
import {ParserTransitionsWm} from './parser_transitions_wm';
import {ParserViewCapture} from './parser_view_capture';
import {ParserWindowManager} from './parser_window_manager';
import {ParserWindowManagerDump} from './parser_window_manager_dump';
@@ -49,6 +50,7 @@ export class ParserFactory {
ParserEventLog,
ParserTransitionsWm,
ParserTransitionsShell,
ParserViewCapture,
];
private parsers = new Map<TraceType, Parser<object>>();

View File

@@ -52,10 +52,9 @@ describe('ParserInputMethodlClients', () => {
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
});
it('retrieves trace entry', () => {
expect(BigInt(parser.getEntry(1, TimestampType.REAL)!.elapsedRealtimeNanos)).toEqual(
15647516364n
);
it('retrieves trace entry', async () => {
const entry = await parser.getEntry(1, TimestampType.REAL);
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(15647516364n);
});
});
@@ -80,10 +79,9 @@ describe('ParserInputMethodlClients', () => {
expect(parser.getTimestamps(TimestampType.REAL)).toBeUndefined();
});
it('retrieves trace entry from elapsed timestamp', () => {
expect(BigInt(parser.getEntry(0, TimestampType.ELAPSED)!.elapsedRealtimeNanos)).toEqual(
1149083651642n
);
it('retrieves trace entry from elapsed timestamp', async () => {
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(1149083651642n);
});
});
});

View File

@@ -44,10 +44,9 @@ describe('ParserInputMethodManagerService', () => {
]);
});
it('retrieves trace entry', () => {
expect(BigInt(parser.getEntry(0, TimestampType.REAL)!.elapsedRealtimeNanos)).toEqual(
15963782518n
);
it('retrieves trace entry', async () => {
const entry = await parser.getEntry(0, TimestampType.REAL);
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(15963782518n);
});
});
@@ -74,10 +73,9 @@ describe('ParserInputMethodManagerService', () => {
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(undefined);
});
it('retrieves trace entry from elapsed timestamp', () => {
expect(BigInt(parser.getEntry(0, TimestampType.ELAPSED)!.elapsedRealtimeNanos)).toEqual(
1149226290110n
);
it('retrieves trace entry from elapsed timestamp', async () => {
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(1149226290110n);
});
});
});

View File

@@ -42,10 +42,9 @@ describe('ParserInputMethodService', () => {
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(expected);
});
it('retrieves trace entry', () => {
expect(BigInt(parser.getEntry(0, TimestampType.REAL)!.elapsedRealtimeNanos)).toEqual(
16578752896n
);
it('retrieves trace entry', async () => {
const entry = await parser.getEntry(0, TimestampType.REAL);
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(16578752896n);
});
});
@@ -70,10 +69,9 @@ describe('ParserInputMethodService', () => {
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(undefined);
});
it('retrieves trace entry', () => {
expect(BigInt(parser.getEntry(0, TimestampType.ELAPSED)!.elapsedRealtimeNanos)).toEqual(
1149230019887n
);
it('retrieves trace entry', async () => {
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(1149230019887n);
});
});
});

View File

@@ -78,15 +78,15 @@ describe('ParserProtoLog', () => {
expect(timestamps.slice(0, 3)).toEqual(expected);
});
it('reconstructs human-readable log message (ELAPSED time)', () => {
const message = parser.getEntry(0, TimestampType.ELAPSED);
it('reconstructs human-readable log message (ELAPSED time)', async () => {
const message = await parser.getEntry(0, TimestampType.ELAPSED);
expect(Object.assign({}, message)).toEqual(expectedFirstLogMessageElapsed);
expect(message).toBeInstanceOf(LogMessage);
});
it('reconstructs human-readable log message (REAL time)', () => {
const message = parser.getEntry(0, TimestampType.REAL)!;
it('reconstructs human-readable log message (REAL time)', async () => {
const message = await parser.getEntry(0, TimestampType.REAL);
expect(Object.assign({}, message)).toEqual(expectedFirstLogMessageReal);
});

View File

@@ -56,14 +56,14 @@ describe('ParserScreenRecordingLegacy', () => {
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(undefined);
});
it('retrieves trace entry', () => {
it('retrieves trace entry', async () => {
{
const entry = parser.getEntry(0, TimestampType.ELAPSED);
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
expect(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(0);
}
{
const entry = parser.getEntry(parser.getLengthEntries() - 1, TimestampType.ELAPSED);
const entry = await parser.getEntry(parser.getLengthEntries() - 1, TimestampType.ELAPSED);
expect(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(2.37, 0.001);
}

View File

@@ -58,14 +58,14 @@ describe('ParserScreenRecording', () => {
expect(timestamps.slice(0, 3)).toEqual(expected);
});
it('retrieves trace entry', () => {
it('retrieves trace entry', async () => {
{
const entry = parser.getEntry(0, TimestampType.REAL);
const entry = await parser.getEntry(0, TimestampType.REAL);
expect(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(0);
}
{
const entry = parser.getEntry(parser.getLengthEntries() - 1, TimestampType.REAL);
const entry = await parser.getEntry(parser.getLengthEntries() - 1, TimestampType.REAL);
expect(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(1.371077, 0.001);
}

View File

@@ -44,8 +44,8 @@ describe('ParserSurfaceFlingerDump', () => {
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(expected);
});
it('retrieves trace entry', () => {
const entry = parser.getEntry(0, TimestampType.ELAPSED);
it('retrieves trace entry', async () => {
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
expect(entry).toBeInstanceOf(LayerTraceEntry);
expect(BigInt(entry.timestamp.systemUptimeNanos.toString())).toEqual(0n);
expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(DUMP_REAL_TIME);

View File

@@ -25,7 +25,7 @@ describe('ParserSurfaceFlinger', () => {
const parser = (await UnitTestUtils.getParser(
'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb'
)) as Parser<LayerTraceEntry>;
const entry = parser.getEntry(0, TimestampType.REAL);
const entry = await parser.getEntry(0, TimestampType.REAL);
{
const layer = entry.flattenedLayers.find((layer: Layer) => layer.id === 27);
@@ -76,8 +76,8 @@ describe('ParserSurfaceFlinger', () => {
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
});
it('formats entry timestamps', () => {
const entry = parser.getEntry(1, TimestampType.REAL);
it('formats entry timestamps', async () => {
const entry = await parser.getEntry(1, TimestampType.REAL);
expect(entry.name).toEqual('2022-07-29T15:04:49.233029376');
expect(BigInt(entry.timestamp.systemUptimeNanos.toString())).toEqual(14631249355n);
expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(1659107089233029344n);
@@ -107,8 +107,8 @@ describe('ParserSurfaceFlinger', () => {
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(undefined);
});
it('formats entry timestamps', () => {
const entry = parser.getEntry(0, TimestampType.ELAPSED);
it('formats entry timestamps', async () => {
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
expect(entry.name).toEqual('14m10s335ms483446ns');
});
});

View File

@@ -56,21 +56,21 @@ describe('ParserTransactions', () => {
expect(timestamps.slice(0, 3)).toEqual(expected);
});
it('retrieves trace entry from real timestamp', () => {
const entry = parser.getEntry(1, TimestampType.REAL);
it('retrieves trace entry from real timestamp', async () => {
const entry = await parser.getEntry(1, TimestampType.REAL);
expect(BigInt((entry as any).elapsedRealtimeNanos)).toEqual(2517952515n);
});
it("decodes 'what' field in proto", () => {
it("decodes 'what' field in proto", async () => {
{
const entry = parser.getEntry(0, TimestampType.REAL) as any;
const entry = (await parser.getEntry(0, TimestampType.REAL)) as any;
expect(entry.transactions[0].layerChanges[0].what).toEqual('eLayerChanged');
expect(entry.transactions[1].layerChanges[0].what).toEqual(
'eFlagsChanged | eDestinationFrameChanged'
);
}
{
const entry = parser.getEntry(222, TimestampType.REAL) as any;
const entry = (await parser.getEntry(222, TimestampType.REAL)) as any;
expect(entry.transactions[1].displayChanges[0].what).toEqual(
'eLayerStackChanged | eDisplayProjectionChanged | eFlagsChanged'
);

View File

@@ -0,0 +1,177 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Timestamp, TimestampType} from 'trace/timestamp';
import {TraceFile} from 'trace/trace_file';
import {TraceType} from 'trace/trace_type';
import {AbstractParser} from './abstract_parser';
import {ExportedData} from './proto_types';
/* TODO: Support multiple Windows in one file upload. */
export class ParserViewCapture extends AbstractParser {
private classNames: string[] = [];
private realToElapsedTimeOffsetNanos: bigint | undefined = undefined;
packageName: string = '';
windowTitle: string = '';
constructor(trace: TraceFile) {
super(trace);
}
override getTraceType(): TraceType {
return TraceType.VIEW_CAPTURE;
}
override getMagicNumber(): number[] {
return ParserViewCapture.MAGIC_NUMBER;
}
override decodeTrace(buffer: Uint8Array): any[] {
const exportedData = ExportedData.decode(buffer) as any;
this.classNames = exportedData.classname;
this.realToElapsedTimeOffsetNanos = BigInt(exportedData.realToElapsedTimeOffsetNanos);
this.packageName = this.shortenAndCapitalize(exportedData.package);
const firstWindowData = exportedData.windowData[0];
this.windowTitle = this.shortenAndCapitalize(firstWindowData.title);
return firstWindowData.frameData;
}
override processDecodedEntry(index: number, timestampType: TimestampType, decodedEntry: any) {
this.formatProperties(decodedEntry.node, this.classNames);
return decodedEntry;
}
private shortenAndCapitalize(name: string): string {
const shortName = name.substring(name.lastIndexOf('.') + 1);
return shortName.charAt(0).toUpperCase() + shortName.slice(1);
}
private formatProperties(root: any /* ViewNode */, classNames: string[]): any /* ViewNode */ {
const DEPTH_MAGNIFICATION = 4;
const VISIBLE = 0;
function inner(
node: any /* ViewNode */,
leftShift: number,
topShift: number,
scaleX: number,
scaleY: number,
depth: number,
isParentVisible: boolean
) {
const newScaleX = scaleX * node.scaleX;
const newScaleY = scaleY * node.scaleY;
const l =
leftShift +
(node.left + node.translationX) * scaleX +
(node.width * (scaleX - newScaleX)) / 2;
const t =
topShift +
(node.top + node.translationY) * scaleY +
(node.height * (scaleY - newScaleY)) / 2;
node.boxPos = {
left: l,
top: t,
width: node.width * newScaleX,
height: node.height * newScaleY,
};
node.name = `${classNames[node.classnameIndex]}@${node.hashcode}`;
node.shortName = node.name.split('.');
node.shortName = node.shortName[node.shortName.length - 1];
node.isVisible = isParentVisible && VISIBLE === node.visibility;
for (let i = 0; i < node.children.length; i++) {
inner(
node.children[i],
l - node.scrollX,
t - node.scrollY,
newScaleX,
newScaleY,
depth + 1,
node.isVisible
);
node.children[i].parent = node;
}
// TODO: Audit these properties
node.depth = depth * DEPTH_MAGNIFICATION;
node.type = 'ViewNode';
node.layerId = 0;
node.isMissing = false;
node.hwcCompositionType = 0;
node.zOrderRelativeOfId = -1;
node.isRootLayer = false;
node.skip = null;
node.id = node.name;
node.stableId = node.id;
node.equals = (other: any /* ViewNode */) => ParserViewCapture.equals(node, other);
}
root.scaleX = root.scaleY = 1;
root.translationX = root.translationY = 0;
inner(root, 0, 0, 1, 1, 0, true);
root.isRootLayer = true;
return root;
}
override getTimestamp(timestampType: TimestampType, frameData: any): undefined | Timestamp {
return Timestamp.from(
timestampType,
BigInt(frameData.timestamp),
this.realToElapsedTimeOffsetNanos
);
}
private static readonly MAGIC_NUMBER = [0x9, 0x78, 0x65, 0x90, 0x65, 0x73, 0x82, 0x65, 0x68];
/** This method is used by the tree_generator to determine if 2 nodes have equivalent properties. */
private static equals(node: any /* ViewNode */, other: any /* ViewNode */): boolean {
if (!node && !other) {
return true;
}
if (!node || !other) {
return false;
}
return (
node.id === other.id &&
node.name === other.name &&
node.hashcode === other.hashcode &&
node.left === other.left &&
node.top === other.top &&
node.height === other.height &&
node.width === other.width &&
node.elevation === other.elevation &&
node.scaleX === other.scaleX &&
node.scaleY === other.scaleY &&
node.scrollX === other.scrollX &&
node.scrollY === other.scrollY &&
node.translationX === other.translationX &&
node.translationY === other.translationY &&
node.alpha === other.alpha &&
node.visibility === other.visibility &&
node.willNotDraw === other.willNotDraw &&
node.clipChildren === other.clipChildren &&
node.depth === other.depth
);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {UnitTestUtils} from 'test/unit/utils';
import {Parser} from 'trace/parser';
import {Timestamp, TimestampType} from 'trace/timestamp';
import {TraceType} from 'trace/trace_type';
describe('ParserViewCapture', () => {
let parser: Parser<object>;
beforeAll(async () => {
parser = await UnitTestUtils.getParser(
'traces/elapsed_and_real_timestamp/com.google.android.apps.nexuslauncher_0.vc'
);
});
it('has expected trace type', () => {
expect(parser.getTraceType()).toEqual(TraceType.VIEW_CAPTURE);
});
it('provides elapsed timestamps', () => {
const expected = [
new Timestamp(TimestampType.ELAPSED, 26231798759n),
new Timestamp(TimestampType.ELAPSED, 26242905367n),
new Timestamp(TimestampType.ELAPSED, 26255550549n),
];
expect(parser.getTimestamps(TimestampType.ELAPSED)!.slice(0, 3)).toEqual(expected);
});
it('provides real timestamps', () => {
const expected = [
new Timestamp(TimestampType.REAL, 1686674380113072216n),
new Timestamp(TimestampType.REAL, 1686674380124178824n),
new Timestamp(TimestampType.REAL, 1686674380136824006n),
];
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
});
it('retrieves trace entry', async () => {
const entry = (await parser.getEntry(1, TimestampType.REAL)) as any;
expect(entry.timestamp).toBeTruthy();
expect(entry.node).toBeTruthy();
});
});

View File

@@ -40,8 +40,8 @@ describe('ParserWindowManagerDump', () => {
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(expected);
});
it('retrieves trace entry', () => {
const entry = parser.getEntry(0, TimestampType.ELAPSED);
it('retrieves trace entry', async () => {
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
expect(entry).toBeInstanceOf(WindowManagerState);
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(0n);
});

View File

@@ -49,15 +49,15 @@ describe('ParserWindowManager', () => {
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
});
it('retrieves trace entry', () => {
const entry = parser.getEntry(1, TimestampType.REAL);
it('retrieves trace entry', async () => {
const entry = await parser.getEntry(1, TimestampType.REAL);
expect(entry).toBeInstanceOf(WindowManagerState);
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(15398076788n);
expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(1659107089999048990n);
});
it('formats entry timestamps', () => {
const entry = parser.getEntry(1, TimestampType.REAL);
it('formats entry timestamps', async () => {
const entry = await parser.getEntry(1, TimestampType.REAL);
expect(entry.name).toEqual('2022-07-29T15:04:49.999048960');
});
});
@@ -82,14 +82,14 @@ describe('ParserWindowManager', () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)).toEqual(expected);
});
it('retrieves trace entry', () => {
const entry = parser.getEntry(0, TimestampType.ELAPSED);
it('retrieves trace entry', async () => {
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
expect(entry).toBeInstanceOf(WindowManagerState);
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(850254319343n);
});
it('formats entry timestamps', () => {
const entry = parser.getEntry(0, TimestampType.ELAPSED);
it('formats entry timestamps', async () => {
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
expect(entry.name).toEqual('14m10s254ms319343ns');
});
});

View File

@@ -25,6 +25,7 @@ import windowManagerJson from 'frameworks/base/core/proto/android/server/windowm
import wmTransitionsJson from 'frameworks/base/core/proto/android/server/windowmanagertransitiontrace.proto';
import inputMethodClientsJson from 'frameworks/base/core/proto/android/view/inputmethod/inputmethodeditortrace.proto';
import shellTransitionsJson from 'frameworks/base/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto';
import viewCaptureJson from 'frameworks/libs/systemui/viewcapturelib/src/com/android/app/viewcapture/proto/view_capture.proto';
import layersJson from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto';
import transactionsJson from 'frameworks/native/services/surfaceflinger/layerproto/transactions.proto';
@@ -62,6 +63,10 @@ const ShellTransitionsTraceFileProto = protobuf.Root.fromJSON(shellTransitionsJs
'com.android.wm.shell.WmShellTransitionTraceProto'
);
const ExportedData = protobuf.Root.fromJSON(viewCaptureJson).lookupType(
'com.android.app.viewcapture.data.ExportedData'
);
export {
AccessibilityTraceFileProto,
InputMethodClientsTraceFileProto,
@@ -74,4 +79,5 @@ export {
WindowManagerTraceFileProto,
WmTransitionsTraceFileProto,
ShellTransitionsTraceFileProto,
ExportedData,
};

View File

@@ -14,7 +14,8 @@
* limitations under the License.
*/
import {Cuj, CujTrace, EventLog, Transition} from 'trace/flickerlib/common';
import {assertDefined} from 'common/assert_utils';
import {Cuj, EventLog, Transition} from 'trace/flickerlib/common';
import {Parser} from 'trace/parser';
import {Timestamp, TimestampType} from 'trace/timestamp';
import {TraceType} from 'trace/trace_type';
@@ -24,11 +25,12 @@ import {ParserEventLog} from './parser_eventlog';
export class TracesParserCujs extends AbstractTracesParser<Transition> {
private readonly eventLogTrace: ParserEventLog | undefined;
private readonly descriptors: string[];
private decodedEntries: Cuj[] | undefined;
constructor(parsers: Array<Parser<object>>) {
super(parsers);
super();
const eventlogTraces = this.parsers.filter((it) => it.getTraceType() === TraceType.EVENT_LOG);
const eventlogTraces = parsers.filter((it) => it.getTraceType() === TraceType.EVENT_LOG);
if (eventlogTraces.length > 0) {
this.eventLogTrace = eventlogTraces[0] as ParserEventLog;
}
@@ -40,35 +42,29 @@ export class TracesParserCujs extends AbstractTracesParser<Transition> {
}
}
override canProvideEntries(): boolean {
return this.eventLogTrace !== undefined;
}
getLengthEntries(): number {
return this.getDecodedEntries().length;
}
getEntry(index: number, timestampType: TimestampType): Transition {
return this.getDecodedEntries()[index];
}
private cujTrace: CujTrace | undefined;
getDecodedEntries(): Cuj[] {
override async parse() {
if (this.eventLogTrace === undefined) {
throw new Error('eventLogTrace not defined');
}
if (this.cujTrace === undefined) {
const events: Event[] = [];
const events: Event[] = [];
for (let i = 0; i < this.eventLogTrace.getLengthEntries(); i++) {
events.push(this.eventLogTrace.getEntry(i, TimestampType.REAL));
}
this.cujTrace = new EventLog(events).cujTrace;
for (let i = 0; i < this.eventLogTrace.getLengthEntries(); i++) {
events.push(await this.eventLogTrace.getEntry(i, TimestampType.REAL));
}
return this.cujTrace.entries;
this.decodedEntries = new EventLog(events).cujTrace.entries;
await this.parseTimestamps();
}
getLengthEntries(): number {
return assertDefined(this.decodedEntries).length;
}
getEntry(index: number, timestampType: TimestampType): Promise<Transition> {
const entry = assertDefined(this.decodedEntries)[index];
return Promise.resolve(entry);
}
override getDescriptors(): string[] {

View File

@@ -14,23 +14,17 @@
* limitations under the License.
*/
import {assertDefined} from 'common/assert_utils';
import {UnitTestUtils} from 'test/unit/utils';
import {Cuj} from 'trace/flickerlib/common';
import {Parser} from 'trace/parser';
import {Timestamp, TimestampType} from 'trace/timestamp';
import {TraceType} from 'trace/trace_type';
import {TracesParserCujs} from './traces_parser_cujs';
describe('ParserCujs', () => {
let parser: Parser<Cuj>;
beforeAll(async () => {
const eventLogParser = assertDefined(
await UnitTestUtils.getParser('traces/eventlog.winscope')
) as Parser<Event>;
parser = new TracesParserCujs([eventLogParser]);
parser = await UnitTestUtils.getTracesParser(['traces/eventlog.winscope']);
});
it('has expected trace type', () => {

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Parser} from 'trace/parser';
import {TracesParserCujs} from './traces_parser_cujs';
import {TracesParserTransitions} from './traces_parser_transitions';
export class TracesParserFactory {
static readonly PARSERS = [TracesParserCujs, TracesParserTransitions];
async createParsers(parsers: Array<Parser<object>>): Promise<Array<Parser<object>>> {
const tracesParsers: Array<Parser<object>> = [];
for (const ParserType of TracesParserFactory.PARSERS) {
try {
const parser = new ParserType(parsers);
await parser.parse();
tracesParsers.push(parser);
break;
} catch (error) {
// skip current parser
}
}
return tracesParsers;
}
}

View File

@@ -14,6 +14,7 @@
* limitations under the License.
*/
import {assertDefined} from 'common/assert_utils';
import {Transition, TransitionsTrace} from 'trace/flickerlib/common';
import {Parser} from 'trace/parser';
import {Timestamp, TimestampType} from 'trace/timestamp';
@@ -24,17 +25,17 @@ export class TracesParserTransitions extends AbstractTracesParser<Transition> {
private readonly wmTransitionTrace: Parser<object> | undefined;
private readonly shellTransitionTrace: Parser<object> | undefined;
private readonly descriptors: string[];
private decodedEntries: Transition[] | undefined;
constructor(parsers: Array<Parser<object>>) {
super(parsers);
const wmTransitionTraces = this.parsers.filter(
super();
const wmTransitionTraces = parsers.filter(
(it) => it.getTraceType() === TraceType.WM_TRANSITION
);
if (wmTransitionTraces.length > 0) {
this.wmTransitionTrace = wmTransitionTraces[0];
}
const shellTransitionTraces = this.parsers.filter(
const shellTransitionTraces = parsers.filter(
(it) => it.getTraceType() === TraceType.SHELL_TRANSITION
);
if (shellTransitionTraces.length > 0) {
@@ -49,54 +50,50 @@ export class TracesParserTransitions extends AbstractTracesParser<Transition> {
}
}
override canProvideEntries(): boolean {
return this.wmTransitionTrace !== undefined && this.shellTransitionTrace !== undefined;
}
getLengthEntries(): number {
return this.getDecodedEntries().length;
}
getEntry(index: number, timestampType: TimestampType): Transition {
return this.getDecodedEntries()[index];
}
private decodedEntries: Transition[] | undefined;
getDecodedEntries(): Transition[] {
if (this.decodedEntries === undefined) {
if (this.wmTransitionTrace === undefined) {
throw new Error('Missing WM Transition trace');
}
if (this.shellTransitionTrace === undefined) {
throw new Error('Missing Shell Transition trace');
}
const wmTransitionEntries: Transition[] = [];
for (let index = 0; index < this.wmTransitionTrace.getLengthEntries(); index++) {
wmTransitionEntries.push(this.wmTransitionTrace.getEntry(index, TimestampType.REAL));
}
const shellTransitionEntries: Transition[] = [];
for (let index = 0; index < this.shellTransitionTrace.getLengthEntries(); index++) {
shellTransitionEntries.push(this.shellTransitionTrace.getEntry(index, TimestampType.REAL));
}
const transitionsTrace = new TransitionsTrace(
wmTransitionEntries.concat(shellTransitionEntries)
);
this.decodedEntries = transitionsTrace.asCompressed().entries as Transition[];
override async parse() {
if (this.wmTransitionTrace === undefined) {
throw new Error('Missing WM Transition trace');
}
return this.decodedEntries;
if (this.shellTransitionTrace === undefined) {
throw new Error('Missing Shell Transition trace');
}
const wmTransitionEntries: Transition[] = [];
for (let index = 0; index < this.wmTransitionTrace.getLengthEntries(); index++) {
wmTransitionEntries.push(await this.wmTransitionTrace.getEntry(index, TimestampType.REAL));
}
const shellTransitionEntries: Transition[] = [];
for (let index = 0; index < this.shellTransitionTrace.getLengthEntries(); index++) {
shellTransitionEntries.push(
await this.shellTransitionTrace.getEntry(index, TimestampType.REAL)
);
}
const transitionsTrace = new TransitionsTrace(
wmTransitionEntries.concat(shellTransitionEntries)
);
this.decodedEntries = transitionsTrace.asCompressed().entries as Transition[];
await this.parseTimestamps();
}
override getLengthEntries(): number {
return assertDefined(this.decodedEntries).length;
}
override getEntry(index: number, timestampType: TimestampType): Promise<Transition> {
const entry = assertDefined(this.decodedEntries)[index];
return Promise.resolve(entry);
}
override getDescriptors(): string[] {
return this.descriptors;
}
getTraceType(): TraceType {
override getTraceType(): TraceType {
return TraceType.TRANSITION;
}

View File

@@ -19,20 +19,15 @@ import {Transition} from 'trace/flickerlib/common';
import {Parser} from 'trace/parser';
import {Timestamp, TimestampType} from 'trace/timestamp';
import {TraceType} from 'trace/trace_type';
import {TracesParserTransitions} from './traces_parser_transitions';
describe('ParserTransitions', () => {
let parser: Parser<Transition>;
beforeAll(async () => {
const wmSideParser = await UnitTestUtils.getParser(
'traces/elapsed_and_real_timestamp/wm_transition_trace.pb'
);
const shellSideParser = await UnitTestUtils.getParser(
'traces/elapsed_and_real_timestamp/shell_transition_trace.pb'
);
parser = new TracesParserTransitions([wmSideParser, shellSideParser]);
parser = await UnitTestUtils.getTracesParser([
'traces/elapsed_and_real_timestamp/wm_transition_trace.pb',
'traces/elapsed_and_real_timestamp/shell_transition_trace.pb',
]);
});
it('has expected trace type', () => {

View File

@@ -18,9 +18,6 @@ import {browser, by, element, ElementFinder} from 'protractor';
import {E2eTestUtils} from './utils';
describe('Cross-Tool Protocol', () => {
const WINSCOPE_URL = 'http://localhost:8080';
const REMOTE_TOOL_MOCK_URL = 'http://localhost:8081';
const TIMESTAMP_IN_BUGREPORT_MESSAGE = '1670509911000000000';
const TIMESTAMP_FROM_REMOTE_TOOL_TO_WINSCOPE = '1670509912000000000';
const TIMESTAMP_FROM_WINSCOPE_TO_REMOTE_TOOL = '1670509913000000000';
@@ -28,12 +25,12 @@ describe('Cross-Tool Protocol', () => {
beforeAll(async () => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
await browser.manage().timeouts().implicitlyWait(15000);
await checkServerIsUp('Remote tool mock', REMOTE_TOOL_MOCK_URL);
await checkServerIsUp('Winscope', WINSCOPE_URL);
await E2eTestUtils.checkServerIsUp('Remote tool mock', E2eTestUtils.REMOTE_TOOL_MOCK_URL);
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
});
beforeEach(async () => {
await browser.get(REMOTE_TOOL_MOCK_URL);
await browser.get(E2eTestUtils.REMOTE_TOOL_MOCK_URL);
});
it('allows communication between remote tool and Winscope', async () => {
@@ -56,14 +53,6 @@ describe('Cross-Tool Protocol', () => {
await checkRemoteToolReceivedTimestamp();
});
const checkServerIsUp = async (name: string, url: string) => {
try {
await browser.get(url);
} catch (error) {
fail(`${name} server (${url}) looks down. Did you start it?`);
}
};
const openWinscopeTabFromRemoteTool = async () => {
await browser.switchTo().window(await getWindowHandleRemoteToolMock());
const buttonElement = element(by.css('.button-open-winscope'));

View File

@@ -23,10 +23,11 @@ describe('Upload traces', () => {
beforeAll(async () => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = DEFAULT_TIMEOUT_MS;
await browser.manage().timeouts().implicitlyWait(DEFAULT_TIMEOUT_MS);
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
});
beforeEach(async () => {
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
await browser.get(E2eTestUtils.WINSCOPE_URL);
});
it('can process bugreport', async () => {

View File

@@ -13,13 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as path from 'path';
import {by, element} from 'protractor';
import {browser, by, element} from 'protractor';
import {CommonTestUtils} from '../common/utils';
class E2eTestUtils extends CommonTestUtils {
static getProductionIndexHtmlPath(): string {
return path.join(CommonTestUtils.getProjectRootPath(), 'dist/prod/index.html');
static readonly WINSCOPE_URL = 'http://localhost:8080';
static readonly REMOTE_TOOL_MOCK_URL = 'http://localhost:8081';
static async checkServerIsUp(name: string, url: string) {
try {
await browser.get(url);
} catch (error) {
fail(`${name} server (${url}) looks down. Did you start it?`);
}
}
static async uploadFixture(...paths: string[]) {

View File

@@ -19,14 +19,16 @@ import {E2eTestUtils} from './utils';
describe('Viewer InputMethodClients', () => {
beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000);
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
}),
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/InputMethodClients.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
await browser.get(E2eTestUtils.WINSCOPE_URL);
});
const viewerPresent = await element(by.css('viewer-input-method')).isPresent();
expect(viewerPresent).toBeTruthy();
});
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/InputMethodClients.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const viewerPresent = await element(by.css('viewer-input-method')).isPresent();
expect(viewerPresent).toBeTruthy();
});
});

View File

@@ -19,16 +19,18 @@ import {E2eTestUtils} from './utils';
describe('Viewer InputMethodManagerService', () => {
beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000);
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
}),
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture(
'traces/elapsed_and_real_timestamp/InputMethodManagerService.pb'
);
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
await browser.get(E2eTestUtils.WINSCOPE_URL);
});
const viewerPresent = await element(by.css('viewer-input-method')).isPresent();
expect(viewerPresent).toBeTruthy();
});
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture(
'traces/elapsed_and_real_timestamp/InputMethodManagerService.pb'
);
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const viewerPresent = await element(by.css('viewer-input-method')).isPresent();
expect(viewerPresent).toBeTruthy();
});
});

View File

@@ -19,14 +19,16 @@ import {E2eTestUtils} from './utils';
describe('Viewer InputMethodService', () => {
beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000);
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
}),
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/InputMethodService.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
await browser.get(E2eTestUtils.WINSCOPE_URL);
});
const viewerPresent = await element(by.css('viewer-input-method')).isPresent();
expect(viewerPresent).toBeTruthy();
});
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/InputMethodService.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const viewerPresent = await element(by.css('viewer-input-method')).isPresent();
expect(viewerPresent).toBeTruthy();
});
});

View File

@@ -19,19 +19,21 @@ import {E2eTestUtils} from './utils';
describe('Viewer ProtoLog', () => {
beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000);
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
}),
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/ProtoLog.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
await browser.get(E2eTestUtils.WINSCOPE_URL);
});
const isViewerRendered = await element(by.css('viewer-protolog')).isPresent();
expect(isViewerRendered).toBeTruthy();
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/ProtoLog.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const isFirstMessageRendered = await element(
by.css('viewer-protolog .scroll-messages .message')
).isPresent();
expect(isFirstMessageRendered).toBeTruthy();
});
const isViewerRendered = await element(by.css('viewer-protolog')).isPresent();
expect(isViewerRendered).toBeTruthy();
const isFirstMessageRendered = await element(
by.css('viewer-protolog .scroll-messages .message')
).isPresent();
expect(isFirstMessageRendered).toBeTruthy();
});
});

View File

@@ -19,7 +19,8 @@ import {E2eTestUtils} from './utils';
describe('Viewer ScreenRecording', () => {
beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000);
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
await browser.get(E2eTestUtils.WINSCOPE_URL);
});
it('processes trace and renders view', async () => {

View File

@@ -19,14 +19,16 @@ import {E2eTestUtils} from './utils';
describe('Viewer SurfaceFlinger', () => {
beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000);
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
}),
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
await browser.get(E2eTestUtils.WINSCOPE_URL);
});
const viewerPresent = await element(by.css('viewer-surface-flinger')).isPresent();
expect(viewerPresent).toBeTruthy();
});
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const viewerPresent = await element(by.css('viewer-surface-flinger')).isPresent();
expect(viewerPresent).toBeTruthy();
});
});

View File

@@ -19,19 +19,20 @@ import {E2eTestUtils} from './utils';
describe('Viewer Transactions', () => {
beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000);
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
}),
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/Transactions.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
await browser.get(E2eTestUtils.WINSCOPE_URL);
});
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/Transactions.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const isViewerRendered = await element(by.css('viewer-transactions')).isPresent();
expect(isViewerRendered).toBeTruthy();
const isViewerRendered = await element(by.css('viewer-transactions')).isPresent();
expect(isViewerRendered).toBeTruthy();
const isFirstEntryRendered = await element(
by.css('viewer-transactions .scroll .entry')
).isPresent();
expect(isFirstEntryRendered).toBeTruthy();
});
const isFirstEntryRendered = await element(
by.css('viewer-transactions .scroll .entry')
).isPresent();
expect(isFirstEntryRendered).toBeTruthy();
});
});

View File

@@ -19,7 +19,8 @@ import {E2eTestUtils} from './utils';
describe('Viewer Transitions', () => {
beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000);
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
await browser.get(E2eTestUtils.WINSCOPE_URL);
});
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture(

View File

@@ -18,15 +18,17 @@ import {E2eTestUtils} from './utils';
describe('Viewer WindowManager', () => {
beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000);
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
}),
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/WindowManager.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
browser.manage().timeouts().implicitlyWait(5000);
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
await browser.get(E2eTestUtils.WINSCOPE_URL);
});
const viewerPresent = await element(by.css('viewer-window-manager')).isPresent();
expect(viewerPresent).toBeTruthy();
});
it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/WindowManager.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const viewerPresent = await element(by.css('viewer-window-manager')).isPresent();
expect(viewerPresent).toBeTruthy();
});
});

View File

@@ -17,11 +17,13 @@ import {browser, by, element} from 'protractor';
import {E2eTestUtils} from './utils';
describe('winscope', () => {
beforeAll(() => {
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
}),
it('has title', () => {
const title = element(by.css('.app-title'));
expect(title.getText()).toContain('Winscope');
});
beforeAll(async () => {
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
await browser.get(E2eTestUtils.WINSCOPE_URL);
});
it('has title', () => {
const title = element(by.css('.app-title'));
expect(title.getText()).toContain('Winscope');
});
});

View File

@@ -18,12 +18,11 @@ import {Timestamp} from 'trace/timestamp';
import {AbsoluteFrameIndex, Trace} from 'trace/trace';
export class TraceUtils {
static extractEntries<T>(trace: Trace<T>): T[] {
const entries = new Array<T>();
trace.forEachEntry((entry) => {
entries.push(entry.getValue());
static async extractEntries<T>(trace: Trace<T>): Promise<T[]> {
const promises = trace.mapEntry(async (entry, index) => {
return await entry.getValue();
});
return entries;
return await Promise.all(promises);
}
static extractTimestamps<T>(trace: Trace<T>): Timestamp[] {
@@ -34,11 +33,12 @@ export class TraceUtils {
return timestamps;
}
static extractFrames<T>(trace: Trace<T>): Map<AbsoluteFrameIndex, T[]> {
static async extractFrames<T>(trace: Trace<T>): Promise<Map<AbsoluteFrameIndex, T[]>> {
const frames = new Map<AbsoluteFrameIndex, T[]>();
trace.forEachFrame((frame, index) => {
frames.set(index, TraceUtils.extractEntries(frame));
const promises = trace.mapFrame(async (frame, index) => {
frames.set(index, await TraceUtils.extractEntries(frame));
});
await Promise.all(promises);
return frames;
}
}

View File

@@ -14,31 +14,37 @@
* limitations under the License.
*/
import {assertDefined} from 'common/assert_utils';
import {AbsoluteFrameIndex} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TraceType} from 'trace/trace_type';
import {TraceUtils} from './trace_utils';
export class TracesUtils {
static extractEntries(traces: Traces): Map<TraceType, Array<{}>> {
static async extractEntries(traces: Traces): Promise<Map<TraceType, Array<{}>>> {
const entries = new Map<TraceType, Array<{}>>();
traces.forEachTrace((trace) => {
entries.set(trace.type, TraceUtils.extractEntries(trace));
const promises = traces.mapTrace(async (trace) => {
entries.set(trace.type, await TraceUtils.extractEntries(trace));
});
await Promise.all(promises);
return entries;
}
static extractFrames(traces: Traces): Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>> {
static async extractFrames(
traces: Traces
): Promise<Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>> {
const frames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
traces.forEachFrame((frame, index) => {
const framePromises = traces.mapFrame(async (frame, index) => {
frames.set(index, new Map<TraceType, Array<{}>>());
frame.forEachTrace((trace, type) => {
frames.get(index)?.set(type, TraceUtils.extractEntries(trace));
const tracePromises = frame.mapTrace(async (trace, type) => {
assertDefined(frames.get(index)).set(type, await TraceUtils.extractEntries(trace));
});
await Promise.all(tracePromises);
});
await Promise.all(framePromises);
return frames;
}

View File

@@ -15,6 +15,7 @@
*/
import {ParserFactory} from 'parsers/parser_factory';
import {TracesParserFactory} from 'parsers/traces_parser_factory';
import {CommonTestUtils} from 'test/common/utils';
import {LayerTraceEntry, WindowManagerState} from 'trace/flickerlib/common';
import {Parser} from 'trace/parser';
@@ -43,6 +44,15 @@ class UnitTestUtils extends CommonTestUtils {
return parsers[0].parser;
}
static async getTracesParser(filenames: string[]): Promise<Parser<object>> {
const parsers = await Promise.all(
filenames.map((filename) => UnitTestUtils.getParser(filename))
);
const tracesParsers = await new TracesParserFactory().createParsers(parsers);
expect(tracesParsers.length).toEqual(1);
return tracesParsers[0];
}
static async getWindowManagerState(): Promise<WindowManagerState> {
return UnitTestUtils.getTraceEntry('traces/elapsed_timestamp/WindowManager.pb');
}

View File

@@ -21,7 +21,12 @@
"rootWindowContainer",
"windowContainer",
"children",
"stableId"
"stableId",
"boxPos",
"classnameIndex",
"depth",
"zOrderRelativeOfId",
"isRootLayer"
],
"intDefColumn": {
"WindowLayoutParams.type": "android.view.WindowManager.LayoutParams.WindowType",
@@ -34,6 +39,7 @@
"WindowLayoutParams.behavior": "android.view.WindowInsetsController.Behavior",
"WindowLayoutParams.fitInsetsSides": "android.view.WindowInsets.Side.InsetsSide",
"InputWindowInfoProto.layoutParamsFlags": "android.view.WindowManager.LayoutParams.Flags",
"InputWindowInfoProto.inputConfig": "android.view.InputWindowHandle.InputConfigFlags",
"Configuration.windowingMode": "android.app.WindowConfiguration.WindowingMode",
"WindowConfiguration.windowingMode": "android.app.WindowConfiguration.WindowingMode",
"Configuration.orientation": "android.content.pm.ActivityInfo.ScreenOrientation",

View File

@@ -20,9 +20,7 @@ import intDefMapping from '../../../../../../prebuilts/misc/common/winscope/intD
import {
toActiveBuffer,
toColor,
toColor3,
toInsets,
toMatrix22,
toPoint,
toPointF,
toRect,
@@ -66,6 +64,9 @@ export class ObjectFormatter {
* @return The "true" properties of the entry as described above
*/
static getProperties(entry: any): string[] {
if (entry === null || entry === undefined) {
return [];
}
const props: string[] = [];
let obj = entry;
@@ -171,7 +172,7 @@ export class ObjectFormatter {
case `ActiveBufferProto`:
return toActiveBuffer(obj);
case `Color3`:
return toColor3(obj);
return toColor(obj, /* hasAlpha */ false);
case `ColorProto`:
return toColor(obj);
case `Long`:
@@ -184,8 +185,6 @@ export class ObjectFormatter {
// definition of insets and rects uses the same object type
case `RectProto`:
return key.toLowerCase().includes('insets') ? toInsets(obj) : toRect(obj);
case `Matrix22`:
return toMatrix22(obj);
case `FloatRectProto`:
return toRectF(obj);
case `RegionProto`:

View File

@@ -42,6 +42,8 @@ const WindowState = require('flicker').android.tools.common.traces.wm.WindowStat
const WindowToken = require('flicker').android.tools.common.traces.wm.WindowToken;
// SF
const HwcCompositionType =
require('flicker').android.tools.common.traces.surfaceflinger.HwcCompositionType;
const Layer = require('flicker').android.tools.common.traces.surfaceflinger.Layer;
const LayerProperties =
require('flicker').android.tools.common.traces.surfaceflinger.LayerProperties;
@@ -76,10 +78,8 @@ const WmTransitionData = require('flicker').android.tools.common.traces.wm.WmTra
// Common
const Size = require('flicker').android.tools.common.datatypes.Size;
const ActiveBuffer = require('flicker').android.tools.common.datatypes.ActiveBuffer;
const Color3 = require('flicker').android.tools.common.datatypes.Color3;
const Color = require('flicker').android.tools.common.datatypes.Color;
const Insets = require('flicker').android.tools.common.datatypes.Insets;
const Matrix22 = require('flicker').android.tools.common.datatypes.Matrix22;
const Matrix33 = require('flicker').android.tools.common.datatypes.Matrix33;
const PlatformConsts = require('flicker').android.tools.common.PlatformConsts;
const Rotation = require('flicker').android.tools.common.Rotation;
@@ -93,14 +93,12 @@ const TimestampFactory = require('flicker').android.tools.common.TimestampFactor
const EMPTY_SIZE = Size.Companion.EMPTY;
const EMPTY_BUFFER = ActiveBuffer.Companion.EMPTY;
const EMPTY_COLOR3 = Color3.Companion.EMPTY;
const EMPTY_COLOR = Color.Companion.EMPTY;
const EMPTY_INSETS = Insets.Companion.EMPTY;
const EMPTY_RECT = Rect.Companion.EMPTY;
const EMPTY_RECTF = RectF.Companion.EMPTY;
const EMPTY_POINT = Point.Companion.EMPTY;
const EMPTY_POINTF = PointF.Companion.EMPTY;
const EMPTY_MATRIX22 = Matrix22.Companion.EMPTY;
const EMPTY_MATRIX33 = Matrix33.Companion.identity(0, 0);
const EMPTY_TRANSFORM = new Transform(0, EMPTY_MATRIX33);
@@ -128,27 +126,17 @@ function toActiveBuffer(proto) {
return EMPTY_BUFFER;
}
function toColor3(proto) {
function toColor(proto, hasAlpha = true) {
if (proto == null) {
return EMPTY_COLOR;
}
const r = proto.r ?? 0;
const g = proto.g ?? 0;
const b = proto.b ?? 0;
if (r || g || b) {
return new Color3(r, g, b);
let a = proto.a;
if (a === null && !hasAlpha) {
a = 1;
}
return EMPTY_COLOR3;
}
function toColor(proto) {
if (proto == null) {
return EMPTY_COLOR;
}
const r = proto.r ?? 0;
const g = proto.g ?? 0;
const b = proto.b ?? 0;
const a = proto.a ?? 0;
if (r || g || b || a) {
return new Color(r, g, b, a);
}
@@ -194,6 +182,22 @@ function toInsets(proto) {
return EMPTY_INSETS;
}
function toCropRect(proto) {
if (proto == null) return EMPTY_RECT;
const right = proto.right || 0;
const left = proto.left || 0;
const bottom = proto.bottom || 0;
const top = proto.top || 0;
// crop (0,0) (-1,-1) means no crop
if (right == -1 && left == 0 && bottom == -1 && top == 0) EMPTY_RECT;
if (right - left <= 0 || bottom - top <= 0) return EMPTY_RECT;
return Rect.Companion.from(left, top, right, bottom);
}
function toRect(proto) {
if (proto == null) {
return EMPTY_RECT;
@@ -261,22 +265,6 @@ function toTransform(proto) {
return EMPTY_TRANSFORM;
}
function toMatrix22(proto) {
if (proto == null) {
return EMPTY_MATRIX22;
}
const dsdx = proto.dsdx ?? 0;
const dtdx = proto.dtdx ?? 0;
const dsdy = proto.dsdy ?? 0;
const dtdy = proto.dtdy ?? 0;
if (dsdx || dtdx || dsdy || dtdy) {
return new Matrix22(dsdx, dtdx, dsdy, dtdy);
}
return EMPTY_MATRIX22;
}
export {
Activity,
Configuration,
@@ -298,13 +286,13 @@ export {
WindowManagerState,
WindowManagerTraceEntryBuilder,
// SF
HwcCompositionType,
Layer,
LayerProperties,
LayerTraceEntry,
LayerTraceEntryBuilder,
LayersTrace,
Transform,
Matrix22,
Matrix33,
Display,
// Eventlog
@@ -328,7 +316,6 @@ export {
Size,
ActiveBuffer,
Color,
Color3,
Insets,
PlatformConsts,
Point,
@@ -343,24 +330,21 @@ export {
toSize,
toActiveBuffer,
toColor,
toColor3,
toCropRect,
toInsets,
toPoint,
toPointF,
toRect,
toRectF,
toRegion,
toMatrix22,
toTransform,
// Constants
EMPTY_BUFFER,
EMPTY_COLOR3,
EMPTY_COLOR,
EMPTY_RECT,
EMPTY_RECTF,
EMPTY_POINT,
EMPTY_POINTF,
EMPTY_MATRIX22,
EMPTY_MATRIX33,
EMPTY_TRANSFORM,
};

View File

@@ -15,11 +15,13 @@
*/
import {
HwcCompositionType,
Layer,
LayerProperties,
Rect,
toActiveBuffer,
toColor,
toCropRect,
toRect,
toRectF,
toRegion,
@@ -47,10 +49,7 @@ Layer.fromProto = (proto: any, excludesCompositionState = false): Layer => {
const inputRegion = toRegion(
proto.inputWindowInfo ? proto.inputWindowInfo.touchableRegion : null
);
let crop: Rect;
if (proto.crop) {
crop = toRect(proto.crop);
}
const crop: Rect = toCropRect(proto.crop);
const properties = new LayerProperties(
visibleRegion,
@@ -67,7 +66,7 @@ Layer.fromProto = (proto: any, excludesCompositionState = false): Layer => {
sourceBounds,
/* effectiveScalingMode */ proto.effectiveScalingMode,
bufferTransform,
/* hwcCompositionType */ proto.hwcCompositionType,
/* hwcCompositionType */ new HwcCompositionType(proto.hwcCompositionType),
hwcCrop,
hwcFrame,
/* backgroundBlurRadius */ proto.backgroundBlurRadius,

View File

@@ -27,14 +27,12 @@ Activity.fromProto = (proto: any, nextSeq: () => number): Activity => {
/* protoChildren */ proto.windowToken.windowContainer?.children ?? [],
/* isActivityInTree */ true,
/* computedZ */ nextSeq,
/* nameOverride */ null,
/* nameOverride */ proto.name,
/* identifierOverride */ proto.identifier
);
const entry = new Activity(
proto.name,
proto.state,
proto.visible,
proto.frontOfTask,
proto.procId,
proto.translucent,

View File

@@ -39,7 +39,8 @@ WindowContainer.fromProto = (
nextSeq: () => number,
nameOverride: string | null = null,
identifierOverride: string | null = null,
tokenOverride: any = null
tokenOverride: any = null,
visibleOverride: boolean | null = null
): WindowContainer => {
if (proto == null) {
return null;
@@ -61,7 +62,7 @@ WindowContainer.fromProto = (
token,
proto.orientation,
proto.surfaceControl?.layerId ?? 0,
proto.visible,
visibleOverride ?? proto.visible,
config,
children,
containerOrder
@@ -105,7 +106,7 @@ WindowContainer.childrenFromProto = (
};
function createConfigurationContainer(proto: any): ConfigurationContainer {
const entry = new ConfigurationContainer(
const entry = ConfigurationContainer.Companion.from(
createConfiguration(proto?.overrideConfiguration ?? null),
createConfiguration(proto?.fullConfiguration ?? null),
createConfiguration(proto?.mergedOverrideConfiguration ?? null)
@@ -125,7 +126,7 @@ function createConfiguration(proto: any): Configuration {
windowConfiguration = createWindowConfiguration(proto.windowConfiguration);
}
return new Configuration(
return Configuration.Companion.from(
windowConfiguration,
proto?.densityDpi ?? 0,
proto?.orientation ?? 0,
@@ -138,7 +139,7 @@ function createConfiguration(proto: any): Configuration {
}
function createWindowConfiguration(proto: any): WindowConfiguration {
return new WindowConfiguration(
return WindowConfiguration.Companion.from(
toRect(proto.appBounds),
toRect(proto.bounds),
toRect(proto.maxBounds),

View File

@@ -34,9 +34,9 @@ WindowManagerState.fromProto = (
realToElapsedTimeOffsetNs: bigint | undefined = undefined,
useElapsedTime = false
): WindowManagerState => {
const inputMethodWIndowAppToken = '';
let inputMethodWIndowAppToken = '';
if (proto.inputMethodWindow != null) {
proto.inputMethodWindow.hashCode.toString(16);
inputMethodWIndowAppToken = proto.inputMethodWindow.hashCode.toString(16);
}
let parseOrder = 0;
@@ -47,21 +47,21 @@ WindowManagerState.fromProto = (
);
const policy = createWindowManagerPolicy(proto.policy);
const entry = new WindowManagerTraceEntryBuilder(
`${elapsedTimestamp}`,
policy,
proto.focusedApp,
proto.focusedDisplayId,
proto.focusedWindow?.title ?? '',
inputMethodWIndowAppToken,
proto.rootWindowContainer.isHomeRecentsComponent,
proto.displayFrozen,
proto.rootWindowContainer.pendingActivities.map((it: any) => it.title),
rootWindowContainer,
keyguardControllerState,
where,
`${realToElapsedTimeOffsetNs ?? 0}`
).build();
const entry = new WindowManagerTraceEntryBuilder()
.setElapsedTimestamp(`${elapsedTimestamp}`)
.setPolicy(policy)
.setFocusedApp(proto.focusedApp)
.setFocusedDisplayId(proto.focusedDisplayId)
.setFocusedWindow(proto.focusedWindow?.title ?? '')
.setInputMethodWindowAppToken(inputMethodWIndowAppToken)
.setIsHomeRecentsComponent(proto.rootWindowContainer.isHomeRecentsComponent)
.setIsDisplayFrozen(proto.displayFrozen)
.setPendingActivities(proto.rootWindowContainer.pendingActivities.map((it: any) => it.title))
.setRoot(rootWindowContainer)
.setKeyguardControllerState(keyguardControllerState)
.setWhere(where)
.setRealToElapsedTimeOffsetNs(`${realToElapsedTimeOffsetNs ?? 0}`)
.build();
addAttributes(entry, proto, realToElapsedTimeOffsetNs === undefined || useElapsedTime);
return entry;

View File

@@ -26,9 +26,9 @@ export class FrameMapper {
constructor(private traces: Traces) {}
computeMapping() {
async computeMapping() {
this.pickMostReliableTraceAndSetInitialFrameInfo();
this.propagateFrameInfoToOtherTraces();
await this.propagateFrameInfoToOtherTraces();
}
private pickMostReliableTraceAndSetInitialFrameInfo() {
@@ -56,9 +56,9 @@ export class FrameMapper {
trace.setFrameInfo(frameMap, frameMap.getFullTraceFramesRange());
}
private propagateFrameInfoToOtherTraces() {
private async propagateFrameInfoToOtherTraces() {
this.tryPropagateFromScreenRecordingToSurfaceFlinger();
this.tryPropagateFromSurfaceFlingerToTransactions();
await this.tryPropagateFromSurfaceFlingerToTransactions();
this.tryPropagateFromTransactionsToWindowManager();
this.tryPropagateFromWindowManagerToProtoLog();
this.tryPropagateFromWindowManagerToIme();
@@ -90,7 +90,7 @@ export class FrameMapper {
surfaceFlinger.setFrameInfo(frameMap, frameMap.getFullTraceFramesRange());
}
private tryPropagateFromSurfaceFlingerToTransactions() {
private async tryPropagateFromSurfaceFlingerToTransactions() {
const frameMapBuilder = this.tryStartFrameMapping(
TraceType.SURFACE_FLINGER,
TraceType.TRANSACTIONS
@@ -103,14 +103,16 @@ export class FrameMapper {
const surfaceFlinger = assertDefined(this.traces.getTrace(TraceType.SURFACE_FLINGER));
const vsyncIdToFrames = new Map<bigint, FramesRange>();
surfaceFlinger.forEachEntry((srcEntry) => {
const vsyncId = this.getVsyncIdProperty(srcEntry, 'vSyncId');
for (let srcEntryIndex = 0; srcEntryIndex < surfaceFlinger.lengthEntries; ++srcEntryIndex) {
const srcEntry = surfaceFlinger.getEntry(srcEntryIndex);
const vsyncId = await this.getVsyncIdProperty(srcEntry, 'vSyncId');
if (vsyncId === undefined) {
return;
continue;
}
const srcFrames = srcEntry.getFramesRange();
if (!srcFrames) {
return;
continue;
}
let frames = vsyncIdToFrames.get(vsyncId);
if (!frames) {
@@ -119,19 +121,20 @@ export class FrameMapper {
frames.start = Math.min(frames.start, srcFrames.start);
frames.end = Math.max(frames.end, srcFrames.end);
vsyncIdToFrames.set(vsyncId, frames);
});
}
transactions.forEachEntry((dstEntry) => {
const vsyncId = this.getVsyncIdProperty(dstEntry, 'vsyncId');
for (let dstEntryIndex = 0; dstEntryIndex < transactions.lengthEntries; ++dstEntryIndex) {
const dstEntry = transactions.getEntry(dstEntryIndex);
const vsyncId = await this.getVsyncIdProperty(dstEntry, 'vsyncId');
if (vsyncId === undefined) {
return;
continue;
}
const frames = vsyncIdToFrames.get(vsyncId);
if (frames === undefined) {
return;
continue;
}
frameMapBuilder.setFrames(dstEntry.getIndex(), frames);
});
}
const frameMap = frameMapBuilder.build();
transactions.setFrameInfo(frameMap, frameMap.getFullTraceFramesRange());
@@ -276,8 +279,11 @@ export class FrameMapper {
return new FrameMapBuilder(dstTrace.lengthEntries, lengthFrames);
}
private getVsyncIdProperty(entry: TraceEntry<object>, propertyKey: string): bigint | undefined {
const entryValue = entry.getValue();
private async getVsyncIdProperty(
entry: TraceEntry<object>,
propertyKey: string
): Promise<bigint | undefined> {
const entryValue = await entry.getValue();
const vsyncId = (entryValue as any)[propertyKey];
if (vsyncId === undefined) {
console.error(`Failed to get trace entry's '${propertyKey}' property:`, entryValue);

View File

@@ -46,7 +46,7 @@ describe('FrameMapper', () => {
let windowManager: Trace<WindowManagerState>;
let traces: Traces;
beforeAll(() => {
beforeAll(async () => {
// Frames F0 F1
// |<------>| |<->|
// PROTO_LOG: 0 1 2 3 4 5
@@ -75,10 +75,10 @@ describe('FrameMapper', () => {
traces = new Traces();
traces.setTrace(TraceType.PROTO_LOG, protoLog);
traces.setTrace(TraceType.WINDOW_MANAGER, windowManager);
new FrameMapper(traces).computeMapping();
await new FrameMapper(traces).computeMapping();
});
it('associates entries/frames', () => {
it('associates entries/frames', async () => {
const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
expectedFrames.set(
0,
@@ -95,7 +95,7 @@ describe('FrameMapper', () => {
])
);
expect(TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
});
});
@@ -104,7 +104,7 @@ describe('FrameMapper', () => {
let windowManager: Trace<WindowManagerState>;
let traces: Traces;
beforeAll(() => {
beforeAll(async () => {
// IME: 0--1--2 3
// | |
// WINDOW_MANAGER: 0 1 2
@@ -131,10 +131,10 @@ describe('FrameMapper', () => {
traces = new Traces();
traces.setTrace(TraceType.INPUT_METHOD_CLIENTS, ime);
traces.setTrace(TraceType.WINDOW_MANAGER, windowManager);
new FrameMapper(traces).computeMapping();
await new FrameMapper(traces).computeMapping();
});
it('associates entries/frames', () => {
it('associates entries/frames', async () => {
const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
expectedFrames.set(
0,
@@ -158,7 +158,7 @@ describe('FrameMapper', () => {
])
);
expect(TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
});
});
@@ -167,7 +167,7 @@ describe('FrameMapper', () => {
let transactions: Trace<object>;
let traces: Traces;
beforeAll(() => {
beforeAll(async () => {
// WINDOW_MANAGER: 0 1 2 3
// | | | \
// TRANSACTIONS: 0 1 2--3 4 5 ... 6 <-- ignored (not connected) because too far
@@ -207,10 +207,10 @@ describe('FrameMapper', () => {
traces = new Traces();
traces.setTrace(TraceType.WINDOW_MANAGER, windowManager);
traces.setTrace(TraceType.TRANSACTIONS, transactions);
new FrameMapper(traces).computeMapping();
await new FrameMapper(traces).computeMapping();
});
it('associates entries/frames', () => {
it('associates entries/frames', async () => {
const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
expectedFrames.set(
0,
@@ -255,7 +255,7 @@ describe('FrameMapper', () => {
])
);
expect(TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
});
});
@@ -264,7 +264,7 @@ describe('FrameMapper', () => {
let surfaceFlinger: Trace<LayerTraceEntry>;
let traces: Traces;
beforeAll(() => {
beforeAll(async () => {
// TRANSACTIONS: 0 1--2 3 4
// \ \ \
// \ \ \
@@ -292,16 +292,16 @@ describe('FrameMapper', () => {
traces = new Traces();
traces.setTrace(TraceType.TRANSACTIONS, transactions);
traces.setTrace(TraceType.SURFACE_FLINGER, surfaceFlinger);
new FrameMapper(traces).computeMapping();
await new FrameMapper(traces).computeMapping();
});
it('associates entries/frames', () => {
it('associates entries/frames', async () => {
const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
expectedFrames.set(
0,
new Map<TraceType, Array<{}>>([
[TraceType.TRANSACTIONS, [transactions.getEntry(0).getValue()]],
[TraceType.SURFACE_FLINGER, [surfaceFlinger.getEntry(0).getValue()]],
[TraceType.TRANSACTIONS, [await transactions.getEntry(0).getValue()]],
[TraceType.SURFACE_FLINGER, [await surfaceFlinger.getEntry(0).getValue()]],
])
);
expectedFrames.set(
@@ -309,20 +309,20 @@ describe('FrameMapper', () => {
new Map<TraceType, Array<{}>>([
[
TraceType.TRANSACTIONS,
[transactions.getEntry(1).getValue(), transactions.getEntry(2).getValue()],
[await transactions.getEntry(1).getValue(), await transactions.getEntry(2).getValue()],
],
[TraceType.SURFACE_FLINGER, [surfaceFlinger.getEntry(1).getValue()]],
[TraceType.SURFACE_FLINGER, [await surfaceFlinger.getEntry(1).getValue()]],
])
);
expectedFrames.set(
2,
new Map<TraceType, Array<{}>>([
[TraceType.TRANSACTIONS, [transactions.getEntry(3).getValue()]],
[TraceType.SURFACE_FLINGER, [surfaceFlinger.getEntry(2).getValue()]],
[TraceType.TRANSACTIONS, [await transactions.getEntry(3).getValue()]],
[TraceType.SURFACE_FLINGER, [await surfaceFlinger.getEntry(2).getValue()]],
])
);
expect(TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
});
});
@@ -331,7 +331,7 @@ describe('FrameMapper', () => {
let screenRecording: Trace<ScreenRecordingTraceEntry>;
let traces: Traces;
beforeAll(() => {
beforeAll(async () => {
// SURFACE_FLINGER: 0 1 2--- 3 4 5 6
// \ \ \ \
// \ \ \ \
@@ -365,10 +365,10 @@ describe('FrameMapper', () => {
traces = new Traces();
traces.setTrace(TraceType.SURFACE_FLINGER, surfaceFlinger);
traces.setTrace(TraceType.SCREEN_RECORDING, screenRecording);
new FrameMapper(traces).computeMapping();
await new FrameMapper(traces).computeMapping();
});
it('associates entries/frames', () => {
it('associates entries/frames', async () => {
const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
expectedFrames.set(
0,
@@ -413,7 +413,7 @@ describe('FrameMapper', () => {
])
);
expect(TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
});
});

View File

@@ -21,7 +21,7 @@ export interface Parser<T> {
getTraceType(): TraceType;
getLengthEntries(): number;
getTimestamps(type: TimestampType): Timestamp[] | undefined;
getEntry(index: number, timestampType: TimestampType): T;
getEntry(index: number, timestampType: TimestampType): Promise<T>;
getDescriptors(): string[];
}

View File

@@ -16,7 +16,6 @@
import {Parser} from './parser';
import {RealTimestamp, Timestamp, TimestampType} from './timestamp';
import {TraceFile} from './trace_file';
import {TraceType} from './trace_type';
export class ParserMock<T> implements Parser<T> {
@@ -30,10 +29,6 @@ export class ParserMock<T> implements Parser<T> {
return TraceType.SURFACE_FLINGER;
}
getTraceFile(): TraceFile {
return new TraceFile(new File([], 'file_name'));
}
getLengthEntries(): number {
return this.entries.length;
}
@@ -45,8 +40,8 @@ export class ParserMock<T> implements Parser<T> {
return this.timestamps;
}
getEntry(index: number): T {
return this.entries[index];
getEntry(index: number): Promise<T> {
return Promise.resolve(this.entries[index]);
}
getDescriptors(): string[] {

View File

@@ -25,7 +25,6 @@ import {
} from './index_types';
import {Parser} from './parser';
import {Timestamp, TimestampType} from './timestamp';
import {TraceFile} from './trace_file';
import {TraceType} from './trace_type';
export {
@@ -66,16 +65,18 @@ export class TraceEntry<T> {
return this.framesRange;
}
getValue(): T {
return this.parser.getEntry(this.index, this.timestamp.getType());
async getValue(): Promise<T> {
return await this.parser.getEntry(this.index, this.timestamp.getType());
}
}
export class Trace<T> {
readonly file?: TraceFile;
readonly type: TraceType;
readonly lengthEntries: number;
readonly fullTrace: Trace<T>;
private readonly parser: Parser<T>;
private readonly descriptors: string[];
private readonly fullTrace: Trace<T>;
private timestampType: TimestampType | undefined;
private readonly entriesRange: EntriesRange;
private frameMap?: FrameMap;
@@ -107,13 +108,16 @@ export class Trace<T> {
}
private constructor(
readonly type: TraceType,
readonly parser: Parser<T>,
readonly descriptors: string[],
type: TraceType,
parser: Parser<T>,
descriptors: string[],
fullTrace: Trace<T> | undefined,
timestampType: TimestampType | undefined,
entriesRange: EntriesRange | undefined
) {
this.type = type;
this.parser = parser;
this.descriptors = descriptors;
this.fullTrace = fullTrace ?? this;
this.entriesRange = entriesRange ?? {start: 0, end: parser.getLengthEntries()};
this.lengthEntries = this.entriesRange.end - this.entriesRange.start;
@@ -323,6 +327,14 @@ export class Trace<T> {
}
}
mapEntry<U>(callback: (entry: TraceEntry<T>, index: RelativeEntryIndex) => U): U[] {
const result: U[] = [];
this.forEachEntry((entry, index) => {
result.push(callback(entry, index));
});
return result;
}
forEachTimestamp(callback: (timestamp: Timestamp, index: RelativeEntryIndex) => void) {
const timestamps = this.getFullTraceTimestamps();
for (let index = 0; index < this.lengthEntries; ++index) {
@@ -340,6 +352,14 @@ export class Trace<T> {
}
}
mapFrame<U>(callback: (frame: Trace<T>, index: AbsoluteFrameIndex) => U): U[] {
const result: U[] = [];
this.forEachFrame((traces, index) => {
result.push(callback(traces, index));
});
return result;
}
getFramesRange(): FramesRange | undefined {
this.checkTraceCanBeAccessedInFrameDomain();
return this.framesRange;

View File

@@ -66,8 +66,8 @@ describe('TraceEntry', () => {
expect(trace.getEntry(5).getFramesRange()).toEqual({start: 4, end: 5});
});
it('getValue()', () => {
expect(trace.getEntry(0).getValue()).toEqual('entry-0');
expect(trace.getEntry(1).getValue()).toEqual('entry-1');
it('getValue()', async () => {
expect(await trace.getEntry(0).getValue()).toEqual('entry-0');
expect(await trace.getEntry(1).getValue()).toEqual('entry-1');
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -43,6 +43,7 @@ export enum TraceType {
ERROR,
TEST_TRACE_STRING,
TEST_TRACE_NUMBER,
VIEW_CAPTURE,
}
export interface TraceEntryTypeMap {
@@ -69,4 +70,5 @@ export interface TraceEntryTypeMap {
[TraceType.ERROR]: object;
[TraceType.TEST_TRACE_STRING]: string;
[TraceType.TEST_TRACE_NUMBER]: number;
[TraceType.VIEW_CAPTURE]: object;
}

View File

@@ -56,6 +56,14 @@ export class Traces {
});
}
mapTrace<T>(callback: (trace: Trace<{}>, type: TraceType) => T): T[] {
const result: T[] = [];
this.forEachTrace((trace, type) => {
result.push(callback(trace, type));
});
return result;
}
forEachFrame(callback: (traces: Traces, index: AbsoluteFrameIndex) => void): void {
let startFrameIndex: AbsoluteFrameIndex = Number.MAX_VALUE;
let endFrameIndex: AbsoluteFrameIndex = Number.MIN_VALUE;
@@ -72,4 +80,20 @@ export class Traces {
callback(this.sliceFrames(i, i + 1), i);
}
}
mapFrame<T>(callback: (traces: Traces, index: AbsoluteFrameIndex) => T): T[] {
const result: T[] = [];
this.forEachFrame((traces, index) => {
result.push(callback(traces, index));
});
return result;
}
getSize(): number {
return this.traces.size;
}
[Symbol.iterator]() {
return this.traces.values();
}
}

View File

@@ -14,11 +14,12 @@
* limitations under the License.
*/
import {assertDefined} from 'common/assert_utils';
import {FunctionUtils} from 'common/function_utils';
import {TracesBuilder} from 'test/unit/traces_builder';
import {TracesUtils} from 'test/unit/traces_utils';
import {TraceBuilder} from 'test/unit/trace_builder';
import {TraceUtils} from 'test/unit/trace_utils';
import {assertDefined} from '../common/assert_utils';
import {FrameMapBuilder} from './frame_map_builder';
import {AbsoluteFrameIndex} from './index_types';
import {RealTimestamp} from './timestamp';
@@ -134,31 +135,31 @@ describe('Traces', () => {
);
});
it('getTrace()', () => {
it('getTrace()', async () => {
expect(
TraceUtils.extractEntries(assertDefined(traces.getTrace(TraceType.TEST_TRACE_STRING)))
await TraceUtils.extractEntries(assertDefined(traces.getTrace(TraceType.TEST_TRACE_STRING)))
).toEqual(extractedEntriesFull.get(TraceType.TEST_TRACE_STRING) as string[]);
expect(
TraceUtils.extractEntries(assertDefined(traces.getTrace(TraceType.TEST_TRACE_NUMBER)))
await TraceUtils.extractEntries(assertDefined(traces.getTrace(TraceType.TEST_TRACE_NUMBER)))
).toEqual(extractedEntriesFull.get(TraceType.TEST_TRACE_NUMBER) as number[]);
expect(traces.getTrace(TraceType.SURFACE_FLINGER)).toBeUndefined();
});
it('sliceTime()', () => {
it('sliceTime()', async () => {
// empty
{
const slice = traces.sliceTime(time3, time3);
expect(TracesUtils.extractEntries(slice)).toEqual(extractedEntriesEmpty);
expect(await TracesUtils.extractEntries(slice)).toEqual(extractedEntriesEmpty);
}
// full
{
const slice = traces.sliceTime();
expect(TracesUtils.extractEntries(slice)).toEqual(extractedEntriesFull);
expect(await TracesUtils.extractEntries(slice)).toEqual(extractedEntriesFull);
}
// middle
{
const slice = traces.sliceTime(time4, time8);
expect(TracesUtils.extractEntries(slice)).toEqual(
expect(await TracesUtils.extractEntries(slice)).toEqual(
new Map<TraceType, Array<{}>>([
[TraceType.TEST_TRACE_STRING, ['2', '3']],
[TraceType.TEST_TRACE_NUMBER, [1, 2]],
@@ -168,7 +169,7 @@ describe('Traces', () => {
// slice away front
{
const slice = traces.sliceTime(time8);
expect(TracesUtils.extractEntries(slice)).toEqual(
expect(await TracesUtils.extractEntries(slice)).toEqual(
new Map<TraceType, Array<{}>>([
[TraceType.TEST_TRACE_STRING, ['4']],
[TraceType.TEST_TRACE_NUMBER, [3, 4]],
@@ -178,7 +179,7 @@ describe('Traces', () => {
// slice away back
{
const slice = traces.sliceTime(undefined, time8);
expect(TracesUtils.extractEntries(slice)).toEqual(
expect(await TracesUtils.extractEntries(slice)).toEqual(
new Map<TraceType, Array<{}>>([
[TraceType.TEST_TRACE_STRING, ['0', '1', '2', '3']],
[TraceType.TEST_TRACE_NUMBER, [0, 1, 2]],
@@ -187,16 +188,16 @@ describe('Traces', () => {
}
});
it('sliceFrames()', () => {
it('sliceFrames()', async () => {
// empty
{
const slice = traces.sliceFrames(1, 1);
expect(TracesUtils.extractFrames(slice)).toEqual(extractedFramesEmpty);
expect(await TracesUtils.extractFrames(slice)).toEqual(extractedFramesEmpty);
}
// full
{
const slice = traces.sliceFrames();
expect(TracesUtils.extractFrames(slice)).toEqual(extractedFramesFull);
expect(await TracesUtils.extractFrames(slice)).toEqual(extractedFramesFull);
}
// middle
{
@@ -204,7 +205,7 @@ describe('Traces', () => {
const expectedFrames = structuredClone(extractedFramesFull);
expectedFrames.delete(0);
expectedFrames.delete(4);
expect(TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
expect(await TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
}
// slice away front
{
@@ -212,7 +213,7 @@ describe('Traces', () => {
const expectedFrames = structuredClone(extractedFramesFull);
expectedFrames.delete(0);
expectedFrames.delete(1);
expect(TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
expect(await TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
}
// slice away back
{
@@ -221,23 +222,24 @@ describe('Traces', () => {
expectedFrames.delete(2);
expectedFrames.delete(3);
expectedFrames.delete(4);
expect(TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
expect(await TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
}
});
it('forEachTrace()', () => {
traces.forEachTrace((trace) => {
it('mapTrace()', async () => {
const promises = traces.mapTrace(async (trace) => {
const expectedEntries = extractedEntriesFull.get(trace.type) as Array<{}>;
const actualEntries = TraceUtils.extractEntries(trace);
const actualEntries = await TraceUtils.extractEntries(trace);
expect(actualEntries).toEqual(expectedEntries);
});
await Promise.all(promises);
});
it('forEachFrame()', () => {
expect(TracesUtils.extractFrames(traces)).toEqual(extractedFramesFull);
it('mapFrame()', async () => {
expect(await TracesUtils.extractFrames(traces)).toEqual(extractedFramesFull);
});
it('it supports empty traces', () => {
it('it supports empty traces', async () => {
const traces = new TracesBuilder()
.setEntries(TraceType.TEST_TRACE_STRING, [])
.setFrameMap(TraceType.TEST_TRACE_STRING, new FrameMapBuilder(0, 0).build())
@@ -246,21 +248,25 @@ describe('Traces', () => {
.setFrameMap(TraceType.TEST_TRACE_NUMBER, new FrameMapBuilder(0, 0).build())
.build();
expect(TracesUtils.extractEntries(traces)).toEqual(extractedEntriesEmpty);
expect(TracesUtils.extractFrames(traces)).toEqual(extractedFramesEmpty);
expect(await TracesUtils.extractEntries(traces)).toEqual(extractedEntriesEmpty);
expect(await TracesUtils.extractFrames(traces)).toEqual(extractedFramesEmpty);
expect(TracesUtils.extractEntries(traces.sliceTime(time1, time10))).toEqual(
expect(await TracesUtils.extractEntries(traces.sliceTime(time1, time10))).toEqual(
extractedEntriesEmpty
);
expect(TracesUtils.extractFrames(traces.sliceTime(time1, time10))).toEqual(
expect(await TracesUtils.extractFrames(traces.sliceTime(time1, time10))).toEqual(
extractedFramesEmpty
);
expect(TracesUtils.extractEntries(traces.sliceFrames(0, 10))).toEqual(extractedEntriesEmpty);
expect(TracesUtils.extractFrames(traces.sliceFrames(0, 10))).toEqual(extractedFramesEmpty);
expect(await TracesUtils.extractEntries(traces.sliceFrames(0, 10))).toEqual(
extractedEntriesEmpty
);
expect(await TracesUtils.extractFrames(traces.sliceFrames(0, 10))).toEqual(
extractedFramesEmpty
);
});
it('it supports unavailable frame mapping', () => {
it('it supports unavailable frame mapping', async () => {
const traces = new TracesBuilder()
.setEntries(TraceType.TEST_TRACE_STRING, ['entry-0'])
.setTimestamps(TraceType.TEST_TRACE_STRING, [time1])
@@ -276,13 +282,17 @@ describe('Traces', () => {
[TraceType.TEST_TRACE_NUMBER, [0]],
]);
expect(TracesUtils.extractEntries(traces)).toEqual(expectedEntries);
expect(TracesUtils.extractEntries(traces.sliceTime())).toEqual(expectedEntries);
expect(await TracesUtils.extractEntries(traces)).toEqual(expectedEntries);
expect(await TracesUtils.extractEntries(traces.sliceTime())).toEqual(expectedEntries);
expect(() => {
traces.sliceFrames();
}).toThrow();
expect(() => {
TracesUtils.extractFrames(traces);
traces.forEachFrame(FunctionUtils.DO_NOTHING);
}).toThrow();
expect(() => {
traces.mapFrame(FunctionUtils.DO_NOTHING);
}).toThrow();
});
});

View File

@@ -101,7 +101,7 @@ export abstract class PresenterInputMethod {
this.notifyViewCallback(this.uiData);
}
onTracePositionUpdate(position: TracePosition) {
async onTracePositionUpdate(position: TracePosition) {
this.uiData = new ImeUiData(this.dependencies);
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
@@ -109,11 +109,11 @@ export abstract class PresenterInputMethod {
const [imeEntry, sfEntry, wmEntry] = this.findTraceEntries(position);
if (imeEntry) {
this.entry = imeEntry.getValue() as TraceTreeNode;
this.entry = (await imeEntry.getValue()) as TraceTreeNode;
this.uiData.highlightedItems = this.highlightedItems;
this.uiData.additionalProperties = this.getAdditionalProperties(
wmEntry?.getValue(),
sfEntry?.getValue()
await wmEntry?.getValue(),
await sfEntry?.getValue()
);
this.uiData.tree = this.generateTree();
this.uiData.hierarchyTableProperties = this.updateHierarchyTableProperties();

View File

@@ -57,7 +57,7 @@ export function executePresenterInputMethodTests(
]);
});
it('is robust to empty trace', () => {
it('is robust to empty trace', async () => {
const traces = new TracesBuilder().setEntries(imeTraceType, []).build();
presenter = createPresenter(traces);
@@ -65,7 +65,7 @@ export function executePresenterInputMethodTests(
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(uiData.tree).toBeFalsy();
presenter.onTracePositionUpdate(position);
await presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(uiData.tree).toBeFalsy();
@@ -73,7 +73,7 @@ export function executePresenterInputMethodTests(
it('is robust to traces without SF', async () => {
await setUpTestEnvironment([imeTraceType, TraceType.WINDOW_MANAGER]);
presenter.onTracePositionUpdate(position);
await presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
@@ -81,7 +81,7 @@ export function executePresenterInputMethodTests(
it('is robust to traces without WM', async () => {
await setUpTestEnvironment([imeTraceType, TraceType.SURFACE_FLINGER]);
presenter.onTracePositionUpdate(position);
await presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
@@ -89,14 +89,14 @@ export function executePresenterInputMethodTests(
it('is robust to traces without WM and SF', async () => {
await setUpTestEnvironment([imeTraceType]);
presenter.onTracePositionUpdate(position);
await presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it('processes trace position updates', () => {
presenter.onTracePositionUpdate(position);
it('processes trace position updates', async () => {
await presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
@@ -120,7 +120,7 @@ export function executePresenterInputMethodTests(
expect(uiData.highlightedItems).toContain(id);
});
it('can update hierarchy tree', () => {
it('can update hierarchy tree', async () => {
//change flat view to true
const userOptions: UserOptions = {
onlyVisible: {
@@ -138,7 +138,7 @@ export function executePresenterInputMethodTests(
};
let expectedChildren = expectHierarchyTreeWithSfSubtree ? 2 : 1;
presenter.onTracePositionUpdate(position);
await presenter.onTracePositionUpdate(position);
expect(uiData.tree?.children.length).toEqual(expectedChildren);
// Filter out non-visible child
@@ -148,7 +148,7 @@ export function executePresenterInputMethodTests(
expect(uiData.tree?.children.length).toEqual(expectedChildren);
});
it('can filter hierarchy tree', () => {
it('can filter hierarchy tree', async () => {
const userOptions: UserOptions = {
onlyVisible: {
name: 'Only visible',
@@ -165,7 +165,7 @@ export function executePresenterInputMethodTests(
};
const expectedChildren = expectHierarchyTreeWithSfSubtree ? 12 : 1;
presenter.onTracePositionUpdate(position);
await presenter.onTracePositionUpdate(position);
presenter.updateHierarchyTree(userOptions);
expect(uiData.tree?.children.length).toEqual(expectedChildren);
@@ -174,15 +174,15 @@ export function executePresenterInputMethodTests(
expect(uiData.tree?.children.length).toEqual(0);
});
it('can set new properties tree and associated ui data', () => {
presenter.onTracePositionUpdate(position);
it('can set new properties tree and associated ui data', async () => {
await presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
// does not check specific tree values as tree transformation method may change
expect(uiData.propertiesTree).toBeTruthy();
});
it('can filter properties tree', () => {
presenter.onTracePositionUpdate(position);
it('can filter properties tree', async () => {
await presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
let nonTerminalChildren =
uiData.propertiesTree?.children?.filter(

View File

@@ -27,6 +27,7 @@ export interface Rectangle {
isVirtual: boolean;
isClickable: boolean;
cornerRadius: number;
depth?: number;
}
export interface Point {

View File

@@ -29,8 +29,8 @@ abstract class ViewerInputMethod implements Viewer {
this.addViewerEventListeners();
}
onTracePositionUpdate(position: TracePosition) {
this.presenter.onTracePositionUpdate(position);
async onTracePositionUpdate(position: TracePosition) {
await this.presenter.onTracePositionUpdate(position);
}
abstract getViews(): View[];

View File

@@ -180,7 +180,11 @@ class Mapper3D {
const maxDisplaySize = this.getMaxDisplaySize(rects2d);
const rects3d = rects2d.map((rect2d): Rect3D => {
z -= Mapper3D.Z_SPACING_MAX * this.zSpacingFactor;
if (rect2d.depth !== undefined) {
z = Mapper3D.Z_SPACING_MAX * this.zSpacingFactor * rect2d.depth;
} else {
z -= Mapper3D.Z_SPACING_MAX * this.zSpacingFactor;
}
const darkFactor = rect2d.isVisible
? (visibleRectsTotal - visibleRectsSoFar++) / visibleRectsTotal

View File

@@ -33,6 +33,7 @@ import {Distance2D} from './types3d';
>Only visible
</mat-checkbox>
<mat-checkbox
*ngIf="enableShowVirtualButton"
color="primary"
[disabled]="mapper3d.getShowOnlyVisibleMode()"
[checked]="mapper3d.getShowVirtualMode()"
@@ -166,6 +167,7 @@ import {Distance2D} from './types3d';
})
export class RectsComponent implements OnInit, OnDestroy {
@Input() title = 'title';
@Input() enableShowVirtualButton: boolean = true;
@Input() set rects(rects: Rectangle[]) {
this.internalRects = rects;
this.drawScene();

View File

@@ -33,7 +33,7 @@ class View {
}
interface Viewer {
onTracePositionUpdate(position: TracePosition): void;
onTracePositionUpdate(position: TracePosition): Promise<void>;
getViews(): View[];
getDependencies(): TraceType[];
}

View File

@@ -25,6 +25,7 @@ import {ViewerScreenRecording} from './viewer_screen_recording/viewer_screen_rec
import {ViewerSurfaceFlinger} from './viewer_surface_flinger/viewer_surface_flinger';
import {ViewerTransactions} from './viewer_transactions/viewer_transactions';
import {ViewerTransitions} from './viewer_transitions/viewer_transitions';
import {ViewerViewCapture} from './viewer_view_capture/viewer_view_capture';
import {ViewerWindowManager} from './viewer_window_manager/viewer_window_manager';
class ViewerFactory {
@@ -41,6 +42,7 @@ class ViewerFactory {
ViewerProtoLog,
ViewerScreenRecording,
ViewerTransitions,
ViewerViewCapture,
];
createViewers(activeTraceTypes: Set<TraceType>, traces: Traces, storage: Storage): Viewer[] {

View File

@@ -15,107 +15,142 @@
*/
import {ArrayUtils} from 'common/array_utils';
import {assertDefined} from 'common/assert_utils';
import {LogMessage} from 'trace/protolog';
import {Trace, TraceEntry} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TraceEntryFinder} from 'trace/trace_entry_finder';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {assertDefined} from '../../common/assert_utils';
import {UiData} from './ui_data';
import {UiData, UiDataMessage} from './ui_data';
export class Presenter {
private readonly trace: Trace<LogMessage>;
private readonly notifyUiDataCallback: (data: UiData) => void;
private entry?: TraceEntry<LogMessage>;
private originalIndicesOfFilteredOutputMessages: number[];
private uiData = UiData.EMPTY;
private originalIndicesOfFilteredOutputMessages: number[] = [];
private tags: string[] = [];
private files: string[] = [];
private levels: string[] = [];
private isInitialized = false;
private allUiDataMessages: UiDataMessage[] = [];
private allTags: string[] = [];
private allSourceFiles: string[] = [];
private allLogLevels: string[] = [];
private tagsFilter: string[] = [];
private filesFilter: string[] = [];
private levelsFilter: string[] = [];
private searchString = '';
constructor(traces: Traces, notifyUiDataCallback: (data: UiData) => void) {
this.trace = assertDefined(traces.getTrace(TraceType.PROTO_LOG));
this.notifyUiDataCallback = notifyUiDataCallback;
this.originalIndicesOfFilteredOutputMessages = [];
this.computeUiDataMessages();
this.notifyUiDataCallback(this.uiData);
}
onTracePositionUpdate(position: TracePosition) {
async onTracePositionUpdate(position: TracePosition) {
await this.initializeIfNeeded();
this.entry = TraceEntryFinder.findCorrespondingEntry(this.trace, position);
this.computeUiDataCurrentMessageIndex();
this.notifyUiDataCallback(this.uiData);
}
onLogLevelsFilterChanged(levels: string[]) {
this.levels = levels;
this.computeUiDataMessages();
this.levelsFilter = levels;
this.computeUiData();
this.computeUiDataCurrentMessageIndex();
this.notifyUiDataCallback(this.uiData);
}
onTagsFilterChanged(tags: string[]) {
this.tags = tags;
this.computeUiDataMessages();
this.tagsFilter = tags;
this.computeUiData();
this.computeUiDataCurrentMessageIndex();
this.notifyUiDataCallback(this.uiData);
}
onSourceFilesFilterChanged(files: string[]) {
this.files = files;
this.computeUiDataMessages();
this.filesFilter = files;
this.computeUiData();
this.computeUiDataCurrentMessageIndex();
this.notifyUiDataCallback(this.uiData);
}
onSearchStringFilterChanged(searchString: string) {
this.searchString = searchString;
this.computeUiDataMessages();
this.computeUiData();
this.computeUiDataCurrentMessageIndex();
this.notifyUiDataCallback(this.uiData);
}
private computeUiDataMessages() {
const allLogLevels = this.getUniqueMessageValues((message: LogMessage) => message.level);
const allTags = this.getUniqueMessageValues((message: LogMessage) => message.tag);
const allSourceFiles = this.getUniqueMessageValues((message: LogMessage) => message.at);
let filteredMessagesAndOriginalIndex = new Array<[number, LogMessage]>();
this.trace.forEachEntry((entry) => {
filteredMessagesAndOriginalIndex.push([entry.getIndex(), entry.getValue()]);
});
if (this.levels.length > 0) {
filteredMessagesAndOriginalIndex = filteredMessagesAndOriginalIndex.filter((value) =>
this.levels.includes(value[1].level)
);
private async initializeIfNeeded() {
if (this.isInitialized) {
return;
}
if (this.tags.length > 0) {
filteredMessagesAndOriginalIndex = filteredMessagesAndOriginalIndex.filter((value) =>
this.tags.includes(value[1].tag)
);
}
this.allUiDataMessages = await this.makeAllUiDataMessages();
if (this.files.length > 0) {
filteredMessagesAndOriginalIndex = filteredMessagesAndOriginalIndex.filter((value) =>
this.files.includes(value[1].at)
);
}
filteredMessagesAndOriginalIndex = filteredMessagesAndOriginalIndex.filter((value) =>
value[1].text.includes(this.searchString)
this.allLogLevels = this.getUniqueMessageValues(
this.allUiDataMessages,
(message: LogMessage) => message.level
);
this.allTags = this.getUniqueMessageValues(
this.allUiDataMessages,
(message: LogMessage) => message.tag
);
this.allSourceFiles = this.getUniqueMessageValues(
this.allUiDataMessages,
(message: LogMessage) => message.at
);
this.originalIndicesOfFilteredOutputMessages = filteredMessagesAndOriginalIndex.map(
(value) => value[0]
);
const filteredMessages = filteredMessagesAndOriginalIndex.map((value) => value[1]);
this.computeUiData();
this.uiData = new UiData(allLogLevels, allTags, allSourceFiles, filteredMessages, undefined);
this.isInitialized = true;
}
private async makeAllUiDataMessages(): Promise<UiDataMessage[]> {
const messages: UiDataMessage[] = [];
for (let originalIndex = 0; originalIndex < this.trace.lengthEntries; ++originalIndex) {
const entry = assertDefined(this.trace.getEntry(originalIndex));
const message = await entry.getValue();
(message as UiDataMessage).originalIndex = originalIndex;
messages.push(message as UiDataMessage);
}
return messages;
}
private computeUiData() {
let filteredMessages = this.allUiDataMessages;
if (this.levelsFilter.length > 0) {
filteredMessages = filteredMessages.filter((value) =>
this.levelsFilter.includes(value.level)
);
}
if (this.tagsFilter.length > 0) {
filteredMessages = filteredMessages.filter((value) => this.tagsFilter.includes(value.tag));
}
if (this.filesFilter.length > 0) {
filteredMessages = filteredMessages.filter((value) => this.filesFilter.includes(value.at));
}
filteredMessages = filteredMessages.filter((value) => value.text.includes(this.searchString));
this.originalIndicesOfFilteredOutputMessages = filteredMessages.map(
(message) => message.originalIndex
);
this.uiData = new UiData(
this.allLogLevels,
this.allTags,
this.allSourceFiles,
filteredMessages,
undefined
);
}
private computeUiDataCurrentMessageIndex() {
@@ -136,10 +171,13 @@ export class Presenter {
) ?? this.originalIndicesOfFilteredOutputMessages.length - 1;
}
private getUniqueMessageValues(getValue: (message: LogMessage) => string): string[] {
private getUniqueMessageValues(
allMessages: LogMessage[],
getValue: (message: LogMessage) => string
): string[] {
const uniqueValues = new Set<string>();
this.trace.forEachEntry((entry) => {
uniqueValues.add(getValue(entry.getValue()));
allMessages.forEach((message) => {
uniqueValues.add(getValue(message));
});
const result = [...uniqueValues];
result.sort();

View File

@@ -14,6 +14,7 @@
* limitations under the License.
*/
import {assertDefined} from 'common/assert_utils';
import {TracesBuilder} from 'test/unit/traces_builder';
import {TraceBuilder} from 'test/unit/trace_builder';
import {LogMessage} from 'trace/protolog';
@@ -23,11 +24,11 @@ import {Traces} from 'trace/traces';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {Presenter} from './presenter';
import {UiData} from './ui_data';
import {UiData, UiDataMessage} from './ui_data';
describe('ViewerProtoLogPresenter', () => {
let presenter: Presenter;
let inputMessages: LogMessage[];
let inputMessages: UiDataMessage[];
let trace: Trace<LogMessage>;
let position10: TracePosition;
let position11: TracePosition;
@@ -43,7 +44,11 @@ describe('ViewerProtoLogPresenter', () => {
new LogMessage('text0', 'time', 'tag0', 'level0', 'sourcefile0', 10n),
new LogMessage('text1', 'time', 'tag1', 'level1', 'sourcefile1', 11n),
new LogMessage('text2', 'time', 'tag2', 'level2', 'sourcefile2', 12n),
];
].map((message, index) => {
(message as UiDataMessage).originalIndex = index;
return message as UiDataMessage;
});
trace = new TraceBuilder<LogMessage>()
.setEntries(inputMessages)
.setTimestamps([time10, time11, time12])
@@ -60,116 +65,121 @@ describe('ViewerProtoLogPresenter', () => {
presenter = new Presenter(traces, (data: UiData) => {
outputUiData = data;
});
await presenter.onTracePositionUpdate(position10); // trigger initialization
});
it('is robust to empty trace', () => {
it('is robust to empty trace', async () => {
const traces = new TracesBuilder().setEntries(TraceType.PROTO_LOG, []).build();
presenter = new Presenter(traces, (data: UiData) => {
outputUiData = data;
});
expect(outputUiData!.messages).toEqual([]);
expect(outputUiData!.currentMessageIndex).toBeUndefined();
expect(assertDefined(outputUiData).messages).toEqual([]);
expect(assertDefined(outputUiData).currentMessageIndex).toBeUndefined();
presenter.onTracePositionUpdate(position10);
expect(outputUiData!.messages).toEqual([]);
expect(outputUiData!.currentMessageIndex).toBeUndefined();
await presenter.onTracePositionUpdate(position10);
expect(assertDefined(outputUiData).messages).toEqual([]);
expect(assertDefined(outputUiData).currentMessageIndex).toBeUndefined();
});
it('processes trace position updates', () => {
presenter.onTracePositionUpdate(position10);
it('processes trace position updates', async () => {
await presenter.onTracePositionUpdate(position10);
expect(outputUiData!.allLogLevels).toEqual(['level0', 'level1', 'level2']);
expect(outputUiData!.allTags).toEqual(['tag0', 'tag1', 'tag2']);
expect(outputUiData!.allSourceFiles).toEqual(['sourcefile0', 'sourcefile1', 'sourcefile2']);
expect(outputUiData!.messages).toEqual(inputMessages);
expect(outputUiData!.currentMessageIndex).toEqual(0);
expect(assertDefined(outputUiData).allLogLevels).toEqual(['level0', 'level1', 'level2']);
expect(assertDefined(outputUiData).allTags).toEqual(['tag0', 'tag1', 'tag2']);
expect(assertDefined(outputUiData).allSourceFiles).toEqual([
'sourcefile0',
'sourcefile1',
'sourcefile2',
]);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
});
it('updates displayed messages according to log levels filter', () => {
expect(outputUiData!.messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
presenter.onLogLevelsFilterChanged([]);
expect(outputUiData!.messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
presenter.onLogLevelsFilterChanged(['level1']);
expect(outputUiData!.messages).toEqual([inputMessages[1]]);
expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]);
presenter.onLogLevelsFilterChanged(['level0', 'level1', 'level2']);
expect(outputUiData!.messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
});
it('updates displayed messages according to tags filter', () => {
expect(outputUiData!.messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
presenter.onTagsFilterChanged([]);
expect(outputUiData!.messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
presenter.onTagsFilterChanged(['tag1']);
expect(outputUiData!.messages).toEqual([inputMessages[1]]);
expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]);
presenter.onTagsFilterChanged(['tag0', 'tag1', 'tag2']);
expect(outputUiData!.messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
});
it('updates displayed messages according to source files filter', () => {
expect(outputUiData!.messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
presenter.onSourceFilesFilterChanged([]);
expect(outputUiData!.messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
presenter.onSourceFilesFilterChanged(['sourcefile1']);
expect(outputUiData!.messages).toEqual([inputMessages[1]]);
expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]);
presenter.onSourceFilesFilterChanged(['sourcefile0', 'sourcefile1', 'sourcefile2']);
expect(outputUiData!.messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
});
it('updates displayed messages according to search string filter', () => {
expect(outputUiData!.messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
presenter.onSearchStringFilterChanged('');
expect(outputUiData!.messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
presenter.onSearchStringFilterChanged('text');
expect(outputUiData!.messages).toEqual(inputMessages);
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
presenter.onSearchStringFilterChanged('text0');
expect(outputUiData!.messages).toEqual([inputMessages[0]]);
expect(assertDefined(outputUiData).messages).toEqual([inputMessages[0]]);
presenter.onSearchStringFilterChanged('text1');
expect(outputUiData!.messages).toEqual([inputMessages[1]]);
expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]);
});
it('computes current message index', () => {
it('computes current message index', async () => {
// Position -> entry #0
presenter.onTracePositionUpdate(position10);
await presenter.onTracePositionUpdate(position10);
presenter.onLogLevelsFilterChanged([]);
expect(outputUiData!.currentMessageIndex).toEqual(0);
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
presenter.onLogLevelsFilterChanged(['level0']);
expect(outputUiData!.currentMessageIndex).toEqual(0);
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
presenter.onLogLevelsFilterChanged([]);
expect(outputUiData!.currentMessageIndex).toEqual(0);
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
// Position -> entry #1
presenter.onTracePositionUpdate(position11);
await presenter.onTracePositionUpdate(position11);
presenter.onLogLevelsFilterChanged([]);
expect(outputUiData!.currentMessageIndex).toEqual(1);
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(1);
presenter.onLogLevelsFilterChanged(['level0']);
expect(outputUiData!.currentMessageIndex).toEqual(0);
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
presenter.onLogLevelsFilterChanged(['level1']);
expect(outputUiData!.currentMessageIndex).toEqual(0);
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
presenter.onLogLevelsFilterChanged(['level0', 'level1']);
expect(outputUiData!.currentMessageIndex).toEqual(1);
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(1);
// Position -> entry #2
presenter.onTracePositionUpdate(position12);
await presenter.onTracePositionUpdate(position12);
presenter.onLogLevelsFilterChanged([]);
expect(outputUiData!.currentMessageIndex).toEqual(2);
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(2);
});
});

View File

@@ -15,16 +15,18 @@
*/
import {LogMessage} from 'trace/protolog';
class UiData {
export interface UiDataMessage extends LogMessage {
originalIndex: number;
}
export class UiData {
constructor(
public allLogLevels: string[],
public allTags: string[],
public allSourceFiles: string[],
public messages: LogMessage[],
public messages: UiDataMessage[],
public currentMessageIndex: undefined | number
) {}
static EMPTY = new UiData([], [], [], [], undefined);
}
export {UiData};

View File

@@ -31,21 +31,21 @@ class ViewerProtoLog implements Viewer {
});
this.htmlElement.addEventListener(Events.LogLevelsFilterChanged, (event) => {
return this.presenter.onLogLevelsFilterChanged((event as CustomEvent).detail);
this.presenter.onLogLevelsFilterChanged((event as CustomEvent).detail);
});
this.htmlElement.addEventListener(Events.TagsFilterChanged, (event) => {
return this.presenter.onTagsFilterChanged((event as CustomEvent).detail);
this.presenter.onTagsFilterChanged((event as CustomEvent).detail);
});
this.htmlElement.addEventListener(Events.SourceFilesFilterChanged, (event) => {
return this.presenter.onSourceFilesFilterChanged((event as CustomEvent).detail);
this.presenter.onSourceFilesFilterChanged((event as CustomEvent).detail);
});
this.htmlElement.addEventListener(Events.SearchStringFilterChanged, (event) => {
return this.presenter.onSearchStringFilterChanged((event as CustomEvent).detail);
this.presenter.onSearchStringFilterChanged((event as CustomEvent).detail);
});
}
onTracePositionUpdate(position: TracePosition) {
this.presenter.onTracePositionUpdate(position);
async onTracePositionUpdate(position: TracePosition) {
await this.presenter.onTracePositionUpdate(position);
}
getViews(): View[] {

View File

@@ -34,10 +34,10 @@ class ViewerScreenRecording implements Viewer {
this.htmlElement = document.createElement('viewer-screen-recording');
}
onTracePositionUpdate(position: TracePosition) {
async onTracePositionUpdate(position: TracePosition) {
const entry = TraceEntryFinder.findCorrespondingEntry(this.trace, position);
(this.htmlElement as unknown as ViewerScreenRecordingComponent).currentTraceEntry =
entry?.getValue();
await entry?.getValue();
}
getViews(): View[] {

View File

@@ -30,8 +30,8 @@ class ViewerStub implements Viewer {
}
}
onTracePositionUpdate(position: TracePosition) {
// do nothing
onTracePositionUpdate(position: TracePosition): Promise<void> {
return Promise.resolve();
}
getViews(): View[] {

View File

@@ -101,7 +101,7 @@ export class Presenter {
this.copyUiDataAndNotifyView();
}
onTracePositionUpdate(position: TracePosition) {
async onTracePositionUpdate(position: TracePosition) {
this.uiData = new UiData();
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
@@ -110,8 +110,8 @@ export class Presenter {
const prevEntry =
entry && entry.getIndex() > 0 ? this.trace.getEntry(entry.getIndex() - 1) : undefined;
this.entry = entry?.getValue() ?? null;
this.previousEntry = prevEntry?.getValue() ?? null;
this.entry = (await entry?.getValue()) ?? null;
this.previousEntry = (await prevEntry?.getValue()) ?? null;
if (this.entry) {
this.uiData.highlightedItems = this.highlightedItems;
this.uiData.rects = this.generateRects();

View File

@@ -62,18 +62,18 @@ describe('PresenterSurfaceFlinger', () => {
presenter = createPresenter(trace);
});
it('is robust to empty trace', () => {
it('is robust to empty trace', async () => {
const emptyTrace = new TraceBuilder<LayerTraceEntry>().setEntries([]).build();
const presenter = createPresenter(emptyTrace);
const positionWithoutTraceEntry = TracePosition.fromTimestamp(new RealTimestamp(0n));
presenter.onTracePositionUpdate(positionWithoutTraceEntry);
await presenter.onTracePositionUpdate(positionWithoutTraceEntry);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.tree).toBeFalsy();
});
it('processes trace position updates', () => {
presenter.onTracePositionUpdate(position);
it('processes trace position updates', async () => {
await presenter.onTracePositionUpdate(position);
expect(uiData.rects.length).toBeGreaterThan(0);
expect(uiData.highlightedItems?.length).toEqual(0);
@@ -89,8 +89,8 @@ describe('PresenterSurfaceFlinger', () => {
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it('creates input data for rects view', () => {
presenter.onTracePositionUpdate(position);
it('creates input data for rects view', async () => {
await presenter.onTracePositionUpdate(position);
expect(uiData.rects.length).toBeGreaterThan(0);
expect(uiData.rects[0].topLeft).toEqual({x: 0, y: 0});
expect(uiData.rects[0].bottomRight).toEqual({x: 1080, y: 118});
@@ -116,7 +116,7 @@ describe('PresenterSurfaceFlinger', () => {
expect(uiData.highlightedItems).toContain(id);
});
it('updates hierarchy tree', () => {
it('updates hierarchy tree', async () => {
//change flat view to true
const userOptions: UserOptions = {
showDiff: {
@@ -137,7 +137,7 @@ describe('PresenterSurfaceFlinger', () => {
},
};
presenter.onTracePositionUpdate(position);
await presenter.onTracePositionUpdate(position);
expect(uiData.tree?.children.length).toEqual(3);
presenter.updateHierarchyTree(userOptions);
@@ -146,7 +146,7 @@ describe('PresenterSurfaceFlinger', () => {
expect(uiData.tree?.children.length).toEqual(94);
});
it('filters hierarchy tree', () => {
it('filters hierarchy tree', async () => {
const userOptions: UserOptions = {
showDiff: {
name: 'Show diff',
@@ -165,7 +165,7 @@ describe('PresenterSurfaceFlinger', () => {
enabled: true,
},
};
presenter.onTracePositionUpdate(position);
await presenter.onTracePositionUpdate(position);
presenter.updateHierarchyTree(userOptions);
expect(uiData.tree?.children.length).toEqual(94);
presenter.filterHierarchyTree('Wallpaper');
@@ -173,14 +173,14 @@ describe('PresenterSurfaceFlinger', () => {
expect(uiData.tree?.children.length).toEqual(4);
});
it('sets properties tree and associated ui data', () => {
presenter.onTracePositionUpdate(position);
it('sets properties tree and associated ui data', async () => {
await presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
// does not check specific tree values as tree transformation method may change
expect(uiData.propertiesTree).toBeTruthy();
});
it('updates properties tree', () => {
it('updates properties tree', async () => {
//change flat view to true
const userOptions: UserOptions = {
showDiff: {
@@ -198,7 +198,7 @@ describe('PresenterSurfaceFlinger', () => {
},
};
presenter.onTracePositionUpdate(position);
await presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
expect(uiData.propertiesTree?.diffType).toBeFalsy();
@@ -207,8 +207,8 @@ describe('PresenterSurfaceFlinger', () => {
expect(uiData.propertiesTree?.diffType).toBeTruthy();
});
it('filters properties tree', () => {
presenter.onTracePositionUpdate(position);
it('filters properties tree', async () => {
await presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
let nonTerminalChildren =
uiData.propertiesTree?.children?.filter(
@@ -226,7 +226,7 @@ describe('PresenterSurfaceFlinger', () => {
});
it('handles displays with no visible layers', async () => {
presenter.onTracePositionUpdate(positionMultiDisplayEntry);
await presenter.onTracePositionUpdate(positionMultiDisplayEntry);
expect(uiData.displayIds.length).toEqual(5);
// we want the ids to be sorted
expect(uiData.displayIds).toEqual([0, 2, 3, 4, 5]);

View File

@@ -57,8 +57,8 @@ class ViewerSurfaceFlinger implements Viewer {
);
}
onTracePositionUpdate(position: TracePosition) {
this.presenter.onTracePositionUpdate(position);
async onTracePositionUpdate(position: TracePosition) {
await this.presenter.onTracePositionUpdate(position);
}
getViews(): View[] {

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022 The Android Open Source Project
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -28,12 +28,20 @@ import {PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
import {UiData, UiDataEntry, UiDataEntryType} from './ui_data';
export class Presenter {
private trace: Trace<object>;
private readonly trace: Trace<object>;
private entry?: TraceEntry<object>;
private originalIndicesOfUiDataEntries: number[];
private uiData = UiData.EMPTY;
private readonly notifyUiDataCallback: (data: UiData) => void;
private static readonly VALUE_NA = 'N/A';
private isInitialized = false;
private allUiDataEntries: UiDataEntry[] = [];
private allVSyncIds: string[] = [];
private allPids: string[] = [];
private allUids: string[] = [];
private allTypes: string[] = [];
private allLayerAndDisplayIds: string[] = [];
private allTransactionIds: string[] = [];
private vsyncIdFilter: string[] = [];
private pidFilter: string[] = [];
private uidFilter: string[] = [];
@@ -42,15 +50,19 @@ export class Presenter {
private idFilter: string | undefined = undefined;
private whatSearchString = '';
private readonly notifyUiDataCallback: (data: UiData) => void;
private static readonly VALUE_NA = 'N/A';
constructor(traces: Traces, notifyUiDataCallback: (data: UiData) => void) {
this.trace = assertDefined(traces.getTrace(TraceType.TRANSACTIONS));
this.notifyUiDataCallback = notifyUiDataCallback;
this.originalIndicesOfUiDataEntries = [];
this.computeUiData();
this.notifyUiDataCallback(this.uiData);
}
onTracePositionUpdate(position: TracePosition) {
async onTracePositionUpdate(position: TracePosition) {
await this.initializeIfNeeded();
this.entry = TraceEntryFinder.findCorrespondingEntry(this.trace, position);
this.uiData.currentEntryIndex = this.computeCurrentEntryIndex();
@@ -67,31 +79,31 @@ export class Presenter {
onVSyncIdFilterChanged(vsyncIds: string[]) {
this.vsyncIdFilter = vsyncIds;
this.computeUiData();
this.uiData = this.computeUiData();
this.notifyUiDataCallback(this.uiData);
}
onPidFilterChanged(pids: string[]) {
this.pidFilter = pids;
this.computeUiData();
this.uiData = this.computeUiData();
this.notifyUiDataCallback(this.uiData);
}
onUidFilterChanged(uids: string[]) {
this.uidFilter = uids;
this.computeUiData();
this.uiData = this.computeUiData();
this.notifyUiDataCallback(this.uiData);
}
onTypeFilterChanged(types: string[]) {
this.typeFilter = types;
this.computeUiData();
this.uiData = this.computeUiData();
this.notifyUiDataCallback(this.uiData);
}
onLayerIdFilterChanged(ids: string[]) {
this.layerIdFilter = ids;
this.computeUiData();
this.uiData = this.computeUiData();
this.notifyUiDataCallback(this.uiData);
}
@@ -101,13 +113,13 @@ export class Presenter {
} else {
this.idFilter = id;
}
this.computeUiData();
this.uiData = this.computeUiData();
this.notifyUiDataCallback(this.uiData);
}
onWhatSearchStringChanged(searchString: string) {
this.whatSearchString = searchString;
this.computeUiData();
this.uiData = this.computeUiData();
this.notifyUiDataCallback(this.uiData);
}
@@ -129,24 +141,46 @@ export class Presenter {
this.notifyUiDataCallback(this.uiData);
}
private computeUiData() {
const entries = this.makeUiDataEntries();
private async initializeIfNeeded() {
if (this.isInitialized) {
return;
}
const allVSyncIds = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) =>
entry.vsyncId.toString()
this.allUiDataEntries = await this.makeUiDataEntries();
this.allVSyncIds = this.getUniqueUiDataEntryValues(
this.allUiDataEntries,
(entry: UiDataEntry) => entry.vsyncId.toString()
);
const allPids = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) => entry.pid);
const allUids = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) => entry.uid);
const allTypes = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) => entry.type);
const allLayerAndDisplayIds = this.getUniqueUiDataEntryValues(
entries,
this.allPids = this.getUniqueUiDataEntryValues(
this.allUiDataEntries,
(entry: UiDataEntry) => entry.pid
);
this.allUids = this.getUniqueUiDataEntryValues(
this.allUiDataEntries,
(entry: UiDataEntry) => entry.uid
);
this.allTypes = this.getUniqueUiDataEntryValues(
this.allUiDataEntries,
(entry: UiDataEntry) => entry.type
);
this.allLayerAndDisplayIds = this.getUniqueUiDataEntryValues(
this.allUiDataEntries,
(entry: UiDataEntry) => entry.layerOrDisplayId
);
const allTransactionIds = this.getUniqueUiDataEntryValues(
entries,
this.allTransactionIds = this.getUniqueUiDataEntryValues(
this.allUiDataEntries,
(entry: UiDataEntry) => entry.transactionId
);
this.uiData = this.computeUiData();
this.isInitialized = true;
}
private computeUiData(): UiData {
const entries = this.allUiDataEntries;
let filteredEntries = entries;
if (this.vsyncIdFilter.length > 0) {
@@ -192,13 +226,13 @@ export class Presenter {
selectedEntryIndex
);
this.uiData = new UiData(
allVSyncIds,
allPids,
allUids,
allTypes,
allLayerAndDisplayIds,
allTransactionIds,
return new UiData(
this.allVSyncIds,
this.allPids,
this.allUids,
this.allTypes,
this.allLayerAndDisplayIds,
this.allTransactionIds,
filteredEntries,
currentEntryIndex,
selectedEntryIndex,
@@ -238,16 +272,15 @@ export class Presenter {
return undefined;
}
private makeUiDataEntries(): UiDataEntry[] {
private async makeUiDataEntries(): Promise<UiDataEntry[]> {
const treeGenerator = new PropertiesTreeGenerator();
const entries: UiDataEntry[] = [];
const formattingOptions = ObjectFormatter.displayDefaults;
ObjectFormatter.displayDefaults = true;
this.trace.forEachEntry((entry, originalIndex) => {
const timestampType = entry.getTimestamp().getType();
const entryProto = entry.getValue() as any;
const realToElapsedTimeOffsetNs = entryProto.realToElapsedTimeOffsetNs;
for (let originalIndex = 0; originalIndex < this.trace.lengthEntries; ++originalIndex) {
const entry = this.trace.getEntry(originalIndex);
const entryProto = (await entry.getValue()) as any;
for (const transactionStateProto of entryProto.transactions) {
for (const layerStateProto of transactionStateProto.layerChanges) {
@@ -395,7 +428,7 @@ export class Presenter {
)
);
}
});
}
ObjectFormatter.displayDefaults = formattingOptions;

View File

@@ -14,6 +14,7 @@
* limitations under the License.
*/
import {assertDefined} from 'common/assert_utils';
import {TracesBuilder} from 'test/unit/traces_builder';
import {TraceBuilder} from 'test/unit/trace_builder';
import {UnitTestUtils} from 'test/unit/utils';
@@ -38,12 +39,12 @@ describe('PresenterTransactions', () => {
parser = await UnitTestUtils.getParser('traces/elapsed_and_real_timestamp/Transactions.pb');
});
beforeEach(() => {
beforeEach(async () => {
outputUiData = undefined;
setUpTestEnvironment(TimestampType.ELAPSED);
await setUpTestEnvironment(TimestampType.ELAPSED);
});
it('is robust to empty trace', () => {
it('is robust to empty trace', async () => {
const traces = new TracesBuilder().setEntries(TraceType.TRANSACTIONS, []).build();
presenter = new Presenter(traces, (data: UiData) => {
outputUiData = data;
@@ -51,14 +52,14 @@ describe('PresenterTransactions', () => {
expect(outputUiData).toEqual(UiData.EMPTY);
presenter.onTracePositionUpdate(TracePosition.fromTimestamp(new RealTimestamp(10n)));
await presenter.onTracePositionUpdate(TracePosition.fromTimestamp(new RealTimestamp(10n)));
expect(outputUiData).toEqual(UiData.EMPTY);
});
it('processes trace position update and computes output UI data', () => {
presenter.onTracePositionUpdate(createTracePosition(0));
it('processes trace position update and computes output UI data', async () => {
await presenter.onTracePositionUpdate(createTracePosition(0));
expect(outputUiData!.allPids).toEqual([
expect(assertDefined(outputUiData).allPids).toEqual([
'N/A',
'0',
'515',
@@ -68,8 +69,15 @@ describe('PresenterTransactions', () => {
'2463',
'3300',
]);
expect(outputUiData!.allUids).toEqual(['N/A', '1000', '1003', '10169', '10235', '10239']);
expect(outputUiData!.allTypes).toEqual([
expect(assertDefined(outputUiData).allUids).toEqual([
'N/A',
'1000',
'1003',
'10169',
'10235',
'10239',
]);
expect(assertDefined(outputUiData).allTypes).toEqual([
'DISPLAY_CHANGED',
'LAYER_ADDED',
'LAYER_CHANGED',
@@ -78,81 +86,89 @@ describe('PresenterTransactions', () => {
'NO_OP',
]);
expect(outputUiData?.allTransactionIds.length).toEqual(1295);
expect(outputUiData?.allLayerAndDisplayIds.length).toEqual(117);
expect(assertDefined(outputUiData).allTransactionIds.length).toEqual(1295);
expect(assertDefined(outputUiData).allLayerAndDisplayIds.length).toEqual(117);
expect(outputUiData?.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
expect(assertDefined(outputUiData).entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
expect(outputUiData?.currentEntryIndex).toEqual(0);
expect(outputUiData?.selectedEntryIndex).toBeUndefined();
expect(outputUiData?.scrollToIndex).toEqual(0);
expect(outputUiData?.currentPropertiesTree).toBeDefined();
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
expect(assertDefined(outputUiData).selectedEntryIndex).toBeUndefined();
expect(assertDefined(outputUiData).scrollToIndex).toEqual(0);
expect(assertDefined(outputUiData).currentPropertiesTree).toBeDefined();
});
it('processes trace position update and updates current entry and scroll position', () => {
presenter.onTracePositionUpdate(createTracePosition(0));
expect(outputUiData!.currentEntryIndex).toEqual(0);
expect(outputUiData!.scrollToIndex).toEqual(0);
it('processes trace position update and updates current entry and scroll position', async () => {
await presenter.onTracePositionUpdate(createTracePosition(0));
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
expect(assertDefined(outputUiData).scrollToIndex).toEqual(0);
presenter.onTracePositionUpdate(createTracePosition(10));
expect(outputUiData!.currentEntryIndex).toEqual(13);
expect(outputUiData!.scrollToIndex).toEqual(13);
await presenter.onTracePositionUpdate(createTracePosition(10));
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13);
expect(assertDefined(outputUiData).scrollToIndex).toEqual(13);
});
it('filters entries according to transaction ID filter', () => {
presenter.onIdFilterChanged('');
expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
expect(assertDefined(outputUiData).entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
presenter.onIdFilterChanged('2211908157465');
expect(new Set(outputUiData!.entries.map((entry) => entry.transactionId))).toEqual(
new Set(['2211908157465'])
);
expect(
new Set(assertDefined(outputUiData).entries.map((entry) => entry.transactionId))
).toEqual(new Set(['2211908157465']));
});
it('filters entries according to VSYNC ID filter', () => {
presenter.onVSyncIdFilterChanged([]);
expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
expect(assertDefined(outputUiData).entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
presenter.onVSyncIdFilterChanged(['1']);
expect(new Set(outputUiData!.entries.map((entry) => entry.vsyncId))).toEqual(new Set([1]));
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.vsyncId))).toEqual(
new Set([1])
);
presenter.onVSyncIdFilterChanged(['1', '3', '10']);
expect(new Set(outputUiData!.entries.map((entry) => entry.vsyncId))).toEqual(
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.vsyncId))).toEqual(
new Set([1, 3, 10])
);
});
it('filters entries according to PID filter', () => {
presenter.onPidFilterChanged([]);
expect(new Set(outputUiData!.entries.map((entry) => entry.pid))).toEqual(
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.pid))).toEqual(
new Set(['N/A', '0', '515', '1593', '2022', '2322', '2463', '3300'])
);
presenter.onPidFilterChanged(['0']);
expect(new Set(outputUiData!.entries.map((entry) => entry.pid))).toEqual(new Set(['0']));
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.pid))).toEqual(
new Set(['0'])
);
presenter.onPidFilterChanged(['0', '515']);
expect(new Set(outputUiData!.entries.map((entry) => entry.pid))).toEqual(new Set(['0', '515']));
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.pid))).toEqual(
new Set(['0', '515'])
);
});
it('filters entries according to UID filter', () => {
presenter.onUidFilterChanged([]);
expect(new Set(outputUiData!.entries.map((entry) => entry.uid))).toEqual(
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.uid))).toEqual(
new Set(['N/A', '1000', '1003', '10169', '10235', '10239'])
);
presenter.onUidFilterChanged(['1000']);
expect(new Set(outputUiData!.entries.map((entry) => entry.uid))).toEqual(new Set(['1000']));
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.uid))).toEqual(
new Set(['1000'])
);
presenter.onUidFilterChanged(['1000', '1003']);
expect(new Set(outputUiData!.entries.map((entry) => entry.uid))).toEqual(
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.uid))).toEqual(
new Set(['1000', '1003'])
);
});
it('filters entries according to type filter', () => {
presenter.onTypeFilterChanged([]);
expect(new Set(outputUiData!.entries.map((entry) => entry.type))).toEqual(
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.type))).toEqual(
new Set([
UiDataEntryType.DISPLAY_CHANGED,
UiDataEntryType.LAYER_ADDED,
@@ -164,12 +180,12 @@ describe('PresenterTransactions', () => {
);
presenter.onTypeFilterChanged([UiDataEntryType.LAYER_ADDED]);
expect(new Set(outputUiData!.entries.map((entry) => entry.type))).toEqual(
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.type))).toEqual(
new Set([UiDataEntryType.LAYER_ADDED])
);
presenter.onTypeFilterChanged([UiDataEntryType.LAYER_ADDED, UiDataEntryType.LAYER_DESTROYED]);
expect(new Set(outputUiData!.entries.map((entry) => entry.type))).toEqual(
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.type))).toEqual(
new Set([UiDataEntryType.LAYER_ADDED, UiDataEntryType.LAYER_DESTROYED])
);
});
@@ -177,27 +193,27 @@ describe('PresenterTransactions', () => {
it('filters entries according to layer or display ID filter', () => {
presenter.onLayerIdFilterChanged([]);
expect(
new Set(outputUiData!.entries.map((entry) => entry.layerOrDisplayId)).size
new Set(assertDefined(outputUiData).entries.map((entry) => entry.layerOrDisplayId)).size
).toBeGreaterThan(20);
presenter.onLayerIdFilterChanged(['1']);
expect(new Set(outputUiData!.entries.map((entry) => entry.layerOrDisplayId))).toEqual(
new Set(['1'])
);
expect(
new Set(assertDefined(outputUiData).entries.map((entry) => entry.layerOrDisplayId))
).toEqual(new Set(['1']));
presenter.onLayerIdFilterChanged(['1', '3']);
expect(new Set(outputUiData!.entries.map((entry) => entry.layerOrDisplayId))).toEqual(
new Set(['1', '3'])
);
expect(
new Set(assertDefined(outputUiData).entries.map((entry) => entry.layerOrDisplayId))
).toEqual(new Set(['1', '3']));
});
it('includes no op transitions', () => {
presenter.onTypeFilterChanged([UiDataEntryType.NO_OP]);
expect(new Set(outputUiData!.entries.map((entry) => entry.type))).toEqual(
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.type))).toEqual(
new Set([UiDataEntryType.NO_OP])
);
for (const entry of outputUiData!.entries) {
for (const entry of assertDefined(outputUiData).entries) {
expect(entry.layerOrDisplayId).toEqual('');
expect(entry.what).toEqual('');
expect(entry.propertiesTree).toEqual({});
@@ -205,74 +221,80 @@ describe('PresenterTransactions', () => {
});
it('filters entries according to "what" search string', () => {
expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
expect(assertDefined(outputUiData).entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
presenter.onWhatSearchStringChanged('');
expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
expect(assertDefined(outputUiData).entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
presenter.onWhatSearchStringChanged('Crop');
expect(outputUiData!.entries.length).toBeLessThan(TOTAL_OUTPUT_ENTRIES);
expect(assertDefined(outputUiData).entries.length).toBeLessThan(TOTAL_OUTPUT_ENTRIES);
presenter.onWhatSearchStringChanged('STRING_WITH_NO_MATCHES');
expect(outputUiData!.entries.length).toEqual(0);
expect(assertDefined(outputUiData).entries.length).toEqual(0);
});
it('updates selected entry and properties tree when entry is clicked', () => {
presenter.onTracePositionUpdate(createTracePosition(0));
expect(outputUiData!.currentEntryIndex).toEqual(0);
expect(outputUiData!.selectedEntryIndex).toBeUndefined();
expect(outputUiData!.scrollToIndex).toEqual(0);
expect(outputUiData!.currentPropertiesTree).toEqual(outputUiData!.entries[0].propertiesTree);
it('updates selected entry and properties tree when entry is clicked', async () => {
await presenter.onTracePositionUpdate(createTracePosition(0));
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
expect(assertDefined(outputUiData).selectedEntryIndex).toBeUndefined();
expect(assertDefined(outputUiData).scrollToIndex).toEqual(0);
expect(assertDefined(outputUiData).currentPropertiesTree).toEqual(
assertDefined(outputUiData).entries[0].propertiesTree
);
presenter.onEntryClicked(10);
expect(outputUiData!.currentEntryIndex).toEqual(0);
expect(outputUiData!.selectedEntryIndex).toEqual(10);
expect(outputUiData!.scrollToIndex).toBeUndefined(); // no scrolling
expect(outputUiData!.currentPropertiesTree).toEqual(outputUiData!.entries[10].propertiesTree);
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
expect(assertDefined(outputUiData).selectedEntryIndex).toEqual(10);
expect(assertDefined(outputUiData).scrollToIndex).toBeUndefined(); // no scrolling
expect(assertDefined(outputUiData).currentPropertiesTree).toEqual(
assertDefined(outputUiData).entries[10].propertiesTree
);
// remove selection when selected entry is clicked again
presenter.onEntryClicked(10);
expect(outputUiData!.currentEntryIndex).toEqual(0);
expect(outputUiData!.selectedEntryIndex).toBeUndefined();
expect(outputUiData!.scrollToIndex).toBeUndefined(); // no scrolling
expect(outputUiData!.currentPropertiesTree).toEqual(outputUiData!.entries[0].propertiesTree);
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
expect(assertDefined(outputUiData).selectedEntryIndex).toBeUndefined();
expect(assertDefined(outputUiData).scrollToIndex).toBeUndefined(); // no scrolling
expect(assertDefined(outputUiData).currentPropertiesTree).toEqual(
assertDefined(outputUiData).entries[0].propertiesTree
);
});
it('computes current entry index', () => {
presenter.onTracePositionUpdate(createTracePosition(0));
expect(outputUiData!.currentEntryIndex).toEqual(0);
it('computes current entry index', async () => {
await presenter.onTracePositionUpdate(createTracePosition(0));
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
presenter.onTracePositionUpdate(createTracePosition(10));
expect(outputUiData!.currentEntryIndex).toEqual(13);
await presenter.onTracePositionUpdate(createTracePosition(10));
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13);
});
it('updates current entry index when filters change', () => {
presenter.onTracePositionUpdate(createTracePosition(10));
it('updates current entry index when filters change', async () => {
await presenter.onTracePositionUpdate(createTracePosition(10));
presenter.onPidFilterChanged([]);
expect(outputUiData!.currentEntryIndex).toEqual(13);
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13);
presenter.onPidFilterChanged(['0']);
expect(outputUiData!.currentEntryIndex).toEqual(10);
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(10);
presenter.onPidFilterChanged(['0', '515']);
expect(outputUiData!.currentEntryIndex).toEqual(11);
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(11);
presenter.onPidFilterChanged(['0', '515', 'N/A']);
expect(outputUiData!.currentEntryIndex).toEqual(13);
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13);
});
it('formats real time', () => {
setUpTestEnvironment(TimestampType.REAL);
expect(outputUiData!.entries[0].time).toEqual('2022-08-03T06:19:01.051480997');
it('formats real time', async () => {
await setUpTestEnvironment(TimestampType.REAL);
expect(assertDefined(outputUiData).entries[0].time).toEqual('2022-08-03T06:19:01.051480997');
});
it('formats elapsed time', () => {
setUpTestEnvironment(TimestampType.ELAPSED);
expect(outputUiData!.entries[0].time).toEqual('2s450ms981445ns');
it('formats elapsed time', async () => {
await setUpTestEnvironment(TimestampType.ELAPSED);
expect(assertDefined(outputUiData).entries[0].time).toEqual('2s450ms981445ns');
});
const setUpTestEnvironment = (timestampType: TimestampType) => {
const setUpTestEnvironment = async (timestampType: TimestampType) => {
trace = new TraceBuilder<object>().setParser(parser).setTimestampType(timestampType).build();
traces = new Traces();
@@ -281,6 +303,8 @@ describe('PresenterTransactions', () => {
presenter = new Presenter(traces, (data: UiData) => {
outputUiData = data;
});
await presenter.onTracePositionUpdate(createTracePosition(0)); // trigger initialization
};
const createTracePosition = (entryIndex: number): TracePosition => {

Some files were not shown because too many files have changed in this diff Show More