@mayurpise/wirespeed 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,13 +6,17 @@ export declare const LATENCY_REQUESTS = 20;
6
6
  export declare const BANDWIDTH_PERCENTILE = 0.9;
7
7
  export declare const LATENCY_PERCENTILE = 0.5;
8
8
  export declare const MIN_REQUEST_DURATION_MS = 10;
9
- export declare const FINISH_REQUEST_DURATION_MS = 1000;
9
+ /** Skip to next phase if any single request exceeds this.
10
+ * On a 1 Gbps link with 8 parallel connections, a 100 MB request takes ~5.3s.
11
+ * 10s gives headroom for gigabit while still bailing on slow links. */
12
+ export declare const FINISH_REQUEST_DURATION_MS = 10000;
10
13
  export declare const ESTIMATED_SERVER_TIME_MS = 10;
11
14
  export declare const RENDER_INTERVAL_MS = 80;
12
15
  export declare const EMA_ALPHA = 0.3;
13
16
  export interface PhaseConfig {
14
17
  bytes: number;
15
18
  count: number;
19
+ /** Max in-flight requests for this phase. */
16
20
  parallel: number;
17
21
  }
18
22
  export declare const DOWNLOAD_PHASES: PhaseConfig[];
@@ -6,21 +6,23 @@ export const LATENCY_REQUESTS = 20;
6
6
  export const BANDWIDTH_PERCENTILE = 0.9;
7
7
  export const LATENCY_PERCENTILE = 0.5;
8
8
  export const MIN_REQUEST_DURATION_MS = 10;
9
- export const FINISH_REQUEST_DURATION_MS = 1000;
9
+ /** Skip to next phase if any single request exceeds this.
10
+ * On a 1 Gbps link with 8 parallel connections, a 100 MB request takes ~5.3s.
11
+ * 10s gives headroom for gigabit while still bailing on slow links. */
12
+ export const FINISH_REQUEST_DURATION_MS = 10_000;
10
13
  export const ESTIMATED_SERVER_TIME_MS = 10;
11
14
  export const RENDER_INTERVAL_MS = 80;
12
15
  export const EMA_ALPHA = 0.3;
13
16
  export const DOWNLOAD_PHASES = [
14
- { bytes: 100_000, count: 1, parallel: 1 },
15
- { bytes: 1_000_000, count: 2, parallel: 4 },
16
- { bytes: 10_000_000, count: 4, parallel: 8 },
17
- { bytes: 25_000_000, count: 4, parallel: 8 },
18
- { bytes: 100_000_000, count: 4, parallel: 8 },
17
+ { bytes: 1_000_000, count: 4, parallel: 4 }, // 4 MB — warm up TCP windows
18
+ { bytes: 10_000_000, count: 4, parallel: 8 }, // 40 MB — ramp
19
+ { bytes: 25_000_000, count: 8, parallel: 8 }, // 200 MB — measure
20
+ { bytes: 100_000_000, count: 8, parallel: 8 }, // 800 MB — saturate
19
21
  ];
20
22
  export const UPLOAD_PHASES = [
21
- { bytes: 100_000, count: 1, parallel: 1 },
22
- { bytes: 1_000_000, count: 2, parallel: 4 },
23
- { bytes: 10_000_000, count: 4, parallel: 6 },
24
- { bytes: 25_000_000, count: 4, parallel: 6 },
23
+ { bytes: 1_000_000, count: 4, parallel: 4 }, // 4 MB — warm up
24
+ { bytes: 10_000_000, count: 4, parallel: 8 }, // 40 MB — ramp
25
+ { bytes: 25_000_000, count: 8, parallel: 8 }, // 200 MB — measure
26
+ { bytes: 100_000_000, count: 4, parallel: 8 }, // 400 MB — saturate
25
27
  ];
26
28
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,qCAAqC,CAAC;AACjE,MAAM,CAAC,MAAM,SAAS,GAAG,mCAAmC,CAAC;AAC7D,MAAM,CAAC,MAAM,YAAY,GAAG,4CAA4C,CAAC;AACzE,MAAM,CAAC,MAAM,gBAAgB,GAAG,wCAAwC,CAAC;AAEzE,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AACxC,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AACtC,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAC1C,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;AAC/C,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAC3C,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AACrC,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC;AAQ7B,MAAM,CAAC,MAAM,eAAe,GAAkB;IAC5C,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;IACzC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;IAC3C,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;IAC5C,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;IAC5C,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;CAC9C,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAkB;IAC1C,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;IACzC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;IAC3C,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;IAC5C,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;CAC7C,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,qCAAqC,CAAC;AACjE,MAAM,CAAC,MAAM,SAAS,GAAG,mCAAmC,CAAC;AAC7D,MAAM,CAAC,MAAM,YAAY,GAAG,4CAA4C,CAAC;AACzE,MAAM,CAAC,MAAM,gBAAgB,GAAG,wCAAwC,CAAC;AAEzE,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AACxC,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AACtC,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAC1C;;wEAEwE;AACxE,MAAM,CAAC,MAAM,0BAA0B,GAAG,MAAM,CAAC;AACjD,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAC3C,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AACrC,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC;AAS7B,MAAM,CAAC,MAAM,eAAe,GAAkB;IAC5C,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAQ,8BAA8B;IACjF,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAQ,eAAe;IACnE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAQ,mBAAmB;IACvE,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAO,oBAAoB;CACzE,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAkB;IAC1C,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAS,kBAAkB;IACtE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAS,eAAe;IACpE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAS,mBAAmB;IACxE,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAQ,oBAAoB;CAC1E,CAAC"}
@@ -3,17 +3,14 @@ import { measureLatency } from './tester/latency.js';
3
3
  import { measureDownload } from './tester/download.js';
4
4
  import { measureUpload } from './tester/upload.js';
5
5
  export async function runSpeedTest(renderer, options) {
6
- // Phase: init
7
6
  renderer.update({ phase: 'init', progress: 0, currentSpeed: 0 });
8
7
  const server = await fetchServerMeta();
9
8
  renderer.update({ server });
10
- // Phase: latency
9
+ // Latency
11
10
  renderer.update({ phase: 'latency', progress: 0, currentSpeed: 0 });
12
- const latency = await measureLatency((_i, _ms) => {
13
- // Could update live latency display here if desired
14
- });
11
+ const latency = await measureLatency((_i, _ms) => { });
15
12
  renderer.update({ latency });
16
- // Phase: download
13
+ // Download
17
14
  let download = null;
18
15
  if (!options.noDownload) {
19
16
  renderer.update({ phase: 'download', progress: 0, currentSpeed: 0 });
@@ -22,7 +19,7 @@ export async function runSpeedTest(renderer, options) {
22
19
  });
23
20
  renderer.update({ download, progress: 1 });
24
21
  }
25
- // Phase: upload
22
+ // Upload
26
23
  let upload = null;
27
24
  if (!options.noUpload) {
28
25
  renderer.update({ phase: 'upload', progress: 0, currentSpeed: 0 });
@@ -31,7 +28,6 @@ export async function runSpeedTest(renderer, options) {
31
28
  });
32
29
  renderer.update({ upload, progress: 1 });
33
30
  }
34
- // Done
35
31
  renderer.update({ phase: 'done' });
36
32
  return { server, latency, download, upload };
37
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AASnD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAkB,EAClB,OAAmB;IAEnB,cAAc;IACd,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;IAEjE,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAE5B,iBAAiB;IACjB,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE;QAC/C,oDAAoD;IACtD,CAAC,CAAC,CAAC;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IAE7B,kBAAkB;IAClB,IAAI,QAAQ,GAA4B,IAAI,CAAC;IAC7C,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;QACrE,QAAQ,GAAG,MAAM,eAAe,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE;YAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,gBAAgB;IAChB,IAAI,MAAM,GAA0B,IAAI,CAAC;IACzC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;QACnE,MAAM,GAAG,MAAM,aAAa,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE;YACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO;IACP,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAEnC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC/C,CAAC"}
1
+ {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AASnD,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAkB,EAClB,OAAmB;IAEnB,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;IAEjE,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAE5B,UAAU;IACV,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,GAAE,CAAC,CAAC,CAAC;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IAE7B,WAAW;IACX,IAAI,QAAQ,GAA4B,IAAI,CAAC;IAC7C,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;QACrE,QAAQ,GAAG,MAAM,eAAe,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE;YAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,SAAS;IACT,IAAI,MAAM,GAA0B,IAAI,CAAC;IACzC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;QACnE,MAAM,GAAG,MAAM,aAAa,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE;YACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IACnC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC/C,CAAC"}
@@ -1,6 +1,13 @@
1
1
  import type { Measurement } from '../tester/types.js';
2
2
  export declare function percentile(sorted: number[], p: number): number;
3
3
  export declare function computeBandwidth(measurements: Measurement[]): number;
4
+ /**
5
+ * Aggregate throughput = total bytes transferred / wall-clock duration.
6
+ * This is the correct metric when multiple connections run in parallel:
7
+ * individual request speeds are low (they share bandwidth), but aggregate
8
+ * reflects the actual link utilization.
9
+ */
10
+ export declare function computeAggregateBandwidth(totalBytes: number, wallTimeMs: number): number;
4
11
  export declare function computeMedianLatency(latencies: number[]): number;
5
12
  export declare function computeJitter(latencies: number[]): number;
6
13
  export declare function ema(current: number, previous: number): number;
@@ -22,6 +22,17 @@ export function computeBandwidth(measurements) {
22
22
  return 0;
23
23
  return percentile(valid, BANDWIDTH_PERCENTILE);
24
24
  }
25
+ /**
26
+ * Aggregate throughput = total bytes transferred / wall-clock duration.
27
+ * This is the correct metric when multiple connections run in parallel:
28
+ * individual request speeds are low (they share bandwidth), but aggregate
29
+ * reflects the actual link utilization.
30
+ */
31
+ export function computeAggregateBandwidth(totalBytes, wallTimeMs) {
32
+ if (wallTimeMs <= 0)
33
+ return 0;
34
+ return (totalBytes * 8 * 1000) / wallTimeMs;
35
+ }
25
36
  export function computeMedianLatency(latencies) {
26
37
  const sorted = [...latencies].sort((a, b) => a - b);
27
38
  return percentile(sorted, LATENCY_PERCENTILE);
@@ -1 +1 @@
1
- {"version":3,"file":"calculator.js","sourceRoot":"","sources":["../../../src/stats/calculator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE5G,MAAM,UAAU,UAAU,CAAC,MAAgB,EAAE,CAAS;IACpD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAClC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,CAAC,CAAE,CAAC;IAE3C,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;IAExB,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IAC9B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,MAAM,CAAC,IAAI,CAAE,GAAG,IAAI,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAE,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,YAA2B;IAC1D,MAAM,KAAK,GAAG,YAAY;SACvB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,uBAAuB,CAAC;SACpD,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;SACpB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAEzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACjC,OAAO,UAAU,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,SAAmB;IACtD,MAAM,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,OAAO,UAAU,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,SAAmB;IAC/C,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACnC,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAE,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,QAAgB;IACnD,OAAO,SAAS,GAAG,OAAO,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,QAAQ,CAAC;AAC1D,CAAC"}
1
+ {"version":3,"file":"calculator.js","sourceRoot":"","sources":["../../../src/stats/calculator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE5G,MAAM,UAAU,UAAU,CAAC,MAAgB,EAAE,CAAS;IACpD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAClC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,CAAC,CAAE,CAAC;IAE3C,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;IAExB,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IAC9B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,MAAM,CAAC,IAAI,CAAE,GAAG,IAAI,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAE,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,YAA2B;IAC1D,MAAM,KAAK,GAAG,YAAY;SACvB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,uBAAuB,CAAC;SACpD,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;SACpB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAEzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACjC,OAAO,UAAU,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAC;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CACvC,UAAkB,EAClB,UAAkB;IAElB,IAAI,UAAU,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,CAAC,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,UAAU,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,SAAmB;IACtD,MAAM,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,OAAO,UAAU,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,SAAmB;IAC/C,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACnC,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAE,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,QAAgB;IACnD,OAAO,SAAS,GAAG,OAAO,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,QAAQ,CAAC;AAC1D,CAAC"}
@@ -1,55 +1,58 @@
1
- import { CF_DOWN_URL, DOWNLOAD_PHASES, FINISH_REQUEST_DURATION_MS } from '../config.js';
1
+ import { performance } from 'node:perf_hooks';
2
+ import { CF_DOWN_URL, DOWNLOAD_PHASES, FINISH_REQUEST_DURATION_MS, MIN_REQUEST_DURATION_MS } from '../config.js';
2
3
  import { timedDownload } from './http-client.js';
3
- import { computeBandwidth, ema } from '../stats/calculator.js';
4
+ import { runPool } from './pool.js';
5
+ import { computeAggregateBandwidth, ema } from '../stats/calculator.js';
4
6
  import { bpsToMbps } from '../stats/units.js';
5
7
  export async function measureDownload(onProgress) {
6
8
  const allMeasurements = [];
7
9
  let liveSpeed = 0;
8
10
  let totalRequests = 0;
9
11
  let completedRequests = 0;
10
- // Count total requests for progress
12
+ const phaseAggregates = [];
11
13
  for (const phase of DOWNLOAD_PHASES) {
12
14
  totalRequests += phase.count;
13
15
  }
14
16
  for (const phase of DOWNLOAD_PHASES) {
15
17
  const url = `${CF_DOWN_URL}?bytes=${phase.bytes}`;
16
- let remaining = phase.count;
17
18
  let phaseHitThreshold = false;
18
- while (remaining > 0) {
19
- const batch = Math.min(remaining, phase.parallel);
20
- const promises = Array.from({ length: batch }, () => timedDownload(url).then(timing => {
21
- const durationMs = timing.endTime - timing.ttfb;
22
- const speedBps = durationMs > 0
23
- ? (timing.bytesTransferred * 8 * 1000) / durationMs
24
- : 0;
25
- const m = {
26
- bytes: timing.bytesTransferred,
27
- durationMs,
28
- speedBps,
29
- };
30
- allMeasurements.push(m);
31
- if (speedBps > 0) {
32
- liveSpeed = liveSpeed === 0 ? speedBps : ema(speedBps, liveSpeed);
33
- }
34
- if (durationMs > FINISH_REQUEST_DURATION_MS) {
35
- phaseHitThreshold = true;
36
- }
37
- completedRequests++;
38
- onProgress?.(completedRequests / totalRequests, liveSpeed);
39
- return m;
40
- }).catch(() => {
41
- completedRequests++;
42
- onProgress?.(completedRequests / totalRequests, liveSpeed);
43
- return null;
44
- }));
45
- await Promise.all(promises);
46
- remaining -= batch;
19
+ let phaseBytes = 0;
20
+ const ctx = { bail: false };
21
+ const phaseStart = performance.now();
22
+ const tasks = Array.from({ length: phase.count }, () => async () => {
23
+ const timing = await timedDownload(url);
24
+ const durationMs = timing.endTime - timing.ttfb;
25
+ const speedBps = durationMs > 0
26
+ ? (timing.bytesTransferred * 8 * 1000) / durationMs
27
+ : 0;
28
+ const m = { bytes: timing.bytesTransferred, durationMs, speedBps };
29
+ allMeasurements.push(m);
30
+ if (durationMs >= MIN_REQUEST_DURATION_MS) {
31
+ phaseBytes += timing.bytesTransferred;
32
+ }
33
+ if (speedBps > 0) {
34
+ const elapsed = performance.now() - phaseStart;
35
+ const aggBps = elapsed > 0 ? (phaseBytes * 8 * 1000) / elapsed : 0;
36
+ liveSpeed = liveSpeed === 0 ? aggBps : ema(aggBps, liveSpeed);
37
+ }
38
+ if (durationMs > FINISH_REQUEST_DURATION_MS) {
39
+ phaseHitThreshold = true;
40
+ }
41
+ completedRequests++;
42
+ onProgress?.(completedRequests / totalRequests, liveSpeed);
43
+ return m;
44
+ });
45
+ await runPool(tasks, phase.parallel, ctx);
46
+ const phaseWallMs = performance.now() - phaseStart;
47
+ if (phaseBytes > 0 && phaseWallMs > 0) {
48
+ phaseAggregates.push(computeAggregateBandwidth(phaseBytes, phaseWallMs));
47
49
  }
48
- // Early termination: if requests in this phase took long enough, skip larger phases
49
50
  if (phaseHitThreshold)
50
51
  break;
51
52
  }
52
- const speedBps = computeBandwidth(allMeasurements);
53
+ const speedBps = phaseAggregates.length > 0
54
+ ? Math.max(...phaseAggregates)
55
+ : 0;
53
56
  return {
54
57
  measurements: allMeasurements,
55
58
  speedBps,
@@ -1 +1 @@
1
- {"version":3,"file":"download.js","sourceRoot":"","sources":["../../../src/tester/download.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"download.js","sourceRoot":"","sources":["../../../src/tester/download.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Sliding-window concurrent executor.
3
+ *
4
+ * Keeps exactly `concurrency` workers busy at all times. As soon as one
5
+ * task finishes, the worker immediately picks up the next — zero idle time
6
+ * between batches.
7
+ *
8
+ * A shared `bail` flag lets any task signal that remaining tasks should be
9
+ * skipped (e.g., on rate-limiting or fatal errors).
10
+ */
11
+ export interface PoolContext {
12
+ /** Set to true to stop dispatching new tasks. In-flight tasks still finish. */
13
+ bail: boolean;
14
+ }
15
+ export declare function runPool<T>(tasks: (() => Promise<T>)[], concurrency: number, ctx?: PoolContext, onComplete?: (result: T, index: number) => void): Promise<T[]>;
@@ -0,0 +1,22 @@
1
+ export async function runPool(tasks, concurrency, ctx, onComplete) {
2
+ const results = new Array(tasks.length);
3
+ let nextIdx = 0;
4
+ const poolCtx = ctx ?? { bail: false };
5
+ const worker = async () => {
6
+ while (nextIdx < tasks.length && !poolCtx.bail) {
7
+ const idx = nextIdx++;
8
+ try {
9
+ const result = await tasks[idx]();
10
+ results[idx] = result;
11
+ onComplete?.(result, idx);
12
+ }
13
+ catch {
14
+ // Failed requests are left undefined — filtered out during stats
15
+ }
16
+ }
17
+ };
18
+ const workerCount = Math.min(concurrency, tasks.length);
19
+ await Promise.all(Array.from({ length: workerCount }, worker));
20
+ return results.filter((r) => r !== undefined);
21
+ }
22
+ //# sourceMappingURL=pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.js","sourceRoot":"","sources":["../../../src/tester/pool.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,KAA2B,EAC3B,WAAmB,EACnB,GAAiB,EACjB,UAA+C;IAE/C,MAAM,OAAO,GAAsB,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3D,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,MAAM,OAAO,GAAG,GAAG,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAEvC,MAAM,MAAM,GAAG,KAAK,IAAmB,EAAE;QACvC,OAAO,OAAO,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAC/C,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,CAAE,EAAE,CAAC;gBACnC,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;gBACtB,UAAU,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,iEAAiE;YACnE,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/D,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAU,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;AACxD,CAAC"}
@@ -1,9 +1,10 @@
1
- import { CF_UP_URL, UPLOAD_PHASES, FINISH_REQUEST_DURATION_MS } from '../config.js';
1
+ import { performance } from 'node:perf_hooks';
2
+ import { CF_UP_URL, UPLOAD_PHASES, FINISH_REQUEST_DURATION_MS, MIN_REQUEST_DURATION_MS } from '../config.js';
2
3
  import { timedUpload } from './http-client.js';
3
- import { computeBandwidth, ema } from '../stats/calculator.js';
4
+ import { runPool } from './pool.js';
5
+ import { computeAggregateBandwidth, ema } from '../stats/calculator.js';
4
6
  import { bpsToMbps } from '../stats/units.js';
5
7
  function generatePayload(bytes) {
6
- // Fill with zeros — Cloudflare discards the body anyway
7
8
  return new Uint8Array(bytes);
8
9
  }
9
10
  export async function measureUpload(onProgress) {
@@ -11,47 +12,50 @@ export async function measureUpload(onProgress) {
11
12
  let liveSpeed = 0;
12
13
  let totalRequests = 0;
13
14
  let completedRequests = 0;
15
+ const phaseAggregates = [];
14
16
  for (const phase of UPLOAD_PHASES) {
15
17
  totalRequests += phase.count;
16
18
  }
17
19
  for (const phase of UPLOAD_PHASES) {
18
20
  const payload = generatePayload(phase.bytes);
19
- let remaining = phase.count;
20
21
  let phaseHitThreshold = false;
21
- while (remaining > 0) {
22
- const batch = Math.min(remaining, phase.parallel);
23
- const promises = Array.from({ length: batch }, () => timedUpload(CF_UP_URL, payload).then(timing => {
24
- const durationMs = (timing.endTime - timing.startTime) - timing.serverTime;
25
- const speedBps = durationMs > 0
26
- ? (timing.bytesTransferred * 8 * 1000) / durationMs
27
- : 0;
28
- const m = {
29
- bytes: timing.bytesTransferred,
30
- durationMs,
31
- speedBps,
32
- };
33
- allMeasurements.push(m);
34
- if (speedBps > 0) {
35
- liveSpeed = liveSpeed === 0 ? speedBps : ema(speedBps, liveSpeed);
36
- }
37
- if (durationMs > FINISH_REQUEST_DURATION_MS) {
38
- phaseHitThreshold = true;
39
- }
40
- completedRequests++;
41
- onProgress?.(completedRequests / totalRequests, liveSpeed);
42
- return m;
43
- }).catch(() => {
44
- completedRequests++;
45
- onProgress?.(completedRequests / totalRequests, liveSpeed);
46
- return null;
47
- }));
48
- await Promise.all(promises);
49
- remaining -= batch;
22
+ let phaseBytes = 0;
23
+ const ctx = { bail: false };
24
+ const phaseStart = performance.now();
25
+ const tasks = Array.from({ length: phase.count }, () => async () => {
26
+ const timing = await timedUpload(CF_UP_URL, payload);
27
+ const durationMs = (timing.endTime - timing.startTime) - timing.serverTime;
28
+ const speedBps = durationMs > 0
29
+ ? (timing.bytesTransferred * 8 * 1000) / durationMs
30
+ : 0;
31
+ const m = { bytes: timing.bytesTransferred, durationMs, speedBps };
32
+ allMeasurements.push(m);
33
+ if (durationMs >= MIN_REQUEST_DURATION_MS) {
34
+ phaseBytes += timing.bytesTransferred;
35
+ }
36
+ if (speedBps > 0) {
37
+ const elapsed = performance.now() - phaseStart;
38
+ const aggBps = elapsed > 0 ? (phaseBytes * 8 * 1000) / elapsed : 0;
39
+ liveSpeed = liveSpeed === 0 ? aggBps : ema(aggBps, liveSpeed);
40
+ }
41
+ if (durationMs > FINISH_REQUEST_DURATION_MS) {
42
+ phaseHitThreshold = true;
43
+ }
44
+ completedRequests++;
45
+ onProgress?.(completedRequests / totalRequests, liveSpeed);
46
+ return m;
47
+ });
48
+ await runPool(tasks, phase.parallel, ctx);
49
+ const phaseWallMs = performance.now() - phaseStart;
50
+ if (phaseBytes > 0 && phaseWallMs > 0) {
51
+ phaseAggregates.push(computeAggregateBandwidth(phaseBytes, phaseWallMs));
50
52
  }
51
53
  if (phaseHitThreshold)
52
54
  break;
53
55
  }
54
- const speedBps = computeBandwidth(allMeasurements);
56
+ const speedBps = phaseAggregates.length > 0
57
+ ? Math.max(...phaseAggregates)
58
+ : 0;
55
59
  return {
56
60
  measurements: allMeasurements,
57
61
  speedBps,
@@ -1 +1 @@
1
- {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../../src/tester/upload.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../../src/tester/upload.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "1.1.1";
2
- export declare const USER_AGENT = "wirespeed/1.1.1";
1
+ export declare const VERSION = "1.2.0";
2
+ export declare const USER_AGENT = "wirespeed/1.2.0";
@@ -1,3 +1,3 @@
1
- export const VERSION = '1.1.1';
1
+ export const VERSION = '1.2.0';
2
2
  export const USER_AGENT = `wirespeed/${VERSION}`;
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mayurpise/wirespeed",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Terminal internet speed test — download, upload, and latency",
5
5
  "author": "mayurpise",
6
6
  "repository": {