speed_test / app.py
euler314's picture
Update app.py
89408c1 verified
# 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)