layout: page title: mtr metrics permalink: /blog/mtr-metrics/ keywords: mtr,traceroute,ping,jitter,rtt,latency,interarrival jitter description: mtr is a combination of traceroute and ping, and can generate a wide variety of metrics. But how are these calculated and what do they mean? lang: de date: 2025-02-17 00:00:00 +0100
mtr is a network diagnostics tool that combines traceroute and ping. It determines the path to a destination and updates it continuously. In addition, mtr determines the round trip time (RTT) for each individual hop on the path. In addition to the RTT, further metrics are calculated for each hop and the destination. These include: the packet loss, the best RTT, the average RTT, the worst RTT, the standard deviation of the RTT, the geometric mean of the RTT, the average jitter, the worst jitter and the interarrival jitter (according to RFC1889).
The metrics can only be calculated if a packet returns. Therefore, lost packets cannot be included in the metrics. The lost packets can only be reflected in the packet loss.
The RTT is the time it takes for a packet to get to a hop and back.
Note: The RTT describes the time it takes for a packet to get there and back. Latency generally describes the time a packet takes from one hop to another - i.e. only the outbound or return path.
Jitter indicates how far apart the various measured RTTs are and therefore how much this fluctuates. Jitter serves a similar function to the standard deviation. Jitter describes how much the packet transit time varies. A low jitter means that the RTT is fairly constant. A high jitter, on the other hand, means that the RTT fluctuates greatly. Example 1: Three RTTs were measured: 5ms, 6ms, 3ms. The average jitter would be very low at 1ms. The RTT therefore remained quite stable during the measurements. Example 2: Three RTTs were measured: 10ms, 100ms, 1ms. The average jitter would be very high at 63ms. The RTT is not stable and changes frequently or often deviates from the previous measurements.
The standard deviation of the RTT has a similar function to jitter. In the first example from jitter, the standard deviation would be 1ms and for the second example it would be 54ms.
In mtr, the values are calculated in ui/net.c. There, the standard deviation is referred to as the "sum of squares of differences from the current average". The interarrival jitter is referred to there as "estimated variance?".
I personally believe that you get a better feel for the metrics if you know how they are calculated. I have therefore created some C code for the calculation (adapted from ui/net.c) as well as a mathematical notation for the calculation.
To illustrate the calculation with concrete values, I carried out a real mtr measurement (10 cycles long). For the fun of it, I did this using an old smartphone on which I had installed Kali NetHunter and which was connected to a Freifunk router. This Freifunk router also had an interconnection to the dn42 through the IC-VPN, so I could ping Burble's pingable address. I recorded the measurement with tcpdump
and cut out the 20 relevant packets (see below). The corresponding pcapng file can be found here. I executed mtr with the following command:
$ mtr -4 --report --json --show-ips --order "LDRSBAWVGMXI" --report-cycles 10 --psize 400 --bitpattern 256 pingable.burble.dn42
mtr has output its measurement as JSON (formatted with jq
):
{
"report": {
"mtr": {
"src": "kali",
"dst": "pingable.burble.dn42",
"tos": 0,
"tests": 10,
"psize": "400",
"bitpattern": "rand(0x00-FF)"
},
"hubs": [
{
"count": 1,
"host": "gw4.ff3l (10.119.0.5)",
"Loss%": 10.0,
"Drop": 1,
"Rcv": 9,
"Snt": 10,
"Best": 34.079,
"Avg": 36.272,
"Wrst": 38.427,
"StDev": 1.473,
"Gmean": 36.245,
"Javg": 1.536,
"Jmax": 3.513,
"Jint": 10.83
},
{
"count": 2,
"host": "gw3.ff3l (10.119.0.4)",
"Loss%": 0.0,
"Drop": 0,
"Rcv": 10,
"Snt": 10,
"Best": 41.578,
"Avg": 45.264,
"Wrst": 50.228,
"StDev": 2.671,
"Gmean": 45.194,
"Javg": 3.176,
"Jmax": 8.65,
"Jint": 24.754
},
{
"count": 3,
"host": "p2pnode.bandura.dn42 (172.22.149.225)",
"Loss%": 0.0,
"Drop": 0,
"Rcv": 10,
"Snt": 10,
"Best": 55.045,
"Avg": 57.674,
"Wrst": 60.757,
"StDev": 1.985,
"Gmean": 57.643,
"Javg": 1.668,
"Jmax": 4.043,
"Jint": 13.546
},
{
"count": 4,
"host": "pingable.burble.dn42 (172.20.129.5)",
"Loss%": 0.0,
"Drop": 0,
"Rcv": 10,
"Snt": 10,
"Best": 60.776,
"Avg": 63.143,
"Wrst": 67.09,
"StDev": 1.927,
"Gmean": 63.115,
"Javg": 2.568,
"Jmax": 6.314,
"Jint": 18.246
}
]
}
}
The measurement contains the final values, but not the individual RTT values. I extracted these from the pcapng file using WireShark. Since the packets arriving at the interface (recorded by tcpdump) can have a delay until they arrive at mtr and mtr processes them, my final values differ slightly from those of mtr.
Note: The pcapng file also contained other data in addition to the mtr measurement. In addition, the mtr measurement consisted of several measurements towards the different hops. For simplicity, I only selected one hop (the target) for the specific values.
Note: I did the measurement once successfully with IPv4 and IPv6. IPv6 also worked except for the reverse DNS.
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
struct measurement_s
{
int last; // last rtt
int jitter; // jitter
int best; // best rtt
int worst; // worst rtt
int jworst; // worst jitter
int returned; // number of pings
int avg; // average rtt
long long ssd; // standard deviation
int javg; // average jitter
int jinta; // interarrival jitter
int gmean; // geometric mean
};
typedef struct measurement_s measurement_t;
static inline int max(int a, int b)
{
return (a > b ? a : b);
}
static inline int min(int a, int b)
{
return (a < b ? a : b);
}
void process_ping(measurement_t * const m, const int rtt);
void post_process_measurement(measurement_t * const m);
int main(void)
{
int rtt[] = {
61172, 64956, 60273, 66617, 61781, 60847, 63437, 61955, 62766, 62580};
size_t rtt_n = sizeof(rtt) / sizeof(*rtt); // Number of measurements
measurement_t m = {.last = 0,
.jitter = 0,
.best = 0,
.worst = 0,
.jworst = 0,
.returned = 0,
.avg = 0,
.ssd = 0,
.javg = 0,
.jinta = 0,
.gmean = 0};
for (size_t i = 0; i < rtt_n; i++)
{
process_ping(&m, rtt[i]);
}
post_process_measurement(&m);
printf("last = %d\n"
"jitter = %d\n"
"best = %d\n"
"worst = %d\n"
"jworst = %d\n"
"returned = %d\n"
"avg = %d\n"
"ssd = %lld\n"
"javg = %d\n"
"jinta = %d\n"
"gmean = %d\n",
m.last,
m.jitter,
m.best,
m.worst,
m.jworst,
m.returned,
m.avg,
m.ssd,
m.javg,
m.jinta,
m.gmean);
return EXIT_SUCCESS;
}
void process_ping(measurement_t * const m, const int rtt)
{
m->returned++;
if (m->returned <= 1) // first ping
{
m->jitter = m->jworst = m->ssd = m->javg = m->jinta = 0;
m->best = m->worst = m->avg = m->gmean = rtt;
}
else
{
// With `.0` a floating point calculation is forced.
m->jitter = abs(rtt - m->last);
m->best = min(m->best, rtt);
m->worst = max(m->worst, rtt);
m->jworst = max(m->jworst, m->jitter);
int oldavg = m->avg;
m->avg += (rtt - m->avg + .0) / m->returned;
m->ssd += (rtt - oldavg + .0) * (rtt - m->avg + .0);
m->javg += (m->jitter - m->javg + .0) / m->returned;
m->jinta += m->jitter - ((m->jinta + 8) >> 4);
m->gmean =
(int) (pow(m->gmean + .0, (m->returned - 1.0) / m->returned) *
pow(rtt + .0, 1.0 / m->returned));
}
m->last = rtt;
}
void post_process_measurement(measurement_t * const m)
{
m->ssd = sqrt(m->ssd / (m->returned - 1.0));
}
The data for the current round is stored in the struct
measurement_s
. Each round - i.e. whenever a ping is received - the function process_ping
is called. This updates the data in the struct
based on the RTT value it has received. When the first ping is received, the metrics are filled with specific start values. Depending on the metric, this is either 0 and the current (and at that time only) RTT. Otherwise, the new values are calculated using appropriate formulas.
Due to the properties of the standard deviation, it is only calculated indirectly. To actually obtain it, the actual standard deviation is calculated (once) in the post_process_measurement
function at the end. This calculation is also carried out in mtr each time the standard deviation is to be displayed.
Finally, everything is output once. Although the attributes last
and returned
are necessary for the calculation, they are only of limited interest as metrics. last
stores the last RTT and returned
the number of successfully received ping replies.
The unit of time measurement is microseconds. Conversion:
Microseconds (μs) = miliseconds (ms) * 1000
Miliseconds (ms) = microseconds (μs) / 1000
Example: 61172μs is 61.172ms; 64.956ms is 64956μs.
Example:
rtt:ℕ0→ℕ0rtt0=61172rtt1=64956rtt2=60273rtt3=66617rtt4=61781rtt5=60847rtt6=63437rtt7=61955rtt8=62766rtt9=62580Example:
jitter0=0jitter1=abs(64956−61172)=3784jitter2=abs(60273−64956)=4683jitter3=abs(66617−60273)=6344jitter4=abs(61781−66617)=4836jitter5=abs(60847−61781)=934jitter6=abs(63437−60847)=2590jitter7=abs(61955−63437)=1482jitter8=abs(62766−61955)=811jitter9=abs(62580−62766)=186Example:
best0=61172best1=min(61172,64956)=61172best2=min(61172,60273)=60273best3=min(60273,66617)=60273best4=min(60273,61781)=60273best5=min(60273,60847)=60273best6=min(60273,63437)=60273best7=min(60273,61955)=60273best8=min(60273,62766)=60273best9=min(60273,62580)=6027360273μs is 60.273ms.
Example:
worst0=61172worst1=max(61172,64956)=64956worst2=max(64956,60273)=64956worst3=max(64956,66617)=66617worst4=max(66617,61781)=66617worst5=max(66617,60847)=66617worst6=max(66617,63437)=66617worst7=max(66617,61955)=66617worst8=max(66617,62766)=66617worst9=max(66617,62580)=6661766617μs is 66.617ms.
Example:
avg0=61172avg1=⌊61172+64956−611722⌋=63064avg2=⌊63064+60273−630643⌋=62133avg3=⌊62133+66617−621334⌋=63254avg4=⌊63254+61781−632545⌋=62959avg5=⌊62959+60847−629596⌋=62607avg6=⌊62607+63437−626077⌋=62725avg7=⌊62725+61955−627258⌋=62628avg8=⌊62628+62766−626289⌋=62643avg9=⌊62643+62580−6264310⌋=6263662636μs is 62.636ms.
1944μs is 1.944ms.
Example:
gmean0=61172gmean1=⌊6117212⋅6495612⌋=63035gmean2=⌊6303523⋅6027313⌋=62100gmean3=⌊6210034⋅6661714⌋=63199gmean4=⌊6319945⋅6178115⌋=62912gmean5=⌊6291256⋅6084716⌋=62563gmean6=⌊6256367⋅6343717⌋=62687gmean7=⌊6268778⋅6195518⌋=62595gmean8=⌊6259589⋅6276619⌋=62613gmean9=⌊62613910⋅62580110⌋=6260962609μs is 62.609ms.
Example:
jworst0=0jworst1=max(0,3784)=3784jworst2=max(3784,4683)=4683jworst3=max(4683,6344)=6344jworst4=max(4683,4836)=6344jworst5=max(4683,934)=6344jworst6=max(4683,2590)=6344jworst7=max(4683,1482)=6344jworst8=max(4683,811)=6344jworst9=max(4683,186)=63446344μs is 6.344ms.
Example:
javg0=0javg1=⌊0+3784−02⌋=1892javg2=⌊1892+4683−18923⌋=2822javg3=⌊2822+6344−28224⌋=3702javg4=⌊3702+4836−37025⌋=3928javg5=⌊3928+934−39286⌋=3429javg6=⌊3429+2590−34297⌋=3309javg7=⌊3309+1482−33098⌋=3080javg8=⌊3080+811−30809⌋=2827javg9=⌊2827+186−282710⌋=25622562μs is 2.562ms.
Example:
jinta0=0jinta1=0+3784−((0+8)>>4)=3784jinta2=3784+4683−((3784+8)>>4)=8230jinta3=8230+6344−((8230+8)>>4)=14060jinta4=14060+4836−((14060+8)>>4)=18017jinta5=18017+934−((18017+8)>>4)=17825jinta6=17825+2590−((17825+8)>>4)=19301jinta7=19301+1482−((19301+8)>>4)=19577jinta8=19577+811−((19577+8)>>4)=19164jinta9=19164+186−((19164+8)>>4)=18152