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