You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

223 lines
6.2 KiB
Python

#!/usr/bin/env python
'''
Python CLI-tool (without need for a GUI) to measure Internet speed with fast.com
'''
import os
import json
import urllib
import urllib2
import sys
#import jsbeautifier
import time
#import threading
from threading import Thread
def gethtmlresult(url,result,index):
'''
get the stuff from url in chuncks of size CHUNK, and keep writing the number of bytes retrieved into result[index]
'''
#print url, index, result[index]
req = urllib2.urlopen(url)
CHUNK = 100 * 1024
i=1
while True:
chunk = req.read(CHUNK)
if not chunk: break
result[index] = i*CHUNK
i=i+1
def application_bytes_to_networkbits(bytes):
# convert bytes (at application layer) to bits (at network layer)
return bytes * 8 * 1.0415
# 8 for bits versus bytes
# 1.0416 for application versus network layers
def findipv4(fqdn):
'''
find IPv4 address of fqdn
'''
import socket
ipv4 = socket.getaddrinfo(fqdn, 80, socket.AF_INET)[0][4][0]
return ipv4
def findipv6(fqdn):
'''
find IPv6 address of fqdn
'''
import socket
ipv6 = socket.getaddrinfo(fqdn, 80, socket.AF_INET6)[0][4][0]
return ipv6
def fast_com(verbose=False, maxtime=15, forceipv4=False, forceipv6=False):
'''
verbose: print debug output
maxtime: max time in seconds to monitor speedtest
forceipv4: force speed test over IPv4
forceipv6: force speed test over IPv6
'''
# go to fast.com to get the javascript file
url = 'https://fast.com/'
try:
urlresult = urllib.urlopen(url)
except:
# no connection at all?
return 0
response = urlresult.read()
for line in response.split('\n'):
# We're looking for a line like
# <script src="/app-40647a.js"></script>
if line.find('script src') >= 0:
#print line
jsname = line.split('"')[1] # At time of writing: '/app-40647a.js'
# From that javascript file, get the token:
url = 'https://fast.com' + jsname
if verbose: print "javascript url is", url
urlresult = urllib.urlopen(url)
allJSstuff = urlresult.read() # this is a obfuscated Javascript file
'''
# OLD STUFF ... beautiful, but needs the js-beautifier module, which was a non-stardard requirement
res = jsbeautifier.beautify(allJSstuff) # ... so un-obfuscate it
for line in res.split('\n'):
if line.find('token:') >= 0:
token = line.split('"')[1]
if verbose: print "token is", token
'''
'''
We're searching for the "token:" in this string:
.dummy,DEFAULT_PARAMS={https:!0,token:"YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm",urlCount:3,e
'''
for line in allJSstuff.split(','):
if line.find('token:') >= 0:
if verbose: print "line is", line
token = line.split('"')[1]
if verbose: print "token is", token
if token:
break
# https://api.fast.com/netflix/speedtest?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=3
# https://api.fast.com/netflix/speedtest?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=3
# lynx --dump 'https://api.fast.com/netflix/speedtest?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=3' | python -mjson.tool
#url = 'https://api.fast.com/netflix/speedtest?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=3'
# With the token, get the (3) speed-test-URLS from api.fast.com (which will be in JSON format):
baseurl = 'https://api.fast.com/'
if forceipv4:
# force IPv4 by connecting to an IPv4 address of api.fast.com (over ... HTTP)
ipv4 = findipv4('api.fast.com')
baseurl = 'http://' + ipv4 + '/' # HTTPS does not work IPv4 addresses, thus use HTTP
elif forceipv6:
# force IPv6
ipv6 = findipv6('api.fast.com')
baseurl = 'http://[' + ipv6 + ']/'
url = baseurl + 'netflix/speedtest?https=true&token=' + token + '&urlCount=3' # Not more than 3 possible
if verbose: print "API url is", url
try:
urlresult = urllib2.urlopen(url, None, 2) # 2 second time-out
except:
# not good
if verbose: print "No connection possible" # probably IPv6, or just no network
return 0 # no connection, thus no speed
jsonresult = urlresult.read()
parsedjson = json.loads(jsonresult)
# Prepare for getting those URLs in a threaded way:
amount = len(parsedjson)
if verbose: print "Number of URLs:", amount
threads = [None] * amount
results = [0] * amount
urls = [None] * amount
i = 0
for jsonelement in parsedjson:
urls[i] = jsonelement['url'] # fill out speed test url from the json format
if verbose: print jsonelement['url']
i = i+1
# Let's check whether it's IPv6:
for url in urls:
fqdn = url.split('/')[2]
try:
socket.getaddrinfo(fqdn, None, socket.AF_INET6)
if verbose: print "IPv6"
except:
pass
# Now start the threads
for i in range(len(threads)):
#print "Thread: i is", i
threads[i] = Thread(target=gethtmlresult, args=(urls[i], results, i))
threads[i].daemon=True
threads[i].start()
# Monitor the amount of bytes (and speed) of the threads
time.sleep(1)
sleepseconds = 3 # 3 seconds sleep
lasttotal = 0
highestspeedkBps = 0
maxdownload = 60 #MB
nrloops = maxtime / sleepseconds
for loop in range(nrloops):
total = 0
for i in range(len(threads)):
#print i, results[i]
total += results[i]
delta = total-lasttotal
speedkBps = (delta/sleepseconds)/(1024)
if verbose:
print "Loop", loop, "Total MB", total/(1024*1024), "Delta MB", delta/(1024*1024), "Speed kB/s:", speedkBps, "aka Mbps %.1f" % (application_bytes_to_networkbits(speedkBps)/1024)
'''
if total/(1024*1024) > maxdownload:
break
'''
lasttotal = total
if speedkBps > highestspeedkBps:
highestspeedkBps = speedkBps
time.sleep(sleepseconds)
'''
print "Now wait for threads to end:"
for i in range(len(threads)):
threads[i].join()
'''
Mbps = (application_bytes_to_networkbits(highestspeedkBps)/1024)
Mbps = float("%.1f" % Mbps)
if verbose: print "Highest Speed (kB/s):", highestspeedkBps, "aka Mbps ", Mbps
#print "Debug: total in bytes", total
return Mbps
######## MAIN #################
if __name__ == "__main__":
print "let's speed test:"
print "\nSpeed test, without logging:"
print fast_com()
print "\nSpeed test, with logging:"
print fast_com(verbose=True)
print "\nSpeed test, IPv4, with verbose logging:"
print fast_com(verbose=True, maxtime=18, forceipv4=True)
print "\nSpeed test, IPv6:"
print fast_com(maxtime=12, forceipv6=True)
print "\n30 second speed test:"
fast_com(verbose=True, maxtime=30)
print "\ndone"