• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!-- Copyright (C) 2019 The Android Open Source Project
2
3     Licensed under the Apache License, Version 2.0 (the "License");
4     you may not use this file except in compliance with the License.
5     You may obtain a copy of the License at
6
7          http://www.apache.org/licenses/LICENSE-2.0
8
9     Unless required by applicable law or agreed to in writing, software
10     distributed under the License is distributed on an "AS IS" BASIS,
11     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12     See the License for the specific language governing permissions and
13     limitations under the License.
14-->
15<template>
16  <flat-card style="min-width: 50em">
17    <md-card-header>
18      <div class="md-title">ADB Connect</div>
19    </md-card-header>
20    <md-card-content v-if="status === STATES.CONNECTING">
21      <md-progress-spinner md-indeterminate></md-progress-spinner>
22    </md-card-content>
23    <md-card-content v-if="status === STATES.NO_PROXY">
24      <md-icon class="md-accent">error</md-icon>
25      <span class="md-subheading">Unable to connect to Winscope ADB proxy</span>
26      <div class="md-body-2">
27        <p>Launch the Winscope ADB Connect proxy to capture traces directly from your browser.</p>
28        <p>Python 3.5+ and ADB is required.</p>
29        <p>Run:</p>
30        <pre>python3 $ANDROID_BUILD_TOP/development/tools/winscope/adb_proxy/winscope_proxy.py</pre>
31        <p>Or get it from the AOSP repository.</p>
32      </div>
33      <div class="md-layout">
34        <md-button class="md-accent" :href="downloadProxyUrl" @click="buttonClicked(`Download from AOSP`)">Download from AOSP</md-button>
35        <md-button class="md-accent" @click="restart">Retry</md-button>
36      </div>
37    </md-card-content>
38    <md-card-content v-if="status === STATES.INVALID_VERSION">
39      <md-icon class="md-accent">update</md-icon>
40      <span class="md-subheading">The version of Winscope ADB Connect proxy running on your machine is incopatibile with Winscope.</span>
41      <div class="md-body-2">
42        <p>Please update the proxy to version {{ proxyClient.VERSION }}</p>
43        <p>Run:</p>
44        <pre>python3 $ANDROID_BUILD_TOP/development/tools/winscope/adb_proxy/winscope_proxy.py</pre>
45        <p>Or get it from the AOSP repository.</p>
46      </div>
47      <div class="md-layout">
48        <md-button class="md-accent" :href="downloadProxyUrl">Download from AOSP</md-button>
49        <md-button class="md-accent" @click="restart">Retry</md-button>
50      </div>
51    </md-card-content>
52    <md-card-content v-if="status === STATES.UNAUTH">
53      <md-icon class="md-accent">lock</md-icon>
54      <span class="md-subheading">Proxy authorisation required</span>
55      <md-field>
56        <label>Enter Winscope proxy token</label>
57        <md-input v-model="proxyClient.store.proxyKey"></md-input>
58      </md-field>
59      <div class="md-body-2">The proxy token is printed to console on proxy launch, copy and paste it above.</div>
60      <div class="md-layout">
61        <md-button class="md-primary" @click="restart">Connect</md-button>
62      </div>
63    </md-card-content>
64    <md-card-content v-if="status === STATES.DEVICES">
65      <div class="md-subheading">{{ Object.keys(proxyClient.devices).length > 0 ? "Connected devices:" : "No devices detected" }}</div>
66      <md-list>
67        <md-list-item v-for="(device, id) in proxyClient.devices" :key="id" @click="proxyClient.selectDevice(id)" :disabled="!device.authorised">
68          <md-icon>{{ device.authorised ? "smartphone" : "screen_lock_portrait" }}</md-icon>
69          <span class="md-list-item-text">{{ device.authorised ? device.model : "unauthorised" }} ({{ id }})</span>
70        </md-list-item>
71      </md-list>
72      <md-progress-spinner :md-size="30" md-indeterminate></md-progress-spinner>
73    </md-card-content>
74    <md-card-content v-if="status === STATES.START_TRACE">
75      <div class="device-choice">
76        <md-list>
77          <md-list-item>
78            <md-icon>smartphone</md-icon>
79            <span class="md-list-item-text">{{ proxyClient.devices[proxyClient.selectedDevice].model }} ({{ proxyClient.selectedDevice }})</span>
80          </md-list-item>
81        </md-list>
82        <md-button class="md-primary" @click="resetLastDevice">Change device</md-button>
83      </div>
84      <div class="trace-section">
85        <h3>Trace targets:</h3>
86        <div class="selection">
87          <md-checkbox class="md-primary" v-for="traceKey in Object.keys(DYNAMIC_TRACES)" :key="traceKey" v-model="traceStore[traceKey]">{{ DYNAMIC_TRACES[traceKey].name }}</md-checkbox>
88        </div>
89        <div class="trace-config">
90            <h4>Surface Flinger config</h4>
91            <div class="selection">
92              <md-checkbox class="md-primary" v-for="config in TRACE_CONFIG['layers_trace']" :key="config" v-model="traceStore[config]">{{config}}</md-checkbox>
93              <div class="selection">
94                <md-field class="config-selection" v-for="selectConfig in Object.keys(SF_SELECTED_CONFIG)" :key="selectConfig">
95                  <md-select v-model="SF_SELECTED_CONFIG_VALUES[selectConfig]" :placeholder="selectConfig">
96                    <md-option value="">{{selectConfig}}</md-option>
97                    <md-option v-for="option in SF_SELECTED_CONFIG[selectConfig]" :key="option" :value="option">{{ option }}</md-option>
98                  </md-select>
99                </md-field>
100              </div>
101            </div>
102        </div>
103        <div class="trace-config">
104            <h4>Window Manager config</h4>
105            <div class="selection">
106              <md-field class="config-selection" v-for="selectConfig in Object.keys(WM_SELECTED_CONFIG)" :key="selectConfig">
107                <md-select v-model="WM_SELECTED_CONFIG_VALUES[selectConfig]" :placeholder="selectConfig">
108                  <md-option value="">{{selectConfig}}</md-option>
109                  <md-option v-for="option in WM_SELECTED_CONFIG[selectConfig]" :key="option" :value="option">{{ option }}</md-option>
110                </md-select>
111              </md-field>
112            </div>
113        </div>
114        <md-button class="md-primary trace-btn" @click="startTrace">Start trace</md-button>
115      </div>
116      <div class="dump-section">
117        <h3>Dump targets:</h3>
118        <div class="selection">
119          <md-checkbox class="md-primary" v-for="dumpKey in Object.keys(DUMPS)" :key="dumpKey" v-model="traceStore[dumpKey]">{{DUMPS[dumpKey].name}}</md-checkbox>
120        </div>
121        <div class="md-layout">
122          <md-button class="md-primary dump-btn" @click="dumpState">Dump state</md-button>
123        </div>
124      </div>
125    </md-card-content>
126    <md-card-content v-if="status === STATES.ERROR">
127      <md-icon class="md-accent">error</md-icon>
128      <span class="md-subheading">Error:</span>
129      <pre>
130        {{ errorText }}
131      </pre>
132      <md-button class="md-primary" @click="restart">Retry</md-button>
133    </md-card-content>
134    <md-card-content v-if="status === STATES.END_TRACE">
135      <span class="md-subheading">Tracing...</span>
136      <md-progress-bar md-mode="indeterminate"></md-progress-bar>
137      <div class="md-layout">
138        <md-button class="md-primary" @click="endTrace">End trace</md-button>
139      </div>
140    </md-card-content>
141    <md-card-content v-if="status === STATES.LOAD_DATA">
142      <span class="md-subheading">Loading data...</span>
143      <md-progress-bar md-mode="determinate" :md-value="loadProgress"></md-progress-bar>
144    </md-card-content>
145  </flat-card>
146</template>
147<script>
148import LocalStore from './localstore.js';
149import FlatCard from './components/FlatCard.vue';
150import {proxyClient, ProxyState, ProxyEndpoint} from './proxyclient/ProxyClient.ts';
151
152// trace options should be added in a nested category
153const TRACES = {
154  'default': {
155    'window_trace': {
156      name: 'Window Manager',
157    },
158    'accessibility_trace': {
159      name: 'Accessibility',
160    },
161    'layers_trace': {
162      name: 'Surface Flinger',
163    },
164    'transactions': {
165      name: 'Transaction',
166    },
167    'proto_log': {
168      name: 'ProtoLog',
169    },
170    'screen_recording': {
171      name: 'Screen Recording',
172    },
173    'ime_trace_clients': {
174      name: 'Input Method Clients',
175    },
176    'ime_trace_service': {
177      name: 'Input Method Service',
178    },
179    'ime_trace_managerservice': {
180      name: 'Input Method Manager Service',
181    },
182  },
183  'arc': {
184    'wayland_trace': {
185      name: 'Wayland',
186    },
187  },
188};
189
190const TRACE_CONFIG = {
191  'layers_trace': [
192    'composition',
193    'metadata',
194    'hwc',
195    'tracebuffers',
196  ],
197};
198
199const SF_SELECTED_CONFIG = {
200  'sfbuffersize': [
201    '4000',
202    '8000',
203    '16000',
204    '32000',
205  ],
206};
207
208const WM_SELECTED_CONFIG = {
209  'wmbuffersize': [
210    '4000',
211    '8000',
212    '16000',
213    '32000',
214  ],
215  'tracingtype': [
216    'frame',
217    'transaction',
218  ],
219  'tracinglevel': [
220    'verbose',
221    'debug',
222    'critical',
223  ],
224};
225
226const DUMPS = {
227  'window_dump': {
228    name: 'Window Manager',
229  },
230  'layers_dump': {
231    name: 'Surface Flinger',
232  },
233};
234
235const CONFIGS = Object.keys(TRACE_CONFIG).flatMap((file) => TRACE_CONFIG[file]);
236
237export default {
238  name: 'dataadb',
239  data() {
240    return {
241      proxyClient,
242      ProxyState,
243      STATES: ProxyState,
244      TRACES,
245      DYNAMIC_TRACES: TRACES['default'],
246      TRACE_CONFIG,
247      SF_SELECTED_CONFIG,
248      WM_SELECTED_CONFIG,
249      SF_SELECTED_CONFIG_VALUES: {},
250      WM_SELECTED_CONFIG_VALUES: {},
251      DUMPS,
252      status: ProxyState.CONNECTING,
253      dataFiles: [],
254      keep_alive_worker: null,
255      errorText: '',
256      loadProgress: 0,
257      traceStore: LocalStore(
258          'trace',
259          Object.assign(
260              this.getAllTraceKeys(TRACES)
261                  .concat(Object.keys(DUMPS))
262                  .concat(CONFIGS)
263                  .reduce(function(obj, key) {
264                    obj[key] = true; return obj;
265                  }, {}),
266          ),
267      ),
268      downloadProxyUrl: 'https://android.googlesource.com/platform/development/+/master/tools/winscope/adb_proxy/winscope_proxy.py',
269      onStateChangeFn: (newState, errorText) => {
270        this.status = newState;
271        this.errorText = errorText;
272      },
273    };
274  },
275  props: ['store'],
276  components: {
277    'flat-card': FlatCard,
278  },
279  methods: {
280    getAllTraceKeys(traces) {
281      let keys = [];
282      for (let dict_key in traces) {
283        for (let key in traces[dict_key]) {
284          keys.push(key);
285        }
286      }
287      return keys;
288    },
289    setAvailableTraces() {
290      this.DYNAMIC_TRACES = this.TRACES['default'];
291      proxyClient.call('GET', ProxyEndpoint.CHECK_WAYLAND, this, function(request, view) {
292        try {
293          if(request.responseText == 'true') {
294            view.appendOptionalTraces('arc');
295          }
296        } catch(err) {
297          console.error(err);
298          proxyClient.setState(ProxyState.ERROR, request.responseText);
299        }
300      });
301    },
302    appendOptionalTraces(device_key) {
303      for(let key in this.TRACES[device_key]) {
304        this.$set(this.DYNAMIC_TRACES, key, this.TRACES[device_key][key]);
305      }
306    },
307    keepAliveTrace() {
308      if (this.status !== ProxyState.END_TRACE) {
309        clearInterval(this.keep_alive_worker);
310        this.keep_alive_worker = null;
311        return;
312      }
313      proxyClient.call('GET', `${ProxyEndpoint.STATUS}${proxyClient.deviceId()}/`, this, function(request, view) {
314        if (request.responseText !== 'True') {
315          view.endTrace();
316        } else if (view.keep_alive_worker === null) {
317          view.keep_alive_worker = setInterval(view.keepAliveTrace, 1000);
318        }
319      });
320    },
321    startTrace() {
322      const requested = this.toTrace();
323      const requestedConfig = this.toTraceConfig();
324      const requestedSelectedSfConfig = this.toSelectedSfTraceConfig();
325      const requestedSelectedWmConfig = this.toSelectedWmTraceConfig();
326      if (requested.length < 1) {
327        proxyClient.setState(ProxyState.ERROR, 'No targets selected');
328        this.recordNewEvent("No targets selected");
329        return;
330      }
331
332      this.recordNewEvent("Start Trace");
333      proxyClient.call('POST', `${ProxyEndpoint.CONFIG_TRACE}${proxyClient.deviceId()}/`, this, null, null, requestedConfig);
334      proxyClient.call('POST', `${ProxyEndpoint.SELECTED_SF_CONFIG_TRACE}${proxyClient.deviceId()}/`, this, null, null, requestedSelectedSfConfig);
335      proxyClient.call('POST',  `${ProxyEndpoint.SELECTED_WM_CONFIG_TRACE}${proxyClient.deviceId()}/`, this, null, null, requestedSelectedWmConfig);
336      proxyClient.setState(ProxyState.END_TRACE);
337      proxyClient.call('POST', `${ProxyEndpoint.START_TRACE}${proxyClient.deviceId()}/`, this, function(request, view) {
338        view.keepAliveTrace();
339      }, null, requested);
340    },
341    dumpState() {
342      this.recordButtonClickedEvent("Dump State");
343      const requested = this.toDump();
344      if (requested.length < 1) {
345        proxyClient.setState(ProxyState.ERROR, 'No targets selected');
346        this.recordNewEvent("No targets selected");
347        return;
348      }
349      proxyClient.setState(ProxyState.LOAD_DATA);
350      proxyClient.call('POST', `${ProxyEndpoint.DUMP}${proxyClient.deviceId()}/`, this, function(request, view) {
351        proxyClient.loadFile(requested, 0, "dump", view);
352      }, null, requested);
353    },
354    endTrace() {
355      proxyClient.setState(ProxyState.LOAD_DATA);
356      proxyClient.call('POST', `${ProxyEndpoint.END_TRACE}${proxyClient.deviceId()}/`, this, function(request, view) {
357        proxyClient.loadFile(view.toTrace(), 0, "trace", view);
358      });
359      this.recordNewEvent("Ended Trace");
360    },
361    toTrace() {
362      return Object.keys(this.DYNAMIC_TRACES)
363          .filter((traceKey) => this.traceStore[traceKey]);
364    },
365    toTraceConfig() {
366      return Object.keys(TRACE_CONFIG)
367          .filter((file) => this.traceStore[file])
368          .flatMap((file) => TRACE_CONFIG[file])
369          .filter((config) => this.traceStore[config]);
370    },
371    toSelectedSfTraceConfig() {
372      const requestedSelectedConfig = {};
373      for (const config in this.SF_SELECTED_CONFIG_VALUES) {
374        if (this.SF_SELECTED_CONFIG_VALUES[config] !== "") {
375          requestedSelectedConfig[config] = this.SF_SELECTED_CONFIG_VALUES[config];
376        }
377      }
378      return requestedSelectedConfig;
379    },
380    toSelectedWmTraceConfig() {
381      const requestedSelectedConfig = {};
382      for (const config in this.WM_SELECTED_CONFIG_VALUES) {
383        if (this.WM_SELECTED_CONFIG_VALUES[config] !== "") {
384          requestedSelectedConfig[config] = this.WM_SELECTED_CONFIG_VALUES[config];
385        }
386      }
387      return requestedSelectedConfig;
388    },
389    toDump() {
390      return Object.keys(DUMPS)
391          .filter((dumpKey) => this.traceStore[dumpKey]);
392    },
393    restart() {
394      this.recordButtonClickedEvent("Connect / Retry");
395      proxyClient.setState(ProxyState.CONNECTING);
396    },
397    resetLastDevice() {
398      this.recordButtonClickedEvent("Change Device");
399      this.proxyClient.resetLastDevice();
400      this.restart();
401    },
402  },
403  created() {
404    proxyClient.setState(ProxyState.CONNECTING);
405    this.proxyClient.onStateChange(this.onStateChangeFn);
406    const urlParams = new URLSearchParams(window.location.search);
407    if (urlParams.has('token')) {
408      this.proxyClient.proxyKey = urlParams.get('token');
409    }
410    this.proxyClient.getDevices();
411  },
412  beforeDestroy() {
413    this.proxyClient.removeOnStateChange(this.onStateChangeFn);
414  },
415  watch: {
416    status: {
417      handler(st) {
418        if (st == ProxyState.CONNECTING) {
419          this.proxyClient.getDevices();
420        }
421        if (st == ProxyState.START_TRACE) {
422          this.setAvailableTraces();
423        }
424      },
425    },
426  },
427};
428
429</script>
430<style scoped>
431.config-selection {
432  width: 150px;
433  display: inline-flex;
434  margin-left: 5px;
435  margin-right: 5px;
436}
437.device-choice {
438  display: inline-flex;
439}
440h3 {
441  margin-bottom: 0;
442}
443.trace-btn, .dump-btn {
444  margin-top: 0;
445}
446pre {
447  white-space: pre-wrap;
448}
449</style>
450