File size: 7,465 Bytes
b2cff8f
 
fb4f63a
 
5f018aa
 
 
fb4f63a
89408c1
 
 
 
 
 
fb4f63a
89408c1
 
 
 
 
 
 
fb4f63a
 
 
89408c1
fb4f63a
 
 
 
 
 
 
 
89408c1
fb4f63a
 
 
 
 
 
 
 
89408c1
 
fb4f63a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89408c1
fb4f63a
 
 
 
 
 
 
 
 
 
89408c1
fb4f63a
 
89408c1
 
 
 
 
 
 
fb4f63a
 
 
 
 
 
89408c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fb4f63a
 
 
 
 
89408c1
fb4f63a
 
 
89408c1
fb4f63a
 
 
 
 
 
 
 
 
 
 
 
 
89408c1
fb4f63a
 
 
 
5f018aa
 
fb4f63a
89408c1
fb4f63a
5f018aa
 
89408c1
 
 
 
 
5f018aa
89408c1
5f018aa
89408c1
5f018aa
 
fb4f63a
 
89408c1
fb4f63a
5f018aa
 
89408c1
 
 
 
 
 
 
 
 
5f018aa
fb4f63a
5f018aa
89408c1
5f018aa
b2cff8f
5f018aa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# app.py

from flask import Flask, Response, request, jsonify
import os

app = Flask(__name__)

# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# Configuration: Test size = 1 GiB
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
DOWNLOAD_SIZE_BYTES = 1 * 1024 * 1024 * 1024  # 1 GiB

# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# Generator for streaming 1 GiB of random bytes in 10 MiB chunks
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
def generate_random_blob():
    sent = 0
    chunk_size = 10 * 1024 * 1024  # 10 MiB per chunk
    while sent < DOWNLOAD_SIZE_BYTES:
        to_send = min(chunk_size, DOWNLOAD_SIZE_BYTES - sent)
        yield os.urandom(to_send)
        sent += to_send


# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# Route: β€œ/” – serve the HTML + JS client
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
@app.route("/")
def index():
    html = """
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <title>Client-Side Speed Test (1 GiB)</title>
      <style>
        body { font-family: sans-serif; max-width: 600px; margin: 2em auto; }
        button { padding: 0.5em 1em; font-size: 1rem; }
        .result { margin-top: 1.5em; }
        .label { font-weight: bold; }
      </style>
    </head>
    <body>
      <h2>Wi-Fi / Ethernet Speed Test (Up to 1 GiB)</h2>
      <p>This test will measure your real Internet speed by downloading <strong>1 GiB</strong> of random data and uploading <strong>1 GiB</strong> back to the server. All timing runs entirely in your browser.</p>
      <button id="startBtn">Start Speed Test</button>

      <div class="result" id="results" style="display: none;">
        <p><span class="label">Download Speed:</span> <span id="dlSpeed">–</span></p>
        <p><span class="label">Upload Speed:</span> <span id="ulSpeed">–</span></p>
        <p><span class="label">Ping:</span> <span id="ping">–</span></p>
      </div>

      <script>
        const startBtn = document.getElementById("startBtn");
        const resultsDiv = document.getElementById("results");
        const dlSpan = document.getElementById("dlSpeed");
        const ulSpan = document.getElementById("ulSpeed");
        const pingSpan = document.getElementById("ping");

        // Convert bits/sec to Mbps string
        function toMbps(bits) {
          return (bits / 1e6).toFixed(2) + " Mbps";
        }

        startBtn.addEventListener("click", async () => {
          startBtn.disabled = true;
          resultsDiv.style.display = "block";
          dlSpan.textContent = "Testing…";
          ulSpan.textContent = "Testing…";
          pingSpan.textContent = "Testing…";

          // 1) Measure ping: 5 tiny GETs to /ping_test
          let pingTimes = [];
          for (let i = 0; i < 5; i++) {
            const t0 = performance.now();
            await fetch("/ping_test?t=" + i, { cache: "no-store" });
            const t1 = performance.now();
            pingTimes.push(t1 - t0);
          }
          const avgPing = pingTimes.reduce((a, b) => a + b, 0) / pingTimes.length;
          pingSpan.textContent = avgPing.toFixed(2) + " ms";

          // 2) Download test: stream 1 GiB from /download_test
          const dlStart = performance.now();
          const response = await fetch("/download_test", { cache: "no-store" });
          const reader = response.body.getReader();
          let dlBytes = 0;
          while (true) {
            const { done, value } = await reader.read();
            if (done) break;
            dlBytes += value.length;
          }
          const dlEnd = performance.now();
          const dlBits = dlBytes * 8;
          const dlDurationSec = (dlEnd - dlStart) / 1000;
          const dlSpeedBps = dlBits / dlDurationSec;
          dlSpan.textContent = toMbps(dlSpeedBps);

          // 3) Upload test: stream 1 GiB of random data to /upload_test
          const uploadTotalBytes = dlBytes; // should be 1 GiB
          const chunkSize = 10 * 1024 * 1024; // 10 MiB
          let sentBytes = 0;

          const uploadStream = new ReadableStream({
            start(controller) {
              function pushChunk() {
                if (sentBytes >= uploadTotalBytes) {
                  controller.close();
                  return;
                }
                const size = Math.min(chunkSize, uploadTotalBytes - sentBytes);
                const chunk = new Uint8Array(size);
                window.crypto.getRandomValues(chunk);
                controller.enqueue(chunk);
                sentBytes += size;
                // immediately queue next
                pushChunk();
              }
              pushChunk();
            }
          });

          const ulStart = performance.now();
          await fetch("/upload_test", {
            method: "POST",
            headers: { "Content-Type": "application/octet-stream" },
            body: uploadStream
          });
          const ulEnd = performance.now();
          const ulDurationSec = (ulEnd - ulStart) / 1000;
          const ulBits = uploadTotalBytes * 8;
          const ulSpeedBps = ulBits / ulDurationSec;
          ulSpan.textContent = toMbps(ulSpeedBps);

          startBtn.disabled = false;
        });
      </script>
    </body>
    </html>
    """
    return Response(html, mimetype="text/html")


# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# Route: /ping_test – empty 200 response for ping
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
@app.route("/ping_test")
def ping_test():
    return Response(status=200)


# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# Route: /download_test – stream 1 GiB of random data
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
@app.route("/download_test")
def download_test():
    headers = {
        "Content-Disposition": f"attachment; filename=\"test_{DOWNLOAD_SIZE_BYTES}.bin\"",
        "Content-Length": str(DOWNLOAD_SIZE_BYTES),
        "Cache-Control": "no-store"
    }
    return Response(
        generate_random_blob(),
        mimetype="application/octet-stream",
        headers=headers
    )


# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# Route: /upload_test – consume uploaded data in chunks and return JSON
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
@app.route("/upload_test", methods=["POST"])
def upload_test():
    total = 0
    # Read the incoming stream in 1 MiB chunks and discard
    while True:
        chunk = request.stream.read(1 * 1024 * 1024)
        if not chunk:
            break
        total += len(chunk)
    # total should be near 1 GiB if upload completed
    return jsonify({"received_bytes": total})


# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
# Run the Flask app on port 7860
# β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=7860, debug=False)