IRTT measures round-trip time, one-way delay and other metrics using UDP packets sent on a fixed period, and produces both user and machine parseable output.
IRTT has reached version 0.9.1. I would appreciate any feedback, which you can send under Issues. However, it could be useful to first review the Roadmap section of the documentation before submitting a new bug or feature request.
Latency is an under-appreciated metric in network and application performance. As of this writing, many broadband connections are well past the point of diminishing returns when it comes to throughput, yet that’s what we continue to take as the primary measure of Internet performance. This is analogous to ordinary car buyers making top speed their first priority.
There is a certain hard to quantify but visceral “latency stress” that comes from waiting in expectation after a web page click, straining through a delayed and garbled VoIP conversation, or losing at your favorite online game (unless you like “lag” as an excuse). Those who work on reducing latency and improving network performance characteristics beyond just throughput may be driven by the idea of helping relieve this stress for others.
IRTT was originally written to improve the latency and packet loss measurements for the excellent Flent tool, but should be useful as a standalone tool as well. Flent was developed by and for the Bufferbloat project, which aims to reduce "chaotic and laggy network performance," making this project valuable to anyone who values their time and sanity while using the Internet.
The goals of this project are to:
See the LIMITATIONS section of the irtt(1) man page.
To install IRTT manually or build from source, you must:
go install github.com/heistp/irtt/cmd/irtt@latest
irtt
executable, which should be in
$HOME/go/bin
, or $GOPATH/bin
if you have $GOPATH
defined, to somewhere
on your PATH
.If you want to build the source for development, you must also:
pandoc
utility for generating man pages and HTML documentation
from their markdown source files. This can be done with apt-get install pandoc
on Debian flavors of Linux or brew install pandoc
on OS/X. See the
Pandoc site for more information.stringer
utility by doing
go install golang.org/x/tools/cmd/stringer@latest
.
This is only necessary if you need to re-generate the *_string.go
files that
are generated by this tool, otherwise the checked in versions may also be
used.build.sh
to build during development, which helps with development
related tasks, such as generating source files and docs, and cross-compiling
for testing. For example, build.sh min linux-amd64
would compile a
minimized binary for Linux on AMD64. See build.sh
for more info and a
"source-documented" list of platforms that the script supports. See this
page for a full list of
valid GOOS GOARCH combinations. build.sh install
runs Go's install command,
which puts the resulting executable in $GOPATH/bin
.If you want to build from a branch, you should first follow the steps above,
then from the github.com/heistp/irtt
directory, do:
git checkout branch
go get ./...
go install ./cmd/irtt
or ./build.sh
and move resulting irtt
executable
to install locationBuilding for iOS:
I have no way to verify this, but I received a report that the following is "close to but not quite the right command" to cross-compile for iOS:
GOOS=ios GOARCH=arm64 IPHONEOS_DEPLOYMENT_TARGET=14.0 CGO_ENABLED=1 CGO_CFLAGS="-arch arm64 -isysroot `xcrun --sdk iphoneos --show-sdk-path` -mios-version-min=10.0" CGO_LDFLAGS="-arch arm64 -isysroot `xcrun --sdk iphoneos --show-sdk-path`" go build -o irtt cmd/irtt/main.go
Please file an issue if you get this working so I can update the doc.
After installing IRTT, see the man pages and their corresponding EXAMPLES sections to get started quickly:
1) Why not just use ping?
Ping may be the preferred tool when measuring minimum latency, or for other reasons. IRTT's reported mean RTT is likely to be a bit higher (on the order of a couple hundred microseconds) and a bit more variable than the results reported by ping, due to the overhead of entering userspace, together with Go's system call overhead and scheduling variability. That said, this overhead should be negligible at most Internet RTTs, and there are advantages that IRTT has over ping when minimum RTT is not what you're measuring:
- In addition to round-trip time, IRTT also measures OWD, IPDV and upstream
vs downstream packet loss.
- Some device vendors prioritize ICMP, so ping may not be an accurate measure
of user-perceived latency.
- IRTT can use HMACs to protect private servers from unauthorized discovery
and use.
- IRTT has a three-way handshake to prevent test traffic redirection from
spoofed source IPs.
- IRTT can fill the payload (if included) with random or arbitrary data.
On Windows, ping has a precision of 0.5ms, while IRTT uses high resolution timer functions for a precision of 100ns (high resolution wall clock only available on Windows 8 or Windows 2012 Server and later).
Also note the following behavioral differences between ping and IRTT:
IRTT makes a stateful connection to the server, whereas ping is stateless.
By default, ping waits for a reply before sending its next request, while IRTT keeps sending requests on the specified interval regardless of whether or not replies are received. The effect of this, for example, is that a fixed-length pause in server packet processing (with packets buffered during the pause) will look like a single high RTT in ping, and multiple high then descending RTTs in IRTT for the duration of the maximum RTT.
2) Is there a public server I can use?
There is a test server running at irtt.heistp.net
with an HMAC key of
irttuser
. Please do not abuse it. To restrict bandwidth, the minimum
interval is set to 100ms, the max length to 256 bytes, and the max duration
to 60 seconds. Example usage:
irtt client --hmac=irttuser irtt.heistp.net
3) How do I run the IRTT server at startup?
This depends on your OS and init system, but see:
irtt.service
file for
systemd, used in Debian
and Ubuntuirtt.openrc
file for OpenRC,
used in Gentoo and Alpine4) Why can't the client connect to the server, and instead I get
Error: no reply from server
?
There are a number of possible reasons for this:
1) You've specified an incorrect hostname or IP address for the server.
2) There is a firewall blocking packets from the client to the server.
Traffic must be allowed on the chosen UDP port (default 2112).
3) There is high packet loss. By default, up to four packets are sent when
the client tries to connect to the server, using timeouts of 1, 2, 4 and 8
seconds. If all of these are lost, the client won't connect to the server.
In environments with known high packet loss, the --timeouts
flag may
be used to send more packets with the chosen timeouts before abandoning
the connection.
4) The server has an HMAC key set with --hmac
and the client either has
not specified a key or it's incorrect. Make sure the client has the
correct HMAC key, also specified with the --hmac
flag.
5) You're trying to connect to a listener that's listening on an unspecified
IP address, but reply packets are coming back on a different route from the
requests, or not coming back at all. This can happen in network
environments with [asymmetric routing and a firewall or NAT]
(https://www.cisco.com/web/services/news/ts_newsletter/tech/chalktalk/archives/200903.html).
There are several possible solutions to this:
-b
flag.--set-src-ip
flag on the server, which explicitly sets the
source address on all reply packets from listeners on unspecified IP
addresses to the destination address that the request was received on.
The only reason this is not done by default is to avoid the extra
per-packet heap allocations required by the golang.org/x/net
packege
to do so.5) Why is the send (or receive) delay negative or much larger than I expect?
The client and server clocks must be synchronized for one-way delay values to
be meaningful (although, the relative change of send and receive delay may be
useful to look at even without clock synchronization). Well-configured NTP hosts may be able to synchronize to within a few milliseconds. PTP (Linux implementation here) is capable of much higher precision. For example, using two PCEngines APU2 boards (which support PTP hardware timestamps) connected directly by Ethernet, the clocks may be synchronized within a few microseconds.
Note that client and server synchronization is not needed for either RTT or
IPDV (even send and receive IPDV) values to be correct. RTT is measured with
client times only, and since IPDV is measuring differences between successive
packets, it's not affected by time synchronization.
6) Why is the receive rate 0 when a single packet is sent?
Receive rate is measured from the time the first packet is received to the time the last packet is received. For a single packet, those times are the same.
7) Why does a test with a one second duration and 200ms interval run for around 800ms and not one second?
The test duration is exclusive, meaning requests will not be sent exactly at or after the test duration has elapsed. In this case, the interval is 200ms, and the fifth and final request is sent at around 800ms from the start of the test. The test ends when all replies have been received from the server, so it may end shortly after 800ms. If there are any outstanding packets, the wait time is observed, which by default is a multiple of the maximum RTT.
8) Why is IPDV not reported when only one packet is received?
IPDV is the difference in delay between successfully returned replies, so at least two reply packets are required to make this calculation.
9) Why does wait fall back to fixed duration when duration is less than RTT?
If a full RTT has not elapsed, there is no way to know how long an appropriate wait time would be, so the wait falls back to a default fixed time (default is 4 seconds, same as ping).
10) Why can't the client connect to the server, and I either see [Drop] [UnknownParam] unknown negotiation param (0x8 = 0)
on the server, or a strange
message on the client like [InvalidServerRestriction] server tried to reduce interval to < 1s, from 1s to 92ns
?
You're using a 0.1 development version of the server with a newer client.
Make sure both client and server are up to date. Going forward, the protocol
is versioned (independently from IRTT in general), and is checked when the
client connects to the server. For now, the protocol versions must match
exactly.
11) Why don't you include median values for send call time, timer error and server processing time?
Those values aren't stored for each round trip, and it's difficult to do a
running calculation of the median, although
[this method](https://rhettinger.wordpress.com/2010/02/06/lost-knowledge/) of
using skip lists appears to have promise. It's a possibility for the future,
but so far it isn't a high priority. If it is for you, file an
[Issue](https://github.com/heistp/irtt/issues).
12) I see you use MD5 for the HMAC. Isn't that insecure?
MD5 should not have practical vulnerabilities when used in a message authenticate
code. See
[this page](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code#Security)
for more info.
13) Are there any plans for translation to other languages?
While some parts of the API were designed to keep i18n possible, there is no
support for i18n built in to the Go standard libraries. It should be possible,
but could be a challenge, and is not something I'm likely to undertake myself.
14) Why do I get Error: failed to allocate results buffer for X round trips (runtime error: makeslice: cap out of range)
?
Your test interval and duration probably require a results buffer that's
larger than Go can allocate on your platform. Lower either your test
interval or duration. See the following additional documentation for
reference: [In-memory results storage](#in-memory-results-storage),
`maxSliceCap` in [slice.go](https://golang.org/src/runtime/slice.go) and
`_MaxMem` in [malloc.go](https://golang.org/src/runtime/malloc.go).
15) Why is little endian byte order used in the packet format?
As for Google's [protobufs](https://github.com/google/protobuf), this was
chosen because the vast majority of modern processors use little-endian byte
order. In the future, packet manipulation may be optimized for little-endian
architecutures by doing conversions with Go's
[unsafe](https://golang.org/pkg/unsafe/) package, but so far this
optimization has not been shown to be necessary.
16) Why does irtt client
use -l
for packet length instead of following ping
and using -s
for size?
I felt it more appropriate to follow the
[RFC 768](https://tools.ietf.org/html/rfc768) term _length_ for UDP packets,
since IRTT uses UDP.
17) Why is the virt size (vsz) memory usage for the server so high in Linux?
This has to do with the way Go allocates memory, but should not cause a
problem. See [this
article](https://deferpanic.com/blog/understanding-golang-memory-usage/) for
more information. File an Issue if your resident usage (rss/res) is high or
you feel that memory consumption is somehow a problem.
18) Why doesn't the server start on Linux when the kernel parameter
ipv6.disable=1
is set?
By default, IRTT tries to listen on both IPv4 and IPv6 addresses, and for
safety, the server shuts down if there are failures on any of the
listeners for any of the addresses. In this case, the server may be
started with the `-4` flag.
19) Why don't you make use of x
library?
We need to keep the executable size as small as possible for embedded
devices, and most external libaries are not compatible with this.
See CHANGES.md.
Planned for v0.9.2...
late
flag to RoundTripreport
command, or similar, to print results from an existing JSON filePlanned for v1.0.0...
chrt -r 99
, --thread
and --gc
Collection area...
Many thanks to both Toke Høiland-Jørgensen and Dave Täht from the Bufferbloat project for their valuable advice. Any problems in design or implementation are entirely my own.