Compare commits
	
		
			204 Commits
		
	
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 22210ca352 | |
|  | 42e96b13dd | |
|  | cadc68b5ae | |
|  | db46af8bcd | |
|  | c58ad3367b | |
|  | 266e53c256 | |
|  | 7ebb9965dd | |
|  | 2658bd50b4 | |
|  | 81bba6070c | |
|  | 681cdf20a5 | |
|  | cdf6002865 | |
|  | 9af203652b | |
|  | 2d5a9ef364 | |
|  | 3109fcf407 | |
|  | 69ddff1a11 | |
|  | fb0569946d | |
|  | f356c7b02d | |
|  | 6cf43b2ff7 | |
|  | 217ce8eff1 | |
|  | b43334f1ec | |
|  | b0b826c870 | |
|  | 9ac1091eae | |
|  | ca2250f700 | |
|  | ddb8db0c94 | |
|  | 72bf53affa | |
|  | a8a3265001 | |
|  | b2654de410 | |
|  | 72ed585c6f | |
|  | 41e599f9c3 | |
|  | c7530bb143 | |
|  | 4ceae77401 | |
|  | 9e185e8f88 | |
|  | 9c2977acfc | |
|  | f8aa20ecdf | |
|  | 8ff923b0fb | |
|  | 35c3ee20ed | |
|  | 0a7823db7a | |
|  | 27a8301192 | |
|  | ee2e647b9b | |
|  | 831c079113 | |
|  | 4f4c1dd8d1 | |
|  | 2c847a1849 | |
|  | e1bab1ab55 | |
|  | 48a3d33ae4 | |
|  | c16ffd4ae7 | |
|  | 9848481d06 | |
|  | 4737a69f10 | |
|  | 6381ba3742 | |
|  | fa2e15ee08 | |
|  | eab354603f | |
|  | e80ccc4647 | |
|  | 5fbe593fc8 | |
|  | f70cc86222 | |
|  | 5c061da8e0 | |
|  | 4457fe9fb8 | |
|  | b27f69d1ad | |
|  | 5a9f82a20a | |
|  | 3cb44f5630 | |
|  | 16054cc3bc | |
|  | d9642b2047 | |
|  | f3a607feb2 | |
|  | 6bfa5922c3 | |
|  | ca72d40033 | |
|  | 3ebb9734a2 | |
|  | 8854d82049 | |
|  | f2a97baf1e | |
|  | 6531677346 | |
|  | 6556be190a | |
|  | 2fe34ecf4e | |
|  | 0e585cbf64 | |
|  | 2fe369fdf8 | |
|  | b33c7533df | |
|  | fe864f6dce | |
|  | 10b3b09f02 | |
|  | 20e5d12a5c | |
|  | 6603954e45 | |
|  | e982830350 | |
|  | 2c89c53a79 | |
|  | 401c469991 | |
|  | 3c1c9d3179 | |
|  | e2f815618b | |
|  | 955a756c96 | |
|  | ceef55488c | |
|  | 20eeadcb0c | |
|  | 4aebe01c3e | |
|  | 1871b26b9a | |
|  | 824c584658 | |
|  | 9806e401e0 | |
|  | 1642d0669f | |
|  | 2e79fbf1dc | |
|  | f20e8808a8 | |
|  | 3feb38d9d4 | |
|  | d712f947d9 | |
|  | 55b3cf14a3 | |
|  | 33e498beb3 | |
|  | 068d71597b | |
|  | 1863c35f6b | |
|  | 823d7dc2b7 | |
|  | 411f1609e8 | |
|  | 7b38e264bc | |
|  | 2acba6ecd7 | |
|  | 53ef57bd5e | |
|  | c512684ffa | |
|  | 6685d91729 | |
|  | 91270dbc67 | |
|  | b75ecc291c | |
|  | 1d6717e714 | |
|  | d41cfc0cb1 | |
|  | 59880107a7 | |
|  | 4280c448cf | |
|  | 9f44a72fdb | |
|  | b075152e3e | |
|  | 2be4d0a5e7 | |
|  | 9299e0860c | |
|  | 292e250990 | |
|  | fd8b8cfa92 | |
|  | 9e3a5b3a59 | |
|  | 53b760dfba | |
|  | d0c927e8ae | |
|  | 6fffcd5b63 | |
|  | f88c41f97f | |
|  | 64b03777da | |
|  | 01abb3ae71 | |
|  | 884c7fed87 | |
|  | 38870b69ea | |
|  | 4bd4b7dfec | |
|  | 5e0bd05c81 | |
|  | b3f9a48cbb | |
|  | 478f9affdd | |
|  | 9ccce5d861 | |
|  | 2a4990c96c | |
|  | 07c38d7194 | |
|  | c5f75f783e | |
|  | 050da542b3 | |
|  | e14f7ed108 | |
|  | a7e9bc695e | |
|  | 77db2ea8f4 | |
|  | e4218c7612 | |
|  | 69bae532c5 | |
|  | 4f7f367391 | |
|  | 08e87f4c54 | |
|  | aa52e550bf | |
|  | 537c5aeda0 | |
|  | 95fe038752 | |
|  | 1c0a029ca6 | |
|  | 9913b9915f | |
|  | a4cb217522 | |
|  | d09ec27cb2 | |
|  | 446e6eb27e | |
|  | 81182c1c94 | |
|  | 51014d5a70 | |
|  | 65145d9aae | |
|  | 308c530f07 | |
|  | abe85d85ff | |
|  | d1b1185bfc | |
|  | 713860a4b4 | |
|  | ff606d0ec1 | |
|  | 3f22a9d815 | |
|  | cb6dee8a77 | |
|  | 7b09d8759f | |
|  | 25d845362c | |
|  | 4b9662e0b3 | |
|  | 93951f1154 | |
|  | 0e6b85d4d5 | |
|  | 6ab5f27300 | |
|  | 2ee26bbf54 | |
|  | 514b310484 | |
|  | 918e70e66d | |
|  | bae642ccde | |
|  | 1df3e76b19 | |
|  | 1e44e9e2f1 | |
|  | 51d0d88b96 | |
|  | 47c17d4a49 | |
|  | d1be67be48 | |
|  | 075cfda9cf | |
|  | 3c04dfefd3 | |
|  | ffd2c7f963 | |
|  | aef4a78831 | |
|  | 72da41e4fc | |
|  | cb77da3d37 | |
|  | 790720b33a | |
|  | 3a31df31c1 | |
|  | 7383ad97af | |
|  | 3cc06168f5 | |
|  | 3ee45cace8 | |
|  | b0e1e58a0b | |
|  | 60c3ec2a5e | |
|  | 65c85a9b15 | |
|  | 795bc51da4 | |
|  | 6c8dd05872 | |
|  | 759ef15636 | |
|  | f907418e6e | |
|  | fe93e9ed75 | |
|  | cea45762ca | |
|  | 328b851a07 | |
|  | ec21971a10 | |
|  | 3558b22de1 | |
|  | c0cd0d1666 | |
|  | 3655a31ac1 | |
|  | c1b9a0db0a | |
|  | b14e104ad1 | |
|  | f1647f2c9e | |
|  | 5bbc3e8bb0 | |
|  | 807df51c83 | 
							
								
								
									
										72
									
								
								.travis.yml
								
								
								
								
							
							
						
						
									
										72
									
								
								.travis.yml
								
								
								
								
							|  | @ -1,35 +1,61 @@ | |||
| language: python | ||||
| sudo: required | ||||
| dist: xenial | ||||
| 
 | ||||
| python: | ||||
|  - 2.7 | ||||
| addons: | ||||
|   apt: | ||||
|     sources: | ||||
|       - deadsnakes | ||||
|     packages: | ||||
|       - python2.4 | ||||
|       - python2.5 | ||||
|       - python2.6 | ||||
|       - python3.2 | ||||
|       - python3.3 | ||||
| 
 | ||||
| env: | ||||
|  - TOXENV=py24 | ||||
|  - TOXENV=py25 | ||||
|  - TOXENV=py26 | ||||
|  - TOXENV=py27 | ||||
|  - TOXENV=py31 | ||||
|  - TOXENV=py32 | ||||
|  - TOXENV=py33 | ||||
|  - TOXENV=py34 | ||||
|  - TOXENV=pypy | ||||
|  - TOXENV=flake8 | ||||
| matrix: | ||||
|   include: | ||||
|     - python: 2.7 | ||||
|       env: TOXENV=flake8 | ||||
|     - python: 2.7 | ||||
|       env: TOXENV=py24 | ||||
|     - python: 2.7 | ||||
|       env: TOXENV=py25 | ||||
|     - python: 2.7 | ||||
|       env: TOXENV=py26 | ||||
|     - python: 2.7 | ||||
|       env: TOXENV=py27 | ||||
|     - python: 2.7 | ||||
|       env: TOXENV=py32 | ||||
|     - python: 2.7 | ||||
|       env: TOXENV=py33 | ||||
|     - python: 3.4 | ||||
|       env: TOXENV=py34 | ||||
|     - python: 3.5 | ||||
|       env: TOXENV=py35 | ||||
|     - python: 3.6 | ||||
|       env: TOXENV=py36 | ||||
|     - python: 3.7 | ||||
|       env: TOXENV=py37 | ||||
|     - python: 3.8-dev | ||||
|       env: TOXENV=py38 | ||||
|     - python: pypy | ||||
|       env: TOXENV=pypy | ||||
|       dist: trusty | ||||
| 
 | ||||
| before_install: | ||||
|  - if [[ $(echo "$TOXENV" | egrep -c "(py2[45]|py3[14])") != 0 ]]; then sudo add-apt-repository -y ppa:fkrull/deadsnakes; fi; | ||||
|  - if [[ $(echo "$TOXENV" | egrep -c "(py2[45]|py3[14])") != 0 ]]; then sudo apt-get update -qq; fi; | ||||
|  - if [[ "$TOXENV" == "py24" ]]; then sudo apt-get install -y python2.4; fi; | ||||
|  - if [[ "$TOXENV" == "py25" ]]; then sudo apt-get install -y python2.5; fi; | ||||
|  - if [[ "$TOXENV" == "py31" ]]; then sudo apt-get install -y python3.1; fi; | ||||
|  - if [[ "$TOXENV" == "py34" ]]; then sudo apt-get install -y python3.4; fi; | ||||
|  - if [[ "$TOXENV" == "pypy" ]]; then sudo apt-get install -y pypy; fi; | ||||
|   - if [[ $(echo "$TOXENV" | egrep -c "py35") != 0 ]]; then pyenv global system 3.5; fi; | ||||
| 
 | ||||
| install: | ||||
|  - if [[ $(echo "$TOXENV" | egrep -c "(py2[45]|py31)") != 0 ]]; then pip install virtualenv==1.7.2 tox==1.3; fi; | ||||
|  - if [[ $(echo "$TOXENV" | egrep -c "(py2[45]|py31)") == 0 ]]; then pip install tox; fi; | ||||
|   - if [[ $(echo "$TOXENV" | egrep -c "py32") != 0 ]]; then pip install setuptools==17.1.1; fi; | ||||
|   - if [[ $(echo "$TOXENV" | egrep -c "(py2[45]|py3[12])") != 0 ]]; then pip install virtualenv==1.7.2 tox==1.3; fi; | ||||
|   - if [[ $(echo "$TOXENV" | egrep -c "(py26|py33)") != 0 ]]; then pip install virtualenv==15.2.0 tox==2.9.1; fi; | ||||
|   - if [[ $(echo "$TOXENV" | egrep -c "(py2[456]|py3[123])") == 0 ]]; then pip install tox; fi; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| script: | ||||
|  - tox | ||||
|   - tox | ||||
| 
 | ||||
| notifications: | ||||
|   email: | ||||
|  |  | |||
|  | @ -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. | ||||
|  |  | |||
							
								
								
									
										102
									
								
								README.rst
								
								
								
								
							
							
						
						
									
										102
									
								
								README.rst
								
								
								
								
							|  | @ -4,20 +4,24 @@ speedtest-cli | |||
| Command line interface for testing internet bandwidth using | ||||
| speedtest.net | ||||
| 
 | ||||
| .. image:: https://pypip.in/v/speedtest-cli/badge.png | ||||
| .. image:: https://img.shields.io/pypi/v/speedtest-cli.svg | ||||
|         :target: https://pypi.python.org/pypi/speedtest-cli/ | ||||
|         :alt: Latest Version | ||||
| .. image:: https://pypip.in/d/speedtest-cli/badge.png | ||||
| .. image:: https://img.shields.io/travis/sivel/speedtest-cli.svg | ||||
|         :target: https://pypi.python.org/pypi/speedtest-cli/ | ||||
|         :alt: Downloads | ||||
| .. image:: https://pypip.in/license/speedtest-cli/badge.png | ||||
|         :alt: Travis | ||||
| .. image:: https://img.shields.io/pypi/l/speedtest-cli.svg | ||||
|         :target: https://pypi.python.org/pypi/speedtest-cli/ | ||||
|         :alt: License | ||||
| 
 | ||||
| Versions | ||||
| -------- | ||||
| 
 | ||||
| speedtest-cli works with Python 2.4-3.4 | ||||
| speedtest-cli works with Python 2.4-3.7 | ||||
| 
 | ||||
| .. image:: https://img.shields.io/pypi/pyversions/speedtest-cli.svg | ||||
|         :target: https://pypi.python.org/pypi/speedtest-cli/ | ||||
|         :alt: Versions | ||||
| 
 | ||||
| Installation | ||||
| ------------ | ||||
|  | @ -47,21 +51,22 @@ or | |||
| :: | ||||
| 
 | ||||
|     git clone https://github.com/sivel/speedtest-cli.git | ||||
|     python speedtest-cli/setup.py install | ||||
|     cd speedtest-cli | ||||
|     python setup.py install | ||||
| 
 | ||||
| Just download (Like the way it used to be) | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| :: | ||||
| 
 | ||||
|     wget -O speedtest-cli https://raw.github.com/sivel/speedtest-cli/master/speedtest_cli.py | ||||
|     wget -O speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py | ||||
|     chmod +x speedtest-cli | ||||
| 
 | ||||
| or | ||||
| 
 | ||||
| :: | ||||
| 
 | ||||
|     curl -o speedtest-cli https://raw.github.com/sivel/speedtest-cli/master/speedtest_cli.py | ||||
|     curl -Lo speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py | ||||
|     chmod +x speedtest-cli | ||||
| 
 | ||||
| Usage | ||||
|  | @ -70,21 +75,80 @@ Usage | |||
| :: | ||||
| 
 | ||||
|     $ speedtest-cli -h | ||||
|     usage: speedtest-cli [-h] [--share] [--simple] [--list] [--server SERVER] | ||||
|                          [--mini MINI] [--source SOURCE] [--version] | ||||
|     usage: speedtest-cli [-h] [--no-download] [--no-upload] [--single] [--bytes] | ||||
|                          [--share] [--simple] [--csv] | ||||
|                          [--csv-delimiter CSV_DELIMITER] [--csv-header] [--json] | ||||
|                          [--list] [--server SERVER] [--exclude EXCLUDE] | ||||
|                          [--mini MINI] [--source SOURCE] [--timeout TIMEOUT] | ||||
|                          [--secure] [--no-pre-allocate] [--version] | ||||
| 
 | ||||
|     Command line interface for testing internet bandwidth using speedtest.net. | ||||
|     -------------------------------------------------------------------------- | ||||
|     https://github.com/sivel/speedtest-cli | ||||
| 
 | ||||
|     optional arguments: | ||||
|       -h, --help       show this help message and exit | ||||
|       --share          Generate and provide a URL to the speedtest.net share | ||||
|                        results image | ||||
|       --simple         Suppress verbose output, only show basic information | ||||
|       --list           Display a list of speedtest.net servers sorted by distance | ||||
|       --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 | ||||
|       --version        Show the version number and exit | ||||
|       -h, --help            show this help message and exit | ||||
|       --no-download         Do not perform download test | ||||
|       --no-upload           Do not perform upload test | ||||
|       --single              Only use a single connection instead of multiple. This | ||||
|                             simulates a typical file transfer. | ||||
|       --bytes               Display values in bytes instead of bits. Does not | ||||
|                             affect the image generated by --share, nor output from | ||||
|                             --json or --csv | ||||
|       --share               Generate and provide a URL to the speedtest.net share | ||||
|                             results image, not displayed with --csv | ||||
|       --simple              Suppress verbose output, only show basic information | ||||
|       --csv                 Suppress verbose output, only show basic information | ||||
|                             in CSV format. Speeds listed in bit/s and not affected | ||||
|                             by --bytes | ||||
|       --csv-delimiter CSV_DELIMITER | ||||
|                             Single character delimiter to use in CSV output. | ||||
|                             Default "," | ||||
|       --csv-header          Print CSV headers | ||||
|       --json                Suppress verbose output, only show basic information | ||||
|                             in JSON format. Speeds listed in bit/s and not | ||||
|                             affected by --bytes | ||||
|       --list                Display a list of speedtest.net servers sorted by | ||||
|                             distance | ||||
|       --server SERVER       Specify a server ID to test against. Can be supplied | ||||
|                             multiple times | ||||
|       --exclude EXCLUDE     Exclude a server from selection. Can be supplied | ||||
|                             multiple times | ||||
|       --mini MINI           URL of the Speedtest Mini server | ||||
|       --source SOURCE       Source IP address to bind to | ||||
|       --timeout TIMEOUT     HTTP timeout in seconds. Default 10 | ||||
|       --secure              Use HTTPS instead of HTTP when communicating with | ||||
|                             speedtest.net operated servers | ||||
|       --no-pre-allocate     Do not pre allocate upload data. Pre allocation is | ||||
|                             enabled by default to improve upload performance. To | ||||
|                             support systems with insufficient memory, use this | ||||
|                             option to avoid a MemoryError | ||||
|       --version             Show the version number and exit | ||||
| 
 | ||||
| 
 | ||||
| Python API | ||||
| ---------- | ||||
| 
 | ||||
| See the `wiki <https://github.com/sivel/speedtest-cli/wiki>`_. | ||||
| 
 | ||||
| 
 | ||||
| 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. | ||||
|  |  | |||
							
								
								
									
										16
									
								
								setup.py
								
								
								
								
							
							
						
						
									
										16
									
								
								setup.py
								
								
								
								
							|  | @ -1,6 +1,6 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright 2012-2014 Matt Martz | ||||
| # Copyright 2012 Matt Martz | ||||
| # All Rights Reserved. | ||||
| # | ||||
| #    Licensed under the Apache License, Version 2.0 (the "License"); you may | ||||
|  | @ -57,7 +57,7 @@ except: | |||
| 
 | ||||
| setup( | ||||
|     name='speedtest-cli', | ||||
|     version=find_version('speedtest_cli.py'), | ||||
|     version=find_version('speedtest.py'), | ||||
|     description=('Command line interface for testing internet bandwidth using ' | ||||
|                  'speedtest.net'), | ||||
|     long_description=long_description, | ||||
|  | @ -66,11 +66,11 @@ setup( | |||
|     author_email='matt@sivel.net', | ||||
|     url='https://github.com/sivel/speedtest-cli', | ||||
|     license='Apache License, Version 2.0', | ||||
|     py_modules=['speedtest_cli'], | ||||
|     py_modules=['speedtest'], | ||||
|     entry_points={ | ||||
|         'console_scripts': [ | ||||
|             'speedtest=speedtest_cli:main', | ||||
|             'speedtest-cli=speedtest_cli:main' | ||||
|             'speedtest=speedtest:main', | ||||
|             'speedtest-cli=speedtest:main' | ||||
|         ] | ||||
|     }, | ||||
|     classifiers=[ | ||||
|  | @ -89,5 +89,11 @@ setup( | |||
|         'Programming Language :: Python :: 3.2', | ||||
|         'Programming Language :: Python :: 3.3', | ||||
|         'Programming Language :: Python :: 3.4', | ||||
|         'Programming Language :: Python :: 3.5', | ||||
|         'Programming Language :: Python :: 3.6', | ||||
|         'Programming Language :: Python :: 3.7', | ||||
|         'Programming Language :: Python :: 3.8', | ||||
|         'Programming Language :: Python :: 3.9', | ||||
|         'Programming Language :: Python :: 3.10', | ||||
|     ] | ||||
| ) | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| .TH "speedtest-cli" 1 "2014-01-26" "speedtest-cli" | ||||
| .TH "speedtest-cli" 1 "2018-01-05" "speedtest-cli" | ||||
| .SH NAME | ||||
| speedtest\-cli \- Test your bandwidth througput using speedtest.net | ||||
| speedtest\-cli \- Command line interface for testing internet bandwidth using speedtest.net | ||||
| .SH SYNOPSIS | ||||
| .B speedtest\-cli | ||||
| [OPTION]... | ||||
| [OPTION...] | ||||
| .SH DESCRIPTION | ||||
| Speedtest.net is a web service for testing your broadband connection by downloading a file | ||||
| from a nearby speedtest.net server on the web. This tool allows you to access the service | ||||
|  | @ -23,9 +23,29 @@ Displays usage for the tool. | |||
| 
 | ||||
| .B Options | ||||
| 
 | ||||
| \fB\-\-no\-download\fR | ||||
| .RS | ||||
| Do not perform download test | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-no\-upload\fR | ||||
| .RS | ||||
| Do not perform upload test | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-single\fR | ||||
| .RS | ||||
| Only use a single connection instead of multiple. This simulates a typical file transfer. | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-bytes\fR | ||||
| .RS | ||||
| Display values in bytes instead of bits. Does not affect the image generated by \-\-share, nor output from \-\-json or \-\-csv | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-share\fR | ||||
| .RS | ||||
| Generate and provide a URL to the speedtest.net share results image | ||||
| Generate and provide a URL to the speedtest.net share results image, not displayed with \-\-csv | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-simple\fR | ||||
|  | @ -33,6 +53,26 @@ Generate and provide a URL to the speedtest.net share results image | |||
| Suppress verbose output, only show basic information | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-csv\fR | ||||
| .RS | ||||
| Suppress verbose output, only show basic information in CSV format. Speeds listed in bit/s and not affected by \-\-bytes | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-csv\-delimiter CSV_DELIMITER\fR | ||||
| .RS | ||||
| Single character delimiter to use in CSV output. Default "," | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-csv\-header\fR | ||||
| .RS | ||||
| Print CSV headers | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-json\fR | ||||
| .RS | ||||
| Suppress verbose output, only show basic information in JSON format. Speeds listed in bit/s and not affected by \-\-bytes | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-list\fR | ||||
| .RS | ||||
| Display a list of speedtest.net servers sorted by distance | ||||
|  | @ -40,7 +80,12 @@ Display a list of speedtest.net servers sorted by distance | |||
| 
 | ||||
| \fB\-\-server SERVER\fR | ||||
| .RS | ||||
| Specify a server ID to test against | ||||
| Specify a server ID to test against. Can be supplied multiple times | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-exclude EXCLUDE\fR | ||||
| .RS | ||||
| Exclude a server from selection. Can be supplied multiple times | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-mini MINI\fR | ||||
|  | @ -53,6 +98,21 @@ URL of the Speedtest Mini server | |||
| Source IP address to bind to | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-timeout TIMEOUT\fR | ||||
| .RS | ||||
| HTTP timeout in seconds. Default 10 | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-secure\fR | ||||
| .RS | ||||
| Use HTTPS instead of HTTP when communicating with speedtest.net operated servers | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-no\-pre\-allocate\fR | ||||
| .RS | ||||
| Do not pre allocate upload data. Pre allocation is enabled by default to improve upload performance. To support systems with insufficient memory, use this option to avoid a MemoryError | ||||
| .RE | ||||
| 
 | ||||
| \fB\-\-version\fR | ||||
| .RS | ||||
| Show the version number and exit | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										676
									
								
								speedtest_cli.py
								
								
								
								
							
							
						
						
									
										676
									
								
								speedtest_cli.py
								
								
								
								
							|  | @ -1,676 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright 2012-2014 Matt Martz | ||||
| # 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. | ||||
| 
 | ||||
| __version__ = '0.2.6' | ||||
| 
 | ||||
| # Some global variables we use | ||||
| source = None | ||||
| shutdown_event = None | ||||
| 
 | ||||
| import math | ||||
| import time | ||||
| import os | ||||
| import sys | ||||
| import threading | ||||
| import re | ||||
| import signal | ||||
| import socket | ||||
| 
 | ||||
| # Used for bound_interface | ||||
| socket_socket = socket.socket | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| # 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 | ||||
| 
 | ||||
| try: | ||||
|     from Queue import Queue | ||||
| except ImportError: | ||||
|     from queue import Queue | ||||
| 
 | ||||
| try: | ||||
|     from urlparse import urlparse | ||||
| except ImportError: | ||||
|     from urllib.parse import urlparse | ||||
| 
 | ||||
| try: | ||||
|     from urlparse import parse_qs | ||||
| except ImportError: | ||||
|     try: | ||||
|         from urllib.parse import parse_qs | ||||
|     except ImportError: | ||||
|         from cgi import parse_qs | ||||
| 
 | ||||
| try: | ||||
|     from hashlib import md5 | ||||
| except ImportError: | ||||
|     from md5 import md5 | ||||
| 
 | ||||
| try: | ||||
|     from argparse import ArgumentParser as ArgParser | ||||
| except ImportError: | ||||
|     from optparse import OptionParser as ArgParser | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| 
 | ||||
| def distance(origin, destination): | ||||
|     """Determine distance between 2 sets of [lat,lon] in km""" | ||||
| 
 | ||||
|     lat1, lon1 = origin | ||||
|     lat2, lon2 = destination | ||||
|     radius = 6371  # km | ||||
| 
 | ||||
|     dlat = math.radians(lat2 - lat1) | ||||
|     dlon = math.radians(lon2 - lon1) | ||||
|     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 | ||||
| 
 | ||||
|     return d | ||||
| 
 | ||||
| 
 | ||||
| class FileGetter(threading.Thread): | ||||
|     """Thread class for retrieving a URL""" | ||||
| 
 | ||||
|     def __init__(self, url, start): | ||||
|         self.url = url | ||||
|         self.result = None | ||||
|         self.starttime = start | ||||
|         threading.Thread.__init__(self) | ||||
| 
 | ||||
|     def run(self): | ||||
|         self.result = [0] | ||||
|         try: | ||||
|             if (time.time() - self.starttime) <= 10: | ||||
|                 f = urlopen(self.url) | ||||
|                 while 1 and not shutdown_event.isSet(): | ||||
|                     self.result.append(len(f.read(10240))) | ||||
|                     if self.result[-1] == 0: | ||||
|                         break | ||||
|                 f.close() | ||||
|         except IOError: | ||||
|             pass | ||||
| 
 | ||||
| 
 | ||||
| def downloadSpeed(files, quiet=False): | ||||
|     """Function to launch FileGetter threads and calculate download speeds""" | ||||
| 
 | ||||
|     start = time.time() | ||||
| 
 | ||||
|     def producer(q, files): | ||||
|         for file in files: | ||||
|             thread = FileGetter(file, start) | ||||
|             thread.start() | ||||
|             q.put(thread, True) | ||||
|             if not quiet and not shutdown_event.isSet(): | ||||
|                 sys.stdout.write('.') | ||||
|                 sys.stdout.flush() | ||||
| 
 | ||||
|     finished = [] | ||||
| 
 | ||||
|     def consumer(q, total_files): | ||||
|         while len(finished) < total_files: | ||||
|             thread = q.get(True) | ||||
|             while thread.isAlive(): | ||||
|                 thread.join(timeout=0.1) | ||||
|             finished.append(sum(thread.result)) | ||||
|             del thread | ||||
| 
 | ||||
|     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() | ||||
|     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)) | ||||
| 
 | ||||
| 
 | ||||
| class FilePutter(threading.Thread): | ||||
|     """Thread class for putting a URL""" | ||||
| 
 | ||||
|     def __init__(self, url, start, size): | ||||
|         self.url = url | ||||
|         chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' | ||||
|         data = chars * (int(round(int(size) / 36.0))) | ||||
|         self.data = ('content1=%s' % data[0:int(size) - 9]).encode() | ||||
|         del data | ||||
|         self.result = None | ||||
|         self.starttime = start | ||||
|         threading.Thread.__init__(self) | ||||
| 
 | ||||
|     def run(self): | ||||
|         try: | ||||
|             if ((time.time() - self.starttime) <= 10 and | ||||
|                     not shutdown_event.isSet()): | ||||
|                 f = urlopen(self.url, self.data) | ||||
|                 f.read(11) | ||||
|                 f.close() | ||||
|                 self.result = len(self.data) | ||||
|             else: | ||||
|                 self.result = 0 | ||||
|         except IOError: | ||||
|             self.result = 0 | ||||
| 
 | ||||
| 
 | ||||
| def uploadSpeed(url, sizes, quiet=False): | ||||
|     """Function to launch FilePutter threads and calculate upload speeds""" | ||||
| 
 | ||||
|     start = time.time() | ||||
| 
 | ||||
|     def producer(q, sizes): | ||||
|         for size in sizes: | ||||
|             thread = FilePutter(url, start, size) | ||||
|             thread.start() | ||||
|             q.put(thread, True) | ||||
|             if not quiet and not shutdown_event.isSet(): | ||||
|                 sys.stdout.write('.') | ||||
|                 sys.stdout.flush() | ||||
| 
 | ||||
|     finished = [] | ||||
| 
 | ||||
|     def consumer(q, total_sizes): | ||||
|         while len(finished) < total_sizes: | ||||
|             thread = q.get(True) | ||||
|             while thread.isAlive(): | ||||
|                 thread.join(timeout=0.1) | ||||
|             finished.append(thread.result) | ||||
|             del thread | ||||
| 
 | ||||
|     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() | ||||
|     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)) | ||||
| 
 | ||||
| 
 | ||||
| def getAttributesByTagName(dom, tagName): | ||||
|     """Retrieve an attribute from an XML document and return it in a | ||||
|     consistent format | ||||
| 
 | ||||
|     Only used with xml.dom.minidom, which is likely only to be used | ||||
|     with python versions older than 2.5 | ||||
|     """ | ||||
|     elem = dom.getElementsByTagName(tagName)[0] | ||||
|     return dict(list(elem.attributes.items())) | ||||
| 
 | ||||
| 
 | ||||
| def getConfig(): | ||||
|     """Download the speedtest.net configuration and return only the data | ||||
|     we are interested in | ||||
|     """ | ||||
| 
 | ||||
|     uh = urlopen('http://www.speedtest.net/speedtest-config.php') | ||||
|     configxml = [] | ||||
|     while 1: | ||||
|         configxml.append(uh.read(10240)) | ||||
|         if len(configxml[-1]) == 0: | ||||
|             break | ||||
|     if int(uh.code) != 200: | ||||
|         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')} | ||||
|     del root | ||||
|     del configxml | ||||
|     return config | ||||
| 
 | ||||
| 
 | ||||
| def closestServers(client, all=False): | ||||
|     """Determine the 5 closest speedtest.net servers based on geographic | ||||
|     distance | ||||
|     """ | ||||
| 
 | ||||
|     uh = urlopen('http://www.speedtest.net/speedtest-servers.php') | ||||
|     serversxml = [] | ||||
|     while 1: | ||||
|         serversxml.append(uh.read(10240)) | ||||
|         if len(serversxml[-1]) == 0: | ||||
|             break | ||||
|     if int(uh.code) != 200: | ||||
|         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') | ||||
|     servers = {} | ||||
|     for server in elements: | ||||
|         try: | ||||
|             attrib = server.attrib | ||||
|         except AttributeError: | ||||
|             attrib = dict(list(server.attributes.items())) | ||||
|         d = distance([float(client['lat']), float(client['lon'])], | ||||
|                      [float(attrib.get('lat')), float(attrib.get('lon'))]) | ||||
|         attrib['d'] = d | ||||
|         if d not in servers: | ||||
|             servers[d] = [attrib] | ||||
|         else: | ||||
|             servers[d].append(attrib) | ||||
|     del root | ||||
|     del serversxml | ||||
|     del elements | ||||
| 
 | ||||
|     closest = [] | ||||
|     for d in sorted(servers.keys()): | ||||
|         for s in servers[d]: | ||||
|             closest.append(s) | ||||
|             if len(closest) == 5 and not all: | ||||
|                 break | ||||
|         else: | ||||
|             continue | ||||
|         break | ||||
| 
 | ||||
|     del servers | ||||
|     return closest | ||||
| 
 | ||||
| 
 | ||||
| def getBestServer(servers): | ||||
|     """Perform a speedtest.net "ping" to determine which speedtest.net | ||||
|     server has the lowest latency | ||||
|     """ | ||||
| 
 | ||||
|     results = {} | ||||
|     for server in servers: | ||||
|         cum = [] | ||||
|         url = os.path.dirname(server['url']) | ||||
|         for i in range(0, 3): | ||||
|             try: | ||||
|                 uh = urlopen('%s/latency.txt' % url) | ||||
|             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(): | ||||
|                 cum.append(total) | ||||
|             else: | ||||
|                 cum.append(3600) | ||||
|             uh.close() | ||||
|         avg = round((sum(cum) / 3) * 1000000, 3) | ||||
|         results[avg] = server | ||||
| 
 | ||||
|     fastest = sorted(results.keys())[0] | ||||
|     best = results[fastest] | ||||
|     best['latency'] = fastest | ||||
| 
 | ||||
|     return best | ||||
| 
 | ||||
| 
 | ||||
| def ctrl_c(signum, frame): | ||||
|     """Catch Ctrl-C key sequence and set a shutdown_event for our threaded | ||||
|     operations | ||||
|     """ | ||||
| 
 | ||||
|     global shutdown_event | ||||
|     shutdown_event.set() | ||||
|     raise SystemExit('\nCancelling...') | ||||
| 
 | ||||
| 
 | ||||
| def version(): | ||||
|     """Print the version""" | ||||
| 
 | ||||
|     raise SystemExit(__version__) | ||||
| 
 | ||||
| 
 | ||||
| def speedtest(): | ||||
|     """Run the full speedtest.net test""" | ||||
| 
 | ||||
|     global shutdown_event, source | ||||
|     shutdown_event = threading.Event() | ||||
| 
 | ||||
|     signal.signal(signal.SIGINT, ctrl_c) | ||||
| 
 | ||||
|     description = ( | ||||
|         'Command line interface for testing internet bandwidth using ' | ||||
|         'speedtest.net.\n' | ||||
|         '------------------------------------------------------------' | ||||
|         '--------------\n' | ||||
|         'https://github.com/sivel/speedtest-cli') | ||||
| 
 | ||||
|     parser = ArgParser(description=description) | ||||
|     # Give optparse.OptionParser an `add_argument` method for | ||||
|     # compatibility with argparse.ArgumentParser | ||||
|     try: | ||||
|         parser.add_argument = parser.add_option | ||||
|     except AttributeError: | ||||
|         pass | ||||
|     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') | ||||
|     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') | ||||
|     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') | ||||
|     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('--version', action='store_true', | ||||
|                         help='Show the version number and exit') | ||||
| 
 | ||||
|     options = parser.parse_args() | ||||
|     if isinstance(options, tuple): | ||||
|         args = options[0] | ||||
|     else: | ||||
|         args = options | ||||
|     del options | ||||
| 
 | ||||
|     # Print the version and exit | ||||
|     if args.version: | ||||
|         version() | ||||
| 
 | ||||
|     # If specified bind to a specific IP address | ||||
|     if args.source: | ||||
|         source = args.source | ||||
|         socket.socket = bound_socket | ||||
| 
 | ||||
|     if not args.simple: | ||||
|         print_('Retrieving speedtest.net configuration...') | ||||
|     try: | ||||
|         config = getConfig() | ||||
|     except URLError: | ||||
|         print_('Cannot retrieve speedtest configuration') | ||||
|         sys.exit(1) | ||||
| 
 | ||||
|     if not args.simple: | ||||
|         print_('Retrieving speedtest.net server list...') | ||||
|     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) | ||||
|             # 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_('\n'.join(serverList).encode('utf-8', 'ignore')) | ||||
|             except NameError: | ||||
|                 print_('\n'.join(serverList)) | ||||
|             except IOError: | ||||
|                 pass | ||||
|             sys.exit(0) | ||||
|     else: | ||||
|         servers = closestServers(config['client']) | ||||
| 
 | ||||
|     if not args.simple: | ||||
|         print_('Testing from %(isp)s (%(ip)s)...' % config['client']) | ||||
| 
 | ||||
|     if args.server: | ||||
|         try: | ||||
|             best = getBestServer(filter(lambda x: x['id'] == args.server, | ||||
|                                         servers)) | ||||
|         except IndexError: | ||||
|             print_('Invalid server ID') | ||||
|             sys.exit(1) | ||||
|     elif args.mini: | ||||
|         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()) | ||||
|         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 | ||||
|         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] | ||||
|     else: | ||||
|         if not args.simple: | ||||
|             print_('Selecting best server based on ping...') | ||||
|         best = getBestServer(servers) | ||||
| 
 | ||||
|     if not args.simple: | ||||
|         # 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) | ||||
|     else: | ||||
|         print_('Ping: %(latency)s ms' % best) | ||||
| 
 | ||||
|     sizes = [350, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000] | ||||
|     urls = [] | ||||
|     for size in sizes: | ||||
|         for i in range(0, 4): | ||||
|             urls.append('%s/random%sx%s.jpg' % | ||||
|                         (os.path.dirname(best['url']), size, size)) | ||||
|     if not args.simple: | ||||
|         print_('Testing download speed', end='') | ||||
|     dlspeed = downloadSpeed(urls, args.simple) | ||||
|     if not args.simple: | ||||
|         print_() | ||||
|     print_('Download: %0.2f M%s/s' % | ||||
|            ((dlspeed / 1000 / 1000) * args.units[1], args.units[0])) | ||||
| 
 | ||||
|     sizesizes = [int(.25 * 1000 * 1000), int(.5 * 1000 * 1000)] | ||||
|     sizes = [] | ||||
|     for size in sizesizes: | ||||
|         for i in range(0, 25): | ||||
|             sizes.append(size) | ||||
|     if not args.simple: | ||||
|         print_('Testing upload speed', end='') | ||||
|     ulspeed = uploadSpeed(best['url'], sizes, args.simple) | ||||
|     if not args.simple: | ||||
|         print_() | ||||
|     print_('Upload: %0.2f M%s/s' % | ||||
|            ((ulspeed / 1000 / 1000) * args.units[1], args.units[0])) | ||||
| 
 | ||||
|     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: | ||||
|         dlspeedk = int(round((dlspeed / 1000) * 8, 0)) | ||||
|         ping = int(round(best['latency'], 0)) | ||||
|         ulspeedk = int(round((ulspeed / 1000) * 8, 0)) | ||||
| 
 | ||||
|         # 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 | ||||
|         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'], | ||||
|             'hash=%s' % md5(('%s-%s-%s-%s' % | ||||
|                              (ping, ulspeedk, dlspeedk, '297aae72')) | ||||
|                             .encode()).hexdigest()] | ||||
| 
 | ||||
|         req = Request('http://www.speedtest.net/api/api.php', | ||||
|                       data='&'.join(apiData).encode()) | ||||
|         req.add_header('Referer', 'http://c.speedtest.net/flash/speedtest.swf') | ||||
|         f = urlopen(req) | ||||
|         response = f.read() | ||||
|         code = f.code | ||||
|         f.close() | ||||
| 
 | ||||
|         if int(code) != 200: | ||||
|             print_('Could not submit results to speedtest.net') | ||||
|             sys.exit(1) | ||||
| 
 | ||||
|         qsargs = parse_qs(response.decode()) | ||||
|         resultid = qsargs.get('resultid') | ||||
|         if not resultid or len(resultid) != 1: | ||||
|             print_('Could not submit results to speedtest.net') | ||||
|             sys.exit(1) | ||||
| 
 | ||||
|         print_('Share results: http://www.speedtest.net/result/%s.png' % | ||||
|                resultid[0]) | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     try: | ||||
|         speedtest() | ||||
|     except KeyboardInterrupt: | ||||
|         print_('\nCancelling...') | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
| 
 | ||||
| # vim:ts=4:sw=4:expandtab | ||||
|  | @ -0,0 +1,37 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # Copyright 2018 Matt Martz | ||||
| # 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. | ||||
| 
 | ||||
| import sys | ||||
| import subprocess | ||||
| 
 | ||||
| cmd = [sys.executable, 'speedtest.py', '--source', '127.0.0.1'] | ||||
| 
 | ||||
| p = subprocess.Popen( | ||||
|     cmd, | ||||
|     stdout=subprocess.PIPE, | ||||
|     stderr=subprocess.PIPE | ||||
| ) | ||||
| 
 | ||||
| stdout, stderr = p.communicate() | ||||
| 
 | ||||
| if p.returncode != 1: | ||||
|     raise SystemExit('%s did not fail with exit code 1' % ' '.join(cmd)) | ||||
| 
 | ||||
| if 'Invalid argument'.encode() not in stderr: | ||||
|     raise SystemExit( | ||||
|         '"Invalid argument" not found in stderr:\n%s' % stderr.decode() | ||||
|     ) | ||||
							
								
								
									
										12
									
								
								tox.ini
								
								
								
								
							
							
						
						
									
										12
									
								
								tox.ini
								
								
								
								
							|  | @ -4,16 +4,22 @@ skipsdist=true | |||
| [testenv] | ||||
| commands = | ||||
|     {envpython} -V | ||||
|     {envpython} speedtest_cli.py | ||||
|     {envpython} -m compileall speedtest.py | ||||
|     {envpython} speedtest.py | ||||
|     {envpython} speedtest.py --source 172.17.0.1 | ||||
|     {envpython} tests/scripts/source.py | ||||
| 
 | ||||
| [testenv:flake8] | ||||
| basepython=python | ||||
| deps=flake8 | ||||
| commands = | ||||
|     {envpython} -V | ||||
|     flake8 speedtest_cli.py | ||||
|     flake8 speedtest.py | ||||
| 
 | ||||
| [testenv:pypy] | ||||
| commands = | ||||
|     pypy -V | ||||
|     pypy speedtest_cli.py | ||||
|     pypy -m compileall speedtest.py | ||||
|     pypy speedtest.py | ||||
|     pypy speedtest.py --source 172.17.0.1 | ||||
|     pypy tests/scripts/source.py | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue