Integrate "UI Traces API" with Winscope App/Core
Bug: b/256564627 Test: npm run build:all && npm run test:all Change-Id: Ic434abc3031b9d53ddb6289fed747971e90c430e
This commit is contained in:
@@ -273,11 +273,7 @@ export class AppComponent implements TraceDataListener {
|
||||
}
|
||||
|
||||
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 @@ export class AppComponent implements TraceDataListener {
|
||||
|
||||
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 @@ export class AppComponent implements TraceDataListener {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
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 @@ export class CollectTracesComponent implements OnInit, OnDestroy {
|
||||
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!');
|
||||
|
||||
@@ -27,7 +27,7 @@ import {TRACE_INFO} from 'app/trace_info';
|
||||
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 @@ import {ParserErrorSnackBarComponent} from './parser_error_snack_bar_component';
|
||||
</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 @@ import {ParserErrorSnackBarComponent} from './parser_error_snack_bar_component';
|
||||
</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 @@ import {ParserErrorSnackBarComponent} from './parser_error_snack_bar_component';
|
||||
</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 @@ export class UploadTracesComponent implements FilesDownloadListener {
|
||||
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 @@ export class UploadTracesComponent implements FilesDownloadListener {
|
||||
|
||||
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();
|
||||
|
||||
@@ -20,9 +20,10 @@ import {RemoteBugreportReceiver} from 'interfaces/remote_bugreport_receiver';
|
||||
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 CrossToolProtocolDependencyInversion = RemoteBugreportReceiver &
|
||||
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 @@ export class Mediator {
|
||||
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 @@ export class Mediator {
|
||||
}
|
||||
|
||||
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 @@ export class Mediator {
|
||||
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 @@ export class Mediator {
|
||||
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 @@ export class Mediator {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -19,7 +19,8 @@ import {CrossToolProtocolStub} from 'cross_tool/cross_tool_protocol_stub';
|
||||
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 @@ describe('Mediator', () => {
|
||||
|
||||
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 @@ describe('Mediator', () => {
|
||||
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 @@ describe('Mediator', () => {
|
||||
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 @@ describe('Mediator', () => {
|
||||
)
|
||||
),
|
||||
];
|
||||
const errors = await tracePipeline.loadTraces(traces);
|
||||
const errors = await tracePipeline.loadTraceFiles(files);
|
||||
expect(errors).toEqual([]);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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 @@ export class TimelineData {
|
||||
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 @@ export class TimelineData {
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 @@ class TracePipeline {
|
||||
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>();
|
||||
|
||||
if (!timestamp) {
|
||||
return traceEntries;
|
||||
}
|
||||
buildTraces() {
|
||||
const commonTimestampType = this.getCommonTimestampType();
|
||||
|
||||
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 @@ 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 {Timeline, TracePipeline};
|
||||
export {TracePipeline};
|
||||
|
||||
@@ -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 @@ describe('TracePipeline', () => {
|
||||
});
|
||||
|
||||
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 @@ describe('TracePipeline', () => {
|
||||
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 @@ describe('TracePipeline', () => {
|
||||
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 @@ describe('TracePipeline', () => {
|
||||
)
|
||||
),
|
||||
];
|
||||
await tracePipeline.loadTraces(traceFiles);
|
||||
await tracePipeline.loadTraceFiles(traceFiles);
|
||||
tracePipeline.buildTraces();
|
||||
|
||||
const video = tracePipeline.getScreenRecordingVideo();
|
||||
expect(video).toBeDefined();
|
||||
@@ -157,12 +126,25 @@ describe('TracePipeline', () => {
|
||||
|
||||
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 @@ describe('TracePipeline', () => {
|
||||
),
|
||||
];
|
||||
|
||||
const errors = await tracePipeline.loadTraces(traceFiles);
|
||||
const errors = await tracePipeline.loadTraceFiles(traceFiles);
|
||||
expect(errors.length).toEqual(0);
|
||||
|
||||
tracePipeline.buildTraces();
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user