簡單瞭解前端效能監控

朝思暮想的虫發表於2024-04-14

作為一名開發來講,以下場景你有沒有遇到:

  • 點選這個按鈕怎麼沒反應了
  • 頁面為什麼白了
  • 怎麼一直正在載入
  • 很多使用者說圖片載入不出來
  • ......

那麼有一款效能監控產品太重要了,但是效能相關的東西實在太多了。那麼從一個熟悉又陌生的api開始,performance。

1.什麼是performance

mdn上是這麼解釋的:Performance 介面可以獲取到當前頁面中與效能相關的資訊。它是 High Resolution Time API 的一部分,同時也融合了 Performance Timeline API、Navigation Timing API、User Timing API 和 Resource Timing API。

直接在控制檯列印:

除去eventCounts和memory相容不是很好的屬性,重點看下剩餘四個。

2.performace.timing屬性

我們配合這張經典圖來了解這些屬性

頁面進來之前:

navigationStart:前一個網頁解除安裝時間。

unloadEventStart:前一個網頁的upload事件開始

unloadEventEnd:前一個網頁的upload事件結束

redirectStart:網頁重定向開始時間

redirectEnd:網頁重定向結束時間

頁面進來之後:

fetchStart:開始請求網頁

domainLookupStart:dns查詢開始

domainLookupEnd:dns查詢結束

connectStart:向伺服器建立握手開始

connectEnd:向伺服器建立握手結束

secureConnectionStart:安全握手開始,預設值0。非https的沒有

requestStart:向伺服器傳送請求開始

responseStart:伺服器返回資料開始

responseEnd:伺服器返回資料結束

解析dom開始:

domLoading:解析dom開始,document.readyState為loading

domInteractive:解析dom結束,document.readyState為interactive

domContentLoadedEventStart:DomContentLoaden所有回撥開始執行

domContentLoadedEventEnd:ContentLoaded結束,dom內容載入完畢

loadEventStart:load事件發生前

loadEventEnd:load事件發生後

3.實戰一波

有了上邊的具體資料,如何進行計算進行監控上報呢。

以下程式碼中的p均為performance.timing物件

3.1 網路連線相關

// 上一個頁面的時間,沒多大用途
const pervPage = p.fetchStart - p.navigationStart 
// 重定向時間
const redirect = p.redirectEnd - p.redirectStart
// dns查詢時間
const dns = p.domainLookupEnd - p.domainLookupStart
// tcp建立時間
const connect = p.connectEnd - p.connectStart
// 網路總耗時
const network = p.connectEnd - p.navigationStart

網路建連層如果太慢或者出問題,那麼首先應該和運維部門溝通,查詢問題,和前端後端關聯性不大。

3.2 網路接收相關

// 前端傳送到接收的時間
const send = p.responseStart - p.requestStart
// 接收資料用時
const receive = p.responseEnd - p.responseStart
// 總耗時
const request = p.responseEnd - p.requestStart

3.3 前端渲染

// dom解析時間
const dom = p.domComplete - p.domLoading
// loadEvent時間
const loadEvent = p.loadEventEnd - p.loadEventStart
// 總耗時
const frontend = p.loadEventEnd - p.domLoading

3.4 關鍵階段

// 頁面完全載入的時間
const load = p.loadEventEnd - p.navigationStart
// dom準備時間
const domReady = p.domainLookupStart - p.navigationStart
// 可操作時間
const interactive = p.domInteractive - p.navigationStart
// 首位元組時間
const ttfb = p.responseStart - p.navigationStart

3.5 使用

// dom解析完成
let isDOMReady = false;
// callback 就是獲取上邊計算performance各個指標的回撥
function domready (callback) {
  if (isDOMReady) return;
  let timer = null;
  let runCheck = () => {
    if (performance.timing.domComplete) {
      clearTimeout(timer);
      callback();
      isDOMReady = true;
      // 1、停止迴圈檢測 2、執行callback
    } else {
      // 再去迴圈檢測
      timer = setTimeout(runCheck, 100);
    }
  }
  if (document.readyState === "interactive") {
    callback();
    return;
  }
  document.addEventListener('DOMContentLoaded', () => {
    // 開始迴圈檢測 是否 DOMContentLoaded 已經完成了
    runCheck();
  })
}
// 在onload事件中
let isOnload = false;
function onload (callback) {
  if (isOnload) return;
  let timer = null;
  let runCheck = () => {
    if (performance.timing.loadEventEnd) {
      clearTimeout(timer);
      callback();
      isOnload = true;
      // 1、停止迴圈檢測 2、執行callback
    } else {
      // 再去迴圈檢測
      timer = setTimeout(runCheck, 100);
    }
  }
  if (document.readyState === "interactive") {
    callback();
    return;
  }
  window.addEventListener('load', () => {
    runCheck();
  })
}

在domready和onload方法中,可以避免出現負數問題。

4. 資源監控

performance提供了getEnteries方法,用來獲取載入的資原始檔,返回陣列形式。

同樣的,我們也可以封裝個方法,根據這些屬性,計算出想要的資訊。程式碼中r代表陣列某一項

const resourceData = {
  initiatorType: r.initiatorType,
  name: r.name,
  duration: parseInt(r.duration),

  // 連線過程
  redirect: r.redirectEnd - r.redirectStart, // 重定向的時間
  dns: r.domainLookupEnd - r.domainLookupStart, // dns查詢的時間
  connect: r.connectEnd - r.connectStart, // tcp連線的時間
  network: r.connectEnd - r.startTime, // 網路總耗時

  // 接收過程
  send: r.responseStart - r.requestStart, // 傳送開始到接收的總時長
  receive: r.responseEnd - r.responseStart, // 接收的總時長
  request: r.responseEnd - r.requestStart, // 接收的總耗時

  // 核心指標
  ttfb: r.responseStart - r.requestStart, // 首位元組時間
}

但是,如果我們開發是一個商城網站,裡邊會有很多css、js、img等資源,他們都值得監控嗎?

可以封裝一個方法,傳入link、script、或者圖片名稱包含包含某些欄位的,或者隨機抽取幾個,我們去自定義去監聽規則。有興趣可以搜尋一下大量日誌log上報策略。

5. ajax請求監控

在實戰專案中,肯定會涉及到網路請求和後端的互動,如何監聽ajax請求的相關資料呢?

透過修改XMLHttpRequest原型,自定義open和send方法來達到目的。

const ajax = () => {
  let xhr = window.XMLHttpRequest;
  if (xhr._eagle_monitor_flag) return;
  xhr._eagle_monitor_flag = true;
  let _originOpen = xhr.prototype.open;
  xhr.prototype.open = function (method, url, async, user, password) {
    // 往xhr上自定義屬性
    this._eagle_xhr_info = {
      url, method, status: null
    }
    return _originOpen.apply(this, arguments);
  }
  let _originSend = xhr.prototype.send;
  xhr.prototype.send = function () {
    let _self = this;
    this._eagle_start_time = Date.now();

    let ajaxEnd = (eventType) => () => {
      if (_self.response) {
        // 統計返回資料的大小
        let responseSize = null;
        switch (_self.responseType) {
          case 'json':
            // JSON相容性問題 && stringify報錯
            responseSize = JSON.stringify(_self.response).length;
            break;
          case 'arraybuffer':
            responseSize = _self.response.byteLength;
            break;
          default: 
            responseSize = _self.responseText.length; // 簡單使用responseText的值
        }
        _self._eagle_xhr_info.event = eventType;
        _self._eagle_xhr_info.status = _self.status;
        _self._eagle_xhr_info.success = _self.status === 200;
        _self._eagle_xhr_info.duration = Date.now() = _self._eagle_start_time;
        _self._eagle_xhr_info.responseSize = responseSize;
        _self._eagle_xhr_info.requestSize = value ? value.length : 0 // value一定有length?百度查一下相容寫法;
        _self._eagle_xhr_info.type = 'xhr';
        // 列印_eagle_xhr_info,進行額外操作
        // console.log(_self._eagle_xhr_info)
        // callback(_self._eagle_xhr_info)
      }
    };

    // 這三種狀態都代表著請求已經結束了
    // 需要統計一些資訊,並進行上報
    this.addEventListener('load', ajaxEnd('load'), false);
    this.addEventListener('error', false);
    this.addEventListener('abort', false);

    return _originSend.apply(this, arguments);
  }
}

相關文章