前端效能精進之優化方法論(二)——分析

2023-02-27 09:01:01

  在上一節中曾提到過兩種效能監控:SYN 和 RUM,那麼對應的也有兩種分析:資料分析和實驗室分析。

  資料分析會通過採集上來的效能資訊來剖析和定位可能存在的各種問題。

  實驗室分析會通過某個線上或原生的測試工具對頁面進行單點測試,得出效能分析報告。

  本文會對前者介紹一些分析實踐,後者會介紹一些比較有名的效能測試工具。

  資料分析的前端程式碼已上傳至 shin-admin,後端程式碼上傳至 shin-server

一、資料分析

  在將資料採集到後就需要立刻儲存,並且按百分位數計算後,需要定期計算和清理。

  各類圖表的輔助可以更好的定位到發生的效能問題。

1)儲存

  在將效能資料採集到後,就需要將它們儲存到資料庫中,例如 MySQL。

  為了避免拖垮伺服器,在伺服器端接收時會通過佇列來非同步新增。

  以我當前公司的實踐為例,將效能資料儲存到 web_performance 表中,表結構如下。

CREATE TABLE `web_performance` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `load` int(11) NOT NULL DEFAULT '0' COMMENT '頁面載入總時間',
  `ready` int(11) NOT NULL DEFAULT '0' COMMENT '使用者可操作時間',
  `paint` int(11) NOT NULL DEFAULT '0' COMMENT '白屏時間',
  `screen` int(11) NOT NULL DEFAULT '0' COMMENT '首屏時間',
  `measure` varchar(1000) COLLATE utf8mb4_bin NOT NULL COMMENT '其它測量引數,用JSON格式儲存',
  `ctime` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `day` int(11) NOT NULL COMMENT '格式化的天(冗餘欄位),用於排序,20210322',
  `hour` tinyint(2) NOT NULL COMMENT '格式化的小時(冗餘欄位),用於分組,11',
  `minute` tinyint(2) DEFAULT NULL COMMENT '格式化的分鐘(冗餘欄位),用於分組,20',
  `identity` varchar(30) COLLATE utf8mb4_bin NOT NULL COMMENT '身份',
  `project` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '專案關鍵字,關聯 web_performance_project 表中的key',
  `ua` varchar(600) COLLATE utf8mb4_bin NOT NULL COMMENT '代理資訊',
  `referer` varchar(200) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '來源地址',
  `referer_path` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '來源地址中的路徑',
  `timing` text COLLATE utf8mb4_bin COMMENT '瀏覽器讀取到的效能引數,用於排查',
  `resource` text COLLATE utf8mb4_bin COMMENT '靜態資源資訊',
  PRIMARY KEY (`id`),
  KEY `idx_project_day` (`project`,`day`),
  KEY `idx_project_day_hour` (`project`,`day`,`hour`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='效能監控'

  referer_path 欄位,用於分析指定頁面的效能。

  表中的 project 欄位會關聯 web_performance_project 表(結構如下所示)中的 key。

CREATE TABLE `web_performance_project` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `key` varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT '唯一值',
  `name` varchar(45) COLLATE utf8mb4_bin NOT NULL COMMENT '專案名稱',
  `ctime` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1:正常  0:刪除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='效能監控專案';

  效能專案就是要監控的頁面,與之前不同,效能的監控粒度會更細,因此需要有個後臺專門管理這類資料。

  key 值是通過名稱得到 16 位 MD5 字串,需要引入 Node.js 的 cryto 庫,如下所示。

const crypto = require('crypto');
const key = crypto.createHash('md5').update(name).digest('hex').substring(0, 16);

  可以對長期維護的網頁建立單獨的效能專案,而對於那些臨時活動可以共用一個專案。

2)百分位數

  均值會受極值的影響,從而讓它不夠準確,無法真實的反映出使用者的效能情況。

  故而選擇了百分位數來解決極值問題,例如前 95% 使用者的首屏時間在 2s 內,

  這種寫法也叫 TP95,表示 95 分位數,TP 是 Top Percentile 的縮寫。

  95 分位數是比較高的統計指標,意味著大多數的使用者都能享受到更好的效能體驗。

  為了能看到變化趨勢,可以採用圖表的方式,例如折線圖,如下所示,橫座標可按天或小時。

  

3)定時任務

  每天可以選一個時間(例如凌晨三點),來統計昨天的紀錄檔資訊。

  例如將計算得到的統計資訊以 JSON 格式(如下所示)儲存到資料庫表的一個欄位中。

  這個欄位可以是 TEXT 型別或更大的 MEDIUMTEXT,一天只插入一條記錄。

{
  hour: {
    x: [11, 14],
    load: ["158", "162"],
    ready: ["157", "162"],
    paint: ["158", "162"],
    screen: ["157", "162"]
  },
  minute: {
    11: {
      x: [11, 18, 30],
      load: ["157", "159", "160"],
      ready: ["156", "159", "160"],
      paint: ["157", "159", "160"],
      screen: ["156", "159", "160"]
    },
    14: {
      x: [9, 16, 17, 18],
      load: ["161", "163", "164", "165"],
      ready: ["161", "163", "164", "165"],
      paint: ["161", "163", "164", "165"],
      screen: ["161", "163", "164", "165"]
    }
  }
}

  還可以選一個時間來做資料清理,因為沒有必要一直將這麼多的資料保留著。

4)資源瀑布圖

  通過資源瀑布圖可以檢視當時的資源載入情況。

  在上報效能引數時,將靜態資源的耗時通過 getEntriesByType() 方法得到(如下所示),然後一起打包給伺服器。

// 靜態資源列表
const resources = performance.getEntriesByType("resource");
const newResources: TypeSendResource[] = [];
resources && resources.forEach((value: PerformanceResourceTiming): void => {
    // 過濾 fetch 請求
    if (value.initiatorType === "fetch") return;
    // 只儲存 1 分鐘內的資源
    if (value.startTime > 60000) return;
    newResources.push({
      name: value.name,
      duration: rounded(value.duration),
      startTime: rounded(value.startTime)
    });
  });
obj.resource = newResources;

  由於我本地業務請求使用的是 XMLHTTPRequest,因此在程式碼中會過濾掉 fetch 請求,只在上報監控資料時採用了 fetch() 函數。

  這可以根據實際情況來處理。在蒐集資源時,1 分鐘以外的都會捨棄,並且只記錄了資源名稱、耗時和開始時間。

  最終的效果如下圖所示,包含一個橫向的柱狀圖,並且在圖中會標註白屏、首屏、load 和 DOMContentLoaded 的時間點。

  

  這樣能對資源的載入做更直觀的比較,便於定位效能問題。

5)堆疊柱狀圖

  先將所有的效能記錄統計出來,然後分別統計白屏和首屏 1 秒內的數量、1-2 秒內、2-3 秒內、3-4 秒內、4+秒的數量,白屏的 SQL 如下所示。

SELECT COUNT(*) FROM `web_performance` WHERE `ctime` >= '2022-06-12 00:00' and `ctime` < '2022-06-13 00:00';
SELECT COUNT(*) FROM `web_performance` WHERE `paint` <= 1000 and `ctime` >= '2022-06-12 00:00' and `ctime` < '2022-06-13 00:00';
SELECT COUNT(*) FROM `web_performance` WHERE `paint` > 1000 and `paint` <= 2000 and `ctime` >= '2022-06-12 00:00' and `ctime` < '2022-06-13 00:00';
SELECT COUNT(*) FROM `web_performance` WHERE `paint` > 2000 and `paint` <= 3000 and `ctime` >= '2022-06-12 00:00' and `ctime` < '2022-06-13 00:00';
SELECT COUNT(*) FROM `web_performance` WHERE `paint` > 3000 and `paint` <= 4000 and `ctime` >= '2022-06-12 00:00' and `ctime` < '2022-06-13 00:00';
SELECT COUNT(*) FROM `web_performance` WHERE `paint` > 4000 `ctime` >= '2022-06-12 00:00' and `ctime` < '2022-06-13 00:00';

  算出後,分母為總數,分子為上述五個值,組成一張堆疊柱狀圖,類似於下面這樣,每種顏色程式碼一個佔比。

  

  這樣就能直觀的看到優化後的效能變化了,可更快的反饋優化結果。

6)階段時序圖

  在將統計的引數全部計算出來後,為了能更直觀的發現效能瓶頸,設計了一張階段時序圖。

  描繪出 TTFB、responseDocumentTime、initDomTreeTime、parseDomTime 和 loadEventTime 所佔用的時間,如下所示。

  橙色豎線表示白屏時間,黑色豎線表示首屏時間。移動到 id 或來源地址,就會提示各類引數。

  

二、實驗室分析

  成熟的效能優化工具不僅能給出網路、渲染等資訊,還能給出各種經過實踐的優化建議,讓我們的優化工作事半功倍。

1)Chrome DevTools

  Chrome 的 DevTools 是一款內建的開發者工具,可用於偵錯頁面、檢視網路請求、列印紀錄檔等常規功能。

  還提供了 Performance 面板,專門用於效能分析,可檢視效能引數的時間點、各階段的耗時、記憶體變化等。

  限於篇幅原因,本文只會重點講解 NetworkPerformance 兩塊面板。

  在 Network 中會呈現頁面中所有的網路請求(可指定型別),並且會給出狀態碼、協定、請求瀑布圖等資訊。

  

  藍線是 DOMContentLoaded(DCL) 事件觸發的時間點,紅線是 Load 事件觸發的時間點。

  No throttling 用於模擬網速,模擬 4G、3G 或 Offline 離線等網路,在實際開發中很常用。

  Performance 需要點選錄制按鈕後,才會開始分析,如下圖所示,內容還是比較多的。

  

  在 Frames 中可以檢視各個階段的頁面截圖,Timings 中可以檢視到 FP、LCP 等效能引數的時間點。

  Main 指的是當前頁面,火焰圖中描繪了 JavaScript 的效能。

  例如 Parse HTML、Evaluate Script(載入 JavaScript)、Compile Script(執行 JavaScript)等。

  在 Summary 的環形圖中,可以看到火焰圖中各種顏色對應的操作,例如:

  • 藍色 Loading 表示載入中,對 HTML、CSS 等資源進行解析工作。
  • 黃色 Scripting 表示執行指令碼,例如執行函數、觸發事件等。
  • 紫色 Rendering 表示渲染,包括 HTML 和 CSS 的變化,例如重繪或重排。
  • 綠色 Painting 表示繪製,將合成的圖層繪製到螢幕中。

  Performance 中的 Network 更注重時序和優先順序,可在此檢視資源載入是否符合預期。

  

  記憶體檢視是一個用不同顏色標註的折線圖(如下所示),包括 JavaScript 堆、DOM 節點數量、事件監聽器數量等資訊。

  

  此處只是蜻蜓點水般介紹了下 Performance 的功能,詳細內容還可以去參考官方英語檔案

2)WebPageTest

  WebPageTest 是一款線上效能分析工具,通過佈置一些特定的場景進行測試,例如不同的網速、瀏覽器、位置等。

  測試完成後,會給出一份效能報告,包括優化等級、效能引數、請求瀑布圖、網頁幻燈片快照、視訊等。

  WebPageTest 的原理是將設定引數傳送到後臺,然後通過瀏覽器相關的代理程式,啟動 Chrome、Firefox 或 IE。

  執行完畢後將資料回傳給後臺,後臺再將資料儲存起來,最後通過各種形式(統計圖、表格等),將分析過的資料呈現給使用者。

  如果是新手,官方還提供了一份快速入門指南作為參考。多年前曾對 WebPageTest 做過分析,有些內容仍然具有參考價值。

  在選擇完瀏覽器和地區後(如下圖所示),點選 Start Test 就開始測試了。

  

  效能報告的第一部分是優化建議和各種指標,包括 LCP、FCP、CLS 等,如下所示。

  

  Speed Index 表示速度指數,衡量頁面內容填充的速度(越低越好),適合頁面優化前後的對比。

  在 Requests Details 中,呈現了請求資訊,檢視包括資源瀑布圖、連線時序圖、請求耗時表、各條請求的頭資訊。

  

  

  

  在 WebPageTest 的幻燈片檢視(Filmstrip View)中,在滑動卷軸時,下面會有根紅線對應這個時刻的資源載入情況。

  

3)Lighthouse

  Lighthouse 會對測試的網站進行打分,包括效能、可存取性、最佳實踐、SEO 和 PWA 五個部分,並且會提供這五個部分的優化建議。

  測量的指標有 6 個,FCP、SI、LCP、CLS、TTI 和 TBT,如下所示。

  

  Lighthouse 的使用方法有多種,第一種是在 Chrome 的 DevTools 中選擇 Lighthouse 面板,不過要使用的話,得安裝代理。

  另一種使用方法是將 Lighthouse 下載到本地,安裝後使用命令來執行測試,如下所示。

lighthouse https://www.pwstrick.com --output html --output-path ./report/report.html

  Lighthouse 給出一些切實可行的優化建議,如下圖所示,在每條建議中,還會給出 Learn More 的連結,瞭解更多優化細節。

  

  雖然是英文的,但用翻譯軟體或自己閱讀都比較容易理解其含義。

  例如修改影象格式、壓縮影象、預載入影響 LCP 的影象、延遲載入螢幕外的影象、減少未使用的指令碼、剔除阻塞渲染的資源等。

總結

  資料分析和實驗室分析是效能優化的兩塊重要組成部分。

  資料分析在採集到效能資訊後,會先進行紀錄檔儲存,然後按指定的百分位數進行資料整理,最後還會定期進行刪除。

  在管理介面提供了幾種檢視來更好的分析效能瓶頸,包括資源瀑布圖、堆疊柱狀圖和階段時序圖。

  在實驗室分析中,主要介紹了 3 款效能測試工具,包括 DevTools Performance、WebPageTest 和 Lighthouse。

  3 款軟體都非常優秀,可以幫助開發人員更快、更準的進行優化工作。