排查 Flashduty monitor 监控告警 - 数据不符合预期的场景

排查 Flashduty monitor 监控告警数据不符合预期的方法:理解 Prometheus query lookback 影响,模拟 /api/v1/query 查询告警时刻数据,并用脚本按告警频率回放 PromQL 结果。

作者 巴辉特

问题说明

用户收到了 Flashduty monitor 监控告警,但是认为数据不符合预期,想要排查具体原因。本文侧重讲解 Prometheus 类型的数据源。

核心要点摘要

  • Flashduty monitor 会周期性查询时序库,Prometheus 类型数据源使用的是 /api/v1/query 接口。
  • Grafana 折线图看到的数据,和告警引擎在某个告警时刻查询到的瞬时结果,可能因为 query lookback 等因素表现不一致。
  • 最直接的排查方式,是模拟 Flashduty monitor 在告警时间点附近的查询方式,逐个时间点回放 PromQL 结果。
  • 脚本中的 promqlprometheus_urlstart_timeend_timestep 都需要按实际告警规则调整。
  • 如果规则是每 15 秒执行一次、连续 4 次触发,可以围绕告警时间向前取一段窗口,并以 15 秒为步长观察每次查询结果。

原理说明

Flashduty monitor 会周期性查询时序库(比如 Prometheus)中的监控数据,调用的是 /api/v1/query 接口(Prometheus 原生告警引擎也是这个逻辑),如果使用 Grafana 查看折线图数据,可能会发现数据不符合预期,通常原因是时序库的 query lookback 参数导致的。

咱们也可以不关心具体原理,就直接模仿 Flashduty monitor 的查询方式,去查询告警的时候的数据,看看结果是否符合预期即可。

排查目标

这类问题的目标不是证明“图上有没有那条线”,而是回答三个更具体的问题:

  1. 告警规则在触发时刻实际查询到了什么数据?
  2. 查询时间点、查询步长和告警规则执行频率是否一致?
  3. Grafana 图表展示的时间范围、采样和 Prometheus 即时查询结果是否存在口径差异?

只要把这三个问题确认清楚,通常就能判断是规则理解问题、查询时间点问题,还是数据源本身的 lookback 行为导致的差异。

排查脚本

下面用 AI 生成了一个 Python 脚本,可以用来查询:

import requests
import argparse
import time
from datetime import datetime, timezone, timedelta

promql = """mem_available_percent"""

def cst_to_unix(cst_str):
    """将东八区时间字符串转换为 Unix 时间戳"""
    tz_cst = timezone(timedelta(hours=8))
    cst_str = cst_str.replace('T', ' ')
    dt = datetime.strptime(cst_str, '%Y-%m-%d %H:%M:%S')
    dt_cst = dt.replace(tzinfo=tz_cst)
    return int(dt_cst.timestamp())

def unix_to_cst_str(ts):
    """将 Unix 时间戳转换回人类易读的东八区时间字符串"""
    tz_cst = timezone(timedelta(hours=8))
    dt = datetime.fromtimestamp(ts, tz=tz_cst)
    return dt.strftime('%Y-%m-%d %H:%M:%S')

def main():
    parser = argparse.ArgumentParser(description="Prometheus Query Tool (SRE Debug Version)")
    parser.add_argument("--prometheus_url", required=True, help="Prometheus base URL")
    parser.add_argument("--start_time", required=True, help="Start time in CST (e.g., '2023-10-01 08:00:00')")
    parser.add_argument("--end_time", required=True, help="End time in CST (e.g., '2023-10-01 08:10:00')")
    # 第四个参数:步长
    parser.add_argument("--step", type=int, default=15, help="Step in seconds (default: 15)")

    args = parser.parse_args()

    try:
        start_ts = cst_to_unix(args.start_time)
        end_ts = cst_to_unix(args.end_time)
    except ValueError as e:
        print(f"Time format error: {e}. Please use 'YYYY-MM-DD HH:MM:SS'")
        return

    api_endpoint = f"{args.prometheus_url.rstrip('/')}/api/v1/query"

    current_ts = start_ts
    print(f"\nQuerying: {args.start_time} to {args.end_time} with step {args.step}s\n")

    while current_ts <= end_ts:
        # 获取人类易读的时间字符串
        readable_time = unix_to_cst_str(current_ts)

        params = {
            "query": promql,
            "time": current_ts
        }

        try:
            response = requests.get(api_endpoint, params=params, timeout=5)
            response.raise_for_status()
            data = response.text

            # 修改后的打印语句:同时包含时间戳和易读时间
            print(f"------------ {current_ts} | {readable_time} -----------")
            print(data + "\n")
        except Exception as e:
            print(f"------------ {current_ts} | {readable_time} -----------\n")
            print(f"Error: {str(e)}\n")

        # 使用传入的步长参数进行递增
        current_ts += args.step

    print(f"Done!")

if __name__ == "__main__":
    main()

最上面的 promql 变量可以修改成你想要查询的 PromQL 表达式。可以把脚本内容保存为 debug.py,然后用下面的命令运行:

python3 debug.py \
  --prometheus_url http://10.99.1.107:9090 \
  --start_time "2025-12-19 21:38:11" \
  --end_time "2025-12-19 21:39:11" \
  --step 15
  • prometheus_url:Prometheus 的访问地址
  • start_time:查询的开始时间,东八区时间
  • end_time:查询的结束时间,东八区时间
  • step:查询的时间步长,单位秒,默认是 15 秒,改成你的告警规则里的频率

比如 2025-12-19 21:39:10 告警了,规则里配置的是 15s 执行一次,连续 4 次告警,那么就可以把结束时间设置成 2025-12-19 21:39:11,开始时间设置成 2025-12-19 21:38:11,步长设置成 15 秒。然后查询测试,看看到底查到什么数据。

这里我故意把结束时间设置为告警时间往后一秒,是担心一些边界情况。具体测试时,你可以多尝试几次不同的时间戳,看看结果。

(py3venv) ulric@ulric-fcc01 misc % python3 debug.py \
  --prometheus_url http://10.99.1.107:9090 \
  --start_time "2025-12-19 21:38:11" \
  --end_time "2025-12-19 21:39:11" \
  --step 15

Querying: 2025-12-19 21:38:11 to 2025-12-19 21:39:11 with step 15s

------------ 1766151491 | 2025-12-19 21:38:11 -----------
{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"mem_available_percent","ident":"cn-beijing.10.99.1.109"},"value":[1766151491,"21.608323685048916"]}]}}

------------ 1766151506 | 2025-12-19 21:38:26 -----------
{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"mem_available_percent","ident":"cn-beijing.10.99.1.109"},"value":[1766151506,"22.473846026331397"]}]}}

------------ 1766151521 | 2025-12-19 21:38:41 -----------
{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"mem_available_percent","ident":"cn-beijing.10.99.1.109"},"value":[1766151521,"21.02999645150345"]}]}}

------------ 1766151536 | 2025-12-19 21:38:56 -----------
{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"mem_available_percent","ident":"cn-beijing.10.99.1.109"},"value":[1766151536,"21.231924036272368"]}]}}

------------ 1766151551 | 2025-12-19 21:39:11 -----------
{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"mem_available_percent","ident":"cn-beijing.10.99.1.109"},"value":[1766151551,"22.203590456875407"]}]}}

Done!

如何解读脚本输出

脚本输出的每一段都包含 Unix 时间戳、东八区可读时间和 Prometheus 返回的原始 JSON。排查时重点看三件事:

  • 返回结果里是否有目标时间序列,例如示例中的 ident="cn-beijing.10.99.1.109"
  • value 中的数值是否达到告警规则的阈值。
  • 连续多个查询点的结果是否满足规则中“连续 N 次”或类似触发条件。

如果脚本查到的数据和 Flashduty monitor 告警判断一致,而 Grafana 图上看起来不一致,就优先检查图表时间范围、采样间隔和 query lookback 影响。如果脚本查到的数据本身就不符合预期,再继续检查 PromQL、数据采集、标签过滤和时序库数据。

结论

排查 Flashduty monitor 告警数据不符合预期时,最短路径是复现告警引擎的查询方式。对于 Prometheus 类型数据源,可以直接调用 /api/v1/query,按告警规则的执行频率在告警时间附近逐点查询。这样能把 Grafana 图表展示、Prometheus 即时查询和 Flashduty monitor 告警判断拆开验证,避免只凭折线图做结论。

FAQ

Q1:为什么 Grafana 图上看到的数据和告警判断不一致? A:原文提到,常见原因是时序库的 query lookback 参数影响;另外,图表展示和告警引擎即时查询的时间点、采样口径也可能不同。

Q2:为什么脚本调用 /api/v1/query 而不是范围查询? A:因为 Flashduty monitor 周期性查询 Prometheus 时使用的是 /api/v1/query 接口,模拟同样的查询方式更接近告警时刻的真实判断。

Q3:step 应该怎么设置? A:step 应尽量设置成告警规则的执行频率。示例中规则每 15 秒执行一次,所以脚本使用 --step 15

Q4:结束时间为什么可以比告警时间晚一秒? A:原文的做法是为了规避边界情况。实际排查时可以多尝试几个相邻时间戳,观察查询结果是否稳定。

延伸路径

继续看解决方案和产品对比

如果你正在做监控、可观测性或故障定位相关选型,建议从解决方案和产品对比继续往下看。

快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云