Files
dsh_shenxian/dsh-server-docs/scripts/nginx-slow.py
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

66 lines
2.1 KiB
Python

#!/usr/bin/env python3
"""nginx 访问日志慢请求/大响应分析(只读)
用法: python3 nginx-slow.py <logfile> [host子串] [最近N行] [起始时间标记]
"""
import re
import sys
log = sys.argv[1]
host = sys.argv[2] if len(sys.argv) > 2 else ''
tail_lines = int(sys.argv[3]) if len(sys.argv) > 3 else 20000
since = sys.argv[4] if len(sys.argv) > 4 else ''
LINE = re.compile(
r'^(?P<ip>\S+) \S+ \S+ \[(?P<time>[^\]]+)\] "(?P<req>[^"]*)" (?P<status>\d{3}) (?P<bytes>\d+)'
r'(?: "[^"]*" "[^"]*")?(?P<rest>.*)$'
)
rows = []
with open(log, 'r', errors='replace') as fh:
lines = fh.readlines()[-tail_lines:]
for line in lines:
if host and host not in line:
continue
if since and since not in line:
continue
m = LINE.match(line)
if not m:
continue
times = re.findall(r'(\d+\.\d+)', m.group('rest'))
rt = float(times[-1]) if times else None
rows.append(
{
'ip': m.group('ip'),
'time': m.group('time'),
'req': m.group('req'),
'status': int(m.group('status')),
'bytes': int(m.group('bytes')),
'rt': rt,
}
)
print(f'匹配请求数: {len(rows)}')
if not rows:
sys.exit(0)
withrt = [r for r in rows if r['rt'] is not None]
if withrt:
ts = [r['rt'] for r in withrt]
slow = [r for r in withrt if r['rt'] > 1]
print(f'耗时: 平均 {sum(ts)/len(ts):.3f}s 中位 {sorted(ts)[len(ts)//2]:.3f}s 最大 {max(ts):.2f}s >1s 的 {len(slow)} 个')
print('\n== 最慢 12 个 ==')
for r in sorted(withrt, key=lambda x: -x['rt'])[:12]:
print(f" {r['rt']:8.2f}s {r['status']} {r['bytes']:>9} B {r['time'][12:]} {r['req']}")
print('\n== 响应体最大 8 个 ==')
for r in sorted(rows, key=lambda x: -x['bytes'])[:8]:
rt = f"{r['rt']:.2f}s" if r['rt'] is not None else ' ? '
print(f" {r['bytes']:>9} B {rt} {r['status']} {r['req']}")
from collections import Counter
c = Counter(r['req'].split(' ')[1].split('?')[0] if ' ' in r['req'] else r['req'] for r in rows)
print('\n== 路径 TOP 10 ==')
for path, n in c.most_common(10):
print(f' {n:5} {path}')