Initial release

This commit is contained in:
adolfintel 2018-08-14 07:24:20 +02:00
parent de3527791f
commit 724c450821
26 changed files with 753 additions and 2220 deletions

View file

@ -1,18 +0,0 @@
# EditorConfig is awesome: http://EditorConfig.org
root = true
[*]
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
charset = utf-8
[*.js]
indent_size = 2
indent_style = space
[*.md]
trim_trailing_whitespace = false

View file

@ -1,110 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>HTML5 Speedtest</title>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:16em;
height:12.5em;
position:relative;
box-sizing:border-box;
}
div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.meterText{
position:absolute;
bottom:1.55em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
div.meterText:empty:before{
content:"0.00";
}
div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testArea canvas{
position:absolute;
top:0; left:0; width:100%; height:100%;
z-index:1;
}
div.testGroup{
display:inline-block;
}
@media all and (max-width:65em){
body{
font-size:1.5vw;
}
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
div.testGroup{
display:block;
margin: 0 auto;
}
}
</style>
<script type="text/javascript" src="server_selector.js"></script>
<script type="text/javascript">
function I(id){return document.getElementById(id);}
function I(i){return document.getElementById(i);}
//LIST OF TEST SERVERS. See documentation for details if needed
var SPEEDTEST_SERVERS=[
{ //this is my demo server, remove it
name:"Speedtest Demo Server (fdossena.com)", //user friendly name for the server
server:"https://speedtest.fdossena.com/testing/", //Full URL to the server, complete with / at the end
dlURL:"garbage.php", //path to download test on this server (garbage.php or replacement)
ulURL:"empty.php", //path to upload test on this server (empty.php or replacement)
pingURL:"empty.php", //path to ping/jitter test on this server (empty.php or replacement)
getIpURL:"getIP.php" //path to getIP on this server (getIP.php or replacement)
},
{
name:"Localhost",
server:"http://127.0.0.1/",
dlURL:"garbage.php",
ulURL:"empty.php",
pingURL:"empty.php",
getIpURL:"getIP.php"
}
//add other servers here, comma separated
];
//SERVER AUTO SELECTION
function initServers(){
fastSelectServer(SPEEDTEST_SERVERS,function(server){
if(server!=null){ //at least 1 server is available
I("loading").className="hidden"; //hide loading message
//configure speedtest to use the specified server
speedtestSettings.url_dl=server.server+server.dlURL;
speedtestSettings.url_ul=server.server+server.ulURL;
speedtestSettings.url_ping=server.server+server.pingURL;
speedtestSettings.url_getIp=server.server+server.getIpURL;
speedtestSettings.telemetry_extra=JSON.stringify({server:server.name}); //add server name as extra data in the telemetry
I("server").textContent=server.name; //show the name of the server in the UI
//show test UI
I("testWrapper").className="visible";
initUI();
}else{ //no servers are available, the test cannot proceed
I("message").innerHTML="No servers available";
}
});
}
var meterBk="#E0E0E0";
var dlColor="#6060AA",
ulColor="#309030",
@ -149,6 +91,9 @@ function msToAmount(s){
//SPEEDTEST AND UI CODE
var w=null; //speedtest worker
var data=null; //data from worker
var speedtestSettings={
//add test settings here if you want
};
function startStop(){
if(w!=null){
//speedtest is running, abort
@ -159,8 +104,8 @@ function startStop(){
initUI();
}else{
//test is not running, begin
w=new Worker('speedtest_worker.min.js');
w.postMessage('start'); //Add optional parameters as a JSON object to this command
w=new Worker('speedtest_worker.js');
w.postMessage('start '+JSON.stringify(speedtestSettings)); //Add optional parameters as a JSON object to this command
I("startStopBtn").className="running";
w.onmessage=function(e){
data=JSON.parse(e.data);
@ -214,46 +159,199 @@ function initUI(){
I("jitText").textContent="";
I("ip").textContent="";
}
</script>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#loading{
background-color:#FFFFFF;
color:#404040;
text-align:center;
}
div.loadCircle{
display:inline-block;
width:2em;
height:2em;
vertical-align:middle;
background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAP1BMVEUAAAB2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZyFzwnAAAAFHRSTlMAEvRFvX406baecwbf0casimhSHyiwmqgAAADpSURBVHja7dbJbQMxAENRahnN5lkc//5rDRAkDeRgHszXgACJoKiIiIiIiIiIiIiIiIiIiIj4HHspsrpAVhdVVguzrA4OWc10WcEqpwKbnBo0OU1Q5NSpsoJFTgOecrrdEag85DRgktNqfoEdTjnd7hrEHMEJvmRUYJbTYk5Agy6nau6Abp5Cm7mDBtRdPi9gyKdU7w4p1fsLvyqs8hl4z9/w3n/Hmr9WoQ65lAU4d7lMYOz//QboRR5jBZibLMZdAR6O/Vfa1PlxNr3XdS3HzK/HVPRu/KnLs8iAOh993VpRRERERMT/fAN60wwWaVyWwAAAAABJRU5ErkJggg==');
background-size:2em 2em;
margin-right:0.5em;
animation: spin 0.6s linear infinite;
}
@keyframes spin{
0%{transform:rotate(0deg);}
100%{transform:rotate(359deg);}
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#serverArea{
margin-top:1em;
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:16em;
height:12.5em;
position:relative;
box-sizing:border-box;
}
div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.meterText{
position:absolute;
bottom:1.55em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
div.meterText:empty:before{
content:"0.00";
}
div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testArea canvas{
position:absolute;
top:0; left:0; width:100%; height:100%;
z-index:1;
}
div.testGroup{
display:inline-block;
}
div.visible{
animation: fadeIn 0.4s;
display:block;
}
div.hidden{
animation: fadeOut 0.4s;
display:none;
}
@keyframes fadeIn{
0%{
opacity:0;
}
100%{
opacity:1;
}
}
@keyframes fadeOut{
0%{
display:block;
opacity:1;
}
100%{
display:block;
opacity:0;
}
}
@media all and (max-width:65em){
body{
font-size:1.5vw;
}
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
div.testGroup{
display:block;
margin: 0 auto;
}
}
</style>
<title>HTML5 Speedtest</title>
</head>
<body>
<body onload="initServers()">
<h1>HTML5 Speedtest</h1>
<div id="startStopBtn" onclick="startStop()"></div>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<canvas id="dlMeter" class="meter"></canvas>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<canvas id="ulMeter" class="meter"></canvas>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Ping</div>
<canvas id="pingMeter" class="meter"></canvas>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<canvas id="jitMeter" class="meter"></canvas>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
IP Address: <span id="ip"></span>
</div>
<div id="loading" class="visible">
<p id="message"><div class="loadCircle"></div>Selecting a server...</p>
</div>
<div id="testWrapper" class="hidden">
<div id="startStopBtn" onclick="startStop()"></div>
<div id="serverArea">
Server: <span id="server"></span>
</div>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<canvas id="dlMeter" class="meter"></canvas>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<canvas id="ulMeter" class="meter"></canvas>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Ping</div>
<canvas id="pingMeter" class="meter"></canvas>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<canvas id="jitMeter" class="meter"></canvas>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
IP Address: <span id="ip"></span>
</div>
</div>
<a href="https://github.com/adolfintel/speedtest">Source code</a>
</div>
<a href="https://github.com/adolfintel/speedtest">Source code</a>
<script type="text/javascript">setTimeout(initUI,100);</script>
</body>
</html>

View file

@ -1,9 +1,179 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>HTML5 Speedtest</title>
<script type="text/javascript" src="server_selector.js"></script>
<script type="text/javascript">
function I(i){return document.getElementById(i);}
//LIST OF TEST SERVERS. See documentation for details if needed
var SPEEDTEST_SERVERS=[
{ //this is my demo server, remove it
name:"Speedtest Demo Server (fdossena.com)", //user friendly name for the server
server:"https://speedtest.fdossena.com/testing/", //Full URL to the server, complete with / at the end
dlURL:"garbage.php", //path to download test on this server (garbage.php or replacement)
ulURL:"empty.php", //path to upload test on this server (empty.php or replacement)
pingURL:"empty.php", //path to ping/jitter test on this server (empty.php or replacement)
getIpURL:"getIP.php" //path to getIP on this server (getIP.php or replacement)
},
{
name:"Localhost",
server:"http://127.0.0.1/",
dlURL:"garbage.php",
ulURL:"empty.php",
pingURL:"empty.php",
getIpURL:"getIP.php"
}
//add other servers here, comma separated
];
//SERVER AUTO SELECTION
function initServers(){
fastSelectServer(SPEEDTEST_SERVERS,function(server){
if(server!=null){ //at least 1 server is available
I("loading").className="hidden"; //hide loading message
//configure speedtest to use the specified server
speedtestSettings.url_dl=server.server+server.dlURL;
speedtestSettings.url_ul=server.server+server.ulURL;
speedtestSettings.url_ping=server.server+server.pingURL;
speedtestSettings.url_getIp=server.server+server.getIpURL;
speedtestSettings.telemetry_extra=JSON.stringify({server:server.name}); //add server name as extra data in the telemetry
I("server").textContent=server.name; //show the name of the server in the UI
//show test UI
I("testWrapper").className="visible";
initUI();
}else{ //no servers are available, the test cannot proceed
I("message").innerHTML="No servers available";
}
});
}
var meterBk="#E0E0E0";
var dlColor="#6060AA",
ulColor="#309030",
pingColor="#AA6060",
jitColor="#AA6060";
var progColor="#EEEEEE";
//CODE FOR GAUGES
function drawMeter(c,amount,bk,fg,progress,prog){
var ctx=c.getContext("2d");
var dp=window.devicePixelRatio||1;
var cw=c.clientWidth*dp, ch=c.clientHeight*dp;
var sizScale=ch*0.0055;
if(c.width==cw&&c.height==ch){
ctx.clearRect(0,0,cw,ch);
}else{
c.width=cw;
c.height=ch;
}
ctx.beginPath();
ctx.strokeStyle=bk;
ctx.lineWidth=16*sizScale;
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,Math.PI*0.1);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle=fg;
ctx.lineWidth=16*sizScale;
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,amount*Math.PI*1.2-Math.PI*1.1);
ctx.stroke();
if(typeof progress !== "undefined"){
ctx.fillStyle=prog;
ctx.fillRect(c.width*0.3,c.height-16*sizScale,c.width*0.4*progress,4*sizScale);
}
}
function mbpsToAmount(s){
return 1-(1/(Math.pow(1.3,Math.sqrt(s))));
}
function msToAmount(s){
return 1-(1/(Math.pow(1.08,Math.sqrt(s))));
}
//SPEEDTEST AND UI CODE
var w=null; //speedtest worker
var data=null; //data from worker
var speedtestSettings={
telemetry_level:"basic"
};
function startStop(){
if(w!=null){
//speedtest is running, abort
w.postMessage('abort');
w=null;
data=null;
I("startStopBtn").className="";
initUI();
}else{
//test is not running, begin
w=new Worker('speedtest_worker.js');
w.postMessage('start '+JSON.stringify(speedtestSettings)); //Add optional parameters as a JSON object to this command
I("startStopBtn").className="running";
I("shareArea").style.display="none";
w.onmessage=function(e){
data=JSON.parse(e.data);
var status=data.testState;
if(status>=4){
//test completed
I("startStopBtn").className="";
w=null;
updateUI(true);
if(status==4){
//if testId is present, show sharing panel, otherwise do nothing
try{
var testId=Number(data.testId);
if(!isNaN(testId)){
var shareURL=window.location.href.substring(0,window.location.href.lastIndexOf("/"))+"/results/?id="+testId;
I("resultsImg").src=shareURL;
I("resultsURL").value=shareURL;
I("testId").innerHTML=testId;
I("shareArea").style.display="";
}
}catch(e){}
}
}
};
}
}
//this function reads the data sent back by the worker and updates the UI
function updateUI(forced){
if(!forced&&(!data||!w)) return;
var status=data.testState;
I("ip").textContent=data.clientIp;
I("dlText").textContent=(status==1&&data.dlStatus==0)?"...":data.dlStatus;
drawMeter(I("dlMeter"),mbpsToAmount(Number(data.dlStatus*(status==1?oscillate():1))),meterBk,dlColor,Number(data.dlProgress),progColor);
I("ulText").textContent=(status==3&&data.ulStatus==0)?"...":data.ulStatus;
drawMeter(I("ulMeter"),mbpsToAmount(Number(data.ulStatus*(status==3?oscillate():1))),meterBk,ulColor,Number(data.ulProgress),progColor);
I("pingText").textContent=data.pingStatus;
drawMeter(I("pingMeter"),msToAmount(Number(data.pingStatus*(status==2?oscillate():1))),meterBk,pingColor,Number(data.pingProgress),progColor);
I("jitText").textContent=data.jitterStatus;
drawMeter(I("jitMeter"),msToAmount(Number(data.jitterStatus*(status==2?oscillate():1))),meterBk,jitColor,Number(data.pingProgress),progColor);
}
function oscillate(){
return 1+0.02*Math.sin(Date.now()/100);
}
//poll the status from the worker (this will call updateUI)
setInterval(function(){
if(w) w.postMessage('status');
},200);
//update the UI every frame
window.requestAnimationFrame=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||(function(callback,element){setTimeout(callback,1000/60);});
function frame(){
requestAnimationFrame(frame);
updateUI();
}
frame(); //start frame loop
//function to (re)initialize UI
function initUI(){
drawMeter(I("dlMeter"),0,meterBk,dlColor,0);
drawMeter(I("ulMeter"),0,meterBk,ulColor,0);
drawMeter(I("pingMeter"),0,meterBk,pingColor,0);
drawMeter(I("jitMeter"),0,meterBk,jitColor,0);
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
}
</script>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
@ -17,6 +187,25 @@
h1{
color:#404040;
}
#loading{
background-color:#FFFFFF;
color:#404040;
text-align:center;
}
div.loadCircle{
display:inline-block;
width:2em;
height:2em;
vertical-align:middle;
background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAP1BMVEUAAAB2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZyFzwnAAAAFHRSTlMAEvRFvX406baecwbf0casimhSHyiwmqgAAADpSURBVHja7dbJbQMxAENRahnN5lkc//5rDRAkDeRgHszXgACJoKiIiIiIiIiIiIiIiIiIiIj4HHspsrpAVhdVVguzrA4OWc10WcEqpwKbnBo0OU1Q5NSpsoJFTgOecrrdEag85DRgktNqfoEdTjnd7hrEHMEJvmRUYJbTYk5Agy6nau6Abp5Cm7mDBtRdPi9gyKdU7w4p1fsLvyqs8hl4z9/w3n/Hmr9WoQ65lAU4d7lMYOz//QboRR5jBZibLMZdAR6O/Vfa1PlxNr3XdS3HzK/HVPRu/KnLs8iAOh993VpRRERERMT/fAN60wwWaVyWwAAAAABJRU5ErkJggg==');
background-size:2em 2em;
margin-right:0.5em;
animation: spin 0.6s linear infinite;
}
@keyframes spin{
0%{transform:rotate(0deg);}
100%{transform:rotate(359deg);}
}
#startStopBtn{
display:inline-block;
margin:0 auto;
@ -45,6 +234,9 @@
#startStopBtn.running:before{
content:"Abort";
}
#serverArea{
margin-top:1em;
}
#test{
margin-top:2em;
margin-bottom:12em;
@ -99,6 +291,32 @@
height:auto;
margin: 0.25em 0;
}
div.visible{
animation: fadeIn 0.4s;
display:block;
}
div.hidden{
animation: fadeOut 0.4s;
display:none;
}
@keyframes fadeIn{
0%{
opacity:0;
}
100%{
opacity:1;
}
}
@keyframes fadeOut{
0%{
display:block;
opacity:1;
}
100%{
display:block;
opacity:0;
}
}
@media all and (max-width:65em){
body{
font-size:1.5vw;
@ -113,184 +331,59 @@
margin: 0 auto;
}
}
</style>
<script type="text/javascript">
function I(id){return document.getElementById(id);}
var meterBk="#E0E0E0";
var dlColor="#6060AA",
ulColor="#309030",
pingColor="#AA6060",
jitColor="#AA6060";
var progColor="#EEEEEE";
//CODE FOR GAUGES
function drawMeter(c,amount,bk,fg,progress,prog){
var ctx=c.getContext("2d");
var dp=window.devicePixelRatio||1;
var cw=c.clientWidth*dp, ch=c.clientHeight*dp;
var sizScale=ch*0.0055;
if(c.width==cw&&c.height==ch){
ctx.clearRect(0,0,cw,ch);
}else{
c.width=cw;
c.height=ch;
}
ctx.beginPath();
ctx.strokeStyle=bk;
ctx.lineWidth=16*sizScale;
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,Math.PI*0.1);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle=fg;
ctx.lineWidth=16*sizScale;
ctx.arc(c.width/2,c.height-58*sizScale,c.height/1.8-ctx.lineWidth,-Math.PI*1.1,amount*Math.PI*1.2-Math.PI*1.1);
ctx.stroke();
if(typeof progress !== "undefined"){
ctx.fillStyle=prog;
ctx.fillRect(c.width*0.3,c.height-16*sizScale,c.width*0.4*progress,4*sizScale);
}
}
function mbpsToAmount(s){
return 1-(1/(Math.pow(1.3,Math.sqrt(s))));
}
function msToAmount(s){
return 1-(1/(Math.pow(1.08,Math.sqrt(s))));
}
//SPEEDTEST AND UI CODE
var w=null; //speedtest worker
var data=null; //data from worker
var testParameters={
telemetry_level:"basic"
//Optional: add more test parameters here
}
function startStop(){
if(w!=null){
//speedtest is running, abort
w.postMessage('abort');
w=null;
data=null;
I("startStopBtn").className="";
initUI();
}else{
//test is not running, begin
w=new Worker('speedtest_worker.min.js');
w.postMessage('start '+JSON.stringify(testParameters));
I("startStopBtn").className="running";
I("shareArea").style.display="none";
w.onmessage=function(e){
data=JSON.parse(e.data);
var status=data.testState;
if(status>=4){
//test completed
I("startStopBtn").className="";
w=null;
updateUI(true);
if(status==4){
//if testId is present, show sharing panel, otherwise do nothing
try{
var testId=Number(data.testId);
if(!isNaN(testId)){
var shareURL=window.location.href.substring(0,window.location.href.lastIndexOf("/"))+"/results/?id="+testId;
I("resultsImg").src=shareURL;
I("resultsURL").value=shareURL;
I("testId").innerHTML=testId;
I("shareArea").style.display="block";
}
}catch(e){}
}
}
};
}
}
//this function reads the data sent back by the worker and updates the UI
function updateUI(forced){
if(!forced&&(!data||!w)) return;
var status=data.testState;
I("ip").textContent=data.clientIp;
I("dlText").textContent=(status==1&&data.dlStatus==0)?"...":data.dlStatus;
drawMeter(I("dlMeter"),mbpsToAmount(Number(data.dlStatus*(status==1?oscillate():1))),meterBk,dlColor,Number(data.dlProgress),progColor);
I("ulText").textContent=(status==3&&data.ulStatus==0)?"...":data.ulStatus;
drawMeter(I("ulMeter"),mbpsToAmount(Number(data.ulStatus*(status==3?oscillate():1))),meterBk,ulColor,Number(data.ulProgress),progColor);
I("pingText").textContent=data.pingStatus;
drawMeter(I("pingMeter"),msToAmount(Number(data.pingStatus*(status==2?oscillate():1))),meterBk,pingColor,Number(data.pingProgress),progColor);
I("jitText").textContent=data.jitterStatus;
drawMeter(I("jitMeter"),msToAmount(Number(data.jitterStatus*(status==2?oscillate():1))),meterBk,jitColor,Number(data.pingProgress),progColor);
}
function oscillate(){
return 1+0.02*Math.sin(Date.now()/100);
}
//poll the status from the worker (this will call updateUI)
setInterval(function(){
if(w) w.postMessage('status');
},200);
//update the UI every frame
window.requestAnimationFrame=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||(function(callback,element){setTimeout(callback,1000/60);});
function frame(){
requestAnimationFrame(frame);
updateUI();
}
frame(); //start frame loop
//function to (re)initialize UI
function initUI(){
drawMeter(I("dlMeter"),0,meterBk,dlColor,0);
drawMeter(I("ulMeter"),0,meterBk,ulColor,0);
drawMeter(I("pingMeter"),0,meterBk,pingColor,0);
drawMeter(I("jitMeter"),0,meterBk,jitColor,0);
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
}
</script>
<title>HTML5 Speedtest</title>
</head>
<body>
<h1>HTML5 Speedtest - Telemetry and sharing example</h1>
<div id="startStopBtn" onclick="startStop()"></div>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<canvas id="dlMeter" class="meter"></canvas>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<canvas id="ulMeter" class="meter"></canvas>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Ping</div>
<canvas id="pingMeter" class="meter"></canvas>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<canvas id="jitMeter" class="meter"></canvas>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
IP Address: <span id="ip"></span>
</div>
<div id="shareArea" style="display:none">
<h3>Share results</h3>
<p>Test ID: <span id="testId"></span></p>
<input type="text" value="" id="resultsURL" readonly="readonly" onclick="this.select();this.focus();this.select();document.execCommand('copy');alert('Link copied')"/>
<img src="" id="resultsImg" />
</div>
<body onload="initServers()">
<h1>HTML5 Speedtest</h1>
<div id="loading" class="visible">
<p id="message"><div class="loadCircle"></div>Selecting a server...</p>
</div>
<div id="testWrapper" class="hidden">
<div id="startStopBtn" onclick="startStop()"></div>
<div id="serverArea">
Server: <span id="server"></span>
</div>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<canvas id="dlMeter" class="meter"></canvas>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<canvas id="ulMeter" class="meter"></canvas>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Ping</div>
<canvas id="pingMeter" class="meter"></canvas>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<canvas id="jitMeter" class="meter"></canvas>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
IP Address: <span id="ip"></span>
</div>
<div id="shareArea" style="display:none">
<h3>Share results</h3>
<p>Test ID: <span id="testId"></span></p>
<input type="text" value="" id="resultsURL" readonly="readonly" onclick="this.select();this.focus();this.select();document.execCommand('copy');alert('Link copied')"/>
<img src="" id="resultsImg" />
</div>
</div>
<a href="https://github.com/adolfintel/speedtest">Source code</a>
</div>
<div>Basic telemetry is active; results will be saved in your database, without the full log. If the results don't appear, or the sharing panel doesn't appear at the end of the test, check the settings in telemetry_settings.php</div>
<a href="https://github.com/adolfintel/speedtest">Source code</a>
<script type="text/javascript">setTimeout(initUI,100);</script>
</body>
</html>

106
Frontend/server_selector.js Normal file
View file

@ -0,0 +1,106 @@
/*
HTML5 Speedtest v4.6.1 MPOT - Server selector
by Federico Dossena
https://github.com/adolfintel/speedtest/
GNU LGPLv3 License
*/
//pings the specified URL, then calls the function result. Result will receive a parameter which is either the time it took to ping the URL, or -1 if something went wrong.
//var PING_TIMEOUT=500; //disabled because it breaks IE11
function ping(url,result){
var xhr=new XMLHttpRequest();
var t=new Date().getTime();
xhr.onload=function(){
if(xhr.responseText.length==0){ //we expect an empty response
var instspd=new Date().getTime()-t; //rough timing estimate
try{
//try to get more accurate timing using performance API
var p=performance.getEntries();
p=p[p.length-1];
var d=p.responseStart-p.requestStart;
if(d<=0) d=p.duration;
if(d>0&&d<instspd) instspd=d;
}catch(e){
}
result(instspd);
}else result(-1);
}.bind(this);
xhr.onerror=function(){
result(-1);
}.bind(this);
xhr.open("GET",url);
/*try{ //disabled because it breaks IE11
xhr.timeout=PING_TIMEOUT;
}catch(e){}*/
xhr.send();
}
//this function repeatedly pings a server to get a good estimate of the ping. When it's done, it calls the done function without parameters. At the end of the execution, the server will have a new parameter called pingT, which is either the best ping we got from the server or -1 if something went wrong.
var PINGS=3, //up to 3 pings are performed, unless the server is down...
SLOW_THRESHOLD=500; //...or one of the pings is above this threshold
function checkServer(server,done){
var i=0;
server.pingT=-1;
var nextPing=function(){
if(i++==PINGS){done(); return;}
ping(server.server+server.pingURL,function(t){
if(t>=0){
if(t<server.pingT||server.pingT==-1) server.pingT=t;
if(t<SLOW_THRESHOLD) nextPing(); else done();
}else done();
}.bind(this));
}.bind(this);
nextPing();
}
/*this function goes through a list of servers, each with this format:
{
name: "User friendly name",
server:"http://yourBackend.com/", <---- make sure there's a / at the end!
dlURL:"garbage.php" <----- path to garbage.php or its replacement on the server
ulURL:"empty.php" <----- path to empty.php or its replacement on the server
pingURL:"empty.php" <----- path to empty.php or its replacement on the server. This is used to ping the server by this selector
getIpURL:"getIP.php" <----- path to getIP.php or its replacement on the server
}
For each server, the ping is measured, then the server with the function result is called with the best server, or null if all the servers were down.
*/
function selectServer(serverList,result){
var i=0;
var done=function(){
var bestServer=null;
for(var i=0;i<serverList.length;i++){
if(serverList[i].pingT!=-1&&(bestServer==null||serverList[i].pingT<bestServer.pingT)) bestServer=serverList[i];
}
result(bestServer);
}.bind(this);
var nextServer=function(){
if(i==serverList.length){done(); return;}
checkServer(serverList[i++],nextServer);
}.bind(this);
nextServer();
}
/*
this function is a faster version of selectServer that tests multiple servers concurrently. Useful for large server lists
*/
var CONCURRENCY=4; //4 seems to be the safest value
function fastSelectServer(serverList,result){
var serverLists=[];
for(var i=0;i<CONCURRENCY;i++){
serverLists[i]=[];
}
for(var i=0;i<serverList.length;i++){
serverLists[i%CONCURRENCY].push(serverList[i]);
}
var completed=0;
var bestServer=null;
for(var i=0;i<CONCURRENCY;i++){
selectServer(serverLists[i],function(server){
if(server!=null){
if(bestServer==null||server.pingT<bestServer.pingT) bestServer=server;
}
completed++;
if(completed==CONCURRENCY) result(bestServer);
}.bind(this));
}
}

View file

@ -1,5 +1,5 @@
/*
HTML5 Speedtest v4.6.1
HTML5 Speedtest v4.6.1 MPOT
by Federico Dossena
https://github.com/adolfintel/speedtest/
GNU LGPLv3 License
@ -37,6 +37,7 @@ var settings = {
getIp_ispInfo_distance: 'km', //km or mi=estimate distance from server in km/mi; set to false to disable distance estimation. getIp_ispInfo must be enabled in order for this to work
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_ul_sendPostRequestBeforeStartingTest: true, //if set to true, a single empty POST request will be sent to the upload test URL. This bypasses bugs with CORS headers in some browsers.
xhr_multistreamDelay: 300, //how much concurrent requests should be delayed
xhr_ignoreErrors: 1, // 0=fail on errors, 1=attempt to restart a stream if it fails, 2=ignore all errors
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)
@ -131,7 +132,7 @@ this.addEventListener('message', function (e) {
if(typeof s.telemetry_level !== 'undefined') settings.telemetry_level = s.telemetry_level === 'basic' ? 1 : s.telemetry_level === 'full' ? 2 : 0; // telemetry level
//transform test_order to uppercase, just in case
settings.test_order=settings.test_order.toUpperCase();
} catch (e) { twarn('Possible error in custom test settings. Some settings may not be applied. Exception: '+e) }
} catch (e) { twarn('Possible error in custom test settings. Some settings might not have been applied. Exception: '+e) }
// run the tests
tlog(JSON.stringify(settings))
test_pointer=0;
@ -282,8 +283,8 @@ function dlTest (done) {
}
}.bind(this), 200)
}
// upload test, calls done function whent it's over
// upload test, calls done function whent it's over
var ulCalled = false // used to prevent multiple accidental calls to ulTest
function ulTest (done) {
tlog('ulTest')
@ -300,107 +301,120 @@ function ulTest (done) {
try { r = new Uint32Array(r); for (var i = 0; i < r.length; i++)r[i] = Math.random()*maxInt } catch (e) { }
reqsmall.push(r)
reqsmall = new Blob(reqsmall)
var totLoaded = 0.0, // total number of transmitted bytes
startT = new Date().getTime(), // timestamp when test was started
graceTimeDone = false, //set to true after the grace time is past
failed = false // set to true if a stream fails
xhr = []
// function to create an upload stream. streams are slightly delayed so that they will not end at the same time
var testStream = function (i, delay) {
setTimeout(function () {
if (testStatus !== 3) return // delayed stream ended up starting after the end of the upload test
tlog('ul test stream started '+i+' '+delay)
var prevLoaded = 0 // number of bytes transmitted last time onprogress was called
var x = new XMLHttpRequest()
xhr[i] = x
var ie11workaround
if (settings.forceIE11Workaround) ie11workaround = true; else {
try {
xhr[i].upload.onprogress
ie11workaround = false
} catch (e) {
ie11workaround = true
}
}
if (ie11workaround) {
// IE11 workarond: xhr.upload does not work properly, therefore we send a bunch of small 256k requests and use the onload event as progress. This is not precise, especially on fast connections
xhr[i].onload = function () {
tlog('ul stream progress event (ie11wa)')
totLoaded += reqsmall.size;
testStream(i, 0)
}
xhr[i].onerror = function () {
// error, abort
tlog('ul stream failed (ie11wa)')
if (settings.xhr_ignoreErrors === 0) failed = true //abort
try { xhr[i].abort() } catch (e) { }
delete (xhr[i])
if (settings.xhr_ignoreErrors === 1) testStream(i,0); //restart stream
}
xhr[i].open('POST', settings.url_ul + url_sep(settings.url_ul) + 'r=' + Math.random(), true) // random string to prevent caching
xhr[i].setRequestHeader('Content-Encoding', 'identity') // disable compression (some browsers may refuse it, but data is incompressible anyway)
xhr[i].send(reqsmall)
} else {
// REGULAR version, no workaround
xhr[i].upload.onprogress = function (event) {
tlog('ul stream progress event '+i+' '+event.loaded)
if (testStatus !== 3) { try { x.abort() } catch (e) { } } // just in case this XHR is still running after the upload test
// 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].upload.onload = function () {
// this stream sent all the garbage data, start again
tlog('ul stream finished '+i)
testStream(i, 0)
}.bind(this)
xhr[i].upload.onerror = function () {
tlog('ul stream failed '+i)
if (settings.xhr_ignoreErrors === 0) failed=true //abort
try { xhr[i].abort() } catch (e) { }
delete (xhr[i])
if (settings.xhr_ignoreErrors === 1) testStream(i, 0) //restart stream
}.bind(this)
// send xhr
xhr[i].open('POST', settings.url_ul + url_sep(settings.url_ul) + 'r=' + Math.random(), true) // random string to prevent caching
xhr[i].setRequestHeader('Content-Encoding', 'identity') // disable compression (some browsers may refuse it, but data is incompressible anyway)
xhr[i].send(req)
}
}.bind(this), 1)
}.bind(this)
// open streams
for (var i = 0; i < settings.xhr_ulMultistream; i++) {
testStream(i, settings.xhr_multistreamDelay * i)
}
// every 200ms, update ulStatus
interval = setInterval(function () {
tlog('UL: '+ulStatus+(graceTimeDone?'':' (in grace time)'))
var t = new Date().getTime() - startT
if (graceTimeDone) ulProgress = t / (settings.time_ul * 1000)
if (t < 200) return
if (!graceTimeDone){
if (t > 1000 * settings.time_ulGraceTime){
if (totLoaded > 0){ // if the connection is so slow that we didn't get a single chunk yet, do not reset
startT = new Date().getTime()
totLoaded = 0.0;
}
graceTimeDone = true;
}
}else{
var speed = totLoaded / (t / 1000.0)
ulStatus = ((speed * 8 * settings.overheadCompensationFactor)/(settings.useMebibits?1048576:1000000)).toFixed(2) // speed is multiplied by 8 to go from bytes to bits, overhead compensation is applied, then everything is divided by 1048576 or 1000000 to go to megabits/mebibits
if (((t / 1000.0) > settings.time_ul && ulStatus > 0) || failed) { // test is over, stop streams and timer
if (failed || isNaN(ulStatus)) ulStatus = 'Fail'
clearRequests()
clearInterval(interval)
ulProgress = 1
tlog('ulTest finished '+ulStatus)
done()
}
}
}.bind(this), 200)
var testFunction=function(){
var totLoaded = 0.0, // total number of transmitted bytes
startT = new Date().getTime(), // timestamp when test was started
graceTimeDone = false, //set to true after the grace time is past
failed = false // set to true if a stream fails
xhr = []
// function to create an upload stream. streams are slightly delayed so that they will not end at the same time
var testStream = function (i, delay) {
setTimeout(function () {
if (testStatus !== 3) return // delayed stream ended up starting after the end of the upload test
tlog('ul test stream started '+i+' '+delay)
var prevLoaded = 0 // number of bytes transmitted last time onprogress was called
var x = new XMLHttpRequest()
xhr[i] = x
var ie11workaround
if (settings.forceIE11Workaround) ie11workaround = true; else {
try {
xhr[i].upload.onprogress
ie11workaround = false
} catch (e) {
ie11workaround = true
}
}
if (ie11workaround) {
// IE11 workarond: xhr.upload does not work properly, therefore we send a bunch of small 256k requests and use the onload event as progress. This is not precise, especially on fast connections
xhr[i].onload = function () {
tlog('ul stream progress event (ie11wa)')
totLoaded += reqsmall.size;
testStream(i, 0)
}
xhr[i].onerror = function () {
// error, abort
tlog('ul stream failed (ie11wa)')
if (settings.xhr_ignoreErrors === 0) failed = true //abort
try { xhr[i].abort() } catch (e) { }
delete (xhr[i])
if (settings.xhr_ignoreErrors === 1) testStream(i,0); //restart stream
}
xhr[i].open('POST', settings.url_ul + url_sep(settings.url_ul) + 'r=' + Math.random(), true) // random string to prevent caching
xhr[i].setRequestHeader('Content-Encoding', 'identity') // disable compression (some browsers may refuse it, but data is incompressible anyway)
xhr[i].send(reqsmall)
} else {
// REGULAR version, no workaround
xhr[i].upload.onprogress = function (event) {
tlog('ul stream progress event '+i+' '+event.loaded)
if (testStatus !== 3) { try { x.abort() } catch (e) { } } // just in case this XHR is still running after the upload test
// 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].upload.onload = function () {
// this stream sent all the garbage data, start again
tlog('ul stream finished '+i)
testStream(i, 0)
}.bind(this)
xhr[i].upload.onerror = function () {
tlog('ul stream failed '+i)
if (settings.xhr_ignoreErrors === 0) failed=true //abort
try { xhr[i].abort() } catch (e) { }
delete (xhr[i])
if (settings.xhr_ignoreErrors === 1) testStream(i, 0) //restart stream
}.bind(this)
// send xhr
xhr[i].open('POST', settings.url_ul + url_sep(settings.url_ul) + 'r=' + Math.random(), true) // random string to prevent caching
xhr[i].setRequestHeader('Content-Encoding', 'identity') // disable compression (some browsers may refuse it, but data is incompressible anyway)
xhr[i].send(req)
}
}.bind(this), 1)
}.bind(this)
// open streams
for (var i = 0; i < settings.xhr_ulMultistream; i++) {
testStream(i, settings.xhr_multistreamDelay * i)
}
// every 200ms, update ulStatus
interval = setInterval(function () {
tlog('UL: '+ulStatus+(graceTimeDone?'':' (in grace time)'))
var t = new Date().getTime() - startT
if (graceTimeDone) ulProgress = t / (settings.time_ul * 1000)
if (t < 200) return
if (!graceTimeDone){
if (t > 1000 * settings.time_ulGraceTime){
if (totLoaded > 0){ // if the connection is so slow that we didn't get a single chunk yet, do not reset
startT = new Date().getTime()
totLoaded = 0.0;
}
graceTimeDone = true;
}
}else{
var speed = totLoaded / (t / 1000.0)
ulStatus = ((speed * 8 * settings.overheadCompensationFactor)/(settings.useMebibits?1048576:1000000)).toFixed(2) // speed is multiplied by 8 to go from bytes to bits, overhead compensation is applied, then everything is divided by 1048576 or 1000000 to go to megabits/mebibits
if (((t / 1000.0) > settings.time_ul && ulStatus > 0) || failed) { // test is over, stop streams and timer
if (failed || isNaN(ulStatus)) ulStatus = 'Fail'
clearRequests()
clearInterval(interval)
ulProgress = 1
tlog('ulTest finished '+ulStatus)
done()
}
}
}.bind(this), 200)
}.bind(this);
if(settings.xhr_ul_sendPostRequestBeforeStartingTest){
tlog('Sending POST request before performing upload test');
xhr=[];
xhr[0]=new XMLHttpRequest();
xhr[0].onload=xhr[0].onerror=function(){
tlog('POST request sent, starting upload test');
testFunction();
}.bind(this);
xhr[0].open('POST',settings.url_ul);
xhr[0].send();
}else testFunction();
}
// ping+jitter test, function done is called when it's over
var ptCalled = false // used to prevent multiple accidental calls to pingTest

View file

@ -1,67 +1,16 @@
# HTML5 Speedtest
# HTML5 Speedtest - Multiple Points of Test
No Flash, No Java, No Websocket, No Bullshit.
__Work in progress!__
This is a very lightweight Speedtest implemented in Javascript, using XMLHttpRequest and Web Workers.
This branch isn't stable yet, don't use it in production.
Documentation isn't done yet.
## Try it
[Take a Speedtest](http://speedtest.fdossena.com)
## Multiple Points of Test
This branch allows usage of multiple test servers instead of just 1. The client runs a server selector, which pings a list of test servers and chooses the one with the best ping.
## Compatibility
Only modern browsers are supported (IE11, latest Edge, latest Chrome, latest Firefox, latest Safari)
## Frontend server
This is the server that hosts the UI, the JS files, and optionally the telemetry
## Features
* Download
* Upload
* Ping
* Jitter
* IP Address
* Telemetry (optional)
* Results sharing (optional)
## Test backends
These are the servers to which the clients can connect and actually do the test. They don't host the UI, only the PHP files required for the test.
![Screenshot](https://speedtest.fdossena.com/screenshot.png)
## Requirements
- A reasonably fast web server with PHP (see doc.md for details and use without PHP)
- Your server must accept large POST requests (up to 20 Megabytes), otherwise the upload test will fail
- It's also better if your server does not use compression, but it's not mandatory
## Quick installation videos
* [Debian 9.0 with Apache](https://fdossena.com/?p=speedtest/quickstart_deb.frag)
* [Windows Server 2016 with IIS](https://fdossena.com/?p=speedtest/quickstart_win.frag)
* [Ubuntu (External)](https://freedif.org/how-to-install-selfhosted-speedtest)
Also, here's an [example config on Ubuntu 16 LTS](https://github.com/adolfintel/speedtest/issues/50)
## How to use in your site
* See the examples
* [Read the wiki](https://github.com/adolfintel/speedtest/wiki)
* Read doc.md
## Docker
Please see the ```docker``` branch
## Node.js backend
A Node.js implementation is available in the ```node``` branch, maintained by [dunklesToast](https://github.com/dunklesToast).
## Donate
[![Donate with Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/fdossena/donate)
[Donate with PayPal](https://www.paypal.me/sineisochronic)
Send ETH at this address: ```0x8A5273d4e2618c4cff2C62d8EB731701FceEd8E3```
## License
Copyright (C) 2016-2018 Federico Dossena
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/lgpl>.

View file

@ -4,4 +4,7 @@ header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Connection: keep-alive");
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Headers: Content-Encoding');
?>

View file

@ -5,6 +5,8 @@
@ini_set('output_handler', '');
// Headers
header('HTTP/1.1 200 OK');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
// Download follows...
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');

View file

@ -6,6 +6,8 @@
*/
$ip = "";
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['X-Real-IP'])) {

445
doc.md
View file

@ -1,445 +0,0 @@
# HTML5 Speedtest
> by Federico Dossena
> Version 4.6.1, August 8, 2018
> [https://github.com/adolfintel/speedtest/](https://github.com/adolfintel/speedtest/)
## Introduction
In this document, we will introduce an XHR based HTML5 Speedtest and see how to use it.
This test measures download speed, upload speed, ping and jitter.
First of all, the requirements to run this test:
* The browser have to support XHR Level 2 and Web Workers and Javascript must be enabled.
* Internet Explorer 11
* Microsoft Edge 12+
* Mozilla Firefox 12+
* Google Chrome / Chromium 31+
* Apple Safari 7.1+
* Opera 18+
* Client side, the test can use up to 500 megabytes of RAM
* Server side, you'll need a fast connection (at least 100 Mbps recommended), and the web server must accept large POST requests (up to 20 megabytes).
The recommended setup is: GNU/Linux, Apache2, PHP, MySQL database (if you want to use telemetry).
If this looks good, let's proceed and see how to use the test.
## Quick installation videos
* [Debian 9.0 with Apache](https://fdossena.com/?p=speedtest/quickstart_deb.frag)
* [Windows Server 2016 with IIS](https://fdossena.com/?p=speedtest/quickstart_win.frag)
## Installation
To install the test on your server, upload the following files:
* `speedtest_worker.min.js`
* `garbage.php`
* `getIP.php`
* `empty.php`
* one of the examples
Later we'll see how to use the test without PHP, and how to configure the telemetry and result sharing if you want to use that.
__Important:__ keep all the files together; all paths are relative to the js file
## Basic usage
You can start using this speedtest on your site without any special knowledge.
Start by copying one of the included examples. Here's a description for each of them:
* `example-basic.html`: This example shows the most basic configuration possible. Everything runs with the default settings, in a very simple page where the output is shown
* `example-pretty.html`: This is a more sophisticated example, with a nicer layout and a start/stop button. __This is the best starting point for most users__
* `example-progressBar.html`: A modified version of `example-pretty.html` with a progress indicator
* `example-customSettings.html`: A modified version of `example-pretty.html` showing how the test can be started with custom parameters
* `example-customSettings2.html`: A modified version of `example-pretty.html` showing how to make a custom test with only download and upload
* `example-gauges.html`: The most sophisticated example, with the same functions as `example-pretty.html` but also gauges and progress indicators for each test. This is the nicest example included, and also a good starting point, but drawing the gauges may slow down the test on slow devices like a Raspberry Pi
* `example-chart.html`: The old example5.html, showing how to use the test with the Chart.js library
These 2 examples require some additional server configuration, discussed in the Telemetry section:
* `example-telemetry.html`: A modified version of `example-pretty.html` with basic telemetry turned on. See the section on Telemetry for details
* `example-telemetry-resultsSharing.html`: A modified version of `example-telemetry.html` with results sharing. This is the most complete and most complex example, showing off all of the speedtest features
### Customizing your example
The included examples are good starting places if you want to have a simple speedtest on your site.
Once you've tested everything and you're sure that everything works, edit it and add some custom stuff like your logo or new colors.
If you want to change the test parameters, for instance to make the download test shorter, you can do so in every example:
Look for the line that contains `postMessage('start `
This is where custom parameters can be passed to the test as a JSON string. You can write the string manually or use ``JSON.stringify`` to do that for you.
Here's an example:
```js
w.postMessage('start {"time_dl":"10"}');
```
This starts the test with default settings, but sets the download test to last only 10 seconds.
Here's a cleaner version using ``JSON.stringify``:
```js
var params={
time_dl:10
}
w.postMessage('start '+JSON.stringify(params))
```
Notice that there is a space after the word `start`, don't forget that!
For a list of all test settings, look below, under Test parameters and Advanced test parameters. __Do not change anything if you don't know what you're doing.__
## Advanced usage
If you don't want to start from one of the examples, here's how to use the worker. Examples are still good for reference, so keep them handy.
To run the test, you need to do 3 things:
* Create the worker
* Write some code that handles the data coming from the worker
* Start the test
### Creating the worker
```js
var w = new Worker("speedtest_worker.min.js")
```
__Important:__ use the minified version, it's smaller!
### Response handler
First, we set up a timer that fetches the status of the worker continuously:
```js
var timer = setInterval(function () {
w.postMessage('status')
}, 100)
```
Then we write a response handler that receives the status and updates the page. Later
we'll see the details of the format of the response.
```js
w.onmessage = function (event) {
var data = JSON.parse(event.data);
if (data.testState >= 4) {
clearInterval(timer) // test is finished or aborted
}
// .. update your page here ..
}
```
#### Response format
The response from the worker is a JSON string containing these entries:
* __testState__: an integer between -1 and 5
* `-1` = Test not started yet
* `0` = Test starting
* `1` = Download test in progress
* `2` = Ping + Jitter test in progress
* `3` = Upload test in progress
* `4` = Test finished
* `5` = Test aborted
* __dlStatus__: either
* Empty string (not started or aborted)
* Download speed in Megabit/s as a number with 2 decimals
* The string "Fail" (test failed)
* __ulStatus__: either
* Empty string (not started or aborted)
* Upload speed in Megabit/s as a number with 2 decimals
* The string "Fail" (test failed)
* __pingStatus__: either
* Empty string (not started or aborted)
* Estimated ping in milliseconds as a number with 2 decimals
* The string "Fail" (test failed)
* __clientIp__: either
* Empty string (not fetched yet or failed)
* The client's IP address as a string (with ISP info if enabled)
* __jitterStatus__: either
* Empty string (not started or aborted)
* Estimated jitter in milliseconds as a number with 2 decimals (lower = stable connection)
* The string "Fail" (test failed)
* __dlProgress__: the progress of the download test as a number between 0 and 1
* __ulProgress__: the progress of the upload test as a number between 0 and 1
* __pingProgress__: the progress of the ping+jitter test as a number between 0 and 1
* __testId__: when telemetry is active, this is the ID of the test as an integer. This string is 'noID' until the test is finished (testState 4). This ID is used for results sharing
Note: clientIp appears before jitterStatus. This is not a mistake, it's to keep the js file compatible with older pages from before the jitter test was introduced.
### Starting the test
To start the test with the default settings, which is usually the best choice, send the start command to the worker:
```js
w.postMessage('start')
```
If you want, you can change these settings and pass them to the worker as JSON when you start it, like this:
```js
w.postMessage('start {"param1": "value1", "param2": "value2", ...}')
```
or this:
```js
var params{
param1:value1,
param2:value2,
...
}
w.postMessage('start '+JSON.stringify(params))
```
#### Test parameters
* __time_dl__: How long the download test should be in seconds. The test will continue regardless of this limit if the speed is still 0.00 when the limit is reached.
* Default: `15`
* Recommended: `>=5`
* __time_ul__: How long the upload test should be in seconds. The test will continue regardless of this limit if the speed is still 0.00 when the limit is reached.
* Default: `15`
* Recommended: `>=10`
* __count_ping__: How many pings to perform in the ping test
* Default: `35`
* Recommended: `>=20`
* __url_dl__: path to garbage.php or a large file to use for the download test.
* Default: `garbage.php`
* __Important:__ path is relative to js file
* __url_ul__: path to an empty file or empty.php to use for the upload test
* Default: `empty.php`
* __Important:__ path is relative to js file
* __url_ping__: path to an empty file or empty.php to use for the ping test
* Default: `empty.php`
* __Important:__ path is relative to js file
* __url_getIp__: path to getIP.php or replacement
* Default: `getIP.php`
* __Important:__ path is relative to js file
* __url_telemetry__: path to telemetry.php or replacement
* Default: `telemetry/telemetry.php`
* __Important:__ path is relative to js file
* __Note:__ you can ignore this parameter if you're not using the telemetry
#### Advanced test parameters
* __test_order__: the order in which tests will be performed. Each character represents an operation:
* `I`: get IP
* `D`: download test
* `U`: upload test
* `P`: ping + jitter test
* `_`: delay 1 second
* Default test order: `IP_D_U`
* __Important:__ Tests can only be run once
* __Important:__ On Firefox, it is better to run the upload test last
* __getIp_ispInfo__: if true, the server will try to get ISP info and pass it along with the IP address. This will add `isp=true` to the request to `url_getIp`. getIP.php accomplishes this using ipinfo.io
* Default: `true`
* __getIp_ispInfo_distance__: if true, the server will try to get an estimate of the distance from the client to the speedtest server. This will add a `distance` argument to the request to `url_getIp`. `__getIp_ispInfo__` must be enabled in order for this to work. getIP.php accomplishes this using ipinfo.io
* `km`: estimate distance in kilometers
* `mi`: estimate distance in miles
* not set: do not measure distance
* Default: `km`
* __enable_quirks__: enables browser-specific optimizations. These optimizations override some of the default settings. They do not override settings that are explicitly set.
* Default: `true`
* __garbagePhp_chunkSize__: size of chunks sent by garbage.php in megabytes
* Default: `20`
* Recommended: `>=10`
* Maximum: `100`
* __xhr_dlMultistream__: how many streams should be opened for the download test
* Default: `10`
* Recommended: `>=3`
* Default override: 3 on Edge if enable_quirks is true
* Default override: 5 on Chromium-based if enable_quirks is true
* __xhr_ulMultistream__: how many streams should be opened for the upload test
* Default: `3`
* Recommended: `>=1`
* Default override: 1 on Firefox if enable_quirks is true
* __xhr_ul_blob_megabytes__: size in megabytes of the blobs sent during the upload test
* Default: `20`
* Default override: 4 on Chromium-based mobile browsers (limitation introduced around version 65). This will be forced
* Default override: IE11 and Edge currently use a different method for the upload test. This parameter is ignored
* __xhr_multistreamDelay__: how long should the multiple streams be delayed (in ms)
* Default: `300`
* Recommended: `>=100`, `<=700`
* __xhr_ignoreErrors__: how to react to errors in download/upload streams and the ping test
* `0`: Fail test on error (behaviour of previous versions of this test)
* `1`: Restart a stream/ping when it fails
* `2`: Ignore all errors
* Default: `1`
* Recommended: `1`
* __time_dlGraceTime__: How long to wait (in seconds) before actually measuring the download speed. This is a good idea because we want to wait for the TCP window to be at its maximum (or close to it)
* Default: `1.5`
* Recommended: `>=0`
* __time_ulGraceTime__: How long to wait (in seconds) before actually measuring the upload speed. This is a good idea because we want to wait for the buffers to be full (avoids the peak at the beginning of the test)
* Default: `3`
* Recommended: `>=1`
* __ping_allowPerformanceApi__: toggles use of Performance API to improve accuracy of Ping/Jitter test on browsers that support it.
* Default: `true`
* __useMebibits__: use mebibits/s instead of megabits/s for the speeds
* Default: `false`
* __overheadCompensationFactor__: compensation for HTTP and network overhead. Default value assumes typical MTUs used over the Internet. You might want to change this if you're using this in your internal network with different MTUs, or if you're using IPv6 instead of IPv4.
* Default: `1.06` probably a decent estimate for all overhead. This was measured empirically by comparing the measured speed and the speed reported by my the network adapter.
* `1048576/925000`: old default value. This is probably too high.
* `1.0513`: HTTP+TCP+IPv6+ETH, over the Internet (empirically tested, not calculated)
* `1.0369`: Alternative value for HTTP+TCP+IPv4+ETH, over the Internet (empirically tested, not calculated)
* `1.081`: Yet another alternative value for over the Internet (empirically tested, not calculated)
* `1514 / 1460`: TCP+IPv4+ETH, ignoring HTTP overhead
* `1514 / 1440`: TCP+IPv6+ETH, ignoring HTTP overhead
* `1`: ignore overheads. This measures the speed at which you actually download and upload files rather than the raw connection speed
* __telemetry_level__: The type of telemetry to use. See the telemetry section for more info about this
* Default: `none`
* `basic`: send results only
* `full`: send results and debug info
* __telemetry_extra__: Extra data that you want to be passed to the telemetry. This is a string field, if you want to pass an object, make sure you use ``JSON.stringify``. This string will be added to the database entry for this test.
### Aborting the test prematurely
The test can be aborted at any time by sending an abort command to the worker:
```js
w.postMessage('abort')
```
This will terminate all network activity and stop the worker.
__Important:__ do not simply kill the worker while it's running, as it may leave pending XHR requests!
### Important notice on backwards compatibility
__Do NOT link the js file from github or fdossena.com directly into your html file. __
A lot of web developers think that referring to the latest version of a library in their project is a good thing. It is not.
Things may change and I don't want to break your project, so do yourself a favor, and keep all files on your server.
You have been warned.
## Using the test without PHP
If your server does not support PHP, or you're using something newer like Node.js, you can still use this test by replacing `garbage.php`, `empty.php` and `getIP.php` with equivalents.
### Replacements
#### Replacement for `garbage.php`
A replacement for `garbage.php` must generate incompressible garbage data.
A large file (10-100 Mbytes) is a possible replacement. You can get [one here](http://downloads.fdossena.com/geth.php?r=speedtest-bigfile).
If you're using Node.js or some other server, your replacement should accept the `ckSize` parameter (via GET) which tells it how many megabytes of garbage to generate.
It is important here to turn off compression, and generate incompressible data.
A symlink to `/dev/urandom` is also ok.
#### Replacement for `empty.php`
Your replacement must simply respond with a HTTP code 200 and send nothing else. You may want to send additional headers to disable caching. The test assumes that Connection:keep-alive is sent by the server.
An empty file can be used for this.
#### Replacement for `getIP.php`
Your replacement can simply respond with the client's IP as plaintext or do something more fancy.
If you want, you can also accept the `isp=true` parameter and also include the ISP info. In this case, return a JSON string containing a string called `processedString` with the text that you want to be displayed in the IP address field, and an object called `rawIspInfo` containing whatever you want (will be included in telemetry if enabled).
#### JS
You need to start the test with your replacements like this:
```js
w.postMessage('start {"url_dl": "newGarbageURL", "url_ul": "newEmptyURL", "url_ping": "newEmptyURL", "url_getIp": "newIpURL"}')
```
## Telemetry
Telemetry currently requires PHP and either MySQL, PostgreSQL or SQLite.
To set up the telemetry, we need to do 4 things:
* copy the `telemetry` folder
* edit `telemetry_settings.php` to add your database settings
* create the database
* enable telemetry
### Creating the database
This step is only for MySQL and PostgreSQL.
Log into your database using phpMyAdmin or a similar software and import the appropriate sql file into an empty database. For MySQL databases use `telemetry_mysql.sql` and for PostgreSQL databases use `telemetry_postgesql.sql`. They're inside the `telemetry` folder. You can delete the files afterwards.
If you see a table called `speedtest_users`, empty, you did it right.
### Configuring `telemetry.php`
Open `telemetry_settings.php` with notepad or a similar text editor.
Set your preferred database, ``$db_type="mysql";``, ``$db_type="sqlite";`` or ``$db_type="postgresql";``
If you choose to use Sqlite3, you must set the path to your database file:
```php
$Sqlite_db_file = "../telemetry.sql";
```
If you choose to use MySQL, you must also add your database credentials:
```php
$MySql_username="USERNAME"; //your database username
$MySql_password="PASSWORD"; //your database password
$MySql_hostname="DB_HOSTNAME"; //database address, usually localhost
$MySql_databasename="DB_NAME"; //the name of the database where you loaded telemetry_mysql.sql
```
If you choose to use PostgreSQL, you must also add your database credentials:
```php
$PostgreSql_username="USERNAME"; //your database username
$PostgreSql_password="PASSWORD"; //your database password
$PostgreSql_hostname="DB_HOSTNAME"; //database address, usually localhost
$PostgreSql_databasename="DB_NAME"; //the name of the database where you loaded telemetry_postgresql.sql
```
### Enabling telemetry
Edit your test page; where you start the worker, you need to specify the `telemetry_level`.
There are 3 levels:
* `none`: telemetry is disabled (default)
* `basic`: telemetry collects IP, ISP info, User Agent, Preferred language, Test results
* `full`: same as above, but also collects a debug log (10-150 Kb each, not recommended unless you're developing the speedtest)
Example:
```js
w.postMessage('start {"telemetry_level":"basic"}')
```
You can use example-telemetryEnabled.html and example-telemetry-resultSharing.html as starting points.
### Results sharing
This feature generates an image that can be share by the user containing the download, upload, ping, jitter and ISP (if enabled).
To use this feature, copy the `results` folder. You can customize the style of the generated image by editing the settings in `results/index.php`.
This feature requires Telemetry to be enabled, and FreeType2 must be installed in PHP (if not already be installed by your distro).
__Important:__ This feature relies on PHP functions `imagefttext` and `imageftbbox` that are well known for being problematic. The most common problem is that they can't find the font files and therefore nothing is drawn. This problem is metioned [here](http://php.net/manual/en/function.imagefttext.php) and was experienced by a lot of users.
### Seeing the results
A basic front-end for visualizing and searching tests by ID is available in `telemetry/stats.php`.
A login is required to access the interface. __Important__: change the default password in `telemetry_settings.php`.
## Troubleshooting
These are the most common issues reported by users, and how to fix them. If you still need help, contact me at [info@fdossena.com](mailto:info@fdossena.com).
#### Download test gives very low result
Are garbage.php and empty.php (or your replacements) reachable?
Press F12, select network and start the test. Do you see errors? (cancelled requests are not errors)
If a small download starts, open it in a text editor. Does it say it's missing openssl_random_pseudo_bytes()? In this case, install OpenSSL (this is usually included when you install Apache and PHP on most distros).
#### Upload test is inaccurate, and/or I see lag spikes
Check your server's maximum POST size, make sure it's at least 20Mbytes, possibly more
#### Download and/or upload results are slightly too optimistic
The test was fine tuned to run over a typical IPv4 internet connection. If you're using it under different conditions, see the ``overheadCompensationFactor`` parameter.
#### All tests are wrong, give extremely high results, browser lags/crashes, ...
You're running the test on localhost, therefore it is trying to measure the speed of your loopback interface. The test is meant to be run over an Internet connection, from a different machine.
#### Ping test shows double the actual ping
Make sure your server is sending the ```Connection:keep-alive``` header
#### The server is behind a load balancer, proxy, etc. and I get the wrong IP address
Edit getIP.php and replace lines 5-13 with what is more appropriate in your scenario.
Example: ```$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];```
#### The results sharing just generates a blank image
If the image doesn't display and the browser displays a broken image icon, FreeType2 is not installed or configured properly.
If the image is blank, this usually happens because PHP can't find the font files inside the `results` folder. You can fix your PHP config or edit `results/index.php` and use absolute paths for the fonts. This is a [known issue with PHP](http://php.net/manual/en/function.imagefttext.php) and no real solution is known.
## Known bugs and limitations
### General
* The ping/jitter test is measured by seeing how long it takes for an empty XHR to complete. It is not an acutal ICMP ping. Different browsers may also show different results, especially on very fast connections on slow devices.
### IE-Specific
* The upload test is not precise on very fast connections with high latency (will probably be fixed by Edge 17)
* On IE11, a same origin policy error is erroneously triggered under unknown conditions. Seems to be related to running the test from unusual URLs like a top level domain (for instance http://abc/speedtest). These are bugs in IE11's implementation of the same origin policy, not in the speedtest itself.
* On IE11, under unknown circumstances, on some systems the test can only be run once, after which speedtest_worker.js will not be loaded by IE until the browser is restarted. This is a rare bug in IE11.
### Firefox-Specific
* On some Linux systems with hardware acceleration turned off, the page rendering makes the browser lag, reducing the accuracy of the ping/jitter test, and potentially even the download and upload tests on very fast connections.
## Making changes
Since this is an open source project, you can modify it.
To make changes to the speedtest itself, edit `speedtest_worker.js`
To create the minified version, use [UglifyJS](https://github.com/mishoo/UglifyJS2) like this:
```
uglifyjs -c -o speedtest_worker.min.js -- speedtest_worker.js
```
Pull requests are very appreciated. If you don't use github (or git), simply contact me at [info@fdossena.com](mailto:info@fdossena.com).
__Important:__ please add your name to modified versions to distinguish them from the main project.
## License
This software is under the GNU LGPL license, Version 3 or newer.
To put it short: you are free to use, study, modify, and redistribute this software and modified versions of it, for free or for money.
You can also use it in proprietary software but all changes to this software must remain under the same GNU LGPL license.
Contact me at [info@fdossena.com](mailto:info@fdossena.com) for other licensing models.

View file

@ -1,39 +0,0 @@
<!DOCTYPE html>
<html>
<meta charset="UTF-8" />
<head>
<title>HTML5 Speedtest</title>
</head>
<body>
<h1>HTML5 Speedtest</h1>
<h4>IP Address</h4>
<p id="ip"></p>
<h4>Download</h4>
<p id="download"></p>
<h4>Upload</h4>
<p id="upload"></p>
<h4>Latency</h4>
<p id="ping"></p>
<script type="text/javascript">
var w = new Worker('speedtest_worker.min.js') // create new worker
setInterval(function () { w.postMessage('status') }, 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
var data = JSON.parse(event.data); //fetch speedtest worker output
document.getElementById('download').textContent = data.dlStatus + ' Mbit/s'
document.getElementById('upload').textContent = data.ulStatus + ' Mbit/s'
document.getElementById('ping').textContent = data.pingStatus + ' ms, ' + data.jitterStatus + ' ms jitter'
document.getElementById('ip').textContent = data.clientIp
}
w.postMessage('start') // start the speedtest (default params. keep garbage.php and empty.dat in the same directory as the js file)
</script>
<a href="https://github.com/adolfintel/speedtest">Source code</a>
</body>
</html>

View file

@ -1,257 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>HTML5 Speedtest</title>
<style type="text/css">
html,
body {
margin: 0;
padding: 0;
border: none;
text-align: center;
}
#startBtn {
display: inline-block;
border: 0.15em solid #000000;
padding: 0.3em 0.5em;
margin: 0.6em;
color: #000000;
text-decoration: none;
}
#ip {
margin: 0.8em 0;
font-size: 1.2em;
}
#chart1Area,
#chart2Area {
width: 100%;
max-width: 30em;
height: 10em;
display: block;
margin: 0 auto;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.bundle.min.js"></script>
<script type="text/javascript">
var w = null
function runTest() {
var chart1ctx = document.getElementById('chart1Area').getContext('2d')
var chart2ctx = document.getElementById('chart2Area').getContext('2d')
var dlDataset = {
label: 'Download',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(75,192,192,0.4)',
borderColor: 'rgba(75,192,192,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(75,192,192,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(75,192,192,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [0],
spanGaps: false
}
var ulDataset = {
label: 'Upload',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(192,192,75,0.4)',
borderColor: 'rgba(192,192,75,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(192,192,75,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(192,192,75,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [0],
spanGaps: false
}
var pingDataset = {
label: 'Ping',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(75,220,75,0.4)',
borderColor: 'rgba(75,220,75,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(75,220,75,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(75,220,75,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [],
spanGaps: false
}
var jitterDataset = {
label: 'Jitter',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(220,75,75,0.4)',
borderColor: 'rgba(220,75,75,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(220,75,75,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(220,75,75,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [],
spanGaps: false
}
var chart1Options = {
type: 'line',
data: {
datasets: [dlDataset, ulDataset]
},
options: {
responsive: true,
legend: {
position: 'bottom'
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: false
},
ticks: {
beginAtZero: true
}
}],
yAxes: [{
display: true,
scaleLabel: 'Speed',
ticks: {
beginAtZero: true
}
}]
}
}
}
var chart2Options = {
type: 'line',
data: {
datasets: [pingDataset, jitterDataset]
},
options: {
responsive: true,
legend: {
position: 'bottom'
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: false
},
ticks: {
beginAtZero: true
}
}],
yAxes: [{
display: true,
scaleLabel: 'Latency',
ticks: {
beginAtZero: true
}
}]
}
}
}
var chart1 = new Chart(chart1ctx, chart1Options)
var chart2 = new Chart(chart2ctx, chart2Options)
document.getElementById('startBtn').style.display = 'none'
document.getElementById('testArea').style.display = ''
document.getElementById('abortBtn').style.display = ''
w = new Worker('speedtest_worker.min.js')
var interval = setInterval(function () { w.postMessage('status') }, 100)
w.onmessage = function (event) {
var data = JSON.parse(event.data)
var status = data.testState
if (status >= 4) {
clearInterval(interval)
document.getElementById('abortBtn').style.display = 'none'
document.getElementById('startBtn').style.display = ''
w = null
}
if (status === 5) {
document.getElementById('testArea').style.display = 'none'
}
if (status === 1 && Number(data.dlStatus) > 0) {
chart1.data.datasets[0].data[~~(20*Number(data.dlProgress))]=(Number(data.dlStatus))
chart1.data.labels[chart1.data.datasets[0].data.length - 1] = ''
chart1.update()
}
if (status === 3 && Number(data.ulStatus) > 0) {
chart1.data.datasets[1].data[~~(20*Number(data.ulProgress))]=(Number(data.ulStatus))
chart1.data.labels[chart1.data.datasets[1].data.length - 1] = ''
chart1.update()
}
if (status === 2 && Number(data.pingStatus) > 0) {
chart2.data.datasets[0].data.push(Number(data.pingStatus))
chart2.data.datasets[1].data.push(Number(data.jitterStatus))
chart2.data.labels[chart2.data.datasets[0].data.length - 1] = ''
chart2.data.labels[chart2.data.datasets[1].data.length - 1] = ''
chart2.update()
}
ip.textContent = data.clientIp
}
w.postMessage('start')
}
function abortTest() {
if (w) w.postMessage('abort')
}
</script>
</head>
<body>
<h1>HTML Speedtest - Chart.js example</h1>
<div id="testArea" style="display:none">
<p id="ip">Please wait...</p>
<h2>Speed</h2>
<canvas id="chart1Area"></canvas>
<h2>Latency</h2>
<canvas id="chart2Area"></canvas>
<br/>
<a href="javascript:abortTest()" id="abortBtn">Abort</a>
</div>
<a href="javascript:runTest()" id="startBtn">Run speedtest</a>
<br/><br/> Charts by <a href="http://www.chartjs.org/">Chart.js</a><br/><br/><a href="https://github.com/adolfintel/speedtest">Source code</a>
</body>
</html>

View file

@ -1,200 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>HTML5 Speedtest</title>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:14em;
height:9em;
position:relative;
box-sizing:border-box;
}
div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.meterText{
position:absolute;
bottom:1.5em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
#dlText{
color:#6060AA;
}
#ulText{
color:#309030;
}
#pingText,#jitText{
color:#AA6060;
}
div.meterText:empty:before{
color:#505050 !important;
content:"0.00";
}
div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testGroup{
display:inline-block;
}
@media all and (max-width:65em){
body{
font-size:1.5vw;
}
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
div.testGroup{
display:block;
margin: 0 auto;
}
}
</style>
<script type="text/javascript">
function I(id){return document.getElementById(id);}
var w=null; //speedtest worker
var parameters={ //custom test parameters. See doc.md for a complete list
time_dl: 10, //download test lasts 10 seconds
time_ul: 10, //upload test lasts 10 seconds
count_ping: 20, //ping+jitter test does 20 pings
getIp_ispInfo: false //will only get IP address without ISP info
};
function startStop(){
if(w!=null){
//speedtest is running, abort
w.postMessage('abort');
w=null;
I("startStopBtn").className="";
initUI();
}else{
//test is not running, begin
w=new Worker('speedtest_worker.min.js');
w.postMessage('start '+JSON.stringify(parameters)); //run the test with custom parameters
I("startStopBtn").className="running";
w.onmessage=function(e){
var data=JSON.parse(e.data);
var status=data.testState;
if(status>=4){
//test completed
I("startStopBtn").className="";
w=null;
}
I("ip").textContent=data.clientIp;
I("dlText").textContent=(status==1&&data.dlStatus==0)?"...":data.dlStatus;
I("ulText").textContent=(status==3&&data.ulStatus==0)?"...":data.ulStatus;
I("pingText").textContent=data.pingStatus;
I("jitText").textContent=data.jitterStatus;
};
}
}
//poll the status from the worker every 200ms (this will also update the UI)
setInterval(function(){
if(w) w.postMessage('status');
},200);
//function to (re)initialize UI
function initUI(){
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
}
</script>
</head>
<body>
<h1>HTML5 Speedtest - Custom settings example</h1>
<div id="startStopBtn" onclick="startStop()"></div>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Ping</div>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
IP Address: <span id="ip"></span>
</div>
</div>
<div>Custom parameters: <span id="params"></span><script type="text/javascript">I("params").textContent=JSON.stringify(parameters)</script></div>
<a href="https://github.com/adolfintel/speedtest">Source code</a>
<script type="text/javascript">initUI();</script>
</body>
</html>

View file

@ -1,172 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>HTML5 Speedtest</title>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:14em;
height:9em;
position:relative;
box-sizing:border-box;
}
div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.meterText{
position:absolute;
bottom:1.5em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
#dlText{
color:#6060AA;
}
#ulText{
color:#309030;
}
#pingText,#jitText{
color:#AA6060;
}
div.meterText:empty:before{
color:#505050 !important;
content:"0.00";
}
div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testGroup{
display:inline-block;
}
@media all and (max-width:65em){
body{
font-size:2vw;
}
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
div.testGroup{
display:block;
margin: 0 auto;
}
}
</style>
<script type="text/javascript">
function I(id){return document.getElementById(id);}
var w=null; //speedtest worker
function startStop(){
if(w!=null){
//speedtest is running, abort
w.postMessage('abort');
w=null;
I("startStopBtn").className="";
initUI();
}else{
//test is not running, begin
w=new Worker('speedtest_worker.min.js');
w.postMessage('start {"test_order":"D_U"}'); //run only download and upload tests, with a 1s pause in between
I("startStopBtn").className="running";
w.onmessage=function(e){
var data=JSON.parse(e.data);
var status=data.testState;
if(status>=4){
//test completed
I("startStopBtn").className="";
w=null;
}
I("dlText").textContent=(status==1&&data.dlStatus==0)?"...":data.dlStatus;
I("ulText").textContent=(status==3&&data.ulStatus==0)?"...":data.ulStatus;
};
}
}
//poll the status from the worker every 200ms (this will also update the UI)
setInterval(function(){
if(w) w.postMessage('status');
},200);
//function to (re)initialize UI
function initUI(){
I("dlText").textContent="";
I("ulText").textContent="";
}
</script>
</head>
<body>
<h1>HTML5 Speedtest - Custom settings example</h1>
<div id="startStopBtn" onclick="startStop()"></div>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
</div>
<a href="https://github.com/adolfintel/speedtest">Source code</a>
<script type="text/javascript">initUI();</script>
</body>
</html>

View file

@ -1,193 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>HTML5 Speedtest</title>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:14em;
height:9em;
position:relative;
box-sizing:border-box;
}
div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.meterText{
position:absolute;
bottom:1.5em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
#dlText{
color:#6060AA;
}
#ulText{
color:#309030;
}
#pingText,#jitText{
color:#AA6060;
}
div.meterText:empty:before{
color:#505050 !important;
content:"0.00";
}
div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testGroup{
display:inline-block;
}
@media all and (max-width:65em){
body{
font-size:1.5vw;
}
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
div.testGroup{
display:block;
margin: 0 auto;
}
}
</style>
<script type="text/javascript">
function I(id){return document.getElementById(id);}
var w=null; //speedtest worker
function startStop(){
if(w!=null){
//speedtest is running, abort
w.postMessage('abort');
w=null;
I("startStopBtn").className="";
initUI();
}else{
//test is not running, begin
w=new Worker('speedtest_worker.min.js');
w.postMessage('start'); //Add optional parameters as a JSON object to this command
I("startStopBtn").className="running";
w.onmessage=function(e){
var data=JSON.parse(e.data);
var status=data.testState;
if(status>=4){
//test completed
I("startStopBtn").className="";
w=null;
}
I("ip").textContent=data.clientIp;
I("dlText").textContent=(status==1&&data.dlStatus==0)?"...":data.dlStatus;
I("ulText").textContent=(status==3&&data.ulStatus==0)?"...":data.ulStatus;
I("pingText").textContent=data.pingStatus;
I("jitText").textContent=data.jitterStatus;
};
}
}
//poll the status from the worker every 200ms (this will also update the UI)
setInterval(function(){
if(w) w.postMessage('status');
},200);
//function to (re)initialize UI
function initUI(){
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
}
</script>
</head>
<body>
<h1>HTML5 Speedtest</h1>
<div id="startStopBtn" onclick="startStop()"></div>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Ping</div>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
IP Address: <span id="ip"></span>
</div>
</div>
<a href="https://github.com/adolfintel/speedtest">Source code</a>
<script type="text/javascript">initUI();</script>
</body>
</html>

View file

@ -1,215 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>HTML5 Speedtest</title>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:14em;
height:9em;
position:relative;
box-sizing:border-box;
}
div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.meterText{
position:absolute;
bottom:1.5em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
#dlText{
color:#6060AA;
}
#ulText{
color:#309030;
}
#pingText,#jitText{
color:#AA6060;
}
div.meterText:empty:before{
color:#505050 !important;
content:"0.00";
}
div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testGroup{
display:inline-block;
}
@media all and (max-width:65em){
body{
font-size:1.5vw;
}
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
div.testGroup{
display:block;
margin: 0 auto;
}
}
#progressBar{
width:90%;
height:0.3em;
background-color:#EEEEEE;
position:relative;
display:block;
margin:0 auto;
margin-bottom:2em;
}
#progress{
position:absolute;
top:0; left:0;
height:100%;
width:0%;
transition: width 2s;
background-color:#90BBFF;
}
</style>
<script type="text/javascript">
function I(id){return document.getElementById(id);}
var w=null; //speedtest worker
function startStop(){
if(w!=null){
//speedtest is running, abort
w.postMessage('abort');
w=null;
I("startStopBtn").className="";
initUI();
}else{
//test is not running, begin
w=new Worker('speedtest_worker.min.js');
w.postMessage('start'); //Add optional parameters as a JSON object to this command
I("startStopBtn").className="running";
w.onmessage=function(e){
var data=JSON.parse(e.data);
var status=data.testState;
if(status>=4){
//test completed
I("startStopBtn").className="";
w=null;
}
I("ip").textContent=data.clientIp;
I("dlText").textContent=(status==1&&data.dlStatus==0)?"...":data.dlStatus;
I("ulText").textContent=(status==3&&data.ulStatus==0)?"...":data.ulStatus;
I("pingText").textContent=data.pingStatus;
I("jitText").textContent=data.jitterStatus;
var prog=(Number(data.dlProgress)*2+Number(data.ulProgress)*2+Number(data.pingProgress))/5;
I("progress").style.width=(100*prog)+"%";
};
}
}
//poll the status from the worker every 200ms (this will also update the UI)
setInterval(function(){
if(w) w.postMessage('status');
},200);
//function to (re)initialize UI
function initUI(){
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
I("progress").style.width="";
}
</script>
</head>
<body>
<h1>HTML5 Speedtest</h1>
<div id="startStopBtn" onclick="startStop()"></div>
<div id="test">
<div id="progressBar"><div id="progress"></div></div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Ping</div>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
IP Address: <span id="ip"></span>
</div>
</div>
<a href="https://github.com/adolfintel/speedtest">Source code</a>
<script type="text/javascript">initUI();</script>
</body>
</html>

View file

@ -1,194 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>HTML5 Speedtest</title>
<style type="text/css">
html,body{
border:none; padding:0; margin:0;
background:#FFFFFF;
color:#202020;
}
body{
text-align:center;
font-family:"Roboto",sans-serif;
}
h1{
color:#404040;
}
#startStopBtn{
display:inline-block;
margin:0 auto;
color:#6060AA;
background-color:rgba(0,0,0,0);
border:0.15em solid #6060FF;
border-radius:0.3em;
transition:all 0.3s;
box-sizing:border-box;
width:8em; height:3em;
line-height:2.7em;
cursor:pointer;
box-shadow: 0 0 0 rgba(0,0,0,0.1), inset 0 0 0 rgba(0,0,0,0.1);
}
#startStopBtn:hover{
box-shadow: 0 0 2em rgba(0,0,0,0.1), inset 0 0 1em rgba(0,0,0,0.1);
}
#startStopBtn.running{
background-color:#FF3030;
border-color:#FF6060;
color:#FFFFFF;
}
#startStopBtn:before{
content:"Start";
}
#startStopBtn.running:before{
content:"Abort";
}
#test{
margin-top:2em;
margin-bottom:12em;
}
div.testArea{
display:inline-block;
width:14em;
height:9em;
position:relative;
box-sizing:border-box;
}
div.testName{
position:absolute;
top:0.1em; left:0;
width:100%;
font-size:1.4em;
z-index:9;
}
div.meterText{
position:absolute;
bottom:1.5em; left:0;
width:100%;
font-size:2.5em;
z-index:9;
}
#dlText{
color:#6060AA;
}
#ulText{
color:#309030;
}
#pingText,#jitText{
color:#AA6060;
}
div.meterText:empty:before{
color:#505050 !important;
content:"0.00";
}
div.unit{
position:absolute;
bottom:2em; left:0;
width:100%;
z-index:9;
}
div.testGroup{
display:inline-block;
}
@media all and (max-width:65em){
body{
font-size:1.5vw;
}
}
@media all and (max-width:40em){
body{
font-size:0.8em;
}
div.testGroup{
display:block;
margin: 0 auto;
}
}
</style>
<script type="text/javascript">
function I(id){return document.getElementById(id);}
var w=null; //speedtest worker
function startStop(){
if(w!=null){
//speedtest is running, abort
w.postMessage('abort');
w=null;
I("startStopBtn").className="";
initUI();
}else{
//test is not running, begin
w=new Worker('speedtest_worker.min.js');
w.postMessage('start {"telemetry_level":"basic"}'); //Add optional parameters (see doc.md)
I("startStopBtn").className="running";
w.onmessage=function(e){
var data=JSON.parse(e.data);
var status=data.testState
if(status>=4){
//test completed
I("startStopBtn").className="";
w=null;
}
I("ip").textContent=data.clientIp;
I("dlText").textContent=(status==1&&data.dlStatus==0)?"...":data.dlStatus;
I("ulText").textContent=(status==3&&data.ulStatus==0)?"...":data.ulStatus;
I("pingText").textContent=data.pingStatus;
I("jitText").textContent=data.jitterStatus;
};
}
}
//poll the status from the worker every 200ms (this will also update the UI)
setInterval(function(){
if(w) w.postMessage('status');
},200);
//function to (re)initialize UI
function initUI(){
I("dlText").textContent="";
I("ulText").textContent="";
I("pingText").textContent="";
I("jitText").textContent="";
I("ip").textContent="";
}
</script>
</head>
<body>
<h1>HTML5 Speedtest - Telemetry example</h1>
<div id="startStopBtn" onclick="startStop()"></div>
<div id="test">
<div class="testGroup">
<div class="testArea">
<div class="testName">Download</div>
<div id="dlText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
<div class="testArea">
<div class="testName">Upload</div>
<div id="ulText" class="meterText"></div>
<div class="unit">Mbps</div>
</div>
</div>
<div class="testGroup">
<div class="testArea">
<div class="testName">Ping</div>
<div id="pingText" class="meterText"></div>
<div class="unit">ms</div>
</div>
<div class="testArea">
<div class="testName">Jitter</div>
<div id="jitText" class="meterText"></div>
<div class="unit">ms</div>
</div>
</div>
<div id="ipArea">
IP Address: <span id="ip"></span>
</div>
</div>
<div>Basic telemetry is active; results will be saved in your database, without the full log. If the results don't appear, check the settings in telemetry_settings.php</div>
<a href="https://github.com/adolfintel/speedtest">Source code</a>
<script type="text/javascript">initUI();</script>
</body>
</html>

File diff suppressed because one or more lines are too long