Merge "Add functionality to upload traces component."
This commit is contained in:
committed by
Android (Google) Code Review
commit
9697901bb3
@@ -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";
|
||||
|
||||
<div *ngIf="!dataLoaded" fxLayout="row wrap" fxLayoutGap="10px grid" class="home">
|
||||
<mat-card class="homepage-card" id="collect-traces-card">
|
||||
<collect-traces [(core)]="core" [(dataLoaded)]="dataLoaded" [store]="store"></collect-traces>
|
||||
<collect-traces [(core)]="core" (dataLoadedChange)="onDataLoadedChange($event)"[store]="store"></collect-traces>
|
||||
</mat-card>
|
||||
<mat-card class="homepage-card" id="upload-traces-card">
|
||||
<upload-traces [(core)]="core"></upload-traces>
|
||||
<upload-traces [(core)]="core" (dataLoadedChange)="onDataLoadedChange($event)"></upload-traces>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
@@ -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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Core>();
|
||||
|
||||
@Input()
|
||||
dataLoaded = false;
|
||||
dataLoaded = false;
|
||||
|
||||
@Output()
|
||||
dataLoadedChange = new EventEmitter<boolean>();
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = (<File>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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
6
tools/winscope-ng/src/app/loaded_trace.ts
Normal file
6
tools/winscope-ng/src/app/loaded_trace.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { TraceType } from "../common/trace/trace_type";
|
||||
|
||||
export interface LoadedTrace {
|
||||
name: string;
|
||||
type: TraceType
|
||||
}
|
||||
37
tools/winscope-ng/src/app/trace_icons.ts
Normal file
37
tools/winscope-ng/src/app/trace_icons.ts
Normal file
@@ -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,
|
||||
};
|
||||
@@ -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: `
|
||||
<mat-card-title>Upload Traces</mat-card-title>
|
||||
<mat-card-title id="title">Upload Traces</mat-card-title>
|
||||
<mat-card-content>
|
||||
<div id="inputfile">
|
||||
<input mat-input type="file" (change)="onInputFile($event)" #fileUpload>
|
||||
<div
|
||||
class="drop-box"
|
||||
ref="drop-box"
|
||||
(dragleave)="onFileDragOut($event)"
|
||||
(dragover)="onFileDragIn($event)"
|
||||
(drop)="onHandleFileDrop($event)"
|
||||
>
|
||||
<div id="inputfile">
|
||||
<input
|
||||
hidden
|
||||
class="input-files"
|
||||
id="fileDropRef"
|
||||
type="file"
|
||||
(change)="onInputFile($event)"
|
||||
#fileDropRef
|
||||
multiple
|
||||
/>
|
||||
<h3 class="drop-info">Drag and drop</h3>
|
||||
<h3 class="drop-info">or click to upload</h3>
|
||||
<button mat-raised-button for="fileDropRef" (click)="fileDropRef.click()">
|
||||
Choose File
|
||||
</button>
|
||||
<div *ngIf="this.loadedTraces.length > 0">
|
||||
<button mat-raised-button (click)="onLoadData()">Load Data</button>
|
||||
<button mat-raised-button (click)="onClearData()">Clear All</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-list
|
||||
class="uploaded-files"
|
||||
*ngIf="this.loadedTraces.length > 0"
|
||||
>
|
||||
<mat-list-item *ngFor="let trace of loadedTraces">
|
||||
<mat-icon>{{TRACE_ICONS[trace.type]}}</mat-icon>
|
||||
<span>{{trace.name}} ({{trace.type}})
|
||||
</span>
|
||||
<button
|
||||
(click)="onRemoveTrace(trace)"
|
||||
><mat-icon class="file-icon">close</mat-icon>
|
||||
</button>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
`,
|
||||
|
||||
styles: [".drop-info{font-weight: normal;}"]
|
||||
})
|
||||
export class UploadTracesComponent {
|
||||
@Input()
|
||||
core: Core = new Core();
|
||||
core?: Core;
|
||||
|
||||
@Output()
|
||||
coreChange = new EventEmitter<Core>();
|
||||
|
||||
dataLoaded = false;
|
||||
|
||||
@Output()
|
||||
dataLoadedChange = new EventEmitter<boolean>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ class ParserFactory {
|
||||
|
||||
async createParsers(traces: Blob[]): Promise<Parser[]> {
|
||||
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}`);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user