Integrate "UI Traces API" with Winscope App/Core
Bug: b/256564627
Test: npm run build:all && npm run test:all
Change-Id: Ic434abc3031b9d53ddb6289fed747971e90c430e
diff --git a/tools/winscope/src/app/components/app_component.ts b/tools/winscope/src/app/components/app_component.ts
index a4c971a..e22b7c7 100644
--- a/tools/winscope/src/app/components/app_component.ts
+++ b/tools/winscope/src/app/components/app_component.ts
@@ -273,11 +273,7 @@
}
getLoadedTraceTypes(): TraceType[] {
- return this.tracePipeline.getLoadedTraces().map((trace) => trace.type);
- }
-
- getVideoData(): Blob | undefined {
- return this.timelineData.getScreenRecordingVideo();
+ return this.tracePipeline.getLoadedTraceFiles().map((trace) => trace.type);
}
onTraceDataLoaded(viewers: Viewer[]) {
@@ -325,8 +321,8 @@
private makeActiveTraceFileInfo(view: View): string {
const traceFile = this.tracePipeline
- .getLoadedTraces()
- .find((trace) => trace.type === view.dependencies[0])?.traceFile;
+ .getLoadedTraceFiles()
+ .find((file) => file.type === view.dependencies[0])?.traceFile;
if (!traceFile) {
return '';
@@ -340,7 +336,7 @@
}
private async makeTraceFilesForDownload(): Promise<File[]> {
- return this.tracePipeline.getLoadedTraces().map((trace) => {
+ return this.tracePipeline.getLoadedTraceFiles().map((trace) => {
const traceType = TRACE_INFO[trace.type].name;
const newName = traceType + '/' + FileUtils.removeDirFromFileName(trace.traceFile.file.name);
return new File([trace.traceFile.file], newName);
diff --git a/tools/winscope/src/app/components/collect_traces_component.ts b/tools/winscope/src/app/components/collect_traces_component.ts
index 521515a..961f798 100644
--- a/tools/winscope/src/app/components/collect_traces_component.ts
+++ b/tools/winscope/src/app/components/collect_traces_component.ts
@@ -29,7 +29,7 @@
import {MatSnackBar} from '@angular/material/snack-bar';
import {TracePipeline} from 'app/trace_pipeline';
import {PersistentStore} from 'common/persistent_store';
-import {TraceFile} from 'trace/trace';
+import {TraceFile} from 'trace/trace_file';
import {Connection} from 'trace_collection/connection';
import {ProxyState} from 'trace_collection/proxy_client';
import {ProxyConnection} from 'trace_collection/proxy_connection';
@@ -518,7 +518,7 @@
console.log('loading files', this.connect.adbData());
this.tracePipeline.clear();
const traceFiles = this.connect.adbData().map((file) => new TraceFile(file));
- const parserErrors = await this.tracePipeline.loadTraces(traceFiles);
+ const parserErrors = await this.tracePipeline.loadTraceFiles(traceFiles);
ParserErrorSnackBarComponent.showIfNeeded(this.ngZone, this.snackBar, parserErrors);
this.traceDataLoaded.emit();
console.log('finished loading data!');
diff --git a/tools/winscope/src/app/components/upload_traces_component.ts b/tools/winscope/src/app/components/upload_traces_component.ts
index 8993667..f53e696 100644
--- a/tools/winscope/src/app/components/upload_traces_component.ts
+++ b/tools/winscope/src/app/components/upload_traces_component.ts
@@ -27,7 +27,7 @@
import {TracePipeline} from 'app/trace_pipeline';
import {FileUtils, OnFile} from 'common/file_utils';
import {FilesDownloadListener} from 'interfaces/files_download_listener';
-import {Trace, TraceFile} from 'trace/trace';
+import {LoadedTraceFile, TraceFile} from 'trace/trace_file';
import {ParserErrorSnackBarComponent} from './parser_error_snack_bar_component';
@Component({
@@ -58,9 +58,9 @@
</load-progress>
<mat-list
- *ngIf="!isLoadingFiles && this.tracePipeline.getLoadedTraces().length > 0"
+ *ngIf="!isLoadingFiles && this.tracePipeline.getLoadedTraceFiles().length > 0"
class="uploaded-files">
- <mat-list-item *ngFor="let trace of this.tracePipeline.getLoadedTraces()">
+ <mat-list-item *ngFor="let trace of this.tracePipeline.getLoadedTraceFiles()">
<mat-icon matListIcon>
{{ TRACE_INFO[trace.type].icon }}
</mat-icon>
@@ -74,7 +74,7 @@
</mat-list>
<div
- *ngIf="!isLoadingFiles && tracePipeline.getLoadedTraces().length === 0"
+ *ngIf="!isLoadingFiles && tracePipeline.getLoadedTraceFiles().length === 0"
class="drop-info">
<p class="mat-body-3 icon">
<mat-icon inline fontIcon="upload"></mat-icon>
@@ -84,7 +84,7 @@
</mat-card-content>
<div
- *ngIf="!isLoadingFiles && tracePipeline.getLoadedTraces().length > 0"
+ *ngIf="!isLoadingFiles && tracePipeline.getLoadedTraceFiles().length > 0"
class="trace-actions-container">
<button
color="primary"
@@ -231,10 +231,10 @@
await this.processFiles(Array.from(droppedFiles));
}
- onRemoveTrace(event: MouseEvent, trace: Trace) {
+ onRemoveTrace(event: MouseEvent, trace: LoadedTraceFile) {
event.preventDefault();
event.stopPropagation();
- this.tracePipeline.removeTrace(trace.type);
+ this.tracePipeline.removeTraceFile(trace.type);
this.changeDetectorRef.detectChanges();
}
@@ -267,7 +267,7 @@
this.progressMessage = 'Parsing files...';
this.changeDetectorRef.detectChanges();
- const parserErrors = await this.tracePipeline.loadTraces(traceFiles, onProgressUpdate);
+ const parserErrors = await this.tracePipeline.loadTraceFiles(traceFiles, onProgressUpdate);
this.isLoadingFiles = false;
this.changeDetectorRef.detectChanges();
diff --git a/tools/winscope/src/app/mediator.ts b/tools/winscope/src/app/mediator.ts
index 8964abf..5513b90 100644
--- a/tools/winscope/src/app/mediator.ts
+++ b/tools/winscope/src/app/mediator.ts
@@ -20,9 +20,10 @@
import {RemoteTimestampReceiver} from 'interfaces/remote_timestamp_receiver';
import {RemoteTimestampSender} from 'interfaces/remote_timestamp_sender';
import {Runnable} from 'interfaces/runnable';
-import {TimestampChangeListener} from 'interfaces/timestamp_change_listener';
import {TraceDataListener} from 'interfaces/trace_data_listener';
+import {TracePositionUpdateListener} from 'interfaces/trace_position_update_listener';
import {Timestamp, TimestampType} from 'trace/timestamp';
+import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {Viewer} from 'viewers/viewer';
import {ViewerFactory} from 'viewers/viewer_factory';
@@ -35,7 +36,7 @@
export type AbtChromeExtensionProtocolDependencyInversion = BuganizerAttachmentsDownloadEmitter &
Runnable;
export type AppComponentDependencyInversion = TraceDataListener;
-export type TimelineComponentDependencyInversion = TimestampChangeListener;
+export type TimelineComponentDependencyInversion = TracePositionUpdateListener;
export type UploadTracesComponentDependencyInversion = FilesDownloadListener;
export class Mediator {
@@ -68,8 +69,8 @@
this.appComponent = appComponent;
this.storage = storage;
- this.timelineData.setOnCurrentTimestampChanged((timestamp) => {
- this.onWinscopeCurrentTimestampChanged(timestamp);
+ this.timelineData.setOnTracePositionUpdate((position) => {
+ this.onWinscopeTracePositionUpdate(position);
});
this.crossToolProtocol.setOnBugreportReceived(
@@ -112,29 +113,25 @@
}
onWinscopeTraceDataLoaded() {
- this.processTraceData();
+ this.processTraces();
}
- onWinscopeCurrentTimestampChanged(timestamp: Timestamp | undefined) {
+ onWinscopeTracePositionUpdate(position: TracePosition) {
this.executeIgnoringRecursiveTimestampNotifications(() => {
- const entries = this.tracePipeline.getTraceEntries(timestamp);
- this.viewers.forEach((viewer) => {
- viewer.notifyCurrentTraceEntries(entries);
- });
+ this.updateViewersTracePosition(position);
- if (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);
- }
+ 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);
}
- this.timelineComponent?.onCurrentTimestampChanged(timestamp);
+ this.timelineComponent?.onTracePositionUpdate(position);
});
}
@@ -171,17 +168,16 @@
return;
}
- if (this.timelineData.getCurrentTimestamp() === timestamp) {
+ if (
+ this.timelineData.getCurrentPosition()?.timestamp.getValueNs() === timestamp.getValueNs()
+ ) {
return; // no timestamp change
}
- const entries = this.tracePipeline.getTraceEntries(timestamp);
- this.viewers.forEach((viewer) => {
- viewer.notifyCurrentTraceEntries(entries);
- });
-
- this.timelineData.setCurrentTimestamp(timestamp);
- this.timelineComponent?.onCurrentTimestampChanged(timestamp);
+ const position = TracePosition.fromTimestamp(timestamp);
+ this.updateViewersTracePosition(position);
+ this.timelineData.setPosition(position);
+ this.timelineComponent?.onTracePositionUpdate(position); //TODO: is this redundant?
});
}
@@ -190,9 +186,10 @@
this.uploadTracesComponent?.onFilesDownloaded(files);
}
- private processTraceData() {
+ private processTraces() {
+ this.tracePipeline.buildTraces();
this.timelineData.initialize(
- this.tracePipeline.getTimelines(),
+ this.tracePipeline.getTraces(),
this.tracePipeline.getScreenRecordingVideo()
);
this.createViewers();
@@ -205,15 +202,26 @@
}
private createViewers() {
- const traceTypes = this.tracePipeline.getLoadedTraces().map((trace) => trace.type);
- this.viewers = new ViewerFactory().createViewers(new Set<TraceType>(traceTypes), this.storage);
+ const traces = this.tracePipeline.getTraces();
+ const traceTypes = new Set<TraceType>();
+ traces.forEachTrace((trace) => {
+ traceTypes.add(trace.type);
+ });
+ this.viewers = new ViewerFactory().createViewers(traceTypes, traces, this.storage);
- // Make sure to update the viewers active entries as soon as they are created.
- if (this.timelineData.getCurrentTimestamp()) {
- this.onWinscopeCurrentTimestampChanged(this.timelineData.getCurrentTimestamp());
+ // Update the viewers as soon as they are created
+ const position = this.timelineData.getCurrentPosition();
+ if (position) {
+ this.onWinscopeTracePositionUpdate(position);
}
}
+ private updateViewersTracePosition(position: TracePosition) {
+ this.viewers.forEach((viewer) => {
+ viewer.onTracePositionUpdate(position);
+ });
+ }
+
private executeIgnoringRecursiveTimestampNotifications(op: () => void) {
if (this.isChangingCurrentTimestamp) {
return;
diff --git a/tools/winscope/src/app/mediator_test.ts b/tools/winscope/src/app/mediator_test.ts
index 28730bc..b597ddc 100644
--- a/tools/winscope/src/app/mediator_test.ts
+++ b/tools/winscope/src/app/mediator_test.ts
@@ -19,7 +19,8 @@
import {MockStorage} from 'test/unit/mock_storage';
import {UnitTestUtils} from 'test/unit/utils';
import {RealTimestamp} from 'trace/timestamp';
-import {TraceFile} from 'trace/trace';
+import {TraceFile} from 'trace/trace_file';
+import {TracePosition} from 'trace/trace_position';
import {ViewerFactory} from 'viewers/viewer_factory';
import {ViewerStub} from 'viewers/viewer_stub';
import {AppComponentStub} from './components/app_component_stub';
@@ -42,6 +43,8 @@
const TIMESTAMP_10 = new RealTimestamp(10n);
const TIMESTAMP_11 = new RealTimestamp(11n);
+ const POSITION_10 = TracePosition.fromTimestamp(TIMESTAMP_10);
+ const POSITION_11 = TracePosition.fromTimestamp(TIMESTAMP_11);
beforeEach(async () => {
timelineComponent = new TimelineComponentStub();
@@ -69,18 +72,18 @@
it('handles data load event from Winscope', async () => {
spyOn(timelineData, 'initialize').and.callThrough();
spyOn(appComponent, 'onTraceDataLoaded');
- spyOn(viewerStub, 'notifyCurrentTraceEntries');
+ spyOn(viewerStub, 'onTracePositionUpdate');
await loadTraces();
expect(timelineData.initialize).toHaveBeenCalledTimes(0);
expect(appComponent.onTraceDataLoaded).toHaveBeenCalledTimes(0);
- expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(0);
+ expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(0);
mediator.onWinscopeTraceDataLoaded();
expect(timelineData.initialize).toHaveBeenCalledTimes(1);
expect(appComponent.onTraceDataLoaded).toHaveBeenCalledOnceWith([viewerStub]);
// notifies viewer about current timestamp on creation
- expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(1);
+ expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
});
//TODO: test "bugreport data from cross-tool protocol" when FileUtils is fully compatible with
@@ -107,95 +110,95 @@
expect(uploadTracesComponent.onFilesDownloaded).toHaveBeenCalledTimes(1);
});
- it('propagates current timestamp changed through timeline', async () => {
+ it('propagates trace position update from timeline data', async () => {
await loadTraces();
mediator.onWinscopeTraceDataLoaded();
- spyOn(viewerStub, 'notifyCurrentTraceEntries');
- spyOn(timelineComponent, 'onCurrentTimestampChanged');
+ spyOn(viewerStub, 'onTracePositionUpdate');
+ spyOn(timelineComponent, 'onTracePositionUpdate');
spyOn(crossToolProtocol, 'sendTimestamp');
- expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(0);
- expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
+ expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(0);
+ expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
// notify timestamp
- timelineData.setCurrentTimestamp(TIMESTAMP_10);
- expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(1);
- expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(1);
+ timelineData.setPosition(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.setCurrentTimestamp(TIMESTAMP_10);
- expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(1);
- expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(1);
+ timelineData.setPosition(POSITION_10);
+ expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
+ expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(1);
// notify another timestamp
- timelineData.setCurrentTimestamp(TIMESTAMP_11);
- expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(2);
- expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(2);
+ timelineData.setPosition(POSITION_11);
+ expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(2);
+ expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(2);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(2);
});
describe('timestamp received from remote tool', () => {
- it('propagates timestamp changes', async () => {
+ it('propagates trace position update', async () => {
await loadTraces();
mediator.onWinscopeTraceDataLoaded();
- spyOn(viewerStub, 'notifyCurrentTraceEntries');
- spyOn(timelineComponent, 'onCurrentTimestampChanged');
- expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(0);
- expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
+ spyOn(viewerStub, 'onTracePositionUpdate');
+ spyOn(timelineComponent, 'onTracePositionUpdate');
+ expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(0);
+ expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
// receive timestamp
await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
- expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(1);
- expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(1);
+ 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.notifyCurrentTraceEntries).toHaveBeenCalledTimes(1);
- expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(1);
+ expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
+ expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(1);
// receive another
await crossToolProtocol.onTimestampReceived(TIMESTAMP_11);
- expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(2);
- expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(2);
+ expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(2);
+ expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(2);
});
it("doesn't propagate timestamp back to remote tool", async () => {
await loadTraces();
mediator.onWinscopeTraceDataLoaded();
- spyOn(viewerStub, 'notifyCurrentTraceEntries');
+ spyOn(viewerStub, 'onTracePositionUpdate');
spyOn(crossToolProtocol, 'sendTimestamp');
// receive timestamp
await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
- expect(viewerStub.notifyCurrentTraceEntries).toHaveBeenCalledTimes(1);
+ expect(viewerStub.onTracePositionUpdate).toHaveBeenCalledTimes(1);
expect(crossToolProtocol.sendTimestamp).toHaveBeenCalledTimes(0);
});
it('defers propagation till traces are loaded and visualized', async () => {
- spyOn(timelineComponent, 'onCurrentTimestampChanged');
+ spyOn(timelineComponent, 'onTracePositionUpdate');
// keep timestamp for later
await crossToolProtocol.onTimestampReceived(TIMESTAMP_10);
- expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
+ expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
// keep timestamp for later (replace previous one)
await crossToolProtocol.onTimestampReceived(TIMESTAMP_11);
- expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
+ expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledTimes(0);
// apply timestamp
await loadTraces();
mediator.onWinscopeTraceDataLoaded();
- expect(timelineComponent.onCurrentTimestampChanged).toHaveBeenCalledWith(TIMESTAMP_11);
+ expect(timelineComponent.onTracePositionUpdate).toHaveBeenCalledWith(POSITION_11);
});
});
const loadTraces = async () => {
- const traces = [
+ const files = [
new TraceFile(
await UnitTestUtils.getFixtureFile('traces/elapsed_and_real_timestamp/SurfaceFlinger.pb')
),
@@ -208,7 +211,7 @@
)
),
];
- const errors = await tracePipeline.loadTraces(traces);
+ const errors = await tracePipeline.loadTraceFiles(files);
expect(errors).toEqual([]);
};
});
diff --git a/tools/winscope/src/app/timeline_data.ts b/tools/winscope/src/app/timeline_data.ts
index 694c8af..ab9082b 100644
--- a/tools/winscope/src/app/timeline_data.ts
+++ b/tools/winscope/src/app/timeline_data.ts
@@ -14,90 +14,89 @@
* limitations under the License.
*/
-import {ArrayUtils} from 'common/array_utils';
import {FunctionUtils} from 'common/function_utils';
import {TimeUtils} from 'common/time_utils';
import {ScreenRecordingUtils} from 'trace/screen_recording_utils';
import {Timestamp, TimestampType} from 'trace/timestamp';
+import {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 {Timeline} from './trace_pipeline';
+import {assertDefined} from '../common/assert_utils';
-export type TimestampCallbackType = (timestamp: Timestamp | undefined) => void;
+export type TracePositionCallbackType = (position: TracePosition) => void;
export interface TimeRange {
from: Timestamp;
to: Timestamp;
}
-interface TimestampWithIndex {
- index: number;
- timestamp: Timestamp;
-}
export class TimelineData {
- private timelines = new Map<TraceType, Timestamp[]>();
- private timestampType?: TimestampType = undefined;
- private explicitlySetTimestamp?: Timestamp = undefined;
- private explicitlySetSelection?: TimeRange = undefined;
- private screenRecordingVideo?: Blob = undefined;
+ private traces = new Traces();
+ private screenRecordingVideo?: Blob;
+ private timestampType?: TimestampType;
+ private firstEntry?: TraceEntry<{}>;
+ private lastEntry?: TraceEntry<{}>;
+ private explicitlySetPosition?: TracePosition;
+ private explicitlySetSelection?: TimeRange;
private activeViewTraceTypes: TraceType[] = []; // dependencies of current active view
- private onCurrentTimestampChanged: TimestampCallbackType = FunctionUtils.DO_NOTHING;
+ private onTracePositionUpdate: TracePositionCallbackType = FunctionUtils.DO_NOTHING;
- initialize(timelines: Timeline[], screenRecordingVideo: Blob | undefined) {
+ initialize(traces: Traces, screenRecordingVideo: Blob | undefined) {
this.clear();
+ this.traces = traces;
this.screenRecordingVideo = screenRecordingVideo;
+ this.firstEntry = this.findFirstEntry();
+ this.lastEntry = this.findLastEntry();
+ this.timestampType = this.firstEntry?.getTimestamp().getType();
- const allTimestamps = timelines.flatMap((timeline) => timeline.timestamps);
- if (allTimestamps.some((timestamp) => timestamp.getType() !== allTimestamps[0].getType())) {
- throw Error('Added timeline has inconsistent timestamps.');
+ const position = this.getCurrentPosition();
+ if (position) {
+ this.onTracePositionUpdate(position);
}
-
- if (allTimestamps.length > 0) {
- this.timestampType = allTimestamps[0].getType();
- }
-
- timelines.forEach((timeline) => {
- this.timelines.set(timeline.traceType, timeline.timestamps);
- });
-
- this.onCurrentTimestampChanged(this.getCurrentTimestamp());
}
- setOnCurrentTimestampChanged(callback: TimestampCallbackType) {
- this.onCurrentTimestampChanged = callback;
+ setOnTracePositionUpdate(callback: TracePositionCallbackType) {
+ this.onTracePositionUpdate = callback;
}
- getCurrentTimestamp(): Timestamp | undefined {
- if (this.explicitlySetTimestamp !== undefined) {
- return this.explicitlySetTimestamp;
+ getCurrentPosition(): TracePosition | undefined {
+ if (this.explicitlySetPosition) {
+ return this.explicitlySetPosition;
}
- if (this.getFirstTimestampOfActiveViewTraces() !== undefined) {
- return this.getFirstTimestampOfActiveViewTraces();
+ const firstActiveEntry = this.getFirstEntryOfActiveViewTraces();
+ if (firstActiveEntry) {
+ return TracePosition.fromTraceEntry(firstActiveEntry);
}
- return this.getFirstTimestamp();
+ if (this.firstEntry) {
+ return TracePosition.fromTraceEntry(this.firstEntry);
+ }
+ return undefined;
}
- setCurrentTimestamp(timestamp: Timestamp | undefined) {
+ setPosition(position: TracePosition | undefined) {
if (!this.hasTimestamps()) {
- console.warn('Attempted to set timestamp on traces with no timestamps/entries...');
+ console.warn('Attempted to set position on traces with no timestamps/entries...');
return;
}
- if (timestamp !== undefined) {
+ if (position) {
if (this.timestampType === undefined) {
- throw Error('Attempted to set explicit timestamp but no timestamp type is available');
+ throw Error('Attempted to set explicit position but no timestamp type is available');
}
- if (timestamp.getType() !== this.timestampType) {
- throw Error('Attempted to set explicit timestamp with incompatible type');
+ if (position.timestamp.getType() !== this.timestampType) {
+ throw Error('Attempted to set explicit position with incompatible timestamp type');
}
}
- this.applyOperationAndNotifyIfCurrentTimestampChanged(() => {
- this.explicitlySetTimestamp = timestamp;
+ this.applyOperationAndNotifyIfCurrentPositionChanged(() => {
+ this.explicitlySetPosition = position;
});
}
setActiveViewTraceTypes(types: TraceType[]) {
- this.applyOperationAndNotifyIfCurrentTimestampChanged(() => {
+ this.applyOperationAndNotifyIfCurrentPositionChanged(() => {
this.activeViewTraceTypes = types;
});
}
@@ -106,130 +105,125 @@
return this.timestampType;
}
- getFullRange(): TimeRange {
- if (!this.hasTimestamps()) {
- throw Error('Trying to get full range when there are no timestamps');
+ getFullTimeRange(): TimeRange {
+ if (!this.firstEntry || !this.lastEntry) {
+ throw Error('Trying to get full time range when there are no timestamps');
}
return {
- from: this.getFirstTimestamp()!,
- to: this.getLastTimestamp()!,
+ from: this.firstEntry.getTimestamp(),
+ to: this.lastEntry.getTimestamp(),
};
}
- getSelectionRange(): TimeRange {
+ getSelectionTimeRange(): TimeRange {
if (this.explicitlySetSelection === undefined) {
- return this.getFullRange();
+ return this.getFullTimeRange();
} else {
return this.explicitlySetSelection;
}
}
- setSelectionRange(selection: TimeRange) {
+ setSelectionTimeRange(selection: TimeRange) {
this.explicitlySetSelection = selection;
}
- getTimelines(): Map<TraceType, Timestamp[]> {
- return this.timelines;
+ getTraces(): Traces {
+ return this.traces;
}
getScreenRecordingVideo(): Blob | undefined {
return this.screenRecordingVideo;
}
- searchCorrespondingScreenRecordingTimeSeconds(timestamp: Timestamp): number | undefined {
- const timestamps = this.timelines.get(TraceType.SCREEN_RECORDING);
- if (!timestamps) {
+ searchCorrespondingScreenRecordingTimeSeconds(position: TracePosition): number | undefined {
+ const trace = this.traces.getTrace(TraceType.SCREEN_RECORDING);
+ if (!trace || trace.lengthEntries === 0) {
return undefined;
}
- const firstTimestamp = timestamps[0];
-
- const correspondingTimestamp = this.searchCorrespondingTimestampFor(
- TraceType.SCREEN_RECORDING,
- timestamp
- )?.timestamp;
- if (correspondingTimestamp === undefined) {
+ const firstTimestamp = trace.getEntry(0).getTimestamp();
+ const entry = TraceEntryFinder.findCorrespondingEntry(trace, position);
+ if (!entry) {
return undefined;
}
- return ScreenRecordingUtils.timestampToVideoTimeSeconds(firstTimestamp, correspondingTimestamp);
+ return ScreenRecordingUtils.timestampToVideoTimeSeconds(firstTimestamp, entry.getTimestamp());
}
hasTimestamps(): boolean {
- return Array.from(this.timelines.values()).some((timestamps) => timestamps.length > 0);
+ return this.firstEntry !== undefined;
}
hasMoreThanOneDistinctTimestamp(): boolean {
- return this.hasTimestamps() && this.getFirstTimestamp() !== this.getLastTimestamp();
+ return (
+ this.hasTimestamps() &&
+ this.firstEntry?.getTimestamp().getValueNs() !== this.lastEntry?.getTimestamp().getValueNs()
+ );
}
- getCurrentTimestampFor(type: TraceType): Timestamp | undefined {
- return this.searchCorrespondingTimestampFor(type, this.getCurrentTimestamp())?.timestamp;
+ getPreviousEntryFor(type: TraceType): TraceEntry<{}> | undefined {
+ const trace = assertDefined(this.traces.getTrace(type));
+ if (trace.lengthEntries === 0) {
+ return undefined;
+ }
+
+ const currentIndex = this.findCurrentEntryFor(type)?.getIndex();
+ if (currentIndex === undefined || currentIndex === 0) {
+ return undefined;
+ }
+
+ return trace.getEntry(currentIndex - 1);
}
- getPreviousTimestampFor(type: TraceType): Timestamp | undefined {
- const currentIndex = this.searchCorrespondingTimestampFor(
- type,
- this.getCurrentTimestamp()
- )?.index;
+ getNextEntryFor(type: TraceType): TraceEntry<{}> | undefined {
+ const trace = assertDefined(this.traces.getTrace(type));
+ if (trace.lengthEntries === 0) {
+ return undefined;
+ }
+ const currentIndex = this.findCurrentEntryFor(type)?.getIndex();
if (currentIndex === undefined) {
- // Only acceptable reason for this to be undefined is if we are before the first entry for this type
- if (
- this.timelines.get(type)!.length === 0 ||
- this.getCurrentTimestamp()!.getValueNs() < this.timelines.get(type)![0].getValueNs()
- ) {
- return undefined;
- }
- throw Error(`Missing active timestamp for trace type ${type}`);
+ return trace.getEntry(0);
}
- const previousIndex = currentIndex - 1;
- if (previousIndex < 0) {
+ if (currentIndex + 1 >= trace.lengthEntries) {
return undefined;
}
- return this.timelines.get(type)?.[previousIndex];
+ return trace.getEntry(currentIndex + 1);
}
- getNextTimestampFor(type: TraceType): Timestamp | undefined {
- const currentIndex =
- this.searchCorrespondingTimestampFor(type, this.getCurrentTimestamp())?.index ?? -1;
-
- if (this.timelines.get(type)?.length === 0 ?? true) {
- throw Error(`Missing active timestamp for trace type ${type}`);
- }
-
- const timestamps = this.timelines.get(type);
- if (timestamps === undefined) {
- throw Error('Timestamps for tracetype not found');
- }
- const nextIndex = currentIndex + 1;
- if (nextIndex >= timestamps.length) {
+ findCurrentEntryFor(type: TraceType): TraceEntry<{}> | undefined {
+ const position = this.getCurrentPosition();
+ if (!position) {
return undefined;
}
-
- return timestamps[nextIndex];
+ return TraceEntryFinder.findCorrespondingEntry(
+ assertDefined(this.traces.getTrace(type)),
+ position
+ );
}
- moveToPreviousTimestampFor(type: TraceType) {
- const prevTimestamp = this.getPreviousTimestampFor(type);
- if (prevTimestamp !== undefined) {
- this.setCurrentTimestamp(prevTimestamp);
+ moveToPreviousEntryFor(type: TraceType) {
+ const prevEntry = this.getPreviousEntryFor(type);
+ if (prevEntry !== undefined) {
+ this.setPosition(TracePosition.fromTraceEntry(prevEntry));
}
}
- moveToNextTimestampFor(type: TraceType) {
- const nextTimestamp = this.getNextTimestampFor(type);
- if (nextTimestamp !== undefined) {
- this.setCurrentTimestamp(nextTimestamp);
+ moveToNextEntryFor(type: TraceType) {
+ const nextEntry = this.getNextEntryFor(type);
+ if (nextEntry !== undefined) {
+ this.setPosition(TracePosition.fromTraceEntry(nextEntry));
}
}
clear() {
- this.applyOperationAndNotifyIfCurrentTimestampChanged(() => {
- this.timelines.clear();
- this.explicitlySetTimestamp = undefined;
+ this.applyOperationAndNotifyIfCurrentPositionChanged(() => {
+ this.traces = new Traces();
+ this.firstEntry = undefined;
+ this.lastEntry = undefined;
+ this.explicitlySetPosition = undefined;
this.timestampType = undefined;
this.explicitlySetSelection = undefined;
this.screenRecordingVideo = undefined;
@@ -237,71 +231,58 @@
});
}
- private getFirstTimestamp(): Timestamp | undefined {
- if (!this.hasTimestamps()) {
- return undefined;
- }
+ private findFirstEntry(): TraceEntry<{}> | undefined {
+ let first: TraceEntry<{}> | undefined = undefined;
- return Array.from(this.timelines.values())
- .map((timestamps) => timestamps[0])
- .filter((timestamp) => timestamp !== undefined)
- .reduce((prev, current) => (prev < current ? prev : current));
+ this.traces.forEachTrace((trace) => {
+ if (trace.lengthEntries === 0) {
+ return;
+ }
+ const candidate = trace.getEntry(0);
+ if (!first || candidate.getTimestamp() < first.getTimestamp()) {
+ first = candidate;
+ }
+ });
+
+ return first;
}
- private getLastTimestamp(): Timestamp | undefined {
- if (!this.hasTimestamps()) {
- return undefined;
- }
+ private findLastEntry(): TraceEntry<{}> | undefined {
+ let last: TraceEntry<{}> | undefined = undefined;
- return Array.from(this.timelines.values())
- .map((timestamps) => timestamps[timestamps.length - 1])
- .filter((timestamp) => timestamp !== undefined)
- .reduce((prev, current) => (prev > current ? prev : current));
+ this.traces.forEachTrace((trace) => {
+ if (trace.lengthEntries === 0) {
+ return;
+ }
+ const candidate = trace.getEntry(trace.lengthEntries - 1);
+ if (!last || candidate.getTimestamp() > last.getTimestamp()) {
+ last = candidate;
+ }
+ });
+
+ return last;
}
- private searchCorrespondingTimestampFor(
- type: TraceType,
- timestamp: Timestamp | undefined
- ): TimestampWithIndex | undefined {
- if (timestamp === undefined) {
+ private getFirstEntryOfActiveViewTraces(): TraceEntry<{}> | undefined {
+ const activeEntries = this.activeViewTraceTypes
+ .map((traceType) => assertDefined(this.traces.getTrace(traceType)))
+ .filter((trace) => trace.lengthEntries > 0)
+ .map((trace) => trace.getEntry(0))
+ .sort((a, b) => {
+ return TimeUtils.compareFn(a.getTimestamp(), b.getTimestamp());
+ });
+ if (activeEntries.length === 0) {
return undefined;
}
-
- if (timestamp.getType() !== this.timestampType) {
- throw Error('Invalid timestamp type');
- }
-
- const timeline = this.timelines.get(type);
- if (timeline === undefined) {
- throw Error(`No timeline for requested trace type ${type}`);
- }
- const index = ArrayUtils.binarySearchLowerOrEqual(timeline, timestamp);
- if (index === undefined) {
- return undefined;
- }
- return {index, timestamp: timeline[index]};
+ return activeEntries[0];
}
- private getFirstTimestampOfActiveViewTraces(): Timestamp | undefined {
- if (this.activeViewTraceTypes.length === 0) {
- return undefined;
- }
- const activeTimestamps = this.activeViewTraceTypes
- .map((traceType) => this.timelines.get(traceType)!)
- .map((timestamps) => timestamps[0])
- .filter((timestamp) => timestamp !== undefined)
- .sort(TimeUtils.compareFn);
- if (activeTimestamps.length === 0) {
- return undefined;
- }
- return activeTimestamps[0];
- }
-
- private applyOperationAndNotifyIfCurrentTimestampChanged(op: () => void) {
- const prevTimestamp = this.getCurrentTimestamp();
+ private applyOperationAndNotifyIfCurrentPositionChanged(op: () => void) {
+ const prevPosition = this.getCurrentPosition();
op();
- if (prevTimestamp !== this.getCurrentTimestamp()) {
- this.onCurrentTimestampChanged(this.getCurrentTimestamp());
+ const currentPosition = this.getCurrentPosition();
+ if (currentPosition && (!prevPosition || !currentPosition.isEqual(prevPosition))) {
+ this.onTracePositionUpdate(currentPosition);
}
}
}
diff --git a/tools/winscope/src/app/timeline_data_test.ts b/tools/winscope/src/app/timeline_data_test.ts
index 362bf1e..09ce031 100644
--- a/tools/winscope/src/app/timeline_data_test.ts
+++ b/tools/winscope/src/app/timeline_data_test.ts
@@ -14,169 +14,162 @@
* limitations under the License.
*/
-import {Timestamp, TimestampType} from 'trace/timestamp';
+import {TracesBuilder} from 'test/unit/traces_builder';
+import {RealTimestamp, Timestamp, TimestampType} from 'trace/timestamp';
+import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {TimelineData} from './timeline_data';
-import {Timeline} from './trace_pipeline';
-class TimestampChangedObserver {
- onCurrentTimestampChanged(timestamp: Timestamp | undefined) {
+class TracePositionUpdateListener {
+ onTracePositionUpdate(position: TracePosition) {
// do nothing
}
}
describe('TimelineData', () => {
let timelineData: TimelineData;
- const timestampChangedObserver = new TimestampChangedObserver();
+ const positionUpdateListener = new TracePositionUpdateListener();
const timestamp10 = new Timestamp(TimestampType.REAL, 10n);
const timestamp11 = new Timestamp(TimestampType.REAL, 11n);
- const timelines: Timeline[] = [
- {
- traceType: TraceType.SURFACE_FLINGER,
- timestamps: [timestamp10],
- },
- {
- traceType: TraceType.WINDOW_MANAGER,
- timestamps: [timestamp11],
- },
- ];
+ const traces = new TracesBuilder()
+ .setTimestamps(TraceType.SURFACE_FLINGER, [timestamp10])
+ .setTimestamps(TraceType.WINDOW_MANAGER, [timestamp11])
+ .build();
+
+ const position10 = TracePosition.fromTraceEntry(
+ traces.getTrace(TraceType.SURFACE_FLINGER)!.getEntry(0)
+ );
+ const position11 = TracePosition.fromTraceEntry(
+ traces.getTrace(TraceType.WINDOW_MANAGER)!.getEntry(0)
+ );
beforeEach(() => {
timelineData = new TimelineData();
- timelineData.setOnCurrentTimestampChanged((timestamp) => {
- timestampChangedObserver.onCurrentTimestampChanged(timestamp);
+ timelineData.setOnTracePositionUpdate((position) => {
+ positionUpdateListener.onTracePositionUpdate(position);
});
});
- it('sets timelines', () => {
- expect(timelineData.getCurrentTimestamp()).toBeUndefined();
+ it('can be initialized', () => {
+ expect(timelineData.getCurrentPosition()).toBeUndefined();
- timelineData.initialize(timelines, undefined);
- expect(timelineData.getCurrentTimestamp()).toEqual(timestamp10);
+ timelineData.initialize(traces, undefined);
+ expect(timelineData.getCurrentPosition()).toBeDefined();
});
- it('uses first timestamp by default', () => {
- timelineData.initialize(timelines, undefined);
- expect(timelineData.getCurrentTimestamp()?.getValueNs()).toEqual(10n);
+ it('uses first entry by default', () => {
+ timelineData.initialize(traces, undefined);
+ expect(timelineData.getCurrentPosition()).toEqual(position10);
});
- it('uses explicit timestamp if set', () => {
- timelineData.initialize(timelines, undefined);
- expect(timelineData.getCurrentTimestamp()?.getValueNs()).toEqual(10n);
+ it('uses explicit position if set', () => {
+ timelineData.initialize(traces, undefined);
+ expect(timelineData.getCurrentPosition()).toEqual(position10);
- const explicitTimestamp = new Timestamp(TimestampType.REAL, 1000n);
- timelineData.setCurrentTimestamp(explicitTimestamp);
- expect(timelineData.getCurrentTimestamp()).toEqual(explicitTimestamp);
+ const explicitPosition = TracePosition.fromTimestamp(new RealTimestamp(1000n));
+ timelineData.setPosition(explicitPosition);
+ expect(timelineData.getCurrentPosition()).toEqual(explicitPosition);
+
+ timelineData.setActiveViewTraceTypes([TraceType.SURFACE_FLINGER]);
+ expect(timelineData.getCurrentPosition()).toEqual(explicitPosition);
timelineData.setActiveViewTraceTypes([TraceType.WINDOW_MANAGER]);
- expect(timelineData.getCurrentTimestamp()).toEqual(explicitTimestamp);
+ expect(timelineData.getCurrentPosition()).toEqual(explicitPosition);
});
- it('sets active trace types and update current timestamp accordingly', () => {
- timelineData.initialize(timelines, undefined);
+ it('sets active trace types and update current position accordingly', () => {
+ timelineData.initialize(traces, undefined);
timelineData.setActiveViewTraceTypes([]);
- expect(timelineData.getCurrentTimestamp()).toEqual(timestamp10);
+ expect(timelineData.getCurrentPosition()).toEqual(position10);
timelineData.setActiveViewTraceTypes([TraceType.WINDOW_MANAGER]);
- expect(timelineData.getCurrentTimestamp()).toEqual(timestamp11);
+ expect(timelineData.getCurrentPosition()).toEqual(position11);
timelineData.setActiveViewTraceTypes([TraceType.SURFACE_FLINGER]);
- expect(timelineData.getCurrentTimestamp()).toEqual(timestamp10);
+ expect(timelineData.getCurrentPosition()).toEqual(position10);
timelineData.setActiveViewTraceTypes([TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER]);
- expect(timelineData.getCurrentTimestamp()).toEqual(timestamp10);
+ expect(timelineData.getCurrentPosition()).toEqual(position10);
});
- it('notifies callback when current timestamp changes', () => {
- spyOn(timestampChangedObserver, 'onCurrentTimestampChanged');
- expect(timestampChangedObserver.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
+ it('executes callback on position update', () => {
+ spyOn(positionUpdateListener, 'onTracePositionUpdate');
+ expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(0);
- timelineData.initialize(timelines, undefined);
- expect(timestampChangedObserver.onCurrentTimestampChanged).toHaveBeenCalledTimes(1);
+ timelineData.initialize(traces, undefined);
+ expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(1);
timelineData.setActiveViewTraceTypes([TraceType.WINDOW_MANAGER]);
- expect(timestampChangedObserver.onCurrentTimestampChanged).toHaveBeenCalledTimes(2);
+ expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(2);
});
- it("doesn't notify observers when current timestamp doesn't change", () => {
- timelineData.initialize(timelines, undefined);
+ it("doesn't execute callback when position doesn't change", () => {
+ timelineData.initialize(traces, undefined);
- spyOn(timestampChangedObserver, 'onCurrentTimestampChanged');
- expect(timestampChangedObserver.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
+ spyOn(positionUpdateListener, 'onTracePositionUpdate');
+ expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(0);
timelineData.setActiveViewTraceTypes([TraceType.SURFACE_FLINGER]);
- expect(timestampChangedObserver.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
+ expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(0);
timelineData.setActiveViewTraceTypes([TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER]);
- expect(timestampChangedObserver.onCurrentTimestampChanged).toHaveBeenCalledTimes(0);
+ expect(positionUpdateListener.onTracePositionUpdate).toHaveBeenCalledTimes(0);
});
it('hasTimestamps()', () => {
expect(timelineData.hasTimestamps()).toBeFalse();
- timelineData.initialize([], undefined);
- expect(timelineData.hasTimestamps()).toBeFalse();
-
- timelineData.initialize(
- [
- {
- traceType: TraceType.SURFACE_FLINGER,
- timestamps: [],
- },
- ],
- undefined
- );
- expect(timelineData.hasTimestamps()).toBeFalse();
-
- timelineData.initialize(
- [
- {
- traceType: TraceType.SURFACE_FLINGER,
- timestamps: [new Timestamp(TimestampType.REAL, 10n)],
- },
- ],
- undefined
- );
- expect(timelineData.hasTimestamps()).toBeTrue();
+ // no trace
+ {
+ const traces = new TracesBuilder().build();
+ timelineData.initialize(traces, undefined);
+ expect(timelineData.hasTimestamps()).toBeFalse();
+ }
+ // trace without timestamps
+ {
+ const traces = new TracesBuilder().setTimestamps(TraceType.SURFACE_FLINGER, []).build();
+ timelineData.initialize(traces, undefined);
+ expect(timelineData.hasTimestamps()).toBeFalse();
+ }
+ // trace with timestamps
+ {
+ const traces = new TracesBuilder()
+ .setTimestamps(TraceType.SURFACE_FLINGER, [timestamp10])
+ .build();
+ timelineData.initialize(traces, undefined);
+ expect(timelineData.hasTimestamps()).toBeTrue();
+ }
});
it('hasMoreThanOneDistinctTimestamp()', () => {
expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeFalse();
- timelineData.initialize([], undefined);
- expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeFalse();
-
- timelineData.initialize(
- [
- {
- traceType: TraceType.SURFACE_FLINGER,
- timestamps: [new Timestamp(TimestampType.REAL, 10n)],
- },
- {
- traceType: TraceType.WINDOW_MANAGER,
- timestamps: [new Timestamp(TimestampType.REAL, 10n)],
- },
- ],
- undefined
- );
- expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeFalse();
-
- timelineData.initialize(
- [
- {
- traceType: TraceType.SURFACE_FLINGER,
- timestamps: [new Timestamp(TimestampType.REAL, 10n)],
- },
- {
- traceType: TraceType.WINDOW_MANAGER,
- timestamps: [new Timestamp(TimestampType.REAL, 11n)],
- },
- ],
- undefined
- );
- expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeTrue();
+ // no trace
+ {
+ const traces = new TracesBuilder().build();
+ timelineData.initialize(traces, undefined);
+ expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeFalse();
+ }
+ // no distinct timestamps
+ {
+ const traces = new TracesBuilder()
+ .setTimestamps(TraceType.SURFACE_FLINGER, [timestamp10])
+ .setTimestamps(TraceType.WINDOW_MANAGER, [timestamp10])
+ .build();
+ timelineData.initialize(traces, undefined);
+ expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeFalse();
+ }
+ // distinct timestamps
+ {
+ const traces = new TracesBuilder()
+ .setTimestamps(TraceType.SURFACE_FLINGER, [timestamp10])
+ .setTimestamps(TraceType.WINDOW_MANAGER, [timestamp11])
+ .build();
+ timelineData.initialize(traces, undefined);
+ expect(timelineData.hasMoreThanOneDistinctTimestamp()).toBeTrue();
+ }
});
});
diff --git a/tools/winscope/src/app/trace_pipeline.ts b/tools/winscope/src/app/trace_pipeline.ts
index 27500a5..5620b4c 100644
--- a/tools/winscope/src/app/trace_pipeline.ts
+++ b/tools/winscope/src/app/trace_pipeline.ts
@@ -14,26 +14,23 @@
* limitations under the License.
*/
-import {ArrayUtils} from 'common/array_utils';
import {FunctionUtils, OnProgressUpdateType} from 'common/function_utils';
-import {Parser} from 'parsers/parser';
import {ParserError, ParserFactory} from 'parsers/parser_factory';
-import {ScreenRecordingTraceEntry} from 'trace/screen_recording';
-import {Timestamp, TimestampType} from 'trace/timestamp';
-import {Trace, TraceFile} from 'trace/trace';
+import {FrameMapper} from 'trace/frame_mapper';
+import {Parser} from 'trace/parser';
+import {TimestampType} from 'trace/timestamp';
+import {Trace} from 'trace/trace';
+import {Traces} from 'trace/traces';
+import {LoadedTraceFile, TraceFile} from 'trace/trace_file';
import {TraceType} from 'trace/trace_type';
-interface Timeline {
- traceType: TraceType;
- timestamps: Timestamp[];
-}
-
class TracePipeline {
private parserFactory = new ParserFactory();
- private parsers: Parser[] = [];
+ private parsers: Array<Parser<object>> = [];
+ private traces?: Traces;
private commonTimestampType?: TimestampType;
- async loadTraces(
+ async loadTraceFiles(
traceFiles: TraceFile[],
onLoadProgressUpdate: OnProgressUpdateType = FunctionUtils.DO_NOTHING
): Promise<ParserError[]> {
@@ -45,78 +42,51 @@
return parserErrors;
}
- removeTrace(type: TraceType) {
+ removeTraceFile(type: TraceType) {
this.parsers = this.parsers.filter((parser) => parser.getTraceType() !== type);
}
- getLoadedTraces(): Trace[] {
- return this.parsers.map((parser: Parser) => parser.getTrace());
+ getLoadedTraceFiles(): LoadedTraceFile[] {
+ return this.parsers.map(
+ (parser: Parser<object>) => new LoadedTraceFile(parser.getTraceFile(), parser.getTraceType())
+ );
}
- getTraceEntries(timestamp: Timestamp | undefined): Map<TraceType, any> {
- const traceEntries: Map<TraceType, any> = new Map<TraceType, any>();
+ buildTraces() {
+ const commonTimestampType = this.getCommonTimestampType();
- if (!timestamp) {
- return traceEntries;
- }
-
+ this.traces = new Traces();
this.parsers.forEach((parser) => {
- const targetTimestamp = timestamp;
- const entry = parser.getTraceEntry(targetTimestamp);
- let prevEntry = null;
-
- const parserTimestamps = parser.getTimestamps(timestamp.getType());
- if (parserTimestamps === undefined) {
- throw new Error(
- `Unexpected timestamp type ${timestamp.getType()}.` +
- ` Not supported by parser for trace type: ${parser.getTraceType()}`
- );
- }
-
- const index = ArrayUtils.binarySearchLowerOrEqual(parserTimestamps, targetTimestamp);
- if (index !== undefined && index > 0) {
- prevEntry = parser.getTraceEntry(parserTimestamps[index - 1]);
- }
-
- if (entry !== undefined) {
- traceEntries.set(parser.getTraceType(), [entry, prevEntry]);
- }
+ const trace = new Trace(
+ parser.getTraceType(),
+ parser.getTraceFile(),
+ undefined,
+ parser,
+ commonTimestampType,
+ {start: 0, end: parser.getLengthEntries()}
+ );
+ this.traces?.setTrace(parser.getTraceType(), trace);
});
-
- return traceEntries;
+ new FrameMapper(this.traces).computeMapping();
}
- getTimelines(): Timeline[] {
- const timelines = this.parsers.map((parser): Timeline => {
- const timestamps = parser.getTimestamps(this.getCommonTimestampType());
- if (timestamps === undefined) {
- throw Error('Failed to get timestamps from parser');
- }
- return {traceType: parser.getTraceType(), timestamps};
- });
-
- return timelines;
+ getTraces(): Traces {
+ this.checkTracesWereBuilt();
+ return this.traces!;
}
getScreenRecordingVideo(): undefined | Blob {
- const parser = this.parsers.find(
- (parser) => parser.getTraceType() === TraceType.SCREEN_RECORDING
- );
- if (!parser) {
+ const screenRecording = this.getTraces().getTrace(TraceType.SCREEN_RECORDING);
+ if (!screenRecording || screenRecording.lengthEntries === 0) {
return undefined;
}
-
- const timestamps = parser.getTimestamps(this.getCommonTimestampType());
- if (!timestamps || timestamps.length === 0) {
- return undefined;
- }
-
- return (parser.getTraceEntry(timestamps[0]) as ScreenRecordingTraceEntry)?.videoData;
+ return screenRecording.getEntry(0).getValue().videoData;
}
clear() {
this.parserFactory = new ParserFactory();
this.parsers = [];
+ this.traces = undefined;
this.commonTimestampType = undefined;
}
@@ -135,6 +105,14 @@
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 {Timeline, TracePipeline};
+export {TracePipeline};
diff --git a/tools/winscope/src/app/trace_pipeline_test.ts b/tools/winscope/src/app/trace_pipeline_test.ts
index daddbf8..43d29c3 100644
--- a/tools/winscope/src/app/trace_pipeline_test.ts
+++ b/tools/winscope/src/app/trace_pipeline_test.ts
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+import {TracesUtils} from 'test/unit/traces_utils';
import {UnitTestUtils} from 'test/unit/utils';
-import {Timestamp, TimestampType} from 'trace/timestamp';
-import {TraceFile} from 'trace/trace';
+import {TraceFile} from 'trace/trace_file';
import {TraceType} from 'trace/trace_type';
import {TracePipeline} from './trace_pipeline';
@@ -27,9 +28,15 @@
});
it('can load valid trace files', async () => {
- expect(tracePipeline.getLoadedTraces().length).toEqual(0);
+ expect(tracePipeline.getLoadedTraceFiles().length).toEqual(0);
+
await loadValidSfWmTraces();
- expect(tracePipeline.getLoadedTraces().length).toEqual(2);
+
+ expect(tracePipeline.getLoadedTraceFiles().length).toEqual(2);
+
+ const traceEntries = TracesUtils.extractEntries(tracePipeline.getTraces());
+ expect(traceEntries.get(TraceType.WINDOW_MANAGER)?.length).toBeGreaterThan(0);
+ expect(traceEntries.get(TraceType.SURFACE_FLINGER)?.length).toBeGreaterThan(0);
});
it('is robust to invalid trace files', async () => {
@@ -37,19 +44,21 @@
new TraceFile(await UnitTestUtils.getFixtureFile('winscope_homepage.png')),
];
- const errors = await tracePipeline.loadTraces(invalidTraceFiles);
+ const errors = await tracePipeline.loadTraceFiles(invalidTraceFiles);
+ tracePipeline.buildTraces();
expect(errors.length).toEqual(1);
- expect(tracePipeline.getLoadedTraces().length).toEqual(0);
+ expect(tracePipeline.getLoadedTraceFiles().length).toEqual(0);
});
it('is robust to mixed valid and invalid trace files', async () => {
- expect(tracePipeline.getLoadedTraces().length).toEqual(0);
- const traces = [
+ expect(tracePipeline.getLoadedTraceFiles().length).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.loadTraces(traces);
- expect(tracePipeline.getLoadedTraces().length).toEqual(1);
+ const errors = await tracePipeline.loadTraceFiles(files);
+ tracePipeline.buildTraces();
+ expect(tracePipeline.getLoadedTraceFiles().length).toEqual(1);
expect(errors.length).toEqual(1);
});
@@ -58,89 +67,48 @@
new TraceFile(await UnitTestUtils.getFixtureFile('traces/no_entries_InputMethodClients.pb')),
];
- const errors = await tracePipeline.loadTraces(traceFilesWithNoEntries);
+ const errors = await tracePipeline.loadTraceFiles(traceFilesWithNoEntries);
+ tracePipeline.buildTraces();
expect(errors.length).toEqual(0);
- expect(tracePipeline.getLoadedTraces().length).toEqual(1);
-
- const timelines = tracePipeline.getTimelines();
- expect(timelines.length).toEqual(1);
- expect(timelines[0].timestamps).toEqual([]);
+ expect(tracePipeline.getLoadedTraceFiles().length).toEqual(1);
});
it('can remove traces', async () => {
await loadValidSfWmTraces();
- expect(tracePipeline.getLoadedTraces().length).toEqual(2);
+ expect(tracePipeline.getLoadedTraceFiles().length).toEqual(2);
- tracePipeline.removeTrace(TraceType.SURFACE_FLINGER);
- expect(tracePipeline.getLoadedTraces().length).toEqual(1);
+ tracePipeline.removeTraceFile(TraceType.SURFACE_FLINGER);
+ tracePipeline.buildTraces();
+ expect(tracePipeline.getLoadedTraceFiles().length).toEqual(1);
- tracePipeline.removeTrace(TraceType.WINDOW_MANAGER);
- expect(tracePipeline.getLoadedTraces().length).toEqual(0);
+ tracePipeline.removeTraceFile(TraceType.WINDOW_MANAGER);
+ tracePipeline.buildTraces();
+ expect(tracePipeline.getLoadedTraceFiles().length).toEqual(0);
});
- it('gets loaded traces', async () => {
+ it('gets loaded trace files', async () => {
await loadValidSfWmTraces();
- const traces = tracePipeline.getLoadedTraces();
- expect(traces.length).toEqual(2);
- expect(traces[0].traceFile.file).toBeTruthy();
+ const files = tracePipeline.getLoadedTraceFiles();
+ expect(files.length).toEqual(2);
+ expect(files[0].traceFile).toBeTruthy();
- const actualTraceTypes = new Set(traces.map((trace) => trace.type));
+ const actualTraceTypes = new Set(files.map((file) => file.type));
const expectedTraceTypes = new Set([TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER]);
expect(actualTraceTypes).toEqual(expectedTraceTypes);
});
- it('gets trace entries for a given timestamp', 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 errors = await tracePipeline.loadTraces(traceFiles);
- expect(errors.length).toEqual(0);
-
- {
- const entries = tracePipeline.getTraceEntries(undefined);
- expect(entries.size).toEqual(0);
- }
- {
- const timestamp = new Timestamp(TimestampType.REAL, 0n);
- const entries = tracePipeline.getTraceEntries(timestamp);
- expect(entries.size).toEqual(0);
- }
- {
- const twoHundredYearsTimestamp = new Timestamp(
- TimestampType.REAL,
- 200n * 365n * 24n * 60n * 3600n * 1000000000n
- );
- const entries = tracePipeline.getTraceEntries(twoHundredYearsTimestamp);
- expect(entries.size).toEqual(2);
- }
- });
-
- it('gets timelines', async () => {
+ it('builds traces', async () => {
await loadValidSfWmTraces();
+ const traces = tracePipeline.getTraces();
- const timelines = tracePipeline.getTimelines();
-
- const actualTraceTypes = new Set(timelines.map((timeline) => timeline.traceType));
- const expectedTraceTypes = new Set([TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER]);
- expect(actualTraceTypes).toEqual(expectedTraceTypes);
-
- timelines.forEach((timeline) => {
- expect(timeline.timestamps.length).toBeGreaterThan(0);
- });
+ expect(traces.getTrace(TraceType.SURFACE_FLINGER)).toBeDefined();
+ expect(traces.getTrace(TraceType.WINDOW_MANAGER)).toBeDefined();
});
it('gets screenrecording data', async () => {
- expect(tracePipeline.getScreenRecordingVideo()).toBeUndefined();
-
const traceFiles = [
new TraceFile(
await UnitTestUtils.getFixtureFile(
@@ -148,7 +116,8 @@
)
),
];
- await tracePipeline.loadTraces(traceFiles);
+ await tracePipeline.loadTraceFiles(traceFiles);
+ tracePipeline.buildTraces();
const video = tracePipeline.getScreenRecordingVideo();
expect(video).toBeDefined();
@@ -157,12 +126,25 @@
it('can be cleared', async () => {
await loadValidSfWmTraces();
- expect(tracePipeline.getLoadedTraces().length).toBeGreaterThan(0);
- expect(tracePipeline.getTimelines().length).toBeGreaterThan(0);
+ expect(tracePipeline.getLoadedTraceFiles().length).toBeGreaterThan(0);
tracePipeline.clear();
- expect(tracePipeline.getLoadedTraces().length).toEqual(0);
- expect(tracePipeline.getTimelines().length).toEqual(0);
+ expect(tracePipeline.getLoadedTraceFiles().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();
});
const loadValidSfWmTraces = async () => {
@@ -175,7 +157,9 @@
),
];
- const errors = await tracePipeline.loadTraces(traceFiles);
+ const errors = await tracePipeline.loadTraceFiles(traceFiles);
expect(errors.length).toEqual(0);
+
+ tracePipeline.buildTraces();
};
});
diff --git a/tools/winscope/src/interfaces/timestamp_change_listener.ts b/tools/winscope/src/interfaces/trace_position_update_listener.ts
similarity index 79%
rename from tools/winscope/src/interfaces/timestamp_change_listener.ts
rename to tools/winscope/src/interfaces/trace_position_update_listener.ts
index 154a1be..571f3ef 100644
--- a/tools/winscope/src/interfaces/timestamp_change_listener.ts
+++ b/tools/winscope/src/interfaces/trace_position_update_listener.ts
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import {Timestamp} from 'trace/timestamp';
+import {TracePosition} from 'trace/trace_position';
-export interface TimestampChangeListener {
- onCurrentTimestampChanged(timestamp: Timestamp | undefined): void;
+export interface TracePositionUpdateListener {
+ onTracePositionUpdate(position: TracePosition): void;
}