| 162 | await asyncio.gather(*tasks) |
| 163 | |
| 164 | def summarize(results: List[Result]): |
| 165 | ok = [r for r in results if r.ok] |
| 166 | fail = [r for r in results if not r.ok] |
| 167 | |
| 168 | connect_ms = [r.connect_ms for r in ok] |
| 169 | fb_ms = [r.first_byte_ms for r in ok] |
| 170 | total_ms = [r.total_ms for r in ok] |
| 171 | |
| 172 | codes = Counter(r.status for r in ok if r.status is not None) |
| 173 | errors = Counter(r.error for r in fail) |
| 174 | |
| 175 | def fmt_dist(c: Counter, top=5): |
| 176 | return ", ".join(f"{k}:{v}" for k,v in c.most_common(top)) or "-" |
| 177 | |
| 178 | def fmt_perc(vals, name): |
| 179 | p = percentiles(vals) |
| 180 | return f"{name} p50={p[50]:.1f} p90={p[90]:.1f} p95={p[95]:.1f} p99={p[99]:.1f}" |
| 181 | |
| 182 | lines = [] |
| 183 | lines.append(f"Total={len(results)} OK={len(ok)} Fail={len(fail)} " |
| 184 | f"SuccessRate={ (len(ok)/len(results)*100 if results else 0):.2f}%") |
| 185 | if ok: |
| 186 | lines.append(fmt_perc(connect_ms, "Connect(ms)")) |
| 187 | lines.append(fmt_perc(fb_ms, "FirstByte(ms)")) |
| 188 | lines.append(fmt_perc(total_ms, "Total(ms)")) |
| 189 | lines.append(f"HTTP Codes: {fmt_dist(codes)}") |
| 190 | if fail: |
| 191 | lines.append(f"Errors: {fmt_dist(errors)}") |
| 192 | # 按 phase 汇总 |
| 193 | by_phase = defaultdict(list) |
| 194 | for r in results: |
| 195 | by_phase[r.phase].append(r) |
| 196 | if len(by_phase) > 1: |
| 197 | lines.append("Per-Phase Success:") |
| 198 | for ph, lst in by_phase.items(): |
| 199 | o = sum(1 for x in lst if x.ok) |
| 200 | lines.append(f" {ph}: {o}/{len(lst)} = {o/len(lst)*100:.1f}%") |
| 201 | return "\n".join(lines) |
| 202 | |
| 203 | def parse_args(): |
| 204 | ap = argparse.ArgumentParser(description="SOCKS5 concurrency test for dynamic prewarm observation") |