Skip to content

SystemServer分析

追踪 SystemServer 的 Perfetto、TimingsTraceAndSlog、ART profile、fdtrack、BinderCallsStats 和 LooperStats 分析入口。

基于android-17.0.0_r1
AndroidSystemServerPerfettosimpleperfTraceBinderLooper性能分析源码阅读

SystemServer分析 ​

本文面向已经读过 SystemServer.run、系统启动时间分析、并行服务启动 和 SystemServer 内存 的读者。本文不把 profiling 简化为“抓一份 trace”,而是回答一个可以沿源码验证的问题:SystemServer 的启动和运行时热点,分别由哪些生产者写入,哪些工具消费,采样开关在什么时候生效,失败时会丢失什么信息。

Android 17 中至少存在五条互补的分析路径:TimingsTraceAndSlog 用同步/异步 trace 标记服务阶段;Perfetto SDK 负责把 framework trace 写入 tracing backend;Zygote/ART profile 让 system_server 的代码路径进入 profile;fdtrack 在 debug build 中追踪文件描述符泄漏并触发 HPROF/abort;BinderCallsStats、LooperStats 和 simpleperf 分别回答 IPC、消息队列和 CPU 栈的问题。它们的输入、时间分辨率和开销不同,不能互相替代。

读完后,你应能从 SystemServer.run() 找到 Perfetto producer 和 Trace.registerWithPerfetto() 的初始化顺序;从 TimingsTraceAndSlog 解释一个 trace 名称如何同时出现在 logcat 和 Perfetto;从 ZygoteInit.handleSystemServerProcess() 判断 profile 开关、构建类型和 classpath 的约束;从 fdtrack 阈值代码定位 HPROF/abort 触发条件;用可复现命令抓取启动、CPU、Binder 和 Looper 数据,并说明每份输出没有覆盖哪些问题。

1. 工具边界 ​

图中的“性能问题”只是选择入口,不是统一数据模型。比如 Perfetto 的 traceBegin 能告诉你 StartPackageManager 持续多久,却不能单独说明耗时来自 Java 锁、Binder 服务端还是内核调度;simpleperf 能给出 CPU 栈,却不告诉你一次 Binder 事务的调用方 UID。分析时必须先选择问题的 owner。

工具/机制生产者主要输出适用时机典型缺口
TimingsTraceAndSlogSystemServer 服务代码trace slice、SystemServerTiming 日志启动/关机同步阶段不提供调用栈
Perfetto SDKProducer、Trace、framework data source.pftrace启动与运行期受 buffer/category/config 影响
ART profileZygote/ART/installdprofile、JIT/AOT 线索userdebug/eng 或实验配置不能代表完整 CPU 时间
fdtrackSystemServer debug 线程 + native fdtrackFD 事件、HPROF、abortdebug build不是 Java heap profiler
BinderCallsStatsBinder runtime/service事务次数、耗时、CPU运行期不给完整调用栈
LooperStatsLooper observerdispatch/delivery 统计运行期需要 observer/采样配置
simpleperfkernel perf eventsCPU sample/callchain运行期或启动窗口权限、符号和采样偏差

2. Perfetto初始化 ​

SystemServer 的 Perfetto 初始化有两个位置:run() 先初始化 producer 的共享内存提示,createSystemContext() 再注册 framework 的 Perfetto SDK tracing。顺序决定后续 Trace 调用使用哪套 backend。

源码文件:frameworks/base/services/java/com/android/server/SystemServer.java

相关函数:SystemServer.run、createSystemContext

java
private void run() {
    // 本文注:producer 必须在后续 trace producer 使用前初始化。
    android.tracing.perfetto.Producer.init(
            new InitArguments(
                    InitArguments.PERFETTO_BACKEND_SYSTEM,
                    4 * 1024));

    TimingsTraceAndSlog t =
            new TimingsTraceAndSlog();
    t.traceBegin("InitBeforeStartServices");
    // ... 写入 start count、locale、Binder 和 SQLite 默认值。
    t.traceEnd();
}

private void createSystemContext() {
    ActivityThread activityThread =
            ActivityThread.systemMain();
    mSystemContext = activityThread.getSystemContext();
    mSystemContext.setTheme(DEFAULT_SYSTEM_THEME);
    final Context systemUiContext =
            activityThread.getSystemUiContext();
    systemUiContext.setTheme(DEFAULT_SYSTEM_THEME);

    // framework Trace 在这里注册 Perfetto SDK categories。
    Trace.registerWithPerfetto();
}

Producer.init() 的 4 * 1024 是共享内存 size hint,单位是 KB;它不是整个 trace buffer 的最终容量。Trace.registerWithPerfetto() 只有在 android.os.Flags.perfettoSdkTracingV3() 为 true 时才注册 SDK backend,否则 Trace 仍可走 libcutils 路径。因而同一段 Trace.traceBegin() 在不同 flag 配置下可能由不同 backend 消费。

源码文件:frameworks/base/core/java/android/tracing/perfetto/Producer.java

相关函数:Producer.init

java
public static void init(InitArguments args) {
    // 本文注:Java 只传递 backend 与共享内存提示,实际 producer 在 native 层初始化。
    nativePerfettoProducerInit(
            args.backends, args.shmemSizeHintKb);
}

Producer.init() 不负责注册 framework categories,也不负责启动一次 trace session;它只是把初始化参数传给 native producer。把它当成“开始录制”会导致错误的时序判断。

源码文件:frameworks/base/core/java/android/os/Trace.java

相关函数:Trace.registerWithPerfetto

java
public static void registerWithPerfetto() {
    if (android.os.Flags.perfettoSdkTracingV3()) {
        com.android.internal.dev.perfetto.sdk.PerfettoTrace
                .register(false /* isBackendInProcess */);
        PerfettoCategories.registerCategories();
    }
}

这个开关是生效边界:category 注册发生后,配置中的 track_event 才能匹配 framework 事件。若 flag 关闭,Perfetto trace 仍可能收集 ftrace/atrace,但不能假定所有 SDK track event 都存在。

3. 时序标记 ​

TimingsTraceAndSlog 是 SystemServer 启动主线的桥接层:traceBegin() 先写 Slog.d,再调用父类 trace;traceEnd() 由父类计算持续时间并通过 logDuration() 输出。同步对象和异步对象使用不同 tag。

源码文件:frameworks/base/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java

相关函数:构造函数、newAsyncLog、traceBegin、logDuration

java
public static TimingsTraceAndSlog newAsyncLog() {
    return new TimingsTraceAndSlog(
            SYSTEM_SERVER_TIMING_ASYNC_TAG,
            Trace.TRACE_TAG_SYSTEM_SERVER);
}

public TimingsTraceAndSlog() {
    this(SYSTEM_SERVER_TIMING_TAG);
}

@Override
public void traceBegin(@NonNull String name) {
    Slog.d(mTag, name);
    super.traceBegin(name);
}

@Override
public void logDuration(String name, long timeMs) {
    super.logDuration(name, timeMs);
    if (BOTTLENECK_DURATION_MS > 0
            && timeMs >= BOTTLENECK_DURATION_MS) {
        Slog.w(mTag,
                "Slow duration for " + name
                        + ": " + timeMs + "ms");
    }
}

SYSTEM_SERVER_TIMING_TAG 和 SYSTEM_SERVER_TIMING_ASYNC_TAG 是 logcat 标签;trace 的类别是 TRACE_TAG_SYSTEM_SERVER。BOTTLENECK_DURATION_MS=-1 表示当前版本默认不额外发 slow-duration warning,不能把 trace slice 缺失解释为方法没有执行。

源码文件:frameworks/base/services/java/com/android/server/SystemServer.java

相关位置:startBootstrapServices、startCoreServices、startOtherServices

java
t.traceBegin("StartActivityManager");
ActivityTaskManagerService atm =
        mSystemServiceManager.startService(
                ActivityTaskManagerService.Lifecycle.class)
                .getService();
mActivityManagerService =
        ActivityManagerService.Lifecycle.startService(
                mSystemServiceManager, atm);
t.traceEnd();

t.traceBegin("StartPowerManager");
mPowerManagerService =
        mSystemServiceManager.startService(
                PowerManagerService.class);
t.traceEnd();

t.traceBegin("StartDisplayManager");
mDisplayManagerService =
        mSystemServiceManager.startService(
                DisplayManagerService.class);
t.traceEnd();

这些 slice 的 owner 是 SystemServer 主线程,持续时间包含被包住的构造、注册和同步依赖;如果服务内部再启动异步任务,主 slice 不会自动等待异步任务。读 trace 时要顺着 Future.get() 或 boot phase 再确认真正的完成点。

真实调用方以 WebView 准备为例:

源码文件:frameworks/base/services/java/com/android/server/SystemServer.java

相关位置:startOtherServices 的 WebViewFactoryPreparation

java
final String WEBVIEW_PREPARATION =
        "WebViewFactoryPreparation";
Future<?> webviewPrep = null;
if (mWebViewUpdateService != null) {
    webviewPrep = SystemServerInitThreadPool.submit(() -> {
        TimingsTraceAndSlog traceLog =
                TimingsTraceAndSlog.newAsyncLog();
        traceLog.traceBegin(WEBVIEW_PREPARATION);
        ConcurrentUtils.waitForFutureNoInterrupt(
                mZygotePreload, "Zygote preload");
        mZygotePreload = null;
        mWebViewUpdateService
                .prepareWebViewInSystemServer();
        traceLog.traceEnd();
    }, WEBVIEW_PREPARATION);
}

// ... 等待 package data 准备。
if (webviewPrep != null) {
    ConcurrentUtils.waitForFutureNoInterrupt(
            webviewPrep, WEBVIEW_PREPARATION);
}

WebView 准备先等待 Zygote preload Future,再在 SystemServer 允许第三方应用前等待自身 Future;因此异步 slice 的结束和系统阶段的 ready 点可能相隔一段时间。

4. 异步追踪 ​

启动线程池中的任务由 SystemServerInitThreadPool.submitTask() 创建异步 trace。异步日志拥有自己的 TimingsTraceAndSlog,因此不会把另一个线程的栈误当作主线程 trace。

源码文件:frameworks/base/services/core/java/com/android/server/SystemServerInitThreadPool.java

相关函数:submitTask

java
private <T> @NonNull Future<T> submitTask(
        @NonNull Callable<T> callable,
        @NonNull String description) {
    synchronized (mPendingTasks) {
        Preconditions.checkState(
                !mShutDown, TAG + " already shut down");
        mPendingTasks.add(description);
    }
    return mService.submit(() -> {
        TimingsTraceAndSlog traceLog =
                TimingsTraceAndSlog.newAsyncLog();
        traceLog.traceBegin(
                "InitThreadPoolExec:" + description);
        T result;
        try {
            result = callable.call();
        } catch (RuntimeException e) {
            Slog.e(TAG,
                    "Failure in " + description + ": " + e,
                    e);
            traceLog.traceEnd();
            throw e;
        }
        synchronized (mPendingTasks) {
            mPendingTasks.remove(description);
        }
        traceLog.traceEnd();
        return result;
    });
}

description 同时出现在 pending 列表、trace 名称和失败日志中,是跨观测口径的关联键。任务异常时会结束 trace 并重新抛出;真实代码在异常路径不会执行正常路径的 pending remove,因此 shutdown dump 可能保留失败任务描述,帮助定位未完成工作。

5. 慢消息 ​

SystemServer 准备主 Looper 时设置 dispatch/delivery 阈值。dispatch 是 Handler 开始到结束处理的时间,delivery 是消息入队到开始处理的等待时间;两者都不是 CPU profile。

源码文件:frameworks/base/services/java/com/android/server/SystemServer.java

相关位置:SystemServer.run 主 Looper 初始化

java
android.os.Process.setThreadPriority(
        android.os.Process.THREAD_PRIORITY_FOREGROUND);
MessageQueue.setUseDeliQueue(true);
Looper.prepareMainLooper();
Looper.getMainLooper().setSlowLogThresholdMs(
        SLOW_DISPATCH_THRESHOLD_MS,
        SLOW_DELIVERY_THRESHOLD_MS);

阈值设置必须发生在消息循环开始前。慢日志只在超过阈值时输出,不能用于计算每条消息的完整分布;要得到分布,应启用 LooperStats 或 Perfetto message queue data source。

6. ART代码画像 ​

SystemServer 的 system-server profile 准备在 Zygote child 已经 fork、但还没有进入 SystemServer.main() 之前完成。它只在 profile 开关打开且构建类型允许时执行,profile 的 code paths 来自 SYSTEMSERVERCLASSPATH 和可选的 STANDALONE_SYSTEMSERVER_JARS。

源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

相关函数:shouldProfileSystemServer、handleSystemServerProcess、prepareSystemServerProfile

java
/* package-private */ static boolean shouldProfileSystemServer() {
    return isExperimentEnabled("profilesystemserver");
}

private static Runnable handleSystemServerProcess(
        ZygoteArguments parsedArgs) {
    final String systemServerClasspath =
            Os.getenv("SYSTEMSERVERCLASSPATH");
    if (systemServerClasspath != null
            && shouldProfileSystemServer()
            && (Build.IS_USERDEBUG || Build.IS_ENG)) {
        try {
            final String standaloneSystemServerJars =
                    Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
            final String systemServerPaths =
                    standaloneSystemServerJars != null
                            ? String.join(":",
                                    systemServerClasspath,
                                    standaloneSystemServerJars)
                            : systemServerClasspath;
            prepareSystemServerProfile(systemServerPaths);
            SystemProperties.set(
                    "debug.tracing.profile_system_server", "1");
        } catch (Exception e) {
            Log.wtf(TAG,
                    "Failed to set up system server profile", e);
        }
    }

    // ... 创建 classloader 并返回 zygoteInit() 入口。
}

isExperimentEnabled() 先读 dalvik.vm.profilesystemserver,再读 runtime-native-boot 的 persistent device config;Zygote 阶段不能依赖尚未初始化的普通 DeviceConfig。user build 即使属性打开,也会因为构建类型条件跳过 profile。

prepareSystemServerProfile() 将 system_server 当作 package android、system user 下的 primary profile:

java
private static void prepareSystemServerProfile(
        String systemServerPaths) throws RemoteException {
    if (systemServerPaths.isEmpty()) return;
    String[] codePaths = systemServerPaths.split(":");

    final IInstalld installd = IInstalld.Stub
            .asInterface(ServiceManager
                    .getService("installd"));
    String systemServerPackageName = "android";
    String systemServerProfileName = "primary.prof";
    installd.prepareAppProfile(
            systemServerPackageName,
            UserHandle.USER_SYSTEM,
            UserHandle.getAppId(Process.SYSTEM_UID),
            systemServerProfileName,
            codePaths[0], null /* dexMetadata */);

    // ... 计算 cur/ref profile 路径。
    VMRuntime.registerAppInfo(
            systemServerPackageName,
            curProfilePath, refProfilePath,
            codePaths,
            VMRuntime.CODE_PATH_TYPE_PRIMARY_APK);
}

这里的 owner 是 installd/ART profile 文件;VMRuntime.registerAppInfo() 让运行中的 SystemServer 把采样结果归入 android primary profile。profile 失败只记录 wtf,随后仍继续 classloader 和 zygoteInit(),所以“profile 没生成”不等于 SystemServer 无法启动。

standalone jar 的预取还会在 profile 模式下主动跳过 AOT artifact:

java
private static void prefetchStandaloneSystemServerJars() {
    if (shouldProfileSystemServer()) {
        // profiling system_server 时将使用 JIT,不预取 AOT artifacts。
        return;
    }
    String envStr =
            Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
    if (TextUtils.isEmpty(envStr)) return;
    for (String jar : envStr.split(":")) {
        try {
            SystemServerClassLoaderFactory
                    .createClassLoader(
                            jar,
                            getOrCreateSystemServerClassLoader());
        } catch (Error e) {
            // 预取只是优化,失败不应杀死进程。
            Log.e(TAG, "Failed to prefetch " + jar, e);
        }
    }
}

这是一条重要的对照变量:开启 profiling 会改变 classloader/AOT 预取行为,因此 profile 模式下的启动耗时不能直接与普通生产启动等价比较。

7. fdtrack现场 ​

fdtrack 是 debug build 的文件描述符泄漏观察路径。SystemServer 周期性读取最大 fd;超过 enable threshold 时先做 GC,再加载 fdtrack;超过 abort threshold 时保存 HPROF 并调用 native abort。

源码文件:frameworks/base/services/java/com/android/server/SystemServer.java

相关函数:spawnFdLeakCheckThread、dumpHprof

java
private static void spawnFdLeakCheckThread() {
    final int enableThreshold = SystemProperties.getInt(
            SYSPROP_FDTRACK_ENABLE_THRESHOLD, 1600);
    final int abortThreshold = SystemProperties.getInt(
            SYSPROP_FDTRACK_ABORT_THRESHOLD, 3000);
    final int checkInterval = SystemProperties.getInt(
            SYSPROP_FDTRACK_INTERVAL, 120);

    new Thread(() -> {
        boolean enabled = false;
        while (true) {
            int maxFd = getMaxFd();
            if (maxFd > enableThreshold) {
                System.gc();
                System.runFinalization();
                maxFd = getMaxFd();
            }
            if (maxFd > enableThreshold && !enabled) {
                Slog.i("System",
                        "fdtrack enable threshold reached, enabling");
                System.loadLibrary("fdtrack");
                enabled = true;
            } else if (maxFd > abortThreshold) {
                Slog.i("System",
                        "fdtrack abort threshold reached, dumping and aborting");
                dumpHprof();
                fdtrackAbort();
            }
            try {
                Thread.sleep(checkInterval * 1000L);
            } catch (InterruptedException ex) {
                continue;
            }
        }
    }).start();
}

这条线程只在 Build.IS_DEBUGGABLE 分支启动;阈值由系统属性覆盖,默认值不是所有产品的承诺。第一次超过 enable threshold 会先做一次 GC,因此一次瞬时 fd 峰值可能下降而不加载 fdtrack。abort 分支可能终止进程,不是无侵入采样。

源码文件:frameworks/base/services/java/com/android/server/SystemServer.java

相关函数:dumpHprof

java
private static final File HEAP_DUMP_PATH =
        new File("/data/system/heapdump/");
private static final int MAX_HEAP_DUMPS = 2;

private static void dumpHprof() {
    TreeSet<File> existingTombstones =
            new TreeSet<>();
    for (File file : HEAP_DUMP_PATH.listFiles()) {
        if (!file.isFile()) continue;
        if (!file.getName().startsWith("fdtrack-")) continue;
        existingTombstones.add(file);
    }
    if (existingTombstones.size() >= MAX_HEAP_DUMPS) {
        for (int i = 0; i < MAX_HEAP_DUMPS - 1; ++i) {
            existingTombstones.pollLast();
        }
        for (File file : existingTombstones) {
            if (!file.delete()) {
                Slog.w("System",
                        "Failed to clean up hprof " + file);
            }
        }
    }
    try {
        String date = new SimpleDateFormat(
                "yyyy-MM-dd-HH-mm-ss").format(new Date());
        Debug.dumpHprofData(
                "/data/system/heapdump/fdtrack-"
                        + date + ".hprof");
    } catch (IOException ex) {
        Slog.e("System",
                "Failed to dump fdtrack hprof", ex);
    }
}

HPROF 数量限制是磁盘清理策略,不是 heap limit。目录不可写或 HPROF 生成失败时只记录错误并返回,不能把现场缺失解释成没有 FD 泄漏。

8. Binder与Looper ​

SystemServer 在 core services 阶段启动 BinderCallsStatsService 和 LooperStatsService:

源码文件:frameworks/base/services/java/com/android/server/SystemServer.java

相关位置:startCoreServices

java
t.traceBegin("StartBinderCallsStatsService");
mSystemServiceManager.startService(
        BinderCallsStatsService.LifeCycle.class);
t.traceEnd();

t.traceBegin("StartLooperStatsService");
mSystemServiceManager.startService(
        LooperStatsService.Lifecycle.class);
t.traceEnd();

这里的 slice 只测量统计服务启动耗时,真正的事务和消息数据要等服务注册并到达对应 boot phase 后产生。

源码文件:frameworks/base/services/core/java/com/android/server/BinderCallsStatsService.java

相关类:BinderCallsStatsService.LifeCycle

java
@Override
public void onStart() {
    mBinderCallsStats = new BinderCallsStats(
            new BinderCallsStats.Injector());
    mWorkSourceProvider =
            new AuthorizedWorkSourceProvider();
    mService = new BinderCallsStatsService(
            mBinderCallsStats, mWorkSourceProvider);
    mNativeBinderStats =
            new NativeBinderStats(getContext());
    publishLocalService(
            Internal.class,
            new Internal(mBinderCallsStats));
    publishBinderService(SERVICE_NAME, mService);
}

@Override
public void onBootPhase(int phase) {
    if (SystemService.PHASE_SYSTEM_SERVICES_READY == phase) {
        mBinderCallsStats.setDeviceState(
                getLocalService(
                        CachedDeviceState.Readonly.class));
        mWorkSourceProvider.systemReady(getContext());
        mService.systemReady(getContext());
        mNativeBinderStats.systemReady();
    }
}

onStart 只建立并发布对象;PHASE_SYSTEM_SERVICES_READY 才安装 observer 和 device state。详细 tracking 可能增加 CPU 开销,比较实验前要固定属性和 dump 参数。

bash
# 输入:system_server 中的 binder_calls_stats service。输出:事务次数、耗时、CPU 和 work source。
adb shell dumpsys binder_calls_stats -a

# 输入:目标 work-source UID。输出:该 UID 归因的 Binder 调用统计。
adb shell dumpsys binder_calls_stats --work-source-uid 1000

# 输入:system_server 中的 looper_stats service。输出:Handler dispatch/delivery 统计。
adb shell dumpsys looper_stats

-a 是 BinderCallsStats 的 verbose 选项,--work-source-uid 过滤归因 UID。LooperStats 的 settings/property 决定采样间隔和 entries cap,空输出不能直接解释为没有消息。

9. CPU采样 ​

simpleperf 通过 kernel perf event 对 system_server 采样,不需要插入 trace slice。它适合回答 CPU 时间花在哪里,不适合替代 wall-clock 阶段。

bash
# 输入:可调试设备上的 system_server PID。输出:10 秒 perf.data。
adb shell simpleperf record \
  -p $(pidof system_server) \
  -f 99 --duration 10 \
  -o /data/local/tmp/system_server.perf.data

# 输入:设备上的采样文件。输出:带调用链的文本报告。
adb pull /data/local/tmp/system_server.perf.data .
simpleperf report-sample \
  --show-callchain \
  system_server.perf.data > system_server.report.txt

# 输入:采样文件。输出:折叠栈文本和 SVG。
simpleperf report-sample \
  --show-callchain system_server.perf.data \
  | stackcollapse-perf.pl \
  | flamegraph.pl > system_server.svg

命令输入分别是 PID、采样文件和 report 文本;输出是二进制采样、符号化文本和 SVG。设备必须允许目标进程 perf 采样,本地还要有匹配 symbols,否则报告可能只有地址。提高采样频率会增加开销。

10. Perfetto采集 ​

配置需要同时打开 kernel ftrace、atrace categories 和 process stats。system_server 是 atrace app 过滤对象;ss、am、wm、dalvik 是 category,而不是任意 logcat tag。

bash
# 输入:文本格式 Perfetto 配置和设备。输出:30 秒 system_server trace。
adb push /tmp/system_server_trace.pbtxt /data/local/tmp/
adb shell perfetto --txt \
  -c /data/local/tmp/system_server_trace.pbtxt \
  -o /data/misc/perfetto-traces/system_server_trace.pftrace
adb pull /data/misc/perfetto-traces/system_server_trace.pftrace .

buffer size 影响丢事件风险和采集开销,RING_BUFFER 满后会覆盖旧事件。采集结束应检查 system_server 进程轨道、sched、Binder 和 framework slices,而不是只看文件存在。

11. 分析时序 ​

profile、Perfetto、BinderCallsStats、LooperStats 和 simpleperf 是并行观测面。profile 在 SystemServer Java 入口前准备,Perfetto producer 在 run 开始初始化,Binder/Looper 统计需要服务注册和 boot phase,simpleperf 的窗口由外部命令决定。

12. 偏差与验证 ​

现象先查不能直接推出
没有 framework track eventPerfetto flag、categories、backendSystemServer 没执行 trace
profile 文件为空构建类型、属性、installd 日志JIT/AOT 没运行
simpleperf 无权限build type、perf_event 策略CPU 没有热点
Binder stats 为空ready phase、settings、是否 reset没有 Binder 调用
Looper stats 很少observer、采样间隔、entries cap主线程没有消息
Perfetto 丢事件buffer、trace stats代码路径没有执行
HPROF 未生成debug build、fd 阈值、目录权限没有 FD 泄漏

源码测试可以验证 tracing API 的局部契约。TimingsTraceAndSlogTest 检查线程归属、嵌套状态、Slog 输出和未匹配的 traceEnd,不验证真实 Perfetto 文件落盘。

源码文件:frameworks/base/services/tests/mockingservicestests/src/com/android/server/utils/TimingsTraceAndSlogTest.java

相关测试:testDifferentThreads、testGetUnfinishedTracesForDebug、testEndNoBegin

java
@Test
public void testDifferentThreads() throws Exception {
    TimingsTraceAndSlog log =
            new TimingsTraceAndSlog(TAG, TRACE_TAG_APP);
    log.traceBegin("test");
    log.traceEnd();

    final List<String> errors = new ArrayList<>();
    Thread t = new Thread(() -> {
        try {
            log.traceBegin("test");
            errors.add("traceBegin should fail");
        } catch (IllegalStateException expected) {
        }
        try {
            log.traceEnd();
            errors.add("traceEnd should fail");
        } catch (IllegalStateException expected) {
        }
    });
    t.start();
    t.join();
    assertThat(errors).isEmpty();
}

@Test
public void testEndNoBegin() throws Exception {
    TimingsTraceAndSlog log =
            new TimingsTraceAndSlog(TAG, TRACE_TAG_APP);
    log.traceEnd();
    verify((MockedVoidMethod) () ->
            Trace.traceEnd(TRACE_TAG_APP));
    verify((MockedVoidMethod) () ->
            Slog.w(TAG,
                    "traceEnd called more times than traceBegin"));
}

第一个测试输入是跨线程复用同一个 trace 对象,断言抛 IllegalStateException 且错误列表为空;第二个输入是没有 begin 的 end,断言仍调用父类 traceEnd 并记录 warning。

Perfetto V3 测试实际创建 SDK session、发送事件并解析 protobuf:

源码文件:frameworks/base/core/tests/coretests/src/android/os/PerfettoTraceV3InitializationTest.java

相关测试:testSendMessageQueueCategoryEvent

java
@BeforeClass
public static void setUpClass() {
    PerfettoTrace.register(true);
    PerfettoCategories.registerCategories();
}

@Test
public void testSendMessageQueueCategoryEvent()
        throws Exception {
    assertThat(PerfettoCategories.MQ_CATEGORY
            .isRegistered()).isTrue();
    PerfettoTrace.Session session =
            new PerfettoTrace.Session(
                    true,
                    getTraceConfig("mq").toByteArray());
    PerfettoTrace.instant(
            PerfettoCategories.MQ_CATEGORY,
            "my_event")
            .addArg("string_key", "foo")
            .emit();

    TraceOuterClass.Trace trace =
            TraceOuterClass.Trace.parseFrom(
                    session.close());
    assertThat(mCategoryNames).containsExactly("mq");
    assertThat(mEventNames).containsExactly("my_event");
    assertThat(mDebugAnnotationNames)
            .containsExactly("string_key");
}

arrange 是打开 V3 flag、注册 categories 和创建 session;action 是发送带 annotation 的 instant event;assert 检查 category、event 和 annotation 名称。它证明 SDK 初始化和事件编码闭环,不证明 SystemServer 实际启动配置一定打开同一 flag。

bash
# 输入:frameworks/base 测试构建环境。输出:trace 线程/嵌套/异常结构测试。
atest FrameworksMockingServicesTests:TimingsTraceAndSlogTest

# 输入:frameworks/base core 测试构建环境。输出:Perfetto V3 SDK protobuf 测试。
atest FrameworksCoreTests:PerfettoTraceV3InitializationTest

# 输入:设备和 Perfetto 配置。输出:可在 Perfetto UI 打开的 trace。
adb shell perfetto --txt \
  -c /data/local/tmp/system_server_trace.pbtxt \
  -o /data/misc/perfetto-traces/system_server_trace.pftrace

测试模块和 flag 必须与当前构建匹配;若测试被 flag rule 跳过,不能把“未执行”当作事件缺失。设备实验还要固定 build type、categories、采样频率、buffer size 和 workload。

13. 收束 ​

SystemServer profiling 是多个生产者和消费者的组合:Zygote/ART 准备代码画像;SystemServer 通过 Producer、Trace 和 TimingsTraceAndSlog 标记 wall-clock 阶段;线程池把异步任务放到独立轨道,并用 Future.get 重新汇合;BinderCallsStats/LooperStats 在 boot phase 后收集运行时统计;simpleperf 从系统外采样 CPU;fdtrack 只在 debug build 的阈值路径中提供 FD/HPROF 现场。

真正的热点定位必须把输入和输出对齐:一个 StartXxx slice 对应哪个线程和 Future;一个 Binder 延迟对应哪个事务和 work source;一次 CPU sample 是否处在同一 workload;一个 profile 或 HPROF 是否因 build type、flag 或失败路径缺失。只有这些边界都能回到源码,profiling 才能从工具操作变成可复现的工程分析。