understanding commited on
Commit
c2f82fe
·
verified ·
1 Parent(s): 5937486

Create speedtest.py

Browse files
Files changed (1) hide show
  1. bot/core/speedtest.py +170 -0
bot/core/speedtest.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PATH: bot/core/speedtest.py
2
+ import asyncio
3
+ import time
4
+ import shutil
5
+ from typing import Dict, Any, Optional
6
+
7
+ import httpx
8
+
9
+ from bot.core.uptime import uptime_seconds
10
+ from bot.core.progress import human_bytes, human_eta
11
+
12
+ DL_URL = "https://speed.cloudflare.com/__down?bytes={bytes}"
13
+ UL_URL = "https://httpbin.org/post" # simple echo endpoint (kabhi kabhi rate-limit ho sakta)
14
+
15
+ def _bps_to_mbps(bps: float) -> float:
16
+ return (bps * 8.0) / 1_000_000.0
17
+
18
+ async def tcp_ping_ms(host: str = "1.1.1.1", port: int = 443, timeout: float = 3.0) -> Optional[float]:
19
+ t0 = time.perf_counter()
20
+ try:
21
+ conn = asyncio.open_connection(host, port)
22
+ r, w = await asyncio.wait_for(conn, timeout=timeout)
23
+ w.close()
24
+ try:
25
+ await w.wait_closed()
26
+ except Exception:
27
+ pass
28
+ return (time.perf_counter() - t0) * 1000.0
29
+ except Exception:
30
+ return None
31
+
32
+ async def download_test(bytes_to_dl: int = 50 * 1024 * 1024, timeout: float = 30.0) -> Dict[str, Any]:
33
+ url = DL_URL.format(bytes=bytes_to_dl)
34
+ t0 = time.perf_counter()
35
+ total = 0
36
+
37
+ async with httpx.AsyncClient(follow_redirects=True, timeout=timeout) as client:
38
+ async with client.stream("GET", url) as resp:
39
+ resp.raise_for_status()
40
+ async for chunk in resp.aiter_bytes():
41
+ total += len(chunk)
42
+
43
+ dt = max(0.0001, time.perf_counter() - t0)
44
+ bps = total / dt
45
+ return {
46
+ "ok": True,
47
+ "bytes": total,
48
+ "sec": dt,
49
+ "bps": bps,
50
+ "mbps": _bps_to_mbps(bps),
51
+ }
52
+
53
+ async def upload_test(bytes_to_ul: int = 10 * 1024 * 1024, timeout: float = 30.0) -> Dict[str, Any]:
54
+ # stream upload (no big RAM spike)
55
+ chunk = b"\x00" * (256 * 1024)
56
+ remaining = bytes_to_ul
57
+
58
+ async def gen():
59
+ nonlocal remaining
60
+ while remaining > 0:
61
+ take = chunk if remaining >= len(chunk) else b"\x00" * remaining
62
+ remaining -= len(take)
63
+ yield take
64
+
65
+ t0 = time.perf_counter()
66
+ async with httpx.AsyncClient(timeout=timeout) as client:
67
+ r = await client.post(
68
+ UL_URL,
69
+ content=gen(),
70
+ headers={"content-type": "application/octet-stream"},
71
+ )
72
+ r.raise_for_status()
73
+
74
+ sent = bytes_to_ul
75
+ dt = max(0.0001, time.perf_counter() - t0)
76
+ bps = sent / dt
77
+ return {
78
+ "ok": True,
79
+ "bytes": sent,
80
+ "sec": dt,
81
+ "bps": bps,
82
+ "mbps": _bps_to_mbps(bps),
83
+ }
84
+
85
+ def storage_info(path: str = ".") -> Dict[str, Any]:
86
+ du = shutil.disk_usage(path)
87
+ return {
88
+ "total": du.total,
89
+ "used": du.used,
90
+ "free": du.free,
91
+ }
92
+
93
+ async def run_speedtest(worker2_stats_fn=None) -> Dict[str, Any]:
94
+ out: Dict[str, Any] = {}
95
+ out["uptime_sec"] = uptime_seconds()
96
+
97
+ # ping (tcp)
98
+ out["ping_ms"] = await tcp_ping_ms()
99
+
100
+ # storage
101
+ st = storage_info(".")
102
+ out["storage_total"] = st["total"]
103
+ out["storage_free"] = st["free"]
104
+
105
+ # download
106
+ try:
107
+ out["download"] = await download_test()
108
+ except Exception as e:
109
+ out["download"] = {"ok": False, "err": f"{type(e).__name__}: {e}"}
110
+
111
+ # upload
112
+ try:
113
+ out["upload"] = await upload_test()
114
+ except Exception as e:
115
+ out["upload"] = {"ok": False, "err": f"{type(e).__name__}: {e}"}
116
+
117
+ # optional: Worker2 KV RTT (stats_today hits KV)
118
+ if worker2_stats_fn:
119
+ t0 = time.perf_counter()
120
+ try:
121
+ j = await worker2_stats_fn()
122
+ out["w2_kv_rtt_ms"] = (time.perf_counter() - t0) * 1000.0
123
+ out["w2_ok"] = bool(isinstance(j, dict) and j.get("ok"))
124
+ except Exception as e:
125
+ out["w2_kv_rtt_ms"] = None
126
+ out["w2_ok"] = False
127
+ out["w2_err"] = f"{type(e).__name__}: {e}"
128
+
129
+ return out
130
+
131
+ def format_report(r: Dict[str, Any]) -> str:
132
+ up = r.get("uptime_sec", 0)
133
+ ping = r.get("ping_ms", None)
134
+
135
+ st_total = r.get("storage_total", 0)
136
+ st_free = r.get("storage_free", 0)
137
+
138
+ dl = r.get("download", {})
139
+ ul = r.get("upload", {})
140
+
141
+ lines = []
142
+ lines.append("🏁 **SpeedTest Report**")
143
+ lines.append("")
144
+ lines.append(f"🕒 Uptime: `{human_eta(up)}`")
145
+ lines.append(f"💾 Storage: free `{human_bytes(st_free)}` / total `{human_bytes(st_total)}`")
146
+ if ping is None:
147
+ lines.append("📡 Ping: `failed`")
148
+ else:
149
+ lines.append(f"📡 Ping (TCP): `{ping:.1f} ms`")
150
+
151
+ # Download
152
+ if dl.get("ok"):
153
+ lines.append(f"⬇️ Download: `{dl['mbps']:.2f} Mbps` ({human_bytes(dl['bytes'])} in {dl['sec']:.2f}s)")
154
+ else:
155
+ lines.append(f"⬇️ Download: `failed` ({dl.get('err','unknown')})")
156
+
157
+ # Upload
158
+ if ul.get("ok"):
159
+ lines.append(f"⬆️ Upload: `{ul['mbps']:.2f} Mbps` ({human_bytes(ul['bytes'])} in {ul['sec']:.2f}s)")
160
+ else:
161
+ lines.append(f"⬆️ Upload: `failed` ({ul.get('err','unknown')})")
162
+
163
+ # Worker2 KV RTT
164
+ if "w2_kv_rtt_ms" in r:
165
+ if r.get("w2_kv_rtt_ms") is None:
166
+ lines.append(f"🗄️ Worker2 KV RTT: `failed` ({r.get('w2_err','')})")
167
+ else:
168
+ lines.append(f"🗄️ Worker2 KV RTT: `{r['w2_kv_rtt_ms']:.1f} ms` (ok={r.get('w2_ok')})")
169
+
170
+ return "\n".join(lines)