Merge "Merge Android 14 QPR1" into main

This commit is contained in:
Xin Li
2023-12-09 00:11:39 +00:00
committed by Gerrit Code Review
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 Pkg.UserSrc=false
Platform.Version=${PLATFORM_VERSION} Platform.Version=${PLATFORM_VERSION}
Platform.CodeName= Platform.CodeName=
Pkg.Revision=1 Pkg.Revision=2
AndroidVersion.ApiLevel=${PLATFORM_SDK_VERSION} AndroidVersion.ApiLevel=${PLATFORM_SDK_VERSION}
AndroidVersion.CodeName=${PLATFORM_VERSION_CODENAME} AndroidVersion.CodeName=${PLATFORM_VERSION_CODENAME}
AndroidVersion.ExtensionLevel=${PLATFORM_SDK_EXTENSION_VERSION} AndroidVersion.ExtensionLevel=${PLATFORM_SDK_EXTENSION_VERSION}

View File

@@ -30,7 +30,7 @@ exports.config = {
args: ['--headless', '--disable-gpu', '--window-size=1280x1024'], 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, allScriptsTimeout: 10000,
getPageTimeout: 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 {ViewerSurfaceFlingerComponent} from 'viewers/viewer_surface_flinger/viewer_surface_flinger_component';
import {ViewerTransactionsComponent} from 'viewers/viewer_transactions/viewer_transactions_component'; import {ViewerTransactionsComponent} from 'viewers/viewer_transactions/viewer_transactions_component';
import {ViewerTransitionsComponent} from 'viewers/viewer_transitions/viewer_transitions_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 {ViewerWindowManagerComponent} from 'viewers/viewer_window_manager/viewer_window_manager_component';
import {AdbProxyComponent} from './components/adb_proxy_component'; import {AdbProxyComponent} from './components/adb_proxy_component';
import {AppComponent} from './components/app_component'; import {AppComponent} from './components/app_component';
@@ -88,6 +89,7 @@ import {WebAdbComponent} from './components/web_adb_component';
ViewerTransactionsComponent, ViewerTransactionsComponent,
ViewerScreenRecordingComponent, ViewerScreenRecordingComponent,
ViewerTransitionsComponent, ViewerTransitionsComponent,
ViewerViewCaptureComponent,
CollectTracesComponent, CollectTracesComponent,
UploadTracesComponent, UploadTracesComponent,
AdbProxyComponent, AdbProxyComponent,

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,10 +14,20 @@
* limitations under the License. * limitations under the License.
*/ */
import {
OnTracePositionUpdate,
TracePositionUpdateEmitter,
} from 'interfaces/trace_position_update_emitter';
import {TracePositionUpdateListener} from 'interfaces/trace_position_update_listener'; import {TracePositionUpdateListener} from 'interfaces/trace_position_update_listener';
import {TracePosition} from 'trace/trace_position'; 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) { onTracePositionUpdate(position: TracePosition) {
// do nothing // do nothing
} }

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import {FunctionUtils} from 'common/function_utils'; import {assertDefined} from 'common/assert_utils';
import {TimeUtils} from 'common/time_utils'; import {TimeUtils} from 'common/time_utils';
import {ScreenRecordingUtils} from 'trace/screen_recording_utils'; import {ScreenRecordingUtils} from 'trace/screen_recording_utils';
import {Timestamp, TimestampType} from 'trace/timestamp'; import {Timestamp, TimestampType} from 'trace/timestamp';
@@ -23,13 +23,12 @@ import {Traces} from 'trace/traces';
import {TraceEntryFinder} from 'trace/trace_entry_finder'; import {TraceEntryFinder} from 'trace/trace_entry_finder';
import {TracePosition} from 'trace/trace_position'; import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type'; import {TraceType} from 'trace/trace_type';
import {assertDefined} from '../common/assert_utils';
export type TracePositionCallbackType = (position: TracePosition) => void;
export interface TimeRange { export interface TimeRange {
from: Timestamp; from: Timestamp;
to: Timestamp; to: Timestamp;
} }
const INVALID_TIMESTAMP = 0n
export class TimelineData { export class TimelineData {
private traces = new Traces(); private traces = new Traces();
@@ -40,25 +39,29 @@ export class TimelineData {
private explicitlySetPosition?: TracePosition; private explicitlySetPosition?: TracePosition;
private explicitlySetSelection?: TimeRange; private explicitlySetSelection?: TimeRange;
private activeViewTraceTypes: TraceType[] = []; // dependencies of current active view private activeViewTraceTypes: TraceType[] = []; // dependencies of current active view
private onTracePositionUpdate: TracePositionCallbackType = FunctionUtils.DO_NOTHING;
initialize(traces: Traces, screenRecordingVideo: Blob | undefined) { initialize(traces: Traces, screenRecordingVideo: Blob | undefined) {
this.clear(); 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.screenRecordingVideo = screenRecordingVideo;
this.firstEntry = this.findFirstEntry(); this.firstEntry = this.findFirstEntry();
this.lastEntry = this.findLastEntry(); this.lastEntry = this.findLastEntry();
this.timestampType = this.firstEntry?.getTimestamp().getType(); this.timestampType = this.firstEntry?.getTimestamp().getType();
const position = this.getCurrentPosition();
if (position) {
this.onTracePositionUpdate(position);
}
}
setOnTracePositionUpdate(callback: TracePositionCallbackType) {
this.onTracePositionUpdate = callback;
} }
getCurrentPosition(): TracePosition | undefined { getCurrentPosition(): TracePosition | undefined {
@@ -90,15 +93,11 @@ export class TimelineData {
} }
} }
this.applyOperationAndNotifyIfCurrentPositionChanged(() => { this.explicitlySetPosition = position;
this.explicitlySetPosition = position;
});
} }
setActiveViewTraceTypes(types: TraceType[]) { setActiveViewTraceTypes(types: TraceType[]) {
this.applyOperationAndNotifyIfCurrentPositionChanged(() => { this.activeViewTraceTypes = types;
this.activeViewTraceTypes = types;
});
} }
getTimestampType(): TimestampType | undefined { getTimestampType(): TimestampType | undefined {
@@ -219,16 +218,14 @@ export class TimelineData {
} }
clear() { clear() {
this.applyOperationAndNotifyIfCurrentPositionChanged(() => { this.traces = new Traces();
this.traces = new Traces(); this.firstEntry = undefined;
this.firstEntry = undefined; this.lastEntry = undefined;
this.lastEntry = undefined; this.explicitlySetPosition = undefined;
this.explicitlySetPosition = undefined; this.timestampType = undefined;
this.timestampType = undefined; this.explicitlySetSelection = undefined;
this.explicitlySetSelection = undefined; this.screenRecordingVideo = undefined;
this.screenRecordingVideo = undefined; this.activeViewTraceTypes = [];
this.activeViewTraceTypes = [];
});
} }
private findFirstEntry(): TraceEntry<{}> | undefined { private findFirstEntry(): TraceEntry<{}> | undefined {
@@ -265,6 +262,7 @@ export class TimelineData {
private getFirstEntryOfActiveViewTraces(): TraceEntry<{}> | undefined { private getFirstEntryOfActiveViewTraces(): TraceEntry<{}> | undefined {
const activeEntries = this.activeViewTraceTypes const activeEntries = this.activeViewTraceTypes
.filter((it) => this.traces.getTrace(it) !== undefined)
.map((traceType) => assertDefined(this.traces.getTrace(traceType))) .map((traceType) => assertDefined(this.traces.getTrace(traceType)))
.filter((trace) => trace.lengthEntries > 0) .filter((trace) => trace.lengthEntries > 0)
.map((trace) => trace.getEntry(0)) .map((trace) => trace.getEntry(0))
@@ -276,13 +274,4 @@ export class TimelineData {
} }
return activeEntries[0]; 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 {TraceType} from 'trace/trace_type';
import {TimelineData} from './timeline_data'; import {TimelineData} from './timeline_data';
class TracePositionUpdateListener {
onTracePositionUpdate(position: TracePosition) {
// do nothing
}
}
describe('TimelineData', () => { describe('TimelineData', () => {
let timelineData: TimelineData; let timelineData: TimelineData;
const positionUpdateListener = new TracePositionUpdateListener();
const timestamp10 = new Timestamp(TimestampType.REAL, 10n); const timestamp10 = new Timestamp(TimestampType.REAL, 10n);
const timestamp11 = new Timestamp(TimestampType.REAL, 11n); const timestamp11 = new Timestamp(TimestampType.REAL, 11n);
@@ -47,9 +39,6 @@ describe('TimelineData', () => {
beforeEach(() => { beforeEach(() => {
timelineData = new TimelineData(); timelineData = new TimelineData();
timelineData.setOnTracePositionUpdate((position) => {
positionUpdateListener.onTracePositionUpdate(position);
});
}); });
it('can be initialized', () => { it('can be initialized', () => {
@@ -59,6 +48,20 @@ describe('TimelineData', () => {
expect(timelineData.getCurrentPosition()).toBeDefined(); 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', () => { it('uses first entry by default', () => {
timelineData.initialize(traces, undefined); timelineData.initialize(traces, undefined);
expect(timelineData.getCurrentPosition()).toEqual(position10); expect(timelineData.getCurrentPosition()).toEqual(position10);
@@ -95,30 +98,6 @@ describe('TimelineData', () => {
expect(timelineData.getCurrentPosition()).toEqual(position10); 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()', () => { it('hasTimestamps()', () => {
expect(timelineData.hasTimestamps()).toBeFalse(); expect(timelineData.hasTimestamps()).toBeFalse();

View File

@@ -108,6 +108,13 @@ export const TRACE_INFO: TraceInfoMap = {
color: '#137333', color: '#137333',
downloadArchiveDir: 'launcher', 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]: { [TraceType.INPUT_METHOD_CLIENTS]: {
name: 'IME Clients', name: 'IME Clients',
icon: IME_ICON, icon: IME_ICON,

View File

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

View File

@@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import {assertDefined} from 'common/assert_utils';
import {TracesUtils} from 'test/unit/traces_utils'; import {TracesUtils} from 'test/unit/traces_utils';
import {UnitTestUtils} from 'test/unit/utils'; import {UnitTestUtils} from 'test/unit/utils';
import {TraceFile} from 'trace/trace_file'; import {TraceFile} from 'trace/trace_file';
@@ -21,26 +22,47 @@ import {TraceType} from 'trace/trace_type';
import {TracePipeline} from './trace_pipeline'; import {TracePipeline} from './trace_pipeline';
describe('TracePipeline', () => { describe('TracePipeline', () => {
let validSfTraceFile: TraceFile;
let validWmTraceFile: TraceFile;
let tracePipeline: TracePipeline; let tracePipeline: TracePipeline;
beforeEach(async () => { 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(); tracePipeline = new TracePipeline();
}); });
it('can load valid trace files', async () => { it('can load valid trace files', async () => {
expect(tracePipeline.getLoadedTraces().length).toEqual(0); expect(tracePipeline.getTraces().getSize()).toEqual(0);
await loadValidSfWmTraces(); 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.WINDOW_MANAGER)?.length).toBeGreaterThan(0);
expect(traceEntries.get(TraceType.SURFACE_FLINGER)?.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 () => { 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 // Could be any file, we just need an instance of File to be used as a fake bugreport archive
const bugreportArchive = await UnitTestUtils.getFixtureFile( const bugreportArchive = await UnitTestUtils.getFixtureFile(
@@ -76,7 +98,14 @@ describe('TracePipeline', () => {
new TraceFile( new TraceFile(
await UnitTestUtils.getFixtureFile( await UnitTestUtils.getFixtureFile(
'traces/elapsed_and_real_timestamp/WindowManager.pb', '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 bugreportArchive
), ),
@@ -98,13 +127,14 @@ describe('TracePipeline', () => {
const mergedFiles = bugreportFiles.concat([plainTraceFile]); const mergedFiles = bugreportFiles.concat([plainTraceFile]);
const errors = await tracePipeline.loadTraceFiles(mergedFiles); const errors = await tracePipeline.loadTraceFiles(mergedFiles);
expect(errors.length).toEqual(0); expect(errors.length).toEqual(0);
tracePipeline.buildTraces(); await tracePipeline.buildTraces();
const traces = tracePipeline.getTraces(); const traces = tracePipeline.getTraces();
expect(traces.getTrace(TraceType.SURFACE_FLINGER)).toBeDefined(); expect(traces.getTrace(TraceType.SURFACE_FLINGER)).toBeDefined();
expect(traces.getTrace(TraceType.TRANSACTIONS)).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.INPUT_METHOD_CLIENTS)).toBeDefined();
expect(traces.getTrace(TraceType.WINDOW_MANAGER)).toBeDefined();
}); });
it('is robust to invalid trace files', async () => { it('is robust to invalid trace files', async () => {
@@ -113,20 +143,20 @@ describe('TracePipeline', () => {
]; ];
const errors = await tracePipeline.loadTraceFiles(invalidTraceFiles); const errors = await tracePipeline.loadTraceFiles(invalidTraceFiles);
tracePipeline.buildTraces(); await tracePipeline.buildTraces();
expect(errors.length).toEqual(1); 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 () => { 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 = [ const files = [
new TraceFile(await UnitTestUtils.getFixtureFile('winscope_homepage.png')), new TraceFile(await UnitTestUtils.getFixtureFile('winscope_homepage.png')),
new TraceFile(await UnitTestUtils.getFixtureFile('traces/dump_WindowManager.pb')), new TraceFile(await UnitTestUtils.getFixtureFile('traces/dump_WindowManager.pb')),
]; ];
const errors = await tracePipeline.loadTraceFiles(files); const errors = await tracePipeline.loadTraceFiles(files);
tracePipeline.buildTraces(); await tracePipeline.buildTraces();
expect(tracePipeline.getLoadedTraces().length).toEqual(1); expect(tracePipeline.getTraces().getSize()).toEqual(1);
expect(errors.length).toEqual(1); expect(errors.length).toEqual(1);
}); });
@@ -136,37 +166,41 @@ describe('TracePipeline', () => {
]; ];
const errors = await tracePipeline.loadTraceFiles(traceFilesWithNoEntries); const errors = await tracePipeline.loadTraceFiles(traceFilesWithNoEntries);
tracePipeline.buildTraces(); await tracePipeline.buildTraces();
expect(errors.length).toEqual(0); expect(errors.length).toEqual(0);
expect(tracePipeline.getLoadedTraces().length).toEqual(1); expect(tracePipeline.getTraces().getSize()).toEqual(1);
}); });
it('can remove traces', async () => { it('can remove traces', async () => {
await loadValidSfWmTraces(); await loadValidSfWmTraces();
expect(tracePipeline.getLoadedTraces().length).toEqual(2); expect(tracePipeline.getTraces().getSize()).toEqual(2);
tracePipeline.removeTraceFile(TraceType.SURFACE_FLINGER); const sfTrace = assertDefined(tracePipeline.getTraces().getTrace(TraceType.SURFACE_FLINGER));
tracePipeline.buildTraces(); const wmTrace = assertDefined(tracePipeline.getTraces().getTrace(TraceType.WINDOW_MANAGER));
expect(tracePipeline.getLoadedTraces().length).toEqual(1);
tracePipeline.removeTraceFile(TraceType.WINDOW_MANAGER); tracePipeline.removeTrace(sfTrace);
tracePipeline.buildTraces(); await tracePipeline.buildTraces();
expect(tracePipeline.getLoadedTraces().length).toEqual(0); 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(); await loadValidSfWmTraces();
const files = tracePipeline.getLoadedTraces(); const traces = tracePipeline.getTraces();
expect(files.length).toEqual(2); expect(traces.getSize()).toEqual(2);
expect(files[0].descriptors).toBeTruthy();
expect(files[0].descriptors.length).toBeGreaterThan(0);
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]); const expectedTraceTypes = new Set([TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER]);
expect(actualTraceTypes).toEqual(expectedTraceTypes); expect(actualTraceTypes).toEqual(expectedTraceTypes);
const sfTrace = assertDefined(traces.getTrace(TraceType.SURFACE_FLINGER));
expect(sfTrace.getDescriptors().length).toBeGreaterThan(0);
}); });
it('builds traces', async () => { it('builds traces', async () => {
@@ -186,49 +220,25 @@ describe('TracePipeline', () => {
), ),
]; ];
await tracePipeline.loadTraceFiles(traceFiles); await tracePipeline.loadTraceFiles(traceFiles);
tracePipeline.buildTraces(); await tracePipeline.buildTraces();
const video = tracePipeline.getScreenRecordingVideo(); const video = await tracePipeline.getScreenRecordingVideo();
expect(video).toBeDefined(); expect(video).toBeDefined();
expect(video!.size).toBeGreaterThan(0); expect(video!.size).toBeGreaterThan(0);
}); });
it('can be cleared', async () => { it('can be cleared', async () => {
await loadValidSfWmTraces(); await loadValidSfWmTraces();
expect(tracePipeline.getLoadedTraces().length).toBeGreaterThan(0); expect(tracePipeline.getTraces().getSize()).toBeGreaterThan(0);
tracePipeline.clear(); tracePipeline.clear();
expect(tracePipeline.getLoadedTraces().length).toEqual(0); expect(tracePipeline.getTraces().getSize()).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();
}); });
const loadValidSfWmTraces = async () => { const loadValidSfWmTraces = async () => {
const traceFiles = [ const traceFiles = [validSfTraceFile, validWmTraceFile];
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 errors = await tracePipeline.loadTraceFiles(traceFiles); const errors = await tracePipeline.loadTraceFiles(traceFiles);
expect(errors.length).toEqual(0); expect(errors.length).toEqual(0);
await tracePipeline.buildTraces();
tracePipeline.buildTraces();
}; };
}); });

View File

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

View File

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

View File

@@ -14,8 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
import {TraceType} from './trace_type'; import {TracePosition} from 'trace/trace_position';
export class LoadedTrace { export type OnTracePositionUpdate = (position: TracePosition) => Promise<void>;
constructor(public descriptors: string[], public type: TraceType) {}
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 {TraceFile} from 'trace/trace_file';
import {TraceType} from 'trace/trace_type'; 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 traceFile: TraceFile;
protected decodedEntries: any[] = []; protected decodedEntries: any[] = [];
private timestamps: Map<TimestampType, Timestamp[]> = new Map<TimestampType, Timestamp[]>(); 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); return this.timestamps.get(type);
} }
getEntry(index: number, timestampType: TimestampType): T { getEntry(index: number, timestampType: TimestampType): Promise<T> {
return this.processDecodedEntry(index, timestampType, this.decodedEntries[index]); const entry = this.processDecodedEntry(index, timestampType, this.decodedEntries[index]);
return Promise.resolve(entry);
} }
// Add default values to the proto objects. // Add default values to the proto objects.

View File

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

View File

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

View File

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

View File

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

View File

@@ -30,6 +30,7 @@ import {ParserSurfaceFlinger} from './parser_surface_flinger';
import {ParserTransactions} from './parser_transactions'; import {ParserTransactions} from './parser_transactions';
import {ParserTransitionsShell} from './parser_transitions_shell'; import {ParserTransitionsShell} from './parser_transitions_shell';
import {ParserTransitionsWm} from './parser_transitions_wm'; import {ParserTransitionsWm} from './parser_transitions_wm';
import {ParserViewCapture} from './parser_view_capture';
import {ParserWindowManager} from './parser_window_manager'; import {ParserWindowManager} from './parser_window_manager';
import {ParserWindowManagerDump} from './parser_window_manager_dump'; import {ParserWindowManagerDump} from './parser_window_manager_dump';
@@ -49,6 +50,7 @@ export class ParserFactory {
ParserEventLog, ParserEventLog,
ParserTransitionsWm, ParserTransitionsWm,
ParserTransitionsShell, ParserTransitionsShell,
ParserViewCapture,
]; ];
private parsers = new Map<TraceType, Parser<object>>(); 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); expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
}); });
it('retrieves trace entry', () => { it('retrieves trace entry', async () => {
expect(BigInt(parser.getEntry(1, TimestampType.REAL)!.elapsedRealtimeNanos)).toEqual( const entry = await parser.getEntry(1, TimestampType.REAL);
15647516364n expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(15647516364n);
);
}); });
}); });
@@ -80,10 +79,9 @@ describe('ParserInputMethodlClients', () => {
expect(parser.getTimestamps(TimestampType.REAL)).toBeUndefined(); expect(parser.getTimestamps(TimestampType.REAL)).toBeUndefined();
}); });
it('retrieves trace entry from elapsed timestamp', () => { it('retrieves trace entry from elapsed timestamp', async () => {
expect(BigInt(parser.getEntry(0, TimestampType.ELAPSED)!.elapsedRealtimeNanos)).toEqual( const entry = await parser.getEntry(0, TimestampType.ELAPSED);
1149083651642n expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(1149083651642n);
);
}); });
}); });
}); });

View File

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

View File

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

View File

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

View File

@@ -56,14 +56,14 @@ describe('ParserScreenRecordingLegacy', () => {
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(undefined); 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(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(0); 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(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(2.37, 0.001); expect(Number(entry.videoTimeSeconds)).toBeCloseTo(2.37, 0.001);
} }

View File

@@ -58,14 +58,14 @@ describe('ParserScreenRecording', () => {
expect(timestamps.slice(0, 3)).toEqual(expected); 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(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(0); 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(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(1.371077, 0.001); expect(Number(entry.videoTimeSeconds)).toBeCloseTo(1.371077, 0.001);
} }

View File

@@ -44,8 +44,8 @@ describe('ParserSurfaceFlingerDump', () => {
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(expected); expect(parser.getTimestamps(TimestampType.REAL)).toEqual(expected);
}); });
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(LayerTraceEntry); expect(entry).toBeInstanceOf(LayerTraceEntry);
expect(BigInt(entry.timestamp.systemUptimeNanos.toString())).toEqual(0n); expect(BigInt(entry.timestamp.systemUptimeNanos.toString())).toEqual(0n);
expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(DUMP_REAL_TIME); expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(DUMP_REAL_TIME);

View File

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

View File

@@ -56,21 +56,21 @@ describe('ParserTransactions', () => {
expect(timestamps.slice(0, 3)).toEqual(expected); expect(timestamps.slice(0, 3)).toEqual(expected);
}); });
it('retrieves trace entry from real timestamp', () => { it('retrieves trace entry from real timestamp', async () => {
const entry = parser.getEntry(1, TimestampType.REAL); const entry = await parser.getEntry(1, TimestampType.REAL);
expect(BigInt((entry as any).elapsedRealtimeNanos)).toEqual(2517952515n); 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[0].layerChanges[0].what).toEqual('eLayerChanged');
expect(entry.transactions[1].layerChanges[0].what).toEqual( expect(entry.transactions[1].layerChanges[0].what).toEqual(
'eFlagsChanged | eDestinationFrameChanged' '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( expect(entry.transactions[1].displayChanges[0].what).toEqual(
'eLayerStackChanged | eDisplayProjectionChanged | eFlagsChanged' '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); expect(parser.getTimestamps(TimestampType.REAL)).toEqual(expected);
}); });
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(WindowManagerState); expect(entry).toBeInstanceOf(WindowManagerState);
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(0n); 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); expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
}); });
it('retrieves trace entry', () => { it('retrieves trace entry', async () => {
const entry = parser.getEntry(1, TimestampType.REAL); const entry = await parser.getEntry(1, TimestampType.REAL);
expect(entry).toBeInstanceOf(WindowManagerState); expect(entry).toBeInstanceOf(WindowManagerState);
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(15398076788n); expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(15398076788n);
expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(1659107089999048990n); expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(1659107089999048990n);
}); });
it('formats entry timestamps', () => { it('formats entry timestamps', async () => {
const entry = parser.getEntry(1, TimestampType.REAL); const entry = await parser.getEntry(1, TimestampType.REAL);
expect(entry.name).toEqual('2022-07-29T15:04:49.999048960'); expect(entry.name).toEqual('2022-07-29T15:04:49.999048960');
}); });
}); });
@@ -82,14 +82,14 @@ describe('ParserWindowManager', () => {
expect(parser.getTimestamps(TimestampType.ELAPSED)).toEqual(expected); expect(parser.getTimestamps(TimestampType.ELAPSED)).toEqual(expected);
}); });
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(WindowManagerState); expect(entry).toBeInstanceOf(WindowManagerState);
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(850254319343n); expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(850254319343n);
}); });
it('formats entry timestamps', () => { it('formats entry timestamps', async () => {
const entry = parser.getEntry(0, TimestampType.ELAPSED); const entry = await parser.getEntry(0, TimestampType.ELAPSED);
expect(entry.name).toEqual('14m10s254ms319343ns'); 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 wmTransitionsJson from 'frameworks/base/core/proto/android/server/windowmanagertransitiontrace.proto';
import inputMethodClientsJson from 'frameworks/base/core/proto/android/view/inputmethod/inputmethodeditortrace.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 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 layersJson from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto';
import transactionsJson from 'frameworks/native/services/surfaceflinger/layerproto/transactions.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' 'com.android.wm.shell.WmShellTransitionTraceProto'
); );
const ExportedData = protobuf.Root.fromJSON(viewCaptureJson).lookupType(
'com.android.app.viewcapture.data.ExportedData'
);
export { export {
AccessibilityTraceFileProto, AccessibilityTraceFileProto,
InputMethodClientsTraceFileProto, InputMethodClientsTraceFileProto,
@@ -74,4 +79,5 @@ export {
WindowManagerTraceFileProto, WindowManagerTraceFileProto,
WmTransitionsTraceFileProto, WmTransitionsTraceFileProto,
ShellTransitionsTraceFileProto, ShellTransitionsTraceFileProto,
ExportedData,
}; };

View File

@@ -14,7 +14,8 @@
* limitations under the License. * 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 {Parser} from 'trace/parser';
import {Timestamp, TimestampType} from 'trace/timestamp'; import {Timestamp, TimestampType} from 'trace/timestamp';
import {TraceType} from 'trace/trace_type'; import {TraceType} from 'trace/trace_type';
@@ -24,11 +25,12 @@ import {ParserEventLog} from './parser_eventlog';
export class TracesParserCujs extends AbstractTracesParser<Transition> { export class TracesParserCujs extends AbstractTracesParser<Transition> {
private readonly eventLogTrace: ParserEventLog | undefined; private readonly eventLogTrace: ParserEventLog | undefined;
private readonly descriptors: string[]; private readonly descriptors: string[];
private decodedEntries: Cuj[] | undefined;
constructor(parsers: Array<Parser<object>>) { 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) { if (eventlogTraces.length > 0) {
this.eventLogTrace = eventlogTraces[0] as ParserEventLog; this.eventLogTrace = eventlogTraces[0] as ParserEventLog;
} }
@@ -40,35 +42,29 @@ export class TracesParserCujs extends AbstractTracesParser<Transition> {
} }
} }
override canProvideEntries(): boolean { override async parse() {
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[] {
if (this.eventLogTrace === undefined) { if (this.eventLogTrace === undefined) {
throw new Error('eventLogTrace not defined'); 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++) { for (let i = 0; i < this.eventLogTrace.getLengthEntries(); i++) {
events.push(this.eventLogTrace.getEntry(i, TimestampType.REAL)); events.push(await this.eventLogTrace.getEntry(i, TimestampType.REAL));
}
this.cujTrace = new EventLog(events).cujTrace;
} }
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[] { override getDescriptors(): string[] {

View File

@@ -14,23 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
import {assertDefined} from 'common/assert_utils';
import {UnitTestUtils} from 'test/unit/utils'; import {UnitTestUtils} from 'test/unit/utils';
import {Cuj} from 'trace/flickerlib/common'; import {Cuj} from 'trace/flickerlib/common';
import {Parser} from 'trace/parser'; import {Parser} from 'trace/parser';
import {Timestamp, TimestampType} from 'trace/timestamp'; import {Timestamp, TimestampType} from 'trace/timestamp';
import {TraceType} from 'trace/trace_type'; import {TraceType} from 'trace/trace_type';
import {TracesParserCujs} from './traces_parser_cujs';
describe('ParserCujs', () => { describe('ParserCujs', () => {
let parser: Parser<Cuj>; let parser: Parser<Cuj>;
beforeAll(async () => { beforeAll(async () => {
const eventLogParser = assertDefined( parser = await UnitTestUtils.getTracesParser(['traces/eventlog.winscope']);
await UnitTestUtils.getParser('traces/eventlog.winscope')
) as Parser<Event>;
parser = new TracesParserCujs([eventLogParser]);
}); });
it('has expected trace type', () => { 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. * limitations under the License.
*/ */
import {assertDefined} from 'common/assert_utils';
import {Transition, TransitionsTrace} from 'trace/flickerlib/common'; import {Transition, TransitionsTrace} from 'trace/flickerlib/common';
import {Parser} from 'trace/parser'; import {Parser} from 'trace/parser';
import {Timestamp, TimestampType} from 'trace/timestamp'; import {Timestamp, TimestampType} from 'trace/timestamp';
@@ -24,17 +25,17 @@ export class TracesParserTransitions extends AbstractTracesParser<Transition> {
private readonly wmTransitionTrace: Parser<object> | undefined; private readonly wmTransitionTrace: Parser<object> | undefined;
private readonly shellTransitionTrace: Parser<object> | undefined; private readonly shellTransitionTrace: Parser<object> | undefined;
private readonly descriptors: string[]; private readonly descriptors: string[];
private decodedEntries: Transition[] | undefined;
constructor(parsers: Array<Parser<object>>) { constructor(parsers: Array<Parser<object>>) {
super(parsers); super();
const wmTransitionTraces = parsers.filter(
const wmTransitionTraces = this.parsers.filter(
(it) => it.getTraceType() === TraceType.WM_TRANSITION (it) => it.getTraceType() === TraceType.WM_TRANSITION
); );
if (wmTransitionTraces.length > 0) { if (wmTransitionTraces.length > 0) {
this.wmTransitionTrace = wmTransitionTraces[0]; this.wmTransitionTrace = wmTransitionTraces[0];
} }
const shellTransitionTraces = this.parsers.filter( const shellTransitionTraces = parsers.filter(
(it) => it.getTraceType() === TraceType.SHELL_TRANSITION (it) => it.getTraceType() === TraceType.SHELL_TRANSITION
); );
if (shellTransitionTraces.length > 0) { if (shellTransitionTraces.length > 0) {
@@ -49,54 +50,50 @@ export class TracesParserTransitions extends AbstractTracesParser<Transition> {
} }
} }
override canProvideEntries(): boolean { override async parse() {
return this.wmTransitionTrace !== undefined && this.shellTransitionTrace !== undefined; if (this.wmTransitionTrace === undefined) {
} throw new Error('Missing WM Transition trace');
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[];
} }
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[] { override getDescriptors(): string[] {
return this.descriptors; return this.descriptors;
} }
getTraceType(): TraceType { override getTraceType(): TraceType {
return TraceType.TRANSITION; return TraceType.TRANSITION;
} }

View File

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

View File

@@ -18,9 +18,6 @@ import {browser, by, element, ElementFinder} from 'protractor';
import {E2eTestUtils} from './utils'; import {E2eTestUtils} from './utils';
describe('Cross-Tool Protocol', () => { 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_IN_BUGREPORT_MESSAGE = '1670509911000000000';
const TIMESTAMP_FROM_REMOTE_TOOL_TO_WINSCOPE = '1670509912000000000'; const TIMESTAMP_FROM_REMOTE_TOOL_TO_WINSCOPE = '1670509912000000000';
const TIMESTAMP_FROM_WINSCOPE_TO_REMOTE_TOOL = '1670509913000000000'; const TIMESTAMP_FROM_WINSCOPE_TO_REMOTE_TOOL = '1670509913000000000';
@@ -28,12 +25,12 @@ describe('Cross-Tool Protocol', () => {
beforeAll(async () => { beforeAll(async () => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
await browser.manage().timeouts().implicitlyWait(15000); await browser.manage().timeouts().implicitlyWait(15000);
await checkServerIsUp('Remote tool mock', REMOTE_TOOL_MOCK_URL); await E2eTestUtils.checkServerIsUp('Remote tool mock', E2eTestUtils.REMOTE_TOOL_MOCK_URL);
await checkServerIsUp('Winscope', WINSCOPE_URL); await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
}); });
beforeEach(async () => { 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 () => { it('allows communication between remote tool and Winscope', async () => {
@@ -56,14 +53,6 @@ describe('Cross-Tool Protocol', () => {
await checkRemoteToolReceivedTimestamp(); 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 () => { const openWinscopeTabFromRemoteTool = async () => {
await browser.switchTo().window(await getWindowHandleRemoteToolMock()); await browser.switchTo().window(await getWindowHandleRemoteToolMock());
const buttonElement = element(by.css('.button-open-winscope')); const buttonElement = element(by.css('.button-open-winscope'));

View File

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

View File

@@ -13,13 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import * as path from 'path'; import {browser, by, element} from 'protractor';
import {by, element} from 'protractor';
import {CommonTestUtils} from '../common/utils'; import {CommonTestUtils} from '../common/utils';
class E2eTestUtils extends CommonTestUtils { class E2eTestUtils extends CommonTestUtils {
static getProductionIndexHtmlPath(): string { static readonly WINSCOPE_URL = 'http://localhost:8080';
return path.join(CommonTestUtils.getProjectRootPath(), 'dist/prod/index.html'); 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[]) { static async uploadFixture(...paths: string[]) {

View File

@@ -19,14 +19,16 @@ import {E2eTestUtils} from './utils';
describe('Viewer InputMethodClients', () => { describe('Viewer InputMethodClients', () => {
beforeAll(async () => { beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000); 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('traces/elapsed_and_real_timestamp/InputMethodClients.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const viewerPresent = await element(by.css('viewer-input-method')).isPresent(); it('processes trace and renders view', async () => {
expect(viewerPresent).toBeTruthy(); 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', () => { describe('Viewer InputMethodManagerService', () => {
beforeAll(async () => { beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000); 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(
'traces/elapsed_and_real_timestamp/InputMethodManagerService.pb'
);
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const viewerPresent = await element(by.css('viewer-input-method')).isPresent(); it('processes trace and renders view', async () => {
expect(viewerPresent).toBeTruthy(); 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', () => { describe('Viewer InputMethodService', () => {
beforeAll(async () => { beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000); 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('traces/elapsed_and_real_timestamp/InputMethodService.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const viewerPresent = await element(by.css('viewer-input-method')).isPresent(); it('processes trace and renders view', async () => {
expect(viewerPresent).toBeTruthy(); 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', () => { describe('Viewer ProtoLog', () => {
beforeAll(async () => { beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000); 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('traces/elapsed_and_real_timestamp/ProtoLog.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const isViewerRendered = await element(by.css('viewer-protolog')).isPresent(); it('processes trace and renders view', async () => {
expect(isViewerRendered).toBeTruthy(); await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/ProtoLog.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const isFirstMessageRendered = await element( const isViewerRendered = await element(by.css('viewer-protolog')).isPresent();
by.css('viewer-protolog .scroll-messages .message') expect(isViewerRendered).toBeTruthy();
).isPresent();
expect(isFirstMessageRendered).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', () => { describe('Viewer ScreenRecording', () => {
beforeAll(async () => { beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000); 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 () => { it('processes trace and renders view', async () => {

View File

@@ -19,14 +19,16 @@ import {E2eTestUtils} from './utils';
describe('Viewer SurfaceFlinger', () => { describe('Viewer SurfaceFlinger', () => {
beforeAll(async () => { beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000); 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('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const viewerPresent = await element(by.css('viewer-surface-flinger')).isPresent(); it('processes trace and renders view', async () => {
expect(viewerPresent).toBeTruthy(); 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', () => { describe('Viewer Transactions', () => {
beforeAll(async () => { beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000); 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('traces/elapsed_and_real_timestamp/Transactions.pb'); it('processes trace and renders view', async () => {
await E2eTestUtils.closeSnackBarIfNeeded(); await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/Transactions.pb');
await E2eTestUtils.clickViewTracesButton(); await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const isViewerRendered = await element(by.css('viewer-transactions')).isPresent(); const isViewerRendered = await element(by.css('viewer-transactions')).isPresent();
expect(isViewerRendered).toBeTruthy(); expect(isViewerRendered).toBeTruthy();
const isFirstEntryRendered = await element( const isFirstEntryRendered = await element(
by.css('viewer-transactions .scroll .entry') by.css('viewer-transactions .scroll .entry')
).isPresent(); ).isPresent();
expect(isFirstEntryRendered).toBeTruthy(); expect(isFirstEntryRendered).toBeTruthy();
}); });
}); });

View File

@@ -19,7 +19,8 @@ import {E2eTestUtils} from './utils';
describe('Viewer Transitions', () => { describe('Viewer Transitions', () => {
beforeAll(async () => { beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000); 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 () => { it('processes trace and renders view', async () => {
await E2eTestUtils.uploadFixture( await E2eTestUtils.uploadFixture(

View File

@@ -18,15 +18,17 @@ import {E2eTestUtils} from './utils';
describe('Viewer WindowManager', () => { describe('Viewer WindowManager', () => {
beforeAll(async () => { beforeAll(async () => {
browser.manage().timeouts().implicitlyWait(1000); browser.manage().timeouts().implicitlyWait(5000);
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('traces/elapsed_and_real_timestamp/WindowManager.pb');
await E2eTestUtils.closeSnackBarIfNeeded();
await E2eTestUtils.clickViewTracesButton();
const viewerPresent = await element(by.css('viewer-window-manager')).isPresent(); it('processes trace and renders view', async () => {
expect(viewerPresent).toBeTruthy(); 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'; import {E2eTestUtils} from './utils';
describe('winscope', () => { describe('winscope', () => {
beforeAll(() => { beforeAll(async () => {
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath()); 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'); 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'; import {AbsoluteFrameIndex, Trace} from 'trace/trace';
export class TraceUtils { export class TraceUtils {
static extractEntries<T>(trace: Trace<T>): T[] { static async extractEntries<T>(trace: Trace<T>): Promise<T[]> {
const entries = new Array<T>(); const promises = trace.mapEntry(async (entry, index) => {
trace.forEachEntry((entry) => { return await entry.getValue();
entries.push(entry.getValue());
}); });
return entries; return await Promise.all(promises);
} }
static extractTimestamps<T>(trace: Trace<T>): Timestamp[] { static extractTimestamps<T>(trace: Trace<T>): Timestamp[] {
@@ -34,11 +33,12 @@ export class TraceUtils {
return timestamps; 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[]>(); const frames = new Map<AbsoluteFrameIndex, T[]>();
trace.forEachFrame((frame, index) => { const promises = trace.mapFrame(async (frame, index) => {
frames.set(index, TraceUtils.extractEntries(frame)); frames.set(index, await TraceUtils.extractEntries(frame));
}); });
await Promise.all(promises);
return frames; return frames;
} }
} }

View File

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

View File

@@ -15,6 +15,7 @@
*/ */
import {ParserFactory} from 'parsers/parser_factory'; import {ParserFactory} from 'parsers/parser_factory';
import {TracesParserFactory} from 'parsers/traces_parser_factory';
import {CommonTestUtils} from 'test/common/utils'; import {CommonTestUtils} from 'test/common/utils';
import {LayerTraceEntry, WindowManagerState} from 'trace/flickerlib/common'; import {LayerTraceEntry, WindowManagerState} from 'trace/flickerlib/common';
import {Parser} from 'trace/parser'; import {Parser} from 'trace/parser';
@@ -43,6 +44,15 @@ class UnitTestUtils extends CommonTestUtils {
return parsers[0].parser; 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> { static async getWindowManagerState(): Promise<WindowManagerState> {
return UnitTestUtils.getTraceEntry('traces/elapsed_timestamp/WindowManager.pb'); return UnitTestUtils.getTraceEntry('traces/elapsed_timestamp/WindowManager.pb');
} }

View File

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

View File

@@ -20,9 +20,7 @@ import intDefMapping from '../../../../../../prebuilts/misc/common/winscope/intD
import { import {
toActiveBuffer, toActiveBuffer,
toColor, toColor,
toColor3,
toInsets, toInsets,
toMatrix22,
toPoint, toPoint,
toPointF, toPointF,
toRect, toRect,
@@ -66,6 +64,9 @@ export class ObjectFormatter {
* @return The "true" properties of the entry as described above * @return The "true" properties of the entry as described above
*/ */
static getProperties(entry: any): string[] { static getProperties(entry: any): string[] {
if (entry === null || entry === undefined) {
return [];
}
const props: string[] = []; const props: string[] = [];
let obj = entry; let obj = entry;
@@ -171,7 +172,7 @@ export class ObjectFormatter {
case `ActiveBufferProto`: case `ActiveBufferProto`:
return toActiveBuffer(obj); return toActiveBuffer(obj);
case `Color3`: case `Color3`:
return toColor3(obj); return toColor(obj, /* hasAlpha */ false);
case `ColorProto`: case `ColorProto`:
return toColor(obj); return toColor(obj);
case `Long`: case `Long`:
@@ -184,8 +185,6 @@ export class ObjectFormatter {
// definition of insets and rects uses the same object type // definition of insets and rects uses the same object type
case `RectProto`: case `RectProto`:
return key.toLowerCase().includes('insets') ? toInsets(obj) : toRect(obj); return key.toLowerCase().includes('insets') ? toInsets(obj) : toRect(obj);
case `Matrix22`:
return toMatrix22(obj);
case `FloatRectProto`: case `FloatRectProto`:
return toRectF(obj); return toRectF(obj);
case `RegionProto`: 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; const WindowToken = require('flicker').android.tools.common.traces.wm.WindowToken;
// SF // SF
const HwcCompositionType =
require('flicker').android.tools.common.traces.surfaceflinger.HwcCompositionType;
const Layer = require('flicker').android.tools.common.traces.surfaceflinger.Layer; const Layer = require('flicker').android.tools.common.traces.surfaceflinger.Layer;
const LayerProperties = const LayerProperties =
require('flicker').android.tools.common.traces.surfaceflinger.LayerProperties; require('flicker').android.tools.common.traces.surfaceflinger.LayerProperties;
@@ -76,10 +78,8 @@ const WmTransitionData = require('flicker').android.tools.common.traces.wm.WmTra
// Common // Common
const Size = require('flicker').android.tools.common.datatypes.Size; const Size = require('flicker').android.tools.common.datatypes.Size;
const ActiveBuffer = require('flicker').android.tools.common.datatypes.ActiveBuffer; 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 Color = require('flicker').android.tools.common.datatypes.Color;
const Insets = require('flicker').android.tools.common.datatypes.Insets; 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 Matrix33 = require('flicker').android.tools.common.datatypes.Matrix33;
const PlatformConsts = require('flicker').android.tools.common.PlatformConsts; const PlatformConsts = require('flicker').android.tools.common.PlatformConsts;
const Rotation = require('flicker').android.tools.common.Rotation; 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_SIZE = Size.Companion.EMPTY;
const EMPTY_BUFFER = ActiveBuffer.Companion.EMPTY; const EMPTY_BUFFER = ActiveBuffer.Companion.EMPTY;
const EMPTY_COLOR3 = Color3.Companion.EMPTY;
const EMPTY_COLOR = Color.Companion.EMPTY; const EMPTY_COLOR = Color.Companion.EMPTY;
const EMPTY_INSETS = Insets.Companion.EMPTY; const EMPTY_INSETS = Insets.Companion.EMPTY;
const EMPTY_RECT = Rect.Companion.EMPTY; const EMPTY_RECT = Rect.Companion.EMPTY;
const EMPTY_RECTF = RectF.Companion.EMPTY; const EMPTY_RECTF = RectF.Companion.EMPTY;
const EMPTY_POINT = Point.Companion.EMPTY; const EMPTY_POINT = Point.Companion.EMPTY;
const EMPTY_POINTF = PointF.Companion.EMPTY; const EMPTY_POINTF = PointF.Companion.EMPTY;
const EMPTY_MATRIX22 = Matrix22.Companion.EMPTY;
const EMPTY_MATRIX33 = Matrix33.Companion.identity(0, 0); const EMPTY_MATRIX33 = Matrix33.Companion.identity(0, 0);
const EMPTY_TRANSFORM = new Transform(0, EMPTY_MATRIX33); const EMPTY_TRANSFORM = new Transform(0, EMPTY_MATRIX33);
@@ -128,27 +126,17 @@ function toActiveBuffer(proto) {
return EMPTY_BUFFER; return EMPTY_BUFFER;
} }
function toColor3(proto) { function toColor(proto, hasAlpha = true) {
if (proto == null) { if (proto == null) {
return EMPTY_COLOR; return EMPTY_COLOR;
} }
const r = proto.r ?? 0; const r = proto.r ?? 0;
const g = proto.g ?? 0; const g = proto.g ?? 0;
const b = proto.b ?? 0; const b = proto.b ?? 0;
if (r || g || b) { let a = proto.a;
return new Color3(r, g, b); 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) { if (r || g || b || a) {
return new Color(r, g, b, a); return new Color(r, g, b, a);
} }
@@ -194,6 +182,22 @@ function toInsets(proto) {
return EMPTY_INSETS; 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) { function toRect(proto) {
if (proto == null) { if (proto == null) {
return EMPTY_RECT; return EMPTY_RECT;
@@ -261,22 +265,6 @@ function toTransform(proto) {
return EMPTY_TRANSFORM; 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 { export {
Activity, Activity,
Configuration, Configuration,
@@ -298,13 +286,13 @@ export {
WindowManagerState, WindowManagerState,
WindowManagerTraceEntryBuilder, WindowManagerTraceEntryBuilder,
// SF // SF
HwcCompositionType,
Layer, Layer,
LayerProperties, LayerProperties,
LayerTraceEntry, LayerTraceEntry,
LayerTraceEntryBuilder, LayerTraceEntryBuilder,
LayersTrace, LayersTrace,
Transform, Transform,
Matrix22,
Matrix33, Matrix33,
Display, Display,
// Eventlog // Eventlog
@@ -328,7 +316,6 @@ export {
Size, Size,
ActiveBuffer, ActiveBuffer,
Color, Color,
Color3,
Insets, Insets,
PlatformConsts, PlatformConsts,
Point, Point,
@@ -343,24 +330,21 @@ export {
toSize, toSize,
toActiveBuffer, toActiveBuffer,
toColor, toColor,
toColor3, toCropRect,
toInsets, toInsets,
toPoint, toPoint,
toPointF, toPointF,
toRect, toRect,
toRectF, toRectF,
toRegion, toRegion,
toMatrix22,
toTransform, toTransform,
// Constants // Constants
EMPTY_BUFFER, EMPTY_BUFFER,
EMPTY_COLOR3,
EMPTY_COLOR, EMPTY_COLOR,
EMPTY_RECT, EMPTY_RECT,
EMPTY_RECTF, EMPTY_RECTF,
EMPTY_POINT, EMPTY_POINT,
EMPTY_POINTF, EMPTY_POINTF,
EMPTY_MATRIX22,
EMPTY_MATRIX33, EMPTY_MATRIX33,
EMPTY_TRANSFORM, EMPTY_TRANSFORM,
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -46,7 +46,7 @@ describe('FrameMapper', () => {
let windowManager: Trace<WindowManagerState>; let windowManager: Trace<WindowManagerState>;
let traces: Traces; let traces: Traces;
beforeAll(() => { beforeAll(async () => {
// Frames F0 F1 // Frames F0 F1
// |<------>| |<->| // |<------>| |<->|
// PROTO_LOG: 0 1 2 3 4 5 // PROTO_LOG: 0 1 2 3 4 5
@@ -75,10 +75,10 @@ describe('FrameMapper', () => {
traces = new Traces(); traces = new Traces();
traces.setTrace(TraceType.PROTO_LOG, protoLog); traces.setTrace(TraceType.PROTO_LOG, protoLog);
traces.setTrace(TraceType.WINDOW_MANAGER, windowManager); 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<{}>>>(); const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
expectedFrames.set( expectedFrames.set(
0, 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 windowManager: Trace<WindowManagerState>;
let traces: Traces; let traces: Traces;
beforeAll(() => { beforeAll(async () => {
// IME: 0--1--2 3 // IME: 0--1--2 3
// | | // | |
// WINDOW_MANAGER: 0 1 2 // WINDOW_MANAGER: 0 1 2
@@ -131,10 +131,10 @@ describe('FrameMapper', () => {
traces = new Traces(); traces = new Traces();
traces.setTrace(TraceType.INPUT_METHOD_CLIENTS, ime); traces.setTrace(TraceType.INPUT_METHOD_CLIENTS, ime);
traces.setTrace(TraceType.WINDOW_MANAGER, windowManager); 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<{}>>>(); const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
expectedFrames.set( expectedFrames.set(
0, 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 transactions: Trace<object>;
let traces: Traces; let traces: Traces;
beforeAll(() => { beforeAll(async () => {
// WINDOW_MANAGER: 0 1 2 3 // WINDOW_MANAGER: 0 1 2 3
// | | | \ // | | | \
// TRANSACTIONS: 0 1 2--3 4 5 ... 6 <-- ignored (not connected) because too far // TRANSACTIONS: 0 1 2--3 4 5 ... 6 <-- ignored (not connected) because too far
@@ -207,10 +207,10 @@ describe('FrameMapper', () => {
traces = new Traces(); traces = new Traces();
traces.setTrace(TraceType.WINDOW_MANAGER, windowManager); traces.setTrace(TraceType.WINDOW_MANAGER, windowManager);
traces.setTrace(TraceType.TRANSACTIONS, transactions); 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<{}>>>(); const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
expectedFrames.set( expectedFrames.set(
0, 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 surfaceFlinger: Trace<LayerTraceEntry>;
let traces: Traces; let traces: Traces;
beforeAll(() => { beforeAll(async () => {
// TRANSACTIONS: 0 1--2 3 4 // TRANSACTIONS: 0 1--2 3 4
// \ \ \ // \ \ \
// \ \ \ // \ \ \
@@ -292,16 +292,16 @@ describe('FrameMapper', () => {
traces = new Traces(); traces = new Traces();
traces.setTrace(TraceType.TRANSACTIONS, transactions); traces.setTrace(TraceType.TRANSACTIONS, transactions);
traces.setTrace(TraceType.SURFACE_FLINGER, surfaceFlinger); 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<{}>>>(); const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
expectedFrames.set( expectedFrames.set(
0, 0,
new Map<TraceType, Array<{}>>([ new Map<TraceType, Array<{}>>([
[TraceType.TRANSACTIONS, [transactions.getEntry(0).getValue()]], [TraceType.TRANSACTIONS, [await transactions.getEntry(0).getValue()]],
[TraceType.SURFACE_FLINGER, [surfaceFlinger.getEntry(0).getValue()]], [TraceType.SURFACE_FLINGER, [await surfaceFlinger.getEntry(0).getValue()]],
]) ])
); );
expectedFrames.set( expectedFrames.set(
@@ -309,20 +309,20 @@ describe('FrameMapper', () => {
new Map<TraceType, Array<{}>>([ new Map<TraceType, Array<{}>>([
[ [
TraceType.TRANSACTIONS, 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( expectedFrames.set(
2, 2,
new Map<TraceType, Array<{}>>([ new Map<TraceType, Array<{}>>([
[TraceType.TRANSACTIONS, [transactions.getEntry(3).getValue()]], [TraceType.TRANSACTIONS, [await transactions.getEntry(3).getValue()]],
[TraceType.SURFACE_FLINGER, [surfaceFlinger.getEntry(2).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 screenRecording: Trace<ScreenRecordingTraceEntry>;
let traces: Traces; let traces: Traces;
beforeAll(() => { beforeAll(async () => {
// SURFACE_FLINGER: 0 1 2--- 3 4 5 6 // SURFACE_FLINGER: 0 1 2--- 3 4 5 6
// \ \ \ \ // \ \ \ \
// \ \ \ \ // \ \ \ \
@@ -365,10 +365,10 @@ describe('FrameMapper', () => {
traces = new Traces(); traces = new Traces();
traces.setTrace(TraceType.SURFACE_FLINGER, surfaceFlinger); traces.setTrace(TraceType.SURFACE_FLINGER, surfaceFlinger);
traces.setTrace(TraceType.SCREEN_RECORDING, screenRecording); 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<{}>>>(); const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
expectedFrames.set( expectedFrames.set(
0, 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; getTraceType(): TraceType;
getLengthEntries(): number; getLengthEntries(): number;
getTimestamps(type: TimestampType): Timestamp[] | undefined; getTimestamps(type: TimestampType): Timestamp[] | undefined;
getEntry(index: number, timestampType: TimestampType): T; getEntry(index: number, timestampType: TimestampType): Promise<T>;
getDescriptors(): string[]; getDescriptors(): string[];
} }

View File

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

View File

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

View File

@@ -66,8 +66,8 @@ describe('TraceEntry', () => {
expect(trace.getEntry(5).getFramesRange()).toEqual({start: 4, end: 5}); expect(trace.getEntry(5).getFramesRange()).toEqual({start: 4, end: 5});
}); });
it('getValue()', () => { it('getValue()', async () => {
expect(trace.getEntry(0).getValue()).toEqual('entry-0'); expect(await trace.getEntry(0).getValue()).toEqual('entry-0');
expect(trace.getEntry(1).getValue()).toEqual('entry-1'); 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, ERROR,
TEST_TRACE_STRING, TEST_TRACE_STRING,
TEST_TRACE_NUMBER, TEST_TRACE_NUMBER,
VIEW_CAPTURE,
} }
export interface TraceEntryTypeMap { export interface TraceEntryTypeMap {
@@ -69,4 +70,5 @@ export interface TraceEntryTypeMap {
[TraceType.ERROR]: object; [TraceType.ERROR]: object;
[TraceType.TEST_TRACE_STRING]: string; [TraceType.TEST_TRACE_STRING]: string;
[TraceType.TEST_TRACE_NUMBER]: number; [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 { forEachFrame(callback: (traces: Traces, index: AbsoluteFrameIndex) => void): void {
let startFrameIndex: AbsoluteFrameIndex = Number.MAX_VALUE; let startFrameIndex: AbsoluteFrameIndex = Number.MAX_VALUE;
let endFrameIndex: AbsoluteFrameIndex = Number.MIN_VALUE; let endFrameIndex: AbsoluteFrameIndex = Number.MIN_VALUE;
@@ -72,4 +80,20 @@ export class Traces {
callback(this.sliceFrames(i, i + 1), i); 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. * limitations under the License.
*/ */
import {assertDefined} from 'common/assert_utils';
import {FunctionUtils} from 'common/function_utils';
import {TracesBuilder} from 'test/unit/traces_builder'; import {TracesBuilder} from 'test/unit/traces_builder';
import {TracesUtils} from 'test/unit/traces_utils'; import {TracesUtils} from 'test/unit/traces_utils';
import {TraceBuilder} from 'test/unit/trace_builder'; import {TraceBuilder} from 'test/unit/trace_builder';
import {TraceUtils} from 'test/unit/trace_utils'; import {TraceUtils} from 'test/unit/trace_utils';
import {assertDefined} from '../common/assert_utils';
import {FrameMapBuilder} from './frame_map_builder'; import {FrameMapBuilder} from './frame_map_builder';
import {AbsoluteFrameIndex} from './index_types'; import {AbsoluteFrameIndex} from './index_types';
import {RealTimestamp} from './timestamp'; import {RealTimestamp} from './timestamp';
@@ -134,31 +135,31 @@ describe('Traces', () => {
); );
}); });
it('getTrace()', () => { it('getTrace()', async () => {
expect( 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[]); ).toEqual(extractedEntriesFull.get(TraceType.TEST_TRACE_STRING) as string[]);
expect( 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[]); ).toEqual(extractedEntriesFull.get(TraceType.TEST_TRACE_NUMBER) as number[]);
expect(traces.getTrace(TraceType.SURFACE_FLINGER)).toBeUndefined(); expect(traces.getTrace(TraceType.SURFACE_FLINGER)).toBeUndefined();
}); });
it('sliceTime()', () => { it('sliceTime()', async () => {
// empty // empty
{ {
const slice = traces.sliceTime(time3, time3); const slice = traces.sliceTime(time3, time3);
expect(TracesUtils.extractEntries(slice)).toEqual(extractedEntriesEmpty); expect(await TracesUtils.extractEntries(slice)).toEqual(extractedEntriesEmpty);
} }
// full // full
{ {
const slice = traces.sliceTime(); const slice = traces.sliceTime();
expect(TracesUtils.extractEntries(slice)).toEqual(extractedEntriesFull); expect(await TracesUtils.extractEntries(slice)).toEqual(extractedEntriesFull);
} }
// middle // middle
{ {
const slice = traces.sliceTime(time4, time8); const slice = traces.sliceTime(time4, time8);
expect(TracesUtils.extractEntries(slice)).toEqual( expect(await TracesUtils.extractEntries(slice)).toEqual(
new Map<TraceType, Array<{}>>([ new Map<TraceType, Array<{}>>([
[TraceType.TEST_TRACE_STRING, ['2', '3']], [TraceType.TEST_TRACE_STRING, ['2', '3']],
[TraceType.TEST_TRACE_NUMBER, [1, 2]], [TraceType.TEST_TRACE_NUMBER, [1, 2]],
@@ -168,7 +169,7 @@ describe('Traces', () => {
// slice away front // slice away front
{ {
const slice = traces.sliceTime(time8); const slice = traces.sliceTime(time8);
expect(TracesUtils.extractEntries(slice)).toEqual( expect(await TracesUtils.extractEntries(slice)).toEqual(
new Map<TraceType, Array<{}>>([ new Map<TraceType, Array<{}>>([
[TraceType.TEST_TRACE_STRING, ['4']], [TraceType.TEST_TRACE_STRING, ['4']],
[TraceType.TEST_TRACE_NUMBER, [3, 4]], [TraceType.TEST_TRACE_NUMBER, [3, 4]],
@@ -178,7 +179,7 @@ describe('Traces', () => {
// slice away back // slice away back
{ {
const slice = traces.sliceTime(undefined, time8); const slice = traces.sliceTime(undefined, time8);
expect(TracesUtils.extractEntries(slice)).toEqual( expect(await TracesUtils.extractEntries(slice)).toEqual(
new Map<TraceType, Array<{}>>([ new Map<TraceType, Array<{}>>([
[TraceType.TEST_TRACE_STRING, ['0', '1', '2', '3']], [TraceType.TEST_TRACE_STRING, ['0', '1', '2', '3']],
[TraceType.TEST_TRACE_NUMBER, [0, 1, 2]], [TraceType.TEST_TRACE_NUMBER, [0, 1, 2]],
@@ -187,16 +188,16 @@ describe('Traces', () => {
} }
}); });
it('sliceFrames()', () => { it('sliceFrames()', async () => {
// empty // empty
{ {
const slice = traces.sliceFrames(1, 1); const slice = traces.sliceFrames(1, 1);
expect(TracesUtils.extractFrames(slice)).toEqual(extractedFramesEmpty); expect(await TracesUtils.extractFrames(slice)).toEqual(extractedFramesEmpty);
} }
// full // full
{ {
const slice = traces.sliceFrames(); const slice = traces.sliceFrames();
expect(TracesUtils.extractFrames(slice)).toEqual(extractedFramesFull); expect(await TracesUtils.extractFrames(slice)).toEqual(extractedFramesFull);
} }
// middle // middle
{ {
@@ -204,7 +205,7 @@ describe('Traces', () => {
const expectedFrames = structuredClone(extractedFramesFull); const expectedFrames = structuredClone(extractedFramesFull);
expectedFrames.delete(0); expectedFrames.delete(0);
expectedFrames.delete(4); expectedFrames.delete(4);
expect(TracesUtils.extractFrames(slice)).toEqual(expectedFrames); expect(await TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
} }
// slice away front // slice away front
{ {
@@ -212,7 +213,7 @@ describe('Traces', () => {
const expectedFrames = structuredClone(extractedFramesFull); const expectedFrames = structuredClone(extractedFramesFull);
expectedFrames.delete(0); expectedFrames.delete(0);
expectedFrames.delete(1); expectedFrames.delete(1);
expect(TracesUtils.extractFrames(slice)).toEqual(expectedFrames); expect(await TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
} }
// slice away back // slice away back
{ {
@@ -221,23 +222,24 @@ describe('Traces', () => {
expectedFrames.delete(2); expectedFrames.delete(2);
expectedFrames.delete(3); expectedFrames.delete(3);
expectedFrames.delete(4); expectedFrames.delete(4);
expect(TracesUtils.extractFrames(slice)).toEqual(expectedFrames); expect(await TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
} }
}); });
it('forEachTrace()', () => { it('mapTrace()', async () => {
traces.forEachTrace((trace) => { const promises = traces.mapTrace(async (trace) => {
const expectedEntries = extractedEntriesFull.get(trace.type) as Array<{}>; const expectedEntries = extractedEntriesFull.get(trace.type) as Array<{}>;
const actualEntries = TraceUtils.extractEntries(trace); const actualEntries = await TraceUtils.extractEntries(trace);
expect(actualEntries).toEqual(expectedEntries); expect(actualEntries).toEqual(expectedEntries);
}); });
await Promise.all(promises);
}); });
it('forEachFrame()', () => { it('mapFrame()', async () => {
expect(TracesUtils.extractFrames(traces)).toEqual(extractedFramesFull); expect(await TracesUtils.extractFrames(traces)).toEqual(extractedFramesFull);
}); });
it('it supports empty traces', () => { it('it supports empty traces', async () => {
const traces = new TracesBuilder() const traces = new TracesBuilder()
.setEntries(TraceType.TEST_TRACE_STRING, []) .setEntries(TraceType.TEST_TRACE_STRING, [])
.setFrameMap(TraceType.TEST_TRACE_STRING, new FrameMapBuilder(0, 0).build()) .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()) .setFrameMap(TraceType.TEST_TRACE_NUMBER, new FrameMapBuilder(0, 0).build())
.build(); .build();
expect(TracesUtils.extractEntries(traces)).toEqual(extractedEntriesEmpty); expect(await TracesUtils.extractEntries(traces)).toEqual(extractedEntriesEmpty);
expect(TracesUtils.extractFrames(traces)).toEqual(extractedFramesEmpty); 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 extractedEntriesEmpty
); );
expect(TracesUtils.extractFrames(traces.sliceTime(time1, time10))).toEqual( expect(await TracesUtils.extractFrames(traces.sliceTime(time1, time10))).toEqual(
extractedFramesEmpty extractedFramesEmpty
); );
expect(TracesUtils.extractEntries(traces.sliceFrames(0, 10))).toEqual(extractedEntriesEmpty); expect(await TracesUtils.extractEntries(traces.sliceFrames(0, 10))).toEqual(
expect(TracesUtils.extractFrames(traces.sliceFrames(0, 10))).toEqual(extractedFramesEmpty); 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() const traces = new TracesBuilder()
.setEntries(TraceType.TEST_TRACE_STRING, ['entry-0']) .setEntries(TraceType.TEST_TRACE_STRING, ['entry-0'])
.setTimestamps(TraceType.TEST_TRACE_STRING, [time1]) .setTimestamps(TraceType.TEST_TRACE_STRING, [time1])
@@ -276,13 +282,17 @@ describe('Traces', () => {
[TraceType.TEST_TRACE_NUMBER, [0]], [TraceType.TEST_TRACE_NUMBER, [0]],
]); ]);
expect(TracesUtils.extractEntries(traces)).toEqual(expectedEntries); expect(await TracesUtils.extractEntries(traces)).toEqual(expectedEntries);
expect(TracesUtils.extractEntries(traces.sliceTime())).toEqual(expectedEntries); expect(await TracesUtils.extractEntries(traces.sliceTime())).toEqual(expectedEntries);
expect(() => { expect(() => {
traces.sliceFrames(); traces.sliceFrames();
}).toThrow(); }).toThrow();
expect(() => { expect(() => {
TracesUtils.extractFrames(traces); traces.forEachFrame(FunctionUtils.DO_NOTHING);
}).toThrow();
expect(() => {
traces.mapFrame(FunctionUtils.DO_NOTHING);
}).toThrow(); }).toThrow();
}); });
}); });

View File

@@ -101,7 +101,7 @@ export abstract class PresenterInputMethod {
this.notifyViewCallback(this.uiData); this.notifyViewCallback(this.uiData);
} }
onTracePositionUpdate(position: TracePosition) { async onTracePositionUpdate(position: TracePosition) {
this.uiData = new ImeUiData(this.dependencies); this.uiData = new ImeUiData(this.dependencies);
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions; this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
this.uiData.propertiesUserOptions = this.propertiesUserOptions; this.uiData.propertiesUserOptions = this.propertiesUserOptions;
@@ -109,11 +109,11 @@ export abstract class PresenterInputMethod {
const [imeEntry, sfEntry, wmEntry] = this.findTraceEntries(position); const [imeEntry, sfEntry, wmEntry] = this.findTraceEntries(position);
if (imeEntry) { if (imeEntry) {
this.entry = imeEntry.getValue() as TraceTreeNode; this.entry = (await imeEntry.getValue()) as TraceTreeNode;
this.uiData.highlightedItems = this.highlightedItems; this.uiData.highlightedItems = this.highlightedItems;
this.uiData.additionalProperties = this.getAdditionalProperties( this.uiData.additionalProperties = this.getAdditionalProperties(
wmEntry?.getValue(), await wmEntry?.getValue(),
sfEntry?.getValue() await sfEntry?.getValue()
); );
this.uiData.tree = this.generateTree(); this.uiData.tree = this.generateTree();
this.uiData.hierarchyTableProperties = this.updateHierarchyTableProperties(); 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(); const traces = new TracesBuilder().setEntries(imeTraceType, []).build();
presenter = createPresenter(traces); presenter = createPresenter(traces);
@@ -65,7 +65,7 @@ export function executePresenterInputMethodTests(
expect(uiData.propertiesUserOptions).toBeTruthy(); expect(uiData.propertiesUserOptions).toBeTruthy();
expect(uiData.tree).toBeFalsy(); expect(uiData.tree).toBeFalsy();
presenter.onTracePositionUpdate(position); await presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy(); expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy(); expect(uiData.propertiesUserOptions).toBeTruthy();
expect(uiData.tree).toBeFalsy(); expect(uiData.tree).toBeFalsy();
@@ -73,7 +73,7 @@ export function executePresenterInputMethodTests(
it('is robust to traces without SF', async () => { it('is robust to traces without SF', async () => {
await setUpTestEnvironment([imeTraceType, TraceType.WINDOW_MANAGER]); await setUpTestEnvironment([imeTraceType, TraceType.WINDOW_MANAGER]);
presenter.onTracePositionUpdate(position); await presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy(); expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy(); expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue(); expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
@@ -81,7 +81,7 @@ export function executePresenterInputMethodTests(
it('is robust to traces without WM', async () => { it('is robust to traces without WM', async () => {
await setUpTestEnvironment([imeTraceType, TraceType.SURFACE_FLINGER]); await setUpTestEnvironment([imeTraceType, TraceType.SURFACE_FLINGER]);
presenter.onTracePositionUpdate(position); await presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy(); expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy(); expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue(); 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 () => { it('is robust to traces without WM and SF', async () => {
await setUpTestEnvironment([imeTraceType]); await setUpTestEnvironment([imeTraceType]);
presenter.onTracePositionUpdate(position); await presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy(); expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy(); expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue(); expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
}); });
it('processes trace position updates', () => { it('processes trace position updates', async () => {
presenter.onTracePositionUpdate(position); await presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy(); expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy(); expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue(); expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
@@ -120,7 +120,7 @@ export function executePresenterInputMethodTests(
expect(uiData.highlightedItems).toContain(id); expect(uiData.highlightedItems).toContain(id);
}); });
it('can update hierarchy tree', () => { it('can update hierarchy tree', async () => {
//change flat view to true //change flat view to true
const userOptions: UserOptions = { const userOptions: UserOptions = {
onlyVisible: { onlyVisible: {
@@ -138,7 +138,7 @@ export function executePresenterInputMethodTests(
}; };
let expectedChildren = expectHierarchyTreeWithSfSubtree ? 2 : 1; let expectedChildren = expectHierarchyTreeWithSfSubtree ? 2 : 1;
presenter.onTracePositionUpdate(position); await presenter.onTracePositionUpdate(position);
expect(uiData.tree?.children.length).toEqual(expectedChildren); expect(uiData.tree?.children.length).toEqual(expectedChildren);
// Filter out non-visible child // Filter out non-visible child
@@ -148,7 +148,7 @@ export function executePresenterInputMethodTests(
expect(uiData.tree?.children.length).toEqual(expectedChildren); expect(uiData.tree?.children.length).toEqual(expectedChildren);
}); });
it('can filter hierarchy tree', () => { it('can filter hierarchy tree', async () => {
const userOptions: UserOptions = { const userOptions: UserOptions = {
onlyVisible: { onlyVisible: {
name: 'Only visible', name: 'Only visible',
@@ -165,7 +165,7 @@ export function executePresenterInputMethodTests(
}; };
const expectedChildren = expectHierarchyTreeWithSfSubtree ? 12 : 1; const expectedChildren = expectHierarchyTreeWithSfSubtree ? 12 : 1;
presenter.onTracePositionUpdate(position); await presenter.onTracePositionUpdate(position);
presenter.updateHierarchyTree(userOptions); presenter.updateHierarchyTree(userOptions);
expect(uiData.tree?.children.length).toEqual(expectedChildren); expect(uiData.tree?.children.length).toEqual(expectedChildren);
@@ -174,15 +174,15 @@ export function executePresenterInputMethodTests(
expect(uiData.tree?.children.length).toEqual(0); expect(uiData.tree?.children.length).toEqual(0);
}); });
it('can set new properties tree and associated ui data', () => { it('can set new properties tree and associated ui data', async () => {
presenter.onTracePositionUpdate(position); await presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree); presenter.newPropertiesTree(selectedTree);
// does not check specific tree values as tree transformation method may change // does not check specific tree values as tree transformation method may change
expect(uiData.propertiesTree).toBeTruthy(); expect(uiData.propertiesTree).toBeTruthy();
}); });
it('can filter properties tree', () => { it('can filter properties tree', async () => {
presenter.onTracePositionUpdate(position); await presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree); presenter.newPropertiesTree(selectedTree);
let nonTerminalChildren = let nonTerminalChildren =
uiData.propertiesTree?.children?.filter( uiData.propertiesTree?.children?.filter(

View File

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

View File

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

View File

@@ -180,7 +180,11 @@ class Mapper3D {
const maxDisplaySize = this.getMaxDisplaySize(rects2d); const maxDisplaySize = this.getMaxDisplaySize(rects2d);
const rects3d = rects2d.map((rect2d): Rect3D => { 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 const darkFactor = rect2d.isVisible
? (visibleRectsTotal - visibleRectsSoFar++) / visibleRectsTotal ? (visibleRectsTotal - visibleRectsSoFar++) / visibleRectsTotal

View File

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

View File

@@ -33,7 +33,7 @@ class View {
} }
interface Viewer { interface Viewer {
onTracePositionUpdate(position: TracePosition): void; onTracePositionUpdate(position: TracePosition): Promise<void>;
getViews(): View[]; getViews(): View[];
getDependencies(): TraceType[]; 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 {ViewerSurfaceFlinger} from './viewer_surface_flinger/viewer_surface_flinger';
import {ViewerTransactions} from './viewer_transactions/viewer_transactions'; import {ViewerTransactions} from './viewer_transactions/viewer_transactions';
import {ViewerTransitions} from './viewer_transitions/viewer_transitions'; 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'; import {ViewerWindowManager} from './viewer_window_manager/viewer_window_manager';
class ViewerFactory { class ViewerFactory {
@@ -41,6 +42,7 @@ class ViewerFactory {
ViewerProtoLog, ViewerProtoLog,
ViewerScreenRecording, ViewerScreenRecording,
ViewerTransitions, ViewerTransitions,
ViewerViewCapture,
]; ];
createViewers(activeTraceTypes: Set<TraceType>, traces: Traces, storage: Storage): Viewer[] { createViewers(activeTraceTypes: Set<TraceType>, traces: Traces, storage: Storage): Viewer[] {

View File

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

View File

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

View File

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

View File

@@ -31,21 +31,21 @@ class ViewerProtoLog implements Viewer {
}); });
this.htmlElement.addEventListener(Events.LogLevelsFilterChanged, (event) => { 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) => { 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) => { 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) => { this.htmlElement.addEventListener(Events.SearchStringFilterChanged, (event) => {
return this.presenter.onSearchStringFilterChanged((event as CustomEvent).detail); this.presenter.onSearchStringFilterChanged((event as CustomEvent).detail);
}); });
} }
onTracePositionUpdate(position: TracePosition) { async onTracePositionUpdate(position: TracePosition) {
this.presenter.onTracePositionUpdate(position); await this.presenter.onTracePositionUpdate(position);
} }
getViews(): View[] { getViews(): View[] {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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