fix(core): update
This commit is contained in:
43
ts/helpers/stats.ts
Normal file
43
ts/helpers/stats.ts
Normal file
@ -0,0 +1,43 @@
|
||||
export function average(values) {
|
||||
let total = 0;
|
||||
|
||||
for (let i = 0; i < values.length; i += 1) {
|
||||
total += values[i];
|
||||
}
|
||||
|
||||
return total / values.length;
|
||||
}
|
||||
|
||||
export function median(values) {
|
||||
const half = Math.floor(values.length / 2);
|
||||
|
||||
values.sort((a, b) => a - b);
|
||||
|
||||
if (values.length % 2) return values[half];
|
||||
|
||||
return (values[half - 1] + values[half]) / 2;
|
||||
}
|
||||
|
||||
export function quartile(values, percentile) {
|
||||
values.sort((a, b) => a - b);
|
||||
const pos = (values.length - 1) * percentile;
|
||||
const base = Math.floor(pos);
|
||||
const rest = pos - base;
|
||||
|
||||
if (values[base + 1] !== undefined) {
|
||||
return values[base] + rest * (values[base + 1] - values[base]);
|
||||
}
|
||||
|
||||
return values[base];
|
||||
}
|
||||
|
||||
export function jitter(values) {
|
||||
// Average distance between consecutive latency measurements...
|
||||
let jitters = [];
|
||||
|
||||
for (let i = 0; i < values.length - 1; i += 1) {
|
||||
jitters.push(Math.abs(values[i] - values[i + 1]));
|
||||
}
|
||||
|
||||
return average(jitters);
|
||||
}
|
188
ts/smartnetwork.classes.cloudflarespeed.ts
Normal file
188
ts/smartnetwork.classes.cloudflarespeed.ts
Normal file
@ -0,0 +1,188 @@
|
||||
import * as plugins from './smartnetwork.plugins';
|
||||
import * as stats from './helpers/stats';
|
||||
|
||||
export class CloudflareSpeed {
|
||||
constructor() {}
|
||||
|
||||
public async speedTest() {
|
||||
const latency = await this.measureLatency();
|
||||
|
||||
const serverLocations = await this.fetchServerLocations();
|
||||
const cgiData = await this.fetchCfCdnCgiTrace();
|
||||
|
||||
return {
|
||||
...latency,
|
||||
ip: cgiData.ip,
|
||||
serverLocation: {
|
||||
shortId: cgiData.colo,
|
||||
name: serverLocations[cgiData.colo],
|
||||
availableLocations: serverLocations,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public async measureLatency() {
|
||||
const measurements: number[] = [];
|
||||
|
||||
for (let i = 0; i < 20; i += 1) {
|
||||
await this.download(1000).then(
|
||||
(response) => {
|
||||
// TTFB - Server processing time
|
||||
measurements.push(response[4] - response[0] - response[6]);
|
||||
},
|
||||
(error) => {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
maxTime: Math.max(...measurements),
|
||||
minTime: Math.min(...measurements),
|
||||
averageTime: stats.average(measurements),
|
||||
medianTime: stats.median(measurements),
|
||||
jitter: stats.jitter(measurements),
|
||||
}
|
||||
|
||||
;
|
||||
}
|
||||
|
||||
public async fetchServerLocations(): Promise<{[key: string]: string}> {
|
||||
const res = JSON.parse(await this.get('speed.cloudflare.com', '/locations'));
|
||||
|
||||
return res.reduce((data, { iata, city }) => {
|
||||
// Bypass prettier "no-assign-param" rules
|
||||
const data1 = data;
|
||||
|
||||
data1[iata] = city;
|
||||
return data1;
|
||||
}, {});
|
||||
}
|
||||
|
||||
public async get(hostname: string, path: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = plugins.https.request(
|
||||
{
|
||||
hostname,
|
||||
path,
|
||||
method: 'GET',
|
||||
},
|
||||
(res) => {
|
||||
const body = [];
|
||||
res.on('data', (chunk) => {
|
||||
body.push(chunk);
|
||||
});
|
||||
res.on('end', () => {
|
||||
try {
|
||||
resolve(Buffer.concat(body).toString());
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
req.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
public async download(bytes) {
|
||||
const options = {
|
||||
hostname: 'speed.cloudflare.com',
|
||||
path: `/__down?bytes=${bytes}`,
|
||||
method: 'GET',
|
||||
};
|
||||
|
||||
return this.request(options);
|
||||
}
|
||||
|
||||
public async request(options, data = '') {
|
||||
let started;
|
||||
let dnsLookup;
|
||||
let tcpHandshake;
|
||||
let sslHandshake;
|
||||
let ttfb;
|
||||
let ended;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
started = plugins.perfHooks.performance.now();
|
||||
const req = plugins.https.request(options, (res) => {
|
||||
res.once('readable', () => {
|
||||
ttfb = plugins.perfHooks.performance.now();
|
||||
});
|
||||
res.on('data', () => {});
|
||||
res.on('end', () => {
|
||||
ended = plugins.perfHooks.performance.now();
|
||||
resolve([
|
||||
started,
|
||||
dnsLookup,
|
||||
tcpHandshake,
|
||||
sslHandshake,
|
||||
ttfb,
|
||||
ended,
|
||||
parseFloat(res.headers['server-timing'].slice(22) as any),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
req.on('socket', (socket) => {
|
||||
socket.on('lookup', () => {
|
||||
dnsLookup = plugins.perfHooks.performance.now();
|
||||
});
|
||||
socket.on('connect', () => {
|
||||
tcpHandshake = plugins.perfHooks.performance.now();
|
||||
});
|
||||
socket.on('secureConnect', () => {
|
||||
sslHandshake = plugins.perfHooks.performance.now();
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
req.write(data);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
public async fetchCfCdnCgiTrace(): Promise<{
|
||||
fl: string,
|
||||
h: string,
|
||||
ip: string,
|
||||
ts: string,
|
||||
visit_scheme: string,
|
||||
uag: string,
|
||||
colo: string,
|
||||
http: string,
|
||||
loc: string,
|
||||
tls: string,
|
||||
sni: string,
|
||||
warp: string,
|
||||
gateway: string,
|
||||
}> {
|
||||
const parseCfCdnCgiTrace = (text) =>
|
||||
text
|
||||
.split('\n')
|
||||
.map((i) => {
|
||||
const j = i.split('=');
|
||||
|
||||
return [j[0], j[1]];
|
||||
})
|
||||
.reduce((data, [k, v]) => {
|
||||
if (v === undefined) return data;
|
||||
|
||||
// Bypass prettier "no-assign-param" rules
|
||||
const data1 = data;
|
||||
// Object.fromEntries is only supported by Node.js 12 or newer
|
||||
data1[k] = v;
|
||||
|
||||
return data1;
|
||||
}, {});
|
||||
|
||||
return this.get('speed.cloudflare.com', '/cdn-cgi/trace').then(parseCfCdnCgiTrace);
|
||||
}
|
||||
}
|
@ -1,33 +1,6 @@
|
||||
import * as plugins from './smartnetwork.plugins';
|
||||
|
||||
export interface ISpeedtestData {
|
||||
timestamp: Date;
|
||||
ping: { jitter: number; latency: number };
|
||||
download: { bandwidth: number; bytes: number; elapsed: number };
|
||||
upload: { bandwidth: number; bytes: number; elapsed: number };
|
||||
packetLoss: number;
|
||||
isp: string;
|
||||
interface: {
|
||||
internalIp: string;
|
||||
name: string;
|
||||
macAddr: string;
|
||||
isVpn: false;
|
||||
externalIp: string;
|
||||
};
|
||||
server: {
|
||||
id: number;
|
||||
name: string;
|
||||
location: string;
|
||||
country: string;
|
||||
host: string;
|
||||
port: number;
|
||||
ip: string;
|
||||
};
|
||||
result: {
|
||||
id: string;
|
||||
url: string;
|
||||
};
|
||||
}
|
||||
import { CloudflareSpeed } from './smartnetwork.classes.cloudflarespeed';
|
||||
|
||||
/**
|
||||
* SmartNetwork simplifies actions within the network
|
||||
@ -38,7 +11,8 @@ export class SmartNetwork {
|
||||
* @param measurementTime
|
||||
*/
|
||||
public async getSpeed() {
|
||||
const test = null;
|
||||
const cloudflareSpeedInstance = new CloudflareSpeed();
|
||||
const test = await cloudflareSpeedInstance.speedTest();
|
||||
return test;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
// native scope
|
||||
import * as os from 'os';
|
||||
import * as https from 'https';
|
||||
import * as perfHooks from 'perf_hooks';
|
||||
|
||||
export { os };
|
||||
export { os, https, perfHooks };
|
||||
|
||||
// @pushrocks scope
|
||||
import * as smartpromise from '@pushrocks/smartpromise';
|
||||
|
Reference in New Issue
Block a user