Files
SenMeshNet/electron/bridges/inspection/aggregationEngine.cjs

262 lines
7.7 KiB
JavaScript
Raw Permalink Normal View History

"use strict";
/**
* 聚合规则存储 + 聚合升级引擎
*
* 规则结构:
* {
* id: "agg_core_switch_offline",
* name: "核心交换机批量离线升级",
* group_by: ["catalog_type", "tier"], // 维度(D6:catalog_type + tier)
* window_minutes: 5,
* threshold_count: 3,
* alert_type_filter: ["connectivity"],
* level_filter: ["critical"],
* target_level: "critical",
* enabled: true,
* }
*
* 触发方式:实时(D2)—— 每来一条新告警就检查窗口内计数
*/
const { STORAGE_KEY_AGGREGATION_RULES } = require("./storageKeys.cjs");
let _rules = null;
function _ensureLoaded(readFn) {
if (_rules === null) {
const raw = readFn(STORAGE_KEY_AGGREGATION_RULES);
_rules = raw ? _safeParse(raw, []) : _getDefaultRules();
}
}
function _safeParse(text, def) {
if (text === null || text === undefined) return def;
if (typeof text === "object") return text;
try { return JSON.parse(text); }
catch { return def; }
}
function _save(writeFn) {
writeFn(STORAGE_KEY_AGGREGATION_RULES, JSON.stringify(_rules));
}
function _getDefaultRules() {
const now = Date.now();
return [
{
id: "agg_core_switch_offline",
name: "核心设备批量离线升级",
group_by: ["catalog_type", "tier"],
window_minutes: 5,
threshold_count: 3,
alert_type_filter: ["connectivity"],
level_filter: ["critical"],
target_level: "critical",
enabled: true,
builtin: true,
created_at: now,
updated_at: now,
},
{
id: "agg_core_performance_spike",
name: "核心设备性能批量异常升级",
group_by: ["catalog_type", "tier"],
window_minutes: 10,
threshold_count: 5,
alert_type_filter: ["performance_high"],
level_filter: ["warning", "critical"],
target_level: "critical",
enabled: true,
builtin: true,
created_at: now,
updated_at: now,
},
];
}
// ---------- 规则 CRUD ----------
function listRules(readFn) {
_ensureLoaded(readFn);
return [..._rules];
}
function getRule(readFn, ruleId) {
_ensureLoaded(readFn);
return _rules.find((r) => r.id === ruleId) || null;
}
function createRule(readFn, writeFn, data) {
_ensureLoaded(readFn);
const rule = {
id: data.id || `agg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
name: data.name,
group_by: data.group_by || ["catalog_type", "tier"],
window_minutes: data.window_minutes || 5,
threshold_count: data.threshold_count || 3,
alert_type_filter: data.alert_type_filter || [],
level_filter: data.level_filter || [],
target_level: data.target_level || "critical",
enabled: data.enabled !== false,
builtin: false,
created_at: Date.now(),
updated_at: Date.now(),
};
_rules.push(rule);
_save(writeFn);
return { ...rule };
}
function updateRule(readFn, writeFn, ruleId, updates) {
_ensureLoaded(readFn);
const idx = _rules.findIndex((r) => r.id === ruleId);
if (idx === -1) return null;
if (_rules[idx].builtin) return null;
_rules[idx] = { ..._rules[idx], ...updates, updated_at: Date.now() };
_save(writeFn);
return { ..._rules[idx] };
}
function deleteRule(readFn, writeFn, ruleId) {
_ensureLoaded(readFn);
const idx = _rules.findIndex((r) => r.id === ruleId);
if (idx === -1) return false;
if (_rules[idx].builtin) return false;
_rules.splice(idx, 1);
_save(writeFn);
return true;
}
// ---------- 实时聚合检查(D2:每来一条新告警就调用)----------
/**
* 检查窗口内活跃告警是否命中聚合规则
* @param {object} readFn - 存储读取函数
* @param {Array} activeAlerts - alertRecordStore 返回的活跃告警列表
* @param {object} deviceProfiles - { [device_id]: { catalog_type, tier, ... } }
* @returns {Array} 命中的聚合规则 [{ rule, groupKey, matchingAlerts }]
*/
function checkAggregation(readFn, activeAlerts, deviceProfiles) {
_ensureLoaded(readFn);
const hits = [];
const now = Date.now();
for (const rule of _rules) {
if (!rule.enabled) continue;
const windowMs = rule.window_minutes * 60 * 1000;
const windowStart = now - windowMs;
// 过滤时间窗口内的活跃告警
const inWindow = activeAlerts.filter((a) => a.triggered_at >= windowStart);
// 按 group_by 维度分组
const groups = new Map();
for (const alert of inWindow) {
const profile = deviceProfiles?.[alert.device_id] || {};
let keyParts = [];
for (const dim of rule.group_by) {
if (dim === "catalog_type") keyParts.push(profile.catalog_type || alert.alert_catalog || "unknown");
else if (dim === "tier") keyParts.push(profile.tier || "unknown");
else keyParts.push("*");
}
const groupKey = keyParts.join("::");
// alert_type / level 过滤
if (rule.alert_type_filter?.length > 0) {
const at = alert.alert_type || "";
if (!rule.alert_type_filter.includes(at)) continue;
}
if (rule.level_filter?.length > 0 && !rule.level_filter.includes(alert.level)) {
continue;
}
if (!groups.has(groupKey)) groups.set(groupKey, []);
groups.get(groupKey).push(alert);
}
// 检查阈值
for (const [groupKey, matchingAlerts] of groups) {
if (matchingAlerts.length >= rule.threshold_count) {
hits.push({ rule, groupKey, matchingAlerts });
}
}
}
return hits;
}
/**
* 升级告警(产出新 alertRecord)
* @param {object} readFn - 存储读取函数
* @param {object} writeFn - 存储写入函数
* @param {string} reason - 升级原因
* @param {object} data - { source_alert_ids, target_level, message }
* @returns {object} 新告警记录
*/
function escalateAlert(readFn, writeFn, reason, data) {
const { alertRecordStore } = require("./alertRecordStore.cjs");
const alert = alertRecordStore._createAlert
? _createEscalationAlert(reason, data)
: _createEscalationAlert(reason, data);
// 直接写到 alertRecordStore(复用内部函数)
const alerts = _loadAlerts(readFn);
alerts.push(alert);
_saveAlerts(writeFn, alerts);
return alert;
}
// alertRecordStore 内部结构复用(直接操作存储避免循环依赖)
function _loadAlerts(readFn) {
const raw = readFn("netcatty_inspection_alert_records_v1");
return raw ? (typeof raw === "string" ? JSON.parse(raw) : raw) : [];
}
function _saveAlerts(writeFn, alerts) {
writeFn("netcatty_inspection_alert_records_v1", JSON.stringify(alerts));
}
function _createEscalationAlert(reason, data) {
const now = Date.now();
const deviceNames = (data.source_alert_ids || []).join(", ");
return {
id: `alert_esc_${now}_${Math.random().toString(36).slice(2, 8)}`,
rule_id: `rule_escalation_${reason}`,
rule_name: reason === "aggregation" ? "聚合升级" : "超时升级",
device_id: "group_escalation",
device_name: `[升级告警] ${deviceNames}`,
metric: "escalation",
metric_value: data.source_alert_ids?.length || 0,
threshold: data.target_level || "critical",
level: data.target_level || "critical",
message: data.message || `${reason}: ${data.source_alert_ids?.length || 0} 台设备告警触发升级`,
triggered_at: now,
status: "active",
acknowledged_by: null,
acknowledged_at: null,
resolved_at: null,
resolved_reason: null,
note: null,
// v1.3 扩展字段
escalation: {
trigger: reason, // aggregation | escalation_fallback_timeout
source_alert_ids: data.source_alert_ids || [],
target_level: data.target_level || "critical",
escalated_at: now,
},
};
}
function _resetForTest() { _rules = null; }
module.exports = {
listRules,
getRule,
createRule,
updateRule,
deleteRule,
checkAggregation,
escalateAlert,
_resetForTest,
};