1/* 2 * Copyright (C) 2025 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import {FileUtils} from 'common/file_utils'; 18import {TimeUtils} from 'common/time/time_utils'; 19import {UserNotifier} from 'common/user_notifier'; 20import {ProgressListener} from 'messaging/progress_listener'; 21import {ProxyTracingWarnings} from 'messaging/user_warnings'; 22import {AdbDeviceConnection} from 'trace_collection/adb/adb_device_connection'; 23import {AdbHostConnection} from 'trace_collection/adb/adb_host_connection'; 24import {AdbConnectionType} from 'trace_collection/adb_connection_type'; 25import {ConnectionStateListener} from 'trace_collection/connection_state_listener'; 26import {MockAdbHostConnection} from 'trace_collection/mock/mock_adb_host_connection'; 27import {UserRequest} from 'trace_collection/user_request'; 28import {WdpHostConnection} from 'trace_collection/wdp/wdp_host_connection'; 29import {WinscopeProxyHostConnection} from 'trace_collection/winscope_proxy/winscope_proxy_host_connection'; 30import {PerfettoSessionModerator} from './perfetto_session_moderator'; 31import {TracingSession} from './tracing_session'; 32import {UserRequestParser} from './user_request_parser'; 33import {WINSCOPE_BACKUP_DIR} from './winscope_backup_dir'; 34 35export class TraceCollectionController { 36 private activeTracingSessions: TracingSession[] = []; 37 private host: AdbHostConnection; 38 39 constructor( 40 connectionType: string, 41 private listener: ConnectionStateListener & ProgressListener, 42 ) { 43 if (connectionType === AdbConnectionType.WDP) { 44 this.host = new WdpHostConnection(listener); 45 } else if (connectionType === AdbConnectionType.MOCK) { 46 this.host = new MockAdbHostConnection(listener); 47 } else { 48 this.host = new WinscopeProxyHostConnection(listener); 49 } 50 } 51 52 getConnectionType(): AdbConnectionType { 53 return this.host.connectionType; 54 } 55 56 async restartConnection(): Promise<void> { 57 await this.host.restart(); 58 } 59 60 setSecurityToken(token: string) { 61 this.host.setSecurityToken(token); 62 } 63 64 getDevices(): AdbDeviceConnection[] { 65 return this.host.getDevices(); 66 } 67 68 cancelDeviceRequests() { 69 this.host.cancelDeviceRequests(); 70 } 71 72 async requestDevices() { 73 this.host.requestDevices(); 74 } 75 76 async onDestroy(device: AdbDeviceConnection) { 77 for (const session of this.activeTracingSessions) { 78 await session.onDestroy(device); 79 } 80 this.host.onDestroy(); 81 } 82 83 async startTrace( 84 device: AdbDeviceConnection, 85 requestedTraces: UserRequest[], 86 ): Promise<void> { 87 const perfettoModerator = new PerfettoSessionModerator(device, false); 88 const sessions = await this.getSessions(perfettoModerator, requestedTraces); 89 this.activeTracingSessions = []; 90 if (sessions.length === 0) { 91 return; 92 } 93 await this.prepareDevice(device, perfettoModerator); 94 for (const session of sessions) { 95 await session.start(device); 96 this.activeTracingSessions.push(session); 97 } 98 // TODO(b/330118129): identify source of additional start latency that affects some traces 99 await TimeUtils.sleepMs(1000); // 1s timeout ensures SR fully started 100 } 101 102 async endTrace(device: AdbDeviceConnection) { 103 for (const [index, session] of this.activeTracingSessions.entries()) { 104 await session.stop(device); 105 this.listener.onProgressUpdate( 106 'Ending trace...', 107 (100 * index) / this.activeTracingSessions.length, 108 ); 109 } 110 await this.moveFiles(device, this.activeTracingSessions); 111 this.activeTracingSessions = []; 112 this.listener.onOperationFinished(true); 113 } 114 115 async dumpState( 116 device: AdbDeviceConnection, 117 requestedDumps: UserRequest[], 118 ): Promise<void> { 119 const perfettoModerator = new PerfettoSessionModerator(device, true); 120 const sessions = await this.getSessions(perfettoModerator, requestedDumps); 121 if (sessions.length === 0) { 122 return; 123 } 124 await this.prepareDevice(device, perfettoModerator); 125 for (const [index, session] of sessions.entries()) { 126 await session.dump(device); 127 this.listener.onProgressUpdate( 128 'Dumping state...', 129 (100 * index) / this.activeTracingSessions.length, 130 ); 131 } 132 await this.moveFiles(device, sessions); 133 this.listener.onOperationFinished(true); 134 } 135 136 async fetchLastSessionData(device: AdbDeviceConnection): Promise<File[]> { 137 const adbData: File[] = []; 138 const paths = await device.findFiles(`${WINSCOPE_BACKUP_DIR}*`, []); 139 for (const [index, filepath] of paths.entries()) { 140 console.debug(`Fetching file ${filepath} from device`); 141 const data = await device.pullFile(filepath); 142 const filename = FileUtils.removeDirFromFileName(filepath); 143 adbData.push(new File([data], filename)); 144 this.listener.onProgressUpdate( 145 'Fetching files...', 146 (100 * index) / paths.length, 147 ); 148 console.debug(`Fetched ${filepath}`); 149 } 150 this.listener.onOperationFinished(true); 151 return adbData; 152 } 153 154 private async getSessions( 155 perfettoModerator: PerfettoSessionModerator, 156 req: UserRequest[], 157 ): Promise<TracingSession[]> { 158 const sessions = await new UserRequestParser() 159 .setPerfettoModerator(perfettoModerator) 160 .setRequests(req) 161 .parse(); 162 163 if (sessions.length === 0) { 164 UserNotifier.add( 165 new ProxyTracingWarnings([ 166 'None of the requested targets are available on this device.', 167 ]), 168 ).notify(); 169 await this.host.restart(); 170 return []; 171 } 172 return sessions; 173 } 174 175 private async prepareDevice( 176 device: AdbDeviceConnection, 177 perfettoModerator: PerfettoSessionModerator, 178 ) { 179 await perfettoModerator.tryStopCurrentPerfettoSession(); 180 await perfettoModerator.clearPreviousConfigFiles(); 181 console.debug('Clearing previous tracing session files from device'); 182 await device.runShellCommand(`su root rm -rf ${WINSCOPE_BACKUP_DIR}`); 183 await device.runShellCommand(`su root mkdir ${WINSCOPE_BACKUP_DIR}`); 184 console.debug('Cleared previous tracing session files from device'); 185 } 186 187 private async moveFiles( 188 device: AdbDeviceConnection, 189 sessions: TracingSession[], 190 ) { 191 for (const [index, session] of sessions.entries()) { 192 await session.moveFiles(device); 193 this.listener.onProgressUpdate( 194 'Moving files...', 195 (100 * index) / sessions.length, 196 ); 197 } 198 } 199} 200