UDP Test Tool

Addition of connection testing tool using UDP transmission to measure the rate
at which the link can be used.

Change-Id: Ia859b1c1a944c4abd0658ea3912a1d3389a46ed6
diff --git a/udp_test/options.py b/udp_test/options.py
new file mode 120000
index 0000000..3508154
--- /dev/null
+++ b/udp_test/options.py
@@ -0,0 +1 @@
+../options.py
\ No newline at end of file
diff --git a/udp_test/udp_test_client.py b/udp_test/udp_test_client.py
new file mode 100755
index 0000000..789fc68
--- /dev/null
+++ b/udp_test/udp_test_client.py
@@ -0,0 +1,109 @@
+#!/usr/bin/python
+"""UDP test client.
+
+This implements the client side of the UDP connection testing tool to evaluate
+the effective speed of a link using bursts of UDP traffic.
+"""
+import select
+import socket
+import struct
+import sys
+import time
+
+import options
+
+optspec = """
+udp_test_client.py [options...] host_ip
+--
+i,interval=     Specify the cycle interval in seconds [2.0]
+t,time=         Specify the test length in seconds; zero means unlimited. [0]
+"""
+
+try:
+  import monotime
+except ImportError:
+  pass
+try:
+  monotime = time.monotime
+except AttributeError:
+  monotime = time.time
+
+
+def parse_address(raw_address):
+  port = 5001
+
+  raw_host_addr = raw_address.split(':')
+  if len(raw_host_addr) > 1:
+    port = int(raw_host_addr[1])
+
+  return (raw_host_addr[0], port)
+
+
+def main():
+  o = options.Options(optspec)
+  opt, _, extra = o.parse(sys.argv[1:])
+
+  interval_length = float(opt.interval)
+
+  host_addr = parse_address(extra[0])
+  sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+
+  packet_counter = 0
+  packets_sent = 0
+  packets_recv = 0
+  respond_time = monotime()
+  start_time = monotime()
+  server_count = 0
+  last_heard = monotime()
+
+  print 'Host Address: %s' % host_addr[0]
+
+  while True:
+    wait_time = interval_length - (monotime() - respond_time)
+    if wait_time < 0:
+      wait_time = 0
+    sock_ready, _, _ = select.select([sock], [], [], wait_time)
+    if sock_ready:
+      data = sock.recv(65536)
+      magic, pack_type = struct.unpack('!4s4s', data[0:8])
+
+      if magic != 'TPDO':
+        continue
+
+      if pack_type == 'RNNG':
+        packet_counter += 1
+        _, _, server_count = struct.unpack('!4s4si', data[0:12])
+        last_heard = monotime()
+    if monotime() - respond_time > interval_length:
+      temp_time = monotime()
+      counter_msg = struct.pack('!4s4siid', 'TPDO', 'UPDT', server_count,
+                                packet_counter, temp_time)
+      sock.sendto(counter_msg, host_addr)
+
+      if packet_counter > 0:
+        cycle_time = temp_time - respond_time
+        send_rate = server_count - packets_sent
+        recv_rate = packet_counter - packets_recv
+        if send_rate == 0:
+          success_rate = 0
+        else:
+          success_rate = float(recv_rate) / send_rate
+
+        print ('Send Rate: %.2fMbps, Receive Rate: %.2fMbps, '
+               'Success Rate: %.2f%%' %
+               (send_rate * len(data) * 8 / 1000000 / cycle_time,
+                recv_rate * len(data) * 8 / 1000000 / cycle_time,
+                success_rate * 100))
+
+        packets_sent = server_count
+        packets_recv = packet_counter
+      respond_time = monotime()
+    if monotime() - start_time > float(opt.time) and opt.time:
+      break
+
+    if monotime() - last_heard > interval_length * 4:
+      sys.stderr.write('Failed, Connection Timed out')
+      sys.exit(1)
+
+if __name__ == '__main__':
+  main()
diff --git a/udp_test/udp_test_host.py b/udp_test/udp_test_host.py
new file mode 100755
index 0000000..b89e864
--- /dev/null
+++ b/udp_test/udp_test_host.py
@@ -0,0 +1,140 @@
+#!/usr/bin/python
+"""UDP test host.
+
+This is the host-side implementation of a UDP test tool to evaluate the speed
+of a connection using bursts of UDP traffic
+"""
+import select
+import socket
+import struct
+import sys
+import time
+
+import options
+
+try:
+  import monotime
+except ImportError:
+  pass
+try:
+  monotime = time.monotime
+except AttributeError:
+  monotime = time.time
+
+optspec = """
+udp_test_host.py [options...]
+--
+p,port=         Specify a port to use. [5001]
+s,size=         Specify the packet size [1472]
+"""
+
+
+def main():
+  o = options.Options(optspec)
+  opt, _, _ = o.parse(sys.argv[1:])
+
+  port = int(opt.port)
+  send_rate = 1000.0  # Hz
+  current_index = 0
+  timeout_val = 10
+
+  sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+  sock.bind(('', port))
+
+  client_addr = ('', 0)
+  last_client_time = 0
+  last_packets_sent = 0
+  last_packets_recv = 0
+  remainder_time = 0
+
+  cycle_counter = 0
+  cycle_time = monotime()
+  msg_length = int(opt.size)
+  counter_msg = bytearray('7' * msg_length)
+  while True:
+    # Account for processing time
+    sock_status, _, _ = select.select([sock], [], [], remainder_time)
+    if sock_status:
+      host_time = monotime()
+      data, addr = sock.recvfrom(65536)
+      magic, pack_type, server_count, client_count, new_time = struct.unpack(
+          '!4s4siid', data)
+      if magic != 'TPDO' or pack_type != 'UPDT':
+        print 'Invalid Packet'
+        continue
+      if addr != client_addr:
+        current_index = 0
+        send_rate = 1000.0  # Hz
+
+        client_addr = addr
+        cycle_time = monotime()
+        cycle_counter = 0
+        # Keeps track of last time client was heard from
+        last_packets_sent = last_packets_recv = 0
+        last_client_time = new_time
+        host_time = monotime()
+        print 'connection established with %s\n' % client_addr[0]
+      else:
+        # Calculate metrics for user
+        cycle_length = new_time - last_client_time
+        measured_pack_sent = (server_count - last_packets_sent) / cycle_length
+        measured_send_rate = measured_pack_sent * msg_length * 8 / 1000000
+        measured_recv_rate = ((client_count - last_packets_recv) /
+                              cycle_length * msg_length * 8 / 1000000)
+        if server_count <= last_packets_sent:
+          success_rate = 0
+        else:
+          success_rate = (float(client_count - last_packets_recv) /
+                          (server_count - last_packets_sent))
+
+        print ('\nAttempted Rate: %.2fMbps, Sending Rate: %.2fMbps, '
+               'Receive Rate: %.2fMbps, Success Rate: %.2f%%'
+               % (send_rate * msg_length * 8 / 1000000,
+                  measured_send_rate,
+                  measured_recv_rate,
+                  success_rate * 100))
+
+        cycle_time = monotime()
+        cycle_counter = 0
+        if float(success_rate) > 0.9:
+          send_rate = 1.1 * measured_pack_sent
+        elif float(success_rate) < 0.8:
+          send_rate = 0.9 * measured_pack_sent
+        else:
+          send_rate = measured_pack_sent
+
+        # Prevent the host from not sending any packets.
+        if send_rate <= 0:
+          send_rate = 10
+        last_packets_recv = client_count
+        last_packets_sent = server_count
+        last_client_time = new_time
+
+    elif client_addr != ('', 0):
+      # No packets from client received
+      burst_counter = 0
+      while True:
+        remainder_time = (cycle_counter + 1) / send_rate - (monotime() -
+                                                            cycle_time)
+        if remainder_time > 0:
+          break
+        cycle_counter += 1
+        current_index += 1
+        counter_msg[0:12] = struct.pack('!4s4si', 'TPDO', 'RNNG', current_index)
+        sock.sendto(counter_msg, client_addr)
+        burst_counter += 1
+        if not current_index % (int(send_rate/10) + 1):
+          sys.stdout.write('.')
+          sys.stdout.flush()
+        if monotime() - host_time > timeout_val:
+          client_addr = ('', 0)
+          sys.stderr.write('\nconnection lost with client\n')
+          remainder_time = 0
+          break
+        if burst_counter > 1000:
+          remainder_time = 0
+          break
+
+if __name__ == '__main__':
+  main()
+