<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何從 0 到 1 搭建性能檢測系統(tǒng)

          共 18113字,需瀏覽 37分鐘

           ·

          2020-10-30 11:46

          前言

          前端頁面性能對用戶留存、用戶直觀體驗有著重要影響,當頁面加載時間超過 2 秒后,加載時間每增加一秒,就會有大量的用戶流失,所以做好頁面性能優(yōu)化,無疑對網(wǎng)站來說是一個非常重要的步驟。

          那如何才能知道一個頁面的性能情況呢?知道了頁面性能情況后又如何進行優(yōu)化呢?一個頁面的性能指標非常多,面對一大堆性能指標,可能一個老手也一時間不知道從何開始分析。而且不同團隊,負責(zé)的業(yè)務(wù)不同,性能分析的指標也不能夠一概而論。打個比方說,對于一般的電商網(wǎng)站,一定會有很多圖片,那圖片加載的性能提升對網(wǎng)站的性能提升作用就比較大。而對于一些由表單組成的中臺頁面,提升圖片加載速度的收益遠小于電商網(wǎng)站。

          總結(jié)來說,不同的團隊有著各自不同的業(yè)務(wù),業(yè)務(wù)之間千差萬別,性能指標也不能一概而論,所以用一套統(tǒng)一的檢測模型覆蓋所有場景是不現(xiàn)實的。本文將介紹如何定制一個屬于自己團隊的性能檢測平臺。

          先看下政采云的性能檢測平臺——百策

          在聊性能指標之前,先講一下 Lighthouse。

          Lighthouse

          Lighthouse 是一個開源的自動化工具,用于分析和改善 Web 應(yīng)用的質(zhì)量。運行 Lighthouse 共有 4 種方式,分別在 Chrome 開發(fā)者工具,Chrome 擴展程序,Node CLI 和 Node module。百策主要基于 Node module 方式,在其基礎(chǔ)上進行擴展開發(fā),Lighthouse 詳細使用參見 Git:https://github.com/GoogleChrome/lighthouse

          下圖為 Lighthouse 檢測頁面性能的一個最終結(jié)果,可以看到其實指標已經(jīng)比較完善了。

          可能有人會問,為什么不直接使用 Lighthouse。首先,由于不可描述的原因,國內(nèi)直接使用 Chrome 開發(fā)者工具中的 Lighthouse 時,會一直處于 Lighthouse is warming up 狀態(tài)。其次,Chrome 擴展程序?qū)τ谛枰卿浀捻撁嬉膊恢С帧W詈螅瑢τ谇把灾校骋恍┒ㄖ菩枨?Lighthouse 也不能全然滿足,所以要基于 Lighthouse 進行定制,做一個滿足業(yè)務(wù)要求的性能檢測平臺。

          整體設(shè)計架構(gòu)

          下圖是百策系統(tǒng)的一個整體架構(gòu)

          • 前端主要使用的是 Antd 和 Antd Charts,包含常規(guī)頁面的展示和部分性能走勢圖表的展示。
          • 服務(wù)端基于 nestjs 開發(fā),接入 Sentry 做報警監(jiān)控。helmet 用于保護系統(tǒng)免受一些眾所周知的 Web 漏洞影響。
          • node-schedule 用于每周定時計算已統(tǒng)計入系統(tǒng)的頁面性能,并通過 nodemailer 發(fā)送郵件。
          • Compression 主要用于啟用 gzip。
          • 最主要的檢測服務(wù)基于 Puppeteer 和 Lighthouse 開發(fā)。

          百策采集頁面性能數(shù)據(jù)的流程

          百策系統(tǒng)監(jiān)控頁面的方式主要采用的方式是合成監(jiān)控,對于什么是合成監(jiān)控,可以參考此文章:螞蟻金服如何把前端性能監(jiān)控做到極致 (https://www.infoq.cn/article/Dxa8aM44oz*Lukk5Ufhy)。總結(jié)來說,合成監(jiān)控的優(yōu)勢就是:能夠采集的數(shù)據(jù)更豐富,并且可以根據(jù)不同的場景定制不同的運行環(huán)境等。首先百策要根據(jù)不同的場景,比如政采云前臺頁面、政采云中臺頁面制定不同的檢測模型。其次百策的主要目標是提升頁面性能,并且需要保證環(huán)境和硬件條件一致的情況下對頁面做性能比對,所以選擇采用合成監(jiān)控更加適合。

          先看下 Chrome Lighthouse 的架構(gòu)圖(圖來源于 Lighthouse Git),主要基于 4 個主要步驟實現(xiàn),分別是交互驅(qū)動,收集,審計以及記錄組成,參考了 Chrome Lighthouse,百策的檢測模型邏輯也主要由這 4 步組成:

          1、頁面交互后,發(fā)起請求調(diào)用服務(wù)。

          2、遍歷當前頁面所需要的收集器,合并為一個總的收集器,并采集數(shù)據(jù)。

          3、將第二步采集到的數(shù)據(jù)做性能計算和評分。

          4、將性能檢測結(jié)果存入數(shù)據(jù)庫。

          百策采集頁面性能數(shù)據(jù)的實現(xiàn)方案

          百策實現(xiàn)頁面性能數(shù)據(jù)采集的方案主要依靠無頭瀏覽器 Puppeteer 結(jié)合 Lighthouse,Puppeteer 是 Chrome 團隊提供的一個無界面 Chrome 工具,人稱無頭瀏覽器,通過 API 來控制 Node 端的 Chrome。百策的主要邏輯是在服務(wù)端起一個無需顯示的 Chrome,通過 Lighthouse 的 API 新建一個標簽頁并打開,Lighthouse 會計算具體的性能指標,具體的檢測邏輯可以參考下圖。接下來我會用關(guān)鍵代碼說明如何實現(xiàn)其中的關(guān)鍵步驟。

          ○ 開始入口

          以下是百策價值 1 個億的代碼,主要流程如下,鉤子函數(shù)是用于在頁面打開的不同時間獲取性能數(shù)據(jù)

          /**
            * 執(zhí)行頁面信息收集
            *
            * @param {PassContext} passContext
            */

          async run(runOptions: RunOptions) {
            const gathererResults = {};
            // 使用 Puppeteer 創(chuàng)建無頭瀏覽器,創(chuàng)建頁面
            const passContext = await this.prepare(runOptions);
            try {
              // 根據(jù)用戶是否輸入了用戶名和密碼判斷是否要登錄政采云
              await this.preLogin(passContext);
                  // 頁面打開前的鉤子函數(shù)
              await this.beforePass(passContext);
                  // 打開頁面,獲取頁面數(shù)據(jù)
              await this.getLhr(passContext);
                  // 頁面打開后的鉤子函數(shù)
              await this.afterPass(passContext, gathererResults);
                  // 收集頁面性能
              return await this.collectArtifact(passContext, gathererResults);
            } catch (error) {
              throw error;
            } finally {
              // 關(guān)閉頁面和無頭瀏覽器
              await this.disposeDriver(passContext);
            }
          }

          ○ 創(chuàng)建無頭瀏覽器

          創(chuàng)建無頭瀏覽器和頁面,并指定瀏覽器對應(yīng)的寬高,指定運行的參數(shù),關(guān)于瀏覽器的參數(shù)可以參考如下文章:Puppeteer API (https://zhaoqize.github.io/puppeteer-api-zh_CN/#?product=Puppeteer&version=v5.3.0&show=api-puppeteerlaunchoptions)。可以將 headless 設(shè)置為 false 看到瀏覽器的創(chuàng)建和 page 的新建,本地調(diào)試可以使用。

          /**
            * 登錄前準備工作,創(chuàng)建瀏覽器和頁面
            *
            * @param {RunOptions} runOptions
            */

          async prepare(runOptions: RunOptions) {
            // puppeteer 啟動的配置項
            const launchOptions: puppeteer.LaunchOptions = {
              headless: true// 是否無頭模式
              defaultViewport: { width: 1440, height: 960 }, // 指定打開頁面的寬高
              // 瀏覽器實例的參數(shù)配置,具體配置可以參考此鏈接:https://peter.sh/experiments/chromium-command-line-switches/
              args: ['--no-sandbox''--disable-dev-shm-usage'],
              executablePath: '/usr/bin/chromium-browser'// 默認 Chromium 執(zhí)行的路徑,此路徑指的是服務(wù)器上 Chromium 安裝的位置
            };
            // 服務(wù)器上運行時使用服務(wù)器上獨立安裝的 Chromium
            // 本地運行的時候使用 node_modules 中的 Chromium
            if (process.env.NODE_ENV === 'development') {
              delete launchOptions.executablePath;
            }
            // 創(chuàng)建瀏覽器對象
            const browser = await puppeteer.launch(launchOptions);
            // 獲取瀏覽器對象的默認第一個標簽頁
            const page = (await browser.pages())[0];
            // 返回瀏覽器和頁面對象
            return { browser, page };
          }

          ○ 模擬登錄

          模擬登錄的場景可以參考另一篇,自動化 Web 性能分析之 Puppeteer 爬蟲實踐中的第四節(jié),大致的實現(xiàn)邏輯如下:通過無頭瀏覽器打開政采云登錄頁,通過 Puppeteer API 模擬輸入用戶名密碼,并模擬點擊登錄按鈕。根據(jù)同一瀏覽器下相同的域名共享 Cookie 的特性,再新開標簽頁打開需要檢測的 URL,便可以開始性能檢測。

          ○ 打開頁面

          如何在 Puppeteer 中使用 Lighthouse 可以參考 Using Puppeteer with Lighthouse (https://github.com/GoogleChrome/lighthouse/blob/master/docs/puppeteer.md)。下面的代碼主要檢測的是桌面端 Web 頁面的性能,后續(xù)會放開更改檢測環(huán)境的功能:可以根據(jù)政采云域名來判斷頁面是手機端還是電腦端,根據(jù)不同的系統(tǒng)環(huán)境,切換不同的瀏覽器參數(shù)。

          /**
            * 在 Puppeteer 中使用 Lighthouse
            *
            * @param {RunOptions} runOptions
            */

          async getLhr(passContext: PassContext) {
            // 獲取瀏覽器對象和檢測鏈接
            const { browser, url } = passContext;
            // 開始檢測
            const { artifacts, lhr } = await lighthouse(url, {
              port: new URL(browser.wsEndpoint()).port,
              output: 'json',
              logLevel: 'info',
              emulatedFormFactor: 'desktop',
              throttling: {
                rttMs: 40,
                throughputKbps: 10 * 1024,
                cpuSlowdownMultiplier: 1,
                requestLatencyMs: 0// 0 means unset
                downloadThroughputKbps: 0,
                uploadThroughputKbps: 0,
              },
              disableDeviceEmulation: true,
              onlyCategories: ['performance'], // 是否只檢測 performance
              // chromeFlags: ['--disable-mobile-emulation', '--disable-storage-reset'],
            });
            // 回填數(shù)據(jù)
            passContext.lhr = lhr;
            passContext.artifacts = artifacts;
          }

          ○ 鉤子函數(shù)

          鉤子函數(shù)實際是一個抽象類,在運行不同的 Gathering 時,對應(yīng)的 Class 會實現(xiàn)該抽象類。鉤子函數(shù)的主要功能在于不同時期注冊回調(diào),主要有 2 個鉤子函數(shù),beforePass 和 afterPass。beforePass 的作用主要是在頁面還沒加載前先注冊一些監(jiān)聽器,比如說想在頁面 load 之后,就拿到 DOM 節(jié)點的深度,那就需要在 beforePass 中注冊監(jiān)聽。afterPass 主要是頁面性能統(tǒng)計完成之后,返回結(jié)構(gòu)化的數(shù)據(jù)。

          /**
            * 執(zhí)行所有收集器中的 afterPass 方法
            *
            * @param {PassContext} passContext
            * @param {GathererResults} gathererResults
            */

          async afterPass(passContext: PassContext, gathererResults: GathererResults) {
            const { page, gatherers } = passContext;
            // 遍歷所有收集器,執(zhí)行 afterPass 方法
            for (const gatherer of gatherers) {
              const gathererResult = await gatherer.afterPass(passContext);
              gathererResults[gatherer.name] = gathererResult;
            }
            // 執(zhí)行完所有方法后截圖記錄
            gathererResults.screenshotBuffer = await page.screenshot();
          }

          ○ 收集器的實現(xiàn)

          百策總共有 6 個收集器,分別是 Domstats Gathering,Image Elements Gathering,Lighthouse Gathering,Metrics Gathering, Network Recorder Gathering 和 Performance Gathering。

          每個收集器都會實現(xiàn)特定的收集功能:

          • Domstats Gathering:收集 DOM 相關(guān)的數(shù)據(jù),比如 DOM 元素數(shù)量,DOM 最大深度,document 是否有滾動條等。
          • Image Elements Gathering:收集所有的圖片,并記錄下圖片的寬高,定位等屬性。
          • Lighthouse Gathering:收集 Lighthouse 相關(guān)的指標:比如 FCP、LCP、TBT、CLS 等等。
          • Metrics Gathering:收集 JS 事件監(jiān)聽數(shù)量,JS 堆棧大小等。
          • Network Recorder Gathering:收集所有頁面請求,包括狀態(tài)碼,請求方式,請求頭,響應(yīng)頭等。
          • Performance Gathering:主要記錄了 window.performance 下的一些數(shù)據(jù),用于計算一些時間。

          以 Domstats Gathering 做為例子,詳細說明如何獲取頁面檢測數(shù)據(jù)。首先實現(xiàn)抽象類的 2 個方法:beforePass 和 afterPass。beforePass 的實現(xiàn)邏輯是對 page 對象添加 domcontentloaded 時間點的監(jiān)聽方法,監(jiān)聽方法的主要功能是判斷 document 是否有橫向滾動條。afterPass 方法主要是獲取 Lighthouse lhr 中的數(shù)據(jù),分析并得到 DOM 最大深度,DOM 節(jié)點數(shù)等。

          import { Gatherer } from './gatherer';
          import { PassContext } from '../interfaces/pass-context.interface';
          // 實現(xiàn) Gatherer 抽象類
          export default class DOMStats extends Gatherer {
            horizontalScrollBar;
            /**
            * 頁面打開前的鉤子函數(shù)
            *
            * @param {PassContext} passContext
            */

            async beforePass(passContext: PassContext) {
              const { browser } = passContext;
              // 當瀏覽器的對象發(fā)生變化的時候,說明新打開頁面了,此時可以獲取到標簽頁 page 對象
              browser.on('targetchanged'async target => {
                const page = await target.page();
                // 等待 dom 文檔加載完成的時候
                page.on('domcontentloaded'async () => {
                  // 通過 evaluate 方法可以獲取到頁面上的元素和方法
                  this.horizontalScrollBar = await page.evaluate(() => {
                    return document.body.scrollWidth > document.body.clientWidth;
                  });
                });
              });
            }
            /**
            * 頁面執(zhí)行結(jié)束后的鉤子函數(shù)
            *
            * @param {PassContext} passContext
            */

            async afterPass(passContext: PassContext) {
              const { artifacts } = passContext;
                  // 從 lighthouse 結(jié)果對象 lhr 中獲取 dom 節(jié)點的 depth,width 和 totalBodyElements
              const {
                DOMStats: { depth, width, totalBodyElements },
              } = artifacts;
              return {
                numElements: totalBodyElements,
                maxDepth: depth.max,
                maxWidth: width.max,
                hasHorizontalScrollBar: !!this.horizontalScrollBar,
              };
            }
          }

          等待所有 Gathering 都執(zhí)行完成之后,數(shù)據(jù)就可以落庫了。

          ○ 根據(jù)模型計算得分

          數(shù)據(jù)入庫后還要根據(jù)不同的模型計算不同的得分。前臺頁面重展示,并且圖片加載會比較多,中臺頁面重表單提交,所以不同的模型一定有不同的計算邏輯。在政采云,前臺頁面我們使用的框架是 Vue, 中臺頁面使用的是 React(部分頁面由于歷史原因用的還是 jQuery)。所以大致可以根據(jù)框架來區(qū)分模型。判斷框架是 Vue 還是 React 可以根據(jù) DOM 是否包含 _reactRootContainer__vue__ 來判斷。

          /**
            * 計算得分方法,根據(jù)模型上的得分配置項最終生成得分并入庫
            *
            * @param {Artifact} artifact
            * @param {string[]} whitelist
            */

          async calc(artifact: Artifact, whitelist?: string[]): Promise  {
             // 根據(jù)每條 metaid 動態(tài)加載不同的計算方法文件,每個 metaid 指的就是一個性能評分指標,比如說是否有橫向滾動條
             const audit =  await  import( `../audits/${this.meta.id}`).then( m => m.default);
               // 執(zhí)行每個計算方法文件中的 audit 方法,計算得分,比如沒有橫向滾動條的時候得5分,有橫向滾動條不得分
             const { rawValue, score, displayValue, details = [] } = audit.audit(artifact, whitelist);
             const auditDto =  new AuditDto();
            auditDto.id =  this.meta.id;
               // 檢測指標名稱展示
            auditDto.title =  this.meta.title;
               // 檢測指標描述
            auditDto.description =  this.meta.description;
               // 檢測指標詳情
            auditDto.details = details;
               // 檢測指標登記,判斷是否計算入得分
            auditDto.level =  this.level;
             // 扣分上限根據(jù)不同的 meta,可能上限也有不同,upperLimitScore 指的是扣分上限,從數(shù)據(jù)庫獲取
            auditDto.score = score *  this.weight <= - this.upperLimitScore ? - this.upperLimitScore : score *  this.weight;
               // 得分情況
            auditDto.rawValue = rawValue;
               // 得分如何展示
            auditDto.displayValue = displayValue;
             return auditDto;
          }

          以下是政采云前臺模型,每一項都是一個檢測指標,告警項只做提示,不實際扣分,前臺主要以圖片加載和展示為準,所以模型設(shè)計上,會更加側(cè)重頁面加載時間的關(guān)鍵指標,并且會著重考慮圖片的展示。

          前面內(nèi)容主要介紹了百策的數(shù)據(jù)采集和評分功能,這也是百策最主要的功能。除了核心功能外,百策還有數(shù)據(jù)看板、提供性能解決方案、性能走勢,性能對比,定時監(jiān)測等功能。在這篇文章中我也不一一闡述了。

          ○ 自動檢測

          當然除了上面這些手動檢測以外,百策也支持自動檢測。自動檢測的主要目的是統(tǒng)計所有收錄在系統(tǒng)中的頁面,統(tǒng)計哪些頁面性能優(yōu)化的最好,哪些優(yōu)化欠佳。具體的邏輯:每周五 2 點會對所有收錄在百策中的頁面進行檢測,將檢測成績最高的 10 個頁面,檢測成績最低的 10 個頁面,檢測成績進步最快的 10 個頁面,自動檢測的邏輯主要通過 node-schedule 實現(xiàn)。發(fā)送郵件可以 ejs 實現(xiàn)渲染模版,定義好模版后通過 nodemailer 發(fā)送即可。

          import {
            Injectable,
            OnModuleInit,
          from '@nestjs/common';
          import * as schedule from 'node-schedule';
          @Injectable()
          export class ScheduleService implements OnModuleInit {
            onModuleInit() {
              this.init();
            }
            async init() {
              // 本地啟動時不執(zhí)行一系列定時任務(wù)
              if (process.env.NODE_ENV !== 'development') {
                // 每周五02:00開始收集頁面性能
                schedule.scheduleJob(`hawkeye-weekly-report`'0 0 2 * * 5'async () => {
                  // 調(diào)用檢測接口記錄性能評分
                  await this.report();
                });
                // 每周五18:00發(fā)送周報
                schedule.scheduleJob(`hawkeye-weekly-send`'0 0 18 * * 5'async () => {
                  // 發(fā)送郵件的具體實現(xiàn)方法,主要通過 ejs 渲染模版,通過 nodemailer 發(fā)送郵件
                  await this.send();
                 });
              }
            }
          }

          ○ 對接魯班

          關(guān)于魯班是什么,可以參考這篇文章:前端工程實踐之可視化搭建系統(tǒng),用一句話來總結(jié),可以說魯班就是政采云的頁面搭建系統(tǒng)。

          在對接魯班時,主要包括了魯班頁面的性能數(shù)據(jù)的錄入和魯班頁面的錄入(方便后續(xù)每周定時檢測)。

          • 魯班性能數(shù)據(jù)的錄入:和在魯班生成頁面時提供一個檢測按鈕,調(diào)用百策性能評分接口,生成檢測數(shù)據(jù)。
          • 魯班頁面的錄入:在魯班的新頁面上線的時候,會自動調(diào)用百策錄入接口,新增的頁面會被錄入到百策系統(tǒng)中。

          結(jié)尾

          如果你也想搭建一個屬于自己的性能檢測平臺,并且恰巧看到了這篇文章,希望此文對你有所幫助。

          本文最主要講的是如何搭建一個性能平臺。當你已經(jīng)能夠搭建性能平臺之后,不妨可以思考下業(yè)務(wù)頁面的檢測模型。

              

          ● 你不知道的 React Hooks(萬字長文,快速入門必備)

          ● UmiJS 中后臺項目實踐

          ● 【效果高能】你不知道的 Animation 動畫技巧



          ·END·

          圖雀社區(qū)

          匯聚精彩的免費實戰(zhàn)教程



          關(guān)注公眾號回復(fù) z 拉學(xué)習(xí)交流群


          喜歡本文,點個“在看”告訴我


          瀏覽 49
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码人妻精品一区二区蜜桃色 | 操逼靠逼 | 久青草电影| 豆花视频精品一二三区 | 99 在线免费观看 |