web效能之資源載入時間分析【Resource Timing】【原創】

JBoss發表於2020-04-03

簡介

在前面一章我們學習了 Web Performance Timing Api 可以用來分析web應用的資源載入、手動卡點的效能(時間),那麼我們現在就來看看怎麼使用Resource Timing 來分析我們的靜態資源的載入情況。

案例:

在平時開發的時候,我們常會遇到這樣的問題反饋;

客戶反饋:朋友我怎麼感覺我們這個應用怎麼初始化載入就這麼慢呢?到底是什麼原因時快時慢的?

開發:我用我的(手機|電腦)測試了沒有問題啊很快啊,而且我也用谷歌除錯工具看了渲染時間確實很快啊。

這個時候對於我們開發來說,我們能想到的方式就是:

  • 先確認是否有指令碼載入阻塞
  • 確認是否有資源載入過慢(本機,自己的網路)
  • 對於使用者說的慢,其實我們自己也不知道是說的什麼慢,然後就各種網上搜一波,優化一波,到底解沒解決問題自己是測試不出來的(不知道有沒有人跟我一樣的感覺就是:開發的裝置執行都沒有什麼問題,到了其他裝置上就開始出問題了)。

其實遇到這個問題的最根本的原因是我們無法用 **資料來證明 **到底是哪裡慢,什麼情況慢,並且我們也無法提前預知到這些問題,總不能讓客戶把他的裝置(手機|電腦)郵寄給你,然後你開始各種除錯吧。

其實上面的這個案例牽扯很多的問題:

  • 不知道使用者當時的實際網路情況 ☆☆☆☆
  • 不知道我們的程式碼在使用者的裝置執行情況 ☆☆☆ (本章不講)
  • 不知道到底是我們的介面慢還是我們的程式碼由於運算的資料量過大導致慢 ☆☆☆☆(本章不講)
  • 還是說程式碼在執行中由於個別的資料沒有做資料驗證(判空|型別不匹配...)導致頁面報錯(本章不講)

可能還有其他的因素的影響;

對於我們來說我們應該優先處理可控、可預知的效能影響因素;

本地除錯分析資源載入問題

分析前程式碼示例:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>demo</title>
  <link href="https://cdn.bootcss.com/animate.css/3.7.2/animate.css" rel="stylesheet">
  <link href="https://cdn.bootcss.com/video.js/7.7.6/alt/video-js-cdn.css" rel="stylesheet">
</head>
<body>
<img src="https://www.baidu.com/img/bd_logo1.png">
<img src="https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1585755631&di=5e0530559acfe3bc031c33c5c6fe38d6&src=http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg">
</body>
<script src="https://cdn.bootcss.com/video.js/7.7.6/alt/video.core.js"></script>
</html>
複製程式碼

以上這個程式碼裡面我們可以看到,我們分別引入了:

  • animate.css
  • video.js
  • video.css
  • 百度的logo
  • 還有一張桌面桌布

我們先本地執行這個頁面,然後看控制檯,他們的耗時分別是多少,下圖是在清除本地快取後,模擬使用者在200M光纖網下的頁面開啟速度

web效能之資源載入時間分析【Resource Timing】【原創】

從上面的資料我們能在自己的本機分析出來,我們用時最久的資源是video.js這個檔案,雖然我用的是CDN,但是其實感覺還是有點時間過長,這裡我們先不考慮指令碼載入阻塞的情況,我們只關注資源的載入時間分析問題上。

web效能之資源載入時間分析【Resource Timing】【原創】

從這個圖片中我們可以發現,有兩個js檔案的連結時間使用較長但是下載很快。

進過上面的一波本地偵錯程式的分析過後,我們貌似找到了一些問題,但是不要忘了這只是在我們自己的裝置上除錯的結果,並且在網路很好的情況(200M光纖網)下。

但是對於使用者的裝置開啟網頁的真實情況我們並不清楚,所以我們需要抓取到這些資料(越多越好),並且做分析。

利用Resource Timing 抓取資源載入過程資訊

我們先使用 performance.getEntriesByType('resource')方法獲取到瀏覽器執行開始至呼叫該方法時的所有資源載入情況的資料。

我們在控制檯輸入

performance.getEntriesByType('resource')
複製程式碼

我們首先清除快取,然後從新執行程式碼,我們來看下本次的執行結果,這次的執行結果很有趣:

web效能之資源載入時間分析【Resource Timing】【原創】

第一我們可以看到,同樣的裝置,同樣的環境,但是我們的時間結果差距會這麼大,居然還有3S以上的載入時間,這直接導致了我們的DOM渲染時間被拉長到了3S+。

如果這個情況不是出現在我們自己除錯的時候,那麼我們永遠不可能發現這個問題,雖然我用的是第三方的CDN,但是還有是不穩定的時候。

這個時候我們來看我們在onload的時候我們呼叫的resource timing 資訊返回是一個陣列,這個陣列返回的是我們這次從開啟網頁一開始到DOM渲染完成的過程里載入過得所有資源資訊。

然後我們隨意點開一個資源看看裡面有什麼資訊是我們可以獲取到的:

web效能之資源載入時間分析【Resource Timing】【原創】

從上圖中我們可以獲取到什麼多的資訊,我們現在看看這些屬性主要都是做什麼用的:

PerformanceResourceTiming

屬性 描述
entryType EntryType的型別resource
name resources URL
startTime 在資源提取開始的時間
duration 整個流程消耗的時間=responseEnd-startTime
initiatorType 發起資源請求的型別
nextHopProtocol 獲取資源的網路協議的字串
workerStart 如果Service Worker執行緒已在執行,則在呼叫FetchEvent之前立即返回DOMHighResTimeStamp,如果尚未執行,則在啟動Service Worker執行緒之前立即返回DOMHighResTimeStamp。 如果資源未被Service Worker攔截,則該屬性將始終返回0
redirectStart 初始重定向的開始獲取時間
redirectEnd 緊接在收到最後一次重定向響應的最後一個位元組後
fetchStart 拉取資源開始時間,緊接在瀏覽器開始獲取資源之前
domainLookupStart 緊接在瀏覽器啟動資源的域名查詢之前
domainLookupEnd 表示瀏覽器完成資源的域名查詢後的時間
connectStart 開始TCP連線:緊接在瀏覽器檢索資源,開始建立與伺服器的連線之前
connectEnd 結束TCP連線:緊接在瀏覽器完成與伺服器的連線以檢索資源之後
secureConnectStart 開始SSL連線:緊接在瀏覽器啟動握手過程之前,以保護當前連線
requestStart 緊接在瀏覽器開始從伺服器請求資源之前
responseStart 緊接在瀏覽器收到伺服器響應的第一個位元組後
responseEnd 緊接在瀏覽器收到資源的最後一個位元組之後或緊接在傳輸連線關閉之前,以先到者為準
secureConnectionStart SSL / 初始連線時間
transferSize 表示獲取資源的大小(以八位位元組為單位)的數字。 包括響應頭欄位和響應payload body的大小。
encodedBodySize 在刪除任何應用的內容編碼之前,從payload body的提取(HTTP或快取記憶體)接收的大小(以八位位元組為單位)的number
decodedBodySize 在刪除任何應用的內容編碼之後,從訊息正文( message body )的提取(HTTP或快取)接收的大小(以八位位元組為單位)的number
serverTiming 包含伺服器時序度量( timing metrics )的PerformanceServerTiming 條目陣列,可用於伺服器傳資料到前端
PerformanceResourceTiming.initiatorType

已知可獲取型別如下:

型別 描述
css css資源型別
img 圖片請求型別
scrpit scrpit指令碼請求型別
xmlhttprequest 介面請求型別
link link請求型別

上述的表格中列出了 resource 型別時的所有屬性及含義:

我們一般來直接把這個資料結構體轉發給後臺進行分析就可以了

注意:上面的所有時間都是相對時間,全部都是相對這個程式一開始執行的疊加時間,不是系統的時間戳

那麼我們有集合上面的資料來進行簡單的分析,得到的結果如下:

我們這個名叫https://cdn.bootcss.com/video.js/7.7.6/alt/video-js-cdn.css的這個資原始檔,從建立資源initiatorType開始到DNS解析結束domainLookupEnd的這段時間都很順滑。

但是到了建立連結這一步(SSL連線)secureConnectionStart這一步就開始卡了,並且卡了3S+,才完成了安全連線的建立,從這時間來看是很不正常的,隨後後面的下載、解析操作都是相當的快。

那麼到這裡我們其實就發現了,我們在開發時可能復現不了的問題,(本次是我們在開發時用偵錯程式有幸看到了),要是使用者在使用的時候,對於我們來說我們是無知的,也沒有做資料記錄的,所以為什麼有時候客戶說慢但是我們卻復現不出來的原因(當然還有可能是網路質量問題,後面我會講怎麼獲取使用者當前網路質量)。

分析資料

這裡我們可以直接用js分析一次我們抓取到的資料:

程式碼修改如下

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>demo</title>
  <link href="https://cdn.bootcss.com/animate.css/3.7.2/animate.css" rel="stylesheet">
  <link href="https://cdn.bootcss.com/video.js/7.7.6/alt/video-js-cdn.css" rel="stylesheet">
  <style>
    img {
      width: 50px;
    }

    .analysisResult {
      margin-top: 50px;
    }

    table {
      margin-top: 8px;
    }

    td {
      min-width: 70px;
      padding: 0 6px;
    }

    .status {

    }

    .status-item {

    }

    .color {
      display: inline-block;
      height: 10px;
      width: 10px;
    }

    .success {
      color: green;
    }

    .success_back {
      background: green;
    }

    .warning {
      color: #E6A23C;
    }

    .warning_back {
      background: #E6A23C;
    }

    .error {
      color: red;
    }

    .error_back {
      background: red;
    }
  </style>
</head>
<body>
<img src="https://www.baidu.com/img/bd_logo1.png">
<img
    src="https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1585755631&di=5e0530559acfe3bc031c33c5c6fe38d6&src=http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg">
<div class="analysisResult">
  <div class="status">
    <span class="status-item">
      <span class="color success_back"></span>
      <span class="text"><=50ms</span>
    </span>

    <span class="status-item">
      <span class="color warning_back"></span>
      <span class="text"><=100ms</span>
    </span>

    <span class="status-item">
      <span class="color error_back"></span>
      <span class="text">>100ms</span>
    </span>
  </div>
  <table border="none">
    <thead>
    <tr>
      <th>檔名稱</th>
      <th>標籤型別</th>
      <th>檔案型別</th>
      <th>解析時間</th>
      <th>連線時間</th>
      <th>響應時間</th>
      <th>檔案大小</th>
      <th>開始時間</th>
      <th>結束時間</th>
      <th>消耗時間</th>
    </tr>
    </thead>
    <tbody id="tableBody">
    </tbody>
  </table>
</div>
</body>
<script src="https://cdn.bootcss.com/video.js/7.7.6/alt/video.core.js"></script>
<script>
  window.onload = function () {
    let resourceLoadTiming = performance.getEntriesByType('resource');
    let tableBody = document.querySelectorAll('#tableBody')[0];
    console.log(resourceLoadTiming);
    resourceLoadTiming.map(({startTime, duration, name, transferSize, responseStart, responseEnd, secureConnectionStart, requestStart, domainLookupStart, domainLookupEnd, initiatorType}) => {
      if (initiatorType === 'css') return;
      const tr = document.createElement('tr');
      let trClass = '';
      let fileName = name.replace(/(^.*\/)(.*)(\..*$)/, '$2$3');
      let fileType = name.replace(/(^.*)(\..*$)/, '$2');
      if (duration <= 50) {
        trClass = 'success';
      } else if (duration <= 100) {
        trClass = 'warning';
      } else {
        trClass = 'error';
      }
      tr.setAttribute('class', trClass);
      tr.innerHTML = `
        <td>${fileName}</td>
        <td>${initiatorType}</td>
        <td>${fileType}</td>
        <td>${(domainLookupEnd - domainLookupStart).toFixed(2)}ms</td>
        <td>${(requestStart - secureConnectionStart).toFixed(2)}ms</td>
        <td>${(responseEnd - responseStart).toFixed(2)}ms</td>
        <td>${(transferSize / 1000).toFixed(2)}kb</td>
        <td>${startTime.toFixed(2)}ms</td>
        <td>${responseEnd.toFixed(2)}ms</td>
        <td>${duration.toFixed(2)}ms</td>
      `;
      tableBody.appendChild(tr);
    });
  };
</script>
</html>

複製程式碼

我們在清除快取後,在執行一次程式碼後就會在頁面上看到我們的分析的資料結果如下:

web效能之資源載入時間分析【Resource Timing】【原創】

上面中因為我們設定了各種等級的閾值,所以可以看到沒有一個資源是在50ms內載入完成的,當然如果是實際的分析中,我們還需要加上一個比例不是所有的資料都能在50ms內返回,比如我們下載的是10M的檔案這個時候的合理閾值就不會是50ms 了。

下面我們再來看看,我們在控制檯看到的資料是否和我們用js分析出來的資料是否一致:

web效能之資源載入時間分析【Resource Timing】【原創】

從上圖中我們可以看到其實我們在控制檯的時間和我們分析出來的時間是一致的。

其實如果再想確認資料的準確性的話,就需要用快取把我們每次清除快取後重新載入的資料快取起來,然後做一個求平均值處理,比如執行100次看平均資料,資料越多越接近真實情況

如果想看實際的執行效果**請搓這裡**

總結

通過上述的一頓操作,我們發現其實,對於資源載入這塊我們是可以通過Resource Timing來分析得到的,所以一個穩定高效能的應用是需要做資料收集分析,並且還需要做預計功能的,這樣才能保證能夠提前預知一些問題,方便我們開發人員進行維護和調優。

通過上面獲取的資料我們可分析出以下資料:

  • 資源請求的DNS的解析時間
  • TCP連線所消耗的時間
  • SSL連線所消耗的時間
  • 請求時間(伺服器處理時間)
  • 響應時間(下載時間)
  • 傳輸的檔案大小
  • 編碼前的檔案大小
  • 解碼後的檔案大小

未來規劃

關於資源分析這塊我自己也在空餘時間開發一個分析資源載入的小外掛WebResourceTiming

預計會在下週釋出V1.0主要功能如下:

  • 可以快速獲取到格式化後的資料(上面描述的各類時間都幫你計算好了)
  • 支援訂閱者/釋出者模式,支援實時監控獲取最新資源資料
  • 還會有一個基於WebResourceTiming模組開發的資源分析報告小外掛WebResourceTimingReport到時都會發布到npm,無任何第三方依賴。
    • WebResourceTimingReport支援配置化閾值和鉤子函式,自定義結果,如果不設定則按預設配置分析,生成報告
    • 支援不生成報告,只獲取渲染報告的資料,方便前端傳輸回後臺進行資料蒐集

後面還會陸續釋出剩餘的幾大類的API的使用介紹文章,和應用場景及相關資料分析小外掛,及相關外掛的使用文件

  • WebRrameTiming 如何分析框架渲染效能資訊,並附帶小外掛
  • WebNavigationTiming 如何分析導航過程資訊,並附帶小外掛
  • WebMarkTiming 如何利用手動打點分析我們的程式碼效能及追蹤使用者行為資料,並附帶小外掛
  • WebPerformanceAnalysis 如何上面的小外掛完成整個web網頁的效能分析,及效能報表生成及相關的優化建議,負載元件庫

2020年4月3日

JBoss

相關文章