# AIOPS **Repository Path**: cjls1527/aiops ## Basic Information - **Project Name**: AIOPS - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-18 - **Last Updated**: 2026-10-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # AIOps 2026 故障识别工具 针对 2026 CCF AIOps 挑战赛「基于多源观测数据的骨干网全栈故障诊断」。 **规则驱动的多源网络故障检测工具** —— 输入原始 CSV 观测数据,输出符合官方 提交格式的故障预测。纯 Python 标准库,无需第三方依赖。 --- ## 30 秒看懂 ``` 原始数据 96 GB ──[预聚合]──► 1.4 GB ──[检测]──► 1,745 命中 ──[聚类]──► 954 个故障事件 ──[转格式]──► result.jsonl ``` | 组件 | 作用 | |---|---| | **39 条 YAML 规则** | 声明"什么算故障",改规则不用改代码 | | **21 个算子** | 判据实现(阶跃检测、状态翻转、日志爆发……) | | **三种预聚合编码** | 按数据性质分流,96 GB → 1.4 GB(69.7x) | | **角色约束** | 按故障大类过滤根因候选,提升 RCA 得分 | | **官方评分器对接** | 本地算分,不消耗提交次数 | --- ## 快速开始 ### 方式一:一条命令跑完 ```bash cd <本目录> python main.py --data-root <原始数据根目录> ``` 它会依次跑 校验规则 → 预聚合 → 检测 → 聚类 → 转格式,最后报告提交文件路径。 ```bash python main.py --check # 只校验规则 python main.py --data-root X --from detect # 跳过预聚合(已有聚合层时) python main.py --data-root X --only detect # 只跑检测 ``` ### 方式二:分步执行(便于排查) ```bash # 0. 环境:Python 3.10+,纯标准库 python --version # 1. 校验规则(秒级,不读数据) python rules/check_rules.py # -> OK: 39 rules valid, 28 distinct fault names, no errors # 2. 预聚合(约 40 分钟,96 GB -> 1.4 GB) python rules/aggregate_all.py --data-root <原始数据根目录> --out outputs/agg_all # 3. 检测(约 5 分钟) python rules/engine.py --data-root outputs/agg_all --aggregates --out outputs/detect_all.json # 4. 聚类成故障事件(秒级) python rules/cluster.py --predictions outputs/detect_all.json --out outputs/events_all.json # 5. 转成官方提交格式 python rules/to_submission.py --events outputs/events_all.json --out outputs/result.jsonl ``` **第 5 步的 `result.jsonl` 就是提交文件。** ### 跳过预聚合(慢,但结果一致) ```bash python rules/engine.py --data-root <原始数据> --out outputs/detect_all.json ``` **注意**:这条路会尝试读 96 GB 原始数据,且 `netflow` 表会被跳过(13 GB/城, 且没有规则直接需要它的原始形态)。 --- ## 数据布局 工具自动识别三种布局: | 布局 | 路径形态 | |---|---| | 原始单城 | `/*.csv` | | 原始多城 | `/_/__data/*.csv` | | 预聚合 | `//_data/agg/*.csv` | **必须一次加载所有城市。** 15 条规则依赖跨城市同伴比较,单城加载会让它们 静默返回 0 命中(实测导致 3/3 真实故障全部漏报)。 --- ## 目录结构 ``` rules/ fault_rules.yaml 39 条规则(声明式,改这里不用改代码) engine.py 检测引擎:加载数据、评估规则、输出命中 preaggregate.py 单城预聚合(降维/变化点/计数三种编码) aggregate_all.py 8 城批量预聚合(支持断点续跑) cluster.py 把命中聚合成故障事件 to_submission.py 转官方提交格式(含角色约束) role_constraints.py 故障大类 -> 允许的根因角色 check_rules.py 规则校验器 encoding_fitness.py 算子与编码的兼容性约束 tools/ score_sample_official.py 用官方评分器本地算分(不消耗提交次数) inspect_faults.py 打印真故障的数据形态(定判据前必用) sample_quality.py 召回率 / 精确率分开报告 audit_false_positives.py 逐条分析误报,标出"抢匹配"的那些 build_link_table.py 从 netflow 提取链路拓扑 topology_summary.py 拓扑结构汇总 selfcheck.py 端到端自检,对照基准 run_pipeline.sh 一条命令跑完 ``` --- ## 算子(21 个已实现) 规则通过 `method` 字段选用算子。 | 算子 | 判据 | 适用数据 | |---|---|---| | `absolute_bound` | 越过物理合理边界 | 连续量 | | `sustained_high` / `sustained_low` | 持续高于/低于同伴 | 连续量 | | `peer_ratio` | 相对同伴中位数的偏离 | 连续量 | | `self_shift` | 自身序列内的突变 | 连续量 | | **`pinned_level`** | **阶跃到新水平并保持** | 连续量 | | `rate_increase` | 相对早期基线抬升 | 连续量 | | `capped_plateau` | 卡在平台值 | 连续量 | | `oscillation` | 符号翻转次数 | **需完整序列** | | `counter_growth` | 计数器增量 | 计数器 | | `state_flip` | 状态量离开健康值 | 状态量 | | `field_change` | 标签字段变化 | 状态量 | | `protocol_mismatch` | 协议值不在白名单 | 状态量 | | `nexthop_unreachable` | 下一跳无法解析 | 状态量+频次 | | `log_burst` | 每分钟错误日志行数 | syslog | | `stagnation` | 累计计数器停止增长 | 计数器 | | `selective_failure` | 多个业务流同时失败 | 业务流 | | `port_disappear` | 服务类流量塌陷 | netflow | | `default_route_shift` | 默认路由下一跳改变 | 路由 | | `correlated_high` | 两个信号同时升高 | 组合 | | `target_mismatch` | 目标区域发生变化 | 业务流 | **未实现**:`level_shift`(报 `unevaluable`,不静默跳过)。 --- ## 预聚合:三种编码 **压缩率取决于值的稳定性,不是表的大小。** 实测(武汉,14 天): | 表 | 原始行 | 处理后 | 压缩 | 编码 | |---|---|---|---|---| | `netflow` | 63,067,603 | 1,080,261 | 58x | 降维聚合 | | `scrape_health` | 262,080 | **37** | **7083x** | 变化点 | | `routing_metrics` | 5,759,720 | 241,922 | 23.8x | 变化点+频次 | | `frr_syslog_events` | 817 | 187 | 4.4x | 每分钟计数 | | `interface_metrics` | 1,208,840 | 463,243 | 2.6x | 变化点 | | `node_metrics` | 181,364 | 181,364 | **1.0x** | 变化点(压不动) | | **合计** | **96.1 GB** | **1.4 GB** | **69.7x** | | **`node_metrics` 压不动是物理事实** —— cpu/内存/磁盘每分钟都在变。 不用有损压缩,因为 `absolute_bound`(cpu 绝对阈值)依赖它的精度。 --- ## 编码兼容性(重要) **变化点编码对某些算子是有损的。** 实测差异: | 规则 | 算子 | 原始 → 聚合 | 原因 | |---|---|---|---| | `route_blackhole_scrape` | `state_flip` | 152 → 12 | ✅ **更准**:raw 把 26 分钟故障报成 26 次 | | `route_blackhole` | `nexthop_unreachable` | 4 → 1 | ✅ 靠频次表恢复 | | `route_bgp_route_flap` | `oscillation` | 3 → **拒绝** | ❌ 抽稀虚增翻转,无法修复 | `encoding_fitness.py` 把算子分三类,`check_rules.py` 会校验声明: - **值判据**(`state_flip` 等)→ 可用聚合层 - **需频次**(`nexthop_unreachable`)→ 需配套 `*_counts.csv` - **依赖采样密度**(`oscillation`、`sustained_*`)→ **必须走原始层** --- ## 输出格式 ### `result.jsonl`(官方提交格式) ```json {"prediction_id":"pred_000001", "start_time":"2026-07-28T12:46:00.000Z", "end_time":"2026-07-28T12:58:00.000Z", "root_cause_top5":[{"rank":1,"network_element_id":"xian-service-vm-1"}, ...], "fault_category":{"major_category":"resource","sub_category":"cpu_pressure"}} ``` ### 置信度分级 | 级别 | 含义 | 基础分 | |---|---|---| | `trusted` | 判据可靠(物理边界、二值事实) | 0.8 | | `suspicious` | 判据相对(与同伴比较) | 0.3 | 引擎按证据调整:+0.15 二值事实、+0.10 远超物理边界、+0.10 多检测器命中; −0.15 基础设施角色、−0.10 相对判据。 --- ## 官方评分规则(从官方仓库读到) ```python 匹配:Dice 时间重叠率 >= 0.4(匈牙利算法全局最优一对一) AD = 0.7 + 0.3 × 时间精度 # 容差 ±180 秒 alpha_fp = 0.7 + 0.3 × precision # ← 误报惩罚 RCA = {1:1.0, 2:0.8, 3:0.6, 4:0.4, 5:0.2} # 掉出 Top5 得 0 Major = 大类是否正确 × 10 Minor = 子类是否正确 × 10 ``` **关键机制**:**误报会"抢走"正确预测的匹配**。匈牙利算法按 Dice 最大分配, 一条 Dice 更高的误报会让正确的预测匹配不上,**RCA/Major/Minor 全部归零**。 所以降误报是双重收益。 ### 本地算分 ```bash python tools/score_sample_official.py # 用官方评分器,不消耗提交次数 ``` **实测成绩(样本数据)**: | 分项 | 分数 | |---|---| | Total | **83.90 / 100** | | AD | 23.90 / 40 | | **RCA** | **40.00 / 40(满分)** | | **Major** | **10.00 / 10(满分)** | | **Minor** | **10.00 / 10(满分)** | --- ## 必须排除的正常现象 **这些不排除会产生海量误报**(都是实测踩出来的): | 现象 | 真相 | |---|---| | `protocol=unknown` 出现在默认路由 `::/0` | 默认路由指向外部网关,正常 | | `protocol=unknown` 出现在本城网段 | 直连子网不学习协议,正常 | | `ipv6_route_exists=0` 仅出现在 `cr-*` | CR 不持有远端明细路由 | | `ospf6_interface_enabled=0` 全是 `virbr0` | 虚拟网桥 | | `ospf6_neighbor_state_code` 的 `6` 和 `2` | Full 与 Twoway 都合法 | | `cr-*` 的 `bgp_*` 为空字符串 | CR 不跑 BGP,空 ≠ 失败 | | `swap_used_ratio` 恒为 `0.00147` | 正常,**不能**用 `state_flip` 判 | | `monitor-vm` 占最高 `disk_io_util` | 系统性干扰源,与故障无关 | **教训**:判据必须用真实数据标定。`protocol_mismatch` 初版凭常识写白名单, 误报 **55,943 条**。 --- ## 数据事实速查 | 事实 | 值 | |---|---| | 城市 | 8 个:beida, shenyang, xian, chengdu, wuhan, shanghai, nanjing, guangzhou | | 每城节点 | 10 个:`br-1/2`、`cr-1/2`、`fw`、`traffic-vm`、`service-vm-1/2/3`、`monitor-vm` | | 数据窗口 | 2026-08-19 04:00 – 2026-09-02 04:00(14 天,20160 分钟) | | 频率 | 1 分钟 | | 地址计划 | `fd00:<城市>:<层>::<主机>`,层 10=环回、20=BR、30=CR、40=fw与VM | | **`fw` 在 netflow 里不存在** | 只有 `br1/br2/cr1/cr2` | | netflow 节点拼写 | 聚合层是 `br1`,其他表是 `br-1` | | `traffic_flow` 无节点维度 | 按 `source_region`/`target_region` 组织 | | `dns_flow_success_total` | **单调递增计数器**,从不归零 | | `web/auth_flow_success_total` | **恒为 0**(不是失败,是没数据) | --- ## 已知限制 1. **`level_shift` 未实现**,对应规则报 `unevaluable` 2. **`netflow` 只读聚合层** —— 原始 13 GB/城,读它等于白费几分钟 3. **15 条规则依赖跨城同伴** —— 单城加载会静默产出 0 命中 4. **`oscillation` 类规则在聚合层上被拒绝** —— 有意设计,不是缺陷 5. **样本只验证了 `resource` 类** —— `firewall`/`link` 类在真实数据上未经检验 --- ## 实测结果(8 城) | 指标 | 值 | |---|---| | 原始数据 | 96.1 GB | | 预聚合层 | 1.4 GB(69.7x) | | 命中 | 1,745(trusted 162 / suspicious 1583) | | 故障事件 | 954 | 各检测器命中: | 检测器 | 命中 | |---|---| | `route_blackhole` | 477 | | `link_rate_limit` | 418 | | `firewall_port_block` | 380 | | `service_dns_down` | 232 | | `resource_cpu_high` | 33 | | `resource_disk_io_pressure` | 20 | | `link_loss_syslog` | 11 | --- ## 环境说明 - **Python 3.10+,纯标准库**,无第三方依赖(不需要 numpy/pandas) - 8 城预聚合约 40 分钟,检测约 5 分钟 - 预聚合支持断点续跑:已完成的城市会跳过 - `aggregate_all.py` 的 `_already_done` **同时识别** `_aggregated.csv` 和 `_changepoints.csv` —— 只认前者会导致每次重启重跑整个数据集 --- ## 相关文档 | 文档 | 内容 | |---|---| | `DESIGN.md` | 设计文档:完整流程、数据事实、踩坑记录、突破过程 | | `BASELINE.md` | 结果基准(用于验证环境一致性) |