understanding commited on
Commit
c37aecc
·
verified ·
1 Parent(s): 9ec7b1a

Update bot/core/speedtest.py

Browse files
Files changed (1) hide show
  1. bot/core/speedtest.py +60 -148
bot/core/speedtest.py CHANGED
@@ -1,170 +1,82 @@
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)
 
 
 
1
  # PATH: bot/core/speedtest.py
2
+ import os
3
  import time
4
  import shutil
5
+ import secrets
 
6
  import httpx
7
 
8
+ # Defaults (can be overridden via env)
9
+ # Cloudflare speed test endpoints (no "HF" mention anywhere)
10
+ DL_URL = os.environ.get("SPEEDTEST_DL_URL", "https://speed.cloudflare.com/__down")
11
+ UP_URL = os.environ.get("SPEEDTEST_UP_URL", "https://speed.cloudflare.com/__up")
12
+ PING_URL = os.environ.get("SPEEDTEST_PING_URL", "https://www.google.com/generate_204")
13
+
14
+ DL_BYTES_DEFAULT = int(float(os.environ.get("SPEEDTEST_DL_BYTES", "8000000"))) # ~8MB
15
+ UP_BYTES_DEFAULT = int(float(os.environ.get("SPEEDTEST_UP_BYTES", "3000000"))) # ~3MB
16
+ TIMEOUT_SEC = float(os.environ.get("SPEEDTEST_TIMEOUT_SEC", "30"))
17
 
18
+ def bytes_to_mb(x: float) -> float:
19
+ # "real mb/s" => treat 1 MB = 1024*1024 bytes
20
+ return float(x) / (1024.0 * 1024.0)
21
 
22
+ def bytes_per_sec_to_mb_s(bps: float) -> float:
23
+ return bytes_to_mb(bps)
24
+
25
+ def disk_total_free(path: str = "/") -> dict:
26
+ du = shutil.disk_usage(path)
27
+ return {"total": du.total, "free": du.free, "used": du.used}
28
 
29
+ async def ping_ms() -> float | None:
30
  t0 = time.perf_counter()
31
  try:
32
+ async with httpx.AsyncClient(timeout=TIMEOUT_SEC, follow_redirects=True) as c:
33
+ r = await c.get(PING_URL, headers={"cache-control": "no-cache"})
34
+ # Even if status not 204, RTT still valid for "ping-like"
35
+ _ = r.status_code
 
 
 
36
  return (time.perf_counter() - t0) * 1000.0
37
  except Exception:
38
  return None
39
 
40
+ async def net_download_test(bytes_to_get: int | None = None) -> dict:
41
+ n = int(bytes_to_get or DL_BYTES_DEFAULT)
42
  t0 = time.perf_counter()
43
+ got = 0
44
 
45
+ try:
46
+ async with httpx.AsyncClient(timeout=TIMEOUT_SEC, follow_redirects=True) as c:
47
+ # Cloudflare accepts ?bytes=...
48
+ url = f"{DL_URL}?bytes={n}"
49
+ async with c.stream("GET", url, headers={"cache-control": "no-cache"}) as r:
50
+ r.raise_for_status()
51
+ async for chunk in r.aiter_bytes():
52
+ got += len(chunk)
53
+ if got >= n:
54
+ break
55
+ except Exception:
56
+ # return partial if any
57
+ pass
 
 
58
 
59
+ sec = max(0.001, time.perf_counter() - t0)
60
+ bps = float(got) / sec
61
+ return {"bytes": got, "seconds": sec, "bps": bps}
 
62
 
63
+ async def net_upload_test(bytes_to_send: int | None = None) -> dict:
64
+ n = int(bytes_to_send or UP_BYTES_DEFAULT)
65
+ payload = secrets.token_bytes(n)
 
 
 
66
 
67
  t0 = time.perf_counter()
68
+ sent = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  try:
70
+ async with httpx.AsyncClient(timeout=TIMEOUT_SEC, follow_redirects=True) as c:
71
+ # Cloudflare accepts ?bytes=... (and body upload)
72
+ url = f"{UP_URL}?bytes={n}"
73
+ r = await c.post(url, content=payload, headers={"content-type": "application/octet-stream"})
74
+ # If server returns error, still count attempt
75
+ _ = r.status_code
76
+ sent = n
77
+ except Exception:
78
+ sent = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
+ sec = max(0.001, time.perf_counter() - t0)
81
+ bps = float(sent) / sec
82
+ return {"bytes": sent, "seconds": sec, "bps": bps}