# 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` | 结果基准(用于验证环境一致性) |