La construcción del sistema de monitoreo front-end más completa hasta el momento (advertencia de texto largo)

Dachang Technology Advanced Front-end Nodo Avanzado

Haga clic en la guía de crecimiento del programador superior, preste atención al número público

Respuesta 1, únete al grupo de intercambio de nodos avanzado

Enlace original: https://juejin.cn/post/7078512301665419295?share_token=37a59eca-da19-4cfc-aeb2-67d4c0d9b0c2
Autor: Locomotora

Visión de conjunto

  • ¿Por qué monitorear el front-end?

  • Objetivo de monitoreo front-end

  • Proceso de monitoreo de front-end

  • Escribir guiones de adquisición

    • Monitoreo del sistema de registro

    • Supervisión de errores

    • Excepción de interfaz

    • monitoreo de pantalla blanca

    • tiempo de carga

    • Rendimiento

    • Catón

    • p.v.

  • problema de extensión

  1. Métricas de seguimiento del rendimiento

  2. Cómo hacer un seguimiento del rendimiento en el front-end

  3. Cómo hacer un monitoreo de errores en línea

  4. Métodos que causan fugas de memoria, cómo monitorear fugas de memoria

  5. Cómo hace Node la supervisión del rendimiento

Dirección de origen (https://github.com/miracle90/monitor)

1. ¿Por qué monitorear el front-end?

  • Encuentre y solucione problemas más rápido

  • base para la toma de decisiones sobre el producto

  • Brinda más posibilidades para la expansión del negocio.

  • Mejore la profundidad técnica y la amplitud de los ingenieros de front-end y cree aspectos destacados del currículum

2. Objetivo de monitoreo de front-end

2.1 Estabilidad

  • error de js: error de ejecución de js, excepción de promesa

  • Error de recurso: js, excepción de carga de recursos css

  • Error de interfaz: ajax, excepción de interfaz de solicitud de recuperación

  • Pantalla blanca: la página está en blanco

2.2 Experiencia de experiencia de usuario

2.3 negocio

  • pv: páginas vistas y clics

  • uv: la cantidad de personas que visitan un sitio con diferente ip

  • El tiempo que los usuarios pasan en cada página.

3. Proceso de monitoreo de front-end

  1. Interfaz

  2. informes de datos

  3. Resumen de procesamiento

  4. Pantalla de visualización

  5. Monitoreo de alarma

d039e648e22e169d48e78831f329aa1b.png

3.1 Soluciones comunes de enterramiento

3.1.1 Código de entierro

  • forma de código de inserción

  • Ventajas: preciso (en cualquier momento, volumen de datos completo)

  • Desventaja: Puntos de carga de trabajo de código

3.1.2 Visualización de puntos enterrados

  • Sustitución de puntos ocultos de código mediante interacción visual

  • Separe el código comercial y el código incrustado, proporcione una página interactiva visual, ingrese como código comercial, a través de este sistema, puede agregar eventos incrustados, etc., en el código comercial. El código de salida final combina el código comercial y el código incrustado. código de punto

  • Use el sistema en lugar de insertar manualmente el código oculto

3.1.3 Entierro sin rastro

  • Cualquier evento en el front-end está vinculado a un identificador, y todos los eventos se registran

  • Al cargar regularmente archivos de registro y cooperar con el análisis de archivos, podemos analizar los datos que queremos y generar informes visuales para el análisis profesional.

  • La ventaja del entierro sin rastro es que se recopilará la cantidad total de datos y no habrá fugas ni entierros por error.

  • La desventaja es que aumenta la presión sobre la transmisión de datos y los servidores, y no puede personalizar de manera flexible la estructura de datos.

4. Escribir el guión de adquisición

4.1 Acceder al sistema de registro

  • Cada empresa generalmente tiene su propio sistema de registro para recibir informes de datos, por ejemplo: Alibaba Cloud

4.2 Supervisión de errores

4.2.1 Clasificación errónea

  • js error (error de ejecución js, excepción de promesa)

  • Excepción de carga de recursos: escuche el error

4.2.2 Análisis de la estructura de datos

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. error de recurso
...
    "errorType": "resourceError",//错误类型
    "filename": "http://localhost:8080/error.js",//访问的文件名
    "tagName": "SCRIPT",//标签名
    "timeStamp": "76",//时间

4.2.3 Implementación

1. Error de carga de recursos + error de ejecución js
//一般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. Promesa de excepción
//当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 Script de recopilación de excepciones de interfaz

4.3.1 Diseño de datos

{
  "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 Implementación

Simule solicitudes con webpack devServer

  • Reescribir los métodos de apertura y envío de xhr

  • Escuche los eventos de carga, error y cancelación

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 Pantalla blanca

  • Una pantalla blanca no es nada en la página

4.4.1 Diseño de datos

{
  "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 Implementación

  • El método elementsFromPoint puede organizar todos los elementos desde el interior hacia el exterior en las coordenadas especificadas en la ventana gráfica actual

  • De acuerdo con la API de elementsFromPoint, obtenga el elemento donde se encuentran la línea central horizontal y la línea central vertical de la pantalla

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 Tiempo de carga

  • DesempeñoTiempo

  • DOMContentLoaded

  • FMP

4.5.1 Significado de la etapa


6ecb2a91ca3a0ae1a562b0630e452b76.png
campo sentido
navegaciónEmpezar Inicialice la página, la marca de tiempo de la descarga de la página anterior en el mismo contexto del navegador, si no hay descarga de la página anterior, es igual al valor de fetchStart
redirigirInicio La hora en que se produce la primera redirección HTTP, hay un salto y es una redirección del mismo dominio, en caso contrario es 0
redirectEnd La hora en que se completó la última redirección; de lo contrario, 0
buscarInicio El momento en que el navegador está listo para buscar el documento con una solicitud http, esto sucede antes de verificar el caché
inicio de búsqueda de dominio El momento en que el nombre de dominio DNS comienza a consultar, si hay un caché local o un keep-alive, el tiempo es 0
fin de búsqueda de dominio Hora de finalización de la consulta del nombre de dominio DNS
conectarEmpezar fetchStartEl tiempo en que TCP comienza a establecer una conexión, o igual al valor si es una conexión persistente
inicio de conexión segura La hora en que se inició la conexión https, o 0 si no es una conexión segura
conectarFin fetchStartEl tiempo para que TCP complete el protocolo de enlace, o igual al valor si se trata de una conexión persistente
solicitudIniciar El momento en que la solicitud HTTP comenzó a leer el documento real, incluida la lectura del caché local
solicitudFin El momento en que finalizó la solicitud HTTP para leer el documento real, incluida la lectura desde el caché local
responseStart Devuelve la marca de tiempo de milisegundos de Unix cuando el navegador recibió (o leyó del caché local) el primer byte del servidor
respuestaFin Devuelve la marca de tiempo de milisegundos de Unix cuando el navegador recibió (o leyó del caché local, o leyó del recurso local) el último byte del servidor
descargarEventoIniciar La marca de tiempo de la descarga de la página anterior es 0 si no hay
descargarEventoEnd unloadEventStartEn consecuencia, devuelve la unloadmarca de tiempo de la finalización de la ejecución de la función.
domCargando Devuelve la marca de tiempo cuando la estructura DOM de la página web actual comienza a analizarse, se carga en este momento y se document.readyStatelanzará readyStateChangeun evento
domInteractivo Devuelve la marca de tiempo cuando la estructura DOM de la página web actual finaliza el análisis y comienza a cargar recursos incrustados document.readyState , interactivey generará readyStateChangeun evento (tenga en cuenta que solo se completa el análisis del árbol DOM y los recursos en la página web no se cargan en este momento)
domContentLoadedEventStart El momento en que ocurre el evento domContentLoaded de la página web
domContentLoadedEventEnd La hora en que se ejecuta el script de evento domContentLoaded de la página web, la hora de domReady
domComplete Cuando se completa el análisis del árbol DOM y el recurso está listo, se document.readyStateconvierte en complete. y generará readystatechangeun evento
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. 网络监控 虽然网络流量监控的优先级没有上述项目那么高,但还是需要对流量进行监控并设置流量上限值。即便应用突然受到用户的青睐,流量暴涨的时候也可以通过数值感知到网站的宣传是否有效。一旦流量超过警戒值,开发者就应当找出流量增长的原因。对于正常增长,应当评估是否该增加硬件设备来为更多用户提供服务。网络流量监控的两个主要指标是流入流量和流出流量

  • Monitoreo del estado de la aplicación Además de estos indicadores duros que deben detectarse, la aplicación también debe proporcionar un mecanismo para retroalimentar su propia información de estado, y el monitoreo externo llamará continuamente a la interfaz de retroalimentación de la aplicación para verificar su estado.

  • Monitoreo de DNS El DNS es la base de las aplicaciones de red.En los productos de servicios externos reales, la mayoría de ellos se basan en nombres de dominio. No es raro que las fallas de DNS causen un impacto generalizado en el producto. Dado que el servicio DNS suele ser estable, es fácil que la gente lo ignore, pero una vez que ocurre una falla, puede ser una falla sin precedentes. Para la estabilidad del producto, también se debe monitorear el estado del DNS del nombre de dominio.

Node 社群


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

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

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

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

Supongo que te gusta

Origin blog.csdn.net/xgangzai/article/details/123814403
Recomendado
Clasificación