r94101 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r94100‎ | r94101 | r94102 >
Date:10:46, 9 August 2011
Author:mark
Status:deferred
Tags:
Comment:
First version without ringbuffer support for checking reordered packets
Modified paths:
  • /trunk/udpmcast/htcpseqcheck.py (added) (history)

Diff [purge]

Index: trunk/udpmcast/htcpseqcheck.py
@@ -0,0 +1,202 @@
 2+#!/usr/bin/env python
 3+#
 4+# htcpseqcheck.py
 5+# measure HTCP multicast packet loss
 6+# Written on 2011/08/05 by Mark Bergsma <mark@wikimedia.org>
 7+#
 8+# $Id$
 9+
 10+import socket, getopt, sys, os, signal, pwd, grp, struct
 11+
 12+debugging = False
 13+sourceseq = {}
 14+loss, total = 0, 0
 15+sources = []
 16+
 17+def createDaemon():
 18+ """
 19+ Detach a process from the controlling terminal and run it in the
 20+ background as a daemon.
 21+ """
 22+
 23+ try:
 24+ # Fork a child process so the parent can exit. This will return control
 25+ # to the command line or shell. This is required so that the new process
 26+ # is guaranteed not to be a process group leader. We have this guarantee
 27+ # because the process GID of the parent is inherited by the child, but
 28+ # the child gets a new PID, making it impossible for its PID to equal its
 29+ # PGID.
 30+ pid = os.fork()
 31+ except OSError, e:
 32+ return((e.errno, e.strerror)) # ERROR (return a tuple)
 33+
 34+ if (pid == 0): # The first child.
 35+
 36+ # Next we call os.setsid() to become the session leader of this new
 37+ # session. The process also becomes the process group leader of the
 38+ # new process group. Since a controlling terminal is associated with a
 39+ # session, and this new session has not yet acquired a controlling
 40+ # terminal our process now has no controlling terminal. This shouldn't
 41+ # fail, since we're guaranteed that the child is not a process group
 42+ # leader.
 43+ os.setsid()
 44+
 45+ # When the first child terminates, all processes in the second child
 46+ # are sent a SIGHUP, so it's ignored.
 47+ signal.signal(signal.SIGHUP, signal.SIG_IGN)
 48+
 49+ try:
 50+ # Fork a second child to prevent zombies. Since the first child is
 51+ # a session leader without a controlling terminal, it's possible for
 52+ # it to acquire one by opening a terminal in the future. This second
 53+ # fork guarantees that the child is no longer a session leader, thus
 54+ # preventing the daemon from ever acquiring a controlling terminal.
 55+ pid = os.fork() # Fork a second child.
 56+ except OSError, e:
 57+ return((e.errno, e.strerror)) # ERROR (return a tuple)
 58+
 59+ if (pid == 0): # The second child.
 60+ # Ensure that the daemon doesn't keep any directory in use. Failure
 61+ # to do this could make a filesystem unmountable.
 62+ os.chdir("/")
 63+ # Give the child complete control over permissions.
 64+ os.umask(0)
 65+ else:
 66+ os._exit(0) # Exit parent (the first child) of the second child.
 67+ else:
 68+ os._exit(0) # Exit parent of the first child.
 69+
 70+ # Close all open files. Try the system configuration variable, SC_OPEN_MAX,
 71+ # for the maximum number of open files to close. If it doesn't exist, use
 72+ # the default value (configurable).
 73+ try:
 74+ maxfd = os.sysconf("SC_OPEN_MAX")
 75+ except (AttributeError, ValueError):
 76+ maxfd = 256 # default maximum
 77+
 78+ for fd in range(0, maxfd):
 79+ try:
 80+ os.close(fd)
 81+ except OSError: # ERROR (ignore)
 82+ pass
 83+
 84+ # Redirect the standard file descriptors to /dev/null.
 85+ os.open("/dev/null", os.O_RDONLY) # standard input (0)
 86+ os.open("/dev/null", os.O_RDWR) # standard output (1)
 87+ os.open("/dev/null", os.O_RDWR) # standard error (2)
 88+
 89+ return(0)
 90+
 91+def debug(msg):
 92+ global debugging
 93+ if debugging:
 94+ print msg;
 95+
 96+def receive_htcp(sock):
 97+ portnr = sock.getsockname()[1];
 98+
 99+ while 1:
 100+ diagram, srcaddr = sock.recvfrom(2**14)
 101+ if not diagram: break
 102+
 103+ checkhtcpseq(diagram, srcaddr[0])
 104+
 105+def checkhtcpseq(diagram, srcaddr):
 106+ global sourceseq, loss, total, sources
 107+
 108+ sources.append(srcaddr)
 109+ transid = struct.unpack('!I', diagram[8:12])[0]
 110+ try:
 111+ diff = transid - sourceseq[srcaddr]
 112+ except:
 113+ return
 114+ else:
 115+ total += diff
 116+ if diff != 1:
 117+ loss += diff-1
 118+ print "Out of order or", diff-1, "missing packet(s) from", srcaddr, "id", transid, "last received id was", sourceseq[srcaddr]
 119+ print "Last 10 sources:", " ".join(sources[-10:])
 120+ print "%d/%d losses (%f%%), %d sources" % (loss, total, float(loss)*100/total, len(sourceseq.keys()))
 121+ finally:
 122+ sourceseq[srcaddr] = transid
 123+ if total % 100 == 0: sources = sources[-10:]
 124+
 125+def join_multicast_group(sock, multicast_group):
 126+ import struct
 127+
 128+ ip_mreq = struct.pack('!4sl', socket.inet_aton(multicast_group),
 129+ socket.INADDR_ANY)
 130+ sock.setsockopt(socket.IPPROTO_IP,
 131+ socket.IP_ADD_MEMBERSHIP,
 132+ ip_mreq)
 133+
 134+ # We do not want to see our own messages back
 135+ sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 0)
 136+
 137+def print_help():
 138+ print 'Usage:\n\thtcpseqcheck [ options ]\n'
 139+ print 'Options:'
 140+ print '\t-d\t\tFork into the background (become a daemon)'
 141+ print '\t-p {portnr}\tUDP port number to listen on (default is 4827)'
 142+ print '\t-j {mcast addr}\tMulticast group to join on startup'
 143+ print '\t-u {username}\tChange uid'
 144+ print '\t-g {group}\tChange group'
 145+ print '\t-v\t\tBe more verbose'
 146+
 147+if __name__ == '__main__':
 148+ host = '0.0.0.0'
 149+ portnr = 4827
 150+ multicast_group = None
 151+ daemon = False
 152+ user = group = None
 153+ opts = 'dhj:p:vu:g:'
 154+
 155+ # Parse options
 156+ options, arguments = getopt.getopt(sys.argv[1:], opts)
 157+ for option, value in options:
 158+ if option == '-j':
 159+ multicast_group = value
 160+ elif option == '-p':
 161+ portnr = int(value)
 162+ elif option == '-h':
 163+ print_help()
 164+ sys.exit()
 165+ elif option == '-d':
 166+ daemon = True
 167+ elif option == '-u':
 168+ user = value
 169+ elif option == '-g':
 170+ group = value
 171+ elif option == '-v':
 172+ debugging = True
 173+
 174+ try:
 175+ # Change uid and gid
 176+ try:
 177+ if group: os.setgid(grp.getgrnam(group).gr_gid)
 178+ if user: os.setuid(pwd.getpwnam(user).pw_uid)
 179+ except:
 180+ print "Error: Could not change uid or gid."
 181+ sys.exit(-1)
 182+
 183+ # Become a daemon
 184+ if daemon:
 185+ createDaemon()
 186+
 187+ # Open the UDP socket
 188+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
 189+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 190+ sock.bind((host, portnr))
 191+
 192+ # Join a multicast group if requested
 193+ if multicast_group is not None:
 194+ debug('Joining multicast group ' + multicast_group)
 195+ join_multicast_group(sock, multicast_group)
 196+
 197+ # Multiplex everything that comes in
 198+ receive_htcp(sock)
 199+ except socket.error, msg:
 200+ print msg[1];
 201+ except KeyboardInterrupt:
 202+ pass
 203+
Property changes on: trunk/udpmcast/htcpseqcheck.py
___________________________________________________________________
Added: svn:executable
1204 + *
Added: svn:mime-type
2205 + text/plain

Status & tagging log