Skip to content

Commit

Permalink
Merge pull request #59 from samgomena/master
Browse files Browse the repository at this point in the history
Calculate Packet Loss Statistics
  • Loading branch information
alessandromaggio authored Nov 12, 2020
2 parents cf20b66 + b46bcef commit b8193fb
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 2 deletions.
9 changes: 9 additions & 0 deletions pythonping/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ def __init__(self, initial_set=[], verbose=False, output=sys.stdout):
self.rtt_avg = 0
self.rtt_min = 0
self.rtt_max = 0
self.packets_lost = 0
for response in initial_set:
self.append(response)

Expand All @@ -169,6 +170,10 @@ def success(self, option=SuccessOn.One):
result = False not in success_list
return result

@property
def packet_loss(self):
return self.packets_lost

@property
def rtt_min_ms(self):
return represent_seconds_in_ms(self.rtt_min)
Expand Down Expand Up @@ -197,6 +202,9 @@ def append(self, value):
self.rtt_max = value.time_elapsed
if value.time_elapsed < self.rtt_min:
self.rtt_min = value.time_elapsed

self.packets_lost = self.packets_lost + (0 if value.success else 1 - self.packets_lost) / len(self)

if self.verbose:
print(value, file=self.output)

Expand Down Expand Up @@ -241,6 +249,7 @@ def __init__(self, target, payload_provider, timeout, socket_options=(), seed_id
self.timeout = timeout
self.responses = ResponseList(verbose=verbose, output=output)
self.seed_id = seed_id
# note that to make Communicator instances thread safe, the seed ID must be unique per thread
if self.seed_id is None:
self.seed_id = os.getpid() & 0xFFFF

Expand Down
19 changes: 18 additions & 1 deletion pythonping/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

class Socket:
DONT_FRAGMENT = (socket.SOL_IP, 10, 1) # Option value for raw socket
PROTO_LOOKUP = {"icmp": socket.IPPROTO_ICMP, "tcp": socket.IPPROTO_TCP, "udp": socket.IPPROTO_UDP,
"ip": socket.IPPROTO_IP, "raw": socket.IPPROTO_RAW}

def __init__(self, destination, protocol, source=None, options=(), buffer_size=2048):
"""Creates a network socket to exchange messages
Expand All @@ -23,14 +25,29 @@ def __init__(self, destination, protocol, source=None, options=(), buffer_size=2
self.destination = socket.gethostbyname(destination)
except socket.gaierror as e:
raise RuntimeError('Cannot resolve address "' + destination + '", try verify your DNS or host file')
self.protocol = socket.getprotobyname(protocol)

self.protocol = Socket.getprotobyname(protocol)
self.buffer_size = buffer_size
if source is not None:
raise NotImplementedError('PythonPing currently does not support specification of source IP')
self.socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, self.protocol)
if options:
self.socket.setsockopt(*options)

# Implementing a version of socket.getprotobyname for this library since built-in is not thread safe
# for python 3.5, 3.6, and 3.7:
# https://bugs.python.org/issue30482
# This bug was causing failures as it would occasionally return a 0 (incorrect) instead of a 1 (correct)
# for the 'icmp' string, causing a OSError for "Protocol not supported" in multi-threaded usage:
# https://github.com/alessandromaggio/pythonping/issues/40
@staticmethod
def getprotobyname(name):
try:
return Socket.PROTO_LOOKUP[name.lower()]
except KeyError:
raise KeyError("'" + str(name) + "' is not in the list of supported proto types: "
+ str(list(Socket.PROTO_LOOKUP.keys())))

def send(self, packet):
"""Sends a raw packet on the stream
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
long_description = file.read()

setup(name='pythonping',
version='1.0.13',
version='1.0.14',
description='A simple way to ping in Python',
url='https://github.com/alessandromaggio/pythonping',
author='Alessandro Maggio',
Expand Down
41 changes: 41 additions & 0 deletions test/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,47 @@ def test_success_half_success(self):
'Unable to calculate success on all with half responses successful'
)

def test_no_packets_lost(self):
rs = executor.ResponseList([
SuccessfulResponseMock(None, 1),
SuccessfulResponseMock(None, 1),
SuccessfulResponseMock(None, 1),
SuccessfulResponseMock(None, 1)
])

self.assertEqual(
rs.packet_loss,
0.0,
"Unable to calculate packet loss correctly when all resposes successful"
)

def test_all_packets_lost(self):
rs = executor.ResponseList([
FailingResponseMock(None, 1),
FailingResponseMock(None, 1),
FailingResponseMock(None, 1),
FailingResponseMock(None, 1)
])

self.assertEqual(
rs.packet_loss,
1.0,
"Unable to calculate packet loss correctly when all resposes failed"
)

def test_some_packets_lost(self):
rs = executor.ResponseList([
SuccessfulResponseMock(None, 1),
SuccessfulResponseMock(None, 1),
FailingResponseMock(None, 1),
FailingResponseMock(None, 1)
])

self.assertEqual(
rs.packet_loss,
0.5,
"Unable to calculate packet loss correctly when some of the responses failed"
)

class CommunicatorTestCase(unittest.TestCase):
"""Tests for Communicator"""
Expand Down

0 comments on commit b8193fb

Please sign in to comment.