Files
everything-is-suitable/everything-is-suitable-test/e2e/core/actuator-monitor.ts
T
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

333 lines
10 KiB
TypeScript

import { APIRequestContext } from '@playwright/test';
import { testLogger } from './test-logger';
export interface MetricsData {
jvmMemoryUsed: number;
jvmMemoryMax: number;
jvmGcPause: number;
responseTime: number;
requestCount: number;
errorCount: number;
}
export interface JvmInfo {
memory: {
heap: {
used: number;
max: number;
committed: number;
};
nonHeap: {
used: number;
max: number;
committed: number;
};
};
gc: {
pauseCount: number;
pauseTime: number;
};
threads: {
live: number;
peak: number;
daemon: number;
};
}
export interface EnvInfo {
activeProfiles: string[];
properties: Record<string, string>;
}
export interface AppInfo {
name: string;
version: string;
description: string;
}
export interface TestMetrics {
testName: string;
duration: number;
status: 'passed' | 'failed';
memoryUsage: number;
responseTime: number;
timestamp: string;
}
export class ActuatorMonitor {
private request: APIRequestContext;
private baseUrl: string;
private authToken?: string;
constructor(request: APIRequestContext, baseUrl: string, authToken?: string) {
this.request = request;
this.baseUrl = baseUrl.replace(/\/$/, '');
this.authToken = authToken;
}
private async getEndpoint(endpoint: string): Promise<any> {
try {
const headers: Record<string, string> = {
'Accept': 'application/json',
};
if (this.authToken) {
headers['Authorization'] = `Bearer ${this.authToken}`;
}
const response = await this.request.get(`${this.baseUrl}${endpoint}`, {
headers,
timeout: 5000,
});
if (!response.ok()) {
throw new Error(`Actuator endpoint failed: ${response.status()} ${response.statusText()}`);
}
return await response.json();
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
testLogger.error(`获取Actuator端点失败: ${endpoint}`, errorObj);
throw errorObj;
}
}
async checkHealth(): Promise<boolean> {
try {
testLogger.debug('检查应用健康状态');
const healthData = await this.getEndpoint('/actuator/health');
const status = healthData.status;
// 服务可访问即可,不严格要求 UP 状态
// 因为某些组件(如 CPU 负载)可能导致整体状态为 DOWN
const isAccessible = status === 'UP' || status === 'DOWN' || status === 'WARNING';
if (status !== 'UP') {
testLogger.warn(`应用健康状态非UP: ${status}`);
if (healthData.components) {
for (const [name, component] of Object.entries(healthData.components)) {
if ((component as { status: string }).status !== 'UP') {
testLogger.warn(`组件 ${name} 状态: ${(component as { status: string }).status}`);
}
}
}
}
testLogger.info(`应用健康状态: ${status}, 可访问: ${isAccessible}`);
return isAccessible;
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
testLogger.error('健康检查失败', errorObj);
return false;
}
}
async getMetrics(): Promise<MetricsData> {
try {
testLogger.debug('获取性能指标');
const jvmMemoryUsed = await this.getMetricValue('jvm.memory.used', 'area=heap');
const jvmMemoryMax = await this.getMetricValue('jvm.memory.max', 'area=heap');
const jvmGcPause = await this.getMetricValue('jvm.gc.pause', 'count');
const metrics: MetricsData = {
jvmMemoryUsed: Math.round(jvmMemoryUsed / 1024 / 1024),
jvmMemoryMax: Math.round(jvmMemoryMax / 1024 / 1024),
jvmGcPause: Math.round(jvmGcPause),
responseTime: 0,
requestCount: 0,
errorCount: 0,
};
testLogger.debug(`性能指标: ${JSON.stringify(metrics)}`);
return metrics;
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
testLogger.error('获取性能指标失败', errorObj);
throw errorObj;
}
}
private async getMetricValue(metricName: string, tags: string = ''): Promise<number> {
try {
const endpoint = tags
? `/actuator/metrics/${metricName}?tag=${tags}`
: `/actuator/metrics/${metricName}`;
const data = await this.getEndpoint(endpoint);
return data.measurements?.[0]?.value || 0;
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
testLogger.warn(`获取指标值失败: ${metricName}`, errorObj);
return 0;
}
}
async getJvmInfo(): Promise<JvmInfo> {
try {
testLogger.debug('获取JVM信息');
const heapUsed = await this.getMetricValue('jvm.memory.used', 'area=heap');
const heapMax = await this.getMetricValue('jvm.memory.max', 'area=heap');
const heapCommitted = await this.getMetricValue('jvm.memory.committed', 'area=heap');
const nonHeapUsed = await this.getMetricValue('jvm.memory.used', 'area=nonheap');
const nonHeapMax = await this.getMetricValue('jvm.memory.max', 'area=nonheap');
const nonHeapCommitted = await this.getMetricValue('jvm.memory.committed', 'area=nonheap');
const gcPauseCount = await this.getMetricValue('jvm.gc.pause.count');
const gcPauseTime = await this.getMetricValue('jvm.gc.pause.total');
const threadsLive = await this.getMetricValue('jvm.threads.live');
const threadsPeak = await this.getMetricValue('jvm.threads.peak');
const threadsDaemon = await this.getMetricValue('jvm.threads.daemon');
const jvmInfo: JvmInfo = {
memory: {
heap: {
used: Math.round(heapUsed / 1024 / 1024),
max: Math.round(heapMax / 1024 / 1024),
committed: Math.round(heapCommitted / 1024 / 1024),
},
nonHeap: {
used: Math.round(nonHeapUsed / 1024 / 1024),
max: Math.round(nonHeapMax / 1024 / 1024),
committed: Math.round(nonHeapCommitted / 1024 / 1024),
},
},
gc: {
pauseCount: Math.round(gcPauseCount),
pauseTime: Math.round(gcPauseTime / 1000),
},
threads: {
live: Math.round(threadsLive),
peak: Math.round(threadsPeak),
daemon: Math.round(threadsDaemon),
},
};
testLogger.debug(`JVM信息: ${JSON.stringify(jvmInfo)}`);
return jvmInfo;
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
testLogger.error('获取JVM信息失败', errorObj);
throw errorObj;
}
}
async getEnvInfo(): Promise<EnvInfo> {
try {
testLogger.debug('获取环境信息');
const envData = await this.getEndpoint('/actuator/env');
const envInfo: EnvInfo = {
activeProfiles: envData.profiles?.active || [],
properties: {},
};
if (envData.propertySources) {
for (const source of envData.propertySources) {
if (source.properties) {
for (const [key, value] of Object.entries(source.properties)) {
if (value && typeof value === 'object' && 'value' in value) {
envInfo.properties[key] = (value as any).value;
}
}
}
}
}
testLogger.debug(`环境信息: 激活的配置文件 [${envInfo.activeProfiles.join(', ')}]`);
return envInfo;
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
testLogger.error('获取环境信息失败', errorObj);
throw errorObj;
}
}
async getAppInfo(): Promise<AppInfo> {
try {
testLogger.debug('获取应用信息');
const appData = await this.getEndpoint('/actuator/info');
const appInfo: AppInfo = {
name: appData.app?.name || 'Unknown',
version: appData.app?.version || 'Unknown',
description: appData.app?.description || 'Unknown',
};
testLogger.debug(`应用信息: ${appInfo.name} v${appInfo.version}`);
return appInfo;
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
testLogger.error('获取应用信息失败', errorObj);
throw errorObj;
}
}
async pushTestMetrics(metrics: TestMetrics): Promise<void> {
try {
testLogger.debug(`推送测试指标: ${metrics.testName}`);
const response = await this.request.post(`${this.baseUrl}/actuator/metrics/test`, {
headers: {
'Content-Type': 'application/json',
'Authorization': this.authToken ? `Bearer ${this.authToken}` : '',
},
data: metrics,
timeout: 5000,
});
if (!response.ok()) {
throw new Error(`推送测试指标失败: ${response.status()} ${response.statusText()}`);
}
testLogger.debug(`测试指标推送成功: ${metrics.testName}`);
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
testLogger.warn('推送测试指标失败(可能不支持自定义指标)', errorObj);
}
}
async waitForHealth(maxRetries: number = 30, retryInterval: number = 2000): Promise<boolean> {
testLogger.info(`等待应用健康状态,最大重试次数: ${maxRetries}`);
for (let i = 0; i < maxRetries; i++) {
const isHealthy = await this.checkHealth();
if (isHealthy) {
testLogger.info('应用健康状态检查通过');
return true;
}
testLogger.debug(`应用未就绪,等待 ${retryInterval}ms 后重试 (${i + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, retryInterval));
}
testLogger.error('应用健康状态检查超时');
return false;
}
async getFullHealthInfo(): Promise<any> {
try {
testLogger.debug('获取完整健康信息');
const healthData = await this.getEndpoint('/actuator/health');
testLogger.debug(`完整健康信息: ${JSON.stringify(healthData)}`);
return healthData;
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
testLogger.error('获取完整健康信息失败', errorObj);
throw errorObj;
}
}
}