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