Skip to content
Draft
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
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ them.
| **autoStart**: *boolean* | Whether to automatically start the measurements on instantiation. | `true` |
| **downloadApiUrl**: *string* | The URL of the API for performing download GET requests. | `https://speed.cloudflare.com/__down` |
| **uploadApiUrl**: *string* | The URL of the API for performing upload POST requests. | `https://speed.cloudflare.com/__up` |
| **bandwidthOrigins**: *string[]* | Origins used for bandwidth requests. The engine appends `/__down` or `/__up` and distributes parallel requests across the origins. When omitted, `downloadApiUrl` and `uploadApiUrl` are used. | `[]` |

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API design question: The current API uses complete endpoint URLs:

downloadApiUrl: "https://speed.cloudflare.com/__down"
uploadApiUrl: "https://speed.cloudflare.com/__up"

This proposal adds bandwidthOrigins and derives /__down and /__up automatically:

bandwidthOrigins: ["https://speed-0.example.com"]

To remain consistent with the existing full-URL approach, alternatives would be paired endpoints:

bandwidthEndpoints: [{ download: ".../__down", upload: ".../__up" }]

or separate downloadApiUrls and uploadApiUrls arrays. Reviewers, which contract do you prefer?

| **parallelism**: *number* | Maximum number of concurrent requests in each download or upload step. Must be a positive integer. | `1` |
| **turnServerUri**: *string* | The URI of the TURN server used to measure packet loss. | `turn.cloudflare.com:3478` |
| **turnServerCredsApiUrl**: *string* | A URI that returns TURN server credentials. Expects a JSON response with `username` and `credential` keys. | - |
| **turnServerUser**: *string* | The username for the TURN server credentials. | - |
Expand Down Expand Up @@ -136,6 +138,26 @@ Each of these measurement sets are bound to a specific file size. The engine fol
| **bytes**: *number* | yes | The file size to request from the download API, or post to the upload API. The bandwidth (calculated as bits per second, or bps) for each request is calculated by dividing the `transferSize` (in bits) by the request duration (excluding the server processing time). | - |
| **count**: *number* | yes | The number of requests to perform for this file size. | - |
| **bypassMinDuration**: *boolean* | no | Whether the `bandwidthMinRequestDuration` check should be ignored, and the engine is instructed to proceed with the measurements of this direction in any case. | `false` |
| **parallelism**: *number* | no | Overrides the global `parallelism` for this step. `count` remains the total number of requests, which are divided into batches of this size. | global value |

Parallel requests in one batch are reported as one bandwidth point. Its `bytes` value is the total payload across the requests, and its `bps` value is calculated across the complete overlapping transfer. When a `sessionId` is configured, the maximum concurrency expected from the configured steps is appended as `parallel=n`.

```js
new SpeedTest({
bandwidthOrigins: [
'https://speed-0.example.com',
'https://speed-1.example.com',
'https://speed-2.example.com',
'https://speed-3.example.com'
],
parallelism: 4,
measurements: [
{ type: 'download', bytes: 1e7, count: 8 },
{ type: 'upload', bytes: 1e7, count: 8 },
{ type: 'download', bytes: 2.5e7, count: 2, parallelism: 1 }
]
});
```

#### packetLoss

Expand Down
12 changes: 10 additions & 2 deletions src/Results/MeasurementCalculations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,16 @@ class MeasurementCalculations {
Object.entries(bandwidthResults)
.map(([bytes, { timings }]) =>
timings.map(
({ bps, duration, ping, measTime, serverTime, transferSize }) => ({
bytes: +bytes,
({
bps,
duration,
ping,
measTime,
serverTime,
transferSize,
transferredBytes
}) => ({
bytes: transferredBytes ?? +bytes,
bps,
duration,
ping,
Expand Down
8 changes: 8 additions & 0 deletions src/config/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export interface BandwidthMeasurementConfig {
bytes: number;
/** Number of requests to issue at this payload size. */
count: number;
/** Maximum requests to run concurrently for this step. Overrides the global value. */
parallelism?: number;
/** If `true`, skip the minimum-duration filter for this round. */
bypassMinDuration?: boolean;
}
Expand Down Expand Up @@ -49,6 +51,8 @@ export interface Config {
downloadApiUrl: string;
/** URL for upload requests. Default: `https://speed.cloudflare.com/__up`. */
uploadApiUrl: string;
/** Origins used for bandwidth requests. `/__down` or `/__up` is appended automatically. */
bandwidthOrigins: string[];
/** URL for per-measurement logging. Set to `null` to disable. Default: `null`. */
logMeasurementApiUrl: string | null;
/** URL for logging test results. Set to `null` to disable. Default: `https://speed.cloudflare.com/__results`. */
Expand All @@ -65,6 +69,8 @@ export interface Config {
rpkiInvalidHost: string;
/** Whether to include credentials (cookies) in fetch requests. Default: `false`. */
includeCredentials: boolean;
/** Maximum concurrent requests in each bandwidth step. Default: `1`. */
parallelism: number;
/** Optional session ID attached to measurement logs. */
sessionId: string | undefined;
/**
Expand Down Expand Up @@ -162,6 +168,7 @@ const defaultConfig: Config = {
// APIs
downloadApiUrl: `${REL_API_URL}/__down`,
uploadApiUrl: `${REL_API_URL}/__up`,
bandwidthOrigins: [],
logMeasurementApiUrl: null,
logAimApiUrl: `${REL_API_URL}/__results`,
turnServerUri: 'turn.speed.cloudflare.com:50000',
Expand All @@ -170,6 +177,7 @@ const defaultConfig: Config = {
turnServerPass: null,
rpkiInvalidHost: 'invalid.rpki.cloudflare.com',
includeCredentials: false,
parallelism: 1,
sessionId: undefined,
authorizationToken: null,
authorizationEnabled: undefined,
Expand Down
Loading