初探 performance – 監控網頁與程序性能

ygp8 9年前發布 | 87K 次閱讀 前端技術 performance
 

使用 window.performance 提供了一組精確的數據,經過簡單的計算就能得出一些網頁性能數據。

配合上報一些客戶端瀏覽器的設備類型等數據,就可以實現簡單的統計啦!

額,先看下兼容性如何: http://caniuse.com/#feat=nav-timing

這篇文章中 Demo 的運行環境為最新的 Chrome 的控制臺,如果你用的是其他瀏覽器,自查兼容性哈~

先來看看在 Chrome 瀏覽器控制臺中執行 window.performance 會出現什么:

初探 performance – 監控網頁與程序性能

簡單解釋下 performance 中的屬性:

先看下一個請求發出的整個過程中,各種環節的時間順序:

初探 performance – 監控網頁與程序性能

// 獲取 performance 數據

var performance = {   

// memory 是非標準屬性,只在 Chrome 有

// 財富問題:我有多少內存

memory : {

usedJSHeapSize :    16100000 , // JS 對象(包括V8引擎內部對象)占用的內存,一定小于 totalJSHeapSize

totalJSHeapSize : 35100000 , // 可使用的內存

jsHeapSizeLimit : 793000000 // 內存大小限制

} ,

//  哲學問題:我從哪里來?

navigation : {

redirectCount : 0 , // 如果有重定向的話,頁面通過幾次重定向跳轉而來

type : 0            // 0   即 TYPE_NAVIGATENEXT 正常進入的頁面(非刷新、非重定向等)

// 1   即 TYPE_RELOAD       通過 window.location.reload() 刷新的頁面

// 2   即 TYPE_BACK_FORWARD 通過瀏覽器的前進后退按鈕進入的頁面(歷史記錄)

// 255 即 TYPE_UNDEFINED    非以上方式進入的頁面

} ,

timing : {

// 在同一個瀏覽器上下文中,前一個網頁(與當前頁面不一定同域)unload 的時間戳,如果無前一個網頁 unload ,則與 fetchStart 值相等

navigationStart : 1441112691935 ,

// 前一個網頁(與當前頁面同域)unload 的時間戳,如果無前一個網頁 unload 或者前一個網頁與當前頁面不同域,則值為 0

unloadEventStart : 0 ,

// 和 unloadEventStart 相對應,返回前一個網頁 unload 事件綁定的回調函數執行完畢的時間戳

unloadEventEnd : 0 ,

// 第一個 HTTP 重定向發生時的時間。有跳轉且是同域名內的重定向才算,否則值為 0

redirectStart : 0 ,

// 最后一個 HTTP 重定向完成時的時間。有跳轉且是同域名內部的重定向才算,否則值為 0

redirectEnd : 0 ,

// 瀏覽器準備好使用 HTTP 請求抓取文檔的時間,這發生在檢查本地緩存之前

fetchStart : 1441112692155 ,

// DNS 域名查詢開始的時間,如果使用了本地緩存(即無 DNS 查詢)或持久連接,則與 fetchStart 值相等

domainLookupStart : 1441112692155 ,

// DNS 域名查詢完成的時間,如果使用了本地緩存(即無 DNS 查詢)或持久連接,則與 fetchStart 值相等

domainLookupEnd : 1441112692155 ,

// HTTP(TCP) 開始建立連接的時間,如果是持久連接,則與 fetchStart 值相等

// 注意如果在傳輸層發生了錯誤且重新建立連接,則這里顯示的是新建立的連接開始的時間

connectStart : 1441112692155 ,

// HTTP(TCP) 完成建立連接的時間(完成握手),如果是持久連接,則與 fetchStart 值相等

// 注意如果在傳輸層發生了錯誤且重新建立連接,則這里顯示的是新建立的連接完成的時間

// 注意這里握手結束,包括安全連接建立完成、SOCKS 授權通過

connectEnd : 1441112692155 ,

// HTTPS 連接開始的時間,如果不是安全連接,則值為 0

secureConnectionStart : 0 ,

// HTTP 請求讀取真實文檔開始的時間(完成建立連接),包括從本地讀取緩存

// 連接錯誤重連時,這里顯示的也是新建立連接的時間

requestStart : 1441112692158 ,

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

responseStart : 1441112692686 ,

// HTTP 響應全部接收完成的時間(獲取到最后一個字節),包括從本地讀取緩存

responseEnd : 1441112692687 ,

// 開始解析渲染 DOM 樹的時間,此時 Document.readyState 變為 loading,并將拋出 readystatechange 相關事件

domLoading : 1441112692690 ,

// 完成解析 DOM 樹的時間,Document.readyState 變為 interactive,并將拋出 readystatechange 相關事件

// 注意只是 DOM 樹解析完成,這時候并沒有開始加載網頁內的資源

domInteractive : 1441112693093 ,

// DOM 解析完成后,網頁內資源加載開始的時間

// 在 DOMContentLoaded 事件拋出前發生

domContentLoadedEventStart : 1441112693093 ,

// DOM 解析完成后,網頁內資源加載完成的時間(如 JS 腳本加載執行完畢)

domContentLoadedEventEnd : 1441112693101 ,

// DOM 樹解析完成,且資源也準備就緒的時間,Document.readyState 變為 complete,并將拋出 readystatechange 相關事件

domComplete : 1441112693214 ,

// load 事件發送給文檔,也即 load 回調函數開始執行的時間

// 注意如果沒有綁定 load 事件,值為 0

loadEventStart : 1441112693214 ,

// load 事件的回調函數執行完畢的時間

loadEventEnd : 1441112693215

// 字母順序

// connectEnd: 1441112692155,

// connectStart: 1441112692155,

// domComplete: 1441112693214,

// domContentLoadedEventEnd: 1441112693101,

// domContentLoadedEventStart: 1441112693093,

// domInteractive: 1441112693093,

// domLoading: 1441112692690,

// domainLookupEnd: 1441112692155,

// domainLookupStart: 1441112692155,

// fetchStart: 1441112692155,

// loadEventEnd: 1441112693215,

// loadEventStart: 1441112693214,

// navigationStart: 1441112691935,

// redirectEnd: 0,

// redirectStart: 0,

// requestStart: 1441112692158,

// responseEnd: 1441112692687,

// responseStart: 1441112692686,

// secureConnectionStart: 0,

// unloadEventEnd: 0,

// unloadEventStart: 0

}

} ;

具體的含義都在注釋里說明了,接下來我們看下能用這些數據做什么?

使用 performance.timing 信息簡單計算出網頁性能數據

在注釋中,我用【重要】標注了我個人認為比較有用的數據,用【原因】標注了為啥要重點關注這個數據

// 計算加載時間

function getPerformanceTiming ( ) {   

var performance = window . performance ;

if ( ! performance ) {

// 當前瀏覽器不支持

console . log ( '你的瀏覽器不支持 performance 接口' ) ;

return ;

}

var t = performance . timing ;

var times = { } ;

//【重要】頁面加載完成的時間

//【原因】這幾乎代表了用戶等待頁面可用的時間

times . loadPage = t . loadEventEnd - t . navigationStart ;

//【重要】解析 DOM 樹結構的時間

//【原因】反省下你的 DOM 樹嵌套是不是太多了!

times . domReady = t . domComplete - t . responseEnd ;

//【重要】重定向的時間

//【原因】拒絕重定向!比如,http://example.com/ 就不該寫成 http://example.com

times . redirect = t . redirectEnd - t . redirectStart ;

//【重要】DNS 查詢時間

//【原因】DNS 預加載做了么?頁面內是不是使用了太多不同的域名導致域名查詢的時間太長?

// 可使用 HTML5 Prefetch 預查詢 DNS ,見:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364)            

times . lookupDomain = t . domainLookupEnd - t . domainLookupStart ;

//【重要】讀取頁面第一個字節的時間

//【原因】這可以理解為用戶拿到你的資源占用的時間,加異地機房了么,加CDN 處理了么?加帶寬了么?加 CPU 運算速度了么?

// TTFB 即 Time To First Byte 的意思

// 維基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte

times . ttfb = t . responseStart - t . navigationStart ;

//【重要】內容加載完成的時間

//【原因】頁面內容經過 gzip 壓縮了么,靜態資源 css/js 等壓縮了么?

times . request = t . responseEnd - t . requestStart ;

//【重要】執行 onload 回調函數的時間

//【原因】是否太多不必要的操作都放到 onload 回調函數里執行了,考慮過延遲加載、按需加載的策略么?

times . loadEvent = t . loadEventEnd - t . loadEventStart ;

// DNS 緩存時間

times . appcache = t . domainLookupStart - t . fetchStart ;

// 卸載頁面的時間

times . unloadEvent = t . unloadEventEnd - t . unloadEventStart ;

// TCP 建立連接完成握手的時間

times . connect = t . connectEnd - t . connectStart ;

return times ;

}

使用performance.getEntries() 獲取所有資源請求的時間數據

這個函數返回的將是一個數組,包含了頁面中所有的 HTTP 請求,這里拿第一個請求 window.performance.getEntries()[0] 舉例。 注意 HTTP 請求有可能命中本地緩存,所以請求響應的間隔將非常短 可以看到,與 performance.timing 對比: 沒有與 DOM 相關的屬性:

  • navigationStart

  • unloadEventStart

  • unloadEventEnd

  • domLoading

  • domInteractive

  • domContentLoadedEventStart

  • domContentLoadedEventEnd

  • domComplete

  • loadEventStart

  • loadEventEnd

新增屬性:

  • name

  • entryType

  • initiatorType

  • duration

與 window.performance.timing 中包含的屬性就不再介紹了:

var entry = {   

// 資源名稱,也是資源的絕對路徑

name : "http://cdn.alloyteam.com/wp-content/themes/alloyteam/style.css" ,

// 資源類型

entryType : "resource" ,

// 誰發起的請求

initiatorType : "link" , // link 即 <link> 標簽

// script 即 <script>

// redirect 即重定向

// 加載時間

duration : 18.13399999809917 ,

redirectStart : 0 ,

redirectEnd : 0 ,

fetchStart : 424.57699999795295 ,

domainLookupStart : 0 ,

domainLookupEnd : 0 ,

connectStart : 0 ,

connectEnd : 0 ,

secureConnectionStart : 0 ,

requestStart : 0 ,

responseStart : 0 ,

responseEnd : 442.7109999960521 ,

startTime : 424.57699999795295

} ;

可以像 getPerformanceTiming 獲取網頁的時間一樣,獲取某個資源的時間:

// 計算加載時間

function getEntryTiming ( entry ) {   

var t = entry ;

var times = { } ;

// 重定向的時間

times . redirect = t . redirectEnd - t . redirectStart ;

// DNS 查詢時間

times . lookupDomain = t . domainLookupEnd - t . domainLookupStart ;

// 內容加載完成的時間

times . request = t . responseEnd - t . requestStart ;

// TCP 建立連接完成握手的時間

times . connect = t . connectEnd - t . connectStart ;

// 掛載 entry 返回

times . name = entry . name ;

times . entryType = entry . entryType ;

times . initiatorType = entry . initiatorType ;

times . duration = entry . duration ;

return times ;

}

// test

// var entries = window.performance.getEntries();

// entries.forEach(function (entry) {

//     var times = getEntryTiming(entry);

//     console.log(times);

// });

使用 performance.now() 精確計算程序執行時間

performance.now() 與 Date.now() 不同的是,返回了以微秒(百萬分之一秒)為單位的時間,更加精準。

并且與 Date.now() 會受系統程序執行阻塞的影響不同,performance.now() 的時間是以恒定速率遞增的,不受系統時間的影響(系統時間可被人為或軟件調整)。

注意 Date.now() 輸出的是 UNIX 時間,即距離 1970 的時間,而 performance.now() 輸出的是相對于 performance.timing.navigationStart(頁面初始化) 的時間。

使用 Date.now() 的差值并非絕對精確,因為計算時間時受系統限制(可能阻塞)。但使用 performance.now() 的差值,并不影響我們計算程序執行的精確時間。

// 計算程序執行的精確時間

function getFunctionTimeWithDate ( func ) {   

var timeStart = Data . now ( ) ;

// 執行開始

func ( ) ;

// 執行結束

var timeEnd = Data . now ( ) ;

// 返回執行時間

return ( timeEnd - timeStart ) ;

}

function getFunctionTimeWithPerformance ( func ) {   

var timeStart = window . performance . now ( ) ;

// 執行開始

func ( ) ;

// 執行結束

var timeEnd = window . performance . now ( ) ;

// 返回執行時間

return ( timeEnd - timeStart ) ;

}

使用 performance.mark() 也可以精確計算程序執行時間

使用 performance.mark() 標記各種時間戳(就像在地圖上打點),保存為各種測量值(測量地圖上的點之間的距離),便可以批量地分析這些數據了。

直接上示例代碼看注釋便明白:

function randomFunc ( n ) {   

if ( ! n ) {

// 生成一個隨機數

n = ~ ~ ( Math . random ( ) * 10000 ) ;

}

var nameStart = 'markStart' + n ;

var nameEnd    = 'markEnd' + n ;

// 函數執行前做個標記

window . performance . mark ( nameStart ) ;

for ( var i = 0 ; i < n ; i ++ ) {

// do nothing

}

// 函數執行后再做個標記

window . performance . mark ( nameEnd ) ;

// 然后測量這個兩個標記間的時間距離,并保存起來

var name = 'measureRandomFunc' + n ;

window . performance . measure ( name , nameStart , nameEnd ) ;

}

// 執行三次看看

randomFunc ( ) ;   

randomFunc ( ) ;   

// 指定一個名字

randomFunc ( 888 ) ;   

// 看下保存起來的標記 mark

var marks = window . performance . getEntriesByType ( 'mark' ) ;   

console . log ( marks ) ;   

初探 performance – 監控網頁與程序性能

// 看下保存起來的測量 measure

var measure = window . performance . getEntriesByType ( 'measure' ) ;   

console . log ( measure ) ;   

初探 performance – 監控網頁與程序性能

// 看下我們自定義的測量

var entries = window . performance . getEntriesByName ( 'measureRandomFunc888' ) ;   

console . log ( entries ) ;   

初探 performance – 監控網頁與程序性能

可以看到,for 循環 measureRandomFunc888 的時候

結束時間為: 4875.1199999969685

開始時間為:4875.112999987323

執行時間為:4875.1199999969685 – 4875.112999987323 = 0.00700000964

標記和測量用完了可以清除掉:

// 清除指定標記

window . performance . clearMarks ( 'markStart888' ) ;   

// 清除所有標記

window . performance . clearMarks ( ) ;

// 清除指定測量

window . performance . clearMeasures ( 'measureRandomFunc' ) ;   

// 清除所有測量

window . performance . clearMeasures ( ) ;   

當然 performance.mark() 只是提供了一些簡便的測量方式,比如之前我們測量 domReady 是這么測的:

// 計算 domReady 時間

var t = performance . timing  

var domReadyTime = t . domComplete - t . responseEnd ;   

console . log ( domReadyTime )   

其實就可以寫成:

window . performance . measure ( 'domReady' , 'responseEnd' , 'domComplete' ) ;   

var domReadyMeasure = window . performance . getEntriesByName ( 'domReady' ) ;   

console . log ( domReadyMeasure ) ;   

初探 performance – 監控網頁與程序性能

拋磚引玉:performance 數據能干啥用?

熟悉 Chrome 開發者工具的朋友應該知道:在開發環境下,其實我們自己打開 Chrome 的開發者工具,切換到網絡面板,就能很詳細的看到網頁性能相關的數據。但當我們需要統計分析用戶打開我們網頁時的性能如何時,我們將 performance 原始信息或通過簡單計算后的信息(如上面寫到的 getPerformanceTiming() 和 getEntryTiming()) 上傳到服務器,配合其他信息(如 HTTP 請求頭信息),就完美啦~

 本文由用戶 ygp8 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!