Skip to content

Commit

Permalink
Add a script to test network latency with ds2
Browse files Browse the repository at this point in the history
I'm planning on using this later on to automate testing of network
performance.

This is a script written by Gregory Clayton <[email protected]>.
  • Loading branch information
sas committed Aug 20, 2018
1 parent 690aeeb commit 7937d7f
Showing 1 changed file with 261 additions and 0 deletions.
261 changes: 261 additions & 0 deletions Support/Scripts/test-protocol-latency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
#!/usr/bin/env python

import datetime
import socket
import sys

one_char_packets = ['+', '-', '\x03']

def checksum(message):
"""
Calculate the GDB server protocol checksum of the message.
The GDB server protocol uses a simple modulo 256 sum.
"""
check = 0
for c in message:
check += ord(c)
return check % 256


def frame_packet(message):
"""
Create a framed packet that's ready to send over the GDB connection
channel.
Framing includes surrounding the message between $ and #, and appending
a two character hex checksum.
"""
if message in one_char_packets:
return message
return "$%s#%02x" % (message, checksum(message))


def escape_binary(message):
"""
Escape the binary message using the process described in the GDB server
protocol documentation.
Most bytes are sent through as-is, but $, #, and { are escaped by writing
a { followed by the original byte mod 0x20.
"""
out = ""
for c in message:
d = ord(c)
if d in (0x23, 0x24, 0x7d):
out += chr(0x7d)
out += chr(d ^ 0x20)
else:
out += c
return out


def hex_encode_bytes(message):
"""
Encode the binary message by converting each byte into a two-character
hex string.
"""
out = ""
for c in message:
out += "%02x" % ord(c)
return out


def hex_decode_bytes(hex_bytes):
"""
Decode the hex string into a binary message by converting each
two-character hex string into a single output byte.
"""
out = ""
hex_len = len(hex_bytes)
i = 0
while i < hex_len - 1:
out += chr(int(hex_bytes[i:i + 2]), 16)
i += 2
return out


def response_is_ok(response):
return response == 'OK'


class client:
"""
A simple TCP-based GDB remote client.
"""

def __init__(self):
self._socket = None
self._receivedData = ""
self._receivedDataOffset = 0
self._shouldSendAck = True
self.trace = False

def connect_to_host(self, port, host='127.0.0.1'):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.settimeout(2)
self._socket = s
self.send_ack()

def stop(self):
self._socket.close()

def send_ack(self):
self.send_packet('+', get_response=False)

def send_nack(self):
self.send_packet('+', get_response=False)

def send_QStartNoAckMode(self):
response = self.send_packet('QStartNoAckMode')
if response_is_ok(response):
self._shouldSendAck = False

def send_qSpeedTest(self, send_size, recv_size):
payload = "qSpeedTest:response_size:%i;data:%s" % (recv_size,
's' * send_size)
response = self.send_packet(payload)
return response

def send_packet(self, packet, get_response=True):
wire_packet = frame_packet(packet)
if self.trace:
print("<%4u> send packet: %s" % (len(wire_packet), wire_packet))
self._socket.send(wire_packet)
if get_response:
if self._shouldSendAck:
ack = self.receive_response()
if ack != '+':
raise ValueError("didn't get ack")
return self.receive_response()
return None

def receive_response(self):
try:
while True:
packet = self._parsePacket()
if packet is not None:
if self.trace:
print("<%4u> read packet: %s" % (len(packet), packet))
if self._shouldSendAck and packet not in one_char_packets:
self.send_ack()
return packet
data = self._socket.recv(1024)
if data is None or len(data) == 0:
return None
# In Python 2, sockets return byte strings. In Python 3,
# sockets return bytes. If we got bytes (and not a byte
# string), decode them to a string for later handling.
if isinstance(data, bytes) and not isinstance(data, str):
data = data.decode()
self._receivedData += data
except self.InvalidPacketException:
type, value, traceback = sys.exc_info()
self._socket.close()
print('error: self.InvalidPacketException(%s), closing.' % (value))
print('self._receivedData = %s' % (self._receivedData))
print('self.self._receivedDataOffset = %u' % (
self._receivedDataOffset))
return None

def _parsePacket(self):
"""
Reads bytes from self._receivedData, returning:
- a packet's contents if a valid packet is found
- the PACKET_ACK unique object if we got an ack
- None if we only have a partial packet
Raises an InvalidPacketException if unexpected data is received
or if checksums fail.
Once a complete packet is found at the front of self._receivedData,
its data is removed form self._receivedData.
"""
data = self._receivedData
i = self._receivedDataOffset
data_len = len(data)
if data_len == 0:
return None
if i == 0:
# If we're looking at the start of the received data, that means
# we're looking for the start of a new packet, denoted by a $.
# It's also possible we'll see an ACK here, denoted by a +
if data[0] == '+':
self._receivedData = data[1:]
return '+'
if data[0] == '-':
self._receivedData = data[1:]
return '-'
if ord(data[0]) == 3:
self._receivedData = data[1:]
return '\x03'
if data[0] == '$':
i += 1
else:
raise self.InvalidPacketException(
"Unexpected leading byte: %s" % data[0])

# If we're looking beyond the start of the received data, then we're
# looking for the end of the packet content, denoted by a #.
# Note that we pick up searching from where we left off last time
while i < data_len and data[i] != '#':
i += 1

# If there isn't enough data left for a checksum, just remember where
# we left off so we can pick up there the next time around
if i > data_len - 3:
self._receivedDataOffset = i
return None

# If we have enough data remaining for the checksum, extract it and
# compare to the packet contents
packet = data[1:i]
i += 1
try:
check = int(data[i:i + 2], 16)
except ValueError:
raise self.InvalidPacketException("Checksum is not valid hex")
i += 2
# Only check the checksum if ACK mode is enabled
if self._shouldSendAck and check != checksum(packet):
raise self.InvalidPacketException(
"Checksum %02x does not match content %02x" %
(check, checksum(packet)))
# remove parsed bytes from _receivedData and reset offset so parsing
# can start on the next packet the next time around
self._receivedData = data[i:]
self._receivedDataOffset = 0
return packet

class InvalidPacketException(Exception):
pass


def main():
args = sys.argv[1:]
port = int(args[0])
gdbremote = client()
gdbremote.connect_to_host(port=port)
gdbremote.send_QStartNoAckMode()
num_packets = 1000
send_sizes = [0, 32, 512, 1024]
recv_sizes = [0, 32, 512, 1024]
for send_size in send_sizes:
for recv_size in recv_sizes:
total_us = 0
for _i in range(num_packets):
start = datetime.datetime.now()
gdbremote.send_qSpeedTest(send_size, recv_size)
end = datetime.datetime.now()
delta = end - start
us = delta.seconds * 1000000 + delta.microseconds
total_us += us
avg_us = float(total_us) / float(num_packets)
print('send=%5u, recv=%5u: avg=%8u us (%5.2f packets per second)' % (
send_size, recv_size, avg_us,
float(1000000) / float(avg_us)))


if __name__ == '__main__':
main()

0 comments on commit 7937d7f

Please sign in to comment.