/** * Health Checker + SLO Monitor — 健康检查器与 SLO 监控 * * HealthChecker: 对数据库连通性、磁盘空间、内存使用执行真实检查。 * SLOMonitor: C-9 修复 — 补全 SLO 监控核心功能(错误率、延迟分布、吞吐量、燃烧速率)。 * * @see docs/生产级通用 AI Agent 智能体桌面应用:完整设计与构建指南.html — 第十一章 */ import { existsSync } from 'fs'; import { app } from 'electron'; import type Database from 'better-sqlite3'; export interface HealthReport { healthy: boolean; checks: HealthCheck[]; timestamp: Date; } export interface HealthCheck { name: string; healthy: boolean; latencyMs?: number; error?: string; } export class HealthChecker { /** * @param getDB 数据库获取器(用于真实验证数据库连通性) * @param dbPath 数据库文件路径(后备:文件存在性检查) */ constructor( private getDB?: () => Database.Database, private dbPath?: string, ) {} async check(): Promise { const checks: HealthCheck[] = []; checks.push(await this.checkDatabase()); checks.push(await this.checkFreeMemory()); checks.push(await this.checkMemoryUsage()); const allHealthy = checks.every((c) => c.healthy); return { healthy: allHealthy, checks, timestamp: new Date() }; } private async checkDatabase(): Promise { const start = Date.now(); try { // 优先使用注入的 DB 实例执行真实 ping if (this.getDB) { const db = this.getDB(); // 执行真实查询验证数据库连通性 db.prepare('SELECT 1').get(); return { name: 'database', healthy: true, latencyMs: Date.now() - start }; } // 后备:检查数据库文件是否存在 if (this.dbPath && existsSync(this.dbPath)) { return { name: 'database', healthy: true, latencyMs: Date.now() - start }; } return { name: 'database', healthy: false, error: 'Database not available' }; } catch (error) { return { name: 'database', healthy: false, error: error instanceof Error ? error.message : String(error), }; } } private async checkFreeMemory(): Promise { try { const userDataPath = app.getPath('userData'); if (!existsSync(userDataPath)) { return { name: 'free_memory', healthy: false, error: 'User data path not accessible' }; } // 检查实际可用内存(Windows/Linux 返回字节数) const { freemem } = await import('os'); const freeBytes = freemem(); const freeMB = freeBytes / (1024 * 1024); // 少于 200MB 视为不健康 if (freeMB < 200) { return { name: 'free_memory', healthy: false, error: `Free memory critically low: ${freeMB.toFixed(0)}MB available`, }; } return { name: 'free_memory', healthy: true }; } catch (error) { return { name: 'free_memory', healthy: false, error: error instanceof Error ? error.message : String(error), }; } } private async checkMemoryUsage(): Promise { const used = process.memoryUsage(); const heapUsedMB = used.heapUsed / 1024 / 1024; const thresholdMB = 512; return { name: 'memory_usage', healthy: heapUsedMB < thresholdMB, ...(heapUsedMB >= thresholdMB ? { error: `Heap usage ${heapUsedMB.toFixed(0)}MB exceeds threshold ${thresholdMB}MB` } : {}), }; } } // ===== C-9 修复: SLO Monitor — SLO 监控核心功能 ===== /** * SLO 监控配置 */ export interface SLOConfig { /** SLO 目标(如 0.999 表示 99.9% 可用性) */ target: number; /** 滑动窗口大小(毫秒,默认 5 分钟) */ windowMs: number; /** 延迟分位数(如 0.95 表示 P95) */ latencyPercentiles: number[]; /** 延迟告警阈值(毫秒) */ latencyThresholdMs: number; } /** * SLO 请求记录 */ interface SLORequestRecord { timestamp: number; latencyMs: number; success: boolean; } /** * SLO 状态报告 */ export interface SLOStatus { /** 当前错误率(0-1) */ errorRate: number; /** 当前吞吐量(请求/秒) */ throughput: number; /** 平均延迟(毫秒) */ avgLatencyMs: number; /** 延迟分位数 */ percentiles: Record; /** 燃烧速率(实际错误率 / 错误预算) */ burnRate: number; /** SLO 目标 */ target: number; /** 错误预算(1 - target) */ errorBudget: number; /** 是否违反 SLO */ violated: boolean; /** 窗口内总请求数 */ totalRequests: number; /** 窗口内错误请求数 */ errorRequests: number; /** 时间戳 */ timestamp: Date; } /** * SLO Monitor — SLO 监控器 * * C-9 修复: 补全 SLO 监控核心功能 * * 功能: * 1. 错误率追踪 — 记录请求成功/失败,计算滑动窗口内错误率 * 2. 延迟分布追踪 — 记录请求延迟,计算 P50/P95/P99 分位数 * 3. 吞吐量追踪 — 计算每秒请求数 * 4. 燃烧速率计算 — 实际错误率 / 错误预算,>1 表示 SLO 即将违反 * * @see docs/生产级通用 AI Agent 智能体桌面应用:完整设计与构建指南.html — 第十一章 */ export class SLOMonitor { private records: SLORequestRecord[] = []; private readonly config: SLOConfig; constructor(config: Partial = {}) { this.config = { target: 0.999, windowMs: 5 * 60 * 1000, // 5 分钟 latencyPercentiles: [0.5, 0.95, 0.99], latencyThresholdMs: 5000, ...config, }; } /** * 记录一次请求 * @param latencyMs 延迟(毫秒) * @param success 是否成功 */ recordRequest(latencyMs: number, success: boolean): void { const record: SLORequestRecord = { timestamp: Date.now(), latencyMs, success, }; this.records.push(record); // 清理过期记录 this.cleanup(); } /** * 清理窗口外的过期记录 */ private cleanup(): void { const cutoff = Date.now() - this.config.windowMs; // 保留最近 10 分钟的数据(2 倍窗口),避免边界效应 this.records = this.records.filter((r) => r.timestamp >= cutoff); } /** * 获取当前 SLO 状态 */ getStatus(): SLOStatus { this.cleanup(); const now = Date.now(); const windowStart = now - this.config.windowMs; const windowRecords = this.records.filter((r) => r.timestamp >= windowStart); const totalRequests = windowRecords.length; const errorRequests = windowRecords.filter((r) => !r.success).length; const errorRate = totalRequests > 0 ? errorRequests / totalRequests : 0; const throughput = totalRequests / (this.config.windowMs / 1000); const latencies = windowRecords.map((r) => r.latencyMs).sort((a, b) => a - b); const avgLatencyMs = latencies.length > 0 ? latencies.reduce((sum, l) => sum + l, 0) / latencies.length : 0; const percentiles: Record = {}; for (const p of this.config.latencyPercentiles) { const key = `P${(p * 100).toFixed(0)}`; percentiles[key] = this.calculatePercentile(latencies, p); } const errorBudget = 1 - this.config.target; const burnRate = errorBudget > 0 ? errorRate / errorBudget : 0; return { errorRate, throughput, avgLatencyMs, percentiles, burnRate, target: this.config.target, errorBudget, violated: burnRate > 1, totalRequests, errorRequests, timestamp: new Date(), }; } /** * 计算分位数 */ private calculatePercentile(sortedValues: number[], percentile: number): number { if (sortedValues.length === 0) return 0; const index = Math.ceil(percentile * sortedValues.length) - 1; return sortedValues[Math.max(0, index)]; } /** * 重置所有记录 */ reset(): void { this.records = []; } /** * 获取配置 */ getConfig(): SLOConfig { return { ...this.config }; } }