Compare commits
29 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 |
|
|
@ -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;
|
||||
|
|
|
|||
21
README.rst
21
README.rst
|
|
@ -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
|
||||
|
|
|
|||
5
setup.py
5
setup.py
|
|
@ -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
|
||||
|
|
@ -92,5 +92,8 @@ setup(
|
|||
'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',
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ Do not perform download test
|
|||
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
|
||||
|
|
|
|||
255
speedtest.py
255
speedtest.py
|
|
@ -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.1'
|
||||
__version__ = '2.1.4b1'
|
||||
|
||||
|
||||
class FakeShutdownEvent(object):
|
||||
|
|
@ -49,10 +49,16 @@ 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)
|
||||
|
||||
# Begin import game to handle Python 2 and Python 3
|
||||
try:
|
||||
|
|
@ -64,14 +70,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
|
||||
from xml.parsers.expat import ExpatError
|
||||
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,
|
||||
|
|
@ -85,9 +92,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
|
||||
|
|
@ -97,6 +104,11 @@ except ImportError:
|
|||
except ImportError:
|
||||
HTTPSConnection = None
|
||||
|
||||
try:
|
||||
from httplib import FakeSocket
|
||||
except ImportError:
|
||||
FakeSocket = None
|
||||
|
||||
try:
|
||||
from Queue import Queue
|
||||
except ImportError:
|
||||
|
|
@ -257,7 +269,6 @@ else:
|
|||
write(arg)
|
||||
write(end)
|
||||
|
||||
|
||||
# Exception "constants" to support Python 2 through Python 3
|
||||
try:
|
||||
import ssl
|
||||
|
|
@ -266,10 +277,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):
|
||||
|
|
@ -391,6 +422,8 @@ class SpeedtestHTTPConnection(HTTPConnection):
|
|||
source_address = kwargs.pop('source_address', None)
|
||||
timeout = kwargs.pop('timeout', 10)
|
||||
|
||||
self._tunnel_host = None
|
||||
|
||||
HTTPConnection.__init__(self, *args, **kwargs)
|
||||
|
||||
self.source_address = source_address
|
||||
|
|
@ -411,33 +444,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)
|
||||
context = kwargs.pop('context', None)
|
||||
timeout = kwargs.pop('timeout', 10)
|
||||
|
||||
self._tunnel_host = None
|
||||
|
||||
HTTPSConnection.__init__(self, *args, **kwargs)
|
||||
|
||||
self.source_address = source_address
|
||||
self._context = context
|
||||
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):
|
||||
|
|
@ -602,7 +677,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__
|
||||
|
|
@ -661,6 +737,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()
|
||||
|
|
@ -700,7 +778,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('.')
|
||||
|
|
@ -739,7 +817,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)))
|
||||
|
|
@ -748,6 +826,8 @@ class HTTPDownloader(threading.Thread):
|
|||
f.close()
|
||||
except IOError:
|
||||
pass
|
||||
except HTTP_ERRORS:
|
||||
pass
|
||||
|
||||
|
||||
class HTTPUploaderData(object):
|
||||
|
|
@ -793,7 +873,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
|
||||
|
|
@ -813,7 +893,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
|
||||
|
||||
|
|
@ -831,7 +911,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:
|
||||
|
|
@ -848,6 +928,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):
|
||||
|
|
@ -1036,10 +1118,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):
|
||||
|
|
@ -1104,9 +1183,9 @@ class Speedtest(object):
|
|||
# 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'])
|
||||
|
|
@ -1234,7 +1313,7 @@ class Speedtest(object):
|
|||
raise SpeedtestServersError(
|
||||
'Malformed speedtest.net server list: %s' % e
|
||||
)
|
||||
elements = root.getiterator('server')
|
||||
elements = etree_iter(root, 'server')
|
||||
except AttributeError:
|
||||
try:
|
||||
root = DOM.parseString(serversxml)
|
||||
|
|
@ -1434,8 +1513,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']:
|
||||
|
|
@ -1450,6 +1533,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(
|
||||
|
|
@ -1460,21 +1546,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,
|
||||
|
|
@ -1482,10 +1573,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)
|
||||
|
|
@ -1496,8 +1588,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 = []
|
||||
|
||||
|
|
@ -1520,13 +1616,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(
|
||||
|
|
@ -1538,21 +1640,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,
|
||||
|
|
@ -1560,9 +1667,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()
|
||||
|
|
@ -1587,7 +1695,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)
|
||||
|
||||
|
||||
|
|
@ -1620,6 +1729,10 @@ 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('--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',
|
||||
const=('byte', 8), default=('bit', 1),
|
||||
help='Display values in bytes instead of bits. Does '
|
||||
|
|
@ -1834,7 +1947,10 @@ def shell():
|
|||
if args.download:
|
||||
printer('Testing download speed', quiet,
|
||||
end=('', '\n')[bool(debug)])
|
||||
speedtest.download(callback=callback)
|
||||
speedtest.download(
|
||||
callback=callback,
|
||||
threads=(None, 1)[args.single]
|
||||
)
|
||||
printer('Download: %0.2f M%s/s' %
|
||||
((results.download / 1000.0 / 1000.0) / args.units[1],
|
||||
args.units[0]),
|
||||
|
|
@ -1845,7 +1961,11 @@ def shell():
|
|||
if args.upload:
|
||||
printer('Testing upload speed', quiet,
|
||||
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' %
|
||||
((results.upload / 1000.0 / 1000.0) / args.units[1],
|
||||
args.units[0]),
|
||||
|
|
@ -1883,7 +2003,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__':
|
||||
|
|
|
|||
Loading…
Reference in New Issue