Merge f1674ad302
into f1647f2c9e
This commit is contained in:
commit
0d33778936
|
@ -18,6 +18,14 @@
|
|||
|
||||
In general, I follow strict pep8 and pyflakes. All code must pass these tests. Since we support python 2.4-3.4 and pypy, pyflakes reports unknown names in python 3. pyflakes is run in python 2.7 only in my tests.
|
||||
|
||||
## Some other points
|
||||
|
||||
1. Do not use `\` for line continuations, long strings should be wrapped in `()`. Imports should start a brand new line in the form of `from foo import...`
|
||||
1. String quoting should be done with single quotes `'`, except for situations where you would otherwise have to escape an internal single quote
|
||||
1. Docstrings should use three double quotes `"""`
|
||||
1. All functions, classes and modules should have docstrings following both the PEP257 and PEP8 standards
|
||||
1. Inline comments should only be used on code where it is not immediately obvious what the code achieves
|
||||
|
||||
# Supported Python Versions
|
||||
|
||||
All code needs to support Python 2.4-3.4 and pypy.
|
||||
|
|
21
README.rst
21
README.rst
|
@ -89,5 +89,26 @@ Usage
|
|||
--server SERVER Specify a server ID to test against
|
||||
--mini MINI URL of the Speedtest Mini server
|
||||
--source SOURCE Source IP address to bind to
|
||||
--traceroute Runs traceroute against test host after speedtest
|
||||
--version Show the version number and exit
|
||||
|
||||
Inconsistency
|
||||
-------------
|
||||
|
||||
It is not a goal of this application to be a reliable latency reporting tool.
|
||||
|
||||
Latency reported by this tool should not be relied on as a value indicative of ICMP
|
||||
style latency. It is a relative value used for determining the lowest latency server
|
||||
for performing the actual speed test against.
|
||||
|
||||
There is the potential for this tool to report results inconsistent with Speedtest.net.
|
||||
There are several concepts to be aware of that factor into the potential inconsistency:
|
||||
|
||||
1. Speedtest.net has migrated to using pure socket tests instead of HTTP based tests
|
||||
2. This application is written in Python
|
||||
3. Different versions of Python will execute certain parts of the code faster than others
|
||||
4. CPU and Memory capacity and speed will play a large part in inconsistency between
|
||||
Speedtest.net and even other machines on the same network
|
||||
|
||||
Issues relating to inconsistencies will be closed as wontfix and without
|
||||
additional reason or context.
|
||||
|
|
139
speedtest_cli.py
139
speedtest_cli.py
|
@ -22,13 +22,14 @@ source = None
|
|||
shutdown_event = None
|
||||
|
||||
import math
|
||||
import time
|
||||
import timeit
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import re
|
||||
import signal
|
||||
import socket
|
||||
import platform
|
||||
|
||||
# Used for bound_interface
|
||||
socket_socket = socket.socket
|
||||
|
@ -48,6 +49,11 @@ try:
|
|||
except ImportError:
|
||||
from urllib.request import urlopen, Request, HTTPError, URLError
|
||||
|
||||
try:
|
||||
from httplib import HTTPConnection, HTTPSConnection
|
||||
except ImportError:
|
||||
from http.client import HTTPConnection, HTTPSConnection
|
||||
|
||||
try:
|
||||
from Queue import Queue
|
||||
except ImportError:
|
||||
|
@ -172,7 +178,7 @@ class FileGetter(threading.Thread):
|
|||
def run(self):
|
||||
self.result = [0]
|
||||
try:
|
||||
if (time.time() - self.starttime) <= 10:
|
||||
if (timeit.default_timer() - self.starttime) <= 10:
|
||||
f = urlopen(self.url)
|
||||
while 1 and not shutdown_event.isSet():
|
||||
self.result.append(len(f.read(10240)))
|
||||
|
@ -186,7 +192,7 @@ class FileGetter(threading.Thread):
|
|||
def downloadSpeed(files, quiet=False):
|
||||
"""Function to launch FileGetter threads and calculate download speeds"""
|
||||
|
||||
start = time.time()
|
||||
start = timeit.default_timer()
|
||||
|
||||
def producer(q, files):
|
||||
for file in files:
|
||||
|
@ -210,14 +216,14 @@ def downloadSpeed(files, quiet=False):
|
|||
q = Queue(6)
|
||||
prod_thread = threading.Thread(target=producer, args=(q, files))
|
||||
cons_thread = threading.Thread(target=consumer, args=(q, len(files)))
|
||||
start = time.time()
|
||||
start = timeit.default_timer()
|
||||
prod_thread.start()
|
||||
cons_thread.start()
|
||||
while prod_thread.isAlive():
|
||||
prod_thread.join(timeout=0.1)
|
||||
while cons_thread.isAlive():
|
||||
cons_thread.join(timeout=0.1)
|
||||
return (sum(finished) / (time.time() - start))
|
||||
return (sum(finished) / (timeit.default_timer() - start))
|
||||
|
||||
|
||||
class FilePutter(threading.Thread):
|
||||
|
@ -235,7 +241,7 @@ class FilePutter(threading.Thread):
|
|||
|
||||
def run(self):
|
||||
try:
|
||||
if ((time.time() - self.starttime) <= 10 and
|
||||
if ((timeit.default_timer() - self.starttime) <= 10 and
|
||||
not shutdown_event.isSet()):
|
||||
f = urlopen(self.url, self.data)
|
||||
f.read(11)
|
||||
|
@ -250,7 +256,7 @@ class FilePutter(threading.Thread):
|
|||
def uploadSpeed(url, sizes, quiet=False):
|
||||
"""Function to launch FilePutter threads and calculate upload speeds"""
|
||||
|
||||
start = time.time()
|
||||
start = timeit.default_timer()
|
||||
|
||||
def producer(q, sizes):
|
||||
for size in sizes:
|
||||
|
@ -274,14 +280,14 @@ def uploadSpeed(url, sizes, quiet=False):
|
|||
q = Queue(6)
|
||||
prod_thread = threading.Thread(target=producer, args=(q, sizes))
|
||||
cons_thread = threading.Thread(target=consumer, args=(q, len(sizes)))
|
||||
start = time.time()
|
||||
start = timeit.default_timer()
|
||||
prod_thread.start()
|
||||
cons_thread.start()
|
||||
while prod_thread.isAlive():
|
||||
prod_thread.join(timeout=0.1)
|
||||
while cons_thread.isAlive():
|
||||
cons_thread.join(timeout=0.1)
|
||||
return (sum(finished) / (time.time() - start))
|
||||
return (sum(finished) / (timeit.default_timer() - start))
|
||||
|
||||
|
||||
def getAttributesByTagName(dom, tagName):
|
||||
|
@ -310,19 +316,23 @@ def getConfig():
|
|||
return None
|
||||
uh.close()
|
||||
try:
|
||||
root = ET.fromstring(''.encode().join(configxml))
|
||||
config = {
|
||||
'client': root.find('client').attrib,
|
||||
'times': root.find('times').attrib,
|
||||
'download': root.find('download').attrib,
|
||||
'upload': root.find('upload').attrib}
|
||||
except AttributeError:
|
||||
root = DOM.parseString(''.join(configxml))
|
||||
config = {
|
||||
'client': getAttributesByTagName(root, 'client'),
|
||||
'times': getAttributesByTagName(root, 'times'),
|
||||
'download': getAttributesByTagName(root, 'download'),
|
||||
'upload': getAttributesByTagName(root, 'upload')}
|
||||
try:
|
||||
root = ET.fromstring(''.encode().join(configxml))
|
||||
config = {
|
||||
'client': root.find('client').attrib,
|
||||
'times': root.find('times').attrib,
|
||||
'download': root.find('download').attrib,
|
||||
'upload': root.find('upload').attrib}
|
||||
except AttributeError:
|
||||
root = DOM.parseString(''.join(configxml))
|
||||
config = {
|
||||
'client': getAttributesByTagName(root, 'client'),
|
||||
'times': getAttributesByTagName(root, 'times'),
|
||||
'download': getAttributesByTagName(root, 'download'),
|
||||
'upload': getAttributesByTagName(root, 'upload')}
|
||||
except SyntaxError:
|
||||
print_('Failed to parse speedtest.net configuration')
|
||||
sys.exit(1)
|
||||
del root
|
||||
del configxml
|
||||
return config
|
||||
|
@ -333,7 +343,7 @@ def closestServers(client, all=False):
|
|||
distance
|
||||
"""
|
||||
|
||||
uh = urlopen('http://www.speedtest.net/speedtest-servers.php')
|
||||
uh = urlopen('http://c.speedtest.net/speedtest-servers-static.php')
|
||||
serversxml = []
|
||||
while 1:
|
||||
serversxml.append(uh.read(10240))
|
||||
|
@ -343,11 +353,15 @@ def closestServers(client, all=False):
|
|||
return None
|
||||
uh.close()
|
||||
try:
|
||||
root = ET.fromstring(''.encode().join(serversxml))
|
||||
elements = root.getiterator('server')
|
||||
except AttributeError:
|
||||
root = DOM.parseString(''.join(serversxml))
|
||||
elements = root.getElementsByTagName('server')
|
||||
try:
|
||||
root = ET.fromstring(''.encode().join(serversxml))
|
||||
elements = root.getiterator('server')
|
||||
except AttributeError:
|
||||
root = DOM.parseString(''.join(serversxml))
|
||||
elements = root.getElementsByTagName('server')
|
||||
except SyntaxError:
|
||||
print_('Failed to parse list of speedtest.net servers')
|
||||
sys.exit(1)
|
||||
servers = {}
|
||||
for server in elements:
|
||||
try:
|
||||
|
@ -380,31 +394,36 @@ def closestServers(client, all=False):
|
|||
|
||||
|
||||
def getBestServer(servers):
|
||||
"""Perform a speedtest.net "ping" to determine which speedtest.net
|
||||
server has the lowest latency
|
||||
"""Perform a speedtest.net latency request to determine which
|
||||
speedtest.net server has the lowest latency
|
||||
"""
|
||||
|
||||
results = {}
|
||||
for server in servers:
|
||||
cum = []
|
||||
url = os.path.dirname(server['url'])
|
||||
url = '%s/latency.txt' % os.path.dirname(server['url'])
|
||||
urlparts = urlparse(url)
|
||||
for i in range(0, 3):
|
||||
try:
|
||||
uh = urlopen('%s/latency.txt' % url)
|
||||
if urlparts[0] == 'https':
|
||||
h = HTTPSConnection(urlparts[1])
|
||||
else:
|
||||
h = HTTPConnection(urlparts[1])
|
||||
start = timeit.default_timer()
|
||||
h.request("GET", urlparts[2])
|
||||
r = h.getresponse()
|
||||
total = (timeit.default_timer() - start)
|
||||
except (HTTPError, URLError):
|
||||
cum.append(3600)
|
||||
continue
|
||||
start = time.time()
|
||||
text = uh.read(9)
|
||||
total = time.time() - start
|
||||
if int(uh.code) == 200 and text == 'test=test'.encode():
|
||||
text = r.read(9)
|
||||
if int(r.status) == 200 and text == 'test=test'.encode():
|
||||
cum.append(total)
|
||||
else:
|
||||
cum.append(3600)
|
||||
uh.close()
|
||||
avg = round((sum(cum) / 3) * 1000000, 3)
|
||||
h.close()
|
||||
avg = round((sum(cum) / 6) * 1000, 3)
|
||||
results[avg] = server
|
||||
|
||||
fastest = sorted(results.keys())[0]
|
||||
best = results[fastest]
|
||||
best['latency'] = fastest
|
||||
|
@ -423,11 +442,28 @@ def ctrl_c(signum, frame):
|
|||
|
||||
|
||||
def version():
|
||||
print getNetworkIp("speedtest-dev.oit.duke.edu")
|
||||
"""Print the version"""
|
||||
|
||||
raise SystemExit(__version__)
|
||||
|
||||
|
||||
def tracerouter(url):
|
||||
host = '{uri.netloc}'.format(uri=urlparse(url))
|
||||
tracer = 'tracert' if (platform.system() == 'Windows') else 'traceroute'
|
||||
print_('Running ', tracer, ' against ', host)
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
p = Popen([tracer, '-d', '-w', '3', host], stdout=PIPE)
|
||||
while True:
|
||||
line = p.stdout.readline()
|
||||
if not line:
|
||||
break
|
||||
print '\t', line,
|
||||
p.wait()
|
||||
return true
|
||||
|
||||
|
||||
def speedtest():
|
||||
"""Run the full speedtest.net test"""
|
||||
|
||||
|
@ -466,6 +502,9 @@ def speedtest():
|
|||
parser.add_argument('--server', help='Specify a server ID to test against')
|
||||
parser.add_argument('--mini', help='URL of the Speedtest Mini server')
|
||||
parser.add_argument('--source', help='Source IP address to bind to')
|
||||
parser.add_argument('--traceroute', action='store_true',
|
||||
help='Runs traceroute against test host '
|
||||
'after speedtest')
|
||||
parser.add_argument('--version', action='store_true',
|
||||
help='Show the version number and exit')
|
||||
|
||||
|
@ -573,22 +612,26 @@ def speedtest():
|
|||
best = servers[0]
|
||||
else:
|
||||
if not args.simple:
|
||||
print_('Selecting best server based on ping...')
|
||||
print_('Selecting best server based on latency...')
|
||||
best = getBestServer(servers)
|
||||
|
||||
if not args.simple:
|
||||
host = '{uri.scheme}://{uri.netloc}/'.format(uri=urlparse(best['url']))
|
||||
print_(best['url'])
|
||||
hostedby = ('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: '
|
||||
'%(latency)s ms' % best)
|
||||
if args.traceroute:
|
||||
hostedby += (' via %s' % host)
|
||||
# Python 2.7 and newer seem to be ok with the resultant encoding
|
||||
# from parsing the XML, but older versions have some issues.
|
||||
# This block should detect whether we need to encode or not
|
||||
try:
|
||||
unicode()
|
||||
print_(('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: '
|
||||
'%(latency)s ms' % best).encode('utf-8', 'ignore'))
|
||||
print_(hostedby.encode('utf-8', 'ignore'))
|
||||
except NameError:
|
||||
print_('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: '
|
||||
'%(latency)s ms' % best)
|
||||
print_(hostedby)
|
||||
else:
|
||||
print_('Ping: %(latency)s ms' % best)
|
||||
print_('Latency: %(latency)s ms' % best)
|
||||
|
||||
sizes = [350, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000]
|
||||
urls = []
|
||||
|
@ -661,6 +704,12 @@ def speedtest():
|
|||
|
||||
print_('Share results: http://www.speedtest.net/result/%s.png' %
|
||||
resultid[0])
|
||||
if args.traceroute:
|
||||
try:
|
||||
tracerouter(best['url'])
|
||||
except:
|
||||
print_('Unable to run Traceroute against ',
|
||||
'{uri.netloc}'.format(uri=urlparse(best['url'])))
|
||||
|
||||
|
||||
def main():
|
||||
|
|
Loading…
Reference in New Issue