Skip to main content

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: nil lets the SDK pick a server near the user.
  • A location ID from KapeLocationService.allLocations tests 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.

EventMeaning
.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​

FieldTypeNotes
downloadMbps / uploadMbpsDouble?nil when the SDK did not measure that phase. Never 0 as a stand-in.
pingKapeSpeedTestPing?latencyMs, jitterMs, packetLossPercent
serverKapeSpeedTestServer?locationId, name, ip
ratingsKapeSpeedTestRatingsoverall, 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, run cancels it, waits up to 10 s for the SDK to go idle, then starts. That operation's caller gets CancellationError.
  • If another speed test is running, from this instance or another one, run throws .busy and leaves that test running.

Errors​

run throws KapeSpeedTestError:

CaseMeaning
.notLicensedThe license does not grant CategoryInstanceDiscovery and SpeedTest. Checked before anything runs.
.noActiveSubscriptionhandle.activeSubscriptionId is empty, for example when the user is signed out.
.busyAnother speed test is running, or the SDK did not go idle in time.
.abortedThe SDK aborted the measurement twice. One abort is retried automatically.
.dnsFailureThe test server could not be resolved.
.networkA network error.
.testFailedThe measurement failed.
.sdk(KapeError)Any other SDK error.