在[上一節](https://www.cnblogs.com/strick/p/17072053.html)中曾提到過兩種性能監控:SYN 和 RUM,那么對應的也有兩種分析:數據分析和實驗室分析。
  數據分析會通過采集上來的性能信息來剖析和定位可能存在的各種問題。
  實驗室分析會通過某個線上或本地的測試工具對頁面進行單點測試,得出性能分析報告。
  本文會對前者介紹一些分析實踐,后者會介紹一些比較有名的性能測試工具。
  數據分析的前端代碼已上傳至[shin-admin](https://github.com/pwstrick/shin-admin),后端代碼上傳至[shin-server](https://github.com/pwstrick/shin-server)。
## 一、數據分析
  在將數據采集到后就需要立刻存儲,并且按百分位數計算后,需要定期計算和清理。
  各類圖表的輔助可以更好的定位到發生的性能問題。
**1)存儲**
  在將性能數據采集到后,就需要將它們存儲到數據庫中,例如 MySQL、MongoDB 等。
  2023-04-13 如果將數據存儲在 MongoDB 中,那么就可以將 JSON 中的屬性作為條件來查詢,例如查詢 measure 對象中的 TTFB 大于 10 的記錄。
  而 MySQL 中的 JSON 都是字符串序列化后再存儲到數據庫中的,所以如果要對某個屬性做查詢,會比較困難。
  為了避免拖垮服務器,在服務端接收時會通過隊列來異步新增。
  以我當前公司的實踐為例,將性能數據存儲到 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 '首屏時間',
`network` int(11) NOT NULL DEFAULT '0' COMMENT '網絡時間,TTFB + responseDocumentTime',
`measure` varchar(1000) COLLATE utf8mb4_bin NOT NULL COMMENT '其它測量參數,用JSON格式保存',
`measure_bottleneck` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '瓶頸指標',
`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(300) 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 '靜態資源信息',
`resource_count` int(11) DEFAULT '0' COMMENT '靜態資源數量',
`os_name` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '操作系統名稱',
`os_version` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '操作系統版本',
`app_version` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '客戶端版本',
`ip` varchar(200) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '客戶端IP',
`country` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '國家',
`province` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '省份',
`city` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '城市',
`isp` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '網絡運營商',
PRIMARY KEY (`id`),
KEY `idx_project_day` (`project`,`day`),
KEY `idx_project_day_hour` (`project`,`day`,`hour`),
KEY `idx_ctime` (`ctime`),
KEY `idx_identity_referer_project` (`identity`, `referer_path`, `project`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='性能監控'
~~~
  referer\_path 字段,用于分析指定頁面的性能。
  2023-05-24 將表中的 referer 字段長度從 200 增加到 300。
  2023-05-24 在表中新增 os_name、os_version、app_version 和 ip 四個字段,用于做性能日志的分析。
  2023-06-04 在表中新增 network 網絡時間字段,其值是 TTFB 和 responseDocumentTime 相加得到的和。
  2023-06-07 在表中新增 country、province、city 和 isp 四個字段,存儲 IP 解析后的信息,包括國家、省份、城市和網絡運營商。
  IP 解析可以選擇付費服務,得到的結果比較準確,并且還能保持更新。或者選擇開源的[離線 IP 庫](https://github.com/lionsoul2014/ip2region),雖然精度不高,但是免費。
  2023-09-26 在表中新增 measure_bottleneck 字段,存儲瓶頸指標,可選值包括 TTFB、responseDocumentTime、initDomTreeTime、parseDomTime、loadEventTime。
  2023-12-15 在表中新增 resource_count 字段,存儲靜態資源的數量。
  表中的 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 分位數是比較高的統計指標,意味著大多數的用戶都能享受到更好的性能體驗。
  為了能看到變化趨勢,可以采用圖表的方式,例如折線圖,如下所示,橫坐標可按天或小時。
:-: 
  2023-06-15 增加分位數下拉選擇框,可選擇 50、75、95 三項。
  75 分位數是大多數用戶的表現,95 分位數是比較極端的場景的用戶表現,當前默認采用 95 分位數。
:-: 
  2023-07-06 增加分位數箱型圖,這是一種用作顯示一組數據分散情況資料的統計圖。
  在此圖中,將 95、75、50、25 以及最小值全部繪制在一個箱子中,清晰地看出這批數據的中位數、異常值、分布區間等信息。
:-: 
**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 => {
const { name, initiatorType, startTime, duration,transferSize } = value;
// 過濾 fetch 請求
if(initiatorType === 'fetch') return;
// 只存儲 1 分鐘內的資源
if(startTime > 60000) return;
newResources.push({
name: name,
duration: rounded(duration),
startTime: rounded(startTime),
transferSize: transferSize // 資源的總大小,包括HTTP首部
});
});
obj.resource = newResources;
~~~
  由于我本地業務請求使用的是 XMLHTTPRequest,因此在代碼中會過濾掉 fetch 請求,只在上報監控數據時采用了 fetch() 函數。
  這可以根據實際情況來處理。在搜集資源時,1 分鐘以外的都會舍棄,并且只記錄了資源名稱、耗時和開始時間。
  2023-12-04 getEntriesByType("resource") 讀取的是一個[PerformanceResourceTiming](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming)實例,它有許多個屬性,其中:
* transferSize:資源的總大小,包括 HTTP 首部。
* encodedBodySize:資源的壓縮大小,不包括 HTTP 首部。
* decodedBodySize:資源的原始大小,不包括 HTTP 首部。
  在上述的 forEach 遍歷的最后增加如下代碼,原先計劃存儲白屏和首屏兩個時刻的資源大小。
  但是發現白屏之前的資源大小要么是 0,要么與首屏時刻的比較接近。
~~~
// 過濾 undefined 和 0 的字段
if(!transferSize)
return;
// 計算白屏之前的資源總大小
if(startTime <= obj.firstPaint) {
transferPaintSize += transferSize;
}
// 計算首屏之前的資源總大小
if(startTime <= obj.firstScreen) {
transferScreenSize += transferSize;
}
~~~
  分析發現,白屏在 1 秒以內,大小為 0 的情況會比較多,可以理解此時獲得了 HTML 文件,但還沒開始請求結構內的資源。
  而在 1 秒以上時,大小接近的情況會比較多。既然此數據并不穩定,那么就只記錄首屏之前的資源總大小(transferScreenSize)。
  在后續的散點圖中,就能展示首屏和資源之間的相關性。
  最終的資源瀑布圖效果如下所示,包含一個橫向的柱狀圖,并且在圖中會標注白屏、首屏、load 和 DOMContentLoaded 的時間點。
:-: 
  這樣能對資源的加載做更直觀的比較,便于定位性能問題。
  2023-07-11 發現 DOMContentLoaded 和 load 的時間異常巨大(如下圖所示),瀑布圖已經消失,查看數據庫了解到性能參數讀取自第一版的規范。
:-: 
  所以需要對參數進行額外的計算,即與 fetchStart 相減。
~~~
const caculateMeasure = {
paint: record.paint,
screen: record.screen,
ready: originalTiming.domContentLoadedEventStart,
load: originalTiming.loadEventStart
};
// 需要判斷性能參數是否來自于第一版
if (!originalTiming.entryType) {
caculateMeasure.ready = originalTiming.domContentLoadedEventStart - originalTiming.fetchStart;
caculateMeasure.load = originalTiming.loadEventStart - originalTiming.fetchStart;
}
~~~
**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';
~~~
  算出后,分母為總數,分子為上述五個值,組成一張堆疊柱狀圖,類似于下面這樣,每種顏色代碼一個占比。
:-: 
  這樣就能直觀的看到優化后的性能變化了,可更快的反饋優化結果。
  2024-03-19 將白屏和首屏的堆疊柱狀圖修改成堆疊面積圖,為了能更好的查看變化趨勢。
:-: 
**6)階段時序圖**
  在將統計的參數全部計算出來后,為了能更直觀的發現性能瓶頸,設計了一張階段時序圖。
  描繪出 TTFB、responseDocumentTime、initDomTreeTime、parseDomTime 和 loadEventTime 所占用的時間,如下所示。
  橙色豎線表示白屏時間,黑色豎線表示首屏時間。移動到 id 或來源地址,就會提示各類參數。
:-: 
  2023-05-24 對階段時序圖進行了一次比較大的優化。
  在階段時序圖中,增加幾個過濾條件,并且點擊 id,可以從屏幕右側邊緣滑出浮層面板(采用[抽屜式的交互](https://4x.ant.design/components/drawer-cn/))。
  2023-09-25 增加瓶頸指標,這樣就能一眼了解到最費時的階段,而性能測試是一個集成了 Lighthouse 的系統,點擊就能生成一份性能報告。
  2023-09-26 將各個階段的數量,在查詢后也一并列出,按從大到小排列。每條記錄再增加區域、identity、操作系統和手機型號。
:-: 
  在彈層中,基本信息包括 IP、客戶端版本、解析的 UA 信息等。
:-: 
  機型提供了跳轉地址,點擊后就能自動到搜索引擎頁面。
:-: 
  性能數據包括 Web Core Vitals 中的 LCP、FP 等,還有經過計算的性能數據,以及瀏覽器提供的原始性能參數。
:-: 
  2024-03-21 為部分指標增加顏色標識,更容易識別當前數據是良好(綠色)、待提升(橙色)還是糟糕(紅色)的。
:-: 
  2023-09-25 增加性能雷達圖,將那 5 個階段指標通過雷達圖描繪出,能更容易觀察性能差異。
:-: 
  資源瀑布圖就是之前那功能,只是搬到了彈層中,就能通過當前日志自動繪制出來,而不必再手動搜索了。
  當日性能覆蓋會統計處于相同性能參數范圍的白屏和首屏數據,例如當前是1-2秒內,則只統計此區間段內的數據。
:-: 
  其中操作系統會加上版本號,例如 Android 13、iOS 15.3.1。客戶端會附帶 APP 的版本號。區間內操作系統的數量,也會有所統計。
:-: 
  2023-06-26 在正式上線后發現,操作系統細分的小版本眾多,用餅圖呈現會比較雜亂。
:-: 
  于是就改成了條形圖,并且做了排序,只展示前 20 條數據,呈現效果非常清晰,重點信息也能突出。
:-: 
  最后會展示行為軌跡,性能日志中有個 identity 字段,可以關聯前端監控日志,這樣就能了解當前性能是否真的在影響用戶的使用體驗。
:-: 
  2023-07-04 增加性能榜單,將白屏時間超過 2 秒的數據分成 3 組,每組顯示頁面路徑和數量,倒序排列。
:-: 
  從榜單中就可知性能差的頁面有哪些,需要重點關注,當然,如果頁面訪問量大,那差頁面的占比自然也會高點。
**7)散點圖**
  2023-12-01 散點圖會將所有的數據以點的形式展現在直角坐標系上,用于展示兩個變量之間的相關性,而橫軸和縱軸不一定從 0 開始。
  如果兩個變量的變化方向相同,則稱之為正相關,同時變大或同時變小。若變化相反,則是負相關;沒有明顯的關系,則是不相關。
:-: 
  散點圖適合那些變量之間存在密切關系,但是這些關系不能用數學公式精確表達的場景。
  但是在分析過程中需要注意,這兩個變量之間的相關性并不等同于確定的因果關系。
  在性能監控中,將關鍵指標(白屏和首屏)和性能參數(TTFB、responseDocumentTime等)之間的關系繪制成了散點圖。
  在查詢條件中,還可以選擇性能項目和分位數,如下圖所示。
:-: 
**8)氣泡圖**
  2023-12-15 氣泡圖通常用于展示和比較數據之間的關系和分布,通過比較氣泡位置和大小來分析數據維度之間的相關性。
  氣泡圖是散點圖的變形,由三個變量組成,例如用氣泡圖展示歷史上世界各國人均收入、人口數量和壽命的關系。
  在性能監控中,增加頁面的資源數量、首屏時間和訪問量之間的關系,SQL 語句如下所示。
~~~
SELECT `referer_path`, COUNT(*) as size, ROUND(AVG(`resource_count`)) as x, ROUND(AVG(`screen`)) as y
FROM `web_performance` WHERE `ctime` >= '2023-12-15 00:00' and `ctime` < '2023-12-26 00:00'
GROUP BY `project`;
~~~
  其中資源數量和首屏時間都取平均數。在查詢條件中,可以選擇時間范圍,如下圖所示。
:-: 
## 二、實驗室分析
  成熟的性能優化工具不僅能給出網絡、渲染等信息,還能給出各種經過實踐的優化建議,讓我們的優化工作事半功倍。
**1)Chrome DevTools**
  Chrome 的[DevTools](https://developer.chrome.com/docs/devtools/overview/)是一款內置的開發者工具,可用于調試頁面、查看網絡請求、打印日志等常規功能。
  還提供了 Performance 面板,專門用于性能分析,可查看性能參數的時間點、各階段的耗時、內存變化等。
  限于篇幅原因,本文只會重點講解[Network](https://developer.chrome.com/docs/devtools/network/reference/)和[Performance](https://developer.chrome.com/docs/devtools/performance/reference/)兩塊面板。
  在 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 的功能,詳細內容還可以去參考官方[英語文檔](https://developer.chrome.com/docs/devtools/performance/reference/)。
**2)WebPageTest**
  WebPageTest 是一款線上[性能分析工具](https://www.webpagetest.org/),通過布置一些特定的場景進行測試,例如不同的網速、瀏覽器、位置等。
  測試完成后,會給出一份性能報告,包括優化等級、性能參數、請求瀑布圖、網頁幻燈片快照、視頻等。
  WebPageTest 的原理是將配置參數發送到后臺,然后通過瀏覽器相關的代理程序,啟動 Chrome、Firefox 或 IE。
  執行完畢后將數據回傳給后臺,后臺再將數據保存起來,最后通過各種形式(統計圖、表格等),將分析過的數據呈現給用戶。
  如果是新手,官方還提供了一份快速[入門指南](https://docs.webpagetest.org/getting-started/)作為參考。多年前曾對 WebPageTest 做過[分析](https://www.cnblogs.com/strick/category/980651.html),有些內容仍然具有參考價值。
  在選擇完瀏覽器和地區后(如下圖所示),點擊 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](https://github.com/GoogleChrome/lighthouse)下載到本地,安裝后使用命令來執行測試,如下所示。
~~~
lighthouse https://www.pwstrick.com --output html --output-path ./report/report.html
~~~
  Lighthouse 給出一些切實可行的優化建議,如下圖所示,在每條建議中,還會給出 Learn More 的鏈接,了解更多優化細節。
:-: 
  雖然是英文的,但用[翻譯軟件](https://www.deepl.com/zh/translator)或自己閱讀都比較容易理解其含義。
  例如修改圖像格式、壓縮圖像、預加載影響 LCP 的圖像、延遲加載屏幕外的圖像、減少未使用的腳本、剔除阻塞渲染的資源等。
  2023-07-21 在階段時序圖列表中,增加性能測試鏈接,用于[Lighthouse](https://github.com/GoogleChrome/lighthouse)的測試。
:-: 
  Lighthouse 可以作為一個庫,在 Node.js 中使用,借助其能力,可以做些自定義的性能測試。
  Chrome 瀏覽器中默認有一欄是 Lighthouse,可用于測試,但是很奇怪,我每次測試頁面,得到的分值都很低,用移動網絡訪問又很快。
  在編寫代碼時發現,有可能是 Lighthouse 默認的網絡限制導致的評分不準,默認[4G](https://github.com/GoogleChrome/lighthouse/blob/main/core/config/constants.js)的下載速度才 1.6Kbps,比我們國內網絡慢很多。
~~~
mobileSlow4G: {
rttMs: 150,
throughputKbps: 1.6 * 1024,
requestLatencyMs: 150 * DEVTOOLS_RTT_ADJUSTMENT_FACTOR,
downloadThroughputKbps: 1.6 * 1024 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR,
uploadThroughputKbps: 750 * DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR,
cpuSlowdownMultiplier: 4,
},
~~~
  所以在自己服務器中做測試時,對該限制做了些放寬,下面是完整的接口代碼,利用 puppeteer 和 lighthouse 兩個庫的能力。
~~~
const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
const config = require('config');
const domain = config.get('domain');
const lighthouse = require('lighthouse');
const crypto = require('crypto');
router.get("/test/lighthouse", async (ctx) => {
const { url, redirect } = ctx.query;
const options = {
args: ["--no-sandbox", "--disable-setuid-sandbox"]
};
// 使用 puppeteer 連接到瀏覽器
const browser = await puppeteer.launch(options);
const key = crypto.createHash("md5").update(url).digest("hex");
// 使用 Lighthouse 進行性能測試
const { lhr, report } = await lighthouse(url, {
port: new URL(browser.wsEndpoint()).port,
output: "html",
logLevel: "info",
throttling: {
throughputKbps: 100 * 1024
}
});
const savePath = path.resolve(__dirname, "../static/lighthouse");
// !fs.existsSync(savePath) && fs.mkdirSync(savePath, { recursive: true });
const resultUrl = savePath + `/${key}.html`;
// 將報告內容寫入到文件中
fs.writeFileSync(resultUrl, report);
await browser.close();
const redirectUrl = `${domain}/static/lighthouse/${key}.html`;
// 若需要跳轉
if (redirect) {
ctx.status = 302; // 臨時跳轉
ctx.redirect(redirectUrl);
return;
}
ctx.body = {
code: 0,
data: {
url: redirectUrl,
score: lhr.categories.performance.score * 100
}
};
});
~~~
  在本地調試時,非常順利,但是在部署到服務器上時,遇到了很多麻煩,不是報錯,就是在連接瀏覽器時卡住,整整困擾了我 3 天。
  網上有很多解決方案,例如安裝[各種依賴](https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#chrome-doesnt-launch-on-linux),如下所示。
~~~
apt install chromium-browser
apt install libxkbcommon0
....
~~~
  在 puppeteer 啟動時,增加 executablePath 參數,指定 chromium-browser 的位置。
~~~
const options = {
args: ["--no-sandbox", "--disable-setuid-sandbox"],
executablePath: '/usr/bin/chromium-browser'
};
~~~
  在項目根目錄增加 puppeteer.config.js 配置文件,指定 cacheDirectory 目錄。
~~~
const { join } = require("path");
const config = {
cacheDirectory: join(__dirname, ".cache", "puppeteer")
};
/**
* @type {import("puppeteer").Configuration}
*/
module.exports = config;
~~~
  雖然按照網上方案進行了優化,但是都無濟于事,最后是將烏班圖版本從 16.04 升級至 20.04 才徹底解決了問題。
## 總結
  數據分析和實驗室分析是性能優化的兩塊重要組成部分。
  數據分析在采集到性能信息后,會先進行日志存儲,然后按指定的百分位數進行數據整理,最后還會定期進行刪除。
  在管理界面提供了幾種視圖來更好的分析性能瓶頸,包括資源瀑布圖、堆疊柱狀圖和階段時序圖。
  在實驗室分析中,主要介紹了 3 款性能測試工具,包括 DevTools Performance、WebPageTest 和 Lighthouse。
  3 款軟件都非常優秀,可以幫助開發人員更快、更準的進行優化工作。
*****
> 原文出處:
[博客園-前端性能精進](https://www.cnblogs.com/strick/category/2267607.html)
[知乎專欄-前端性能精進](https://www.zhihu.com/column/c_1610941255021780992)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎閱讀。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020