perfetto实战指南:从架构原理到SQL分析,全面掌握Android性能调优
发布时间:2026/9/29 6:39:09来源:尧图网络
1. perfetto到底解决了什么问题先说个真实场景。前阵子有个项目反馈说App在低端机上滑动列表掉帧严重一帧能跑到40多毫秒。我们当时用的还是老一套systrace抓回来的trace里只有CPU调度和核心线程的运行段主线程看起来并没有长时间占用CPU但帧就是画不完。你盯着那条时间轴翻来覆去看不到GPU在干什么、看不到SurfaceFlinger在为什么排队、更看不到App进程里某个后台任务是不是在偷偷占用主线程。最后只能靠猜猜布局问题、猜渲染问题、猜GC问题一个个去验证。这种状态持续了很久直到我们把工具链切到perfetto才第一次看清楚了掉帧的完整因果链——不是主线程本身跑得慢而是它被一个低优先级的Binder调用打断了而那个Binder调用的另一端在等SurfaceFlinger释放一个缓冲区。放在systrace里你只会看到主线程有一小段灰色空白根本不知道它在等什么在perfetto里整个等待链、锁关系、任务依赖全部串在一条统一时间轴上一眼就能定位到根因。perfetto这个名字你可能已经听过它是Android和Linux系统层面的性能分析工具集本质上就是systrace的继任者。别看systrace还能用Google已经在Android 12之后慢慢停掉systrace的开发把资源全部投入到perfetto这条线上。它做的事情可以概括成三块第一把CPU调度、内存、磁盘IO、GPU、进程线程、Binder、电源等几十个数据源的事件全部抓下来第二用Protobuf统一编码成二进制trace文件紧凑、高效、可无损扩展第三配套一套Web UI和SQL分析引擎让我们可以在浏览器里交互式查看也可以用SQL像查数据库一样去挖掘数据。适合谁用范围很大。日常做应用性能优化的Android开发拿它定位启动慢、掉帧、ANR、卡顿做系统开发的工程师拿它看调度延迟、中断、功耗做Linux服务器优化的运维拿它分析负载高、IO瓶颈、核间迁移甚至做游戏引擎性能分析的人也可以拿perfetto的自定义事件API往自己的引擎里埋点。它是一个通用到几乎什么样的性能问题都能往上套的工具。我对perfetto的评价就一句话它是目前Android/Linux平台上把“采集能力”和“分析能力”平衡得最好的性能工具。以前我们为了看不同维度的数据要分别抓systrace、atrace、top、iostat、dmesg然后手工对齐时间轴痛苦不堪现在perfetto一个工具全搞定而且数据全部落在同一根时间轴上任何事件之间是否存在先后依赖拖一拖缩放一下就能看出来。下面这篇文章我会按照自己的实际使用经历从架构原理、抓取方式、数据分析、SQL查询到踩坑心得完整梳理一遍。不管你是第一次接触perfetto还是已经在用但想挖得更深应该都能找到有用的东西。2. 从systrace到perfetto核心架构与设计思路变化2.1 为什么systrace不够用了systrace本质上是基于ftrace的一层壳。你把systrace跑起来它做的事情是打开内核ftrace的N个tracepoint把调度事件、CPU频率变化、时钟事件等数据记录下来同时通过atrace去抓用户空间的几个系统进程比如SurfaceFlinger、InputDispatcher打点出来的日志最后合成一个HTML文件。这套方案在Android 4到8时代是够用的。那个年代应用层级的问题大多集中在主线程做了太多事systrace能看到主线程执行段的颜色块和函数名配合sched事件看有没有被抢占基本就能定位大多数卡顿。但随着系统复杂度上升比如多摄像头流水线、动态性能调频、多核异构调度、GPU渲染管线并行化之后单纯靠几个tracepoint已经无法反映全貌。你想知道GPU硬件到底干活了没有想知道内存分配器里锁竞争的情况想知道某个低频任务是不是被cpuset拉进了大核又限制住了systrace全都无能为力。2.2 perfetto的架构分层perfetto的架构从设计之初就不是“加强版systrace”而是按照“数据采集系统”的标准来做的。它的核心组件有以下几层。最底层是数据源层也就是producer。每个producer负责一个特定领域的数据采集比如ftrace_producer负责内核ftrace事件heapprofd负责native堆内存采样traced_perf负责perf事件采样gpu_producer负责GPU频率与活动状态还有我们应用层可以自己写一个TrackEvent数据源往里丢自定义事件。每个producer独立进程运行互不影响。中间层是traced守护进程它负责接收所有producer的数据按照不同buffer分块存储。这里有一点必须强调perfetto的buffer是按“数据源”分区的你可以给不同数据源分配不同大小的独立buffer一个数据源写满不会挤爆另一个。比如你想长时间压测性能CPU调度事件和内存事件都很密集就把这两个buffer调大而某个辅助数据源不关心就给它很小的buffer。这比systrace那种“一个trace缓冲灌满天”的模型灵活太多。最上层是消费和解析层。抓完之后的trace文件通过trace_processor_shell加载它会把二进制trace解析成一张张关系表然后我们可以用标准SQL去查询。SQL查询是perfetto最迷人的地方后面我用一整节专门讲。整个流程用一张全景描述producer采集 → traced写入共享内存 → 落地成.protobuf文件 → trace_processor解析 → SQL查询/UI渲染。每个环节都有独立模块这也让perfetto天然适合修修补补——你想加一个新数据源完全不影响已有的调度事件解析逻辑。2.3 时间轴为什么能对齐性能分析里最难的一件事不是“抓到事件”而是“确认多事件之间的先后关系”。systrace的软肋在于用户空间日志和内核tracepoint之间没有完全统一的时间基准经常出现几个毫秒的偏差。perfetto解决了这个问题所有事件在进入buffer之前就统一打上系统boottime时钟的时间戳无论这个事件来自内核ftrace还是来自应用App的自定义打点时间基准完全一致。这个设计带来的实际好处非常明显。比如我前面提到那个“Binder等待SurfaceFlinger”的案例在perfetto里我可以把App主线程的runnable时间和SurfaceFlinger释放缓冲区的时刻直接放在同一时间轴上对比谁等谁、等多久一目了然。如果时间戳对不齐这种跨进程依赖分析完全没有可能做。所以后面你在使用perfetto时如果自己在代码里埋点一定要确保使用TRACE_TIME类型的时钟不要用TRACE_TIME_MONOTONIC或者其他单调时钟否则事件间的对齐关系会错乱。2.4 数据格式为什么是Protobuf而不是文本早期systrace输出的其实是文本事件流ftrace输出文本事件加上atrace的文本行解析靠正则表达式效率低而且字段一多就难维护。perfetto选择用Protobuf把每个事件编码成二进制格式。你不用去懂Protobuf的具体细节只需要明白两个特点。第一它是结构化的每个字段都有明确的编号和类型读出来的就是一个对象而不是一行字符串第二它是紧凑可追加的新版本perfetto往trace里加字段不会破坏旧版本解析器对已有字段的读取所以老trace文件放在新版UI里打开大部分数据依然可用。这对一个长期演进的项目来说特别重要你用一年前的perfetto版本抓的trace今天照样能用最新UI分析不存在“旧抓的trace废了”这种事。2.5 trace文件的构成不是纯事件流如果你实际抓过perfetto你可能会注意到一个trace文件解压之后除了事件数据还有大量“元数据”进程名、线程名、调用栈符号映射、ftrace格式描述、系统版本信息、以及所有数据源的格式配置。这些元数据的作用是保证trace文件离开抓取机器之后在任何电脑上都能完整还原现场不依赖目标机上的符号文件。这个设计在远程分析场景里太重要了。我经常在客户现场的Android设备上抓数据带回办公室用笔记本打开分析。设备上的/system/bin/app_process可执行文件路径映射、so库符号地址、甚至内核函数名都直接打包进了trace文件里。分析机上不需要重新拉取符号UI里直接就能看到函数名。如果是自己开发的App要抓native调用栈不用符号文件也可以看函数偏移范围再把本地的带符号so拉出来手动修offset就能还原这在native内存泄漏分析中非常实用。3. 不想记命令也能抓perfetto的采集方式与关键配置3.1 最快的起步方式Perfetto UI直接抓很多人一上来就被perfetto的命令行参数吓到觉得抓个trace怎么这么复杂。其实如果只是偶尔分析一次最简单的路线是直接用Perfetto UI。在浏览器打开ui.perfetto.dev页面左侧有个“Record new trace”按钮点进去就是图形化采集配置面板。你在面板里勾选需要数据源CPU、调度、内存、GPU、电源等设置buffer大小和时长选择目标设备然后点“Start recording”。它会通过websocket把adb命令推给连接的设备抓完自动把trace文件拉回浏览器并开启分析。这个方式适合刚入门或者快速复现问题。它的好处是你不用记任何参数所有可选项都以开关和滑块的形式呈现选完就抓。我自己在演示给同事看的时候也经常这么干因为现场给人家敲一长串config命令不如点几个勾来得直观。3.2 命令行生产方式一套可以反复用的config模板但命令行方式在自动化场景里无法替代。比如我们要在自动化测试回归中每次跑测试都自动抓一段trace那必须能脚本化地调用adb shell perfetto。这里我贴一份我常用的抓取config覆盖了最常见的需要CPU调度、进程线程状态、内存、磁盘、GPU和电源。{ duration_ms: 15000, buffers: [ { size_kb: 65536, fill_policy: RING_BUFFER }, { size_kb: 16384, fill_policy: RING_BUFFER } ], data_sources: [ { config: { name: linux.ftrace, target_buffer: 0, ftrace_config: { ftrace_events: [ sched/sched_switch, sched/sched_wakeup, sched/sched_wakeup_new, sched/sched_process_exit, power/cpu_idle, power/cpu_frequency, task/task_newtask, task/task_rename, sched/sched_process_free ] } } }, { config: { name: android.log, target_buffer: 1, android_log_config: { log_ids: [main, system, radio] } } }, { config: { name: linux.process_stats, target_buffer: 1, process_stats_config: { proc_stats_period_ms: 100 } } } ] }用这份config抓trace的命令是adb shell perfetto -c /data/local/tmp/config.pb -o /data/local/tmp/trace.perfetto-trace adb pull /data/local/tmp/trace.perfetto-trace ./trace.perfetto-trace需要提醒的是config文件必须是二进制protobuf格式你不能直接把JSON文件塞给-c参数。最简单的生成方式有两种一种是在Perfetto UI的Record面板里配置好选项后点击右上角的“Copy config”按钮它会给你一段等价的protobuf二进制文本base64编码或者直接保存为.pb文件另一种是用perfetto源码里的protoc从trace_config.proto生成。日常我最常用的是前者在UI里勾好选项生成配置文件保存下来复用以后改几个参数就行非常方便。3.3 快速抓取一行命令搞定常规场景如果不想搞config文件只想快速看一眼当前设备状态perfetto也提供了一种极简的抓取方式直接在命令里指定数据源adb shell perfetto -o /data/local/tmp/trace.perfetto-trace -t 10s sched freq idle这行的意思是抓10秒钟-t 10s开启sched调度事件、freq频率变化、idle空闲状态三个数据源。抓完自动停止trace文件输出到指定路径。这种快速抓取方式在复现问题现场时特别有用因为你可以盯着现象现象一出现马上抓一段成本低不干扰用户操作。3.4 长时间后台采集与按需触发有些性能问题复现周期长比如内存缓慢泄漏、偶发的IO尖峰可能得跑几小时甚至隔夜才能复现一两次。这时候长时间采集模式就派上用场了。两个关键配置值得关注一个是duration_ms设为-1表示无限时采集直到显式停掉为止另一个是数据源里加上linux.sys_stats它每隔一段固定时间采样一次全局CPU、内存、IO信息可以用来观察长时间趋势。配合长时间采集我强烈建议你学会用“触发模式”。在config里设置trigger_config定义触发条件和触发后采集时长。比如当cpu.time_in_state某个事件累计达到阈值时自动开始正式采集并记录触发点前后各30秒的数据。这就好比接了一台“性能事件录像机”平时低负载待机不录一旦满足条件才录制关键片段既省存储又不会漏掉偶发问题。3.5 采集时的性能开销控制抓trace本身是有开销的特别是高频的sched_switch事件在8核机器上轻松每秒产生几十万条事件。如果把所有数据源全开、buffer开到最大抓trace本身可能就会影响App行为导致测出来的性能数据失真。这是很多初学者最容易踩的坑为了“看得全”什么都抓结果分析出来的根本不是真实问题。我的经验是第一阶段先做“骨架采集”只开调度和进程状态两个最基础的数据源跑2到5分钟看整体CPU分布。等到时间轴上的热点区域确定了再针对性地开第二个更精细的采集加入内存、GPU或自定义TrackEvent只抓热点前后各10秒。这种分层抓取策略既能保证数据完整又不会因为工具本身的负担把性能带偏。4. 一次完整的性能卡顿分析从抓取到定位根因的链路4.1 案例背景与抓取过程一个典型的场景某个App在指定页面上滑时帧率明显下降用户操作一阵一阵卡顿。我们用固定步骤复现在复现的同时用perfetto抓取约20秒包含滑动操作的trace。抓取的具体命令我一般会写成带-c配置的完整方式保证调度事件、GPU活动、SurfaceFlinger、内存状态都包含在内。抓取完成后用Perfetto UI打开trace文件或者直接用trace_processor_shell导入然后进入SQL分析模式。我习惯先不着急看可视化时间轴而是先跑一条SQL看一下整体帧时间分布这样能快速确定是不是渲染线程的问题把分析范围缩小。4.2 第一步用Frames数据定位掉帧区间perfetto里Android的Choreographer打点数据会生成一张名为frames的表记录了每一帧的开始时间、结束时间和持续时长。掉帧与否用SQL一眼就能看清楚select ts / 1e6 as ts_ms, dur / 1e6 as dur_ms, name, process_name from frames where dur / 1e6 16.7 order by dur desc limit 10;把这10条最慢的帧对应的ts找出来回到UI里把主时间轴定位到对应的毫秒位置那些帧对应的时间段就是需要重点分析的区间。4.3 第二步定位主线程在掉帧时刻做了什么主线程执行的活动在perfetto里对应slices表的ui_thread切片。我用片段筛选过滤出掉帧时间段内主线程的所有运行切片按耗时排序select ts / 1e6 as ts_ms, dur / 1e6 as dur_ms, name from slices where track_id in ( select id from track where name 主线程名称 ) and ts / 1e6 between 3000 and 3020 order by dur desc;这里把“主线程名称”替换成你应用的实际线程名。结果通常能直接看到耗时最长的那几个函数比如Layout、measure、某个自定义绘制方法、bitmap.compress之类的。到这一步问题大概率已经缩小到一个方法了。4.4 第三步排查线程调度和锁等待有时候主线程切片本身看起来并不长但帧就是晚了这种情况问题往往出在“等待”而不是“执行”。回到UI里选中掉帧时间段内的主线程轨道如果看到有sched_blocked_uninterruptible、Binder transaction、Lock contention这类状态说明主线程在执行间隙被阻塞了。此时切换到sched事件看主线程当时运行在哪个CPU上被谁抢占。更直接用SQL查阻塞睡眠的持续时间select tid, comm, sum(dur) as total_blocked_ms from sched_slice where tid 12345 and state D and ts / 1e6 between 3000 and 3020 group by tid, comm;D状态代表不可中断睡眠常见于IO等待。如果主线程频繁进出D状态那就要看磁盘IO和存储是不是瓶颈。如果是S状态睡眠那就看唤醒它的线程是谁perfetto里sched_wakeup事件会记录唤醒者IoTools那条线直接指过去就行。4.5 第四步关联GPU与SurfaceFlinger如果主线程和调度都没问题帧还是掉那大概率是渲染流水线出问题。需要看GPU活动轨道确认GPU干活时间是否异常。常见情况是GPU频率没有及时抬高、渲染命令排队太多、SurfaceFlinger合成时间变长。此时可以直接看SurfaceFlinger的调用栈所在线程的切片再用SQL查它每帧合成时间select ts / 1e6 as ts_ms, dur / 1e6 as dur_ms, name from slices where track_id in ( select id from track where name like %SurfaceFlinger% ) and name like %repaint% and dur / 1e6 16.7 order by dur desc;一直查到这里整个链路就已经打通了掉帧区间 → 主线程执行 / 等待 → CPU调度 / 锁竞争 → GPU合成每一步都有数据支撑不需要再看日志靠猜。我自己在实践中最快的一次从拿到trace到定位到具体方法用这种方式只花了不到十分钟。5. SQL显神通把trace当成数据库来挖掘5.1 trace_processor带来的分析范式转变前面提到perfetto解析后会把事件数据表格化成若干张表这一节展开聊。这是个巨大的优势以前在systrace里你只能在时间轴上用肉眼找问题脑力全花在“找”上现在perfetto支持SQL相当于把整个trace变成sqlite数据库你可以在里面做聚合、筛选、联表、排序把分析变成“写查询”而不是“盯屏幕”。这个转变对效率的提升比想象中要大得多。TraceProcessor的表很多常用的有slices执行片段、sched_sliceCPU调度、thread线程信息、process进程信息、frames帧、heap_profile_allocation内存采样、binder_txnBinder事务、freqCPU频率、tracks轨道元信息。表结构和字段信息可以在ui.perfetto.dev的“Inspect”标签里探索。5.2 高频查询模板直接抄走用有时候想快速了解一个trace的整体健康状况我会直接跑下面这套“性能体检”查询。查整段时间内最影响主线程的top 10函数select t.name as thread_name, s.name as function_name, count(*) as call_count, sum(s.dur) as total_dur_ms from slices s join thread_track tt on s.track_id tt.id join thread t on tt.thread_id t.id where t.name 主线程名称 group by t.name, s.name order by total_dur_ms desc limit 10;调用次数多且单次耗时还不短的函数就是优化的重点。还可以查Binder事务的平均耗时和调用量定位跨进程通信是不是瓶颈select bt.name as binder_name, count(*) as txn_count, avg(bt.dur) as avg_dur_ms from binder_txn bt group by bt.name order by avg_dur_ms desc limit 20;查CPU频率变化趋势判断是不是因为调度器没拉到高频率导致性能下降select cpu, freq, count(*) as sample_count from cpu_freq group by cpu, freq order by cpu, sample_count desc;如果发现高频率档位的sample_count占比很小说明系统在大部分时间都保持在低频运行可以判断是调度策略、温控策略或者负载不足导致频率没拉高。5.3 自定义TrackEvent在UI和SQL里看自己的埋点perfetto最实用的功能之一就是支持应用自己埋点。你在代码里TRACE_EVENT(tag, my_function)这个事件在全景时间轴和SQL表里都会出现和其他系统事件同处一条时间线。这让App层的问题和系统事件发生严格的时间关联成为可能。比如你在启动过程里埋了几个关键节点AppOnCreate、MainActivityOnResume、FirstFrameRendered。开启perfetto的TrackEvent数据源抓一次启动过程然后跑SQLselect name, ts / 1e6 as ts_ms from slices where name in (AppOnCreate, MainActivityOnResume, FirstFrameRendered) order by ts;这三个时间点之间的差值就是每个阶段的耗时配合系统调度数据你立刻能看到每个阶段里主线程被谁抢占了。对native层perfetto同样提供了C API。在代码里加PERFETTO_TRACE_EVENT(category, name);编译时带上perfetto的track_event源文件就能在native层埋点。如果你在看Android系统源码会发现许多核心服务的代码里已经预埋了perfetto打点比如SurfaceFlinger的commit事件、InputDispatcher的deliverInputEvent这些已经自动包含在系统trace里了不需要你额外做任何事。5.4 用SQL做自动化回归分析SQL查询还有一个高级玩法把性能分析做成自动化脚本。trace_processor_shell支持直接执行SQL并输出结果我们可以写一个shell脚本每次回归测试抓完trace后自动跑几个关键查询把指标和阈值对比超了就标记失败。这个思路非常适合做性能测试CI。trace_processor_shell --query select count(*) from frames where dur 41666666 trace.perfetto-trace上面这条命令会把超过一帧两倍时长4.1666666千万纳秒的帧数量统计出来。在CI里连续跑一周你就有了掉帧次数的趋势数据而不是某一次人工分析的孤例。6. 实践中绕不开的坑我的踩坑记录与解决思路6.1 trace文件太大UI打开卡顿perfetto抓个30秒全数据能轻松产出1GB以上的trace文件。UI打开时会加载和渲染所有事件如果内存不够或者机器配置不高直接卡死。我这边遇到过几次别人的trace文件发过来ui.perfetto.dev打开之后等了五分钟还是白屏。解决思路不复杂一是抓取的时候控制buffer大小不要无限开二是分析的时候只用trace_processor_shell加SQL做第一步筛选先统计出兴趣时间段然后缩小时间范围再看UI。trace_processor_shell本身是无头环境下运行的内存占用比UI小很多。三是利用perfetto的降采样能力用argv0 -d或者--trim之类的工具把低频、无关的事件去掉再做可视化。6.2 抓trace的瞬间性能问题反而不复现了这是一个很经典的现象。开启perfetto之后采集进程本身会占用CPU和内存如果设备资源本来就紧张抓trace本身可能导致原本卡顿的场景变得“不卡了”或者反过来变得更卡测出来的数据完全失真。我的应对策略前面已经提过就是分层抓取。第一次只抓调度和进程状态把干扰降到最低确认问题确实存在后再做第二次精细化采集。如果两次结果差异大就要警惕是不是perfetto自身开销造成的假象。另外perfetto有lightweight模式它在buffer写满时采用压缩策略减少写入量对低端机友好很多必要时可以开启。6.3 无法抓取或perfetto命令找不到Android设备上如果系统版本低于Android 10perfetto命令可能不存在或者即使存在也因为没有systrace权限导致运行失败。这时候可以先用adb shell perfetto --version确认版本如果提示命令无法找到优先检查设备的系统版本和厂商定制ROM是否裁剪了perfetto。部分国产ROM在出厂时会裁掉perfetto二进制尤其是低端机。替代方案是把perfetto的可执行文件push到/data/local/tmp/目录手工跑因为它是静态链接的不依赖系统库。另一个常见的坑是SELinux限制。设备开启了enforcing模式perfetto写入/data/local/tmp以及读取某些内核节点可能被拒绝。最稳妥的办法是抓取完用adb pull把文件拉出来不要在设备上尝试看文件更不要在抓取过程中用adb shell操作太多其他文件避免挤出trace数据导致缓冲区溢出。6.4 时间同步和时钟漂移导致的假象虽然perfetto已经统一了时钟源但在长时间抓取、设备深度睡眠唤醒之后偶尔还是能碰到事件时间戳轻微错位的现象。判断方法很简单抓一段trace后看idle事件和cpu_frequency事件的先后顺序如果频率下降事件发生在idle事件之前几十微秒属于正常范围如果反过来了或者偏移达到毫秒级别说明时钟有问题这段trace的可信度要打折。在Android设备上更常见的是应用层自定义埋点时用了错误的时钟。TRACE_EVENT默认是用boottime但如果你在native代码里手动取clock_gettime(CLOCK_MONOTONIC)来记录事件时间就会和时间轴产生偏移。我自己就踩过这个坑自己写的某个事件在UI上总是比系统事件晚了几十毫秒排查半天发现是埋点代码用了既不是boottime也不是realtime的时钟最后改成perfetto内部提供的时钟API才对上。6.5 buffer溢出导致早期事件丢失长时间采集时如果RING_BUFFER模式的buffer被写满最早的事件会被覆盖掉。很多时候你打开trace发现开头一段数据不完整或者某个时间段的调度事件明显缺失大概率就是buffer溢出被冲掉了。解决方式是在config里合理设置buffers数组的大小并尽量让事件量小的数据源和事件量大的数据源分开用不同的buffer避免大流量事件把小流量事件的数据全部挤出。6.6 分析时区分“事实”和“噪音”最后说一个偏方法论的心得。perfetto给的数据非常丰富但数据多不等于结论准。比如看到某个线程的runnable状态特别长只能说明它“准备好了但没有被调度”并不能说明“它在等锁”要结合sched_wakeup的唤醒来源、锁等待切片和Binder事务信息综合判断。我一直建议分析的时候至少同时看三条轨道目标线程的切片轨道、目标线程所在CPU的调度轨道、锁或者Binder对应的同步对象轨道三者相互印证才能下结论。写在最后perfetto这套工具从抓取、存储、解析到分析每一层都体现了“工程化”的思路。它不只是一个看trace的工具更像一整套性能数据的采集与分析平台。早期我们做性能问题排查大部分时间花在找数据和对时间轴上真正用于思考原因的时间很少现在有了perfetto我可以把时间花在“为什么”上而不是“怎么找到”上。如果你之前一直在用systrace建议从一个小需求开始切换到perfetto抓一次本应用的启动过程跑一下第二、三节提到的SQL看看能不能找到主线程在启动期间具体在忙什么。跑通一次之后你就能感受到那套“时间轴SQL”的组合拳有多顺手了。后面写代码的时候记得顺手加几个TRACE_EVENT埋点时间长了积累下来的数据资产比性能日志有用得多。
网站建设高端定制企业官网