From 41f56f75932a6e90d68790a1faa19bf2acdb7d9e Mon Sep 17 00:00:00 2001 From: mouxin <494624263qq@gmail.com> Date: Mon, 13 Apr 2026 17:07:36 +0800 Subject: [PATCH] stat-cache-hitrate: remove watch mode and loop guidance --- .../skills/stat-cache-hitrate/SKILL.md | 15 ++-- .../references/report_templates.md | 10 --- .../scripts/stat_cache_hitrate.py | 68 ++++++++++++++++--- 3 files changed, 64 insertions(+), 29 deletions(-) diff --git a/fastdeploy/golang_router/.claude/skills/stat-cache-hitrate/SKILL.md b/fastdeploy/golang_router/.claude/skills/stat-cache-hitrate/SKILL.md index 251cbb04c2a..3d52bc4b2be 100644 --- a/fastdeploy/golang_router/.claude/skills/stat-cache-hitrate/SKILL.md +++ b/fastdeploy/golang_router/.claude/skills/stat-cache-hitrate/SKILL.md @@ -4,7 +4,7 @@ description: > 统计 FastDeploy Go Router 日志中的三层 cache 命中率指标,生成可视化报告。 三层指标:Prefix Hit Ratio(KV Cache 内容复用度)、Session Hit Rate(请求级路由粘性)、 Per-Worker Cache Stats(各 prefill worker 的缓存利用排名)。支持全量统计、tail 快速查看、 - 持续监控模式、指定时间段统计(--start/--end)。 + 指定时间段统计(--start/--end)。 当用户提到以下内容时触发此 skill:统计/查看 cache 命中率、查看 cache-aware 调度效果、 查看缓存预热情况、统计 hitRatio、查看 prefix 命中率、session hit rate。 @@ -37,9 +37,8 @@ IMPORTANT: 执行前阅读 references/log_formats.md 了解日志格式和解析 ### 2. 分析模式 必须使用 **AskUserQuestion 的离散选项**(不要只发纯文本编号,避免客户端偶发不显示第 4 项): - 选项 1: `全量统计(默认)` — 扫描完整日志 -- 选项 2: `快速查看尾部` — 只看最近的数据(可指定行数如 2000 或时间如 30m) -- 选项 3: `持续监控` — 全量分析后提示监控命令 -- 选项 4: `指定时间段` — 分析特定时间范围(如 `--start "16:00" --end "17:00"`) +- 选项 2: `快速查看尾部` — 只看最近的数据(可指定 `2000/2k` 行,或 `30m/2h/1d` 时间窗口) +- 选项 3: `指定时间段` — 分析特定时间范围(如 `--start "16:00" --end "17:00"`) 若用户选择“指定时间段”,直接让用户填写: - 从 `xxx` 开始,到 `xxx` 结束(`start/end` 可只填一个); @@ -66,10 +65,10 @@ python3 .claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py <日志 # 快速查看尾部数据 python3 .claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py <日志文件> --tail # 默认最后 2000 行 python3 .claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py <日志文件> --tail 5000 # 指定行数 -python3 .claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py <日志文件> --tail 30m # 指定时间 - -# 持续监控 -python3 .claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py <日志文件> --watch +python3 .claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py <日志文件> --tail 2k # 行数缩写 +python3 .claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py <日志文件> --tail 30m # 分钟窗口 +python3 .claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py <日志文件> --tail 2h # 小时窗口 +python3 .claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py <日志文件> --tail 1d # 天窗口 # 指定时间段(--start 和 --end 可单独或同时使用) python3 .claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py <日志文件> --start "16:00:00" --end "17:00:00" diff --git a/fastdeploy/golang_router/.claude/skills/stat-cache-hitrate/references/report_templates.md b/fastdeploy/golang_router/.claude/skills/stat-cache-hitrate/references/report_templates.md index f5a0def5f55..ebca39be2c4 100644 --- a/fastdeploy/golang_router/.claude/skills/stat-cache-hitrate/references/report_templates.md +++ b/fastdeploy/golang_router/.claude/skills/stat-cache-hitrate/references/report_templates.md @@ -198,14 +198,4 @@ output = f"{bar} {percentage}% (N={count})" +---+---+---+---+---→ -5m -4m -3m -2m -1m -💡 持续跟踪: /loop 30s /analyze-cache-hitrate --tail -``` - -## --watch 持续监控模板 - -`--watch` 模式先输出完整报告(同终端概览报告模板),末尾额外提示: - -``` -💡 全量分析完成。持续跟踪后续变化: - /loop 30s /analyze-cache-hitrate --tail ``` diff --git a/fastdeploy/golang_router/.claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py b/fastdeploy/golang_router/.claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py index bd85730b7d1..fc729af47c7 100644 --- a/fastdeploy/golang_router/.claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py +++ b/fastdeploy/golang_router/.claude/skills/stat-cache-hitrate/scripts/stat_cache_hitrate.py @@ -8,11 +8,12 @@ 3. Per-Worker Stats — 各 worker 缓存利用排名 用法: - python3 stat_cache_hitrate.py [--tail N|Nm] [--watch] [--output DIR] + python3 stat_cache_hitrate.py [--tail N|2k|30m|2h|1d] [--output DIR] """ import argparse import json +import math import os import re import subprocess @@ -170,7 +171,7 @@ def count_lines(filepath): def read_lines(filepath, tail=None): """读取日志文件,支持 tail 模式。""" - if tail: + if tail is not None: if isinstance(tail, str) and tail.endswith("m"): # 按时间 tail:读取全部行,过滤最近 N 分钟 minutes = int(tail[:-1]) @@ -282,7 +283,7 @@ def extract_data(filepath, tail=None): strategy_recs = grep_and_parse(filepath, STRATEGY_PATTERN, "parse-cache-strategy", tail) stats_recs = grep_and_parse(filepath, STATS_PATTERN, "parse-stats", tail) inference_count = grep_count(filepath, r"\] \[POST\] /v1/chat/completions |\] \[POST\] /v1/completions ", tail) - line_count = int(tail) if tail and not (isinstance(tail, str) and tail.endswith("m")) else total + line_count = int(tail) if tail is not None and not (isinstance(tail, str) and tail.endswith("m")) else total return strategy_recs, stats_recs, inference_count, line_count @@ -984,8 +985,12 @@ def parse_args(): epilog=__doc__, ) parser.add_argument("log_file", help="日志文件路径") - parser.add_argument("--tail", nargs="?", const="2000", help="只分析尾部数据(行数如 2000,或时间如 30m)") - parser.add_argument("--watch", action="store_true", help="全量分析后提示持续监控命令") + parser.add_argument( + "--tail", + nargs="?", + const="2000", + help="只分析尾部数据(支持 2000/2k 行,或 30m/2h/1d 时间窗口)", + ) parser.add_argument( "--output", default=None, help="详细报告输出目录(默认:skill_output/stat-cache-hitrate//)" ) @@ -996,6 +1001,45 @@ def parse_args(): return parser.parse_args() +def parse_tail_arg(tail_str): + """解析 --tail 参数,返回 int(行数) 或 'm'(时间窗口)。""" + if tail_str is None: + return None + + s = str(tail_str).strip().lower() + if not s: + raise ValueError("--tail 不能为空") + + # 行数: 2000 + if re.fullmatch(r"\d+", s): + value = int(s) + if value <= 0: + raise ValueError("--tail 行数必须 > 0") + return value + + # 行数缩写: 2k => 2000 + m = re.fullmatch(r"(\d+)k", s) + if m: + value = int(m.group(1)) * 1000 + if value <= 0: + raise ValueError("--tail 行数必须 > 0") + return value + + # 时间窗口: 30m/2h/1d(最终统一成分钟) + m = re.fullmatch(r"(\d+)(m|h|d)", s) + if m: + num = int(m.group(1)) + unit = m.group(2) + if num <= 0: + raise ValueError("--tail 时间窗口必须 > 0") + factor = {"m": 1, "h": 60, "d": 1440}[unit] + minutes = num * factor + minutes = max(1, math.ceil(minutes)) + return f"{minutes}m" + + raise ValueError("不支持的 --tail 格式:请使用 2000/2k 或 30m/2h/1d") + + def main(): args = parse_args() @@ -1009,6 +1053,12 @@ def main(): print("Error: --tail 与 --start/--end 不能同时使用,请选择其一", file=sys.stderr) sys.exit(1) + try: + tail = parse_tail_arg(args.tail) + except ValueError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + # 时间范围预过滤(--start 和 --end 可单独或同时指定) import atexit @@ -1023,7 +1073,7 @@ def main(): print(f'时间范围过滤: {start_ts or "..."} ~ {end_ts or "..."}', file=sys.stderr) # Phase 2: 提取 + 解析 - strategy_recs, stats_recs, inference_count, line_count = extract_data(log_file, args.tail) + strategy_recs, stats_recs, inference_count, line_count = extract_data(log_file, tail) if not strategy_recs and not stats_recs: print( @@ -1039,7 +1089,7 @@ def main(): diagnosis = cross_diagnose(prefix_hr, session_hr) # Phase 4: 输出 - if args.tail: + if tail is not None: print(format_tail_report(args.log_file, line_count, prefix_hr, session_hr, scheduling)) else: time_span = compute_time_span(strategy_recs, stats_recs) @@ -1094,9 +1144,5 @@ def main(): print(f" - Session 明细: {session_abs}") print(f" URI: {session_uri}") - if args.watch: - print("\n\U0001f4a1 持续跟踪: /loop 30s /stat-cache-hitrate --tail") - - if __name__ == "__main__": main()