Skip to content

Winscope采集链

追踪 Winscope 中的用户选择如何变成 Perfetto 或 legacy trace 会话,并解释停止、超时、文件搬运和加载边界。

基于android-17.0.0_r1
AndroidWinscopePerfettoWindowManagerSurfaceFlinger

Winscope采集链 ​

Winscope 的核心问题不是“在哪个页面点击采集”,而是一次用户选择如何变成设备上的采集会话,最后变成浏览器可以加载的文件。Android 17 的 platform/development project 中,tools/winscope 将这条链拆成请求解析、会话编排、设备命令、文件搬运和前端加载几个 owner;Perfetto 可用时优先走统一 data source,条件不满足时才为部分目标创建 legacy session。

本文所说的 Winscope 控制器代码属于 AOSP 的 platform/development project;它与 frameworks/base、system/core 是不同的源码项目。本文只追踪这个 project 中的控制器和 ADB 适配层,不把设备侧的 traced、WindowManager 或 SurfaceFlinger producer 当作同一份源码来解释。

本文面向已经读过 Perfetto追踪、ADB架构 和 ADB文件传输 的读者。需要理解 TypeScript 类、Promise、ADB shell 和 Perfetto data source;不展开 external/perfetto 服务端、浏览器 Angular 视图渲染、WMS/SF proto 字段含义。读完后,读者应能从 TraceCollectionController.startTrace() 找到一个请求的实际 target,解释它何时走 Perfetto、何时退回 legacy,以及 trace 为什么会被移动到备份目录后才拉回主机。

1. 采集边界 ​

Winscope 内部有四类对象,不能混为“采集器”:

对象主要源码持有内容下游消费者
UserRequesttools/winscope/src/trace_collection/user_request.tsUI 选择的 target 与配置UserRequestParser
TraceTargettools/winscope/src/trace_collection/trace_target.tssetup/start/stop 命令和文件匹配规则TracingSession
TracingSessiontools/winscope/src/trace_collection/controller/tracing_session.ts一个 target 的运行状态AdbDeviceConnection
TraceCollectionControllertools/winscope/src/trace_collection/controller/trace_collection_controller.ts活跃会话列表和阶段顺序UI 进度、下载文件

external/perfetto 不在本 checkout 的证据范围内。因此本文可以证明 Winscope 如何构造 Perfetto 配置、调用设备命令并收集文件,但不能据此声称 traced 如何调度共享内存,也不能把 Winscope 的 parser 实现冒充 Trace Processor 实现。

2. 请求解析 ​

2.1 目标映射 ​

UserRequest 只有 target 和配置树;它不携带 shell 命令。UserRequestParser 先用 targetPerfettoDsMap 将目标映射到 data source,例如 SURFACE_FLINGER_TRACE 对应 android.surfaceflinger.layers,WINDOW_MANAGER_TRACE 对应 android.windowmanager,INPUT 对应 android.input.inputevent。

源码文件:development/tools/winscope/src/trace_collection/controller/user_request_parser.ts

ts
// development/tools/winscope/src/trace_collection/controller/user_request_parser.ts
private readonly targetPerfettoDsMap = new Map([
  [UiTraceTarget.SURFACE_FLINGER_TRACE, 'android.surfaceflinger.layers'],
  [UiTraceTarget.WINDOW_MANAGER_TRACE, 'android.windowmanager'],
  [UiTraceTarget.TRANSACTIONS, 'android.surfaceflinger.transactions'],
  [UiTraceTarget.INPUT, 'android.input.inputevent'],
  [UiTraceTarget.WINDOW_MANAGER_DUMP, 'android.windowmanager'],
  [UiTraceTarget.SURFACE_FLINGER_DUMP, 'android.surfaceflinger.layers'],
]);

这张表只说明“请求可能使用哪个 data source”,不说明设备一定支持它。支持性由 PerfettoSessionModerator.isDataSourceAvailable() 查询设备上的 perfetto --query 决定。

2.2 Fallback条件 ​

parse() 对每个请求独立作出选择:data source 存在且当前 Perfetto 会话数未达到 5,才把配置累积到同一个 Perfetto session;否则调用 getNonPerfettoTargets() 创建 legacy target。需要注意,多个可用 Perfetto 请求会合并为一个 session,而多个 legacy 请求仍可能产生多个 TracingSession。

源码文件:development/tools/winscope/src/trace_collection/controller/user_request_parser.ts

相关函数/类型:UserRequestParser.parse

ts
// user_request_parser.ts
const dataSourceAvailable =
  ds !== undefined && (await perfettoModerator.isDataSourceAvailable(ds));
const isPerfetto =
  !(await perfettoModerator.isTooManySessions()) && dataSourceAvailable;

if (isPerfetto) {
  const configFileDs = this.getPerfettoDataSourceConfig(req);
  if (configFileDs) perfettoConfigDataSources.push(configFileDs);
} else {
  const targets = this.getNonPerfettoTargets(req);
  if (targets) traceTargets.push(...targets);
}

所以“请求回退到 legacy”可能由两个不同原因触发:设备没有对应 data source,或者设备上的 Perfetto 并发会话达到 5 个。源码只对第二种情况发出“将尝试 legacy traces”的通知;文章不能把所有回退都解释成权限错误。

3. 会话编排 ​

3.1 启动顺序 ​

TraceCollectionController.startTrace() 负责阶段顺序,而不负责具体 target 的命令细节:创建 moderator,解析请求,清理旧配置和备份目录,逐个启动 session,把成功启动的 session 放入 activeTracingSessions,最后额外等待 1 秒以覆盖 screen recording 的启动延迟。

源码文件:development/tools/winscope/src/trace_collection/controller/trace_collection_controller.ts

相关函数/类型:startTrace

ts
// trace_collection_controller.ts
async startTrace(device: AdbDeviceConnection, requestedTraces: UserRequest[]) {
  const perfettoModerator = new PerfettoSessionModerator(device, false);
  const sessions = await this.getSessions(perfettoModerator, requestedTraces);
  this.activeTracingSessions = [];
  if (sessions.length === 0) return;
  await this.prepareDevice(device, perfettoModerator);
  for (const session of sessions) {
    await session.start(device);
    this.activeTracingSessions.push(session);
  }
  await new Timer(1000).sleepMs();
}

这里有两个重要的生效时机:备份目录在启动前被清空并重新创建;只有 session.start() 成功返回后,session 才加入停止列表。若第二个 session 启动失败,后续 endTrace() 只会看到已经加入列表的会话,调用方需要结合错误状态判断设备上是否留下了部分采集。

3.2 Target状态 ​

TracingSession 用一个布尔值记录 target 是否由本对象启动。start() 先执行 setup,再调用设备的 startTrace(),成功后才将 isTracing 设为 true;stop() 在未启动时直接返回,已启动时调用 endTrace() 后清除状态。

源码文件:development/tools/winscope/src/trace_collection/controller/tracing_session.ts

相关函数/类型:TracingSession

ts
// tracing_session.ts
async start(device: AdbDeviceConnection) {
  await this.setup(device);
  await device.startTrace(this.target);
  this.isTracing = true;
}

async stop(device: AdbDeviceConnection) {
  if (!this.isTracing) return;
  await device.endTrace(this.target);
  this.isTracing = false;
}

onDestroy() 当前调用 this.stop(device) 但没有 await。这意味着页面销毁路径发起的是异步停止请求,不能把函数返回当成设备已经停止的证明;这是源码明确暴露的生命周期边界。

4. Perfetto会话 ​

4.1 配置生成 ​

PerfettoSessionModerator.makePerfettoTraceTarget() 把 data source 片段包装成设备端配置文件。除了用户选择的 data source,还会强制加入 linux.process_stats 和 linux.ftrace,配置一个 500000 KB 的 ring buffer,开启写文件,并使用固定的 unique_session_name 便于后续 attach/stop。

源码文件:development/tools/winscope/src/trace_collection/controller/perfetto_session_moderator.ts

相关函数/类型:makePerfettoTraceTarget

ts
// perfetto_session_moderator.ts
data_sources {
  config {
    name: "linux.process_stats"
    target_buffer: 0
    process_stats_config {
      scan_all_processes_on_start: true
    }
  }
}
data_sources: {
  config {
    name: "linux.ftrace"
    ftrace_config {
      ftrace_events: "ftrace/print"
      ftrace_events: "task/task_newtask"
      atrace_categories: "ss"
      atrace_categories: "wm"
    }
  }
}
buffers: {
  size_kb: 500000
  fill_policy: RING_BUFFER
}
write_into_file: true
unique_session_name: "winscope proxy perfetto tracing"

duration_ms: 0 表示由控制器显式停止,而不是到固定时长自动结束。endTrace() 最终执行 perfetto --attach=WINSCOPE-PROXY-TRACING-SESSION --stop,所以名称和启动命令必须成对出现。

4.2 并发保护 ​

启动前 tryStopCurrentPerfettoSession() 会查询现有 session;如果发现同名的 Winscope session,先执行 attach stop。isTooManySessions() 将并发数 >= 5 视为过多并触发 legacy fallback。这个阈值是 Winscope 控制器的策略,不是 Perfetto 服务端的通用上限。

5. Legacy路径 ​

5.1 WMS目标 ​

当 android.windowmanager 不可用时,getWmTraceLegacyTarget() 仍然构造一个 target。setup 阶段设置 tracing type、level 和 buffer size;start 阶段执行 cmd window tracing start;stop 阶段执行 cmd window tracing stop;文件匹配器在 /data/misc/wmtrace/ 中寻找 wm_trace.winscope 或旧的 .pb 文件。

源码文件:development/tools/winscope/src/trace_collection/controller/user_request_parser.ts

相关函数/类型:getWmTraceLegacyTarget

ts
// user_request_parser.ts
const setupCmds = [
  `su root cmd window tracing ${selectedConfigs['tracingtype']}`,
  `su root cmd window tracing level ${selectedConfigs['tracinglevel']}`,
  `su root cmd window tracing size ${selectedConfigs['wmbuffersize']}`,
];

return new TraceTarget(
  'WmLegacyTrace',
  setupCmds,
  'su root cmd window tracing start',
  'su root cmd window tracing stop',
  [new AdbFileIdentifier('/data/misc/wmtrace/', ['wm_trace.winscope', 'wm_trace.pb'], 'wm_trace')],
);

legacy target 并不等于“旧 Android 行为”:它是 Android 17 Winscope 在 data source 不可用时仍保留的兼容采集路径。其文件内容和 WMS 服务内部写入逻辑属于另一个源码边界。

5.2 SF目标 ​

SurfaceFlinger legacy target 同样保留 setup/start/stop/file 四元组,但 setup 使用 service call SurfaceFlinger 设置 buffer 与 trace flags。这里的整数码和 flag 位图来自 Winscope 当前代码,不应从旧文章或记忆中推导;如果版本变化,应以对应 tag 的 user_request_parser.ts 为准。

6. 文件搬运 ​

6.1 设备暂存 ​

prepareDevice() 在每次采集前删除 /data/local/tmp/last_winscope_tracing_session/,随后重新创建目录。采集结束后,TracingSession.moveFiles() 按 target 的 fileIdentifiers 搜索文件,并执行“存在则复制、复制成功后删除原文件”的命令。

源码文件:development/tools/winscope/src/trace_collection/controller/tracing_session.ts

相关函数/类型:moveFiles

ts
// tracing_session.ts
const isRoot = await device.checkRoot();
const maybeRootParam = isRoot ? 'su root ' : '';
const output = await device.runShellCommand(
  maybeRootParam +
    `[ -f ${filepath} ] && ` +
    maybeRootParam +
    `cp -p ${filepath} ${WINSCOPE_BACKUP_DIR}${file.destName} && ` +
    maybeRootParam +
    `rm -f ${filepath}`,
);

这个顺序解释了一个常见现象:原始 /data/misc/... 文件消失并不表示 trace 丢失,可能只是已经被搬到备份目录。反过来,复制命令失败时 catch 只记录 warning,控制器仍会继续处理其他 session;因此“采集停止成功”不能推出“每个文件都已搬运成功”。

6.2 主机拉取 ​

fetchLastSessionData() 搜索备份目录,逐个调用 device.pullFile(),再用 removeDirFromFileName() 生成浏览器端 File 对象。Winscope proxy 连接中,pullFile() 通过 HTTP FETCH endpoint 取得 JSON,将 base64 内容解码成 Uint8Array;失败时返回空数组并通知 UI。

源码文件:development/tools/winscope/src/trace_collection/winscope_proxy/winscope_proxy_device_connection.ts

相关函数/类型:pullFile

ts
// winscope_proxy_device_connection.ts
override async pullFile(filepath: string): Promise<Uint8Array> {
  return await new Promise<Uint8Array>((resolve) => {
    getFromProxy(
      `${Endpoint.FETCH}${this.encodedId}/${filepath}`,
      this.securityHeader,
      (response) => resolve(this.onSuccessFetchFile(response, filepath)),
      async (newState, errorText) => {
        this.setState(newState, errorText);
        resolve(Uint8Array.from([]));
      },
      'arraybuffer',
    );
  });
}

7. 取消与失败 ​

7.1 设备断开 ​

AdbHostConnection.onDestroy() 先销毁 host,再调用每个设备的 onDestroy()。Winscope proxy device 的 onDestroy() 将 isTracing 设为 false 并清除 keep-alive 定时器;正在运行的 trace 是否已经被设备端 stop,取决于 proxy 的后续请求和 TracingSession.onDestroy() 的异步调用,不能把浏览器对象销毁理解为同步清理完成。

7.2 保活超时 ​

代理设备启动 target 后会每秒访问 STATUS endpoint。若响应不是 True,它清除该 target 的定时器并报告 TRACE_TIMEOUT;若仍在采集且没有现存 worker,则重新建立 1 秒间隔。这条状态只证明 Winscope proxy 对目标的保活检查结果,不证明设备端文件完整。

源码文件:development/tools/winscope/src/trace_collection/winscope_proxy/winscope_proxy_device_connection.ts

相关函数/类型:startTrace

ts
// winscope_proxy_device_connection.ts
if (request.text !== 'True') {
  this.clearTraceAliveWorker(targetName);
  this.logger.warn(targetName + ' timed out');
  await this.listener.onConnectionStateChange(ConnectionState.TRACE_TIMEOUT);
} else if (!workerExists && this.isTracing) {
  const worker = window.setInterval(
    () => this.keepTraceAlive(targetName),
    1000,
  );
  this.keepTraceAliveWorkers.push({name: targetName, worker});
}

7.3 部分成功 ​

控制器按 session 顺序启动和停止,文件搬运按 session 顺序执行;任何一个 moveFiles() 内部的复制失败只记录 warning。排查部分成功时,应分别检查:

  1. activeTracingSessions 中是否包含该 target;
  2. /data/local/tmp/last_winscope_tracing_session/ 是否有对应文件;
  3. pullFile() 是否返回空字节数组;
  4. parser 是否在文件已拉回后才报格式错误。

8. 采集测试 ​

8.1 解析测试 ​

user_request_parser_test.ts 是请求分流的第一组对应测试。应重点阅读 data source 可用、不可用、Perfetto session 过多和多个请求合并等测试;这些测试证明的是 target 选择和配置拼接,不证明设备上的真实 WMS/SF 服务已经写出文件。

8.2 会话测试 ​

tracing_session_test.ts 覆盖 setup、start/stop 状态、dump 和文件搬运命令。它能证明 TraceTarget 的命令被按顺序交给 AdbDeviceConnection,不能证明 shell 命令在真实设备上拥有足够权限。

8.3 控制器测试 ​

trace_collection_controller_test.ts 验证控制器的 prepare、session 生命周期、move/fetch 阶段和空 target 警告。阅读断言时要区分两种范围:mock 只证明调用顺序和参数;真实 trace 文件格式仍需用设备采集或官方 proto/parser 测试验证。

8.4 设备连接测试 ​

winscope_proxy_device_connection_test.ts 验证 start/end endpoint、base64 文件解码、状态超时和 keep-alive worker。它证明 HTTP 适配层的状态传播,不证明 proxy 服务端如何执行 adb shell。

9. 可执行验证 ​

不连接设备也能完成源码层验证:

bash
git grep -n "targetPerfettoDsMap\|isTooManySessions\|makePerfettoTraceTarget" \
  development/tools/winscope/src/trace_collection
git grep -n "fetchLastSessionData\|moveFiles\|TRACE_TIMEOUT" \
  development/tools/winscope/src/trace_collection

在具备 Android 17 设备和已启动 Winscope proxy 的环境中,可按以下顺序建立可观察证据:先只请求 WINDOW_MANAGER_TRACE,再同时请求 SURFACE_FLINGER_TRACE;比较 perfetto --query 中是否形成一个合并 session;停止后检查备份目录文件名;最后观察浏览器端 File 是否非空。这个实验能证明请求分流、文件搬运和下载链,不覆盖 WMS/SF 具体 proto 字段正确性。

10. 源码导航 ​

从入口继续阅读时建议按以下顺序跳转:

  1. development/tools/winscope/src/trace_collection/controller/trace_collection_controller.ts:阶段编排和 owner 列表;
  2. development/tools/winscope/src/trace_collection/controller/user_request_parser.ts:Perfetto/legacy 分流与 target 构造;
  3. development/tools/winscope/src/trace_collection/controller/perfetto_session_moderator.ts:配置、并发和 attach stop;
  4. development/tools/winscope/src/trace_collection/controller/tracing_session.ts:setup、状态和文件搬运;
  5. development/tools/winscope/src/trace_collection/winscope_proxy/winscope_proxy_device_connection.ts:HTTP endpoint、文件解码和超时;
  6. development/tools/winscope/src/trace_collection/controller/*_test.ts:把调用顺序与失败边界反向核对。

沿这条顺序阅读,读者可以把“采集按钮”还原为一组具体的状态变化和设备命令,也能知道哪些结论仍属于 Perfetto、WMS 或 SurfaceFlinger 的外部边界。