()
| 219 | print("="*70) |
| 220 | |
| 221 | async def main(): |
| 222 | args = parse_args() |
| 223 | if args.seed is not None: |
| 224 | random.seed(args.seed) |
| 225 | |
| 226 | print_header() |
| 227 | results: List[Result] = [] |
| 228 | t0 = time.monotonic() |
| 229 | |
| 230 | if args.concurrency: |
| 231 | if not args.total: |
| 232 | print("--total 必须指定(固定并发模式)", file=sys.stderr) |
| 233 | sys.exit(1) |
| 234 | print(f"[Phase single] concurrency={args.concurrency} total={args.total}") |
| 235 | await run_phase("phase1", args.concurrency, args.total, |
| 236 | args.timeout, args.jitter, results) |
| 237 | else: |
| 238 | # ramp 模式 |
| 239 | stages = [int(x.strip()) for x in args.ramp.split(",") if x.strip()] |
| 240 | if not stages: |
| 241 | print("无效 ramp 列表", file=sys.stderr) |
| 242 | sys.exit(1) |
| 243 | if not args.per_stage: |
| 244 | print("--per-stage 必须指定(ramp 模式)", file=sys.stderr) |
| 245 | sys.exit(1) |
| 246 | for i, c in enumerate(stages, 1): |
| 247 | print(f"[Phase {i}] concurrency={c} total={args.per_stage}") |
| 248 | await run_phase(f"phase{i}", c, args.per_stage, |
| 249 | args.timeout, args.jitter, results) |
| 250 | |
| 251 | elapsed = time.monotonic() - t0 |
| 252 | print("\n=== Summary ===") |
| 253 | print(summarize(results)) |
| 254 | print(f"Elapsed: {elapsed:.2f}s Approx QPS: {len(results)/elapsed:.1f}") |
| 255 | |
| 256 | if args.save: |
| 257 | out = [] |
| 258 | for r in results: |
| 259 | out.append({ |
| 260 | "ok": r.ok, |
| 261 | "error": r.error, |
| 262 | "connect_ms": r.connect_ms, |
| 263 | "first_byte_ms": r.first_byte_ms, |
| 264 | "total_ms": r.total_ms, |
| 265 | "status": r.status, |
| 266 | "phase": r.phase |
| 267 | }) |
| 268 | with open(args.save, "w") as f: |
| 269 | json.dump(out, f, ensure_ascii=False, indent=2) |
| 270 | print(f"Saved JSON results -> {args.save}") |
| 271 | |
| 272 | if __name__ == "__main__": |
| 273 | try: |
no test coverage detected