From 42b10df22d19cbd73b550068082164631b9aebca Mon Sep 17 00:00:00 2001 From: Priyanka Patel Date: Fri, 5 Aug 2022 18:20:19 +0000 Subject: [PATCH] Add functionality to upload traces component. Upload multiple files via clicking to browse or drag and drop. Test: spin up winscope and see if the files upload. Bug: b/241571689 Change-Id: I67b801008ddcfa5be3ae724eb4db6f9b723eebd6 --- tools/winscope-ng/src/app/app.component.ts | 27 +++-- .../src/app/collect_traces.component.ts | 25 ++-- tools/winscope-ng/src/app/core.ts | 30 ++++- tools/winscope-ng/src/app/loaded_trace.ts | 6 + tools/winscope-ng/src/app/trace_icons.ts | 37 ++++++ .../src/app/upload_traces.component.ts | 114 +++++++++++++++--- tools/winscope-ng/src/parsers/parser.ts | 4 + .../winscope-ng/src/parsers/parser_factory.ts | 13 +- tools/winscope-ng/src/styles.css | 38 ++++++ 9 files changed, 246 insertions(+), 48 deletions(-) create mode 100644 tools/winscope-ng/src/app/loaded_trace.ts create mode 100644 tools/winscope-ng/src/app/trace_icons.ts diff --git a/tools/winscope-ng/src/app/app.component.ts b/tools/winscope-ng/src/app/app.component.ts index dc6eca0b7..42aa42bac 100644 --- a/tools/winscope-ng/src/app/app.component.ts +++ b/tools/winscope-ng/src/app/app.component.ts @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Inject, Injector} from "@angular/core"; +import {Component, Inject, Injector, Input} from "@angular/core"; import {createCustomElement} from "@angular/elements"; import {Timestamp, TimestampType} from "common/trace/timestamp"; import {PersistentStore} from "common/persistent_store"; import {ViewerWindowManagerComponent} from "viewers/viewer_window_manager/viewer_window_manager.component"; import {Core} from "./core"; -import {ProxyState} from "trace_collection/proxy_client"; +import {ProxyState, proxyClient} from "trace_collection/proxy_client"; @Component({ selector: "app-root", @@ -30,10 +30,10 @@ import {ProxyState} from "trace_collection/proxy_client";
- + - +
@@ -61,7 +61,8 @@ export class AppComponent { core: Core; states = ProxyState; store: PersistentStore = new PersistentStore(); - dataLoaded = false; + @Input() dataLoaded = false; + viewersCreated = false; constructor( @Inject(Injector) injector: Injector @@ -73,12 +74,14 @@ export class AppComponent { } } - onCoreChange(newCore: Core) { - this.core = newCore; - } - - onDataLoadedChange(loaded: boolean) { - this.dataLoaded = loaded; + onDataLoadedChange(dataLoaded: boolean) { + if (dataLoaded && !this.viewersCreated) { + this.core.createViewers(); + const dummyTimestamp = this.core.getTimestamps()[1]; //TODO: get timestamp from time scrub + this.core.notifyCurrentTimestamp(dummyTimestamp); + this.viewersCreated = true; + this.dataLoaded = dataLoaded; + } } public notifyCurrentTimestamp() { @@ -88,6 +91,8 @@ export class AppComponent { public clearData() { this.dataLoaded = false; + this.viewersCreated = false; this.core.clearData(); + proxyClient.adbData = []; } } diff --git a/tools/winscope-ng/src/app/collect_traces.component.ts b/tools/winscope-ng/src/app/collect_traces.component.ts index 04fdca90d..aaffc66bf 100644 --- a/tools/winscope-ng/src/app/collect_traces.component.ts +++ b/tools/winscope-ng/src/app/collect_traces.component.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, Input, OnInit, Output, EventEmitter } from "@angular/core"; +import { Component, Input, OnInit, Output, EventEmitter, NgZone, Inject } from "@angular/core"; import { ProxyConnection } from "trace_collection/proxy_connection"; import { Connection } from "trace_collection/connection"; import { setTraces } from "trace_collection/set_traces"; import { ProxyState } from "../trace_collection/proxy_client"; -import { traceConfigurations, configMap, SelectionConfiguration, TraceConfigurationMap, EnableConfiguration } from "../trace_collection/trace_collection_utils"; +import { traceConfigurations, configMap, SelectionConfiguration, EnableConfiguration } from "../trace_collection/trace_collection_utils"; import { Core } from "app/core"; import { PersistentStore } from "../common/persistent_store"; @@ -123,13 +123,12 @@ export class CollectTracesComponent implements OnInit { store: PersistentStore = new PersistentStore(); @Input() - core: Core = new Core(); + core?: Core; @Output() coreChange = new EventEmitter(); - @Input() - dataLoaded = false; + dataLoaded = false; @Output() dataLoadedChange = new EventEmitter(); @@ -143,6 +142,8 @@ export class CollectTracesComponent implements OnInit { } } + constructor(@Inject(NgZone) private ngZone: NgZone) {} + ngOnDestroy(): void { this.connect.proxy?.removeOnProxyChange(this.onProxyChange); } @@ -257,7 +258,7 @@ export class CollectTracesComponent implements OnInit { if (!setTraces.dumpError) { await this.loadFiles(); } else { - this.core.clearData(); + this.core?.clearData(); } } @@ -272,11 +273,13 @@ export class CollectTracesComponent implements OnInit { public async loadFiles() { console.log("loading files", this.connect.adbData()); - await this.core.bootstrap(this.connect.adbData()); - this.dataLoaded = true; - this.dataLoadedChange.emit(this.dataLoaded); - this.coreChange.emit(this.core); - console.log("finished loading data!"); + this.core?.clearData(); + await this.core?.addTraces(this.connect.adbData()); + this.ngZone.run(() => { + this.dataLoaded = true; + this.dataLoadedChange.emit(this.dataLoaded); + console.log("finished loading data!"); + }); } public tabClass(adbTab: boolean) { diff --git a/tools/winscope-ng/src/app/core.ts b/tools/winscope-ng/src/app/core.ts index 1f2810395..30ce5b927 100644 --- a/tools/winscope-ng/src/app/core.ts +++ b/tools/winscope-ng/src/app/core.ts @@ -18,9 +18,9 @@ import {TraceType} from "common/trace/trace_type"; import {Parser} from "parsers/parser"; import {ParserFactory} from "parsers/parser_factory"; import { setTraces } from "trace_collection/set_traces"; -import { proxyClient } from "trace_collection/proxy_client"; -import {Viewer} from "viewers/viewer"; -import {ViewerFactory} from "viewers/viewer_factory"; +import { Viewer } from "viewers/viewer"; +import { ViewerFactory } from "viewers/viewer_factory"; +import { LoadedTrace } from "app/loaded_trace"; class Core { private parsers: Parser[]; @@ -31,11 +31,18 @@ class Core { this.viewers = []; } - async bootstrap(traces: Blob[]) { - this.clearData(); + async addTraces(traces: Blob[]) { + traces = this.parsers.map(parser => parser.getTrace()).concat(traces); this.parsers = await new ParserFactory().createParsers(traces); console.log("created parsers: ", this.parsers); + } + + removeTrace(type: TraceType) { + this.parsers = this.parsers.filter(parser => parser.getTraceType() !== type); + } + + createViewers() { const activeTraceTypes = this.parsers.map(parser => parser.getTraceType()); console.log("active trace types: ", activeTraceTypes); @@ -43,10 +50,22 @@ class Core { console.log("created viewers: ", this.viewers); } + getLoadedTraces(): LoadedTrace[] { + return this.parsers.map((parser: Parser) => { + const name = (parser.getTrace()).name; + const type = parser.getTraceType(); + return {name: name, type: type}; + }); + } + getViews(): HTMLElement[] { return this.viewers.map(viewer => viewer.getView()); } + loadedTraceTypes(): TraceType[] { + return this.parsers.map(parser => parser.getTraceType()); + } + getTimestamps(): Timestamp[] { for (const type of [TimestampType.REAL, TimestampType.ELAPSED]) { const mergedTimestamps: Timestamp[] = []; @@ -90,7 +109,6 @@ class Core { this.parsers = []; this.viewers = []; setTraces.dataReady = false; - proxyClient.adbData = []; } } diff --git a/tools/winscope-ng/src/app/loaded_trace.ts b/tools/winscope-ng/src/app/loaded_trace.ts new file mode 100644 index 000000000..137b56727 --- /dev/null +++ b/tools/winscope-ng/src/app/loaded_trace.ts @@ -0,0 +1,6 @@ +import { TraceType } from "../common/trace/trace_type"; + +export interface LoadedTrace { + name: string; + type: TraceType +} diff --git a/tools/winscope-ng/src/app/trace_icons.ts b/tools/winscope-ng/src/app/trace_icons.ts new file mode 100644 index 000000000..14cec84d7 --- /dev/null +++ b/tools/winscope-ng/src/app/trace_icons.ts @@ -0,0 +1,37 @@ +import { TraceType } from "../common/trace/trace_type"; + +const WINDOW_MANAGER_ICON = "view_compact"; +const SURFACE_FLINGER_ICON = "filter_none"; +const SCREEN_RECORDING_ICON = "videocam"; +const TRANSACTION_ICON = "timeline"; +const WAYLAND_ICON = "filter_none"; +const PROTO_LOG_ICON = "notes"; +const SYSTEM_UI_ICON = "filter_none"; +const LAUNCHER_ICON = "filter_none"; +const IME_ICON = "keyboard"; +const ACCESSIBILITY_ICON = "filter_none"; +const TAG_ICON = "details"; +const TRACE_ERROR_ICON = "warning"; + +type iconMap = { + [key: number]: string; +} + +export const TRACE_ICONS: iconMap = { + [TraceType.ACCESSIBILITY]: ACCESSIBILITY_ICON, + [TraceType.WINDOW_MANAGER]: WINDOW_MANAGER_ICON, + [TraceType.SURFACE_FLINGER]: SURFACE_FLINGER_ICON, + [TraceType.SCREEN_RECORDING]: SCREEN_RECORDING_ICON, + [TraceType.TRANSACTIONS]: TRANSACTION_ICON, + [TraceType.TRANSACTIONS_LEGACY]: TRANSACTION_ICON, + [TraceType.WAYLAND]: WAYLAND_ICON, + [TraceType.WAYLAND_DUMP]: WAYLAND_ICON, + [TraceType.PROTO_LOG]: PROTO_LOG_ICON, + [TraceType.SYSTEM_UI]: SYSTEM_UI_ICON, + [TraceType.LAUNCHER]: LAUNCHER_ICON, + [TraceType.INPUT_METHOD_CLIENTS]: IME_ICON, + [TraceType.INPUT_METHOD_SERVICE]: IME_ICON, + [TraceType.INPUT_METHOD_MANAGER_SERVICE]: IME_ICON, + [TraceType.TAG]: TAG_ICON, + [TraceType.ERROR]: TRACE_ERROR_ICON, +}; diff --git a/tools/winscope-ng/src/app/upload_traces.component.ts b/tools/winscope-ng/src/app/upload_traces.component.ts index 4b3b5cd54..d62c1dc91 100644 --- a/tools/winscope-ng/src/app/upload_traces.component.ts +++ b/tools/winscope-ng/src/app/upload_traces.component.ts @@ -13,50 +13,130 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, Input, Output, EventEmitter } from "@angular/core"; +import { Component, Input, Inject, Output, EventEmitter, NgZone } from "@angular/core"; import { Core } from "app/core"; - +import { TRACE_ICONS } from "app/trace_icons"; +import { LoadedTrace } from "app/loaded_trace"; @Component({ selector: "upload-traces", template: ` - Upload Traces + Upload Traces -
- +
+
+ +

Drag and drop

+

or click to upload

+ +
+ + +
+
+ + + + {{TRACE_ICONS[trace.type]}} + {{trace.name}} ({{trace.type}}) + + + + `, - + styles: [".drop-info{font-weight: normal;}"] }) export class UploadTracesComponent { @Input() - core: Core = new Core(); + core?: Core; @Output() coreChange = new EventEmitter(); + dataLoaded = false; + + @Output() + dataLoadedChange = new EventEmitter(); + + loadedTraces: LoadedTrace[] = []; + TRACE_ICONS = TRACE_ICONS; + + constructor(@Inject(NgZone) private ngZone: NgZone) {} + public async onInputFile(event: Event) { const files = this.getInputFiles(event); - await this.core.bootstrap(files); + await this.processFiles(files); + } - const viewersDiv = document.querySelector("div#viewers")!; - viewersDiv.innerHTML = ""; - this.core.getViews().forEach(view => viewersDiv!.appendChild(view) ); - this.coreChange.emit(this.core); - - const timestampsDiv = document.querySelector("div#timestamps")!; - timestampsDiv.innerHTML = `Retrieved ${this.core.getTimestamps().length} unique timestamps`; + public async processFiles(files: File[]) { + await this.core?.addTraces(files); + this.ngZone.run(() => { + if (this.core) this.loadedTraces = this.core.getLoadedTraces(); + }); } //TODO: extend with support for multiple files, archives, etc... private getInputFiles(event: Event): File[] { const files: any = (event?.target as HTMLInputElement)?.files; - if (!files || !files[0]) { return []; } + return Array.from(files); + } - return [files[0]]; + public onLoadData() { + this.dataLoaded = true; + this.dataLoadedChange.emit(this.dataLoaded); + } + + public onClearData() { + this.core?.clearData(); + } + + public onFileDragIn(e: DragEvent) { + e.preventDefault(); + e.stopPropagation(); + } + + public onFileDragOut(e: DragEvent) { + e.preventDefault(); + e.stopPropagation(); + } + + async onHandleFileDrop(e: DragEvent) { + e.preventDefault(); + e.stopPropagation(); + const droppedFiles = e.dataTransfer?.files; + if(!droppedFiles) return; + await this.processFiles(Array.from(droppedFiles)); + } + + public onRemoveTrace(trace: LoadedTrace) { + this.core?.removeTrace(trace.type); + this.loadedTraces = this.loadedTraces.filter(loaded => loaded.type !== trace.type); } } diff --git a/tools/winscope-ng/src/parsers/parser.ts b/tools/winscope-ng/src/parsers/parser.ts index 9614a2dae..fd7f2cbbb 100644 --- a/tools/winscope-ng/src/parsers/parser.ts +++ b/tools/winscope-ng/src/parsers/parser.ts @@ -57,6 +57,10 @@ abstract class Parser { public abstract getTraceType(): TraceType; + public getTrace(): Blob { + return this.trace; + } + public getTimestamps(type: TimestampType): undefined|Timestamp[] { return this.timestamps.get(type); } diff --git a/tools/winscope-ng/src/parsers/parser_factory.ts b/tools/winscope-ng/src/parsers/parser_factory.ts index fac3ba087..9c063686e 100644 --- a/tools/winscope-ng/src/parsers/parser_factory.ts +++ b/tools/winscope-ng/src/parsers/parser_factory.ts @@ -43,6 +43,7 @@ class ParserFactory { async createParsers(traces: Blob[]): Promise { const parsers: Parser[] = []; + const completedParserTypes: any[] = []; for (const [index, trace] of traces.entries()) { console.log(`Loading trace #${index}`); @@ -50,9 +51,15 @@ class ParserFactory { try { const parser = new ParserType(trace); await parser.parse(); - parsers.push(parser); - console.log(`Successfully loaded trace with parser type ${ParserType.name}`); - break; + if (completedParserTypes.includes(ParserType)) { + console.log(`Already successfully loaded a trace with parser type ${ParserType.name}`); + break; + } else { + parsers.push(parser); + completedParserTypes.push(ParserType); + console.log(`Successfully loaded trace with parser type ${ParserType.name}`); + break; + } } catch(error) { console.log(`Failed to load trace with parser type ${ParserType.name}`); diff --git a/tools/winscope-ng/src/styles.css b/tools/winscope-ng/src/styles.css index 274d4f56c..feaa90e9f 100644 --- a/tools/winscope-ng/src/styles.css +++ b/tools/winscope-ng/src/styles.css @@ -53,6 +53,12 @@ mat-icon { vertical-align: middle; } +.file-icon { + vertical-align: middle; + outline: none !important; + background: none !important; +} + .card-block { margin: 15px; } @@ -66,4 +72,36 @@ button.mat-raised-button { .tab.inactive { background-color:white; color: black; +} + +.drop-box { + outline: 2px dashed rgb(194, 65, 108); /* the dash box */ + outline-offset: -10px; + background: white; + color: rgb(194, 65, 108); + padding: 10px 10px 10px 10px; + min-height: 200px; /* minimum height */ + position: relative; + cursor: pointer; + display: flex; + flex-direction: column; + text-align: center; + align-items: center; + justify-items: center; +} + +.input { + opacity: 0 !important; + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; +} + +.file-input-prompt { + color: white; + background-color: rgb(194, 65, 108); + border-radius: 21.5px; + cursor: pointer; } \ No newline at end of file