性能收集
自动采集页面导航、长任务、慢资源、Web Vitals,并支持手动性能事务与可控的长时间后台恢复刷新。
性能收集由 PerformancePlugin 提供,需要 opt-in 显式装配,受 performance Consent 管控。可在 Playground · 性能收集 在线体验手动事务与 Web Vitals。
如何使用
import { TripoLens, PerformancePlugin } from '@tripo3d/lens';
const trace = new TripoLens({
app: 'shop',
configUrl: '...',
endpoint: '...',
plugins: [
new PerformancePlugin(), // 全默认即采集导航 / 长任务 / 慢资源 / Web Vitals
],
});
await trace.init();装上即生效,无需任何业务改动:导航性能、长任务、慢资源、Web Vitals 都会自动采集。SSR 渲染性能默认关,按需在 装配方式 里打开。
验证是否生效
正常加载页面后,在 Network 面板观察发往 /v2/trace/events 的请求(导航性能在 load 后上报)。
Nuxt 项目零接线
@tripo3d/lens/nuxt(Nuxt Module)默认开启性能采集,并独有 SSR 渲染性能(见 进阶 · SSR 渲染),无需手写 new PerformancePlugin()。错误与行为埋点在该 Module 里同样默认开启,可显式配 errors: false / tracking: false 关闭。完整选项见 Nuxt 接入。
性能子类:op 即子类型
所有性能事件统一为 type: 'performance',子类由 payload.op 区分,信封尾段 <app>.Perf。共七类采集器(导航 / 长任务 / 慢资源 / Web Vitals / 手动打点 / SSR / stale refresh),对应下表八种 op 形态与各自开关——后面的装配、手动、进阶都按这些子类展开:
payload.op | 来源 | 开关 |
|---|---|---|
navigation | 每页一条导航复合事件(PerformanceNavigationTiming) | 始终采 |
longtask | 长任务,LoAF 脚本级归因 / 不支持时降级聚合 | 始终采 |
resource.* | 耗时超阈值的慢资源 | 始终采(阈值 slowResourceMs / 上限 maxSlowResource) |
web-vital.* | LCP / CLS / INP 等官方五项 | webVitals(默认开) |
ui.flow 等自定义 | 手动事务 startTransaction | 手动 |
hydration | SSR 水合标记 markHydration | 手动 / Nuxt 自动 |
ssr.render / ssr.request | SSR 渲染 waterfall | captureSsr(默认关,见 SSR 采集) |
navigation.stale-refresh | 长时间后台恢复刷新决策与确认(tags.trigger 区分 on-show / on-hide) | 远程 performance.stalePageRefresh(默认关) |
各子类 payload:锚点透传,差值归后端
自动采集统一遵循「锚点透传」范式:data 里放的是原始时间轴锚点(相对 timeOrigin 的毫秒时刻,round ms),各阶段差值(DNS / TCP / TTFB / DOM 解析……)的计算归后端;顶层 duration 只是冗余便捷值,不是真实数据来源。
navigation(每页一条,load 后上报;start_at = 0、duration = loadEventEnd 仅为便捷值),data 字段分四组:
| 分组 | 字段 |
|---|---|
| 时间轴锚点(相对 timeOrigin) | fetchStart domainLookupStart domainLookupEnd connectStart connectEnd secureConnectionStart requestStart responseStart responseEnd redirectStart redirectEnd domInteractive domContentLoadedEventStart domContentLoadedEventEnd domComplete loadEventStart loadEventEnd workerStart |
| 绝对基准 | timeOrigin(epoch 毫秒;把相对轴锚回真实时间,跨端 / SSR 对齐用) |
| 体积 | transferSize encodedBodySize decodedBodySize |
| 元信息 | nextHopProtocol redirectCount type;SSR 场景另带 renderId |
resource.*(慢资源逐条上报):op 为 resource. 加 initiatorType(无则归 resource.other),start_at = startTime;data 含 responseEnd(相对 timeOrigin 的锚点)+ 体积三件套 transferSize / encodedBodySize / decodedBodySize。
web-vital.*:data.value 为指标值(CLS 无量纲保留 4 位小数,其余 round 取整 ms),另带可选 rating / navigationType / renderId(供后端 join 同一次加载的 vital 与 navigation)。守卫:负值 / NaN / Infinity / 超越界上限(CLS 10、INP 30000、FCP·LCP·TTFB 60000)静默丢弃。
hydration:data 含 hydrationStart(= domInteractive)与 hydrationComplete(= 打点时刻)双锚点——与 navigation 同坐标系(相对 timeOrigin),差值交后端;start_at = hydrationStart,duration 仅为冗余便捷值;有 renderId 才带 data.renderId。
装配方式
PerformancePlugin 构造参数是 PerformancePluginOptions(字段全可选,分采集开关、慢资源两组):
// 全用默认:采集导航 / 长任务 / 慢资源 / Web Vitals(不含 SSR 渲染)
new PerformancePlugin();
// 打开 SSR 渲染性能采集(还需服务端注入容器)
new PerformancePlugin({ captureSsr: true });
// 全字段细调(值均为默认值,按需覆盖)
new PerformancePlugin({
// 采集开关
webVitals: true, // Web Vitals 五项(默认开)
captureSsr: false, // SSR 渲染性能(默认关,需服务端注入容器)
// 慢资源
slowResourceMs: 1000, // 慢资源阈值 ms
maxSlowResource: 20, // 每会话慢资源上限
});PerformancePluginOptions
先速查,再展开重要项:
| 字段 | 类型 / 默认 | 说明 |
|---|---|---|
webVitals | boolean / true | 启用 Web Vitals 五项 |
captureSsr | boolean / false | SSR 渲染性能(默认关,需手动打开) |
slowResourceMs | number / 1000 | 慢资源阈值 ms |
maxSlowResource | number / 20 | 每会话慢资源上限 |
采集开关
webVitals(默认true):启用 Web Vitals 五项指标采集(web-vitals 已进主包,无需另装)。captureSsr(默认false):采集 SSR 渲染性能,需手动打开;需服务端先注入容器,否则即便打开也自然 no-op、零成本(与ErrorPlugin.captureSsr对称)。机制见 进阶 · SSR 渲染。
慢资源
slowResourceMs(默认1000):资源加载耗时≥此值即判为慢资源逐条上报。maxSlowResource(默认20):每会话慢资源上报上限,超限停采防风暴。
长时间后台恢复刷新(stale page refresh)
该能力默认关闭,只在 PerformancePlugin 已装配、performance consent 已授权,且远程配置中的 performance.stalePageRefresh.enabled 为 true 时运行。它用于页面在后台停留较久后恢复时,避免继续使用可能已经过期的页面状态。
{
"performance": {
"stalePageRefresh": {
"enabled": true,
"thresholdMs": 1800000,
"routeMode": "allowlist",
"routes": ["/model/**", "/task/**"]
}
}
}只有后台时长 严格大于 thresholdMs 才进入刷新候选;相等时不刷新。路由规则只匹配 pathname,不包含 query/hash:* 匹配单层,/** 匹配当前路径及其后续层级(例如 /task/** 同时匹配 /task 与 /task/1)。后台期间 href 变化会跳过,canReload 业务 veto 返回 false 或抛错也会跳过。
决策事件与刷新确认事件都使用 event_type=performance、payload.op=navigation.stale-refresh:
stage=decision:记录reload或skip、原因(threshold_exceeded、route_changed、route_filtered、business_veto、invalid_clock等)。stage=confirmed:刷新前写入短期 receipt;新页面仅在 pathname 一致且 receipt 未超过 10 分钟时确认一次,确认后立即删除。receipt 写入失败按 fail-safe 处理,不刷新。
两条触发路径
该能力有两条独立的触发路径,tags.trigger 区分:
on-show(默认,1.3.0 起):用户切回页面时判定,后台停留严格大于thresholdMs才刷新。刷新过程用户可见。on-hide(需显式开启onHide.enabled):页面从加载起存活超过minPageAgeMs后发生 hide,延迟delayMs在后台执行一次刷新。用户回来看到的已是新页面。
on-hide 路径有三重保证不会「切一下就刷一下」:页龄不足 minPageAgeMs 的 hide 一律静默不武装;延迟窗口内用户返回即取消(零事件、零刷新);单次语义——后台刷出的新页面出生即 hidden,没有 visible→hidden 转变,不会再次武装。因此每个标签页至多每 minPageAgeMs 刷一次,且每次都要求一次真实的用户切走动作。
两条路径是抢先/兜底关系而非竞争:延迟未到用户就回来时由 on-show 按自己的 thresholdMs 判定(间隔不足则不刷);后台刷新被业务 veto 或定时器被浏览器冻结时,on-show 路径在用户回来时兜底接管。
on-hide 路径特有的两个观测字段:data.pageAgeMs(武装时页龄)与 tags.reason = timer_missed(定时器被冻结未点火,用户返回时补报)——后者是后台定时器实际点火率的唯一数据源。
止血通道按粒度递增:onHide.enabled: false(只关后台刷新)→ stalePageRefresh.enabled: false(连 on-show 一起关)→ killSwitch: true(全 SDK 熔断)。
上线建议先使用窄 allowlist 和较长阈值观察 decision/confirmed 比例,再逐步扩大路由;紧急情况下将远程 killSwitch 设为 true,或关闭 stalePageRefresh.enabled。
手动性能:事务与子 Span
自动采集之外,可手动度量一段业务流程的耗时。startTransaction 开启一个事务,startChild 在其下开子 Span;各自 finish() 时各 emit 一条 performance 事件,duration 由 start 到 finish 自动计算;事务本身与子 Span 均带 data.transaction_id(自包含关联,后端无需额外上下文即可归组),子 Span 另可带 data.description(传了才有)。
// 1. 开启事务(op 缺省 'ui.flow')
const tx = trace.startTransaction({ name: 'checkout', op: 'user.flow' });
// 2. 事务内的子阶段(可多个,串行 / 并行均可)
const span = tx.startChild({ op: 'http.client', description: 'POST /v2/orders' });
await createOrder();
span.finish(); // emit 一条子 span
// 3. 结束事务
tx.finish(); // emit 事务本身| 方法 | 签名 | 说明 |
|---|---|---|
trace.startTransaction | ({ name, op? }) => Transaction | 开启事务;op 缺省 'ui.flow'。未装插件 / 未授权时返回 NOOP 事务、不抛错 |
tx.startChild | ({ op, description? }) => Span | 事务下的子阶段;description 作为 span 名(缺省取 op) |
tx.finish() / span.finish() | () => void | 结束并 emit;duration = finish − start,自动计算 |
trace.markHydration | () => void | 标记 SSR 水合完成,emit 一条 hydration span(仅首次有效,重复调用 no-op) |
缺能力安全 no-op
未装 PerformancePlugin 或未授权 performance 时,startTransaction 返回 NOOP 事务,其 startChild / finish 与 markHydration 全是安全空操作——业务代码无需判空。
const tx = trace.startTransaction({ name: 'flow' }); // 即便没装插件也安全
tx.startChild({ op: 'step' }).finish();
tx.finish();SSR 水合标记
markHydration() 在客户端注水完成时打点,SDK 组一条 hydration span(op: 'hydration'):data 透传 hydrationStart(= domInteractive)/ hydrationComplete(= 打点时刻)双锚点,与 navigation 同坐标系(相对 timeOrigin),差值交后端;start_at = hydrationStart,duration 仅为冗余便捷值(保非负);有 renderId 才带 data.renderId。Nuxt 项目由适配器自动调用,手写 SSR 才需在水合后自行调用一次,详见 SSR 采集。
进阶:长任务归因与 SSR 渲染
longtask 的脚本级归因与 ssr.* 渲染 waterfall 是两块独立机制,单独说清。
长任务归因(LoAF)
长任务为聚合上报:onHide(visibility hidden)/ onUnload 时 flush 一条汇总(count = 0 不发),flush 后清零继续累加——因此一次会话可能发出多条 summary。data 形态:
- 基础:
count/maxDuration/totalDuration(ms)。 - 脚本级归因(Chromium 123+,标记
tags.src='loaf'):data.scripts为会话内耗时最重的 top-5 脚本,每项{ source, fn, duration, count }——source为脱敏后的脚本 URL、fn为函数名,可直接定位到卡顿代码。非 Chromium / 旧版浏览器自动降级为无归因的 Long Tasks 聚合(仅基础三项)。
单脚本耗时低于 5ms 不计入归因;
source去除 query/hash 并截断,控制上报体积与基数。
SSR 渲染性能:接入与实现
SSR 渲染发生在浏览器之前,客户端 SDK 看不到。lens 用「服务端只采集 + 注入容器、客户端注水后统一上报」补上这段(与 ErrorPlugin.captureSsr 同一套机制,容器独立)。
接入(两侧,详见 SSR 采集):
- 客户端:
new PerformancePlugin({ captureSsr: true })(默认关);Nuxt 适配器已默认接好。 - 服务端:采集渲染各阶段耗时 + 出站请求计时,注入
window.__TRIPO_SSR_PERF__容器。服务端只采集 + 注入、绝不直发请求。 - 容器不存在自然 no-op——所以客户端先打开也安全、零成本。
实现:
- 一次性读取:
setup时读一次window.__TRIPO_SSR_PERF__(读后清空),expandSsrPerf展开成ssr.render(1 条)+ssr.request(每出站 1 条)。 - renderId 贯穿:从容器取出的
renderId注入到本次hydration/navigation/web-vital.*事件——后端按renderIdjoin,用render[0, totalMs]+ 每条request[startOffset, +duration]重建 SSR 瀑布。 - 限额:每次渲染出站请求 ≤100 条(超出保留最慢 N 条并标
truncated)。
服务端的容器注入、并发请求归属等接线细节在 SSR 采集,本页只覆盖客户端这半。
实现原理
性能插件只采集与产出 span,不自行采样:
七类采集器(导航/长任务/慢资源/Web Vitals/手动/SSR/stale refresh)
→ 统一产出 type:'performance' 的 RawEvent(op 区分子类)
→ emit(P2)→ L4 Sampler 按 control.performance 采样 → L5整体采样率由远程配置 control.performance 下发('1'–'10' → 10%–100%,默认按配置);慢资源黑名单走 performance.resourceTime,命中即排除。此外 SDK 自身上报接口(ingestUrl)被硬排除、且叠在远端黑名单之前——防止「上报 POST → 被记为慢资源 → 又触发上报」的反馈环(不依赖远端配置,同源反代 / 未配黑名单同样拦截)。
API 说明
手动性能 API 经 trace 门面暴露(受 performance Consent 管控,未装 / 未授权时返回 NOOP、安全 no-op)。
trace.startTransaction({ name, op? })
- 含义与作用:开启一个手动性能事务。
- 类型:
(opts: { name: string; op?: string }) => Transaction。 - 注意:
op缺省'ui.flow';未装 / 未授权时返回 NOOP 事务,其startChild/finish均安全空操作。
Transaction.startChild({ op, description? })
- 含义与作用:在事务下开一个子 Span。
- 类型:
(opts: { op: string; description?: string }) => Span。 - 注意:
description作为 span 名(缺省取op);事务与子 Span 均带data.transaction_id(自包含关联),子 Span 另带data.description(传了才有)。
Transaction.finish() / Span.finish()
- 含义与作用:结束并 emit 一条
performance事件。 - 类型:
() => void。 - 注意:
duration= finish − start,自动计算。
trace.markHydration()
- 含义与作用:标记 SSR 水合完成,emit 一条
hydrationspan。 - 类型:
() => void。 - 注意:
data带hydrationStart/hydrationComplete双锚点(差值交后端),duration仅为冗余便捷值;仅首次有效(去重),重复调用 no-op;Nuxt 适配器自动调用。
new PerformancePlugin(options?)
- 含义与作用:构造性能监控插件(opt-in,经构造参数
plugins装配)。 - 类型:
new PerformancePlugin(options?: PerformancePluginOptions)。 - 注意:全部字段见 装配方式;受
performanceConsent 管控。
注意事项
- 采样在远程配置侧:插件本体不采样,整体采样率由
control.performance远程下发,不在插件选项里。 - 慢资源有会话上限(
maxSlowResource,默认 20),防止资源风暴刷量。 startTransaction缺能力不抛错:未装插件 / 未授权时返回 NOOP 事务,tx.finish()安全无副作用。- 受
performanceConsent 管控:未授权不 setup。