2012-09-10 21:18:56 +02:00
#!/usr/bin/env python
2013-01-01 21:02:45 +01:00
# -*- coding: utf-8 -*-
2014-04-23 17:22:22 +02:00
# Copyright 2012-2014 Matt Martz
2013-01-07 19:29:42 +01:00
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
2012-09-10 21:18:56 +02:00
2014-08-05 19:56:05 +02:00
__version__ = ' 0.3.1 '
2013-09-16 20:07:24 +02:00
2013-11-18 19:02:30 +01:00
# Some global variables we use
source = None
shutdown_event = None
2012-09-10 21:18:56 +02:00
import os
2013-07-26 23:34:07 +02:00
import re
2014-06-28 02:30:22 +02:00
import sys
import math
2013-09-06 05:04:02 +02:00
import signal
2013-11-11 17:04:32 +01:00
import socket
2014-06-28 02:30:22 +02:00
import timeit
import threading
2013-11-11 17:04:32 +01:00
# Used for bound_interface
socket_socket = socket . socket
2013-12-02 17:13:41 +01:00
try :
import xml . etree . cElementTree as ET
except ImportError :
try :
import xml . etree . ElementTree as ET
except ImportError :
from xml . dom import minidom as DOM
ET = None
2013-07-26 23:11:10 +02:00
2013-11-18 19:11:07 +01:00
# Begin import game to handle Python 2 and Python 3
try :
from urllib2 import urlopen , Request , HTTPError , URLError
except ImportError :
from urllib . request import urlopen , Request , HTTPError , URLError
2014-05-27 16:30:39 +02:00
try :
from httplib import HTTPConnection , HTTPSConnection
except ImportError :
from http . client import HTTPConnection , HTTPSConnection
2013-07-26 23:11:10 +02:00
try :
from Queue import Queue
except ImportError :
from queue import Queue
2013-07-26 23:34:07 +02:00
try :
from urlparse import urlparse
except ImportError :
from urllib . parse import urlparse
2013-01-12 17:48:58 +01:00
try :
from urlparse import parse_qs
except ImportError :
2013-07-26 23:11:10 +02:00
try :
from urllib . parse import parse_qs
except ImportError :
from cgi import parse_qs
2013-01-12 15:22:11 +01:00
try :
from hashlib import md5
2013-01-12 17:48:58 +01:00
except ImportError :
2013-01-12 15:22:11 +01:00
from md5 import md5
2013-07-26 23:11:10 +02:00
2013-01-24 21:40:29 +01:00
try :
from argparse import ArgumentParser as ArgParser
except ImportError :
from optparse import OptionParser as ArgParser
2012-09-10 21:18:56 +02:00
2013-07-26 23:11:10 +02:00
try :
import builtins
except ImportError :
def print_ ( * args , * * kwargs ) :
""" The new-style print function taken from
https : / / pypi . python . org / pypi / six /
"""
fp = kwargs . pop ( " file " , sys . stdout )
if fp is None :
return
def write ( data ) :
if not isinstance ( data , basestring ) :
data = str ( data )
fp . write ( data )
want_unicode = False
sep = kwargs . pop ( " sep " , None )
if sep is not None :
if isinstance ( sep , unicode ) :
want_unicode = True
elif not isinstance ( sep , str ) :
raise TypeError ( " sep must be None or a string " )
end = kwargs . pop ( " end " , None )
if end is not None :
if isinstance ( end , unicode ) :
want_unicode = True
elif not isinstance ( end , str ) :
raise TypeError ( " end must be None or a string " )
if kwargs :
raise TypeError ( " invalid keyword arguments to print() " )
if not want_unicode :
for arg in args :
if isinstance ( arg , unicode ) :
want_unicode = True
break
if want_unicode :
newline = unicode ( " \n " )
space = unicode ( " " )
else :
newline = " \n "
space = " "
if sep is None :
sep = space
if end is None :
end = newline
for i , arg in enumerate ( args ) :
if i :
write ( sep )
write ( arg )
write ( end )
else :
print_ = getattr ( builtins , ' print ' )
del builtins
2013-11-11 17:04:32 +01:00
def bound_socket ( * args , * * kwargs ) :
""" Bind socket to a specified source IP address """
global source
sock = socket_socket ( * args , * * kwargs )
sock . bind ( ( source , 0 ) )
return sock
2012-09-10 21:18:56 +02:00
def distance ( origin , destination ) :
2013-01-07 20:01:14 +01:00
""" Determine distance between 2 sets of [lat,lon] in km """
2012-09-10 21:18:56 +02:00
2013-01-07 20:01:14 +01:00
lat1 , lon1 = origin
lat2 , lon2 = destination
radius = 6371 # km
2012-09-10 21:18:56 +02:00
2013-10-08 14:09:55 +02:00
dlat = math . radians ( lat2 - lat1 )
dlon = math . radians ( lon2 - lon1 )
2013-01-07 20:01:14 +01:00
a = ( math . sin ( dlat / 2 ) * math . sin ( dlat / 2 ) + math . cos ( math . radians ( lat1 ) )
* math . cos ( math . radians ( lat2 ) ) * math . sin ( dlon / 2 )
* math . sin ( dlon / 2 ) )
c = 2 * math . atan2 ( math . sqrt ( a ) , math . sqrt ( 1 - a ) )
d = radius * c
2012-09-10 21:18:56 +02:00
2013-01-07 20:01:14 +01:00
return d
2012-09-10 21:18:56 +02:00
class FileGetter ( threading . Thread ) :
2013-11-18 19:11:07 +01:00
""" Thread class for retrieving a URL """
2013-01-07 20:01:14 +01:00
def __init__ ( self , url , start ) :
self . url = url
self . result = None
self . starttime = start
threading . Thread . __init__ ( self )
def run ( self ) :
2013-08-08 16:52:54 +02:00
self . result = [ 0 ]
2013-01-07 20:01:14 +01:00
try :
2014-06-25 18:26:14 +02:00
if ( timeit . default_timer ( ) - self . starttime ) < = 10 :
2013-07-26 23:11:10 +02:00
f = urlopen ( self . url )
2013-10-08 10:31:05 +02:00
while 1 and not shutdown_event . isSet ( ) :
2013-08-08 16:52:54 +02:00
self . result . append ( len ( f . read ( 10240 ) ) )
if self . result [ - 1 ] == 0 :
2013-02-18 19:02:18 +01:00
break
2013-01-07 20:01:14 +01:00
f . close ( )
except IOError :
2013-08-08 16:52:54 +02:00
pass
2012-09-10 21:18:56 +02:00
2013-01-24 21:40:29 +01:00
def downloadSpeed ( files , quiet = False ) :
2013-11-18 19:11:07 +01:00
""" Function to launch FileGetter threads and calculate download speeds """
2014-06-25 18:26:14 +02:00
start = timeit . default_timer ( )
2013-01-07 20:01:14 +01:00
def producer ( q , files ) :
for file in files :
thread = FileGetter ( file , start )
thread . start ( )
q . put ( thread , True )
2013-10-08 10:31:05 +02:00
if not quiet and not shutdown_event . isSet ( ) :
2013-01-24 21:40:29 +01:00
sys . stdout . write ( ' . ' )
sys . stdout . flush ( )
2013-01-07 20:01:14 +01:00
finished = [ ]
def consumer ( q , total_files ) :
while len ( finished ) < total_files :
thread = q . get ( True )
2013-10-08 10:31:05 +02:00
while thread . isAlive ( ) :
2013-09-06 05:04:02 +02:00
thread . join ( timeout = 0.1 )
2013-08-08 16:52:54 +02:00
finished . append ( sum ( thread . result ) )
del thread
2013-01-07 20:01:14 +01:00
2013-02-18 18:47:43 +01:00
q = Queue ( 6 )
2013-01-07 20:01:14 +01:00
prod_thread = threading . Thread ( target = producer , args = ( q , files ) )
cons_thread = threading . Thread ( target = consumer , args = ( q , len ( files ) ) )
2014-06-25 18:26:14 +02:00
start = timeit . default_timer ( )
2013-01-07 20:01:14 +01:00
prod_thread . start ( )
cons_thread . start ( )
2013-10-08 10:31:05 +02:00
while prod_thread . isAlive ( ) :
2013-09-06 05:04:02 +02:00
prod_thread . join ( timeout = 0.1 )
2013-10-08 10:31:05 +02:00
while cons_thread . isAlive ( ) :
2013-09-06 05:04:02 +02:00
cons_thread . join ( timeout = 0.1 )
2014-06-25 18:26:14 +02:00
return ( sum ( finished ) / ( timeit . default_timer ( ) - start ) )
2012-09-10 21:18:56 +02:00
class FilePutter ( threading . Thread ) :
2013-11-18 19:11:07 +01:00
""" Thread class for putting a URL """
2013-01-07 20:01:14 +01:00
def __init__ ( self , url , start , size ) :
self . url = url
2013-08-08 16:52:54 +02:00
chars = ' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ '
data = chars * ( int ( round ( int ( size ) / 36.0 ) ) )
2013-10-08 14:09:55 +02:00
self . data = ( ' content1= %s ' % data [ 0 : int ( size ) - 9 ] ) . encode ( )
2013-01-12 17:46:56 +01:00
del data
2013-01-07 20:01:14 +01:00
self . result = None
self . starttime = start
threading . Thread . __init__ ( self )
def run ( self ) :
try :
2014-06-25 18:26:14 +02:00
if ( ( timeit . default_timer ( ) - self . starttime ) < = 10 and
2013-10-08 10:31:05 +02:00
not shutdown_event . isSet ( ) ) :
2013-07-26 23:11:10 +02:00
f = urlopen ( self . url , self . data )
2013-08-23 16:31:09 +02:00
f . read ( 11 )
2013-01-07 20:01:14 +01:00
f . close ( )
2013-02-18 19:02:18 +01:00
self . result = len ( self . data )
2013-01-07 20:01:14 +01:00
else :
2013-02-18 19:02:18 +01:00
self . result = 0
2013-01-07 20:01:14 +01:00
except IOError :
2013-02-18 19:02:18 +01:00
self . result = 0
2012-09-10 21:18:56 +02:00
2013-01-24 21:40:29 +01:00
def uploadSpeed ( url , sizes , quiet = False ) :
2013-11-18 19:11:07 +01:00
""" Function to launch FilePutter threads and calculate upload speeds """
2014-06-25 18:26:14 +02:00
start = timeit . default_timer ( )
2013-01-07 20:01:14 +01:00
def producer ( q , sizes ) :
for size in sizes :
thread = FilePutter ( url , start , size )
thread . start ( )
q . put ( thread , True )
2013-10-08 10:31:05 +02:00
if not quiet and not shutdown_event . isSet ( ) :
2013-01-24 21:40:29 +01:00
sys . stdout . write ( ' . ' )
sys . stdout . flush ( )
2013-01-07 20:01:14 +01:00
finished = [ ]
def consumer ( q , total_sizes ) :
while len ( finished ) < total_sizes :
thread = q . get ( True )
2013-10-08 10:31:05 +02:00
while thread . isAlive ( ) :
2013-09-06 05:04:02 +02:00
thread . join ( timeout = 0.1 )
2013-02-18 19:02:18 +01:00
finished . append ( thread . result )
2013-08-08 16:52:54 +02:00
del thread
2013-01-07 20:01:14 +01:00
2013-02-18 18:47:43 +01:00
q = Queue ( 6 )
2013-01-07 20:01:14 +01:00
prod_thread = threading . Thread ( target = producer , args = ( q , sizes ) )
cons_thread = threading . Thread ( target = consumer , args = ( q , len ( sizes ) ) )
2014-06-25 18:26:14 +02:00
start = timeit . default_timer ( )
2013-01-07 20:01:14 +01:00
prod_thread . start ( )
cons_thread . start ( )
2013-10-08 10:31:05 +02:00
while prod_thread . isAlive ( ) :
2013-09-06 05:04:02 +02:00
prod_thread . join ( timeout = 0.1 )
2013-10-08 10:31:05 +02:00
while cons_thread . isAlive ( ) :
2013-09-06 05:04:02 +02:00
cons_thread . join ( timeout = 0.1 )
2014-06-25 18:26:14 +02:00
return ( sum ( finished ) / ( timeit . default_timer ( ) - start ) )
2012-09-10 21:18:56 +02:00
2013-01-12 15:20:59 +01:00
def getAttributesByTagName ( dom , tagName ) :
2013-11-18 19:11:07 +01:00
""" Retrieve an attribute from an XML document and return it in a
consistent format
2013-12-02 17:13:41 +01:00
Only used with xml . dom . minidom , which is likely only to be used
with python versions older than 2.5
2013-11-18 19:11:07 +01:00
"""
2013-01-12 15:20:59 +01:00
elem = dom . getElementsByTagName ( tagName ) [ 0 ]
2013-07-26 23:11:10 +02:00
return dict ( list ( elem . attributes . items ( ) ) )
2013-01-12 15:20:59 +01:00
2012-09-10 21:18:56 +02:00
def getConfig ( ) :
2013-01-07 20:01:14 +01:00
""" Download the speedtest.net configuration and return only the data
we are interested in
"""
2013-07-26 23:11:10 +02:00
uh = urlopen ( ' http://www.speedtest.net/speedtest-config.php ' )
2013-12-02 17:13:41 +01:00
configxml = [ ]
while 1 :
configxml . append ( uh . read ( 10240 ) )
if len ( configxml [ - 1 ] ) == 0 :
break
2013-01-12 15:20:59 +01:00
if int ( uh . code ) != 200 :
2013-01-07 20:01:14 +01:00
return None
uh . close ( )
2013-12-02 17:13:41 +01:00
try :
2014-06-25 18:06:42 +02:00
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 )
2013-01-12 15:20:59 +01:00
del root
2013-12-02 17:13:41 +01:00
del configxml
2013-01-07 20:01:14 +01:00
return config
2012-09-10 21:18:56 +02:00
2013-01-30 17:49:32 +01:00
def closestServers ( client , all = False ) :
2013-01-07 20:01:14 +01:00
""" Determine the 5 closest speedtest.net servers based on geographic
distance
"""
2014-07-25 15:56:11 +02:00
uh = urlopen ( ' http://www.speedtest.net/speedtest-servers-static.php ' )
2013-12-02 17:13:41 +01:00
serversxml = [ ]
while 1 :
serversxml . append ( uh . read ( 10240 ) )
if len ( serversxml [ - 1 ] ) == 0 :
break
2013-01-12 15:20:59 +01:00
if int ( uh . code ) != 200 :
2013-01-07 20:01:14 +01:00
return None
uh . close ( )
2013-12-02 17:13:41 +01:00
try :
2014-06-25 18:06:42 +02:00
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 )
2013-01-23 20:24:02 +01:00
servers = { }
2013-12-02 17:13:41 +01:00
for server in elements :
try :
attrib = server . attrib
except AttributeError :
attrib = dict ( list ( server . attributes . items ( ) ) )
2013-01-07 20:01:14 +01:00
d = distance ( [ float ( client [ ' lat ' ] ) , float ( client [ ' lon ' ] ) ] ,
2013-01-12 15:29:52 +01:00
[ float ( attrib . get ( ' lat ' ) ) , float ( attrib . get ( ' lon ' ) ) ] )
2013-01-30 17:49:32 +01:00
attrib [ ' d ' ] = d
2013-01-23 20:24:02 +01:00
if d not in servers :
servers [ d ] = [ attrib ]
else :
servers [ d ] . append ( attrib )
2013-04-27 15:56:21 +02:00
del root
2013-12-02 17:13:41 +01:00
del serversxml
del elements
2013-01-07 20:01:14 +01:00
closest = [ ]
2013-01-23 00:52:46 +01:00
for d in sorted ( servers . keys ( ) ) :
for s in servers [ d ] :
closest . append ( s )
2013-01-30 17:49:32 +01:00
if len ( closest ) == 5 and not all :
2013-01-23 00:52:46 +01:00
break
else :
continue
break
2013-01-07 20:01:14 +01:00
del servers
return closest
2012-09-10 21:18:56 +02:00
2013-01-07 20:01:14 +01:00
def getBestServer ( servers ) :
2014-05-27 16:30:39 +02:00
""" Perform a speedtest.net latency request to determine which
speedtest . net server has the lowest latency
2013-01-07 20:01:14 +01:00
"""
results = { }
for server in servers :
2013-08-23 16:31:45 +02:00
cum = [ ]
2014-05-27 16:30:39 +02:00
url = ' %s /latency.txt ' % os . path . dirname ( server [ ' url ' ] )
urlparts = urlparse ( url )
2013-07-26 23:11:10 +02:00
for i in range ( 0 , 3 ) :
2013-11-04 23:00:33 +01:00
try :
2014-05-27 16:30:39 +02:00
if urlparts [ 0 ] == ' https ' :
h = HTTPSConnection ( urlparts [ 1 ] )
else :
h = HTTPConnection ( urlparts [ 1 ] )
2014-06-25 18:26:14 +02:00
start = timeit . default_timer ( )
2014-05-27 16:30:39 +02:00
h . request ( " GET " , urlparts [ 2 ] )
r = h . getresponse ( )
2014-06-25 18:26:14 +02:00
total = ( timeit . default_timer ( ) - start )
2014-07-30 00:14:41 +02:00
except ( HTTPError , URLError , socket . error ) :
2013-11-04 23:00:33 +01:00
cum . append ( 3600 )
continue
2014-05-27 16:30:39 +02:00
text = r . read ( 9 )
if int ( r . status ) == 200 and text == ' test=test ' . encode ( ) :
2013-08-23 16:31:45 +02:00
cum . append ( total )
2013-01-07 20:01:14 +01:00
else :
2013-08-23 16:31:45 +02:00
cum . append ( 3600 )
2014-05-27 16:30:39 +02:00
h . close ( )
avg = round ( ( sum ( cum ) / 6 ) * 1000 , 3 )
2013-01-07 20:01:14 +01:00
results [ avg ] = server
2013-01-23 00:52:46 +01:00
fastest = sorted ( results . keys ( ) ) [ 0 ]
best = results [ fastest ]
best [ ' latency ' ] = fastest
2013-01-07 20:01:14 +01:00
return best
2012-09-10 21:18:56 +02:00
2013-09-06 05:04:02 +02:00
def ctrl_c ( signum , frame ) :
2013-11-18 19:11:07 +01:00
""" Catch Ctrl-C key sequence and set a shutdown_event for our threaded
operations
"""
2013-09-06 05:04:02 +02:00
global shutdown_event
shutdown_event . set ( )
raise SystemExit ( ' \n Cancelling... ' )
2013-09-16 20:07:24 +02:00
def version ( ) :
2013-11-18 19:11:07 +01:00
""" Print the version """
2013-09-16 20:07:24 +02:00
raise SystemExit ( __version__ )
2013-01-07 20:01:14 +01:00
def speedtest ( ) :
""" Run the full speedtest.net test """
2012-09-10 21:18:56 +02:00
2013-11-11 17:04:32 +01:00
global shutdown_event , source
2013-09-06 05:04:02 +02:00
shutdown_event = threading . Event ( )
signal . signal ( signal . SIGINT , ctrl_c )
2013-01-24 21:40:29 +01:00
description = (
' Command line interface for testing internet bandwidth using '
' speedtest.net. \n '
' ------------------------------------------------------------ '
' -------------- \n '
' https://github.com/sivel/speedtest-cli ' )
parser = ArgParser ( description = description )
2013-11-18 19:11:07 +01:00
# Give optparse.OptionParser an `add_argument` method for
# compatibility with argparse.ArgumentParser
2013-01-24 21:40:29 +01:00
try :
parser . add_argument = parser . add_option
except AttributeError :
pass
2014-02-26 14:34:11 +01:00
parser . add_argument ( ' --bytes ' , dest = ' units ' , action = ' store_const ' ,
const = ( ' bytes ' , 1 ) , default = ( ' bits ' , 8 ) ,
help = ' Display values in bytes instead of bits. Does '
' not affect the image generated by --share ' )
2013-01-24 21:40:29 +01:00
parser . add_argument ( ' --share ' , action = ' store_true ' ,
help = ' Generate and provide a URL to the speedtest.net '
' share results image ' )
parser . add_argument ( ' --simple ' , action = ' store_true ' ,
help = ' Suppress verbose output, only show basic '
' information ' )
2013-01-30 17:49:32 +01:00
parser . add_argument ( ' --list ' , action = ' store_true ' ,
help = ' Display a list of speedtest.net servers '
' sorted by distance ' )
parser . add_argument ( ' --server ' , help = ' Specify a server ID to test against ' )
2013-07-26 23:34:07 +02:00
parser . add_argument ( ' --mini ' , help = ' URL of the Speedtest Mini server ' )
2013-11-11 17:04:32 +01:00
parser . add_argument ( ' --source ' , help = ' Source IP address to bind to ' )
2013-09-16 20:07:24 +02:00
parser . add_argument ( ' --version ' , action = ' store_true ' ,
help = ' Show the version number and exit ' )
2014-08-08 02:35:55 +02:00
parser . add_argument ( ' --rrdfile ' , help = ' Output results to an RRD file, three columns, ping (ms), download (bits per second), and upload (bits per second) ' )
2013-01-24 21:40:29 +01:00
options = parser . parse_args ( )
if isinstance ( options , tuple ) :
args = options [ 0 ]
else :
args = options
del options
2013-11-18 19:11:07 +01:00
# Print the version and exit
2013-09-16 20:07:24 +02:00
if args . version :
version ( )
2013-11-18 19:11:07 +01:00
# If specified bind to a specific IP address
2013-11-11 17:04:32 +01:00
if args . source :
source = args . source
socket . socket = bound_socket
2013-01-24 21:40:29 +01:00
if not args . simple :
2013-07-26 23:11:10 +02:00
print_ ( ' Retrieving speedtest.net configuration... ' )
2013-11-11 17:04:32 +01:00
try :
config = getConfig ( )
except URLError :
print_ ( ' Cannot retrieve speedtest configuration ' )
sys . exit ( 1 )
2012-09-10 21:18:56 +02:00
2013-01-24 21:40:29 +01:00
if not args . simple :
2013-07-26 23:11:10 +02:00
print_ ( ' Retrieving speedtest.net server list... ' )
2013-01-30 17:49:32 +01:00
if args . list or args . server :
servers = closestServers ( config [ ' client ' ] , True )
if args . list :
serverList = [ ]
for server in servers :
line = ( ' %(id)4s ) %(sponsor)s ( %(name)s , %(country)s ) '
' [ %(d)0.2f km] ' % server )
serverList . append ( line )
2013-12-02 19:59:56 +01:00
# 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
2013-01-30 17:49:32 +01:00
try :
2013-12-02 19:59:56 +01:00
unicode ( )
2013-07-26 23:11:10 +02:00
print_ ( ' \n ' . join ( serverList ) . encode ( ' utf-8 ' , ' ignore ' ) )
2013-12-02 19:59:56 +01:00
except NameError :
print_ ( ' \n ' . join ( serverList ) )
2013-01-30 17:49:32 +01:00
except IOError :
pass
sys . exit ( 0 )
else :
servers = closestServers ( config [ ' client ' ] )
2012-09-10 21:18:56 +02:00
2013-01-25 18:53:45 +01:00
if not args . simple :
2013-07-26 23:11:10 +02:00
print_ ( ' Testing from %(isp)s ( %(ip)s )... ' % config [ ' client ' ] )
2013-01-30 17:49:32 +01:00
if args . server :
try :
best = getBestServer ( filter ( lambda x : x [ ' id ' ] == args . server ,
servers ) )
except IndexError :
2013-07-26 23:11:10 +02:00
print_ ( ' Invalid server ID ' )
2013-01-30 17:49:32 +01:00
sys . exit ( 1 )
2013-07-29 00:25:19 +02:00
elif args . mini :
2013-07-26 23:34:07 +02:00
name , ext = os . path . splitext ( args . mini )
if ext :
url = os . path . dirname ( args . mini )
else :
url = args . mini
urlparts = urlparse ( url )
try :
f = urlopen ( args . mini )
except :
print_ ( ' Invalid Speedtest Mini URL ' )
sys . exit ( 1 )
else :
text = f . read ( )
f . close ( )
extension = re . findall ( ' upload_extension: " ([^ " ]+) " ' , text . decode ( ) )
2014-04-17 23:13:55 +02:00
if not extension :
for ext in [ ' php ' , ' asp ' , ' aspx ' , ' jsp ' ] :
try :
f = urlopen ( ' %s /speedtest/upload. %s ' % ( args . mini , ext ) )
except :
pass
else :
data = f . read ( ) . strip ( )
if ( f . code == 200 and
len ( data . splitlines ( ) ) == 1 and
re . match ( ' size=[0-9] ' , data ) ) :
extension = [ ext ]
break
2013-07-26 23:34:07 +02:00
if not urlparts or not extension :
print_ ( ' Please provide the full URL of your Speedtest Mini server ' )
sys . exit ( 1 )
servers = [ {
' sponsor ' : ' Speedtest Mini ' ,
' name ' : urlparts [ 1 ] ,
' d ' : 0 ,
' url ' : ' %s /speedtest/upload. %s ' % ( url . rstrip ( ' / ' ) , extension [ 0 ] ) ,
' latency ' : 0 ,
' id ' : 0
} ]
try :
best = getBestServer ( servers )
except :
best = servers [ 0 ]
2013-01-30 17:49:32 +01:00
else :
if not args . simple :
2014-05-27 16:30:39 +02:00
print_ ( ' Selecting best server based on latency... ' )
2013-01-30 17:49:32 +01:00
best = getBestServer ( servers )
2013-01-25 18:53:45 +01:00
2013-01-24 21:40:29 +01:00
if not args . simple :
2013-12-02 19:59:56 +01:00
# 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 ' ) )
except NameError :
print_ ( ' Hosted by %(sponsor)s ( %(name)s ) [ %(d)0.2f km]: '
' %(latency)s ms ' % best )
2013-01-24 21:40:29 +01:00
else :
2014-06-28 02:26:54 +02:00
print_ ( ' Ping: %(latency)s ms ' % best )
2013-01-07 20:01:14 +01:00
sizes = [ 350 , 500 , 750 , 1000 , 1500 , 2000 , 2500 , 3000 , 3500 , 4000 ]
urls = [ ]
for size in sizes :
2013-07-26 23:11:10 +02:00
for i in range ( 0 , 4 ) :
2013-01-07 20:01:14 +01:00
urls . append ( ' %s /random %s x %s .jpg ' %
( os . path . dirname ( best [ ' url ' ] ) , size , size ) )
2013-01-24 21:40:29 +01:00
if not args . simple :
2013-07-26 23:11:10 +02:00
print_ ( ' Testing download speed ' , end = ' ' )
2013-01-24 21:40:29 +01:00
dlspeed = downloadSpeed ( urls , args . simple )
if not args . simple :
2013-07-26 23:11:10 +02:00
print_ ( )
2014-02-26 14:34:11 +01:00
print_ ( ' Download: %0.2f M %s /s ' %
( ( dlspeed / 1000 / 1000 ) * args . units [ 1 ] , args . units [ 0 ] ) )
2013-01-07 20:01:14 +01:00
2013-04-15 14:03:10 +02:00
sizesizes = [ int ( .25 * 1000 * 1000 ) , int ( .5 * 1000 * 1000 ) ]
2013-01-07 20:01:14 +01:00
sizes = [ ]
for size in sizesizes :
2013-07-26 23:11:10 +02:00
for i in range ( 0 , 25 ) :
2013-01-07 20:01:14 +01:00
sizes . append ( size )
2013-01-24 21:40:29 +01:00
if not args . simple :
2013-07-26 23:11:10 +02:00
print_ ( ' Testing upload speed ' , end = ' ' )
2013-01-24 21:40:29 +01:00
ulspeed = uploadSpeed ( best [ ' url ' ] , sizes , args . simple )
if not args . simple :
2013-07-26 23:11:10 +02:00
print_ ( )
2014-02-26 14:34:11 +01:00
print_ ( ' Upload: %0.2f M %s /s ' %
( ( ulspeed / 1000 / 1000 ) * args . units [ 1 ] , args . units [ 0 ] ) )
2014-08-08 02:35:55 +02:00
if args . rrdfile :
import rrdtool
rrdping = int ( round ( best [ ' latency ' ] , 0 ) )
rrdDLSpeedBits = int ( round ( dlspeed * 8 , 0 ) )
rrdULSpeedBits = int ( round ( ulspeed * 8 , 0 ) )
ret = rrdtool . update ( args . rrdfile , ' N: ' + ` rrdping ` + ' : ' + ` rrdDLSpeedBits ` + ' : ' + ` rrdULSpeedBits ` ) ;
2013-01-24 21:40:29 +01:00
2013-07-26 23:34:07 +02:00
if args . share and args . mini :
print_ ( ' Cannot generate a speedtest.net share results image while '
' testing against a Speedtest Mini server ' )
elif args . share :
2013-04-15 14:03:10 +02:00
dlspeedk = int ( round ( ( dlspeed / 1000 ) * 8 , 0 ) )
2013-01-24 21:40:29 +01:00
ping = int ( round ( best [ ' latency ' ] , 0 ) )
2013-04-15 14:03:10 +02:00
ulspeedk = int ( round ( ( ulspeed / 1000 ) * 8 , 0 ) )
2013-01-24 21:40:29 +01:00
2013-11-18 19:11:07 +01:00
# Build the request to send results back to speedtest.net
# We use a list instead of a dict because the API expects parameters
# in a certain order
2013-01-24 21:40:29 +01:00
apiData = [
' download= %s ' % dlspeedk ,
' ping= %s ' % ping ,
' upload= %s ' % ulspeedk ,
' promo= ' ,
' startmode= %s ' % ' pingselect ' ,
' recommendedserverid= %s ' % best [ ' id ' ] ,
' accuracy= %s ' % 1 ,
' serverid= %s ' % best [ ' id ' ] ,
2013-07-26 23:11:10 +02:00
' hash= %s ' % md5 ( ( ' %s - %s - %s - %s ' %
( ping , ulspeedk , dlspeedk , ' 297aae72 ' ) )
. encode ( ) ) . hexdigest ( ) ]
2013-01-24 21:40:29 +01:00
2013-07-26 23:11:10 +02:00
req = Request ( ' http://www.speedtest.net/api/api.php ' ,
data = ' & ' . join ( apiData ) . encode ( ) )
2013-01-24 21:40:29 +01:00
req . add_header ( ' Referer ' , ' http://c.speedtest.net/flash/speedtest.swf ' )
2013-07-26 23:11:10 +02:00
f = urlopen ( req )
2013-01-24 21:40:29 +01:00
response = f . read ( )
code = f . code
f . close ( )
if int ( code ) != 200 :
2013-07-26 23:11:10 +02:00
print_ ( ' Could not submit results to speedtest.net ' )
2013-01-24 21:40:29 +01:00
sys . exit ( 1 )
2013-07-26 23:11:10 +02:00
qsargs = parse_qs ( response . decode ( ) )
2013-01-24 21:40:29 +01:00
resultid = qsargs . get ( ' resultid ' )
if not resultid or len ( resultid ) != 1 :
2013-07-26 23:11:10 +02:00
print_ ( ' Could not submit results to speedtest.net ' )
2013-01-24 21:40:29 +01:00
sys . exit ( 1 )
2013-07-26 23:11:10 +02:00
print_ ( ' Share results: http://www.speedtest.net/result/ %s .png ' %
2013-01-24 21:40:29 +01:00
resultid [ 0 ] )
2012-09-10 21:18:56 +02:00
2013-07-27 00:25:49 +02:00
def main ( ) :
2013-01-25 18:53:57 +01:00
try :
speedtest ( )
except KeyboardInterrupt :
2013-07-26 23:11:10 +02:00
print_ ( ' \n Cancelling... ' )
2013-01-01 21:02:45 +01:00
2013-10-08 14:09:55 +02:00
2013-07-27 00:25:49 +02:00
if __name__ == ' __main__ ' :
main ( )
2013-01-07 20:01:14 +01:00
# vim:ts=4:sw=4:expandtab