Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ new SpeedTest({ configOptions })
| **measureUploadLoadedLatency**: *boolean* | Whether to perform additional latency measurements simultaneously with upload requests, to measure loaded latency (during upload). | `true` |
| **loadedLatencyThrottle**: *number* | Time interval to wait in between loaded latency requests (in milliseconds). | 400 |
| **bandwidthFinishRequestDuration**: *number* | The minimum duration (in milliseconds) to reach in download/upload measurement sets for halting further measurements with larger file sizes in the same direction. | 1000 |
| **bandwidthAbortRequestDuration**: *number* | The minimum duration (in milliseconds) to reach in download/upload measurement sets for aborting the test early | 45000 |
| **estimatedServerTime**: *number* | If the download/upload APIs do not return a server-timing response header containing the time spent in the server, this fixed value (in milliseconds) will be subtracted from all time-to-first-byte calculations. | 10 |
| **latencyPercentile**: *number* | The percentile (between 0 and 1) used to calculate latency from a set of measurements. | 0.5 |
| **bandwidthPercentile**: *number* | The percentile (between 0 and 1) used to calculate bandwidth from a set of measurements. | 0.9 |
Expand Down
3 changes: 2 additions & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export default [
output: [
{
format: 'es',
file: `dist/${fileName}.js`
file: `dist/${fileName}.js`,
sourcemap: true
}
],
external: [...Object.keys(dependencies || {})],
Expand Down
5 changes: 4 additions & 1 deletion src/config/defaultConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ export default {
measureDownloadLoadedLatency: true,
measureUploadLoadedLatency: true,
loadedLatencyThrottle: 400, // ms in between loaded latency requests
bandwidthFinishRequestDuration: 1000, // download/upload duration (ms) to reach for stopping further measurements
bandwidthFinishRequestDuration: 1000, // download/upload duration (ms) to reach for stopping further measurements of that type
estimatedServerTime: 10, // ms to discount from latency calculation (if not present in response headers)

// Test abort
bandwidthAbortRequestDuration: 45000, // download/upload duration (ms) to abort measurement early and stop further measurements of that type

// Result interpretation
latencyPercentile: 0.5, // Percentile used to calculate latency from a set of measurements
bandwidthPercentile: 0.9, // Percentile used to calculate bandwidth from a set of measurements
Expand Down
54 changes: 39 additions & 15 deletions src/engines/BandwidthEngine/BandwidthEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class BandwidthMeasurementEngine {
}

finishRequestDuration = 1000; // download/upload duration (ms) to reach for stopping further measurements
abortRequestDuration = 0;
getServerTime = cfGetServerTime; // method to extract server time from response

#responseHook = r => r; // pipe-through of response objects
Expand Down Expand Up @@ -118,7 +119,6 @@ class BandwidthMeasurementEngine {

// Public methods
pause() {
clearTimeout(this.#currentNextMsmTimeoutId);
this.#cancelCurrentMeasurement();
this.#setRunning(false);
}
Expand All @@ -144,8 +144,12 @@ class BandwidthMeasurementEngine {
#minDuration = -Infinity; // of current measurement
#throttleMs = 0;
#estimatedServerTime = 0;
#currentFetchPromise = undefined;
#currentNextMsmTimeoutId = undefined;

/**
* Aborts the current measurement.
* @type AbortController
*/
#currentAbortController = undefined;

// Internal methods
#setRunning(running) {
Expand Down Expand Up @@ -267,8 +271,29 @@ class BandwidthMeasurementEngine {
this.#fetchOptions
);

// AbortController and timeout is shared between all retries
if (this.#retries === 0) {
this.#currentAbortController = new AbortController();
if (this.abortRequestDuration) {
const abortTimeout = setTimeout(() => {
this.#cancelCurrentMeasurement();
this.#retries = 0;
this.#setRunning(false);
this.#onConnectionError(
`${isDown ? 'Download' : 'Upload'} measurement of ${numBytes} bytes aborted. Measurement exceeded bandwidthAbortRequestDuration (${this.abortRequestDuration}ms)`
);
}, this.abortRequestDuration);
this.#currentAbortController.signal.addEventListener('abort', () =>
clearTimeout(abortTimeout)
);
}
}

let serverTime;
const curPromise = (this.#currentFetchPromise = fetch(url, fetchOpt)
fetch(url, {
...fetchOpt,
signal: this.#currentAbortController.signal
})
.then(r => {
if (r.ok) return r;
throw Error(r.statusText);
Expand All @@ -289,12 +314,7 @@ class BandwidthMeasurementEngine {
return body;
})
)
.then((_, reject) => {
if (curPromise._cancel) {
reject('cancelled');
return;
}

.then(() => {
const perf = performance.getEntriesByName(url).slice(-1)[0]; // get latest perf timing
const timing = {
transferSize: perf.transferSize,
Expand Down Expand Up @@ -343,16 +363,21 @@ class BandwidthMeasurementEngine {
this.#retries = 0;

if (this.#throttleMs) {
this.#currentNextMsmTimeoutId = setTimeout(
const throttleTimeout = setTimeout(
() => this.#nextMeasurement(),
this.#throttleMs
);
this.#currentAbortController.signal.addEventListener('abort', () =>
clearTimeout(throttleTimeout)
);
} else {
this.#nextMeasurement();
}
})
.catch(error => {
if (curPromise._cancel) return;
if (this.#currentAbortController.signal.aborted) {
return;
}
console.warn(`Error fetching ${url}: ${error}`);

if (this.#retries++ < MAX_RETRIES) {
Expand All @@ -364,12 +389,11 @@ class BandwidthMeasurementEngine {
`Connection failed to ${url}. Gave up after ${MAX_RETRIES} retries.`
);
}
}));
});
}

#cancelCurrentMeasurement() {
const curPromise = this.#currentFetchPromise;
curPromise && (curPromise._cancel = true);
this.#currentAbortController.abort();
}
}

Expand Down
1 change: 1 addition & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface ConfigOptions {
measureUploadLoadedLatency?: boolean,
loadedLatencyThrottle?: number,
bandwidthFinishRequestDuration?: number,
bandwidthAbortRequestDuration?: number,
estimatedServerTime?: number;

// Result interpretation
Expand Down
4 changes: 4 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ class MeasurementEngine {
engine.fetchOptions = {
credentials: this.#config.includeCredentials ? 'include' : undefined
};
engine.abortRequestDuration =
this.#config.bandwidthAbortRequestDuration;

engine.onMeasurementResult = engine.onNewMeasurementStarted = (
meas,
Expand Down Expand Up @@ -372,6 +374,8 @@ class MeasurementEngine {
};
engine.finishRequestDuration =
this.#config.bandwidthFinishRequestDuration;
engine.abortRequestDuration =
this.#config.bandwidthAbortRequestDuration;

engine.onNewMeasurementStarted = ({ count, bytes }) => {
const res = (msmResults.results = Object.assign(
Expand Down
Loading