此段代码压缩后会变成
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 最有可能影响性能的地方为:
- 监控初始化时执行各类监听的过程
- 监控事件上报请求对业务的影响
- SDK 维护数据缓存时的内存使用情况
性能衡量过程使用 Benchmark 性能衡量工具的目的便是为了知道 SDK 运行过程中每一个函数执行的耗时,给业务带来多大的影响,是否会引起 longtask 。由于我们的监控 SDK 包含了性能、请求、资源等各类前端监控能力,这些功能的实现依赖对页面各类事件的监听、性能指标的获取、请求对象的包装 。除此之外,SDK还提供给用户(开发者)调用的方法,例如配置页面信息、自定义埋点、更改监控行为等能力 。根据 SDK 以上行为和能力,我们将测试分为两个模块:
- 接入 SDK 后自动运行的各类监控,这些行为大部分会在页面加载之初执行,若此部分性能劣化,会严重影响到所有前端业务用户的首屏加载 。
- 用户端(开发者)调用的方法,我们会将此类方法包装成 client 对象以 npm 包的形式给开发者调用,这部分方法的执行由用户控制 , 可能存在频繁调用的情况,因此也应避免耗时过长的调用出现 。
下面我们以使用 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()
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 使用 nvm 对 node 进行版本管理
- 四 Selenium4.0+Python3系列 - 常见元素操作(含鼠标键盘事件)
- iQOO8系列配置_iQOO8系列参数详情
- 30 《吐血整理》高级系列教程-吃透Fiddler抓包教程-Fiddler如何抓取Android7.0以上的Https包-番外篇
- 从0搭建vue3组件库: 如何完整搭建一个前端脚手架?
- 树的邻接矩阵、双亲孩子表示法…… C++ 不知树系列之初识树
- .NET Core C#系列之XiaoFeng.Data.IQueryableX ORM框架
- flutter系列之:永远不用担心组件溢出的Wrap
- 荣耀x20评测_荣耀x20评测表现
- 抛砖系列之redis监控命令