mirror of
https://github.com/sivel/speedtest-cli.git
synced 2025-12-16 09:40:05 +01:00
Compare commits
10 Commits
thread-lim
...
fae3918bfc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fae3918bfc | ||
|
|
12d868f8d9 | ||
|
|
f317af3bf4 | ||
|
|
8fcd4a019e | ||
|
|
22210ca352 | ||
|
|
42e96b13dd | ||
|
|
cadc68b5ae | ||
|
|
db46af8bcd | ||
|
|
c58ad3367b | ||
|
|
266e53c256 |
3
setup.py
3
setup.py
@@ -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',
|
||||
]
|
||||
)
|
||||
|
||||
147
speedtest.py
147
speedtest.py
@@ -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.1.2a'
|
||||
__version__ = '2.1.4b1'
|
||||
|
||||
|
||||
class FakeShutdownEvent(object):
|
||||
@@ -49,6 +49,8 @@ class FakeShutdownEvent(object):
|
||||
"Dummy method to always return false"""
|
||||
return False
|
||||
|
||||
is_set = isSet
|
||||
|
||||
|
||||
# Some global variables we use
|
||||
DEBUG = False
|
||||
@@ -56,6 +58,19 @@ _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:
|
||||
@@ -266,17 +281,6 @@ else:
|
||||
write(arg)
|
||||
write(end)
|
||||
|
||||
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
|
||||
|
||||
|
||||
# Exception "constants" to support Python 2 through Python 3
|
||||
try:
|
||||
import ssl
|
||||
@@ -293,6 +297,23 @@ except ImportError:
|
||||
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):
|
||||
"""Base exception for this module"""
|
||||
@@ -413,6 +434,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
|
||||
@@ -433,17 +456,23 @@ 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
|
||||
@@ -451,14 +480,30 @@ if HTTPSConnection:
|
||||
|
||||
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()
|
||||
|
||||
if ssl:
|
||||
try:
|
||||
kwargs = {}
|
||||
if hasattr(ssl, 'SSLContext'):
|
||||
kwargs['server_hostname'] = self.host
|
||||
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)
|
||||
@@ -745,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('.')
|
||||
@@ -784,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)))
|
||||
@@ -793,6 +838,8 @@ class HTTPDownloader(threading.Thread):
|
||||
f.close()
|
||||
except IOError:
|
||||
pass
|
||||
except HTTP_ERRORS:
|
||||
pass
|
||||
|
||||
|
||||
class HTTPUploaderData(object):
|
||||
@@ -838,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
|
||||
@@ -858,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
|
||||
|
||||
@@ -876,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:
|
||||
@@ -893,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):
|
||||
@@ -920,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
|
||||
|
||||
@@ -1146,9 +1195,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'])
|
||||
@@ -1696,11 +1745,14 @@ def parse_args():
|
||||
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),
|
||||
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')
|
||||
@@ -1710,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 ","')
|
||||
@@ -1719,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')
|
||||
@@ -1813,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')
|
||||
|
||||
@@ -1827,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:
|
||||
@@ -1914,9 +1977,9 @@ def shell():
|
||||
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]),
|
||||
printer('Download: %0.2f %s/s' %
|
||||
(results.download / SIZEMAP[units],
|
||||
units),
|
||||
quiet)
|
||||
else:
|
||||
printer('Skipping download test', quiet)
|
||||
@@ -1929,9 +1992,9 @@ def shell():
|
||||
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]),
|
||||
printer('Upload: %0.2f %s/s' %
|
||||
(results.upload / SIZEMAP[units],
|
||||
units),
|
||||
quiet)
|
||||
else:
|
||||
printer('Skipping upload test', quiet)
|
||||
|
||||
Reference in New Issue
Block a user