long_term

Speed Test

Jul 22nd, 2014
295
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 16.63 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Copyright 2013 Matt Martz
  4. # All Rights Reserved.
  5. #
  6. #    Licensed under the Apache License, Version 2.0 (the "License"); you may
  7. #    not use this file except in compliance with the License. You may obtain
  8. #    a copy of the License at
  9. #
  10. #         http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. #    Unless required by applicable law or agreed to in writing, software
  13. #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14. #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15. #    License for the specific language governing permissions and limitations
  16. #    under the License.
  17.  
  18. __version__ = '0.2.2'
  19.  
  20. try:
  21.     from urllib2 import urlopen, Request
  22. except ImportError:
  23.     from urllib.request import urlopen, Request
  24.  
  25. import math
  26. import time
  27. import os
  28. import sys
  29. import threading
  30. import re
  31. import signal
  32. from xml.dom import minidom as DOM
  33.  
  34. try:
  35.     from Queue import Queue
  36. except ImportError:
  37.     from queue import Queue
  38.  
  39. try:
  40.     from urlparse import urlparse
  41. except ImportError:
  42.     from urllib.parse import urlparse
  43.  
  44. try:
  45.     from urlparse import parse_qs
  46. except ImportError:
  47.     try:
  48.         from urllib.parse import parse_qs
  49.     except ImportError:
  50.         from cgi import parse_qs
  51.  
  52. try:
  53.     from hashlib import md5
  54. except ImportError:
  55.     from md5 import md5
  56.  
  57. try:
  58.     from argparse import ArgumentParser as ArgParser
  59. except ImportError:
  60.     from optparse import OptionParser as ArgParser
  61.  
  62. try:
  63.     import builtins
  64. except ImportError:
  65.     def print_(*args, **kwargs):
  66.         """The new-style print function taken from
  67.        https://pypi.python.org/pypi/six/
  68.  
  69.        """
  70.         fp = kwargs.pop("file", sys.stdout)
  71.         if fp is None:
  72.             return
  73.  
  74.         def write(data):
  75.             if not isinstance(data, basestring):
  76.                 data = str(data)
  77.             fp.write(data)
  78.  
  79.         want_unicode = False
  80.         sep = kwargs.pop("sep", None)
  81.         if sep is not None:
  82.             if isinstance(sep, unicode):
  83.                 want_unicode = True
  84.             elif not isinstance(sep, str):
  85.                 raise TypeError("sep must be None or a string")
  86.         end = kwargs.pop("end", None)
  87.         if end is not None:
  88.             if isinstance(end, unicode):
  89.                 want_unicode = True
  90.             elif not isinstance(end, str):
  91.                 raise TypeError("end must be None or a string")
  92.         if kwargs:
  93.             raise TypeError("invalid keyword arguments to print()")
  94.         if not want_unicode:
  95.             for arg in args:
  96.                 if isinstance(arg, unicode):
  97.                     want_unicode = True
  98.                     break
  99.         if want_unicode:
  100.             newline = unicode("\n")
  101.             space = unicode(" ")
  102.         else:
  103.             newline = "\n"
  104.             space = " "
  105.         if sep is None:
  106.             sep = space
  107.         if end is None:
  108.             end = newline
  109.         for i, arg in enumerate(args):
  110.             if i:
  111.                 write(sep)
  112.             write(arg)
  113.         write(end)
  114. else:
  115.     print_ = getattr(builtins, 'print')
  116.     del builtins
  117.  
  118.  
  119. def distance(origin, destination):
  120.     """Determine distance between 2 sets of [lat,lon] in km"""
  121.  
  122.     lat1, lon1 = origin
  123.     lat2, lon2 = destination
  124.     radius = 6371  # km
  125.  
  126.     dlat = math.radians(lat2 - lat1)
  127.     dlon = math.radians(lon2 - lon1)
  128.     a = (math.sin(dlat / 2) * math.sin(dlat / 2) + math.cos(math.radians(lat1))
  129.          * math.cos(math.radians(lat2)) * math.sin(dlon / 2)
  130.          * math.sin(dlon / 2))
  131.     c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
  132.     d = radius * c
  133.  
  134.     return d
  135.  
  136.  
  137. class FileGetter(threading.Thread):
  138.     def __init__(self, url, start):
  139.         self.url = url
  140.         self.result = None
  141.         self.starttime = start
  142.         threading.Thread.__init__(self)
  143.  
  144.     def run(self):
  145.         self.result = [0]
  146.         try:
  147.             if (time.time() - self.starttime) <= 10:
  148.                 f = urlopen(self.url)
  149.                 while 1 and not shutdown_event.isSet():
  150.                     self.result.append(len(f.read(10240)))
  151.                     if self.result[-1] == 0:
  152.                         break
  153.                 f.close()
  154.         except IOError:
  155.             pass
  156.  
  157.  
  158. def downloadSpeed(files, quiet=False):
  159.     start = time.time()
  160.  
  161.     def producer(q, files):
  162.         for file in files:
  163.             thread = FileGetter(file, start)
  164.             thread.start()
  165.             q.put(thread, True)
  166.             if not quiet and not shutdown_event.isSet():
  167.                 sys.stdout.write('.')
  168.                 sys.stdout.flush()
  169.  
  170.     finished = []
  171.  
  172.     def consumer(q, total_files):
  173.         while len(finished) < total_files:
  174.             thread = q.get(True)
  175.             while thread.isAlive():
  176.                 thread.join(timeout=0.1)
  177.             finished.append(sum(thread.result))
  178.             del thread
  179.  
  180.     q = Queue(6)
  181.     prod_thread = threading.Thread(target=producer, args=(q, files))
  182.     cons_thread = threading.Thread(target=consumer, args=(q, len(files)))
  183.     start = time.time()
  184.     prod_thread.start()
  185.     cons_thread.start()
  186.     while prod_thread.isAlive():
  187.         prod_thread.join(timeout=0.1)
  188.     while cons_thread.isAlive():
  189.         cons_thread.join(timeout=0.1)
  190.     return (sum(finished) / (time.time() - start))
  191.  
  192.  
  193. class FilePutter(threading.Thread):
  194.     def __init__(self, url, start, size):
  195.         self.url = url
  196.         chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  197.         data = chars * (int(round(int(size) / 36.0)))
  198.         self.data = ('content1=%s' % data[0:int(size) - 9]).encode()
  199.         del data
  200.         self.result = None
  201.         self.starttime = start
  202.         threading.Thread.__init__(self)
  203.  
  204.     def run(self):
  205.         try:
  206.             if ((time.time() - self.starttime) <= 10 and
  207.                     not shutdown_event.isSet()):
  208.                 f = urlopen(self.url, self.data)
  209.                 f.read(11)
  210.                 f.close()
  211.                 self.result = len(self.data)
  212.             else:
  213.                 self.result = 0
  214.         except IOError:
  215.             self.result = 0
  216.  
  217.  
  218. def uploadSpeed(url, sizes, quiet=False):
  219.     start = time.time()
  220.  
  221.     def producer(q, sizes):
  222.         for size in sizes:
  223.             thread = FilePutter(url, start, size)
  224.             thread.start()
  225.             q.put(thread, True)
  226.             if not quiet and not shutdown_event.isSet():
  227.                 sys.stdout.write('.')
  228.                 sys.stdout.flush()
  229.  
  230.     finished = []
  231.  
  232.     def consumer(q, total_sizes):
  233.         while len(finished) < total_sizes:
  234.             thread = q.get(True)
  235.             while thread.isAlive():
  236.                 thread.join(timeout=0.1)
  237.             finished.append(thread.result)
  238.             del thread
  239.  
  240.     q = Queue(6)
  241.     prod_thread = threading.Thread(target=producer, args=(q, sizes))
  242.     cons_thread = threading.Thread(target=consumer, args=(q, len(sizes)))
  243.     start = time.time()
  244.     prod_thread.start()
  245.     cons_thread.start()
  246.     while prod_thread.isAlive():
  247.         prod_thread.join(timeout=0.1)
  248.     while cons_thread.isAlive():
  249.         cons_thread.join(timeout=0.1)
  250.     return (sum(finished) / (time.time() - start))
  251.  
  252.  
  253. def getAttributesByTagName(dom, tagName):
  254.     elem = dom.getElementsByTagName(tagName)[0]
  255.     return dict(list(elem.attributes.items()))
  256.  
  257.  
  258. def getConfig():
  259.     """Download the speedtest.net configuration and return only the data
  260.    we are interested in
  261.    """
  262.  
  263.     uh = urlopen('http://www.speedtest.net/speedtest-config.php')
  264.     configxml = uh.read()
  265.     if int(uh.code) != 200:
  266.         return None
  267.     uh.close()
  268.     root = DOM.parseString(configxml)
  269.     config = {
  270.         'client': getAttributesByTagName(root, 'client'),
  271.         'times': getAttributesByTagName(root, 'times'),
  272.         'download': getAttributesByTagName(root, 'download'),
  273.         'upload': getAttributesByTagName(root, 'upload')}
  274.  
  275.     del root
  276.     return config
  277.  
  278.  
  279. def closestServers(client, all=False):
  280.     """Determine the 5 closest speedtest.net servers based on geographic
  281.    distance
  282.    """
  283.  
  284.     uh = urlopen('http://www.speedtest.net/speedtest-servers.php')
  285.     serversxml = uh.read()
  286.     if int(uh.code) != 200:
  287.         return None
  288.     uh.close()
  289.     root = DOM.parseString(serversxml)
  290.     servers = {}
  291.     for server in root.getElementsByTagName('server'):
  292.         attrib = dict(list(server.attributes.items()))
  293.         d = distance([float(client['lat']), float(client['lon'])],
  294.                      [float(attrib.get('lat')), float(attrib.get('lon'))])
  295.         attrib['d'] = d
  296.         if d not in servers:
  297.             servers[d] = [attrib]
  298.         else:
  299.             servers[d].append(attrib)
  300.     del root
  301.  
  302.     closest = []
  303.     for d in sorted(servers.keys()):
  304.         for s in servers[d]:
  305.             closest.append(s)
  306.             if len(closest) == 5 and not all:
  307.                 break
  308.         else:
  309.             continue
  310.         break
  311.  
  312.     del servers
  313.     return closest
  314.  
  315.  
  316. def getBestServer(servers):
  317.     """Perform a speedtest.net "ping" to determine which speedtest.net
  318.    server has the lowest latency
  319.    """
  320.  
  321.     results = {}
  322.     for server in servers:
  323.         cum = []
  324.         url = os.path.dirname(server['url'])
  325.         for i in range(0, 3):
  326.             uh = urlopen('%s/latency.txt' % url)
  327.             start = time.time()
  328.             text = uh.read(9)
  329.             total = time.time() - start
  330.             if int(uh.code) == 200 and text == 'test=test'.encode():
  331.                 cum.append(total)
  332.             else:
  333.                 cum.append(3600)
  334.             uh.close()
  335.         avg = round((sum(cum) / 3) * 1000000, 3)
  336.         results[avg] = server
  337.  
  338.     fastest = sorted(results.keys())[0]
  339.     best = results[fastest]
  340.     best['latency'] = fastest
  341.  
  342.     return best
  343.  
  344.  
  345. def ctrl_c(signum, frame):
  346.     global shutdown_event
  347.     shutdown_event.set()
  348.     raise SystemExit('\nCancelling...')
  349.  
  350.  
  351. def version():
  352.     raise SystemExit(__version__)
  353.  
  354.  
  355. def speedtest():
  356.     """Run the full speedtest.net test"""
  357.  
  358.     global shutdown_event
  359.     shutdown_event = threading.Event()
  360.  
  361.     signal.signal(signal.SIGINT, ctrl_c)
  362.  
  363.     description = (
  364.         'Command line interface for testing internet bandwidth using '
  365.         'speedtest.net.\n'
  366.         '------------------------------------------------------------'
  367.         '--------------\n'
  368.         'https://github.com/sivel/speedtest-cli')
  369.  
  370.     parser = ArgParser(description=description)
  371.     try:
  372.         parser.add_argument = parser.add_option
  373.     except AttributeError:
  374.         pass
  375.     parser.add_argument('--share', action='store_true',
  376.                         help='Generate and provide a URL to the speedtest.net '
  377.                              'share results image')
  378.     parser.add_argument('--simple', action='store_true',
  379.                         help='Suppress verbose output, only show basic '
  380.                              'information')
  381.     parser.add_argument('--list', action='store_true',
  382.                         help='Display a list of speedtest.net servers '
  383.                              'sorted by distance')
  384.     parser.add_argument('--server', help='Specify a server ID to test against')
  385.     parser.add_argument('--mini', help='URL of the Speedtest Mini server')
  386.     parser.add_argument('--version', action='store_true',
  387.                         help='Show the version number and exit')
  388.  
  389.     options = parser.parse_args()
  390.     if isinstance(options, tuple):
  391.         args = options[0]
  392.     else:
  393.         args = options
  394.     del options
  395.  
  396.     if args.version:
  397.         version()
  398.  
  399.     if not args.simple:
  400.         print_('Retrieving speedtest.net configuration...')
  401.     config = getConfig()
  402.  
  403.     if not args.simple:
  404.         print_('Retrieving speedtest.net server list...')
  405.     if args.list or args.server:
  406.         servers = closestServers(config['client'], True)
  407.         if args.list:
  408.             serverList = []
  409.             for server in servers:
  410.                 line = ('%(id)4s) %(sponsor)s (%(name)s, %(country)s) '
  411.                         '[%(d)0.2f km]' % server)
  412.                 serverList.append(line)
  413.             try:
  414.                 print_('\n'.join(serverList).encode('utf-8', 'ignore'))
  415.             except IOError:
  416.                 pass
  417.             sys.exit(0)
  418.     else:
  419.         servers = closestServers(config['client'])
  420.  
  421.     if not args.simple:
  422.         print_('Testing from %(isp)s (%(ip)s)...' % config['client'])
  423.  
  424.     if args.server:
  425.         try:
  426.             best = getBestServer(filter(lambda x: x['id'] == args.server,
  427.                                         servers))
  428.         except IndexError:
  429.             print_('Invalid server ID')
  430.             sys.exit(1)
  431.     elif args.mini:
  432.         name, ext = os.path.splitext(args.mini)
  433.         if ext:
  434.             url = os.path.dirname(args.mini)
  435.         else:
  436.             url = args.mini
  437.         urlparts = urlparse(url)
  438.         try:
  439.             f = urlopen(args.mini)
  440.         except:
  441.             print_('Invalid Speedtest Mini URL')
  442.             sys.exit(1)
  443.         else:
  444.             text = f.read()
  445.             f.close()
  446.         extension = re.findall('upload_extension: "([^"]+)"', text.decode())
  447.         if not urlparts or not extension:
  448.             print_('Please provide the full URL of your Speedtest Mini server')
  449.             sys.exit(1)
  450.         servers = [{
  451.             'sponsor': 'Speedtest Mini',
  452.             'name': urlparts[1],
  453.             'd': 0,
  454.             'url': '%s/speedtest/upload.%s' % (url.rstrip('/'), extension[0]),
  455.             'latency': 0,
  456.             'id': 0
  457.         }]
  458.         try:
  459.             best = getBestServer(servers)
  460.         except:
  461.             best = servers[0]
  462.     else:
  463.         if not args.simple:
  464.             print_('Selecting best server based on ping...')
  465.         best = getBestServer(servers)
  466.  
  467.     if not args.simple:
  468.         print_('Hosted by %(sponsor)s (%(name)s) [%(d)0.2f km]: '
  469.                '%(latency)s ms' % best)
  470.     else:
  471.         print_('Ping: %(latency)s ms' % best)
  472.  
  473.     sizes = [350, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000]
  474.     urls = []
  475.     for size in sizes:
  476.         for i in range(0, 4):
  477.             urls.append('%s/random%sx%s.jpg' %
  478.                         (os.path.dirname(best['url']), size, size))
  479.     if not args.simple:
  480.         print_('Testing download speed', end='')
  481.     dlspeed = downloadSpeed(urls, args.simple)
  482.     if not args.simple:
  483.         print_()
  484.     print_('Download: %0.2f Mbit/s' % ((dlspeed / 1000 / 1000) * 8))
  485.  
  486.     sizesizes = [int(.25 * 1000 * 1000), int(.5 * 1000 * 1000)]
  487.     sizes = []
  488.     for size in sizesizes:
  489.         for i in range(0, 25):
  490.             sizes.append(size)
  491.     if not args.simple:
  492.         print_('Testing upload speed', end='')
  493.     ulspeed = uploadSpeed(best['url'], sizes, args.simple)
  494.     if not args.simple:
  495.         print_()
  496.     print_('Upload: %0.2f Mbit/s' % ((ulspeed / 1000 / 1000) * 8))
  497.  
  498.     if args.share and args.mini:
  499.         print_('Cannot generate a speedtest.net share results image while '
  500.                'testing against a Speedtest Mini server')
  501.     elif args.share:
  502.         dlspeedk = int(round((dlspeed / 1000) * 8, 0))
  503.         ping = int(round(best['latency'], 0))
  504.         ulspeedk = int(round((ulspeed / 1000) * 8, 0))
  505.  
  506.         apiData = [
  507.             'download=%s' % dlspeedk,
  508.             'ping=%s' % ping,
  509.             'upload=%s' % ulspeedk,
  510.             'promo=',
  511.             'startmode=%s' % 'pingselect',
  512.             'recommendedserverid=%s' % best['id'],
  513.             'accuracy=%s' % 1,
  514.             'serverid=%s' % best['id'],
  515.             'hash=%s' % md5(('%s-%s-%s-%s' %
  516.                              (ping, ulspeedk, dlspeedk, '297aae72'))
  517.                             .encode()).hexdigest()]
  518.  
  519.         req = Request('http://www.speedtest.net/api/api.php',
  520.                       data='&'.join(apiData).encode())
  521.         req.add_header('Referer', 'http://c.speedtest.net/flash/speedtest.swf')
  522.         f = urlopen(req)
  523.         response = f.read()
  524.         code = f.code
  525.         f.close()
  526.  
  527.         if int(code) != 200:
  528.             print_('Could not submit results to speedtest.net')
  529.             sys.exit(1)
  530.  
  531.         qsargs = parse_qs(response.decode())
  532.         resultid = qsargs.get('resultid')
  533.         if not resultid or len(resultid) != 1:
  534.             print_('Could not submit results to speedtest.net')
  535.             sys.exit(1)
  536.  
  537.         print_('Share results: http://www.speedtest.net/result/%s.png' %
  538.                resultid[0])
  539.  
  540.  
  541. def main():
  542.     try:
  543.         speedtest()
  544.     except KeyboardInterrupt:
  545.         print_('\nCancelling...')
  546.  
  547.  
  548. if __name__ == '__main__':
  549.     main()
  550.  
  551. # vim:ts=4:sw=4:expandtab
Advertisement
Add Comment
Please, Sign In to add comment