也許你有聽(tīng)過(guò)一個(gè)問(wèn)題,你這款 web 應(yīng)用性能怎么樣呀?你會(huì)回答什么呢?是否會(huì)優(yōu)于海量 web 應(yīng)用市場(chǎng)呢?本文就來(lái)整理下如何進(jìn)行 web 性能監(jiān)控?包括我們需要監(jiān)控的指標(biāo)、監(jiān)控的分類(lèi)、performance 分析以及如何監(jiān)控。
但是,如何進(jìn)行 web 性能監(jiān)控本身是一個(gè)很大的話(huà)題,文中只會(huì)側(cè)重一部分進(jìn)行研究,某些內(nèi)容不是很全面。
前言:為什么需要監(jiān)控?
web 的性能一定程度上影響了用戶(hù)留存率,Google DoubleClick 研究表明:如果一個(gè)移動(dòng)端頁(yè)面加載時(shí)長(zhǎng)超過(guò) 3 秒,用戶(hù)就會(huì)放棄而離開(kāi)。BBC 發(fā)現(xiàn)網(wǎng)頁(yè)加載時(shí)長(zhǎng)每增加 1 秒,用戶(hù)就會(huì)流失 10%。
我們希望通過(guò)監(jiān)控來(lái)知道 web 應(yīng)用性能的現(xiàn)狀和趨勢(shì),找到 web 應(yīng)用的瓶頸?某次發(fā)布后的性能情況怎么樣?是否發(fā)布后對(duì)性能有影響?感知到業(yè)務(wù)出錯(cuò)的概率?業(yè)務(wù)的穩(wěn)定性怎么樣?
監(jiān)控什么?
首先我們需要知道應(yīng)該監(jiān)控些什么呢?有哪些具體的指標(biāo)?
google 開(kāi)發(fā)者提出了一種 RAIL
模型來(lái)衡量應(yīng)用性能,即:Response
、Animation
、Idle
、Load
,分別代表著 web 應(yīng)用生命周期的四個(gè)不同方面。并指出最好的性能指標(biāo)是:100ms 內(nèi)響應(yīng)用戶(hù)輸入;動(dòng)畫(huà)或者滾動(dòng)需在 10ms 內(nèi)產(chǎn)生下一幀;最大化空閑時(shí)間;頁(yè)面加載時(shí)長(zhǎng)不超過(guò) 5 秒。
我們可轉(zhuǎn)化為三個(gè)方面來(lái)看:響應(yīng)速度、頁(yè)面穩(wěn)定性、外部服務(wù)調(diào)用
- 響應(yīng)速度:頁(yè)面初始訪問(wèn)速度 + 交互響應(yīng)速度
- 頁(yè)面穩(wěn)定性:頁(yè)面出錯(cuò)率
- 外部服務(wù)調(diào)用:網(wǎng)絡(luò)請(qǐng)求訪問(wèn)速度
1.頁(yè)面訪問(wèn)速度:白屏、首屏?xí)r間、可交互時(shí)間
我們來(lái)看看 google 開(kāi)發(fā)者針對(duì)用戶(hù)體驗(yàn),提出的幾個(gè)性能指標(biāo)
這幾個(gè)指標(biāo)其實(shí)都是根據(jù)用戶(hù)體驗(yàn),提煉出對(duì)應(yīng)的性能指標(biāo)
1)first paint (FP) and first contentful paint (FCP)
首次渲染、首次有內(nèi)容的渲染
這兩個(gè)指標(biāo)瀏覽器已經(jīng)標(biāo)準(zhǔn)化了,從 performance 的 The Paint Timing API
可以獲取到,一般來(lái)說(shuō)兩個(gè)時(shí)間相同,但也有情況下兩者不同。
2)First meaningful paint and hero element timing
首次有意義的渲染、頁(yè)面關(guān)鍵元素
我們假設(shè)當(dāng)一個(gè)網(wǎng)頁(yè)的 DOM 結(jié)構(gòu)發(fā)生劇烈的變化的時(shí)候,就是這個(gè)網(wǎng)頁(yè)主要內(nèi)容出現(xiàn)的時(shí)候,那么在這樣的一個(gè)時(shí)間點(diǎn)上,就是首次有意義的渲染。這個(gè)指標(biāo)瀏覽器還沒(méi)有規(guī)范,畢竟很難統(tǒng)一一個(gè)標(biāo)準(zhǔn)來(lái)定義網(wǎng)站的主體內(nèi)容。
google lighthouse 定義的 first meaningful paint
:https://docs.google.com/document/d/1BR94tJdZLsin5poeet0XoTW60M0SjvOJQttKT-JK8HI/view
3)Time to interactive
可交互時(shí)間
4)長(zhǎng)任務(wù)
瀏覽器是單線(xiàn)程的,如果長(zhǎng)任務(wù)過(guò)多,那必然會(huì)影響著用戶(hù)響應(yīng)時(shí)長(zhǎng)。好的應(yīng)用需要最大化空閑時(shí)間,以保證能最快響應(yīng)用戶(hù)的輸入。
2.頁(yè)面穩(wěn)定性:頁(yè)面出錯(cuò)情況
- 資源加載錯(cuò)誤
- JS 執(zhí)行報(bào)錯(cuò)
3.外部服務(wù)調(diào)用
- CGI 耗時(shí)
- CGI 成功率
- CDN 資源耗時(shí)
監(jiān)控的分類(lèi)?
web 性能監(jiān)控可分為兩類(lèi),一類(lèi)是合成監(jiān)控(Synthetic Monitoring,SYN),另一類(lèi)是真實(shí)用戶(hù)監(jiān)控(Real User Monitoring,RUM)
合成監(jiān)控
合成監(jiān)控是采用 web 瀏覽器模擬器來(lái)加載網(wǎng)頁(yè),通過(guò)模擬終端用戶(hù)可能的操作來(lái)采集對(duì)應(yīng)的性能指標(biāo),最后輸出一個(gè)網(wǎng)站性能報(bào)告。例如:Lighthouse
、PageSpeed
、WebPageTest
、Pingdom
、PhantomJS
等。
1. Lighthouse
Lighthouse
是 google 一個(gè)開(kāi)源的自動(dòng)化工具,運(yùn)行 Lighthouse
的方式有兩種:一種是作為 Chrome 擴(kuò)展程序運(yùn)行;另一種作為命令行工具運(yùn)行。Chrome 擴(kuò)展程序提供了一個(gè)對(duì)用戶(hù)更友好的界面,方便讀取報(bào)告。通過(guò)命令行工具可以將 Lighthouse 集成到持續(xù)集成系統(tǒng)。
展示了白屏、首屏、可交互時(shí)間等性能指標(biāo)和 SEO、PWA 等。
騰訊文檔移動(dòng)端官網(wǎng)首頁(yè)測(cè)速結(jié)果:
2. PageSpeed
https://developers.google.com/speed/pagespeed/insights/
不僅展示了一些主要的性能指標(biāo)數(shù)據(jù),還給出了部分性能優(yōu)化建議。
騰訊文檔移動(dòng)端首頁(yè)測(cè)速結(jié)果和性能優(yōu)化建議:
3. WebPageTest
WebPageTest
給出性能測(cè)速結(jié)果和資源加載的瀑布圖。
4. Pingdom
注意:Pingdom 不僅提供合成監(jiān)控,也提供真實(shí)用戶(hù)監(jiān)控。
合成監(jiān)控方式的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):
- 無(wú)侵入性。
- 簡(jiǎn)單快捷。缺點(diǎn):
- 不是真實(shí)的用戶(hù)訪問(wèn)情況,只是模擬的。
- 沒(méi)法考慮到登錄的情況,對(duì)于需要登錄的頁(yè)面就無(wú)法監(jiān)控到。
二、真實(shí)用戶(hù)監(jiān)控
真實(shí)用戶(hù)監(jiān)控是一種被動(dòng)監(jiān)控技術(shù),是一種應(yīng)用服務(wù),被監(jiān)控的 web 應(yīng)用通過(guò) sdk 等方式接入該服務(wù),將真實(shí)的用戶(hù)訪問(wèn)、交互等性能指標(biāo)數(shù)據(jù)收集上報(bào)、通過(guò)數(shù)據(jù)清洗加工后形成性能分析報(bào)表。例如 FrontJs
、oneapm
、Datadog
等。
1. oneapm
https://www.oneapm.com/bi/feature.html
功能包括:大盤(pán)數(shù)據(jù)、特征統(tǒng)計(jì)、慢加載追蹤、訪問(wèn)頁(yè)面、腳本錯(cuò)誤、AJAX、組合分析、報(bào)表、告警等。
2. Datadog
https://www.datadoghq.com/rum/
3. FrontJs
功能包括:訪問(wèn)性能、異常監(jiān)控、報(bào)表、趨勢(shì)等。這種監(jiān)控方式的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):
- 是真實(shí)用戶(hù)訪問(wèn)情況。
- 可以觀察歷史性能趨勢(shì)。
- 有一些額外的功能:報(bào)表推送、監(jiān)控告警等等。缺點(diǎn):
- 有侵入性,會(huì)一定程度上響應(yīng) web 性能。
performance 分析
在講如何監(jiān)控之前,先來(lái)看看瀏覽器提供的 performance api,這也是性能監(jiān)控?cái)?shù)據(jù)的主要來(lái)源。
performance 提供高精度的時(shí)間戳,精度可達(dá)納秒級(jí)別,且不會(huì)隨操作系統(tǒng)時(shí)間設(shè)置的影響。
目前市場(chǎng)上的支持情況:主流瀏覽器都支持,大可放心使用。
基本屬性
performance.navigation: 頁(yè)面是加載還是刷新、發(fā)生了多少次重定向performance.timing: 頁(yè)面加載的各階段時(shí)長(zhǎng)
各階段的含義:
performance.memory:基本內(nèi)存使用情況,Chrome 添加的一個(gè)非標(biāo)準(zhǔn)擴(kuò)展
performance.timeorigin: 性能測(cè)量開(kāi)始時(shí)的時(shí)間的高精度時(shí)間戳
基本方法
performance.getEntries()
通過(guò)這個(gè)方法可以獲取到所有的 performance
實(shí)體對(duì)象,通過(guò) getEntriesByName
和 getEntriesByType
方法可對(duì)所有的 performance
實(shí)體對(duì)象 進(jìn)行過(guò)濾,返回特定類(lèi)型的實(shí)體。
mark 方法 和 measure 方法的結(jié)合可打點(diǎn)計(jì)時(shí),獲取某個(gè)函數(shù)執(zhí)行耗時(shí)等。
- performance.getEntriesByName()
- performance.getEntriesByType()
- performance.mark()
- performance.clearMarks()
- performance.measure()
- performance.clearMeasures()
- performance.now() ...
提供的 API
performance 也提供了多種 API,不同的 API 之間可能會(huì)有重疊的部分。
1. PerformanceObserver API
用于檢測(cè)性能的事件,這個(gè) API 利用了觀察者模式。
獲取資源信息2. Navigation Timing API
https://www.w3.org/TR/navigation-timing-2/
performance.getEntriesByType("navigation");
不同階段之間是連續(xù)的嗎? —— 不連續(xù)
每個(gè)階段都一定會(huì)發(fā)生嗎?—— 不一定
- 重定向次數(shù):performance.navigation.redirectCount
- 重定向耗時(shí): redirectEnd - redirectStart
- DNS 解析耗時(shí): domainLookupEnd - domainLookupStart
- TCP 連接耗時(shí): connectEnd - connectStart
- SSL 安全連接耗時(shí): connectEnd - secureConnectionStart
- 網(wǎng)絡(luò)請(qǐng)求耗時(shí) (TTFB): responseStart - requestStart
- 數(shù)據(jù)傳輸耗時(shí): responseEnd - responseStart
- DOM 解析耗時(shí): domInteractive - responseEnd
- 資源加載耗時(shí): loadEventStart - domContentLoadedEventEnd
- 首包時(shí)間: responseStart - domainLookupStart
- 白屏?xí)r間: responseEnd - fetchStart
- 首次可交互時(shí)間: domInteractive - fetchStart
- DOM Ready 時(shí)間: domContentLoadEventEnd - fetchStart
- 頁(yè)面完全加載時(shí)間: loadEventStart - fetchStart
- http 頭部大小:transferSize - encodedBodySize
3. Resource Timing APIhttps://w3c.github.io/resource-timing/
performance.getEntriesByType("resource");
// 某類(lèi)資源的加載時(shí)間,可測(cè)量圖片、js、css、
XHRresourceListEntries.forEach(resource => {
if (resource.initiatorType == 'img') {
console.info(`Time taken to load ${resource.name}: `, resource.responseEnd - resource.startTime);
}});
這個(gè)數(shù)據(jù)和 chrome 調(diào)式工具里 network 的瀑布圖數(shù)據(jù)是一樣的。
4. paint Timing API
https://w3c.github.io/paint-timing/
首屏渲染時(shí)間、首次有內(nèi)容渲染時(shí)間5. User Timing API
https://www.w3.org/TR/user-timing-2/#introduction
主要是利用 mark 和 measure 方法去打點(diǎn)計(jì)算某個(gè)階段的耗時(shí),例如某個(gè)函數(shù)的耗時(shí)等。
<head>
<script>
// 通常在head標(biāo)簽尾部時(shí),打個(gè)標(biāo)記,這個(gè)通常會(huì)視為白屏?xí)r間
performance.mark("first paint time");
</script>
</head>
<body>
...
<script>
// get the first paint time
const fp = Math.ceil(performance.getEntriesByName('first paint time')[0].startTime);
</script>
</body>
6. High Resolution Time APIhttps://w3c.github.io/hr-time/#dom-performance-timeorigin
主要包括 now() 方法和 timeOrigin 屬性。
7. Performance Timeline APIhttps://www.w3.org/TR/performance-timeline-2/#introduction
總結(jié)
基于 performance 我們可以測(cè)量如下幾個(gè)方面:
mark、measure、navigation、resource、paint、frame。
let p = window.performance.getEntries();
重定向次數(shù):performance.navigation.redirectCount
JS 資源數(shù)量: p.filter(ele => ele.initiatorType === "script").length
CSS 資源數(shù)量:p.filter(ele => ele.initiatorType === "css").length
AJAX 請(qǐng)求數(shù)量:p.filter(ele => ele.initiatorType === "xmlhttprequest").length
IMG 資源數(shù)量:p.filter(ele => ele.initiatorType === "img").length
總資源數(shù)量: window.performance.getEntriesByType("resource").length
不重復(fù)的耗時(shí)時(shí)段區(qū)分:
- 重定向耗時(shí): redirectEnd - redirectStart
- DNS 解析耗時(shí): domainLookupEnd - domainLookupStart
- TCP 連接耗時(shí): connectEnd - connectStart
- SSL 安全連接耗時(shí): connectEnd - secureConnectionStart
- 網(wǎng)絡(luò)請(qǐng)求耗時(shí) (TTFB): responseStart - requestStart
- HTML 下載耗時(shí):responseEnd - responseStart
- DOM 解析耗時(shí): domInteractive - responseEnd
- 資源加載耗時(shí): loadEventStart - domContentLoadedEventEnd
其他組合分析:
- 白屏?xí)r間: domLoading - fetchStart
- 粗略首屏?xí)r間: loadEventEnd - fetchStart 或者 domInteractive - fetchStart
- DOM Ready 時(shí)間: domContentLoadEventEnd - fetchStart
- 頁(yè)面完全加載時(shí)間: loadEventStart - fetchStart
JS 總加載耗時(shí):
const p = window.performance.getEntries();
let cssR = p.filter(ele => ele.initiatorType === "script");
Math.max(...cssR.map((ele) => ele.responseEnd)) - Math.min(...cssR.map((ele) => ele.startTime));
CSS 總加載耗時(shí):
const p = window.performance.getEntries();
let cssR = p.filter(ele => ele.initiatorType === "css");
Math.max(...cssR.map((ele) => ele.responseEnd)) - Math.min(...cssR.map((ele) => ele.startTime));
npm install -g lighthouse
當(dāng)頁(yè)面鏈接至使用 target="_blank" 的另一個(gè)頁(yè)面時(shí),兩個(gè)頁(yè)面將在同一個(gè)進(jìn)程上運(yùn)行。 如果新頁(yè)面正在執(zhí)行開(kāi)銷(xiāo)極大的 JavaScript,當(dāng)前頁(yè)面性能可能會(huì)受影響。
另外,target="_blank" 也有一個(gè)安全漏洞。新的頁(yè)面可以通過(guò) window.opener 訪問(wèn)舊的窗口對(duì)象,甚至可以使用 window.opener.location = newURL 將舊頁(yè)面導(dǎo)航至不同的網(wǎng)址。
當(dāng)設(shè)置rel="noopener"時(shí)chrome會(huì)在獨(dú)立的進(jìn)程中打開(kāi)新頁(yè)面,同時(shí)會(huì)阻止window.opener,因此不存在跨窗口訪問(wèn)。
<a target="_blank" rel="noopener" >
轉(zhuǎn)原文:騰訊前端團(tuán)隊(duì)是如何做web性能監(jiān)控的?https://mp.weixin.qq.com/s/y2jh7oT36XdmHM9fImLl_w