應用:前端性能監控performance

  項目上線後,一般都是都會作埋點,作數據監控、異常監控以及性能監控,本文主要聊聊前端性能監控。重點講performance怎麼作性能監控。javascript

  前端性能監控一般檢測某些方面的加載時間,經過獲得的加載時間的長短來判斷項目某方面的性能怎樣,具體是哪些方面呢???咱們先來看下頁面加載是一個怎樣的過程:css

頁面加載

  頁面加載的方式有兩種,一種是加載完資源文件後經過javascript動態獲取接口數據,而後數據返回渲染內容,這就是先後端分離的項目頁面加載方式。另外一種就是服務器渲染,同構直出前端頁面,這種方式相對前一種方式頁面加載的速度要快不少,性能方面相對也就要好些。可是目前大部分的項目都是先後端分離,本文重點描述這種方式。html

  1. 輸入網址,回車
  2. 緩存解析:若是瀏覽器本地緩存有資源從緩存取資源
  3. 域名解析:DNS解析,將域名解析成IP,若是緩存中存在,直接叢緩存中取IP,不用作域名解析
  4. 發送請求:向服務器發送請求
  5. TCP鏈接、三次握手:創建瀏覽器端和服務器端鏈接
  6. 服務器接到請求:服務器響應請求
  7. 數據傳輸
  8. 瀏覽器端拿到數據,解析html文件,構建DOM樹,CSSOM樹,js文件的加載可能會阻塞頁面的渲染
  9. 初始的html被徹底加載和解析後會觸發DOMContentLoaded事件
  10. CSSOM樹和DOM樹構建完成後會開始生成Render樹,繪製
  11. 在生成Render樹的過程當中,瀏覽器就開始調用GPU繪製,合成圖層,將內容顯示在屏幕上
  12. 沒有文件傳輸,四次揮手,TCP鏈接斷開

  上面就是頁面加載的基本過程,沒有展開講,若是要展開講的話,十篇文章也講不完,在本文中只須要清楚大概的整個過程,後期安排系統的整理上述整個過程的內容,作文字輸出。前端

  頁面加載整個過程當中主要分爲:白屏、重定向、DNS查詢、TCP鏈接、HTTP請求、DOM解析、DOMreadyonload等,這些也就是咱們前端性能監控包括的主要方面。咱們須要監控這幾個方面的數據,作數據分析,進一步作前端性能優化。例如如何加快首屏加載時間、減小http請求時間等等。vue

  下面講講本文主角,performance,不是Chrome開發者工具的Performance面板,固然Chrome開發者工具也能作性能監控。java

performance

  performance是前端性能監控的API,能夠獲取到當前頁面中也與性能相關的信息。git

  咱們來看看天貓商城首頁經過window.performance這個API,獲取到的一些信息: github

  從上面的信息能夠看到 window.performance是一個對象,包含了四個屬性: memorynavigationtimeOrigintiming,以及一個事件處理程序 onresourcetimingbufferfull,咱們再來看看這幾個分別表明什麼?

performance.memory

  在Chrome中添加的一個非標準擴展,memory這個屬性提供了一個能夠獲取到基本內存使用狀況的對象MemoryInfochrome

performance.memory = {
  jsHeapSizeLimit, // 內存大小限制,單位是字節B
  totalJSHeapSize, // 可以使用的內存大小,單位是字節B
  usedJSHeapSize   // JS對象佔用的內存大小,單位是字節B
}
複製代碼

  若是usedJSHeapSize的值大於totalJSHeapSize,會出現內存泄露的問題,因此不能大於totalJSHeapSize的值。後端

performance.navigation

  返回PerformanceNavigation對象,提供了在指定的時間段發生的操做相關信息,包括頁面是加載仍是刷新、發生了多少重定向等。

performance.navigation = {
  redirectCount: xxx,
  type: xxx
}
複製代碼

  PerformanceNavigation對象有兩個屬性,redirectCounttype

performance.navigation.redirectCount

  只讀屬性,表示達到這個頁面以前通過多少次重定向跳轉,可是這個接口有同源策略的限制,僅能檢測到同源的重定向。

performance.navigation.type

  只讀屬性,有四個返回值:0,1,2,225:

  • 0:表示當前頁面是經過點擊連接,書籤和表單提交,或者腳本操做,或者在url中直接輸入地址,至關於常數performance.navigation.TYPE_NAVIGATE
  • 1:表示點擊刷新頁面按鈕或者通過Location.reload()方法顯示的頁面,至關於常數performance.navigation.TYPE_RELOAD
  • 2:表示頁面經過歷史記錄和前進後退訪問的,至關於常數performance.navigation.TYPE_BACK_FORWARD
  • 225:表示任何其餘加載方式,至關於常數performance.navigation.TYPE_RESERVED

performance.timeOrigin

  返回性能測量開始的時間的高精度時間戳。

  上面的時間就表示開始性能測試的時間。

performance.onresourcetimingbufferfull

  一個回調的EventTarget,當觸發resourcetimingbufferfull事件的時候會被調用。

performance.timing

  返回PerformanceTiming對象,包含了各類與瀏覽器性能相關的數據,提供了瀏覽器處理頁面的各個階段的耗時,其總體結構能夠參考下圖:

   PerformanceTiming對象中的屬性都是隻讀屬性,值都是精確到 Unix毫秒的時間戳:

navigationStart

  返回當前瀏覽器窗口的前一個頁面的關閉,發生unload事件時的時間戳。若是沒有前一個頁面,則等於fetchStart屬性。

unloadEventStart

  返回若是前一個頁面與當前頁面同域,則返回前一個頁面unload事件發生時的時間戳。若是沒有沒有前一個頁面,或者以前的頁面跳轉不是在同一個域名內,則返回值爲0

unloadEventStart

  和unloadEventStart相對應,返回前一個頁面unload事件綁定的回調函數執行完畢的時間戳。若是沒有沒有前一個頁面,或者以前的頁面跳轉不是在同一個域名內,則返回值爲0

redirectStart

  返回第一個http重定向發生時的時間戳。有跳轉而且是同域名內的重定向,不然返回值爲0

redirectEnd

  返回最後一個http重定向完成時的時間戳。有跳轉而且是同域名內的重定向,不然返回值爲0

fetchStart

  返回瀏覽器準備好使用http請求抓取文檔的時間戳,這發生在檢查本地緩存以前

domainLookupStart

  返回DNS域名查詢開始的時間戳,若是使用了本地緩存(也就是沒有作DNS查詢,直接從緩存中取到IP)或者使用了持久鏈接,則與fetchStart值相等

domainLookupEnd

  返回DNS域名查詢完成的時間戳,若是使用了本地緩存(也就是沒有作DNS查詢,直接從緩存中取到IP)或者使用了持久鏈接,則與fetchStart值相等

connectStart

  返回httpTCP)開始創建鏈接的時間戳,若是是持久鏈接,則與fetchStart值相等。若是在傳輸層發生了錯誤而且從新創建鏈接,則這裏顯示的是新創建的鏈接開始的時間戳

connectEnd

  返回httpTCP)完成創建鏈接的時間戳,完成了四次握手,若是是持久鏈接,則與fetchStart值相等。若是在傳輸層發生了錯誤而且從新創建鏈接,則這裏顯示的是新創建的鏈接完成的時間戳。鏈接創建指的是全部握手和認證過程所有結束

secureConnectionStart

  返回https鏈接開始的時間戳,若是不是安全鏈接,不然返回值爲0

requestStart

  返回http請求讀取真實文檔開始的時間戳(完成創建鏈接),包括從本地讀取緩存。若是鏈接錯誤重連時,這裏顯示的也是新創建鏈接的時間戳

responseStart

  返回http開始接收響應的時間戳(獲取到第一個字節),包括從本地讀取緩存

responseEnd

  返回http響應所有接收完成的時間戳(獲取到最後一個字節),包括從本地讀取緩存

domLoading

  返回開始解析渲染DOM樹的時間戳,此時Document.readyState變爲loading,並將拋出readystatechange相關事件

domInteractive

  返回完成解析DOM樹的時間戳,Document.readyState變爲interactive,並將拋出readystatechange相關事件。這裏只是DOM樹解析完成,這時候並無開始加載網頁內的資源

domContentLoadedEventStart

  返回DOM解析完成後,網頁內資源加載開始的時間戳。即全部須要被執行的腳本開始被解析了。在DOMContentLoaded事件拋出前發生

domContentLoadedEventEnd

  返回DOM解析完成後,網頁內資源加載完成的時間戳。例如JS腳本加載執行完成,不論執行順序。DOMContentLoaded事件也已經完成

domComplete

  返回DOM解析完成,且資源也準備就緒的時間戳。Document.readyState變爲complete,並將拋出readystatechange相關事件

loadEventStart

  返回load事件發送給文檔,load回調函數開始執行的時間戳。若是沒有綁定load事件,返回值爲0

loadEventEnd

  返回load事件的回調函數執行完畢的時間戳。若是沒有綁定load事件,返回值爲0

  上面已經解釋了相關屬性的含義,經過上面的數據能作不少幫助咱們作性能監控的事情:

  • 頁面加載完成時間:表明了用戶等待頁面可用的時間
let performance = window.performance;
let t = performance.timing;
let time = t.loadEventEnd - t.navigationStart;
複製代碼
  • 解析DOM樹結構的時間:判斷DOM樹嵌套狀況
let performance = window.performance;
let t = performance.timing;
let time = t.domComplete - t.responseEnd;
複製代碼
  • 重定向的時間
let performance = window.performance;
let t = performance.timing;
let time = t.redirectEnd - t.redirectStart;
複製代碼
  • DNS查詢時間:可作預加載,緩存,減小查詢時間
let performance = window.performance;
let t = performance.timing;
let time = t.domainLookupEnd - t.domainLookupStart;
複製代碼
  • 白屏時間:讀取頁面第一個字節的時間
let performance = window.performance;
let t = performance.timing;
let time = t.responseStart - t.navigationStart;
複製代碼
  • 內容加載完成的時間
let performance = window.performance;
let t = performance.timing;
let time = t.responseEnd - t.requestStart;
複製代碼
  • 執行onload回調函數的時間
let performance = window.performance;
let t = performance.timing;
let time = t.loadEventEnd - t.loadEventStart;
複製代碼
  • DNS緩存時間
let performance = window.performance;
let t = performance.timing;
let time = t.domainLookupStart - t.fetchStart;
複製代碼
  • 卸載頁面的時間
let performance = window.performance;
let t = performance.timing;
let time = t.unloadEventEnd - t.unloadEventStart;
複製代碼
  • TCP創建鏈接完成握手的時間
let performance = window.performance;
let t = performance.timing;
let time = t.connectEnd - t.connectStart;
複製代碼

  咱們能夠計算出頁面加載的過程當中各階段的耗時,根據時長判斷某階段的性能怎麼樣,再作進一步的優化處理。這是性能優化很重要的一步,咱們須要定位到具體的哪一個階段耗時過長,對症下藥。

  performance也提供了一些方法,咱們來看看一些經常使用的方法:

自定義統計方法

performance.mark()

  建立一個DOMHighResTimeStamp保存在資源緩存數據中,可經過performance.getEntries()等相關接口獲取。簡單的理解就是能夠作標記,也就是「打點」

performance.mark(name);
複製代碼

performance.clearMarks()

  用於清除標記,若是不加參數,就表示清除全部標記。

performance.clearMarks(name); // 清除指定標記
performance.clearMarks(); // 清除全部標記
複製代碼

performance.measure()

  計算兩個mark之間的時長,建立一個DOMHighResTimeStamp保存在資源緩存數據中,可經過performance.getEntries()等相關接口獲取。

performance.measure(name, startMark, endMark);
複製代碼

performance.clearMeasures

  移除緩存中全部entryTypemeasure的資源數據。

performance.clearMeasures(name); // 清除指定記錄間隔數據
performance.clearMeasures(); // 清除全部記錄間隔數據
複製代碼

  上面的四個API,能夠自定義統計一些數據,例如統計某函數的執行時間。

  在Vue中也有用到,爲了追蹤組件的性能,在Vue2.X中全局配置API有這麼個方法:

Vue.config.performance = false;
複製代碼

  設置爲true以在瀏覽器開發工具的性能/時間線面板中啓用對組件初始化、編譯、渲染和打補丁的性能追蹤。只適用於開發模式和支持performance.mark API的瀏覽器上。咱們入口文件中開啓,開啓後可使用Vue Performance Devtool 這個chrome插件來查看各組件加載狀況:

if (process.env.NODE_ENV !== 'production') {
  Vue.config.performance = true;
}
複製代碼

  在 Vue源碼中,也是經過 performance.markperformance.measure來實現的,咱們看下具體的源碼實現:

// vue/src/core/util/perf.js
import { inBrowser } from './env'

export let mark
export let measure

if (process.env.NODE_ENV !== 'production') { // 環境判斷 開發環境執行下面程序
  const perf = inBrowser && window.performance // 瀏覽器環境
  /* istanbul ignore if */
  if (
    perf &&
    perf.mark &&
    perf.measure &&
    perf.clearMarks &&
    perf.clearMeasures
  ) {
    mark = tag => perf.mark(tag) // 給定tag打點,作標記
    measure = (name, startTag, endTag) => {
      perf.measure(name, startTag, endTag) // 計算兩個mark之間的時長
      perf.clearMarks(startTag) // 清除startTag標記
      perf.clearMarks(endTag) // 清除endTag標記
      // perf.clearMeasures(name) // 清除指定記錄間隔數據
    }
  }
}
複製代碼

  從上面的代碼能夠看出,尤大大經過markmeasure兩個函數對performance.mark()performance.measure()進行了封裝。咱們再來看看在源碼中怎麼應用的:

// vue/src/core/instance/init.js
import { mark, measure } from '../util/perf'
export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    vm._uid = uid++
    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag) // 開始標記
    }
    // .... 中間代碼省略
    // 一系列初始化函數
    // initLifecycle(vm)
    // ....
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag) // 結束標記
      measure(`vue ${vm._name} init`, startTag, endTag) // 計算兩個mark之間的時長
    }
  }
}
複製代碼

  上面是Vue初始化實現的相關程序,上面的打點標記就是爲了追蹤組件初始化的性能狀況,就是在初始化的代碼的開頭和結尾分別使用mark函數打上兩個標記,而後經過measure函數對這兩個標記點進行性能計算。在編譯、渲染和打補丁也作了性能追蹤,感興趣的同窗能夠看看。

  上面是Vue中對performance的應用,起初看vue源碼的時候看到這塊不是很明白,不影響總體的閱讀,沒有太關係,最近工做上有用到performance,想到Vue好像也用到了,就回頭看看Vue中的應用,如今是整明白了。咱們再來看看一個比較簡單實例,更好的理解下:

performance.mark('markStart'); // 標記一個開始點
for(let i = 0; i < 10; i++) {
  console.log(i);
}
performance.mark('markEnd'); // 標記一個結束點
performance.measure('measureVal', 'markStart', 'markEnd');
let measures = performance.getEntriesByName('measureVal');
let measure = measures[0]
console.log('milliseconds: ', measure.duartion); // 0.8999999990919605
// 清除標記
performance.clearMarks();
performance.clearMeasures();
複製代碼

  上面就能夠經過提供的API計算出for循環的執行時間。這幾個API在性能監控應用比較頻繁。

性能資源獲取方法

performance.getEntries

  獲取全部資源請求的時間數據,這個函數返回一個按startTime排序的對象數組:

  從上面能夠看出,返回都是資源頁面加載的相關數據,不少屬性與performance.timing同樣,在這就再也不解釋了。在這裏梳理其餘幾個重要的屬性:

  • name: 資源名稱,是資源的絕對路徑或調用mark方法自定義的名稱(例如entryTyperesource時,name表示資源的路徑)。
  • duration,一個DOMHighResTimeStamp對象,獲取該資源消耗時長。
  • startTime,一個DOMHighResTimeStamp對象,開始獲取該資源的時間。
  • entryType
該類型對象 描述
mark PerformanceMark 經過mark()方法添加到數組中的對象
measure PerformanceMeasure 經過measure()方法添加到數組中的對象
paint PerformancePaintTiming 值爲first-paint首次繪製、first-contentful-paint首次內容繪製
resource PerformanceResourceTiming 全部資源加載時間,用處最多
navigation PerformanceNavigationTiming 現除chromeOpera外均不支持,導航相關信息
frame PerformanceFrameTiming 現瀏覽器均未支持
  • initiatorType,初始化該資源的資源類型:
發起對象 描述
a Element link/script/img/iframe 經過標籤形式加載的資源,值是該節點名的小寫形式
a CSS resourc css 經過css樣式加載的資源,好比backgroundurl方式加載資源
a XMLHttpRequest object xmlhttprequest/fetch 經過xhr加載的資源
a PerformanceNavigationTiming object navigation 當對象是PerformanceNavigationTiming時返回

performance.getEntriesByName

  根據參數nametype獲取一組當前頁面已經加載的資源數據。name的取值對應到資源數據中的name字段,type取值對應到資源數據中的entryType字段。

let entries = window.performance.getEntriesByName(name, type);
複製代碼

performance.getEntriesByType

  根據參數type獲取一組當前頁面已經加載的資源數據。type取值對應到資源數據中的entryType字段

let entries = window.performance.getEntriesByType(type);
複製代碼

  getEntriesByNamegetEntriesByType能夠經過指定參數獲取某類型的資源數據,這樣咱們能夠資源數據分類統計,得出各種數據的狀況。

  上面就是performance的主要內容了,能夠經過這個API作不少關於性能監控的事情,須要根據本身具體的場景制定合適的方案。熟悉Chrome開發者工具的同窗,也是能夠經過performance面板來作性能監控,那爲啥還提供API來作呢,其實更多的但願經過打點統計數據量,能夠經過可視化的方式對數據分析,更直觀,進而作下一步操做。

結語

  文章若有不正確的地方歡迎各位大佬指正,也但願有幸看到文章的同窗也有收穫,一塊兒成長!

——本文首發於我的公衆號———

最後,歡迎你們關注個人公衆號,一塊兒學習交流。
相關文章
相關標籤/搜索