• 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 {assertDefined} from 'common/assert_utils';
18import {AdbFileIdentifier, TraceTarget} from 'trace_collection/trace_target';
19import {UiTraceTarget} from 'trace_collection/ui/ui_trace_target';
20import {UserRequest, UserRequestConfig} from 'trace_collection/user_request';
21import {PerfettoSessionModerator} from './perfetto_session_moderator';
22import {TracingSession} from './tracing_session';
23
24const WINSCOPE_EXT = '.winscope';
25const WINSCOPE_EXT_LEGACY = '.pb';
26const WINSCOPE_EXTS = [WINSCOPE_EXT, WINSCOPE_EXT_LEGACY];
27const WINSCOPE_DIR = '/data/misc/wmtrace/';
28
29function makeMatchersWithWinscopeExts(matcher: string) {
30  return WINSCOPE_EXTS.map((ext) => `${matcher}${ext}`);
31}
32
33export class UserRequestParser {
34  private readonly targetPerfettoDsMap = new Map([
35    [UiTraceTarget.SURFACE_FLINGER_TRACE, 'android.surfaceflinger.layers'],
36    [UiTraceTarget.WINDOW_MANAGER_TRACE, 'android.windowmanager'],
37    [UiTraceTarget.IME, 'android.inputmethod'],
38    [UiTraceTarget.TRANSACTIONS, 'android.surfaceflinger.transactions'],
39    [UiTraceTarget.PROTO_LOG, 'android.protolog'],
40    [UiTraceTarget.TRANSITIONS, 'com.android.wm.shell.transition'],
41    [UiTraceTarget.VIEW_CAPTURE, 'android.viewcapture'],
42    [UiTraceTarget.INPUT, 'android.input.inputevent'],
43    [UiTraceTarget.SURFACE_FLINGER_DUMP, 'android.surfaceflinger.layers'],
44  ]);
45
46  private perfettoModerator: PerfettoSessionModerator | undefined;
47  private requests: UserRequest[] | undefined;
48
49  setPerfettoModerator(value: PerfettoSessionModerator) {
50    this.perfettoModerator = value;
51    return this;
52  }
53
54  setRequests(value: UserRequest[]) {
55    this.requests = value;
56    return this;
57  }
58
59  async parse(): Promise<TracingSession[]> {
60    const traceTargets: TraceTarget[] = [];
61    const perfettoSetup: string[] = [];
62    const perfettoModerator = assertDefined(this.perfettoModerator);
63
64    for (const req of assertDefined(this.requests)) {
65      const ds = this.targetPerfettoDsMap.get(req.target);
66      const dataSourceAvailable =
67        ds !== undefined && (await perfettoModerator.isDataSourceAvailable(ds));
68
69      const isPerfetto =
70        !(await perfettoModerator.isTooManySessions()) && dataSourceAvailable;
71
72      if (isPerfetto) {
73        const cmd = this.getPerfettoSetupCommand(req);
74        if (cmd) {
75          perfettoSetup.push(cmd);
76        }
77      } else {
78        const targets = this.getNonPerfettoTargets(req);
79        if (targets) {
80          traceTargets.push(...targets);
81        }
82      }
83    }
84
85    const sessions = traceTargets.map((target) => {
86      return new TracingSession(target);
87    });
88    if (perfettoSetup.length > 0) {
89      sessions.push(perfettoModerator.createTracingSession(perfettoSetup));
90    }
91    return sessions;
92  }
93
94  private getPerfettoSetupCommand(req: UserRequest): string | undefined {
95    switch (req.target) {
96      case UiTraceTarget.SURFACE_FLINGER_TRACE:
97        return this.getSfTracePerfettoSetupCommand(req);
98      case UiTraceTarget.WINDOW_MANAGER_TRACE:
99        return this.getWmTracePerfettoSetupCommand(req);
100      case UiTraceTarget.VIEW_CAPTURE:
101        return this.getVcPerfettoSetupCommand();
102      case UiTraceTarget.TRANSACTIONS:
103        return this.getTransactionsPerfettoSetupCommand();
104      case UiTraceTarget.PROTO_LOG:
105        return this.getProtologPerfettoSetupCommand();
106      case UiTraceTarget.IME:
107        return this.getImePerfettoSetupCommand();
108      case UiTraceTarget.TRANSITIONS:
109        return this.getTransitionsPerfettoSetupCommand();
110      case UiTraceTarget.INPUT:
111        return this.getInputPerfettoSetupCommand();
112      case UiTraceTarget.SURFACE_FLINGER_DUMP:
113        return this.getSfDumpPerfettoSetupCommand();
114      default:
115        return undefined;
116    }
117  }
118
119  private getNonPerfettoTargets(req: UserRequest) {
120    switch (req.target) {
121      case UiTraceTarget.SURFACE_FLINGER_TRACE:
122        return [this.getSfTraceLegacyTarget(req)];
123      case UiTraceTarget.WINDOW_MANAGER_TRACE:
124        return [this.getWmTraceLegacyTarget(req)];
125      case UiTraceTarget.VIEW_CAPTURE:
126        return [this.getVcLegacyTarget()];
127      case UiTraceTarget.TRANSACTIONS:
128        return [this.getTransactionsLegacyTarget()];
129      case UiTraceTarget.PROTO_LOG:
130        return [this.getProtologLegacyTarget()];
131      case UiTraceTarget.IME:
132        return [this.getImeLegacyTarget()];
133      case UiTraceTarget.TRANSITIONS:
134        return [this.getTransitionsLegacyTarget()];
135      case UiTraceTarget.SCREEN_RECORDING:
136        return this.getScreenRecordingTargets(req);
137      case UiTraceTarget.WAYLAND:
138        return [this.getWaylandTarget()];
139      case UiTraceTarget.EVENTLOG:
140        return [this.getEventlogTarget()];
141      case UiTraceTarget.SURFACE_FLINGER_DUMP:
142        return [this.getSfDumpLegacyTarget()];
143      case UiTraceTarget.WINDOW_MANAGER_DUMP:
144        return [this.getWmDumpLegacyTarget()];
145      case UiTraceTarget.SCREENSHOT:
146        return this.getScreenshotTargets(req);
147      default:
148        return undefined;
149    }
150  }
151
152  private getSfTracePerfettoSetupCommand(req: UserRequest) {
153    const flagsMap: {[key: string]: string} = {
154      'input': 'TRACE_FLAG_INPUT',
155      'composition': 'TRACE_FLAG_COMPOSITION',
156      'metadata': 'TRACE_FLAG_EXTRA',
157      'hwc': 'TRACE_FLAG_HWC',
158      'tracebuffers': 'TRACE_FLAG_BUFFERS',
159      'virtualdisplays': 'TRACE_FLAG_VIRTUAL_DISPLAYS',
160    };
161    const {flags} = new SfRequestConfigParser(flagsMap).parse(req.config);
162
163    const spacer = '\n      ';
164    const flagsCmd = flags
165      .map((flag: string) => {
166        return `trace_flags: ${flagsMap[flag]}`;
167      })
168      .join(spacer);
169    return this.perfettoModerator?.createSetupCommand(
170      'android.surfaceflinger.layers',
171      `surfaceflinger_layers_config: {
172      mode: MODE_ACTIVE${flagsCmd.length === 0 ? '' : spacer + flagsCmd}
173    }`,
174    );
175  }
176
177  private getSfTraceLegacyTarget(req: UserRequest) {
178    const flagsMap: {[key: string]: number} = {
179      'input': 1 << 1,
180      'composition': 1 << 2,
181      'metadata': 1 << 3,
182      'hwc': 1 << 4,
183      'tracebuffers': 1 << 5,
184      'virtualdisplays': 1 << 6,
185    };
186    const {flags, selectedConfigs} = new SfRequestConfigParser(flagsMap).parse(
187      req.config,
188    );
189    let flagsValue = 0;
190    for (const flag of flags) {
191      flagsValue |= flagsMap[flag];
192    }
193    const setupCommands = [
194      `su root service call SurfaceFlinger 1029 i32 ${selectedConfigs['sfbuffersize']}$`,
195      `su root service call SurfaceFlinger 1033 i32 ${flagsValue}`,
196    ];
197
198    return new TraceTarget(
199      'SfLegacyTrace',
200      setupCommands,
201      'su root service call SurfaceFlinger 1025 i32 1' +
202        '\necho "SF layers trace (legacy) started."',
203      'su root service call SurfaceFlinger 1025 i32 0 >/dev/null 2>&1' +
204        '\necho "SF layers trace (legacy) stopped."',
205      [
206        new AdbFileIdentifier(
207          WINSCOPE_DIR,
208          makeMatchersWithWinscopeExts('layers_trace'),
209          'layers_trace',
210        ),
211      ],
212    );
213  }
214
215  private getWmTracePerfettoSetupCommand(req: UserRequest) {
216    const selectedConfigs = new WmRequestConfigParser().parse(req.config);
217
218    const logLevelMap: {[key: string]: string} = {
219      'verbose': 'LOG_LEVEL_VERBOSE',
220      'debug': 'LOG_LEVEL_DEBUG',
221      'critical': 'LOG_LEVEL_CRITICAL',
222    };
223
224    const frequencyMap: {[key: string]: string} = {
225      'frame': 'LOG_FREQUENCY_FRAME',
226      'transaction': 'LOG_FREQUENCY_TRANSACTION',
227    };
228
229    const logLevel = logLevelMap[selectedConfigs['tracinglevel']];
230    const logFrequency = frequencyMap[selectedConfigs['tracingtype']];
231    return this.perfettoModerator?.createSetupCommand(
232      'android.windowmanager',
233      `windowmanager_config: {
234      log_level: ${logLevel}
235      log_frequency: ${logFrequency}
236    }`,
237    );
238  }
239
240  private getWmTraceLegacyTarget(req: UserRequest) {
241    const selectedConfigs = new WmRequestConfigParser().parse(req.config);
242
243    const setupCmds = [
244      `su root cmd window tracing ${selectedConfigs['tracingtype']}`,
245      `su root cmd window tracing level ${selectedConfigs['tracinglevel']}`,
246      `su root cmd window tracing size ${selectedConfigs['wmbuffersize']}`,
247    ];
248
249    return new TraceTarget(
250      'WmLegacyTrace',
251      setupCmds,
252      'su root cmd window tracing start' +
253        '\necho "WM trace (legacy) started."',
254      'su root cmd window tracing stop' + '\necho "WM trace (legacy) stopped."',
255      [
256        new AdbFileIdentifier(
257          WINSCOPE_DIR,
258          makeMatchersWithWinscopeExts('wm_trace'),
259          'window_trace',
260        ),
261      ],
262    );
263  }
264
265  private getVcPerfettoSetupCommand() {
266    return this.perfettoModerator?.createSetupCommand('android.viewcapture');
267  }
268
269  private getVcLegacyTarget() {
270    return new TraceTarget(
271      'VcLegacy',
272      [],
273      'su root settings put global view_capture_enabled 1' +
274        '\necho "ViewCapture tracing (legacy) started."',
275      'su root sh -c "cmd launcherapps dump-view-hierarchies >/data/misc/wmtrace/view_capture_trace.zip"' +
276        '\nsu root settings put global view_capture_enabled 0' +
277        '\necho "ViewCapture tracing (legacy) stopped."',
278      [
279        new AdbFileIdentifier(
280          WINSCOPE_DIR,
281          ['view_capture_trace.zip'],
282          'view_capture_trace.zip',
283        ),
284      ],
285    );
286  }
287
288  private getTransactionsPerfettoSetupCommand() {
289    return this.perfettoModerator?.createSetupCommand(
290      'android.surfaceflinger.transactions',
291      `surfaceflinger_transactions_config: {
292      mode: MODE_ACTIVE
293    }`,
294    );
295  }
296
297  private getTransactionsLegacyTarget() {
298    return new TraceTarget(
299      'TransactionsLegacy',
300      [],
301      'su root service call SurfaceFlinger 1041 i32 1' +
302        '\necho "SF transactions trace (legacy) started."',
303      'su root service call SurfaceFlinger 1041 i32 0 >/dev/null 2>&1' +
304        '\necho "SF transactions trace (legacy) stopped."',
305      [
306        new AdbFileIdentifier(
307          WINSCOPE_DIR,
308          makeMatchersWithWinscopeExts('transactions_trace'),
309          'transactions',
310        ),
311      ],
312    );
313  }
314
315  private getProtologPerfettoSetupCommand() {
316    return this.perfettoModerator?.createSetupCommand(
317      'android.protolog',
318      `protolog_config: {
319      tracing_mode: ENABLE_ALL
320    }`,
321    );
322  }
323
324  private getProtologLegacyTarget() {
325    return new TraceTarget(
326      'ProtologLegacy',
327      [],
328      'su root cmd window logging start' +
329        '\necho "ProtoLog (legacy) started."',
330      'su root cmd window logging stop >/dev/null 2>&1' +
331        '\necho "ProtoLog (legacy) stopped."',
332      [
333        new AdbFileIdentifier(
334          WINSCOPE_DIR,
335          makeMatchersWithWinscopeExts('wm_log'),
336          'proto_log',
337        ),
338      ],
339    );
340  }
341
342  private getImePerfettoSetupCommand() {
343    return this.perfettoModerator?.createSetupCommand('android.inputmethod');
344  }
345
346  private getImeLegacyTarget() {
347    return new TraceTarget(
348      'ImeLegacy',
349      [],
350      'su root ime tracing start\necho "IME tracing (legacy) started."',
351      'su root ime tracing stop >/dev/null 2>&1\necho "IME tracing (legacy) stopped."',
352      [
353        new AdbFileIdentifier(
354          WINSCOPE_DIR,
355          makeMatchersWithWinscopeExts('ime_trace_clients'),
356          'ime_trace_clients',
357        ),
358        new AdbFileIdentifier(
359          WINSCOPE_DIR,
360          makeMatchersWithWinscopeExts('ime_trace_service'),
361          'ime_trace_service',
362        ),
363        new AdbFileIdentifier(
364          WINSCOPE_DIR,
365          makeMatchersWithWinscopeExts('ime_trace_managerservice'),
366          'ime_trace_managerservice',
367        ),
368      ],
369    );
370  }
371
372  private getTransitionsPerfettoSetupCommand() {
373    return this.perfettoModerator?.createSetupCommand(
374      'com.android.wm.shell.transition',
375    );
376  }
377
378  private getTransitionsLegacyTarget() {
379    return new TraceTarget(
380      'TransitionsLegacy',
381      [],
382      'su root cmd window shell tracing start ' +
383        '&& su root dumpsys activity service SystemUIService WMShell transitions tracing start' +
384        '\necho "Transition traces (legacy) started."',
385      'su root cmd window shell tracing stop ' +
386        '&& su root dumpsys activity service SystemUIService WMShell transitions tracing stop >/dev/null 2>&1' +
387        '\n echo "Transition traces (legacy) stopped."',
388      [
389        new AdbFileIdentifier(
390          WINSCOPE_DIR,
391          makeMatchersWithWinscopeExts('wm_transition_trace'),
392          'wm_transition_trace',
393        ),
394        new AdbFileIdentifier(
395          WINSCOPE_DIR,
396          makeMatchersWithWinscopeExts('shell_transition_trace'),
397          'shell_transition_trace',
398        ),
399      ],
400    );
401  }
402
403  private getInputPerfettoSetupCommand() {
404    return this.perfettoModerator?.createSetupCommand(
405      'android.input.inputevent',
406      `android_input_event_config {
407      mode: TRACE_MODE_TRACE_ALL
408    }`,
409    );
410  }
411
412  private getScreenRecordingTargets(req: UserRequest) {
413    const {identifiers, showPointerAndTouches} =
414      new ScreenRecordingConfigParser().parse(req.config);
415
416    const val = showPointerAndTouches ? '1' : '0';
417    const setupCmd = `settings put system show_touches ${val} && settings put system pointer_location ${val}`;
418    const stopCmd = `settings put system pointer_location 0 && \
419      settings put system show_touches 0 && \
420      pkill -l SIGINT screenrecord >/dev/null 2>&1`;
421
422    return identifiers.map((id, index) => {
423      const startArgs = id === 'active' ? '' : ` --display-id ${id}`;
424      const startCmd = `
425      screenrecord --bugreport --bit-rate 8M${startArgs} /data/local/tmp/screen_${id}.mp4 & \
426      echo "ScreenRecorder started."
427      `;
428
429      return new TraceTarget(
430        'ScreenRecording' + id,
431        index === 0 ? [setupCmd] : [],
432        startCmd,
433        stopCmd,
434        [
435          new AdbFileIdentifier(
436            `/data/local/tmp/screen_${id}.mp4`,
437            [],
438            `screen_recording_${id}`,
439          ),
440        ],
441        true,
442      );
443    });
444  }
445
446  private getScreenshotTargets(req: UserRequest) {
447    const identifiers = new ScreenshotConfigParser().parse(req.config);
448
449    return identifiers.map((id) => {
450      const startArgs = id === 'active' ? '' : ` -d ${id}`;
451      const startCmd = `screencap -p${startArgs} > /data/local/tmp/screenshot_${id}.png`;
452
453      return new TraceTarget('Screenshot' + id, [], startCmd, '', [
454        new AdbFileIdentifier(
455          `/data/local/tmp/screenshot_${id}.png`,
456          [],
457          `screenshot_${id}.png`,
458        ),
459      ]);
460    });
461  }
462
463  private getWaylandTarget() {
464    return new TraceTarget(
465      'Wayland',
466      [],
467      'su root service call Wayland 26 i32 1 >/dev/null\necho "Wayland trace started."',
468      'su root service call Wayland 26 i32 0 >/dev/null\necho "Wayland trace ended."',
469      [
470        new AdbFileIdentifier(
471          '/data/misc/wltrace',
472          makeMatchersWithWinscopeExts('wl_trace'),
473          'wl_trace',
474        ),
475      ],
476    );
477  }
478
479  private getEventlogTarget() {
480    const startTimeSeconds = (Date.now() / 1000).toString();
481    return new TraceTarget(
482      'Eventlog',
483      [],
484      'rm -f /data/local/tmp/eventlog.winscope' + '\n echo "EventLog started."',
485      'echo "EventLog\\n" > /data/local/tmp/eventlog.winscope ' +
486        `&& su root logcat -b events -v threadtime -v printable -v uid -v nsec -v epoch -b events -t ${startTimeSeconds} >> /data/local/tmp/eventlog.winscope`,
487      [
488        new AdbFileIdentifier(
489          '/data/local/tmp',
490          makeMatchersWithWinscopeExts('eventlog'),
491          'eventlog',
492        ),
493      ],
494    );
495  }
496
497  private getSfDumpPerfettoSetupCommand() {
498    return this.perfettoModerator?.createSetupCommand(
499      'android.surfaceflinger.layers',
500      `surfaceflinger_layers_config: {
501      mode: MODE_DUMP
502      trace_flags: TRACE_FLAG_INPUT
503      trace_flags: TRACE_FLAG_COMPOSITION
504      trace_flags: TRACE_FLAG_HWC
505      trace_flags: TRACE_FLAG_BUFFERS
506      trace_flags: TRACE_FLAG_VIRTUAL_DISPLAYS
507    }`,
508    );
509  }
510
511  private getSfDumpLegacyTarget() {
512    return new TraceTarget(
513      'SfDumpLegacy',
514      [],
515      `su root dumpsys SurfaceFlinger --proto > /data/local/tmp/sf_dump${WINSCOPE_EXT}`,
516      '',
517      [
518        new AdbFileIdentifier(
519          `/data/local/tmp/sf_dump${WINSCOPE_EXT}`,
520          [],
521          'layers_dump',
522        ),
523      ],
524    );
525  }
526
527  private getWmDumpLegacyTarget() {
528    return new TraceTarget(
529      'WmDumpLegacy',
530      [],
531      `su root dumpsys window --proto > /data/local/tmp/wm_dump${WINSCOPE_EXT}`,
532      '',
533      [
534        new AdbFileIdentifier(
535          `/data/local/tmp/wm_dump${WINSCOPE_EXT}`,
536          [],
537          'window_dump',
538        ),
539      ],
540    );
541  }
542}
543
544class SfRequestConfigParser {
545  private readonly configs: {[key: string]: string} = {
546    'sfbuffersize': '16000',
547  };
548
549  constructor(private readonly flagsMap: object) {}
550
551  parse(req: UserRequestConfig[]) {
552    const flags: string[] = [];
553    req.forEach((config) => {
554      if (config.key in this.flagsMap) {
555        flags.push(config.key);
556      } else if (
557        config.key in this.configs &&
558        typeof config.value === 'string'
559      ) {
560        this.configs[config.key] = config.value ?? '';
561      }
562    });
563    return {flags, selectedConfigs: this.configs};
564  }
565}
566
567class WmRequestConfigParser {
568  private readonly configs: {[key: string]: string} = {
569    'wmbuffersize': '16000',
570    'tracinglevel': 'debug',
571    'tracingtype': 'frame',
572  };
573
574  parse(req: UserRequestConfig[]) {
575    req.forEach((config) => {
576      if (config.key in this.configs && typeof config.value === 'string') {
577        this.configs[config.key] = config.value ?? this.configs[config.key];
578      }
579    });
580    return this.configs;
581  }
582}
583
584abstract class MediaBasedConfigParser {
585  protected getIdentifiers(req: UserRequestConfig[]): string[] {
586    const identifiers = ['active'];
587    const config = req.find((c) => c.key === 'displays');
588    if (config?.value) {
589      if (typeof config.value === 'string') {
590        const display = this.parseDisplayId(config.value);
591        if (display) return [display];
592      } else {
593        const displays = config.value.map((v) => this.parseDisplayId(v));
594        if (displays.length > 0) return displays;
595      }
596    }
597    return identifiers;
598  }
599
600  private parseDisplayId(displayValue: string): string {
601    // display value comes in form '"<displayName>" <displayId> <otherInfo>'
602    // where '"<displayName>"' is optional
603    if (displayValue[0] === '"') {
604      displayValue = displayValue.split('"')[2].trim();
605    }
606    return displayValue.split(' ')[0];
607  }
608}
609
610class ScreenshotConfigParser extends MediaBasedConfigParser {
611  parse(req: UserRequestConfig[]) {
612    return this.getIdentifiers(req);
613  }
614}
615
616class ScreenRecordingConfigParser extends MediaBasedConfigParser {
617  parse(req: UserRequestConfig[]) {
618    return {
619      identifiers: this.getIdentifiers(req),
620      showPointerAndTouches: req.find((c) => c.key === 'pointer_and_touches'),
621    };
622  }
623}
624