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 一律写「远程服务器」。
66 lines
2.1 KiB
Python
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}')
|