深入淺出 Performance 工具 - API
概述
日常開發任務中,對於性能優化或多或少會接觸到一些內容,可能也參照過雅虎 35 條軍規 [1] 進行過相關的性能優化,但是具體的優化結果以及實際的頁面速度如何,我們怎麼去看呢?以及出現性能問題了,我們如何通過現有工具進行定位 & 解決?也就是今天我要給大家介紹的內容主題了「Performance」,主題偏向工具介紹,主要從下面 4 個方面介紹今天的內容。
-
Chrome Perormance 工具使用:介紹如何使用瀏覽器提供的工具定位
-
Performance Api 監測網頁性能:介紹如何自己去做性能數據的提取
-
現存檢測工具:三方性能檢測工具介紹
-
Performance 工具小試:通過一個小例子運用 Performance
Chrome Performance 工具使用
Chrome 中 Performance 可以在上圖中看到,主要分了幾個板塊
控制面板(Controls)
開啓記錄,停止記錄,配置記錄期間需要記錄的內容。
操作主要分了 2 個區域,操作 1 區從左到右依次是 "Record/Stop"、"Reload" 和 "Clear",
-
"Record/Stop":一般用於錄製頁面交互過程的性能變化數據,選擇任意想要測試的過程,點擊 "Record",並在測量結束之後,點擊 "Stop",之後 Chrome 就會自動解析這段時間內抓取的數據,並生成報告。
-
"Reload":一般用於錄製首屏加載的性能變化數據,它會自動刷新整個頁面,並開始記錄性能。
-
"Clear":用於清除性能報告數據
操作 2 區可以選擇報告展示內容,從左到右依次是 Screenshots、Memory、Web Vitals
-
Screenshots:打開後可以在概覽區看到屏幕的截圖
-
Memory:打開內存監控
-
WebVitals
概覽面板(Overview)
主要是對頁面表現行爲的一個概述,區域由三個圖形記錄組成。
- FPS(Frames Per Second): 綠色的柱越高, FPS 值也越高。FPS 圖表上方的紅色小塊指明瞭長幀 (long frame),這些可能是卡頓。
-
CPU(CPU Resources): 這個面積圖 (area chart) 表明了哪種事件在消耗 CPU 資源。
-
NET: 每種不同顏色的條代表一種資源。
-
條越長表明獲取該資源所花的時間越長。
-
每個條中的淺色部分代表等待時間(資源請求被髮送到收到第一個響應字節的時間),深色部分代表文件傳輸時間(從收到第一個字節到這個資源完全被下載好)
-
藍色 代表 HTML 文件,黃色 代表 Script 文件,紫色 代表 Stylesheets 文件, 綠色 代表 Media 文件,灰色 代表其他資源。
火焰圖(Flame Chart)
-
火焰圖(Flame Chart): 可視化 CPU 堆棧 (stack) 信息記錄。
-
從不同的角度分析框選區域 。例如:Network,Frames, Interactions, Main 等
-
在火焰圖面板上你可能看到三根垂直的線,藍線代表 DOMContentLoaded 事件,綠線代表渲染開始的時間 (time to first paint),紅線代表 load 事件。
其實這裏我們主要需要關注 Main,因爲他是主線程的一個執行情況的監控。點開後,我們可以看當前線程裏面一些任務的執行堆棧耗時,我們需要重點關注一些標紅(也就是有較高耗時)的任務。
詳細信息(Detail)
當有具體事件被選擇時,該面板展示這個事件的更多詳細信息。如果沒有事件被選擇,該面板展示當前所選時間段的一些信息。詳細面板支持精確到毫秒級別的分析,詳細面板主要分了
-
Summary 面板:從宏觀層面概括了瀏覽器加載的總時間,主要記錄了各個階段的名稱、佔用時間、顏色信息。這裏一般來說,需要着重關注的有兩個:一是黃色的區域,代表腳本執行時間,另一個是紫色的渲染時間。
-
顏色:藍色 ;英文:Loading;含義:加載
-
顏色:黃色 ;英文:Scripting;含義:腳本
-
顏色:紫色 ;英文:Rendering;含義:渲染
-
顏色:綠色 ;英文:Painting;含義:繪製
-
顏色:深灰 ;英文:Other;含義:其他
-
顏色:淺灰 ;英文:Idle;含義:空閒
-
Bottom-Up 面板:Bottom-Up 中一共三列數據
-
Self Time:代表任務自身執行所消耗的時間。
-
Total Time:代表此任務及其調用的附屬子任務一共消耗的時間。
-
Activity:具體的活動,部分帶有 Source Map 鏈接,可以直接定位到花費時間的具體源碼,方便我們進行定位和優化。Activity 中也有標註各自的顏色,和 Summary 中顏色是對應的。可以根據顏色快速判斷是腳本執行、加載、還是渲染過程。
-
Call-Tree 面板:Bottom-Up 類似事件冒泡,Call Tree 類似事件捕獲。自上而下的 Call-Tree 更符合我們的人類正常思維,可以更直觀地分析瀏覽器對頁面的 build 精確到毫秒級的情況
-
Event-Log 面板:展示所有階段包括 loading、javascripting、rendering、painting 中各事件的耗時情況,並提供了 filter 輸入框和按鈕供你快速過濾,常見的優化級別中一般用不到它。
Performance Api 監測網頁性能
除了瀏覽器爲我們提供的 Performance 性能檢測調試工具外,W3C 也定義了一套 Performance 標準,各個瀏覽器廠商基於標準提供了監控網絡性能的一系列基礎 Api,這些 Api 可以提供檢測白屏時間、首屏時間、用戶可操作的時間節點,頁面總下載的時間、DNS 查詢的時間、TCP 鏈接的時間等。我們完全可以利用這個搭建一個簡易的性能監控工具,當然監控系統包含了數據採集 -> 數據存儲 -> 清洗 -> 監控幾個過程,不過目前我們這裏簡單運用一下 Performance Api 就只考慮採集階段。
提供的能力
- 屬性篇
performance 的所有 Api&property 掛載在 window 下面的 performance 屬性中,可以看到目前提供的一系列屬性,關於各個屬性的介紹,參照網上對 aip 的解釋,有大量資料可供查詢。
如上圖所展現,performance 包含三個對象,分別爲 memory、navigation、timing
-
memory:是和內存相關的,其提供對內存使用情況的描述,我們可以使用這個屬性來訂閱頁面內存變化情況
-
jsHeapSizeLimit:堆內存大小的限制
-
totalJSHeapSize:總堆內存的大小
-
usedJSHeapSize:已經使用的堆內存大小
-
navigation:含義是頁面的來源信息,表述頁面怎麼跳轉過來的,該對象有 2 個屬性值
-
redirectCount**:** 記錄重定向次數,如果有重定向的話,頁面通過幾次重定向跳轉而來,默認爲 0
-
type**:** 頁面打開的方式,默認爲 0,可取值爲「0:表示正常進入該頁面 (非刷新、非重定向)」、「1:表示通過 window.location.reload 刷新的頁面」、「2:表示通過瀏覽器的前進、後退按鈕進入的頁面」、「255:表示非以上的方式進入頁面的」
-
timing:提供頁面加載過程中一系列關鍵時間點的高精度測量,它包含了網絡、解析、加載等一系列的時間數據,我們監控網頁性能也是基於此提供的屬性。爲了方便理解,從網上找了一張圖片來解釋關鍵節點的含義。
-
navigationStart**:** 一個頁面卸載結束時的時間戳。如果沒有上一個頁面的話,那麼該值會和 fetchStart 的值相同
-
redirectStart**:** 第一個 http 重定向開始的時間戳,如果沒有重定向,或者重定向到一個不同源的話,那麼該值返回爲 0
-
redirectEnd**:** 最後一個 HTTP 重定向完成時的時間戳。如果沒有重定向,或者重定向到一個不同的源,該值也返回爲 0
-
fetchStart**:** 瀏覽器準備好使用 http 請求抓取文檔的時間 (發生在檢查本地緩存之前)。
-
domainLookupStart**:**DNS 域名查詢開始的時間,如果使用了本地緩存話,或持久鏈接,該值則與 fetchStart 值相同
-
domainLookupEnd**:**DNS 域名查詢完成的時間,如果使用了本地緩存話,或 持久鏈接,該值則與 fetchStart 值相同
-
connectStart**:**HTTP 開始建立連接的時間,如果是持久鏈接的話,該值則和 fetchStart 值相同,如果在傳輸層發生了錯誤且需要重新建立連接的話,那麼在這裏顯示的是新建立的鏈接開始時間
-
secureConnectionStart**:**HTTPS 連接開始的時間,如果不是安全連接,則值爲 0
-
connectEnd:HTTP 完成建立連接的時間 (完成握手)。如果是持久鏈接的話,該值則和 fetchStart 值相同,如果在傳輸層發生了錯誤且需要重新建立連接的話,那麼在這裏顯示的是新建立的鏈接完成時間
-
requestStart**:**http 請求讀取真實文檔開始的時間,包括從本地讀取緩存,鏈接錯誤重連時
-
responseStart**:** 開始接收到響應的時間 (獲取到第一個字節的那個時候)。包括從本地讀取緩存
-
responseEnd**:**HTTP 響應全部接收完成時的時間 (獲取到最後一個字節)。包括從本地讀取緩存
-
unloadEventStart**:** 前一個網頁(和當前頁面同域)unload 的時間戳,如果沒有前一個網頁或前一個網頁是不同的域的話,那麼該值爲 0
-
unloadEventEnd**:** 和 unloadEventStart 相對應,返回是前一個網頁 unload 事件綁定的回調函數執行完畢的時間戳。
-
domLoading**:** 開始解析渲染 DOM 樹的時間
-
domInteractive**:** 完成解析 DOM 樹的時間(只是 DOM 樹解析完成,但是並沒有開始加載網頁的資源)
-
domContentLoadedEventStart**:**DOM 解析完成後,網頁內資源加載開始的時間
-
domContentLoadedEventEnd**:**DOM 解析完成後,網頁內資源加載完成的時間
-
domComplete**:**DOM 樹解析完成,且資源也準備就緒的時間。Document.readyState 變爲 complete,並將拋出 readystatechange 相關事件
-
loadEventStart**:**load 事件發送給文檔。也即 load 回調函數開始執行的時間,如果沒有綁定 load 事件,則該值爲 0
-
loadEventEnd**:**load 事件的回調函數執行完畢的時間,如果沒有綁定 load 事件,該值爲 0
- 方法篇
如上圖,截取的圖片,Performance 提供了一些,這裏我主要介紹一下 now() 方法和 getEntries() 方法。其他的網上資料也比較多和全,可以查閱 https://juejin.cn/post/6844903801518981133#heading-54
-
now 方法:提供精度相對較高的時間計算主要有下面兩個特點
-
和 JavaScript 中其他可用的時間類函數(比如
Date.now
)不同的是,window.performance.now()
返回的時間戳沒有被限制在一毫秒的精確度內,相反,它們以浮點數的形式表示時間,精度最高可達微秒級。 -
另外一個不同點是,
window.performance.now()
是以一個恆定的速率慢慢增加的,它不會受到系統時間的影響(系統時鐘可能會被手動調整或被 NTP 等軟件篡改)。
我們可以用這個方法來衡量函數執行的時間,達到監控函數執行效率的效果
`const fun = () => {
// do something
}
const startExcuteTime = window.performance.now();
fun();
const endExcuteTime = window.performance.now();
console.log("fun 函數執行了" + (endExcuteTime - startExcuteTime) + "毫秒.")
`
- getEntries 方法:通過該方法,我們能夠拿到頁面所有資源請求的詳細情況,這個方法返回值根據傳入的參數不同會有不同。但返回值的結構都是一樣的,都是一個對象數組,每個對象是對資源的請求過程的描述,在 console 調用 performance.getEntries(),可以直接看到當前頁面所有資源的加載過程。
點開數組中的元素,每個元素詳細記錄了資源請求關鍵節點的時間,所以我們完全可以利用這個來實現對資源的請求監控。
更多 Api 細節,可以參考司內文章再看一次 Performance 接口 [2]
簡單實現指標計算
一個監控系統大致可以分爲這個下面階段,我們這裏就先關注一下數據的採集階段。數據採集階段設計到兩點,一個是數據的蒐集,一個是數據的上報。
-
數據的蒐集:數據蒐集依賴於 Performance Api 拿到性能數據,我們參照一定的計算指標,得到計算值的集合。
-
數據的上報:將蒐集到的數據上報到服務器,上報使用的方式也就是發送一個 http 請求, 不過目前因爲監控數據採用 XHR 的請求上報,受到條件限制比較多,數據容易丟失,容易漏報,且對頁面性能有一定的影響。而 sendBecan 是瀏覽器爲了解決這些問題,它會使用戶代理在有機會時異步地向服務器發送數據,同時不會延遲頁面的卸載或影響下一導航的載入性能。這就解決了提交分析數據時的所有的問題:數據可靠,傳輸異步並且不會影響下一頁面的加載。具體可以參考:https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/sendBeacon
下面是 Slardar 源碼截圖,可以看到他們上報監控數據優先採用的 sendBecan,降級策略爲 XHR 請求。
可以通過 performance api 來實現我們經常關注的一些指標的計算和上報
` 重定向耗時 = redirectEnd - redirectStart;
DNS 查詢耗時 = domainLookupEnd - domainLookupStart;
TCP 鏈接耗時 = connectEnd - connectStart;
HTTP 請求耗時 = responseEnd - responseStart;
解析 dom 樹耗時 = domComplete - domInteractive;
白屏時間 = responseStart - navigationStart;
DOMready 時間 = domContentLoadedEventEnd - navigationStart;
onload 時間 = loadEventEnd - navigationStart;
`
現存的一些網頁性能檢測工具
除了前面兩種方式能夠檢測頁面性能外,還有一些三方的工具 or 平臺爲我們提供了檢測能力。
-
LightHouse
-
PageSpeed
-
YSlow
下面是使用 LightHouse 的截圖,Lighthouse 生成的不僅僅是一些性能相關的數據,他除了能給我們提供頁面性能檢測外,還爲我們列出了一系列的優化建議,我們對網站或者頁面的優化,可以參照建議一步步進行優化。
- React 中性能定位工具
提供組件級別的渲染分析 React 性能測量和分析 [3]React Profiler 介紹 – React Blog[4]
Performance 工具小試
學浪老師端項目代碼目前跑在兩個大的宿主環境中「CEF 套殼」「瀏覽器」,項目一期的時候,整體項目是採用的單入口多路有方式,並且來說項目的打包也沒有優化,整體上呈現出
-
訪問混亂(瀏覽器能訪問 CEF 殼子內的一系列路由)
-
打包混亂(出現多種重複打包,導致編譯慢)
-
引用混亂(因爲是是一套入口,很多隻是在 CEF 內引用的文件,在單入口文件中引用了,導致瀏覽器加載了一系列不必要的靜態資源)
上面的一系列問題,導致學浪整體頁面加載速度非常的慢,後續學浪側專門組織了一次大的重構優化,進行了項目入口的拆分 & 打包過程的的拆分,整體上現的學浪項目結構是多入口多路由,且區分宿主環境的。從目前的表現來看,頁面的加載速度相對於以前提升了非常多。目前的加載時長度在我當前網絡情況下 DomContentLoad 大概在 2S 左右
是否還有優化空間,將頁面加載時間降得更低?我們可以通過 Performance 的 NetWork 火焰圖看看到底是哪些文件的加載耗時長,延長了 DomContentLoad 觸發時機。首先 DomContentLoad 事件觸發影響因素有 html 下載、dom 解析、js 腳本下載 & 執行,都會影響 DomContentLoad 觸發。
通過觀察 NetWork 的情況,很明顯看到 DCL 的時機,在一個 encoding.js 文件加載完成後,再觸發的,而這個文件的加載時間長達 2.13s,可謂是佔據了首頁加載的 80% 左右的時間,那麼就想如何優化這個腳本的加載時長?有幾種思路
-
壓縮 encoding.js 使得體積減小(前提是沒有壓縮的情況下)
-
看是否是首頁強依賴文件(如果是後續依賴,可以採用異步腳本 defer or async 來解決下載阻塞了 DomContentLoad 問題)
-
看是否真正有使用(最好的辦法就是不用 😈,這樣就完全省去了時間)
-
當然還有其他一系列客戶端緩存、cdn 加速等一系列策略
找到了原因和思路,於是開始先對文件背景溯源,發現由於這個文件是爲了處理一些教室內 sdk 在不同瀏覽器內的 pollfiy,但是目前因爲通過階段一的大包 & 入口的拆分,教室內 sdk 的相關資源不會出現在瀏覽器環境加載了,因此在瀏覽器環境內實際不再使用,godless 我們可以直接刪除,看下效果。(實際看 encoding.js 文件也是沒有壓縮過的,如果實際文件有在用,我們可以採取使用壓縮文件)
整體上 DCL 的觸發時間由 2.13s 降低爲 972ms,效果還是比較明顯的。通過一個很小的分析案例 + 很小的優化說明下 Performance 面板中相關模塊的使用。
總結
本文主要介紹了通過工具的使用來定位性能問題以及通過 Performance Api 來自己做一些指標的計算統計,目前公司內的 Sladar 已經爲我們提供了比較全面的數據分析,但是對於一些定位頁面性能的基礎工具和基礎能力的瞭解對於日常的工作中也是有幫助的
參考資料
[1] 雅虎 35 條軍規:https://juejin.cn/post/6844903657318645767
[2] 再看一次 Performance 接口:https://bytedance.feishu.cn/docs/doccnZoHj24ab8HVh42aQEowZGh#
[3]React 性能測量和分析:https://juejin.cn/post/6844903869378641933#heading-4
[4]React Profiler 介紹 – React Blog:https://zh-hans.reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html
[5]React 性能測量和分析:https://juejin.cn/post/6844903869378641933#heading-4
[6]React Profiler 介紹 – React Blog:https://zh-hans.reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/d-vudj57Hyf8dQXxkYkIxw