FPS(帧率)是图像领域中的定义,是指画面每秒渲染帧数,FPS 一般在 0-60 之间,低于 30 时人眼能明显感觉到卡顿。页面交互过程中页面展示是否流畅,页面中的动画是否存在卡顿等,都需要通过 FPS 的统计指标作为页面性能的参考依据。

如下图,通过 Chrome devtools 右侧菜单 -> more tools -> Rendering -> 勾选 Frame Rendering Stats,则会在页面左上角显示实时 Frame Rate(FPS)和 GPU 内存使用情况的小窗。


缺点 :生产环境数据无法收集上报,需要人工实时观测;比较适合在开发阶段进行自测
window.requestAnimationFrame() 告诉浏览器你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行回调。回调函数执行次数通常与浏览器屏幕刷新次数相匹配,一般是每秒 60 次。
那么正好可以利用 requestAnimationFrame API 的特性来计算统计 FPS ,原理如下:
假设动画在时间 A 开始执行,在时间 B 结束,耗时 (B-A) s,这期间 requestAnimationFrame 一共执行了 n 次,则此段动画的 FPS = n / (B-A)。
requestAnimationFrame 在不掉帧的情况下一秒内会执行 60 次,即 FPS = 60 / 1。
统计 FPS 核心代码如下:
let lastTime = performance.now();
let frames = 0;
const loop = () => {
const currentTime = performance.now();
frames += 1;
if (currentTime > 1000 + lastTime) {
fps = Math.round((frames * 1000) / (currentTime - lastTime));
frames = 0;
lastTime = currentTime;
console.log(`fps:${fps}`);
}
window.requestAnimationFrame(loop);
}
loop();在生产环境,只需要通过 requestAnimationFrame 统计出监控阶段的回调调用次数,即可计算出对应 FPS,对 FPS 也比较方便进行收集和上报,是目前使用最多的 FPS 统计方式。
缺点:
现有的前端 FPS 统计方式存在一些痛点,解决痛点希望满足以下方面:
目前 alloyperf 的 FPS 统计工具模块,已经实现并满足以上要求,在 CI 流水线定时统计腾讯文档页面 FPS 数据并定时生成性能报告。后面章节,将介绍 alloyperf FPS 统计的实现原理。
alloyperf FPS 统计工具实现主要利用 Selenium WebDriver 和 chrominum:

Selenium 是 ThoughtWorks 提供的一个强大的基于浏览器的开源自动化测试工具集,Selenium WebDriver 是工具集其中一个子工具,主要用于在各种浏览器上自动化测试 web 应用。
它对浏览器提供的原生 API 进行了封装,使其成为一套更加面向对象的 Selenium WebDriver API,使用这套 API 可以操控浏览器的开启、关闭,打开网页,操作界面元素,还可以操作浏览器 devtools 等,由于使用的原生 API,其速度与稳定性都会好很多。
Selenium WebDriver 通过 JsonWireProtocol 协议与各浏览器的 driver 进行通信(例如:ChromeDriver 即为 Chromium 实现了 JsonWireProtocol 协议),Selenium 对不同厂商的各个 driver 进行了封装,如:selenium-chrome-driver、selenium-edge-driver、selenium-firefox-driver 等,可支持各种主流浏览器的自动化测试。
Selenium WebDriver 架构如下图所示:

对于 FPS 的统计,Chrome tracing 是核心也是本文的重点,下面重点介绍。
Tracing ecosystem 即 tracing 的生态系统,tracing 即跟踪应用运行过程并生成记录的行为。Tracing ecosystem 的运行基于"trace 文件",trace 文件包含所有的跟踪记录数据,Tracing ecosystem 包含两种工具:
记录并生成 trace 文件的工具有很多,比如:Android 的 systrace 命令行工具、开源的 adb_trace 等,web 前端常用的有 chrome devtools 中 performance record 功能、chrome tracing 的 record 功能。
解析展示 trace 文件的工具,web 前端常用的 chrome devtools performance、chrome tracing 同样具有这样的强大能力,chrome tracing 相对展示的信息更加详细。

chrome devtools performance 图示

chrome tracing 图示
chrome tracing 是内置在 chrome 中的工具,可用来收集和解析展示非常详细的性能跟踪数据,在 devtools 无法满足需求时,可使用此工具来进行更加复杂或具体的性能分析。
通过 chrome tracing 的 record 按钮进行记录后即可生成对应的跟踪数据,chrome tracing 内部通过 trace viewer 可直接对产生的数据进行解析和展示:

Trace viewer 结果展示
Trace viewer 可以对 record 产生的 trace 数据直接进行展示,也可以 load 对应的 trace json 文件并进行解析展示。展示结果如上图,时序按从左到右排列,通过左侧的 Processes 和 Threads 进行细分,右侧每一个小色块对应一个 TRACEEVENT(即 Chromium 内部 tracing 库生成的单个记录事件点)。
在 trace viewer 中点选对应的 TRACEEVENT 色块,甚至可以直接点击下方的详情跳转到相关的 Chromnium 源码:


跳转 Chromium 源码展示
Chromnium 通过 TRACE_EVENT0 函数将对应的 EVENT 记录到对应的 category,例如上图将 ProxyImpl::NotifyReadyToCommitOnImpl 记录到 cc(即 Chrome Compositor 合成器)。
同时,Trace viewer 结果展示图中,还可以通过菜单选择对应的 flow 展示某个 event 流的轨迹走向,例如单帧在渲染进程中的 flow 大致是经历如下阶段:
所以通过 TRACE_EVENT 的 flow 轨迹,即可以非常精细地看到页面每一帧的具体渲染流程。
Trace Viewer 可以识别四种不同格式的 trace 文件,JSON 类型格式包括 JSON 数组和 JSON 对象,另外两种是 Linux ftrace 数据类型。比较通用的是 JSON 格式,也是 chrome tracing 使用的格式,Linux ftrace 类型本文不做赘述。
JSON 数组(chrome devtools performance 生成格式):
[{"args":{"name":"swapper"},"cat":"__metadata","name":"thread_name","ph":"M","pid":337,"tid":0,"ts":0},
{"args":{"name":"CrBrowserMain"},"cat":"__metadata","name":"thread_name","ph":"M","pid":337,"tid":775,"ts":0}]JSON 对象(chrome tracing 生成格式):
{
"traceEvents":[
{"args":{"name":"swapper"},"cat":"__metadata","name":"thread_name","ph":"M","pid":337,"tid":0,"ts":0},
{"args":{"name":"Compositor"},"cat":"__metadata","name":"thread_name","ph":"M","pid":7546,"tid":42243,"ts":0}
],
"displayTimeUnit": "ns",
"systemTraceEvents": "SystemTraceData",
"otherData": {"version": "My Application v1.0" },
"stackFrames": {...}
"samples": [...],
}两种格式结构略有不同,但每条 TRACE_EVENT 对应的 args 字段基本一致,本文只需关注:
通过以上得出结论:通过 flow 确认每一帧渲染必定经过哪些关键 TRACE_EVENT ,然后分析对应的 trace 文件,即可计算得到 FPS 数据。
下图为帧绘制内容数据的 flow 流向示意图,与 Chrome tracing 的 flow 轨迹对应:

帧绘制内容数据的 flow 流向示意图
如图所示,绘制内容的数据流向要经过几个不同的进程和线程,不同的线程的任务由 Chromnium 中不同模块(对应 category)负责,blink 主要负责主线程、cc 主要负责合成器线程、viz 主要负责 gpu 相关。
在通过 Chrome tracing 跟踪 flow 和跟踪 chromnium 相关源码过程中,主要发现以下关键点:

Commit流程
最终确定每一帧必定走到的 TRACEEVENT 有合成器线程 ScheduledActionBeginMainFrame 阶段,因此选取 cat="cc"、name="Scheduler::NotifyBeginMainFrameStarted"的 event 作为 FPS 统计的关键 TRACEEVENT。
确定 FPS 统计关键 Trace Event 后,核心问题就得到了解决,计算 FPS 大体流程如下:

针对 1.3 中提到的目前现有 web 前端 FPS 统计方式的痛点,alloyperf fps 模块都已经实现了相应的解决。
目前 alloyperf fps 模块已经在腾讯文档 CI 流水线运行,日常输出 FPS 性能报告。 alloyperf 其他模块(首屏统计、内存监测等)正在陆续开发中,后续 FPS 模块也将持续优化支持更多平台和场景的测试,流水线接入更多的应用品类。