This shows you the differences between the selected revision and the current version of the page.
| en:scytheman:zeugs:code:bandwidthtool 2010/03/06 14:44 | en:scytheman:zeugs:code:bandwidthtool 2010/08/22 18:53 current | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | [[en:scytheman|↑ up]] | ||
| + | |||
| ====== bandwidthTool ====== | ====== bandwidthTool ====== | ||
| ===== Description ===== | ===== Description ===== | ||
| - | bandwidthTool is a tiny program to measure the end to end capacity (bottleneck bandwidth) of a path with the help of the ''packet train dispersion technique''. The main idea is to send multiple packet pairs/trains from one end to the other consisting of multiple packets of the same size sent back-to-back. Then, the dispersion (distance between the last bit of the first and last packet) is measured, corresponding to the capacity if the links do not carry other traffic. Cross-traffic can increase or decrease the measured dispersion, causing under- or overestimation of the path capacity. | + | {{scytheman:zeugs:bandwidthtool.tar.gz|bandwidthTool}} is a tiny program to measure the //end to end capacity// (bottleneck bandwidth) of a path with the help of the //packet train dispersion technique//. The main idea is to send multiple packet pairs/trains from one end to the other consisting of multiple packets of the same size sent back-to-back. Then, the dispersion (distance between the last bit of the first and last packet) is measured, corresponding to the capacity if the links do not carry other traffic (see figure). Cross-traffic can increase or decrease the measured dispersion, causing under- or overestimation of the path capacity. |
| + | |||
| + | {{scytheman:zeugs:code:packet_pairs.png|packet pair technique}} | ||
| Measurements can be adapted by either changing the packet size or using different train lengths. Using the default settings below, //800 * 15 bytes (11.7 KiB)// are sufficient for one single measurement. | Measurements can be adapted by either changing the packet size or using different train lengths. Using the default settings below, //800 * 15 bytes (11.7 KiB)// are sufficient for one single measurement. | ||
| Line 53: | Line 57: | ||
| // number of packets per train | // number of packets per train | ||
| #define TRAIN_LEN 15 | #define TRAIN_LEN 15 | ||
| - | |||
| - | class SOBandwidthTool | ||
| - | { | ||
| - | private: | ||
| - | /// time difference in micro seconds | ||
| - | int timeDiff(const struct timeval& tv1, const struct timeval& tv2); | ||
| - | |||
| - | public: | ||
| - | int receiver(const std::string& hostAddr); | ||
| - | int sender(); | ||
| - | }; | ||
| /// time difference in micro seconds | /// time difference in micro seconds | ||
| Line 70: | Line 63: | ||
| } | } | ||
| - | int SOBandwidthTool::receiver(const std::string& hostAddr) { | + | int SOBandwidthTool::receiver(const std::string& hostAddr) { |
| - | // get host address | + | // get host address list |
| - | struct addrinfo* addr; | + | struct addrinfo hints; |
| - | int err = getaddrinfo(hostAddr.c_str(), 0, 0, &addr); | + | memset(&hints, 0, sizeof(struct addrinfo)); |
| + | hints.ai_socktype = SOCK_STREAM; | ||
| + | std::stringstream cport; | ||
| + | cport << TCP_PORT; | ||
| + | struct addrinfo* addrList; | ||
| + | int err = getaddrinfo(hostAddr.c_str(), cport.str().c_str(), &hints, &addrList); | ||
| if(err != 0) { | if(err != 0) { | ||
| std::cerr << "Error: Could not resolve host address: " << gai_strerror(err) << std::endl; | std::cerr << "Error: Could not resolve host address: " << gai_strerror(err) << std::endl; | ||
| Line 87: | Line 85: | ||
| int opt = 1; | int opt = 1; | ||
| - | if (setsockopt(tcpSock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) == -1) { | + | if(setsockopt(tcpSock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) == -1) { |
| std::cerr << "Error: Could not set SO_REUSEADDR on TCP socket: " << strerror(errno) << std::endl; | std::cerr << "Error: Could not set SO_REUSEADDR on TCP socket: " << strerror(errno) << std::endl; | ||
| + | close(tcpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| - | struct sockaddr_in tcpAddr; | + | // getaddrinfo() returns a list of addresses, try each until connect() succeeds |
| - | memset(&tcpAddr, 0, sizeof tcpAddr); | + | struct addrinfo* addr; |
| - | tcpAddr.sin_family = AF_INET; | + | for(addr = addrList; addr != NULL; addr = addr->ai_next) { |
| - | tcpAddr.sin_addr.s_addr = ((struct sockaddr_in*)addr->ai_addr)->sin_addr.s_addr; | + | std::cout << "Trying to connect to " << inet_ntoa(((struct sockaddr_in*)addr->ai_addr)->sin_addr) |
| - | tcpAddr.sin_port = htons(TCP_PORT); | + | << ":" << ntohs(((struct sockaddr_in*)addr->ai_addr)->sin_port) << std::endl; |
| - | + | if(connect(tcpSock, addr->ai_addr, addr->ai_addrlen) != -1) { | |
| - | std::cout << "Trying to connect to TCP socket on " << hostAddr << std::endl; | + | // got right address, keep pointer for udp socket later |
| - | if(connect(tcpSock, (struct sockaddr*)&tcpAddr, sizeof(tcpAddr)) == -1) { | + | break; |
| - | std::cerr << "Error: Could not connect TCP socket: " << strerror(errno) << std::endl; | + | } |
| + | } | ||
| + | |||
| + | if(addr == NULL) { | ||
| + | std::cerr << "Error: Could not connect to host " << hostAddr << std::endl; | ||
| + | close(tcpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| Line 110: | Line 114: | ||
| struct timeval now, last; | struct timeval now, last; | ||
| int msg = 42; | int msg = 42; | ||
| - | std::cout << "Measuring RTT." << std::endl; | + | std::cout << "Measuring RTT: "; |
| for(unsigned int i = 0; i < RTT_PROBES; i++) { | for(unsigned int i = 0; i < RTT_PROBES; i++) { | ||
| // transmit message | // transmit message | ||
| if(send(tcpSock, &msg, sizeof(msg), 0) != sizeof(msg)) { | if(send(tcpSock, &msg, sizeof(msg), 0) != sizeof(msg)) { | ||
| std::cerr << "Error: Could not send on TCP socket: " << strerror(errno) << std::endl; | std::cerr << "Error: Could not send on TCP socket: " << strerror(errno) << std::endl; | ||
| + | close(tcpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| Line 123: | Line 128: | ||
| if(recv(tcpSock, buf, sizeof(msg), 0) != sizeof(msg)) { | if(recv(tcpSock, buf, sizeof(msg), 0) != sizeof(msg)) { | ||
| std::cerr << "Error: Could not receive on TCP socket: " << strerror(errno) << std::endl; | std::cerr << "Error: Could not receive on TCP socket: " << strerror(errno) << std::endl; | ||
| + | close(tcpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| Line 130: | Line 136: | ||
| int duration = timeDiff(last, now); | int duration = timeDiff(last, now); | ||
| totalRtt += duration; | totalRtt += duration; | ||
| - | std::cout << duration / 1000.0 << "ms\t" << std::flush; | + | std::cout << duration / 1000.0 << " ms " << std::flush; |
| } | } | ||
| } | } | ||
| Line 137: | Line 143: | ||
| double rtt = totalRtt / ((RTT_PROBES - 2) * 1000.0); | double rtt = totalRtt / ((RTT_PROBES - 2) * 1000.0); | ||
| - | std::cout << "Average RTT is " << rtt << "ms" << std::endl; | + | std::cout << "Average RTT is " << rtt << " ms" << std::endl; |
| // | // | ||
| Line 168: | Line 174: | ||
| if(sendto(udpSock, packet.c_str(), PACKET_LEN, 0, (struct sockaddr*)&udpAddr, sizeof(udpAddr)) != PACKET_LEN) { | if(sendto(udpSock, packet.c_str(), PACKET_LEN, 0, (struct sockaddr*)&udpAddr, sizeof(udpAddr)) != PACKET_LEN) { | ||
| std::cerr << "Error: Could not send on UDP socket: " << strerror(errno) << std::endl; | std::cerr << "Error: Could not send on UDP socket: " << strerror(errno) << std::endl; | ||
| + | close(udpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| Line 186: | Line 193: | ||
| int opt = 1; | int opt = 1; | ||
| - | if (setsockopt(tcpSock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) == -1) { | + | if(setsockopt(tcpSock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) == -1) { |
| std::cerr << "Error: Could not set SO_REUSEADDR on TCP socket: " << strerror(errno) << std::endl; | std::cerr << "Error: Could not set SO_REUSEADDR on TCP socket: " << strerror(errno) << std::endl; | ||
| + | close(tcpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| Line 198: | Line 206: | ||
| if(bind(tcpSock, (struct sockaddr*)&tcpAddr, sizeof(tcpAddr)) == -1) { | if(bind(tcpSock, (struct sockaddr*)&tcpAddr, sizeof(tcpAddr)) == -1) { | ||
| std::cerr << "Error: Could not bind TCP socket: " << strerror(errno) << std::endl; | std::cerr << "Error: Could not bind TCP socket: " << strerror(errno) << std::endl; | ||
| + | close(tcpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| - | std::cout << "Listening on TCP socket." << std::endl; | + | std::cout << "Listening on TCP socket on port " << TCP_PORT << std::endl; |
| - | if (listen(tcpSock, 1) == -1) { | + | if(listen(tcpSock, 1) == -1) { |
| std::cerr << "Error: Could not listen on TCP socket: " << strerror(errno) << std::endl; | std::cerr << "Error: Could not listen on TCP socket: " << strerror(errno) << std::endl; | ||
| + | close(tcpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| - | int conSock;; | + | int conSock; |
| socklen_t tcpAddrLen = sizeof(tcpAddr); | socklen_t tcpAddrLen = sizeof(tcpAddr); | ||
| if((conSock = accept(tcpSock, (struct sockaddr*)&tcpAddr, &tcpAddrLen)) == -1) { | if((conSock = accept(tcpSock, (struct sockaddr*)&tcpAddr, &tcpAddrLen)) == -1) { | ||
| std::cerr << "Error: Could not accept TCP connection: " << strerror(errno) << std::endl; | std::cerr << "Error: Could not accept TCP connection: " << strerror(errno) << std::endl; | ||
| + | close(tcpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| Line 224: | Line 235: | ||
| if(recv(conSock, buf, sizeof(msg), 0) != sizeof(msg)) { | if(recv(conSock, buf, sizeof(msg), 0) != sizeof(msg)) { | ||
| std::cerr << "Error: Could not receive on TCP socket: " << strerror(errno) << std::endl; | std::cerr << "Error: Could not receive on TCP socket: " << strerror(errno) << std::endl; | ||
| + | close(tcpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| Line 229: | Line 241: | ||
| if(send(conSock, &msg, sizeof(msg), 0) != sizeof(msg)) { | if(send(conSock, &msg, sizeof(msg), 0) != sizeof(msg)) { | ||
| std::cerr << "Error: Could not send on TCP socket: " << strerror(errno) << std::endl; | std::cerr << "Error: Could not send on TCP socket: " << strerror(errno) << std::endl; | ||
| + | close(tcpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| Line 254: | Line 267: | ||
| if(bind(udpSock, (struct sockaddr*)&udpAddr, sizeof(udpAddr)) == -1) { | if(bind(udpSock, (struct sockaddr*)&udpAddr, sizeof(udpAddr)) == -1) { | ||
| std::cerr << "Error: Could not bind UDP socket: " << strerror(errno) << std::endl; | std::cerr << "Error: Could not bind UDP socket: " << strerror(errno) << std::endl; | ||
| + | close(udpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| Line 266: | Line 280: | ||
| if(recvfrom(udpSock, recvMsg, PACKET_LEN, 0, (struct sockaddr*)&udpAddr, &udpAddrLen) != PACKET_LEN) { | if(recvfrom(udpSock, recvMsg, PACKET_LEN, 0, (struct sockaddr*)&udpAddr, &udpAddrLen) != PACKET_LEN) { | ||
| std::cerr << "Error: Could not receive on UDP socket: " << strerror(errno) << std::endl; | std::cerr << "Error: Could not receive on UDP socket: " << strerror(errno) << std::endl; | ||
| + | close(udpSock); | ||
| return -1; | return -1; | ||
| } | } | ||
| Line 279: | Line 294: | ||
| int diff_ns = timeDiff(first, last) * 1000; | int diff_ns = timeDiff(first, last) * 1000; | ||
| double diff_s = timeDiff(first, last) / 1000.0 / 1000.0; | double diff_s = timeDiff(first, last) / 1000.0 / 1000.0; | ||
| - | std::cout << "Duration: " << diff_ns << "ns" << " = " << diff_ns / 1000.0 / 1000.0 << "ms" << std::endl; | + | std::cout << "Duration: " |
| + | << diff_ns << " ns = " | ||
| + | << diff_ns / 1000.0 / 1000.0 << " ms" << std::endl; | ||
| double disp_s = ((TRAIN_LEN - 1) * PACKET_LEN) / double(diff_s); | double disp_s = ((TRAIN_LEN - 1) * PACKET_LEN) / double(diff_s); | ||
| - | std::cout << "Dispersion: " << disp_s / 1000.0 << "ms" << std::endl; | + | std::cout << "Dispersion: " << disp_s / 1000.0 << " ms" << std::endl; |
| - | std::cout << "Capacity: " << disp_s << "b/s = " << disp_s / 1024.0 / 1024.0 << "MB/s" << std::endl; | + | std::cout << "Capacity: " |
| + | << disp_s * 8.0 << " bit/s = " | ||
| + | << disp_s * 8.0 / 1000.0 << " kbit/s = " | ||
| + | << disp_s * 8.0 / 1000.0 / 1000.0 << " Mbit/s = " | ||
| + | << disp_s * 8.0 / 1000.0 / 1000.0 / 1000.0 << " Gbit/s" << std::endl; | ||
| return 0; | return 0; | ||
| Line 310: | Line 331: | ||
| bw.sender(); | bw.sender(); | ||
| } else if(*it == "-r" || *it == "--receive") { | } else if(*it == "-r" || *it == "--receive") { | ||
| - | if (++it < args.end()) { | + | if(++it < args.end()) { |
| bw.receiver(*it); | bw.receiver(*it); | ||
| } else { | } else { | ||
| Line 332: | Line 353: | ||
| ===== Analysis ===== | ===== Analysis ===== | ||
| + | |||
| + | To analyze the capacity estimation method, different scenarios were tested with 100 measurements for each of them. | ||
| * capacity: the capacity of the bottleneck link (there may be other links with higher capacity on the path) | * capacity: the capacity of the bottleneck link (there may be other links with higher capacity on the path) | ||
| Line 341: | Line 364: | ||
| | 192 kbit | light | 177.947 kbit | 178.374 kbit | 177.063 kbit | 216.482 kbit | | | 192 kbit | light | 177.947 kbit | 178.374 kbit | 177.063 kbit | 216.482 kbit | | ||
| | 2 Mbit | light | 1.830 Mbit | 1.830 Mbit | 1.396 Mbit | 2.549 Mbit | | | 2 Mbit | light | 1.830 Mbit | 1.830 Mbit | 1.396 Mbit | 2.549 Mbit | | ||
| + | | 2 Mbit | heavy | 1.821 Mbit | 1.802 Mbit | 1.359 Mbit | 2.252 Mbit | | ||
| | 100 Mbit | light | 89.960 Mbit | 90.278 Mbit | 84.929 Mbit | 95.624 Mbit | | | 100 Mbit | light | 89.960 Mbit | 90.278 Mbit | 84.929 Mbit | 95.624 Mbit | | ||
| | 100 Mbit | heavy | 90.504 Mbit | 201.605 Mbit | 72.846 Mbit | 1518.648 Mbit | | | 100 Mbit | heavy | 90.504 Mbit | 201.605 Mbit | 72.846 Mbit | 1518.648 Mbit | | ||
| - | This table shows, that cross-traffic can influence measurements heavily. Yet, the median is stable enough to rely on it, providing no overestimation of the capacity. | + | As this table shows, cross-traffic can influence measurements heavily (especially in the 100 Mbit scenario). Yet, the median is stable enough to rely on it, providing no overestimation of the capacity. |
| ==== WLAN ==== | ==== WLAN ==== | ||
| Line 352: | Line 377: | ||
| | 24 Mbit | heavy | 19.885 Mbit | 17.090 Mbit | 1.839 Mbit | 23.449 Mbit | | | 24 Mbit | heavy | 19.885 Mbit | 17.090 Mbit | 1.839 Mbit | 23.449 Mbit | | ||
| - | Here, cross-traffic also leads to significant measurement errors. But also light cross-traffic leads to a higher variance compared to LAN. | + | Here, (even light) cross-traffic leads to significant measurement errors. Nevertheless the median is quite stable and provides an adequate capacity estimation. |
| Note: As there is no constant medium using wireless networks, the capacity was measured using ''iperf''. | Note: As there is no constant medium using wireless networks, the capacity was measured using ''iperf''. | ||
| ==== Figures ==== | ==== Figures ==== | ||
| + | |||
| + | The following figures illustrate the measured capacities (100 tests each). Also note the y-axis when comparing two similar looking plots. | ||
| === LAN === | === LAN === | ||
| Line 362: | Line 389: | ||
| 192kbit: | 192kbit: | ||
| [[http://choerbaert.org/wiki/_media/scytheman:zeugs:code:bw_lan_ct_192kbit.png|{{scytheman:zeugs:code:bw_lan_ct_192kbit_thb.png}}]] | [[http://choerbaert.org/wiki/_media/scytheman:zeugs:code:bw_lan_ct_192kbit.png|{{scytheman:zeugs:code:bw_lan_ct_192kbit_thb.png}}]] | ||
| + | |||
| 2Mbit: | 2Mbit: | ||
| + | [[http://choerbaert.org/wiki/_media/scytheman:zeugs:code:bw_lan_2mbit.png|{{scytheman:zeugs:code:bw_lan_2mbit_thb.png}}]] | ||
| + | 2Mbit (cross-traffic): | ||
| [[http://choerbaert.org/wiki/_media/scytheman:zeugs:code:bw_lan_ct_2mbit.png|{{scytheman:zeugs:code:bw_lan_ct_2mbit_thb.png}}]] | [[http://choerbaert.org/wiki/_media/scytheman:zeugs:code:bw_lan_ct_2mbit.png|{{scytheman:zeugs:code:bw_lan_ct_2mbit_thb.png}}]] | ||
| + | |||
| 100Mbit: | 100Mbit: | ||
| [[http://choerbaert.org/wiki/_media/scytheman:zeugs:code:bw_lan_100mbit.png|{{scytheman:zeugs:code:bw_lan_100mbit_thb.png}}]] | [[http://choerbaert.org/wiki/_media/scytheman:zeugs:code:bw_lan_100mbit.png|{{scytheman:zeugs:code:bw_lan_100mbit_thb.png}}]] | ||