import i18n from '@/i18n'; // 中间件监控可视化辅助 // Logan.Li export interface MwConfigPublic { username?: string; database?: string; redis_mode?: 'standalone' | 'cluster'; } export interface MwTarget { id: number; name: string; type: string; status: string; ip: string; port: number; description: string; sub_type?: string; config_public?: MwConfigPublic; latest_metrics: Record; last_seen_at?: string; } export interface MwHistoryPoint { timestamp: number; value: number; metric_name?: string; } export type MwTimeRange = '1h' | '6h' | '24h' | '7d'; export const MW_TIME_RANGE_HOURS: Record = { '1h': 1, '6h': 6, '24h': 24, '7d': 168, }; export interface MwChartDef { key: string; label: string; color: string; unit?: string; percent?: boolean; formatter?: (v: number) => string; /** 累计 counter 转周期增量 // Logan.Li */ counterDelta?: boolean; } export type HealthLevel = 'ok' | 'warn' | 'crit'; export interface MwHealthIssue { level: HealthLevel; message: string; } export const REDIS_PRIMARY_KEYS = ['redis_qps', 'redis_used_memory', 'redis_hit_rate', 'redis_connected_clients']; export const MYSQL_PRIMARY_KEYS = ['mysql_qps', 'mysql_connections', 'mysql_buffer_hit_rate', 'mysql_slow_queries']; export const REDIS_CHARTS: MwChartDef[] = [ { key: 'redis_qps', label: 'QPS', color: 'var(--dg-blue)', unit: 'ops/s' }, { key: 'redis_used_memory', label: i18n.t('monitor.mwtargetchartsp.s44cdb3f7'), color: 'var(--dg-red)', formatter: fmtMemory }, { key: 'redis_hit_rate', label: i18n.t('monitor.mwtargetchartsp.s6787355b'), color: 'var(--dg-green)', percent: true }, { key: 'redis_connected_clients', label: i18n.t('kubernetes.topology.connections'), color: 'var(--dg-cyan)' }, { key: 'redis_db_size', label: i18n.t('monitor.helpersts.sc5f65eec'), color: 'var(--dg-purple)' }, { key: 'redis_blocked_clients', label: i18n.t('monitor.helpersts.s2ad27e85'), color: 'var(--dg-orange)' }, ]; export const MYSQL_CHARTS: MwChartDef[] = [ { key: 'mysql_qps', label: 'QPS', color: 'var(--dg-blue)', unit: 'q/s' }, { key: 'mysql_connections', label: i18n.t('kubernetes.topology.connections'), color: 'var(--dg-cyan)' }, { key: 'mysql_threads_running', label: i18n.t('monitor.mwtargetchartsp.se0f6abb7'), color: 'var(--dg-purple)' }, { key: 'mysql_buffer_hit_rate', label: i18n.t('monitor.mwtargetchartsp.sa2d02e56'), color: 'var(--dg-green)', percent: true }, { key: 'mysql_slow_queries', label: i18n.t('monitor.helpersts.sbd73bca3'), color: 'var(--dg-orange)', unit: i18n.t('monitor.helpersts.s3878fe68'), counterDelta: true }, { key: 'mysql_bytes_sent', label: i18n.t('monitor.mwtargetchartsp.s90d6afb7'), color: 'var(--dg-amber)', formatter: fmtMemory }, ]; export function fmtMemory(bytes: number): string { if (bytes == null || bytes === 0) return '--'; if (bytes > 1e9) return `${(bytes / 1e9).toFixed(1)} GB`; if (bytes > 1e6) return `${(bytes / 1e6).toFixed(1)} MB`; if (bytes > 1e3) return `${(bytes / 1e3).toFixed(1)} KB`; return `${bytes} B`; } export function fmtHitRate(v: number): string { if (v == null || Number.isNaN(v)) return '--'; return `${(v * 100).toFixed(1)}%`; } export function fmtVal(v: number, precision = 0): string { if (v == null || Number.isNaN(v)) return '--'; return v.toFixed(precision); } export function fmtChartTime(ts: number): string { const d = new Date(ts * 1000); const mm = String(d.getMonth() + 1).padStart(2, '0'); const dd = String(d.getDate()).padStart(2, '0'); const hh = String(d.getHours()).padStart(2, '0'); const mi = String(d.getMinutes()).padStart(2, '0'); return `${mm}-${dd} ${hh}:${mi}`; } export function fmtMetricDisplay(def: MwChartDef, value: number): string { if (def.formatter) return def.formatter(value); if (def.percent) return fmtHitRate(value); return fmtVal(value, def.key.includes('qps') ? 1 : 0); } export function statusColor(status: string): string { switch (status) { case 'online': return 'var(--dg-green)'; case 'warning': return 'var(--dg-yellow)'; case 'error': case 'offline': return 'var(--dg-red)'; default: return 'var(--dg-text-muted)'; } } export function statusTextClass(status: string): string { switch (status) { case 'online': return 'dg-text-green'; case 'warning': return 'dg-text-yellow'; case 'error': case 'offline': return 'dg-text-red'; default: return 'dg-text-muted'; } } export function statusBg(status: string): string { switch (status) { case 'online': return 'bg-emerald-400'; case 'warning': return 'bg-amber-400'; case 'error': case 'offline': return 'bg-red-400'; default: return 'bg-[var(--dg-bg-active)]'; } } export function redisModeLabel(mode?: string): string { return mode === 'cluster' ? i18n.t('k8s.topology.typeCluster') : i18n.t('monitor.helpersts.s047499fc'); } export function redisMemoryPct(metrics: Record): number | null { const used = metrics.redis_used_memory; const max = metrics.redis_maxmemory; if (!used || !max || max <= 0) return null; return Math.min(100, (used / max) * 100); } export function mysqlConnPct(metrics: Record): number | null { const conn = metrics.mysql_connections; const max = metrics.mysql_max_connections; if (!conn || !max || max <= 0) return null; return Math.min(100, (conn / max) * 100); } /** 累计 counter 转周期增量(如慢查询)// Logan.Li */ export function counterToDelta( points: Array<{ timestamp: number; value: number }>, ): Array<{ timestamp: number; value: number }> { if (points.length < 2) return []; const out: Array<{ timestamp: number; value: number }> = []; for (let i = 1; i < points.length; i++) { const prev = points[i - 1]; const cur = points[i]; const delta = cur.value - prev.value; if (delta >= 0) out.push({ timestamp: cur.timestamp, value: delta }); } return out; } /** 按指标名分组历史时序 // Logan.Li */ export function groupHistoryByMetric( rows: Array<{ metric_name: string; timestamp: number; value: number }>, ): Record> { const out: Record> = {}; for (const row of rows) { if (!out[row.metric_name]) out[row.metric_name] = []; out[row.metric_name].push({ timestamp: row.timestamp, value: row.value }); } for (const key of Object.keys(out)) { out[key].sort((a, b) => a.timestamp - b.timestamp); } return out; } export function gaugeTone(pct: number, warn = 70, crit = 85): HealthLevel { if (pct >= crit) return 'crit'; if (pct >= warn) return 'warn'; return 'ok'; } export function gaugeToneLow(pct: number, warn = 90, crit = 80): HealthLevel { if (pct < crit) return 'crit'; if (pct < warn) return 'warn'; return 'ok'; } export function healthLevelColor(level: HealthLevel): string { if (level === 'crit') return 'var(--dg-red)'; if (level === 'warn') return 'var(--dg-yellow)'; return 'var(--dg-green)'; } /** 评估实例健康告警 // Logan.Li */ export function evalMwHealth(target: MwTarget): MwHealthIssue[] { const m = target.latest_metrics || {}; const issues: MwHealthIssue[] = []; if (target.status !== 'online') { issues.push({ level: 'crit', message: i18n.t('monitor.helpersts.s4ace6502') }); return issues; } if (target.description === 'redis') { if (m.redis_hit_rate > 0 && m.redis_hit_rate < 0.8) issues.push({ level: 'crit', message: i18n.t('monitor.helpersts.sff1d979b') }); else if (m.redis_hit_rate > 0 && m.redis_hit_rate < 0.9) issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.s11902306') }); const memPct = redisMemoryPct(m); if (memPct != null && memPct > 90) issues.push({ level: 'crit', message: i18n.t('monitor.helpersts.s07eee3b9') }); else if (memPct != null && memPct > 80) issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.sbb098add') }); if (m.redis_blocked_clients > 0) issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.s2ad27e85') }); if (m.redis_evicted_keys > 0) issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.sc4db73a4') }); } else if (target.description === 'mysql') { if (m.mysql_buffer_hit_rate > 0 && m.mysql_buffer_hit_rate < 0.9) { issues.push({ level: 'crit', message: i18n.t('monitor.helpersts.s99844184') }); } else if (m.mysql_buffer_hit_rate > 0 && m.mysql_buffer_hit_rate < 0.95) { issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.s41ec17d6') }); } const connPct = mysqlConnPct(m); if (connPct != null && connPct > 90) issues.push({ level: 'crit', message: i18n.t('monitor.helpersts.s5b95d546') }); else if (connPct != null && connPct > 80) issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.se0b952ab') }); if (m.mysql_threads_running > 20) issues.push({ level: 'warn', message: i18n.t('monitor.helpersts.s56174d14') }); } return issues; } export function worstHealthLevel(issues: MwHealthIssue[]): HealthLevel { if (issues.some((i) => i.level === 'crit')) return 'crit'; if (issues.some((i) => i.level === 'warn')) return 'warn'; return 'ok'; } export function fmtLastSeen(iso?: string): string { if (!iso) return '--'; const d = new Date(iso); if (Number.isNaN(d.getTime())) return '--'; const diff = Math.floor((Date.now() - d.getTime()) / 1000); if (diff < 60) return `${diff}s 前`; if (diff < 3600) return `${Math.floor(diff / 60)}m 前`; if (diff < 86400) return `${Math.floor(diff / 3600)}h 前`; return `${d.getMonth() + 1}/${d.getDate()} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; }