Speed Test
KapeSpeedTest measures download, upload and latency against a Kape server. It is its own product
and depends on KapeCore only, so a target can add it without the tunnel libraries.
Add the KapeSpeedTest product to your target, then:
import KapeSpeedTest
let speedTest = KapeSpeedTest(handle: handle)
let result = try await speedTest.run(locationId: nil) { progress in
switch progress {
case let .serverSelected(server, ping): print("testing \(server?.name ?? "?"), \(ping.latencyMs) ms")
case let .download(mbps, gaugeMax): print("down \(mbps) / \(gaugeMax)")
case let .upload(mbps, gaugeMax): print("up \(mbps) / \(gaugeMax)")
}
}
print(result.downloadMbps ?? 0, result.uploadMbps ?? 0, result.ratings.overall)
Choosing the server
locationId: nillets the SDK pick a server near the user.- A location ID from
KapeLocationService.allLocationstests that location. The test runs over TCP, because not every server answers the UDP probe. - A Dedicated IP location's ID is its IP address, which the speed test cannot resolve. Pass the
catalog location the Dedicated IP belongs to, or
nil.
result.server is the server actually tested. Its locationId can differ from the one you asked
for.
The test measures whatever network the device is on. To measure through the VPN, connect first. To measure the ISP, disconnect first.
Progress
onProgress is called in order and never after run returns. It may be called off the main actor,
so hop to the main actor before you touch UI.
| Event | Meaning |
|---|---|
.serverSelected(server:ping:) | The SDK picked the server. It can repeat if the SDK retries the run; the latest one wins. |
.download(currentMbps:gaugeMaxMbps:) | Live download throughput. gaugeMaxMbps is a suggested scale for a gauge. It can change between updates or be 0, so keep your own default (for example 1000). |
.upload(currentMbps:gaugeMaxMbps:) | Live upload throughput, in the same shape. |
Result
| Field | Type | Notes |
|---|---|---|
downloadMbps / uploadMbps | Double? | nil when the SDK did not measure that phase. Never 0 as a stand-in. |
ping | KapeSpeedTestPing? | latencyMs, jitterMs, packetLossPercent |
server | KapeSpeedTestServer? | locationId, name, ip |
ratings | KapeSpeedTestRatings | overall, gaming, streaming, videoConference, browsing, downloads |
Each rating is .excellent, .good, .ok, .degraded, .poor or .unknown. If ranking fails, the
measurements are still returned and every rating is .unknown.
Cancelling
Cancel the task that called run, or call speedTest.cancel(). Either way, run throws
CancellationError and the measurement stops. cancel() does nothing when this instance has no
run in progress.
let task = Task { try await speedTest.run(locationId: nil) }
// later
task.cancel()
The library does not watch the network. If the network changes, the VPN connects or disconnects, or the app goes to the background, cancel the run yourself. The measurement would be wrong anyway.
One operation at a time
The SDK runs one ping, Smart Location or speed test operation at a time.
- If a ping or Smart Location operation is running,
runcancels it, waits up to 10 s for the SDK to go idle, then starts. That operation's caller getsCancellationError. - If another speed test is running, from this instance or another one,
runthrows.busyand leaves that test running.
Errors
run throws KapeSpeedTestError:
| Case | Meaning |
|---|---|
.notLicensed | The license does not grant CategoryInstanceDiscovery and SpeedTest. Checked before anything runs. |
.noActiveSubscription | handle.activeSubscriptionId is empty, for example when the user is signed out. |
.busy | Another speed test is running, or the SDK did not go idle in time. |
.aborted | The SDK aborted the measurement twice. One abort is retried automatically. |
.dnsFailure | The test server could not be resolved. |
.network | A network error. |
.testFailed | The measurement failed. |
.sdk(KapeError) | Any other SDK error. |