cloudFPGA (cF) API  1.0
The documentation of the source code of cloudFPGA (cF)
tc_UdpEcho.py
Go to the documentation of this file.
1 #/*
2 # * Copyright 2016 -- 2021 IBM Corporation
3 # *
4 # * Licensed under the Apache License, Version 2.0 (the "License");
5 # * you may not use this file except in compliance with the License.
6 # * You may obtain a copy of the License at
7 # *
8 # * http://www.apache.org/licenses/LICENSE-2.0
9 # *
10 # * Unless required by applicable law or agreed to in writing, software
11 # * distributed under the License is distributed on an "AS IS" BASIS,
12 # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # * See the License for the specific language governing permissions and
14 # * limitations under the License.
15 # */
16 
17 # *****************************************************************************
18 # * @file : tc_UdpEcho.py
19 # * @brief : A multi-threaded script to send and receive traffic on the
20 # * UDP connection of an FPGA module.
21 # *
22 # * System: : cloudFPGA
23 # * Component : cFp_BringUp/ROLE
24 # * Language : Python 3
25 # *
26 # *****************************************************************************
27 
28 # ### REQUIRED PYTHON PACKAGES ################################################
29 import argparse
30 import datetime
31 import errno
32 import socket
33 import threading
34 import time
35 
36 # ### REQUIRED TESTCASE MODULES ###############################################
37 from tc_utils import *
38 
39 
40 def udp_tx(sock, message, count, lock, verbose=False):
41  """UDP Tx Thread.
42  :param sock The socket to send to.
43  :param message The message string to sent.
44  :param count The number of datagrams to send.
45  :param lock A semaphore to access the global variable 'gBytesInFlight'.
46  :param verbose Enables verbosity.
47  :return None"""
48  global gBytesInFlight
49 
50  if verbose:
51  print("The following message of %d bytes will be sent out %d times:\n Message=%s\n" %
52  (len(message), count, message.decode()))
53  loop = 0
54  startTime = datetime.datetime.now()
55  while loop < count:
56  if gBytesInFlight < 4096:
57  # FYI - UDP does not provide any flow-control.
58  # If we push bytes into the FPGA faster than we drain, some bytes might be dropped.
59  try:
60  sock.sendall(message)
61  except socket.error as exc:
62  # Any exception
63  print("[EXCEPTION] Socket error while receiving :: %s" % exc)
64  exit(1)
65  else:
66  lock.acquire()
67  gBytesInFlight += len(message)
68  # print("TX - %d" % gBytesInFlight)
69  lock.release()
70  loop += 1
71  endTime = datetime.datetime.now()
72  elapseTime = endTime - startTime;
73  bandwidth = len(message) * 8 * count * 1.0 / (elapseTime.total_seconds() * 1024 * 1024)
74  print("[INFO] Sent a total of %d bytes." % (count*len(message)))
75  print("##################################################")
76  print("#### UDP TX DONE with bandwidth = %6.1f Mb/s ####" % bandwidth)
77  print("##################################################")
78  print()
79 
80 
81 def udp_rx(sock, message, count, lock, verbose=False):
82  """UDP Rx Thread.
83  :param sock The socket to receive from.
84  :param message The expected string message to be received.
85  :param count The number of datagrams to receive.
86  :param lock A semaphore to access the global variable 'gBytesInFlight'.
87  :param verbose, Enables verbosity.
88  :return None"""
89  global gBytesInFlight
90 
91  loop = 0
92  rxBytes = 0
93  nrErr = 0
94  startTime = datetime.datetime.now()
95  while rxBytes < count*len(message):
96  try:
97  data = sock.recv(len(message))
98  except IOError as e:
99  # On non blocking connections - when there are no incoming data, error is going to be raised
100  # Some operating systems will indicate that using AGAIN, and some using WOULDBLOCK error code
101  # We are going to check for both - if one of them - that's expected, means no incoming data,
102  # continue as normal. If we got different error code - something happened
103  if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK:
104  print('[ERROR] Socket reading error: {}'.format(str(e)))
105  exit(1)
106  # We just did not receive anything
107  continue
108  except socket.error as exc:
109  # Any other exception
110  print("[EXCEPTION] Socket error while receiving :: %s" % exc)
111  exit(1)
112  else:
113  lock.acquire()
114  gBytesInFlight -= len(data)
115  # print("RX - %d" % gBytesInFlight)
116  lock.release()
117  rxBytes += len(data)
118  if data == message:
119  if verbose:
120  print("Loop=%d | RxBytes=%d" % (loop, rxBytes))
121  else:
122  print("Loop=%d | RxBytes=%d" % (loop, rxBytes))
123  print(" KO | Received Message=%s" % data.decode())
124  print(" | Expecting Message=%s" % message)
125  nrErr += 1
126  loop += 1
127  endTime = datetime.datetime.now()
128  elapseTime = endTime - startTime;
129  bandwidth = len(message) * 8 * count * 1.0 / (elapseTime.total_seconds() * 1024 * 1024)
130  print("[INFO] Received a total of %d bytes." % rxBytes)
131  print("##################################################")
132  print("#### UDP RX DONE with bandwidth = %6.1f Mb/s ####" % bandwidth)
133  print("##################################################")
134  print()
135 
136 
137 def udp_txrx_loop(sock, message, count, verbose=False):
138  """UDP Tx-Rx Single-Thread Loop.
139  :param sock The socket to send/receive to/from.
140  :param message The message string to sent.
141  :param count The number of datagrams to send.
142  :param verbose Enables verbosity.
143  :return None"""
144  if verbose:
145  print("[INFO] The following message of %d bytes will be sent out %d times:\n Message=%s\n" %
146  (len(message), count, message.decode()))
147  nrErr = 0
148  loop = 0
149  rxByteCnt = 0
150  startTime = datetime.datetime.now()
151  while loop < count:
152  # Send datagram
153  # -------------------
154  try:
155  sock.sendall(message)
156  finally:
157  pass
158  # Receive datagram
159  # -------------------
160  try:
161  data = sock.recv(len(message))
162  rxByteCnt += len(data)
163  if data == message:
164  if verbose:
165  print("Loop=%d | RxBytes=%d" % (loop, rxByteCnt))
166  else:
167  print("Loop=%d | RxBytes=%d" % (loop, rxByteCnt))
168  print(" KO | Received Message=%s" % data.decode())
169  print(" | Expecting Message=%s" % message)
170  nrErr += 1
171  except IOError as e:
172  # On non blocking connections - when there are no incoming data, error is going to be raised
173  # Some operating systems will indicate that using AGAIN, and some using WOULDBLOCK error code
174  # We are going to check for both - if one of them - that's expected, means no incoming data,
175  # continue as normal. If we got different error code - something happened
176  if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK:
177  print('[ERROR] Socket reading error: {}'.format(str(e)))
178  exit(1)
179  # We just did not receive anything
180  continue
181  except socket.error as exc:
182  # Any other exception
183  print("[EXCEPTION] Socket error while receiving :: %s" % exc)
184  # exit(1)
185  finally:
186  pass
187  loop += 1
188  endTime = datetime.datetime.now()
189  elapseTime = endTime - startTime
190  bandwidth = len(message) * 8 * count * 1.0 / (elapseTime.total_seconds() * 1024 * 1024)
191  print("[INFO] Transferred a total of %d bytes." % rxByteCnt)
192  print("#####################################################")
193  print("#### UDP Tx/Rx DONE with bandwidth = %6.1f Mb/s ####" % bandwidth)
194  print("#####################################################")
195  print()
196 
197 
198 def udp_txrx_ramp(sock, message, count, verbose=False):
199  """UDP Tx-Rx Single-Thread Ramp.
200  :param sock The socket to send/receive to/from.
201  :param message The message string to sent.
202  :param count The number of datagrams to send.
203  :param verbose Enables verbosity.
204  :return None"""
205  if verbose:
206  print("[INFO] The following message of %d bytes will be sent out incrementally %d times:\n Message=%s\n" %
207  (len(message), count, message.decode()))
208  nrErr = 0
209  loop = 0
210  rxByteCnt = 0
211  startTime = datetime.datetime.now()
212  while loop < count:
213  i = 1
214  while i <= len(message):
215  subMsg = message[0:i]
216 
217  # Send datagram
218  # -------------------
219  try:
220  sock.sendall(subMsg)
221  finally:
222  pass
223  # Receive datagram
224  # -------------------
225  try:
226  data = sock.recv(len(subMsg))
227  rxByteCnt += len(data)
228  if data == subMsg:
229  if verbose:
230  print("Loop=%d | RxBytes=%d" % (loop, len(data)))
231  else:
232  print("Loop=%d | RxBytes=%d" % (loop, len(data)))
233  print(" KO | Received Message=%s" % data.decode())
234  print(" | Expecting Message=%s" % subMsg)
235  nrErr += 1
236  except IOError as e:
237  # On non blocking connections - when there are no incoming data, error is going to be raised
238  # Some operating systems will indicate that using AGAIN, and some using WOULDBLOCK error code
239  # We are going to check for both - if one of them - that's expected, means no incoming data,
240  # continue as normal. If we got different error code - something happened
241  if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK:
242  print('[ERROR] Socket reading error: {}'.format(str(e)))
243  exit(1)
244  # We just did not receive anything
245  continue
246  except socket.error as exc:
247  # Any other exception
248  print("[EXCEPTION] Socket error while receiving :: %s" % exc)
249  # exit(1)
250  finally:
251  pass
252  i += 1
253  loop += 1
254  endTime = datetime.datetime.now()
255  elapseTime = endTime - startTime;
256  bandwidth = (rxByteCnt * 8 * count * 1.0) / (elapseTime.total_seconds() * 1024 * 1024)
257  megaBytes = (rxByteCnt * 1.0) / (1024 * 1024 * 1.0)
258  print("[INFO] Transferred a total of %.1f MB." % megaBytes)
259  print("#####################################################")
260  print("#### UDP Tx/Rx DONE with bandwidth = %6.1f Mb/s ####" % bandwidth)
261  print("#####################################################")
262  print()
263 
264 
265 
270 
271 # STEP-1: Parse the command line strings into Python objects
272 # -----------------------------------------------------------------------------
273 parser = argparse.ArgumentParser(description='A script to send/receive UDP data to/from an FPGA module.')
274 parser.add_argument('-fi', '--fpga_ipv4', type=str, default='',
275  help='The IPv4 address of the FPGA (a.k.a image_ip / e.g. 10.12.200.163)')
276 parser.add_argument('-fp', '--fpga_port', type=int, default=8803,
277  help='The UDP port of the FPGA (default is 8803)')
278 parser.add_argument('-ii', '--inst_id', type=int, default=0,
279  help='The instance ID assigned by the cloudFPGA Resource Manager (e.g. 42)')
280 parser.add_argument('-lc', '--loop_count', type=int, default=10,
281  help='The number of test runs (default is 10)')
282 parser.add_argument('-mi', '--mngr_ipv4', type=str, default='10.12.0.132',
283  help='The IPv4 address of the cloudFPGA Resource Manager (default is 10.12.0.132)')
284 parser.add_argument('-mp', '--mngr_port', type=int, default=8080,
285  help='The TCP port of the cloudFPGA Resource Manager (default is 8080)')
286 parser.add_argument('-mt', '--multi_threading', action="store_true",
287  help='Enable multi_threading')
288 parser.add_argument('-nr', '--no_restart', action="store_true",
289  help='Do not restart the FPGA for this run')
290 parser.add_argument('-sd', '--seed', type=int, default=-1,
291  help='The initial number to seed the pseudo-random number generator.')
292 parser.add_argument('-sz', '--size', type=int, default=-1,
293  help='The size of the datagram to generate.')
294 parser.add_argument('-un', '--user_name', type=str, default='',
295  help='A user-name as used to log in ZYC2 (.e.g \'fab\')')
296 parser.add_argument('-up', '--user_passwd', type=str, default='',
297  help='The ZYC2 password attached to the user-name')
298 parser.add_argument('-v', '--verbose', action="store_true",
299  help='Enable verbosity')
300 
301 args = parser.parse_args()
302 
303 if args.user_name == '' or args.user_passwd == '':
304  print("\nWARNING: You must provide a ZYC2 user name and the corresponding password for this script to execute.\n")
305  exit(1)
306 
307 # STEP-2a: Retrieve the IP address of the FPGA module (this will be the SERVER)
308 # -----------------------------------------------------------------------------
309 ipFpga = getFpgaIpv4(args)
310 
311 # STEP-2b: Retrieve the instance Id assigned by the cloudFPGA Resource Manager
312 # -----------------------------------------------------------------------------
313 instId = getInstanceId(args)
314 
315 # STEP-2c: Retrieve the IP address of the cF Resource Manager
316 # -----------------------------------------------------------------------------
317 ipResMngr = getResourceManagerIpv4(args)
318 
319 # STEP-3a: Retrieve the UDP listen port of the FPGA server
320 # -----------------------------------------------------------------------------
321 portFpga = getFpgaPort(args)
322 
323 # STEP-3b: Retrieve the TCP port of the cloudFPGA Resource Manager
324 # -----------------------------------------------------------------------------
325 portResMngr = getResourceManagerPort(args)
326 
327 # STEP-?: Configure the application registers
328 # -----------------------------------------------------------------------------
329 # TODO print("\nNow: Configuring the application registers.")
330 # TODO udpEchoPathThruMode = (0x0 << 0) # See DIAG_CTRL_2 register
331 
332 # STEP-4: Trigger the FPGA role to restart (i.e. perform SW reset of the role)
333 # -----------------------------------------------------------------------------
334 if not args.no_restart:
335  # restartApp(instId, ipResMngr, portResMngr, args.user_name, args.user_passwd)
336  print("[INFO] *******************************\n")
337 else:
338  print("[INFO] This run is executed without restarting the application.\n")
339 
340 # STEP-5: Ping the FPGA
341 # -----------------------------------------------------------------------------
342 pingFpga(ipFpga)
343 
344 # STEP-6a: Set the FPGA socket association
345 # -----------------------------------------------------------------------------
346 fpgaAssociation = (str(ipFpga), portFpga)
347 
348 # STEP-6b: Set the HOST socket association (optional)
349 # Info: Linux selects a source port from an ephemeral port range, which by
350 # default is a set to range from 32768 to 61000. You can check it
351 # with the command:
352 # > cat /proc/sys/net/ipv4/ip_local_port_range
353 # If we want to force the source port ourselves, we must use the
354 # "bind before connect" trick.
355 # -----------------------------------------------------------------------------
356 if 0:
357  udpSP = portFpga + 49152 # 8803 + 0xC000
358  hostAssociation = (ipSaStr, udpSP)
359 
360 # STEP-8a: Create a UDP/IP socket
361 # -----------------------------------------------------------------------------
362 try:
363  udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
364 except Exception as exc:
365  print("[EXCEPTION] %s" % exc)
366  exit(1)
367 
368 
369 # [FIXME - Disable the IP fragmentation via setsockopt()]
370 # See for also:
371 # https://stackoverflow.com/questions/973439/how-to-set-the-dont-fragment-df-flag-on-a-socket
372 # https://stackoverflow.com/questions/26440761/why-isnt-dont-fragment-flag-getting-disabled
373 
374 
375 # STEP-8b: Bind before connect (optional).
376 # This trick enables us to ask the kernel to select a specific source IP and
377 # source PORT by calling bind() before calling connect().
378 # -----------------------------------------------------------------------------
379 if 0:
380  try:
381  udpSock.bind(hostAssociation)
382  print('Binding the socket address of the HOST to {%s, %d}' % hostAssociation)
383  except Exception as exc:
384  print("[EXCEPTION] %s" % exc)
385  exit(1)
386 
387 # STEP-9a: Connect to the remote FPGA
388 # Info: Although UDP is connectionless, 'connect()' might still be called. This enables
389 # the OS kernel to set the default destination address for the send, which makes it
390 # faster to send a message.
391 # -----------------------------------------------------------------------------
392 try:
393  udpSock.connect(fpgaAssociation)
394 except Exception as exc:
395  print("[EXCEPTION] %s" % exc)
396  exit(1)
397 else:
398  print('\nSuccessful connection with socket address of FPGA at {%s, %d} \n' % fpgaAssociation)
399 
400 # STEP-9b: Set the socket non-blocking
401 # --------------------------------------
402 udpSock.setblocking(False)
403 udpSock.settimeout(5)
404 
405 # STEP-10: Setup the test
406 # -------------------------------
407 print("[INFO] Testcase `%s` is run with:" % (os.path.basename(__file__)))
408 seed = args.seed
409 if seed == -1:
410  seed = random.randint(0, 100000)
411 random.seed(seed)
412 print("\t\t seed = %d" % seed)
413 
414 size = args.size
415 if size == -1:
416  size = random.randint(1, UDP_MDS)
417 elif size > UDP_MDS:
418  print('\nERROR: ')
419  print("[ERROR] The UDP stack does not support the reception of datagrams larger than %d bytes.\n" % UDP_MDS)
420  exit(1)
421 print("\t\t size = %d" % size)
422 
423 count = args.loop_count
424 print("\t\t loop = %d" % count)
425 
426 if seed % 1:
427  message = str_static_gen(size)
428 else:
429  message = str_rand_gen(size)
430 
431 verbose = args.verbose
432 
433 print("[INFO] This testcase is sending traffic from HOST-to-FPGA and back from FPGA-to-HOST.")
434 if args.multi_threading:
435  print("[INFO] This run is executed in multi-threading mode.\n")
436  # Global variable
437  gBytesInFlight = 0
438  # Creating a lock
439  lock = threading.Lock()
440  # STEP-11: Create Rx and Tx threads
441  # -----------------------------------
442  tx_thread = threading.Thread(target=udp_tx, args=(udpSock, message, count, lock, args.verbose))
443  rx_thread = threading.Thread(target=udp_rx, args=(udpSock, message, count, lock, args.verbose))
444  # STEP-12: Start the threads
445  # ---------------------------
446  tx_thread.start()
447  rx_thread.start()
448  # STEP-13: Wait for threads to terminate
449  # ----------------------------------------
450  tx_thread.join()
451  rx_thread.join()
452 else:
453  print("[INFO] This run is executed in single-threading mode.\n")
454  if seed == 0:
455  udp_txrx_ramp(udpSock, message, count, args.verbose)
456  else:
457  udp_txrx_loop(udpSock, message, count, args.verbose)
458 
459 # STEP-14: Close socket
460 # -----------------------
461 time.sleep(2)
462 udpSock.close()
463 
464 
def udp_rx(sock, message, count, lock, verbose=False)
Definition: tc_UdpEcho.py:81
def udp_txrx_ramp(sock, message, count, verbose=False)
Definition: tc_UdpEcho.py:198
def udp_tx(sock, message, count, lock, verbose=False)
Definition: tc_UdpEcho.py:40
def udp_txrx_loop(sock, message, count, verbose=False)
Definition: tc_UdpEcho.py:137
def getFpgaIpv4(args)
Definition: tc_utils.py:95
def getInstanceId(args)
Definition: tc_utils.py:153
def getResourceManagerIpv4(args)
Definition: tc_utils.py:124
def str_rand_gen(size)
Definition: tc_utils.py:89
def getFpgaPort(args)
Definition: tc_utils.py:113
def str_static_gen(size)
Definition: tc_utils.py:79
def pingFpga(ipFpga)
Definition: tc_utils.py:199
def getResourceManagerPort(args)
Definition: tc_utils.py:142