H5性能監(jiān)控「Performance」

? 性能監(jiān)控 在前端一直是一個口頭上備受關(guān)注但開發(fā)中又常被忽略的點纲熏,畢竟不是每個開發(fā)者很容易就做到的事帝美。好在HTML5新增了performance特性鲤遥,它是High Resolution Time API 的一部分咙轩,目的在于獲取到當前頁面中與性能相關(guān)的信息愤惰,以便幫助開發(fā)者直觀感受頁面性能及針對問題優(yōu)化荸哟。
? 了解如何監(jiān)控頁面性能前嘱能,我們先回顧幾個指標:
(1)白屏?xí)r間:頁面被打開擎浴,到首字節(jié)渲染呈現(xiàn)所需的時間滥朱。
(2)首屏?xí)r間:首屏內(nèi)容渲染完成所需的時間根暑。
(3)下載時間(HTTP請求耗時):頁面所需資源從服務(wù)器上下載完成所需的時間。
(4)DOM樹解析時間:資源下載完成到頁面構(gòu)建展示出來所需的時間徙邻。
? ...
? 這些信息都如何獲扰畔印?在此標準之前鹃栽,也有一些手段可以實現(xiàn)躏率,但H5的performance直接來源于瀏覽器躯畴,與手工Date.time,Cookie等對比薇芝,使用上更方便蓬抄,數(shù)據(jù)上更準確。(Date.now()會受程序阻塞影響)
?


$ Performance 屬性

? 關(guān)于performance屬性夯到,建議讀者自己在工具編輯器上直接打印出來看看更能真切的體會該接口嚷缭。本文主要介紹前兩者,對其他內(nèi)容感興趣的同學(xué)耍贾,可以 戳這里

  1. .timing(只讀):對象阅爽;包含了延遲相關(guān)的性能信息。
  2. .navigation(只讀):對象荐开;包含了指定的時間段里發(fā)生的操作相關(guān)信息付翁,包括頁面是加載還是刷新、發(fā)生了多少次重定向等等晃听。
  3. .timeOrigin(只讀):即將失效百侧。用于返回性能測量開始時的高精度時間戳。
  4. .memory:由chrome拓展的非標準屬性能扒,用于返回基本內(nèi)存的使用情況佣渴。注意非chrome不支持。
    ?

# Performance.timing 只讀

const PerformanceTiming = window.performance.timing

? 返回值為一個對象初斑,記錄著完整的頁面加載信息辛润。其各個節(jié)點如下:

圖片摘自網(wǎng)絡(luò)

? 看著上圖,回顧一下一般意義的頁面加載過程:瀏覽器向服務(wù)器請求資源 --> DOM結(jié)構(gòu)解析 --> 構(gòu)建DOM樹 --> 構(gòu)建CSS規(guī)則樹 --> 構(gòu)建渲染樹 --> 繪制頁面见秤∩笆可以看出,這個過程只是上圖中的某一小部分鹃答,我們來詳談一下實際的整個過程

  • Prompt for unload 階段
    • .navigationStart:瀏覽器完成卸載前一個文檔的時間晦溪。如果沒前一個文檔,則該值與第三步.fetchStart的值相同挣跋。
    • .unloadEventStart:返回前一個同源文檔出發(fā)卸載(unload)事件前的時間三圆。如果沒有前一個文檔,或前文檔與本文檔不同源避咆,或需重定向舟肉,則返回0。
    • .unloadEventEnd:返回前一個同源文檔完成卸載的時間查库。如果沒有或文檔不同源路媚,則返回0.
  • Redirect 階段
    • .redirectStart:http重定向開始的時間。如果中間有多個重定向樊销,且每個重定向均同源整慎,則返回第一個重定向的.fetchStart時間脏款,若不同源,則為0
    • .redirectEnd:http重定向結(jié)束時間裤园。如果中間有多個重定向且均同源撤师,則返回最后一個重定向結(jié)束時間。若不同源拧揽,則為0剃盾。
  • App cache 階段
    • .fetchStart:瀏覽器準備好使用HTTP請求來獲取(fetch)文檔的時間,這個時間會在檢查任何應(yīng)用緩存之前淤袜。
  • DNS查詢階段
    • .domainLookupStart:用戶代理對當前文檔所屬域進行DNS查詢開始的時間痒谴。如果是長連接(如websocket),或本地緩存了铡羡,則該值與.fetchStart相同
    • .domainLookupEnd:域名查詢結(jié)束的時間积蔚。如果是長連接,或本地緩存了烦周,則該值與.fetchStart相同
  • TCP連接階段
    • .connectStart:用戶代理開始向服務(wù)器請求所需文檔時库倘,連接建立的開始時間。如果是長連接论矾,或本地緩存了,則該值與.fetchStart相同
    • .secureConnectStart:返回與服務(wù)器開始SSL握手時的時間杆勇。異常情況同上贪壳。
    • .connectEnd: HTTP握手成功,認證結(jié)束蚜退,連接建立時的時間闰靴。如果是長連接,或本地緩存了钻注,則該值與.fetchStart相同蚂且。
  • Request 階段
    • requestStart:從服務(wù)器/緩存/本地資源中開始請求文檔的時間。如果連接發(fā)生斷開重連幅恋,該信息會被刷新杏死。
    • 沒有請求結(jié)束時間是因為該動作發(fā)生在服務(wù)器端,且受數(shù)據(jù)鏈路等各個因素影響捆交,瀏覽器并不能準確反饋該信息
  • Response 階段
    • .responseStart:從服務(wù)器/緩存/本地資源中接收到第一個字節(jié)時的時間淑翼。如果連接發(fā)生斷開重連,該信息會被刷新品追。
    • .responseEnd:從服務(wù)器/緩存/本地資源中接收到最后一個字節(jié)時的時間玄括。如果連接提前關(guān)閉,則返回提前關(guān)閉的時間肉瓦。獲取該值時需注意要在Response結(jié)束之后遭京,如window.onload胃惜,否則可能不準確。
  • Processing 執(zhí)行階段
    • .domLoading:資源下載完成哪雕,開始解析DOM結(jié)構(gòu)船殉,當 Document.readyState 的值更新為loading時的時間。
    • .domInteractive:DOM解析完成热监,開始加載內(nèi)嵌資源捺弦,即Document.readyState的值更新為interactive時的時間
    • 執(zhí)行階段內(nèi)的 DOMContentLoaded 階段
      • .domContentLoadedEventStart:解析器發(fā)送DOMContentLoaded事件,所有需要被執(zhí)行的腳本均解析完成時的時間孝扛。
      • .domContentLoadedEventEnd:所有立即執(zhí)行的腳本均執(zhí)行完成時的時間列吼。不執(zhí)行的腳本如懶加載資源不在該范圍內(nèi)。
    • .domComplete:當前文檔解析完成苦始,document.readyState的值更新為complete時的時間寞钥。
  • load 業(yè)務(wù)涉入階段
    • .loadEventStart:文檔觸發(fā)load事件的時間,如果還沒觸發(fā)陌选,則返回0理郑。
    • .loadEventEnd:文檔結(jié)束load事件的時間,未觸發(fā)則返回0咨油。
      ?

# 性能監(jiān)控指標

? 通過以上的各個事件分析您炉,不難得出如下各個時間段:

const timing = window.performance.timing
  • DNS解析耗時timing.domainLookupEnd - timing.domainLookupStart
  • TCP連接耗時timing.connectEnd - timing.connectStart
  • 發(fā)送請求耗時timing.responseStart - timing.requestStart
  • 接收請求耗時timing.responseEnd - timing.responseStart
  • 解析DOM耗時timing.domInteractive - timing.domLoading
  • 頁面加載完成timing.domContentLoadedEventStart - timing.domInteractive
  • DOMContentLoaded事件耗時timing.domContentLoadedEventEnd - timing.domContentLoadedEventStart
  • DOM加載完成timing.domComplete - timing.domContentLoadedEventEnd
  • DOMLoad事件耗時timing.loadEventEnd - timing.loadEventStart

? 除此之外,在文首提到的其他幾個性能指標役电,如下:

  • 白屏?xí)r間timing.responseStart - timing.navigationStart
  • 首屏?xí)r間timing.domComplete- timing.navigationStart
  • 資源下載總耗時timing.responseEnd - timing.requestStart;
  • 請求完畢至DOM加載timing.domInteractive - timing.responseEnd
    ?

# 實戰(zhàn)案例

? 封裝一個函數(shù)如下赚爵,注釋前半部為參數(shù)功能,后半部為監(jiān)控到頁面性能問題時可能的原因

function getPerformanceTiming () {
  var performance = window.performance
  // 瀏覽器兼容性考慮
  if(!performance) {
    console.log('您的瀏覽器不支持 performance 接口')
    return
  }

  const t = performance.timing
  let times = {}
  
  // 頁面加載完成時間 - 用戶需等待頁面可用時間
  times.loadPage = t.loadEventEnd - t.navigationStart
  
  // 解析dom樹結(jié)構(gòu)時間 - DOM樹嵌套不宜太深
  times.domReady = t.domeComplete - t.responseEnd
  
  // 重定向時間 - 若拒絕重定向法瑟,檢查是否有類似‘http://example.com/’寫成‘http://example.com’錯誤
  times.redirect = t.redirectEnd - redirectStart
  
  // DNS解析時間 - 可增加DNS預(yù)加載冀膝。頁面涉及域名是否過多
  times.lookupDomian = t.domainLookupEnd - t.domainLookupStart
  
  // 首字節(jié)響應(yīng)時間 - 數(shù)據(jù)鏈路的響應(yīng)速度,受機房霎挟,CDN窝剖,帶寬,服務(wù)器性能等影響
  times.ttfb = t.responseStart - t.navigationStart
  
  // 資源加載完成時間 - Nginx上配置gzip壓縮減少下載資源
  times.request = t.responseEnd - t.reuqestStart
  
  // onload執(zhí)行效率 - 避免過多邏輯在onload中執(zhí)行酥夭,考慮資源懶加載赐纱,延遲獲取等
  times.loadEvent = t.loadEventEnd - t.loadEventStart
  
  // DNS緩存時間
  times.appcache = t.domianLookupStart - t.fetchStart
  
  // 卸載頁面時間
  times.unloadEvent = t.unloadEventEnd - t.unloadEventStart
  
  // TCP連接建立及完成握手時間
  times.connect = t.connectEnd - t.connectStart
  
  return times
}

?

# Performance.navigation 只讀

? .navigation返回一個performanceNavigation對象,提供了在指定的時間段里發(fā)生的操作和相關(guān)信息熬北,包括頁面是加載千所、刷新還是重定向。

const navigation = window.performance.navigation

? 該對象返回值信息如下

  • 頁面載入類型 - type
    • 0:同TYPE_NAVIGATE蒜埋;如點擊鏈接淫痰,url輸入,腳本執(zhí)行跳轉(zhuǎn)整份,或書簽和表單的提交等方式載入
    • 1:同TYPE_RELOAD待错;如點擊刷新頁面按鈕籽孙,或腳本Location.reload()載入
    • 2:同TYPE_BACK_FORWARD;通過歷史記錄的前進和后退進入
    • 255:同TYPE_RESERVED火俄;通過其他方式進入
  • 重定向次數(shù) - redirectCount
  • 序列化方法 - toJson()
    鏈式調(diào)用辦法犯建,將PerformanceNavigation轉(zhuǎn)化為JSON對象。
    ?

$ Performance 方法

? timing屬性主要針對文檔載入及之前的各個節(jié)點性能監(jiān)控瓜客,無法落實到其他業(yè)務(wù)邏輯執(zhí)行适瓦。想要監(jiān)控更多信息,就需要使用Performance接口提供的方法來實現(xiàn)谱仪。

# now() (單位ms)

? performance.now()方法返回了相對于 performance.timing.navigationStart(頁面初始化) 的時間玻熙,而Date.now()返回的是UNIX時間也就是距1970年的時間。且因為performance.now()的時間是以一定速率慢慢增加的疯攒,不受系統(tǒng)時間影響嗦随,也不受進程阻塞影響,比Date.now()時間來的更精準一些敬尺。

let t0 = window.performance.now();
todo()
let t1 = window.performance.now();
console.log("todo執(zhí)行時間:"枚尼, (t1 - t0) + "毫秒.")

# getEntries()

? 返回一個按startTime排序的數(shù)組,包含加載本頁面所有的資源請求相關(guān)時間數(shù)據(jù)的集合砂吞。為更好的理解看一個entry實例數(shù)據(jù)署恍,以訪問https://www.baidu.com/為例:

const entries = window.performance.getEntries()
console.log(entries)

以下為返回數(shù)組的第一項:

? 可以發(fā)現(xiàn),整個 Performance.timing 的數(shù)據(jù)節(jié)點均已包含蜻直。除此之外盯质,還包括了以下幾個信息:

  • name:資源名稱。是資源的絕對路徑或mark()方法自定義的名稱袭蝗。
  • startTime:開始時間
  • duration:加載時間
  • entryType:資源類型;詳情如下
  • initiatorType:請求發(fā)起者般婆;詳情如下

entryType的值

描述
mark 通過mark()添加到數(shù)組中的對象
measure 通過measure()添加到數(shù)組中的對象
resource 所有資源加載時間(重要)
navigation 導(dǎo)航相關(guān)信息到腥,僅chrome和Opera支持
frame -
server -

initiatorType的值

發(fā)起對象 描述
link/script/img/iframe 某個標簽元素 標簽形式加載
css 某個css樣式 通過css樣式加載,如backgroundurl()資源
xmlhttprequest 某個http請求 通過xhr加載的資源
navigation 某個performanceNavigation對象 當對象是PerformanceNavigationTiming時返回

? 因此蔚袍,我們獲取其性能時間數(shù)據(jù)可封裝函數(shù)如下

// 計算加載時間
function getEntryTiming (entry) {  
  var t = entry;
  var times = {};

  // 重定向的時間
  times.redirect = t.redirectEnd - t.redirectStart;

  // DNS 查詢時間
  times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;

  // 內(nèi)容加載完成的時間
  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;
}
 
// run it
var entries = window.performance.getEntries();
entries.forEach(function (entry) {
  var times = getEntryTiming(entry);
  console.log(times);
});

? 執(zhí)行該方法會發(fā)現(xiàn)乡范,一個全量的entries存在了過多的干擾信息,如果要從中挑出某些有用項進行比較只能通過數(shù)組過濾手段實現(xiàn)比較麻煩啤咽,好在performance接口提供了這個方法

# getEntriesByType()

? performance.getEntriesByType()方法返回給定類型的entries數(shù)組集合晋辆,其本質(zhì)就是在全量數(shù)據(jù)中按entryType屬性過濾,返回過濾后的數(shù)據(jù)宇整,效果等同于Array.filter()瓶佳。該方法常配合mark()方法使用,用來獲取用戶自己打的標簽數(shù)據(jù)鳞青。

entries = window.performance.getEntriesByType(type);

# getEntriesByName()

? 使用辦法同getEntriesByType()霸饲,接受一個參數(shù)为朋,用于指定entries名稱『衤觯可以用來統(tǒng)計某一個函數(shù)被執(zhí)行的次數(shù)及各個執(zhí)行時刻习寸,另一個更重要的是用來檢索measure測量的duration耗時。
?

# mark()

? 使用performance.mark()也可以精準的計算程序的執(zhí)行時間傻工。思路就是在某些關(guān)鍵位置插入一些標記霞溪,當程序運行到標記處時,Performance會入棧一個entry中捆。這樣鸯匹,通過在需要分析性能的邏輯段落前后插入不同的標記,來實現(xiàn)對該處性能的監(jiān)控轨香。

function markSample(name) {
  const markStart = name + '_markStart'
  const markEnd= name + '_markEnd'
  
  window.performance.mark(markStart)
  
  for(let i = 0; i < 100; i++) {
    for(let j = 0; j < 100; j++) {
      // TODO:
    }
  }
  
  window.performance.mark(markEnd)
  
  
}

// run it
markSample(‘first’)

const marks = window.performance.getEntriesByType('mark')
console.log(marks)

執(zhí)行結(jié)果會包含四個關(guān)鍵屬性忽你,如下:

# measure()

? performance.measure()用于測量兩個標記之間執(zhí)行的時間,并把它賦值給第一個參數(shù)(measure名稱)上臂容。如在上例的markSample函數(shù)底部插入一下代碼

window.performance.measure('measure_test', markStart, markEnd)
var measureTest= window.performance.getEntriesByName('measure_test');  
console.log(measureTest); 

?
? 值得關(guān)注的是科雳,由于標記在插入后,每次程序執(zhí)行到此處將入棧一個entry脓杉,而該數(shù)據(jù)是記錄在全局的window下的糟秘,因此當標記過多或被執(zhí)行次數(shù)太多時,可能出現(xiàn)內(nèi)存污染等問題球散,因此尿赚,這就要求在標記使用結(jié)束后及時清除他們。

# clearMarks()

? performance.clearMarks()接受 0/1 個參數(shù)蕉堰,表示將要清除的標記名稱

// 指定清除某個標記
window.performance.clearMarks('first_markStart')
// 清除所有標記
window.performance.clearMarks()

# clearMeasures()

? 測量完成后也應(yīng)當及時清除凌净,用法:

// 清除指定測量
window.performance.clearMeasures('first_measure');  
// 清除所有測量
window.performance.clearMeasures();

?

$ 使用mark測量timing事件

? 可能有個錯誤的理解就是performance.measure()只能測量performance.mark()的標記,其實不然屋讶,比如冰寻,在timing中,我們是這么測量domReady事件的:

cosnt t = performance.timing  
const domReady = t.domComplete - t.responseEnd;  
console.log(domReady )  

也可以使用measure()來實現(xiàn)如下:

window.performance.measure('domReady','responseEnd' , 'domComplete');  
var domReadyMeasure = window.performance.getEntriesByName('domReady');  
console.log(domReadyMeasure);  

?

$ refs

參考文獻
performance - MDN
HTML5 performance API 草案.
初探performance - AlloyTeam

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末皿渗,一起剝皮案震驚了整個濱河市斩芭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乐疆,老刑警劉巖划乖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異挤土,居然都是意外死亡琴庵,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來细卧,“玉大人尉桩,你說我怎么就攤上這事√懊恚” “怎么了蜘犁?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長止邮。 經(jīng)常有香客問我这橙,道長,這世上最難降的妖魔是什么导披? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任屈扎,我火速辦了婚禮,結(jié)果婚禮上撩匕,老公的妹妹穿的比我還像新娘鹰晨。我一直安慰自己,他們只是感情好止毕,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布模蜡。 她就那樣靜靜地躺著,像睡著了一般扁凛。 火紅的嫁衣襯著肌膚如雪忍疾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天谨朝,我揣著相機與錄音卤妒,去河邊找鬼。 笑死字币,一個胖子當著我的面吹牛则披,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洗出,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼士复,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了共苛?” 一聲冷哼從身側(cè)響起判没,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蜓萄,失蹤者是張志新(化名)和其女友劉穎隅茎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫉沽,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡辟犀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绸硕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堂竟。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡魂毁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出出嘹,到底是詐尸還是另有隱情席楚,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布税稼,位于F島的核電站烦秩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏郎仆。R本人自食惡果不足惜只祠,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扰肌。 院中可真熱鬧抛寝,春花似錦、人聲如沸曙旭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽夷狰。三九已至岭皂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沼头,已是汗流浹背爷绘。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留进倍,地道東北人土至。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像猾昆,于是被迫代替她去往敵國和親陶因。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容