前端监控系列4 | SDK 体积与性能优化实践( 二 )

此段代码压缩后会变成
let d="addEventLister",e="load";a[d](e,cb),b[d](e,cb),c[d](e,cb);我们还可以使用 TSTransformer 或者 babel plugin 来帮我们自动地完成上述过程 。

值得注意的是,这个方法在 web 端并不能取得很好的收益,因为浏览器在传输数据时会做 gzip 压缩,已经将重复信息用最高效的算法压缩了,我们做的并不会比 gzip 更好 。但是在需要嵌入移动端 app 的监控 SDK 来说,这一做法能减少约 10 ~ 15% 产物体积 。
除了体积优化以外,随着需求不断增加,功能不断完善,不可避免的会影响到 SDK 的性能 。接下来,我们介绍如何测量并优化 SDK 的性能 。
使用工具进行性能衡量通常来说,监控类 SDK 最有可能影响性能的地方为:
  1. 监控初始化时执行各类监听的过程
  2. 监控事件上报请求对业务的影响
  3. SDK 维护数据缓存时的内存使用情况
接下来 , 我们着重从以上几个维度来衡量并优化 SDK 的性能 。
性能衡量过程使用 Benchmark 性能衡量工具的目的便是为了知道 SDK 运行过程中每一个函数执行的耗时,给业务带来多大的影响,是否会引起 longtask 。由于我们的监控 SDK 包含了性能、请求、资源等各类前端监控能力,这些功能的实现依赖对页面各类事件的监听、性能指标的获取、请求对象的包装 。除此之外,SDK还提供给用户(开发者)调用的方法,例如配置页面信息、自定义埋点、更改监控行为等能力 。根据 SDK 以上行为和能力,我们将测试分为两个模块:
  1. 接入 SDK 后自动运行的各类监控,这些行为大部分会在页面加载之初执行,若此部分性能劣化,会严重影响到所有前端业务用户的首屏加载 。
  2. 用户端(开发者)调用的方法,我们会将此类方法包装成 client 对象以 npm 包的形式给开发者调用,这部分方法的执行由用户控制 , 可能存在频繁调用的情况,因此也应避免耗时过长的调用出现 。
在前一篇文章前端监控系列1| 字节的前端监控SDK是怎样设计的中我们讲到,我们的 SDK 在设计时已经做到的尽可能的解耦,各个模块各司其职 , 这一特点非常便于我们针对各个模块方法进行单独的性能衡量 。
下面我们以使用 benny 这一开源工具为例,展示一段方便理解 benchmark 过程的伪代码,仅作参考:
benny 是一个非常简单易用的 benchmark 工具 , 通过 suite 方法创建测试用例组合 , 通过add方法添加需要测试的函数 , cycle方法用于多次循环执行测试用例 , complete用于添加测试完成之后的回调函数 。更多详细的使用说明可以查阅官方文档 。
const { suite, add, cycle, complete, save } = require('benny')// 衡量 SDK 各类监控初始化运行性能suite('collectors setup',add('route', () => route(context)),add('exception', () => exception(context)),add('ajax', () => ajax(context)),add('FCP', getFCP),add('LCP', getLCP),add('longtask', getLongtask),cycle(),complete(),)// 衡量 Client 实例方法耗时suite('npm client',add('set config', () => client.config({pid})),add('set context', () => client.context.set({ something })),add('send custom pv', () => client.sendPageView(pid)),add('send custom event', () => client.sendCustom(ev)),// ...cycle(),complete(),)通常这类 benchmark 工具都是在 Node 上执行的,但是我们的 SDK 是个前端监控 SDK,依赖了非常多的浏览器环境对象,我们几乎不可能在 Node 环境去创造或模拟这些对象,我们有没有办法在浏览器里去运行这段脚本 , 做性能自动化测试呢?
利用 Puppeteer 在浏览器环境中执行 Benchmark由于我们的前端监控依赖浏览器环境 , 我们可以将上述 benchmark 测试代码打包成 commonjs 之后放入 headless chrome 浏览器中执行 , 并通过 puppeteer 收集执行结果 。
Puppeteer 是一个 Node 模块 , 提供了通过 Devtool Protocol 控制 Chrome 或者 Chromium 的能力 。Puppeteer 默认运行 Chrome 的无头版本 , 也可以通过设置运行 Chrome 用户界面版 。
下面是一段方便理解操作 puppeteer 过程的伪代码,仅作参考,实际情况较为复杂,需要等待未完成的异步请求等:
const browser = await puppeteer.launch()const page = await browser.newPage()const cdp = await page.target().createCDPSession()// 用于 benchmark 脚本和 puppeteer 之间的通信,用以收集结果await page.evaluate(() => (window.benchmarks = []))// 将 pushResult 方法暴露给浏览器,来将结果收集到 node 端await page.exposeFunction('pushResult',(result: any) => benchmark.results.push(result))await cdp.send('Profiler.enable')await cdp.send('Profiler.start')// 开始执行 benchmarkawait page.addScriptTag({content: file.toString(),})await Promise.race([timeout, allBenchmarksDone()])// profile 可用于绘制火焰图const { profile } = await cdp.send('Profiler.stop')await page.close()

推荐阅读