Skip to content

性能收集 ​

自动采集页面导航、长任务、慢资源、Web Vitals,并支持手动性能事务与可控的长时间后台恢复刷新。

性能收集由 PerformancePlugin 提供,需要 opt-in 显式装配,受 performance Consent 管控。可在 Playground · 性能收集 在线体验手动事务与 Web Vitals。

如何使用 ​

ts
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手动
hydrationSSR 水合标记 markHydration手动 / Nuxt 自动
ssr.render / ssr.requestSSR 渲染 waterfallcaptureSsr(默认关,见 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(字段全可选,分采集开关、慢资源两组):

ts
// 全用默认:采集导航 / 长任务 / 慢资源 / 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 ​

先速查,再展开重要项:

字段类型 / 默认说明
webVitalsboolean / true启用 Web Vitals 五项
captureSsrboolean / falseSSR 渲染性能(默认关,需手动打开)
slowResourceMsnumber / 1000慢资源阈值 ms
maxSlowResourcenumber / 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 时运行。它用于页面在后台停留较久后恢复时,避免继续使用可能已经过期的页面状态。

jsonc
{
  "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(传了才有)。

ts
// 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 全是安全空操作——业务代码无需判空。

ts
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——所以客户端先打开也安全、零成本。

实现:

  1. 一次性读取:setup 时读一次 window.__TRIPO_SSR_PERF__(读后清空),expandSsrPerf 展开成 ssr.render(1 条)+ ssr.request(每出站 1 条)。
  2. renderId 贯穿:从容器取出的 renderId 注入到本次 hydration / navigation / web-vital.* 事件——后端按 renderId join,用 render[0, totalMs] + 每条 request[startOffset, +duration] 重建 SSR 瀑布。
  3. 限额:每次渲染出站请求 ≤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 一条 hydration span。
  • 类型:() => void。
  • 注意:data 带 hydrationStart / hydrationComplete 双锚点(差值交后端),duration 仅为冗余便捷值;仅首次有效(去重),重复调用 no-op;Nuxt 适配器自动调用。

new PerformancePlugin(options?) ​

  • 含义与作用:构造性能监控插件(opt-in,经构造参数 plugins 装配)。
  • 类型:new PerformancePlugin(options?: PerformancePluginOptions)。
  • 注意:全部字段见 装配方式;受 performance Consent 管控。

注意事项 ​

  • 采样在远程配置侧:插件本体不采样,整体采样率由 control.performance 远程下发,不在插件选项里。
  • 慢资源有会话上限(maxSlowResource,默认 20),防止资源风暴刷量。
  • startTransaction 缺能力不抛错:未装插件 / 未授权时返回 NOOP 事务,tx.finish() 安全无副作用。
  • 受 performance Consent 管控:未授权不 setup。

下一步 ​

基于 MIT 协议发布(内部使用)