前端性能監(jiān)控初步實(shí)戰(zhàn)

前言

在當(dāng)下前后端分離的主流環(huán)境下闸盔,前端部分的優(yōu)化變得越來越重要扳还。為了提升前端的性能和用戶體驗(yàn)哄陶,我覺得可能需要從三個(gè)維度采集數(shù)據(jù)進(jìn)行分析帆阳。

  1. 前端埋點(diǎn)。通過埋點(diǎn)收集和統(tǒng)計(jì)網(wǎng)頁的UV/PV屋吨、設(shè)備型號(hào)蜒谤、瀏覽器等數(shù)據(jù)進(jìn)行分析山宾,比如可以有針對(duì)性對(duì)使用比較靠前的設(shè)備、瀏覽器等做優(yōu)化和體驗(yàn)芭逝。
  2. 網(wǎng)頁性能收集和監(jiān)控塌碌。 采集一個(gè)頁面從請(qǐng)求開始到完成這個(gè)過程中的數(shù)據(jù)指標(biāo)。比如收集和監(jiān)控首屏加載時(shí)間旬盯、dom渲染時(shí)長(zhǎng)台妆、響應(yīng)比較慢的接口等。有了這些數(shù)據(jù)可以很直觀和針對(duì)性的對(duì)網(wǎng)頁的性能進(jìn)行優(yōu)化和升級(jí)胖翰。
  3. 錯(cuò)誤收集和監(jiān)控接剩。收集網(wǎng)頁中的js的報(bào)錯(cuò)、靜態(tài)資源加載報(bào)錯(cuò)萨咳,保證網(wǎng)頁正常訪問和降低bug率懊缺。

1.前端埋點(diǎn)

采集用戶的行為,監(jiān)控產(chǎn)品在用戶端的使用情況培他,根據(jù)數(shù)據(jù)可以明確鹃两,前端可以針對(duì)性的做優(yōu)化和體驗(yàn)。

可以簡(jiǎn)單的初步建立這樣的數(shù)據(jù)模型:

{
 ip: ip地址舀凛,統(tǒng)計(jì)uv
 ua: 瀏覽器的userAgent //方便區(qū)分瀏覽器的品牌
 os: 系統(tǒng)名稱
 current_page_all: 當(dāng)前頁面訪問總次數(shù)
 width:瀏覽器寬度,   
 height:瀏覽器高度, //統(tǒng)計(jì)瀏覽器的尺寸
 current_enter_time: 進(jìn)入當(dāng)前頁面的時(shí)間戳
 current_leave_time: 離開當(dāng)前頁面的時(shí)間戳
 project_id: 項(xiàng)目的id,
 url: 當(dāng)前url,
 user_uni_id: 臨時(shí)分配給用戶唯一的id
 ....
 還有其他
}

2.在網(wǎng)頁中植入對(duì)應(yīng)的js代碼

//進(jìn)入
document.addEventListener('DOMContentLoaded',function(){
   ...
   let args = {
     ua: navigator.userAgent,
     os: navigator.platform,
     width: document.body.clientWidth || document.documentElement.clientWidth,
     height: document.body.clientHeight || document.documentElement.clientHeight,
     project_id: md5('abcd'),
     user_uni_id: '臨時(shí)分配給用戶唯一的id',
     url: window.location.href,
     current_enter_time: new Date().getTime()
     ....
   };
   let img = new Image();
   img.onload = function() {
     img = null;
   };
   img.src= `https://localhost:9700/bury.gif?args=${qs(args)}`;
 });

//離開
window.onbeforeunload = function() {
   ...
   let args = {
     ...
     leave_time: new Date().getTime()
     ....
   };
   let img = new Image();
   img.onload = function() {
     img = null;
   };
   img.src= `https://localhost:9700/bury.gif?args=${qs(args)}`;
}
  1. 收集數(shù)據(jù)并上報(bào)
const fs = require('fs');
route.get('/bury.gif',async (ctx)=>{
  let queryStr = ctx.querystring;
  let d = new Date();
  let year = d.getFullYear();
  let month = d.getMonth()+1;
  let day = d.getDate()+1;
  fs.writeFile(`../logs/${year}-${month}-${day}.log`,queryStr,{flag:'a',encoding:'utf-8',mode:'0666'},function(e){});
   ctx.status = 200;
   ctx.type = 'image/gif';
   ctx.body = {};
});

簡(jiǎn)單的設(shè)想方案是先把數(shù)據(jù)收集到文本文件里俊扳,然后定時(shí)的分析這些文本文件,然后把篩選后的放到數(shù)據(jù)庫中猛遍。

2.網(wǎng)頁性能收集

網(wǎng)頁性能主要是收集是輸入url地址到網(wǎng)頁請(qǐng)求完成資源下載完成這段時(shí)間范圍內(nèi)一些請(qǐng)求馋记、加載等指標(biāo)。

  • 比如舉幾個(gè)簡(jiǎn)單的指標(biāo):

  • 首屏加載時(shí)長(zhǎng)

  • HTML 文檔被加載和解析完成

  • 網(wǎng)頁加載完成時(shí)間

  • 白屏?xí)r間

  • 其他…

  1. 具體實(shí)現(xiàn)方案(一):
//index.html
<html>
 <head>
   <script>
     window.startTime = Date.now();
   </script>
 </head>
 <body>
   <script>
     let diff = Date.now()-window.startTime;
     console.log('白屏?xí)r長(zhǎng)'+diff);
     document.addEventListener('DOMContentLoaded',()=>{
       console.log('HTML 文檔被加載和解析完成時(shí)間'+Date.now()-window.startTime); 
     });
     window.onload = function() {
       console.log('網(wǎng)頁加載完成時(shí)間'+Date.now()-window.startTime); 
     };
   </script>
   <script src="a.js"></script>
   <script src="b.js"></script>
   ....
 </body>
</html>
  1. 使用performance API

Performance是W3C性能小組引入進(jìn)來的一個(gè)新的API懊烤,他可以很好的獲取到首屏加載時(shí)間梯醒、白屏?xí)r間、dns查詢時(shí)間等腌紧,是一個(gè)很方便的獲取網(wǎng)頁性能指標(biāo)的API,而且目前大部分主流瀏覽器是支持的茸习。

https://www.caniuse.com/

1.Performance一些常用用法的總結(jié)

let timing = window.performance.timing
//白屏?xí)r間
timing.responseStart - timing.navigationStart
//DNS 查詢時(shí)長(zhǎng)
timing.domainLookupEnd - timing.domainLookupStart
//request請(qǐng)求耗時(shí)
timing.responseEnd - timing.responseStart
//HTML 文檔被加載和解析完成耗時(shí)
timing.domComplete - timing.domInteractive
//網(wǎng)頁加載完成耗時(shí)
timing.loadEventEnd - timing.navigationStart
//重定向耗時(shí)
timing.redirectEnd - timing.redirectStart;
//占用的內(nèi)存
window.performance.memory.usedJSHeapSize;
  1. 此外還有一些高級(jí)用法壁肋,比如可以收集一些請(qǐng)求和靜態(tài)資源的請(qǐng)求時(shí)間
let  time = [];
let entryLists = window.performance.getEntries();
for(let i=0;i<entryLists.length;i++) {
  let item = entryLists[i];
  let obj = {};
     let soureTypes = ['script','css','xmlhttprequest','link','img'];
     if(soureTypes.indexOf(item.initiatorType)>=0){
       obj.name = item.name;  
       //請(qǐng)求時(shí)間
       obj.reqTime = item.responseEnd - item.responseStart;
       time.push(obj);
     }
}

3.關(guān)于Performance的更多用法可以參考:

http://www.reibang.com/p/1355232d525a
https://blog.csdn.net/hb_zhouyj/article/details/89888646
  1. 收集數(shù)據(jù)上報(bào)

收集上報(bào)數(shù)據(jù)逮光,使用koa2創(chuàng)建接口performance.gif

const fs = require('fs');
route.get('/performance.gif',async (ctx)=>{
  let queryStr = ctx.querystring;
  let d = new Date();
  let year = d.getFullYear();
  let month = d.getMonth()+1;
  let day = d.getDate()+1;
  fs.writeFile(`../performance-logs/${year}-${month}-${day}.log`,queryStr,{flag:'a',encoding:'utf-8',mode:'0666'},function(e){});
   ctx.status = 200;
   ctx.type = 'image/gif';
   ctx.body = {};
});

3.錯(cuò)誤收集和監(jiān)控

錯(cuò)誤收集主要就是針對(duì)js報(bào)錯(cuò)、靜態(tài)資源加載等出錯(cuò)信息進(jìn)行收集墩划。

  1. 收集js報(bào)錯(cuò)最先想到的可能是try catch。
try {
    console.log(b);
    } catch(e) {
      console.log(e);
      sendErrorReq();
    };

但是使用使用的話嗡综,每個(gè)頁面收集錯(cuò)誤都要充斥這try catch乙帮,這樣其實(shí)是不太好的。

而且catch似乎沒辦法捕獲到異步的操作极景。

try {
      setTimeout(()=>{
       console.log(b); 
      })
    } catch(e) {
      console.log(e);
      sendErrorReq();
    };

試了一下沒有執(zhí)行到catch里邊的sendErrorReq函數(shù)察净。

使用try catch是一種局部錯(cuò)誤監(jiān)聽方式驾茴。

  1. 用window.onerror或者是window.addEventListener(‘error’)

window.onerror

/**
      *msg 錯(cuò)誤信息
      *url 錯(cuò)誤所在的頁面地址
      *row 錯(cuò)誤所在的行數(shù)
      *col 錯(cuò)誤所在的列數(shù)
      **/
     window.onerror = function(msg,url,row,col) {
       console.log(msg,url,row,col, error);
     };
    b();

使用window.onerror可以檢測(cè)到j(luò)s的報(bào)錯(cuò),但是沒法監(jiān)聽靜態(tài)資源加載失敗的情況氢卡。

<img src="一個(gè)不存在的圖片地址" alt="">

window.addEventListener(‘error’)

window.addEventListener(‘error’)能夠監(jiān)聽到靜態(tài)資源加載出錯(cuò)

window.addEventListener('error',(e)=>{
      let localName = e.srcElement.localName;
      let currentSrc = e.srcElement.currentSrc;
      if(localName=='img') {
        console.log(`圖片${currentSrc}加載失敗了`);
        sendErrorData(currentSrc);
      }
      ...
      // e.preventDefault();
    },true);

必須設(shè)為捕獲過程中執(zhí)行锈至,否則依然無法監(jiān)聽。


image-1.png

3. promise錯(cuò)誤的收集

new Promise((resolve,reject)=>{
       reject('hi');
    }).catch(e=>{
      console.log(e);
      sendErrorData(e)
    });
axios.get(...).catch(e=>{
  console.log(e);
   sendErrorData(e)
})

但是有種情況译秦,如果promise不加catch的話峡捡,
沒法通過window.onerror去監(jiān)聽,但是還是通過監(jiān)聽unhandledrejection事件去收集的

4.unhandledrejection

window.addEventListener('unhandledrejection', event => {
       console.log('error:'+event.reason); 
        sendErrorData(event.reason);
    });   
    new Promise((resolve,reject)=>{
       reject('hi');
    });

在看下在vue中收集報(bào)錯(cuò)筑悴,以vue-cli3創(chuàng)建的項(xiàng)目進(jìn)行演示

測(cè)試了一下window.onerror這種方式 無法監(jiān)聽錯(cuò)誤的们拙。

在網(wǎng)上找了下原因


image-2.png

可以看到在vue的源碼里,因?yàn)槿绻麤]有定義errorHandler就會(huì)走到logError這個(gè)方法阁吝,所以沒法使用window.onerror進(jìn)行監(jiān)聽砚婆。

5.errorHandler

在vue的手冊(cè)中,推薦監(jiān)聽vue報(bào)錯(cuò)的可以使用errorHandler這個(gè)配置方法突勇。

//main.js
Vue.config.errorHandler = function (err, vm, info) {
  console.log('錯(cuò)誤是:', err)
  sendErrorData(err);
}
然后隨便故意寫錯(cuò)
mounted() {
  a();
}

就能收集到報(bào)錯(cuò)信息了


image-3.png

當(dāng)然最后把收集的錯(cuò)誤上報(bào)給服務(wù)器装盯,創(chuàng)建一個(gè)接口error.gif。

const fs = require('fs');
route.get('/error.gif',async (ctx)=>{
  let queryStr = ctx.querystring;
  ....
  fs.writeFile(`../error-logs/${year}-${month}-${day}.log`,queryStr,{flag:'a',encoding:'utf-8',mode:'0666'},function(e){});
   ctx.status = 200;
   ctx.type = 'image/gif';
   ...
});
  • 遺留問題:如何從souremap中收集js的錯(cuò)誤信息甲馋。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末埂奈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子摔刁,更是在濱河造成了極大的恐慌挥转,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件共屈,死亡現(xiàn)場(chǎng)離奇詭異绑谣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拗引,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門借宵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人矾削,你說我怎么就攤上這事壤玫。” “怎么了哼凯?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵欲间,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我断部,道長(zhǎng)猎贴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮她渴,結(jié)果婚禮上达址,老公的妹妹穿的比我還像新娘。我一直安慰自己趁耗,他們只是感情好沉唠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著苛败,像睡著了一般满葛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上著拭,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天纱扭,我揣著相機(jī)與錄音,去河邊找鬼儡遮。 笑死乳蛾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鄙币。 我是一名探鬼主播肃叶,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼十嘿!你這毒婦竟也來了奋岁?” 一聲冷哼從身側(cè)響起聂沙,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后诞帐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嗅辣,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡也祠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年茫船,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片招盲。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡低缩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出曹货,到底是詐尸還是另有隱情咆繁,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布顶籽,位于F島的核電站玩般,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏礼饱。R本人自食惡果不足惜壤短,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一设拟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧久脯,春花似錦、人聲如沸镰吆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽万皿。三九已至摧找,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間牢硅,已是汗流浹背蹬耘。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留减余,地道東北人综苔。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像位岔,于是被迫代替她去往敵國(guó)和親如筛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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