这篇文章是《前端体验优化实战》小册第二节发布后半年追加的重写优化版。
使用了最新版的 Grafana Alloy 作为演示,并且配套视频教程,致力于解决大家在安装过程中遇到的报错、数据未上报等问题。
有了获取用户体验数据的工具web-vitals,许多人会直接在本地开发环境或内部测试环境获取用户体验指标数据,用于评估优化效果,但是这样的做法有显著的问题:
- 样本总量较少:开发测试环境的用户数量较为有限,往往只有个别内部同事访问,收集到的数据量较少,数据的波动也会很大。
- 数据不具有广泛代表性:只在内部环境收集
web-vitals数据,忽略了生产环境中真实用户千差万别的网络状况、软硬件性能,这样的数据不能代表真正的用户体验。
所以,只在开发测试环境使用web-vitals收集用户体验指标数据是远远不够的,我们需要到广阔无垠的生产环境中收集最广大用户的真实数据,以便准确了解现状、确定优化目标、评估优化效果。
笔者在此推荐一套经过实践检验、开发体验较好的生产环境数据采集工具:Prometheus 和 Grafana。
一、 Prometheus 及 Grafana 简介
Prometheus 是一款开源的数据监控解决方案,主要包括以下模块:
- 面向各种编程语言的数据采集SDK(例如面向 Node.js 的NPM包客户端:prom-client)
- 接收数据上报的服务器后端应用
- 基于时间序的数据库
- 基础的数据可视化前端应用
具有强大的拓展能力,可以方便快速地融合进已有的项目中,作为数据监控中台的工具使用。
Grafana 是一款开源的数据可视化工具,主要有以下特性:
- 兼容 Prometheus 在内各种数据库的数据查询工具
- 内置海量可视化图表模板的前端应用
- 支持免费的私有化部署
将 Prometheus 与 Grafana 整合进我们的项目中就能实现强大的数据收集、可视化能力。
下面我们以Node.js为例,演示如何接入这2款工具。
二、 接入手把手教学
为了便于各位操作,我录制了完整演示视频:【手把手教你前端优化神器Grafana】 www.bilibili.com/video/BV1Ks…
大家可以对照着图文内容参考。
1. 本地运行数据收集应用
运行数据收集 Node.js 应用推荐使用笔者编写的本书配套数据收集示例应用:github.com/JuniorTour/…
1. 克隆数据收集示例应用:node-prometheus-grafana-demo
我们可以直接 git clone https://github.com/JuniorTour/node-prometheus-grafana-demo.git 本书配套数据收集示例应用:github.com/JuniorTour/…
克隆到本地后,运行也很简单,只需要执行:
npm install
npm run start
运行 npm run start 后,prom-client就会自动开始采集一批默认的 Node.js 应用数据。
2. 核心逻辑讲解
数据收集示例应用:node-prometheus-grafana-demo的核心代码逻辑是下列代码,主要功能是:
- 初始化
prom-client:const register = new Registry(); - 启用采集默认指标:
.collectDefaultMetrics({ register }); - 新增路径为
/metrics的 HTTP 接口,向外部提供数据。
// 1. src/prom-client.js
const client = require('prom-client');
const Registry = client.Registry;
const register = new Registry();
client.collectDefaultMetrics({ register });
module.exports = {
register,
};
// 2. src/app.js
const express = require('express');
const { register, useCounter, useGauge } = require('./prom-client');
const app = express();
const metricPath = '/metrics';
app.get(metricPath, async (_req, res) => {
try {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
} catch (err) {
res.status(500).end(err);
}
});
3. 检查确认数据收集应用
数据收集应用运行成功后,访问应用的/metrics接口(http://localhost:4001/metrics),应该能看到页面响应了许多数据,这些就是prom-client收集到的数据,示例如图:
如果这个接口没有正确响应数据,建议检查报错和代码逻辑,或使用
npm run dev("dev": "node --inspect-brk ./src/app.js",)调试/metrics接口的逻辑。
2. 安装配置Grafana Alloy
Grafana Alloy 是 Grafana 官方推出的数据上报应用,核心功能是把本地收集到的数据,定时刮取、上报到远端的 Grafana。
Grafana提供了分步骤的插件,帮助我们方便地完成这一阶段,具体步骤是:
1. 添加连接
注册、登录 Grafana 后,通过Home - Connections - Add new connection - 搜索 Node.js,在页面中选择添加Node.js作为数据源,继而选择使用 Grafana Alloy:
2. 安装配置
点击 Configure using Grafana Alloy 后,按照页面中的分步骤指引,分别完成:
1. 选择平台
2. 下载安装 Alloy:
安装的同时也会生成一份默认的配置文件,路径是:
- Linux:
/etc/alloy/config.alloy - macOS:
$(brew --prefix)/etc/alloy/config.alloy - Windows:
%ProgramFiles%\GrafanaLabs\Alloy\config.alloy,例如:C:\Program Files\GrafanaLabs\Alloy\config.alloy
3. 选择配置:
这一步一般不需要操作,因为前几步已经选择了对应平台的配置。
4. 准备配置文件(重要!):
- 首先,教程中指引我们编写 Node.js 代码,初始化上传数据逻辑,这部分工作就是我们在上一节“1. 本地运行数据收集应用”中完成的步骤。
- 其次,第二部分需要我们修改配置文件,增加对 Node.js 的集成。增加的配置共有3段:
discovery.relabel "metrics_integrations_integrations_nodejs" {
# 1...
}
prometheus.scrape "metrics_integrations_integrations_nodejs" {
# 2...
}
prometheus.relabel "metrics_integrations_integrations_nodejs" {
# 3...
}
5. 重启 Alloy 并检测连接
修改配置文件后,需要重启 Alloy 才能生效。
# For Windows
sc stop "Alloy"
sc start "Alloy"
sc query "Alloy" | find "STATE"
# 打印 RUNNING 表示启动完成
# For Mac
brew services restart alloy
重启成功后,点击 Test Connection 即可验证连接效果。
如果连接没有成功,请向下翻到第4节:《Alloy 问题排查》。
官方GitHub:github.com/grafana/all…
配置上报指标文档:grafana.com/docs/alloy/…
▶点击展开完整配置示例(不要简单复制,其中包含了身份验证用的 basic_auth.password ,每个账户都不一样):
prometheus.exporter.self "alloy_check" { }
discovery.relabel "alloy_check" {
targets = prometheus.exporter.self.alloy_check.targets
rule {
target_label = "instance"
replacement = constants.hostname
}
rule {
target_label = "alloy_hostname"
replacement = constants.hostname
}
rule {
target_label = "job"
replacement = "integrations/alloy-check"
}
}
prometheus.scrape "alloy_check" {
targets = discovery.relabel.alloy_check.output
forward_to = [prometheus.relabel.alloy_check.receiver]
scrape_interval = "60s"
}
prometheus.relabel "alloy_check" {
forward_to = [prometheus.remote_write.metrics_service.receiver]
rule {
source_labels = ["__name__"]
regex = "(prometheus_target_sync_length_seconds_sum|prometheus_target_scrapes_.*|prometheus_target_interval.*|prometheus_sd_discovered_targets|alloy_build.*|prometheus_remote_write_wal_samples_appended_total|process_start_time_seconds)"
action = "keep"
}
}
prometheus.remote_write "metrics_service" {
endpoint {
url = "https://prometheus-prod-18-prod-ap-southeast-0.grafana.net/api/prom/push"
basic_auth {
username = "698333"
password = "glc_eyJvIjoiNz....TAifX0="
}
}
}
loki.write "grafana_cloud_loki" {
endpoint {
url = "https://logs-prod-011.grafana.net/loki/api/v1/push"
basic_auth {
username = "348136"
password = "glc_eyJvIj....TAifX0="
}
}
}
discovery.relabel "metrics_integrations_integrations_nodejs" {
targets = [{
__address__ = "localhost:4001",
}]
rule {
target_label = "instance"
replacement = constants.hostname
}
}
prometheus.scrape "metrics_integrations_integrations_nodejs" {
targets = discovery.relabel.metrics_integrations_integrations_nodejs.output
forward_to = [prometheus.relabel.metrics_integrations_integrations_nodejs.receiver]
job_name = "integrations/nodejs"
}
prometheus.relabel "metrics_integrations_integrations_nodejs" {
forward_to = [prometheus.remote_write.metrics_service.receiver]
rule {
source_labels = ["__name__"]
regex = ".+"
action = "keep"
}
}
logging {
level = "info"
format = "logfmt"
}
6. 导入预配置图表
Grafana 为 Node.js 准备了一套预配置的示例图表,点击 Install 按钮就可以复制到自己的看板上:
如果在这套预配置图表中能看到数据,就说明我们的安装配置初步完成了。
7. 修改配置,支持上传自定义指标
Alloy默认的配置中,用regex字段写死了收集指标的指定名称:
prometheus.relabel "metrics_integrations_integrations_nodejs" {
forward_to = [prometheus.remote_write.metrics_service.receiver]
rule {
source_labels = ["__name__"]
regex = "up|process_resident_memory_bytes|process_cpu_seconds_total|...."
action = "keep"
}
}
但是我们的目标是上报收集任意名称的指标,所以需要修改regex字段配置为".+",表示任意字符的指标,都可以被收集上报:
prometheus.relabel "metrics_integrations_integrations_nodejs" {
forward_to = [prometheus.remote_write.metrics_service.receiver]
rule {
source_labels = ["__name__"]
regex = ".+"
action = "keep"
}
}
修改配置后,别忘了再次重启Alloy:
# For Windows
sc stop "Alloy"
sc start "Alloy"
sc query "Alloy" | find "STATE"
# 打印 RUNNING 表示启动完成
# For Mac
brew services restart alloy
如果以上步骤,一切顺利就可以愉快地进行下一步:《3. 上报自定义指标并创建可视化图表》了。
如果遇到了各种问题、报错,也很正常,不用担心,毕竟咱们工程师就是解决问题的人。
我也总结了一些常见的问题排查思路,供各位参考:
3. Alloy问题排查
法一:使用自带UI排查
Grafana Alloy启动后,访问 http://localhost:12345 就可以使用自带的UI网页检查运行状况,正常情况下,各个Components应该都是 Healthy 状态:
如果某一项 Component 不是 Healthy 状态,点击 View 按钮,跳转到详情页,一般会有更多上下文信息,供我们排查解决。
法二:检查 Alloy 连接状况
在 Grafana 页面中访问Home - Connections - Collector - 搜索 Alloy 并选择 - Configure可以检查 Alloy 在 Grafana 云端的连接状况。
Alloy 连接状况页面 path 是: /a/grafana-collector-app/alloy/monitoring ,其中域名需要替换为你的 Grafana 实例域名。
例如我的域名URL:juniortour.grafana.net/a/grafana-c…
如果你的 Alloy 运行正常,应该能看到图表中绘制出类似下图的数据。
如果这些图表并未展示任何数据,那可能意味着你的 Alloy 安装配置,还未完成,请检查上述安装配置步骤。
如果中间的图表《Scrape Failures》有某项指标大于 0,也意味着你的 Alloy 运行时有报错,建议根据报错指标名称进行搜索,排查修复问题。
法三:检查 Log
通过检查 Alloy 运行时记录的 log,也有助于判断遇到的问题。
Log默认并未开启,开启log的具体做法是,找到你的 Alloy 配置文件,增加 logging block 配置:
logging {
level = "info"
format = "logfmt"
}
config修改之后,记得再次重启 Alloy:
# For Windows
sc stop "Alloy"
sc start "Alloy"
sc query "Alloy" | find "STATE"
# 打印 RUNNING 表示启动完成
# For Mac
brew services restart alloy
不同的系统,log分别存放在不同的位置,文档:
- Windows Alloy:默认保存在 事件查看器 Event Viewer 中,右键 windows 按钮 - 事件查看器即可打开:
| 入口 | UI |
|---|---|
- For system logs:
$(brew --prefix)/var/log/alloy.log - For error logs:
$(brew --prefix)/var/log/alloy.err.log
$(brew --prefix)一般是/opt/homebrew
- 其他系统 Log 配置文档:grafana.com/docs/grafan…
如果在 log 中发现了ERROR报错信息,可以在官方 GitHub 中搜索或创建 Issue 提问:github.com/grafana/all…
其他问题QA收集
| 问题 | 解决方案 |
|---|---|
Mac 系统无数据上报,error log 报错 panic: pattern "GET /debug/pprof/" (registered at /usr/lib/go/src/net/http/pprof/pprof.go:100) conflicts with pattern | 参考:github.com/grafana/all… ,把Go 语言 运行时降级到 1.22。 |
| TODO:欢迎评论区补充 | 我会同步到这里 |
3. 上报自定义指标
为了将前端收集到的web-vitals数据,发送到Grafana,后端服务需要基于prom-client的能力,增加一批自定义指标。
1. 搭建后端服务器接收指标数据
让我们继续在app.js中添加以下代码:
完整代码实现,请参考 node-prometheus-grafana-demo 示例项目中的《feat: 增加自定义计数指标及接口》commit
src/app.js
/*
2. 新增POST方法接口,接收客户端传来的任意自定义指标
*/
app.post('/counter-metric', function (req, res) {
const { name, help, labels } = req.body;
if (!name) {
console.warn(
`/counter-metric WARN: no name req: ${JSON.stringify(req.body, null, 2)}`
);
return;
}
useCounter({ name, help, labels });
const message = `/counter-metric name=${name} labels=${JSON.stringify(
labels
)}`;
console.log(message);
res.status(200).json({ message });
});
src\prom-client.js:
const client = require('prom-client');
const Registry = client.Registry;
const register = new Registry();
client.collectDefaultMetrics({ register });
function useCounter({ name, help, labels }) {
/* 3. */
let counter = register.getSingleMetric(name);
if (!counter) {
counter = new client.Counter({
name,
help,
registers: [register],
labelNames: Object.keys(labels),
});
}
counter.inc(labels, 1);
}
module.exports = {
useCounter,
register,
};
通过这2段代码,我们实现了:
- 增加了另一个POST方法的HTTP接口
/counter-metric,用于接受其他应用通过HTTP请求上报的数据,在本节中主要用于接收前端应用发送来的web-vitals数据; - 在
src\prom-client.js中封装prom-client的new client.Counter(),以便于我们在/counter-metric接口中调用,记录计数类型的数据。
每次有请求发送到服务器,都会触发计数器指标加一(couter.inc(labels, 1);),并且会通过labels字段记录上报的各种自定义数据,例如名称(name)、评分(rating)等;
Grafana文档中,把Grafana Alloy 通过
/metrics收集数据的过程为刮取(scrape) ,非常生动形象。
有了这部分代码,我们就可以从前端应用中上报任意数据到Grafana,例如:
// 客户端发送数据用法:
await fetch('http://localhost:4001/counter-metric', {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: 'FCP',
help: 'FCP data',
labels: {
rating: 'good',
}
}),
})
综上所述,完整数据收集流程即:
- 前端应用通过HTTP请求,将
web-vitals数据发送到数据收集后端服务; - 数据收集后端服务调用
prom-client的自定义计数指标记录数据到内存中; - Grafana Alloy 应用,通过
/metric接口定期刮取数据,上报到 Grafana 远端服务器;
接下来我们可以基于之前的《获取web-vitals数据在线 DEMO》: output.jsbin.com/bizanep 学习从前端应用上报数据到 Node.js 数据收集后端服务的逻辑。
2. 增加前端上报数据逻辑
我们的改动主要有2部分:
- 增加
report方法,用于把调用web-vitals库API获得的数据,通过HTTP请求到发送到数据收集后端应用的/counter-metric接口,并附带name, rating这2个字段作为请求体,以便我们在Grafana中查询过滤,实现想要的可视化效果; - 在DEMO已有的
onGetWebVitalsData方法中增加report()方法的调用,将指标数据中的name, rating作为参数传入;
对应代码如下:
async function report(name, labels, help = 'default help') {
await fetch('http://localhost:4001/counter-metric', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name,
help,
labels,
}),
});
}
function onGetWebVitalsData(data) {
// ...
report(name, { rating });
}
完整示例请参考 《发送 web-vitals 数据 DEMO》:output.jsbin.com/xibimaj/1
运行这些新增改动后,我们就能在开发中工具的Network面板中看到发送数据的HTTP请求,以及数据收集后端应用的响应了:
有了这样一套前后端工作流,我们的web-vitals数据就可以方便地发送到 Grafana,让我们能基于这些数据创建出各式各样的可视化图表。
下一节,我们就会基于上述成果,学习创建 Grafana 可视化图表,并最终搭建出有助于解决数据异常波动的问题《堆叠百分比评分图》。
4. 创建 Grafana 可视化图表指南
5. 堆叠百分比评分图优点及示例
第4、5小节内容,请移步:《前端体验优化实战》小册第三节。