blob: b39266c841dca7b5803a1995d066e542ed0740ee [file] [log] [blame]
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {fetchWithTimeout} from '../../../base/http_utils';
import {VERSION} from '../../../gen/perfetto_version';
import {AdbConnectionImpl} from '../adb_connection_impl';
import {
DataSource,
OnTargetChangeCallback,
RecordingTargetV2,
TargetInfo,
TracingSession,
TracingSessionListener,
} from '../recording_interfaces_v2';
import {
CUSTOM_TRACED_CONSUMER_SOCKET_PATH,
DEFAULT_TRACED_CONSUMER_SOCKET_PATH,
TRACEBOX_DEVICE_PATH,
TRACEBOX_FETCH_TIMEOUT,
} from '../recording_utils';
import {TracedTracingSession} from '../traced_tracing_session';
export abstract class AndroidTarget implements RecordingTargetV2 {
private consumerSocketPath = DEFAULT_TRACED_CONSUMER_SOCKET_PATH;
protected androidApiLevel?: number;
protected dataSources?: DataSource[];
protected constructor(
private adbConnection: AdbConnectionImpl,
private onTargetChange: OnTargetChangeCallback) {}
abstract getInfo(): TargetInfo;
// This is called when a usb USBConnectionEvent of type 'disconnect' event is
// emitted. This event is emitted when the USB connection is lost (example:
// when the user unplugged the connecting cable).
async disconnect(disconnectMessage?: string): Promise<void> {
await this.adbConnection.disconnect(disconnectMessage);
}
// Starts a tracing session in order to fetch information such as apiLevel
// and dataSources from the device. Then, it cancels the session.
async fetchTargetInfo(listener: TracingSessionListener): Promise<void> {
const tracingSession = await this.createTracingSession(listener);
tracingSession.cancel();
}
// We do not support long tracing on Android.
canCreateTracingSession(recordingMode: string): boolean {
return recordingMode !== 'LONG_TRACE';
}
async createTracingSession(tracingSessionListener: TracingSessionListener):
Promise<TracingSession> {
this.adbConnection.onStatus = tracingSessionListener.onStatus;
this.adbConnection.onDisconnect = tracingSessionListener.onDisconnect;
if (!this.androidApiLevel) {
// 1. Fetch the API version from the device.
const version = await this.adbConnection.shellAndGetOutput(
'getprop ro.build.version.sdk');
this.androidApiLevel = Number(version);
this.onTargetChange();
// 2. For older OS versions we push the tracebox binary.
if (this.androidApiLevel < 29) {
await this.pushTracebox();
this.consumerSocketPath = CUSTOM_TRACED_CONSUMER_SOCKET_PATH;
await this.adbConnection.shellAndWaitCompletion(
this.composeTraceboxCommand('traced'));
await this.adbConnection.shellAndWaitCompletion(
this.composeTraceboxCommand('traced_probes'));
}
}
const adbStream =
await this.adbConnection.connectSocket(this.consumerSocketPath);
// 3. Start a tracing session.
const tracingSession =
new TracedTracingSession(adbStream, tracingSessionListener);
await tracingSession.initConnection();
if (!this.dataSources) {
// 4. Fetch dataSources from QueryServiceState.
this.dataSources = await tracingSession.queryServiceState();
this.onTargetChange();
}
return tracingSession;
}
async pushTracebox() {
const arch = await this.fetchArchitecture();
const shortVersion = VERSION.split('-')[0];
const requestUrl =
`https://commondatastorage.googleapis.com/perfetto-luci-artifacts/${
shortVersion}/${arch}/tracebox`;
const fetchResponse = await fetchWithTimeout(
requestUrl, {method: 'get'}, TRACEBOX_FETCH_TIMEOUT);
const traceboxBin = await fetchResponse.arrayBuffer();
await this.adbConnection.push(
new Uint8Array(traceboxBin), TRACEBOX_DEVICE_PATH);
// We explicitly set the tracebox permissions because adb does not reliably
// set permissions when uploading the binary.
await this.adbConnection.shellAndWaitCompletion(
`chmod 755 ${TRACEBOX_DEVICE_PATH}`);
}
async fetchArchitecture() {
const abiList = await this.adbConnection.shellAndGetOutput(
'getprop ro.vendor.product.cpu.abilist');
// If multiple ABIs are allowed, the 64bit ones should have higher priority.
if (abiList.includes('arm64-v8a')) {
return 'android-arm64';
} else if (abiList.includes('x86')) {
return 'android-x86';
} else if (abiList.includes('armeabi-v7a') || abiList.includes('armeabi')) {
return 'android-arm';
} else if (abiList.includes('x86_64')) {
return 'android-x64';
}
// Most devices have arm64 architectures, so we should return this if
// nothing else is found.
return 'android-arm64';
}
canConnectWithoutContention(): Promise<boolean> {
return this.adbConnection.canConnectWithoutContention();
}
composeTraceboxCommand(applet: string) {
// 1. Set the consumer socket.
return 'PERFETTO_CONSUMER_SOCK_NAME=@traced_consumer ' +
// 2. Set the producer socket.
'PERFETTO_PRODUCER_SOCK_NAME=@traced_producer ' +
// 3. Start the applet in the background.
`/data/local/tmp/tracebox ${applet} --background`;
}
}