问题说明
用户收到了 Flashduty monitor 监控告警,但是认为数据不符合预期,想要排查具体原因。本文侧重讲解 Prometheus 类型的数据源。
核心要点摘要
- Flashduty monitor 会周期性查询时序库,Prometheus 类型数据源使用的是
/api/v1/query接口。 - Grafana 折线图看到的数据,和告警引擎在某个告警时刻查询到的瞬时结果,可能因为 query lookback 等因素表现不一致。
- 最直接的排查方式,是模拟 Flashduty monitor 在告警时间点附近的查询方式,逐个时间点回放 PromQL 结果。
- 脚本中的
promql、prometheus_url、start_time、end_time和step都需要按实际告警规则调整。 - 如果规则是每 15 秒执行一次、连续 4 次触发,可以围绕告警时间向前取一段窗口,并以 15 秒为步长观察每次查询结果。
原理说明
Flashduty monitor 会周期性查询时序库(比如 Prometheus)中的监控数据,调用的是 /api/v1/query 接口(Prometheus 原生告警引擎也是这个逻辑),如果使用 Grafana 查看折线图数据,可能会发现数据不符合预期,通常原因是时序库的 query lookback 参数导致的。
咱们也可以不关心具体原理,就直接模仿 Flashduty monitor 的查询方式,去查询告警的时候的数据,看看结果是否符合预期即可。
排查目标
这类问题的目标不是证明“图上有没有那条线”,而是回答三个更具体的问题:
- 告警规则在触发时刻实际查询到了什么数据?
- 查询时间点、查询步长和告警规则执行频率是否一致?
- 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:原文的做法是为了规避边界情况。实际排查时可以多尝试几个相邻时间戳,观察查询结果是否稳定。