2. 基于报警日志的报警风暴时间窗口定位

为什么不能只看“报警总数”

AMS 报警日志里,单条记录通常包含 时间戳、报警点标识(Tag)、报警类型(ALM/RTN)、优先级、来源设备/网段。报警风暴的本质不是“某天报警多”,而是在很短的时间跨度内,报警到达速率显著超过基线。因此定位风暴需要两步:先把离散时间戳聚合成“每分钟报警数”这样的速率序列,再用滑动窗口找出速率持续超阈值的区间,最后区分起始点(第一次越限的时刻)与持续时段(越限到回落到阈值以下的区间)。

时间戳聚类与滑动窗口的计算口径

设日志按时间升序排列,取统计粒度 Δt(工程上常用 1 min),第 k 个桶的报警数为 N(k)。滑动窗口宽度 W(例如 5 min),窗口内报警总数 S(k)=ΣN(i),i 从 k−W+1 到 k。定义基线 B 为风暴前一段平稳期的每分钟报警数中位数,阈值 T = max(α·B, N_min),其中 α 为放大倍数(如 5),N_min 为防止基线过低导致误触发的最小绝对量(如 10 条/min)。当 S(k) ≥ T·W 时判定该窗口为风暴窗口。为避免单点抖动造成多次起停,可要求连续 m 个窗口越限才确认起始点。

口径要先定死再算:Δt、W、α、N_min、m 这五个参数一旦确定,同一份日志的定位结果就可复现。建议把参数写进排障记录,便于与后续案例的趋势对齐。
报警日志 → 分钟桶 → 滑动窗口 → 风暴区间 ① 原始报警日志 10:02:11 P-101 ALM H 10:02:13 T-201 ALM M 10:02:14 L-305 ALM L 10:02:15 P-101 RTN — 10:02:16 T-201 ALM M 10:02:18 F-402 ALM H …(按时间升序) Δt=1min ② 每分钟报警数 N(k) 10:00 10:05 10:10 10:15 W=5min ③ 窗口内总数 S(k) 与阈值 阈值 T·W 越限区间 ④ 定位结果:起始点与持续时段 10:00 10:03 10:06 10:09 10:12 10:15 10:18 平稳期(基线 B) 风暴持续时段 ≈ 10:06–10:12 起始点 回落点 起始点 = 连续 m 个窗口首次越限的时刻;持续时段 = 起始点到窗口总数回落到阈值以下的时刻。 该时段即后续参数趋势对齐、过滤规则验证与根因排查的统一时间基准。

用一段可运行的脚本完成定位

下面用 Python 标准库实现上述口径,输入为从 AMS 导出的 CSV(字段:ts,tag,type,priority,source),输出风暴起始点、持续时段与峰值窗口。示例数据为教学构造,运行结果属于按给定输入直接推出的预期值,不是现场实测。

import csv
from datetime import datetime, timedelta
from collections import Counter

# ---- 参数(先定死,保证可复现)----
DT_MIN   = 1      # 分钟桶粒度
W        = 5      # 滑动窗口宽度(桶数)
ALPHA    = 5.0    # 相对基线放大倍数
N_MIN    = 10     # 每分钟最小绝对报警数
M_CONFIRM= 2      # 连续越限窗口数才确认起始

def load(path):
    rows = []
    with open(path, newline='', encoding='utf-8') as f:
        for r in csv.DictReader(f):
            r['ts'] = datetime.strptime(r['ts'], '%Y-%m-%d %H:%M:%S')
            rows.append(r)
    rows.sort(key=lambda x: x['ts'])
    return rows

def minute_buckets(rows):
    if not rows:
        return {}, None, None
    t0 = rows[0]['ts'].replace(second=0, microsecond=0)
    t1 = rows[-1]['ts'].replace(second=0, microsecond=0)
    n = int((t1 - t0).total_seconds() // 60) + 1
    cnt = Counter()
    for r in rows:
        k = int((r['ts'].replace(second=0, microsecond=0) - t0).total_seconds() // 60)
        cnt[k] += 1
    return {k: cnt.get(k, 0) for k in range(n)}, t0, n

def baseline(buckets, n):
    # 取前 20% 桶的中位数作为平稳期基线
    head = [buckets[k] for k in range(max(1, n // 5))]
    head.sort()
    return head[len(head) // 2]

def sliding(buckets, n):
    s, out = 0, []
    for k in range(n):
        s += buckets[k]
        if k >= W:
            s -= buckets[k - W]
        out.append(s if k >= W - 1 else None)
    return out

def locate(path):
    rows = load(path)
    buckets, t0, n = minute_buckets(rows)
    B = baseline(buckets, n)
    T = max(ALPHA * B, N_MIN)
    S = sliding(buckets, n)
    over = [k for k in range(n) if S[k] is not None and S[k] >= T * W]

    # 连续 m 个越限窗口才确认起始
    start = None
    for i in range(len(over) - M_CONFIRM + 1):
        if over[i + M_CONFIRM - 1] - over[i] == M_CONFIRM - 1:
            start = over[i] - W + 1
            break
    if start is None:
        return {'baseline': B, 'threshold': T, 'storm': None}

    end = start
    for k in range(start, n):
        if S[k] is not None and S[k] >= T * W:
            end = k
        elif k > end + W:
            break

    peak_k = max(range(start, end + 1), key=lambda k: S[k] or 0)
    return {
        'baseline': B,
        'threshold': T,
        'start': t0 + timedelta(minutes=start),
        'end':   t0 + timedelta(minutes=end),
        'peak_time': t0 + timedelta(minutes=peak_k),
        'peak_count': S[peak_k],
        'duration_min': end - start + 1,
    }

if __name__ == '__main__':
    print(locate('ams_alarm_log.csv'))

按图中示例数据(10:00–10:18,平稳期约 2–3 条/min,10:06–10:12 明显抬升)运行,预期输出形如:baseline≈2, threshold≈10, start=10:06, end=10:12, duration_min≈7。若实际日志的基线为 0(夜间无报警),则 T 由 N_MIN 兜底,避免 α·B=0 导致任何窗口都越限。

结果判读与常见坑

  • 起始点偏移:滑动窗口是“回看”的,S(k) 首次越限的桶 k 对应的是窗口末端,真实起始点要回退 W−1 个桶,脚本里用 start = over[i] - W + 1 修正。
  • 抖动导致多次起停:若日志中报警呈“爆发—短暂回落—再爆发”,可把 M_CONFIRM 调大,或对 S(k) 做 3 点中值平滑后再判越限。
  • 重复报警放大计数:同一 Tag 的 ALM 未 RTN 前反复刷新会虚增 N(k)。定位阶段可先按 (tag, type) 去重,或只统计 ALM 边沿,避免把“刷新”误判为风暴。
  • 时间基准统一:AMS 服务器时间、控制器时间、历史库时间可能存在秒级到分钟级偏差,定位前应确认日志时间戳来源,并在排障记录中注明,否则后续与趋势数据对齐会错位。
定位出的时间窗口只是“报警密集区”,不等于“故障发生区”。真实工艺异常可能早于起始点数分钟,因此后续应把窗口向前后各扩展 5–10 min 再取参数趋势,而不是只截取窗口内部。