0%

前端性能监控体系——Real User Monitoring最佳实践

Real User Monitoring (RUM) 是衡量真实用户体验的关键工具,通过全面的性能监控和数据分析,帮助团队持续优化Web应用的性能和用户体验。

介绍

  在当今数字化时代,用户体验已成为产品成功的关键因素。前端性能不仅影响用户满意度,还直接关系到转化率、留存率等业务指标。传统的实验室测试无法完全反映真实的用户体验,因此 Real User Monitoring (RUM) 成为了现代 Web 应用性能优化的核心手段。本文将深入探讨 RUM 的核心概念、指标体系、工具选型和最佳实践。

RUM核心指标体系

Core Web Vitals

Google 提出的 Core Web Vitals 指标是衡量用户体验的核心标准。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Core Web Vitals 监控实现
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

class CoreWebVitalsMonitor {
constructor() {
this.metrics = {
CLS: null, // Cumulative Layout Shift
FID: null, // First Input Delay
FCP: null, // First Contentful Paint
LCP: null, // Largest Contentful Paint
TTFB: null // Time to First Byte
};

this.setupMonitoring();
}

setupMonitoring() {
// CLS (累积布局偏移)
getCLS(metric => {
this.metrics.CLS = metric;
this.sendMetric(metric, 'CLS');

// 检测布局偏移问题
if (metric.value > 0.1) {
this.logLayoutShifts();
}
});

// FID (首次输入延迟)
getFID(metric => {
this.metrics.FID = metric;
this.sendMetric(metric, 'FID');

if (metric.value > 100) {
this.analyzeBlockingTasks();
}
});

// FCP (首次内容绘制)
getFCP(metric => {
this.metrics.FCP = metric;
this.sendMetric(metric, 'FCP');
});

// LCP (最大内容绘制)
getLCP(metric => {
this.metrics.LCP = metric;
this.sendMetric(metric, 'LCP');

if (metric.value > 2500) { // 2.5秒
this.analyzeLargestElement();
}
});

// TTFB (首字节时间)
getTTFB(metric => {
this.metrics.TTFB = metric;
this.sendMetric(metric, 'TTFB');
});
}

sendMetric(metric, metricName) {
// 发送指标到监控系统
navigator.sendBeacon('/api/metrics', JSON.stringify({
name: metricName,
value: metric.value,
rating: metric.rating,
id: metric.id,
navigationType: metric.navigationType,
userAgent: navigator.userAgent,
timestamp: Date.now()
}));
}

async logLayoutShifts() {
// 详细记录布局偏移信息
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.hadRecentInput) return; // 过滤用户输入导致的偏移

this.sendMetric({
name: 'layout-shift',
value: entry.value,
sources: entry.sources.map(source => ({
node: source.node?.tagName,
previousRect: source.previousRect,
currentRect: source.currentRect
}))
}, 'layout-shift-detail');
});
});

observer.observe({ entryTypes: ['layout-shift'] });
}

async analyzeBlockingTasks() {
// 分析阻塞主线程的任务
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.duration > 50) { // 长任务阈值
this.sendMetric({
name: 'long-task',
value: entry.duration,
startTime: entry.startTime,
name: entry.name
}, 'long-task-detail');
}
});
});

observer.observe({ entryTypes: ['longtask'] });
}
}

async analyzeLargestElement() {
// 分析最大内容元素
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const largestEntry = entries.reduce((largest, entry) =>
entry.size > largest.size ? entry : largest
);

this.sendMetric({
name: 'largest-element',
element: largestEntry.element?.tagName,
size: largestEntry.size,
url: largestEntry.url,
loadTime: largestEntry.loadTime
}, 'largest-element-detail');
});

observer.observe({ entryTypes: ['largest-contentful-paint'] });
}
}
}

// 初始化监控
const rumMonitor = new CoreWebVitalsMonitor();

用户体验指标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
// 综合用户体验指标监控
class UserExperienceMetrics {
constructor() {
this.session = {
id: this.generateSessionId(),
startTime: Date.now(),
interactions: [],
errors: [],
performance: {}
};

this.setupComprehensiveMonitoring();
}

setupComprehensiveMonitoring() {
// 页面加载时间
this.monitorPageLoadTime();

// 用户交互响应时间
this.monitorInteractionLatency();

// 网络请求性能
this.monitorNetworkPerformance();

// 资源加载性能
this.monitorResourceLoading();

// 错误监控
this.setupErrorMonitoring();
}

monitorPageLoadTime() {
// 页面完全加载时间
if (document.readyState === 'complete') {
this.recordPageLoadTime();
} else {
window.addEventListener('load', () => {
this.recordPageLoadTime();
});
}
}

recordPageLoadTime() {
const perfData = performance.getEntriesByType('navigation')[0];
if (perfData) {
this.session.performance.pageLoadTime = perfData.loadEventEnd - perfData.fetchStart;
this.session.performance.domContentLoaded = perfData.domContentLoadedEventEnd - perfData.fetchStart;
this.session.performance.ttfb = perfData.responseStart - perfData.requestStart;
}
}

monitorInteractionLatency() {
// 监控用户交互延迟
['click', 'keydown', 'touchstart'].forEach(eventType => {
document.addEventListener(eventType, (event) => {
const startTime = performance.now();

// 模拟交互处理延迟
setTimeout(() => {
const endTime = performance.now();
const latency = endTime - startTime;

this.session.interactions.push({
type: eventType,
element: event.target.tagName,
latency,
timestamp: Date.now()
});

// 如果延迟过高,记录问题
if (latency > 100) { // 100ms阈值
this.recordPerformanceIssue('high-latency', {
type: eventType,
latency,
element: event.target.tagName
});
}
}, 0);
}, true);
});
}

monitorNetworkPerformance() {
// 重写 fetch 以监控网络请求
const originalFetch = window.fetch;
window.fetch = async (...args) => {
const startTime = performance.now();
const startTimestamp = Date.now();

try {
const response = await originalFetch(...args);
const endTime = performance.now();
const duration = endTime - startTime;

this.recordNetworkRequest({
url: args[0],
method: args[1]?.method || 'GET',
duration,
status: response.status,
size: response.headers.get('content-length'),
timestamp: startTimestamp
});

return response;
} catch (error) {
this.recordNetworkError({
url: args[0],
method: args[1]?.method || 'GET',
error: error.message,
timestamp: Date.now()
});
throw error;
}
};

// 监控 XMLHttpRequest
const originalXHR = window.XMLHttpRequest;
const self = this;

window.XMLHttpRequest = function() {
const xhr = new originalXHR();
const originalOpen = xhr.open;
const originalSend = xhr.send;

xhr.open = function(method, url) {
xhr._url = url;
xhr._method = method;
return originalOpen.apply(this, arguments);
};

xhr.send = function(body) {
const startTime = performance.now();
const startTimestamp = Date.now();

const originalOnReadyStateChange = xhr.onreadystatechange;
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) { // 请求完成
const endTime = performance.now();
const duration = endTime - startTime;

self.recordNetworkRequest({
url: xhr._url,
method: xhr._method,
duration,
status: xhr.status,
size: xhr.getResponseHeader('content-length'),
timestamp: startTimestamp
});
}

if (originalOnReadyStateChange) {
originalOnReadyStateChange.apply(this, arguments);
}
};

return originalSend.apply(this, arguments);
};

return xhr;
};
}

recordNetworkRequest(request) {
this.session.performance.networkRequests = this.session.performance.networkRequests || [];
this.session.performance.networkRequests.push(request);

// 记录慢请求
if (request.duration > 1000) { // 1秒阈值
this.recordPerformanceIssue('slow-network', request);
}
}

monitorResourceLoading() {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
this.recordResourceLoad({
name: entry.name,
type: entry.entryType,
duration: entry.duration,
transferSize: entry.transferSize,
decodedBodySize: entry.decodedBodySize,
contentType: entry.responseEnd,
timestamp: Date.now()
});
});
});

observer.observe({ entryTypes: ['resource', 'navigation'] });
}
}

recordResourceLoad(resource) {
this.session.performance.resources = this.session.performance.resources || [];
this.session.performance.resources.push(resource);

// 记录大资源或慢加载资源
if (resource.decodedBodySize > 1024 * 1024 || resource.duration > 2000) {
this.recordPerformanceIssue('heavy-resource', resource);
}
}

setupErrorMonitoring() {
// 监控 JavaScript 错误
window.addEventListener('error', (event) => {
this.recordError({
type: 'javascript',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack,
timestamp: Date.now()
});
});

// 监控 Promise 拒绝
window.addEventListener('unhandledrejection', (event) => {
this.recordError({
type: 'promise',
message: event.reason?.message || String(event.reason),
stack: event.reason?.stack,
timestamp: Date.now()
});
});

// 监控资源加载错误
window.addEventListener('error', (event) => {
if (event.target !== window) {
this.recordError({
type: 'resource',
resource: event.target.src || event.target.href,
element: event.target.tagName,
timestamp: Date.now()
});
}
}, true);
}

recordError(error) {
this.session.errors.push(error);

// 发送错误报告
this.sendErrorReport(error);
}

recordPerformanceIssue(type, details) {
const issue = {
type,
details,
session: this.session.id,
userAgent: navigator.userAgent,
url: window.location.href,
timestamp: Date.now()
};

// 发送性能问题报告
navigator.sendBeacon('/api/performance-issues', JSON.stringify(issue));
}

generateSessionId() {
return Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
}

// 发送会话数据
sendSessionData() {
navigator.sendBeacon('/api/session-data', JSON.stringify(this.session));
}

// 定期发送数据
startReporting(interval = 30000) { // 30秒间隔
setInterval(() => {
if (this.session.interactions.length > 0 || this.session.errors.length > 0) {
this.sendSessionData();
// 清空已发送的数据
this.session.interactions = [];
this.session.errors = [];
}
}, interval);
}
}

// 初始化用户体验监控
const userExperienceMonitor = new UserExperienceMetrics();
userExperienceMonitor.startReporting();

监控工具选型与集成

自建监控系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
// 自建 RUM 系统后端接口模拟
class RUMServer {
constructor() {
this.metricsStore = new MetricsStore();
this.alertEngine = new AlertEngine();
this.dashboard = new DashboardGenerator();
}

// 接收性能指标
async receiveMetrics(req, res) {
try {
const metrics = JSON.parse(req.body);

// 验证指标数据
if (!this.validateMetrics(metrics)) {
return res.status(400).json({ error: 'Invalid metrics data' });
}

// 存储指标
await this.metricsStore.store(metrics);

// 检查告警条件
await this.alertEngine.checkAlerts(metrics);

res.status(200).json({ success: true });
} catch (error) {
console.error('Error processing metrics:', error);
res.status(500).json({ error: 'Internal server error' });
}
}

validateMetrics(metrics) {
const requiredFields = ['name', 'value', 'rating', 'timestamp'];
return requiredFields.every(field => metrics.hasOwnProperty(field));
}

// 生成性能报告
async generatePerformanceReport(filters = {}) {
const metrics = await this.metricsStore.getMetrics(filters);

return {
summary: this.calculateSummary(metrics),
trends: this.calculateTrends(metrics),
breakdowns: this.calculateBreakdowns(metrics),
recommendations: this.generateRecommendations(metrics)
};
}

calculateSummary(metrics) {
const grouped = this.groupBy(metrics, 'name');

return Object.entries(grouped).map(([name, metricGroup]) => {
const values = metricGroup.map(m => m.value);
return {
name,
avg: values.reduce((a, b) => a + b, 0) / values.length,
p95: this.percentile(values, 95),
p99: this.percentile(values, 99),
count: metricGroup.length
};
});
}

calculateTrends(metrics) {
// 按时间窗口聚合数据
const hourly = this.aggregateByTime(metrics, 'hour');
const daily = this.aggregateByTime(metrics, 'day');

return { hourly, daily };
}

groupBy(array, key) {
return array.reduce((result, item) => {
(result[item[key]] = result[item[key]] || []).push(item);
return result;
}, {});
}

percentile(sortedArray, percentile) {
const index = Math.ceil(percentile / 100 * sortedArray.length) - 1;
return sortedArray[Math.max(0, index)];
}

aggregateByTime(metrics, unit) {
// 按时间单位聚合指标
const aggregates = {};

metrics.forEach(metric => {
const timeKey = this.getTimeKey(metric.timestamp, unit);

if (!aggregates[timeKey]) {
aggregates[timeKey] = {
values: [],
count: 0,
sum: 0
};
}

aggregates[timeKey].values.push(metric.value);
aggregates[timeKey].count++;
aggregates[timeKey].sum += metric.value;
});

return Object.entries(aggregates).map(([time, data]) => ({
time,
avg: data.sum / data.count,
count: data.count,
p95: this.percentile(data.values.sort((a, b) => a - b), 95)
}));
}

getTimeKey(timestamp, unit) {
const date = new Date(timestamp);

switch (unit) {
case 'hour':
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()} ${date.getHours()}`;
case 'day':
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
default:
return timestamp;
}
}
}

// 指标存储系统
class MetricsStore {
constructor() {
this.metrics = [];
this.retentionPeriod = 30 * 24 * 60 * 60 * 1000; // 30天
}

async store(metric) {
// 添加元数据
const enhancedMetric = {
...metric,
id: this.generateId(),
receivedAt: Date.now(),
userAgent: metric.userAgent || 'unknown',
url: metric.url || 'unknown',
environment: this.detectEnvironment(metric.url)
};

this.metrics.push(enhancedMetric);

// 定期清理旧数据
this.cleanupOldMetrics();
}

detectEnvironment(url) {
if (url.includes('localhost') || url.includes('127.0.0.1')) {
return 'development';
} else if (url.includes('staging') || url.includes('test')) {
return 'staging';
}
return 'production';
}

cleanupOldMetrics() {
const cutoff = Date.now() - this.retentionPeriod;
this.metrics = this.metrics.filter(metric => metric.receivedAt > cutoff);
}

async getMetrics(filters = {}) {
let filtered = this.metrics;

// 应用过滤器
if (filters.metricName) {
filtered = filtered.filter(m => m.name === filters.metricName);
}

if (filters.environment) {
filtered = filtered.filter(m => m.environment === filters.environment);
}

if (filters.timeRange) {
const { start, end } = filters.timeRange;
filtered = filtered.filter(m => m.receivedAt >= start && m.receivedAt <= end);
}

if (filters.urlPattern) {
filtered = filtered.filter(m => m.url.includes(filters.urlPattern));
}

return filtered;
}

generateId() {
return Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
}
}

// 告警引擎
class AlertEngine {
constructor() {
this.alertRules = [
{
name: 'High CLS',
condition: (metric) => metric.name === 'CLS' && metric.value > 0.1,
severity: 'high'
},
{
name: 'Slow FCP',
condition: (metric) => metric.name === 'FCP' && metric.value > 1800,
severity: 'medium'
},
{
name: 'Slow LCP',
condition: (metric) => metric.name === 'LCP' && metric.value > 2500,
severity: 'medium'
},
{
name: 'High Error Rate',
condition: (metric) => metric.type === 'error-rate' && metric.value > 0.05,
severity: 'high'
}
];
}

async checkAlerts(metrics) {
const alerts = [];

for (const rule of this.alertRules) {
if (rule.condition(metrics)) {
alerts.push({
rule: rule.name,
severity: rule.severity,
metric: metrics,
timestamp: Date.now()
});
}
}

if (alerts.length > 0) {
await this.sendAlerts(alerts);
}
}

async sendAlerts(alerts) {
// 发送告警通知
for (const alert of alerts) {
console.log(`Alert: ${alert.rule} - Severity: ${alert.severity}`);

// 这里可以集成 Slack、邮件等通知渠道
await this.notify(alert);
}
}

async notify(alert) {
// 通知实现
const notificationPayload = {
title: `Performance Alert: ${alert.rule}`,
message: `Severity: ${alert.severity}\nValue: ${alert.metric.value}`,
timestamp: new Date(alert.timestamp).toISOString()
};

// 发送到通知服务
// await fetch('/api/notifications', {
// method: 'POST',
// body: JSON.stringify(notificationPayload)
// });
}
}

第三方监控工具集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// 集成第三方监控工具
class ThirdPartyIntegration {
constructor() {
this.integrations = new Map();
this.setupIntegrations();
}

setupIntegrations() {
// Google Analytics 4
if (process.env.GA_MEASUREMENT_ID) {
this.integrations.set('ga4', new GA4Integration());
}

// Sentry 错误监控
if (process.env.SENTRY_DSN) {
this.integrations.set('sentry', new SentryIntegration());
}

// DataDog APM
if (process.env.DATADOG_API_KEY) {
this.integrations.set('datadog', new DatadogIntegration());
}

// New Relic
if (process.env.NEW_RELIC_LICENSE_KEY) {
this.integrations.set('newrelic', new NewRelicIntegration());
}
}

async sendPerformanceMetrics(metrics) {
for (const [name, integration] of this.integrations) {
try {
await integration.sendPerformanceData(metrics);
} catch (error) {
console.error(`Error sending to ${name}:`, error);
}
}
}

async sendError(error) {
for (const [name, integration] of this.integrations) {
try {
await integration.sendError(error);
} catch (err) {
console.error(`Error reporting to ${name}:`, err);
}
}
}
}

// Google Analytics 4 集成
class GA4Integration {
constructor() {
this.measurementId = process.env.GA_MEASUREMENT_ID;
this.apiSecret = process.env.GA_API_SECRET;
}

async sendPerformanceData(metrics) {
// 发送自定义指标到 GA4
if (window.gtag) {
window.gtag('event', 'performance_metric', {
metric_name: metrics.name,
metric_value: metrics.value,
metric_rating: metrics.rating,
page_location: window.location.href,
user_agent: navigator.userAgent
});
}

// 发送 Core Web Vitals
if (metrics.name && ['CLS', 'FID', 'FCP', 'LCP'].includes(metrics.name)) {
window.gtag('event', metrics.name.toLowerCase(), {
value: metrics.value,
event_label: metrics.rating
});
}
}

async sendError(error) {
if (window.gtag) {
window.gtag('event', 'exception', {
description: error.message,
fatal: error.fatal || false
});
}
}
}

// Sentry 集成
class SentryIntegration {
constructor() {
if (window.Sentry) {
window.Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 0.1, // 采样率
integrations: [
new window.Sentry.BrowserTracing(),
new window.Sentry.Replay({
maskAllText: false,
blockAllMedia: false
})
]
});
}
}

async sendPerformanceData(metrics) {
if (window.Sentry) {
const transaction = window.Sentry.startTransaction({
name: `metric-${metrics.name}`,
op: 'web.vital'
});

transaction.setMeasurement(metrics.name, metrics.value, 'millisecond');
transaction.finish();
}
}

async sendError(error) {
if (window.Sentry) {
window.Sentry.captureException(error, {
contexts: {
performance: error.performanceContext
}
});
}
}
}

// DataDog 集成
class DatadogIntegration {
constructor() {
this.applicationId = process.env.DATADOG_APPLICATION_ID;
this.clientToken = process.env.DATADOG_CLIENT_TOKEN;
}

async sendPerformanceData(metrics) {
if (window.DD_RUM) {
window.DD_RUM.addAction('performance-metric', {
name: metrics.name,
value: metrics.value,
rating: metrics.rating,
url: window.location.href
});
}
}

async sendError(error) {
if (window.DD_RUM) {
window.DD_RUM.addError(error);
}
}
}

性能优化策略

基于监控数据的优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// 性能优化建议引擎
class PerformanceOptimizer {
constructor() {
this.optimizationRules = new OptimizationRules();
this.recommendationEngine = new RecommendationEngine();
}

async analyzePerformanceData(metrics) {
const insights = [];

// 分析 Core Web Vitals
const coreVitals = this.extractCoreVitals(metrics);
insights.push(...this.analyzeCoreVitals(coreVitals));

// 分析网络性能
const networkData = this.extractNetworkData(metrics);
insights.push(...this.analyzeNetworkPerformance(networkData));

// 分析资源加载
const resourceData = this.extractResourceData(metrics);
insights.push(...this.analyzeResourcePerformance(resourceData));

// 生成优化建议
const recommendations = await this.recommendationEngine.generate(
insights,
metrics.context
);

return {
insights,
recommendations,
optimizationPlan: this.createOptimizationPlan(recommendations)
};
}

extractCoreVitals(metrics) {
return metrics.filter(m => ['CLS', 'FID', 'FCP', 'LCP', 'TTFB'].includes(m.name));
}

analyzeCoreVitals(vitals) {
const insights = [];

// 分析 CLS
const clsMetrics = vitals.filter(m => m.name === 'CLS');
if (clsMetrics.length > 0) {
const avgCls = clsMetrics.reduce((sum, m) => sum + m.value, 0) / clsMetrics.length;
if (avgCls > 0.1) {
insights.push({
category: 'layout-instability',
severity: 'high',
message: `High CLS detected: ${avgCls.toFixed(3)}`,
suggestions: [
'Add explicit dimensions to images and videos',
'Avoid dynamically injecting content without reservation',
'Use CSS transforms instead of changing layout properties'
]
});
}
}

// 分析 FID
const fidMetrics = vitals.filter(m => m.name === 'FID');
if (fidMetrics.length > 0) {
const avgFid = fidMetrics.reduce((sum, m) => sum + m.value, 0) / fidMetrics.length;
if (avgFid > 100) {
insights.push({
category: 'input-latency',
severity: 'medium',
message: `High FID detected: ${avgFid.toFixed(0)}ms`,
suggestions: [
'Reduce JavaScript execution time',
'Break long tasks into smaller chunks',
'Use web workers for heavy computations',
'Implement progressive loading'
]
});
}
}

// 分析 LCP
const lcpMetrics = vitals.filter(m => m.name === 'LCP');
if (lcpMetrics.length > 0) {
const avgLcp = lcpMetrics.reduce((sum, m) => sum + m.value, 0) / lcpMetrics.length;
if (avgLcp > 2500) {
insights.push({
category: 'loading-performance',
severity: 'medium',
message: `Slow LCP detected: ${avgLcp.toFixed(0)}ms`,
suggestions: [
'Optimize largest contentful paint element',
'Implement resource hints (preload, preconnect)',
'Optimize critical rendering path',
'Use appropriate image formats and sizes'
]
});
}
}

return insights;
}

extractNetworkData(metrics) {
return metrics.filter(m => m.type === 'network-request');
}

analyzeNetworkPerformance(networkData) {
const insights = [];

// 计算平均请求时间
const avgDuration = networkData.reduce((sum, req) => sum + req.duration, 0) / networkData.length;

if (avgDuration > 1000) {
insights.push({
category: 'network-performance',
severity: 'medium',
message: `Slow average network requests: ${avgDuration.toFixed(0)}ms`,
suggestions: [
'Implement CDN for static assets',
'Enable compression (gzip, brotli)',
'Use resource caching strategies',
'Optimize API response times'
]
});
}

// 检查失败请求
const failedRequests = networkData.filter(req => req.status >= 400);
if (failedRequests.length / networkData.length > 0.05) { // 5% 失败率
insights.push({
category: 'network-reliability',
severity: 'high',
message: `High error rate: ${(failedRequests.length / networkData.length * 100).toFixed(1)}%`,
suggestions: [
'Investigate backend service reliability',
'Implement retry mechanisms',
'Add error boundaries',
'Monitor third-party service health'
]
});
}

return insights;
}

createOptimizationPlan(recommendations) {
return {
priority: this.prioritizeRecommendations(recommendations),
timeline: this.estimateImplementationTimeline(recommendations),
expectedImpact: this.calculateExpectedImpact(recommendations),
resourcesNeeded: this.estimateResources(recommendations)
};
}

prioritizeRecommendations(recommendations) {
return recommendations.sort((a, b) => {
// 优先级:严重程度 + 预期影响 + 实现难度的倒数
const priorityA = this.calculatePriorityScore(a);
const priorityB = this.calculatePriorityScore(b);
return priorityB - priorityA;
});
}

calculatePriorityScore(rec) {
const severityScore = { high: 3, medium: 2, low: 1 }[rec.severity] || 1;
const impactScore = rec.expectedImpact || 1;
const difficultyScore = 5 - (rec.implementationDifficulty || 3); // 难度越低分越高

return severityScore * 0.4 + impactScore * 0.4 + difficultyScore * 0.2;
}
}

// 推荐引擎
class RecommendationEngine {
constructor() {
this.machineLearningModel = new PerformanceMLModel();
}

async generate(insights, context) {
// 基于历史数据和 ML 模型生成推荐
const recommendations = [];

for (const insight of insights) {
const mlRecommendation = await this.machineLearningModel.predict(
insight,
context
);

recommendations.push({
...insight,
...mlRecommendation,
confidence: mlRecommendation.confidence || 0.8
});
}

// 去重和合并相似建议
return this.deduplicateRecommendations(recommendations);
}

deduplicateRecommendations(recommendations) {
const seen = new Set();
return recommendations.filter(rec => {
const key = `${rec.category}-${rec.message}`;
if (seen.has(key)) {
return false;
}
seen.add(key);
return true;
});
}
}

// 性能机器学习模型(简化版)
class PerformanceMLModel {
async predict(insight, context) {
// 这里应该是真正的 ML 模型
// 简化实现,基于规则的推荐
switch (insight.category) {
case 'layout-instability':
return {
expectedImpact: 0.25, // 25% 性能提升预期
implementationDifficulty: 2, // 难度等级 1-5
recommendedTechniques: ['css-containment', 'image-dimensions', 'font-loading']
};
case 'input-latency':
return {
expectedImpact: 0.35,
implementationDifficulty: 3,
recommendedTechniques: ['code-splitting', 'web-workers', 'debounce-inputs']
};
case 'loading-performance':
return {
expectedImpact: 0.40,
implementationDifficulty: 2,
recommendedTechniques: ['lazy-loading', 'resource-prioritization', 'caching']
};
default:
return {
expectedImpact: 0.15,
implementationDifficulty: 2,
recommendedTechniques: ['general-optimizations']
};
}
}
}

实时监控与告警

实时数据分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// 实时性能监控面板
class RealTimePerformanceDashboard {
constructor() {
this.websocket = new WebSocket('ws://localhost:8080/performance-stream');
this.metricsBuffer = [];
this.setupWebSocketHandlers();
}

setupWebSocketHandlers() {
this.websocket.onmessage = (event) => {
const metric = JSON.parse(event.data);
this.metricsBuffer.push(metric);

// 保持缓冲区大小
if (this.metricsBuffer.length > 1000) {
this.metricsBuffer = this.metricsBuffer.slice(-500);
}

this.updateDashboard(metric);
this.checkRealTimeAlerts(metric);
};

this.websocket.onclose = () => {
// 重新连接逻辑
setTimeout(() => {
this.websocket = new WebSocket('ws://localhost:8080/performance-stream');
this.setupWebSocketHandlers();
}, 5000);
};
}

updateDashboard(latestMetric) {
// 更新实时指标
this.updateMetricGauges(latestMetric);
this.updateTrendCharts(latestMetric);
this.updateStatusIndicators(latestMetric);
}

updateMetricGauges(metric) {
const gauge = document.getElementById(`gauge-${metric.name}`);
if (gauge) {
const value = metric.value;
const normalizedValue = this.normalizeMetricValue(metric.name, value);

gauge.style.setProperty('--value', normalizedValue);
gauge.querySelector('.value').textContent = value.toFixed(2);

// 更新颜色状态
this.updateGaugeColor(gauge, normalizedValue);
}
}

normalizeMetricValue(metricName, value) {
// 将不同指标标准化到 0-100 范围
switch (metricName) {
case 'CLS':
// CLS 值越小越好,反转并缩放
return Math.min(value * 1000, 100);
case 'FID':
case 'FCP':
case 'LCP':
// 时间指标,越小越好,反转
return Math.min(value / 10, 100);
default:
return Math.min(value, 100);
}
}

updateGaugeColor(gauge, normalizedValue) {
const indicator = gauge.querySelector('.indicator');
if (normalizedValue < 30) {
indicator.className = 'indicator good';
} else if (normalizedValue < 70) {
indicator.className = 'indicator fair';
} else {
indicator.className = 'indicator poor';
}
}

updateTrendCharts(metric) {
// 更新趋势图
const chartContainer = document.getElementById('trend-chart');
if (chartContainer && window.Chart) {
const ctx = chartContainer.getContext('2d');

if (!this.trendChart) {
this.trendChart = new window.Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: metric.name,
data: [],
borderColor: '#36a2eb',
tension: 0.1
}]
},
options: {
responsive: true,
scales: {
y: { beginAtZero: true }
}
}
});
}

// 添加新数据点
this.trendChart.data.labels.push(new Date().toLocaleTimeString());
this.trendChart.data.datasets[0].data.push(metric.value);

// 限制数据显示点数
if (this.trendChart.data.labels.length > 50) {
this.trendChart.data.labels.shift();
this.trendChart.data.datasets[0].data.shift();
}

this.trendChart.update();
}
}

checkRealTimeAlerts(metric) {
// 实时告警检查
const alerts = this.evaluateRealTimeConditions(metric);

alerts.forEach(alert => {
this.triggerAlert(alert);
this.logAlert(alert);
});
}

evaluateRealTimeConditions(metric) {
const alerts = [];

// 定义实时告警条件
if (metric.name === 'CLS' && metric.value > 0.25) {
alerts.push({
type: 'critical-layout-shift',
severity: 'critical',
message: `Critical CLS spike: ${metric.value}`,
metric: metric
});
}

if (metric.name === 'FID' && metric.value > 300) {
alerts.push({
type: 'critical-input-delay',
severity: 'critical',
message: `Critical FID spike: ${metric.value}ms`,
metric: metric
});
}

// 检查错误率突增
if (metric.name === 'error-rate' && metric.value > 0.1) {
alerts.push({
type: 'high-error-rate',
severity: 'critical',
message: `High error rate: ${(metric.value * 100).toFixed(1)}%`,
metric: metric
});
}

return alerts;
}

triggerAlert(alert) {
// 触发告警通知
this.showNotification(alert);
this.sendAlertToChannel(alert);
}

showNotification(alert) {
// 显示浏览器通知
if (Notification.permission === 'granted') {
new Notification(`Performance Alert: ${alert.type}`, {
body: alert.message,
icon: '/favicon.ico'
});
}

// 在页面上显示告警
const alertElement = document.createElement('div');
alertElement.className = `alert alert-${alert.severity}`;
alertElement.innerHTML = `
<strong>${alert.type}</strong>: ${alert.message}
<button onclick="this.parentElement.remove()">×</button>
`;

document.getElementById('alerts-container')?.appendChild(alertElement);
}

sendAlertToChannel(alert) {
// 发送到告警通道(Slack, Teams, 邮件等)
fetch('/api/alerts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(alert)
});
}
}

// 初始化实时监控
const dashboard = new RealTimePerformanceDashboard();

RUM 监控不仅仅是收集数据,更重要的是基于数据采取行动。建立完善的监控体系后,关键是要定期分析数据、识别性能瓶颈,并制定相应的优化策略。

总结

  Real User Monitoring 是现代 Web 应用不可或缺的一部分。通过全面的性能监控,我们能够深入了解真实用户的体验,识别性能瓶颈,并做出数据驱动的优化决策。成功的 RUM 实施需要:

  1. 全面的指标覆盖:从 Core Web Vitals 到用户体验指标,确保关键性能维度都被监控。
  2. 合适的工具选择:根据团队规模、预算和需求选择合适的监控工具或构建自定义系统。
  3. 实时监控能力:及时发现和响应性能问题。
  4. 数据驱动的优化:基于监控数据制定性能优化计划。
  5. 持续改进:建立持续监控和优化的流程。

  随着 Web 技术的不断发展,性能监控也在演进。未来的 RUM 系统将更加智能化,能够自动识别问题、预测性能趋势,并提供更精准的优化建议。

bulb