• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2022 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
15import {fetchWithTimeout} from '../../../base/http_utils';
16import {VERSION} from '../../../gen/perfetto_version';
17import {AdbConnectionImpl} from '../adb_connection_impl';
18import {
19  DataSource,
20  OnTargetChangeCallback,
21  RecordingTargetV2,
22  TargetInfo,
23  TracingSession,
24  TracingSessionListener,
25} from '../recording_interfaces_v2';
26import {
27  CUSTOM_TRACED_CONSUMER_SOCKET_PATH,
28  DEFAULT_TRACED_CONSUMER_SOCKET_PATH,
29  TRACEBOX_DEVICE_PATH,
30  TRACEBOX_FETCH_TIMEOUT,
31} from '../recording_utils';
32import {TracedTracingSession} from '../traced_tracing_session';
33
34export abstract class AndroidTarget implements RecordingTargetV2 {
35  private consumerSocketPath = DEFAULT_TRACED_CONSUMER_SOCKET_PATH;
36  protected androidApiLevel?: number;
37  protected dataSources?: DataSource[];
38
39  protected constructor(
40      private adbConnection: AdbConnectionImpl,
41      private onTargetChange: OnTargetChangeCallback) {}
42
43  abstract getInfo(): TargetInfo;
44
45  // This is called when a usb USBConnectionEvent of type 'disconnect' event is
46  // emitted. This event is emitted when the USB connection is lost (example:
47  // when the user unplugged the connecting cable).
48  async disconnect(disconnectMessage?: string): Promise<void> {
49    await this.adbConnection.disconnect(disconnectMessage);
50  }
51
52  // Starts a tracing session in order to fetch information such as apiLevel
53  // and dataSources from the device. Then, it cancels the session.
54  async fetchTargetInfo(listener: TracingSessionListener): Promise<void> {
55    const tracingSession = await this.createTracingSession(listener);
56    tracingSession.cancel();
57  }
58
59  // We do not support long tracing on Android.
60  canCreateTracingSession(recordingMode: string): boolean {
61    return recordingMode !== 'LONG_TRACE';
62  }
63
64  async createTracingSession(tracingSessionListener: TracingSessionListener):
65      Promise<TracingSession> {
66    this.adbConnection.onStatus = tracingSessionListener.onStatus;
67    this.adbConnection.onDisconnect = tracingSessionListener.onDisconnect;
68
69    if (!this.androidApiLevel) {
70      // 1. Fetch the API version from the device.
71      const version = await this.adbConnection.shellAndGetOutput(
72          'getprop ro.build.version.sdk');
73      this.androidApiLevel = Number(version);
74
75      this.onTargetChange();
76
77      // 2. For older OS versions we push the tracebox binary.
78      if (this.androidApiLevel < 29) {
79        await this.pushTracebox();
80        this.consumerSocketPath = CUSTOM_TRACED_CONSUMER_SOCKET_PATH;
81
82        await this.adbConnection.shellAndWaitCompletion(
83            this.composeTraceboxCommand('traced'));
84        await this.adbConnection.shellAndWaitCompletion(
85            this.composeTraceboxCommand('traced_probes'));
86      }
87    }
88
89    const adbStream =
90        await this.adbConnection.connectSocket(this.consumerSocketPath);
91
92    // 3. Start a tracing session.
93    const tracingSession =
94        new TracedTracingSession(adbStream, tracingSessionListener);
95    await tracingSession.initConnection();
96
97    if (!this.dataSources) {
98      // 4. Fetch dataSources from QueryServiceState.
99      this.dataSources = await tracingSession.queryServiceState();
100
101      this.onTargetChange();
102    }
103    return tracingSession;
104  }
105
106  async pushTracebox() {
107    const arch = await this.fetchArchitecture();
108    const shortVersion = VERSION.split('-')[0];
109    const requestUrl =
110        `https://commondatastorage.googleapis.com/perfetto-luci-artifacts/${
111            shortVersion}/${arch}/tracebox`;
112    const fetchResponse = await fetchWithTimeout(
113        requestUrl, {method: 'get'}, TRACEBOX_FETCH_TIMEOUT);
114    const traceboxBin = await fetchResponse.arrayBuffer();
115    await this.adbConnection.push(
116        new Uint8Array(traceboxBin), TRACEBOX_DEVICE_PATH);
117
118    // We explicitly set the tracebox permissions because adb does not reliably
119    // set permissions when uploading the binary.
120    await this.adbConnection.shellAndWaitCompletion(
121        `chmod 755 ${TRACEBOX_DEVICE_PATH}`);
122  }
123
124  async fetchArchitecture() {
125    const abiList = await this.adbConnection.shellAndGetOutput(
126        'getprop ro.vendor.product.cpu.abilist');
127    // If multiple ABIs are allowed, the 64bit ones should have higher priority.
128    if (abiList.includes('arm64-v8a')) {
129      return 'android-arm64';
130    } else if (abiList.includes('x86')) {
131      return 'android-x86';
132    } else if (abiList.includes('armeabi-v7a') || abiList.includes('armeabi')) {
133      return 'android-arm';
134    } else if (abiList.includes('x86_64')) {
135      return 'android-x64';
136    }
137    // Most devices have arm64 architectures, so we should return this if
138    // nothing else is found.
139    return 'android-arm64';
140  }
141
142  canConnectWithoutContention(): Promise<boolean> {
143    return this.adbConnection.canConnectWithoutContention();
144  }
145
146  composeTraceboxCommand(applet: string) {
147    // 1. Set the consumer socket.
148    return 'PERFETTO_CONSUMER_SOCK_NAME=@traced_consumer ' +
149        // 2. Set the producer socket.
150        'PERFETTO_PRODUCER_SOCK_NAME=@traced_producer ' +
151        // 3. Start the applet in the background.
152        `/data/local/tmp/tracebox ${applet} --background`;
153  }
154}
155