We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
前端监控上报领域,已经有很多成熟的实践,例如通过 performance timing api 获取页面的一些性能,耗时指标。
但对于一些前端 js 代码执行性能开销导致的 性能问题,我们通常只能局限在自己设备上,通过 chrome devtool 的 proformance 进行记录,分析,排查。
通过 performance 面板分析线上 Long Task 代码
开发者很难知道 web 应用中的 JavaScript 在真实用户设备上的各种情况下的执行情况,而且无法有效收集堆栈样本。这样的问题排查会存在一些局限:
统计样本过少,结论可能失真。 真实用户的 js 运行情况,在环境,场景,设备,网络等方面,都可能存在差异性,从而导致不一样的 js 运行效率。从单个设备采集得到的 代码性能开销数据,仅适用于个体,而不适用于总体。这可能会导致误导性的结论。
线上缺少 sourcemap,无法定位准确的源码执行函数。
JS-Self-Profiling 是一个新的 API,它提供一个标准的 Profiler API ,
通过提供 API 来操作浏览器底层的代码采样分析,Web 应用可以收集丰富的执行数据,以最小的开销进行聚合和分析。 本质上和 chrome devtool performance 功能类同。
基本原理:以固定的频率,采样运行时的调用堆栈上运行的内容。
JS-Self-Profiling API 暂时处于 ECMAScript Stage 2 draft 阶段。
Document-Policy
# 响应头设置 Document-Policy: js-profiling
不想跑一个服务来配置响应头的话,除了通过一些代理配置工具实现指定响应头设置外,也可以通过一些 chrome extension 配置响应头
// 开始采集,此处最大采集时长 = 10ms * 10000 = 100s const profiler = new Profiler({ sampleInterval: 10, // 采用间隔 maxBufferSize: 10000 // 采样缓冲区最大数量 }); // 很多业务代码执行... // 停止数据采集 const trace = await profiler.stop(); // 上报 采样 数据 reportToServer(trace)
配合 performance timing api ,进行上报页面加载时的 性能上报
const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 }); window.addEventListener('load', async () => { const trace = await profiler.stop(); reportToServer({ timing: performance.timing, trace, }); });
const trace = await profiler.stop(); // trace 数据结构为: /* { "resource": string[], "frames": ProfileFrame[], "stacks": ProfileStack[], "sameples": ProfileSample[], } */
// 例子: { "frames": [], "resource": [ "http://localhost:3000/index.js" "https://lf-cdn-tos.bytescm.com/obj/static/apaas/kunlun/app_dev_main/production/externals/vendors/react-dom/16.13.1/amd/react-dom.production.min.js", "https://lf-cdn-tos.bytescm.com/obj/static/apaas/kunlun/app_dev_main/production/externals/vendors/react/16.13.1/amd/react.production.min.js", "https://lf3-short.ibytedapm.com/slardar/fe/sdk-web/browser.cn.js?bid=kunlun_fe_web&globalName=KSlardarWeb", // ... ], "sameples": [], "stacks": [] }
interface ProfileFrame { name: string // 当前帧对应代码执行的函数名 resourceId: number // 调用函数对应的 文件,可联合上述的 resource 定位到对应的文件 line: number // 函数在 代码文件的第几行, 第几列执行 column: number } // 例子 "frames": [ // Profiler 的初始化 { "name": "Profiler" }, // main 函数, 函数位置在 resource[0]对应的 js 文件中 第 23 行 20 列 { "column": 20, "line": 23, "name": "main", "resourceId": 0 }, { "column": 1, "line": 1, "name": "", // 无对应函数,js 执行的起始 "resourceId": 0 }, // sendTrace函数, 位置... { "column": 19, "line": 5, "name": "sendTrace", "resourceId": 0 }, // doSomething 函数, 位置... { "column": 21, "line": 14, "name": "doSomething", "resourceId": 0 } ],
interface ProfileStack { frameId: number // 对应上述 frame 数组的 index parentId: number // 当前 stack 数组的 index,可以确定函数的调用栈先后顺序关系。 } // 参考例子 "stacks": [ { "frameId": 2 // 对应上述的 frames[2],无对应函数,js 执行的起始 }, { "frameId": 1, // 对应上述的 frames[1], 函数 main() "parentId": 0 }, { "frameId": 0, // 对应 Profiler "parentId": 1 // stacks[1],即父层级是 main 函数,所以调用栈是 main() -> Profiler }, { "frameId": 4, // 以此类推,调用栈: main() -> doSomething() "parentId": 1 }, { "frameId": 3, // 调用栈: main() -> doSomething() -> sendTrace() "parentId": 3 } ]
interface ProfileSample { // 相对于 页面初始化的时间。 // 例如,页面打开 10s 过后,开始 profiling, 第一个 samples[0].timestamp 值是 10000 ,单位 ms timestamp: number // 对应上述 stack 数组的 index stackId?: number } // 例子 samples: [{ // 第一帧的 js 行为,调用栈是 stacks[2],即 main()->Profiler "stackId": 2, "timestamp": 256.27000004053116 }, { // 过了 10ms,进行第2次采集,该时间节点的 js 行为,调用栈是 stacks[4], // 即 main() -> doSomething() -> sendTrace() "stackId": 4, "timestamp": 267.8799999952316 }, { // 又过了 10ms,进行第3次采集,该时间节点的 js 行为,调用栈是 stacks[3] // 即 main() -> doSomething() "stackId": 3, "timestamp": 278.2450000047684 }, // ... ]
根据 samples 的时序描述,可以得出如下的调用堆栈时序图
已知 Profiler 采集到的数据包含了以下信息
根据这些信息,可以将采集到的 trace 数据可视化成 火焰图。
// TODO: [Demo 访问地址]
每个调用堆栈信息一般都包含了 代码调用时的:
如果获取到 resourceURI 源码对应的 sourcemap 文件,即逆向出 混淆加密前的 ts 代码源码,以及该函数对应 ts 代码的位置。
JS-Self-Profiling API 是为了采集业务 js 代码的执行开销。
对于 JS-Self-Profiling 开销大小,是否会对业务造成负面影响的问题, FaceBook 对此有过统计:启用了 JS-Self-Profiling 的应用,加载速度比原本变慢了,幅度 < 1% 。 也就是说,采集行为本身对业务的影响微乎其微。 结论参考来源: https://github.com/WICG/js-self-profiling/blob/main/doc/tpac-2021-slides.pdf
根据 Web 应用的复杂度不同,Profiler 的 sampleInterval 采样率配置不同,采集得到的 profile 数据大小也不一样。
根据 aPaaS 开发后台 Profile 采集数据大小统计: 在 采样间隔为 10ms ,平均采集 1s ,会产生 1~2kb (gzip 后) 的数据包。
这可能会带来比较高的流量开销,以及数据存储成本。
数据预过滤。 实际应用中,开发者可能只关心一些 long task 的函数调用场景,其余绝大部分的 js 代码执行调用栈数据可能都无需关注。计算过滤出 long task 的 trace 数据,丢弃掉其他数据,既可以降低数据块大小,又能够减少干扰信息。
应用场景控制。 尽管 JS-Self-Profiling 对业务影响有限,但考虑其对业务运行时依然有细微影响。应该尽可能控制 仅在某些特定的需要性能分析的页面或者业务场景 中启用,不应滥用。
采样率控制。 控制采样率,或者只在指定特征的群体启用。
还处于草案阶段,目前主流浏览器仅 chrome 浏览器内核版本 94+ 支持
业界使用情况: Sentry Profiling for Browser [Beta]
The text was updated successfully, but these errors were encountered:
No branches or pull requests
1. 背景
前端监控上报领域,已经有很多成熟的实践,例如通过 performance timing api 获取页面的一些性能,耗时指标。
但对于一些前端 js 代码执行性能开销导致的 性能问题,我们通常只能局限在自己设备上,通过 chrome devtool 的 proformance 进行记录,分析,排查。
开发者很难知道 web 应用中的 JavaScript 在真实用户设备上的各种情况下的执行情况,而且无法有效收集堆栈样本。这样的问题排查会存在一些局限:
统计样本过少,结论可能失真。
真实用户的 js 运行情况,在环境,场景,设备,网络等方面,都可能存在差异性,从而导致不一样的 js 运行效率。从单个设备采集得到的 代码性能开销数据,仅适用于个体,而不适用于总体。这可能会导致误导性的结论。
线上缺少 sourcemap,无法定位准确的源码执行函数。
2. 简要概述
2.1 JS-Self-Profiling 是什么?
JS-Self-Profiling 是一个新的 API,它提供一个标准的 Profiler API ,
通过提供 API 来操作浏览器底层的代码采样分析,Web 应用可以收集丰富的执行数据,以最小的开销进行聚合和分析。 本质上和 chrome devtool performance 功能类同。
基本原理:以固定的频率,采样运行时的调用堆栈上运行的内容。
JS-Self-Profiling API 暂时处于 ECMAScript Stage 2 draft 阶段。
2.2 JS-Self-Profiling 怎么用?
Document-Policy
允许使用 js-self-profiling API不想跑一个服务来配置响应头的话,除了通过一些代理配置工具实现指定响应头设置外,也可以通过一些 chrome extension 配置响应头
配合 performance timing api ,进行上报页面加载时的 性能上报
3. 实践探索
3.1 Profile 采集上报
3.1.1 采样数据结构
例如,以下的数据样例说明采样中, 函数 main,sendTrace, doSomething 被执行过了。
根据 stacks 数据可以得到所有函数的调用关系。例如以下例子可以得出 代码函数调用栈 A() -> B() -> C()
根据 samples 的时序描述,可以得出如下的调用堆栈时序图

3.2 数据可视化
已知 Profiler 采集到的数据包含了以下信息
根据这些信息,可以将采集到的 trace 数据可视化成 火焰图。
// TODO: [Demo 访问地址]

3.3 配合 SourceMap 定位问题代码
每个调用堆栈信息一般都包含了 代码调用时的:
如果获取到 resourceURI 源码对应的 sourcemap 文件,即逆向出 混淆加密前的 ts 代码源码,以及该函数对应 ts 代码的位置。
3.4 采集优化
3.4.1 API 自身性能开销
JS-Self-Profiling API 是为了采集业务 js 代码的执行开销。
因为即便是最简单的 js 代码 i++ 执行,都会占用 js 线程的资源,区别是占用资源的多与少。
对于 JS-Self-Profiling 开销大小,是否会对业务造成负面影响的问题,
FaceBook 对此有过统计:启用了 JS-Self-Profiling 的应用,加载速度比原本变慢了,幅度 < 1% 。 也就是说,采集行为本身对业务的影响微乎其微。
结论参考来源: https://github.com/WICG/js-self-profiling/blob/main/doc/tpac-2021-slides.pdf
3.4.2 数据块大小
根据 Web 应用的复杂度不同,Profiler 的 sampleInterval 采样率配置不同,采集得到的 profile 数据大小也不一样。
根据 aPaaS 开发后台 Profile 采集数据大小统计:
在 采样间隔为 10ms ,平均采集 1s ,会产生 1~2kb (gzip 后) 的数据包。
这可能会带来比较高的流量开销,以及数据存储成本。
3.4.3 优化策略
数据预过滤。
实际应用中,开发者可能只关心一些 long task 的函数调用场景,其余绝大部分的 js 代码执行调用栈数据可能都无需关注。计算过滤出 long task 的 trace 数据,丢弃掉其他数据,既可以降低数据块大小,又能够减少干扰信息。
应用场景控制。
尽管 JS-Self-Profiling 对业务影响有限,但考虑其对业务运行时依然有细微影响。应该尽可能控制 仅在某些特定的需要性能分析的页面或者业务场景 中启用,不应滥用。
采样率控制。
控制采样率,或者只在指定特征的群体启用。
5. 数据流转链路
还处于草案阶段,目前主流浏览器仅 chrome 浏览器内核版本 94+ 支持

业界使用情况: Sentry Profiling for Browser [Beta]
The text was updated successfully, but these errors were encountered: