如何從 0 到 1 搭建性能檢測系統

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前端頁面性能對用戶留存、用戶直觀體驗有着重要影響,當頁面加載時間超過 2 秒後,加載時間每增加一秒,就會有大量的用戶流失,所以做好頁面性能優化,無疑對網站來說是一個非常重要的步驟。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那如何才能知道一個頁面的性能情況呢?知道了頁面性能情況後又如何進行優化呢?一個頁面的性能指標非常多,面對一大堆性能指標,可能一個老手也一時間不知道從何開始分析。而且不同團隊,負責的業務不同,性能分析的指標也不能夠一概而論。打個比方說,對於一般的電商網站,一定會有很多圖片,那圖片加載的性能提升對網站的性能提升作用就比較大。而對於一些由表單組成的中臺頁面,提升圖片加載速度的收益遠小於電商網站。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總結來說,不同的團隊有着各自不同的業務,業務之間千差萬別,性能指標也不能一概而論,所以用一套統一的檢測模型覆蓋所有場景是不現實的。本文將介紹如何定製一個屬於自己團隊的性能檢測平臺。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先看下政採雲的性能檢測平臺——百策"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/3f\/3f90f234da230fc319683137d24d39ae.gif","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在聊性能指標之前,先講一下 Lighthouse。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Lighthouse"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Lighthouse 是一個開源的自動化工具,用於分析和改善 Web 應用的質量。運行 Lighthouse 共有 4 種方式,分別在 Chrome 開發者工具,Chrome 擴展程序,Node CLI 和 Node module。百策主要基於 Node module 方式,在其基礎上進行擴展開發,Lighthouse 詳細使用參見 Git:https:\/\/github.com\/GoogleChrome\/lighthouse"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下圖爲 Lighthouse 檢測頁面性能的一個最終結果,可以看到其實指標已經比較完善了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/e4\/e4bb00b50b71d2745cc2fe775b940b1b.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可能有人會問,爲什麼不直接使用 Lighthouse。首先,由於不可描述的原因,國內直接使用 Chrome 開發者工具中的 Lighthouse 時,會一直處於 Lighthouse is warming up 狀態。其次,Chrome 擴展程序對於需要登錄的頁面也不支持。最後,對於前言中,某一些定製需求 Lighthouse 也不能全然滿足,所以要基於 Lighthouse 進行定製,做一個滿足業務要求的性能檢測平臺。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"整體設計架構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下圖是百策系統的一個整體架構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前端主要使用的是 Antd 和 Antd Charts,包含常規頁面的展示和部分性能走勢圖表的展示。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務端基於 nestjs 開發,接入 Sentry 做報警監控。helmet 用於保護系統免受一些衆所周知的 Web 漏洞影響。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"node-schedule 用於每週定時計算已統計入系統的頁面性能,並通過 nodemailer 發送郵件。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Compression 主要用於啓用 gzip。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最主要的檢測服務基於 Puppeteer 和 Lighthouse 開發。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/dc\/dc4847ddbbeb5e60f700bc8fbccaaa0c.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"百策採集頁面性能數據的流程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"百策系統監控頁面的方式主要採用的方式是合成監控,對於什麼是合成監控,可以參考此文章:"},{"type":"text","marks":[{"type":"strong"}],"text":"螞蟻金服如何把前端性能監控做到極致"},{"type":"text","text":" (https:\/\/www.infoq.cn\/article\/Dxa8aM44oz*Lukk5Ufhy)。總結來說,合成監控的優勢就是:能夠採集的數據更豐富,並且可以根據不同的場景定製不同的運行環境等。首先百策要根據不同的場景,比如政採雲前臺頁面、政採雲中臺頁面制定不同的檢測模型。其次百策的主要目標是提升頁面性能,並且需要保證環境和硬件條件一致的情況下對頁面做性能比對,所以選擇採用合成監控更加適合。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先看下 Chrome Lighthouse 的架構圖(圖來源於 Lighthouse Git),主要基於 4 個主要步驟實現,分別是交互驅動,收集,審計以及記錄組成,參考了 Chrome Lighthouse,百策的檢測模型邏輯也主要由這 4 步組成:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、頁面交互後,發起請求調用服務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、遍歷當前頁面所需要的收集器,合併爲一個總的收集器,並採集數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、將第二步採集到的數據做性能計算和評分。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4、將性能檢測結果存入數據庫。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/7a\/7a42598e73dbc8b2feaa87416fad285f.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"百策採集頁面性能數據的實現方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"百策實現頁面性能數據採集的方案主要依靠無頭瀏覽器 Puppeteer 結合 Lighthouse,Puppeteer 是 Chrome 團隊提供的一個無界面 Chrome 工具,人稱無頭瀏覽器,通過 API 來控制 Node 端的 Chrome。百策的主要邏輯是在服務端起一個無需顯示的 Chrome,通過 Lighthouse 的 API 新建一個標籤頁並打開,Lighthouse 會計算具體的性能指標,具體的檢測邏輯可以參考下圖。接下來我會用關鍵代碼說明如何實現其中的關鍵步驟。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/7a\/7abdc63c6d496703c92b80cb3b666937.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"○ 開始入口"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下是百策價值 1 個億的代碼,主要流程如下,鉤子函數是用於在頁面打開的不同時間獲取性能數據"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/**\n  * 執行頁面信息收集\n  *\n  * @param {PassContext} passContext\n  *\/\nasync run(runOptions: RunOptions) {\n  const gathererResults = {};\n  \/\/ 使用 Puppeteer 創建無頭瀏覽器,創建頁面\n  const passContext = await this.prepare(runOptions);\n  try {\n    \/\/ 根據用戶是否輸入了用戶名和密碼判斷是否要登錄政採雲\n    await this.preLogin(passContext);\n        \/\/ 頁面打開前的鉤子函數\n    await this.beforePass(passContext);\n        \/\/ 打開頁面,獲取頁面數據\n    await this.getLhr(passContext);\n        \/\/ 頁面打開後的鉤子函數\n    await this.afterPass(passContext, gathererResults);\n        \/\/ 收集頁面性能\n    return await this.collectArtifact(passContext, gathererResults);\n  } catch (error) {\n    throw error;\n  } finally {\n    \/\/ 關閉頁面和無頭瀏覽器\n    await this.disposeDriver(passContext);\n  }\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"○ 創建無頭瀏覽器"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"創建無頭瀏覽器和頁面,並指定瀏覽器對應的寬高,指定運行的參數,關於瀏覽器的參數可以參考如下文章:"},{"type":"text","marks":[{"type":"strong"}],"text":"Puppeteer API"},{"type":"text","text":" (https:\/\/zhaoqize.github.io\/puppeteer-api-zh_CN\/#?product=Puppeteer&version=v5.3.0&show=api-puppeteerlaunchoptions)。可以將 headless 設置爲 false 看到瀏覽器的創建和 page 的新建,本地調試可以使用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/**\n  * 登錄前準備工作,創建瀏覽器和頁面\n  *\n  * @param {RunOptions} runOptions\n  *\/\nasync prepare(runOptions: RunOptions) {\n  \/\/ puppeteer 啓動的配置項\n  const launchOptions: puppeteer.LaunchOptions = {\n    headless: true, \/\/ 是否無頭模式\n    defaultViewport: { width: 1440, height: 960 }, \/\/ 指定打開頁面的寬高\n    \/\/ 瀏覽器實例的參數配置,具體配置可以參考此鏈接:https:\/\/peter.sh\/experiments\/chromium-command-line-switches\/\n    args: ['--no-sandbox', '--disable-dev-shm-usage'],\n    executablePath: '\/usr\/bin\/chromium-browser', \/\/ 默認 Chromium 執行的路徑,此路徑指的是服務器上 Chromium 安裝的位置\n  };\n  \/\/ 服務器上運行時使用服務器上獨立安裝的 Chromium\n  \/\/ 本地運行的時候使用 node_modules 中的 Chromium\n  if (process.env.NODE_ENV === 'development') {\n    delete launchOptions.executablePath;\n  }\n  \/\/ 創建瀏覽器對象\n  const browser = await puppeteer.launch(launchOptions);\n  \/\/ 獲取瀏覽器對象的默認第一個標籤頁\n  const page = (await browser.pages())[0];\n  \/\/ 返回瀏覽器和頁面對象\n  return { browser, page };\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":" 模擬登錄"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"模擬登錄的場景可以參考另一篇,"},{"type":"link","attrs":{"href":"http:\/\/mp.weixin.qq.com\/s?__biz=MzI0NTE5NzYyMw==&mid=2247483769&idx=1&sn=f4d65fe8b60d8870b0a243ad5da48048&chksm=e9537f21de24f637f9a95ce3e108b2f9caff6f0d40ad571b9616b589b75f246ef80eb3a770f8&scene=21#wechat_redirect","title":null,"type":null},"content":[{"type":"text","text":"自動化 Web 性能分析之 Puppeteer 爬蟲實踐"}]},{"type":"text","text":"中的第四節,大致的實現邏輯如下:通過無頭瀏覽器打開政採雲登錄頁,通過 Puppeteer API 模擬輸入用戶名密碼,並模擬點擊登錄按鈕。根據同一瀏覽器下相同的域名共享 Cookie 的特性,再新開標籤頁打開需要檢測的 URL,便可以開始性能檢測。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"○ 打開頁面"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何在 Puppeteer 中使用 Lighthouse 可以參考 "},{"type":"text","marks":[{"type":"strong"}],"text":"Using Puppeteer with Lighthouse"},{"type":"text","text":" (https:\/\/github.com\/GoogleChrome\/lighthouse\/blob\/master\/docs\/puppeteer.md)。下面的代碼主要檢測的是桌面端 Web 頁面的性能,後續會放開更改檢測環境的功能:可以根據政採雲域名來判斷頁面是手機端還是電腦端,根據不同的系統環境,切換不同的瀏覽器參數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\/**\n  * 在 Puppeteer 中使用 Lighthouse\n  *\n  * @param {RunOptions} runOptions\n  *\/\nasync getLhr(passContext: PassContext) {\n  \/\/ 獲取瀏覽器對象和檢測鏈接\n  const { browser, url } = passContext;\n  \/\/ 開始檢測\n  const { artifacts, lhr } = await lighthouse(url, {\n    port: new URL(browser.wsEndpoint()).port,\n    output: 'json',\n    logLevel: 'info',\n    emulatedFormFactor: 'desktop',\n    throttling: {\n      rttMs: 40,\n      throughputKbps: 10 * 1024,\n      cpuSlowdownMultiplier: 1,\n      requestLatencyMs: 0, \/\/ 0 means unset\n      downloadThroughputKbps: 0,\n      uploadThroughputKbps: 0,\n    },\n    disableDeviceEmulation: true,\n    onlyCategories: ['performance'], \/\/ 是否只檢測 performance\n    \/\/ chromeFlags: ['--disable-mobile-emulation', '--disable-storage-reset'],\n  });\n  \/\/ 回填數據\n  passContext.lhr = lhr;\n  passContext.artifacts = artifacts;\n}\n"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"○ 鉤子函數"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"鉤子函數實際是一個抽象類,在運行不同的 Gathering 時,對應的 Class 會實現該抽象類。鉤子函數的主要功能在於不同時期註冊回調,主要有 2 個鉤子函數,beforePass 和 afterPass。beforePass 的作用主要是在頁面還沒加載前先註冊一些監聽器,比如說想在頁面 load 之後,就拿到 DOM 節點的深度,那就需要在 beforePass 中註冊監聽。afterPass 主要是頁面性能統計完成之後,返回結構化的數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"\/**\n  * 執行所有收集器中的 afterPass 方法\n  *\n  * @param {PassContext} passContext\n  * @param {GathererResults} gathererResults\n  *\/\nasync afterPass(passContext: PassContext, gathererResults: GathererResults) {\n  const { page, gatherers } = passContext;\n  \/\/ 遍歷所有收集器,執行 afterPass 方法\n  for (const gatherer of gatherers) {\n    const gathererResult = await gatherer.afterPass(passContext);\n    gathererResults[gatherer.name] = gathererResult;\n  }\n  \/\/ 執行完所有方法後截圖記錄\n  gathererResults.screenshotBuffer = await page.screenshot();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"○ 收集器的實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"百策總共有 6 個收集器,分別是 Domstats Gathering,Image Elements Gathering,Lighthouse Gathering,Metrics Gathering, Network Recorder Gathering 和 Performance Gathering。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個收集器都會實現特定的收集功能:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Domstats Gathering:收集 DOM 相關的數據,比如 DOM 元素數量,DOM 最大深度,document 是否有滾動條等。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Image Elements Gathering:收集所有的圖片,並記錄下圖片的寬高,定位等屬性。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Lighthouse Gathering:收集 Lighthouse 相關的指標:比如 FCP、LCP、TBT、CLS 等等。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Metrics Gathering:收集 JS 事件監聽數量,JS 堆棧大小等。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Network Recorder Gathering:收集所有頁面請求,包括狀態碼,請求方式,請求頭,響應頭等。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Performance Gathering:主要記錄了 window.performance 下的一些數據,用於計算一些時間。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以 Domstats Gathering 做爲例子,詳細說明如何獲取頁面檢測數據。首先實現抽象類的 2 個方法:beforePass 和 afterPass。beforePass 的實現邏輯是對 page 對象添加 domcontentloaded 時間點的監聽方法,監聽方法的主要功能是判斷 document 是否有橫向滾動條。afterPass 方法主要是獲取 Lighthouse lhr 中的數據,分析並得到 DOM 最大深度,DOM 節點數等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"import { Gatherer } from '.\/gatherer';\nimport { PassContext } from '..\/interfaces\/pass-context.interface';\n\/\/ 實現 Gatherer 抽象類\nexport default class DOMStats extends Gatherer {\n  horizontalScrollBar;\n  \/**\n  * 頁面打開前的鉤子函數\n  *\n  * @param {PassContext} passContext\n  *\/\n  async beforePass(passContext: PassContext) {\n    const { browser } = passContext;\n    \/\/ 當瀏覽器的對象發生變化的時候,說明新打開頁面了,此時可以獲取到標籤頁 page 對象\n    browser.on('targetchanged', async target => {\n      const page = await target.page();\n      \/\/ 等待 dom 文檔加載完成的時候\n      page.on('domcontentloaded', async () => {\n        \/\/ 通過 evaluate 方法可以獲取到頁面上的元素和方法\n        this.horizontalScrollBar = await page.evaluate(() => {\n          return document.body.scrollWidth > document.body.clientWidth;\n        });\n      });\n    });\n  }\n  \/**\n  * 頁面執行結束後的鉤子函數\n  *\n  * @param {PassContext} passContext\n  *\/\n  async afterPass(passContext: PassContext) {\n    const { artifacts } = passContext;\n        \/\/ 從 lighthouse 結果對象 lhr 中獲取 dom 節點的 depth,width 和 totalBodyElements\n    const {\n      DOMStats: { depth, width, totalBodyElements },\n    } = artifacts;\n    return {\n      numElements: totalBodyElements,\n      maxDepth: depth.max,\n      maxWidth: width.max,\n      hasHorizontalScrollBar: !!this.horizontalScrollBar,\n    };\n  }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"等待所有 Gathering 都執行完成之後,數據就可以落庫了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"○ 根據模型計算得分"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據入庫後還要根據不同的模型計算不同的得分。前臺頁面重展示,並且圖片加載會比較多,中臺頁面重表單提交,所以不同的模型一定有不同的計算邏輯。在政採雲,前臺頁面我們使用的框架是 Vue, 中臺頁面使用的是 React(部分頁面由於歷史原因用的還是 jQuery)。所以大致可以根據框架來區分模型。判斷框架是 Vue 還是 React 可以根據 DOM 是否包含 "},{"type":"codeinline","content":[{"type":"text","text":"_reactRootContainer"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"__vue__"}]},{"type":"text","text":" 來判斷。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\/**\n * 計算得分方法,根據模型上的得分配置項最終生成得分併入庫\n *\n * @param {Artifact} artifact\n * @param {string[]} whitelist\n *\/\nasync calc(artifact: Artifact, whitelist?: string[]): Promise {\n \/\/ 根據每條 metaid 動態加載不同的計算方法文件,每個 metaid 指的就是一個性能評分指標,比如說是否有橫向滾動條\n const audit = await import(`..\/audits\/${this.meta.id}`).then(m => m.default);\n \/\/ 執行每個計算方法文件中的 audit 方法,計算得分,比如沒有橫向滾動條的時候得5分,有橫向滾動條不得分\n const { rawValue, score, displayValue, details = [] } = audit.audit(artifact, whitelist);\n const auditDto = new AuditDto();\n auditDto.id = this.meta.id;\n \/\/ 檢測指標名稱展示\n auditDto.title = this.meta.title;\n \/\/ 檢測指標描述\n auditDto.description = this.meta.description;\n \/\/ 檢測指標詳情\n auditDto.details = details;\n \/\/ 檢測指標登記,判斷是否計算入得分\n auditDto.level = this.level;\n \/\/ 扣分上限根據不同的 meta,可能上限也有不同,upperLimitScore 指的是扣分上限,從數據庫獲取\n auditDto.score = score * this.weight <= -this.upperLimitScore ? -this.upperLimitScore : score * this.weight;\n \/\/ 得分情況\n auditDto.rawValue = rawValue;\n \/\/ 得分如何展示\n auditDto.displayValue = displayValue;\n return auditDto;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下是政採雲前臺模型,每一項都是一個檢測指標,告警項只做提示,不實際扣分,前臺主要以圖片加載和展示爲準,所以模型設計上,會更加側重頁面加載時間的關鍵指標,並且會着重考慮圖片的展示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/7d\/7d626648d5750df8a5114453f6598e40.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面內容主要介紹了百策的數據採集和評分功能,這也是百策最主要的功能。除了核心功能外,百策還有數據看板、提供性能解決方案、性能走勢,性能對比,定時監測等功能。在這篇文章中我也不一一闡述了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"○ 自動檢測"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然除了上面這些手動檢測以外,百策也支持自動檢測。自動檢測的主要目的是統計所有收錄在系統中的頁面,統計哪些頁面性能優化的最好,哪些優化欠佳。具體的邏輯:每週五 2 點會對所有收錄在百策中的頁面進行檢測,將檢測成績最高的 10 個頁面,檢測成績最低的 10 個頁面,檢測成績進步最快的 10 個頁面,自動檢測的邏輯主要通過 node-schedule 實現。發送郵件可以 ejs 實現渲染模版,定義好模版後通過 nodemailer 發送即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"typescript"},"content":[{"type":"text","text":"import {\n  Injectable,\n  OnModuleInit,\n} from '@nestjs\/common';\nimport * as schedule from 'node-schedule';\n@Injectable()\nexport class ScheduleService implements OnModuleInit {\n  onModuleInit() {\n    this.init();\n  }\n  async init() {\n    \/\/ 本地啓動時不執行一系列定時任務\n    if (process.env.NODE_ENV !== 'development') {\n      \/\/ 每週五02:00開始收集頁面性能\n      schedule.scheduleJob(`hawkeye-weekly-report`, '0 0 2 * * 5', async () => {\n        \/\/ 調用檢測接口記錄性能評分\n        await this.report();\n      });\n      \/\/ 每週五18:00發送週報\n      schedule.scheduleJob(`hawkeye-weekly-send`, '0 0 18 * * 5', async () => {\n        \/\/ 發送郵件的具體實現方法,主要通過 ejs 渲染模版,通過 nodemailer 發送郵件\n        await this.send();\n       });\n    }\n  }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/d6\/d6b0e0e7c3f9863eacf441ca4f054bdb.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"○ 對接魯班"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於魯班是什麼,可以參考這篇文章:"},{"type":"link","attrs":{"href":"http:\/\/mp.weixin.qq.com\/s?__biz=MzI0NTE5NzYyMw==&mid=2247483754&idx=1&sn=50cfe278d16eee9bc10cec0571e93863&chksm=e9537f32de24f6242a29eee3f6363bb720aae5e0cecc53f298be9947bed4a0b6b66841f21120&scene=21#wechat_redirect","title":null,"type":null},"content":[{"type":"text","text":"前端工程實踐之可視化搭建系統"}]},{"type":"text","text":",用一句話來總結,可以說魯班就是政採雲的頁面搭建系統。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在對接魯班時,主要包括了魯班頁面的性能數據的錄入和魯班頁面的錄入(方便後續每週定時檢測)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"魯班性能數據的錄入:和在魯班生成頁面時提供一個檢測按鈕,調用百策性能評分接口,生成檢測數據。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"魯班頁面的錄入:在魯班的新頁面上線的時候,會自動調用百策錄入接口,新增的頁面會被錄入到百策系統中。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結尾"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你也想搭建一個屬於自己的性能檢測平臺,並且恰巧看到了這篇文章,希望此文對你有所幫助。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文最主要講的是如何搭建一個性能平臺。當你已經能夠搭建性能平臺之後,不妨可以思考下業務頁面的檢測模型。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"頭圖:Unsplash"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:句號"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:https:\/\/mp.weixin.qq.com\/s\/D5Dwcg7uYemuBXF00uCVFg"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:如何從 0 到 1 搭建性能檢測系統(修正版)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來源:政採雲前端團隊 - 微信公衆號 [ID:Zoo-Team]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉載:著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章