<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          迄今為止最全的前端監(jiān)控體系搭建篇(長文預警)

          共 22682字,需瀏覽 46分鐘

           ·

          2022-04-20 21:20

          點擊上方?前端Q,關(guān)注公眾號

          回復加群,加入前端Q技術(shù)交流群


          原文鏈接: https://juejin.cn/post/7078512301665419295?share_token=37a59eca-da19-4cfc-aeb2-67d4c0d9b0c2
          作者: 火車頭

          概覽

          • 為什么要做前端監(jiān)控

          • 前端監(jiān)控目標

          • 前端監(jiān)控流程

          • 編寫采集腳本

            • 日志系統(tǒng)監(jiān)控

            • 錯誤監(jiān)控

            • 接口異常

            • 白屏監(jiān)控

            • 加載時間

            • 性能指標

            • 卡頓

            • pv

          • 擴展問題

            1. 性能監(jiān)控指標

            2. 前端怎么做性能監(jiān)控

            3. 線上錯誤監(jiān)控怎么做

            4. 導致內(nèi)存泄漏的方法,怎么監(jiān)控內(nèi)存泄漏

            5. Node 怎么做性能監(jiān)控

          源碼地址 ? ? ?(https://github.com/miracle90/monitor)

          1. 為什么要做前端監(jiān)控

          • 更快的發(fā)現(xiàn)問題和解決問題

          • 做產(chǎn)品的決策依據(jù)

          • 為業(yè)務(wù)擴展提供了更多可能性

          • 提升前端工程師的技術(shù)深度和廣度,打造簡歷亮點

          2. 前端監(jiān)控目標

          2.1 穩(wěn)定性 stability

          • js錯誤:js執(zhí)行錯誤、promise異常

          • 資源錯誤:js、css資源加載異常

          • 接口錯誤:ajax、fetch請求接口異常

          • 白屏:頁面空白

          2.2 用戶體驗 experience

          2.3 業(yè)務(wù) business

          • pv:頁面瀏覽量和點擊量

          • uv:訪問某個站點的不同ip的人數(shù)

          • 用戶在每一個頁面的停留時間

          3. 前端監(jiān)控流程

          1. 前端埋點

          2. 數(shù)據(jù)上報

          3. 加工匯總

          4. 可視化展示

          5. 監(jiān)控報警

          3.1 常見的埋點方案

          3.1.1 代碼埋點

          • 嵌入代碼的形式

          • 優(yōu)點:精確(任意時刻,數(shù)據(jù)量全面)

          • 缺點:代碼工作量點

          3.1.2 可視化埋點

          • 通過可視化交互的手段,代替代碼埋點

          • 將業(yè)務(wù)代碼和埋點代碼分離,提供一個可視化交互的頁面,輸入為業(yè)務(wù)代碼,通過這個系統(tǒng),可以在業(yè)務(wù)代碼中自定義的增加埋點事件等等,最后輸出的代碼耦合了業(yè)務(wù)代碼和埋點代碼

          • 用系統(tǒng)來代替手工插入埋點代碼

          3.1.3 無痕埋點

          • 前端的任意一個事件被綁定一個標識,所有的事件都被記錄下來

          • 通過定期上傳記錄文件,配合文件解析,解析出來我們想要的數(shù)據(jù),并生成可視化報告供專業(yè)人員分析

          • 無痕埋點的優(yōu)點是采集全量數(shù)據(jù),不會出現(xiàn)漏埋和誤埋等現(xiàn)象

          • 缺點是給數(shù)據(jù)傳輸和服務(wù)器增加壓力,也無法靈活定制數(shù)據(jù)結(jié)構(gòu)

          4. 編寫采集腳本

          4.1 接入日志系統(tǒng)

          • 各公司一般都有自己的日志系統(tǒng),接收數(shù)據(jù)上報,例如:阿里云

          4.2 監(jiān)控錯誤

          4.2.1 錯誤分類

          • js錯誤(js執(zhí)行錯誤,promise異常)

          • 資源加載異常:監(jiān)聽error

          4.2.2 數(shù)據(jù)結(jié)構(gòu)分析

          1. jsError
          {
          "title": "前端監(jiān)控系統(tǒng)", // 頁面標題
          "url": "http://localhost:8080/", // 頁面URL
          "timestamp": "1590815288710", // 訪問時間戳
          "userAgent": "Chrome", // 用戶瀏覽器類型
          "kind": "stability", // 大類
          "type": "error", // 小類
          "errorType": "jsError", // 錯誤類型
          "message": "Uncaught TypeError: Cannot set property 'error' of undefined", // 類型詳情
          "filename": "http://localhost:8080/", // 訪問的文件名
          "position": "0:0", // 行列信息
          "stack": "btnClick (http://localhost:8080/:20:39)^HTMLInputElement.onclick (http://localhost:8080/:14:72)", // 堆棧信息
          "selector": "HTML BODY #container .content INPUT" // 選擇器
          }
          2. promiseError
          {
          ...
          "errorType": "promiseError",//錯誤類型
          "message": "someVar is not defined",//類型詳情
          "stack": "http://localhost:8080/:24:29^new Promise ()^btnPromiseClick (http://localhost:8080/:23:13)^HTMLInputElement.onclick (http://localhost:8080/:15:86)",//堆棧信息
          "selector": "HTML BODY #container .content INPUT"//選擇器
          }
          3. resourceError
              ...
          "errorType": "resourceError",//錯誤類型
          "filename": "http://localhost:8080/error.js",//訪問的文件名
          "tagName": "SCRIPT",//標簽名
          "timeStamp": "76",//時間

          4.2.3 實現(xiàn)

          1. 資源加載錯誤 + js執(zhí)行錯誤
          //一般JS運行時錯誤使用window.onerror捕獲處理
          window.addEventListener(
          "error",
          function (event) {
          let lastEvent = getLastEvent();
          // 有 e.target.src(href) 的認定為資源加載錯誤
          if (event.target && (event.target.src || event.target.href)) {
          tracker.send({
          //資源加載錯誤
          kind: "stability", //穩(wěn)定性指標
          type: "error", //resource
          errorType: "resourceError",
          filename: event.target.src || event.target.href, //加載失敗的資源
          tagName: event.target.tagName, //標簽名
          timeStamp: formatTime(event.timeStamp), //時間
          selector: getSelector(event.path || event.target), //選擇器
          });
          } else {
          tracker.send({
          kind: "stability", //穩(wěn)定性指標
          type: "error", //error
          errorType: "jsError", //jsError
          message: event.message, //報錯信息
          filename: event.filename, //報錯鏈接
          position: (event.lineNo || 0) + ":" + (event.columnNo || 0), //行列號
          stack: getLines(event.error.stack), //錯誤堆棧
          selector: lastEvent
          ? getSelector(lastEvent.path || lastEvent.target)
          : "", //CSS選擇器
          });
          }
          },
          true
          ); // true代表在捕獲階段調(diào)用,false代表在冒泡階段捕獲,使用true或false都可以
          2. promise異常
          //當Promise 被 reject 且沒有 reject 處理器的時候,會觸發(fā) unhandledrejection 事件
          window.addEventListener(
          "unhandledrejection",
          function (event) {
          let lastEvent = getLastEvent();
          let message = "";
          let line = 0;
          let column = 0;
          let file = "";
          let stack = "";
          if (typeof event.reason === "string") {
          message = event.reason;
          } else if (typeof event.reason === "object") {
          message = event.reason.message;
          }
          let reason = event.reason;
          if (typeof reason === "object") {
          if (reason.stack) {
          var matchResult = reason.stack.match(/at\s+(.+):(\d+):(\d+)/);
          if (matchResult) {
          file = matchResult[1];
          line = matchResult[2];
          column = matchResult[3];
          }
          stack = getLines(reason.stack);
          }
          }
          tracker.send({
          //未捕獲的promise錯誤
          kind: "stability", //穩(wěn)定性指標
          type: "error", //jsError
          errorType: "promiseError", //unhandledrejection
          message: message, //標簽名
          filename: file,
          position: line + ":" + column, //行列
          stack,
          selector: lastEvent
          ? getSelector(lastEvent.path || lastEvent.target)
          : "",
          });
          },
          true
          ); // true代表在捕獲階段調(diào)用,false代表在冒泡階段捕獲,使用true或false都可以

          4.3 接口異常采集腳本

          4.3.1 數(shù)據(jù)設(shè)計

          {
          "title": "前端監(jiān)控系統(tǒng)", //標題
          "url": "http://localhost:8080/", //url
          "timestamp": "1590817024490", //timestamp
          "userAgent": "Chrome", //瀏覽器版本
          "kind": "stability", //大類
          "type": "xhr", //小類
          "eventType": "load", //事件類型
          "pathname": "/success", //路徑
          "status": "200-OK", //狀態(tài)碼
          "duration": "7", //持續(xù)時間
          "response": "{\"id\":1}", //響應內(nèi)容
          "params": "" //參數(shù)
          }
          {
          "title": "前端監(jiān)控系統(tǒng)",
          "url": "http://localhost:8080/",
          "timestamp": "1590817025617",
          "userAgent": "Chrome",
          "kind": "stability",
          "type": "xhr",
          "eventType": "load",
          "pathname": "/error",
          "status": "500-Internal Server Error",
          "duration": "7",
          "response": "",
          "params": "name=zhufeng"
          }

          4.3.2 實現(xiàn)

          使用webpack devServer模擬請求

          • 重寫xhr的open、send方法

          • 監(jiān)聽load、error、abort事件

          import tracker from "../util/tracker";
          export function injectXHR() {
          let XMLHttpRequest = window.XMLHttpRequest;
          let oldOpen = XMLHttpRequest.prototype.open;
          XMLHttpRequest.prototype.open = function (
          method,
          url,
          async,
          username,
          password
          )
          {
          // 上報的接口不用處理
          if (!url.match(/logstores/) && !url.match(/sockjs/)) {
          this.logData = {
          method,
          url,
          async,
          username,
          password,
          };
          }
          return oldOpen.apply(this, arguments);
          };
          let oldSend = XMLHttpRequest.prototype.send;
          let start;
          XMLHttpRequest.prototype.send = function (body) {
          if (this.logData) {
          start = Date.now();
          let handler = (type) => (event) => {
          let duration = Date.now() - start;
          let status = this.status;
          let statusText = this.statusText;
          tracker.send({
          //未捕獲的promise錯誤
          kind: "stability", //穩(wěn)定性指標
          type: "xhr", //xhr
          eventType: type, //load error abort
          pathname: this.logData.url, //接口的url地址
          status: status + "-" + statusText,
          duration: "" + duration, //接口耗時
          response: this.response ? JSON.stringify(this.response) : "",
          params: body || "",
          });
          };
          this.addEventListener("load", handler("load"), false);
          this.addEventListener("error", handler("error"), false);
          this.addEventListener("abort", handler("abort"), false);
          }
          oldSend.apply(this, arguments);
          };
          }

          4.4 白屏

          • 白屏就是頁面上什么都沒有

          4.4.1 數(shù)據(jù)設(shè)計

          {
          "title": "前端監(jiān)控系統(tǒng)",
          "url": "http://localhost:8080/",
          "timestamp": "1590822618759",
          "userAgent": "chrome",
          "kind": "stability", //大類
          "type": "blank", //小類
          "emptyPoints": "0", //空白點
          "screen": "2049x1152", //分辨率
          "viewPoint": "2048x994", //視口
          "selector": "HTML BODY #container" //選擇器
          }

          4.4.2 實現(xiàn)

          • elementsFromPoint方法可以獲取到當前視口內(nèi)指定坐標處,由里到外排列的所有元素

          • 根據(jù) elementsFromPoint api,獲取屏幕水平中線和豎直中線所在的元素

          import tracker from "../util/tracker";
          import onload from "../util/onload";
          function getSelector(element) {
          var selector;
          if (element.id) {
          selector = `#${element.id}`;
          } else if (element.className && typeof element.className === "string") {
          selector =
          "." +
          element.className
          .split(" ")
          .filter(function (item) {
          return !!item;
          })
          .join(".");
          } else {
          selector = element.nodeName.toLowerCase();
          }
          return selector;
          }
          export function blankScreen() {
          const wrapperSelectors = ["body", "html", "#container", ".content"];
          let emptyPoints = 0;
          function isWrapper(element) {
          let selector = getSelector(element);
          if (wrapperSelectors.indexOf(selector) >= 0) {
          emptyPoints++;
          }
          }
          onload(function () {
          let xElements, yElements;
          debugger;
          for (let i = 1; i <= 9; i++) {
          xElements = document.elementsFromPoint(
          (window.innerWidth * i) / 10,
          window.innerHeight / 2
          );
          yElements = document.elementsFromPoint(
          window.innerWidth / 2,
          (window.innerHeight * i) / 10
          );
          isWrapper(xElements[0]);
          isWrapper(yElements[0]);
          }
          if (emptyPoints >= 0) {
          let centerElements = document.elementsFromPoint(
          window.innerWidth / 2,
          window.innerHeight / 2
          );
          tracker.send({
          kind: "stability",
          type: "blank",
          emptyPoints: "" + emptyPoints,
          screen: window.screen.width + "x" + window.screen.height,
          viewPoint: window.innerWidth + "x" + window.innerHeight,
          selector: getSelector(centerElements[0]),
          });
          }
          });
          }
          //screen.width 屏幕的寬度 screen.height 屏幕的高度
          //window.innerWidth 去除工具條與滾動條的窗口寬度 window.innerHeight 去除工具條與滾動條的窗口高度

          4.5 加載時間

          • PerformanceTiming

          • DOMContentLoaded

          • FMP

          4.5.1 階段含義


          字段含義
          navigationStart初始化頁面,在同一個瀏覽器上下文中前一個頁面unload的時間戳,如果沒有前一個頁面的unload,則與fetchStart值相等
          redirectStart第一個HTTP重定向發(fā)生的時間,有跳轉(zhuǎn)且是同域的重定向,否則為0
          redirectEnd最后一個重定向完成時的時間,否則為0
          fetchStart瀏覽器準備好使用http請求獲取文檔的時間,這發(fā)生在檢查緩存之前
          domainLookupStartDNS域名開始查詢的時間,如果有本地的緩存或keep-alive則時間為0
          domainLookupEndDNS域名結(jié)束查詢的時間
          connectStartTCP開始建立連接的時間,如果是持久連接,則與fetchStart值相等
          secureConnectionStarthttps 連接開始的時間,如果不是安全連接則為0
          connectEndTCP完成握手的時間,如果是持久連接則與fetchStart值相等
          requestStartHTTP請求讀取真實文檔開始的時間,包括從本地緩存讀取
          requestEndHTTP請求讀取真實文檔結(jié)束的時間,包括從本地緩存讀取
          responseStart返回瀏覽器從服務(wù)器收到(或從本地緩存讀?。┑谝粋€字節(jié)時的Unix毫秒時間戳
          responseEnd返回瀏覽器從服務(wù)器收到(或從本地緩存讀取,或從本地資源讀取)最后一個字節(jié)時的Unix毫秒時間戳
          unloadEventStart前一個頁面的unload的時間戳 如果沒有則為0
          unloadEventEndunloadEventStart相對應,返回的是unload函數(shù)執(zhí)行完成的時間戳
          domLoading返回當前網(wǎng)頁DOM結(jié)構(gòu)開始解析時的時間戳,此時document.readyState變成loading,并將拋出readyStateChange事件
          domInteractive返回當前網(wǎng)頁DOM結(jié)構(gòu)結(jié)束解析、開始加載內(nèi)嵌資源時時間戳,document.readyState?變成interactive,并將拋出readyStateChange事件(注意只是DOM樹解析完成,這時候并沒有開始加載網(wǎng)頁內(nèi)的資源)
          domContentLoadedEventStart網(wǎng)頁domContentLoaded事件發(fā)生的時間
          domContentLoadedEventEnd網(wǎng)頁domContentLoaded事件腳本執(zhí)行完畢的時間,domReady的時間
          domCompleteDOM樹解析完成,且資源也準備就緒的時間,document.readyState變成complete.并將拋出readystatechange事件
          loadEventStartload 事件發(fā)送給文檔,也即load回調(diào)函數(shù)開始執(zhí)行的時間
          loadEventEndload回調(diào)函數(shù)執(zhí)行完成的時間


          4.5.2 階段計算

          字段描述計算方式意義
          unload前一個頁面卸載耗時unloadEventEnd – unloadEventStart-
          redirect重定向耗時redirectEnd – redirectStart重定向的時間
          appCache緩存耗時domainLookupStart – fetchStart讀取緩存的時間
          dnsDNS 解析耗時domainLookupEnd – domainLookupStart可觀察域名解析服務(wù)是否正常
          tcpTCP 連接耗時connectEnd – connectStart建立連接的耗時
          sslSSL 安全連接耗時connectEnd – secureConnectionStart反映數(shù)據(jù)安全連接建立耗時
          ttfbTime to First Byte(TTFB)網(wǎng)絡(luò)請求耗時responseStart – requestStartTTFB是發(fā)出頁面請求到接收到應答數(shù)據(jù)第一個字節(jié)所花費的毫秒數(shù)
          response響應數(shù)據(jù)傳輸耗時responseEnd – responseStart觀察網(wǎng)絡(luò)是否正常
          domDOM解析耗時domInteractive – responseEnd觀察DOM結(jié)構(gòu)是否合理,是否有JS阻塞頁面解析
          dclDOMContentLoaded 事件耗時domContentLoadedEventEnd – domContentLoadedEventStart當 HTML 文檔被完全加載和解析完成之后,DOMContentLoaded 事件被觸發(fā),無需等待樣式表、圖像和子框架的完成加載
          resources資源加載耗時domComplete – domContentLoadedEventEnd可觀察文檔流是否過大
          domReadyDOM階段渲染耗時domContentLoadedEventEnd – fetchStartDOM樹和頁面資源加載完成時間,會觸發(fā)domContentLoaded事件
          首次渲染耗時首次渲染耗時responseEnd-fetchStart加載文檔到看到第一幀非空圖像的時間,也叫白屏時間
          首次可交互時間首次可交互時間domInteractive-fetchStartDOM樹解析完成時間,此時document.readyState為interactive
          首包時間耗時首包時間responseStart-domainLookupStartDNS解析到響應返回給瀏覽器第一個字節(jié)的時間
          頁面完全加載時間頁面完全加載時間loadEventStart - fetchStart-
          onLoadonLoad事件耗時loadEventEnd – loadEventStart


          4.5.3 數(shù)據(jù)結(jié)構(gòu)

          {
          "title": "前端監(jiān)控系統(tǒng)",
          "url": "http://localhost:8080/",
          "timestamp": "1590828364183",
          "userAgent": "chrome",
          "kind": "experience",
          "type": "timing",
          "connectTime": "0",
          "ttfbTime": "1",
          "responseTime": "1",
          "parseDOMTime": "80",
          "domContentLoadedTime": "0",
          "timeToInteractive": "88",
          "loadTime": "89"
          }

          4.5.4 實現(xiàn)

          import onload from "../util/onload";
          import tracker from "../util/tracker";
          import formatTime from "../util/formatTime";
          import getLastEvent from "../util/getLastEvent";
          import getSelector from "../util/getSelector";
          export function timing() {
          onload(function () {
          setTimeout(() => {
          const {
          fetchStart,
          connectStart,
          connectEnd,
          requestStart,
          responseStart,
          responseEnd,
          domLoading,
          domInteractive,
          domContentLoadedEventStart,
          domContentLoadedEventEnd,
          loadEventStart,
          } = performance.timing;
          tracker.send({
          kind: "experience",
          type: "timing",
          connectTime: connectEnd - connectStart, //TCP連接耗時
          ttfbTime: responseStart - requestStart, //ttfb
          responseTime: responseEnd - responseStart, //Response響應耗時
          parseDOMTime: loadEventStart - domLoading, //DOM解析渲染耗時
          domContentLoadedTime:
          domContentLoadedEventEnd - domContentLoadedEventStart, //DOMContentLoaded事件回調(diào)耗時
          timeToInteractive: domInteractive - fetchStart, //首次可交互時間
          loadTime: loadEventStart - fetchStart, //完整的加載時間
          });
          }, 3000);
          });
          }

          4.6 性能指標

          • PerformanceObserver.observe方法用于觀察傳入的參數(shù)中指定的性能條目類型的集合。當記錄一個指定類型的性能條目時,性能監(jiān)測對象的回調(diào)函數(shù)將會被調(diào)用

          • entryType

          • paint-timing

          • event-timing

          • LCP

          • FMP

          • time-to-interactive

          字段描述備注計算方式
          FPFirst Paint(首次繪制)包括了任何用戶自定義的背景繪制,它是首先將像素繪制到屏幕的時刻
          FCPFirst Content Paint(首次內(nèi)容繪制)是瀏覽器將第一個 DOM 渲染到屏幕的時間,可能是文本、圖像、SVG等,這其實就是白屏時間
          FMPFirst Meaningful Paint(首次有意義繪制)頁面有意義的內(nèi)容渲染的時間
          LCP(Largest Contentful Paint)(最大內(nèi)容渲染)代表在viewport中最大的頁面元素加載的時間
          DCL(DomContentLoaded)(DOM加載完成)當 HTML 文檔被完全加載和解析完成之后, DOMContentLoaded?事件被觸發(fā),無需等待樣式表、圖像和子框架的完成加載
          L(onLoad)當依賴的資源全部加載完畢之后才會觸發(fā)
          TTI(Time to Interactive) 可交互時間用于標記應用已進行視覺渲染并能可靠響應用戶輸入的時間點
          FIDFirst Input Delay(首次輸入延遲)用戶首次和頁面交互(單擊鏈接,點擊按鈕等)到頁面響應交互的時間

          4.6.1 數(shù)據(jù)結(jié)構(gòu)設(shè)計

          1. paint
          {
          "title": "前端監(jiān)控系統(tǒng)",
          "url": "http://localhost:8080/",
          "timestamp": "1590828364186",
          "userAgent": "chrome",
          "kind": "experience",
          "type": "paint",
          "firstPaint": "102",
          "firstContentPaint": "2130",
          "firstMeaningfulPaint": "2130",
          "largestContentfulPaint": "2130"
          }
          2. firstInputDelay
          {
          "title": "前端監(jiān)控系統(tǒng)",
          "url": "http://localhost:8080/",
          "timestamp": "1590828477284",
          "userAgent": "chrome",
          "kind": "experience",
          "type": "firstInputDelay",
          "inputDelay": "3",
          "duration": "8",
          "startTime": "4812.344999983907",
          "selector": "HTML BODY #container .content H1"
          }

          4.6.2 實現(xiàn)

          關(guān)鍵時間節(jié)點通過window.performance.timing獲取

          import tracker from "../utils/tracker";
          import onload from "../utils/onload";
          import getLastEvent from "../utils/getLastEvent";
          import getSelector from "../utils/getSelector";

          export function timing() {
          let FMP, LCP;
          // 增加一個性能條目的觀察者
          new PerformanceObserver((entryList, observer) => {
          const perfEntries = entryList.getEntries();
          FMP = perfEntries[0];
          observer.disconnect(); // 不再觀察了
          }).observe({ entryTypes: ["element"] }); // 觀察頁面中有意義的元素
          // 增加一個性能條目的觀察者
          new PerformanceObserver((entryList, observer) => {
          const perfEntries = entryList.getEntries();
          const lastEntry = perfEntries[perfEntries.length - 1];
          LCP = lastEntry;
          observer.disconnect(); // 不再觀察了
          }).observe({ entryTypes: ["largest-contentful-paint"] }); // 觀察頁面中最大的元素
          // 增加一個性能條目的觀察者
          new PerformanceObserver((entryList, observer) => {
          const lastEvent = getLastEvent();
          const firstInput = entryList.getEntries()[0];
          if (firstInput) {
          // 開始處理的時間 - 開始點擊的時間,差值就是處理的延遲
          let inputDelay = firstInput.processingStart - firstInput.startTime;
          let duration = firstInput.duration; // 處理的耗時
          if (inputDelay > 0 || duration > 0) {
          tracker.send({
          kind: "experience", // 用戶體驗指標
          type: "firstInputDelay", // 首次輸入延遲
          inputDelay: inputDelay ? formatTime(inputDelay) : 0, // 延遲的時間
          duration: duration ? formatTime(duration) : 0,
          startTime: firstInput.startTime, // 開始處理的時間
          selector: lastEvent
          ? getSelector(lastEvent.path || lastEvent.target)
          : "",
          });
          }
          }
          observer.disconnect(); // 不再觀察了
          }).observe({ type: "first-input", buffered: true }); // 第一次交互

          // 剛開始頁面內(nèi)容為空,等頁面渲染完成,再去做判斷
          onload(function () {
          setTimeout(() => {
          const {
          fetchStart,
          connectStart,
          connectEnd,
          requestStart,
          responseStart,
          responseEnd,
          domLoading,
          domInteractive,
          domContentLoadedEventStart,
          domContentLoadedEventEnd,
          loadEventStart,
          } = window.performance.timing;
          // 發(fā)送時間指標
          tracker.send({
          kind: "experience", // 用戶體驗指標
          type: "timing", // 統(tǒng)計每個階段的時間
          connectTime: connectEnd - connectStart, // TCP連接耗時
          ttfbTime: responseStart - requestStart, // 首字節(jié)到達時間
          responseTime: responseEnd - responseStart, // response響應耗時
          parseDOMTime: loadEventStart - domLoading, // DOM解析渲染的時間
          domContentLoadedTime:
          domContentLoadedEventEnd - domContentLoadedEventStart, // DOMContentLoaded事件回調(diào)耗時
          timeToInteractive: domInteractive - fetchStart, // 首次可交互時間
          loadTime: loadEventStart - fetchStart, // 完整的加載時間
          });
          // 發(fā)送性能指標
          let FP = performance.getEntriesByName("first-paint")[0];
          let FCP = performance.getEntriesByName("first-contentful-paint")[0];
          console.log("FP", FP);
          console.log("FCP", FCP);
          console.log("FMP", FMP);
          console.log("LCP", LCP);
          tracker.send({
          kind: "experience",
          type: "paint",
          firstPaint: FP ? formatTime(FP.startTime) : 0,
          firstContentPaint: FCP ? formatTime(FCP.startTime) : 0,
          firstMeaningfulPaint: FMP ? formatTime(FMP.startTime) : 0,
          largestContentfulPaint: LCP
          ? formatTime(LCP.renderTime || LCP.loadTime)
          : 0,
          });
          }, 3000);
          });
          }

          4.7 卡頓

          • 響應用戶交互的響應時間如果大于100ms,用戶就會感覺卡頓

          4.7.1 數(shù)據(jù)設(shè)計 longTask

          {
          "title": "前端監(jiān)控系統(tǒng)",
          "url": "http://localhost:8080/",
          "timestamp": "1590828656781",
          "userAgent": "chrome",
          "kind": "experience",
          "type": "longTask",
          "eventType": "mouseover",
          "startTime": "9331",
          "duration": "200",
          "selector": "HTML BODY #container .content"
          }

          4.7.2 實現(xiàn)

          • new PerformanceObserver

          • entry.duration > 100 判斷大于100ms,即可認定為長任務(wù)

          • 使用 requestIdleCallback上報數(shù)據(jù)

          import tracker from "../util/tracker";
          import formatTime from "../util/formatTime";
          import getLastEvent from "../util/getLastEvent";
          import getSelector from "../util/getSelector";
          export function longTask() {
          new PerformanceObserver((list) => {
          list.getEntries().forEach((entry) => {
          if (entry.duration > 100) {
          let lastEvent = getLastEvent();
          requestIdleCallback(() => {
          tracker.send({
          kind: "experience",
          type: "longTask",
          eventType: lastEvent.type,
          startTime: formatTime(entry.startTime), // 開始時間
          duration: formatTime(entry.duration), // 持續(xù)時間
          selector: lastEvent
          ? getSelector(lastEvent.path || lastEvent.target)
          : "",
          });
          });
          }
          });
          }).observe({ entryTypes: ["longtask"] });
          }

          4.8 PV、UV、用戶停留時間

          4.8.1 數(shù)據(jù)設(shè)計 business

          {
          "title": "前端監(jiān)控系統(tǒng)",
          "url": "http://localhost:8080/",
          "timestamp": "1590829304423",
          "userAgent": "chrome",
          "kind": "business",
          "type": "pv",
          "effectiveType": "4g",
          "rtt": "50",
          "screen": "2049x1152"
          }

          4.8.2 PV、UV、用戶停留時間

          PV(page view) 是頁面瀏覽量,UV(Unique visitor)用戶訪問量。PV 只要訪問一次頁面就算一次,UV 同一天內(nèi)多次訪問只算一次。

          對于前端來說,只要每次進入頁面上報一次 PV 就行,UV 的統(tǒng)計放在服務(wù)端來做,主要是分析上報的數(shù)據(jù)來統(tǒng)計得出 UV。

          import tracker from "../util/tracker";
          export function pv() {
          tracker.send({
          kind: "business",
          type: "pv",
          startTime: performance.now(),
          pageURL: getPageURL(),
          referrer: document.referrer,
          uuid: getUUID(),
          });
          let startTime = Date.now();
          window.addEventListener(
          "beforeunload",
          () => {
          let stayTime = Date.now() - startTime;
          tracker.send({
          kind: "business",
          type: "stayTime",
          stayTime,
          pageURL: getPageURL(),
          uuid: getUUID(),
          });
          },
          false
          );
          }

          擴展問題

          1. 性能監(jiān)控指標

          2. 前端怎么做性能監(jiān)控

          3. 線上錯誤監(jiān)控怎么做

          4. 導致內(nèi)存泄漏的方法,怎么監(jiān)控內(nèi)存泄漏

          5. Node 怎么做性能監(jiān)控

          1.?性能監(jiān)控指標


          指標名稱解釋
          FPFirst-Paint 首次渲染表示瀏覽器從開始請求網(wǎng)站到屏幕渲染第一個像素點的時間
          FCPFirst-Contentful-Paint 首次內(nèi)容渲染表示瀏覽器渲染出第一個內(nèi)容的時間,這個內(nèi)容可以是文本、圖片或SVG元素等等,不包括iframe和白色背景的canvas元素
          SISpeed Index 速度指數(shù)表明了網(wǎng)頁內(nèi)容的可見填充速度
          LCPLargest Contentful Paint 最大內(nèi)容繪制標記了渲染出最大文本或圖片的時間
          TTITime to Interactive 可交互時間頁面從開始加載到主要子資源完成渲染,并能夠快速、可靠的響應用戶輸入所需的時間
          TBTTotal Blocking Time 總阻塞時間測量 FCP 與 TTI 之間的總時間,這期間,主線程被阻塞的時間過長,無法作出輸入響應
          FIDFirst Input Delay 首次輸入延遲測量加載響應度的一個以用戶為中心的重要指標
          CLSCumulative Layout Shift 累積布局偏移測量的是整個頁面生命周期內(nèi)發(fā)生的所有意外布局偏移中最大一連串的布局偏移分數(shù)
          DCLDOMContentLoaded當初始的?HTML 文檔被完全加載和解析完成之后,DOMContentLoaded 事件被觸發(fā),而無需等待樣式表、圖像和子框架的完成加載
          LLoad檢測一個完全加載的頁面,頁面的html、css、js、圖片等資源都已經(jīng)加載完之后才會觸發(fā) load 事件

          2. 前端怎么做性能監(jiān)控

          • FP、FCP、LCP、CLS、FID、FMP 可通過 PerformanceObserver獲取

          • TCP連接耗時、首字節(jié)到達時間、response響應耗時、DOM解析渲染的時間、TTI、DCL、L等可通過performance.timing獲取

          • 長任務(wù)監(jiān)聽,PerformanceObserver 監(jiān)聽 longTask

          const {
          fetchStart,
          connectStart,
          connectEnd,
          requestStart,
          responseStart,
          responseEnd,
          domLoading,
          domInteractive,
          domContentLoadedEventStart,
          domContentLoadedEventEnd,
          loadEventStart,
          } = window.performance.timing;
          const obj = {
          kind: "experience", // 用戶體驗指標
          type: "timing", // 統(tǒng)計每個階段的時間
          dnsTime: domainLookupEnd - domainLookupStart, // DNS查詢時間
          connectTime: connectEnd - connectStart, // TCP連接耗時
          ttfbTime: responseStart - requestStart, // 首字節(jié)到達時間
          responseTime: responseEnd - responseStart, // response響應耗時
          parseDOMTime: loadEventStart - domLoading, // DOM解析渲染的時間
          domContentLoadedTime:
          domContentLoadedEventEnd - domContentLoadedEventStart, // DOMContentLoaded事件回調(diào)耗時
          timeToInteractive: domInteractive - fetchStart, // 首次可交互時間
          loadTime: loadEventStart - fetchStart, // 完整的加載時間
          }

          3. 線上錯誤監(jiān)控怎么做

          • 資源加載錯誤 window.addEventListener('error') 判斷e.target.src || href

          • js運行時錯誤 window.addEventListener('error')

          • promise異常 window.addEventListener('unhandledrejection')

          • 接口異常 重寫xhr 的 open send方法,監(jiān)控 load、error、abort,進行上報

          4. 導致內(nèi)存泄漏的方法,怎么監(jiān)控內(nèi)存泄漏

          • 全局變量

          • 被遺忘的定時器

          • 脫離Dom的引用

          • 閉包

          監(jiān)控內(nèi)存泄漏

          • window.performance.memory

          • 開發(fā)階段

            • 瀏覽器的 Performance

            • 移動端可使用 PerformanceDog

          5. Node 怎么做性能監(jiān)控

          1. 日志監(jiān)控 可以通過監(jiān)控異常日志的變動,將新增的異常類型和數(shù)量反映出來 監(jiān)控日志可以實現(xiàn)pv和uv的監(jiān)控,通過pv/uv的監(jiān)控,可以知道使用者們的使用習慣,預知訪問高峰

          2. 響應時間 響應時間也是一個需要監(jiān)控的點。一旦系統(tǒng)的某個子系統(tǒng)出現(xiàn)異?;蛘咝阅芷款i將會導致系統(tǒng)的響應時間變長。響應時間可以在nginx一類的反向代理上監(jiān)控,也可以通過應用自己產(chǎn)生訪問日志來監(jiān)控

          3. 進程監(jiān)控 監(jiān)控日志和響應時間都能較好地監(jiān)控到系統(tǒng)的狀態(tài),但是它們的前提是系統(tǒng)是運行狀態(tài)的,所以監(jiān)控進程是比前兩者更為緊要的任務(wù)。監(jiān)控進程一般是檢查操作系統(tǒng)中運行的應用進程數(shù),比如對于采用多進程架構(gòu)的web應用,就需要檢查工作進程的數(shù),如果低于低估值,就應當發(fā)出警報

          4. 磁盤監(jiān)控 磁盤監(jiān)控主要是監(jiān)控磁盤的用量。由于寫日志頻繁的緣故,磁盤空間漸漸被用光。一旦磁盤不夠用將會引發(fā)系統(tǒng)的各種問題,給磁盤的使用量設(shè)置一個上限,一旦磁盤用量超過警戒值,服務(wù)器的管理者應該整理日志或者清理磁盤

          5. 內(nèi)存監(jiān)控 對于node而言,一旦出現(xiàn)內(nèi)存泄漏,不是那么容易排查的。監(jiān)控服務(wù)器的內(nèi)存使用情況。如果內(nèi)存只升不降,那么鐵定存在內(nèi)存泄漏問題。符合正常的內(nèi)存使用應該是有升有降,在訪問量大的時候上升,在訪問量回落的時候,占用量也隨之回落。監(jiān)控內(nèi)存異常時間也是防止系統(tǒng)出現(xiàn)異常的好方法。如果突然出現(xiàn)內(nèi)存異常,也能夠追蹤到近期的哪些代碼改動導致的問題

          6. cpu占用監(jiān)控 服務(wù)器的cpu占用監(jiān)控也是必不可少的項,cpu的使用分為用戶態(tài)、內(nèi)核態(tài)、IOWait等。如果用戶態(tài)cpu使用率較高,說明服務(wù)器上的應用需要大量的cpu開銷;如果內(nèi)核態(tài)cpu使用率較高,說明服務(wù)器需要花費大量時間進行進程調(diào)度或者系統(tǒng)調(diào)用;IOWait使用率反映的是cpu等待磁盤I/O操作;cpu的使用率中,用戶態(tài)小于70%,內(nèi)核態(tài)小于35%且整體小于70%,處于正常范圍。監(jiān)控cpu占用情況,可以幫助分析應用程序在實際業(yè)務(wù)中的狀況。合理設(shè)置監(jiān)控閾值能夠很好地預警

          7. cpu load監(jiān)控 cpu load又稱cpu平均負載。它用來描述操作系統(tǒng)當前的繁忙程度,又簡單地理解為cpu在單位時間內(nèi)正在使用和等待使用cpu的平均任務(wù)數(shù)。它有3個指標,即1分鐘的平均負載、5分鐘的平均負載,15分鐘的平均負載。cpu load過高說明進程數(shù)量過多,這在node中可能體現(xiàn)在用于進程模塊反復啟動新的進程。監(jiān)控該值可以防止意外發(fā)生

          8. I/O負載 I/O負載指的主要是磁盤I/O。反應的是磁盤上的讀寫情況,對于node編寫的應用,主要是面向網(wǎng)絡(luò)業(yè)務(wù),是不太可能出現(xiàn)I/O負載過高的情況,大多數(shù)的I/O壓力來自于數(shù)據(jù)庫。不管node進程是否與數(shù)據(jù)庫或其他I/O密集的應用共同處理相同的服務(wù)器,我們都應該監(jiān)控該值防止意外情況

          9. 網(wǎng)絡(luò)監(jiān)控 雖然網(wǎng)絡(luò)流量監(jiān)控的優(yōu)先級沒有上述項目那么高,但還是需要對流量進行監(jiān)控并設(shè)置流量上限值。即便應用突然受到用戶的青睞,流量暴漲的時候也可以通過數(shù)值感知到網(wǎng)站的宣傳是否有效。一旦流量超過警戒值,開發(fā)者就應當找出流量增長的原因。對于正常增長,應當評估是否該增加硬件設(shè)備來為更多用戶提供服務(wù)。網(wǎng)絡(luò)流量監(jiān)控的兩個主要指標是流入流量和流出流量

            • 應用狀態(tài)監(jiān)控 除了這些硬性需要檢測的指標之外,應用還應該提供一種機制來反饋其自身的狀態(tài)信息,外部監(jiān)控將會持續(xù)性地調(diào)用應用地反饋接口來檢查它地健康狀態(tài)。

            • dns監(jiān)控 dns是網(wǎng)絡(luò)應用的基礎(chǔ),在實際的對外服務(wù)產(chǎn)品中,多數(shù)都對域名有依賴。dns故障導致產(chǎn)品出現(xiàn)大面積影響的事件并不少見。由于dns服務(wù)通常是穩(wěn)定的,容易讓人忽略,但是一旦出現(xiàn)故障,就可能是史無前例的故障。對于產(chǎn)品的穩(wěn)定性,域名dns狀態(tài)也需要加入監(jiān)控。



            往期推薦


            秒啊!答好這5個問題,就入門Docker了
            (字節(jié)/華為/美團)前端面經(jīng)記錄冷冷清清的金三銀四
            構(gòu)建大型高質(zhì)量的前端工程完全指南

            最后


            • 歡迎加我微信,拉你進技術(shù)群,長期交流學習...

            • 歡迎關(guān)注「前端Q」,認真學前端,做個專業(yè)的技術(shù)人...

            點個在看支持我吧
          瀏覽 76
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩一级在线播放免费观看 | 国产精品美女久久久久久久久 | 又大又粗免费视频 | 国产精品污污网站免费 | 日韩A片免费视频 |