cf-speedtest
Version:
CLI tool for testing internet speed using Cloudflare's network
203 lines (195 loc) • 5.56 kB
JavaScript
// 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
};