DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> WEB網站前端 >> WEB前端代碼 >> 前端性能監控方案調研
前端性能監控方案調研
編輯:WEB前端代碼     

1. 業界案例

目前前端性能監控系統大致為分兩類:以GA為代表的代碼監控和以webpagetest為代表的工具監控

代碼監控依托於js代碼並部署到需監控的頁面,手動計算時間差或者使用浏覽器的的API進行數據統計。

影響代碼監控數據的因素有以下幾種:

  • 浏覽器渲染機制;
  • 浏覽器對API的實現程度,比如performance API;

工具監控不用將統計代碼部署到頁面中,一般依托於虛擬機。以webpageTest為例,輸入需統計的url並且選擇運行次url的浏覽器版本,webpageTest後台虛擬機對url進行請求分析後便可以給出各種性能指標,比如瀑布流、靜態文件數量、首屏渲染時間等等。

代碼監控和工具監控的對比如下表:

根據目前業務需求以及成本預算,最終決定采用代碼監控方案。以下分別介紹代碼監控各方面的實現細節。

2. 前端性能監控指標

前端性能統計的數據大致有以下幾個:

  • 白屏時間:從打開網站到有內容渲染出來的時間節點;
  • 首屏時間:首屏內容渲染完畢的時間節點;
  • 用戶可操作時間節點:domready觸發節點;
  • 總下載時間:window.onload的觸發節點。

下面介紹幾種以上幾個數據的統計方案。

2.1 常規統計方案

使用注入代碼監控的方式統計以上指標,在沒有一些浏覽器新API(如下文將提到的timing API)的支持下,得到的數據大都是估值,雖然不准確,但也有一定的參考價值。

2.1.1 白屏時間

白屏時間節點指的是從用戶進入網站(輸入url、刷新、跳轉等方式)的時刻開始計算,一直到頁面有內容展示出來的時間節點。這個過程包括dns查詢、建立tcp連接、發送首個http請求(如果使用https還要介入TLS的驗證時間)、返回html文檔、html文檔head解析完畢。

使用注入代碼監控無法獲取解析html文檔之前的時間信息,目前普遍使用的白屏時間統計方案是在html文檔的head中所有的靜態資源以及內嵌腳本/樣式之前記錄一個時間點,在head最底部記錄另一個時間點,兩者的差值作為白屏時間。如下:

<html>
<head>
<meta charset="UTF-8"/>
<!--這裡還有一大串meta信息-->
<script>
var start_time = new Date();//統計起點,實際為html開始解析的時間節點
</script>
<link href='a.css'></link>
<script src='a.js'></script>
<script>
var end_time = new Date();//統計起點,實際為html開始解析的時間節點
</script>
</head>
<body>
</body>
</html>

上述代碼中的end_timestart_time的差值一般作為白屏時間的估值,但理論上來講,這個差值只是浏覽器解析html文檔head的時間,並非准確的白屏時間。

2.1.2 首屏時間

首屏時間的統計比較復雜,目前應用比較廣的方案是將首屏的圖片、iframe等資源添加onload事件,獲取最慢的一個。

這種方案比較適合首屏元素數量固定的頁面,比如移動端首屏不論屏幕大小都展示相同數量的內容,響應式得改變內容的字體、尺寸等。但是對於首屏元素不固定的頁面,這種方案並不適用,最典型的就是PC端頁面,不同屏幕尺寸下展示的首屏內容不同。上述方案便不適用於此場景。

2.1.3 可操作時間

用戶可操作的時間節點即dom ready觸發的時間,使用jquery可以通過$(document).ready()獲取此數據,如果不使用jQuery可以參考這裡通過原生方法實現dom ready。

2.1.4 總下載時間

總下載時間即window.onload觸發的時間節點。

目前大多數web產品都有異步加載的內容,比如圖片的lazyload等。如果總下載時間需要統計到這些數據,可以借鑒AOP的理念,在請求異步內容之前和之後分別打點,最後計算差值。不過通常來講,我們說的總下載時間並不包括異步加載的內容。

2.2 使用window.performance API

window.performance 是W3C性能小組引入的新的API,目前IE9以上的浏覽器都支持。一個performance對象的完整結構如下圖所示:

memory字段代表JavaScript對內存的占用。

navigation字段統計的是一些網頁導航相關的數據:

  1. redirectCount:重定向的數量(只讀),但是這個接口有同源策略限制,即僅能檢測同源的重定向;
  2. type 返回值應該是0,1,2 中的一個。分別對應三個枚舉值:
    • 0 : TYPE_NAVIGATE (用戶通過常規導航方式訪問頁面,比如點一個鏈接,或者一般的get方式)
    • 1 : TYPE_RELOAD (用戶通過刷新,包括JS調用刷新接口等方式訪問頁面)
    • 2 : TYPE_BACK_FORWARD (用戶通過後退按鈕訪問本頁面)

最重要的是timing字段的統計數據,它包含了網絡、解析等一系列的時間數據。

2.2.1 timing API

timing的整體結構如下圖所示:

各字段的含義如下:

  • startTime:有些浏覽器實現為navigationStart,代表浏覽器開始unload前一個頁面文檔的開始時間節點。比如我們當前正在浏覽baidu.com,在地址欄輸入google.com並回車,浏覽器的執行動作依次為:unload當前文檔(即baidu.com)->請求下一文檔(即google.com)。navigationStart的值便是觸發unload當前文檔的時間節點。

    如果當前文檔為空,則navigationStart的值等於fetchStart。

  • redirectStartredirectEnd:如果頁面是由redirect而來,則redirectStart和redirectEnd分別代表redirect開始和結束的時間節點;
  • unloadEventStartunloadEventEnd:如果前一個文檔和請求的文檔是同一個域的,則unloadEventStartunloadEventEnd分別代表浏覽器unload前一個文檔的開始和結束時間節點。否則兩者都等於0;
  • fetchStart是指在浏覽器發起任何請求之前的時間值。在fetchStart和domainLookupStart之間,浏覽器會檢查當前文檔的緩存;
  • domainLookupStartdomainLookupEnd分別代表DNS查詢的開始和結束時間節點。如果浏覽器沒有進行DNS查詢(比如使用了cache),則兩者的值都等於fetchStart
  • connectStartconnectEnd分別代表TCP建立連接和連接成功的時間節點。如果浏覽器沒有進行TCP連接(比如使用持久化連接webscoket),則兩者都等於domainLookupEnd
  • secureConnectionStart:可選。如果頁面使用HTTPS,它的值是安全連接握手之前的時刻。如果該屬性不可用,則返回undefined。如果該屬性可用,但沒有使用HTTPS,則返回0;
  • requestStart代表浏覽器發起請求的時間節點,請求的方式可以是請求服務器、緩存、本地資源等;
  • responseStartresponseEnd分別代表浏覽器收到從服務器端(或緩存、本地資源)響應回的第一個字節和最後一個字節數據的時刻;
  • domLoading代表浏覽器開始解析html文檔的時間節點。我們知道IE浏覽器下的document有readyState屬性,domLoading的值就等於readyState改變為loading的時間節點;
  • domInteractive代表浏覽器解析html文檔的狀態為interactive時的時間節點。domInteractive並非DOMReady,它早於DOMReady觸發,代表html文檔解析完畢(即dom tree創建完成)但是內嵌資源(比如外鏈css、js等)還未加載的時間點;
  • domContentLoadedEventStart:代表DOMContentLoaded事件觸發的時間節點:

    頁面文檔完全加載並解析完畢之後,會觸發DOMContentLoaded事件,HTML文檔不會等待樣式文件,圖片文件,子框架頁面的加載(load事件可以用來檢測HTML頁面是否完全加載完畢(fully-loaded))。

  • domContentLoadedEventEnd:代表DOMContentLoaded事件完成的時間節點,此刻用戶可以對頁面進行操作,也就是jQuery中的domready時間;
  • domComplete:html文檔完全解析完畢的時間節點;
  • loadEventStartloadEventEnd分別代表onload事件觸發和結束的時間節點

2.2.2 計算性能指標

可以使用Navigation.timing 統計到的時間數據來計算一些頁面性能指標,比如DNS查詢耗時、白屏時間、domready等等。如下:

  • DNS查詢耗時 = domainLookupEnd - domainLookupStart
  • TCP鏈接耗時 = connectEnd - connectStart
  • request請求耗時 = responseEnd - responseStart
  • 解析dom樹耗時 = domComplete - domInteractive
  • 白屏時間 = domloadng - fetchStart
  • domready時間 = domContentLoadedEventEnd - fetchStart
  • onload時間 = loadEventEnd - fetchStart
2.2.3 Resource timing API

Resource timing API是用來統計靜態資源相關的時間信息,詳細的內容請參考W3C Resource timing。這裡我們只介紹performance.getEntries方法,它可以獲取頁面中每個靜態資源的請求,如下:

可以看到performance.getEntries返回一個數組,數組的每個元素代表對應的靜態資源的信息,比如上圖展示的第一個元素對應的資源類型initiatorType是圖片img,請求花費的時間就是duration的值。

關於Resource timing API的使用場景,感興趣的同學可以深入研究。

2.3 參考資料

  1. Facebook測速方案;
  2. Measuring Page Load Speed with Navigation Timing;
  3. 前端數據之美 -- 基礎篇;
  4. 7 天打造前端性能監控系統;
  5. phantomjs ;
  6. 前端相關數據監控;
  7. 研究首屏時間?你先要知道這幾點細節

3. JavaScript代碼異常監控

JavaScript異常一般有兩方面:語法錯誤運行時錯誤。兩種錯誤的捕獲和處理方式不同,從而影響具體的方案選型。通常來說,處理JS異常的方案有兩種:try...catch捕獲 和 window.onerror捕獲。以下就兩種方案分別分析各自的優劣。

雖然語法錯誤本應該在開發構建階段使用測試工具避免,但難免會有馬失前蹄部署到線上的時候。

3.1 try...catch捕獲

這種方案要求開發人員在編寫代碼的時候,在預估有異常發生的代碼段使用try...catch,在發生異常時將異常信息發送給接口:

try{
//可能發生異常的代碼段
}catch(e){
//將異常信息發送服務端
}

try...catch的優點是可以細化到每個代碼塊,並且可以自定義錯誤信息以便統計。

具體到上文提到的兩種js異常,try...catch無法捕獲語法錯誤,當遇到語法錯誤時,浏覽器仍然會拋出錯誤Uncaught SyntaxError,但是不會被捕獲,不會走進catch的代碼塊內。

另外,如果try代碼塊中有回調函數也不會被捕獲,比如:

try{
var btn = $('#btn');
    btn.on('click',function(){
        //throw error
    });
}catch(e){}

上述代碼中btn的監聽函數裡拋出的異常無法被外層的catch捕獲到,必須額外套一層:

try{
var btn = $('#btn');
    btn.on('click',function(){
        try{
            //throw error
        }catch(e){}
    });
}catch(e){}

綜上所述,try...catch方案的部署非常復雜,如果人工部署除了要求巨量的工作量,還跟開發人員的能力和經驗有關。如果依賴編譯工具部署(比如fis),那每個代碼塊都套一層try...catch也是非常難看的並且容易引發一些不可預估的問題。

3.2 window.onerror捕獲

這種方式不需要開發人員在代碼中書寫大量的try...catch,通過給window添加onerror監聽,在js發生異常的時候便可以捕獲到錯誤信息,語法異常和運行異常均可被捕獲到。但是window.onerror這個監聽必須放在所有js文件之前才可以保證能夠捕獲到所有的異常信息。

window.onerror事件的詳細信息參考這裡。

/**
 * @param {String}  errorMessage   錯誤信息
 * @param {String}  scriptURL      出錯文件的URL
 * @param {Long}    lineNumber     出錯代碼的行號
 * @param {Long}    columnNumber   出錯代碼的列號
 * @param {Object}  errorObj       錯誤信息Object
 */
window.onerror = function(errorMessage, scriptURL, lineNumber,columnNumber,errorObj) { 
    // code..
}

onerror的實現方式各浏覽器略有差異,但是前三個參數都是相同的,某些低版本浏覽器沒有後兩個參數。

最後一個參數errorObj各浏覽器實現的程度不一致,具體可參考這裡。

下圖是被onerror捕獲到的一個異常的具體信息:

綜上所述,window.onerror方案的優點是減少了開發人員的工作量,部署方便,並且可以捕獲語法錯誤和運行錯誤。缺點是錯誤信息不能自定義,並且errorObj每種浏覽器的實現有略微差異,導致需統計的信息有局限性。

3.3 跨域JS文件異常的捕獲

為了提高web性能,目前大部分web產品架構中都有CDN這一環,將資源部署到不同的域名上,充分利用浏覽器的並發請求機制。那麼在跨域JS文件中發生異常的時候,onerror監聽會捕獲到什麼信息呢?請看下圖:

只有一個稍微有價值的信息Script error,其他什麼信息都沒有,為什麼會這樣呢?

我們都知道浏覽器有同源資源限制,常規狀態下是無法進行跨域請求的。而script、img、iframe標簽的src屬性是沒有這種限制的,這也是很多跨域方案的基礎。但是即使script標簽可以請求到異域的js文件,此文件中的信息也並不能暴露到當前域內,這也是浏覽器的安全措施所致。

那麼有沒有辦法獲取到異域資源的異常信息呢?

其實很簡單,目前可以說基本上所有的web產品對於js/css/image等靜態資源都在服務端設置了Access-Control-Allow-Origin: *的響應頭,也就是允許跨域請求。在這個環境下,只要我們在請求跨域資源的script標簽上添加一個crossorigin屬性即可:

<script src="http://static.toutiao.com/test.js" crossorigin></script>

這樣的話,異域的test.js文件中發生異常時便可以被當前域的onerror監聽捕獲到詳細的異常信息。

3.4 參考資料

  1. 構建web前端異常監控系統;
  2. 前端代碼異常日志收集與監控;
  3. 前端代碼異常監控
XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved