Compare commits
	
		
			201 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 | 
							
								
								
									
										70
									
								
								.travis.yml
								
								
								
								
							
							
						
						
									
										70
									
								
								.travis.yml
								
								
								
								
							|  | @ -1,32 +1,58 @@ | ||||||
| language: python | language: python | ||||||
|  | sudo: required | ||||||
|  | dist: xenial | ||||||
| 
 | 
 | ||||||
| python: | addons: | ||||||
|  - 2.7 |   apt: | ||||||
|  |     sources: | ||||||
|  |       - deadsnakes | ||||||
|  |     packages: | ||||||
|  |       - python2.4 | ||||||
|  |       - python2.5 | ||||||
|  |       - python2.6 | ||||||
|  |       - python3.2 | ||||||
|  |       - python3.3 | ||||||
| 
 | 
 | ||||||
| env: | matrix: | ||||||
|  - TOXENV=py24 |   include: | ||||||
|  - TOXENV=py25 |     - python: 2.7 | ||||||
|  - TOXENV=py26 |       env: TOXENV=flake8 | ||||||
|  - TOXENV=py27 |     - python: 2.7 | ||||||
|  - TOXENV=py31 |       env: TOXENV=py24 | ||||||
|  - TOXENV=py32 |     - python: 2.7 | ||||||
|  - TOXENV=py33 |       env: TOXENV=py25 | ||||||
|  - TOXENV=py34 |     - python: 2.7 | ||||||
|  - TOXENV=pypy |       env: TOXENV=py26 | ||||||
|  - TOXENV=flake8 |     - 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: | 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 "py35") != 0 ]]; then pyenv global system 3.5; 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; |  | ||||||
| 
 | 
 | ||||||
| install: | 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 "py32") != 0 ]]; then pip install setuptools==17.1.1; fi; | ||||||
|  - if [[ $(echo "$TOXENV" | egrep -c "(py2[45]|py31)") == 0 ]]; then pip install tox; 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: | script: | ||||||
|   - tox |   - tox | ||||||
|  |  | ||||||
|  | @ -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. | 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 | # Supported Python Versions | ||||||
| 
 | 
 | ||||||
| All code needs to support Python 2.4-3.4 and pypy. | All code needs to support Python 2.4-3.4 and pypy. | ||||||
|  |  | ||||||
							
								
								
									
										93
									
								
								README.rst
								
								
								
								
							
							
						
						
									
										93
									
								
								README.rst
								
								
								
								
							|  | @ -4,20 +4,24 @@ speedtest-cli | ||||||
| Command line interface for testing internet bandwidth using | Command line interface for testing internet bandwidth using | ||||||
| speedtest.net | 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/ |         :target: https://pypi.python.org/pypi/speedtest-cli/ | ||||||
|         :alt: Latest Version |         :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/ |         :target: https://pypi.python.org/pypi/speedtest-cli/ | ||||||
|         :alt: Downloads |         :alt: Travis | ||||||
| .. image:: https://pypip.in/license/speedtest-cli/badge.png | .. image:: https://img.shields.io/pypi/l/speedtest-cli.svg | ||||||
|         :target: https://pypi.python.org/pypi/speedtest-cli/ |         :target: https://pypi.python.org/pypi/speedtest-cli/ | ||||||
|         :alt: License |         :alt: License | ||||||
| 
 | 
 | ||||||
| Versions | 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 | Installation | ||||||
| ------------ | ------------ | ||||||
|  | @ -47,21 +51,22 @@ or | ||||||
| :: | :: | ||||||
| 
 | 
 | ||||||
|     git clone https://github.com/sivel/speedtest-cli.git |     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) | 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 |     chmod +x speedtest-cli | ||||||
| 
 | 
 | ||||||
| or | 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 |     chmod +x speedtest-cli | ||||||
| 
 | 
 | ||||||
| Usage | Usage | ||||||
|  | @ -70,9 +75,12 @@ Usage | ||||||
| :: | :: | ||||||
| 
 | 
 | ||||||
|     $ speedtest-cli -h |     $ speedtest-cli -h | ||||||
|     usage: speedtest-cli [-h] [--bytes] [--share] [--simple] [--list] |     usage: speedtest-cli [-h] [--no-download] [--no-upload] [--single] [--bytes] | ||||||
|                          [--server SERVER] [--mini MINI] [--source SOURCE] |                          [--share] [--simple] [--csv] | ||||||
|                          [--version] |                          [--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. |     Command line interface for testing internet bandwidth using speedtest.net. | ||||||
|     -------------------------------------------------------------------------- |     -------------------------------------------------------------------------- | ||||||
|  | @ -80,14 +88,67 @@ Usage | ||||||
| 
 | 
 | ||||||
|     optional arguments: |     optional arguments: | ||||||
|       -h, --help            show this help message and exit |       -h, --help            show this help message and exit | ||||||
|       --bytes          Display values in bytes instead of bits. Does not affect |       --no-download         Do not perform download test | ||||||
|                        the image generated by --share |       --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 |       --share               Generate and provide a URL to the speedtest.net share | ||||||
|                        results image |                             results image, not displayed with --csv | ||||||
|       --simple              Suppress verbose output, only show basic information |       --simple              Suppress verbose output, only show basic information | ||||||
|       --list           Display a list of speedtest.net servers sorted by distance |       --csv                 Suppress verbose output, only show basic information | ||||||
|       --server SERVER  Specify a server ID to test against |                             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 |       --mini MINI           URL of the Speedtest Mini server | ||||||
|       --source SOURCE       Source IP address to bind to |       --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 |       --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 | #!/usr/bin/env python | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| # Copyright 2012-2014 Matt Martz | # Copyright 2012 Matt Martz | ||||||
| # All Rights Reserved. | # All Rights Reserved. | ||||||
| # | # | ||||||
| #    Licensed under the Apache License, Version 2.0 (the "License"); you may | #    Licensed under the Apache License, Version 2.0 (the "License"); you may | ||||||
|  | @ -57,7 +57,7 @@ except: | ||||||
| 
 | 
 | ||||||
| setup( | setup( | ||||||
|     name='speedtest-cli', |     name='speedtest-cli', | ||||||
|     version=find_version('speedtest_cli.py'), |     version=find_version('speedtest.py'), | ||||||
|     description=('Command line interface for testing internet bandwidth using ' |     description=('Command line interface for testing internet bandwidth using ' | ||||||
|                  'speedtest.net'), |                  'speedtest.net'), | ||||||
|     long_description=long_description, |     long_description=long_description, | ||||||
|  | @ -66,11 +66,11 @@ setup( | ||||||
|     author_email='matt@sivel.net', |     author_email='matt@sivel.net', | ||||||
|     url='https://github.com/sivel/speedtest-cli', |     url='https://github.com/sivel/speedtest-cli', | ||||||
|     license='Apache License, Version 2.0', |     license='Apache License, Version 2.0', | ||||||
|     py_modules=['speedtest_cli'], |     py_modules=['speedtest'], | ||||||
|     entry_points={ |     entry_points={ | ||||||
|         'console_scripts': [ |         'console_scripts': [ | ||||||
|             'speedtest=speedtest_cli:main', |             'speedtest=speedtest:main', | ||||||
|             'speedtest-cli=speedtest_cli:main' |             'speedtest-cli=speedtest:main' | ||||||
|         ] |         ] | ||||||
|     }, |     }, | ||||||
|     classifiers=[ |     classifiers=[ | ||||||
|  | @ -89,5 +89,11 @@ setup( | ||||||
|         'Programming Language :: Python :: 3.2', |         'Programming Language :: Python :: 3.2', | ||||||
|         'Programming Language :: Python :: 3.3', |         'Programming Language :: Python :: 3.3', | ||||||
|         'Programming Language :: Python :: 3.4', |         '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-04-23" "speedtest-cli" | .TH "speedtest-cli" 1 "2018-01-05" "speedtest-cli" | ||||||
| .SH NAME | .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 | .SH SYNOPSIS | ||||||
| .B speedtest\-cli | .B speedtest\-cli | ||||||
| [OPTION]... | [OPTION...] | ||||||
| .SH DESCRIPTION | .SH DESCRIPTION | ||||||
| Speedtest.net is a web service for testing your broadband connection by downloading a file | 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 | from a nearby speedtest.net server on the web. This tool allows you to access the service | ||||||
|  | @ -23,14 +23,29 @@ Displays usage for the tool. | ||||||
| 
 | 
 | ||||||
| .B Options | .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 | \fB\-\-bytes\fR | ||||||
| .RS | .RS | ||||||
| Display values in bytes instead of bits. Does not affect the image generated by \-\-share | Display values in bytes instead of bits. Does not affect the image generated by \-\-share, nor output from \-\-json or \-\-csv | ||||||
| .RE | .RE | ||||||
| 
 | 
 | ||||||
| \fB\-\-share\fR | \fB\-\-share\fR | ||||||
| .RS | .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 | .RE | ||||||
| 
 | 
 | ||||||
| \fB\-\-simple\fR | \fB\-\-simple\fR | ||||||
|  | @ -38,6 +53,26 @@ Generate and provide a URL to the speedtest.net share results image | ||||||
| Suppress verbose output, only show basic information | Suppress verbose output, only show basic information | ||||||
| .RE | .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 | \fB\-\-list\fR | ||||||
| .RS | .RS | ||||||
| Display a list of speedtest.net servers sorted by distance | Display a list of speedtest.net servers sorted by distance | ||||||
|  | @ -45,7 +80,12 @@ Display a list of speedtest.net servers sorted by distance | ||||||
| 
 | 
 | ||||||
| \fB\-\-server SERVER\fR | \fB\-\-server SERVER\fR | ||||||
| .RS | .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 | .RE | ||||||
| 
 | 
 | ||||||
| \fB\-\-mini MINI\fR | \fB\-\-mini MINI\fR | ||||||
|  | @ -58,6 +98,21 @@ URL of the Speedtest Mini server | ||||||
| Source IP address to bind to | Source IP address to bind to | ||||||
| .RE | .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 | \fB\-\-version\fR | ||||||
| .RS | .RS | ||||||
| Show the version number and exit | 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.7' |  | ||||||
| 
 |  | ||||||
| # 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] | [testenv] | ||||||
| commands = | commands = | ||||||
|     {envpython} -V |     {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] | [testenv:flake8] | ||||||
| basepython=python | basepython=python | ||||||
| deps=flake8 | deps=flake8 | ||||||
| commands = | commands = | ||||||
|     {envpython} -V |     {envpython} -V | ||||||
|     flake8 speedtest_cli.py |     flake8 speedtest.py | ||||||
| 
 | 
 | ||||||
| [testenv:pypy] | [testenv:pypy] | ||||||
| commands = | commands = | ||||||
|     pypy -V |     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