Implemented multistream; Implemented jitter test; Implemented Fetch API (default disabled); Added more settings; Improved code quality; Improved accuracy; Added browser-specific optimizations; Added minified version; Expanded documentation; Some bug fixes
This commit is contained in:
parent
e5612b4f10
commit
fce6a67be0
10 changed files with 343 additions and 121 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
doc.odt
|
||||||
|
cktest.html
|
|
@ -11,14 +11,13 @@ This is a very lightweight Speedtest implemented in Javascript, using XMLHttpReq
|
||||||
Only modern browsers are supported (Edge 12+)
|
Only modern browsers are supported (Edge 12+)
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- A reasonably fast web server
|
- A reasonably fast web server. PHP is optional but recommended (see doc.pdf for details)
|
||||||
- Some way to generate garbage data using either the included PHP script, a [big file of random data](http://downloads.fdossena.com/geth.php?r=speedtest-bigfile), or a symlink to /dev/urandom
|
- Some way to generate garbage data (PHP script included, see doc.pdf for other solutions)
|
||||||
- Your server must accept large POST requests (up to 10 Megabytes), otherwise the upload test will fail
|
- Your server must accept large POST requests (up to 20 Megabytes), otherwise the upload test will fail
|
||||||
- Client side, there must not be any type of buffering (such as a proxy), or you may get incorrect results
|
|
||||||
- It's also better if your server does not use compression, but it's not mandatory
|
- It's also better if your server does not use compression, but it's not mandatory
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
See the examples, it's really simple.
|
See the examples or doc.pdf
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Copyright (C) 2016-2017 Federico Dossena
|
Copyright (C) 2016-2017 Federico Dossena
|
||||||
|
|
BIN
doc.pdf
Normal file
BIN
doc.pdf
Normal file
Binary file not shown.
|
@ -14,13 +14,13 @@
|
||||||
<h4>Latency</h4>
|
<h4>Latency</h4>
|
||||||
<div id="ping"></div>
|
<div id="ping"></div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var w=new Worker("speedtest_worker.js"); //create new worker
|
var w=new Worker("speedtest_worker.min.js"); //create new worker
|
||||||
setInterval(function(){w.postMessage("status");}.bind(this),100); //ask for status every 100ms
|
setInterval(function(){w.postMessage("status");}.bind(this),100); //ask for status every 100ms
|
||||||
w.onmessage=function(event){ //when status is received, split the string and put the values in the appropriate fields
|
w.onmessage=function(event){ //when status is received, split the string and put the values in the appropriate fields
|
||||||
var data=event.data.split(";"); //string format: status;download;upload;ping (speeds are in mbit/s) (status: 0=not started, 1=downloading, 2=uploading, 3=ping, 4=done, 5=aborted)
|
var data=event.data.split(";"); //string format: status;download;upload;ping (speeds are in mbit/s) (status: 0=not started, 1=downloading, 2=uploading, 3=ping, 4=done, 5=aborted)
|
||||||
document.getElementById("download").innerHTML=data[1]+" Mbit/s";
|
document.getElementById("download").innerHTML=data[1]+" Mbit/s";
|
||||||
document.getElementById("upload").innerHTML=data[2]+" Mbit/s";
|
document.getElementById("upload").innerHTML=data[2]+" Mbit/s";
|
||||||
document.getElementById("ping").innerHTML=data[3]+" ms";
|
document.getElementById("ping").innerHTML=data[3]+" ms, "+data[5]+" ms jitter";
|
||||||
document.getElementById("ip").innerHTML=data[4];
|
document.getElementById("ip").innerHTML=data[4];
|
||||||
}
|
}
|
||||||
w.postMessage("start"); //start the speedtest (default params. keep garbage.php and empty.dat in the same directory as the js file)
|
w.postMessage("start"); //start the speedtest (default params. keep garbage.php and empty.dat in the same directory as the js file)
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
}
|
}
|
||||||
div.test{
|
div.test{
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
width:30vw;
|
margin:1em;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
div.testName,div.meterUnit{
|
div.testName,div.meterUnit{
|
||||||
|
@ -48,22 +48,28 @@
|
||||||
<div class="testName">Latency</div>
|
<div class="testName">Latency</div>
|
||||||
<div class="meter"> <span id="ping"></span> </div>
|
<div class="meter"> <span id="ping"></span> </div>
|
||||||
<div class="meterUnit">ms</div>
|
<div class="meterUnit">ms</div>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<div class="testName">Jitter</div>
|
||||||
|
<div class="meter"> <span id="jitter"></span> </div>
|
||||||
|
<div class="meterUnit">ms</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="ip"></div>
|
<div id="ip"></div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var w=new Worker("speedtest_worker.js");
|
var w=new Worker("speedtest_worker.min.js");
|
||||||
var interval=setInterval(function(){w.postMessage("status");}.bind(this),100);
|
var interval=setInterval(function(){w.postMessage("status");}.bind(this),100);
|
||||||
w.onmessage=function(event){
|
w.onmessage=function(event){
|
||||||
var data=event.data.split(";");
|
var data=event.data.split(";");
|
||||||
var status=Number(data[0]);
|
var status=Number(data[0]);
|
||||||
var dl=document.getElementById("download"),ul=document.getElementById("upload"),ping=document.getElementById("ping"),ip=document.getElementById("ip");
|
var dl=document.getElementById("download"),ul=document.getElementById("upload"),ping=document.getElementById("ping"),ip=document.getElementById("ip"),jitter=document.getElementById("jitter");
|
||||||
dl.className=status==1?"flash":"";ul.className=status==2?"flash":"";ping.className=status==3?"flash":"";
|
dl.className=status==1?"flash":"";ping.className=status==2?"flash":"";jitter.className=ul.className=status==3?"flash":"";
|
||||||
if(status>=4){
|
if(status>=4){
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}
|
}
|
||||||
dl.innerHTML=data[1];
|
dl.innerHTML=data[1];
|
||||||
ul.innerHTML=data[2];
|
ul.innerHTML=data[2];
|
||||||
ping.innerHTML=data[3];
|
ping.innerHTML=data[3];
|
||||||
|
jitter.innerHTML=data[5];
|
||||||
ip.innerHTML="Your IP: "+data[4];
|
ip.innerHTML="Your IP: "+data[4];
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
w.postMessage('start {"url_dl":"garbage.php","url_ul":"empty.dat","url_ping":"empty.dat","time_dl":"10","time_ul":"15","count_ping":"30"}'); //start with custom parameters. paths are relative to js file. you can omit parameters that you don't want to change
|
w.postMessage('start {"url_dl":"garbage.php","url_ul":"empty.dat","url_ping":"empty.dat","time_dl":"10","time_ul":"15","count_ping":"30"}'); //start with custom parameters. paths are relative to js file. you can omit parameters that you don't want to change
|
||||||
|
|
|
@ -11,16 +11,18 @@
|
||||||
}
|
}
|
||||||
div.test{
|
div.test{
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
width:30vw;
|
margin:1em;
|
||||||
|
font-size:2vw;
|
||||||
|
min-width:20vw;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
}
|
}
|
||||||
div.testName,div.meterUnit{
|
div.testName,div.meterUnit{
|
||||||
font-size:3vw;
|
font-size:1em;
|
||||||
}
|
}
|
||||||
div.meter{
|
div.meter{
|
||||||
font-size:6vw;
|
font-size:1.5em;
|
||||||
line-height:1.5em;
|
line-height:1.5em;
|
||||||
height:1.5em !important;
|
height:2em !important;
|
||||||
}
|
}
|
||||||
.flash{
|
.flash{
|
||||||
animation:flash 0.6s linear infinite;
|
animation:flash 0.6s linear infinite;
|
||||||
|
@ -41,6 +43,11 @@
|
||||||
margin:0.8em 0;
|
margin:0.8em 0;
|
||||||
font-size:1.2em;
|
font-size:1.2em;
|
||||||
}
|
}
|
||||||
|
@media all and (max-width: 50em){
|
||||||
|
div.test{
|
||||||
|
font-size:2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var w=null;
|
var w=null;
|
||||||
|
@ -48,13 +55,13 @@
|
||||||
document.getElementById("startBtn").style.display="none";
|
document.getElementById("startBtn").style.display="none";
|
||||||
document.getElementById("testArea").style.display="";
|
document.getElementById("testArea").style.display="";
|
||||||
document.getElementById("abortBtn").style.display="";
|
document.getElementById("abortBtn").style.display="";
|
||||||
w=new Worker("speedtest_worker.js");
|
w=new Worker("speedtest_worker.min.js");
|
||||||
var interval=setInterval(function(){w.postMessage("status");}.bind(this),100);
|
var interval=setInterval(function(){w.postMessage("status");}.bind(this),100);
|
||||||
w.onmessage=function(event){
|
w.onmessage=function(event){
|
||||||
var data=event.data.split(";");
|
var data=event.data.split(";");
|
||||||
var status=Number(data[0]);
|
var status=Number(data[0]);
|
||||||
var dl=document.getElementById("download"),ul=document.getElementById("upload"),ping=document.getElementById("ping"),ip=document.getElementById("ip");
|
var dl=document.getElementById("download"),ul=document.getElementById("upload"),ping=document.getElementById("ping"),ip=document.getElementById("ip"),jitter=document.getElementById("jitter");
|
||||||
dl.className=status==1?"flash":"";ul.className=status==2?"flash":"";ping.className=status==3?"flash":"";
|
dl.className=status==1?"flash":"";ping.className=status==2?"flash":"";jitter.className=ul.className=status==3?"flash":"";
|
||||||
if(status>=4){
|
if(status>=4){
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
document.getElementById("abortBtn").style.display="none";
|
document.getElementById("abortBtn").style.display="none";
|
||||||
|
@ -67,6 +74,7 @@
|
||||||
dl.innerHTML=data[1];
|
dl.innerHTML=data[1];
|
||||||
ul.innerHTML=data[2];
|
ul.innerHTML=data[2];
|
||||||
ping.innerHTML=data[3];
|
ping.innerHTML=data[3];
|
||||||
|
jitter.innerHTML=data[5];
|
||||||
ip.innerHTML=data[4];
|
ip.innerHTML=data[4];
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
w.postMessage("start");
|
w.postMessage("start");
|
||||||
|
@ -94,6 +102,11 @@
|
||||||
<div class="testName">Latency</div>
|
<div class="testName">Latency</div>
|
||||||
<div class="meter"> <span id="ping"></span> </div>
|
<div class="meter"> <span id="ping"></span> </div>
|
||||||
<div class="meterUnit">ms</div>
|
<div class="meterUnit">ms</div>
|
||||||
|
</div>
|
||||||
|
<div class="test">
|
||||||
|
<div class="testName">Jitter</div>
|
||||||
|
<div class="meter"> <span id="jitter"></span> </div>
|
||||||
|
<div class="meterUnit">ms</div>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<a href="javascript:abortTest()" id="abortBtn">Abort</a>
|
<a href="javascript:abortTest()" id="abortBtn">Abort</a>
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
var w=null;
|
var w=null;
|
||||||
var ggdl,ggul,ggping;
|
var ggdl,ggul,ggping;
|
||||||
function runTest(){
|
function runTest(){
|
||||||
w=new Worker("speedtest_worker.js");
|
w=new Worker("speedtest_worker.min.js");
|
||||||
var interval=setInterval(function(){w.postMessage("status");}.bind(this),100);
|
var interval=setInterval(function(){w.postMessage("status");}.bind(this),100);
|
||||||
document.getElementById("abortBtn").style.display="";
|
document.getElementById("abortBtn").style.display="";
|
||||||
document.getElementById("startBtn").style.display="none";
|
document.getElementById("startBtn").style.display="none";
|
||||||
|
@ -82,8 +82,9 @@
|
||||||
updateGauge(ggul, data[2]);
|
updateGauge(ggul, data[2]);
|
||||||
updateGauge(ggping, data[3]);
|
updateGauge(ggping, data[3]);
|
||||||
document.getElementById("ip").innerHTML="Your IP: "+data[4];
|
document.getElementById("ip").innerHTML="Your IP: "+data[4];
|
||||||
|
updateGauge(ggjitter, data[5]);
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
w.postMessage('start {"time_ul":"10", "time_dl":"10", "count_ping":"20", "url_dl":"garbage.php","url_ul":"empty.dat","url_ping":"empty.dat","url_getIp":"getIP.php"}');
|
w.postMessage('start {"time_ul":"10", "time_dl":"10", "count_ping":"50", "url_dl":"garbage.php","url_ul":"empty.dat","url_ping":"empty.dat","url_getIp":"getIP.php"}');
|
||||||
}
|
}
|
||||||
function abortTest(){
|
function abortTest(){
|
||||||
if(w)w.postMessage("abort");
|
if(w)w.postMessage("abort");
|
||||||
|
@ -138,7 +139,25 @@
|
||||||
refreshAnimationTime: 300,
|
refreshAnimationTime: 300,
|
||||||
value: 0,
|
value: 0,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 10,
|
max: 100,
|
||||||
|
decimals : 2,
|
||||||
|
formatNumber: true,
|
||||||
|
humanFriendly : false,
|
||||||
|
levelColors: [
|
||||||
|
"#999999",
|
||||||
|
"#993333"
|
||||||
|
]
|
||||||
|
});
|
||||||
|
ggjitter = new JustGage({
|
||||||
|
id: 'ggjitter',
|
||||||
|
title: "Jitter",
|
||||||
|
label: "ms",
|
||||||
|
titleFontFamily : "Open Sans",
|
||||||
|
valueFontFamily : "Open Sans",
|
||||||
|
refreshAnimationTime: 300,
|
||||||
|
value: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
decimals : 2,
|
decimals : 2,
|
||||||
formatNumber: true,
|
formatNumber: true,
|
||||||
humanFriendly : false,
|
humanFriendly : false,
|
||||||
|
@ -146,7 +165,6 @@
|
||||||
"#999999",
|
"#999999",
|
||||||
"#993333"
|
"#993333"
|
||||||
]
|
]
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -166,6 +184,7 @@
|
||||||
<div class="meter" id="ggdl"></div>
|
<div class="meter" id="ggdl"></div>
|
||||||
<div class="meter" id="ggul"></div>
|
<div class="meter" id="ggul"></div>
|
||||||
<div class="meter" id="ggping"></div>
|
<div class="meter" id="ggping"></div>
|
||||||
|
<div class="meter" id="ggjitter"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="ip"></div>
|
<div id="ip"></div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -17,7 +17,7 @@ header("Pragma: no-cache");
|
||||||
// Generate data
|
// Generate data
|
||||||
$data=openssl_random_pseudo_bytes(1048576);
|
$data=openssl_random_pseudo_bytes(1048576);
|
||||||
// Deliver chunks of 1048576 bytes
|
// Deliver chunks of 1048576 bytes
|
||||||
for($i=0;$i<100;$i++){
|
for($i=0;$i<intval($_GET["ckSize"]);$i++){
|
||||||
echo $data;
|
echo $data;
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,129 @@
|
||||||
var testStatus=0,dlStatus="",ulStatus="",pingStatus="",clientIp="";
|
/*
|
||||||
var settings={time_ul:15,time_dl:15,count_ping:35,url_dl:"garbage.php",url_ul:"empty.dat",url_ping:"empty.dat",url_getIp:"getIP.php"};
|
HTML5 Speedtest v4.0
|
||||||
var xhr=null;
|
by Federico Dossena
|
||||||
|
https://github.com/adolfintel/speedtest/
|
||||||
|
GNU LGPLv3 License
|
||||||
|
*/
|
||||||
|
|
||||||
|
//data reported to main thread
|
||||||
|
var testStatus=0, //0=not started, 1=download test, 2=ping+jitter test, 3=upload test, 4=finished, 5=abort/error
|
||||||
|
dlStatus="", //download speed in megabit/s with 2 decimal digits
|
||||||
|
ulStatus="", //upload speed in megabit/s with 2 decimal digits
|
||||||
|
pingStatus="", //ping in milliseconds with 2 decimal digits
|
||||||
|
jitterStatus="", //jitter in milliseconds with 2 decimal digits
|
||||||
|
clientIp=""; //client's IP address as reported by getIP.php
|
||||||
|
|
||||||
|
//test settings. can be overridden by sending specific values with the start command
|
||||||
|
var settings={
|
||||||
|
time_ul:15, //duration of upload test in seconds
|
||||||
|
time_dl:15, //duration of download test in seconds
|
||||||
|
count_ping:35, //number of pings to perform in ping test
|
||||||
|
url_dl:"garbage.php", //path to a large file or garbage.php, used for download test. must be relative to this js file
|
||||||
|
url_ul:"empty.dat", //path to an empty file, used for upload test. must be relative to this js file
|
||||||
|
url_ping:"empty.dat", //path to an empty file, used for ping test. must be relative to this js file
|
||||||
|
url_getIp:"getIP.php", //path to getIP.php relative to this js file, or a similar thing that outputs the client's ip
|
||||||
|
xhr_dlMultistream:10, //number of download streams to use (can be different if enable_quirks is active)
|
||||||
|
xhr_ulMultistream:3, //number of upload streams to use (can be different if enable_quirks is active)
|
||||||
|
xhr_dlUseBlob:false, //if set to true, it reduces ram usage but uses the hard drive (useful with large garbagePhp_chunkSize and/or high xhr_dlMultistream)
|
||||||
|
garbagePhp_chunkSize:20, //size of chunks sent by garbage.php (can be different if enable_quirks is active)
|
||||||
|
enable_quirks:true, //enable quirks for specific browsers. currently it overrides settings to optimize for specific browsers, unless they are already being overridden with the start command
|
||||||
|
allow_fetchAPI:false, //enables Fetch API. currently disabled because it leaks memory like no tomorrow
|
||||||
|
force_fetchAPI:false //when Fetch API is enabled, it will force usage on every browser that supports it
|
||||||
|
};
|
||||||
|
|
||||||
|
var xhr=null, //array of currently active xhr requests
|
||||||
|
interval=null; //timer used in tests
|
||||||
|
|
||||||
|
/*
|
||||||
|
when set to true (automatically) the download test will use the fetch api instead of xhr.
|
||||||
|
fetch api is used if
|
||||||
|
-allow_fetchAPI is true AND
|
||||||
|
-(we're on chrome that supports fetch api AND enable_quirks is true) OR (we're on any browser that supports fetch api AND force_fetchAPI is true)
|
||||||
|
*/
|
||||||
|
var useFetchAPI=false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
listener for commands from main thread to this worker.
|
||||||
|
commands:
|
||||||
|
-status: returns the current status as a string of values spearated by a semicolon (;) in this order: testStatus;dlStatus;ulStatus;pingStatus;clientIp;jitterStatus
|
||||||
|
-abort: aborts the current test
|
||||||
|
-start: starts the test. optionally, settings can be passed as JSON.
|
||||||
|
example: start {"time_ul":"10", "time_dl":"10", "count_ping":"50"}
|
||||||
|
*/
|
||||||
this.addEventListener('message', function(e){
|
this.addEventListener('message', function(e){
|
||||||
var params=e.data.split(" ");
|
var params=e.data.split(" ");
|
||||||
if(params[0]=="status"){
|
if(params[0]=="status"){ //return status
|
||||||
postMessage(testStatus+";"+dlStatus+";"+ulStatus+";"+pingStatus+";"+clientIp);
|
postMessage(testStatus+";"+dlStatus+";"+ulStatus+";"+pingStatus+";"+clientIp+";"+jitterStatus);
|
||||||
}
|
}
|
||||||
if(params[0]=="start"&&testStatus==0){
|
if(params[0]=="start"&&testStatus==0){ //start new test
|
||||||
testStatus=1;
|
testStatus=1;
|
||||||
try{
|
try{
|
||||||
|
//parse settings, if present
|
||||||
var s=JSON.parse(e.data.substring(5));
|
var s=JSON.parse(e.data.substring(5));
|
||||||
if(typeof s.url_dl != "undefined") settings.url_dl=s.url_dl;
|
if(typeof s.url_dl != "undefined") settings.url_dl=s.url_dl; //download url
|
||||||
if(typeof s.url_ul != "undefined") settings.url_ul=s.url_ul;
|
if(typeof s.url_ul != "undefined") settings.url_ul=s.url_ul; //upload url
|
||||||
if(typeof s.url_ping != "undefined") settings.url_ping=s.url_ping;
|
if(typeof s.url_ping != "undefined") settings.url_ping=s.url_ping; //ping url
|
||||||
if(typeof s.url_getIp != "undefined") settings.url_getIp=s.url_getIp;
|
if(typeof s.url_getIp != "undefined") settings.url_getIp=s.url_getIp; //url to getIP.php
|
||||||
if(typeof s.time_dl != "undefined") settings.time_dl=s.time_dl;
|
if(typeof s.time_dl != "undefined") settings.time_dl=s.time_dl; //duration of download test
|
||||||
if(typeof s.time_ul != "undefined") settings.time_ul=s.time_ul;
|
if(typeof s.time_ul != "undefined") settings.time_ul=s.time_ul; //duration of upload test
|
||||||
if(typeof s.count_ping != "undefined") settings.count_ping=s.count_ping;
|
if(typeof s.enable_quirks != "undefined") settings.enable_quirks=s.enable_quirks; //enable quirks or not
|
||||||
}catch(e){}
|
if(typeof s.allow_fetchAPI != "undefined") settings.allow_fetchAPI=s.allow_fetchAPI; //allows fetch api to be used if supported
|
||||||
getIp(function(){dlTest(function(){testStatus=2;ulTest(function(){testStatus=3;pingTest(function(){testStatus=4;});});})});
|
//quirks for specific browsers. more may be added in future releases
|
||||||
|
if(settings.enable_quirks){
|
||||||
|
var ua=navigator.userAgent;
|
||||||
|
if(/Firefox.(\d+\.\d+)/i.test(ua)){
|
||||||
|
//ff more precise with 1 upload stream
|
||||||
|
settings.xhr_ulMultistream=1;
|
||||||
|
}
|
||||||
|
if(/Edge.(\d+\.\d+)/i.test(ua)){
|
||||||
|
//edge more precise with 3 download streams
|
||||||
|
settings.xhr_dlMultistream=3;
|
||||||
|
}
|
||||||
|
if((/Safari.(\d+)/i.test(ua))&&!(/Chrome.(\d+)/i.test(ua))){
|
||||||
|
//safari more precise with 10 upload streams and 5mb chunks for download test
|
||||||
|
settings.xhr_ulMultistream=10;
|
||||||
|
settings.garbagePhp_chunkSize=5;
|
||||||
|
}
|
||||||
|
if(/Chrome.(\d+)/i.test(ua)&&(!!self.fetch)){
|
||||||
|
//chrome can't handle large xhr very well, use fetch api if available and allowed
|
||||||
|
if(settings.allow_fetchAPI) useFetchAPI=true;
|
||||||
|
//chrome more precise with 5 streams
|
||||||
|
settings.xhr_dlMultistream=5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(typeof s.count_ping != "undefined") settings.count_ping=s.count_ping; //number of pings for ping test
|
||||||
|
if(typeof s.xhr_dlMultistream != "undefined") settings.xhr_dlMultistream=s.xhr_dlMultistream; //number of download streams
|
||||||
|
if(typeof s.xhr_ulMultistream != "undefined") settings.xhr_ulMultistream=s.xhr_ulMultistream; //number of upload streams
|
||||||
|
if(typeof s.xhr_dlUseBlob != "undefined") settings.xhr_dlUseBlob=s.xhr_dlUseBlob; //use blob for download test
|
||||||
|
if(typeof s.garbagePhp_chunkSize != "undefined") settings.garbagePhp_chunkSize=s.garbagePhp_chunkSize; //size of garbage.php chunks
|
||||||
|
if(typeof s.force_fetchAPI != "undefined") settings.force_fetchAPI=s.force_fetchAPI; //use fetch api on all browsers that support it if enabled
|
||||||
|
if(settings.allow_fetchAPI&&settings.force_fetchAPI&&(!!self.fetch)) useFetchAPI=true;
|
||||||
|
}catch(e){console.log(e)}
|
||||||
|
//run the tests
|
||||||
|
console.log(settings);
|
||||||
|
console.log("Fetch API: "+useFetchAPI);
|
||||||
|
getIp(function(){dlTest(function(){testStatus=2;pingTest(function(){testStatus=3;ulTest(function(){testStatus=4;});});})});
|
||||||
}
|
}
|
||||||
if(params[0]=="abort"){
|
if(params[0]=="abort"){ //abort command
|
||||||
try{if(xhr)xhr.abort();}catch(e){}
|
clearRequests(); //stop all xhr activity
|
||||||
testStatus=5;dlStatus="";ulStatus="";pingStatus="";
|
if(interval)clearInterval(interval); //clear timer if present
|
||||||
|
testStatus=5;dlStatus="";ulStatus="";pingStatus="";jitterStatus=""; //set test as aborted
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
//stops all XHR activity, aggressively
|
||||||
|
function clearRequests(){
|
||||||
|
if(xhr){
|
||||||
|
for(var i=0;i<xhr.length;i++){
|
||||||
|
if(useFetchAPI)try{xhr[i].cancelRequested=true;}catch(e){}
|
||||||
|
try{xhr[i].onprogress=null; xhr[i].onload=null; xhr[i].onerror=null;}catch(e){}
|
||||||
|
try{xhr[i].upload.onprogress=null; xhr[i].upload.onload=null; xhr[i].upload.onerror=null;}catch(e){}
|
||||||
|
try{xhr[i].abort();}catch(e){}
|
||||||
|
try{delete(xhr[i]);}catch(e){}
|
||||||
|
}
|
||||||
|
xhr=null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//gets client's IP using url_getIp, then calls the done function
|
||||||
function getIp(done){
|
function getIp(done){
|
||||||
xhr=new XMLHttpRequest();
|
xhr=new XMLHttpRequest();
|
||||||
xhr.onload=function(){
|
xhr.onload=function(){
|
||||||
|
@ -37,101 +136,184 @@ function getIp(done){
|
||||||
xhr.open("GET",settings.url_getIp+"?r="+Math.random(),true);
|
xhr.open("GET",settings.url_getIp+"?r="+Math.random(),true);
|
||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
var dlCalled=false;
|
//download test, calls done function when it's over
|
||||||
|
var dlCalled=false; //used to prevent multiple accidental calls to dlTest
|
||||||
function dlTest(done){
|
function dlTest(done){
|
||||||
if(dlCalled) return; else dlCalled=true;
|
if(dlCalled) return; else dlCalled=true; //dlTest already called?
|
||||||
var firstTick=true,startT=new Date().getTime(), prevT=new Date().getTime(),prevLoaded=0,speed=0.0;
|
var totLoaded=0.0, //total number of loaded bytes
|
||||||
xhr=new XMLHttpRequest();
|
startT=new Date().getTime(), //timestamp when test was started
|
||||||
xhr.onprogress=function(event){
|
failed=false; //set to true if a stream fails
|
||||||
var instspd=event.loaded<=0?speed:((event.loaded-prevLoaded)/((new Date().getTime()-prevT)/1000.0))*1.25;
|
xhr=[];
|
||||||
if(isNaN(instspd)||!isFinite(instspd)||instspd<0) return;
|
//function to create a download stream
|
||||||
if(firstTick){
|
var testStream=function(i,delay){
|
||||||
speed=instspd;
|
setTimeout(function(){ //delay creation of a stream slightly so that the new stream is completely detached from the one that created it
|
||||||
firstTick=false;
|
if(testStatus!=1) return; //delayed stream ended up starting after the end of the download test
|
||||||
}else{
|
if(useFetchAPI){
|
||||||
speed=speed*0.9+instspd*0.1;
|
xhr[i]=fetch(settings.url_dl+"?r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize).then(function(response) {
|
||||||
}
|
var reader = response.body.getReader();
|
||||||
prevLoaded=event.loaded;
|
var consume=function() {
|
||||||
prevT=new Date().getTime();
|
return reader.read().then(function(result){
|
||||||
dlStatus=((speed*8)/1048576.0).toFixed(2);
|
if(result.done) testStream(i); else{
|
||||||
if(((prevT-startT)/1000.0)>settings.time_dl){xhr.onprogress=null; xhr.onload=null; xhr.onerror=null; try{xhr.abort();}catch(e){} xhr=null; done();}
|
totLoaded+=result.value.length;
|
||||||
}.bind(this);
|
if(xhr[i].canelRequested) reader.cancel();
|
||||||
xhr.onload=function(){
|
}
|
||||||
prevT=new Date().getTime(); prevLoaded=0; fistTick=true;
|
return consume();
|
||||||
xhr.open("GET",settings.url_dl+"?r="+Math.random(),true);
|
}.bind(this));
|
||||||
xhr.send();
|
}.bind(this);
|
||||||
}.bind(this);
|
return consume();
|
||||||
xhr.onerror=function(){
|
}.bind(this));
|
||||||
dlStatus="Fail";
|
}else{
|
||||||
try{xhr.abort();}catch(e){}
|
var prevLoaded=0; //number of bytes loaded last time onprogress was called
|
||||||
xhr=null;
|
var x=new XMLHttpRequest();
|
||||||
done();
|
xhr[i]=x;
|
||||||
}.bind(this);
|
xhr[i].onprogress=function(event){
|
||||||
xhr.open("GET",settings.url_dl+"?r="+Math.random(),true);
|
if(testStatus!=1){try{x.abort();}catch(e){}} //just in case this XHR is still running after the download test
|
||||||
xhr.send();
|
//progress event, add number of new loaded bytes to totLoaded
|
||||||
|
var loadDiff=event.loaded<=0?0:(event.loaded-prevLoaded);
|
||||||
|
if(isNaN(loadDiff)||!isFinite(loadDiff)||loadDiff<0) return; //just in case
|
||||||
|
totLoaded+=loadDiff;
|
||||||
|
prevLoaded=event.loaded;
|
||||||
|
}.bind(this);
|
||||||
|
xhr[i].onload=function(){
|
||||||
|
//the large file has been loaded entirely, start again
|
||||||
|
testStream(i,0);
|
||||||
|
}.bind(this);
|
||||||
|
xhr[i].onerror=function(){
|
||||||
|
//error, abort
|
||||||
|
failed=true;
|
||||||
|
try{xhr[i].abort();}catch(e){}
|
||||||
|
delete(xhr[i]);
|
||||||
|
}.bind(this);
|
||||||
|
//send xhr
|
||||||
|
if(settings.xhr_dlUseBlob) xhr[i].responseType='blob'; else xhr[i].responseType='arraybuffer';
|
||||||
|
xhr[i].open("GET",settings.url_dl+"?r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize,true); //random string to prevent caching
|
||||||
|
xhr[i].send();
|
||||||
|
}
|
||||||
|
}.bind(this),1+delay);
|
||||||
|
}.bind(this);
|
||||||
|
//open streams
|
||||||
|
for(var i=0;i<settings.xhr_dlMultistream;i++){
|
||||||
|
testStream(i,100*i);
|
||||||
|
}
|
||||||
|
//every 200ms, update dlStatus
|
||||||
|
interval=setInterval(function(){
|
||||||
|
var t=new Date().getTime()-startT;
|
||||||
|
if(t<200) return;
|
||||||
|
var speed=totLoaded/(t/1000.0);
|
||||||
|
dlStatus=((speed*8)/925000.0).toFixed(2); //925000 instead of 1048576 to account for overhead
|
||||||
|
if((t/1000.0)>settings.time_dl||failed){ //test is over, stop streams and timer
|
||||||
|
if(failed||isNaN(dlStatus)) dlStatus="Fail";
|
||||||
|
clearRequests();
|
||||||
|
clearInterval(interval);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}.bind(this),200);
|
||||||
}
|
}
|
||||||
var ulCalled=false;
|
//upload test, calls done function whent it's over
|
||||||
|
//garbage data for upload test (1mb of random bytes repeated 20 times, for a total of 20mb)
|
||||||
|
var r=new ArrayBuffer(1048576);
|
||||||
|
try{r=new Float32Array(r);for(var i=0;i<r.length;i++)r[i]=Math.random();}catch(e){}
|
||||||
|
var req=[];
|
||||||
|
for(var i=0;i<20;i++) req.push(r);
|
||||||
|
req=new Blob(req);
|
||||||
|
var ulCalled=false; //used to prevent multiple accidental calls to ulTest
|
||||||
function ulTest(done){
|
function ulTest(done){
|
||||||
if(ulCalled) return; else ulCalled=true;
|
if(ulCalled) return; else ulCalled=true; //ulTest already called?
|
||||||
var firstTick=true,startT=new Date().getTime(), prevT=new Date().getTime(),prevLoaded=0,speed=0.0;
|
var totLoaded=0.0, //total number of transmitted bytes
|
||||||
xhr=new XMLHttpRequest();
|
startT=new Date().getTime(), //timestamp when test was started
|
||||||
xhr.upload.onprogress=function(event){
|
failed=false; //set to true if a stream fails
|
||||||
var instspd=event.loaded<=0?speed:((event.loaded-prevLoaded)/((new Date().getTime()-prevT)/1000.0))*1.25;
|
xhr=[];
|
||||||
if(isNaN(instspd)||!isFinite(instspd)||instspd<0) return;
|
//function to create an upload stream
|
||||||
if(firstTick){
|
var testStream=function(i,delay){
|
||||||
firstTick=false;
|
setTimeout(function(){ //delay creation of a stream slightly so that the new stream is completely detached from the one that created it
|
||||||
}else{
|
if(testStatus!=3) return; //delayed stream ended up starting after the end of the upload test
|
||||||
speed=instspd<speed?(speed*0.4+instspd*0.6):(speed*0.8+instspd*0.2);
|
var prevLoaded=0; //number of bytes transmitted last time onprogress was called
|
||||||
}
|
var x=new XMLHttpRequest();
|
||||||
prevLoaded=event.loaded;
|
xhr[i]=x;
|
||||||
prevT=new Date().getTime();
|
xhr[i].upload.onprogress=function(event){
|
||||||
ulStatus=((speed*8)/1048576.0).toFixed(2);
|
if(testStatus!=3){try{x.abort();}catch(e){}} //just in case this XHR is still running after the upload test
|
||||||
if(((prevT-startT)/1000.0)>settings.time_ul){xhr.upload.onprogress=null; xhr.upload.onload=null; xhr.upload.onerror=null; try{xhr.abort();}catch(e){} xhr=null; done();}
|
//progress event, add number of new loaded bytes to totLoaded
|
||||||
}.bind(this);
|
var loadDiff=event.loaded<=0?0:(event.loaded-prevLoaded);
|
||||||
xhr.upload.onload=function(){
|
if(isNaN(loadDiff)||!isFinite(loadDiff)||loadDiff<0) return; //just in case
|
||||||
prevT=new Date().getTime(); prevLoaded=0; fistTick=true;
|
totLoaded+=loadDiff;
|
||||||
xhr.open("POST",settings.url_ul+"?r="+Math.random(),true);
|
prevLoaded=event.loaded;
|
||||||
xhr.send(r);
|
}.bind(this);
|
||||||
}.bind(this);
|
xhr[i].upload.onload=function(){
|
||||||
xhr.upload.onerror=function(){
|
//this stream sent all 20mb of garbage data, start again
|
||||||
ulStatus="Fail";
|
testStream(i,0);
|
||||||
try{xhr.abort();}catch(e){}
|
}.bind(this);
|
||||||
xhr=null;
|
xhr[i].upload.onerror=function(){
|
||||||
done();
|
//error, abort
|
||||||
}.bind(this);
|
failed=true;
|
||||||
xhr.open("POST",settings.url_ul+"?r="+Math.random(),true);
|
try{xhr[i].abort();}catch(e){}
|
||||||
xhr.setRequestHeader('Content-Encoding','identity');
|
delete(xhr[i]);
|
||||||
var r=new ArrayBuffer(1048576);
|
}.bind(this);
|
||||||
try{r=new Float32Array(r);for(var i=0;i<r.length;i++)r[i]=Math.random();}catch(e){}
|
//send xhr
|
||||||
var req=[];
|
xhr[i].open("POST",settings.url_ul+"?r="+Math.random(),true); //random string to prevent caching
|
||||||
for(var i=0;i<20;i++) req.push(r);
|
xhr[i].setRequestHeader('Content-Encoding','identity'); //disable compression (some browsers may refuse it, but data is incompressible anyway)
|
||||||
req=new Blob(req);
|
xhr[i].send(req);
|
||||||
xhr.send(req);
|
}.bind(this),1);
|
||||||
|
}.bind(this);
|
||||||
|
//open streams
|
||||||
|
for(var i=0;i<settings.xhr_ulMultistream;i++){
|
||||||
|
testStream(i,100*i);
|
||||||
|
}
|
||||||
|
//every 200ms, update ulStatus
|
||||||
|
interval=setInterval(function(){
|
||||||
|
var t=new Date().getTime()-startT;
|
||||||
|
if(t<200) return;
|
||||||
|
var speed=totLoaded/(t/1000.0);
|
||||||
|
ulStatus=((speed*8)/925000.0).toFixed(2); //925000 instead of 1048576 to account for overhead
|
||||||
|
if((t/1000.0)>settings.time_ul||failed){ //test is over, stop streams and timer
|
||||||
|
if(failed||isNaN(ulStatus)) ulStatus="Fail";
|
||||||
|
clearRequests();
|
||||||
|
clearInterval(interval);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}.bind(this),200);
|
||||||
}
|
}
|
||||||
var ptCalled=false;
|
//ping+jitter test, function done is called when it's over
|
||||||
|
var ptCalled=false; //used to prevent multiple accidental calls to pingTest
|
||||||
function pingTest(done){
|
function pingTest(done){
|
||||||
if(ptCalled) return; else ptCalled=true;
|
if(ptCalled) return; else ptCalled=true; //pingTest already called?
|
||||||
var prevT=null,ping=0.0,i=0;
|
var prevT=null, //last time a pong was received
|
||||||
|
ping=0.0, //current ping value
|
||||||
|
jitter=0.0, //current jitter value
|
||||||
|
i=0, //counter of pongs received
|
||||||
|
prevInstspd=0; //last ping time, used for jitter calculation
|
||||||
|
xhr=[];
|
||||||
|
//ping function
|
||||||
var doPing=function(){
|
var doPing=function(){
|
||||||
prevT=new Date().getTime();
|
prevT=new Date().getTime();
|
||||||
xhr=new XMLHttpRequest();
|
xhr[0]=new XMLHttpRequest();
|
||||||
xhr.onload=function(){
|
xhr[0].onload=function(){
|
||||||
|
//pong
|
||||||
if(i==0){
|
if(i==0){
|
||||||
prevT=new Date().getTime();
|
prevT=new Date().getTime(); //first pong
|
||||||
}else{
|
}else{
|
||||||
var instspd=(new Date().getTime()-prevT)/2;
|
var instspd=(new Date().getTime()-prevT)/2;
|
||||||
if(i==1)ping=instspd; else ping=ping*0.9+instspd*0.1;
|
var instjitter=Math.abs(instspd-prevInstspd);
|
||||||
|
if(i==1)ping=instspd; /*first ping, can't tell jiutter yet*/ else{
|
||||||
|
ping=ping*0.9+instspd*0.1; //ping, weighted average
|
||||||
|
jitter=instjitter>jitter?(jitter*0.2+instjitter*0.8):(jitter*0.9+instjitter*0.1); //update jitter, weighted average. spikes in ping values are given more weight.
|
||||||
|
}
|
||||||
|
prevInstspd=instspd;
|
||||||
}
|
}
|
||||||
pingStatus=ping.toFixed(2);
|
pingStatus=ping.toFixed(2);
|
||||||
|
jitterStatus=jitter.toFixed(2);
|
||||||
i++;
|
i++;
|
||||||
if(i<settings.count_ping) doPing(); else done();
|
if(i<settings.count_ping) doPing(); else done(); //more pings to do?
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
xhr.onerror=function(){
|
xhr[0].onerror=function(){
|
||||||
|
//a ping failed, cancel test
|
||||||
pingStatus="Fail";
|
pingStatus="Fail";
|
||||||
|
jitterStatus="Fail";
|
||||||
|
clearRequests();
|
||||||
done();
|
done();
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
xhr.open("GET",settings.url_ping+"?r="+Math.random(),true);
|
//sent xhr
|
||||||
xhr.send();
|
xhr[0].open("GET",settings.url_ping+"?r="+Math.random(),true); //random string to prevent caching
|
||||||
|
xhr[0].send();
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
doPing();
|
doPing(); //start first ping
|
||||||
}
|
}
|
||||||
|
|
1
speedtest_worker.min.js
vendored
Normal file
1
speedtest_worker.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue