The most complete front-end monitoring system construction so far (long text warning)

Dachang Technology Advanced Front-end Node Advanced

Click on the top programmer's growth guide, pay attention to the public number

Reply 1, join the advanced Node exchange group

Original link: https://juejin.cn/post/7078512301665419295?share_token=37a59eca-da19-4cfc-aeb2-67d4c0d9b0c2
Author: Locomotive

Overview

  • Why do front-end monitoring

  • Front-end monitoring target

  • Front-end monitoring process

  • Write acquisition scripts

    • Log system monitoring

    • Error monitoring

    • Interface exception

    • white screen monitoring

    • load time

    • Performance

    • Caton

    • pv

  • extension problem

  1. Performance Monitoring Metrics

  2. How to do performance monitoring on the front end

  3. How to do online error monitoring

  4. Methods that cause memory leaks, how to monitor memory leaks

  5. How Node does performance monitoring

Source address (https://github.com/miracle90/monitor)

1. Why do front-end monitoring

  • Find and fix problems faster

  • product decision-making basis

  • Provides more possibilities for business expansion

  • Improve the technical depth and breadth of front-end engineers and create resume highlights

2. Front-end monitoring target

2.1 Stability

  • js error: js execution error, promise exception

  • Resource error: js, css resource loading exception

  • Interface error: ajax, fetch request interface exception

  • White screen: the page is blank

2.2 User experience experience

2.3 business

  • pv: page views and clicks

  • uv: the number of people visiting a site with different ip

  • The time users spend on each page

3. Front-end monitoring process

  1. front end

  2. data reporting

  3. Processing summary

  4. Visual display

  5. Monitoring alarm

d039e648e22e169d48e78831f329aa1b.png

3.1 Common burying solutions

3.1.1 Code Burial

  • form of embed code

  • Advantages: accurate (any time, comprehensive data volume)

  • Disadvantage: Code workload points

3.1.2 Visualizing buried points

  • Replacing code buried points by means of visual interaction

  • Separate business code and embedded code, provide a visual interactive page, input as business code, through this system, you can add embedded events and so on in the business code. The final output code couples the business code and the embedded code. point code

  • Use the system instead of manually inserting buried code

3.1.3 Traceless burial

  • Any event in the front end is bound to an identifier, and all events are recorded

  • By regularly uploading record files and cooperating with file analysis, we can parse out the data we want, and generate visual reports for professional analysis.

  • The advantage of traceless burial is that the full amount of data will be collected, and there will be no leakage or mistaken burial.

  • The disadvantage is that it increases the pressure on data transmission and servers, and cannot flexibly customize the data structure

4. Write the acquisition script

4.1 Access the log system

  • Each company generally has its own log system to receive data reports, for example: Alibaba Cloud

4.2 Monitoring errors

4.2.1 Misclassification

  • js error (js execution error, promise exception)

  • Resource loading exception: listen for error

4.2.2 Data Structure Analysis

1. jsError
{
    "title": "前端监控系统", // 页面标题
    "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 (<anonymous>)^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 Implementation

1. Resource loading error + js execution error
//一般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", //稳定性指标
        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", //稳定性指标
        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代表在捕获阶段调用,false代表在冒泡阶段捕获,使用true或false都可以
2. Promise exception
//当Promise 被 reject 且没有 reject 处理器的时候,会触发 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", //稳定性指标
      type: "error", //jsError
      errorType: "promiseError", //unhandledrejection
      message: message, //标签名
      filename: file,
      position: line + ":" + column, //行列
      stack,
      selector: lastEvent
        ? getSelector(lastEvent.path || lastEvent.target)
        : "",
    });
  },
  true
); // true代表在捕获阶段调用,false代表在冒泡阶段捕获,使用true或false都可以

4.3 Interface exception collection script

4.3.1 Data Design

{
  "title": "前端监控系统", //标题
  "url": "http://localhost:8080/", //url
  "timestamp": "1590817024490", //timestamp
  "userAgent": "Chrome", //浏览器版本
  "kind": "stability", //大类
  "type": "xhr", //小类
  "eventType": "load", //事件类型
  "pathname": "/success", //路径
  "status": "200-OK", //状态码
  "duration": "7", //持续时间
  "response": "{\"id\":1}", //响应内容
  "params": "" //参数
}
{
  "title": "前端监控系统",
  "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 Implementation

Simulate requests with webpack devServer

  • Rewrite the open and send methods of xhr

  • Listen to load, error, abort events

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", //稳定性指标
          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 White screen

  • A white screen is nothing on the page

4.4.1 Data Design

{
  "title": "前端监控系统",
  "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 Implementation

  • The elementsFromPoint method can get all the elements arranged from the inside to the outside at the specified coordinates in the current viewport

  • According to elementsFromPoint api, get the element where the horizontal centerline and vertical centerline of the screen are located

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 Loading time

  • PerformanceTiming

  • DOMContentLoaded

  • FMP

4.5.1 Stage meaning


6ecb2a91ca3a0ae1a562b0630e452b76.png
field meaning
navigationStart Initialize the page, the timestamp of the previous page unload in the same browser context, if there is no unload of the previous page, it is equal to the fetchStart value
redirectStart The time when the first HTTP redirection occurs, there is a jump and it is a redirection of the same domain, otherwise it is 0
redirectEnd The time when the last redirect was completed, otherwise 0
fetchStart The time when the browser is ready to fetch the document with an http request, this happens before checking the cache
domainLookupStart The time when the DNS domain name starts to query, if there is a local cache or keep-alive, the time is 0
domainLookupEnd DNS domain name end query time
connectStart fetchStartThe time when TCP starts to establish a connection, or equal to the value if it is a persistent connection
secureConnectionStart The time when the https connection started, or 0 if it is not a secure connection
connectEnd fetchStartThe time for TCP to complete the handshake, or equal to the value if it is a persistent connection
requestStart The time when the HTTP request started to read the real document, including reading from the local cache
requestEnd The time when the HTTP request to read the real document ended, including reading from the local cache
responseStart Returns the Unix millisecond timestamp when the browser received (or read from the local cache) the first byte from the server
responseEnd Returns the Unix millisecond timestamp when the browser received (or read from local cache, or read from local resource) the last byte from the server
unloadEventStart The timestamp of the unload of the previous page is 0 if none
unloadEventEnd unloadEventStartCorrespondingly, it returns the unloadtimestamp of the completion of the function execution
domLoading Returns the timestamp when the DOM structure of the current web page starts parsing, it becomes loading at this time , and an event document.readyStatewill be thrownreadyStateChange
domInteractive Returns the timestamp when the DOM structure of the current web page ends parsing and starts loading embedded resources document.readyState , interactiveand will throw readyStateChangean event (note that only the DOM tree parsing is completed, and the resources in the web page are not loaded at this time)
domContentLoadedEventStart The time when the web page domContentLoaded event occurs
domContentLoadedEventEnd The time when the web page domContentLoaded event script is executed, the time of domReady
domComplete When the DOM tree parsing is complete and the resource is ready, it document.readyStatebecomes complete. and will throw readystatechangean event
loadEventStart load 事件发送给文档,也即load回调函数开始执行的时间
loadEventEnd load回调函数执行完成的时间


4.5.2 阶段计算

字段 描述 计算方式 意义
unload 前一个页面卸载耗时 unloadEventEnd – unloadEventStart -
redirect 重定向耗时 redirectEnd – redirectStart 重定向的时间
appCache 缓存耗时 domainLookupStart – fetchStart 读取缓存的时间
dns DNS 解析耗时 domainLookupEnd – domainLookupStart 可观察域名解析服务是否正常
tcp TCP 连接耗时 connectEnd – connectStart 建立连接的耗时
ssl SSL 安全连接耗时 connectEnd – secureConnectionStart 反映数据安全连接建立耗时
ttfb Time to First Byte(TTFB)网络请求耗时 responseStart – requestStart TTFB是发出页面请求到接收到应答数据第一个字节所花费的毫秒数
response 响应数据传输耗时 responseEnd – responseStart 观察网络是否正常
dom DOM解析耗时 domInteractive – responseEnd 观察DOM结构是否合理,是否有JS阻塞页面解析
dcl DOMContentLoaded 事件耗时 domContentLoadedEventEnd – domContentLoadedEventStart 当 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,无需等待样式表、图像和子框架的完成加载
resources 资源加载耗时 domComplete – domContentLoadedEventEnd 可观察文档流是否过大
domReady DOM阶段渲染耗时 domContentLoadedEventEnd – fetchStart DOM树和页面资源加载完成时间,会触发domContentLoaded事件
首次渲染耗时 首次渲染耗时 responseEnd-fetchStart 加载文档到看到第一帧非空图像的时间,也叫白屏时间
首次可交互时间 首次可交互时间 domInteractive-fetchStart DOM树解析完成时间,此时document.readyState为interactive
首包时间耗时 首包时间 responseStart-domainLookupStart DNS解析到响应返回给浏览器第一个字节的时间
页面完全加载时间 页面完全加载时间 loadEventStart - fetchStart -
onLoad onLoad事件耗时 loadEventEnd – loadEventStart
48da8a5951a3464f7ffae16beffa8056.png


4.5.3 数据结构

{
  "title": "前端监控系统",
  "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 实现

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事件回调耗时
        timeToInteractive: domInteractive - fetchStart, //首次可交互时间
        loadTime: loadEventStart - fetchStart, //完整的加载时间
      });
    }, 3000);
  });
}

4.6 性能指标

  • PerformanceObserver.observe方法用于观察传入的参数中指定的性能条目类型的集合。当记录一个指定类型的性能条目时,性能监测对象的回调函数将会被调用

  • entryType

  • paint-timing

  • event-timing

  • LCP

  • FMP

  • time-to-interactive

字段 描述 备注 计算方式
FP First Paint(首次绘制) 包括了任何用户自定义的背景绘制,它是首先将像素绘制到屏幕的时刻
FCP First Content Paint(首次内容绘制) 是浏览器将第一个 DOM 渲染到屏幕的时间,可能是文本、图像、SVG等,这其实就是白屏时间
FMP First Meaningful Paint(首次有意义绘制) 页面有意义的内容渲染的时间
LCP (Largest Contentful Paint)(最大内容渲染) 代表在viewport中最大的页面元素加载的时间
DCL (DomContentLoaded)(DOM加载完成) 当 HTML 文档被完全加载和解析完成之后, DOMContentLoaded 事件被触发,无需等待样式表、图像和子框架的完成加载
L (onLoad) 当依赖的资源全部加载完毕之后才会触发
TTI (Time to Interactive) 可交互时间 用于标记应用已进行视觉渲染并能可靠响应用户输入的时间点
FID First Input Delay(首次输入延迟) 用户首次和页面交互(单击链接,点击按钮等)到页面响应交互的时间
e312d902830b4092943116e792f53663.png 8985b5501e06d808c8a0a98e02698742.png

4.6.1 数据结构设计

1. paint
{
  "title": "前端监控系统",
  "url": "http://localhost:8080/",
  "timestamp": "1590828364186",
  "userAgent": "chrome",
  "kind": "experience",
  "type": "paint",
  "firstPaint": "102",
  "firstContentPaint": "2130",
  "firstMeaningfulPaint": "2130",
  "largestContentfulPaint": "2130"
}
2. firstInputDelay
{
  "title": "前端监控系统",
  "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 实现

关键时间节点通过window.performance.timing获取

4a36a3d2ced0888c5b86745381e7cfe5.png
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 }); // 第一次交互

  // 刚开始页面内容为空,等页面渲染完成,再去做判断
  onload(function () {
    setTimeout(() => {
      const {
        fetchStart,
        connectStart,
        connectEnd,
        requestStart,
        responseStart,
        responseEnd,
        domLoading,
        domInteractive,
        domContentLoadedEventStart,
        domContentLoadedEventEnd,
        loadEventStart,
      } = window.performance.timing;
      // 发送时间指标
      tracker.send({
        kind: "experience", // 用户体验指标
        type: "timing", // 统计每个阶段的时间
        connectTime: connectEnd - connectStart, // TCP连接耗时
        ttfbTime: responseStart - requestStart, // 首字节到达时间
        responseTime: responseEnd - responseStart, // response响应耗时
        parseDOMTime: loadEventStart - domLoading, // DOM解析渲染的时间
        domContentLoadedTime:
          domContentLoadedEventEnd - domContentLoadedEventStart, // DOMContentLoaded事件回调耗时
        timeToInteractive: domInteractive - fetchStart, // 首次可交互时间
        loadTime: loadEventStart - fetchStart, // 完整的加载时间
      });
      // 发送性能指标
      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 数据设计 longTask

{
  "title": "前端监控系统",
  "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 实现

  • new PerformanceObserver

  • entry.duration > 100 判断大于100ms,即可认定为长任务

  • 使用 requestIdleCallback上报数据

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), // 持续时间
            selector: lastEvent
              ? getSelector(lastEvent.path || lastEvent.target)
              : "",
          });
        });
      }
    });
  }).observe({ entryTypes: ["longtask"] });
}

4.8 PV、UV、用户停留时间

4.8.1 数据设计 business

{
  "title": "前端监控系统",
  "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 同一天内多次访问只算一次。

对于前端来说,只要每次进入页面上报一次 PV 就行,UV 的统计放在服务端来做,主要是分析上报的数据来统计得出 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. 性能监控指标

  2. 前端怎么做性能监控

  3. 线上错误监控怎么做

  4. 导致内存泄漏的方法,怎么监控内存泄漏

  5. Node 怎么做性能监控

1. 性能监控指标

指标 名称 解释
FP First-Paint 首次渲染 表示浏览器从开始请求网站到屏幕渲染第一个像素点的时间
FCP First-Contentful-Paint 首次内容渲染 表示浏览器渲染出第一个内容的时间,这个内容可以是文本、图片或SVG元素等等,不包括iframe和白色背景的canvas元素
SI Speed Index 速度指数 表明了网页内容的可见填充速度
LCP Largest Contentful Paint 最大内容绘制 标记了渲染出最大文本或图片的时间
TTI Time to Interactive 可交互时间 页面从开始加载到主要子资源完成渲染,并能够快速、可靠的响应用户输入所需的时间
TBT Total Blocking Time 总阻塞时间 测量 FCP 与 TTI 之间的总时间,这期间,主线程被阻塞的时间过长,无法作出输入响应
FID First Input Delay 首次输入延迟 测量加载响应度的一个以用户为中心的重要指标
CLS Cumulative Layout Shift 累积布局偏移 测量的是整个页面生命周期内发生的所有意外布局偏移中最大一连串的布局偏移分数
DCL DOMContentLoaded 当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载
L Load 检测一个完全加载的页面,页面的html、css、js、图片等资源都已经加载完之后才会触发 load 事件

2. 前端怎么做性能监控

  • FP、FCP、LCP、CLS、FID、FMP 可通过 PerformanceObserver获取

  • TCP连接耗时、首字节到达时间、response响应耗时、DOM解析渲染的时间、TTI、DCL、L等可通过performance.timing获取

  • 长任务监听,PerformanceObserver 监听 longTask

const {
    fetchStart,
    connectStart,
    connectEnd,
    requestStart,
    responseStart,
    responseEnd,
    domLoading,
    domInteractive,
    domContentLoadedEventStart,
    domContentLoadedEventEnd,
    loadEventStart,
} = window.performance.timing;
const obj = {
    kind: "experience", // 用户体验指标
    type: "timing", // 统计每个阶段的时间
    dnsTime: domainLookupEnd - domainLookupStart, // DNS查询时间
    connectTime: connectEnd - connectStart, // TCP连接耗时
    ttfbTime: responseStart - requestStart, // 首字节到达时间
    responseTime: responseEnd - responseStart, // response响应耗时
    parseDOMTime: loadEventStart - domLoading, // DOM解析渲染的时间
    domContentLoadedTime:
      domContentLoadedEventEnd - domContentLoadedEventStart, // DOMContentLoaded事件回调耗时
    timeToInteractive: domInteractive - fetchStart, // 首次可交互时间
    loadTime: loadEventStart - fetchStart, // 完整的加载时间
}
6c7ef5917542f6d31e8ff6b63c4a1953.png

3. 线上错误监控怎么做

  • 资源加载错误 window.addEventListener('error') 判断e.target.src || href

  • js运行时错误 window.addEventListener('error')

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

  • 接口异常 重写xhr 的 open send方法,监控 load、error、abort,进行上报

4. 导致内存泄漏的方法,怎么监控内存泄漏

  • 全局变量

  • 被遗忘的定时器

  • 脱离Dom的引用

  • 闭包

监控内存泄漏

  • window.performance.memory

  • 开发阶段

    • 浏览器的 Performance

    • 移动端可使用 PerformanceDog

15fa94781bf6c3ad440b2ed21af0360b.png

5. Node 怎么做性能监控

  1. 日志监控 可以通过监控异常日志的变动,将新增的异常类型和数量反映出来 监控日志可以实现pv和uv的监控,通过pv/uv的监控,可以知道使用者们的使用习惯,预知访问高峰

  2. 响应时间 响应时间也是一个需要监控的点。一旦系统的某个子系统出现异常或者性能瓶颈将会导致系统的响应时间变长。响应时间可以在nginx一类的反向代理上监控,也可以通过应用自己产生访问日志来监控

  3. 进程监控 监控日志和响应时间都能较好地监控到系统的状态,但是它们的前提是系统是运行状态的,所以监控进程是比前两者更为紧要的任务。监控进程一般是检查操作系统中运行的应用进程数,比如对于采用多进程架构的web应用,就需要检查工作进程的数,如果低于低估值,就应当发出警报

  4. 磁盘监控 磁盘监控主要是监控磁盘的用量。由于写日志频繁的缘故,磁盘空间渐渐被用光。一旦磁盘不够用将会引发系统的各种问题,给磁盘的使用量设置一个上限,一旦磁盘用量超过警戒值,服务器的管理者应该整理日志或者清理磁盘

  5. 内存监控 对于node而言,一旦出现内存泄漏,不是那么容易排查的。监控服务器的内存使用情况。如果内存只升不降,那么铁定存在内存泄漏问题。符合正常的内存使用应该是有升有降,在访问量大的时候上升,在访问量回落的时候,占用量也随之回落。监控内存异常时间也是防止系统出现异常的好方法。如果突然出现内存异常,也能够追踪到近期的哪些代码改动导致的问题

  6. cpu占用监控 服务器的cpu占用监控也是必不可少的项,cpu的使用分为用户态、内核态、IOWait等。如果用户态cpu使用率较高,说明服务器上的应用需要大量的cpu开销;如果内核态cpu使用率较高,说明服务器需要花费大量时间进行进程调度或者系统调用;IOWait使用率反映的是cpu等待磁盘I/O操作;cpu的使用率中,用户态小于70%,内核态小于35%且整体小于70%,处于正常范围。监控cpu占用情况,可以帮助分析应用程序在实际业务中的状况。合理设置监控阈值能够很好地预警

  7. cpu load监控 cpu load又称cpu平均负载。它用来描述操作系统当前的繁忙程度,又简单地理解为cpu在单位时间内正在使用和等待使用cpu的平均任务数。它有3个指标,即1分钟的平均负载、5分钟的平均负载,15分钟的平均负载。cpu load过高说明进程数量过多,这在node中可能体现在用于进程模块反复启动新的进程。监控该值可以防止意外发生

  8. I/O负载 I/O负载指的主要是磁盘I/O。反应的是磁盘上的读写情况,对于node编写的应用,主要是面向网络业务,是不太可能出现I/O负载过高的情况,大多数的I/O压力来自于数据库。不管node进程是否与数据库或其他I/O密集的应用共同处理相同的服务器,我们都应该监控该值防止意外情况

  9. 网络监控 虽然网络流量监控的优先级没有上述项目那么高,但还是需要对流量进行监控并设置流量上限值。即便应用突然受到用户的青睐,流量暴涨的时候也可以通过数值感知到网站的宣传是否有效。一旦流量超过警戒值,开发者就应当找出流量增长的原因。对于正常增长,应当评估是否该增加硬件设备来为更多用户提供服务。网络流量监控的两个主要指标是流入流量和流出流量

  • Application status monitoring In addition to these hard indicators that need to be detected, the application should also provide a mechanism to feedback its own status information, and external monitoring will continuously call the application's feedback interface to check its health status.

  • DNS monitoring DNS is the basis of network applications. In actual external service products, most of them rely on domain names. It is not uncommon for DNS failures to cause widespread product impact. Since the DNS service is usually stable, it is easy for people to ignore it, but once a failure occurs, it may be an unprecedented failure. For the stability of the product, the domain name DNS status also needs to be monitored.

Node 社群


我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

如果你觉得这篇内容对你有帮助,我想请你帮我2个小忙:

1. 点个「在看」,让更多人也能看到这篇文章2. 订阅官方博客 www.inode.club 让我们一起成长

点赞和在看就是最大的支持❤️

Guess you like

Origin blog.csdn.net/xgangzai/article/details/123814403