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
Métricas de seguimiento del rendimiento
Cómo hacer un seguimiento del rendimiento en el front-end
Cómo hacer un monitoreo de errores en línea
Métodos que causan fugas de memoria, cómo monitorear fugas de memoria
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
Interfaz
informes de datos
Resumen de procesamiento
Pantalla de visualización
Monitoreo de alarma
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
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 | fetchStart El 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 | fetchStart El 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 | unloadEventStart En consecuencia, devuelve la unload marca 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.readyState lanzará readyStateChange un 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 , interactive y generará readyStateChange un 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.readyState convierte en complete . y generará readystatechange un 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 |
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(首次输入延迟) | 用户首次和页面交互(单击链接,点击按钮等)到页面响应交互的时间 |
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获取
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
);
}
扩展问题
性能监控指标
前端怎么做性能监控
线上错误监控怎么做
导致内存泄漏的方法,怎么监控内存泄漏
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, // 完整的加载时间
}
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
5. Node 怎么做性能监控
日志监控 可以通过监控异常日志的变动,将新增的异常类型和数量反映出来 监控日志可以实现pv和uv的监控,通过pv/uv的监控,可以知道使用者们的使用习惯,预知访问高峰
响应时间 响应时间也是一个需要监控的点。一旦系统的某个子系统出现异常或者性能瓶颈将会导致系统的响应时间变长。响应时间可以在nginx一类的反向代理上监控,也可以通过应用自己产生访问日志来监控
进程监控 监控日志和响应时间都能较好地监控到系统的状态,但是它们的前提是系统是运行状态的,所以监控进程是比前两者更为紧要的任务。监控进程一般是检查操作系统中运行的应用进程数,比如对于采用多进程架构的web应用,就需要检查工作进程的数,如果低于低估值,就应当发出警报
磁盘监控 磁盘监控主要是监控磁盘的用量。由于写日志频繁的缘故,磁盘空间渐渐被用光。一旦磁盘不够用将会引发系统的各种问题,给磁盘的使用量设置一个上限,一旦磁盘用量超过警戒值,服务器的管理者应该整理日志或者清理磁盘
内存监控 对于node而言,一旦出现内存泄漏,不是那么容易排查的。监控服务器的内存使用情况。如果内存只升不降,那么铁定存在内存泄漏问题。符合正常的内存使用应该是有升有降,在访问量大的时候上升,在访问量回落的时候,占用量也随之回落。监控内存异常时间也是防止系统出现异常的好方法。如果突然出现内存异常,也能够追踪到近期的哪些代码改动导致的问题
cpu占用监控 服务器的cpu占用监控也是必不可少的项,cpu的使用分为用户态、内核态、IOWait等。如果用户态cpu使用率较高,说明服务器上的应用需要大量的cpu开销;如果内核态cpu使用率较高,说明服务器需要花费大量时间进行进程调度或者系统调用;IOWait使用率反映的是cpu等待磁盘I/O操作;cpu的使用率中,用户态小于70%,内核态小于35%且整体小于70%,处于正常范围。监控cpu占用情况,可以帮助分析应用程序在实际业务中的状况。合理设置监控阈值能够很好地预警
cpu load监控 cpu load又称cpu平均负载。它用来描述操作系统当前的繁忙程度,又简单地理解为cpu在单位时间内正在使用和等待使用cpu的平均任务数。它有3个指标,即1分钟的平均负载、5分钟的平均负载,15分钟的平均负载。cpu load过高说明进程数量过多,这在node中可能体现在用于进程模块反复启动新的进程。监控该值可以防止意外发生
I/O负载 I/O负载指的主要是磁盘I/O。反应的是磁盘上的读写情况,对于node编写的应用,主要是面向网络业务,是不太可能出现I/O负载过高的情况,大多数的I/O压力来自于数据库。不管node进程是否与数据库或其他I/O密集的应用共同处理相同的服务器,我们都应该监控该值防止意外情况
网络监控 虽然网络流量监控的优先级没有上述项目那么高,但还是需要对流量进行监控并设置流量上限值。即便应用突然受到用户的青睐,流量暴涨的时候也可以通过数值感知到网站的宣传是否有效。一旦流量超过警戒值,开发者就应当找出流量增长的原因。对于正常增长,应当评估是否该增加硬件设备来为更多用户提供服务。网络流量监控的两个主要指标是流入流量和流出流量
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 让我们一起成长
点赞和在看就是最大的支持❤️