Compare commits
27 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 |
|
|
@ -1,4 +1,6 @@
|
||||||
language: python
|
language: python
|
||||||
|
sudo: required
|
||||||
|
dist: xenial
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
|
|
@ -33,8 +35,13 @@ matrix:
|
||||||
env: TOXENV=py35
|
env: TOXENV=py35
|
||||||
- python: 3.6
|
- python: 3.6
|
||||||
env: TOXENV=py36
|
env: TOXENV=py36
|
||||||
|
- python: 3.7
|
||||||
|
env: TOXENV=py37
|
||||||
|
- python: 3.8-dev
|
||||||
|
env: TOXENV=py38
|
||||||
- python: pypy
|
- python: pypy
|
||||||
env: TOXENV=pypy
|
env: TOXENV=pypy
|
||||||
|
dist: trusty
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- if [[ $(echo "$TOXENV" | egrep -c "py35") != 0 ]]; then pyenv global system 3.5; fi;
|
- if [[ $(echo "$TOXENV" | egrep -c "py35") != 0 ]]; then pyenv global system 3.5; fi;
|
||||||
|
|
|
||||||
21
README.rst
21
README.rst
|
|
@ -51,7 +51,8 @@ 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)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -74,21 +75,23 @@ Usage
|
||||||
::
|
::
|
||||||
|
|
||||||
$ speedtest-cli -h
|
$ speedtest-cli -h
|
||||||
usage: speedtest-cli [-h] [--no-download] [--no-upload] [--bytes] [--share]
|
usage: speedtest-cli [-h] [--no-download] [--no-upload] [--single] [--bytes]
|
||||||
[--simple] [--csv] [--csv-delimiter CSV_DELIMITER]
|
[--share] [--simple] [--csv]
|
||||||
[--csv-header] [--json] [--list] [--server SERVER]
|
[--csv-delimiter CSV_DELIMITER] [--csv-header] [--json]
|
||||||
[--exclude EXCLUDE] [--mini MINI] [--source SOURCE]
|
[--list] [--server SERVER] [--exclude EXCLUDE]
|
||||||
[--timeout TIMEOUT] [--secure] [--no-pre-allocate]
|
[--mini MINI] [--source SOURCE] [--timeout TIMEOUT]
|
||||||
[--version]
|
[--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.
|
||||||
--------------------------------------------------------------------------
|
--------------------------------------------------------------------------
|
||||||
https://github.com/sivel/speedtest-cli
|
https://github.com/sivel/speedtest-cli
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--no-download Do not perform download test
|
--no-download Do not perform download test
|
||||||
--no-upload Do not perform upload 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
|
--bytes Display values in bytes instead of bits. Does not
|
||||||
affect the image generated by --share, nor output from
|
affect the image generated by --share, nor output from
|
||||||
--json or --csv
|
--json or --csv
|
||||||
|
|
|
||||||
5
setup.py
5
setup.py
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2012-2018 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
|
||||||
|
|
@ -92,5 +92,8 @@ setup(
|
||||||
'Programming Language :: Python :: 3.5',
|
'Programming Language :: Python :: 3.5',
|
||||||
'Programming Language :: Python :: 3.6',
|
'Programming Language :: Python :: 3.6',
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
|
'Programming Language :: Python :: 3.8',
|
||||||
|
'Programming Language :: Python :: 3.9',
|
||||||
|
'Programming Language :: Python :: 3.10',
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@ Do not perform download test
|
||||||
Do not perform upload test
|
Do not perform upload test
|
||||||
.RE
|
.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, nor output from \-\-json or \-\-csv
|
Display values in bytes instead of bits. Does not affect the image generated by \-\-share, nor output from \-\-json or \-\-csv
|
||||||
|
|
|
||||||
234
speedtest.py
234
speedtest.py
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2012-2018 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
|
||||||
|
|
@ -15,18 +15,18 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import csv
|
import csv
|
||||||
import sys
|
import datetime
|
||||||
import math
|
|
||||||
import errno
|
import errno
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
import signal
|
import signal
|
||||||
import socket
|
import socket
|
||||||
import timeit
|
import sys
|
||||||
import datetime
|
|
||||||
import platform
|
|
||||||
import threading
|
import threading
|
||||||
|
import timeit
|
||||||
import xml.parsers.expat
|
import xml.parsers.expat
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -36,7 +36,7 @@ except ImportError:
|
||||||
gzip = None
|
gzip = None
|
||||||
GZIP_BASE = object
|
GZIP_BASE = object
|
||||||
|
|
||||||
__version__ = '2.0.2'
|
__version__ = '2.1.4b1'
|
||||||
|
|
||||||
|
|
||||||
class FakeShutdownEvent(object):
|
class FakeShutdownEvent(object):
|
||||||
|
|
@ -49,10 +49,16 @@ class FakeShutdownEvent(object):
|
||||||
"Dummy method to always return false"""
|
"Dummy method to always return false"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
is_set = isSet
|
||||||
|
|
||||||
|
|
||||||
# Some global variables we use
|
# Some global variables we use
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
_GLOBAL_DEFAULT_TIMEOUT = object()
|
_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)
|
||||||
|
|
||||||
# Begin import game to handle Python 2 and Python 3
|
# Begin import game to handle Python 2 and Python 3
|
||||||
try:
|
try:
|
||||||
|
|
@ -64,14 +70,15 @@ except ImportError:
|
||||||
json = None
|
json = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import xml.etree.cElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
except ImportError:
|
|
||||||
try:
|
try:
|
||||||
import xml.etree.ElementTree as ET
|
from xml.etree.ElementTree import _Element as ET_Element
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from xml.dom import minidom as DOM
|
pass
|
||||||
from xml.parsers.expat import ExpatError
|
except ImportError:
|
||||||
ET = None
|
from xml.dom import minidom as DOM
|
||||||
|
from xml.parsers.expat import ExpatError
|
||||||
|
ET = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urllib2 import (urlopen, Request, HTTPError, URLError,
|
from urllib2 import (urlopen, Request, HTTPError, URLError,
|
||||||
|
|
@ -97,6 +104,11 @@ except ImportError:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HTTPSConnection = None
|
HTTPSConnection = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from httplib import FakeSocket
|
||||||
|
except ImportError:
|
||||||
|
FakeSocket = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from Queue import Queue
|
from Queue import Queue
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
@ -257,7 +269,6 @@ else:
|
||||||
write(arg)
|
write(arg)
|
||||||
write(end)
|
write(end)
|
||||||
|
|
||||||
|
|
||||||
# Exception "constants" to support Python 2 through Python 3
|
# Exception "constants" to support Python 2 through Python 3
|
||||||
try:
|
try:
|
||||||
import ssl
|
import ssl
|
||||||
|
|
@ -274,6 +285,23 @@ except ImportError:
|
||||||
ssl = None
|
ssl = None
|
||||||
HTTP_ERRORS = (HTTPError, URLError, socket.error, BadStatusLine)
|
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):
|
class SpeedtestException(Exception):
|
||||||
"""Base exception for this module"""
|
"""Base exception for this module"""
|
||||||
|
|
@ -394,6 +422,8 @@ class SpeedtestHTTPConnection(HTTPConnection):
|
||||||
source_address = kwargs.pop('source_address', None)
|
source_address = kwargs.pop('source_address', None)
|
||||||
timeout = kwargs.pop('timeout', 10)
|
timeout = kwargs.pop('timeout', 10)
|
||||||
|
|
||||||
|
self._tunnel_host = None
|
||||||
|
|
||||||
HTTPConnection.__init__(self, *args, **kwargs)
|
HTTPConnection.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
self.source_address = source_address
|
self.source_address = source_address
|
||||||
|
|
@ -414,17 +444,23 @@ class SpeedtestHTTPConnection(HTTPConnection):
|
||||||
self.source_address
|
self.source_address
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self._tunnel_host:
|
||||||
|
self._tunnel()
|
||||||
|
|
||||||
|
|
||||||
if HTTPSConnection:
|
if HTTPSConnection:
|
||||||
class SpeedtestHTTPSConnection(HTTPSConnection,
|
class SpeedtestHTTPSConnection(HTTPSConnection):
|
||||||
SpeedtestHTTPConnection):
|
|
||||||
"""Custom HTTPSConnection to support source_address across
|
"""Custom HTTPSConnection to support source_address across
|
||||||
Python 2.4 - Python 3
|
Python 2.4 - Python 3
|
||||||
"""
|
"""
|
||||||
|
default_port = 443
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
source_address = kwargs.pop('source_address', None)
|
source_address = kwargs.pop('source_address', None)
|
||||||
timeout = kwargs.pop('timeout', 10)
|
timeout = kwargs.pop('timeout', 10)
|
||||||
|
|
||||||
|
self._tunnel_host = None
|
||||||
|
|
||||||
HTTPSConnection.__init__(self, *args, **kwargs)
|
HTTPSConnection.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
@ -432,17 +468,51 @@ if HTTPSConnection:
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"Connect to a host on a given (SSL) port."
|
"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 ssl:
|
if ssl:
|
||||||
if hasattr(ssl, 'SSLContext'):
|
|
||||||
kwargs['server_hostname'] = self.host
|
|
||||||
try:
|
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)
|
self.sock = self._context.wrap_socket(self.sock, **kwargs)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.sock = ssl.wrap_socket(self.sock, **kwargs)
|
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):
|
def _build_connection(connection, source_address, timeout, context=None):
|
||||||
|
|
@ -607,7 +677,8 @@ def build_user_agent():
|
||||||
|
|
||||||
ua_tuple = (
|
ua_tuple = (
|
||||||
'Mozilla/5.0',
|
'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(),
|
'Python/%s' % platform.python_version(),
|
||||||
'(KHTML, like Gecko)',
|
'(KHTML, like Gecko)',
|
||||||
'speedtest-cli/%s' % __version__
|
'speedtest-cli/%s' % __version__
|
||||||
|
|
@ -666,6 +737,8 @@ def catch_request(request, opener=None):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
uh = _open(request)
|
uh = _open(request)
|
||||||
|
if request.get_full_url() != uh.geturl():
|
||||||
|
printer('Redirected to %s' % uh.geturl(), debug=True)
|
||||||
return uh, False
|
return uh, False
|
||||||
except HTTP_ERRORS:
|
except HTTP_ERRORS:
|
||||||
e = get_exception()
|
e = get_exception()
|
||||||
|
|
@ -705,7 +778,7 @@ def print_dots(shutdown_event):
|
||||||
status
|
status
|
||||||
"""
|
"""
|
||||||
def inner(current, total, start=False, end=False):
|
def inner(current, total, start=False, end=False):
|
||||||
if shutdown_event.isSet():
|
if event_is_set(shutdown_event):
|
||||||
return
|
return
|
||||||
|
|
||||||
sys.stdout.write('.')
|
sys.stdout.write('.')
|
||||||
|
|
@ -744,7 +817,7 @@ class HTTPDownloader(threading.Thread):
|
||||||
try:
|
try:
|
||||||
if (timeit.default_timer() - self.starttime) <= self.timeout:
|
if (timeit.default_timer() - self.starttime) <= self.timeout:
|
||||||
f = self._opener(self.request)
|
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) <=
|
(timeit.default_timer() - self.starttime) <=
|
||||||
self.timeout):
|
self.timeout):
|
||||||
self.result.append(len(f.read(10240)))
|
self.result.append(len(f.read(10240)))
|
||||||
|
|
@ -753,6 +826,8 @@ class HTTPDownloader(threading.Thread):
|
||||||
f.close()
|
f.close()
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
except HTTP_ERRORS:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HTTPUploaderData(object):
|
class HTTPUploaderData(object):
|
||||||
|
|
@ -798,7 +873,7 @@ class HTTPUploaderData(object):
|
||||||
|
|
||||||
def read(self, n=10240):
|
def read(self, n=10240):
|
||||||
if ((timeit.default_timer() - self.start) <= self.timeout and
|
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)
|
chunk = self.data.read(n)
|
||||||
self.total.append(len(chunk))
|
self.total.append(len(chunk))
|
||||||
return chunk
|
return chunk
|
||||||
|
|
@ -818,7 +893,7 @@ class HTTPUploader(threading.Thread):
|
||||||
self.request = request
|
self.request = request
|
||||||
self.request.data.start = self.starttime = start
|
self.request.data.start = self.starttime = start
|
||||||
self.size = size
|
self.size = size
|
||||||
self.result = None
|
self.result = 0
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.i = i
|
self.i = i
|
||||||
|
|
||||||
|
|
@ -836,7 +911,7 @@ class HTTPUploader(threading.Thread):
|
||||||
request = self.request
|
request = self.request
|
||||||
try:
|
try:
|
||||||
if ((timeit.default_timer() - self.starttime) <= self.timeout and
|
if ((timeit.default_timer() - self.starttime) <= self.timeout and
|
||||||
not self._shutdown_event.isSet()):
|
not event_is_set(self._shutdown_event)):
|
||||||
try:
|
try:
|
||||||
f = self._opener(request)
|
f = self._opener(request)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
|
@ -853,6 +928,8 @@ class HTTPUploader(threading.Thread):
|
||||||
self.result = 0
|
self.result = 0
|
||||||
except (IOError, SpeedtestUploadTimeout):
|
except (IOError, SpeedtestUploadTimeout):
|
||||||
self.result = sum(self.request.data.total)
|
self.result = sum(self.request.data.total)
|
||||||
|
except HTTP_ERRORS:
|
||||||
|
self.result = 0
|
||||||
|
|
||||||
|
|
||||||
class SpeedtestResults(object):
|
class SpeedtestResults(object):
|
||||||
|
|
@ -1041,10 +1118,7 @@ class Speedtest(object):
|
||||||
@property
|
@property
|
||||||
def best(self):
|
def best(self):
|
||||||
if not self._best:
|
if not self._best:
|
||||||
raise SpeedtestMissingBestServer(
|
self.get_best_server()
|
||||||
'get_best_server not called or not able to determine best '
|
|
||||||
'server'
|
|
||||||
)
|
|
||||||
return self._best
|
return self._best
|
||||||
|
|
||||||
def get_config(self):
|
def get_config(self):
|
||||||
|
|
@ -1109,9 +1183,9 @@ class Speedtest(object):
|
||||||
# times = get_attributes_by_tag_name(root, 'times')
|
# times = get_attributes_by_tag_name(root, 'times')
|
||||||
client = get_attributes_by_tag_name(root, 'client')
|
client = get_attributes_by_tag_name(root, 'client')
|
||||||
|
|
||||||
ignore_servers = list(
|
ignore_servers = [
|
||||||
map(int, server_config['ignoreids'].split(','))
|
int(i) for i in server_config['ignoreids'].split(',') if i
|
||||||
)
|
]
|
||||||
|
|
||||||
ratio = int(upload['ratio'])
|
ratio = int(upload['ratio'])
|
||||||
upload_max = int(upload['maxchunkcount'])
|
upload_max = int(upload['maxchunkcount'])
|
||||||
|
|
@ -1239,7 +1313,7 @@ class Speedtest(object):
|
||||||
raise SpeedtestServersError(
|
raise SpeedtestServersError(
|
||||||
'Malformed speedtest.net server list: %s' % e
|
'Malformed speedtest.net server list: %s' % e
|
||||||
)
|
)
|
||||||
elements = root.getiterator('server')
|
elements = etree_iter(root, 'server')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
try:
|
try:
|
||||||
root = DOM.parseString(serversxml)
|
root = DOM.parseString(serversxml)
|
||||||
|
|
@ -1439,8 +1513,12 @@ class Speedtest(object):
|
||||||
printer('Best Server:\n%r' % best, debug=True)
|
printer('Best Server:\n%r' % best, debug=True)
|
||||||
return best
|
return best
|
||||||
|
|
||||||
def download(self, callback=do_nothing):
|
def download(self, callback=do_nothing, threads=None):
|
||||||
"""Test download speed against speedtest.net"""
|
"""Test download speed against speedtest.net
|
||||||
|
|
||||||
|
A ``threads`` value of ``None`` will fall back to those dictated
|
||||||
|
by the speedtest.net configuration
|
||||||
|
"""
|
||||||
|
|
||||||
urls = []
|
urls = []
|
||||||
for size in self.config['sizes']['download']:
|
for size in self.config['sizes']['download']:
|
||||||
|
|
@ -1455,6 +1533,9 @@ class Speedtest(object):
|
||||||
build_request(url, bump=i, secure=self._secure)
|
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):
|
def producer(q, requests, request_count):
|
||||||
for i, request in enumerate(requests):
|
for i, request in enumerate(requests):
|
||||||
thread = HTTPDownloader(
|
thread = HTTPDownloader(
|
||||||
|
|
@ -1465,21 +1546,26 @@ class Speedtest(object):
|
||||||
opener=self._opener,
|
opener=self._opener,
|
||||||
shutdown_event=self._shutdown_event
|
shutdown_event=self._shutdown_event
|
||||||
)
|
)
|
||||||
|
while in_flight['threads'] >= max_threads:
|
||||||
|
timeit.time.sleep(0.001)
|
||||||
thread.start()
|
thread.start()
|
||||||
q.put(thread, True)
|
q.put(thread, True)
|
||||||
|
in_flight['threads'] += 1
|
||||||
callback(i, request_count, start=True)
|
callback(i, request_count, start=True)
|
||||||
|
|
||||||
finished = []
|
finished = []
|
||||||
|
|
||||||
def consumer(q, request_count):
|
def consumer(q, request_count):
|
||||||
|
_is_alive = thread_is_alive
|
||||||
while len(finished) < request_count:
|
while len(finished) < request_count:
|
||||||
thread = q.get(True)
|
thread = q.get(True)
|
||||||
while thread.isAlive():
|
while _is_alive(thread):
|
||||||
thread.join(timeout=0.1)
|
thread.join(timeout=0.001)
|
||||||
|
in_flight['threads'] -= 1
|
||||||
finished.append(sum(thread.result))
|
finished.append(sum(thread.result))
|
||||||
callback(thread.i, request_count, end=True)
|
callback(thread.i, request_count, end=True)
|
||||||
|
|
||||||
q = Queue(self.config['threads']['download'])
|
q = Queue(max_threads)
|
||||||
prod_thread = threading.Thread(target=producer,
|
prod_thread = threading.Thread(target=producer,
|
||||||
args=(q, requests, request_count))
|
args=(q, requests, request_count))
|
||||||
cons_thread = threading.Thread(target=consumer,
|
cons_thread = threading.Thread(target=consumer,
|
||||||
|
|
@ -1487,10 +1573,11 @@ class Speedtest(object):
|
||||||
start = timeit.default_timer()
|
start = timeit.default_timer()
|
||||||
prod_thread.start()
|
prod_thread.start()
|
||||||
cons_thread.start()
|
cons_thread.start()
|
||||||
while prod_thread.isAlive():
|
_is_alive = thread_is_alive
|
||||||
prod_thread.join(timeout=0.1)
|
while _is_alive(prod_thread):
|
||||||
while cons_thread.isAlive():
|
prod_thread.join(timeout=0.001)
|
||||||
cons_thread.join(timeout=0.1)
|
while _is_alive(cons_thread):
|
||||||
|
cons_thread.join(timeout=0.001)
|
||||||
|
|
||||||
stop = timeit.default_timer()
|
stop = timeit.default_timer()
|
||||||
self.results.bytes_received = sum(finished)
|
self.results.bytes_received = sum(finished)
|
||||||
|
|
@ -1501,8 +1588,12 @@ class Speedtest(object):
|
||||||
self.config['threads']['upload'] = 8
|
self.config['threads']['upload'] = 8
|
||||||
return self.results.download
|
return self.results.download
|
||||||
|
|
||||||
def upload(self, callback=do_nothing, pre_allocate=True):
|
def upload(self, callback=do_nothing, pre_allocate=True, threads=None):
|
||||||
"""Test upload speed against speedtest.net"""
|
"""Test upload speed against speedtest.net
|
||||||
|
|
||||||
|
A ``threads`` value of ``None`` will fall back to those dictated
|
||||||
|
by the speedtest.net configuration
|
||||||
|
"""
|
||||||
|
|
||||||
sizes = []
|
sizes = []
|
||||||
|
|
||||||
|
|
@ -1525,13 +1616,19 @@ class Speedtest(object):
|
||||||
)
|
)
|
||||||
if pre_allocate:
|
if pre_allocate:
|
||||||
data.pre_allocate()
|
data.pre_allocate()
|
||||||
|
|
||||||
|
headers = {'Content-length': size}
|
||||||
requests.append(
|
requests.append(
|
||||||
(
|
(
|
||||||
build_request(self.best['url'], data, secure=self._secure),
|
build_request(self.best['url'], data, secure=self._secure,
|
||||||
|
headers=headers),
|
||||||
size
|
size
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
max_threads = threads or self.config['threads']['upload']
|
||||||
|
in_flight = {'threads': 0}
|
||||||
|
|
||||||
def producer(q, requests, request_count):
|
def producer(q, requests, request_count):
|
||||||
for i, request in enumerate(requests[:request_count]):
|
for i, request in enumerate(requests[:request_count]):
|
||||||
thread = HTTPUploader(
|
thread = HTTPUploader(
|
||||||
|
|
@ -1543,21 +1640,26 @@ class Speedtest(object):
|
||||||
opener=self._opener,
|
opener=self._opener,
|
||||||
shutdown_event=self._shutdown_event
|
shutdown_event=self._shutdown_event
|
||||||
)
|
)
|
||||||
|
while in_flight['threads'] >= max_threads:
|
||||||
|
timeit.time.sleep(0.001)
|
||||||
thread.start()
|
thread.start()
|
||||||
q.put(thread, True)
|
q.put(thread, True)
|
||||||
|
in_flight['threads'] += 1
|
||||||
callback(i, request_count, start=True)
|
callback(i, request_count, start=True)
|
||||||
|
|
||||||
finished = []
|
finished = []
|
||||||
|
|
||||||
def consumer(q, request_count):
|
def consumer(q, request_count):
|
||||||
|
_is_alive = thread_is_alive
|
||||||
while len(finished) < request_count:
|
while len(finished) < request_count:
|
||||||
thread = q.get(True)
|
thread = q.get(True)
|
||||||
while thread.isAlive():
|
while _is_alive(thread):
|
||||||
thread.join(timeout=0.1)
|
thread.join(timeout=0.001)
|
||||||
|
in_flight['threads'] -= 1
|
||||||
finished.append(thread.result)
|
finished.append(thread.result)
|
||||||
callback(thread.i, request_count, end=True)
|
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,
|
prod_thread = threading.Thread(target=producer,
|
||||||
args=(q, requests, request_count))
|
args=(q, requests, request_count))
|
||||||
cons_thread = threading.Thread(target=consumer,
|
cons_thread = threading.Thread(target=consumer,
|
||||||
|
|
@ -1565,9 +1667,10 @@ class Speedtest(object):
|
||||||
start = timeit.default_timer()
|
start = timeit.default_timer()
|
||||||
prod_thread.start()
|
prod_thread.start()
|
||||||
cons_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)
|
prod_thread.join(timeout=0.1)
|
||||||
while cons_thread.isAlive():
|
while _is_alive(cons_thread):
|
||||||
cons_thread.join(timeout=0.1)
|
cons_thread.join(timeout=0.1)
|
||||||
|
|
||||||
stop = timeit.default_timer()
|
stop = timeit.default_timer()
|
||||||
|
|
@ -1592,7 +1695,8 @@ def ctrl_c(shutdown_event):
|
||||||
def version():
|
def version():
|
||||||
"""Print the version"""
|
"""Print the version"""
|
||||||
|
|
||||||
printer(__version__)
|
printer('speedtest-cli %s' % __version__)
|
||||||
|
printer('Python %s' % sys.version.replace('\n', ''))
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1625,6 +1729,10 @@ def parse_args():
|
||||||
parser.add_argument('--no-upload', dest='upload', default=True,
|
parser.add_argument('--no-upload', dest='upload', default=True,
|
||||||
action='store_const', const=False,
|
action='store_const', const=False,
|
||||||
help='Do not perform upload test')
|
help='Do not perform upload test')
|
||||||
|
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', dest='units', action='store_const',
|
parser.add_argument('--bytes', dest='units', action='store_const',
|
||||||
const=('byte', 8), default=('bit', 1),
|
const=('byte', 8), default=('bit', 1),
|
||||||
help='Display values in bytes instead of bits. Does '
|
help='Display values in bytes instead of bits. Does '
|
||||||
|
|
@ -1839,7 +1947,10 @@ def shell():
|
||||||
if args.download:
|
if args.download:
|
||||||
printer('Testing download speed', quiet,
|
printer('Testing download speed', quiet,
|
||||||
end=('', '\n')[bool(debug)])
|
end=('', '\n')[bool(debug)])
|
||||||
speedtest.download(callback=callback)
|
speedtest.download(
|
||||||
|
callback=callback,
|
||||||
|
threads=(None, 1)[args.single]
|
||||||
|
)
|
||||||
printer('Download: %0.2f M%s/s' %
|
printer('Download: %0.2f M%s/s' %
|
||||||
((results.download / 1000.0 / 1000.0) / args.units[1],
|
((results.download / 1000.0 / 1000.0) / args.units[1],
|
||||||
args.units[0]),
|
args.units[0]),
|
||||||
|
|
@ -1850,7 +1961,11 @@ def shell():
|
||||||
if args.upload:
|
if args.upload:
|
||||||
printer('Testing upload speed', quiet,
|
printer('Testing upload speed', quiet,
|
||||||
end=('', '\n')[bool(debug)])
|
end=('', '\n')[bool(debug)])
|
||||||
speedtest.upload(callback=callback, pre_allocate=args.pre_allocate)
|
speedtest.upload(
|
||||||
|
callback=callback,
|
||||||
|
pre_allocate=args.pre_allocate,
|
||||||
|
threads=(None, 1)[args.single]
|
||||||
|
)
|
||||||
printer('Upload: %0.2f M%s/s' %
|
printer('Upload: %0.2f M%s/s' %
|
||||||
((results.upload / 1000.0 / 1000.0) / args.units[1],
|
((results.upload / 1000.0 / 1000.0) / args.units[1],
|
||||||
args.units[0]),
|
args.units[0]),
|
||||||
|
|
@ -1888,7 +2003,10 @@ def main():
|
||||||
e = get_exception()
|
e = get_exception()
|
||||||
# Ignore a successful exit, or argparse exit
|
# Ignore a successful exit, or argparse exit
|
||||||
if getattr(e, 'code', 1) not in (0, 2):
|
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__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue