Compare commits
227 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 | |
|
|
8e0d5eaec8 | |
|
|
966fd2c86d | |
|
|
8d1cf7ac1e | |
|
|
20c9882b09 | |
|
|
23fba2520a | |
|
|
3e9c6e3532 | |
|
|
8a308040a9 | |
|
|
e6913368ef | |
|
|
f559d79ded | |
|
|
3d55c7d91e | |
|
|
e4f493954a | |
|
|
18408ee938 | |
|
|
2da8a26038 | |
|
|
1cf091ebc7 | |
|
|
ae4267fbb8 | |
|
|
8a047be93f | |
|
|
724563049c | |
|
|
4809428de7 | |
|
|
c7636b3aac | |
|
|
acf396aba0 | |
|
|
6d777db198 | |
|
|
756f04da76 | |
|
|
890a3edb7e |
|
|
@ -0,0 +1,62 @@
|
|||
language: python
|
||||
sudo: required
|
||||
dist: xenial
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- deadsnakes
|
||||
packages:
|
||||
- python2.4
|
||||
- python2.5
|
||||
- python2.6
|
||||
- python3.2
|
||||
- python3.3
|
||||
|
||||
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 "py35") != 0 ]]; then pyenv global system 3.5; fi;
|
||||
|
||||
install:
|
||||
- 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
|
||||
|
||||
notifications:
|
||||
email:
|
||||
- matt@sivel.net
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# Pull Requests
|
||||
|
||||
## Pull requests should be
|
||||
|
||||
1. Made against the `devel` branch.
|
||||
1. Made from a git feature branch.
|
||||
|
||||
## Pull requests will not be accepted that
|
||||
|
||||
1. Are not made against the `devel` branch
|
||||
1. Are submitted from a branch named `devel`
|
||||
1. Do not pass pep8/pyflakes/flake8
|
||||
1. Do not work with Python 2.4-3.4 or pypy
|
||||
1. Add python modules not included with the Python standard library
|
||||
1. Are made by editing files via the GitHub website
|
||||
|
||||
# Coding Guidelines
|
||||
|
||||
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.
|
||||
|
||||
# Permitted Python Modules
|
||||
|
||||
Only modules included in the standard library are permitted for use in this application. This application should not be dependent on any 3rd party modules that would need to be installed external to just Python itself.
|
||||
|
||||
# Testing
|
||||
|
||||
Currently there are no unit tests, but they are planned.
|
||||
107
README.rst
107
README.rst
|
|
@ -4,15 +4,24 @@ speedtest-cli
|
|||
Command line interface for testing internet bandwidth using
|
||||
speedtest.net
|
||||
|
||||
.. image:: https://pypip.in/v/speedtest-cli/badge.png
|
||||
:target: https://crate.io/packages/speedtest-cli
|
||||
.. image:: https://pypip.in/d/speedtest-cli/badge.png
|
||||
:target: https://crate.io/packages/speedtest-cli
|
||||
.. image:: https://img.shields.io/pypi/v/speedtest-cli.svg
|
||||
:target: https://pypi.python.org/pypi/speedtest-cli/
|
||||
:alt: Latest Version
|
||||
.. image:: https://img.shields.io/travis/sivel/speedtest-cli.svg
|
||||
:target: https://pypi.python.org/pypi/speedtest-cli/
|
||||
: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.3
|
||||
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
|
||||
------------
|
||||
|
|
@ -42,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
|
||||
|
|
@ -65,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.
|
||||
|
|
|
|||
87
setup.py
87
setup.py
|
|
@ -1,28 +1,99 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2012 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 setuptools
|
||||
import os
|
||||
import re
|
||||
import codecs
|
||||
|
||||
setuptools.setup(
|
||||
from setuptools import setup
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
# Read the version number from a source file.
|
||||
# Why read it, and not import?
|
||||
# see https://groups.google.com/d/topic/pypa-dev/0PkjVpcxTzQ/discussion
|
||||
def find_version(*file_paths):
|
||||
# Open in Latin-1 so that we avoid encoding errors.
|
||||
# Use codecs.open for Python 2 compatibility
|
||||
try:
|
||||
f = codecs.open(os.path.join(here, *file_paths), 'r', 'latin1')
|
||||
version_file = f.read()
|
||||
f.close()
|
||||
except:
|
||||
raise RuntimeError("Unable to find version string.")
|
||||
|
||||
# The version line must have the form
|
||||
# __version__ = 'ver'
|
||||
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
|
||||
version_file, re.M)
|
||||
if version_match:
|
||||
return version_match.group(1)
|
||||
raise RuntimeError("Unable to find version string.")
|
||||
|
||||
|
||||
# Get the long description from the relevant file
|
||||
try:
|
||||
f = codecs.open('README.rst', encoding='utf-8')
|
||||
long_description = f.read()
|
||||
f.close()
|
||||
except:
|
||||
long_description = ''
|
||||
|
||||
|
||||
setup(
|
||||
name='speedtest-cli',
|
||||
version='0.2.3',
|
||||
version=find_version('speedtest.py'),
|
||||
description=('Command line interface for testing internet bandwidth using '
|
||||
'speedtest.net'),
|
||||
long_description=open('README.rst').read(),
|
||||
long_description=long_description,
|
||||
keywords='speedtest speedtest.net',
|
||||
author='Matt Martz',
|
||||
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=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Programming Language :: Python',
|
||||
'Environment :: Console',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Operating System :: OS Independent'
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.4',
|
||||
'Programming Language :: Python :: 2.5',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.1',
|
||||
'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',
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
.TH "speedtest-cli" 1 "2018-01-05" "speedtest-cli"
|
||||
.SH NAME
|
||||
speedtest\-cli \- Command line interface for testing internet bandwidth using speedtest.net
|
||||
.SH SYNOPSIS
|
||||
.B speedtest\-cli
|
||||
[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
|
||||
from the command line.
|
||||
|
||||
Speedtest mini is a version of the Speedtest.net server that you can host locally.
|
||||
|
||||
.SH OPTIONS
|
||||
Usage: speedtest\-cli [OPTION...]
|
||||
|
||||
.B Help Options
|
||||
|
||||
\fB\-h, \-\-help\fR
|
||||
.RS
|
||||
Displays usage for the tool.
|
||||
.RE
|
||||
|
||||
.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, not displayed with \-\-csv
|
||||
.RE
|
||||
|
||||
\fB\-\-simple\fR
|
||||
.RS
|
||||
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
|
||||
.RE
|
||||
|
||||
\fB\-\-server SERVER\fR
|
||||
.RS
|
||||
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
|
||||
.RS
|
||||
URL of the Speedtest Mini server
|
||||
.RE
|
||||
|
||||
\fB\-\-source SOURCE\fR
|
||||
.RS
|
||||
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
|
||||
.RE
|
||||
|
||||
.SH EXAMPLES
|
||||
|
||||
\fBAutomatically find closest server and start testing\fR
|
||||
.RS
|
||||
speedtest\-cli
|
||||
.RE
|
||||
|
||||
\fBSpecify testing against server 1491\fR
|
||||
.RS
|
||||
speedtest-cli \-\-server 1491
|
||||
.RE
|
||||
|
||||
\fBTesting against Speedtest Mini\fR
|
||||
.RS
|
||||
speedtest-cli \-\-mini 172.18.66.1
|
||||
.RE
|
||||
|
||||
.SH REPORTING BUGS
|
||||
Please file issues on the Github bug tracker: https://github.com/sivel/speedtest\-cli
|
||||
|
||||
.SH AUTHORS
|
||||
This manual page was written by Jonathan Carter <jonathan@ubuntu.com>
|
||||
Speedtest\-cli was written by Matt Martz <matt@sivel.net>
|
||||
File diff suppressed because it is too large
Load Diff
657
speedtest_cli.py
657
speedtest_cli.py
|
|
@ -1,657 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2013 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.3'
|
||||
|
||||
# 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(''.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(''.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:
|
||||
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('--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 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 Mbit/s' % ((dlspeed / 1000 / 1000) * 8))
|
||||
|
||||
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 Mbit/s' % ((ulspeed / 1000 / 1000) * 8))
|
||||
|
||||
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()
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[tox]
|
||||
skipsdist=true
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
{envpython} -V
|
||||
{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.py
|
||||
|
||||
[testenv:pypy]
|
||||
commands =
|
||||
pypy -V
|
||||
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