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