前端監(jiān)控筆記(二)

性能監(jiān)控

頁面加載檢測(cè) Performance Navigation Timing

反映頁面加載耗時(shí),通常結(jié)合 P50、P75、P90 數(shù)據(jù)進(jìn)行分析。

  • DNS 統(tǒng)計(jì):domainLookupEnd - domainLookupStart,分析 DNS 耗時(shí)統(tǒng)計(jì)。
    • 正常解析時(shí)間在 0-30ms。
    • 如果持續(xù)偏高后又降低,說明內(nèi)部緩存生效。
    • 優(yōu)化:rel="dns-prefetch" 配置,減少域名數(shù)量,更換更快 DNS 服務(wù)商。
  • 不帶 SSL 的連接:connectEnd - connectStart,比對(duì)連接。
    • 分析 TCP 連接耗時(shí),如果為 0 說明命中連接復(fù)用 keep-alive。
    • 如果偏高反映 RTT 高。
    • 優(yōu)化:HTTP/2 多路復(fù)用,CDN,keep-alive 連接復(fù)用,rel="preconnect" 提前建立連接。
  • 帶 SSL:connectEnd - secureConnectionStart,比對(duì) SSL 的開銷。
    • 衡量 TLS 握手耗時(shí),正常 50-200ms。
    • 過高說明證書鏈過長,加密算法過重或者握手往返次數(shù)多。
    • 優(yōu)化:開啟 TLS 1.3,把握手 2 RTT 降到 1 RTT,開啟 Session Ticket 復(fù)用 TLS 會(huì)話,優(yōu)化證書鏈,移除多余中間證書。
  • TTFB 首字節(jié)時(shí)間:responseStart - requestStart,反映請(qǐng)求開始到收到響應(yīng),反映服務(wù)端的響應(yīng)速度。
    • 反映服務(wù)端或網(wǎng)絡(luò)質(zhì)量,建議小于800ms。
    • 如果 DNS 和 TCP 連接都正常則是服務(wù)端的問題,如果 DNS、TCP 連接都異常,則是網(wǎng)絡(luò)質(zhì)量問題。
    • 優(yōu)化方案:服務(wù)端優(yōu)化接口響應(yīng)速度,Redis 緩存;CDN 緩存響應(yīng),數(shù)據(jù)庫索引,SSR 或者邊緣計(jì)算。
  • download 響應(yīng)時(shí)間:responseEnd - responseStart。
    • 反映資源體積大小。
    • 如果時(shí)間過長,需要考慮優(yōu)化資源體積。
    • 優(yōu)化:gzip / brotli 壓縮,接口分頁,圖片使用 WebP 等格式,靜態(tài)資源通過 CDN 請(qǐng)求。
  • domParse DOM 獲取到解析完成時(shí)間:domInteractive - responseEnd,從得到響應(yīng)到可交互的時(shí)間。
    • 反映收到 HTML 到 DOM 解析的時(shí)間。
    • 如果過高說明 HTML 過大,或者有同步 JS 阻塞線程。
    • 優(yōu)化:JS defer / async 避免阻塞 HTML,減少 DOM 節(jié)點(diǎn),SSR 流式渲染,非關(guān)鍵 CSS 異步加載。
  • domReady DOM 準(zhǔn)備完成時(shí)間:domContentLoadedEventEnd - navigationStart,從導(dǎo)航開始到 DOM 加載完成這段時(shí)間。
    • DOMContentLoaded 觸發(fā)時(shí)間,前端可交互的時(shí)間節(jié)點(diǎn)。
    • 是前面內(nèi)容的時(shí)間綜合。
    • 優(yōu)化:減少渲染內(nèi)容,代碼分割,懶加載。
  • loadTime:loadEventEnd - navigationStart,統(tǒng)計(jì)整體加載時(shí)間。
    • 完整加載時(shí)間。
    • 如果比 domReady 高很多,說明有大量異步資源。
    • 優(yōu)化:圖片懶加載,動(dòng)態(tài)注入延遲加載埋點(diǎn)等腳本,preload 加載關(guān)鍵資源,字體可以設(shè)置 font-display: swap 避免阻塞。
const [nav] = performance.getEntriesByType('navigation')
// 單位是毫秒
const metrics = {
  dns:      nav.domainLookupEnd - nav.domainLookupStart,
  tcp:      nav.connectEnd      - nav.connectStart,
  ssl:      nav.connectEnd      - nav.secureConnectionStart,  // HTTPS 才有
  ttfb:     nav.responseStart   - nav.requestStart,           // 首字節(jié)時(shí)間
  download: nav.responseEnd     - nav.responseStart,
  domParse: nav.domInteractive  - nav.responseEnd,
  domReady: nav.domContentLoadedEventEnd - nav.navigationStart,
  loadTime: nav.loadEventEnd    - nav.navigationStart, // navigationStart 換成 startTime 或 0
}

PerformanceObserver API

通過監(jiān)聽等待主動(dòng)推送(Push 模式),只要觸發(fā)就會(huì)監(jiān)聽,定義 buffered: true,可以拿到注冊(cè)之前的性能數(shù)據(jù)。

Web Vitals 監(jiān)聽設(shè)計(jì)

LCP:獲取 largest-contentful-paint 最后一條的 startTime。

FID:監(jiān)聽 first-input,entry.processingStart - entry.startTime 統(tǒng)計(jì) FID。

CLS:監(jiān)聽 layout-shift,總和累加 entry.value,需要通過 hadRecentInput: false 只統(tǒng)計(jì)非用戶引起的布局偏移。

TTFB:使用 Navigation Timing 統(tǒng)計(jì) metrics。

FCP:監(jiān)聽 paint,通過 name 區(qū)分 FP 和 FCP,然后按 startTime 統(tǒng)計(jì)。

const observer = new PerformanceObserver((list) => {
    // 捕獲獲取
  for (const entry of list.getEntries()) {
    if (entry.entryType === 'largest-contentful-paint') {
      console.log('LCP:', entry.startTime)
    }
  }
})

// 可觀測(cè)的 entryType 列表
observer.observe({ type: 'navigation',              buffered: true })
observer.observe({ type: 'resource',                buffered: true }) 
observer.observe({ type: 'largest-contentful-paint', buffered: true })
observer.observe({ type: 'first-input',             buffered: true })  // FID
observer.observe({ type: 'layout-shift',            buffered: true })  // CLS
observer.observe({ type: 'longtask',                buffered: true })  // 超過 50ms 的任務(wù)
observer.observe({ type: 'paint',       buffered: true })  // FP / FCP               

類型

  1. navigation:Navigation Timing 監(jiān)聽。
  2. paint:FP / FCP 監(jiān)聽,首個(gè)有意義的內(nèi)容,name 獲取類型,startTime 獲取從導(dǎo)航開始的毫秒數(shù)(FCP < 1.8 s)。
observer.observe({ type: 'paint', buffered: true })

entry.name            // 'first-paint' | 'first-contentful-paint'
entry.startTime       // 從導(dǎo)航開始到該時(shí)間點(diǎn)的毫秒數(shù)
  1. largest-contentful-paint:LCP,最大元素,會(huì)多次觸發(fā)更新,只取最后一次(LCP < 2.5 s)。
    • 可以通過 url 分析是哪個(gè)圖片拖慢加載。
    • 通過 element 分析是哪個(gè)元素拖慢渲染。
entry.startTime       // LCP 發(fā)生時(shí)間,建議 < 2.5s
entry.size            // 元素面積(px2),越大越可能是 LCP 候選
entry.element         // 觸發(fā) LCP 的 DOM 元素,定位是哪個(gè)元素拖慢了渲染
entry.url             // 如果是圖片資源,這里是圖片 URL,否則為空
entry.loadTime        // 資源加載完成時(shí)間
entry.renderTime      // 元素實(shí)際渲染時(shí)間(跨域圖片可能為 0)
  1. first-input:FID,反映主線程是否繁忙、是否有長任務(wù)阻塞事件響應(yīng)。
entry.startTime       // 用戶第一次輸入的時(shí)間
entry.processingStart // 瀏覽器開始處理這個(gè)事件的時(shí)間
entry.processingEnd   // 處理完成時(shí)間

// 核心計(jì)算
FID = entry.processingStart - entry.startTime  // 輸入延遲,建議 < 100ms
  1. layout-shift:CLS,布局偏移量,統(tǒng)計(jì)布局偏移總和進(jìn)行計(jì)算。
entry.value           // 本次位移分?jǐn)?shù)(0~1)
entry.hadRecentInput  // 是否因用戶交互觸發(fā)(是的話不計(jì)入 CLS)
entry.sources         // 位移的元素列表
  └── source.node     // 具體哪個(gè) DOM 元素發(fā)生了位移
  └── source.previousRect / currentRect  // 位移前后的位置

// 核心計(jì)算:累加所有 hadRecentInput=false 的 entry.value
CLS = sum(entry.value)  // 建議 < 0.1
  1. longtask:長任務(wù)統(tǒng)計(jì),會(huì)影響 FID 和頁面響應(yīng),結(jié)合 startTime 定位卡頓時(shí)間。
// PerformanceLongTaskTiming 

entry.startTime       // 任務(wù)開始時(shí)間
entry.duration        // 任務(wù)耗時(shí)(必然 > 50ms)
entry.attribution     // 任務(wù)來源信息
  └── [0].name        // 'self' | 'same-origin-ancestor' 等
  └── [0].containerType  // 'iframe' | 'embed' | 'object' | 'window'
  └── [0].containerSrc   // 如果是 iframe,是其 src


// name
// 'self' 長任務(wù)發(fā)生在當(dāng)前頁面自己的主線程里。最常見的情況,說明是你自己的代碼(或你加載的腳本)造成了阻塞。
// 'same-origin-ancestor' 當(dāng)前頁面是一個(gè) iframe,長任務(wù)發(fā)生在它的父頁面里,且父頁面和 iframe 同域。
// 'same-origin-descendant' 長任務(wù)發(fā)生在當(dāng)前頁面嵌套的某個(gè) iframe 里,且那個(gè) iframe 和當(dāng)前頁面同域。
// 'same-origin' 同源但無法確定是祖先還是子孫關(guān)系。
// 'cross-origin-ancestor' 父頁面是跨域的,長任務(wù)來自父頁面,但出于安全限制,具體信息會(huì)被隱藏。
// 'cross-origin-descendant' 嵌套的 iframe 是跨域的,長任務(wù)來自那個(gè) iframe。這種情況下 containerSrc 等信息也會(huì)被屏蔽。
// 'unknown' 通常是瀏覽器內(nèi)部任務(wù)或無法追蹤的來源。
  1. resource:監(jiān)聽資源加載耗時(shí)和資源類型,可以分析傳輸數(shù)據(jù)大小、是否命中緩存、響應(yīng)時(shí)長、TTFB、解壓和壓縮前后大小。
entry.name            // 資源 URL
entry.initiatorType   // 'script' | 'img' | 'css' | 'fetch' | 'xmlhttprequest' ...
entry.duration        // 資源總加載耗時(shí)

// 細(xì)分階段(和 Navigation Timing 一致)
entry.domainLookupEnd - entry.domainLookupStart  // DNS
entry.connectEnd      - entry.connectStart        // TCP
entry.responseStart   - entry.requestStart        // TTFB
entry.responseEnd     - entry.responseStart        // Download

entry.transferSize    // 實(shí)際傳輸大?。ê?qǐng)求頭),為 0 說明命中緩存
entry.encodedBodySize // 壓縮后的響應(yīng)體大小
entry.decodedBodySize // 解壓后的響應(yīng)體大小
  1. event:監(jiān)聽事件交互,可以統(tǒng)計(jì)交互耗時(shí)、輸入延遲、渲染延遲,可用于替代 FID。默認(rèn)只有事件耗時(shí)超過 104ms 才會(huì)被監(jiān)聽,需要配置 durationThreshold(通用配置)來降低閾值。104ms 是推薦的關(guān)注臨界點(diǎn)(INP 推薦 < 200ms),避免頻繁觸發(fā)造成性能開銷。
entry.name              // 事件名:'click' | 'keydown' | 'pointerdown' | 'input' ...
entry.startTime         // 事件發(fā)生時(shí)間(用戶實(shí)際交互的時(shí)間點(diǎn))
entry.processingStart   // 瀏覽器開始執(zhí)行事件回調(diào)的時(shí)間
entry.processingEnd     // 事件回調(diào)執(zhí)行完畢的時(shí)間
entry.duration          // 從事件發(fā)生到下一幀繪制的總耗時(shí)(以 8ms 對(duì)齊取整)
entry.cancelable        // 事件是否可被 preventDefault

輸入延遲 = processingStart - startTime   // 主線程繁忙導(dǎo)致事件排隊(duì)等待時(shí)長
處理耗時(shí) = processingEnd - processingStart //事件回調(diào)執(zhí)行時(shí)長
渲染延遲 = duration - (processingEnd - startTime) // 回調(diào)完成到下一幀的等待時(shí)長

總結(jié)

  • **Navigation Timing **:基于 Navigation Timing 拆解頁面加載過程,針對(duì)從 DNS 到 頁面加載階段進(jìn)行優(yōu)化。
  • PerformanceObserver:通過 PerformanceObserver 采集 FCP、LCP、CLS、FID 指標(biāo),,結(jié)合 buffered: true 的推送式監(jiān)聽策略來完善整體的性能指標(biāo)監(jiān)聽。

參考內(nèi)容

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容