Running clients and servers¶
These examples use the APIs available in 0.3.0. Follow Getting started.
Choose a protocol and direction¶
from iperf3_lib import Client, ClientConfig, Protocol
result = Client(
ClientConfig(
server="127.0.0.1",
protocol=Protocol.UDP,
duration=5,
rate=10_000_000,
parallel=1,
)
).run()
rate is a target in bits per second for each stream. With multiple streams,
the native rate limit applies independently to each one. rate=0 disables
that limit. These are the upstream
iperf3 bitrate semantics.
When rate=None, the wrapper sets UDP to 1,048,576 bits/s and leaves TCP/SCTP
at their native defaults. When blksize=None, UDP uses libiperf's dynamic
selection, SCTP uses 65,536 bytes, and TCP keeps its native default.
| Configuration | Traffic direction |
|---|---|
| Defaults | Client sends to server. |
reverse=True |
Server sends to client. |
bidirectional=True |
Both directions run simultaneously. |
reverse and bidirectional cannot both be enabled. Two separate forward and
reverse runs measure different conditions from one simultaneous bidirectional
run. Keep that distinction when comparing measurements.
All accepted fields, defaults, and bounds are in the configuration reference. SCTP requires both the operating system and libiperf build to support SCTP.
Integrate with asyncio¶
import asyncio
from iperf3_lib import Client, ClientConfig
async def main() -> None:
config = ClientConfig(server="127.0.0.1", duration=2)
result = await Client(config).arun()
print(result.ok, result.summary_mbps)
asyncio.run(main())
arun() moves the operation to an executor thread. Cancelling the awaiting
task, including through an asyncio timeout, does not terminate that operation.
Pass await Client(config).arun(timeout=10) to select an isolated worker with
its own execution deadline. A task's cancellation alone is not evidence that
the native library is idle.
Serialize basic direct native operations within a process. libiperf's global
error state and blocking operations are not treated as reentrant. Multiple
Client objects or separate executor threads do not provide isolation.
Expanded controls, MPTCP, streaming, explicit execution timeouts and event
callbacks select the Python/CFFI worker. See native execution
for the path-selection and delivery contract; no iperf3 subprocess is used.
Handle failures¶
Client.run() and Client.arun() return Result(ok=False, error=...) when
libiperf reports an IperfError during the run or returns no JSON. Some errors
are raised instead:
| Error | Meaning |
|---|---|
TypeError, ValueError from ClientConfig |
A configuration value or combination is invalid. |
UnsupportedFeatureError |
The wrapper or loaded native library cannot apply a requested feature. |
IperfLibraryError |
Loading, allocation, or native setup verification failed. |
JSON decoding errors or parser ValueError |
The returned native JSON could not be decoded or normalized. |
TimeoutError |
The worker execution deadline expired; its process was terminated and reaped. |
| Callback exception | Delivery stopped; the exception propagates after the active run and worker shutdown. |
Do not use result.ok as a performance acceptance decision: a completed test
can have low throughput or substantial loss. Choose application thresholds with
AssessmentPolicy, then use assess_plan for
repeated trials and baseline assessment.
Use the Python server wrapper¶
Run the server in a separate process from your client:
from iperf3_lib import Server, ServerConfig
server = Server(config=ServerConfig(
port=5201,
bind_address="127.0.0.1",
address_family="ipv4",
idle_timeout_seconds=10,
))
result = server.run_once(timeout=15)
print(result.ok, result.reporting_role, result.error)
bind_address selects an address assigned to the local server. For a remote
network, substitute that interface's assigned IP. bind_device selects a device
name separately. The concise Server(port=5201, bind_host="127.0.0.1") form
remains available, with bind_host serving as an address alias. Do not combine
legacy constructor arguments with config. The
configuration reference
lists native bitrate/duration policies, authentication and all other controls.
run_once() returns a normalized server Result; aserve_once() returns the
same result through an executor thread. Server operations always use an isolated
Python worker. Native failures are retained in results; setup errors raise.
An idle exit with no JSON produces an incomplete result.
For a bounded sequential session:
def record_result(result):
print(result.ok, result.reporting_role, result.error)
server.serve_forever(on_result=record_result, max_runs=3, timeout=60)
Each iteration creates and configures a fresh native test, then frees it before
on_result receives the attempt. The same worker process serves the sequential
session; there is no native-test reset/reuse. on_result receives failures too.
Without that callback, a native failed result raises IperfError from
serve_forever(). max_runs limits the attempt count. timeout covers
startup and the whole serving session, not 60 seconds per client. A failed
or incomplete attempt ends the session. Event callbacks can also be supplied
with on_event, which enables streaming automatically; callback errors propagate
after worker shutdown.
stop() prevents the next iteration but does not interrupt a blocked listener
or active test. Use an explicit timeout for a bounded session. The stop flag
is not reset: a later serve_forever() returns without starting a worker,
although one-shot calls remain available. Concurrent use of one Server
instance raises RuntimeError.