前端性能監(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í)長混驰、響應(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)映皆。

可以簡單的初步建立這樣的數(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 = {};
});

簡單的設(shè)想方案是先把數(shù)據(jù)收集到文本文件里轰枝,然后定時(shí)的分析這些文本文件捅彻,然后把篩選后的放到數(shù)據(jù)庫中。

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

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

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

  • 首屏加載時(shí)長

  • 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長'+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í)長
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

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

可以看到在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-20200518190547647.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ò)誤信息燃乍。![image-
image-20200518201331509.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宛琅,隨后出現(xiàn)的幾起案子橘沥,更是在濱河造成了極大的恐慌,老刑警劉巖夯秃,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件座咆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡仓洼,警方通過查閱死者的電腦和手機(jī)介陶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來色建,“玉大人哺呜,你說我怎么就攤上這事』粒” “怎么了某残?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長陵吸。 經(jīng)常有香客問我玻墅,道長,這世上最難降的妖魔是什么壮虫? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任澳厢,我火速辦了婚禮环础,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘剩拢。我一直安慰自己线得,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布徐伐。 她就那樣靜靜地躺著贯钩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪办素。 梳的紋絲不亂的頭發(fā)上角雷,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音摸屠,去河邊找鬼谓罗。 笑死粱哼,一個(gè)胖子當(dāng)著我的面吹牛季二,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揭措,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼胯舷,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了绊含?” 一聲冷哼從身側(cè)響起桑嘶,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎躬充,沒想到半個(gè)月后逃顶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡充甚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年以政,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伴找。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盈蛮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出技矮,到底是詐尸還是另有隱情抖誉,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布衰倦,位于F島的核電站袒炉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏樊零。R本人自食惡果不足惜梳杏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧十性,春花似錦叛溢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至霞势,卻和暖如春烹植,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背愕贡。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來泰國打工草雕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人固以。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓墩虹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親憨琳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诫钓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

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