this repo has no description
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();