Compare commits

...

48 Commits

Author SHA1 Message Date
VisualPlugin
fae3918bfc Merge 12d868f8d9 into 22210ca352 2024-04-12 22:20:03 +00:00
Windows81
12d868f8d9 2024-04-12T22:19Z 2024-04-12 22:19:18 +00:00
Windows81
f317af3bf4 Fixed cases where --bytes and --units are both not supplied. 2021-09-19 16:47:03 +00:00
Windows81
8fcd4a019e Added additional unit flexibility for download/upload speeds. 2021-07-28 22:34:52 +00:00
Matt Martz
22210ca352 Python 3.10 support 2021-07-07 14:50:15 -05:00
Matt Martz
42e96b13dd Bump to 2.1.3 2021-04-08 08:45:29 -05:00
Matt Martz
cadc68b5ae Handle case where ignoreids is empty or contains empty ids 2021-04-08 08:44:32 -05:00
Matt Martz
db46af8bcd Ensure we catch HTTP errors on upload/download. Fixes #752 2021-01-19 17:04:47 -06:00
Matt Martz
c58ad3367b Bump release 2019-08-22 09:48:18 -05:00
Matt Martz
266e53c256 Fix proxy support. Fixes #610 2019-08-22 09:45:01 -05:00
Matt Martz
7ebb9965dd Ensure threads don't start before a position in the queue is available.
Fixes #628
2019-08-22 09:43:19 -05:00
Matt Martz
2658bd50b4 Bump devel version 2019-03-13 15:57:05 -05:00
Matt Martz
81bba6070c Add support for py38 without deprecation warnings (#585)
* Add support for py38 without deprecation warnings

* Address Py2.5 issue

* Add py3.7 and 3.8

* xenial

* pypy trusty
2019-03-13 15:56:00 -05:00
Matt Martz
681cdf20a5 Re-enable python 2.4 and 2.5 testing 2019-03-12 11:01:31 -05:00
Matt Martz
cdf6002865 Bump to 2.1.1 2019-03-12 10:58:17 -05:00
Matt Martz
9af203652b Python2.4/2.5 SSL support 2019-03-12 10:55:23 -05:00
Matt Martz
2d5a9ef364 Switch copyright from range, to date started 2019-03-11 10:03:12 -05:00
Matt Martz
3109fcf407 Update usage 2019-03-11 09:57:19 -05:00
Matt Martz
69ddff1a11 Disable py2.4/2.5 tests for now 2019-03-05 11:44:19 -06:00
Matt Martz
fb0569946d Bump to 2.1.0 for upcoming release 2019-03-05 10:55:57 -06:00
Matt Martz
f356c7b02d ensure ERROR doesn't print an empty string 2019-02-19 17:17:25 -06:00
Matt Martz
6cf43b2ff7 linting fix 2019-02-19 16:58:50 -06:00
Matt Martz
217ce8eff1 ssl.wrap_socket doesn't support server_hostname. See #572 2019-02-19 16:56:26 -06:00
Matt Martz
b43334f1ec Switch from platform.system to platform.platform. Fixes #574 2019-02-19 16:38:15 -06:00
Matt Martz
b0b826c870 Add the python version to the version output 2019-01-23 11:34:23 -06:00
Matt Martz
9ac1091eae Add debug support to show if a URL request resulted in a redirect 2019-01-23 11:34:00 -06:00
Matt Martz
ca2250f700 Add functionality for single threaded testing. Fixes #571 2019-01-23 11:33:30 -06:00
Matt Martz
ddb8db0c94 Fix install instructions with git clone. Fixes #566 2019-01-02 09:18:21 -06:00
Matt Martz
72bf53affa Fix linting error 2018-12-03 10:44:49 -06:00
liuxu
a8a3265001 Fix python3 upload problem
In python3, if Content-length is not set,urllib.request.AbstractHTTPHandler::do_request_() will use "Transfer-encoding:chunked", which will cause HTTPUploader not to exit until timeout
2018-12-03 10:26:09 -06:00
Alex Ward
b2654de410 Automatically resolve .best property (#514)
* automatically call get_best_server

* add back SpeedtestBestServerFailer exception
2018-12-03 10:20:28 -06:00
Matt Martz
72ed585c6f Bump to v2.0.2 2018-05-24 11:06:29 -05:00
Matt Martz
41e599f9c3 Ensure we are utilizing the context created by HTTPSConnection, or falling back to ssl. Fixes #517 2018-05-24 09:37:39 -05:00
Matt Martz
c7530bb143 Bump to 2.0.1 for release 2018-05-23 15:26:20 -05:00
Matt Martz
4ceae77401 Handle virtualenv and tox versions for py2.6/3.3 2018-05-22 16:38:10 -05:00
Matt Martz
9e185e8f88 Properly handle older HTTPSConnection. Fixes #513 2018-05-22 15:28:00 -05:00
Matt Martz
9c2977acfc Gracefully handle XML parsing errors. Fixes #490 #491 2018-03-09 09:46:10 -06:00
Matt Martz
f8aa20ecdf Move results.share() to ensure csv and json have access to it. Fixes #483 2018-02-20 14:59:08 -06:00
Matt Martz
8ff923b0fb Bump to 2.0.1a 2018-02-13 16:22:23 -06:00
Matt Martz
35c3ee20ed Exit with nicer error if lat/lon is not valid 2018-02-13 16:21:57 -06:00
Matt Martz
0a7823db7a Tested through 3.7 2018-02-05 16:33:07 -06:00
Matt Martz
27a8301192 Replace downloads badge with travis 2018-02-05 16:33:01 -06:00
Matt Martz
ee2e647b9b Remove deprecated speedtest_cli.py 2018-02-05 16:25:59 -06:00
Matt Martz
831c079113 Bump for release 2018-02-05 16:17:03 -06:00
Matt Martz
4f4c1dd8d1 Update man page 2018-02-05 16:16:51 -06:00
Matt Martz
2c847a1849 Add some guard code for places where sys.stdout and stderr are replaced with some other incompatible object 2018-01-26 15:52:06 -06:00
Matt Martz
e1bab1ab55 Only add terminal colors with DEBUG if stdout is a tty 2018-01-08 16:57:26 -06:00
Matt Martz
48a3d33ae4 Bump to beta 2018-01-03 09:16:51 -06:00
6 changed files with 364 additions and 151 deletions

View File

@@ -1,4 +1,6 @@
language: python
sudo: required
dist: xenial
addons:
apt:
@@ -33,8 +35,13 @@ matrix:
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;
@@ -42,7 +49,10 @@ before_install:
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 "(py2[45]|py3[12])") == 0 ]]; then pip install tox; 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

View File

@@ -7,9 +7,9 @@ speedtest.net
.. 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/pypi/dm/speedtest-cli.svg
.. image:: https://img.shields.io/travis/sivel/speedtest-cli.svg
:target: https://pypi.python.org/pypi/speedtest-cli/
:alt: Downloads
:alt: Travis
.. image:: https://img.shields.io/pypi/l/speedtest-cli.svg
:target: https://pypi.python.org/pypi/speedtest-cli/
:alt: License
@@ -17,7 +17,7 @@ speedtest.net
Versions
--------
speedtest-cli works with Python 2.4-3.6
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/
@@ -51,7 +51,8 @@ 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)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -74,21 +75,23 @@ Usage
::
$ speedtest-cli -h
usage: speedtest-cli [-h] [--no-download] [--no-upload] [--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]
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
--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

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2012-2016 Matt Martz
# Copyright 2012 Matt Martz
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -66,7 +66,7 @@ setup(
author_email='matt@sivel.net',
url='https://github.com/sivel/speedtest-cli',
license='Apache License, Version 2.0',
py_modules=['speedtest', 'speedtest_cli'],
py_modules=['speedtest'],
entry_points={
'console_scripts': [
'speedtest=speedtest:main',
@@ -91,5 +91,9 @@ setup(
'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',
]
)

View File

@@ -1,4 +1,4 @@
.TH "speedtest-cli" 1 "2014-04-23" "speedtest-cli"
.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
@@ -23,14 +23,29 @@ Displays usage for the tool.
.B Options
\fB\-\-no\-download\fR
.RS
Do not perform download test
.RE
\fB\-\-no\-upload\fR
.RS
Do not perform upload test
.RE
\fB\-\-single\fR
.RS
Only use a single connection instead of multiple. This simulates a typical file transfer.
.RE
\fB\-\-bytes\fR
.RS
Display values in bytes instead of bits. Does not affect the image generated by \-\-share
Display values in bytes instead of bits. Does not affect the image generated by \-\-share, nor output from \-\-json or \-\-csv
.RE
\fB\-\-share\fR
.RS
Generate and provide a URL to the speedtest.net share results image
Generate and provide a URL to the speedtest.net share results image, not displayed with \-\-csv
.RE
\fB\-\-simple\fR
@@ -43,12 +58,12 @@ Suppress verbose output, only show basic information
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
\fB\-\-csv\-delimiter CSV_DELIMITER\fR
.RS
Single character delimiter to use in CSV output. Default ","
.RE
\fB\-\-csv-header\fR
\fB\-\-csv\-header\fR
.RS
Print CSV headers
.RE
@@ -65,7 +80,12 @@ Display a list of speedtest.net servers sorted by distance
\fB\-\-server SERVER\fR
.RS
Specify a server ID to test against
Specify a server ID to test against. Can be supplied multiple times
.RE
\fB\-\-exclude EXCLUDE\fR
.RS
Exclude a server from selection. Can be supplied multiple times
.RE
\fB\-\-mini MINI\fR
@@ -88,6 +108,11 @@ HTTP timeout in seconds. Default 10
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

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2012-2018 Matt Martz
# Copyright 2012 Matt Martz
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -15,18 +15,18 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import re
import csv
import sys
import math
import datetime
import errno
import math
import os
import platform
import re
import signal
import socket
import timeit
import datetime
import platform
import sys
import threading
import timeit
import xml.parsers.expat
try:
@@ -36,7 +36,7 @@ except ImportError:
gzip = None
GZIP_BASE = object
__version__ = '2.0.0a'
__version__ = '2.1.4b1'
class FakeShutdownEvent(object):
@@ -49,10 +49,28 @@ class FakeShutdownEvent(object):
"Dummy method to always return false"""
return False
is_set = isSet
# Some global variables we use
DEBUG = False
_GLOBAL_DEFAULT_TIMEOUT = object()
PY25PLUS = sys.version_info[:2] >= (2, 5)
PY26PLUS = sys.version_info[:2] >= (2, 6)
PY32PLUS = sys.version_info[:2] >= (3, 2)
PY310PLUS = sys.version_info[:2] >= (3, 10)
SIZEMAP = {
"Mibyte": 8. * 1024 * 1024,
"Mbyte": 8. * 1000 * 1000,
"Mibit": 1. * 1024 * 1024,
"Mbit": 1. * 1000 * 1000,
"Kibyte": 8. * 1024,
"Kbyte": 8. * 1000,
"Kibit": 1. * 1024,
"Kbit": 1. * 1000,
"byte": 8.,
"bit": 1.,
}
# Begin import game to handle Python 2 and Python 3
try:
@@ -64,13 +82,15 @@ except ImportError:
json = None
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
try:
import xml.etree.ElementTree as ET
from xml.etree.ElementTree import _Element as ET_Element
except ImportError:
from xml.dom import minidom as DOM
ET = None
pass
except ImportError:
from xml.dom import minidom as DOM
from xml.parsers.expat import ExpatError
ET = None
try:
from urllib2 import (urlopen, Request, HTTPError, URLError,
@@ -84,9 +104,9 @@ except ImportError:
HTTPErrorProcessor, OpenerDirector)
try:
from httplib import HTTPConnection
from httplib import HTTPConnection, BadStatusLine
except ImportError:
from http.client import HTTPConnection
from http.client import HTTPConnection, BadStatusLine
try:
from httplib import HTTPSConnection
@@ -96,6 +116,11 @@ except ImportError:
except ImportError:
HTTPSConnection = None
try:
from httplib import FakeSocket
except ImportError:
FakeSocket = None
try:
from Queue import Queue
except ImportError:
@@ -165,8 +190,14 @@ except ImportError:
self.flush()
_py3_print = getattr(builtins, 'print')
_py3_utf8_stdout = _Py3Utf8Output(sys.stdout)
_py3_utf8_stderr = _Py3Utf8Output(sys.stderr)
try:
_py3_utf8_stdout = _Py3Utf8Output(sys.stdout)
_py3_utf8_stderr = _Py3Utf8Output(sys.stderr)
except OSError:
# sys.stdout/sys.stderr is not a compatible stdout/stderr object
# just use it and hope things go ok
_py3_utf8_stdout = sys.stdout
_py3_utf8_stderr = sys.stderr
def to_utf8(v):
"""No-op encode to utf-8 for py3"""
@@ -250,7 +281,6 @@ else:
write(arg)
write(end)
# Exception "constants" to support Python 2 through Python 3
try:
import ssl
@@ -259,10 +289,30 @@ try:
except AttributeError:
CERT_ERROR = tuple()
HTTP_ERRORS = ((HTTPError, URLError, socket.error, ssl.SSLError) +
CERT_ERROR)
HTTP_ERRORS = (
(HTTPError, URLError, socket.error, ssl.SSLError, BadStatusLine) +
CERT_ERROR
)
except ImportError:
HTTP_ERRORS = (HTTPError, URLError, socket.error)
ssl = None
HTTP_ERRORS = (HTTPError, URLError, socket.error, BadStatusLine)
if PY32PLUS:
etree_iter = ET.Element.iter
elif PY25PLUS:
etree_iter = ET_Element.getiterator
if PY26PLUS:
thread_is_alive = threading.Thread.is_alive
else:
thread_is_alive = threading.Thread.isAlive
def event_is_set(event):
try:
return event.is_set()
except AttributeError:
return event.isSet()
class SpeedtestException(Exception):
@@ -278,7 +328,11 @@ class SpeedtestHTTPError(SpeedtestException):
class SpeedtestConfigError(SpeedtestException):
"""Configuration provided is invalid"""
"""Configuration XML is invalid"""
class SpeedtestServersError(SpeedtestException):
"""Servers XML is invalid"""
class ConfigRetrievalError(SpeedtestHTTPError):
@@ -378,13 +432,13 @@ class SpeedtestHTTPConnection(HTTPConnection):
"""
def __init__(self, *args, **kwargs):
source_address = kwargs.pop('source_address', None)
context = kwargs.pop('context', None)
timeout = kwargs.pop('timeout', 10)
self._tunnel_host = None
HTTPConnection.__init__(self, *args, **kwargs)
self.source_address = source_address
self._context = context
self.timeout = timeout
def connect(self):
@@ -402,23 +456,75 @@ class SpeedtestHTTPConnection(HTTPConnection):
self.source_address
)
if self._tunnel_host:
self._tunnel()
if HTTPSConnection:
class SpeedtestHTTPSConnection(HTTPSConnection,
SpeedtestHTTPConnection):
class SpeedtestHTTPSConnection(HTTPSConnection):
"""Custom HTTPSConnection to support source_address across
Python 2.4 - Python 3
"""
default_port = 443
def __init__(self, *args, **kwargs):
source_address = kwargs.pop('source_address', None)
timeout = kwargs.pop('timeout', 10)
self._tunnel_host = None
HTTPSConnection.__init__(self, *args, **kwargs)
self.timeout = timeout
self.source_address = source_address
def connect(self):
"Connect to a host on a given (SSL) port."
try:
self.sock = socket.create_connection(
(self.host, self.port),
self.timeout,
self.source_address
)
except (AttributeError, TypeError):
self.sock = create_connection(
(self.host, self.port),
self.timeout,
self.source_address
)
SpeedtestHTTPConnection.connect(self)
if self._tunnel_host:
self._tunnel()
kwargs = {}
if hasattr(ssl, 'SSLContext'):
kwargs['server_hostname'] = self.host
self.sock = self._context.wrap_socket(self.sock, **kwargs)
if ssl:
try:
kwargs = {}
if hasattr(ssl, 'SSLContext'):
if self._tunnel_host:
kwargs['server_hostname'] = self._tunnel_host
else:
kwargs['server_hostname'] = self.host
self.sock = self._context.wrap_socket(self.sock, **kwargs)
except AttributeError:
self.sock = ssl.wrap_socket(self.sock)
try:
self.sock.server_hostname = self.host
except AttributeError:
pass
elif FakeSocket:
# Python 2.4/2.5 support
try:
self.sock = FakeSocket(self.sock, socket.ssl(self.sock))
except AttributeError:
raise SpeedtestException(
'This version of Python does not support HTTPS/SSL '
'functionality'
)
else:
raise SpeedtestException(
'This version of Python does not support HTTPS/SSL '
'functionality'
)
def _build_connection(connection, source_address, timeout, context=None):
@@ -583,7 +689,8 @@ def build_user_agent():
ua_tuple = (
'Mozilla/5.0',
'(%s; U; %s; en-us)' % (platform.system(), platform.architecture()[0]),
'(%s; U; %s; en-us)' % (platform.platform(),
platform.architecture()[0]),
'Python/%s' % platform.python_version(),
'(KHTML, like Gecko)',
'speedtest-cli/%s' % __version__
@@ -642,6 +749,8 @@ def catch_request(request, opener=None):
try:
uh = _open(request)
if request.get_full_url() != uh.geturl():
printer('Redirected to %s' % uh.geturl(), debug=True)
return uh, False
except HTTP_ERRORS:
e = get_exception()
@@ -681,7 +790,7 @@ def print_dots(shutdown_event):
status
"""
def inner(current, total, start=False, end=False):
if shutdown_event.isSet():
if event_is_set(shutdown_event):
return
sys.stdout.write('.')
@@ -720,7 +829,7 @@ class HTTPDownloader(threading.Thread):
try:
if (timeit.default_timer() - self.starttime) <= self.timeout:
f = self._opener(self.request)
while (not self._shutdown_event.isSet() and
while (not event_is_set(self._shutdown_event) and
(timeit.default_timer() - self.starttime) <=
self.timeout):
self.result.append(len(f.read(10240)))
@@ -729,6 +838,8 @@ class HTTPDownloader(threading.Thread):
f.close()
except IOError:
pass
except HTTP_ERRORS:
pass
class HTTPUploaderData(object):
@@ -774,7 +885,7 @@ class HTTPUploaderData(object):
def read(self, n=10240):
if ((timeit.default_timer() - self.start) <= self.timeout and
not self._shutdown_event.isSet()):
not event_is_set(self._shutdown_event)):
chunk = self.data.read(n)
self.total.append(len(chunk))
return chunk
@@ -794,7 +905,7 @@ class HTTPUploader(threading.Thread):
self.request = request
self.request.data.start = self.starttime = start
self.size = size
self.result = None
self.result = 0
self.timeout = timeout
self.i = i
@@ -812,7 +923,7 @@ class HTTPUploader(threading.Thread):
request = self.request
try:
if ((timeit.default_timer() - self.starttime) <= self.timeout and
not self._shutdown_event.isSet()):
not event_is_set(self._shutdown_event)):
try:
f = self._opener(request)
except TypeError:
@@ -829,6 +940,8 @@ class HTTPUploader(threading.Thread):
self.result = 0
except (IOError, SpeedtestUploadTimeout):
self.result = sum(self.request.data.total)
except HTTP_ERRORS:
self.result = 0
class SpeedtestResults(object):
@@ -856,7 +969,7 @@ class SpeedtestResults(object):
self.client = client or {}
self._share = None
self.timestamp = '%sZ' % datetime.datetime.utcnow().isoformat()
self.timestamp = '%sZ' % datetime.datetime.now(datetime.UTC).isoformat()
self.bytes_received = 0
self.bytes_sent = 0
@@ -1017,10 +1130,7 @@ class Speedtest(object):
@property
def best(self):
if not self._best:
raise SpeedtestMissingBestServer(
'get_best_server not called or not able to determine best '
'server'
)
self.get_best_server()
return self._best
def get_config(self):
@@ -1036,16 +1146,16 @@ class Speedtest(object):
uh, e = catch_request(request, opener=self._opener)
if e:
raise ConfigRetrievalError(e)
configxml = []
configxml_list = []
stream = get_response_stream(uh)
while 1:
try:
configxml.append(stream.read(1024))
configxml_list.append(stream.read(1024))
except (OSError, EOFError):
raise ConfigRetrievalError(get_exception())
if len(configxml[-1]) == 0:
if len(configxml_list[-1]) == 0:
break
stream.close()
uh.close()
@@ -1053,10 +1163,18 @@ class Speedtest(object):
if int(uh.code) != 200:
return None
printer('Config XML:\n%s' % ''.encode().join(configxml), debug=True)
configxml = ''.encode().join(configxml_list)
printer('Config XML:\n%s' % configxml, debug=True)
try:
root = ET.fromstring(''.encode().join(configxml))
try:
root = ET.fromstring(configxml)
except ET.ParseError:
e = get_exception()
raise SpeedtestConfigError(
'Malformed speedtest.net configuration: %s' % e
)
server_config = root.find('server-config').attrib
download = root.find('download').attrib
upload = root.find('upload').attrib
@@ -1064,16 +1182,22 @@ class Speedtest(object):
client = root.find('client').attrib
except AttributeError:
root = DOM.parseString(''.join(configxml))
try:
root = DOM.parseString(configxml)
except ExpatError:
e = get_exception()
raise SpeedtestConfigError(
'Malformed speedtest.net configuration: %s' % e
)
server_config = get_attributes_by_tag_name(root, 'server-config')
download = get_attributes_by_tag_name(root, 'download')
upload = get_attributes_by_tag_name(root, 'upload')
# times = get_attributes_by_tag_name(root, 'times')
client = get_attributes_by_tag_name(root, 'client')
ignore_servers = list(
map(int, server_config['ignoreids'].split(','))
)
ignore_servers = [
int(i) for i in server_config['ignoreids'].split(',') if i
]
ratio = int(upload['ratio'])
upload_max = int(upload['maxchunkcount'])
@@ -1113,7 +1237,13 @@ class Speedtest(object):
'upload_max': upload_count * size_count
})
self.lat_lon = (float(client['lat']), float(client['lon']))
try:
self.lat_lon = (float(client['lat']), float(client['lon']))
except ValueError:
raise SpeedtestConfigError(
'Unknown location: lat=%r lon=%r' %
(client.get('lat'), client.get('lon'))
)
printer('Config:\n%r' % self.config, debug=True)
@@ -1167,13 +1297,13 @@ class Speedtest(object):
stream = get_response_stream(uh)
serversxml = []
serversxml_list = []
while 1:
try:
serversxml.append(stream.read(1024))
serversxml_list.append(stream.read(1024))
except (OSError, EOFError):
raise ServersRetrievalError(get_exception())
if len(serversxml[-1]) == 0:
if len(serversxml_list[-1]) == 0:
break
stream.close()
@@ -1182,15 +1312,28 @@ class Speedtest(object):
if int(uh.code) != 200:
raise ServersRetrievalError()
printer('Servers XML:\n%s' % ''.encode().join(serversxml),
debug=True)
serversxml = ''.encode().join(serversxml_list)
printer('Servers XML:\n%s' % serversxml, debug=True)
try:
try:
root = ET.fromstring(''.encode().join(serversxml))
elements = root.getiterator('server')
try:
root = ET.fromstring(serversxml)
except ET.ParseError:
e = get_exception()
raise SpeedtestServersError(
'Malformed speedtest.net server list: %s' % e
)
elements = etree_iter(root, 'server')
except AttributeError:
root = DOM.parseString(''.join(serversxml))
try:
root = DOM.parseString(serversxml)
except ExpatError:
e = get_exception()
raise SpeedtestServersError(
'Malformed speedtest.net server list: %s' % e
)
elements = root.getElementsByTagName('server')
except (SyntaxError, xml.parsers.expat.ExpatError):
raise ServersRetrievalError()
@@ -1382,8 +1525,12 @@ class Speedtest(object):
printer('Best Server:\n%r' % best, debug=True)
return best
def download(self, callback=do_nothing):
"""Test download speed against speedtest.net"""
def download(self, callback=do_nothing, threads=None):
"""Test download speed against speedtest.net
A ``threads`` value of ``None`` will fall back to those dictated
by the speedtest.net configuration
"""
urls = []
for size in self.config['sizes']['download']:
@@ -1398,6 +1545,9 @@ class Speedtest(object):
build_request(url, bump=i, secure=self._secure)
)
max_threads = threads or self.config['threads']['download']
in_flight = {'threads': 0}
def producer(q, requests, request_count):
for i, request in enumerate(requests):
thread = HTTPDownloader(
@@ -1408,21 +1558,26 @@ class Speedtest(object):
opener=self._opener,
shutdown_event=self._shutdown_event
)
while in_flight['threads'] >= max_threads:
timeit.time.sleep(0.001)
thread.start()
q.put(thread, True)
in_flight['threads'] += 1
callback(i, request_count, start=True)
finished = []
def consumer(q, request_count):
_is_alive = thread_is_alive
while len(finished) < request_count:
thread = q.get(True)
while thread.isAlive():
thread.join(timeout=0.1)
while _is_alive(thread):
thread.join(timeout=0.001)
in_flight['threads'] -= 1
finished.append(sum(thread.result))
callback(thread.i, request_count, end=True)
q = Queue(self.config['threads']['download'])
q = Queue(max_threads)
prod_thread = threading.Thread(target=producer,
args=(q, requests, request_count))
cons_thread = threading.Thread(target=consumer,
@@ -1430,10 +1585,11 @@ class Speedtest(object):
start = timeit.default_timer()
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)
_is_alive = thread_is_alive
while _is_alive(prod_thread):
prod_thread.join(timeout=0.001)
while _is_alive(cons_thread):
cons_thread.join(timeout=0.001)
stop = timeit.default_timer()
self.results.bytes_received = sum(finished)
@@ -1444,8 +1600,12 @@ class Speedtest(object):
self.config['threads']['upload'] = 8
return self.results.download
def upload(self, callback=do_nothing, pre_allocate=True):
"""Test upload speed against speedtest.net"""
def upload(self, callback=do_nothing, pre_allocate=True, threads=None):
"""Test upload speed against speedtest.net
A ``threads`` value of ``None`` will fall back to those dictated
by the speedtest.net configuration
"""
sizes = []
@@ -1468,13 +1628,19 @@ class Speedtest(object):
)
if pre_allocate:
data.pre_allocate()
headers = {'Content-length': size}
requests.append(
(
build_request(self.best['url'], data, secure=self._secure),
build_request(self.best['url'], data, secure=self._secure,
headers=headers),
size
)
)
max_threads = threads or self.config['threads']['upload']
in_flight = {'threads': 0}
def producer(q, requests, request_count):
for i, request in enumerate(requests[:request_count]):
thread = HTTPUploader(
@@ -1486,21 +1652,26 @@ class Speedtest(object):
opener=self._opener,
shutdown_event=self._shutdown_event
)
while in_flight['threads'] >= max_threads:
timeit.time.sleep(0.001)
thread.start()
q.put(thread, True)
in_flight['threads'] += 1
callback(i, request_count, start=True)
finished = []
def consumer(q, request_count):
_is_alive = thread_is_alive
while len(finished) < request_count:
thread = q.get(True)
while thread.isAlive():
thread.join(timeout=0.1)
while _is_alive(thread):
thread.join(timeout=0.001)
in_flight['threads'] -= 1
finished.append(thread.result)
callback(thread.i, request_count, end=True)
q = Queue(self.config['threads']['upload'])
q = Queue(threads or self.config['threads']['upload'])
prod_thread = threading.Thread(target=producer,
args=(q, requests, request_count))
cons_thread = threading.Thread(target=consumer,
@@ -1508,9 +1679,10 @@ class Speedtest(object):
start = timeit.default_timer()
prod_thread.start()
cons_thread.start()
while prod_thread.isAlive():
_is_alive = thread_is_alive
while _is_alive(prod_thread):
prod_thread.join(timeout=0.1)
while cons_thread.isAlive():
while _is_alive(cons_thread):
cons_thread.join(timeout=0.1)
stop = timeit.default_timer()
@@ -1535,7 +1707,8 @@ def ctrl_c(shutdown_event):
def version():
"""Print the version"""
printer(__version__)
printer('speedtest-cli %s' % __version__)
printer('Python %s' % sys.version.replace('\n', ''))
sys.exit(0)
@@ -1568,11 +1741,18 @@ def parse_args():
parser.add_argument('--no-upload', dest='upload', default=True,
action='store_const', const=False,
help='Do not perform upload test')
parser.add_argument('--bytes', dest='units', action='store_const',
const=('byte', 8), default=('bit', 1),
parser.add_argument('--single', default=False, action='store_true',
help='Only use a single connection instead of '
'multiple. This simulates a typical file '
'transfer.')
parser.add_argument('--bytes', default=False, action='store_true',
help='Display values in bytes instead of bits. Does '
'not affect the image generated by --share, nor '
'output from --json or --csv')
parser.add_argument('--units', choices=tuple(SIZEMAP),
help='Determines which units to display values in. Does '
'not affect the image generated by --share, nor '
'output from --json or --csv')
parser.add_argument('--share', action='store_true',
help='Generate and provide a URL to the speedtest.net '
'share results image, not displayed with --csv')
@@ -1582,7 +1762,7 @@ def parse_args():
parser.add_argument('--csv', action='store_true', default=False,
help='Suppress verbose output, only show basic '
'information in CSV format. Speeds listed in '
'bit/s and not affected by --bytes')
'bit/s and not affected by --bytes or --units')
parser.add_argument('--csv-delimiter', default=',', type=PARSER_TYPE_STR,
help='Single character delimiter to use in CSV '
'output. Default ","')
@@ -1591,7 +1771,7 @@ def parse_args():
parser.add_argument('--json', action='store_true', default=False,
help='Suppress verbose output, only show basic '
'information in JSON format. Speeds listed in '
'bit/s and not affected by --bytes')
'bit/s and not affected by --bytes or --units')
parser.add_argument('--list', action='store_true',
help='Display a list of speedtest.net servers '
'sorted by distance')
@@ -1653,7 +1833,10 @@ def printer(string, quiet=False, debug=False, error=False, **kwargs):
return
if debug:
out = '\033[1;30mDEBUG: %s\033[0m' % string
if sys.stdout.isatty():
out = '\033[1;30mDEBUG: %s\033[0m' % string
else:
out = 'DEBUG: %s' % string
else:
out = string
@@ -1682,6 +1865,10 @@ def shell():
raise SpeedtestCLIError('Cannot supply both --no-download and '
'--no-upload')
if args.bytes and args.units:
raise SpeedtestCLIError('Cannot supply both --bytes and '
'--units')
if len(args.csv_delimiter) != 1:
raise SpeedtestCLIError('--csv-delimiter must be a single character')
@@ -1696,6 +1883,13 @@ def shell():
if debug:
DEBUG = True
if args.bytes:
units = 'Mbyte'
elif args.units:
units = args.units
else:
units = 'Mbit'
if args.simple or args.csv or args.json:
quiet = True
else:
@@ -1779,10 +1973,13 @@ def shell():
if args.download:
printer('Testing download speed', quiet,
end=('', '\n')[bool(debug)])
speedtest.download(callback=callback)
printer('Download: %0.2f M%s/s' %
((results.download / 1000.0 / 1000.0) / args.units[1],
args.units[0]),
speedtest.download(
callback=callback,
threads=(None, 1)[args.single]
)
printer('Download: %0.2f %s/s' %
(results.download / SIZEMAP[units],
units),
quiet)
else:
printer('Skipping download test', quiet)
@@ -1790,16 +1987,23 @@ def shell():
if args.upload:
printer('Testing upload speed', quiet,
end=('', '\n')[bool(debug)])
speedtest.upload(callback=callback, pre_allocate=args.pre_allocate)
printer('Upload: %0.2f M%s/s' %
((results.upload / 1000.0 / 1000.0) / args.units[1],
args.units[0]),
speedtest.upload(
callback=callback,
pre_allocate=args.pre_allocate,
threads=(None, 1)[args.single]
)
printer('Upload: %0.2f %s/s' %
(results.upload / SIZEMAP[units],
units),
quiet)
else:
printer('Skipping upload test', quiet)
printer('Results:\n%r' % results.dict(), debug=True)
if not args.simple and args.share:
results.share()
if args.simple:
printer('Ping: %s ms\nDownload: %0.2f M%s/s\nUpload: %0.2f M%s/s' %
(results.ping,
@@ -1810,8 +2014,6 @@ def shell():
elif args.csv:
printer(results.csv(delimiter=args.csv_delimiter))
elif args.json:
if args.share:
results.share()
printer(results.json())
if args.share and not machine_format:
@@ -1827,7 +2029,10 @@ def main():
e = get_exception()
# Ignore a successful exit, or argparse exit
if getattr(e, 'code', 1) not in (0, 2):
raise SystemExit('ERROR: %s' % e)
msg = '%s' % e
if not msg:
msg = '%r' % e
raise SystemExit('ERROR: %s' % msg)
if __name__ == '__main__':

View File

@@ -1,34 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2012-2016 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 warnings
DEPRECATED_MSG = ('The file speedtest_cli.py has been deprecated in favor of '
'speedtest.py\nand is available for download at:\n\n'
'https://raw.githubusercontent.com/sivel/speedtest-cli/'
'master/speedtest.py')
if __name__ == '__main__':
raise SystemExit(DEPRECATED_MSG)
else:
try:
from speedtest import *
except ImportError:
raise SystemExit(DEPRECATED_MSG)
else:
warnings.warn(DEPRECATED_MSG, UserWarning)