• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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