Files
aiops/frontend/src/pages/monitor/host-monitor/utils.ts
T
LoganandClaude 471353e3d7 feat: 监控告警体系统一重构 + 自动化任务调度引擎 + 监控大屏拆分
告警中心
- 新增统一告警(alert_unified)、监控告警规则(monitor_alert_rule)、指标目录(alert_metric_catalog)
- 通知策略与消息分发(alert_notify_policy/dispatch/message)、静默规则
- 告警引擎按阈值/SNMP 拆分(alert_engine_monitor/snmp)

监控采集
- SNMP 常量/模板与历史指标(snmp templates/constants、snmp_metrics_history、snmp_collect)
- 中间件监控 Redis 采集与种子(middleware_monitor_redis/seed)
- 站点探测 pkg(siteprobe) + 站点视图(site_view)

自动化任务
- cron 调度器(cronscheduler)、脚本运行器(scriptrunner)、系统任务(systask)及运行记录(cron_job_run、system_task)

审计与凭证
- 审计设置(audit_setting) + 历史清理(auditcleaner)

前端
- 监控页面(host/middleware/site/snmp)拆分为子模块并大屏化
- 告警中心多 Tab(规则/指标目录/通知策略/静默)、任务管理组件重构、Agent CLI Markdown 渲染
- 移除旧脚本卡片/详情/日志组件,统一为 ScriptTable/ScriptCodeEditor/TerminalOutput

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-01 23:44:45 +08:00

198 lines
8.2 KiB
TypeScript

// 主机监控工具函数 // Logan.Li
import type { BucketPoint, HostMonitorTimeRange } from './types';
import type { JsonData } from '@/types/common';
/** 按时间跨度格式化 X 轴标签(避免 7 天范围只显示时分) // Logan.Li */
export function formatAxisTime(ts: number, spanMinutes: number): string {
const d = new Date(ts * 1000);
if (spanMinutes <= 180) {
return d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false });
}
if (spanMinutes <= 2880) {
return d.toLocaleString('zh-CN', {
month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false,
});
}
return d.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' });
}
function recordTimestamp(r: JsonData): number {
const raw = r.report_time || r.created_at;
if (!raw) return 0;
const t = new Date(raw as string).getTime();
return Number.isNaN(t) ? 0 : t / 1000;
}
/** 客户端二次过滤,确保图表与所选时间一致 // Logan.Li */
export function filterHistoryByRange(history: JsonData[], range: HostMonitorTimeRange): JsonData[] {
if (!history.length) return history;
const now = Date.now();
let sinceMs: number;
let untilMs = now;
if (range.preset === 'custom' && range.start && range.end) {
sinceMs = new Date(range.start).getTime();
untilMs = new Date(range.end).getTime();
} else {
const mins = range.minutes ?? 60;
sinceMs = now - mins * 60_000;
}
return history.filter((r) => {
const ts = recordTimestamp(r) * 1000;
return ts >= sinceMs && ts <= untilMs;
});
}
export function bucketWithStats(
raw: number[],
timestamps: number[],
targetBuckets: number,
spanMinutes: number,
): BucketPoint[] {
if (!raw || raw.length === 0) return [];
const perBucket = Math.max(1, Math.ceil(raw.length / targetBuckets));
const result: BucketPoint[] = [];
for (let i = 0; i < raw.length; i += perBucket) {
const slice = raw.slice(i, i + perBucket);
const tsSlice = timestamps.slice(i, i + perBucket);
const sum = slice.reduce((a, b) => a + b, 0);
const avg = sum / slice.length;
const max = Math.max(...slice);
const min = Math.min(...slice);
const ts = tsSlice[Math.floor(tsSlice.length / 2)] || 0;
result.push({
time: formatAxisTime(ts, spanMinutes),
timestamp: ts,
avg: Number(avg.toFixed(2)),
max: Number(max.toFixed(2)),
min: Number(min.toFixed(2)),
count: slice.length,
});
}
return result.slice(-targetBuckets);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function parseJSON(raw: unknown): JsonData {
if (!raw) return {};
if (typeof raw === 'object') return raw;
try { return JSON.parse(raw as string); } catch { return {}; }
}
export function formatBPS(bps: number): string {
if (bps >= 1024 * 1024 * 1024) return `${(bps / 1024 / 1024 / 1024).toFixed(1)} GB/s`;
if (bps >= 1024 * 1024) return `${(bps / 1024 / 1024).toFixed(1)} MB/s`;
if (bps >= 1024) return `${(bps / 1024).toFixed(1)} KB/s`;
return `${bps.toFixed(1)} B/s`;
}
interface TimeConfig { buckets: number; spanMinutes: number; }
export function buildMetricsFromHistory(history: JsonData[], timeConfig: TimeConfig) {
const spanMinutes = timeConfig.spanMinutes;
const bucket = (raw: number[], ts: number[]) =>
bucketWithStats(raw, ts, timeConfig.buckets, spanMinutes);
const rawCpu: number[] = []; const rawMem: number[] = []; const rawLoad1: number[] = [];
const rawDisk: number[] = []; const rawTcp: number[] = []; const rawTw: number[] = []; const rawCw: number[] = [];
const rawRx: number[] = []; const rawTx: number[] = [];
const rawIoR: number[] = []; const rawIoW: number[] = [];
const rawSwap: number[] = []; const rawFD: number[] = [];
const rawIOWait: number[] = []; const rawMemAvail: number[] = [];
const rawIoUtil: number[] = []; const rawConnTotal: number[] = [];
const rawRxErr: number[] = []; const rawTxErr: number[] = [];
const rawInode: number[] = [];
const timestamps: number[] = [];
for (const r of history) {
const ts = recordTimestamp(r);
timestamps.push(ts);
rawCpu.push(r.cpu_usage || 0);
rawIOWait.push(r.cpu_iowait || 0);
rawMem.push(r.mem_usage || 0);
rawMemAvail.push(r.mem_available || 0);
rawLoad1.push(r.load1 || 0);
rawDisk.push(r.disk_usage || 0);
const diskInfoArr = parseJSON(r.disk_info);
if (Array.isArray(diskInfoArr) && diskInfoArr.length > 0) {
const rootDisk = diskInfoArr.find((d: JsonData) => d.mount === '/') || diskInfoArr[0];
rawInode.push(rootDisk?.inode_usage_pct || 0);
} else {
rawInode.push(0);
}
const net = parseJSON(r.net_info);
rawTcp.push(net.tcp_established || 0);
rawTw.push(net.tcp_time_wait || 0);
rawCw.push(net.tcp_close_wait || 0);
rawConnTotal.push((net.tcp_established || 0) + (net.tcp_time_wait || 0) + (net.tcp_close_wait || 0));
rawRx.push(net.net_rx_bps || 0);
rawTx.push(net.net_tx_bps || 0);
rawRxErr.push(net.net_rx_errors || 0);
rawTxErr.push(net.net_tx_errors || 0);
const dio = parseJSON(r.disk_io_info);
rawIoR.push(dio.read_bps || 0);
rawIoW.push(dio.write_bps || 0);
rawIoUtil.push(dio.util || 0);
const swap = parseJSON(r.swap_info);
rawSwap.push(swap?.usage_percent || 0);
const fd = parseJSON(r.fd_info);
rawFD.push(fd?.usage_pct || 0);
}
return {
cpu: bucket(rawCpu, timestamps),
mem: bucket(rawMem, timestamps),
load1: bucket(rawLoad1, timestamps),
disk: bucket(rawDisk, timestamps),
tcpEst: bucket(rawTcp, timestamps),
tcpTW: bucket(rawTw, timestamps),
tcpCW: bucket(rawCw, timestamps),
rx: bucket(rawRx.map((v) => v / 1024), timestamps),
tx: bucket(rawTx.map((v) => v / 1024), timestamps),
ioRead: bucket(rawIoR.map((v) => v / 1024), timestamps),
ioWrite: bucket(rawIoW.map((v) => v / 1024), timestamps),
swap: bucket(rawSwap, timestamps),
fd: bucket(rawFD, timestamps),
iowait: bucket(rawIOWait, timestamps),
memAvail: bucket(rawMemAvail, timestamps),
ioUtil: bucket(rawIoUtil, timestamps),
connTotal: bucket(rawConnTotal, timestamps),
rxErr: bucket(rawRxErr, timestamps),
txErr: bucket(rawTxErr, timestamps),
inode: bucket(rawInode, timestamps),
};
}
/** 从过滤后历史计算实际数据时间跨度(用于展示) // Logan.Li */
export function getActualDataSpan(history: JsonData[], range: HostMonitorTimeRange): string | null {
if (!history.length) return null;
const ts = history.map(recordTimestamp).filter((t) => t > 0);
if (!ts.length) return null;
const min = Math.min(...ts);
const max = Math.max(...ts);
const spanMin = Math.max(1, Math.round((max - min) / 60));
return `${formatAxisTime(min, spanMin)} — ${formatAxisTime(max, spanMin)} · ${history.length} 点`;
}
export function getCurrentVal(latest: JsonData, netInfo: JsonData, diskIOInfo: JsonData, swapInfo: JsonData, fdInfo: JsonData,
key: string, fallback = '--',
): string {
if (!latest) return fallback;
if (key === 'cpu') return latest.cpu_usage != null ? `${latest.cpu_usage.toFixed(1)}%` : fallback;
if (key === 'mem') return latest.mem_usage != null ? `${latest.mem_usage.toFixed(1)}%` : fallback;
if (key === 'disk') return latest.disk_usage != null ? `${latest.disk_usage.toFixed(1)}%` : fallback;
if (key === 'load1') return latest.load1 != null ? String(latest.load1) : fallback;
if (key === 'tcp') return netInfo.tcp_established != null ? String(netInfo.tcp_established) : fallback;
if (key === 'tw') return netInfo.tcp_time_wait != null ? String(netInfo.tcp_time_wait) : fallback;
if (key === 'rx') return netInfo.net_rx_bps != null ? `${(netInfo.net_rx_bps / 1024).toFixed(1)} KB/s` : fallback;
if (key === 'tx') return netInfo.net_tx_bps != null ? `${(netInfo.net_tx_bps / 1024).toFixed(1)} KB/s` : fallback;
if (key === 'ioR') return diskIOInfo.read_bps != null ? `${formatBPS(diskIOInfo.read_bps)}` : fallback;
if (key === 'ioW') return diskIOInfo.write_bps != null ? `${formatBPS(diskIOInfo.write_bps)}` : fallback;
if (key === 'swap') return swapInfo?.usage_percent != null ? `${swapInfo.usage_percent.toFixed(1)}%` : fallback;
if (key === 'fd') return fdInfo?.usage_pct != null ? `${fdInfo.usage_pct.toFixed(1)}%` : fallback;
return fallback;
}