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。
| 工具/机制 | 生产者 | 主要输出 | 适用时机 | 典型缺口 |
|---|---|---|---|---|
TimingsTraceAndSlog | SystemServer 服务代码 | trace slice、SystemServerTiming 日志 | 启动/关机同步阶段 | 不提供调用栈 |
| Perfetto SDK | Producer、Trace、framework data source | .pftrace | 启动与运行期 | 受 buffer/category/config 影响 |
| ART profile | Zygote/ART/installd | profile、JIT/AOT 线索 | userdebug/eng 或实验配置 | 不能代表完整 CPU 时间 |
| fdtrack | SystemServer debug 线程 + native fdtrack | FD 事件、HPROF、abort | debug build | 不是 Java heap profiler |
| BinderCallsStats | Binder runtime/service | 事务次数、耗时、CPU | 运行期 | 不给完整调用栈 |
| LooperStats | Looper observer | dispatch/delivery 统计 | 运行期 | 需要 observer/采样配置 |
| simpleperf | kernel perf events | CPU 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
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
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
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
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
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
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
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 初始化
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
/* 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:
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:
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
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
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
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
@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 参数。
# 输入: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 阶段。
# 输入:可调试设备上的 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。
# 输入:文本格式 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 event | Perfetto flag、categories、backend | SystemServer 没执行 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
@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
@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。
# 输入: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 才能从工具操作变成可复现的工程分析。
