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

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

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

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

這篇文章中 Demo 的運行環境爲最新的 Chrome 的控制檯,若是你用的是其餘瀏覽器,自查兼容性哈~瀏覽器

先來看看在 Chrome 瀏覽器控制檯中執行 window.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 相關的屬性:app

  • navigationStartdom

  • 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);

// 看下保存起來的測量 measure
var measure = window.performance.getEntriesByType('measure');
console.log(measure);

// 看下咱們自定義的測量
var entries = window.performance.getEntriesByName('measureRandomFunc888');
console.log(entries);

能夠看到,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 數據能幹啥用?

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

(完)感謝閱讀!
原文連接

相關文章
相關標籤/搜索