UNPKG

cf-speedtest

Version:

CLI tool for testing internet speed using Cloudflare's network

203 lines (195 loc) 5.56 kB
// src/logger/logger.ts import { styleText } from "util"; function toMbps(bps) { return (bps / 1e6).toFixed(2); } function logServerLocation(colo) { console.log( styleText(["bold", "blue"], "\u{1F4CD} Server Location:"), styleText("green", colo) ); } function logLatency(ms) { console.log( styleText(["bold", "cyan"], "\u{1F3D3} Latency:"), styleText("green", `${ms.toFixed(2)}ms`) ); } function logJitter(ms) { console.log( styleText(["bold", "cyan"], "\u{1F4CA} Jitter:"), styleText("green", `${ms.toFixed(2)}ms`) ); } function logDownload(bps) { console.log( styleText(["bold", "yellow"], "\u2B07\uFE0F Download:"), styleText("green", `${toMbps(bps)} Mbps`) ); } function logUpload(bps) { console.log( styleText(["bold", "magenta"], "\u2B06\uFE0F Upload:"), styleText("green", `${toMbps(bps)} Mbps`) ); } function logResults(results) { logServerLocation(results.serverLocation); logLatency(results.latency); logJitter(results.jitter); logDownload(results.download); logUpload(results.upload); } // src/measurements/download.ts import { get } from "https"; var DOWNLOAD_URL = "https://speed.cloudflare.com/__down"; function measureDownloadOnce(bytes) { const url = `${DOWNLOAD_URL}?bytes=${bytes}`; return new Promise((resolve, reject) => { const start = performance.now(); get(url, (res) => { let received = 0; res.on("data", (chunk) => { received += chunk.length; }); res.on("end", () => { const durationMs = performance.now() - start; const bps = received * 8 / (durationMs / 1e3); resolve({ bytes: received, durationMs, bps }); }); res.on("error", reject); }).on("error", reject); }); } async function measureDownload(measurements) { const results = []; for (const { bytes, count, bypassMinDuration } of measurements) { for (let i = 0; i < count; i++) { const result = await measureDownloadOnce(bytes); if (bypassMinDuration || result.durationMs >= 10) { results.push(result.bps); } } } return results; } // src/utils/stats.ts function percentile(values, p) { if (values.length === 0) return 0; if (values.length === 1) return values[0]; const sorted = [...values].sort((a, b) => a - b); const index = p / 100 * (sorted.length - 1); const lower = Math.floor(index); const upper = Math.ceil(index); if (lower === upper) return sorted[lower]; const weight = index - lower; return sorted[lower] + (sorted[upper] - sorted[lower]) * weight; } function jitter(samples) { if (samples.length <= 1) return 0; let sum = 0; for (let i = 1; i < samples.length; i++) { sum += Math.abs(samples[i] - samples[i - 1]); } return sum / (samples.length - 1); } // src/measurements/latency.ts var TRACE_URL = "https://1.1.1.1/cdn-cgi/trace"; async function measurePing() { const start = performance.now(); await fetch(TRACE_URL, { method: "HEAD" }); const end = performance.now(); return end - start; } async function measureLatency(numPackets) { const pings = []; for (let i = 0; i < numPackets; i++) { const ping = await measurePing(); pings.push(ping); } return { latency: percentile(pings, 50), jitter: jitter(pings) }; } // src/measurements/upload.ts var UPLOAD_URL = "https://speed.cloudflare.com/__up"; async function measureUploadOnce(bytes) { const body = new Uint8Array(bytes); const start = performance.now(); await fetch(UPLOAD_URL, { method: "POST", body, headers: { "Content-Type": "application/octet-stream" } }); const end = performance.now(); const durationMs = end - start; const bps = bytes * 8 / (durationMs / 1e3); return { bytes, durationMs, bps }; } async function measureUpload(measurements) { const results = []; for (const { bytes, count } of measurements) { for (let i = 0; i < count; i++) { const result = await measureUploadOnce(bytes); if (result.durationMs >= 10) { results.push(result.bps); } } } return results; } // src/utils/location.ts async function getServerLocation() { const response = await fetch("https://1.1.1.1/cdn-cgi/trace"); const text = await response.text(); const pairs = /* @__PURE__ */ new Map(); for (const line of text.split("\n")) { const idx = line.indexOf("="); if (idx !== -1) { pairs.set(line.slice(0, idx), line.slice(idx + 1)); } } return { ip: pairs.get("ip") ?? "Unknown", colo: pairs.get("colo") ?? "Unknown", country: pairs.get("loc") ?? "Unknown" }; } // src/index.ts var DOWNLOAD_MEASUREMENTS = [ { bytes: 1e5, count: 1, bypassMinDuration: true }, // warmup { bytes: 1e5, count: 9 }, { bytes: 1e6, count: 8 }, { bytes: 1e7, count: 6 }, { bytes: 25e6, count: 4 }, { bytes: 1e8, count: 3 }, { bytes: 25e7, count: 2 } ]; var UPLOAD_MEASUREMENTS = [ { bytes: 1e5, count: 8 }, { bytes: 1e6, count: 6 }, { bytes: 1e7, count: 4 }, { bytes: 25e6, count: 4 }, { bytes: 5e7, count: 3 } ]; async function runCLI() { const location = await getServerLocation(); await measureLatency(1); const { latency, jitter: jitter2 } = await measureLatency(20); const downloadSamples = await measureDownload(DOWNLOAD_MEASUREMENTS); const uploadSamples = await measureUpload(UPLOAD_MEASUREMENTS); logResults({ latency, jitter: jitter2, download: percentile(downloadSamples, 90), upload: percentile(uploadSamples, 90), serverLocation: location.colo }); } export { runCLI };