• 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 {UserNotifier} from 'common/user_notifier';
18import {ProxyTracingWarnings} from 'messaging/user_warnings';
19import {AdbDeviceConnection} from 'trace_collection/adb/adb_device_connection';
20import {AdbFileIdentifier, TraceTarget} from 'trace_collection/trace_target';
21import {TracingSession} from './tracing_session';
22
23// Perfetto files
24export const PERFETTO_TRACE_FILE =
25  '/data/misc/perfetto-traces/winscope-proxy-trace.perfetto-trace';
26export const PERFETTO_DUMP_FILE =
27  '/data/misc/perfetto-traces/winscope-proxy-dump.perfetto-trace';
28export const PERFETTO_TRACE_CONFIG_FILE =
29  '/data/misc/perfetto-configs/winscope-proxy-trace.conf';
30export const PERFETTO_DUMP_CONFIG_FILE =
31  '/data/misc/perfetto-configs/winscope-proxy-dump.conf';
32export const PERFETTO_UNIQUE_SESSION_NAME = 'winscope proxy perfetto tracing';
33
34// Perfetto query helpers
35export const PERFETTO_TRACING_SESSIONS_START = `TRACING SESSIONS:
36
37ID      UID     STATE      BUF (#) KB   DUR (s)   #DS  STARTED  NAME
38===     ===     =====      ==========   =======   ===  =======  ====\n`;
39export const PERFETTO_TRACING_SESSIONS_END =
40  '\nNOTE: Some tracing sessions are not reported in the list above.';
41
42export class PerfettoSessionModerator {
43  private queryResult: string | undefined;
44  private prevSessionActive = false;
45  private concurrentSessions: number | undefined;
46  private configFilepath: string;
47
48  constructor(private device: AdbDeviceConnection, private isDump: boolean) {
49    this.configFilepath = isDump
50      ? PERFETTO_DUMP_CONFIG_FILE
51      : PERFETTO_TRACE_CONFIG_FILE;
52  }
53
54  async clearPreviousConfigFiles() {
55    console.debug('Clearing perfetto config file for previous tracing session');
56    await this.device.runShellCommand(`su root rm -f ${this.configFilepath}`);
57    console.debug('Cleared perfetto config file for previous tracing session');
58  }
59
60  async isTooManySessions() {
61    if (this.concurrentSessions === undefined) {
62      this.concurrentSessions = await this.getConcurrentSessions();
63    }
64    const tooManyPerfettoSessions = this.concurrentSessions >= 5;
65    if (tooManyPerfettoSessions) {
66      const warning =
67        'Limit of 5 Perfetto sessions reached on device. Will attempt to collect legacy traces.';
68      UserNotifier.add(new ProxyTracingWarnings([warning])).notify();
69    }
70    return tooManyPerfettoSessions;
71  }
72
73  async tryStopCurrentPerfettoSession() {
74    if (this.concurrentSessions === undefined) {
75      this.concurrentSessions = await this.getConcurrentSessions();
76    }
77    if (!this.prevSessionActive) {
78      return;
79    }
80    console.debug('Stopping already-running winscope perfetto session.');
81    await this.device?.runShellCommand(
82      'perfetto --attach=WINSCOPE-PROXY-TRACING-SESSION --stop',
83    );
84    this.prevSessionActive = false;
85    console.debug('Stopped already-running winscope perfetto session.');
86  }
87
88  async isDataSourceAvailable(ds: string): Promise<boolean> {
89    const queryResult = await this.getQueryResult();
90    return queryResult.includes(ds);
91  }
92
93  createTracingSession(setupCommands: string[]): TracingSession {
94    if (this.isDump) {
95      return new TracingSession(this.makePerfettoDumpTarget(setupCommands));
96    } else {
97      return new TracingSession(this.makePerfettoTraceTarget(setupCommands));
98    }
99  }
100
101  createSetupCommand(ds: string, config?: string): string {
102    const spacer = '\n    ';
103    return `cat << EOF >> ${this.configFilepath}
104data_sources: {
105  config {
106    name: "${ds}"${config ? spacer + config : ''}
107  }
108}
109EOF`;
110  }
111
112  private makePerfettoDumpTarget(setupCommands: string[]) {
113    return new TraceTarget(
114      'PerfettoDump',
115      setupCommands,
116      `cat << EOF >> ${PERFETTO_DUMP_CONFIG_FILE}
117buffers: {
118  size_kb: 500000
119  fill_policy: RING_BUFFER
120}
121duration_ms: 1
122EOF
123rm -f ${PERFETTO_DUMP_FILE}
124perfetto --out ${PERFETTO_DUMP_FILE} --txt --config ${PERFETTO_DUMP_CONFIG_FILE}
125echo 'Dumped perfetto'`,
126      '',
127      [new AdbFileIdentifier(PERFETTO_DUMP_FILE, [], 'dump.perfetto-trace')],
128    );
129  }
130
131  private makePerfettoTraceTarget(setupCommands: string[]) {
132    return new TraceTarget(
133      'PerfettoTrace',
134      setupCommands,
135      `cat << EOF >> ${PERFETTO_TRACE_CONFIG_FILE}
136buffers: {
137  size_kb: 500000
138  fill_policy: RING_BUFFER
139}
140duration_ms: 0
141file_write_period_ms: 999999999
142write_into_file: true
143unique_session_name: "${PERFETTO_UNIQUE_SESSION_NAME}"
144EOF
145rm -f ${PERFETTO_TRACE_FILE}
146perfetto --out ${PERFETTO_TRACE_FILE} --txt --config ${PERFETTO_TRACE_CONFIG_FILE} --detach=WINSCOPE-PROXY-TRACING-SESSION
147echo 'Perfetto trace started.'`,
148      `perfetto --attach=WINSCOPE-PROXY-TRACING-SESSION --stop
149echo 'Perfetto trace stopped.'`,
150      [new AdbFileIdentifier(PERFETTO_TRACE_FILE, [], 'trace.perfetto-trace')],
151    );
152  }
153
154  private async getConcurrentSessions(): Promise<number> {
155    const queryRes = await this.getQueryResult();
156    const startIndex = queryRes.indexOf(PERFETTO_TRACING_SESSIONS_START);
157    let numberOfConcurrentSessions = 0;
158    if (startIndex !== -1) {
159      let concurrentSessions = queryRes.slice(startIndex);
160      console.debug(`Concurrent sessions:\n${concurrentSessions}`);
161      concurrentSessions = concurrentSessions.slice(
162        PERFETTO_TRACING_SESSIONS_START.length,
163      );
164
165      const endIndex = concurrentSessions.indexOf(
166        PERFETTO_TRACING_SESSIONS_END,
167      );
168      if (endIndex !== -1) {
169        concurrentSessions = concurrentSessions.slice(0, endIndex);
170      }
171
172      numberOfConcurrentSessions =
173        concurrentSessions.length > 0
174          ? concurrentSessions.trim().split('\n').length
175          : 0;
176
177      if (concurrentSessions.includes(PERFETTO_UNIQUE_SESSION_NAME)) {
178        this.prevSessionActive = true;
179        numberOfConcurrentSessions -= 1;
180      }
181    }
182    return numberOfConcurrentSessions;
183  }
184
185  private async getQueryResult() {
186    if (this.queryResult === undefined) {
187      this.queryResult = await this.device.runShellCommand('perfetto --query');
188    }
189    return this.queryResult;
190  }
191}
192