Merge Android 14 QPR1
Merged-In: Ic7232e2b9b8090ec108a2e8a945d07b52389e26a Bug: 315507370 Change-Id: I53fabecd504cb3aaf6b1f9c7a9335eacd4309b28
This commit is contained in:
@@ -2,7 +2,7 @@ Pkg.Desc=Android SDK Platform ${PLATFORM_VERSION}
|
||||
Pkg.UserSrc=false
|
||||
Platform.Version=${PLATFORM_VERSION}
|
||||
Platform.CodeName=
|
||||
Pkg.Revision=1
|
||||
Pkg.Revision=2
|
||||
AndroidVersion.ApiLevel=${PLATFORM_SDK_VERSION}
|
||||
AndroidVersion.CodeName=${PLATFORM_VERSION_CODENAME}
|
||||
AndroidVersion.ExtensionLevel=${PLATFORM_SDK_EXTENSION_VERSION}
|
||||
|
||||
@@ -30,7 +30,7 @@ exports.config = {
|
||||
args: ['--headless', '--disable-gpu', '--window-size=1280x1024'],
|
||||
},
|
||||
},
|
||||
chromeDriver: './node_modules/webdriver-manager/selenium/chromedriver_113.0.5672.63',
|
||||
chromeDriver: './node_modules/webdriver-manager/selenium/chromedriver_114.0.5735.90',
|
||||
|
||||
allScriptsTimeout: 10000,
|
||||
getPageTimeout: 10000,
|
||||
|
||||
@@ -58,6 +58,7 @@ import {ViewerScreenRecordingComponent} from 'viewers/viewer_screen_recording/vi
|
||||
import {ViewerSurfaceFlingerComponent} from 'viewers/viewer_surface_flinger/viewer_surface_flinger_component';
|
||||
import {ViewerTransactionsComponent} from 'viewers/viewer_transactions/viewer_transactions_component';
|
||||
import {ViewerTransitionsComponent} from 'viewers/viewer_transitions/viewer_transitions_component';
|
||||
import {ViewerViewCaptureComponent} from 'viewers/viewer_view_capture/viewer_view_capture_component';
|
||||
import {ViewerWindowManagerComponent} from 'viewers/viewer_window_manager/viewer_window_manager_component';
|
||||
import {AdbProxyComponent} from './components/adb_proxy_component';
|
||||
import {AppComponent} from './components/app_component';
|
||||
@@ -88,6 +89,7 @@ import {WebAdbComponent} from './components/web_adb_component';
|
||||
ViewerTransactionsComponent,
|
||||
ViewerScreenRecordingComponent,
|
||||
ViewerTransitionsComponent,
|
||||
ViewerViewCaptureComponent,
|
||||
CollectTracesComponent,
|
||||
UploadTracesComponent,
|
||||
AdbProxyComponent,
|
||||
|
||||
@@ -32,8 +32,8 @@ import {FileUtils} from 'common/file_utils';
|
||||
import {PersistentStore} from 'common/persistent_store';
|
||||
import {CrossToolProtocol} from 'cross_tool/cross_tool_protocol';
|
||||
import {TraceDataListener} from 'interfaces/trace_data_listener';
|
||||
import {LoadedTrace} from 'trace/loaded_trace';
|
||||
import {Timestamp} from 'trace/timestamp';
|
||||
import {Trace} from 'trace/trace';
|
||||
import {TraceType} from 'trace/trace_type';
|
||||
import {proxyClient, ProxyState} from 'trace_collection/proxy_client';
|
||||
import {ViewerInputMethodComponent} from 'viewers/components/viewer_input_method_component';
|
||||
@@ -43,6 +43,7 @@ import {ViewerScreenRecordingComponent} from 'viewers/viewer_screen_recording/vi
|
||||
import {ViewerSurfaceFlingerComponent} from 'viewers/viewer_surface_flinger/viewer_surface_flinger_component';
|
||||
import {ViewerTransactionsComponent} from 'viewers/viewer_transactions/viewer_transactions_component';
|
||||
import {ViewerTransitionsComponent} from 'viewers/viewer_transitions/viewer_transitions_component';
|
||||
import {ViewerViewCaptureComponent} from 'viewers/viewer_view_capture/viewer_view_capture_component';
|
||||
import {ViewerWindowManagerComponent} from 'viewers/viewer_window_manager/viewer_window_manager_component';
|
||||
import {CollectTracesComponent} from './collect_traces_component';
|
||||
import {SnackBarOpener} from './snack_bar_opener';
|
||||
@@ -61,7 +62,7 @@ import {UploadTracesComponent} from './upload_traces_component';
|
||||
|
||||
<div class="spacer">
|
||||
<mat-icon
|
||||
*ngIf="activeTrace"
|
||||
*ngIf="dataLoaded && activeTrace"
|
||||
class="icon"
|
||||
[matTooltip]="TRACE_INFO[activeTrace.type].name"
|
||||
[style]="{color: TRACE_INFO[activeTrace.type].color, marginRight: '0.5rem'}">
|
||||
@@ -213,7 +214,7 @@ export class AppComponent implements TraceDataListener {
|
||||
isDarkModeOn!: boolean;
|
||||
dataLoaded = false;
|
||||
activeView?: View;
|
||||
activeTrace?: LoadedTrace;
|
||||
activeTrace?: Trace<object>;
|
||||
activeTraceFileInfo = '';
|
||||
collapsedTimelineHeight = 0;
|
||||
@ViewChild(UploadTracesComponent) uploadTracesComponent?: UploadTracesComponent;
|
||||
@@ -284,6 +285,12 @@ export class AppComponent implements TraceDataListener {
|
||||
createCustomElement(ViewerTransitionsComponent, {injector})
|
||||
);
|
||||
}
|
||||
if (!customElements.get('viewer-view-capture')) {
|
||||
customElements.define(
|
||||
'viewer-view-capture',
|
||||
createCustomElement(ViewerViewCaptureComponent, {injector})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
@@ -302,7 +309,7 @@ export class AppComponent implements TraceDataListener {
|
||||
}
|
||||
|
||||
getLoadedTraceTypes(): TraceType[] {
|
||||
return this.tracePipeline.getLoadedTraces().map((trace) => trace.type);
|
||||
return this.tracePipeline.getTraces().mapTrace((trace) => trace.type);
|
||||
}
|
||||
|
||||
onTraceDataLoaded(viewers: Viewer[]) {
|
||||
@@ -338,11 +345,11 @@ export class AppComponent implements TraceDataListener {
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
onActiveViewChanged(view: View) {
|
||||
async onActiveViewChanged(view: View) {
|
||||
this.activeView = view;
|
||||
this.activeTrace = this.getActiveTrace(view);
|
||||
this.activeTraceFileInfo = this.makeActiveTraceFileInfo(view);
|
||||
this.timelineData.setActiveViewTraceTypes(view.dependencies);
|
||||
await this.mediator.onWinscopeActiveViewChanged(view);
|
||||
}
|
||||
|
||||
goToLink(url: string) {
|
||||
@@ -356,13 +363,17 @@ export class AppComponent implements TraceDataListener {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `${trace.descriptors.join(', ')}`;
|
||||
return `${trace.getDescriptors().join(', ')}`;
|
||||
}
|
||||
|
||||
private getActiveTrace(view: View): LoadedTrace | undefined {
|
||||
return this.tracePipeline
|
||||
.getLoadedTraces()
|
||||
.find((trace) => trace.type === view.dependencies[0]);
|
||||
private getActiveTrace(view: View): Trace<object> | undefined {
|
||||
let activeTrace: Trace<object> | undefined;
|
||||
this.tracePipeline.getTraces().forEachTrace((trace) => {
|
||||
if (trace.type === view.dependencies[0]) {
|
||||
activeTrace = trace;
|
||||
}
|
||||
});
|
||||
return activeTrace;
|
||||
}
|
||||
|
||||
private async makeTraceFilesForDownload(): Promise<File[]> {
|
||||
|
||||
@@ -37,7 +37,7 @@ import {SingleTimelineComponent} from './single_timeline_component';
|
||||
template: `
|
||||
<div id="expanded-timeline-wrapper" #expandedTimelineWrapper>
|
||||
<div
|
||||
*ngFor="let trace of getTraces(); trackBy: trackTraceBySelectedTimestamp"
|
||||
*ngFor="let trace of this.timelineData.getTraces(); trackBy: trackTraceBySelectedTimestamp"
|
||||
class="timeline">
|
||||
<div class="icon-wrapper">
|
||||
<mat-icon
|
||||
@@ -148,14 +148,6 @@ export class ExpandedTimelineComponent {
|
||||
|
||||
TRACE_INFO = TRACE_INFO;
|
||||
|
||||
getTraces(): Array<Trace<{}>> {
|
||||
const traces = new Array<Trace<{}>>();
|
||||
this.timelineData.getTraces().forEachTrace((trace) => {
|
||||
traces.push(trace);
|
||||
});
|
||||
return traces;
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(event: Event) {
|
||||
this.resizeCanvases();
|
||||
|
||||
@@ -107,9 +107,11 @@ export class MiniTimelineComponent {
|
||||
|
||||
private getTracesToShow(): Traces {
|
||||
const traces = new Traces();
|
||||
this.selectedTraces.forEach((type) => {
|
||||
traces.setTrace(type, assertDefined(this.timelineData.getTraces().getTrace(type)));
|
||||
});
|
||||
this.selectedTraces
|
||||
.filter((type) => this.timelineData.getTraces().getTrace(type) !== undefined)
|
||||
.forEach((type) => {
|
||||
traces.setTrace(type, assertDefined(this.timelineData.getTraces().getTrace(type)));
|
||||
});
|
||||
return traces;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,14 @@ import {FormControl, FormGroup, Validators} from '@angular/forms';
|
||||
import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
|
||||
import {TimelineData} from 'app/timeline_data';
|
||||
import {TRACE_INFO} from 'app/trace_info';
|
||||
import {assertDefined} from 'common/assert_utils';
|
||||
import {FunctionUtils} from 'common/function_utils';
|
||||
import {StringUtils} from 'common/string_utils';
|
||||
import {TimeUtils} from 'common/time_utils';
|
||||
import {
|
||||
OnTracePositionUpdate,
|
||||
TracePositionUpdateEmitter,
|
||||
} from 'interfaces/trace_position_update_emitter';
|
||||
import {TracePositionUpdateListener} from 'interfaces/trace_position_update_listener';
|
||||
import {ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType} from 'trace/timestamp';
|
||||
import {TracePosition} from 'trace/trace_position';
|
||||
@@ -287,7 +293,7 @@ import {MiniTimelineComponent} from './mini_timeline_component';
|
||||
`,
|
||||
],
|
||||
})
|
||||
export class TimelineComponent implements TracePositionUpdateListener {
|
||||
export class TimelineComponent implements TracePositionUpdateEmitter, TracePositionUpdateListener {
|
||||
readonly TOGGLE_BUTTON_CLASS: string = 'button-toggle-expansion';
|
||||
readonly MAX_SELECTED_TRACES = 3;
|
||||
|
||||
@@ -358,6 +364,8 @@ export class TimelineComponent implements TracePositionUpdateListener {
|
||||
|
||||
TRACE_INFO = TRACE_INFO;
|
||||
|
||||
private onTracePositionUpdateCallback: OnTracePositionUpdate = FunctionUtils.DO_NOTHING_ASYNC;
|
||||
|
||||
constructor(
|
||||
@Inject(DomSanitizer) private sanitizer: DomSanitizer,
|
||||
@Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef
|
||||
@@ -381,17 +389,21 @@ export class TimelineComponent implements TracePositionUpdateListener {
|
||||
this.collapsedTimelineSizeChanged.emit(height);
|
||||
}
|
||||
|
||||
setOnTracePositionUpdate(callback: OnTracePositionUpdate) {
|
||||
this.onTracePositionUpdateCallback = callback;
|
||||
}
|
||||
|
||||
getVideoCurrentTime() {
|
||||
return this.timelineData.searchCorrespondingScreenRecordingTimeSeconds(
|
||||
this.getCurrentTracePosition()
|
||||
);
|
||||
}
|
||||
|
||||
private seekTimestamp: Timestamp | undefined;
|
||||
private seekTracePosition?: TracePosition;
|
||||
|
||||
getCurrentTracePosition(): TracePosition {
|
||||
if (this.seekTimestamp !== undefined) {
|
||||
return TracePosition.fromTimestamp(this.seekTimestamp);
|
||||
if (this.seekTracePosition) {
|
||||
return this.seekTracePosition;
|
||||
}
|
||||
|
||||
const position = this.timelineData.getCurrentPosition();
|
||||
@@ -411,8 +423,9 @@ export class TimelineComponent implements TracePositionUpdateListener {
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
updatePosition(position: TracePosition) {
|
||||
async updatePosition(position: TracePosition) {
|
||||
this.timelineData.setPosition(position);
|
||||
await this.onTracePositionUpdateCallback(position);
|
||||
}
|
||||
|
||||
usingRealtime(): boolean {
|
||||
@@ -420,7 +433,11 @@ export class TimelineComponent implements TracePositionUpdateListener {
|
||||
}
|
||||
|
||||
updateSeekTimestamp(timestamp: Timestamp | undefined) {
|
||||
this.seekTimestamp = timestamp;
|
||||
if (timestamp) {
|
||||
this.seekTracePosition = TracePosition.fromTimestamp(timestamp);
|
||||
} else {
|
||||
this.seekTracePosition = undefined;
|
||||
}
|
||||
this.updateTimeInputValuesToCurrentTimestamp();
|
||||
}
|
||||
|
||||
@@ -464,11 +481,11 @@ export class TimelineComponent implements TracePositionUpdateListener {
|
||||
}
|
||||
|
||||
@HostListener('document:keydown', ['$event'])
|
||||
handleKeyboardEvent(event: KeyboardEvent) {
|
||||
async handleKeyboardEvent(event: KeyboardEvent) {
|
||||
if (event.key === 'ArrowLeft') {
|
||||
this.moveToPreviousEntry();
|
||||
await this.moveToPreviousEntry();
|
||||
} else if (event.key === 'ArrowRight') {
|
||||
this.moveToNextEntry();
|
||||
await this.moveToNextEntry();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,6 +493,9 @@ export class TimelineComponent implements TracePositionUpdateListener {
|
||||
if (!this.internalActiveTrace) {
|
||||
return false;
|
||||
}
|
||||
if (this.timelineData.getTraces().getTrace(this.internalActiveTrace) === undefined) {
|
||||
return false;
|
||||
}
|
||||
return this.timelineData.getPreviousEntryFor(this.internalActiveTrace) !== undefined;
|
||||
}
|
||||
|
||||
@@ -483,45 +503,50 @@ export class TimelineComponent implements TracePositionUpdateListener {
|
||||
if (!this.internalActiveTrace) {
|
||||
return false;
|
||||
}
|
||||
if (this.timelineData.getTraces().getTrace(this.internalActiveTrace) === undefined) {
|
||||
return false;
|
||||
}
|
||||
return this.timelineData.getNextEntryFor(this.internalActiveTrace) !== undefined;
|
||||
}
|
||||
|
||||
moveToPreviousEntry() {
|
||||
async moveToPreviousEntry() {
|
||||
if (!this.internalActiveTrace) {
|
||||
return;
|
||||
}
|
||||
this.timelineData.moveToPreviousEntryFor(this.internalActiveTrace);
|
||||
await this.onTracePositionUpdateCallback(assertDefined(this.timelineData.getCurrentPosition()));
|
||||
}
|
||||
|
||||
moveToNextEntry() {
|
||||
async moveToNextEntry() {
|
||||
if (!this.internalActiveTrace) {
|
||||
return;
|
||||
}
|
||||
this.timelineData.moveToNextEntryFor(this.internalActiveTrace);
|
||||
await this.onTracePositionUpdateCallback(assertDefined(this.timelineData.getCurrentPosition()));
|
||||
}
|
||||
|
||||
humanElapsedTimeInputChange(event: Event) {
|
||||
async humanElapsedTimeInputChange(event: Event) {
|
||||
if (event.type !== 'change') {
|
||||
return;
|
||||
}
|
||||
const target = event.target as HTMLInputElement;
|
||||
const timestamp = TimeUtils.parseHumanElapsed(target.value);
|
||||
this.timelineData.setPosition(TracePosition.fromTimestamp(timestamp));
|
||||
await this.updatePosition(TracePosition.fromTimestamp(timestamp));
|
||||
this.updateTimeInputValuesToCurrentTimestamp();
|
||||
}
|
||||
|
||||
humanRealTimeInputChanged(event: Event) {
|
||||
async humanRealTimeInputChanged(event: Event) {
|
||||
if (event.type !== 'change') {
|
||||
return;
|
||||
}
|
||||
const target = event.target as HTMLInputElement;
|
||||
|
||||
const timestamp = TimeUtils.parseHumanReal(target.value);
|
||||
this.timelineData.setPosition(TracePosition.fromTimestamp(timestamp));
|
||||
await this.updatePosition(TracePosition.fromTimestamp(timestamp));
|
||||
this.updateTimeInputValuesToCurrentTimestamp();
|
||||
}
|
||||
|
||||
nanosecondsInputTimeChange(event: Event) {
|
||||
async nanosecondsInputTimeChange(event: Event) {
|
||||
if (event.type !== 'change') {
|
||||
return;
|
||||
}
|
||||
@@ -531,7 +556,7 @@ export class TimelineComponent implements TracePositionUpdateListener {
|
||||
this.timelineData.getTimestampType()!,
|
||||
StringUtils.parseBigIntStrippingUnit(target.value)
|
||||
);
|
||||
this.timelineData.setPosition(TracePosition.fromTimestamp(timestamp));
|
||||
await this.updatePosition(TracePosition.fromTimestamp(timestamp));
|
||||
this.updateTimeInputValuesToCurrentTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
OnTracePositionUpdate,
|
||||
TracePositionUpdateEmitter,
|
||||
} from 'interfaces/trace_position_update_emitter';
|
||||
import {TracePositionUpdateListener} from 'interfaces/trace_position_update_listener';
|
||||
import {TracePosition} from 'trace/trace_position';
|
||||
|
||||
export class TimelineComponentStub implements TracePositionUpdateListener {
|
||||
export class TimelineComponentStub
|
||||
implements TracePositionUpdateEmitter, TracePositionUpdateListener
|
||||
{
|
||||
setOnTracePositionUpdate(callback: OnTracePositionUpdate) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
onTracePositionUpdate(position: TracePosition) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
import {TRACE_INFO} from 'app/trace_info';
|
||||
import {TracePipeline} from 'app/trace_pipeline';
|
||||
import {ProgressListener} from 'interfaces/progress_listener';
|
||||
import {LoadedTrace} from 'trace/loaded_trace';
|
||||
import {Trace} from 'trace/trace';
|
||||
import {LoadProgressComponent} from './load_progress_component';
|
||||
|
||||
@Component({
|
||||
@@ -57,15 +57,15 @@ import {LoadProgressComponent} from './load_progress_component';
|
||||
</load-progress>
|
||||
|
||||
<mat-list
|
||||
*ngIf="!isLoadingFiles && this.tracePipeline.getLoadedTraces().length > 0"
|
||||
*ngIf="!isLoadingFiles && this.tracePipeline.getTraces().getSize() > 0"
|
||||
class="uploaded-files">
|
||||
<mat-list-item *ngFor="let trace of this.tracePipeline.getLoadedTraces()">
|
||||
<mat-list-item *ngFor="let trace of this.tracePipeline.getTraces()">
|
||||
<mat-icon matListIcon>
|
||||
{{ TRACE_INFO[trace.type].icon }}
|
||||
</mat-icon>
|
||||
|
||||
<p matLine>{{ TRACE_INFO[trace.type].name }}</p>
|
||||
<p matLine *ngFor="let descriptor of trace.descriptors">{{ descriptor }}</p>
|
||||
<p matLine *ngFor="let descriptor of trace.getDescriptors()">{{ descriptor }}</p>
|
||||
|
||||
<button color="primary" mat-icon-button (click)="onRemoveTrace($event, trace)">
|
||||
<mat-icon>close</mat-icon>
|
||||
@@ -73,9 +73,7 @@ import {LoadProgressComponent} from './load_progress_component';
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
|
||||
<div
|
||||
*ngIf="!isLoadingFiles && tracePipeline.getLoadedTraces().length === 0"
|
||||
class="drop-info">
|
||||
<div *ngIf="!isLoadingFiles && tracePipeline.getTraces().getSize() === 0" class="drop-info">
|
||||
<p class="mat-body-3 icon">
|
||||
<mat-icon inline fontIcon="upload"></mat-icon>
|
||||
</p>
|
||||
@@ -84,7 +82,7 @@ import {LoadProgressComponent} from './load_progress_component';
|
||||
</mat-card-content>
|
||||
|
||||
<div
|
||||
*ngIf="!isLoadingFiles && tracePipeline.getLoadedTraces().length > 0"
|
||||
*ngIf="!isLoadingFiles && tracePipeline.getTraces().getSize() > 0"
|
||||
class="trace-actions-container">
|
||||
<button
|
||||
color="primary"
|
||||
@@ -238,10 +236,10 @@ export class UploadTracesComponent implements ProgressListener {
|
||||
this.filesUploaded.emit(Array.from(droppedFiles));
|
||||
}
|
||||
|
||||
onRemoveTrace(event: MouseEvent, trace: LoadedTrace) {
|
||||
onRemoveTrace(event: MouseEvent, trace: Trace<object>) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.tracePipeline.removeTraceFile(trace.type);
|
||||
this.tracePipeline.removeTrace(trace);
|
||||
this.onOperationFinished();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,17 +22,19 @@ import {RemoteTimestampReceiver} from 'interfaces/remote_timestamp_receiver';
|
||||
import {RemoteTimestampSender} from 'interfaces/remote_timestamp_sender';
|
||||
import {Runnable} from 'interfaces/runnable';
|
||||
import {TraceDataListener} from 'interfaces/trace_data_listener';
|
||||
import {TracePositionUpdateEmitter} from 'interfaces/trace_position_update_emitter';
|
||||
import {TracePositionUpdateListener} from 'interfaces/trace_position_update_listener';
|
||||
import {UserNotificationListener} from 'interfaces/user_notification_listener';
|
||||
import {Timestamp, TimestampType} from 'trace/timestamp';
|
||||
import {TraceFile} from 'trace/trace_file';
|
||||
import {TracePosition} from 'trace/trace_position';
|
||||
import {TraceType} from 'trace/trace_type';
|
||||
import {Viewer} from 'viewers/viewer';
|
||||
import {View, Viewer} from 'viewers/viewer';
|
||||
import {ViewerFactory} from 'viewers/viewer_factory';
|
||||
import {TimelineData} from './timeline_data';
|
||||
import {TracePipeline} from './trace_pipeline';
|
||||
|
||||
type TimelineComponentInterface = TracePositionUpdateListener & TracePositionUpdateEmitter;
|
||||
type CrossToolProtocolInterface = RemoteBugreportReceiver &
|
||||
RemoteTimestampReceiver &
|
||||
RemoteTimestampSender;
|
||||
@@ -43,7 +45,7 @@ export class Mediator {
|
||||
private crossToolProtocol: CrossToolProtocolInterface;
|
||||
private uploadTracesComponent?: ProgressListener;
|
||||
private collectTracesComponent?: ProgressListener;
|
||||
private timelineComponent?: TracePositionUpdateListener;
|
||||
private timelineComponent?: TimelineComponentInterface;
|
||||
private appComponent: TraceDataListener;
|
||||
private userNotificationListener: UserNotificationListener;
|
||||
private storage: Storage;
|
||||
@@ -73,18 +75,14 @@ export class Mediator {
|
||||
this.userNotificationListener = userNotificationListener;
|
||||
this.storage = storage;
|
||||
|
||||
this.timelineData.setOnTracePositionUpdate((position) => {
|
||||
this.onWinscopeTracePositionUpdate(position);
|
||||
});
|
||||
|
||||
this.crossToolProtocol.setOnBugreportReceived(
|
||||
async (bugreport: File, timestamp?: Timestamp) => {
|
||||
await this.onRemoteBugreportReceived(bugreport, timestamp);
|
||||
}
|
||||
);
|
||||
|
||||
this.crossToolProtocol.setOnTimestampReceived((timestamp: Timestamp) => {
|
||||
this.onRemoteTimestampReceived(timestamp);
|
||||
this.crossToolProtocol.setOnTimestampReceived(async (timestamp: Timestamp) => {
|
||||
await this.onRemoteTimestampReceived(timestamp);
|
||||
});
|
||||
|
||||
this.abtChromeExtensionProtocol.setOnBuganizerAttachmentsDownloadStart(() => {
|
||||
@@ -106,8 +104,11 @@ export class Mediator {
|
||||
this.collectTracesComponent = collectTracesComponent;
|
||||
}
|
||||
|
||||
setTimelineComponent(timelineComponent: TracePositionUpdateListener | undefined) {
|
||||
setTimelineComponent(timelineComponent: TimelineComponentInterface | undefined) {
|
||||
this.timelineComponent = timelineComponent;
|
||||
this.timelineComponent?.setOnTracePositionUpdate(async (position) => {
|
||||
await this.onTimelineTracePositionUpdate(position);
|
||||
});
|
||||
}
|
||||
|
||||
onWinscopeInitialized() {
|
||||
@@ -133,23 +134,46 @@ export class Mediator {
|
||||
await this.processLoadedTraceFiles();
|
||||
}
|
||||
|
||||
onWinscopeTracePositionUpdate(position: TracePosition) {
|
||||
this.executeIgnoringRecursiveTimestampNotifications(() => {
|
||||
this.updateViewersTracePosition(position);
|
||||
async onWinscopeActiveViewChanged(view: View) {
|
||||
this.timelineData.setActiveViewTraceTypes(view.dependencies);
|
||||
await this.propagateTracePosition(this.timelineData.getCurrentPosition());
|
||||
}
|
||||
|
||||
const timestamp = position.timestamp;
|
||||
if (timestamp.getType() !== TimestampType.REAL) {
|
||||
console.warn(
|
||||
'Cannot propagate timestamp change to remote tool.' +
|
||||
` Remote tool expects timestamp type ${TimestampType.REAL},` +
|
||||
` but Winscope wants to notify timestamp type ${timestamp.getType()}.`
|
||||
);
|
||||
} else {
|
||||
this.crossToolProtocol.sendTimestamp(timestamp);
|
||||
}
|
||||
async onTimelineTracePositionUpdate(position: TracePosition) {
|
||||
await this.propagateTracePosition(position);
|
||||
}
|
||||
|
||||
this.timelineComponent?.onTracePositionUpdate(position);
|
||||
private async propagateTracePosition(
|
||||
position?: TracePosition,
|
||||
omitCrossToolProtocol: boolean = false
|
||||
) {
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO (b/289478304): update only visible viewers (1 tab viewer + overlay viewers)
|
||||
const promises = this.viewers.map((viewer) => {
|
||||
return viewer.onTracePositionUpdate(position);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
|
||||
this.timelineComponent?.onTracePositionUpdate(position);
|
||||
|
||||
if (omitCrossToolProtocol) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timestamp = position.timestamp;
|
||||
if (timestamp.getType() !== TimestampType.REAL) {
|
||||
console.warn(
|
||||
'Cannot propagate timestamp change to remote tool.' +
|
||||
` Remote tool expects timestamp type ${TimestampType.REAL},` +
|
||||
` but Winscope wants to notify timestamp type ${timestamp.getType()}.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.crossToolProtocol.sendTimestamp(timestamp);
|
||||
}
|
||||
|
||||
private onBuganizerAttachmentsDownloadStart() {
|
||||
@@ -167,38 +191,30 @@ export class Mediator {
|
||||
this.currentProgressListener = this.uploadTracesComponent;
|
||||
await this.processRemoteFilesReceived([bugreport]);
|
||||
if (timestamp !== undefined) {
|
||||
this.onRemoteTimestampReceived(timestamp);
|
||||
await this.onRemoteTimestampReceived(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
private onRemoteTimestampReceived(timestamp: Timestamp) {
|
||||
this.executeIgnoringRecursiveTimestampNotifications(() => {
|
||||
this.lastRemoteToolTimestampReceived = timestamp;
|
||||
private async onRemoteTimestampReceived(timestamp: Timestamp) {
|
||||
this.lastRemoteToolTimestampReceived = timestamp;
|
||||
|
||||
if (!this.isTraceDataVisualized) {
|
||||
return; // apply timestamp later when traces are visualized
|
||||
}
|
||||
if (!this.isTraceDataVisualized) {
|
||||
return; // apply timestamp later when traces are visualized
|
||||
}
|
||||
|
||||
if (this.timelineData.getTimestampType() !== timestamp.getType()) {
|
||||
console.warn(
|
||||
'Cannot apply new timestamp received from remote tool.' +
|
||||
` Remote tool notified timestamp type ${timestamp.getType()},` +
|
||||
` but Winscope is accepting timestamp type ${this.timelineData.getTimestampType()}.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (this.timelineData.getTimestampType() !== timestamp.getType()) {
|
||||
console.warn(
|
||||
'Cannot apply new timestamp received from remote tool.' +
|
||||
` Remote tool notified timestamp type ${timestamp.getType()},` +
|
||||
` but Winscope is accepting timestamp type ${this.timelineData.getTimestampType()}.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this.timelineData.getCurrentPosition()?.timestamp.getValueNs() === timestamp.getValueNs()
|
||||
) {
|
||||
return; // no timestamp change
|
||||
}
|
||||
const position = TracePosition.fromTimestamp(timestamp);
|
||||
this.timelineData.setPosition(position);
|
||||
|
||||
const position = TracePosition.fromTimestamp(timestamp);
|
||||
this.updateViewersTracePosition(position);
|
||||
this.timelineData.setPosition(position);
|
||||
this.timelineComponent?.onTracePositionUpdate(position); //TODO: is this redundant?
|
||||
});
|
||||
await this.propagateTracePosition(this.timelineData.getCurrentPosition(), true);
|
||||
}
|
||||
|
||||
private async processRemoteFilesReceived(files: File[]) {
|
||||
@@ -234,23 +250,23 @@ export class Mediator {
|
||||
// allow the UI to update before making the main thread very busy
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
this.tracePipeline.buildTraces();
|
||||
await this.tracePipeline.buildTraces();
|
||||
this.currentProgressListener?.onOperationFinished();
|
||||
|
||||
this.timelineData.initialize(
|
||||
this.tracePipeline.getTraces(),
|
||||
this.tracePipeline.getScreenRecordingVideo()
|
||||
await this.tracePipeline.getScreenRecordingVideo()
|
||||
);
|
||||
this.createViewers();
|
||||
await this.createViewers();
|
||||
this.appComponent.onTraceDataLoaded(this.viewers);
|
||||
this.isTraceDataVisualized = true;
|
||||
|
||||
if (this.lastRemoteToolTimestampReceived !== undefined) {
|
||||
this.onRemoteTimestampReceived(this.lastRemoteToolTimestampReceived);
|
||||
await this.onRemoteTimestampReceived(this.lastRemoteToolTimestampReceived);
|
||||
}
|
||||
}
|
||||
|
||||
private createViewers() {
|
||||
private async createViewers() {
|
||||
const traces = this.tracePipeline.getTraces();
|
||||
const traceTypes = new Set<TraceType>();
|
||||
traces.forEachTrace((trace) => {
|
||||
@@ -258,26 +274,17 @@ export class Mediator {
|
||||
});
|
||||
this.viewers = new ViewerFactory().createViewers(traceTypes, traces, this.storage);
|
||||
|
||||
// Update the viewers as soon as they are created
|
||||
const position = this.timelineData.getCurrentPosition();
|
||||
if (position) {
|
||||
this.onWinscopeTracePositionUpdate(position);
|
||||
}
|
||||
// Set position as soon as the viewers are created
|
||||
await this.propagateTracePosition(this.timelineData.getCurrentPosition(), true);
|
||||
}
|
||||
|
||||
private updateViewersTracePosition(position: TracePosition) {
|
||||
this.viewers.forEach((viewer) => {
|
||||
viewer.onTracePositionUpdate(position);
|
||||
});
|
||||
}
|
||||
|
||||
private executeIgnoringRecursiveTimestampNotifications(op: () => void) {
|
||||
private async executeIgnoringRecursiveTimestampNotifications(op: () => Promise<void>) {
|
||||
if (this.isChangingCurrentTimestamp) {
|
||||
return;
|
||||
}
|
||||
this.isChangingCurrentTimestamp = true;
|
||||
try {
|
||||
op();
|
||||
await op();
|
||||
} finally {
|
||||
this.isChangingCurrentTimestamp = false;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ describe('Mediator', () => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
timelineComponent = new TimelineComponentStub();
|
||||
tracePipeline = new TracePipeline();
|
||||
timelineData = new TimelineData();
|
||||
abtChromeExtensionProtocol = new AbtChromeExtensionProtocolStub();
|
||||
@@ -94,6 +93,8 @@ describe('Mediator', () => {
|
||||
spyOn(timelineData, 'initialize').and.callThrough(),
|
||||
spyOn(appComponent, 'onTraceDataLoaded'),
|
||||
spyOn(viewerStub, 'onTracePositionUpdate'),
|
||||
spyOn(timelineComponent, 'onTracePositionUpdate'),
|
||||
spyOn(crossToolProtocol, 'sendTimestamp'),
|
||||
];
|
||||
|
||||
await mediator.onWinscopeFilesUploaded(inputFiles);
|
||||
@@ -113,8 +114,11 @@ describe('Mediator', () => {
|
||||
expect(uploadTracesComponent.onOperationFinished).toHaveBeenCalled();
|
||||
expect(timelineData.initialize).toHaveBeenCalledTimes(1);
|
||||
expect(appComponent.onTraceDataLoaded).toHaveBeenCalledOnceWith([viewerStub]);
|
||||
// notifies viewer about current timestamp on creation
|
||||
|
||||
// propagates trace position on viewers creation
|
||||
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('handles collected traces from Winscope', async () => {
|
||||
@@ -124,6 +128,8 @@ describe('Mediator', () => {
|
||||
spyOn(timelineData, 'initialize').and.callThrough(),
|
||||
spyOn(appComponent, 'onTraceDataLoaded'),
|
||||
spyOn(viewerStub, 'onTracePositionUpdate'),
|
||||
spyOn(timelineComponent, 'onTracePositionUpdate'),
|
||||
spyOn(crossToolProtocol, 'sendTimestamp'),
|
||||
];
|
||||
|
||||
await mediator.onWinscopeFilesCollected(inputFiles);
|
||||
@@ -132,8 +138,11 @@ describe('Mediator', () => {
|
||||
expect(collectTracesComponent.onOperationFinished).toHaveBeenCalled();
|
||||
expect(timelineData.initialize).toHaveBeenCalledTimes(1);
|
||||
expect(appComponent.onTraceDataLoaded).toHaveBeenCalledOnceWith([viewerStub]);
|
||||
// notifies viewer about current timestamp on creation
|
||||
|
||||
// propagates trace position on viewers creation
|
||||
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
//TODO: test "bugreport data from cross-tool protocol" when FileUtils is fully compatible with
|
||||
@@ -160,7 +169,7 @@ describe('Mediator', () => {
|
||||
expect(uploadTracesComponent.onOperationFinished).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('propagates trace position update from timeline data', async () => {
|
||||
it('propagates trace position update from timeline component', async () => {
|
||||
await loadTraceFiles();
|
||||
await mediator.onWinscopeViewTracesRequest();
|
||||
|
||||
@@ -171,20 +180,14 @@ describe('Mediator', () => {
|
||||
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
|
||||
|
||||
// notify timestamp
|
||||
timelineData.setPosition(POSITION_10);
|
||||
// notify position
|
||||
await mediator.onTimelineTracePositionUpdate(POSITION_10);
|
||||
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(1);
|
||||
|
||||
// notify same timestamp again (ignored, no timestamp change)
|
||||
timelineData.setPosition(POSITION_10);
|
||||
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(1);
|
||||
|
||||
// notify another timestamp
|
||||
timelineData.setPosition(POSITION_11);
|
||||
// notify position
|
||||
await mediator.onTimelineTracePositionUpdate(POSITION_11);
|
||||
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(2);
|
||||
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(2);
|
||||
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(2);
|
||||
@@ -205,12 +208,7 @@ describe('Mediator', () => {
|
||||
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
|
||||
// receive same timestamp again (ignored, no timestamp change)
|
||||
await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
|
||||
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
|
||||
// receive another
|
||||
// receive timestamp
|
||||
await crossToolProtocol.onTimestampReceived(TIMESTAMP_11);
|
||||
expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(2);
|
||||
expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(2);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {FunctionUtils} from 'common/function_utils';
|
||||
import {assertDefined} from 'common/assert_utils';
|
||||
import {TimeUtils} from 'common/time_utils';
|
||||
import {ScreenRecordingUtils} from 'trace/screen_recording_utils';
|
||||
import {Timestamp, TimestampType} from 'trace/timestamp';
|
||||
@@ -23,13 +23,12 @@ import {Traces} from 'trace/traces';
|
||||
import {TraceEntryFinder} from 'trace/trace_entry_finder';
|
||||
import {TracePosition} from 'trace/trace_position';
|
||||
import {TraceType} from 'trace/trace_type';
|
||||
import {assertDefined} from '../common/assert_utils';
|
||||
|
||||
export type TracePositionCallbackType = (position: TracePosition) => void;
|
||||
export interface TimeRange {
|
||||
from: Timestamp;
|
||||
to: Timestamp;
|
||||
}
|
||||
const INVALID_TIMESTAMP = 0n
|
||||
|
||||
export class TimelineData {
|
||||
private traces = new Traces();
|
||||
@@ -40,25 +39,29 @@ export class TimelineData {
|
||||
private explicitlySetPosition?: TracePosition;
|
||||
private explicitlySetSelection?: TimeRange;
|
||||
private activeViewTraceTypes: TraceType[] = []; // dependencies of current active view
|
||||
private onTracePositionUpdate: TracePositionCallbackType = FunctionUtils.DO_NOTHING;
|
||||
|
||||
initialize(traces: Traces, screenRecordingVideo: Blob | undefined) {
|
||||
this.clear();
|
||||
|
||||
this.traces = traces;
|
||||
this.traces = new Traces();
|
||||
traces.forEachTrace((trace, type) => {
|
||||
if (type === TraceType.WINDOW_MANAGER) {
|
||||
// Filter out WindowManager dumps with no timestamp from timeline
|
||||
if (
|
||||
trace.lengthEntries === 1 &&
|
||||
trace.getEntry(0).getTimestamp().getValueNs() === INVALID_TIMESTAMP
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.traces.setTrace(type, trace);
|
||||
});
|
||||
|
||||
this.screenRecordingVideo = screenRecordingVideo;
|
||||
this.firstEntry = this.findFirstEntry();
|
||||
this.lastEntry = this.findLastEntry();
|
||||
this.timestampType = this.firstEntry?.getTimestamp().getType();
|
||||
|
||||
const position = this.getCurrentPosition();
|
||||
if (position) {
|
||||
this.onTracePositionUpdate(position);
|
||||
}
|
||||
}
|
||||
|
||||
setOnTracePositionUpdate(callback: TracePositionCallbackType) {
|
||||
this.onTracePositionUpdate = callback;
|
||||
}
|
||||
|
||||
getCurrentPosition(): TracePosition | undefined {
|
||||
@@ -90,15 +93,11 @@ export class TimelineData {
|
||||
}
|
||||
}
|
||||
|
||||
this.applyOperationAndNotifyIfCurrentPositionChanged(() => {
|
||||
this.explicitlySetPosition = position;
|
||||
});
|
||||
this.explicitlySetPosition = position;
|
||||
}
|
||||
|
||||
setActiveViewTraceTypes(types: TraceType[]) {
|
||||
this.applyOperationAndNotifyIfCurrentPositionChanged(() => {
|
||||
this.activeViewTraceTypes = types;
|
||||
});
|
||||
this.activeViewTraceTypes = types;
|
||||
}
|
||||
|
||||
getTimestampType(): TimestampType | undefined {
|
||||
@@ -219,16 +218,14 @@ export class TimelineData {
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.applyOperationAndNotifyIfCurrentPositionChanged(() => {
|
||||
this.traces = new Traces();
|
||||
this.firstEntry = undefined;
|
||||
this.lastEntry = undefined;
|
||||
this.explicitlySetPosition = undefined;
|
||||
this.timestampType = undefined;
|
||||
this.explicitlySetSelection = undefined;
|
||||
this.screenRecordingVideo = undefined;
|
||||
this.activeViewTraceTypes = [];
|
||||
});
|
||||
this.traces = new Traces();
|
||||
this.firstEntry = undefined;
|
||||
this.lastEntry = undefined;
|
||||
this.explicitlySetPosition = undefined;
|
||||
this.timestampType = undefined;
|
||||
this.explicitlySetSelection = undefined;
|
||||
this.screenRecordingVideo = undefined;
|
||||
this.activeViewTraceTypes = [];
|
||||
}
|
||||
|
||||
private findFirstEntry(): TraceEntry<{}> | undefined {
|
||||
@@ -265,6 +262,7 @@ export class TimelineData {
|
||||
|
||||
private getFirstEntryOfActiveViewTraces(): TraceEntry<{}> | undefined {
|
||||
const activeEntries = this.activeViewTraceTypes
|
||||
.filter((it) => this.traces.getTrace(it) !== undefined)
|
||||
.map((traceType) => assertDefined(this.traces.getTrace(traceType)))
|
||||
.filter((trace) => trace.lengthEntries > 0)
|
||||
.map((trace) => trace.getEntry(0))
|
||||
@@ -276,13 +274,4 @@ export class TimelineData {
|
||||
}
|
||||
return activeEntries[0];
|
||||
}
|
||||
|
||||
private applyOperationAndNotifyIfCurrentPositionChanged(op: () => void) {
|
||||
const prevPosition = this.getCurrentPosition();
|
||||
op();
|
||||
const currentPosition = this.getCurrentPosition();
|
||||
if (currentPosition && (!prevPosition || !currentPosition.isEqual(prevPosition))) {
|
||||
this.onTracePositionUpdate(currentPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,16 +20,8 @@ import {TracePosition} from 'trace/trace_position';
|
||||
import {TraceType} from 'trace/trace_type';
|
||||
import {TimelineData} from './timeline_data';
|
||||
|
||||
class TracePositionUpdateListener {
|
||||
onTracePositionUpdate(position: TracePosition) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
describe('TimelineData', () => {
|
||||
let timelineData: TimelineData;
|
||||
const positionUpdateListener = new TracePositionUpdateListener();
|
||||
|
||||
const timestamp10 = new Timestamp(TimestampType.REAL, 10n);
|
||||
const timestamp11 = new Timestamp(TimestampType.REAL, 11n);
|
||||
|
||||
@@ -47,9 +39,6 @@ describe('TimelineData', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
timelineData = new TimelineData();
|
||||
timelineData.setOnTracePositionUpdate((position) => {
|
||||
positionUpdateListener.onTracePositionUpdate(position);
|
||||
});
|
||||
});
|
||||
|
||||
it('can be initialized', () => {
|
||||
@@ -59,6 +48,20 @@ describe('TimelineData', () => {
|
||||
expect(timelineData.getCurrentPosition()).toBeDefined();
|
||||
});
|
||||
|
||||
it('ignores dumps with no timestamp', () => {
|
||||
expect(timelineData.getCurrentPosition()).toBeUndefined();
|
||||
|
||||
const traces = new TracesBuilder()
|
||||
.setTimestamps(TraceType.SURFACE_FLINGER, [timestamp10, timestamp11])
|
||||
.setTimestamps(TraceType.WINDOW_MANAGER, [new Timestamp(TimestampType.REAL, 0n)])
|
||||
.build();
|
||||
|
||||
timelineData.initialize(traces, undefined);
|
||||
expect(timelineData.getTraces().getTrace(TraceType.WINDOW_MANAGER)).toBeUndefined();
|
||||
expect(timelineData.getFullTimeRange().from).toBe(timestamp10);
|
||||
expect(timelineData.getFullTimeRange().to).toBe(timestamp11);
|
||||
});
|
||||
|
||||
it('uses first entry by default', () => {
|
||||
timelineData.initialize(traces, undefined);
|
||||
expect(timelineData.getCurrentPosition()).toEqual(position10);
|
||||
@@ -95,30 +98,6 @@ describe('TimelineData', () => {
|
||||
expect(timelineData.getCurrentPosition()).toEqual(position10);
|
||||
});
|
||||
|
||||
it('executes callback on position update', () => {
|
||||
spyOn(positionUpdateListener, 'onTracePositionUpdate');
|
||||
expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(0);
|
||||
|
||||
timelineData.initialize(traces, undefined);
|
||||
expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(1);
|
||||
|
||||
timelineData.setActiveViewTraceTypes([TraceType.WINDOW_MANAGER]);
|
||||
expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("doesn't execute callback when position doesn't change", () => {
|
||||
timelineData.initialize(traces, undefined);
|
||||
|
||||
spyOn(positionUpdateListener, 'onTracePositionUpdate');
|
||||
expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(0);
|
||||
|
||||
timelineData.setActiveViewTraceTypes([TraceType.SURFACE_FLINGER]);
|
||||
expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(0);
|
||||
|
||||
timelineData.setActiveViewTraceTypes([TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER]);
|
||||
expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('hasTimestamps()', () => {
|
||||
expect(timelineData.hasTimestamps()).toBeFalse();
|
||||
|
||||
|
||||
@@ -108,6 +108,13 @@ export const TRACE_INFO: TraceInfoMap = {
|
||||
color: '#137333',
|
||||
downloadArchiveDir: 'launcher',
|
||||
},
|
||||
// TODO: Choose ViewCapture icon, color, title name, and download archive directory
|
||||
[TraceType.VIEW_CAPTURE]: {
|
||||
name: 'View Capture',
|
||||
icon: LAUNCHER_ICON,
|
||||
color: '#137333',
|
||||
downloadArchiveDir: 'launcher',
|
||||
},
|
||||
[TraceType.INPUT_METHOD_CLIENTS]: {
|
||||
name: 'IME Clients',
|
||||
icon: IME_ICON,
|
||||
|
||||
@@ -16,10 +16,8 @@
|
||||
|
||||
import {FunctionUtils, OnProgressUpdateType} from 'common/function_utils';
|
||||
import {ParserError, ParserFactory} from 'parsers/parser_factory';
|
||||
import {TracesParserCujs} from 'parsers/traces_parser_cujs';
|
||||
import {TracesParserTransitions} from 'parsers/traces_parser_transitions';
|
||||
import {TracesParserFactory} from 'parsers/traces_parser_factory';
|
||||
import {FrameMapper} from 'trace/frame_mapper';
|
||||
import {LoadedTrace} from 'trace/loaded_trace';
|
||||
import {Parser} from 'trace/parser';
|
||||
import {TimestampType} from 'trace/timestamp';
|
||||
import {Trace} from 'trace/trace';
|
||||
@@ -29,9 +27,10 @@ import {TraceType} from 'trace/trace_type';
|
||||
|
||||
class TracePipeline {
|
||||
private parserFactory = new ParserFactory();
|
||||
private tracesParserFactory = new TracesParserFactory();
|
||||
private parsers: Array<Parser<object>> = [];
|
||||
private files = new Map<TraceType, TraceFile>();
|
||||
private traces?: Traces;
|
||||
private traces = new Traces();
|
||||
private commonTimestampType?: TimestampType;
|
||||
|
||||
async loadTraceFiles(
|
||||
@@ -39,81 +38,69 @@ class TracePipeline {
|
||||
onLoadProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING
|
||||
): Promise<ParserError[]> {
|
||||
traceFiles = await this.filterBugreportFilesIfNeeded(traceFiles);
|
||||
const [parsers, parserErrors] = await this.parserFactory.createParsers(
|
||||
const [fileAndParsers, parserErrors] = await this.parserFactory.createParsers(
|
||||
traceFiles,
|
||||
onLoadProgressUpdate
|
||||
);
|
||||
this.parsers = parsers.map((it) => it.parser);
|
||||
|
||||
const tracesParsers = [
|
||||
new TracesParserTransitions(this.parsers),
|
||||
new TracesParserCujs(this.parsers),
|
||||
];
|
||||
for (const tracesParser of tracesParsers) {
|
||||
if (tracesParser.canProvideEntries()) {
|
||||
this.parsers.push(tracesParser);
|
||||
}
|
||||
for (const fileAndParser of fileAndParsers) {
|
||||
this.files.set(fileAndParser.parser.getTraceType(), fileAndParser.file);
|
||||
}
|
||||
|
||||
for (const parser of parsers) {
|
||||
this.files.set(parser.parser.getTraceType(), parser.file);
|
||||
}
|
||||
const newParsers = fileAndParsers.map((it) => it.parser);
|
||||
this.parsers = this.parsers.concat(newParsers);
|
||||
|
||||
if (this.parsers.some((it) => it.getTraceType() === TraceType.TRANSITION)) {
|
||||
this.parsers = this.parsers.filter(
|
||||
(it) =>
|
||||
it.getTraceType() !== TraceType.WM_TRANSITION &&
|
||||
it.getTraceType() !== TraceType.SHELL_TRANSITION
|
||||
);
|
||||
const tracesParsers = await this.tracesParserFactory.createParsers(this.parsers);
|
||||
|
||||
const allParsers = this.parsers.concat(tracesParsers);
|
||||
|
||||
this.traces = new Traces();
|
||||
allParsers.forEach((parser) => {
|
||||
const trace = Trace.newUninitializedTrace(parser);
|
||||
this.traces?.setTrace(parser.getTraceType(), trace);
|
||||
});
|
||||
|
||||
const hasTransitionTrace = this.traces
|
||||
.mapTrace((trace) => trace.type)
|
||||
.some((type) => type === TraceType.TRANSITION);
|
||||
if (hasTransitionTrace) {
|
||||
this.traces.deleteTrace(TraceType.WM_TRANSITION);
|
||||
this.traces.deleteTrace(TraceType.SHELL_TRANSITION);
|
||||
}
|
||||
|
||||
return parserErrors;
|
||||
}
|
||||
|
||||
removeTraceFile(type: TraceType) {
|
||||
this.parsers = this.parsers.filter((parser) => parser.getTraceType() !== type);
|
||||
removeTrace(trace: Trace<object>) {
|
||||
this.parsers = this.parsers.filter((parser) => parser.getTraceType() !== trace.type);
|
||||
this.traces.deleteTrace(trace.type);
|
||||
}
|
||||
|
||||
getLoadedFiles(): Map<TraceType, TraceFile> {
|
||||
return this.files;
|
||||
}
|
||||
|
||||
getLoadedTraces(): LoadedTrace[] {
|
||||
return this.parsers.map(
|
||||
(parser: Parser<object>) => new LoadedTrace(parser.getDescriptors(), parser.getTraceType())
|
||||
);
|
||||
}
|
||||
|
||||
buildTraces() {
|
||||
async buildTraces() {
|
||||
const commonTimestampType = this.getCommonTimestampType();
|
||||
|
||||
this.traces = new Traces();
|
||||
this.parsers.forEach((parser) => {
|
||||
const trace = Trace.newUninitializedTrace(parser);
|
||||
trace.init(commonTimestampType);
|
||||
this.traces?.setTrace(parser.getTraceType(), trace);
|
||||
});
|
||||
|
||||
new FrameMapper(this.traces).computeMapping();
|
||||
this.traces.forEachTrace((trace) => trace.init(commonTimestampType));
|
||||
await new FrameMapper(this.traces).computeMapping();
|
||||
}
|
||||
|
||||
getTraces(): Traces {
|
||||
this.checkTracesWereBuilt();
|
||||
return this.traces!;
|
||||
return this.traces;
|
||||
}
|
||||
|
||||
getScreenRecordingVideo(): undefined | Blob {
|
||||
async getScreenRecordingVideo(): Promise<undefined | Blob> {
|
||||
const screenRecording = this.getTraces().getTrace(TraceType.SCREEN_RECORDING);
|
||||
if (!screenRecording || screenRecording.lengthEntries === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return screenRecording.getEntry(0).getValue().videoData;
|
||||
return (await screenRecording.getEntry(0).getValue()).videoData;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.parserFactory = new ParserFactory();
|
||||
this.parsers = [];
|
||||
this.traces = undefined;
|
||||
this.traces = new Traces();
|
||||
this.commonTimestampType = undefined;
|
||||
this.files = new Map<TraceType, TraceFile>();
|
||||
}
|
||||
@@ -130,9 +117,13 @@ class TracePipeline {
|
||||
return files;
|
||||
}
|
||||
|
||||
const BUGREPORT_TRACE_DIRS = ['FS/data/misc/wmtrace/', 'FS/data/misc/perfetto-traces/'];
|
||||
const isFileWithinBugreportTraceDir = (file: TraceFile) => {
|
||||
for (const traceDir of BUGREPORT_TRACE_DIRS) {
|
||||
const BUGREPORT_FILES_ALLOWLIST = [
|
||||
'FS/data/misc/wmtrace/',
|
||||
'FS/data/misc/perfetto-traces/',
|
||||
'proto/window_CRITICAL.proto',
|
||||
];
|
||||
const isFileAllowlisted = (file: TraceFile) => {
|
||||
for (const traceDir of BUGREPORT_FILES_ALLOWLIST) {
|
||||
if (file.file.name.startsWith(traceDir)) {
|
||||
return true;
|
||||
}
|
||||
@@ -144,7 +135,7 @@ class TracePipeline {
|
||||
file.parentArchive === bugreportMainEntry.parentArchive;
|
||||
|
||||
return files.filter((file) => {
|
||||
return isFileWithinBugreportTraceDir(file) || !fileBelongsToBugreport(file);
|
||||
return isFileAllowlisted(file) || !fileBelongsToBugreport(file);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -163,14 +154,6 @@ class TracePipeline {
|
||||
|
||||
throw Error('Failed to find common timestamp type across all traces');
|
||||
}
|
||||
|
||||
private checkTracesWereBuilt() {
|
||||
if (!this.traces) {
|
||||
throw new Error(
|
||||
`Can't access traces before building them. Did you forget to call '${this.buildTraces.name}'?`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {TracePipeline};
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {assertDefined} from 'common/assert_utils';
|
||||
import {TracesUtils} from 'test/unit/traces_utils';
|
||||
import {UnitTestUtils} from 'test/unit/utils';
|
||||
import {TraceFile} from 'trace/trace_file';
|
||||
@@ -21,26 +22,47 @@ import {TraceType} from 'trace/trace_type';
|
||||
import {TracePipeline} from './trace_pipeline';
|
||||
|
||||
describe('TracePipeline', () => {
|
||||
let validSfTraceFile: TraceFile;
|
||||
let validWmTraceFile: TraceFile;
|
||||
let tracePipeline: TracePipeline;
|
||||
|
||||
beforeEach(async () => {
|
||||
validSfTraceFile = new TraceFile(
|
||||
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb')
|
||||
);
|
||||
validWmTraceFile = new TraceFile(
|
||||
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/WindowManager.pb')
|
||||
);
|
||||
tracePipeline = new TracePipeline();
|
||||
});
|
||||
|
||||
it('can load valid trace files', async () => {
|
||||
expect(tracePipeline.getLoadedTraces().length).toEqual(0);
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(0);
|
||||
|
||||
await loadValidSfWmTraces();
|
||||
|
||||
expect(tracePipeline.getLoadedTraces().length).toEqual(2);
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(2);
|
||||
|
||||
const traceEntries = TracesUtils.extractEntries(tracePipeline.getTraces());
|
||||
const traceEntries = await TracesUtils.extractEntries(tracePipeline.getTraces());
|
||||
expect(traceEntries.get(TraceType.WINDOW_MANAGER)?.length).toBeGreaterThan(0);
|
||||
expect(traceEntries.get(TraceType.SURFACE_FLINGER)?.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('can load a new file without dropping already-loaded traces', async () => {
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(0);
|
||||
|
||||
await tracePipeline.loadTraceFiles([validSfTraceFile]);
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(1);
|
||||
|
||||
await tracePipeline.loadTraceFiles([validWmTraceFile]);
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(2);
|
||||
|
||||
await tracePipeline.loadTraceFiles([validWmTraceFile]); // ignored (duplicated)
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(2);
|
||||
});
|
||||
|
||||
it('can load bugreport and ignores non-trace dirs', async () => {
|
||||
expect(tracePipeline.getLoadedTraces().length).toEqual(0);
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(0);
|
||||
|
||||
// Could be any file, we just need an instance of File to be used as a fake bugreport archive
|
||||
const bugreportArchive = await UnitTestUtils.getFixtureFile(
|
||||
@@ -76,7 +98,14 @@ describe('TracePipeline', () => {
|
||||
new TraceFile(
|
||||
await UnitTestUtils.getFixtureFile(
|
||||
'traces/elapsed_and_real_timestamp/WindowManager.pb',
|
||||
'FS/data/misc/ignored-dir/window_manager.bp'
|
||||
'proto/window_CRITICAL.proto'
|
||||
),
|
||||
bugreportArchive
|
||||
),
|
||||
new TraceFile(
|
||||
await UnitTestUtils.getFixtureFile(
|
||||
'traces/elapsed_and_real_timestamp/wm_transition_trace.pb',
|
||||
'FS/data/misc/ignored-dir/wm_transition_trace.bp'
|
||||
),
|
||||
bugreportArchive
|
||||
),
|
||||
@@ -98,13 +127,14 @@ describe('TracePipeline', () => {
|
||||
const mergedFiles = bugreportFiles.concat([plainTraceFile]);
|
||||
const errors = await tracePipeline.loadTraceFiles(mergedFiles);
|
||||
expect(errors.length).toEqual(0);
|
||||
tracePipeline.buildTraces();
|
||||
await tracePipeline.buildTraces();
|
||||
const traces = tracePipeline.getTraces();
|
||||
|
||||
expect(traces.getTrace(TraceType.SURFACE_FLINGER)).toBeDefined();
|
||||
expect(traces.getTrace(TraceType.TRANSACTIONS)).toBeDefined();
|
||||
expect(traces.getTrace(TraceType.WINDOW_MANAGER)).toBeUndefined(); // ignored
|
||||
expect(traces.getTrace(TraceType.WM_TRANSITION)).toBeUndefined(); // ignored
|
||||
expect(traces.getTrace(TraceType.INPUT_METHOD_CLIENTS)).toBeDefined();
|
||||
expect(traces.getTrace(TraceType.WINDOW_MANAGER)).toBeDefined();
|
||||
});
|
||||
|
||||
it('is robust to invalid trace files', async () => {
|
||||
@@ -113,20 +143,20 @@ describe('TracePipeline', () => {
|
||||
];
|
||||
|
||||
const errors = await tracePipeline.loadTraceFiles(invalidTraceFiles);
|
||||
tracePipeline.buildTraces();
|
||||
await tracePipeline.buildTraces();
|
||||
expect(errors.length).toEqual(1);
|
||||
expect(tracePipeline.getLoadedTraces().length).toEqual(0);
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(0);
|
||||
});
|
||||
|
||||
it('is robust to mixed valid and invalid trace files', async () => {
|
||||
expect(tracePipeline.getLoadedTraces().length).toEqual(0);
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(0);
|
||||
const files = [
|
||||
new TraceFile(await UnitTestUtils.getFixtureFile('winscope_homepage.png')),
|
||||
new TraceFile(await UnitTestUtils.getFixtureFile('traces/dump_WindowManager.pb')),
|
||||
];
|
||||
const errors = await tracePipeline.loadTraceFiles(files);
|
||||
tracePipeline.buildTraces();
|
||||
expect(tracePipeline.getLoadedTraces().length).toEqual(1);
|
||||
await tracePipeline.buildTraces();
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(1);
|
||||
expect(errors.length).toEqual(1);
|
||||
});
|
||||
|
||||
@@ -136,37 +166,41 @@ describe('TracePipeline', () => {
|
||||
];
|
||||
|
||||
const errors = await tracePipeline.loadTraceFiles(traceFilesWithNoEntries);
|
||||
tracePipeline.buildTraces();
|
||||
await tracePipeline.buildTraces();
|
||||
|
||||
expect(errors.length).toEqual(0);
|
||||
|
||||
expect(tracePipeline.getLoadedTraces().length).toEqual(1);
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(1);
|
||||
});
|
||||
|
||||
it('can remove traces', async () => {
|
||||
await loadValidSfWmTraces();
|
||||
expect(tracePipeline.getLoadedTraces().length).toEqual(2);
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(2);
|
||||
|
||||
tracePipeline.removeTraceFile(TraceType.SURFACE_FLINGER);
|
||||
tracePipeline.buildTraces();
|
||||
expect(tracePipeline.getLoadedTraces().length).toEqual(1);
|
||||
const sfTrace = assertDefined(tracePipeline.getTraces().getTrace(TraceType.SURFACE_FLINGER));
|
||||
const wmTrace = assertDefined(tracePipeline.getTraces().getTrace(TraceType.WINDOW_MANAGER));
|
||||
|
||||
tracePipeline.removeTraceFile(TraceType.WINDOW_MANAGER);
|
||||
tracePipeline.buildTraces();
|
||||
expect(tracePipeline.getLoadedTraces().length).toEqual(0);
|
||||
tracePipeline.removeTrace(sfTrace);
|
||||
await tracePipeline.buildTraces();
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(1);
|
||||
|
||||
tracePipeline.removeTrace(wmTrace);
|
||||
await tracePipeline.buildTraces();
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(0);
|
||||
});
|
||||
|
||||
it('gets loaded trace files', async () => {
|
||||
it('gets loaded traces', async () => {
|
||||
await loadValidSfWmTraces();
|
||||
|
||||
const files = tracePipeline.getLoadedTraces();
|
||||
expect(files.length).toEqual(2);
|
||||
expect(files[0].descriptors).toBeTruthy();
|
||||
expect(files[0].descriptors.length).toBeGreaterThan(0);
|
||||
const traces = tracePipeline.getTraces();
|
||||
expect(traces.getSize()).toEqual(2);
|
||||
|
||||
const actualTraceTypes = new Set(files.map((file) => file.type));
|
||||
const actualTraceTypes = new Set(traces.mapTrace((trace) => trace.type));
|
||||
const expectedTraceTypes = new Set([TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER]);
|
||||
expect(actualTraceTypes).toEqual(expectedTraceTypes);
|
||||
|
||||
const sfTrace = assertDefined(traces.getTrace(TraceType.SURFACE_FLINGER));
|
||||
expect(sfTrace.getDescriptors().length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('builds traces', async () => {
|
||||
@@ -186,49 +220,25 @@ describe('TracePipeline', () => {
|
||||
),
|
||||
];
|
||||
await tracePipeline.loadTraceFiles(traceFiles);
|
||||
tracePipeline.buildTraces();
|
||||
await tracePipeline.buildTraces();
|
||||
|
||||
const video = tracePipeline.getScreenRecordingVideo();
|
||||
const video = await tracePipeline.getScreenRecordingVideo();
|
||||
expect(video).toBeDefined();
|
||||
expect(video!.size).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('can be cleared', async () => {
|
||||
await loadValidSfWmTraces();
|
||||
expect(tracePipeline.getLoadedTraces().length).toBeGreaterThan(0);
|
||||
expect(tracePipeline.getTraces().getSize()).toBeGreaterThan(0);
|
||||
|
||||
tracePipeline.clear();
|
||||
expect(tracePipeline.getLoadedTraces().length).toEqual(0);
|
||||
expect(() => {
|
||||
tracePipeline.getTraces();
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
tracePipeline.getScreenRecordingVideo();
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('throws if accessed before traces are built', async () => {
|
||||
expect(() => {
|
||||
tracePipeline.getTraces();
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
tracePipeline.getScreenRecordingVideo();
|
||||
}).toThrow();
|
||||
expect(tracePipeline.getTraces().getSize()).toEqual(0);
|
||||
});
|
||||
|
||||
const loadValidSfWmTraces = async () => {
|
||||
const traceFiles = [
|
||||
new TraceFile(
|
||||
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb')
|
||||
),
|
||||
new TraceFile(
|
||||
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/WindowManager.pb')
|
||||
),
|
||||
];
|
||||
|
||||
const traceFiles = [validSfTraceFile, validWmTraceFile];
|
||||
const errors = await tracePipeline.loadTraceFiles(traceFiles);
|
||||
expect(errors.length).toEqual(0);
|
||||
|
||||
tracePipeline.buildTraces();
|
||||
await tracePipeline.buildTraces();
|
||||
};
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ export class CrossToolProtocol
|
||||
{
|
||||
private remoteTool?: RemoteTool;
|
||||
private onBugreportReceived: OnBugreportReceived = FunctionUtils.DO_NOTHING_ASYNC;
|
||||
private onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING;
|
||||
private onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING_ASYNC;
|
||||
|
||||
constructor() {
|
||||
window.addEventListener('message', async (event) => {
|
||||
@@ -89,10 +89,12 @@ export class CrossToolProtocol
|
||||
case MessageType.BUGREPORT:
|
||||
console.log('Cross-tool protocol received bugreport message:', message);
|
||||
await this.onMessageBugreportReceived(message as MessageBugReport);
|
||||
console.log('Cross-tool protocol processes bugreport message:', message);
|
||||
break;
|
||||
case MessageType.TIMESTAMP:
|
||||
console.log('Cross-tool protocol received timestamp message:', message);
|
||||
this.onMessageTimestampReceived(message as MessageTimestamp);
|
||||
await this.onMessageTimestampReceived(message as MessageTimestamp);
|
||||
console.log('Cross-tool protocol processed timestamp message:', message);
|
||||
break;
|
||||
case MessageType.FILES:
|
||||
console.log('Cross-tool protocol received unexpected files message', message);
|
||||
@@ -106,11 +108,11 @@ export class CrossToolProtocol
|
||||
private async onMessageBugreportReceived(message: MessageBugReport) {
|
||||
const timestamp =
|
||||
message.timestampNs !== undefined ? new RealTimestamp(message.timestampNs) : undefined;
|
||||
this.onBugreportReceived(message.file, timestamp);
|
||||
await this.onBugreportReceived(message.file, timestamp);
|
||||
}
|
||||
|
||||
private onMessageTimestampReceived(message: MessageTimestamp) {
|
||||
private async onMessageTimestampReceived(message: MessageTimestamp) {
|
||||
const timestamp = new RealTimestamp(message.timestampNs);
|
||||
this.onTimestampReceived(timestamp);
|
||||
await this.onTimestampReceived(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export class CrossToolProtocolStub
|
||||
implements RemoteBugreportReceiver, RemoteTimestampReceiver, RemoteTimestampSender
|
||||
{
|
||||
onBugreportReceived: OnBugreportReceived = FunctionUtils.DO_NOTHING_ASYNC;
|
||||
onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING;
|
||||
onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING_ASYNC;
|
||||
|
||||
setOnBugreportReceived(callback: OnBugreportReceived) {
|
||||
this.onBugreportReceived = callback;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import {RealTimestamp} from 'trace/timestamp';
|
||||
|
||||
export type OnTimestampReceived = (timestamp: RealTimestamp) => void;
|
||||
export type OnTimestampReceived = (timestamp: RealTimestamp) => Promise<void>;
|
||||
|
||||
export interface RemoteTimestampReceiver {
|
||||
setOnTimestampReceived(callback: OnTimestampReceived): void;
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {TraceType} from './trace_type';
|
||||
import {TracePosition} from 'trace/trace_position';
|
||||
|
||||
export class LoadedTrace {
|
||||
constructor(public descriptors: string[], public type: TraceType) {}
|
||||
export type OnTracePositionUpdate = (position: TracePosition) => Promise<void>;
|
||||
|
||||
export interface TracePositionUpdateEmitter {
|
||||
setOnTracePositionUpdate(callback: OnTracePositionUpdate): void;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import {Timestamp, TimestampType} from 'trace/timestamp';
|
||||
import {TraceFile} from 'trace/trace_file';
|
||||
import {TraceType} from 'trace/trace_type';
|
||||
|
||||
abstract class AbstractParser<T extends object = object> implements Parser<object> {
|
||||
abstract class AbstractParser<T extends object = object> implements Parser<T> {
|
||||
protected traceFile: TraceFile;
|
||||
protected decodedEntries: any[] = [];
|
||||
private timestamps: Map<TimestampType, Timestamp[]> = new Map<TimestampType, Timestamp[]>();
|
||||
@@ -78,8 +78,9 @@ abstract class AbstractParser<T extends object = object> implements Parser<objec
|
||||
return this.timestamps.get(type);
|
||||
}
|
||||
|
||||
getEntry(index: number, timestampType: TimestampType): T {
|
||||
return this.processDecodedEntry(index, timestampType, this.decodedEntries[index]);
|
||||
getEntry(index: number, timestampType: TimestampType): Promise<T> {
|
||||
const entry = this.processDecodedEntry(index, timestampType, this.decodedEntries[index]);
|
||||
return Promise.resolve(entry);
|
||||
}
|
||||
|
||||
// Add default values to the proto objects.
|
||||
|
||||
@@ -16,42 +16,33 @@
|
||||
|
||||
import {Parser} from 'trace/parser';
|
||||
import {Timestamp, TimestampType} from 'trace/timestamp';
|
||||
import {TraceFile} from 'trace/trace_file';
|
||||
import {TraceType} from 'trace/trace_type';
|
||||
|
||||
export abstract class AbstractTracesParser<T> implements Parser<T> {
|
||||
constructor(readonly parsers: Array<Parser<object>>) {}
|
||||
private timestampsSet: boolean = false;
|
||||
private timestamps: Map<TimestampType, Timestamp[]> = new Map<TimestampType, Timestamp[]>();
|
||||
|
||||
getTraceFile(): TraceFile {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
abstract canProvideEntries(): boolean;
|
||||
abstract parse(): Promise<void>;
|
||||
|
||||
abstract getDescriptors(): string[];
|
||||
|
||||
abstract getTraceType(): TraceType;
|
||||
|
||||
abstract getEntry(index: number, timestampType: TimestampType): T;
|
||||
abstract getEntry(index: number, timestampType: TimestampType): Promise<T>;
|
||||
|
||||
abstract getLengthEntries(): number;
|
||||
|
||||
getTimestamps(type: TimestampType): Timestamp[] | undefined {
|
||||
this.setTimestamps();
|
||||
return this.timestamps.get(type);
|
||||
}
|
||||
|
||||
private setTimestamps() {
|
||||
if (this.timestampsSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
protected async parseTimestamps() {
|
||||
for (const type of [TimestampType.ELAPSED, TimestampType.REAL]) {
|
||||
const timestamps: Timestamp[] = [];
|
||||
let areTimestampsValid = true;
|
||||
|
||||
for (let index = 0; index < this.getLengthEntries(); index++) {
|
||||
const entry = this.getEntry(index, type);
|
||||
const entry = await this.getEntry(index, type);
|
||||
const timestamp = this.getTimestamp(type, entry);
|
||||
if (timestamp === undefined) {
|
||||
areTimestampsValid = false;
|
||||
@@ -68,8 +59,5 @@ export abstract class AbstractTracesParser<T> implements Parser<T> {
|
||||
this.timestampsSet = true;
|
||||
}
|
||||
|
||||
abstract getTimestamp(type: TimestampType, decodedEntry: any): undefined | Timestamp;
|
||||
|
||||
private timestampsSet: boolean = false;
|
||||
private timestamps: Map<TimestampType, Timestamp[]> = new Map<TimestampType, Timestamp[]>();
|
||||
protected abstract getTimestamp(type: TimestampType, decodedEntry: any): undefined | Timestamp;
|
||||
}
|
||||
|
||||
@@ -48,11 +48,9 @@ describe('ParserAccessibility', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('retrieves trace entry', () => {
|
||||
const timestamp = new Timestamp(TimestampType.REAL, 1659107089100562784n);
|
||||
expect(BigInt(parser.getEntry(1, TimestampType.REAL).elapsedRealtimeNanos)).toEqual(
|
||||
14499599656n
|
||||
);
|
||||
it('retrieves trace entry', async () => {
|
||||
const entry = await parser.getEntry(1, TimestampType.REAL);
|
||||
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(14499599656n);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -53,17 +53,12 @@ describe('Parser', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('retrieves trace entries', () => {
|
||||
expect(
|
||||
BigInt(parser.getEntry(0, TimestampType.REAL)!.timestamp.unixNanos.toString())
|
||||
).toEqual(1659107089075566202n);
|
||||
expect(
|
||||
BigInt(
|
||||
parser
|
||||
.getEntry(parser.getLengthEntries() - 1, TimestampType.REAL)!
|
||||
.timestamp.unixNanos.toString()
|
||||
)
|
||||
).toEqual(1659107091700249187n);
|
||||
it('retrieves trace entries', async () => {
|
||||
let entry = await parser.getEntry(0, TimestampType.REAL);
|
||||
expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(1659107089075566202n);
|
||||
|
||||
entry = await parser.getEntry(parser.getLengthEntries() - 1, TimestampType.REAL);
|
||||
expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(1659107091700249187n);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,17 +78,12 @@ describe('Parser', () => {
|
||||
expect(parser.getTimestamps(TimestampType.ELAPSED)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('retrieves trace entries', () => {
|
||||
expect(
|
||||
BigInt(parser.getEntry(0, TimestampType.ELAPSED)!.timestamp.elapsedNanos.toString())
|
||||
).toEqual(850254319343n);
|
||||
expect(
|
||||
BigInt(
|
||||
parser
|
||||
.getEntry(parser.getLengthEntries() - 1, TimestampType.ELAPSED)!
|
||||
.timestamp.elapsedNanos.toString()
|
||||
)
|
||||
).toEqual(850782750048n);
|
||||
it('retrieves trace entries', async () => {
|
||||
let entry = await parser.getEntry(0, TimestampType.ELAPSED);
|
||||
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(850254319343n);
|
||||
|
||||
entry = await parser.getEntry(parser.getLengthEntries() - 1, TimestampType.ELAPSED);
|
||||
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(850782750048n);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,8 +51,8 @@ describe('ParserEventLog', () => {
|
||||
expect(parser.getTimestamps(TimestampType.ELAPSED)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('contains parsed jank CUJ events', () => {
|
||||
const entry = parser.getEntry(18, TimestampType.REAL);
|
||||
expect(entry instanceof CujEvent).toBeTrue();
|
||||
it('contains parsed jank CUJ events', async () => {
|
||||
const entry = await parser.getEntry(18, TimestampType.REAL);
|
||||
expect(entry).toBeInstanceOf(CujEvent);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ import {ParserSurfaceFlinger} from './parser_surface_flinger';
|
||||
import {ParserTransactions} from './parser_transactions';
|
||||
import {ParserTransitionsShell} from './parser_transitions_shell';
|
||||
import {ParserTransitionsWm} from './parser_transitions_wm';
|
||||
import {ParserViewCapture} from './parser_view_capture';
|
||||
import {ParserWindowManager} from './parser_window_manager';
|
||||
import {ParserWindowManagerDump} from './parser_window_manager_dump';
|
||||
|
||||
@@ -49,6 +50,7 @@ export class ParserFactory {
|
||||
ParserEventLog,
|
||||
ParserTransitionsWm,
|
||||
ParserTransitionsShell,
|
||||
ParserViewCapture,
|
||||
];
|
||||
|
||||
private parsers = new Map<TraceType, Parser<object>>();
|
||||
|
||||
@@ -52,10 +52,9 @@ describe('ParserInputMethodlClients', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('retrieves trace entry', () => {
|
||||
expect(BigInt(parser.getEntry(1, TimestampType.REAL)!.elapsedRealtimeNanos)).toEqual(
|
||||
15647516364n
|
||||
);
|
||||
it('retrieves trace entry', async () => {
|
||||
const entry = await parser.getEntry(1, TimestampType.REAL);
|
||||
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(15647516364n);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,10 +79,9 @@ describe('ParserInputMethodlClients', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('retrieves trace entry from elapsed timestamp', () => {
|
||||
expect(BigInt(parser.getEntry(0, TimestampType.ELAPSED)!.elapsedRealtimeNanos)).toEqual(
|
||||
1149083651642n
|
||||
);
|
||||
it('retrieves trace entry from elapsed timestamp', async () => {
|
||||
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
|
||||
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(1149083651642n);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,10 +44,9 @@ describe('ParserInputMethodManagerService', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('retrieves trace entry', () => {
|
||||
expect(BigInt(parser.getEntry(0, TimestampType.REAL)!.elapsedRealtimeNanos)).toEqual(
|
||||
15963782518n
|
||||
);
|
||||
it('retrieves trace entry', async () => {
|
||||
const entry = await parser.getEntry(0, TimestampType.REAL);
|
||||
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(15963782518n);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,10 +73,9 @@ describe('ParserInputMethodManagerService', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('retrieves trace entry from elapsed timestamp', () => {
|
||||
expect(BigInt(parser.getEntry(0, TimestampType.ELAPSED)!.elapsedRealtimeNanos)).toEqual(
|
||||
1149226290110n
|
||||
);
|
||||
it('retrieves trace entry from elapsed timestamp', async () => {
|
||||
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
|
||||
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(1149226290110n);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,10 +42,9 @@ describe('ParserInputMethodService', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('retrieves trace entry', () => {
|
||||
expect(BigInt(parser.getEntry(0, TimestampType.REAL)!.elapsedRealtimeNanos)).toEqual(
|
||||
16578752896n
|
||||
);
|
||||
it('retrieves trace entry', async () => {
|
||||
const entry = await parser.getEntry(0, TimestampType.REAL);
|
||||
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(16578752896n);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,10 +69,9 @@ describe('ParserInputMethodService', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('retrieves trace entry', () => {
|
||||
expect(BigInt(parser.getEntry(0, TimestampType.ELAPSED)!.elapsedRealtimeNanos)).toEqual(
|
||||
1149230019887n
|
||||
);
|
||||
it('retrieves trace entry', async () => {
|
||||
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
|
||||
expect(BigInt(entry.elapsedRealtimeNanos)).toEqual(1149230019887n);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -78,15 +78,15 @@ describe('ParserProtoLog', () => {
|
||||
expect(timestamps.slice(0, 3)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('reconstructs human-readable log message (ELAPSED time)', () => {
|
||||
const message = parser.getEntry(0, TimestampType.ELAPSED);
|
||||
it('reconstructs human-readable log message (ELAPSED time)', async () => {
|
||||
const message = await parser.getEntry(0, TimestampType.ELAPSED);
|
||||
|
||||
expect(Object.assign({}, message)).toEqual(expectedFirstLogMessageElapsed);
|
||||
expect(message).toBeInstanceOf(LogMessage);
|
||||
});
|
||||
|
||||
it('reconstructs human-readable log message (REAL time)', () => {
|
||||
const message = parser.getEntry(0, TimestampType.REAL)!;
|
||||
it('reconstructs human-readable log message (REAL time)', async () => {
|
||||
const message = await parser.getEntry(0, TimestampType.REAL);
|
||||
|
||||
expect(Object.assign({}, message)).toEqual(expectedFirstLogMessageReal);
|
||||
});
|
||||
|
||||
@@ -56,14 +56,14 @@ describe('ParserScreenRecordingLegacy', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('retrieves trace entry', () => {
|
||||
it('retrieves trace entry', async () => {
|
||||
{
|
||||
const entry = parser.getEntry(0, TimestampType.ELAPSED);
|
||||
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
|
||||
expect(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
|
||||
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(0);
|
||||
}
|
||||
{
|
||||
const entry = parser.getEntry(parser.getLengthEntries() - 1, TimestampType.ELAPSED);
|
||||
const entry = await parser.getEntry(parser.getLengthEntries() - 1, TimestampType.ELAPSED);
|
||||
expect(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
|
||||
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(2.37, 0.001);
|
||||
}
|
||||
|
||||
@@ -58,14 +58,14 @@ describe('ParserScreenRecording', () => {
|
||||
expect(timestamps.slice(0, 3)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('retrieves trace entry', () => {
|
||||
it('retrieves trace entry', async () => {
|
||||
{
|
||||
const entry = parser.getEntry(0, TimestampType.REAL);
|
||||
const entry = await parser.getEntry(0, TimestampType.REAL);
|
||||
expect(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
|
||||
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(0);
|
||||
}
|
||||
{
|
||||
const entry = parser.getEntry(parser.getLengthEntries() - 1, TimestampType.REAL);
|
||||
const entry = await parser.getEntry(parser.getLengthEntries() - 1, TimestampType.REAL);
|
||||
expect(entry).toBeInstanceOf(ScreenRecordingTraceEntry);
|
||||
expect(Number(entry.videoTimeSeconds)).toBeCloseTo(1.371077, 0.001);
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ describe('ParserSurfaceFlingerDump', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('retrieves trace entry', () => {
|
||||
const entry = parser.getEntry(0, TimestampType.ELAPSED);
|
||||
it('retrieves trace entry', async () => {
|
||||
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
|
||||
expect(entry).toBeInstanceOf(LayerTraceEntry);
|
||||
expect(BigInt(entry.timestamp.systemUptimeNanos.toString())).toEqual(0n);
|
||||
expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(DUMP_REAL_TIME);
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('ParserSurfaceFlinger', () => {
|
||||
const parser = (await UnitTestUtils.getParser(
|
||||
'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb'
|
||||
)) as Parser<LayerTraceEntry>;
|
||||
const entry = parser.getEntry(0, TimestampType.REAL);
|
||||
const entry = await parser.getEntry(0, TimestampType.REAL);
|
||||
|
||||
{
|
||||
const layer = entry.flattenedLayers.find((layer: Layer) => layer.id === 27);
|
||||
@@ -76,8 +76,8 @@ describe('ParserSurfaceFlinger', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('formats entry timestamps', () => {
|
||||
const entry = parser.getEntry(1, TimestampType.REAL);
|
||||
it('formats entry timestamps', async () => {
|
||||
const entry = await parser.getEntry(1, TimestampType.REAL);
|
||||
expect(entry.name).toEqual('2022-07-29T15:04:49.233029376');
|
||||
expect(BigInt(entry.timestamp.systemUptimeNanos.toString())).toEqual(14631249355n);
|
||||
expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(1659107089233029344n);
|
||||
@@ -107,8 +107,8 @@ describe('ParserSurfaceFlinger', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('formats entry timestamps', () => {
|
||||
const entry = parser.getEntry(0, TimestampType.ELAPSED);
|
||||
it('formats entry timestamps', async () => {
|
||||
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
|
||||
expect(entry.name).toEqual('14m10s335ms483446ns');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -56,21 +56,21 @@ describe('ParserTransactions', () => {
|
||||
expect(timestamps.slice(0, 3)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('retrieves trace entry from real timestamp', () => {
|
||||
const entry = parser.getEntry(1, TimestampType.REAL);
|
||||
it('retrieves trace entry from real timestamp', async () => {
|
||||
const entry = await parser.getEntry(1, TimestampType.REAL);
|
||||
expect(BigInt((entry as any).elapsedRealtimeNanos)).toEqual(2517952515n);
|
||||
});
|
||||
|
||||
it("decodes 'what' field in proto", () => {
|
||||
it("decodes 'what' field in proto", async () => {
|
||||
{
|
||||
const entry = parser.getEntry(0, TimestampType.REAL) as any;
|
||||
const entry = (await parser.getEntry(0, TimestampType.REAL)) as any;
|
||||
expect(entry.transactions[0].layerChanges[0].what).toEqual('eLayerChanged');
|
||||
expect(entry.transactions[1].layerChanges[0].what).toEqual(
|
||||
'eFlagsChanged | eDestinationFrameChanged'
|
||||
);
|
||||
}
|
||||
{
|
||||
const entry = parser.getEntry(222, TimestampType.REAL) as any;
|
||||
const entry = (await parser.getEntry(222, TimestampType.REAL)) as any;
|
||||
expect(entry.transactions[1].displayChanges[0].what).toEqual(
|
||||
'eLayerStackChanged | eDisplayProjectionChanged | eFlagsChanged'
|
||||
);
|
||||
|
||||
177
tools/winscope/src/parsers/parser_view_capture.ts
Normal file
177
tools/winscope/src/parsers/parser_view_capture.ts
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
57
tools/winscope/src/parsers/parser_view_capture_test.ts
Normal file
57
tools/winscope/src/parsers/parser_view_capture_test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
@@ -40,8 +40,8 @@ describe('ParserWindowManagerDump', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('retrieves trace entry', () => {
|
||||
const entry = parser.getEntry(0, TimestampType.ELAPSED);
|
||||
it('retrieves trace entry', async () => {
|
||||
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
|
||||
expect(entry).toBeInstanceOf(WindowManagerState);
|
||||
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(0n);
|
||||
});
|
||||
|
||||
@@ -49,15 +49,15 @@ describe('ParserWindowManager', () => {
|
||||
expect(parser.getTimestamps(TimestampType.REAL)!.slice(0, 3)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('retrieves trace entry', () => {
|
||||
const entry = parser.getEntry(1, TimestampType.REAL);
|
||||
it('retrieves trace entry', async () => {
|
||||
const entry = await parser.getEntry(1, TimestampType.REAL);
|
||||
expect(entry).toBeInstanceOf(WindowManagerState);
|
||||
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(15398076788n);
|
||||
expect(BigInt(entry.timestamp.unixNanos.toString())).toEqual(1659107089999048990n);
|
||||
});
|
||||
|
||||
it('formats entry timestamps', () => {
|
||||
const entry = parser.getEntry(1, TimestampType.REAL);
|
||||
it('formats entry timestamps', async () => {
|
||||
const entry = await parser.getEntry(1, TimestampType.REAL);
|
||||
expect(entry.name).toEqual('2022-07-29T15:04:49.999048960');
|
||||
});
|
||||
});
|
||||
@@ -82,14 +82,14 @@ describe('ParserWindowManager', () => {
|
||||
expect(parser.getTimestamps(TimestampType.ELAPSED)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('retrieves trace entry', () => {
|
||||
const entry = parser.getEntry(0, TimestampType.ELAPSED);
|
||||
it('retrieves trace entry', async () => {
|
||||
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
|
||||
expect(entry).toBeInstanceOf(WindowManagerState);
|
||||
expect(BigInt(entry.timestamp.elapsedNanos.toString())).toEqual(850254319343n);
|
||||
});
|
||||
|
||||
it('formats entry timestamps', () => {
|
||||
const entry = parser.getEntry(0, TimestampType.ELAPSED);
|
||||
it('formats entry timestamps', async () => {
|
||||
const entry = await parser.getEntry(0, TimestampType.ELAPSED);
|
||||
expect(entry.name).toEqual('14m10s254ms319343ns');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,6 +25,7 @@ import windowManagerJson from 'frameworks/base/core/proto/android/server/windowm
|
||||
import wmTransitionsJson from 'frameworks/base/core/proto/android/server/windowmanagertransitiontrace.proto';
|
||||
import inputMethodClientsJson from 'frameworks/base/core/proto/android/view/inputmethod/inputmethodeditortrace.proto';
|
||||
import shellTransitionsJson from 'frameworks/base/libs/WindowManager/Shell/proto/wm_shell_transition_trace.proto';
|
||||
import viewCaptureJson from 'frameworks/libs/systemui/viewcapturelib/src/com/android/app/viewcapture/proto/view_capture.proto';
|
||||
import layersJson from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto';
|
||||
import transactionsJson from 'frameworks/native/services/surfaceflinger/layerproto/transactions.proto';
|
||||
|
||||
@@ -62,6 +63,10 @@ const ShellTransitionsTraceFileProto = protobuf.Root.fromJSON(shellTransitionsJs
|
||||
'com.android.wm.shell.WmShellTransitionTraceProto'
|
||||
);
|
||||
|
||||
const ExportedData = protobuf.Root.fromJSON(viewCaptureJson).lookupType(
|
||||
'com.android.app.viewcapture.data.ExportedData'
|
||||
);
|
||||
|
||||
export {
|
||||
AccessibilityTraceFileProto,
|
||||
InputMethodClientsTraceFileProto,
|
||||
@@ -74,4 +79,5 @@ export {
|
||||
WindowManagerTraceFileProto,
|
||||
WmTransitionsTraceFileProto,
|
||||
ShellTransitionsTraceFileProto,
|
||||
ExportedData,
|
||||
};
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Cuj, CujTrace, EventLog, Transition} from 'trace/flickerlib/common';
|
||||
import {assertDefined} from 'common/assert_utils';
|
||||
import {Cuj, EventLog, Transition} from 'trace/flickerlib/common';
|
||||
import {Parser} from 'trace/parser';
|
||||
import {Timestamp, TimestampType} from 'trace/timestamp';
|
||||
import {TraceType} from 'trace/trace_type';
|
||||
@@ -24,11 +25,12 @@ import {ParserEventLog} from './parser_eventlog';
|
||||
export class TracesParserCujs extends AbstractTracesParser<Transition> {
|
||||
private readonly eventLogTrace: ParserEventLog | undefined;
|
||||
private readonly descriptors: string[];
|
||||
private decodedEntries: Cuj[] | undefined;
|
||||
|
||||
constructor(parsers: Array<Parser<object>>) {
|
||||
super(parsers);
|
||||
super();
|
||||
|
||||
const eventlogTraces = this.parsers.filter((it) => it.getTraceType() === TraceType.EVENT_LOG);
|
||||
const eventlogTraces = parsers.filter((it) => it.getTraceType() === TraceType.EVENT_LOG);
|
||||
if (eventlogTraces.length > 0) {
|
||||
this.eventLogTrace = eventlogTraces[0] as ParserEventLog;
|
||||
}
|
||||
@@ -40,35 +42,29 @@ export class TracesParserCujs extends AbstractTracesParser<Transition> {
|
||||
}
|
||||
}
|
||||
|
||||
override canProvideEntries(): boolean {
|
||||
return this.eventLogTrace !== undefined;
|
||||
}
|
||||
|
||||
getLengthEntries(): number {
|
||||
return this.getDecodedEntries().length;
|
||||
}
|
||||
|
||||
getEntry(index: number, timestampType: TimestampType): Transition {
|
||||
return this.getDecodedEntries()[index];
|
||||
}
|
||||
|
||||
private cujTrace: CujTrace | undefined;
|
||||
getDecodedEntries(): Cuj[] {
|
||||
override async parse() {
|
||||
if (this.eventLogTrace === undefined) {
|
||||
throw new Error('eventLogTrace not defined');
|
||||
}
|
||||
|
||||
if (this.cujTrace === undefined) {
|
||||
const events: Event[] = [];
|
||||
const events: Event[] = [];
|
||||
|
||||
for (let i = 0; i < this.eventLogTrace.getLengthEntries(); i++) {
|
||||
events.push(this.eventLogTrace.getEntry(i, TimestampType.REAL));
|
||||
}
|
||||
|
||||
this.cujTrace = new EventLog(events).cujTrace;
|
||||
for (let i = 0; i < this.eventLogTrace.getLengthEntries(); i++) {
|
||||
events.push(await this.eventLogTrace.getEntry(i, TimestampType.REAL));
|
||||
}
|
||||
|
||||
return this.cujTrace.entries;
|
||||
this.decodedEntries = new EventLog(events).cujTrace.entries;
|
||||
|
||||
await this.parseTimestamps();
|
||||
}
|
||||
|
||||
getLengthEntries(): number {
|
||||
return assertDefined(this.decodedEntries).length;
|
||||
}
|
||||
|
||||
getEntry(index: number, timestampType: TimestampType): Promise<Transition> {
|
||||
const entry = assertDefined(this.decodedEntries)[index];
|
||||
return Promise.resolve(entry);
|
||||
}
|
||||
|
||||
override getDescriptors(): string[] {
|
||||
|
||||
@@ -14,23 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {assertDefined} from 'common/assert_utils';
|
||||
import {UnitTestUtils} from 'test/unit/utils';
|
||||
import {Cuj} from 'trace/flickerlib/common';
|
||||
import {Parser} from 'trace/parser';
|
||||
import {Timestamp, TimestampType} from 'trace/timestamp';
|
||||
import {TraceType} from 'trace/trace_type';
|
||||
import {TracesParserCujs} from './traces_parser_cujs';
|
||||
|
||||
describe('ParserCujs', () => {
|
||||
let parser: Parser<Cuj>;
|
||||
|
||||
beforeAll(async () => {
|
||||
const eventLogParser = assertDefined(
|
||||
await UnitTestUtils.getParser('traces/eventlog.winscope')
|
||||
) as Parser<Event>;
|
||||
|
||||
parser = new TracesParserCujs([eventLogParser]);
|
||||
parser = await UnitTestUtils.getTracesParser(['traces/eventlog.winscope']);
|
||||
});
|
||||
|
||||
it('has expected trace type', () => {
|
||||
|
||||
40
tools/winscope/src/parsers/traces_parser_factory.ts
Normal file
40
tools/winscope/src/parsers/traces_parser_factory.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {assertDefined} from 'common/assert_utils';
|
||||
import {Transition, TransitionsTrace} from 'trace/flickerlib/common';
|
||||
import {Parser} from 'trace/parser';
|
||||
import {Timestamp, TimestampType} from 'trace/timestamp';
|
||||
@@ -24,17 +25,17 @@ export class TracesParserTransitions extends AbstractTracesParser<Transition> {
|
||||
private readonly wmTransitionTrace: Parser<object> | undefined;
|
||||
private readonly shellTransitionTrace: Parser<object> | undefined;
|
||||
private readonly descriptors: string[];
|
||||
private decodedEntries: Transition[] | undefined;
|
||||
|
||||
constructor(parsers: Array<Parser<object>>) {
|
||||
super(parsers);
|
||||
|
||||
const wmTransitionTraces = this.parsers.filter(
|
||||
super();
|
||||
const wmTransitionTraces = parsers.filter(
|
||||
(it) => it.getTraceType() === TraceType.WM_TRANSITION
|
||||
);
|
||||
if (wmTransitionTraces.length > 0) {
|
||||
this.wmTransitionTrace = wmTransitionTraces[0];
|
||||
}
|
||||
const shellTransitionTraces = this.parsers.filter(
|
||||
const shellTransitionTraces = parsers.filter(
|
||||
(it) => it.getTraceType() === TraceType.SHELL_TRANSITION
|
||||
);
|
||||
if (shellTransitionTraces.length > 0) {
|
||||
@@ -49,54 +50,50 @@ export class TracesParserTransitions extends AbstractTracesParser<Transition> {
|
||||
}
|
||||
}
|
||||
|
||||
override canProvideEntries(): boolean {
|
||||
return this.wmTransitionTrace !== undefined && this.shellTransitionTrace !== undefined;
|
||||
}
|
||||
|
||||
getLengthEntries(): number {
|
||||
return this.getDecodedEntries().length;
|
||||
}
|
||||
|
||||
getEntry(index: number, timestampType: TimestampType): Transition {
|
||||
return this.getDecodedEntries()[index];
|
||||
}
|
||||
|
||||
private decodedEntries: Transition[] | undefined;
|
||||
getDecodedEntries(): Transition[] {
|
||||
if (this.decodedEntries === undefined) {
|
||||
if (this.wmTransitionTrace === undefined) {
|
||||
throw new Error('Missing WM Transition trace');
|
||||
}
|
||||
|
||||
if (this.shellTransitionTrace === undefined) {
|
||||
throw new Error('Missing Shell Transition trace');
|
||||
}
|
||||
|
||||
const wmTransitionEntries: Transition[] = [];
|
||||
for (let index = 0; index < this.wmTransitionTrace.getLengthEntries(); index++) {
|
||||
wmTransitionEntries.push(this.wmTransitionTrace.getEntry(index, TimestampType.REAL));
|
||||
}
|
||||
|
||||
const shellTransitionEntries: Transition[] = [];
|
||||
for (let index = 0; index < this.shellTransitionTrace.getLengthEntries(); index++) {
|
||||
shellTransitionEntries.push(this.shellTransitionTrace.getEntry(index, TimestampType.REAL));
|
||||
}
|
||||
|
||||
const transitionsTrace = new TransitionsTrace(
|
||||
wmTransitionEntries.concat(shellTransitionEntries)
|
||||
);
|
||||
|
||||
this.decodedEntries = transitionsTrace.asCompressed().entries as Transition[];
|
||||
override async parse() {
|
||||
if (this.wmTransitionTrace === undefined) {
|
||||
throw new Error('Missing WM Transition trace');
|
||||
}
|
||||
|
||||
return this.decodedEntries;
|
||||
if (this.shellTransitionTrace === undefined) {
|
||||
throw new Error('Missing Shell Transition trace');
|
||||
}
|
||||
|
||||
const wmTransitionEntries: Transition[] = [];
|
||||
for (let index = 0; index < this.wmTransitionTrace.getLengthEntries(); index++) {
|
||||
wmTransitionEntries.push(await this.wmTransitionTrace.getEntry(index, TimestampType.REAL));
|
||||
}
|
||||
|
||||
const shellTransitionEntries: Transition[] = [];
|
||||
for (let index = 0; index < this.shellTransitionTrace.getLengthEntries(); index++) {
|
||||
shellTransitionEntries.push(
|
||||
await this.shellTransitionTrace.getEntry(index, TimestampType.REAL)
|
||||
);
|
||||
}
|
||||
|
||||
const transitionsTrace = new TransitionsTrace(
|
||||
wmTransitionEntries.concat(shellTransitionEntries)
|
||||
);
|
||||
|
||||
this.decodedEntries = transitionsTrace.asCompressed().entries as Transition[];
|
||||
|
||||
await this.parseTimestamps();
|
||||
}
|
||||
|
||||
override getLengthEntries(): number {
|
||||
return assertDefined(this.decodedEntries).length;
|
||||
}
|
||||
|
||||
override getEntry(index: number, timestampType: TimestampType): Promise<Transition> {
|
||||
const entry = assertDefined(this.decodedEntries)[index];
|
||||
return Promise.resolve(entry);
|
||||
}
|
||||
|
||||
override getDescriptors(): string[] {
|
||||
return this.descriptors;
|
||||
}
|
||||
|
||||
getTraceType(): TraceType {
|
||||
override getTraceType(): TraceType {
|
||||
return TraceType.TRANSITION;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,20 +19,15 @@ import {Transition} from 'trace/flickerlib/common';
|
||||
import {Parser} from 'trace/parser';
|
||||
import {Timestamp, TimestampType} from 'trace/timestamp';
|
||||
import {TraceType} from 'trace/trace_type';
|
||||
import {TracesParserTransitions} from './traces_parser_transitions';
|
||||
|
||||
describe('ParserTransitions', () => {
|
||||
let parser: Parser<Transition>;
|
||||
|
||||
beforeAll(async () => {
|
||||
const wmSideParser = await UnitTestUtils.getParser(
|
||||
'traces/elapsed_and_real_timestamp/wm_transition_trace.pb'
|
||||
);
|
||||
const shellSideParser = await UnitTestUtils.getParser(
|
||||
'traces/elapsed_and_real_timestamp/shell_transition_trace.pb'
|
||||
);
|
||||
|
||||
parser = new TracesParserTransitions([wmSideParser, shellSideParser]);
|
||||
parser = await UnitTestUtils.getTracesParser([
|
||||
'traces/elapsed_and_real_timestamp/wm_transition_trace.pb',
|
||||
'traces/elapsed_and_real_timestamp/shell_transition_trace.pb',
|
||||
]);
|
||||
});
|
||||
|
||||
it('has expected trace type', () => {
|
||||
|
||||
@@ -18,9 +18,6 @@ import {browser, by, element, ElementFinder} from 'protractor';
|
||||
import {E2eTestUtils} from './utils';
|
||||
|
||||
describe('Cross-Tool Protocol', () => {
|
||||
const WINSCOPE_URL = 'http://localhost:8080';
|
||||
const REMOTE_TOOL_MOCK_URL = 'http://localhost:8081';
|
||||
|
||||
const TIMESTAMP_IN_BUGREPORT_MESSAGE = '1670509911000000000';
|
||||
const TIMESTAMP_FROM_REMOTE_TOOL_TO_WINSCOPE = '1670509912000000000';
|
||||
const TIMESTAMP_FROM_WINSCOPE_TO_REMOTE_TOOL = '1670509913000000000';
|
||||
@@ -28,12 +25,12 @@ describe('Cross-Tool Protocol', () => {
|
||||
beforeAll(async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
|
||||
await browser.manage().timeouts().implicitlyWait(15000);
|
||||
await checkServerIsUp('Remote tool mock', REMOTE_TOOL_MOCK_URL);
|
||||
await checkServerIsUp('Winscope', WINSCOPE_URL);
|
||||
await E2eTestUtils.checkServerIsUp('Remote tool mock', E2eTestUtils.REMOTE_TOOL_MOCK_URL);
|
||||
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await browser.get(REMOTE_TOOL_MOCK_URL);
|
||||
await browser.get(E2eTestUtils.REMOTE_TOOL_MOCK_URL);
|
||||
});
|
||||
|
||||
it('allows communication between remote tool and Winscope', async () => {
|
||||
@@ -56,14 +53,6 @@ describe('Cross-Tool Protocol', () => {
|
||||
await checkRemoteToolReceivedTimestamp();
|
||||
});
|
||||
|
||||
const checkServerIsUp = async (name: string, url: string) => {
|
||||
try {
|
||||
await browser.get(url);
|
||||
} catch (error) {
|
||||
fail(`${name} server (${url}) looks down. Did you start it?`);
|
||||
}
|
||||
};
|
||||
|
||||
const openWinscopeTabFromRemoteTool = async () => {
|
||||
await browser.switchTo().window(await getWindowHandleRemoteToolMock());
|
||||
const buttonElement = element(by.css('.button-open-winscope'));
|
||||
|
||||
@@ -23,10 +23,11 @@ describe('Upload traces', () => {
|
||||
beforeAll(async () => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = DEFAULT_TIMEOUT_MS;
|
||||
await browser.manage().timeouts().implicitlyWait(DEFAULT_TIMEOUT_MS);
|
||||
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
await browser.get(E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
|
||||
it('can process bugreport', async () => {
|
||||
|
||||
@@ -13,13 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import * as path from 'path';
|
||||
import {by, element} from 'protractor';
|
||||
import {browser, by, element} from 'protractor';
|
||||
import {CommonTestUtils} from '../common/utils';
|
||||
|
||||
class E2eTestUtils extends CommonTestUtils {
|
||||
static getProductionIndexHtmlPath(): string {
|
||||
return path.join(CommonTestUtils.getProjectRootPath(), 'dist/prod/index.html');
|
||||
static readonly WINSCOPE_URL = 'http://localhost:8080';
|
||||
static readonly REMOTE_TOOL_MOCK_URL = 'http://localhost:8081';
|
||||
|
||||
static async checkServerIsUp(name: string, url: string) {
|
||||
try {
|
||||
await browser.get(url);
|
||||
} catch (error) {
|
||||
fail(`${name} server (${url}) looks down. Did you start it?`);
|
||||
}
|
||||
}
|
||||
|
||||
static async uploadFixture(...paths: string[]) {
|
||||
|
||||
@@ -19,14 +19,16 @@ import {E2eTestUtils} from './utils';
|
||||
describe('Viewer InputMethodClients', () => {
|
||||
beforeAll(async () => {
|
||||
browser.manage().timeouts().implicitlyWait(1000);
|
||||
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
}),
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/InputMethodClients.pb');
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
|
||||
await browser.get(E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
|
||||
const viewerPresent = await element(by.css('viewer-input-method')).isPresent();
|
||||
expect(viewerPresent).toBeTruthy();
|
||||
});
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/InputMethodClients.pb');
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
|
||||
const viewerPresent = await element(by.css('viewer-input-method')).isPresent();
|
||||
expect(viewerPresent).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,16 +19,18 @@ import {E2eTestUtils} from './utils';
|
||||
describe('Viewer InputMethodManagerService', () => {
|
||||
beforeAll(async () => {
|
||||
browser.manage().timeouts().implicitlyWait(1000);
|
||||
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
}),
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture(
|
||||
'traces/elapsed_and_real_timestamp/InputMethodManagerService.pb'
|
||||
);
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
|
||||
await browser.get(E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
|
||||
const viewerPresent = await element(by.css('viewer-input-method')).isPresent();
|
||||
expect(viewerPresent).toBeTruthy();
|
||||
});
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture(
|
||||
'traces/elapsed_and_real_timestamp/InputMethodManagerService.pb'
|
||||
);
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
|
||||
const viewerPresent = await element(by.css('viewer-input-method')).isPresent();
|
||||
expect(viewerPresent).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,14 +19,16 @@ import {E2eTestUtils} from './utils';
|
||||
describe('Viewer InputMethodService', () => {
|
||||
beforeAll(async () => {
|
||||
browser.manage().timeouts().implicitlyWait(1000);
|
||||
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
}),
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/InputMethodService.pb');
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
|
||||
await browser.get(E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
|
||||
const viewerPresent = await element(by.css('viewer-input-method')).isPresent();
|
||||
expect(viewerPresent).toBeTruthy();
|
||||
});
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/InputMethodService.pb');
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
|
||||
const viewerPresent = await element(by.css('viewer-input-method')).isPresent();
|
||||
expect(viewerPresent).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,19 +19,21 @@ import {E2eTestUtils} from './utils';
|
||||
describe('Viewer ProtoLog', () => {
|
||||
beforeAll(async () => {
|
||||
browser.manage().timeouts().implicitlyWait(1000);
|
||||
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
}),
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/ProtoLog.pb');
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
|
||||
await browser.get(E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
|
||||
const isViewerRendered = await element(by.css('viewer-protolog')).isPresent();
|
||||
expect(isViewerRendered).toBeTruthy();
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/ProtoLog.pb');
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
|
||||
const isFirstMessageRendered = await element(
|
||||
by.css('viewer-protolog .scroll-messages .message')
|
||||
).isPresent();
|
||||
expect(isFirstMessageRendered).toBeTruthy();
|
||||
});
|
||||
const isViewerRendered = await element(by.css('viewer-protolog')).isPresent();
|
||||
expect(isViewerRendered).toBeTruthy();
|
||||
|
||||
const isFirstMessageRendered = await element(
|
||||
by.css('viewer-protolog .scroll-messages .message')
|
||||
).isPresent();
|
||||
expect(isFirstMessageRendered).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,8 @@ import {E2eTestUtils} from './utils';
|
||||
describe('Viewer ScreenRecording', () => {
|
||||
beforeAll(async () => {
|
||||
browser.manage().timeouts().implicitlyWait(1000);
|
||||
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
|
||||
await browser.get(E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
|
||||
it('processes trace and renders view', async () => {
|
||||
|
||||
@@ -19,14 +19,16 @@ import {E2eTestUtils} from './utils';
|
||||
describe('Viewer SurfaceFlinger', () => {
|
||||
beforeAll(async () => {
|
||||
browser.manage().timeouts().implicitlyWait(1000);
|
||||
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
}),
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb');
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
|
||||
await browser.get(E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
|
||||
const viewerPresent = await element(by.css('viewer-surface-flinger')).isPresent();
|
||||
expect(viewerPresent).toBeTruthy();
|
||||
});
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb');
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
|
||||
const viewerPresent = await element(by.css('viewer-surface-flinger')).isPresent();
|
||||
expect(viewerPresent).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,19 +19,20 @@ import {E2eTestUtils} from './utils';
|
||||
describe('Viewer Transactions', () => {
|
||||
beforeAll(async () => {
|
||||
browser.manage().timeouts().implicitlyWait(1000);
|
||||
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
}),
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/Transactions.pb');
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
|
||||
await browser.get(E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/Transactions.pb');
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
|
||||
const isViewerRendered = await element(by.css('viewer-transactions')).isPresent();
|
||||
expect(isViewerRendered).toBeTruthy();
|
||||
const isViewerRendered = await element(by.css('viewer-transactions')).isPresent();
|
||||
expect(isViewerRendered).toBeTruthy();
|
||||
|
||||
const isFirstEntryRendered = await element(
|
||||
by.css('viewer-transactions .scroll .entry')
|
||||
).isPresent();
|
||||
expect(isFirstEntryRendered).toBeTruthy();
|
||||
});
|
||||
const isFirstEntryRendered = await element(
|
||||
by.css('viewer-transactions .scroll .entry')
|
||||
).isPresent();
|
||||
expect(isFirstEntryRendered).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,8 @@ import {E2eTestUtils} from './utils';
|
||||
describe('Viewer Transitions', () => {
|
||||
beforeAll(async () => {
|
||||
browser.manage().timeouts().implicitlyWait(1000);
|
||||
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
|
||||
await browser.get(E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture(
|
||||
|
||||
@@ -18,15 +18,17 @@ import {E2eTestUtils} from './utils';
|
||||
|
||||
describe('Viewer WindowManager', () => {
|
||||
beforeAll(async () => {
|
||||
browser.manage().timeouts().implicitlyWait(1000);
|
||||
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
}),
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/WindowManager.pb');
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
browser.manage().timeouts().implicitlyWait(5000);
|
||||
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
|
||||
await browser.get(E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
|
||||
const viewerPresent = await element(by.css('viewer-window-manager')).isPresent();
|
||||
expect(viewerPresent).toBeTruthy();
|
||||
});
|
||||
it('processes trace and renders view', async () => {
|
||||
await E2eTestUtils.uploadFixture('traces/elapsed_and_real_timestamp/WindowManager.pb');
|
||||
await E2eTestUtils.closeSnackBarIfNeeded();
|
||||
await E2eTestUtils.clickViewTracesButton();
|
||||
|
||||
const viewerPresent = await element(by.css('viewer-window-manager')).isPresent();
|
||||
expect(viewerPresent).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,11 +17,13 @@ import {browser, by, element} from 'protractor';
|
||||
import {E2eTestUtils} from './utils';
|
||||
|
||||
describe('winscope', () => {
|
||||
beforeAll(() => {
|
||||
browser.get('file://' + E2eTestUtils.getProductionIndexHtmlPath());
|
||||
}),
|
||||
it('has title', () => {
|
||||
const title = element(by.css('.app-title'));
|
||||
expect(title.getText()).toContain('Winscope');
|
||||
});
|
||||
beforeAll(async () => {
|
||||
await E2eTestUtils.checkServerIsUp('Winscope', E2eTestUtils.WINSCOPE_URL);
|
||||
await browser.get(E2eTestUtils.WINSCOPE_URL);
|
||||
});
|
||||
|
||||
it('has title', () => {
|
||||
const title = element(by.css('.app-title'));
|
||||
expect(title.getText()).toContain('Winscope');
|
||||
});
|
||||
});
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -18,12 +18,11 @@ import {Timestamp} from 'trace/timestamp';
|
||||
import {AbsoluteFrameIndex, Trace} from 'trace/trace';
|
||||
|
||||
export class TraceUtils {
|
||||
static extractEntries<T>(trace: Trace<T>): T[] {
|
||||
const entries = new Array<T>();
|
||||
trace.forEachEntry((entry) => {
|
||||
entries.push(entry.getValue());
|
||||
static async extractEntries<T>(trace: Trace<T>): Promise<T[]> {
|
||||
const promises = trace.mapEntry(async (entry, index) => {
|
||||
return await entry.getValue();
|
||||
});
|
||||
return entries;
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
|
||||
static extractTimestamps<T>(trace: Trace<T>): Timestamp[] {
|
||||
@@ -34,11 +33,12 @@ export class TraceUtils {
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
static extractFrames<T>(trace: Trace<T>): Map<AbsoluteFrameIndex, T[]> {
|
||||
static async extractFrames<T>(trace: Trace<T>): Promise<Map<AbsoluteFrameIndex, T[]>> {
|
||||
const frames = new Map<AbsoluteFrameIndex, T[]>();
|
||||
trace.forEachFrame((frame, index) => {
|
||||
frames.set(index, TraceUtils.extractEntries(frame));
|
||||
const promises = trace.mapFrame(async (frame, index) => {
|
||||
frames.set(index, await TraceUtils.extractEntries(frame));
|
||||
});
|
||||
await Promise.all(promises);
|
||||
return frames;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,31 +14,37 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {assertDefined} from 'common/assert_utils';
|
||||
import {AbsoluteFrameIndex} from 'trace/trace';
|
||||
import {Traces} from 'trace/traces';
|
||||
import {TraceType} from 'trace/trace_type';
|
||||
import {TraceUtils} from './trace_utils';
|
||||
|
||||
export class TracesUtils {
|
||||
static extractEntries(traces: Traces): Map<TraceType, Array<{}>> {
|
||||
static async extractEntries(traces: Traces): Promise<Map<TraceType, Array<{}>>> {
|
||||
const entries = new Map<TraceType, Array<{}>>();
|
||||
|
||||
traces.forEachTrace((trace) => {
|
||||
entries.set(trace.type, TraceUtils.extractEntries(trace));
|
||||
const promises = traces.mapTrace(async (trace) => {
|
||||
entries.set(trace.type, await TraceUtils.extractEntries(trace));
|
||||
});
|
||||
await Promise.all(promises);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
static extractFrames(traces: Traces): Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>> {
|
||||
static async extractFrames(
|
||||
traces: Traces
|
||||
): Promise<Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>> {
|
||||
const frames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
|
||||
|
||||
traces.forEachFrame((frame, index) => {
|
||||
const framePromises = traces.mapFrame(async (frame, index) => {
|
||||
frames.set(index, new Map<TraceType, Array<{}>>());
|
||||
frame.forEachTrace((trace, type) => {
|
||||
frames.get(index)?.set(type, TraceUtils.extractEntries(trace));
|
||||
const tracePromises = frame.mapTrace(async (trace, type) => {
|
||||
assertDefined(frames.get(index)).set(type, await TraceUtils.extractEntries(trace));
|
||||
});
|
||||
await Promise.all(tracePromises);
|
||||
});
|
||||
await Promise.all(framePromises);
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import {ParserFactory} from 'parsers/parser_factory';
|
||||
import {TracesParserFactory} from 'parsers/traces_parser_factory';
|
||||
import {CommonTestUtils} from 'test/common/utils';
|
||||
import {LayerTraceEntry, WindowManagerState} from 'trace/flickerlib/common';
|
||||
import {Parser} from 'trace/parser';
|
||||
@@ -43,6 +44,15 @@ class UnitTestUtils extends CommonTestUtils {
|
||||
return parsers[0].parser;
|
||||
}
|
||||
|
||||
static async getTracesParser(filenames: string[]): Promise<Parser<object>> {
|
||||
const parsers = await Promise.all(
|
||||
filenames.map((filename) => UnitTestUtils.getParser(filename))
|
||||
);
|
||||
const tracesParsers = await new TracesParserFactory().createParsers(parsers);
|
||||
expect(tracesParsers.length).toEqual(1);
|
||||
return tracesParsers[0];
|
||||
}
|
||||
|
||||
static async getWindowManagerState(): Promise<WindowManagerState> {
|
||||
return UnitTestUtils.getTraceEntry('traces/elapsed_timestamp/WindowManager.pb');
|
||||
}
|
||||
|
||||
@@ -21,7 +21,12 @@
|
||||
"rootWindowContainer",
|
||||
"windowContainer",
|
||||
"children",
|
||||
"stableId"
|
||||
"stableId",
|
||||
"boxPos",
|
||||
"classnameIndex",
|
||||
"depth",
|
||||
"zOrderRelativeOfId",
|
||||
"isRootLayer"
|
||||
],
|
||||
"intDefColumn": {
|
||||
"WindowLayoutParams.type": "android.view.WindowManager.LayoutParams.WindowType",
|
||||
@@ -34,6 +39,7 @@
|
||||
"WindowLayoutParams.behavior": "android.view.WindowInsetsController.Behavior",
|
||||
"WindowLayoutParams.fitInsetsSides": "android.view.WindowInsets.Side.InsetsSide",
|
||||
"InputWindowInfoProto.layoutParamsFlags": "android.view.WindowManager.LayoutParams.Flags",
|
||||
"InputWindowInfoProto.inputConfig": "android.view.InputWindowHandle.InputConfigFlags",
|
||||
"Configuration.windowingMode": "android.app.WindowConfiguration.WindowingMode",
|
||||
"WindowConfiguration.windowingMode": "android.app.WindowConfiguration.WindowingMode",
|
||||
"Configuration.orientation": "android.content.pm.ActivityInfo.ScreenOrientation",
|
||||
|
||||
@@ -20,9 +20,7 @@ import intDefMapping from '../../../../../../prebuilts/misc/common/winscope/intD
|
||||
import {
|
||||
toActiveBuffer,
|
||||
toColor,
|
||||
toColor3,
|
||||
toInsets,
|
||||
toMatrix22,
|
||||
toPoint,
|
||||
toPointF,
|
||||
toRect,
|
||||
@@ -66,6 +64,9 @@ export class ObjectFormatter {
|
||||
* @return The "true" properties of the entry as described above
|
||||
*/
|
||||
static getProperties(entry: any): string[] {
|
||||
if (entry === null || entry === undefined) {
|
||||
return [];
|
||||
}
|
||||
const props: string[] = [];
|
||||
let obj = entry;
|
||||
|
||||
@@ -171,7 +172,7 @@ export class ObjectFormatter {
|
||||
case `ActiveBufferProto`:
|
||||
return toActiveBuffer(obj);
|
||||
case `Color3`:
|
||||
return toColor3(obj);
|
||||
return toColor(obj, /* hasAlpha */ false);
|
||||
case `ColorProto`:
|
||||
return toColor(obj);
|
||||
case `Long`:
|
||||
@@ -184,8 +185,6 @@ export class ObjectFormatter {
|
||||
// definition of insets and rects uses the same object type
|
||||
case `RectProto`:
|
||||
return key.toLowerCase().includes('insets') ? toInsets(obj) : toRect(obj);
|
||||
case `Matrix22`:
|
||||
return toMatrix22(obj);
|
||||
case `FloatRectProto`:
|
||||
return toRectF(obj);
|
||||
case `RegionProto`:
|
||||
|
||||
@@ -42,6 +42,8 @@ const WindowState = require('flicker').android.tools.common.traces.wm.WindowStat
|
||||
const WindowToken = require('flicker').android.tools.common.traces.wm.WindowToken;
|
||||
|
||||
// SF
|
||||
const HwcCompositionType =
|
||||
require('flicker').android.tools.common.traces.surfaceflinger.HwcCompositionType;
|
||||
const Layer = require('flicker').android.tools.common.traces.surfaceflinger.Layer;
|
||||
const LayerProperties =
|
||||
require('flicker').android.tools.common.traces.surfaceflinger.LayerProperties;
|
||||
@@ -76,10 +78,8 @@ const WmTransitionData = require('flicker').android.tools.common.traces.wm.WmTra
|
||||
// Common
|
||||
const Size = require('flicker').android.tools.common.datatypes.Size;
|
||||
const ActiveBuffer = require('flicker').android.tools.common.datatypes.ActiveBuffer;
|
||||
const Color3 = require('flicker').android.tools.common.datatypes.Color3;
|
||||
const Color = require('flicker').android.tools.common.datatypes.Color;
|
||||
const Insets = require('flicker').android.tools.common.datatypes.Insets;
|
||||
const Matrix22 = require('flicker').android.tools.common.datatypes.Matrix22;
|
||||
const Matrix33 = require('flicker').android.tools.common.datatypes.Matrix33;
|
||||
const PlatformConsts = require('flicker').android.tools.common.PlatformConsts;
|
||||
const Rotation = require('flicker').android.tools.common.Rotation;
|
||||
@@ -93,14 +93,12 @@ const TimestampFactory = require('flicker').android.tools.common.TimestampFactor
|
||||
|
||||
const EMPTY_SIZE = Size.Companion.EMPTY;
|
||||
const EMPTY_BUFFER = ActiveBuffer.Companion.EMPTY;
|
||||
const EMPTY_COLOR3 = Color3.Companion.EMPTY;
|
||||
const EMPTY_COLOR = Color.Companion.EMPTY;
|
||||
const EMPTY_INSETS = Insets.Companion.EMPTY;
|
||||
const EMPTY_RECT = Rect.Companion.EMPTY;
|
||||
const EMPTY_RECTF = RectF.Companion.EMPTY;
|
||||
const EMPTY_POINT = Point.Companion.EMPTY;
|
||||
const EMPTY_POINTF = PointF.Companion.EMPTY;
|
||||
const EMPTY_MATRIX22 = Matrix22.Companion.EMPTY;
|
||||
const EMPTY_MATRIX33 = Matrix33.Companion.identity(0, 0);
|
||||
const EMPTY_TRANSFORM = new Transform(0, EMPTY_MATRIX33);
|
||||
|
||||
@@ -128,27 +126,17 @@ function toActiveBuffer(proto) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
function toColor3(proto) {
|
||||
function toColor(proto, hasAlpha = true) {
|
||||
if (proto == null) {
|
||||
return EMPTY_COLOR;
|
||||
}
|
||||
const r = proto.r ?? 0;
|
||||
const g = proto.g ?? 0;
|
||||
const b = proto.b ?? 0;
|
||||
if (r || g || b) {
|
||||
return new Color3(r, g, b);
|
||||
let a = proto.a;
|
||||
if (a === null && !hasAlpha) {
|
||||
a = 1;
|
||||
}
|
||||
return EMPTY_COLOR3;
|
||||
}
|
||||
|
||||
function toColor(proto) {
|
||||
if (proto == null) {
|
||||
return EMPTY_COLOR;
|
||||
}
|
||||
const r = proto.r ?? 0;
|
||||
const g = proto.g ?? 0;
|
||||
const b = proto.b ?? 0;
|
||||
const a = proto.a ?? 0;
|
||||
if (r || g || b || a) {
|
||||
return new Color(r, g, b, a);
|
||||
}
|
||||
@@ -194,6 +182,22 @@ function toInsets(proto) {
|
||||
return EMPTY_INSETS;
|
||||
}
|
||||
|
||||
function toCropRect(proto) {
|
||||
if (proto == null) return EMPTY_RECT;
|
||||
|
||||
const right = proto.right || 0;
|
||||
const left = proto.left || 0;
|
||||
const bottom = proto.bottom || 0;
|
||||
const top = proto.top || 0;
|
||||
|
||||
// crop (0,0) (-1,-1) means no crop
|
||||
if (right == -1 && left == 0 && bottom == -1 && top == 0) EMPTY_RECT;
|
||||
|
||||
if (right - left <= 0 || bottom - top <= 0) return EMPTY_RECT;
|
||||
|
||||
return Rect.Companion.from(left, top, right, bottom);
|
||||
}
|
||||
|
||||
function toRect(proto) {
|
||||
if (proto == null) {
|
||||
return EMPTY_RECT;
|
||||
@@ -261,22 +265,6 @@ function toTransform(proto) {
|
||||
return EMPTY_TRANSFORM;
|
||||
}
|
||||
|
||||
function toMatrix22(proto) {
|
||||
if (proto == null) {
|
||||
return EMPTY_MATRIX22;
|
||||
}
|
||||
const dsdx = proto.dsdx ?? 0;
|
||||
const dtdx = proto.dtdx ?? 0;
|
||||
const dsdy = proto.dsdy ?? 0;
|
||||
const dtdy = proto.dtdy ?? 0;
|
||||
|
||||
if (dsdx || dtdx || dsdy || dtdy) {
|
||||
return new Matrix22(dsdx, dtdx, dsdy, dtdy);
|
||||
}
|
||||
|
||||
return EMPTY_MATRIX22;
|
||||
}
|
||||
|
||||
export {
|
||||
Activity,
|
||||
Configuration,
|
||||
@@ -298,13 +286,13 @@ export {
|
||||
WindowManagerState,
|
||||
WindowManagerTraceEntryBuilder,
|
||||
// SF
|
||||
HwcCompositionType,
|
||||
Layer,
|
||||
LayerProperties,
|
||||
LayerTraceEntry,
|
||||
LayerTraceEntryBuilder,
|
||||
LayersTrace,
|
||||
Transform,
|
||||
Matrix22,
|
||||
Matrix33,
|
||||
Display,
|
||||
// Eventlog
|
||||
@@ -328,7 +316,6 @@ export {
|
||||
Size,
|
||||
ActiveBuffer,
|
||||
Color,
|
||||
Color3,
|
||||
Insets,
|
||||
PlatformConsts,
|
||||
Point,
|
||||
@@ -343,24 +330,21 @@ export {
|
||||
toSize,
|
||||
toActiveBuffer,
|
||||
toColor,
|
||||
toColor3,
|
||||
toCropRect,
|
||||
toInsets,
|
||||
toPoint,
|
||||
toPointF,
|
||||
toRect,
|
||||
toRectF,
|
||||
toRegion,
|
||||
toMatrix22,
|
||||
toTransform,
|
||||
// Constants
|
||||
EMPTY_BUFFER,
|
||||
EMPTY_COLOR3,
|
||||
EMPTY_COLOR,
|
||||
EMPTY_RECT,
|
||||
EMPTY_RECTF,
|
||||
EMPTY_POINT,
|
||||
EMPTY_POINTF,
|
||||
EMPTY_MATRIX22,
|
||||
EMPTY_MATRIX33,
|
||||
EMPTY_TRANSFORM,
|
||||
};
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
HwcCompositionType,
|
||||
Layer,
|
||||
LayerProperties,
|
||||
Rect,
|
||||
toActiveBuffer,
|
||||
toColor,
|
||||
toCropRect,
|
||||
toRect,
|
||||
toRectF,
|
||||
toRegion,
|
||||
@@ -47,10 +49,7 @@ Layer.fromProto = (proto: any, excludesCompositionState = false): Layer => {
|
||||
const inputRegion = toRegion(
|
||||
proto.inputWindowInfo ? proto.inputWindowInfo.touchableRegion : null
|
||||
);
|
||||
let crop: Rect;
|
||||
if (proto.crop) {
|
||||
crop = toRect(proto.crop);
|
||||
}
|
||||
const crop: Rect = toCropRect(proto.crop);
|
||||
|
||||
const properties = new LayerProperties(
|
||||
visibleRegion,
|
||||
@@ -67,7 +66,7 @@ Layer.fromProto = (proto: any, excludesCompositionState = false): Layer => {
|
||||
sourceBounds,
|
||||
/* effectiveScalingMode */ proto.effectiveScalingMode,
|
||||
bufferTransform,
|
||||
/* hwcCompositionType */ proto.hwcCompositionType,
|
||||
/* hwcCompositionType */ new HwcCompositionType(proto.hwcCompositionType),
|
||||
hwcCrop,
|
||||
hwcFrame,
|
||||
/* backgroundBlurRadius */ proto.backgroundBlurRadius,
|
||||
|
||||
@@ -27,14 +27,12 @@ Activity.fromProto = (proto: any, nextSeq: () => number): Activity => {
|
||||
/* protoChildren */ proto.windowToken.windowContainer?.children ?? [],
|
||||
/* isActivityInTree */ true,
|
||||
/* computedZ */ nextSeq,
|
||||
/* nameOverride */ null,
|
||||
/* nameOverride */ proto.name,
|
||||
/* identifierOverride */ proto.identifier
|
||||
);
|
||||
|
||||
const entry = new Activity(
|
||||
proto.name,
|
||||
proto.state,
|
||||
proto.visible,
|
||||
proto.frontOfTask,
|
||||
proto.procId,
|
||||
proto.translucent,
|
||||
|
||||
@@ -39,7 +39,8 @@ WindowContainer.fromProto = (
|
||||
nextSeq: () => number,
|
||||
nameOverride: string | null = null,
|
||||
identifierOverride: string | null = null,
|
||||
tokenOverride: any = null
|
||||
tokenOverride: any = null,
|
||||
visibleOverride: boolean | null = null
|
||||
): WindowContainer => {
|
||||
if (proto == null) {
|
||||
return null;
|
||||
@@ -61,7 +62,7 @@ WindowContainer.fromProto = (
|
||||
token,
|
||||
proto.orientation,
|
||||
proto.surfaceControl?.layerId ?? 0,
|
||||
proto.visible,
|
||||
visibleOverride ?? proto.visible,
|
||||
config,
|
||||
children,
|
||||
containerOrder
|
||||
@@ -105,7 +106,7 @@ WindowContainer.childrenFromProto = (
|
||||
};
|
||||
|
||||
function createConfigurationContainer(proto: any): ConfigurationContainer {
|
||||
const entry = new ConfigurationContainer(
|
||||
const entry = ConfigurationContainer.Companion.from(
|
||||
createConfiguration(proto?.overrideConfiguration ?? null),
|
||||
createConfiguration(proto?.fullConfiguration ?? null),
|
||||
createConfiguration(proto?.mergedOverrideConfiguration ?? null)
|
||||
@@ -125,7 +126,7 @@ function createConfiguration(proto: any): Configuration {
|
||||
windowConfiguration = createWindowConfiguration(proto.windowConfiguration);
|
||||
}
|
||||
|
||||
return new Configuration(
|
||||
return Configuration.Companion.from(
|
||||
windowConfiguration,
|
||||
proto?.densityDpi ?? 0,
|
||||
proto?.orientation ?? 0,
|
||||
@@ -138,7 +139,7 @@ function createConfiguration(proto: any): Configuration {
|
||||
}
|
||||
|
||||
function createWindowConfiguration(proto: any): WindowConfiguration {
|
||||
return new WindowConfiguration(
|
||||
return WindowConfiguration.Companion.from(
|
||||
toRect(proto.appBounds),
|
||||
toRect(proto.bounds),
|
||||
toRect(proto.maxBounds),
|
||||
|
||||
@@ -34,9 +34,9 @@ WindowManagerState.fromProto = (
|
||||
realToElapsedTimeOffsetNs: bigint | undefined = undefined,
|
||||
useElapsedTime = false
|
||||
): WindowManagerState => {
|
||||
const inputMethodWIndowAppToken = '';
|
||||
let inputMethodWIndowAppToken = '';
|
||||
if (proto.inputMethodWindow != null) {
|
||||
proto.inputMethodWindow.hashCode.toString(16);
|
||||
inputMethodWIndowAppToken = proto.inputMethodWindow.hashCode.toString(16);
|
||||
}
|
||||
|
||||
let parseOrder = 0;
|
||||
@@ -47,21 +47,21 @@ WindowManagerState.fromProto = (
|
||||
);
|
||||
const policy = createWindowManagerPolicy(proto.policy);
|
||||
|
||||
const entry = new WindowManagerTraceEntryBuilder(
|
||||
`${elapsedTimestamp}`,
|
||||
policy,
|
||||
proto.focusedApp,
|
||||
proto.focusedDisplayId,
|
||||
proto.focusedWindow?.title ?? '',
|
||||
inputMethodWIndowAppToken,
|
||||
proto.rootWindowContainer.isHomeRecentsComponent,
|
||||
proto.displayFrozen,
|
||||
proto.rootWindowContainer.pendingActivities.map((it: any) => it.title),
|
||||
rootWindowContainer,
|
||||
keyguardControllerState,
|
||||
where,
|
||||
`${realToElapsedTimeOffsetNs ?? 0}`
|
||||
).build();
|
||||
const entry = new WindowManagerTraceEntryBuilder()
|
||||
.setElapsedTimestamp(`${elapsedTimestamp}`)
|
||||
.setPolicy(policy)
|
||||
.setFocusedApp(proto.focusedApp)
|
||||
.setFocusedDisplayId(proto.focusedDisplayId)
|
||||
.setFocusedWindow(proto.focusedWindow?.title ?? '')
|
||||
.setInputMethodWindowAppToken(inputMethodWIndowAppToken)
|
||||
.setIsHomeRecentsComponent(proto.rootWindowContainer.isHomeRecentsComponent)
|
||||
.setIsDisplayFrozen(proto.displayFrozen)
|
||||
.setPendingActivities(proto.rootWindowContainer.pendingActivities.map((it: any) => it.title))
|
||||
.setRoot(rootWindowContainer)
|
||||
.setKeyguardControllerState(keyguardControllerState)
|
||||
.setWhere(where)
|
||||
.setRealToElapsedTimeOffsetNs(`${realToElapsedTimeOffsetNs ?? 0}`)
|
||||
.build();
|
||||
|
||||
addAttributes(entry, proto, realToElapsedTimeOffsetNs === undefined || useElapsedTime);
|
||||
return entry;
|
||||
|
||||
@@ -26,9 +26,9 @@ export class FrameMapper {
|
||||
|
||||
constructor(private traces: Traces) {}
|
||||
|
||||
computeMapping() {
|
||||
async computeMapping() {
|
||||
this.pickMostReliableTraceAndSetInitialFrameInfo();
|
||||
this.propagateFrameInfoToOtherTraces();
|
||||
await this.propagateFrameInfoToOtherTraces();
|
||||
}
|
||||
|
||||
private pickMostReliableTraceAndSetInitialFrameInfo() {
|
||||
@@ -56,9 +56,9 @@ export class FrameMapper {
|
||||
trace.setFrameInfo(frameMap, frameMap.getFullTraceFramesRange());
|
||||
}
|
||||
|
||||
private propagateFrameInfoToOtherTraces() {
|
||||
private async propagateFrameInfoToOtherTraces() {
|
||||
this.tryPropagateFromScreenRecordingToSurfaceFlinger();
|
||||
this.tryPropagateFromSurfaceFlingerToTransactions();
|
||||
await this.tryPropagateFromSurfaceFlingerToTransactions();
|
||||
this.tryPropagateFromTransactionsToWindowManager();
|
||||
this.tryPropagateFromWindowManagerToProtoLog();
|
||||
this.tryPropagateFromWindowManagerToIme();
|
||||
@@ -90,7 +90,7 @@ export class FrameMapper {
|
||||
surfaceFlinger.setFrameInfo(frameMap, frameMap.getFullTraceFramesRange());
|
||||
}
|
||||
|
||||
private tryPropagateFromSurfaceFlingerToTransactions() {
|
||||
private async tryPropagateFromSurfaceFlingerToTransactions() {
|
||||
const frameMapBuilder = this.tryStartFrameMapping(
|
||||
TraceType.SURFACE_FLINGER,
|
||||
TraceType.TRANSACTIONS
|
||||
@@ -103,14 +103,16 @@ export class FrameMapper {
|
||||
const surfaceFlinger = assertDefined(this.traces.getTrace(TraceType.SURFACE_FLINGER));
|
||||
|
||||
const vsyncIdToFrames = new Map<bigint, FramesRange>();
|
||||
surfaceFlinger.forEachEntry((srcEntry) => {
|
||||
const vsyncId = this.getVsyncIdProperty(srcEntry, 'vSyncId');
|
||||
|
||||
for (let srcEntryIndex = 0; srcEntryIndex < surfaceFlinger.lengthEntries; ++srcEntryIndex) {
|
||||
const srcEntry = surfaceFlinger.getEntry(srcEntryIndex);
|
||||
const vsyncId = await this.getVsyncIdProperty(srcEntry, 'vSyncId');
|
||||
if (vsyncId === undefined) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
const srcFrames = srcEntry.getFramesRange();
|
||||
if (!srcFrames) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
let frames = vsyncIdToFrames.get(vsyncId);
|
||||
if (!frames) {
|
||||
@@ -119,19 +121,20 @@ export class FrameMapper {
|
||||
frames.start = Math.min(frames.start, srcFrames.start);
|
||||
frames.end = Math.max(frames.end, srcFrames.end);
|
||||
vsyncIdToFrames.set(vsyncId, frames);
|
||||
});
|
||||
}
|
||||
|
||||
transactions.forEachEntry((dstEntry) => {
|
||||
const vsyncId = this.getVsyncIdProperty(dstEntry, 'vsyncId');
|
||||
for (let dstEntryIndex = 0; dstEntryIndex < transactions.lengthEntries; ++dstEntryIndex) {
|
||||
const dstEntry = transactions.getEntry(dstEntryIndex);
|
||||
const vsyncId = await this.getVsyncIdProperty(dstEntry, 'vsyncId');
|
||||
if (vsyncId === undefined) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
const frames = vsyncIdToFrames.get(vsyncId);
|
||||
if (frames === undefined) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
frameMapBuilder.setFrames(dstEntry.getIndex(), frames);
|
||||
});
|
||||
}
|
||||
|
||||
const frameMap = frameMapBuilder.build();
|
||||
transactions.setFrameInfo(frameMap, frameMap.getFullTraceFramesRange());
|
||||
@@ -276,8 +279,11 @@ export class FrameMapper {
|
||||
return new FrameMapBuilder(dstTrace.lengthEntries, lengthFrames);
|
||||
}
|
||||
|
||||
private getVsyncIdProperty(entry: TraceEntry<object>, propertyKey: string): bigint | undefined {
|
||||
const entryValue = entry.getValue();
|
||||
private async getVsyncIdProperty(
|
||||
entry: TraceEntry<object>,
|
||||
propertyKey: string
|
||||
): Promise<bigint | undefined> {
|
||||
const entryValue = await entry.getValue();
|
||||
const vsyncId = (entryValue as any)[propertyKey];
|
||||
if (vsyncId === undefined) {
|
||||
console.error(`Failed to get trace entry's '${propertyKey}' property:`, entryValue);
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('FrameMapper', () => {
|
||||
let windowManager: Trace<WindowManagerState>;
|
||||
let traces: Traces;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
// Frames F0 F1
|
||||
// |<------>| |<->|
|
||||
// PROTO_LOG: 0 1 2 3 4 5
|
||||
@@ -75,10 +75,10 @@ describe('FrameMapper', () => {
|
||||
traces = new Traces();
|
||||
traces.setTrace(TraceType.PROTO_LOG, protoLog);
|
||||
traces.setTrace(TraceType.WINDOW_MANAGER, windowManager);
|
||||
new FrameMapper(traces).computeMapping();
|
||||
await new FrameMapper(traces).computeMapping();
|
||||
});
|
||||
|
||||
it('associates entries/frames', () => {
|
||||
it('associates entries/frames', async () => {
|
||||
const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
|
||||
expectedFrames.set(
|
||||
0,
|
||||
@@ -95,7 +95,7 @@ describe('FrameMapper', () => {
|
||||
])
|
||||
);
|
||||
|
||||
expect(TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
|
||||
expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('FrameMapper', () => {
|
||||
let windowManager: Trace<WindowManagerState>;
|
||||
let traces: Traces;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
// IME: 0--1--2 3
|
||||
// | |
|
||||
// WINDOW_MANAGER: 0 1 2
|
||||
@@ -131,10 +131,10 @@ describe('FrameMapper', () => {
|
||||
traces = new Traces();
|
||||
traces.setTrace(TraceType.INPUT_METHOD_CLIENTS, ime);
|
||||
traces.setTrace(TraceType.WINDOW_MANAGER, windowManager);
|
||||
new FrameMapper(traces).computeMapping();
|
||||
await new FrameMapper(traces).computeMapping();
|
||||
});
|
||||
|
||||
it('associates entries/frames', () => {
|
||||
it('associates entries/frames', async () => {
|
||||
const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
|
||||
expectedFrames.set(
|
||||
0,
|
||||
@@ -158,7 +158,7 @@ describe('FrameMapper', () => {
|
||||
])
|
||||
);
|
||||
|
||||
expect(TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
|
||||
expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,7 +167,7 @@ describe('FrameMapper', () => {
|
||||
let transactions: Trace<object>;
|
||||
let traces: Traces;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
// WINDOW_MANAGER: 0 1 2 3
|
||||
// | | | \
|
||||
// TRANSACTIONS: 0 1 2--3 4 5 ... 6 <-- ignored (not connected) because too far
|
||||
@@ -207,10 +207,10 @@ describe('FrameMapper', () => {
|
||||
traces = new Traces();
|
||||
traces.setTrace(TraceType.WINDOW_MANAGER, windowManager);
|
||||
traces.setTrace(TraceType.TRANSACTIONS, transactions);
|
||||
new FrameMapper(traces).computeMapping();
|
||||
await new FrameMapper(traces).computeMapping();
|
||||
});
|
||||
|
||||
it('associates entries/frames', () => {
|
||||
it('associates entries/frames', async () => {
|
||||
const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
|
||||
expectedFrames.set(
|
||||
0,
|
||||
@@ -255,7 +255,7 @@ describe('FrameMapper', () => {
|
||||
])
|
||||
);
|
||||
|
||||
expect(TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
|
||||
expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -264,7 +264,7 @@ describe('FrameMapper', () => {
|
||||
let surfaceFlinger: Trace<LayerTraceEntry>;
|
||||
let traces: Traces;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
// TRANSACTIONS: 0 1--2 3 4
|
||||
// \ \ \
|
||||
// \ \ \
|
||||
@@ -292,16 +292,16 @@ describe('FrameMapper', () => {
|
||||
traces = new Traces();
|
||||
traces.setTrace(TraceType.TRANSACTIONS, transactions);
|
||||
traces.setTrace(TraceType.SURFACE_FLINGER, surfaceFlinger);
|
||||
new FrameMapper(traces).computeMapping();
|
||||
await new FrameMapper(traces).computeMapping();
|
||||
});
|
||||
|
||||
it('associates entries/frames', () => {
|
||||
it('associates entries/frames', async () => {
|
||||
const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
|
||||
expectedFrames.set(
|
||||
0,
|
||||
new Map<TraceType, Array<{}>>([
|
||||
[TraceType.TRANSACTIONS, [transactions.getEntry(0).getValue()]],
|
||||
[TraceType.SURFACE_FLINGER, [surfaceFlinger.getEntry(0).getValue()]],
|
||||
[TraceType.TRANSACTIONS, [await transactions.getEntry(0).getValue()]],
|
||||
[TraceType.SURFACE_FLINGER, [await surfaceFlinger.getEntry(0).getValue()]],
|
||||
])
|
||||
);
|
||||
expectedFrames.set(
|
||||
@@ -309,20 +309,20 @@ describe('FrameMapper', () => {
|
||||
new Map<TraceType, Array<{}>>([
|
||||
[
|
||||
TraceType.TRANSACTIONS,
|
||||
[transactions.getEntry(1).getValue(), transactions.getEntry(2).getValue()],
|
||||
[await transactions.getEntry(1).getValue(), await transactions.getEntry(2).getValue()],
|
||||
],
|
||||
[TraceType.SURFACE_FLINGER, [surfaceFlinger.getEntry(1).getValue()]],
|
||||
[TraceType.SURFACE_FLINGER, [await surfaceFlinger.getEntry(1).getValue()]],
|
||||
])
|
||||
);
|
||||
expectedFrames.set(
|
||||
2,
|
||||
new Map<TraceType, Array<{}>>([
|
||||
[TraceType.TRANSACTIONS, [transactions.getEntry(3).getValue()]],
|
||||
[TraceType.SURFACE_FLINGER, [surfaceFlinger.getEntry(2).getValue()]],
|
||||
[TraceType.TRANSACTIONS, [await transactions.getEntry(3).getValue()]],
|
||||
[TraceType.SURFACE_FLINGER, [await surfaceFlinger.getEntry(2).getValue()]],
|
||||
])
|
||||
);
|
||||
|
||||
expect(TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
|
||||
expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -331,7 +331,7 @@ describe('FrameMapper', () => {
|
||||
let screenRecording: Trace<ScreenRecordingTraceEntry>;
|
||||
let traces: Traces;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
// SURFACE_FLINGER: 0 1 2--- 3 4 5 6
|
||||
// \ \ \ \
|
||||
// \ \ \ \
|
||||
@@ -365,10 +365,10 @@ describe('FrameMapper', () => {
|
||||
traces = new Traces();
|
||||
traces.setTrace(TraceType.SURFACE_FLINGER, surfaceFlinger);
|
||||
traces.setTrace(TraceType.SCREEN_RECORDING, screenRecording);
|
||||
new FrameMapper(traces).computeMapping();
|
||||
await new FrameMapper(traces).computeMapping();
|
||||
});
|
||||
|
||||
it('associates entries/frames', () => {
|
||||
it('associates entries/frames', async () => {
|
||||
const expectedFrames = new Map<AbsoluteFrameIndex, Map<TraceType, Array<{}>>>();
|
||||
expectedFrames.set(
|
||||
0,
|
||||
@@ -413,7 +413,7 @@ describe('FrameMapper', () => {
|
||||
])
|
||||
);
|
||||
|
||||
expect(TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
|
||||
expect(await TracesUtils.extractFrames(traces)).toEqual(expectedFrames);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export interface Parser<T> {
|
||||
getTraceType(): TraceType;
|
||||
getLengthEntries(): number;
|
||||
getTimestamps(type: TimestampType): Timestamp[] | undefined;
|
||||
getEntry(index: number, timestampType: TimestampType): T;
|
||||
getEntry(index: number, timestampType: TimestampType): Promise<T>;
|
||||
|
||||
getDescriptors(): string[];
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import {Parser} from './parser';
|
||||
import {RealTimestamp, Timestamp, TimestampType} from './timestamp';
|
||||
import {TraceFile} from './trace_file';
|
||||
import {TraceType} from './trace_type';
|
||||
|
||||
export class ParserMock<T> implements Parser<T> {
|
||||
@@ -30,10 +29,6 @@ export class ParserMock<T> implements Parser<T> {
|
||||
return TraceType.SURFACE_FLINGER;
|
||||
}
|
||||
|
||||
getTraceFile(): TraceFile {
|
||||
return new TraceFile(new File([], 'file_name'));
|
||||
}
|
||||
|
||||
getLengthEntries(): number {
|
||||
return this.entries.length;
|
||||
}
|
||||
@@ -45,8 +40,8 @@ export class ParserMock<T> implements Parser<T> {
|
||||
return this.timestamps;
|
||||
}
|
||||
|
||||
getEntry(index: number): T {
|
||||
return this.entries[index];
|
||||
getEntry(index: number): Promise<T> {
|
||||
return Promise.resolve(this.entries[index]);
|
||||
}
|
||||
|
||||
getDescriptors(): string[] {
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
} from './index_types';
|
||||
import {Parser} from './parser';
|
||||
import {Timestamp, TimestampType} from './timestamp';
|
||||
import {TraceFile} from './trace_file';
|
||||
import {TraceType} from './trace_type';
|
||||
|
||||
export {
|
||||
@@ -66,16 +65,18 @@ export class TraceEntry<T> {
|
||||
return this.framesRange;
|
||||
}
|
||||
|
||||
getValue(): T {
|
||||
return this.parser.getEntry(this.index, this.timestamp.getType());
|
||||
async getValue(): Promise<T> {
|
||||
return await this.parser.getEntry(this.index, this.timestamp.getType());
|
||||
}
|
||||
}
|
||||
|
||||
export class Trace<T> {
|
||||
readonly file?: TraceFile;
|
||||
readonly type: TraceType;
|
||||
readonly lengthEntries: number;
|
||||
readonly fullTrace: Trace<T>;
|
||||
|
||||
private readonly parser: Parser<T>;
|
||||
private readonly descriptors: string[];
|
||||
private readonly fullTrace: Trace<T>;
|
||||
private timestampType: TimestampType | undefined;
|
||||
private readonly entriesRange: EntriesRange;
|
||||
private frameMap?: FrameMap;
|
||||
@@ -107,13 +108,16 @@ export class Trace<T> {
|
||||
}
|
||||
|
||||
private constructor(
|
||||
readonly type: TraceType,
|
||||
readonly parser: Parser<T>,
|
||||
readonly descriptors: string[],
|
||||
type: TraceType,
|
||||
parser: Parser<T>,
|
||||
descriptors: string[],
|
||||
fullTrace: Trace<T> | undefined,
|
||||
timestampType: TimestampType | undefined,
|
||||
entriesRange: EntriesRange | undefined
|
||||
) {
|
||||
this.type = type;
|
||||
this.parser = parser;
|
||||
this.descriptors = descriptors;
|
||||
this.fullTrace = fullTrace ?? this;
|
||||
this.entriesRange = entriesRange ?? {start: 0, end: parser.getLengthEntries()};
|
||||
this.lengthEntries = this.entriesRange.end - this.entriesRange.start;
|
||||
@@ -323,6 +327,14 @@ export class Trace<T> {
|
||||
}
|
||||
}
|
||||
|
||||
mapEntry<U>(callback: (entry: TraceEntry<T>, index: RelativeEntryIndex) => U): U[] {
|
||||
const result: U[] = [];
|
||||
this.forEachEntry((entry, index) => {
|
||||
result.push(callback(entry, index));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
forEachTimestamp(callback: (timestamp: Timestamp, index: RelativeEntryIndex) => void) {
|
||||
const timestamps = this.getFullTraceTimestamps();
|
||||
for (let index = 0; index < this.lengthEntries; ++index) {
|
||||
@@ -340,6 +352,14 @@ export class Trace<T> {
|
||||
}
|
||||
}
|
||||
|
||||
mapFrame<U>(callback: (frame: Trace<T>, index: AbsoluteFrameIndex) => U): U[] {
|
||||
const result: U[] = [];
|
||||
this.forEachFrame((traces, index) => {
|
||||
result.push(callback(traces, index));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
getFramesRange(): FramesRange | undefined {
|
||||
this.checkTraceCanBeAccessedInFrameDomain();
|
||||
return this.framesRange;
|
||||
|
||||
@@ -66,8 +66,8 @@ describe('TraceEntry', () => {
|
||||
expect(trace.getEntry(5).getFramesRange()).toEqual({start: 4, end: 5});
|
||||
});
|
||||
|
||||
it('getValue()', () => {
|
||||
expect(trace.getEntry(0).getValue()).toEqual('entry-0');
|
||||
expect(trace.getEntry(1).getValue()).toEqual('entry-1');
|
||||
it('getValue()', async () => {
|
||||
expect(await trace.getEntry(0).getValue()).toEqual('entry-0');
|
||||
expect(await trace.getEntry(1).getValue()).toEqual('entry-1');
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -43,6 +43,7 @@ export enum TraceType {
|
||||
ERROR,
|
||||
TEST_TRACE_STRING,
|
||||
TEST_TRACE_NUMBER,
|
||||
VIEW_CAPTURE,
|
||||
}
|
||||
|
||||
export interface TraceEntryTypeMap {
|
||||
@@ -69,4 +70,5 @@ export interface TraceEntryTypeMap {
|
||||
[TraceType.ERROR]: object;
|
||||
[TraceType.TEST_TRACE_STRING]: string;
|
||||
[TraceType.TEST_TRACE_NUMBER]: number;
|
||||
[TraceType.VIEW_CAPTURE]: object;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,14 @@ export class Traces {
|
||||
});
|
||||
}
|
||||
|
||||
mapTrace<T>(callback: (trace: Trace<{}>, type: TraceType) => T): T[] {
|
||||
const result: T[] = [];
|
||||
this.forEachTrace((trace, type) => {
|
||||
result.push(callback(trace, type));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
forEachFrame(callback: (traces: Traces, index: AbsoluteFrameIndex) => void): void {
|
||||
let startFrameIndex: AbsoluteFrameIndex = Number.MAX_VALUE;
|
||||
let endFrameIndex: AbsoluteFrameIndex = Number.MIN_VALUE;
|
||||
@@ -72,4 +80,20 @@ export class Traces {
|
||||
callback(this.sliceFrames(i, i + 1), i);
|
||||
}
|
||||
}
|
||||
|
||||
mapFrame<T>(callback: (traces: Traces, index: AbsoluteFrameIndex) => T): T[] {
|
||||
const result: T[] = [];
|
||||
this.forEachFrame((traces, index) => {
|
||||
result.push(callback(traces, index));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
getSize(): number {
|
||||
return this.traces.size;
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this.traces.values();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {assertDefined} from 'common/assert_utils';
|
||||
import {FunctionUtils} from 'common/function_utils';
|
||||
import {TracesBuilder} from 'test/unit/traces_builder';
|
||||
import {TracesUtils} from 'test/unit/traces_utils';
|
||||
import {TraceBuilder} from 'test/unit/trace_builder';
|
||||
import {TraceUtils} from 'test/unit/trace_utils';
|
||||
import {assertDefined} from '../common/assert_utils';
|
||||
import {FrameMapBuilder} from './frame_map_builder';
|
||||
import {AbsoluteFrameIndex} from './index_types';
|
||||
import {RealTimestamp} from './timestamp';
|
||||
@@ -134,31 +135,31 @@ describe('Traces', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('getTrace()', () => {
|
||||
it('getTrace()', async () => {
|
||||
expect(
|
||||
TraceUtils.extractEntries(assertDefined(traces.getTrace(TraceType.TEST_TRACE_STRING)))
|
||||
await TraceUtils.extractEntries(assertDefined(traces.getTrace(TraceType.TEST_TRACE_STRING)))
|
||||
).toEqual(extractedEntriesFull.get(TraceType.TEST_TRACE_STRING) as string[]);
|
||||
expect(
|
||||
TraceUtils.extractEntries(assertDefined(traces.getTrace(TraceType.TEST_TRACE_NUMBER)))
|
||||
await TraceUtils.extractEntries(assertDefined(traces.getTrace(TraceType.TEST_TRACE_NUMBER)))
|
||||
).toEqual(extractedEntriesFull.get(TraceType.TEST_TRACE_NUMBER) as number[]);
|
||||
expect(traces.getTrace(TraceType.SURFACE_FLINGER)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('sliceTime()', () => {
|
||||
it('sliceTime()', async () => {
|
||||
// empty
|
||||
{
|
||||
const slice = traces.sliceTime(time3, time3);
|
||||
expect(TracesUtils.extractEntries(slice)).toEqual(extractedEntriesEmpty);
|
||||
expect(await TracesUtils.extractEntries(slice)).toEqual(extractedEntriesEmpty);
|
||||
}
|
||||
// full
|
||||
{
|
||||
const slice = traces.sliceTime();
|
||||
expect(TracesUtils.extractEntries(slice)).toEqual(extractedEntriesFull);
|
||||
expect(await TracesUtils.extractEntries(slice)).toEqual(extractedEntriesFull);
|
||||
}
|
||||
// middle
|
||||
{
|
||||
const slice = traces.sliceTime(time4, time8);
|
||||
expect(TracesUtils.extractEntries(slice)).toEqual(
|
||||
expect(await TracesUtils.extractEntries(slice)).toEqual(
|
||||
new Map<TraceType, Array<{}>>([
|
||||
[TraceType.TEST_TRACE_STRING, ['2', '3']],
|
||||
[TraceType.TEST_TRACE_NUMBER, [1, 2]],
|
||||
@@ -168,7 +169,7 @@ describe('Traces', () => {
|
||||
// slice away front
|
||||
{
|
||||
const slice = traces.sliceTime(time8);
|
||||
expect(TracesUtils.extractEntries(slice)).toEqual(
|
||||
expect(await TracesUtils.extractEntries(slice)).toEqual(
|
||||
new Map<TraceType, Array<{}>>([
|
||||
[TraceType.TEST_TRACE_STRING, ['4']],
|
||||
[TraceType.TEST_TRACE_NUMBER, [3, 4]],
|
||||
@@ -178,7 +179,7 @@ describe('Traces', () => {
|
||||
// slice away back
|
||||
{
|
||||
const slice = traces.sliceTime(undefined, time8);
|
||||
expect(TracesUtils.extractEntries(slice)).toEqual(
|
||||
expect(await TracesUtils.extractEntries(slice)).toEqual(
|
||||
new Map<TraceType, Array<{}>>([
|
||||
[TraceType.TEST_TRACE_STRING, ['0', '1', '2', '3']],
|
||||
[TraceType.TEST_TRACE_NUMBER, [0, 1, 2]],
|
||||
@@ -187,16 +188,16 @@ describe('Traces', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('sliceFrames()', () => {
|
||||
it('sliceFrames()', async () => {
|
||||
// empty
|
||||
{
|
||||
const slice = traces.sliceFrames(1, 1);
|
||||
expect(TracesUtils.extractFrames(slice)).toEqual(extractedFramesEmpty);
|
||||
expect(await TracesUtils.extractFrames(slice)).toEqual(extractedFramesEmpty);
|
||||
}
|
||||
// full
|
||||
{
|
||||
const slice = traces.sliceFrames();
|
||||
expect(TracesUtils.extractFrames(slice)).toEqual(extractedFramesFull);
|
||||
expect(await TracesUtils.extractFrames(slice)).toEqual(extractedFramesFull);
|
||||
}
|
||||
// middle
|
||||
{
|
||||
@@ -204,7 +205,7 @@ describe('Traces', () => {
|
||||
const expectedFrames = structuredClone(extractedFramesFull);
|
||||
expectedFrames.delete(0);
|
||||
expectedFrames.delete(4);
|
||||
expect(TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
|
||||
expect(await TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
|
||||
}
|
||||
// slice away front
|
||||
{
|
||||
@@ -212,7 +213,7 @@ describe('Traces', () => {
|
||||
const expectedFrames = structuredClone(extractedFramesFull);
|
||||
expectedFrames.delete(0);
|
||||
expectedFrames.delete(1);
|
||||
expect(TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
|
||||
expect(await TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
|
||||
}
|
||||
// slice away back
|
||||
{
|
||||
@@ -221,23 +222,24 @@ describe('Traces', () => {
|
||||
expectedFrames.delete(2);
|
||||
expectedFrames.delete(3);
|
||||
expectedFrames.delete(4);
|
||||
expect(TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
|
||||
expect(await TracesUtils.extractFrames(slice)).toEqual(expectedFrames);
|
||||
}
|
||||
});
|
||||
|
||||
it('forEachTrace()', () => {
|
||||
traces.forEachTrace((trace) => {
|
||||
it('mapTrace()', async () => {
|
||||
const promises = traces.mapTrace(async (trace) => {
|
||||
const expectedEntries = extractedEntriesFull.get(trace.type) as Array<{}>;
|
||||
const actualEntries = TraceUtils.extractEntries(trace);
|
||||
const actualEntries = await TraceUtils.extractEntries(trace);
|
||||
expect(actualEntries).toEqual(expectedEntries);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
});
|
||||
|
||||
it('forEachFrame()', () => {
|
||||
expect(TracesUtils.extractFrames(traces)).toEqual(extractedFramesFull);
|
||||
it('mapFrame()', async () => {
|
||||
expect(await TracesUtils.extractFrames(traces)).toEqual(extractedFramesFull);
|
||||
});
|
||||
|
||||
it('it supports empty traces', () => {
|
||||
it('it supports empty traces', async () => {
|
||||
const traces = new TracesBuilder()
|
||||
.setEntries(TraceType.TEST_TRACE_STRING, [])
|
||||
.setFrameMap(TraceType.TEST_TRACE_STRING, new FrameMapBuilder(0, 0).build())
|
||||
@@ -246,21 +248,25 @@ describe('Traces', () => {
|
||||
.setFrameMap(TraceType.TEST_TRACE_NUMBER, new FrameMapBuilder(0, 0).build())
|
||||
.build();
|
||||
|
||||
expect(TracesUtils.extractEntries(traces)).toEqual(extractedEntriesEmpty);
|
||||
expect(TracesUtils.extractFrames(traces)).toEqual(extractedFramesEmpty);
|
||||
expect(await TracesUtils.extractEntries(traces)).toEqual(extractedEntriesEmpty);
|
||||
expect(await TracesUtils.extractFrames(traces)).toEqual(extractedFramesEmpty);
|
||||
|
||||
expect(TracesUtils.extractEntries(traces.sliceTime(time1, time10))).toEqual(
|
||||
expect(await TracesUtils.extractEntries(traces.sliceTime(time1, time10))).toEqual(
|
||||
extractedEntriesEmpty
|
||||
);
|
||||
expect(TracesUtils.extractFrames(traces.sliceTime(time1, time10))).toEqual(
|
||||
expect(await TracesUtils.extractFrames(traces.sliceTime(time1, time10))).toEqual(
|
||||
extractedFramesEmpty
|
||||
);
|
||||
|
||||
expect(TracesUtils.extractEntries(traces.sliceFrames(0, 10))).toEqual(extractedEntriesEmpty);
|
||||
expect(TracesUtils.extractFrames(traces.sliceFrames(0, 10))).toEqual(extractedFramesEmpty);
|
||||
expect(await TracesUtils.extractEntries(traces.sliceFrames(0, 10))).toEqual(
|
||||
extractedEntriesEmpty
|
||||
);
|
||||
expect(await TracesUtils.extractFrames(traces.sliceFrames(0, 10))).toEqual(
|
||||
extractedFramesEmpty
|
||||
);
|
||||
});
|
||||
|
||||
it('it supports unavailable frame mapping', () => {
|
||||
it('it supports unavailable frame mapping', async () => {
|
||||
const traces = new TracesBuilder()
|
||||
.setEntries(TraceType.TEST_TRACE_STRING, ['entry-0'])
|
||||
.setTimestamps(TraceType.TEST_TRACE_STRING, [time1])
|
||||
@@ -276,13 +282,17 @@ describe('Traces', () => {
|
||||
[TraceType.TEST_TRACE_NUMBER, [0]],
|
||||
]);
|
||||
|
||||
expect(TracesUtils.extractEntries(traces)).toEqual(expectedEntries);
|
||||
expect(TracesUtils.extractEntries(traces.sliceTime())).toEqual(expectedEntries);
|
||||
expect(await TracesUtils.extractEntries(traces)).toEqual(expectedEntries);
|
||||
expect(await TracesUtils.extractEntries(traces.sliceTime())).toEqual(expectedEntries);
|
||||
|
||||
expect(() => {
|
||||
traces.sliceFrames();
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
TracesUtils.extractFrames(traces);
|
||||
traces.forEachFrame(FunctionUtils.DO_NOTHING);
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
traces.mapFrame(FunctionUtils.DO_NOTHING);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,7 +101,7 @@ export abstract class PresenterInputMethod {
|
||||
this.notifyViewCallback(this.uiData);
|
||||
}
|
||||
|
||||
onTracePositionUpdate(position: TracePosition) {
|
||||
async onTracePositionUpdate(position: TracePosition) {
|
||||
this.uiData = new ImeUiData(this.dependencies);
|
||||
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
|
||||
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
|
||||
@@ -109,11 +109,11 @@ export abstract class PresenterInputMethod {
|
||||
const [imeEntry, sfEntry, wmEntry] = this.findTraceEntries(position);
|
||||
|
||||
if (imeEntry) {
|
||||
this.entry = imeEntry.getValue() as TraceTreeNode;
|
||||
this.entry = (await imeEntry.getValue()) as TraceTreeNode;
|
||||
this.uiData.highlightedItems = this.highlightedItems;
|
||||
this.uiData.additionalProperties = this.getAdditionalProperties(
|
||||
wmEntry?.getValue(),
|
||||
sfEntry?.getValue()
|
||||
await wmEntry?.getValue(),
|
||||
await sfEntry?.getValue()
|
||||
);
|
||||
this.uiData.tree = this.generateTree();
|
||||
this.uiData.hierarchyTableProperties = this.updateHierarchyTableProperties();
|
||||
|
||||
@@ -57,7 +57,7 @@ export function executePresenterInputMethodTests(
|
||||
]);
|
||||
});
|
||||
|
||||
it('is robust to empty trace', () => {
|
||||
it('is robust to empty trace', async () => {
|
||||
const traces = new TracesBuilder().setEntries(imeTraceType, []).build();
|
||||
presenter = createPresenter(traces);
|
||||
|
||||
@@ -65,7 +65,7 @@ export function executePresenterInputMethodTests(
|
||||
expect(uiData.propertiesUserOptions).toBeTruthy();
|
||||
expect(uiData.tree).toBeFalsy();
|
||||
|
||||
presenter.onTracePositionUpdate(position);
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
expect(uiData.hierarchyUserOptions).toBeTruthy();
|
||||
expect(uiData.propertiesUserOptions).toBeTruthy();
|
||||
expect(uiData.tree).toBeFalsy();
|
||||
@@ -73,7 +73,7 @@ export function executePresenterInputMethodTests(
|
||||
|
||||
it('is robust to traces without SF', async () => {
|
||||
await setUpTestEnvironment([imeTraceType, TraceType.WINDOW_MANAGER]);
|
||||
presenter.onTracePositionUpdate(position);
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
expect(uiData.hierarchyUserOptions).toBeTruthy();
|
||||
expect(uiData.propertiesUserOptions).toBeTruthy();
|
||||
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
|
||||
@@ -81,7 +81,7 @@ export function executePresenterInputMethodTests(
|
||||
|
||||
it('is robust to traces without WM', async () => {
|
||||
await setUpTestEnvironment([imeTraceType, TraceType.SURFACE_FLINGER]);
|
||||
presenter.onTracePositionUpdate(position);
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
expect(uiData.hierarchyUserOptions).toBeTruthy();
|
||||
expect(uiData.propertiesUserOptions).toBeTruthy();
|
||||
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
|
||||
@@ -89,14 +89,14 @@ export function executePresenterInputMethodTests(
|
||||
|
||||
it('is robust to traces without WM and SF', async () => {
|
||||
await setUpTestEnvironment([imeTraceType]);
|
||||
presenter.onTracePositionUpdate(position);
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
expect(uiData.hierarchyUserOptions).toBeTruthy();
|
||||
expect(uiData.propertiesUserOptions).toBeTruthy();
|
||||
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
|
||||
});
|
||||
|
||||
it('processes trace position updates', () => {
|
||||
presenter.onTracePositionUpdate(position);
|
||||
it('processes trace position updates', async () => {
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
expect(uiData.hierarchyUserOptions).toBeTruthy();
|
||||
expect(uiData.propertiesUserOptions).toBeTruthy();
|
||||
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
|
||||
@@ -120,7 +120,7 @@ export function executePresenterInputMethodTests(
|
||||
expect(uiData.highlightedItems).toContain(id);
|
||||
});
|
||||
|
||||
it('can update hierarchy tree', () => {
|
||||
it('can update hierarchy tree', async () => {
|
||||
//change flat view to true
|
||||
const userOptions: UserOptions = {
|
||||
onlyVisible: {
|
||||
@@ -138,7 +138,7 @@ export function executePresenterInputMethodTests(
|
||||
};
|
||||
|
||||
let expectedChildren = expectHierarchyTreeWithSfSubtree ? 2 : 1;
|
||||
presenter.onTracePositionUpdate(position);
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
expect(uiData.tree?.children.length).toEqual(expectedChildren);
|
||||
|
||||
// Filter out non-visible child
|
||||
@@ -148,7 +148,7 @@ export function executePresenterInputMethodTests(
|
||||
expect(uiData.tree?.children.length).toEqual(expectedChildren);
|
||||
});
|
||||
|
||||
it('can filter hierarchy tree', () => {
|
||||
it('can filter hierarchy tree', async () => {
|
||||
const userOptions: UserOptions = {
|
||||
onlyVisible: {
|
||||
name: 'Only visible',
|
||||
@@ -165,7 +165,7 @@ export function executePresenterInputMethodTests(
|
||||
};
|
||||
|
||||
const expectedChildren = expectHierarchyTreeWithSfSubtree ? 12 : 1;
|
||||
presenter.onTracePositionUpdate(position);
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
presenter.updateHierarchyTree(userOptions);
|
||||
expect(uiData.tree?.children.length).toEqual(expectedChildren);
|
||||
|
||||
@@ -174,15 +174,15 @@ export function executePresenterInputMethodTests(
|
||||
expect(uiData.tree?.children.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('can set new properties tree and associated ui data', () => {
|
||||
presenter.onTracePositionUpdate(position);
|
||||
it('can set new properties tree and associated ui data', async () => {
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
presenter.newPropertiesTree(selectedTree);
|
||||
// does not check specific tree values as tree transformation method may change
|
||||
expect(uiData.propertiesTree).toBeTruthy();
|
||||
});
|
||||
|
||||
it('can filter properties tree', () => {
|
||||
presenter.onTracePositionUpdate(position);
|
||||
it('can filter properties tree', async () => {
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
presenter.newPropertiesTree(selectedTree);
|
||||
let nonTerminalChildren =
|
||||
uiData.propertiesTree?.children?.filter(
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface Rectangle {
|
||||
isVirtual: boolean;
|
||||
isClickable: boolean;
|
||||
cornerRadius: number;
|
||||
depth?: number;
|
||||
}
|
||||
|
||||
export interface Point {
|
||||
|
||||
@@ -29,8 +29,8 @@ abstract class ViewerInputMethod implements Viewer {
|
||||
this.addViewerEventListeners();
|
||||
}
|
||||
|
||||
onTracePositionUpdate(position: TracePosition) {
|
||||
this.presenter.onTracePositionUpdate(position);
|
||||
async onTracePositionUpdate(position: TracePosition) {
|
||||
await this.presenter.onTracePositionUpdate(position);
|
||||
}
|
||||
|
||||
abstract getViews(): View[];
|
||||
|
||||
@@ -180,7 +180,11 @@ class Mapper3D {
|
||||
const maxDisplaySize = this.getMaxDisplaySize(rects2d);
|
||||
|
||||
const rects3d = rects2d.map((rect2d): Rect3D => {
|
||||
z -= Mapper3D.Z_SPACING_MAX * this.zSpacingFactor;
|
||||
if (rect2d.depth !== undefined) {
|
||||
z = Mapper3D.Z_SPACING_MAX * this.zSpacingFactor * rect2d.depth;
|
||||
} else {
|
||||
z -= Mapper3D.Z_SPACING_MAX * this.zSpacingFactor;
|
||||
}
|
||||
|
||||
const darkFactor = rect2d.isVisible
|
||||
? (visibleRectsTotal - visibleRectsSoFar++) / visibleRectsTotal
|
||||
|
||||
@@ -33,6 +33,7 @@ import {Distance2D} from './types3d';
|
||||
>Only visible
|
||||
</mat-checkbox>
|
||||
<mat-checkbox
|
||||
*ngIf="enableShowVirtualButton"
|
||||
color="primary"
|
||||
[disabled]="mapper3d.getShowOnlyVisibleMode()"
|
||||
[checked]="mapper3d.getShowVirtualMode()"
|
||||
@@ -166,6 +167,7 @@ import {Distance2D} from './types3d';
|
||||
})
|
||||
export class RectsComponent implements OnInit, OnDestroy {
|
||||
@Input() title = 'title';
|
||||
@Input() enableShowVirtualButton: boolean = true;
|
||||
@Input() set rects(rects: Rectangle[]) {
|
||||
this.internalRects = rects;
|
||||
this.drawScene();
|
||||
|
||||
@@ -33,7 +33,7 @@ class View {
|
||||
}
|
||||
|
||||
interface Viewer {
|
||||
onTracePositionUpdate(position: TracePosition): void;
|
||||
onTracePositionUpdate(position: TracePosition): Promise<void>;
|
||||
getViews(): View[];
|
||||
getDependencies(): TraceType[];
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {ViewerScreenRecording} from './viewer_screen_recording/viewer_screen_rec
|
||||
import {ViewerSurfaceFlinger} from './viewer_surface_flinger/viewer_surface_flinger';
|
||||
import {ViewerTransactions} from './viewer_transactions/viewer_transactions';
|
||||
import {ViewerTransitions} from './viewer_transitions/viewer_transitions';
|
||||
import {ViewerViewCapture} from './viewer_view_capture/viewer_view_capture';
|
||||
import {ViewerWindowManager} from './viewer_window_manager/viewer_window_manager';
|
||||
|
||||
class ViewerFactory {
|
||||
@@ -41,6 +42,7 @@ class ViewerFactory {
|
||||
ViewerProtoLog,
|
||||
ViewerScreenRecording,
|
||||
ViewerTransitions,
|
||||
ViewerViewCapture,
|
||||
];
|
||||
|
||||
createViewers(activeTraceTypes: Set<TraceType>, traces: Traces, storage: Storage): Viewer[] {
|
||||
|
||||
@@ -15,107 +15,142 @@
|
||||
*/
|
||||
|
||||
import {ArrayUtils} from 'common/array_utils';
|
||||
import {assertDefined} from 'common/assert_utils';
|
||||
import {LogMessage} from 'trace/protolog';
|
||||
import {Trace, TraceEntry} from 'trace/trace';
|
||||
import {Traces} from 'trace/traces';
|
||||
import {TraceEntryFinder} from 'trace/trace_entry_finder';
|
||||
import {TracePosition} from 'trace/trace_position';
|
||||
import {TraceType} from 'trace/trace_type';
|
||||
import {assertDefined} from '../../common/assert_utils';
|
||||
import {UiData} from './ui_data';
|
||||
import {UiData, UiDataMessage} from './ui_data';
|
||||
|
||||
export class Presenter {
|
||||
private readonly trace: Trace<LogMessage>;
|
||||
private readonly notifyUiDataCallback: (data: UiData) => void;
|
||||
private entry?: TraceEntry<LogMessage>;
|
||||
private originalIndicesOfFilteredOutputMessages: number[];
|
||||
private uiData = UiData.EMPTY;
|
||||
private originalIndicesOfFilteredOutputMessages: number[] = [];
|
||||
|
||||
private tags: string[] = [];
|
||||
private files: string[] = [];
|
||||
private levels: string[] = [];
|
||||
private isInitialized = false;
|
||||
private allUiDataMessages: UiDataMessage[] = [];
|
||||
private allTags: string[] = [];
|
||||
private allSourceFiles: string[] = [];
|
||||
private allLogLevels: string[] = [];
|
||||
|
||||
private tagsFilter: string[] = [];
|
||||
private filesFilter: string[] = [];
|
||||
private levelsFilter: string[] = [];
|
||||
private searchString = '';
|
||||
|
||||
constructor(traces: Traces, notifyUiDataCallback: (data: UiData) => void) {
|
||||
this.trace = assertDefined(traces.getTrace(TraceType.PROTO_LOG));
|
||||
this.notifyUiDataCallback = notifyUiDataCallback;
|
||||
this.originalIndicesOfFilteredOutputMessages = [];
|
||||
this.computeUiDataMessages();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
onTracePositionUpdate(position: TracePosition) {
|
||||
async onTracePositionUpdate(position: TracePosition) {
|
||||
await this.initializeIfNeeded();
|
||||
this.entry = TraceEntryFinder.findCorrespondingEntry(this.trace, position);
|
||||
this.computeUiDataCurrentMessageIndex();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
onLogLevelsFilterChanged(levels: string[]) {
|
||||
this.levels = levels;
|
||||
this.computeUiDataMessages();
|
||||
this.levelsFilter = levels;
|
||||
this.computeUiData();
|
||||
this.computeUiDataCurrentMessageIndex();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
onTagsFilterChanged(tags: string[]) {
|
||||
this.tags = tags;
|
||||
this.computeUiDataMessages();
|
||||
this.tagsFilter = tags;
|
||||
this.computeUiData();
|
||||
this.computeUiDataCurrentMessageIndex();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
onSourceFilesFilterChanged(files: string[]) {
|
||||
this.files = files;
|
||||
this.computeUiDataMessages();
|
||||
this.filesFilter = files;
|
||||
this.computeUiData();
|
||||
this.computeUiDataCurrentMessageIndex();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
onSearchStringFilterChanged(searchString: string) {
|
||||
this.searchString = searchString;
|
||||
this.computeUiDataMessages();
|
||||
this.computeUiData();
|
||||
this.computeUiDataCurrentMessageIndex();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
private computeUiDataMessages() {
|
||||
const allLogLevels = this.getUniqueMessageValues((message: LogMessage) => message.level);
|
||||
const allTags = this.getUniqueMessageValues((message: LogMessage) => message.tag);
|
||||
const allSourceFiles = this.getUniqueMessageValues((message: LogMessage) => message.at);
|
||||
|
||||
let filteredMessagesAndOriginalIndex = new Array<[number, LogMessage]>();
|
||||
this.trace.forEachEntry((entry) => {
|
||||
filteredMessagesAndOriginalIndex.push([entry.getIndex(), entry.getValue()]);
|
||||
});
|
||||
|
||||
if (this.levels.length > 0) {
|
||||
filteredMessagesAndOriginalIndex = filteredMessagesAndOriginalIndex.filter((value) =>
|
||||
this.levels.includes(value[1].level)
|
||||
);
|
||||
private async initializeIfNeeded() {
|
||||
if (this.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.tags.length > 0) {
|
||||
filteredMessagesAndOriginalIndex = filteredMessagesAndOriginalIndex.filter((value) =>
|
||||
this.tags.includes(value[1].tag)
|
||||
);
|
||||
}
|
||||
this.allUiDataMessages = await this.makeAllUiDataMessages();
|
||||
|
||||
if (this.files.length > 0) {
|
||||
filteredMessagesAndOriginalIndex = filteredMessagesAndOriginalIndex.filter((value) =>
|
||||
this.files.includes(value[1].at)
|
||||
);
|
||||
}
|
||||
|
||||
filteredMessagesAndOriginalIndex = filteredMessagesAndOriginalIndex.filter((value) =>
|
||||
value[1].text.includes(this.searchString)
|
||||
this.allLogLevels = this.getUniqueMessageValues(
|
||||
this.allUiDataMessages,
|
||||
(message: LogMessage) => message.level
|
||||
);
|
||||
this.allTags = this.getUniqueMessageValues(
|
||||
this.allUiDataMessages,
|
||||
(message: LogMessage) => message.tag
|
||||
);
|
||||
this.allSourceFiles = this.getUniqueMessageValues(
|
||||
this.allUiDataMessages,
|
||||
(message: LogMessage) => message.at
|
||||
);
|
||||
|
||||
this.originalIndicesOfFilteredOutputMessages = filteredMessagesAndOriginalIndex.map(
|
||||
(value) => value[0]
|
||||
);
|
||||
const filteredMessages = filteredMessagesAndOriginalIndex.map((value) => value[1]);
|
||||
this.computeUiData();
|
||||
|
||||
this.uiData = new UiData(allLogLevels, allTags, allSourceFiles, filteredMessages, undefined);
|
||||
this.isInitialized = true;
|
||||
}
|
||||
|
||||
private async makeAllUiDataMessages(): Promise<UiDataMessage[]> {
|
||||
const messages: UiDataMessage[] = [];
|
||||
|
||||
for (let originalIndex = 0; originalIndex < this.trace.lengthEntries; ++originalIndex) {
|
||||
const entry = assertDefined(this.trace.getEntry(originalIndex));
|
||||
const message = await entry.getValue();
|
||||
(message as UiDataMessage).originalIndex = originalIndex;
|
||||
messages.push(message as UiDataMessage);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
private computeUiData() {
|
||||
let filteredMessages = this.allUiDataMessages;
|
||||
|
||||
if (this.levelsFilter.length > 0) {
|
||||
filteredMessages = filteredMessages.filter((value) =>
|
||||
this.levelsFilter.includes(value.level)
|
||||
);
|
||||
}
|
||||
|
||||
if (this.tagsFilter.length > 0) {
|
||||
filteredMessages = filteredMessages.filter((value) => this.tagsFilter.includes(value.tag));
|
||||
}
|
||||
|
||||
if (this.filesFilter.length > 0) {
|
||||
filteredMessages = filteredMessages.filter((value) => this.filesFilter.includes(value.at));
|
||||
}
|
||||
|
||||
filteredMessages = filteredMessages.filter((value) => value.text.includes(this.searchString));
|
||||
|
||||
this.originalIndicesOfFilteredOutputMessages = filteredMessages.map(
|
||||
(message) => message.originalIndex
|
||||
);
|
||||
|
||||
this.uiData = new UiData(
|
||||
this.allLogLevels,
|
||||
this.allTags,
|
||||
this.allSourceFiles,
|
||||
filteredMessages,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
private computeUiDataCurrentMessageIndex() {
|
||||
@@ -136,10 +171,13 @@ export class Presenter {
|
||||
) ?? this.originalIndicesOfFilteredOutputMessages.length - 1;
|
||||
}
|
||||
|
||||
private getUniqueMessageValues(getValue: (message: LogMessage) => string): string[] {
|
||||
private getUniqueMessageValues(
|
||||
allMessages: LogMessage[],
|
||||
getValue: (message: LogMessage) => string
|
||||
): string[] {
|
||||
const uniqueValues = new Set<string>();
|
||||
this.trace.forEachEntry((entry) => {
|
||||
uniqueValues.add(getValue(entry.getValue()));
|
||||
allMessages.forEach((message) => {
|
||||
uniqueValues.add(getValue(message));
|
||||
});
|
||||
const result = [...uniqueValues];
|
||||
result.sort();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {assertDefined} from 'common/assert_utils';
|
||||
import {TracesBuilder} from 'test/unit/traces_builder';
|
||||
import {TraceBuilder} from 'test/unit/trace_builder';
|
||||
import {LogMessage} from 'trace/protolog';
|
||||
@@ -23,11 +24,11 @@ import {Traces} from 'trace/traces';
|
||||
import {TracePosition} from 'trace/trace_position';
|
||||
import {TraceType} from 'trace/trace_type';
|
||||
import {Presenter} from './presenter';
|
||||
import {UiData} from './ui_data';
|
||||
import {UiData, UiDataMessage} from './ui_data';
|
||||
|
||||
describe('ViewerProtoLogPresenter', () => {
|
||||
let presenter: Presenter;
|
||||
let inputMessages: LogMessage[];
|
||||
let inputMessages: UiDataMessage[];
|
||||
let trace: Trace<LogMessage>;
|
||||
let position10: TracePosition;
|
||||
let position11: TracePosition;
|
||||
@@ -43,7 +44,11 @@ describe('ViewerProtoLogPresenter', () => {
|
||||
new LogMessage('text0', 'time', 'tag0', 'level0', 'sourcefile0', 10n),
|
||||
new LogMessage('text1', 'time', 'tag1', 'level1', 'sourcefile1', 11n),
|
||||
new LogMessage('text2', 'time', 'tag2', 'level2', 'sourcefile2', 12n),
|
||||
];
|
||||
].map((message, index) => {
|
||||
(message as UiDataMessage).originalIndex = index;
|
||||
return message as UiDataMessage;
|
||||
});
|
||||
|
||||
trace = new TraceBuilder<LogMessage>()
|
||||
.setEntries(inputMessages)
|
||||
.setTimestamps([time10, time11, time12])
|
||||
@@ -60,116 +65,121 @@ describe('ViewerProtoLogPresenter', () => {
|
||||
presenter = new Presenter(traces, (data: UiData) => {
|
||||
outputUiData = data;
|
||||
});
|
||||
await presenter.onTracePositionUpdate(position10); // trigger initialization
|
||||
});
|
||||
|
||||
it('is robust to empty trace', () => {
|
||||
it('is robust to empty trace', async () => {
|
||||
const traces = new TracesBuilder().setEntries(TraceType.PROTO_LOG, []).build();
|
||||
presenter = new Presenter(traces, (data: UiData) => {
|
||||
outputUiData = data;
|
||||
});
|
||||
|
||||
expect(outputUiData!.messages).toEqual([]);
|
||||
expect(outputUiData!.currentMessageIndex).toBeUndefined();
|
||||
expect(assertDefined(outputUiData).messages).toEqual([]);
|
||||
expect(assertDefined(outputUiData).currentMessageIndex).toBeUndefined();
|
||||
|
||||
presenter.onTracePositionUpdate(position10);
|
||||
expect(outputUiData!.messages).toEqual([]);
|
||||
expect(outputUiData!.currentMessageIndex).toBeUndefined();
|
||||
await presenter.onTracePositionUpdate(position10);
|
||||
expect(assertDefined(outputUiData).messages).toEqual([]);
|
||||
expect(assertDefined(outputUiData).currentMessageIndex).toBeUndefined();
|
||||
});
|
||||
|
||||
it('processes trace position updates', () => {
|
||||
presenter.onTracePositionUpdate(position10);
|
||||
it('processes trace position updates', async () => {
|
||||
await presenter.onTracePositionUpdate(position10);
|
||||
|
||||
expect(outputUiData!.allLogLevels).toEqual(['level0', 'level1', 'level2']);
|
||||
expect(outputUiData!.allTags).toEqual(['tag0', 'tag1', 'tag2']);
|
||||
expect(outputUiData!.allSourceFiles).toEqual(['sourcefile0', 'sourcefile1', 'sourcefile2']);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).allLogLevels).toEqual(['level0', 'level1', 'level2']);
|
||||
expect(assertDefined(outputUiData).allTags).toEqual(['tag0', 'tag1', 'tag2']);
|
||||
expect(assertDefined(outputUiData).allSourceFiles).toEqual([
|
||||
'sourcefile0',
|
||||
'sourcefile1',
|
||||
'sourcefile2',
|
||||
]);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
|
||||
});
|
||||
|
||||
it('updates displayed messages according to log levels filter', () => {
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onLogLevelsFilterChanged([]);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onLogLevelsFilterChanged(['level1']);
|
||||
expect(outputUiData!.messages).toEqual([inputMessages[1]]);
|
||||
expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]);
|
||||
|
||||
presenter.onLogLevelsFilterChanged(['level0', 'level1', 'level2']);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
});
|
||||
|
||||
it('updates displayed messages according to tags filter', () => {
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onTagsFilterChanged([]);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onTagsFilterChanged(['tag1']);
|
||||
expect(outputUiData!.messages).toEqual([inputMessages[1]]);
|
||||
expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]);
|
||||
|
||||
presenter.onTagsFilterChanged(['tag0', 'tag1', 'tag2']);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
});
|
||||
|
||||
it('updates displayed messages according to source files filter', () => {
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onSourceFilesFilterChanged([]);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onSourceFilesFilterChanged(['sourcefile1']);
|
||||
expect(outputUiData!.messages).toEqual([inputMessages[1]]);
|
||||
expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]);
|
||||
|
||||
presenter.onSourceFilesFilterChanged(['sourcefile0', 'sourcefile1', 'sourcefile2']);
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
});
|
||||
|
||||
it('updates displayed messages according to search string filter', () => {
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onSearchStringFilterChanged('');
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onSearchStringFilterChanged('text');
|
||||
expect(outputUiData!.messages).toEqual(inputMessages);
|
||||
expect(assertDefined(outputUiData).messages).toEqual(inputMessages);
|
||||
|
||||
presenter.onSearchStringFilterChanged('text0');
|
||||
expect(outputUiData!.messages).toEqual([inputMessages[0]]);
|
||||
expect(assertDefined(outputUiData).messages).toEqual([inputMessages[0]]);
|
||||
|
||||
presenter.onSearchStringFilterChanged('text1');
|
||||
expect(outputUiData!.messages).toEqual([inputMessages[1]]);
|
||||
expect(assertDefined(outputUiData).messages).toEqual([inputMessages[1]]);
|
||||
});
|
||||
|
||||
it('computes current message index', () => {
|
||||
it('computes current message index', async () => {
|
||||
// Position -> entry #0
|
||||
presenter.onTracePositionUpdate(position10);
|
||||
await presenter.onTracePositionUpdate(position10);
|
||||
presenter.onLogLevelsFilterChanged([]);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
|
||||
|
||||
presenter.onLogLevelsFilterChanged(['level0']);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
|
||||
|
||||
presenter.onLogLevelsFilterChanged([]);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
|
||||
|
||||
// Position -> entry #1
|
||||
presenter.onTracePositionUpdate(position11);
|
||||
await presenter.onTracePositionUpdate(position11);
|
||||
presenter.onLogLevelsFilterChanged([]);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(1);
|
||||
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(1);
|
||||
|
||||
presenter.onLogLevelsFilterChanged(['level0']);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
|
||||
|
||||
presenter.onLogLevelsFilterChanged(['level1']);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(0);
|
||||
|
||||
presenter.onLogLevelsFilterChanged(['level0', 'level1']);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(1);
|
||||
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(1);
|
||||
|
||||
// Position -> entry #2
|
||||
presenter.onTracePositionUpdate(position12);
|
||||
await presenter.onTracePositionUpdate(position12);
|
||||
presenter.onLogLevelsFilterChanged([]);
|
||||
expect(outputUiData!.currentMessageIndex).toEqual(2);
|
||||
expect(assertDefined(outputUiData).currentMessageIndex).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,16 +15,18 @@
|
||||
*/
|
||||
import {LogMessage} from 'trace/protolog';
|
||||
|
||||
class UiData {
|
||||
export interface UiDataMessage extends LogMessage {
|
||||
originalIndex: number;
|
||||
}
|
||||
|
||||
export class UiData {
|
||||
constructor(
|
||||
public allLogLevels: string[],
|
||||
public allTags: string[],
|
||||
public allSourceFiles: string[],
|
||||
public messages: LogMessage[],
|
||||
public messages: UiDataMessage[],
|
||||
public currentMessageIndex: undefined | number
|
||||
) {}
|
||||
|
||||
static EMPTY = new UiData([], [], [], [], undefined);
|
||||
}
|
||||
|
||||
export {UiData};
|
||||
|
||||
@@ -31,21 +31,21 @@ class ViewerProtoLog implements Viewer {
|
||||
});
|
||||
|
||||
this.htmlElement.addEventListener(Events.LogLevelsFilterChanged, (event) => {
|
||||
return this.presenter.onLogLevelsFilterChanged((event as CustomEvent).detail);
|
||||
this.presenter.onLogLevelsFilterChanged((event as CustomEvent).detail);
|
||||
});
|
||||
this.htmlElement.addEventListener(Events.TagsFilterChanged, (event) => {
|
||||
return this.presenter.onTagsFilterChanged((event as CustomEvent).detail);
|
||||
this.presenter.onTagsFilterChanged((event as CustomEvent).detail);
|
||||
});
|
||||
this.htmlElement.addEventListener(Events.SourceFilesFilterChanged, (event) => {
|
||||
return this.presenter.onSourceFilesFilterChanged((event as CustomEvent).detail);
|
||||
this.presenter.onSourceFilesFilterChanged((event as CustomEvent).detail);
|
||||
});
|
||||
this.htmlElement.addEventListener(Events.SearchStringFilterChanged, (event) => {
|
||||
return this.presenter.onSearchStringFilterChanged((event as CustomEvent).detail);
|
||||
this.presenter.onSearchStringFilterChanged((event as CustomEvent).detail);
|
||||
});
|
||||
}
|
||||
|
||||
onTracePositionUpdate(position: TracePosition) {
|
||||
this.presenter.onTracePositionUpdate(position);
|
||||
async onTracePositionUpdate(position: TracePosition) {
|
||||
await this.presenter.onTracePositionUpdate(position);
|
||||
}
|
||||
|
||||
getViews(): View[] {
|
||||
|
||||
@@ -34,10 +34,10 @@ class ViewerScreenRecording implements Viewer {
|
||||
this.htmlElement = document.createElement('viewer-screen-recording');
|
||||
}
|
||||
|
||||
onTracePositionUpdate(position: TracePosition) {
|
||||
async onTracePositionUpdate(position: TracePosition) {
|
||||
const entry = TraceEntryFinder.findCorrespondingEntry(this.trace, position);
|
||||
(this.htmlElement as unknown as ViewerScreenRecordingComponent).currentTraceEntry =
|
||||
entry?.getValue();
|
||||
await entry?.getValue();
|
||||
}
|
||||
|
||||
getViews(): View[] {
|
||||
|
||||
@@ -30,8 +30,8 @@ class ViewerStub implements Viewer {
|
||||
}
|
||||
}
|
||||
|
||||
onTracePositionUpdate(position: TracePosition) {
|
||||
// do nothing
|
||||
onTracePositionUpdate(position: TracePosition): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
getViews(): View[] {
|
||||
|
||||
@@ -101,7 +101,7 @@ export class Presenter {
|
||||
this.copyUiDataAndNotifyView();
|
||||
}
|
||||
|
||||
onTracePositionUpdate(position: TracePosition) {
|
||||
async onTracePositionUpdate(position: TracePosition) {
|
||||
this.uiData = new UiData();
|
||||
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
|
||||
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
|
||||
@@ -110,8 +110,8 @@ export class Presenter {
|
||||
const prevEntry =
|
||||
entry && entry.getIndex() > 0 ? this.trace.getEntry(entry.getIndex() - 1) : undefined;
|
||||
|
||||
this.entry = entry?.getValue() ?? null;
|
||||
this.previousEntry = prevEntry?.getValue() ?? null;
|
||||
this.entry = (await entry?.getValue()) ?? null;
|
||||
this.previousEntry = (await prevEntry?.getValue()) ?? null;
|
||||
if (this.entry) {
|
||||
this.uiData.highlightedItems = this.highlightedItems;
|
||||
this.uiData.rects = this.generateRects();
|
||||
|
||||
@@ -62,18 +62,18 @@ describe('PresenterSurfaceFlinger', () => {
|
||||
presenter = createPresenter(trace);
|
||||
});
|
||||
|
||||
it('is robust to empty trace', () => {
|
||||
it('is robust to empty trace', async () => {
|
||||
const emptyTrace = new TraceBuilder<LayerTraceEntry>().setEntries([]).build();
|
||||
const presenter = createPresenter(emptyTrace);
|
||||
|
||||
const positionWithoutTraceEntry = TracePosition.fromTimestamp(new RealTimestamp(0n));
|
||||
presenter.onTracePositionUpdate(positionWithoutTraceEntry);
|
||||
await presenter.onTracePositionUpdate(positionWithoutTraceEntry);
|
||||
expect(uiData.hierarchyUserOptions).toBeTruthy();
|
||||
expect(uiData.tree).toBeFalsy();
|
||||
});
|
||||
|
||||
it('processes trace position updates', () => {
|
||||
presenter.onTracePositionUpdate(position);
|
||||
it('processes trace position updates', async () => {
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
|
||||
expect(uiData.rects.length).toBeGreaterThan(0);
|
||||
expect(uiData.highlightedItems?.length).toEqual(0);
|
||||
@@ -89,8 +89,8 @@ describe('PresenterSurfaceFlinger', () => {
|
||||
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
|
||||
});
|
||||
|
||||
it('creates input data for rects view', () => {
|
||||
presenter.onTracePositionUpdate(position);
|
||||
it('creates input data for rects view', async () => {
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
expect(uiData.rects.length).toBeGreaterThan(0);
|
||||
expect(uiData.rects[0].topLeft).toEqual({x: 0, y: 0});
|
||||
expect(uiData.rects[0].bottomRight).toEqual({x: 1080, y: 118});
|
||||
@@ -116,7 +116,7 @@ describe('PresenterSurfaceFlinger', () => {
|
||||
expect(uiData.highlightedItems).toContain(id);
|
||||
});
|
||||
|
||||
it('updates hierarchy tree', () => {
|
||||
it('updates hierarchy tree', async () => {
|
||||
//change flat view to true
|
||||
const userOptions: UserOptions = {
|
||||
showDiff: {
|
||||
@@ -137,7 +137,7 @@ describe('PresenterSurfaceFlinger', () => {
|
||||
},
|
||||
};
|
||||
|
||||
presenter.onTracePositionUpdate(position);
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
expect(uiData.tree?.children.length).toEqual(3);
|
||||
|
||||
presenter.updateHierarchyTree(userOptions);
|
||||
@@ -146,7 +146,7 @@ describe('PresenterSurfaceFlinger', () => {
|
||||
expect(uiData.tree?.children.length).toEqual(94);
|
||||
});
|
||||
|
||||
it('filters hierarchy tree', () => {
|
||||
it('filters hierarchy tree', async () => {
|
||||
const userOptions: UserOptions = {
|
||||
showDiff: {
|
||||
name: 'Show diff',
|
||||
@@ -165,7 +165,7 @@ describe('PresenterSurfaceFlinger', () => {
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
presenter.onTracePositionUpdate(position);
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
presenter.updateHierarchyTree(userOptions);
|
||||
expect(uiData.tree?.children.length).toEqual(94);
|
||||
presenter.filterHierarchyTree('Wallpaper');
|
||||
@@ -173,14 +173,14 @@ describe('PresenterSurfaceFlinger', () => {
|
||||
expect(uiData.tree?.children.length).toEqual(4);
|
||||
});
|
||||
|
||||
it('sets properties tree and associated ui data', () => {
|
||||
presenter.onTracePositionUpdate(position);
|
||||
it('sets properties tree and associated ui data', async () => {
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
presenter.newPropertiesTree(selectedTree);
|
||||
// does not check specific tree values as tree transformation method may change
|
||||
expect(uiData.propertiesTree).toBeTruthy();
|
||||
});
|
||||
|
||||
it('updates properties tree', () => {
|
||||
it('updates properties tree', async () => {
|
||||
//change flat view to true
|
||||
const userOptions: UserOptions = {
|
||||
showDiff: {
|
||||
@@ -198,7 +198,7 @@ describe('PresenterSurfaceFlinger', () => {
|
||||
},
|
||||
};
|
||||
|
||||
presenter.onTracePositionUpdate(position);
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
presenter.newPropertiesTree(selectedTree);
|
||||
expect(uiData.propertiesTree?.diffType).toBeFalsy();
|
||||
|
||||
@@ -207,8 +207,8 @@ describe('PresenterSurfaceFlinger', () => {
|
||||
expect(uiData.propertiesTree?.diffType).toBeTruthy();
|
||||
});
|
||||
|
||||
it('filters properties tree', () => {
|
||||
presenter.onTracePositionUpdate(position);
|
||||
it('filters properties tree', async () => {
|
||||
await presenter.onTracePositionUpdate(position);
|
||||
presenter.newPropertiesTree(selectedTree);
|
||||
let nonTerminalChildren =
|
||||
uiData.propertiesTree?.children?.filter(
|
||||
@@ -226,7 +226,7 @@ describe('PresenterSurfaceFlinger', () => {
|
||||
});
|
||||
|
||||
it('handles displays with no visible layers', async () => {
|
||||
presenter.onTracePositionUpdate(positionMultiDisplayEntry);
|
||||
await presenter.onTracePositionUpdate(positionMultiDisplayEntry);
|
||||
expect(uiData.displayIds.length).toEqual(5);
|
||||
// we want the ids to be sorted
|
||||
expect(uiData.displayIds).toEqual([0, 2, 3, 4, 5]);
|
||||
|
||||
@@ -57,8 +57,8 @@ class ViewerSurfaceFlinger implements Viewer {
|
||||
);
|
||||
}
|
||||
|
||||
onTracePositionUpdate(position: TracePosition) {
|
||||
this.presenter.onTracePositionUpdate(position);
|
||||
async onTracePositionUpdate(position: TracePosition) {
|
||||
await this.presenter.onTracePositionUpdate(position);
|
||||
}
|
||||
|
||||
getViews(): View[] {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -28,12 +28,20 @@ import {PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
|
||||
import {UiData, UiDataEntry, UiDataEntryType} from './ui_data';
|
||||
|
||||
export class Presenter {
|
||||
private trace: Trace<object>;
|
||||
private readonly trace: Trace<object>;
|
||||
private entry?: TraceEntry<object>;
|
||||
private originalIndicesOfUiDataEntries: number[];
|
||||
private uiData = UiData.EMPTY;
|
||||
private readonly notifyUiDataCallback: (data: UiData) => void;
|
||||
private static readonly VALUE_NA = 'N/A';
|
||||
|
||||
private isInitialized = false;
|
||||
private allUiDataEntries: UiDataEntry[] = [];
|
||||
private allVSyncIds: string[] = [];
|
||||
private allPids: string[] = [];
|
||||
private allUids: string[] = [];
|
||||
private allTypes: string[] = [];
|
||||
private allLayerAndDisplayIds: string[] = [];
|
||||
private allTransactionIds: string[] = [];
|
||||
|
||||
private vsyncIdFilter: string[] = [];
|
||||
private pidFilter: string[] = [];
|
||||
private uidFilter: string[] = [];
|
||||
@@ -42,15 +50,19 @@ export class Presenter {
|
||||
private idFilter: string | undefined = undefined;
|
||||
private whatSearchString = '';
|
||||
|
||||
private readonly notifyUiDataCallback: (data: UiData) => void;
|
||||
private static readonly VALUE_NA = 'N/A';
|
||||
|
||||
constructor(traces: Traces, notifyUiDataCallback: (data: UiData) => void) {
|
||||
this.trace = assertDefined(traces.getTrace(TraceType.TRANSACTIONS));
|
||||
this.notifyUiDataCallback = notifyUiDataCallback;
|
||||
this.originalIndicesOfUiDataEntries = [];
|
||||
this.computeUiData();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
onTracePositionUpdate(position: TracePosition) {
|
||||
async onTracePositionUpdate(position: TracePosition) {
|
||||
await this.initializeIfNeeded();
|
||||
|
||||
this.entry = TraceEntryFinder.findCorrespondingEntry(this.trace, position);
|
||||
|
||||
this.uiData.currentEntryIndex = this.computeCurrentEntryIndex();
|
||||
@@ -67,31 +79,31 @@ export class Presenter {
|
||||
|
||||
onVSyncIdFilterChanged(vsyncIds: string[]) {
|
||||
this.vsyncIdFilter = vsyncIds;
|
||||
this.computeUiData();
|
||||
this.uiData = this.computeUiData();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
onPidFilterChanged(pids: string[]) {
|
||||
this.pidFilter = pids;
|
||||
this.computeUiData();
|
||||
this.uiData = this.computeUiData();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
onUidFilterChanged(uids: string[]) {
|
||||
this.uidFilter = uids;
|
||||
this.computeUiData();
|
||||
this.uiData = this.computeUiData();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
onTypeFilterChanged(types: string[]) {
|
||||
this.typeFilter = types;
|
||||
this.computeUiData();
|
||||
this.uiData = this.computeUiData();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
onLayerIdFilterChanged(ids: string[]) {
|
||||
this.layerIdFilter = ids;
|
||||
this.computeUiData();
|
||||
this.uiData = this.computeUiData();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
@@ -101,13 +113,13 @@ export class Presenter {
|
||||
} else {
|
||||
this.idFilter = id;
|
||||
}
|
||||
this.computeUiData();
|
||||
this.uiData = this.computeUiData();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
onWhatSearchStringChanged(searchString: string) {
|
||||
this.whatSearchString = searchString;
|
||||
this.computeUiData();
|
||||
this.uiData = this.computeUiData();
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
@@ -129,24 +141,46 @@ export class Presenter {
|
||||
this.notifyUiDataCallback(this.uiData);
|
||||
}
|
||||
|
||||
private computeUiData() {
|
||||
const entries = this.makeUiDataEntries();
|
||||
private async initializeIfNeeded() {
|
||||
if (this.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allVSyncIds = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) =>
|
||||
entry.vsyncId.toString()
|
||||
this.allUiDataEntries = await this.makeUiDataEntries();
|
||||
|
||||
this.allVSyncIds = this.getUniqueUiDataEntryValues(
|
||||
this.allUiDataEntries,
|
||||
(entry: UiDataEntry) => entry.vsyncId.toString()
|
||||
);
|
||||
const allPids = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) => entry.pid);
|
||||
const allUids = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) => entry.uid);
|
||||
const allTypes = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) => entry.type);
|
||||
const allLayerAndDisplayIds = this.getUniqueUiDataEntryValues(
|
||||
entries,
|
||||
this.allPids = this.getUniqueUiDataEntryValues(
|
||||
this.allUiDataEntries,
|
||||
(entry: UiDataEntry) => entry.pid
|
||||
);
|
||||
this.allUids = this.getUniqueUiDataEntryValues(
|
||||
this.allUiDataEntries,
|
||||
(entry: UiDataEntry) => entry.uid
|
||||
);
|
||||
this.allTypes = this.getUniqueUiDataEntryValues(
|
||||
this.allUiDataEntries,
|
||||
(entry: UiDataEntry) => entry.type
|
||||
);
|
||||
this.allLayerAndDisplayIds = this.getUniqueUiDataEntryValues(
|
||||
this.allUiDataEntries,
|
||||
(entry: UiDataEntry) => entry.layerOrDisplayId
|
||||
);
|
||||
const allTransactionIds = this.getUniqueUiDataEntryValues(
|
||||
entries,
|
||||
this.allTransactionIds = this.getUniqueUiDataEntryValues(
|
||||
this.allUiDataEntries,
|
||||
(entry: UiDataEntry) => entry.transactionId
|
||||
);
|
||||
|
||||
this.uiData = this.computeUiData();
|
||||
|
||||
this.isInitialized = true;
|
||||
}
|
||||
|
||||
private computeUiData(): UiData {
|
||||
const entries = this.allUiDataEntries;
|
||||
|
||||
let filteredEntries = entries;
|
||||
|
||||
if (this.vsyncIdFilter.length > 0) {
|
||||
@@ -192,13 +226,13 @@ export class Presenter {
|
||||
selectedEntryIndex
|
||||
);
|
||||
|
||||
this.uiData = new UiData(
|
||||
allVSyncIds,
|
||||
allPids,
|
||||
allUids,
|
||||
allTypes,
|
||||
allLayerAndDisplayIds,
|
||||
allTransactionIds,
|
||||
return new UiData(
|
||||
this.allVSyncIds,
|
||||
this.allPids,
|
||||
this.allUids,
|
||||
this.allTypes,
|
||||
this.allLayerAndDisplayIds,
|
||||
this.allTransactionIds,
|
||||
filteredEntries,
|
||||
currentEntryIndex,
|
||||
selectedEntryIndex,
|
||||
@@ -238,16 +272,15 @@ export class Presenter {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private makeUiDataEntries(): UiDataEntry[] {
|
||||
private async makeUiDataEntries(): Promise<UiDataEntry[]> {
|
||||
const treeGenerator = new PropertiesTreeGenerator();
|
||||
const entries: UiDataEntry[] = [];
|
||||
const formattingOptions = ObjectFormatter.displayDefaults;
|
||||
ObjectFormatter.displayDefaults = true;
|
||||
|
||||
this.trace.forEachEntry((entry, originalIndex) => {
|
||||
const timestampType = entry.getTimestamp().getType();
|
||||
const entryProto = entry.getValue() as any;
|
||||
const realToElapsedTimeOffsetNs = entryProto.realToElapsedTimeOffsetNs;
|
||||
for (let originalIndex = 0; originalIndex < this.trace.lengthEntries; ++originalIndex) {
|
||||
const entry = this.trace.getEntry(originalIndex);
|
||||
const entryProto = (await entry.getValue()) as any;
|
||||
|
||||
for (const transactionStateProto of entryProto.transactions) {
|
||||
for (const layerStateProto of transactionStateProto.layerChanges) {
|
||||
@@ -395,7 +428,7 @@ export class Presenter {
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ObjectFormatter.displayDefaults = formattingOptions;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {assertDefined} from 'common/assert_utils';
|
||||
import {TracesBuilder} from 'test/unit/traces_builder';
|
||||
import {TraceBuilder} from 'test/unit/trace_builder';
|
||||
import {UnitTestUtils} from 'test/unit/utils';
|
||||
@@ -38,12 +39,12 @@ describe('PresenterTransactions', () => {
|
||||
parser = await UnitTestUtils.getParser('traces/elapsed_and_real_timestamp/Transactions.pb');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
outputUiData = undefined;
|
||||
setUpTestEnvironment(TimestampType.ELAPSED);
|
||||
await setUpTestEnvironment(TimestampType.ELAPSED);
|
||||
});
|
||||
|
||||
it('is robust to empty trace', () => {
|
||||
it('is robust to empty trace', async () => {
|
||||
const traces = new TracesBuilder().setEntries(TraceType.TRANSACTIONS, []).build();
|
||||
presenter = new Presenter(traces, (data: UiData) => {
|
||||
outputUiData = data;
|
||||
@@ -51,14 +52,14 @@ describe('PresenterTransactions', () => {
|
||||
|
||||
expect(outputUiData).toEqual(UiData.EMPTY);
|
||||
|
||||
presenter.onTracePositionUpdate(TracePosition.fromTimestamp(new RealTimestamp(10n)));
|
||||
await presenter.onTracePositionUpdate(TracePosition.fromTimestamp(new RealTimestamp(10n)));
|
||||
expect(outputUiData).toEqual(UiData.EMPTY);
|
||||
});
|
||||
|
||||
it('processes trace position update and computes output UI data', () => {
|
||||
presenter.onTracePositionUpdate(createTracePosition(0));
|
||||
it('processes trace position update and computes output UI data', async () => {
|
||||
await presenter.onTracePositionUpdate(createTracePosition(0));
|
||||
|
||||
expect(outputUiData!.allPids).toEqual([
|
||||
expect(assertDefined(outputUiData).allPids).toEqual([
|
||||
'N/A',
|
||||
'0',
|
||||
'515',
|
||||
@@ -68,8 +69,15 @@ describe('PresenterTransactions', () => {
|
||||
'2463',
|
||||
'3300',
|
||||
]);
|
||||
expect(outputUiData!.allUids).toEqual(['N/A', '1000', '1003', '10169', '10235', '10239']);
|
||||
expect(outputUiData!.allTypes).toEqual([
|
||||
expect(assertDefined(outputUiData).allUids).toEqual([
|
||||
'N/A',
|
||||
'1000',
|
||||
'1003',
|
||||
'10169',
|
||||
'10235',
|
||||
'10239',
|
||||
]);
|
||||
expect(assertDefined(outputUiData).allTypes).toEqual([
|
||||
'DISPLAY_CHANGED',
|
||||
'LAYER_ADDED',
|
||||
'LAYER_CHANGED',
|
||||
@@ -78,81 +86,89 @@ describe('PresenterTransactions', () => {
|
||||
'NO_OP',
|
||||
]);
|
||||
|
||||
expect(outputUiData?.allTransactionIds.length).toEqual(1295);
|
||||
expect(outputUiData?.allLayerAndDisplayIds.length).toEqual(117);
|
||||
expect(assertDefined(outputUiData).allTransactionIds.length).toEqual(1295);
|
||||
expect(assertDefined(outputUiData).allLayerAndDisplayIds.length).toEqual(117);
|
||||
|
||||
expect(outputUiData?.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
|
||||
expect(assertDefined(outputUiData).entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
|
||||
|
||||
expect(outputUiData?.currentEntryIndex).toEqual(0);
|
||||
expect(outputUiData?.selectedEntryIndex).toBeUndefined();
|
||||
expect(outputUiData?.scrollToIndex).toEqual(0);
|
||||
expect(outputUiData?.currentPropertiesTree).toBeDefined();
|
||||
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).selectedEntryIndex).toBeUndefined();
|
||||
expect(assertDefined(outputUiData).scrollToIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).currentPropertiesTree).toBeDefined();
|
||||
});
|
||||
|
||||
it('processes trace position update and updates current entry and scroll position', () => {
|
||||
presenter.onTracePositionUpdate(createTracePosition(0));
|
||||
expect(outputUiData!.currentEntryIndex).toEqual(0);
|
||||
expect(outputUiData!.scrollToIndex).toEqual(0);
|
||||
it('processes trace position update and updates current entry and scroll position', async () => {
|
||||
await presenter.onTracePositionUpdate(createTracePosition(0));
|
||||
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).scrollToIndex).toEqual(0);
|
||||
|
||||
presenter.onTracePositionUpdate(createTracePosition(10));
|
||||
expect(outputUiData!.currentEntryIndex).toEqual(13);
|
||||
expect(outputUiData!.scrollToIndex).toEqual(13);
|
||||
await presenter.onTracePositionUpdate(createTracePosition(10));
|
||||
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13);
|
||||
expect(assertDefined(outputUiData).scrollToIndex).toEqual(13);
|
||||
});
|
||||
|
||||
it('filters entries according to transaction ID filter', () => {
|
||||
presenter.onIdFilterChanged('');
|
||||
expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
|
||||
expect(assertDefined(outputUiData).entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
|
||||
|
||||
presenter.onIdFilterChanged('2211908157465');
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.transactionId))).toEqual(
|
||||
new Set(['2211908157465'])
|
||||
);
|
||||
expect(
|
||||
new Set(assertDefined(outputUiData).entries.map((entry) => entry.transactionId))
|
||||
).toEqual(new Set(['2211908157465']));
|
||||
});
|
||||
|
||||
it('filters entries according to VSYNC ID filter', () => {
|
||||
presenter.onVSyncIdFilterChanged([]);
|
||||
expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
|
||||
expect(assertDefined(outputUiData).entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
|
||||
|
||||
presenter.onVSyncIdFilterChanged(['1']);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.vsyncId))).toEqual(new Set([1]));
|
||||
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.vsyncId))).toEqual(
|
||||
new Set([1])
|
||||
);
|
||||
|
||||
presenter.onVSyncIdFilterChanged(['1', '3', '10']);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.vsyncId))).toEqual(
|
||||
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.vsyncId))).toEqual(
|
||||
new Set([1, 3, 10])
|
||||
);
|
||||
});
|
||||
|
||||
it('filters entries according to PID filter', () => {
|
||||
presenter.onPidFilterChanged([]);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.pid))).toEqual(
|
||||
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.pid))).toEqual(
|
||||
new Set(['N/A', '0', '515', '1593', '2022', '2322', '2463', '3300'])
|
||||
);
|
||||
|
||||
presenter.onPidFilterChanged(['0']);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.pid))).toEqual(new Set(['0']));
|
||||
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.pid))).toEqual(
|
||||
new Set(['0'])
|
||||
);
|
||||
|
||||
presenter.onPidFilterChanged(['0', '515']);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.pid))).toEqual(new Set(['0', '515']));
|
||||
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.pid))).toEqual(
|
||||
new Set(['0', '515'])
|
||||
);
|
||||
});
|
||||
|
||||
it('filters entries according to UID filter', () => {
|
||||
presenter.onUidFilterChanged([]);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.uid))).toEqual(
|
||||
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.uid))).toEqual(
|
||||
new Set(['N/A', '1000', '1003', '10169', '10235', '10239'])
|
||||
);
|
||||
|
||||
presenter.onUidFilterChanged(['1000']);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.uid))).toEqual(new Set(['1000']));
|
||||
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.uid))).toEqual(
|
||||
new Set(['1000'])
|
||||
);
|
||||
|
||||
presenter.onUidFilterChanged(['1000', '1003']);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.uid))).toEqual(
|
||||
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.uid))).toEqual(
|
||||
new Set(['1000', '1003'])
|
||||
);
|
||||
});
|
||||
|
||||
it('filters entries according to type filter', () => {
|
||||
presenter.onTypeFilterChanged([]);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.type))).toEqual(
|
||||
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.type))).toEqual(
|
||||
new Set([
|
||||
UiDataEntryType.DISPLAY_CHANGED,
|
||||
UiDataEntryType.LAYER_ADDED,
|
||||
@@ -164,12 +180,12 @@ describe('PresenterTransactions', () => {
|
||||
);
|
||||
|
||||
presenter.onTypeFilterChanged([UiDataEntryType.LAYER_ADDED]);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.type))).toEqual(
|
||||
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.type))).toEqual(
|
||||
new Set([UiDataEntryType.LAYER_ADDED])
|
||||
);
|
||||
|
||||
presenter.onTypeFilterChanged([UiDataEntryType.LAYER_ADDED, UiDataEntryType.LAYER_DESTROYED]);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.type))).toEqual(
|
||||
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.type))).toEqual(
|
||||
new Set([UiDataEntryType.LAYER_ADDED, UiDataEntryType.LAYER_DESTROYED])
|
||||
);
|
||||
});
|
||||
@@ -177,27 +193,27 @@ describe('PresenterTransactions', () => {
|
||||
it('filters entries according to layer or display ID filter', () => {
|
||||
presenter.onLayerIdFilterChanged([]);
|
||||
expect(
|
||||
new Set(outputUiData!.entries.map((entry) => entry.layerOrDisplayId)).size
|
||||
new Set(assertDefined(outputUiData).entries.map((entry) => entry.layerOrDisplayId)).size
|
||||
).toBeGreaterThan(20);
|
||||
|
||||
presenter.onLayerIdFilterChanged(['1']);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.layerOrDisplayId))).toEqual(
|
||||
new Set(['1'])
|
||||
);
|
||||
expect(
|
||||
new Set(assertDefined(outputUiData).entries.map((entry) => entry.layerOrDisplayId))
|
||||
).toEqual(new Set(['1']));
|
||||
|
||||
presenter.onLayerIdFilterChanged(['1', '3']);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.layerOrDisplayId))).toEqual(
|
||||
new Set(['1', '3'])
|
||||
);
|
||||
expect(
|
||||
new Set(assertDefined(outputUiData).entries.map((entry) => entry.layerOrDisplayId))
|
||||
).toEqual(new Set(['1', '3']));
|
||||
});
|
||||
|
||||
it('includes no op transitions', () => {
|
||||
presenter.onTypeFilterChanged([UiDataEntryType.NO_OP]);
|
||||
expect(new Set(outputUiData!.entries.map((entry) => entry.type))).toEqual(
|
||||
expect(new Set(assertDefined(outputUiData).entries.map((entry) => entry.type))).toEqual(
|
||||
new Set([UiDataEntryType.NO_OP])
|
||||
);
|
||||
|
||||
for (const entry of outputUiData!.entries) {
|
||||
for (const entry of assertDefined(outputUiData).entries) {
|
||||
expect(entry.layerOrDisplayId).toEqual('');
|
||||
expect(entry.what).toEqual('');
|
||||
expect(entry.propertiesTree).toEqual({});
|
||||
@@ -205,74 +221,80 @@ describe('PresenterTransactions', () => {
|
||||
});
|
||||
|
||||
it('filters entries according to "what" search string', () => {
|
||||
expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
|
||||
expect(assertDefined(outputUiData).entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
|
||||
|
||||
presenter.onWhatSearchStringChanged('');
|
||||
expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
|
||||
expect(assertDefined(outputUiData).entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
|
||||
|
||||
presenter.onWhatSearchStringChanged('Crop');
|
||||
expect(outputUiData!.entries.length).toBeLessThan(TOTAL_OUTPUT_ENTRIES);
|
||||
expect(assertDefined(outputUiData).entries.length).toBeLessThan(TOTAL_OUTPUT_ENTRIES);
|
||||
|
||||
presenter.onWhatSearchStringChanged('STRING_WITH_NO_MATCHES');
|
||||
expect(outputUiData!.entries.length).toEqual(0);
|
||||
expect(assertDefined(outputUiData).entries.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('updates selected entry and properties tree when entry is clicked', () => {
|
||||
presenter.onTracePositionUpdate(createTracePosition(0));
|
||||
expect(outputUiData!.currentEntryIndex).toEqual(0);
|
||||
expect(outputUiData!.selectedEntryIndex).toBeUndefined();
|
||||
expect(outputUiData!.scrollToIndex).toEqual(0);
|
||||
expect(outputUiData!.currentPropertiesTree).toEqual(outputUiData!.entries[0].propertiesTree);
|
||||
it('updates selected entry and properties tree when entry is clicked', async () => {
|
||||
await presenter.onTracePositionUpdate(createTracePosition(0));
|
||||
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).selectedEntryIndex).toBeUndefined();
|
||||
expect(assertDefined(outputUiData).scrollToIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).currentPropertiesTree).toEqual(
|
||||
assertDefined(outputUiData).entries[0].propertiesTree
|
||||
);
|
||||
|
||||
presenter.onEntryClicked(10);
|
||||
expect(outputUiData!.currentEntryIndex).toEqual(0);
|
||||
expect(outputUiData!.selectedEntryIndex).toEqual(10);
|
||||
expect(outputUiData!.scrollToIndex).toBeUndefined(); // no scrolling
|
||||
expect(outputUiData!.currentPropertiesTree).toEqual(outputUiData!.entries[10].propertiesTree);
|
||||
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).selectedEntryIndex).toEqual(10);
|
||||
expect(assertDefined(outputUiData).scrollToIndex).toBeUndefined(); // no scrolling
|
||||
expect(assertDefined(outputUiData).currentPropertiesTree).toEqual(
|
||||
assertDefined(outputUiData).entries[10].propertiesTree
|
||||
);
|
||||
|
||||
// remove selection when selected entry is clicked again
|
||||
presenter.onEntryClicked(10);
|
||||
expect(outputUiData!.currentEntryIndex).toEqual(0);
|
||||
expect(outputUiData!.selectedEntryIndex).toBeUndefined();
|
||||
expect(outputUiData!.scrollToIndex).toBeUndefined(); // no scrolling
|
||||
expect(outputUiData!.currentPropertiesTree).toEqual(outputUiData!.entries[0].propertiesTree);
|
||||
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
|
||||
expect(assertDefined(outputUiData).selectedEntryIndex).toBeUndefined();
|
||||
expect(assertDefined(outputUiData).scrollToIndex).toBeUndefined(); // no scrolling
|
||||
expect(assertDefined(outputUiData).currentPropertiesTree).toEqual(
|
||||
assertDefined(outputUiData).entries[0].propertiesTree
|
||||
);
|
||||
});
|
||||
|
||||
it('computes current entry index', () => {
|
||||
presenter.onTracePositionUpdate(createTracePosition(0));
|
||||
expect(outputUiData!.currentEntryIndex).toEqual(0);
|
||||
it('computes current entry index', async () => {
|
||||
await presenter.onTracePositionUpdate(createTracePosition(0));
|
||||
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(0);
|
||||
|
||||
presenter.onTracePositionUpdate(createTracePosition(10));
|
||||
expect(outputUiData!.currentEntryIndex).toEqual(13);
|
||||
await presenter.onTracePositionUpdate(createTracePosition(10));
|
||||
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13);
|
||||
});
|
||||
|
||||
it('updates current entry index when filters change', () => {
|
||||
presenter.onTracePositionUpdate(createTracePosition(10));
|
||||
it('updates current entry index when filters change', async () => {
|
||||
await presenter.onTracePositionUpdate(createTracePosition(10));
|
||||
|
||||
presenter.onPidFilterChanged([]);
|
||||
expect(outputUiData!.currentEntryIndex).toEqual(13);
|
||||
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13);
|
||||
|
||||
presenter.onPidFilterChanged(['0']);
|
||||
expect(outputUiData!.currentEntryIndex).toEqual(10);
|
||||
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(10);
|
||||
|
||||
presenter.onPidFilterChanged(['0', '515']);
|
||||
expect(outputUiData!.currentEntryIndex).toEqual(11);
|
||||
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(11);
|
||||
|
||||
presenter.onPidFilterChanged(['0', '515', 'N/A']);
|
||||
expect(outputUiData!.currentEntryIndex).toEqual(13);
|
||||
expect(assertDefined(outputUiData).currentEntryIndex).toEqual(13);
|
||||
});
|
||||
|
||||
it('formats real time', () => {
|
||||
setUpTestEnvironment(TimestampType.REAL);
|
||||
expect(outputUiData!.entries[0].time).toEqual('2022-08-03T06:19:01.051480997');
|
||||
it('formats real time', async () => {
|
||||
await setUpTestEnvironment(TimestampType.REAL);
|
||||
expect(assertDefined(outputUiData).entries[0].time).toEqual('2022-08-03T06:19:01.051480997');
|
||||
});
|
||||
|
||||
it('formats elapsed time', () => {
|
||||
setUpTestEnvironment(TimestampType.ELAPSED);
|
||||
expect(outputUiData!.entries[0].time).toEqual('2s450ms981445ns');
|
||||
it('formats elapsed time', async () => {
|
||||
await setUpTestEnvironment(TimestampType.ELAPSED);
|
||||
expect(assertDefined(outputUiData).entries[0].time).toEqual('2s450ms981445ns');
|
||||
});
|
||||
|
||||
const setUpTestEnvironment = (timestampType: TimestampType) => {
|
||||
const setUpTestEnvironment = async (timestampType: TimestampType) => {
|
||||
trace = new TraceBuilder<object>().setParser(parser).setTimestampType(timestampType).build();
|
||||
|
||||
traces = new Traces();
|
||||
@@ -281,6 +303,8 @@ describe('PresenterTransactions', () => {
|
||||
presenter = new Presenter(traces, (data: UiData) => {
|
||||
outputUiData = data;
|
||||
});
|
||||
|
||||
await presenter.onTracePositionUpdate(createTracePosition(0)); // trigger initialization
|
||||
};
|
||||
|
||||
const createTracePosition = (entryIndex: number): TracePosition => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user