From afc3931069b833fac2734e76583632b9d06d296c Mon Sep 17 00:00:00 2001 From: hbasria Date: Thu, 26 May 2016 13:31:27 +0300 Subject: [PATCH 1/2] latency (MIN/MAX/AVG), jitter, MOS calcs added --- README | 10 +++- ping.py | 181 ++++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 138 insertions(+), 53 deletions(-) mode change 100644 => 100755 ping.py diff --git a/README b/README index 1488af0..78662f7 100644 --- a/README +++ b/README @@ -1,2 +1,10 @@ Originally from http://svn.pylucid.net/pylucid/CodeSnippets/ping.py -This version maintained at http://github.com/samuel/python-ping \ No newline at end of file +This version maintained at http://github.com/hbasria/python-ping + +./ping.py 8.8.8.8 + +Statistics for 8.8.8.8: + - packet loss: 1 (10.00%) + - latency (MIN/MAX/AVG): 62/141/96 + - jitter: 8.2129 + - MOS: 4.3 diff --git a/ping.py b/ping.py old mode 100644 new mode 100755 index 73400fa..5950bd7 --- a/ping.py +++ b/ping.py @@ -67,19 +67,17 @@ $Rev: $ $Author: $ """ - - -import os, sys, socket, struct, select, time - -if sys.platform == "win32": - # On Windows, the best timer is time.clock() - default_timer = time.clock -else: - # On most other platforms the best timer is time.time() - default_timer = time.time +import collections +import getopt +import os +import select +import socket +import struct +import sys +import time # From /usr/include/linux/icmp.h; your milage may vary. -ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris. +ICMP_ECHO_REQUEST = 8 # Seems to be the same on Solaris. def checksum(source_string): @@ -88,19 +86,19 @@ def checksum(source_string): to suggest that it gives the same answers as in_cksum in ping.c """ sum = 0 - countTo = (len(source_string)/2)*2 + countTo = (len(source_string) / 2) * 2 count = 0 - while count> 16) + (sum & 0xffff) + sum = (sum >> 16) + (sum & 0xffff) sum = sum + (sum >> 16) answer = ~sum answer = answer & 0xffff @@ -117,22 +115,19 @@ def receive_one_ping(my_socket, ID, timeout): """ timeLeft = timeout while True: - startedSelect = default_timer() + startedSelect = time.time() whatReady = select.select([my_socket], [], [], timeLeft) - howLongInSelect = (default_timer() - startedSelect) - if whatReady[0] == []: # Timeout + howLongInSelect = (time.time() - startedSelect) + if whatReady[0] == []: # Timeout return - timeReceived = default_timer() + timeReceived = time.time() recPacket, addr = my_socket.recvfrom(1024) icmpHeader = recPacket[20:28] type, code, checksum, packetID, sequence = struct.unpack( "bbHHh", icmpHeader ) - # Filters out the echo request itself. - # This can be tested by pinging 127.0.0.1 - # You'll see your own request - if type != 8 and packetID == ID: + if packetID == ID: bytesInDouble = struct.calcsize("d") timeSent = struct.unpack("d", recPacket[28:28 + bytesInDouble])[0] return timeReceived - timeSent @@ -146,7 +141,7 @@ def send_one_ping(my_socket, dest_addr, ID): """ Send one ping to the given >dest_addr<. """ - dest_addr = socket.gethostbyname(dest_addr) + dest_addr = socket.gethostbyname(dest_addr) # Header is type (8), code (8), checksum (16), id (16), sequence (16) my_checksum = 0 @@ -155,7 +150,7 @@ def send_one_ping(my_socket, dest_addr, ID): header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, ID, 1) bytesInDouble = struct.calcsize("d") data = (192 - bytesInDouble) * "Q" - data = struct.pack("d", default_timer()) + data + data = struct.pack("d", time.time()) + data # Calculate the checksum on the data and the dummy header. my_checksum = checksum(header + data) @@ -166,7 +161,7 @@ def send_one_ping(my_socket, dest_addr, ID): "bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), ID, 1 ) packet = header + data - my_socket.sendto(packet, (dest_addr, 1)) # Don't know about the 1 + my_socket.sendto(packet, (dest_addr, 1)) # Don't know about the 1 def do_one(dest_addr, timeout): @@ -175,7 +170,7 @@ def do_one(dest_addr, timeout): """ icmp = socket.getprotobyname("icmp") try: - my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp) + my_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_ICMP) except socket.error, (errno, msg): if errno == 1: # Operation not permitted @@ -184,7 +179,7 @@ def do_one(dest_addr, timeout): " running as root." ) raise socket.error(msg) - raise # raise the original error + raise # raise the original error my_ID = os.getpid() & 0xFFFF @@ -195,29 +190,111 @@ def do_one(dest_addr, timeout): return delay -def verbose_ping(dest_addr, timeout = 2, count = 4): - """ - Send >count< ping to >dest_addr< with the given >timeout< and display - the result. - """ - for i in xrange(count): - print "ping %s..." % dest_addr, - try: - delay = do_one(dest_addr, timeout) - except socket.gaierror, e: - print "failed. (socket error: '%s')" % e[1] - break +def ping(dest, count=10, timeout=2): + lost = 0 # Number of loss packets + mos = 0 # Mean Opinion Score + latency = [] # Delay values [MIN. MAX, AVG] + jitter = [] # Jitter values [MAX, AVG] + time_sent = [] # Timestamp when packet is sent + time_recv = [] # Timestamp when packet is received + PingResult = collections.namedtuple('PingResult', 'lost lost_perc min max avg jitter mos') - if delay == None: - print "failed. (timeout within %ssec.)" % timeout + if count <= 0: + raise Exception("count must be greater than zero.") + + if timeout <= 0: + Exception("timeout must be greater than zero.") + + for i in range(0, count): + try: + time_sent.append(int(round(time.time() * 1000))) + d = do_one(dest, timeout) + if d == None: + lost = lost + 1 + time_recv.append(None) + continue + else: + time_recv.append(int(round(time.time() * 1000))) + except: + raise Exception("Socket error") + + # Calculate Latency: + latency.append(time_recv[i] - time_sent[i]) + + # Calculate Jitter with the previous packet + # http://toncar.cz/Tutorials/VoIP/VoIP_Basics_Jitter.html + if len(jitter) == 0: + # First packet received, Jitter = 0 + jitter.append(0) else: - delay = delay * 1000 - print "get ping in %0.4fms" % delay - print + # Find previous received packet: + for h in reversed(range(0, i)): + if time_recv[h] != None: + break + # Calculate difference of relative transit times: + drtt = (time_recv[i] - time_recv[h]) - (time_sent[i] - time_sent[h]) + jitter.append(jitter[len(jitter) - 1] + (abs(drtt) - jitter[len(jitter) - 1]) / float(16)) + + # Calculating MOS + if len(latency) > 0: + EffectiveLatency = sum(latency) / len(latency) + max(jitter) * 2 + 10 + if EffectiveLatency < 160: + R = 93.2 - (EffectiveLatency / 40) + else: + R = 93.2 - (EffectiveLatency - 120) / 10 + # Now, let's deduct 2.5 R values per percentage of packet loss + R = R - (lost * 2.5) + # Convert the R into an MOS value.(this is a known formula) + mos = 1 + (0.035) * R + (.000007) * R * (R - 60) * (100 - R) + + # Setting values (timeout, lost and mos are already calculated) + lost_perc = lost / float(count) * 100 + if len(latency) > 0: + min_latency = min(latency) + max_latency = max(latency) + avg_latency = sum(latency) / len(latency) + else: + min_latency = 'NaN' + max_latency = 'NaN' + avg_latency = 'NaN' + if len(jitter) != 0: + tot_jitter = jitter[len(jitter) - 1] + else: + tot_jitter = 'NaN' + + return PingResult(lost=lost, lost_perc=lost_perc, min=min_latency, max=max_latency, avg=avg_latency, + jitter=tot_jitter, mos=mos) if __name__ == '__main__': - verbose_ping("heise.de") - verbose_ping("google.com") - verbose_ping("a-test-url-taht-is-not-available.com") - verbose_ping("192.168.1.1") + dest, timeout, count = None, 2, 10 + + try: + dest = sys.argv[1] + opts, args = getopt.getopt(sys.argv[1:], ':hc:t:d:o:f:') + except Exception as err: + print 'Usage: %s 8.8.8.8 -c [count] -t [timeout]' % sys.argv[0] + sys.exit(1) + + for opt, arg in opts: + if opt in '-h': + print 'Usage: %s -c -t -d ' % sys.argv[0] + sys.exit(1) + + if opt in '-c': + count = int(arg) + elif opt in '-t': + timeout = int(arg) + + result = ping(dest, timeout=timeout, count=count) + + print("Statistics for %s:" % (dest)) + print(" - packet loss: %i (%.2f%%)" % (result.lost, result.lost_perc)) + print(" - latency (MIN/MAX/AVG): %s/%s/%s" % (result.min, result.max, result.avg)) + + if type(result.jitter) != str: + print(" - jitter: %.4f" % result.jitter) + else: + print(" - jitter: %s" % result.jitter) + + print(" - MOS: %.1f" % result.mos) From 7ff607b38b85a76f20212f05dd6a20f6409a5bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hasan=20Basri=20Ate=C5=9F?= Date: Sun, 29 May 2016 15:44:17 +0300 Subject: [PATCH 2/2] description updated --- ping.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ping.py b/ping.py index 5950bd7..a5b8686 100755 --- a/ping.py +++ b/ping.py @@ -4,7 +4,7 @@ A pure python ping implementation using raw socket. - Note that ICMP messages can only be sent from processes running as root. + Note root access not required Derived from ping.c distributed in Linux's netkit. That code is @@ -60,6 +60,10 @@ Januari 27, 2015 Changed receive response to not accept ICMP request messages. It was possible to receive the very request that was sent. + + May 29, 2016 + root access not required + latency (MIN/MAX/AVG) calc added Last commit info: ~~~~~~~~~~~~~~~~~