Adding ability to run and end traces via proxy.

Finishing proxy migration by creating functionality to run and end
traces and dumps after selecting trace config.

Test: Connect a device via remote device proxy and follow the proxy
workflow for a trace or dump. Should see a placeholder message saying data loaded (trace
views not yet created).

Bug: b/238113543
Change-Id: Ic7e0948341511f6ec0bf1021d2ffbb7b198c9410
This commit is contained in:
Priyanka Patel
2022-07-20 16:38:49 +00:00
parent b6ee5d4794
commit 64b08efa13
18 changed files with 847 additions and 494 deletions

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Component, Input, Output, EventEmitter } from "@angular/core";
import { ProxyClient, ProxyState } from "./proxy_client";
import { ProxyClient, ProxyState } from "../trace_collection/proxy_client";
@Component({
selector: "adb-proxy",
@@ -29,7 +29,7 @@ import { ProxyClient, ProxyState } from "./proxy_client";
<p>Python 3.5+ and ADB are required.</p>
<p>Run:</p>
<pre>python3</pre>
<pre>$ANDROID_BUILD_TOP/development/tools/winscope-ng/adb/winscope_proxy.py</pre>
<pre>$ANDROID_BUILD_TOP/development/tools/winscope-ng/src/adb/winscope_proxy.py</pre>
<p>Or get it from the AOSP repository.</p>
</div>
<div class="md-layout">
@@ -42,14 +42,14 @@ import { ProxyClient, ProxyState } from "./proxy_client";
<div *ngIf="proxy.state===states.INVALID_VERSION">
<div>
<mat-icon>update</mat-icon>
<mat-icon class="icon-message">update</mat-icon>
<span class="icon-message">Your local proxy version is incompatible with Winscope.</span>
</div>
<div class="md-body-2" layout="layout-md">
<p>Please update the proxy to version {{ proxyVersion }}.</p>
<p>Run:</p>
<pre>python3</pre>
<pre>$ANDROID_BUILD_TOP/development/tools/winscope-ng/adb/winscope_proxy.py</pre>
<pre>$ANDROID_BUILD_TOP/development/tools/winscope-ng/src/adb/winscope_proxy.py</pre>
<p>Or get it from the AOSP repository.</p>
</div>
<div class="md-layout">
@@ -62,7 +62,7 @@ import { ProxyClient, ProxyState } from "./proxy_client";
<div *ngIf="proxy.state===states.UNAUTH">
<div>
<mat-icon>lock</mat-icon>
<mat-icon class="icon-message">lock</mat-icon>
<span class="icon-message">Proxy authorisation required</span>
</div>
<div class="md-body-2" layout="layout-md">
@@ -78,23 +78,20 @@ import { ProxyClient, ProxyState } from "./proxy_client";
</div>
`,
styles: [".proxy-key-field {width: 30rem}", ".icon-message {vertical-align: middle;}"]
styles: [".proxy-key-field {width: 30rem}"]
})
export class AdbProxyComponent {
states = ProxyState;
proxyKeyItem = "";
@Input()
proxy: any = {};
readonly proxyVersion = this.proxy.VERSION;
proxy: any = {};
@Output()
proxyChange = new EventEmitter<ProxyClient>();
proxyChange = new EventEmitter<ProxyClient>();
@Output() addKey = new EventEmitter<string>();
states = ProxyState;
proxyKeyItem = "";
readonly proxyVersion = this.proxy.VERSION;
readonly downloadProxyUrl: string = "https://android.googlesource.com/platform/development/+/master/tools/winscope-ng/adb/winscope_proxy.py";
public restart() {
@@ -102,4 +99,4 @@ export class AdbProxyComponent {
this.proxy.setState(this.states.CONNECTING);
this.proxyChange.emit(this.proxy);
}
}
}

View File

@@ -26,9 +26,9 @@ import { PersistentStore } from "../common/persistent_store";
<div id="title">
<span>Winscope Viewer 2.0</span>
</div>
<div class="card-container" fxLayout="row wrap" fxLayoutGap="10px grid">
<div *ngIf="!dataLoaded" class="card-container" fxLayout="row wrap" fxLayoutGap="10px grid">
<mat-card class="homepage-card">
<collect-traces [store]="store"></collect-traces>
<collect-traces [(core)]="core" [(dataLoaded)]="dataLoaded" [store]="store"></collect-traces>
</mat-card>
<mat-card class="homepage-card">
<mat-card-title>Upload Traces</mat-card-title>
@@ -38,10 +38,11 @@ import { PersistentStore } from "../common/persistent_store";
</mat-card>
</div>
<div id="inputfile">
<button type="button" mat-raised-button (click)="fileInput.click()">Choose File</button>
<input hidden (change)="onInputFile($event)" #fileInput type="file" id="file">
<input type="file" (change)="onInputFile($event)" #fileUpload>
<div *ngIf="dataLoaded">
<mat-card class="homepage-card">
<mat-card-title>Loaded data</mat-card-title>
<button mat-raised-button (click)="clearData()">Back to Home</button>
</mat-card>
</div>
<div id="timescrub">
@@ -59,9 +60,10 @@ import { PersistentStore } from "../common/persistent_store";
export class AppComponent {
title = "winscope-ng";
private core!: Core;
core: Core = new Core();
states = ProxyState;
store: PersistentStore = new PersistentStore();
dataLoaded: boolean = false;
constructor(
@Inject(Injector) injector: Injector
@@ -70,10 +72,16 @@ export class AppComponent {
createCustomElement(ViewerWindowManagerComponent, {injector}));
}
public async onInputFile(event: Event) {
const files = await this.getInputFiles(event);
onCoreChange(newCore: Core) {
this.core = newCore;
}
this.core = new Core();
onDataLoadedChange(loaded: boolean) {
this.dataLoaded = loaded;
}
public async onInputFile(event: Event) {
const files = this.getInputFiles(event);
await this.core.bootstrap(files);
const viewersDiv = document.querySelector("div#viewers")!;
@@ -99,4 +107,9 @@ export class AppComponent {
return [files[0]];
}
public clearData() {
this.dataLoaded = false;
this.core.clearData();
}
}

View File

@@ -18,10 +18,10 @@ import { HttpClientModule } from "@angular/common/http";
import { AppComponent } from "./app.component";
import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
import { CollectTracesComponent } from "trace_collection/collect_traces.component";
import { AdbProxyComponent } from "trace_collection/adb_proxy.component";
import { WebAdbComponent } from "trace_collection/web_adb/web_adb.component";
import { TraceConfigComponent } from "trace_collection/trace_config.component";
import { CollectTracesComponent } from "./collect_traces.component";
import { AdbProxyComponent } from "./adb_proxy.component";
import { WebAdbComponent } from "./web_adb.component";
import { TraceConfigComponent } from "./trace_config.component";
@NgModule({
declarations: [

View File

@@ -0,0 +1,316 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Input, OnInit, Output, EventEmitter } from "@angular/core";
import { ProxyConnection, Device, configureTraces } from "../trace_collection/connection";
import { ProxyState } from "../trace_collection/proxy_client";
import { traceConfigurations, configMap, SelectionConfiguration } from "../trace_collection/trace_collection_utils";
import { Core } from "app/core";
import { PersistentStore } from "../common/persistent_store";
@Component({
selector: "collect-traces",
template: `
<mat-card-title>Collect Traces</mat-card-title>
<mat-card-content>
<div *ngIf="connect.isConnectingState()">Connecting...</div>
<div id="set-up-adb" *ngIf="!adbSuccess()">
<button mat-raised-button [ngClass]="tabClass(true)" (click)="displayAdbProxyTab()">ADB Proxy</button>
<button mat-raised-button [ngClass]="tabClass(false)" (click)="displayWebAdbTab()">Web ADB</button>
<adb-proxy *ngIf="isAdbProxy" [(proxy)]="connect.proxy" (addKey)="onAddKey($event)"></adb-proxy>
<web-adb *ngIf="!isAdbProxy"></web-adb>
</div>
<div id="devices-connecting" *ngIf="connect.isDevicesState()">
<div> {{ devices().length > 0 ? "Connected devices:" : "No devices detected" }}</div>
<mat-list class="device-choice">
<mat-list-item *ngFor="let deviceId of devices()" (click)="selectDevice(deviceId)">
<mat-icon class="icon-message">
{{ connect.proxy.devices[deviceId].authorised ? "smartphone" : "screen_lock_portrait" }}
</mat-icon>
<span class="icon-message">
{{ connect.proxy.devices[deviceId].authorised ? connect.proxy.devices[deviceId].model : "unauthorised" }} ({{ deviceId }})
</span>
</mat-list-item>
</mat-list>
</div>
<div id="trace-collection-config" *ngIf="connect.isStartTraceState()">
<div class="device-choice">
<mat-list class="device-choice">
<mat-list-item>
<mat-icon class="icon-message">smartphone</mat-icon>
<span class="icon-message">
{{ selectedDevice().model }} ({{ connect.proxy.selectedDevice }})
</span>
</mat-list-item>
</mat-list>
</div>
<div class="trace-section">
<div class="md-layout">
<button mat-raised-button class="md-accent" (click)="startTracing()">Start Trace</button>
<button mat-raised-button (click)="dumpState()">Dump State</button>
<button mat-raised-button class="md-primary" (click)="resetLastDevice()">Change Device</button>
</div>
<h3>Trace targets:</h3>
<trace-config
*ngFor="let traceKey of objectKeys(connect.DYNAMIC_TRACES())"
[trace]="connect.DYNAMIC_TRACES()[traceKey]"
></trace-config>
</div>
<div class="dump-section">
<h3>Dump targets:</h3>
<div class="selection">
<mat-checkbox
class="md-primary"
*ngFor="let dumpKey of objectKeys(connect.DUMPS)"
[(ngModel)]="connect.DUMPS[dumpKey].enabled"
>{{connect.DUMPS[dumpKey].name}}</mat-checkbox>
</div>
</div>
</div>
<div id="unknown-error" *ngIf="connect.isErrorState()">
<mat-icon class="icon-message">error</mat-icon>
<span class="icon-message">Error:</span>
<pre>
{{ connect.proxy.errorText }}
</pre>
<button mat-raised-button (click)="restart()">Retry</button>
</div>
<div id="end-tracing" *ngIf="connect.isEndTraceState()">
<span class="md-subheading">Tracing...</span>
<mat-progress-bar md-indeterminate value="{{connect.loadProgress}}"></mat-progress-bar>
<button mat-raised-button (click)="endTrace()">End trace</button>
</div>
<div id="load-data" *ngIf="connect.isLoadDataState()">
<span class="md-subheading">Loading data...</span>
<mat-progress-bar md-indeterminate></mat-progress-bar>
</div>
</mat-card-content>
`,
styles: [".device-choice {cursor: pointer}"]
})
export class CollectTracesComponent implements OnInit {
objectKeys = Object.keys;
isAdbProxy = true;
startTrace = false;
startDump = false;
traceConfigurations = traceConfigurations;
connect: any = new ProxyConnection();
downloadProxyUrl = "https://android.googlesource.com/platform/development/+/master/tools/winscope-ng/adb/winscope_proxy.py";
@Input()
store: PersistentStore = new PersistentStore();
@Input()
core: Core = new Core();
@Output()
coreChange = new EventEmitter<Core>();
@Input()
dataLoaded: boolean = false;
@Output()
dataLoadedChange = new EventEmitter<boolean>();
ngOnInit(): void {
if (this.isAdbProxy) {
this.connect = new ProxyConnection();
} else {
//TODO: change to WebAdbConnection
this.connect = new ProxyConnection();
}
}
ngOnDestroy(): void {
this.connect.proxy.removeOnProxyChange(this.onProxyChange);
}
public onAddKey(key: string) {
this.store.addToStore("adb.proxyKey", key);
this.connect.setProxyKey(key);
this.restart();
}
public onProxyChange(newState: ProxyState) {
this.connect.onConnectChange(newState);
}
public adbSuccess() {
return this.connect.adbSuccess();
}
public devices(): Array<string> {
return this.connect.devices();
}
public selectedDevice(): Device {
return this.connect.selectedDevice();
}
public restart() {
this.connect.restart();
}
public resetLastDevice() {
this.connect.resetLastDevice();
}
public selectDevice(id: string) {
this.connect.selectDevice(id);
}
public displayAdbProxyTab() {
this.isAdbProxy = true;
this.connect = new ProxyConnection();
}
public displayWebAdbTab() {
this.isAdbProxy = false;
//TODO: change to WebAdbConnection
this.connect = new ProxyConnection();
}
public requestedTraces() {
const tracesFromCollection: Array<any> = [];
const req = Object.keys(this.connect.DYNAMIC_TRACES())
.filter((traceKey:string) => {
const traceConfig = this.connect.DYNAMIC_TRACES()[traceKey];
if (traceConfig.isTraceCollection) {
traceConfig.config.enableConfigs.forEach((innerTrace:any) => {
if (innerTrace.enabled) {
tracesFromCollection.push(innerTrace.key);
}
});
return false;
}
return traceConfig.run;
});
return req.concat(tracesFromCollection);
}
public requestedDumps() {
return Object.keys(this.connect.DUMPS)
.filter((dumpKey:any) => {
return this.connect.DUMPS[dumpKey].enabled;
});
}
public requestedEnableConfig(): Array<string> | null{
const req: Array<string> = [];
Object.keys(this.connect.DYNAMIC_TRACES())
.forEach((traceKey:any) => {
const trace = this.connect.DYNAMIC_TRACES()[traceKey];
if(!trace.isTraceCollection
&& trace.run
&& trace.config
&& trace.config.enableConfigs) {
trace.config.enableConfigs.forEach((con:any) => {
if (con.enabled) {
req.push(con.key);
}
});
}
});
if (req.length === 0) {
return null;
}
return req;
}
public requestedSelection(traceType: string) {
if (!this.connect.DYNAMIC_TRACES()[traceType].run) {
return null;
}
const selected: configMap = {};
this.connect.DYNAMIC_TRACES()[traceType].config.selectionConfigs.forEach(
(con: SelectionConfiguration) => {
selected[con.key] = con.value;
}
);
return selected;
}
public startTracing() {
this.startTrace = true;
console.log("begin tracing");
configureTraces.reqTraces = this.requestedTraces();
const reqEnableConfig = this.requestedEnableConfig();
const reqSelectedSfConfig = this.requestedSelection("layers_trace");
const reqSelectedWmConfig = this.requestedSelection("window_trace");
if (configureTraces.reqTraces.length < 1) {
this.connect.throwNoTargetsError();
return;
}
this.connect.startTrace(
reqEnableConfig,
reqSelectedSfConfig,
reqSelectedWmConfig
);
}
public async dumpState() {
this.startDump = true;
console.log("begin dump");
configureTraces.reqDumps = this.requestedDumps();
await this.connect.dumpState();
while (!this.connect.proxy.dataReady) {
await this.waitForData(1000);
}
await this.loadFiles();
}
public async endTrace() {
console.log("end tracing");
await this.connect.endTrace();
while (!this.connect.proxy.dataReady) {
await this.waitForData(1000);
}
await this.loadFiles();
}
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!");
}
public tabClass(adbTab: boolean) {
let isActive: string;
if (adbTab) {
isActive = this.isAdbProxy ? "active" : "inactive";
} else {
isActive = !this.isAdbProxy ? "active" : "inactive";
}
return ["tab", isActive];
}
private waitForData(ms: number) {
return new Promise( resolve => setTimeout(resolve, ms) );
}
}

View File

@@ -71,6 +71,11 @@ class Core {
viewer.notifyCurrentTraceEntries(traceEntries);
});
}
clearData() {
this.parsers = [];
this.viewers = [];
}
}
export { Core };

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Input, OnChanges, SimpleChanges } from "@angular/core";
import { EnableConfiguration, SelectionConfiguration, TraceConfiguration } from "../trace_collection/trace_collection_utils";
@Component({
selector: "trace-config",
template: `
<div class="card-block">
<mat-checkbox
[checked]="trace.run"
[indeterminate]="trace.isTraceCollection ? someTraces() : false"
(change)="changeRunTrace($event.checked)"
>{{trace.name}}</mat-checkbox>
<div class="adv-config" *ngIf="trace.config">
<mat-checkbox
*ngFor="let enableConfig of traceEnableConfigs()"
[disabled]="!trace.run && !trace.isTraceCollection"
[(ngModel)]="enableConfig.enabled"
(ngModelChange)="changeTraceCollectionConfig()"
>{{enableConfig.name}}</mat-checkbox>
<div class="selection" *ngIf="trace.config.selectionConfigs">
<mat-form-field
appearance="fill"
class="config-selection"
*ngFor="let selectionConfig of traceSelectionConfigs()"
><mat-label>{{selectionConfig.name}}</mat-label>
<mat-select [(value)]="selectionConfig.value" [disabled]="!trace.run">
<mat-option
*ngFor="let option of selectionConfig.options"
value="{{option}}"
>{{ option }}</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</div>
`,
styles: [".adv-config {margin-left: 5rem;}"],
})
export class TraceConfigComponent {
@Input()
trace: TraceConfiguration = {};
public traceEnableConfigs(): Array<EnableConfiguration> {
if (this.trace.config) {
return this.trace.config.enableConfigs;
} else {
return [];
}
}
public traceSelectionConfigs(): Array<SelectionConfiguration> {
if (this.trace.config) {
return this.trace.config.selectionConfigs;
} else {
return [];
}
}
public someTraces(): boolean {
return this.traceEnableConfigs().filter(trace => trace.enabled).length > 0
&& !this.trace.run;
}
public changeRunTrace(run: boolean): void {
this.trace.run = run;
if (this.trace.isTraceCollection) {
this.traceEnableConfigs().forEach((c: EnableConfiguration) => (c.enabled = run));
}
}
public changeTraceCollectionConfig(): void {
if (this.trace.isTraceCollection) {
this.trace.run = this.traceEnableConfigs().every((c: EnableConfiguration) => c.enabled);
}
}
}

View File

@@ -49,6 +49,10 @@ mat-icon {
margin: 5px;
}
.icon-message {
vertical-align: middle;
}
.card-block {
margin: 15px;
}

View File

@@ -1,222 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Component, Input, OnInit} from "@angular/core";
import { Connection, ProxyConnection, Device } from "./connection";
import { ProxyState } from "./proxy_client";
import { traceConfigurations, configMap } from "./trace_collection_utils";
import { PersistentStore } from "../common/persistent_store";
@Component({
selector: "collect-traces",
template: `
<mat-card-title>Collect Traces</mat-card-title>
<mat-card-content>
<div *ngIf="connect.state()===states.CONNECTING">Connecting...</div>
<div id="set-up-adb" *ngIf="!adbSuccess()">
<button mat-raised-button [ngClass]="tabClass(true)" (click)="displayAdbProxyTab()">ADB Proxy</button>
<button mat-raised-button [ngClass]="tabClass(false)" (click)="displayWebAdbTab()">Web ADB</button>
<adb-proxy *ngIf="isAdbProxy" [(proxy)]="connect.proxy" (addKey)="onAddKey($event)"></adb-proxy>
<web-adb *ngIf="!isAdbProxy"></web-adb>
</div>
<div id="devices-connecting" *ngIf="connect.state()===states.DEVICES">
<div> {{ devices().length > 0 ? "Connected devices:" : "No devices detected" }}</div>
<mat-list class="device-choice">
<mat-list-item *ngFor="let deviceId of devices()" (click)="selectDevice(deviceId)">
<mat-icon>{{ connect.proxy.devices[deviceId].authorised ? "smartphone" : "screen_lock_portrait" }}</mat-icon>
<span class="md-list-item-text">{{ connect.proxy.devices[deviceId].authorised ? connect.proxy.devices[deviceId].model : "unauthorised" }} ({{ deviceId }})</span>
</mat-list-item>
</mat-list>
</div>
<div id="trace-collection-config" *ngIf="connect.state()===states.START_TRACE">
<div class="device-choice">
<mat-list class="device-choice">
<mat-list-item>
<mat-icon>smartphone</mat-icon>
<span class="md-list-item-text">{{ selectedDevice().model }} ({{ connect.proxy.selectedDevice }})</span>
</mat-list-item>
</mat-list>
</div>
<div class="trace-section">
<div class="md-layout">
<button mat-raised-button class="md-accent" (click)="startTracing()">Start Trace</button>
<button mat-raised-button (click)="dumpState()">Dump State</button>
<button mat-raised-button class="md-primary" (click)="resetLastDevice()">Change Device</button>
</div>
<h3>Trace targets:</h3>
<trace-config
*ngFor="let trace of traceConfigurations"
[name]="trace.name"
[defaultCheck]="trace.defaultCheck"
[configs]="trace.config ? trace.config : null"
></trace-config>
</div>
<div class="dump-section">
<h3>Dump targets:</h3>
<div class="selection">
<mat-checkbox class="md-primary" *ngFor="let dumpKey of objectKeys(DUMPS)" [checked]="true">{{DUMPS[dumpKey]}}</mat-checkbox>
</div>
</div>
</div>
<div id="unknown-error" *ngIf="connect.state()===states.ERROR">
<mat-icon>error</mat-icon>
<span class="md-subheading">Error:</span>
<pre>
{{ errorText }}
</pre>
<button mat-raised-button (click)="restart()">Retry</button>
</div>
<div id="end-tracing" *ngIf="connect.state()===states.END_TRACE">
<span class="md-subheading">Tracing...</span>
<mat-progress-bar md-indeterminate></mat-progress-bar>
<pre>
{{ errorText }}
</pre>
<button mat-raised-button (click)="endTrace()">End trace</button>
</div>
<div id="load-data" *ngIf="connect.state()===states.LOAD_DATA">
<span class="md-subheading">Loading data...</span>
<mat-progress-bar md-indeterminate></mat-progress-bar>
</div>
</mat-card-content>
`,
styles: [".device-choice {cursor: pointer}"]
})
export class CollectTracesComponent implements OnInit {
objectKeys = Object.keys;
isAdbProxy = true;
startTrace = false;
startDump = false;
errorText = "";
traceConfigurations = traceConfigurations;
connect: any = new ProxyConnection();
downloadProxyUrl = "https://android.googlesource.com/platform/development/+/master/tools/winscope-ng/adb/winscope_proxy.py";
states = ProxyState;
@Input()
store: PersistentStore = new PersistentStore();
ngOnInit(): void {
if (this.isAdbProxy) {
this.connect = new ProxyConnection();
} else {
//TODO: change to WebAdbConnection
this.connect = new ProxyConnection();
}
}
ngOnDestroy(): void {
this.connect.proxy.removeOnProxyChange(this.onProxyChange);
}
public onAddKey(key: string) {
this.store.addToStore("adb.proxyKey", key);
this.connect.setProxyKey(key);
this.restart();
}
public onConnectChange(newState: Connection) {
this.connect.onConnectChange(newState);
}
public onProxyChange(newState: ProxyState, errorText: string) {
this.connect.onConnectChange(newState);
}
public adbSuccess() {
return this.connect.adbSuccess();
}
public devices(): Array<string> {
return this.connect.devices();
}
public selectedDevice(): Device {
return this.connect.selectedDevice();
}
public restart() {
this.connect.restart();
}
public resetLastDevice() {
this.connect.resetLastDevice();
}
public selectDevice(id: string) {
this.connect.selectDevice(id);
}
public displayAdbProxyTab() {
this.isAdbProxy = true;
this.connect = new ProxyConnection();
}
public displayWebAdbTab() {
this.isAdbProxy = false;
//TODO: change to WebAdbConnection
this.connect = new ProxyConnection();
}
public startTracing() {
this.startTrace = true;
console.log("begin tracing");
}
public dumpState() {
this.startDump = true;
console.log("begin dump");
}
public endTrace() {
console.log("end trace");
}
public setAvailableTraces() {
this.connect.setAvailableTraces();
}
public tabClass(adbTab: boolean) {
let isActive: string;
if (adbTab) {
isActive = this.isAdbProxy ? "active" : "inactive";
} else {
isActive = !this.isAdbProxy ? "active" : "inactive";
}
return ["tab", isActive];
}
DYNAMIC_TRACES: any = null;
DUMPS: configMap = {
"window_dump": "Window Manager",
"layers_dump": "Surface Flinger"
};
}

View File

@@ -1,5 +1,5 @@
import { proxyClient, ProxyState, ProxyEndpoint, ProxyClient } from "trace_collection/proxy_client";
import {TRACES, traceConfigurations} from "./trace_collection_utils";
import { proxyRequest, proxyClient, ProxyState, ProxyEndpoint } from "trace_collection/proxy_client";
import { TRACES } from "./trace_collection_utils";
export interface Device {
authorised: boolean;
@@ -13,24 +13,44 @@ export interface Connection {
selectedDevice(): Device;
restart(): any;
selectDevice(id:string): any;
DYNAMIC_TRACES: any;
DYNAMIC_TRACES(): any;
state(): ProxyState;
onConnectChange(newState: any): any;
setAvailableTraces(): any;
resetLastDevice(): any;
isDevicesState(): boolean;
isStartTraceState(): boolean;
isErrorState(): boolean;
isEndTraceState(): boolean;
isLoadDataState(): boolean;
isConnectingState(): boolean;
isNoProxy(): boolean;
isInvalidProxy(): boolean;
isUnauthProxy(): boolean;
throwNoTargetsError(): any;
startTrace(
reqEnableConfig?: Array<string>,
reqSelectedSfConfig?: any,
reqSelectedWmConfig?: any
): any;
DUMPS: any;
endTrace(): any;
dumpState(req:Array<string>): any;
adbData(): any;
}
export class ProxyConnection implements Connection {
DYNAMIC_TRACES: any;
proxy = proxyClient;
public state() {
return this.proxy.state;
}
public devices() {
return Object.keys(this.proxy.devices);
}
DUMPS: any = {
"window_dump": {
name: "Window Manager",
enabled: true,
},
"layers_dump": {
name: "Surface Flinger",
enabled: true,
}
};
keep_alive_worker: any = null;
notConnected = [
ProxyState.NO_PROXY,
ProxyState.UNAUTH,
@@ -47,26 +67,68 @@ export class ProxyConnection implements Connection {
this.proxy.getDevices();
}
public onConnectChange(newState: ProxyState) {
if (newState === ProxyState.CONNECTING) {
proxyClient.getDevices();
}
if (newState == ProxyState.START_TRACE) {
proxyClient.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, function(request: any,view:any) {
try {
if(request.responseText == "true") {
//view.appendOptionalTraces('arc', view.TRACES);
}
} catch(err) {
console.error(err);
proxyClient.setState(ProxyState.ERROR, request.responseText);
}
});
}
public devices() {
return Object.keys(this.proxy.devices);
}
public adbData() {
return this.proxy.adbData;
}
DYNAMIC_TRACES() {
return configureTraces.DYNAMIC_TRACES;
}
public state() {
return this.proxy.state;
}
public isDevicesState() {
return this.state() === ProxyState.DEVICES;
}
public isStartTraceState() {
return this.state() === ProxyState.START_TRACE;
}
public isErrorState() {
return this.state() === ProxyState.ERROR;
}
public isEndTraceState() {
return this.state() === ProxyState.END_TRACE;
}
public isLoadDataState() {
return this.state() === ProxyState.LOAD_DATA;
}
public isConnectingState() {
return this.state() === ProxyState.CONNECTING;
}
public isNoProxy() {
return this.state() === ProxyState.NO_PROXY;
}
public isInvalidProxy() {
return this.state() === ProxyState.INVALID_VERSION;
}
public isUnauthProxy() {
return this.state() === ProxyState.UNAUTH;
}
public throwNoTargetsError() {
this.proxy.setState(ProxyState.ERROR, "No targets selected");
}
public dataReady() {
return this.proxy.dataReady;
}
public setProxyKey(key: string) {
proxyClient.proxyKey = key;
this.proxy.proxyKey = key;
this.restart();
}
@@ -83,7 +145,7 @@ export class ProxyConnection implements Connection {
}
public resetLastDevice() {
this.proxy.resetLastDevice();
this.proxy.store.addToStore("adb.lastDevice", "");
this.restart();
}
@@ -91,17 +153,82 @@ export class ProxyConnection implements Connection {
this.proxy.selectDevice(id);
}
public setAvailableTraces() {
this.DYNAMIC_TRACES = TRACES["default"];
proxyClient.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, function(request:any, view:any) {
try {
if(request.responseText == "true") {
//view.appendOptionalTraces('arc');
}
} catch(err) {
console.error(err);
proxyClient.setState(ProxyState.ERROR, request.responseText);
public keepAliveTrace(view:any) {
if (!view.isEndTraceState()) {
clearInterval(view.keep_alive_worker);
view.keep_alive_worker = null;
return;
}
proxyRequest.call("GET", `${ProxyEndpoint.STATUS}${view.proxy.selectedDevice}/`, view, function(request:any, newView:any) {
if (request.responseText !== "True") {
newView.endTrace();
} else if (newView.keep_alive_worker === null) {
newView.keep_alive_worker = setInterval(newView.keepAliveTrace, 1000, newView);
}
});
}
public startTrace(
reqEnableConfig: any,
reqSelectedSfConfig: any,
reqSelectedWmConfig: any
) {
if (reqEnableConfig) {
proxyRequest.call("POST", `${ProxyEndpoint.CONFIG_TRACE}${this.proxy.selectedDevice}/`, this, null, null, reqEnableConfig);
}
if (reqSelectedSfConfig) {
proxyRequest.call("POST", `${ProxyEndpoint.SELECTED_SF_CONFIG_TRACE}${this.proxy.selectedDevice}/`, this, null, null, reqSelectedSfConfig);
}
if (reqSelectedWmConfig) {
proxyRequest.call("POST", `${ProxyEndpoint.SELECTED_WM_CONFIG_TRACE}${this.proxy.selectedDevice}/`, this, null, null, reqSelectedWmConfig);
}
proxyClient.setState(ProxyState.END_TRACE);
proxyRequest.call("POST", `${ProxyEndpoint.START_TRACE}${this.proxy.selectedDevice}/`, this, function(request:any, view:any) {
view.keepAliveTrace(view);
}, null, configureTraces.reqTraces);
}
public async endTrace() {
this.proxy.setState(ProxyState.LOAD_DATA);
await proxyRequest.call("POST", `${ProxyEndpoint.END_TRACE}${this.proxy.selectedDevice}/`, this,
async function (request:any, view:any) {
await proxyClient.updateAdbData(configureTraces.reqTraces, 0, "trace", view);
});
}
public dumpState() {
if (configureTraces.reqDumps.length < 1) {
this.proxy.setState(ProxyState.ERROR, "No targets selected");
return;
}
this.proxy.setState(ProxyState.LOAD_DATA);
proxyRequest.call("POST", `${ProxyEndpoint.DUMP}${this.proxy.selectedDevice}/`, this, function(request:any, view:any) {
view.proxy.updateAdbData(configureTraces.reqDumps, 0, "dump", view);
}, null, configureTraces.reqDumps);
}
public onConnectChange(newState: ProxyState) {
if (newState === ProxyState.CONNECTING) {
proxyClient.getDevices();
}
if (newState == ProxyState.START_TRACE) {
configureTraces.setAvailableTraces();
}
}
}
class ConfigureTraces {
DYNAMIC_TRACES = TRACES["default"];
reqTraces: string[] = [];
reqDumps: string[] = [];
setAvailableTraces() {
proxyRequest.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, proxyRequest.setAvailableTraces);
}
appendOptionalTraces(view:any, device_key:string) {
for(const key in TRACES[device_key]) {
view.DYNAMIC_TRACES[key] = TRACES[device_key][key];
}
}
}
export const configureTraces = new ConfigureTraces();

View File

@@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PersistentStore } from "common/persistent_store";
import { PersistentStore } from "../common/persistent_store";
import { TRACES } from "./trace_collection_utils";
export enum ProxyState {
ERROR = 0,
@@ -40,25 +41,11 @@ export enum ProxyEndpoint {
CHECK_WAYLAND = "/checkwayland/",
}
export class ProxyClient {
readonly WINSCOPE_PROXY_URL = "http://localhost:5544";
readonly VERSION = "0.8";
state: ProxyState = ProxyState.CONNECTING;
stateChangeListeners: {(param:ProxyState, errorText:string): void;}[] = [];
refresh_worker: NodeJS.Timeout | undefined = undefined;
devices: any = {};
selectedDevice = "";
errorText = "";
proxyKey = "";
lastDevice = "";
store = new PersistentStore();
call(method: string, path: string, view: any, onSuccess: any, type = null, jsonRequest = null) {
// from here, all requests to the proxy are made
class ProxyRequest {
async call(method: string, path: string, view: any, onSuccess: any, type: any = null, jsonRequest:any = null) {
const request = new XMLHttpRequest();
const client = this;
const client = proxyClient;
request.onreadystatechange = function() {
if (this.readyState !== 4) {
return;
@@ -79,7 +66,7 @@ export class ProxyClient {
} else if (this.responseType === "arraybuffer") {
client.errorText = String.fromCharCode.apply(null, new Array(this.response));
}
client.setState(ProxyState.ERROR);
client.setState(ProxyState.ERROR, client.errorText);
}
};
request.responseType = type || "";
@@ -98,6 +85,88 @@ export class ProxyClient {
}
}
getDevices = function(request: any, view: any) {
const client = proxyClient;
try {
client.devices = JSON.parse(request.responseText);
const last = client.store.getFromStore("adb.lastDevice");
if (last && client.devices[last] &&
client.devices[last].authorised) {
client.selectDevice(last);
} else {
if (client.refresh_worker === null) {
client.refresh_worker = setInterval(client.getDevices, 1000);
}
client.setState(ProxyState.DEVICES);
}
} catch (err) {
console.error(err);
client.errorText = request.responseText;
client.setState(ProxyState.ERROR, client.errorText);
}
};
setAvailableTraces = function(request:any, view:any) {
try {
view.DYNAMIC_TRACES = TRACES["default"];
if(request.responseText == "true") {
view.appendOptionalTraces(view, "arc");
}
} catch(err) {
proxyClient.setState(ProxyState.ERROR, request.responseText);
}
};
updateAdbData = async (request: any, view: any) => {
let idx = proxyClient.adbParams.idx;
let files = proxyClient.adbParams.files;
let traceType = proxyClient.adbParams.traceType;
try {
const enc = new TextDecoder("utf-8");
const resp = enc.decode(request.response);
const filesByType = JSON.parse(resp);
for (const filetype in filesByType) {
const files = filesByType[filetype];
for (const encodedFileBuffer of files) {
const buffer = Uint8Array.from(atob(encodedFileBuffer), (c) => c.charCodeAt(0));
const blob = new Blob([buffer]);
proxyClient.adbData.push(blob);
}
}
if (idx < files.length - 1) {
proxyClient.updateAdbData(files, idx + 1, traceType, view);
} else {
proxyClient.dataReady = true;
}
} catch (error) {
proxyClient.setState(ProxyState.ERROR, request.responseText);
}
}
}
export const proxyRequest = new ProxyRequest();
// stores all the changing variables from proxy and sets up calls from ProxyRequest
export class ProxyClient {
readonly WINSCOPE_PROXY_URL = "http://localhost:5544";
readonly VERSION = "0.8";
state: ProxyState = ProxyState.CONNECTING;
stateChangeListeners: {(param:ProxyState, errorText:string): void;}[] = [];
refresh_worker: NodeJS.Timer | null = null;
devices: any = {};
selectedDevice = "";
errorText = "";
adbData: Array<any> = [];
proxyKey = "";
lastDevice = "";
store = new PersistentStore();
dataReady: boolean = false;
adbParams = {
files: [],
idx: -1,
traceType: null,
};
setState(state:ProxyState, errorText = "") {
this.state = state;
this.errorText = errorText;
@@ -117,46 +186,25 @@ export class ProxyClient {
getDevices() {
if (this.state !== ProxyState.DEVICES && this.state !== ProxyState.CONNECTING) {
clearInterval(this.refresh_worker);
this.refresh_worker = undefined;
clearInterval(this.refresh_worker!);
this.refresh_worker = null;
return;
}
const client = this;
this.call("GET", ProxyEndpoint.DEVICES, this, function(request: any, view: any) {
try {
client.devices = JSON.parse(request.responseText);
const last = client.store.getFromStore("adb.lastDevice");
if (last && client.devices[last] &&
client.devices[last].authorised) {
client.selectDevice(last);
} else {
if (client.refresh_worker === undefined) {
client.refresh_worker = setInterval(client.getDevices, 1000);
}
client.setState(ProxyState.DEVICES);
}
} catch (err) {
console.error(err);
client.errorText = request.responseText;
client.setState(ProxyState.ERROR);
}
});
proxyRequest.call("GET", ProxyEndpoint.DEVICES, this, proxyRequest.getDevices);
}
selectDevice(device_id: string) {
this.selectedDevice = device_id;
this.store.addToStore("adb.lastDevice", device_id);
this.lastDevice = device_id;
this.setState(ProxyState.START_TRACE);
}
deviceId() {
return this.selectedDevice;
}
resetLastDevice() {
this.lastDevice = "";
this.store.addToStore("adb.lastDevice", "");
async updateAdbData(files:any, idx:any, traceType:any, view: any) {
this.adbParams.files = files;
this.adbParams.idx = idx;
this.adbParams.traceType = traceType;
await proxyRequest.call("GET", `${ProxyEndpoint.FETCH}${this.selectedDevice}/${files[idx]}/`, view,
proxyRequest.updateAdbData, "arraybuffer");
}
}

View File

@@ -1,10 +1,14 @@
interface TraceConfiguration {
name: string,
defaultCheck?: boolean,
export interface TraceConfiguration {
name?: string,
run?: boolean,
isTraceCollection?: boolean,
config?: ConfigurationOptions
}
interface TraceConfigurationMap {
[key: string]: TraceConfiguration
}
export interface ConfigurationOptions {
enableConfigs: Array<EnableConfiguration>,
selectionConfigs: Array<SelectionConfiguration>
@@ -12,10 +16,12 @@ export interface ConfigurationOptions {
export interface EnableConfiguration {
name: string,
defaultCheck: boolean,
key: string,
enabled: boolean,
}
export interface SelectionConfiguration {
key: string,
name: string,
options: Array<string>,
value: string
@@ -25,46 +31,10 @@ export type configMap = {
[key: string]: Array<string> | string;
}
export const TRACES = {
"default": {
"window_trace": {
name: "Window Manager",
},
"accessibility_trace": {
name: "Accessibility",
},
"layers_trace": {
name: "Surface Flinger",
},
"transactions": {
name: "Transaction",
},
"proto_log": {
name: "ProtoLog",
},
"screen_recording": {
name: "Screen Recording",
},
"ime_trace_clients": {
name: "Input Method Clients",
},
"ime_trace_service": {
name: "Input Method Service",
},
"ime_trace_managerservice": {
name: "Input Method Manager Service",
},
},
"arc": {
"wayland_trace": {
name: "Wayland",
},
},
};
const wmTraceSelectionConfigs = [
const wmTraceSelectionConfigs: Array<SelectionConfiguration> = [
{
name: "wmbuffersize (KB)",
key: "wmbuffersize",
name: "buffer size (KB)",
options: [
"4000",
"8000",
@@ -74,7 +44,8 @@ const wmTraceSelectionConfigs = [
value: "4000"
},
{
name: "tracingtype",
key: "tracingtype",
name: "tracing type",
options: [
"frame",
"transaction",
@@ -82,7 +53,8 @@ const wmTraceSelectionConfigs = [
value: "frame"
},
{
name: "tracinglevel",
key: "tracinglevel",
name: "tracing level",
options: [
"verbose",
"debug",
@@ -92,54 +64,126 @@ const wmTraceSelectionConfigs = [
},
];
export const traceConfigurations: Array<TraceConfiguration> = [
const sfTraceEnableConfigs: Array<EnableConfiguration> = [
{
name: "Surface Flinger",
defaultCheck: true,
config: {
enableConfigs: [
{name: "composition", defaultCheck: false},
{name: "metadata", defaultCheck: false},
{name: "hwc", defaultCheck: false},
{name: "tracebuffers", defaultCheck: false}
],
selectionConfigs: [
{
name: "sfbuffersize (KB)",
options: ["4000","8000","16000","32000",],
value: "4000"
}
]
}
name: "composition",
key: "composition",
enabled: true
},
{
name: "metadata",
key: "metadata",
enabled: true
},
{
name: "hwc",
key: "hwc",
enabled: true
},
{
name: "trace buffers",
key: "tracebuffers",
enabled: true
}
];
const sfTraceSelectionConfigs: Array<SelectionConfiguration> = [
{
key: "sfbuffersize",
name: "buffer size (KB)",
options: ["4000","8000","16000","32000"],
value: "4000"
}
];
export const traceConfigurations: TraceConfigurationMap = {
"layers_trace": {
name: "Surface Flinger",
run: true,
config: {
enableConfigs: sfTraceEnableConfigs,
selectionConfigs: sfTraceSelectionConfigs,
}
},
"window_trace": {
name: "Window Manager",
defaultCheck: true,
run: true,
config: {
enableConfigs: [],
selectionConfigs: wmTraceSelectionConfigs,
}
},
{
"screen_recording": {
name: "Screen Recording",
run: true,
},
{
name: "Accessibility",
},
{
name: "Transaction",
},
{
"ime_tracing": {
name: "IME Tracing",
defaultCheck: true,
run: true,
isTraceCollection: true,
config: {
enableConfigs: [
{name: "Input Method Clients", defaultCheck: true},
{name: "Input Method Service", defaultCheck: true},
{name: "Input Method Manager Service", defaultCheck: true},
{
name: "Input Method Clients",
key: "ime_trace_clients",
enabled: true,
},
{
name: "Input Method Service",
key: "ime_trace_service",
enabled: true,
},
{
name: "Input Method Manager Service",
key: "ime_trace_managerservice",
enabled: true,
},
],
selectionConfigs: []
}
},
];
"ime_trace_clients": {
name: "Input Method Clients",
run: true,
},
"ime_trace_service": {
name: "Input Method Service",
run: true,
},
"ime_trace_managerservice": {
name: "Input Method Manager Service",
run: true,
},
"accessibility_trace": {
name: "Accessibility",
run: false,
},
"transactions": {
name: "Transaction",
run: false,
},
"proto_log": {
name: "ProtoLog",
run: false,
},
"wayland_trace": {
name: "Wayland",
run: false,
},
};
export const TRACES: { [key: string]: TraceConfigurationMap; } = {
"default": {
"window_trace": traceConfigurations["window_trace"],
"accessibility_trace": traceConfigurations["accessibility_trace"],
"layers_trace": traceConfigurations["layers_trace"],
"transactions": traceConfigurations["transactions"],
"proto_log": traceConfigurations["proto_log"],
"screen_recording": traceConfigurations["screen_recording"],
"ime_tracing": traceConfigurations["ime_tracing"],
},
"arc": {
"wayland_trace": traceConfigurations["wayland_trace"],
},
};

View File

@@ -1,73 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ConfigurationOptions, SelectionConfiguration } from "./trace_collection_utils";
import { Component, Input, Output, EventEmitter } from "@angular/core";
import { ProxyState, ProxyClient } from "./proxy_client";
@Component({
selector: "trace-config",
template: `
<div class="card-block">
<mat-checkbox class="md-primary" [checked]="defaultCheck">{{name}}</mat-checkbox>
<div class="adv-config" *ngIf="configs">
<mat-checkbox class="md-primary" *ngFor="let enableConfig of traceEnableConfigs()" [checked]="enableConfig.defaultCheck">{{enableConfig.name}}</mat-checkbox>
<div class="selection">
<mat-form-field appearance="fill" class="config-selection" *ngFor="let con of traceSelectionConfigs()">
<mat-label>{{con.name}}</mat-label>
<mat-select>
<mat-option
*ngFor="let option of con.options"
[value]="con.value"
>{{ option }}</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</div>
`,
styles: [".adv-config {margin-left: 5rem;}"],
})
export class TraceConfigComponent {
states = ProxyState;
objectKeys = Object.keys;
@Input()
name = "";
@Input()
configs: ConfigurationOptions | null = null;
@Input()
defaultCheck: boolean | undefined = false;
public traceEnableConfigs(): Array<any> {
if (this.configs && this.configs.enableConfigs) {
return this.configs.enableConfigs;
} else {
return [];
}
}
public traceSelectionConfigs(): Array<SelectionConfiguration> {
if (this.configs) {
return this.configs.selectionConfigs;
} else {
return [];
}
}
}