feat(scripts): 添加针对code_js1接口的压测脚本和结果
添加压测脚本run_stress_code_js1.sh和stress_code_js1.py,用于对code_js1接口进行并发压力测试 生成压测结果JSON文件,包含请求统计、延迟和吞吐量等指标
This commit is contained in:
29
scripts/results/code_js1_20251010_231059.json
Normal file
29
scripts/results/code_js1_20251010_231059.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"url": "http://127.0.0.1:9898/api/dynamic/code_js1",
|
||||
"method": "POST",
|
||||
"total": 3000,
|
||||
"concurrency": 150,
|
||||
"timeout_sec": 8.0,
|
||||
"ok": 3000,
|
||||
"errors": 0,
|
||||
"has_data": 3000,
|
||||
"duration_ms": 13766.637417022139,
|
||||
"throughput_rps": 217.91813854925584,
|
||||
"status_counts": {
|
||||
"200": 3000
|
||||
},
|
||||
"latency_ms": {
|
||||
"min": 550.376542028971,
|
||||
"avg": 686.4437982002273,
|
||||
"max": 980.3312500007451,
|
||||
"p50": 671.9021250028163,
|
||||
"p90": 800.2895419485867,
|
||||
"p95": 885.534834000282,
|
||||
"p99": 946.9203340122476
|
||||
},
|
||||
"resp_size_bytes": {
|
||||
"avg": 556,
|
||||
"min": 556,
|
||||
"max": 556
|
||||
}
|
||||
}
|
||||
29
scripts/results/code_js1_nocsv_20251010_231332.json
Normal file
29
scripts/results/code_js1_nocsv_20251010_231332.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"url": "http://127.0.0.1:9898/api/dynamic/code_js1",
|
||||
"method": "POST",
|
||||
"total": 100,
|
||||
"concurrency": 20,
|
||||
"timeout_sec": 5.0,
|
||||
"ok": 100,
|
||||
"errors": 0,
|
||||
"has_data": 100,
|
||||
"duration_ms": 486.8026250042021,
|
||||
"throughput_rps": 205.42206402263503,
|
||||
"status_counts": {
|
||||
"200": 100
|
||||
},
|
||||
"latency_ms": {
|
||||
"min": 56.31004192400724,
|
||||
"avg": 94.25131994648837,
|
||||
"max": 153.34666694980115,
|
||||
"p50": 79.22116597183049,
|
||||
"p90": 147.41216704715043,
|
||||
"p95": 151.8984999274835,
|
||||
"p99": 152.9659579973668
|
||||
},
|
||||
"resp_size_bytes": {
|
||||
"avg": 556,
|
||||
"min": 556,
|
||||
"max": 556
|
||||
}
|
||||
}
|
||||
29
scripts/results/code_js1_quick_20251010_232217.json
Normal file
29
scripts/results/code_js1_quick_20251010_232217.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"url": "http://127.0.0.1:9898/api/dynamic/code_js1",
|
||||
"method": "POST",
|
||||
"total": 50,
|
||||
"concurrency": 10,
|
||||
"timeout_sec": 8.0,
|
||||
"ok": 50,
|
||||
"errors": 0,
|
||||
"has_data": 50,
|
||||
"duration_ms": 266.0578330978751,
|
||||
"throughput_rps": 187.9290657140939,
|
||||
"status_counts": {
|
||||
"200": 50
|
||||
},
|
||||
"latency_ms": {
|
||||
"min": 25.911415927112103,
|
||||
"avg": 50.4597524413839,
|
||||
"max": 117.64616600703448,
|
||||
"p50": 37.48199995607138,
|
||||
"p90": 103.3676250372082,
|
||||
"p95": 107.0811670506373,
|
||||
"p99": 108.35866699926555
|
||||
},
|
||||
"resp_size_bytes": {
|
||||
"avg": 556,
|
||||
"min": 556,
|
||||
"max": 556
|
||||
}
|
||||
}
|
||||
95
scripts/run_stress_code_js1.sh
Normal file
95
scripts/run_stress_code_js1.sh
Normal file
@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 默认参数
|
||||
URL="http://127.0.0.1:9898/api/dynamic/code_js1"
|
||||
METHOD="POST"
|
||||
TOTAL=1000
|
||||
CONCURRENCY=50
|
||||
TIMEOUT=8
|
||||
HEADERS='{"Content-Type":"application/json"}'
|
||||
BODY='{}'
|
||||
OUT_PREFIX="scripts/results/code_js1"
|
||||
|
||||
print_usage() {
|
||||
cat <<EOF
|
||||
用法:
|
||||
./scripts/run_stress_code_js1.sh [选项]
|
||||
|
||||
选项:
|
||||
-u, --url URL 压测目标地址(默认:${URL})
|
||||
-m, --method METHOD 请求方法(默认:${METHOD})
|
||||
-t, --total N 总请求数(默认:${TOTAL})
|
||||
-c, --concurrency N 并发数(默认:${CONCURRENCY})
|
||||
-T, --timeout SEC 单次请求超时秒数(默认:${TIMEOUT})
|
||||
-H, --headers JSON 请求头 JSON 字符串(默认:${HEADERS})
|
||||
-b, --body JSON 请求体 JSON 字符串(默认:${BODY})
|
||||
-o, --out-prefix PREFIX 结果前缀(默认:${OUT_PREFIX},只保存 JSON)
|
||||
-h, --help 显示帮助
|
||||
|
||||
示例:
|
||||
# 快速压测 3000 次,请求并发 150,POST 空体,输出到 scripts/results/
|
||||
./scripts/run_stress_code_js1.sh -t 3000 -c 150 -m POST -o scripts/results/code_js1
|
||||
|
||||
# 自定义 URL 与请求体
|
||||
./scripts/run_stress_code_js1.sh \
|
||||
--url http://127.0.0.1:9898/api/dynamic/code_js1 \
|
||||
--method POST \
|
||||
--body '{"foo":"bar"}' \
|
||||
--headers '{"Content-Type":"application/json"}' \
|
||||
--total 2000 --concurrency 100 --timeout 10 \
|
||||
--out-prefix scripts/results/code_js1_run2
|
||||
EOF
|
||||
}
|
||||
|
||||
# 解析参数(支持短/长选项)
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-u|--url) URL="$2"; shift 2 ;;
|
||||
-m|--method) METHOD="$2"; shift 2 ;;
|
||||
-t|--total) TOTAL="$2"; shift 2 ;;
|
||||
-c|--concurrency) CONCURRENCY="$2"; shift 2 ;;
|
||||
-T|--timeout) TIMEOUT="$2"; shift 2 ;;
|
||||
-H|--headers) HEADERS="$2"; shift 2 ;;
|
||||
-b|--body) BODY="$2"; shift 2 ;;
|
||||
-o|--out-prefix) OUT_PREFIX="$2"; shift 2 ;;
|
||||
-h|--help) print_usage; exit 0 ;;
|
||||
*) echo "未知参数:$1"; echo; print_usage; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 环境校验
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "错误:未找到 python3,请先安装 Python。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "scripts/stress_code_js1.py" ]]; then
|
||||
echo "错误:未找到 scripts/stress_code_js1.py,请确认项目目录正确。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 确保输出目录存在
|
||||
mkdir -p "$(dirname "$OUT_PREFIX")"
|
||||
|
||||
echo "运行压测:url=$URL method=$METHOD total=$TOTAL concurrency=$CONCURRENCY timeout=$TIMEOUT"
|
||||
echo "请求头:$HEADERS"
|
||||
echo "请求体:$BODY"
|
||||
echo "结果前缀:$OUT_PREFIX (只保存 JSON)"
|
||||
echo
|
||||
|
||||
set -x
|
||||
python3 scripts/stress_code_js1.py \
|
||||
--url "$URL" \
|
||||
--method "$METHOD" \
|
||||
--total "$TOTAL" \
|
||||
--concurrency "$CONCURRENCY" \
|
||||
--timeout "$TIMEOUT" \
|
||||
--headers "$HEADERS" \
|
||||
--body "$BODY" \
|
||||
--out-prefix "$OUT_PREFIX"
|
||||
set +x
|
||||
|
||||
echo
|
||||
echo "完成。请在上方输出中查看保存的 JSON 结果路径。"
|
||||
156
scripts/stress_code_js1.py
Normal file
156
scripts/stress_code_js1.py
Normal file
@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse, json, time, os, concurrent.futures, statistics
|
||||
from urllib import request
|
||||
from urllib.error import HTTPError, URLError
|
||||
|
||||
def parse_headers(hstr: str):
|
||||
if not hstr:
|
||||
return {}
|
||||
try:
|
||||
return json.loads(hstr)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def has_data(payload):
|
||||
if 'data' not in payload or payload['data'] is None:
|
||||
return False
|
||||
v = payload['data']
|
||||
if isinstance(v, (list, dict)):
|
||||
return len(v) > 0
|
||||
if isinstance(v, str):
|
||||
return len(v.strip()) > 0
|
||||
return True
|
||||
|
||||
def one_request(idx, url, method, headers, body_bytes, timeout):
|
||||
start = time.perf_counter()
|
||||
try:
|
||||
req = request.Request(url, data=body_bytes, headers=headers, method=method)
|
||||
with request.urlopen(req, timeout=timeout) as resp:
|
||||
body = resp.read()
|
||||
elapsed = (time.perf_counter() - start) * 1000.0
|
||||
try:
|
||||
payload = json.loads(body)
|
||||
return {
|
||||
'idx': idx,
|
||||
'ok': True,
|
||||
'elapsed_ms': elapsed,
|
||||
'has_data': has_data(payload),
|
||||
'status': resp.status,
|
||||
'size': len(body)
|
||||
}
|
||||
except Exception as je:
|
||||
return {
|
||||
'idx': idx,
|
||||
'ok': False,
|
||||
'elapsed_ms': elapsed,
|
||||
'error': f'invalid_json: {je}',
|
||||
}
|
||||
except HTTPError as e:
|
||||
elapsed = (time.perf_counter() - start) * 1000.0
|
||||
return {'idx': idx, 'ok': False, 'elapsed_ms': elapsed, 'error': f'HTTP {e.code}: {e.reason}'}
|
||||
except URLError as e:
|
||||
elapsed = (time.perf_counter() - start) * 1000.0
|
||||
return {'idx': idx, 'ok': False, 'elapsed_ms': elapsed, 'error': f'URL error: {e.reason}'}
|
||||
except Exception as e:
|
||||
elapsed = (time.perf_counter() - start) * 1000.0
|
||||
return {'idx': idx, 'ok': False, 'elapsed_ms': elapsed, 'error': str(e)}
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Simple concurrent stress test runner for code_js1 API')
|
||||
parser.add_argument('--url', required=True, help='Target URL')
|
||||
parser.add_argument('--method', default='POST', help='HTTP method, default POST')
|
||||
parser.add_argument('--total', type=int, default=2000, help='Total requests')
|
||||
parser.add_argument('--concurrency', type=int, default=100, help='Concurrent workers')
|
||||
parser.add_argument('--timeout', type=float, default=5.0, help='Per-request timeout in seconds')
|
||||
parser.add_argument('--headers', default='{"Content-Type":"application/json"}', help='JSON headers string')
|
||||
parser.add_argument('--body', default='{}', help='JSON body string (for POST)')
|
||||
parser.add_argument('--out-prefix', default='scripts/results/code_js1', help='Output file prefix (without extension)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
headers = parse_headers(args.headers)
|
||||
try:
|
||||
body_obj = json.loads(args.body) if args.body else {}
|
||||
except Exception:
|
||||
body_obj = {}
|
||||
body_bytes = json.dumps(body_obj).encode('utf-8') if args.method.upper() != 'GET' else None
|
||||
|
||||
print(f"Starting stress: url={args.url} method={args.method} total={args.total} concurrency={args.concurrency}")
|
||||
|
||||
start_all = time.perf_counter()
|
||||
latencies = []
|
||||
errors = []
|
||||
ok_count = 0
|
||||
has_data_count = 0
|
||||
resp_sizes = []
|
||||
statuses = {}
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=args.concurrency) as ex:
|
||||
futures = [ex.submit(one_request, i+1, args.url, args.method, headers, body_bytes, args.timeout) for i in range(args.total)]
|
||||
for fut in concurrent.futures.as_completed(futures):
|
||||
r = fut.result()
|
||||
if r.get('ok'):
|
||||
ok_count += 1
|
||||
latencies.append(r['elapsed_ms'])
|
||||
if r.get('has_data'): has_data_count += 1
|
||||
resp_sizes.append(r.get('size', 0))
|
||||
statuses[r.get('status', 0)] = statuses.get(r.get('status', 0), 0) + 1
|
||||
else:
|
||||
errors.append({'idx': r['idx'], 'error': r.get('error'), 'elapsed_ms': r.get('elapsed_ms')})
|
||||
|
||||
dur_ms = (time.perf_counter() - start_all) * 1000.0
|
||||
throughput = ok_count / (dur_ms / 1000.0) if dur_ms > 0 else 0.0
|
||||
|
||||
result = {
|
||||
'url': args.url,
|
||||
'method': args.method,
|
||||
'total': args.total,
|
||||
'concurrency': args.concurrency,
|
||||
'timeout_sec': args.timeout,
|
||||
'ok': ok_count,
|
||||
'errors': len(errors),
|
||||
'has_data': has_data_count,
|
||||
'duration_ms': dur_ms,
|
||||
'throughput_rps': throughput,
|
||||
'status_counts': statuses,
|
||||
}
|
||||
|
||||
if latencies:
|
||||
lat_sorted = sorted(latencies)
|
||||
def pct(p):
|
||||
idx = max(min(int(p*len(lat_sorted)) - 1, len(lat_sorted)-1), 0)
|
||||
return lat_sorted[idx]
|
||||
result['latency_ms'] = {
|
||||
'min': lat_sorted[0],
|
||||
'avg': statistics.mean(latencies),
|
||||
'max': lat_sorted[-1],
|
||||
'p50': pct(0.50),
|
||||
'p90': pct(0.90),
|
||||
'p95': pct(0.95),
|
||||
'p99': pct(0.99),
|
||||
}
|
||||
result['resp_size_bytes'] = {
|
||||
'avg': statistics.mean(resp_sizes) if resp_sizes else 0,
|
||||
'min': min(resp_sizes) if resp_sizes else 0,
|
||||
'max': max(resp_sizes) if resp_sizes else 0,
|
||||
}
|
||||
|
||||
# ensure results dir exists
|
||||
out_dir = os.path.dirname(args.out_prefix)
|
||||
if out_dir and not os.path.exists(out_dir):
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
ts = time.strftime('%Y%m%d_%H%M%S')
|
||||
json_path = f"{args.out_prefix}_{ts}.json"
|
||||
|
||||
# save JSON summary only
|
||||
with open(json_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(result, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# print summary
|
||||
print("\n=== 压测结果总结 ===")
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
print(f"结果已保存: {json_path}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user