本规范描述了一种 API,允许 Web 应用程序控制采样分析器,以 测量客户端 JavaScript 的执行时间。
目前,复杂的 Web 应用程序只能有限地了解客户端的 JS 执行时间花在了何处。由于无法高效地 收集堆栈样本,应用程序只能在其代码中插入 不精确且可能显著降低 执行速度的分析钩子。通过提供用于操作采样分析器的 API, 应用程序能够以极低的开销收集丰富的执行数据, 以便进行汇总和分析。
以下示例演示了用户如何分析一项开销较大的操作,并每隔 10ms 收集一次 JS 执行 样本。可以将跟踪数据发送到服务器进行分析,以调试异常值,并汇总分析 JS 执行 特征。
const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });
const start = performance.now();
for (let i = 0; i < 1000000; i++) {
doWork();
}
const duration = performance.now() - start;
const trace = await profiler.stop();
const traceJson = JSON.stringify({
duration,
trace,
});
sendTrace(traceJson);
另一个常见的实际场景是分析整个页面加载过程中的 JS。此示例分析 onload 事件,并将性能计时数据与跟踪数据一起发送。
const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });
window.addEventListener('load', async () => {
const trace = await profiler.stop();
const traceJson = JSON.stringify({
timing: performance.timing,
trace,
});
sendTrace(traceJson);
});
// 页面其余部分的 JS 初始化逻辑
样本是对给定时间点 瞬时执行状态的描述。每个样本都与一个堆栈相关联。
堆栈是一个帧列表,必须按照 从最外层帧到最内层帧的顺序依次排列。
帧是堆栈上下文中的一个元素, 包含有关当前执行状态的信息。
分析会话是样本的抽象生产者。每个会话具有:
{started, paused, stopped} 之一。不要求用户代理以此频率获取样本。但是,建议 优先按照此频率进行采样,以 生成质量更高的跟踪数据。
应当支持同一页面上的多个分析会话。
在 started 状态下,每当采样间隔结束时,用户代理应当尽最大努力
[= in parallel =]执行获取样本算法来
捕获样本。
在 paused 和 stopped 状态下,用户代理不应捕获样本。
分析会话必须以 started 状态开始。
用户代理可以将会话从 started 转换为 paused,也可以从 paused
转换为 started。
如果浏览上下文不在前台,建议用户代理暂停对分析会话的 采样。
处于 stopped 状态的会话不得转换为 started 或 paused 状态。
要在给定一个分析会话的情况下获取样本,请执行以下步骤:
stopped,
然后返回。
要在给定绑定到 stack 的执行上下文堆栈的情况下获取堆栈 ID,请执行 以下步骤:
undefined。undefined,则返回 parentId。要在给定绑定到 context 的执行 上下文的情况下获取帧 ID,请执行以下步骤:
undefined。ScriptOrModule。
ScriptOrModule。
上述逻辑旨在确保无法访问的
脚本所调用的内置函数不会暴露在跟踪数据中,方法是使用调用
它们的 ScriptOrModule 进行归属。
“包含调用 |instance| 的函数的 ScriptOrModule”
应当得到更严格的定义。我们可以利用堆栈中最顶层且
定义了 ScriptOrModule 的执行上下文来提供此信息,但这并不理想——
理论上可能存在其他机制,将内置函数加入执行
上下文堆栈,在这种情况下归属将无效。
undefined。true,则返回 undefined。
此检查确保我们不会包含通过 CORS 跨源响应提供的跨源脚本中的堆栈帧。我们可能需要考虑重命名静默错误,以便更好地反映 此用例。
要为 list 中的 item 获取元素 ID,请执行以下步骤:
[Exposed=(Window, Worker)]
interface Profiler : EventTarget {
readonly attribute DOMHighResTimeStamp sampleInterval;
readonly attribute boolean stopped;
constructor(ProfilerInitOptions options);
Promise<ProfilerTrace> stop();
};
sampleInterval 属性必须反映关联的分析会话的采样间隔,并以 DOMHighResTimeStamp 表示。
当且仅当分析会话的状态为
stopped 时,stopped 属性必须为 true。
RangeError。
"js-profiling-mode" 的策略值。令
|profilingMode| 为其结果。
"eager" 也不是 "lazy":
"js-profiling" 的策略值。令
|jsProfilingEnabled| 为其结果。
"NotAllowedError"
DOMException。
"NotAllowedError" DOMException。
«[{{ProfilerTrace/resources}} → «», {{ProfilerTrace/frames}} → «», {{ProfilerTrace/stacks}} → «», {{ProfilerTrace/samples}} → «»]»。
停止分析器并返回跟踪数据。此方法必须运行以下步骤:
stopped,则返回以 "InvalidStateError"
DOMException [= a promise
rejected with =]。
stopped。
typedef DOMString ProfilerResource;
dictionary ProfilerTrace {
required sequence<ProfilerResource> resources;
required sequence<ProfilerFrame> frames;
required sequence<ProfilerStack> stacks;
required sequence<ProfilerSample> samples;
};
resources 属性必须返回由获取 样本算法设置的 ProfilerResource 列表。
frames 属性必须返回由获取样本 算法设置的 ProfilerFrame 列表。
stacks 属性必须返回由获取样本 算法设置的 ProfilerStack 列表。
samples 属性必须返回由获取样本 算法设置的 ProfilerSample 列表。
受 V8 跟踪 事件格式 和 Gecko 分析格式的启发, 此表示形式旨在易于并能够高效地序列化。
dictionary ProfilerSample {
required DOMHighResTimeStamp timestamp;
unsigned long long stackId;
};
timestamp 必须返回其初始化时的值。
stackId 必须返回其初始化时的值。
dictionary ProfilerStack {
unsigned long long parentId;
required unsigned long long frameId;
};
parentId 必须返回其初始化时的值。
frameId 必须返回其初始化时的值。
dictionary ProfilerFrame {
required DOMString name;
unsigned long long resourceId;
unsigned long long line;
unsigned long long column;
};
name 必须返回其初始化时的值。
resourceId 必须返回其初始化时的值。
line 必须返回其初始化时的值。
column 必须返回其初始化时的值。
dictionary ProfilerInitOptions {
required DOMHighResTimeStamp sampleInterval;
required unsigned long maxBufferSize;
};
ProfilerInitOptions 必须支持以下字段:
本规范在文档 策略中定义了配置 点,用于控制分析能力和初始化行为。
js-profiling-mode
本规范定义了一个名称为 js-profiling-mode 的配置
点。其类型
为 enum,允许值为 "eager" 和 "lazy"。其默认
值为空字符串。
设置后,此策略授权脚本使用 JS 自我分析 API。
eager
当 js-profiling-mode 设置为 "eager" 时,它向用户代理表明
预计将在页面加载期间进行分析。
用户代理可以使用此提示尽早初始化分析基础设施,并在文档加载期间尽早存储所需的 元数据和预热分析组件。但是, 即使未主动使用分析,这也可能引入不可忽略的性能开销。此 开销可能会对首次内容绘制(FCP)和最大内容 绘制(LCP)等指标产生负面影响。
lazy
当 js-profiling-mode 设置为 "lazy" 时,它向用户代理表明
将有条件地使用分析。
用户代理可以使用此提示,将与分析相关的初始化开销推迟到首次实例化 Profiler 时,从而在未主动使用分析的关键渲染阶段避免 性能开销。但是,如果初始化发生在用户交互 处理期间,则可能对下次绘制交互(INP)产生负面影响。此模式特别 适用于根据采样决策有条件地启用分析的文档。
js-profiling(已弃用)
本规范还定义了一个名称为 js-profiling 的配置
点。其类型
为 boolean,默认
值为 false。
启用后,此策略授权脚本使用 JS 自我分析 API,并指示用户代理
尽早初始化分析基础设施(在语义上等同于
js-profiling-mode=eager)。
js-profiling 布尔配置点已弃用,建议改用
js-profiling-mode。如果二者都已指定,则 js-profiling-mode 优先,
并且必须忽略 js-profiling。实现应当支持 js-profiling
以实现向后兼容,但将来可以移除支持。
为了实现用户代理自动化和应用程序测试,本文档定义了以下 [[WebDriver]] 扩展命令。
| HTTP 方法 | URI 模板 |
|---|---|
| POST | `/session/{session id}/forcesample` |
强制采样扩展命令强制 所有[=分析会话=][=获取样本=],以便实现更具确定性的测试。
远端步骤如下:
以下各节详细说明了此 API 的一些隐私和安全设计选择,并阐述了针对 各类攻击的防护策略。
此 API 通过要求所有经由获取样本算法包含的函数都必须定义在通过
静默错误属性以 CORS 同源方式提供的脚本中,来避免暴露跨源脚本的内容。
浏览器内置函数(例如 performance.now())也必须仅在由
[= CORS-same-origin =]脚本调用时才被包含。
因此,除了通过手动插桩已经能够获得的信息外,此 API 不会暴露任何有关 跨源脚本内容或执行特征的新信息。如果用户代理选择支持极低的 采样间隔值(例如小于 一毫秒),则建议验证这一点仍然成立。
通过获取样本算法中的领域检查,此 API 不应能够观察到跨源执行上下文。
因此,与分析器共享代理的跨源 iframes 和其他执行上下文,
其执行情况不会通过此
API 被观察到。
对于任何可能引入新的高分辨率计时 信息来源的 API,时序攻击始终是一个需要关注的问题。跟踪数据中收集的时间戳应当与 [[?HR-Time]] 的 当前高分辨率时间使用相同来源,以避免暴露新的侧信道攻击途径。
请参阅 [[?HR-Time]] 中关于时钟 分辨率的讨论。