this repo has no description
at master 257 lines 6.5 kB view raw
1#!/usr/bin/env node 2 3const { performance } = require("perf_hooks"); 4const https = require("https"); 5const { magenta, bold, yellow, green, blue } = require("./chalk.js"); 6const stats = require("./stats.js"); 7 8async function get(hostname, path) { 9 return new Promise((resolve, reject) => { 10 const req = https.request( 11 { 12 hostname, 13 path, 14 method: "GET", 15 }, 16 (res) => { 17 const body = []; 18 res.on("data", (chunk) => { 19 body.push(chunk); 20 }); 21 res.on("end", () => { 22 try { 23 resolve(Buffer.concat(body).toString()); 24 } catch (e) { 25 reject(e); 26 } 27 }); 28 req.on("error", (err) => { 29 reject(err); 30 }); 31 }, 32 ); 33 34 req.end(); 35 }); 36} 37 38async function fetchServerLocationData() { 39 const res = JSON.parse(await get("speed.cloudflare.com", "/locations")); 40 41 return res.reduce((data, { iata, city }) => { 42 // Bypass prettier "no-assign-param" rules 43 const data1 = data; 44 45 data1[iata] = city; 46 return data1; 47 }, {}); 48} 49 50function fetchCfCdnCgiTrace() { 51 const parseCfCdnCgiTrace = (text) => 52 text 53 .split("\n") 54 .map((i) => { 55 const j = i.split("="); 56 57 return [j[0], j[1]]; 58 }) 59 .reduce((data, [k, v]) => { 60 if (v === undefined) return data; 61 62 // Bypass prettier 63 // "no-assign-param" rules 64 const data1 = data; 65 // Object.fromEntries is only 66 // supported by Node.js 12 or newer 67 data1[k] = v; 68 69 return data1; 70 }, {}); 71 72 return get("speed.cloudflare.com", "/cdn-cgi/trace").then(parseCfCdnCgiTrace); 73} 74 75function request(options, data = "") { 76 let started; 77 let dnsLookup; 78 let tcpHandshake; 79 let sslHandshake; 80 let ttfb; 81 let ended; 82 83 options.agent = new https.Agent(options); 84 85 return new Promise((resolve, reject) => { 86 started = performance.now(); 87 const req = https.request(options, (res) => { 88 res.once("readable", () => { 89 ttfb = performance.now(); 90 }); 91 res.on("data", () => {}); 92 res.on("end", () => { 93 ended = performance.now(); 94 resolve([started, dnsLookup, tcpHandshake, sslHandshake, ttfb, ended, parseFloat(res.headers["server-timing"].slice(22))]); 95 }); 96 }); 97 98 req.on("socket", (socket) => { 99 socket.on("lookup", () => { 100 dnsLookup = performance.now(); 101 }); 102 socket.on("connect", () => { 103 tcpHandshake = performance.now(); 104 }); 105 socket.on("secureConnect", () => { 106 sslHandshake = performance.now(); 107 }); 108 }); 109 110 req.on("error", (error) => { 111 reject(error); 112 }); 113 114 req.write(data); 115 req.end(); 116 }); 117} 118 119function download(bytes) { 120 const options = { 121 hostname: "speed.cloudflare.com", 122 path: `/__down?bytes=${bytes}`, 123 method: "GET", 124 }; 125 126 return request(options); 127} 128 129function upload(bytes) { 130 const data = "0".repeat(bytes); 131 const options = { 132 hostname: "speed.cloudflare.com", 133 path: "/__up", 134 method: "POST", 135 headers: { 136 "Content-Length": Buffer.byteLength(data), 137 }, 138 }; 139 140 return request(options, data); 141} 142 143function measureSpeed(bytes, duration) { 144 return (bytes * 8) / (duration / 1000) / 1e6; 145} 146 147async function measureLatency() { 148 const measurements = []; 149 150 for (let i = 0; i < 20; i += 1) { 151 await download(1000).then( 152 (response) => { 153 // TTFB - Server processing time 154 measurements.push(response[4] - response[0] - response[6]); 155 }, 156 (error) => { 157 console.log(`Error: ${error}`); 158 }, 159 ); 160 } 161 162 return [Math.min(...measurements), Math.max(...measurements), stats.average(measurements), stats.median(measurements), stats.jitter(measurements)]; 163} 164 165async function measureDownload(bytes, iterations) { 166 const measurements = []; 167 168 for (let i = 0; i < iterations; i += 1) { 169 await download(bytes).then( 170 (response) => { 171 const transferTime = response[5] - response[4]; 172 measurements.push(measureSpeed(bytes, transferTime)); 173 }, 174 (error) => { 175 console.log(`Error: ${error}`); 176 }, 177 ); 178 } 179 180 return measurements; 181} 182 183async function measureUpload(bytes, iterations) { 184 const measurements = []; 185 186 for (let i = 0; i < iterations; i += 1) { 187 await upload(bytes).then( 188 (response) => { 189 const transferTime = response[6]; 190 measurements.push(measureSpeed(bytes, transferTime)); 191 }, 192 (error) => { 193 console.log(`Error: ${error}`); 194 }, 195 ); 196 } 197 198 return measurements; 199} 200 201function logInfo(text, data) { 202 console.log(bold(" ".repeat(15 - text.length), `${text}:`, blue(data))); 203} 204 205function logLatency(data) { 206 console.log(bold(" Latency:", magenta(`${data[3].toFixed(2)} ms`))); 207 console.log(bold(" Jitter:", magenta(`${data[4].toFixed(2)} ms`))); 208} 209 210function logSpeedTestResult(size, test) { 211 const speed = stats.median(test).toFixed(2); 212 console.log(bold(" ".repeat(9 - size.length), size, "speed:", yellow(`${speed} Mbps`))); 213} 214 215function logDownloadSpeed(tests) { 216 console.log(bold(" Download speed:", green(stats.quartile(tests, 0.9).toFixed(2), "Mbps"))); 217} 218 219function logUploadSpeed(tests) { 220 console.log(bold(" Upload speed:", green(stats.quartile(tests, 0.9).toFixed(2), "Mbps"))); 221} 222 223async function speedTest() { 224 const [ping, serverLocationData, { ip, loc, colo }] = await Promise.all([measureLatency(), fetchServerLocationData(), fetchCfCdnCgiTrace()]); 225 226 const city = serverLocationData[colo]; 227 logInfo("Server location", `${city} (${colo})`); 228 logInfo("Your IP", `${ip} (${loc})`); 229 230 logLatency(ping); 231 232 const testDown1 = await measureDownload(101000, 10); 233 logSpeedTestResult("100kB", testDown1); 234 235 const testDown2 = await measureDownload(1001000, 8); 236 logSpeedTestResult("1MB", testDown2); 237 238 const testDown3 = await measureDownload(10001000, 6); 239 logSpeedTestResult("10MB", testDown3); 240 241 const testDown4 = await measureDownload(25001000, 4); 242 logSpeedTestResult("25MB", testDown4); 243 244 const testDown5 = await measureDownload(100001000, 1); 245 logSpeedTestResult("100MB", testDown5); 246 247 const downloadTests = [...testDown1, ...testDown2, ...testDown3, ...testDown4, ...testDown5]; 248 logDownloadSpeed(downloadTests); 249 250 const testUp1 = await measureUpload(11000, 10); 251 const testUp2 = await measureUpload(101000, 10); 252 const testUp3 = await measureUpload(1001000, 8); 253 const uploadTests = [...testUp1, ...testUp2, ...testUp3]; 254 logUploadSpeed(uploadTests); 255} 256 257speedTest();