超全對照!前端監(jiān)控的性能指標與數(shù)據(jù)采集

導語 | 前端監(jiān)控可以讓你更了解自己的網(wǎng)站,更早地發(fā)現(xiàn)和解決存在的問題,再通過優(yōu)化來提升網(wǎng)站的性能和體驗。那么,如何衡量一個網(wǎng)站的好壞?有什么指標?性能數(shù)據(jù)如何采集?本文圍繞這些問題和你一起探討。
一、為什么要做前端性能監(jiān)控
可能你也有過這樣的經歷:
有用戶反饋你的網(wǎng)站很慢,然后你立馬緊張地在瀏覽器上打開用戶反饋的網(wǎng)站。經過檢查,可能你的網(wǎng)站一切正常,也可能你的網(wǎng)站真的很慢,甚至打不開了。
有一天老板問你:“咱們的網(wǎng)站性能體驗怎么樣?”你該如何回答?“挺好的,很快,這個月沒有發(fā)生過故障....”老板再問:“怎么個好法?” “很快打開”“有多快?” “沒統(tǒng)計過....” 然后就沒有然后了.....
如果我們有前端監(jiān)控,就有能力:
第一時間發(fā)現(xiàn)問題
也許在用戶反饋這個問題之前,該問題已經存在多日了,只是一直用戶沒有反饋。如果有性能方面的監(jiān)控,可以第一時間去發(fā)現(xiàn)問題,及時解決,把影響面、影響時長降到最低。
全面掌握數(shù)據(jù),驅動優(yōu)化性能,提高系統(tǒng)穩(wěn)定性
通過監(jiān)控采集到頁面性能、用戶使用方面的數(shù)據(jù),可以系統(tǒng)、全面地掌握系統(tǒng)運行情況。
提升用戶體驗
加快內容顯示速度,縮短交互延時。
二、前端性能監(jiān)控分類
性能監(jiān)控,可分為兩類:合成監(jiān)控和真實用戶監(jiān)控。
1. 合成監(jiān)控
模擬一個用戶使用場景,提交需要進行分析的頁面,再通過一系列的打點、分析去完成一些指標項的數(shù)據(jù)收集,最后呈現(xiàn)分析報告。例如Google的 Lighthouse,目前最新版的google chrome自帶的頁面性能分析工具。
調出開發(fā)工具(win: F12, mac: fn+f12)

Lighthouse主要三個指標:性能、可交互性、最佳實踐。
在性能方面,具體的指標有:
First Contentful Paint 首次內容渲染;
Time to Interactive 可交互時間;
Speed Index 速度指數(shù);
Total Blocking Time 總的阻塞時間;
Largest Contentful Paint 最大內容渲染;
Cumulative Layout Shift 累積布局偏移;
每一項指標還會給出具體的優(yōu)化建議,例如性能方面的優(yōu)化建議:

Lighthouse 系統(tǒng)架構圖:

2. 真實用戶監(jiān)控

真實用戶監(jiān)控, 記錄的是真實的用戶當時訪問頁面時的真實的數(shù)據(jù),在訪問結果時把采集到的數(shù)據(jù)上報到服務器,再經過數(shù)據(jù)清洗、加工等工作后,在監(jiān)控平臺上呈現(xiàn)監(jiān)控數(shù)據(jù)。
3. 合成監(jiān)控和真實用戶監(jiān)控的區(qū)別
合成監(jiān)控的優(yōu)缺點:
| 優(yōu)點 | 缺點 |
|---|---|
使用簡單,現(xiàn)有工具 | 模擬用戶場景,無法全部還原真實場景 |
| 采集數(shù)據(jù)豐富,如硬件指標、瀑布圖 | 單次運行,數(shù)據(jù)不夠穩(wěn)定 |
不影響真實用戶訪問性能 | 數(shù)據(jù)量少,無法覆蓋所有場景 |
真實用戶監(jiān)控的優(yōu)缺點:
| 優(yōu)點 | 缺點 |
|---|---|
采集用戶真實使用數(shù)據(jù) | 無法采集到硬件相關信息 |
| 樣本量大,可以全覆蓋,減少統(tǒng)計誤差 | 因需要上報,無法采集完整的資源加載瀑布圖 |
性能數(shù)據(jù)與其它數(shù)據(jù)關聯(lián)產生更大的價值 | 無法可視化展示頁面加載過程 |
區(qū)別:
| 對比項 | 合成監(jiān)控 | 真實用戶監(jiān)控 |
|---|---|---|
實現(xiàn)難度&成本 | 較低 | 較高 |
采集數(shù)據(jù)豐富度 | 豐富 | 基礎 |
采集樣本量 | 小 | 大 |
適用場景 | 自有業(yè)務,用戶量小,定性分析 | 中臺產品,用戶量大,定量分析 |
因為真實用戶監(jiān)控也是在運行時執(zhí)行,所以這種真實用戶監(jiān)控比較難采集到一些硬件相關的指標,也很難去采集這個頁面執(zhí)行的幻燈片(即逐幀截圖)。當然技術上可以用JS把當前頁面保存成一個Canvas,做一些逐幀對比,甚至把這些數(shù)據(jù)回傳回去。但是在實踐過程中,我們肯定不會這樣做,因為這對用戶的流量是極大的浪費。介紹完這兩種監(jiān)控方案我們來看它們的對比。
本文要講的是真實用戶監(jiān)控。
三、前端性能如何衡量
1. Google Web Vitals
評估一個網(wǎng)站的用戶體驗涉及到多個指標,有些還與網(wǎng)站的內容有關,但還是有一些共性的指標的,而Core Web Vitals體現(xiàn)了最關鍵的幾項指標。此類核心用戶體驗需求包括頁面內容的加載體驗、交互性和視覺穩(wěn)定性,這些方面共同組成 2020 Core Web Vitals 的基礎。

LCP:Largest Contentful Paint,顯示最大內容元素所需時間 (衡量網(wǎng)站初次載入速度);
FID:First Input Delay 首次輸入延遲時間 (衡量網(wǎng)站互動順暢程度);
CLS:Cumulative Layout Shift 累計布局位移 (衡量網(wǎng)頁元件視覺穩(wěn)定性);
除了以上三個主要衡量指標,還有FCP和TTFB:
FCP: First Contentful Paint 首次內容繪制,標記瀏覽器渲染來自 DOM 第一位內容的時間點;
TTFB: Time To First Byte 讀取頁面第一個字節(jié)的時間;
雖然LCP最大內容繪制是最重要的負載指標,但其也高度依賴于首次內容繪制 (FCP) 和首字節(jié)響應時間 (TTFB),這些指標對監(jiān)控和改進均具有非常重要的意義。
2. API耗時
很多時候頁面上的數(shù)據(jù)是通過異步請求后臺API后再進行渲染得到的,API耗時直接影響了LCP數(shù)據(jù)和用戶體驗。
LCP 最大內容渲染
LCP以用戶為中心,來衡量頁面加載“完成”所用的時間,當頁面中最大一塊內容被渲染出來時,被認為是頁面加載“完成”了。以前,用load\DOMContentLoaded件反應頁面加載速度,后來使用了更精準的FCP(首次內容渲染),但是從用戶角度出發(fā),只有主要的內容顯示出來了才算是加載完成。
其最大指的是實際Element長寬大小,Margin / Padding / Border等CSS大小效果不計入。包含的種類為<img>,url,<video>及包含文字節(jié)點的Block或Inline Element,未來可能會再加入<svg>。因為網(wǎng)頁上的Element可能持續(xù)加載,最大的Element也可能持續(xù)改變 (如文字載入完,然后載入圖片) ,所以當每一個當下最大的Element載完,瀏覽器會發(fā)出一個PerformanceEntry Metric,直到使用者可以進行Keydown / Scrolling / Tapping等操作,Browser才會停止發(fā)送Entry,故只要抓到最后一次Entry,即能判斷LCP的持續(xù)時間。
如下圖所示,綠色區(qū)域是LCP不斷改變的偵測對象,也能看到FCP與LCP的判斷差異。

FID 衡量網(wǎng)站互動順暢程度
如何衡量網(wǎng)站操作的順暢程度,Google采用FID指標,其定義為在TTI的時間內第一個互動事件的開始時間與瀏覽器回應事件的時間差,其互動事件為單次事件如Clicks / Taps / Key Presses等,其他連續(xù)性事件Scrolling / Zooming則不計,如下圖所示:

為什么要取在TTI發(fā)生的第一次的操作事件,Google給的理由有以下三點:
3)但是FID的計算有其明顯的問題,如當使用者在Main Thread閑置時操作,那FID可能就短,若不操作則FID則無法計算。這對開發(fā)者來說,很難去衡量網(wǎng)站的FID符合良好的標準,所以Google給的建議是透過降低TBT的時間來降低FID的值,當TBT越短,其FID就越好。
CLS 衡量網(wǎng)頁元件視覺穩(wěn)定性
你可能有過這樣的經歷, 當你準備點某一個按鈕或內容是,它突然移動了,然后你點了另外一個按鈕。
例如下圖,當你準備點擊“確認提交”按鈕時,按鈕上方加載了一個提示框,導致下面的按鈕被往下移動,在你原來要點擊的位置的元素由原來的“確認提交”按鈕,變成了“放棄申請”按鈕。在你點擊時,變成了放棄該訂單,前面的工作白白被浪費了,這是大家都不愿意看到的,體驗非常不好,令人抓狂。

導致這種預料之外的的內容布局移動,可能是因為資源的異步加載、JS對dom元素的動態(tài)操作、未知尺寸圖片加載等等。對用戶來說,這是一種糟糕的用戶體驗。CLS就是用來衡量此類的體能指標。

什么是一個好的CLS分數(shù)?75%以上的用戶小于0.1。
布局偏移的具體內容
布局偏移是由Layout Instability API定義的。這個API會在任意時間上報layout-shift的條目,當一個可見元素在兩幀之間,改變了它的起始位置(默認的writing mode下指的是top和left屬性)。這些元素被當成不穩(wěn)定元素。
需要注意的是,布局偏移只發(fā)生在已經存在的元素改變起始位置的時候。如果一個新的元素被添加到dom上,或者已存在的元素改變它的尺寸,除非改變了其他元素的起始位置,否則都不算布局偏移。
其 CLS 代表的是每個元素非預期位移的累積,而每個位移的算法如下: 元素位移分數(shù) (Layout Shift Score) = 影響范圍 (Impact Fraction) * 移動距離 (Distance Fraction)。

上圖中,元素在一幀中占了屏幕的一半。下一幀,元素下移了25%的視圖高度。紅色虛線框起來的部分就是不穩(wěn)定元素在兩幀的占的視圖總和(75%),所以影響分數(shù)是0.75。

上圖,不穩(wěn)定元素在縱向移動了25%,所以距離分數(shù)是0.25。
所以布局偏移分數(shù)是:
CLS: 0.75 * 0.25 = 0.1875API 指標關注哪些數(shù)據(jù)?
除了從請求到返回的耗時外,還有請求列隊時間、請求發(fā)起時間。
如果一個API從發(fā)起請求到數(shù)據(jù)返回很快,但是由于需要列隊等待或是依賴其它數(shù)據(jù)都原因而被推遲發(fā)起請求,從用戶角色看,這也是一個很慢的接口。所以作為開發(fā)者還需要關注API請求是否能夠盡快地被發(fā)起。
四、前端性能數(shù)據(jù)采集
通過上面的內容,我們了解了網(wǎng)站性能監(jiān)控的一些指標,接下來看看這些指標數(shù)據(jù)是如何獲取的。
1. web-vitals庫
對于LCP、FID、CLS數(shù)據(jù),可以直接安裝web-vitals庫:
??(https://github.com/GoogleChrome/web-vitals)
如何安裝:
npm install web-vitals使用方法:
import {getLCP,getFID,getCLS} from'web-vitals';getCLS(console.log);getFID(console.log);getLCP(console.log);
打開頁面,在瀏覽器控制臺上就可以看到類似的數(shù)據(jù):

實際使用時把console.log替換成你要處理的方法即可。當然,還可以使用getFCP、getTTFB方法來獲取相應的數(shù)據(jù)。
2. performance API
為了幫助開發(fā)者更好地衡量和改進前端頁面性能,W3C性能小組引入了Navigation Timing API,實現(xiàn)了自動、精準的頁面性能打點。performance能提供哪些時間節(jié)點?在瀏覽器控制臺中執(zhí)行window.performance.timing;可以得到類似如下輸出:

這些屬性和值代表什么呢?之此之前,我們先來看下這張圖:

上圖是實時監(jiān)控性能模型,可以看到我們的頁面加載被定義成了很多個階段。可以大致分為5個階段:
1)開始計時
2)重定向
3)網(wǎng)絡連接
4)數(shù)據(jù)交互
5)頁面渲染
各屬性對應的意義如下:
| 屬性 | 說明 |
|---|---|
| navigationStart | 同一個瀏覽器上下文的上一個文檔卸載結束時的時間戳,如果沒有上一個文檔,這個值會和fetchStart相同。 |
| unloadEventStart | unload事件拋出時的時間戳,如果沒有上一個文檔,這個值會是0。 |
| unloadEventEnd | unload事件處理完成的時間戳,如果沒有上一個文檔,這個值會是0。 |
| redirectStart | 第一個HTTP重定向開始時的時間戳,沒有重定向或者重定向中的不同源,這個值會是0。 |
| redirectEnd | 最后一個HTTP重定向開始時的時間戳,沒有重定向或者重定向中的不同源,這個值會是0。 |
| fetchStart | 瀏覽器準備好使用HTTP請求來獲取文檔的時間戳。發(fā)送在檢查緩存之前。 |
| domainLookupStart | 域名查詢開始的時間戳,如果使用了持續(xù)連接或者緩存,則與fetchStart一致。 |
| domainLookupEnd | 域名查詢結束的時間戳,如果使用了持續(xù)連接或者緩存,則與fetchStart一致。 |
| connectStart | HTTP請求開始向服務器發(fā)送時的時間戳,如果使用了持續(xù)連接,則與fetchStart一致。 |
| connectEnd | 瀏覽器與服務器之間連接建立(所有握手和認證過程全部結束)的時間戳,如果使用了持續(xù)連接,則與fetchStart一致。 |
| secureConnectionStart | 瀏覽器與服務器開始安全鏈接握手時的時間戳,如果當前網(wǎng)頁不需要安全連接,這個值會是0。 |
| requestStart | 瀏覽器向服務器發(fā)出HTTP請求的時間戳。 |
| responseStart | 瀏覽器從服務器收到(或從本地緩存讀?。┑谝粋€字節(jié)時的時間戳。 |
| responseEnd | 瀏覽器從服務器收到(或從本地緩存讀?。┳詈笠粋€字節(jié)時(如果在此之前HTTP連接已經關閉,則返回關閉時)的時間戳。 |
| domLoading | 當前網(wǎng)頁DOM結構開始解析時的時間戳。 |
| domInteractive | 當前網(wǎng)頁DOM結構解析完成,開始加載內嵌資源時的時間戳。 |
| domContentLoadedEventStart | 需要被執(zhí)行的腳本已經被解析的時間戳。 |
| domContentLoadedEventEnd | 需要立即執(zhí)行的腳本已經被執(zhí)行的時間戳。 |
| domComplete | 當前文檔解析完成的時間戳。 |
| loadEventStartload | 事件被發(fā)送時的時間戳,如果這個事件還未被發(fā)送,它的值將會是0。 |
| loadEventEnd | load事件結束時的時間戳,如果這個事件還未被發(fā)送,它的值將會是0。 |
通過以上時間點,我們就可以計算出如下的前端性能指標,如:
| 重定向耗時 | redirectEnd - redirectStart |
| DNS 解析耗時 | domainLookupEnd - domainLookupStart |
| TCP 連接耗時 | connectEnd - connectStart |
| SSL 安全連接耗時 | connectEnd - secureConnectionStart |
| 網(wǎng)絡請求耗時 (TTFB) | responseStart - requestStart |
| 數(shù)據(jù)傳輸耗時 | responseEnd - responseStart |
| DOM 解析耗時 | domInteractive - responseEnd |
| 資源加載耗時 | loadEventStart - domContentLoadedEventEnd |
| 首包時間 | responseStart - domainLookupStart |
| 白屏時間 | responseEnd - fetchStart |
| 首次可交互時間 | domInteractive - fetchStart |
| DOM Ready 時間 | domContentLoadEventEnd - fetchStart |
| 首屏加載時間 | domComplete - navigationStart |
除performance.timming外,performance API 還有一些方法幫助你獲取到更多有用的信息,例如:
performance.getEntries()
獲取到每個資源、xmlhttpRequest的相關參數(shù),例如發(fā)起時間,資源地址,耗時等。

點擊放大
performance.getEntriesByType(type)
根據(jù)資源類型來查看某種類型的資源耗時等信息。

performance.getEntriesByName(name, type)
根據(jù)名稱和資源來源來獲取相關信息。

Performance.now()
可用于記錄當前代碼執(zhí)行的時間點,不同于new Date().getTime()。
| Date().getTime() | 毫秒,當前時間戳,受系統(tǒng)時間的影響,距離 1970 的時間;如:1619014021294 |
| performance.new() | 微秒(百萬分之一秒),時間是以恒定速率遞增,不受系統(tǒng)程序執(zhí)行阻塞的影響,相對于 performance.timing.navigationStart(頁面初始化) 的時間。如:3956.404999946244 |
Performance.mark()
通過在各個地方打點,標記每個點的執(zhí)行時間點。
3. js錯誤、vue錯誤、api錯誤的采集
通過window.onerror可以捕獲JS錯誤信息:
/*** JS 錯誤捕獲* @param {String} msg 錯誤消息* @param {String} url 引發(fā)錯誤的腳本的URL* @param {Number} lineNo 發(fā)生錯誤的行號* @param {Number} columnNo 發(fā)生錯誤的行的列號* @param {Object} error 錯誤對象*/window.onerror = (msg, url, lineNo, columnNo, error) => {console.log(error.stack);// do something.....} ;
vue錯誤不能使用window.onerror來捕獲,vue提供了Vue.config.errorHandler方法來捕獲vue錯誤,例如:
/*** Vue錯誤捕獲* @param {Object} error 錯誤對象*/Vue.config.errorHandler = (error) => {console.log(error);// do something.....};
如果你使用axios來處理API,在axios發(fā)送請求后,使用catch,或在Axios攔截器統(tǒng)一處理,例如:
new Promise((resolve, reject) => {axios.post(url, data).then((response) => {}).catch(function(error){console.log(error);// do something.....})})// 或在Axios攔截器統(tǒng)一處理Axios.interceptors.response.use((response) => {},(error) => {console.log(error);// do something.....},);
以上就是關于前端性能監(jiān)控方面的一些衡量指標、數(shù)據(jù)采集方法,為下一步的性能優(yōu)化提供了方向和數(shù)據(jù)支持。
作者簡介
何瑞,騰訊AB實驗平臺前端開發(fā)工程師。
6月5日,Techo TVP 開發(fā)者峰會 ServerlessDays China 2021,即將重磅來襲!
掃碼立即參會贏好禮??
