improvements and new example (#29)

Merge with #29. Using standard js code style (ugh...); rewritten documentation in markdown; Added example6
This commit is contained in:
anoy 2017-05-15 12:02:32 +02:00 committed by Federico Dossena
parent 0dd0eeaa86
commit 3f06ab1381
12 changed files with 1365 additions and 976 deletions

18
.editorconfig Normal file
View file

@ -0,0 +1,18 @@
# 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

239
doc.md Normal file
View file

@ -0,0 +1,239 @@
# HTML5 Speedtest
> by Federico Dossena
> Version 4.2, April 26 2017
> [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).
Apache2 and PHP are recommended, but not mandatory.
If this looks good, let's proceed and see how to use the test.
## Installation
To install the test on your server, upload the following files:
* `speedtest_worker.min.js`
* `garbage.php`
* `getIP.php`
* `empty.dat`
You may also want to upload one of the examples to test it.
Later we'll see how to use the test without PHP.
__Important:__ keep all the files together; all paths are relative to the js file
## Usage
To run the test, you need to do 3 things:
* Create the worker
* Write some code that handles the responses 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 = event.data.split(';')
var testState = data[0]
var dlStatus = data[1]
var ulStatus = data[2]
var pingStatus = data[3]
var jitterStatus = data[5]
var clientIp = data[4]
if (testState >= 4) {
clearInterval(timer) // test is finished or aborted
}
// .. update your page here ..
}
```
#### Response format
The response from the worker is composed of values separated by `;` (semicolon) in this
format:
`testState;dlStatus;ulStatus;pingStatus;clientIp;jitterStatus`
* __testState__ is an integer 0-5
* `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__ is either
* Empty string (not started or aborted)
* Download speed in Megabit/s as a number with 2 digits
* The string "Fail" (test failed)
* __ulStatus__ is either
* Empty string (not started or aborted)
* Upload speed in Megabit/s as a number with 2 digits
* The string "Fail" (test failed)
* __pingStatus__ is either
* Empty string (not started or aborted)
* Estimated ping in milliseconds as a number with 2 digits
* The string "Fail" (test failed)
* __clientIp__ is either
* Empty string (not fetched yet or failed)
* The client's IP address as a string
* __jitterStatus__ is either
* Empty string (not started or aborted)
* Estimated jitter in milliseconds as a number with 2 digits (lower = stable connection)
* The string "Fail" (test failed)
### Starting the test
To start the test, send the start command to the worker:
```js
w.postMessage('start')
```
This starts the test with the default settings, which is usually the best choice. If you want, you can change these settings and pass them to the worker as JSON with like this:
```js
w.postMessage('start {"param1": "value1", "param2": "value2", ...}')
```
#### Test parameters
* __time_dl__: How long the download test should be in seconds
* Default: `15`
* Recommended: `>=5`
* __time_ul__: How long the upload test should be in seconds
* 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 ab empty file or empty.dat to use for the upload test
* Default: `empty.dat`
* __Important:__ path is relative to js file
* __url_ping__: path to an empty file or empty.dat to use for the ping test
* Default: `empty.dat`
* __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
* __enable_quirks__: enables browser-specific optimizations. These optimizations override some of the default settings below. 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`
* Default override: 5 on Safari if enable_quirks is true
* __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
* Default override: 10 on Safari if enable_quirks is true
* __allow_fetchAPI__: allow the use of Fetch API for the download test instead of regular XHR. Experimental, not recommended.
* Default: `false`
* __force_fetchAPI__: forces the use of Fetch API on all browsers that support it
* Default: `false`
Fetch API are used if the following conditions are met:
* allow_fetchAPI is true
* Chromium-based browser with support for Fetch API and enable_quirks is true
OR force_fetchAPI is true and the browser supports Fetch API
### 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 will leave pending XHR requests!
## 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` and `getIP.php`.
### 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 `getIP.php`
Your replacement must simply respond with the client's IP as plaintext. Nothing fancy.
### JS
You need to start the test with your replacements like this:
```js
w.postMessage('start {"url_dl": "newGarbageURL", "url_getIp": "newIpURL"}')
```
## Known bugs and limitations
* __Chrome:__ high CPU usage from XHR requests with very fast connections (like gigabit).
For this reason, the test may report inaccurate results if your CPU is too slow. (Does not affect most computers)
* __IE11:__ the upload test is not precise on very fast connections
* __Safari:__ works, but needs more testing and tweaking for 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 like this:
```
uglifyjs -c --screw-ie8 speedtest_worker.js > speedtest_worker.min.js
```
Pull requests are much appreciated. If you don't use github (or git), simply contact me.
__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.

BIN
doc.pdf

Binary file not shown.

View file

@ -1,29 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>Speedtest</title>
<title>Speedtest</title>
</head>
<body>
<h1>Speedtest</h1>
<h4>IP Address</h4>
<div id="ip"></div>
<h4>Download</h4>
<div id="download"></div>
<h4>Upload</h4>
<div id="upload"></div>
<h4>Latency</h4>
<div id="ping"></div>
<script type="text/javascript">
var w=new Worker("speedtest_worker.min.js"); //create new worker
setInterval(function(){w.postMessage("status");}.bind(this),100); //ask for status every 100ms
w.onmessage=function(event){ //when status is received, split the string and put the values in the appropriate fields
var data=event.data.split(";"); //string format: status;download;upload;ping (speeds are in mbit/s) (status: 0=not started, 1=downloading, 2=uploading, 3=ping, 4=done, 5=aborted)
document.getElementById("download").innerHTML=data[1]+" Mbit/s";
document.getElementById("upload").innerHTML=data[2]+" Mbit/s";
document.getElementById("ping").innerHTML=data[3]+" ms, "+data[5]+" ms jitter";
document.getElementById("ip").innerHTML=data[4];
<h1>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 = event.data.split(';') // string format: status;download;upload;ping (speeds are in mbit/s) (status: 0=not started, 1=downloading, 2=uploading, 3=ping, 4=done, 5=aborted)
document.getElementById('download').textContent = data[1] + ' Mbit/s'
document.getElementById('upload').textContent = data[2] + ' Mbit/s'
document.getElementById('ping').textContent = data[3] + ' ms, ' + data[5] + ' ms jitter'
document.getElementById('ip').textContent = data[4]
}
w.postMessage("start"); //start the speedtest (default params. keep garbage.php and empty.dat in the same directory as the js file)
</script>
w.postMessage('start') // start the speedtest (default params. keep garbage.php and empty.dat in the same directory as the js file)
</script>
</body>
</html>

View file

@ -1,38 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<head>
<title>Speedtest</title>
<style type="text/css">
html,body{
margin:0;
padding:0;
border:none;
text-align:center;
}
div.test{
display:inline-block;
margin:1em;
text-align:center;
}
div.testName,div.meterUnit{
font-size:3vw;
}
div.meter{
font-size:6vw;
line-height:1.5em;
height:1.5em !important;
}
.flash{
animation:flash 0.6s linear infinite;
}
@keyframes flash{
0%{opacity:0.6;}
50%{opacity:1;}
html,
body {
margin: 0;
padding: 0;
border: none;
text-align: center;
}
div.test {
display: inline-block;
margin: 1em;
text-align: center;
}
div.testName,
div.meterUnit {
font-size: 3vw;
}
div.meter {
font-size: 6vw;
line-height: 1.5em;
height: 1.5em !important;
}
.flash {
animation: flash 0.6s linear infinite;
}
@keyframes flash {
0% { opacity: 0.6; }
50% { opacity: 1; }
}
</style>
</head>
<body>
</head>
<body>
<h1>Speedtest</h1>
<div class="test">
<div class="testName">Download</div>
@ -56,23 +63,29 @@
</div>
<div id="ip"></div>
<script type="text/javascript">
var w=new Worker("speedtest_worker.min.js");
var interval=setInterval(function(){w.postMessage("status");}.bind(this),100);
w.onmessage=function(event){
var data=event.data.split(";");
var status=Number(data[0]);
var dl=document.getElementById("download"),ul=document.getElementById("upload"),ping=document.getElementById("ping"),ip=document.getElementById("ip"),jitter=document.getElementById("jitter");
dl.className=status==1?"flash":"";ping.className=status==2?"flash":"";jitter.className=ul.className=status==3?"flash":"";
if(status>=4){
clearInterval(interval);
var w = new Worker('speedtest_worker.min.js')
var interval = setInterval(function () { w.postMessage('status') }, 100)
w.onmessage = function (event) {
var data = event.data.split(';')
var status = Number(data[0])
var dl = document.getElementById('download')
var ul = document.getElementById('upload')
var ping = document.getElementById('ping')
var ip = document.getElementById('ip')
var jitter = document.getElementById('jitter')
dl.className = status === 1 ? 'flash' : ''
ping.className = status === 2 ? 'flash' : ''
jitter.className = ul.className = status === 3 ? 'flash' : ''
if (status >= 4) {
clearInterval(interval)
}
dl.innerHTML=data[1];
ul.innerHTML=data[2];
ping.innerHTML=data[3];
jitter.innerHTML=data[5];
ip.innerHTML="Your IP: "+data[4];
}.bind(this);
w.postMessage('start {"url_dl":"garbage.php","url_ul":"empty.dat","url_ping":"empty.dat","time_dl":"10","time_ul":"15","count_ping":"30"}'); //start with custom parameters. paths are relative to js file. you can omit parameters that you don't want to change
dl.textContent = data[1]
ul.textContent = data[2]
ping.textContent = data[3]
jitter.textContent = data[5]
ip.textContent = 'Your IP: ' + data[4]
}
w.postMessage('start {"url_dl": "garbage.php", "url_ul": "empty.dat", "url_ping": "empty.dat", "time_dl": "10", "time_ul": "15", "count_ping": "30"}') // start with custom parameters. paths are relative to js file. you can omit parameters that you don't want to change
</script>
</body>
</body>
</html>

View file

@ -1,90 +1,107 @@
<!DOCTYPE html>
<html>
<head>
<head>
<title>Speedtest</title>
<style type="text/css">
html,body{
margin:0;
padding:0;
border:none;
text-align:center;
html,
body {
margin: 0;
padding: 0;
border: none;
text-align: center;
}
div.test{
display:inline-block;
margin:1em;
font-size:2vw;
min-width:20vw;
text-align:center;
div.test {
display: inline-block;
margin: 1em;
font-size: 2vw;
min-width: 20vw;
text-align: center;
}
div.testName,div.meterUnit{
font-size:1em;
div.testName,
div.meterUnit {
font-size: 1em;
}
div.meter{
font-size:1.5em;
line-height:1.5em;
height:2em !important;
div.meter {
font-size: 1.5em;
line-height: 1.5em;
height: 2em !important;
}
.flash{
animation:flash 0.6s linear infinite;
.flash {
animation: flash 0.6s linear infinite;
}
@keyframes flash{
0%{opacity:0.6;}
50%{opacity:1;}
@keyframes flash {
0% { opacity: 0.6; }
50% { opacity: 1; }
}
a{
display:inline-block;
border:0.15em solid #000000;
padding:0.3em 0.5em;
margin:0.6em;
color:#000000;
text-decoration:none;
a {
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;
#ip {
margin: 0.8em 0;
font-size: 1.2em;
}
@media all and (max-width: 50em){
div.test{
font-size:2em;
@media all and (max-width: 50em) {
div.test {
font-size: 2em;
}
}
</style>
<script type="text/javascript">
var w=null;
function runTest(){
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");}.bind(this),100);
w.onmessage=function(event){
var data=event.data.split(";");
var status=Number(data[0]);
var dl=document.getElementById("download"),ul=document.getElementById("upload"),ping=document.getElementById("ping"),ip=document.getElementById("ip"),jitter=document.getElementById("jitter");
dl.className=status==1?"flash":"";ping.className=status==2?"flash":"";jitter.className=ul.className=status==3?"flash":"";
if(status>=4){
clearInterval(interval);
document.getElementById("abortBtn").style.display="none";
document.getElementById("startBtn").style.display="";
w=null;
var w = null
function runTest() {
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 = event.data.split(';')
var status = Number(data[0])
var dl = document.getElementById('download')
var ul = document.getElementById('upload')
var ping = document.getElementById('ping')
var ip = document.getElementById('ip')
var jitter = document.getElementById('jitter')
dl.className = status === 1 ? 'flash' : ''
ping.className = status === 2 ? 'flash' : ''
jitter.className = ul.className = status === 3 ? 'flash' : ''
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 === 5) {
document.getElementById('testArea').style.display = 'none'
}
dl.innerHTML=data[1];
ul.innerHTML=data[2];
ping.innerHTML=data[3];
jitter.innerHTML=data[5];
ip.innerHTML=data[4];
}.bind(this);
w.postMessage("start");
dl.textContent = data[1]
ul.textContent = data[2]
ping.textContent = data[3]
jitter.textContent = data[5]
ip.textContent = data[4]
}
function abortTest(){
if(w)w.postMessage("abort");
w.postMessage('start')
}
function abortTest() {
if (w) w.postMessage('abort')
}
</script>
</head>
<body>
</head>
<body>
<h1>Speedtest</h1>
<div id="testArea" style="display:none">
<div id="ip">Please wait...</div>
@ -112,5 +129,5 @@
<a href="javascript:abortTest()" id="abortBtn">Abort</a>
</div>
<a href="javascript:runTest()" id="startBtn">Run speedtest</a>
</body>
</body>
</html>

View file

@ -1,39 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<head>
<title>Speedtest</title>
<style type="text/css">
html,body{
margin:0;
padding:0;
border:none;
text-align:center;
html,
body {
margin: 0;
padding: 0;
border: none;
text-align: center;
font-family: 'Open Sans';
}
h1,h2,h3,h4,h5,h6
{
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: 'Roboto', sans-serif;
font-weight: 700;
}
div.meter{
display:inline-block;
height:300px;
width:400px;
div.meter {
display: inline-block;
height: 300px;
width: 400px;
text-align: center;
font-size:6vw;
font-size: 6vw;
}
div#testArea
{
div#testArea {
display: flex;
justify-content: center;
flex-flow: row wrap;
}
a {
text-decoration: none;
}
@ -53,132 +55,131 @@
font-family: 'Roboto';
}
#ip{
margin:1em 0;
font-size:1.2em;
#ip {
margin: 1em 0;
font-size: 1.2em;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.4/raphael-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/justgage/1.2.2/justgage.min.js"></script>
<script type="text/javascript">
var w=null;
var ggdl,ggul,ggping;
function runTest(){
w=new Worker("speedtest_worker.min.js");
var interval=setInterval(function(){w.postMessage("status");}.bind(this),100);
document.getElementById("abortBtn").style.display="";
document.getElementById("startBtn").style.display="none";
w.onmessage=function(event){
var data=event.data.split(";");
var status=Number(data[0]);
if(status>=4){
clearInterval(interval);
document.getElementById("abortBtn").style.display="none";
document.getElementById("startBtn").style.display="";
w=null;
var w = null
var ggdl, ggul, ggping
function runTest() {
w = new Worker('speedtest_worker.min.js')
var interval = setInterval(function () { w.postMessage('status') }, 100)
document.getElementById('abortBtn').style.display = ''
document.getElementById('startBtn').style.display = 'none'
w.onmessage = function (event) {
var data = event.data.split(';')
var status = Number(data[0])
if (status >= 4) {
clearInterval(interval)
document.getElementById('abortBtn').style.display = 'none'
document.getElementById('startBtn').style.display = ''
w = null
}
updateGauge(ggdl, data[1]);
updateGauge(ggul, data[2]);
updateGauge(ggping, data[3]);
document.getElementById("ip").innerHTML="Your IP: "+data[4];
updateGauge(ggjitter, data[5]);
}.bind(this);
w.postMessage('start {"time_ul":"10", "time_dl":"10", "count_ping":"50", "url_dl":"garbage.php","url_ul":"empty.dat","url_ping":"empty.dat","url_getIp":"getIP.php"}');
updateGauge(ggdl, data[1])
updateGauge(ggul, data[2])
updateGauge(ggping, data[3])
document.getElementById('ip').textContent = 'Your IP: ' + data[4]
updateGauge(ggjitter, data[5])
}
function abortTest(){
if(w)w.postMessage("abort");
w.postMessage('start {"time_ul": "10", "time_dl": "10", "count_ping": "50", "url_dl": "garbage.php", "url_ul": "empty.dat", "url_ping": "empty.dat", "url_getIp": "getIP.php"}')
}
function abortTest() {
if (w) w.postMessage('abort')
}
document.addEventListener("DOMContentLoaded", function(event) {
document.addEventListener('DOMContentLoaded', function (event) {
ggdl = new JustGage({
id: 'ggdl',
title: "Download",
label: "Mbit/s",
titleFontFamily : "Open Sans",
valueFontFamily : "Open Sans",
title: 'Download',
label: 'Mbit/s',
titleFontFamily: 'Open Sans',
valueFontFamily: 'Open Sans',
refreshAnimationTime: 300,
value: 0,
min: 0,
max: 10,
decimals : 2,
decimals: 2,
formatNumber: true,
humanFriendly : false,
humanFriendly: false,
levelColors: [
"#999999",
"#339933"
'#999999',
'#339933'
]
});
})
ggul = new JustGage({
id: 'ggul',
title: "Upload",
label: "Mbit/s",
titleFontFamily : "Open Sans",
valueFontFamily : "Open Sans",
title: 'Upload',
label: 'Mbit/s',
titleFontFamily: 'Open Sans',
valueFontFamily: 'Open Sans',
refreshAnimationTime: 300,
value: 0,
min: 0,
max: 10,
decimals : 2,
decimals: 2,
formatNumber: true,
humanFriendly : false,
humanFriendly: false,
levelColors: [
"#999999",
"#333399"
'#999999',
'#333399'
]
});
})
ggping = new JustGage({
id: 'ggping',
title: "Ping",
label: "ms",
titleFontFamily : "Open Sans",
valueFontFamily : "Open Sans",
title: 'Ping',
label: 'ms',
titleFontFamily: 'Open Sans',
valueFontFamily: 'Open Sans',
refreshAnimationTime: 300,
value: 0,
min: 0,
max: 100,
decimals : 2,
decimals: 2,
formatNumber: true,
humanFriendly : false,
humanFriendly: false,
levelColors: [
"#999999",
"#993333"
'#999999',
'#993333'
]
});
})
ggjitter = new JustGage({
id: 'ggjitter',
title: "Jitter",
label: "ms",
titleFontFamily : "Open Sans",
valueFontFamily : "Open Sans",
title: 'Jitter',
label: 'ms',
titleFontFamily: 'Open Sans',
valueFontFamily: 'Open Sans',
refreshAnimationTime: 300,
value: 0,
min: 0,
max: 100,
decimals : 2,
decimals: 2,
formatNumber: true,
humanFriendly : false,
humanFriendly: false,
levelColors: [
"#999999",
"#993333"
'#999999',
'#993333'
]
});
});
})
})
function updateGauge(gauge, value)
{
function updateGauge(gauge, value) {
// Alway use next power of 2 as maximum
var max = Math.max(Math.pow(2, Math.ceil(Math.log2(value))), gauge.config.max);
var max = Math.max(Math.pow(2, Math.ceil(Math.log2(value))), gauge.config.max)
// Refresh the gauge
gauge.refresh(value, max);
gauge.refresh(value, max)
}
</script>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700|Roboto:400,500,700" rel="stylesheet">
</head>
<body>
</head>
<body>
<h1>Speed Test</h1>
<div id="testArea">
<div class="meter" id="ggdl"></div>
@ -191,6 +192,6 @@
<a href="javascript:runTest()" id="startBtn" class="button">Start</a>
<a href="javascript:abortTest()" id="abortBtn" class="button" style="display:none;">Abort</a>
</div>
<p>Fonts: <a href="https://fonts.google.com/">Google Gonts</a> | Gauges: <a href="http://justgage.com/">justgage.com</a></p>
</body>
<p>Fonts: <a href="https://fonts.google.com/">Google Fonts</a> | Gauges: <a href="http://justgage.com/">justgage.com</a></p>
</body>
</html>

View file

@ -1,250 +1,256 @@
<!DOCTYPE html>
<html>
<head>
<head>
<title>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;
html,
body {
margin: 0;
padding: 0;
border: none;
text-align: center;
}
#chart1Area, #chart2Area{
width:100%;
max-width:30em;
height:10em;
display:block;
margin:0 auto;
#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="example5_data/Chart.bundle.min.js"></script>
<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'),
chart2ctx=document.getElementById("chart2Area").getContext('2d');
var dlDataset={
label: "Download",
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)",
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",
pointBorderColor: 'rgba(75,192,192,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(75,192,192,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointHoverBackgroundColor: 'rgba(75,192,192,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [0],
spanGaps: false,
},
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,
},
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,
},
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,
spanGaps: false
}
;
var chart1Options={
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]
datasets: [dlDataset, ulDataset]
},
options:{
options: {
responsive: true,
legend: {
position: 'bottom',
position: 'bottom'
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: false,
display: false
},
ticks:{
ticks: {
beginAtZero: true
}
}],
yAxes: [{
display: true,
scaleLabel: "Speed",
ticks:{
scaleLabel: 'Speed',
ticks: {
beginAtZero: true
}
}]
}
}
},
chart2Options={
}
var chart2Options = {
type: 'line',
data: {
datasets: [pingDataset,jitterDataset]
datasets: [pingDataset, jitterDataset]
},
options:{
options: {
responsive: true,
legend: {
position: 'bottom',
position: 'bottom'
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: false,
display: false
},
ticks:{
ticks: {
beginAtZero: true
}
}],
yAxes: [{
display: true,
scaleLabel: "Latency",
ticks:{
scaleLabel: 'Latency',
ticks: {
beginAtZero: true
}
}]
}
}
};
}
var chart1 = new Chart(chart1ctx, chart1Options),
chart2 = new Chart(chart2ctx, chart2Options);
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");}.bind(this),100);
w.onmessage=function(event){
var data=event.data.split(";");
var status=Number(data[0]);
if(status>=4){
clearInterval(interval);
document.getElementById("abortBtn").style.display="none";
document.getElementById("startBtn").style.display="";
w=null;
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 = event.data.split(';')
var status = Number(data[0])
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 === 5) {
document.getElementById('testArea').style.display = 'none'
}
if(status==1&&Number(data[1])>0){
chart1.data.datasets[0].data.push(Number(data[1]));
chart1.data.labels[chart1.data.datasets[0].data.length-1]="";
chart1.update();
if (status === 1 && Number(data[1]) > 0) {
chart1.data.datasets[0].data.push(Number(data[1]))
chart1.data.labels[chart1.data.datasets[0].data.length - 1] = ''
chart1.update()
}
if(status==3&&Number(data[2])>0){
chart1.data.datasets[1].data.push(Number(data[2]));
chart1.data.labels[chart1.data.datasets[1].data.length-1]="";
chart1.update();
if (status === 3 && Number(data[2]) > 0) {
chart1.data.datasets[1].data.push(Number(data[2]))
chart1.data.labels[chart1.data.datasets[1].data.length - 1] = ''
chart1.update()
}
if(status==2&&Number(data[3])>0){
chart2.data.datasets[0].data.push(Number(data[3]));
chart2.data.datasets[1].data.push(Number(data[5]));
chart2.data.labels[chart2.data.datasets[0].data.length-1]="";
chart2.data.labels[chart2.data.datasets[1].data.length-1]="";
chart2.update();
if (status === 2 && Number(data[3]) > 0) {
chart2.data.datasets[0].data.push(Number(data[3]))
chart2.data.datasets[1].data.push(Number(data[5]))
chart2.data.labels[chart2.data.datasets[0].data.length - 1] = ''
chart2.data.labels[chart2.data.datasets[1].data.length - 1] = ''
chart2.update()
}
ip.innerHTML=data[4];
}.bind(this);
w.postMessage("start");
ip.textContent = data[4]
}
function abortTest(){
if(w)w.postMessage("abort");
w.postMessage('start')
}
function abortTest() {
if (w) w.postMessage('abort')
}
</script>
</head>
<body>
</head>
<body>
<h1>Speedtest</h1>
<div id="testArea" style="display:none">
<div id="ip">Please wait...</div>
<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>
</body>
<br/><br/> Charts by <a href="http://www.chartjs.org/">Chart.js</a>
</body>
</html>

File diff suppressed because one or more lines are too long

104
example6.html Normal file
View file

@ -0,0 +1,104 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="referrer" content="no-referrer" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>Speedtest</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet" />
<style type="text/css">
.st-block {
text-align: center;
}
.st-btn {
margin-top: -0.5rem;
margin-left: 1.5rem;
}
.st-value>span:empty::before {
content: "0.00";
color: #636c72;
}
#st-ip:empty::before {
content: "___.___.___.___";
color: #636c72;
}
</style>
</head>
<body class="my-4">
<div class="container">
<div class="row">
<div class="col-sm-12 mb-3">
<p class="h1">
Speedtest
<button id="st-start" class="btn btn-outline-primary st-btn" onclick="startTest()">Start</button>
<button id="st-stop" class="btn btn-danger st-btn" onclick="stopTest()" hidden="true">Stop</button>
</p>
<p class="lead">
Your IP: <span id="st-ip"></span>
</p>
</div>
<div class="col-lg-3 col-md-6 mb-3 st-block">
<h3>Download</h3>
<p class="display-4 st-value"><span id="st-download"></span></p>
<p class="lead">Mbit/s</p>
</div>
<div class="col-lg-3 col-md-6 mb-3 st-block">
<h3>Upload</h3>
<p class="display-4 st-value"><span id="st-upload"></span></p>
<p class="lead">Mbit/s</p>
</div>
<div class="col-lg-3 col-md-6 mb-3 st-block">
<h3>Ping</h3>
<p class="display-4 st-value"><span id="st-ping"></span></p>
<p class="lead">ms</p>
</div>
<div class="col-lg-3 col-md-6 mb-3 st-block">
<h3>Jitter</h3>
<p class="display-4 st-value"><span id="st-jitter"></span></p>
<p class="lead">ms</p>
</div>
</div>
</div>
<script type="text/javascript">
var worker = null
function startTest() {
document.getElementById('st-start').hidden = true
document.getElementById('st-stop').hidden = false
worker = new Worker('speedtest_worker.min.js')
var interval = setInterval(function () { worker.postMessage('status') }, 100)
worker.onmessage = function (event) {
var download = document.getElementById('st-download')
var upload = document.getElementById('st-upload')
var ping = document.getElementById('st-ping')
var jitter = document.getElementById('st-jitter')
var ip = document.getElementById('st-ip')
var data = event.data.split(';')
var status = Number(data[0])
if (status >= 4) {
clearInterval(interval)
document.getElementById('st-start').hidden = false
document.getElementById('st-stop').hidden = true
w = null
}
if (status === 5) {
// speedtest cancelled, clear output data
data = []
}
download.textContent = data[1]
upload.textContent = data[2]
ping.textContent = data[3]
ip.textContent = data[4]
jitter.textContent = data[5]
}
worker.postMessage('start')
}
function stopTest() {
if (worker) worker.postMessage('abort')
}
</script>
</body>
</html>

View file

@ -5,34 +5,34 @@
GNU LGPLv3 License
*/
//data reported to main thread
var testStatus=0, //0=not started, 1=download test, 2=ping+jitter test, 3=upload test, 4=finished, 5=abort/error
dlStatus="", //download speed in megabit/s with 2 decimal digits
ulStatus="", //upload speed in megabit/s with 2 decimal digits
pingStatus="", //ping in milliseconds with 2 decimal digits
jitterStatus="", //jitter in milliseconds with 2 decimal digits
clientIp=""; //client's IP address as reported by getIP.php
// data reported to main thread
var testStatus = 0 // 0=not started, 1=download test, 2=ping+jitter test, 3=upload test, 4=finished, 5=abort/error
var dlStatus = '' // download speed in megabit/s with 2 decimal digits
var ulStatus = '' // upload speed in megabit/s with 2 decimal digits
var pingStatus = '' // ping in milliseconds with 2 decimal digits
var jitterStatus = '' // jitter in milliseconds with 2 decimal digits
var clientIp = '' // client's IP address as reported by getIP.php
//test settings. can be overridden by sending specific values with the start command
var settings={
time_ul:15, //duration of upload test in seconds
time_dl:15, //duration of download test in seconds
count_ping:35, //number of pings to perform in ping test
url_dl:"garbage.php", //path to a large file or garbage.php, used for download test. must be relative to this js file
url_ul:"empty.dat", //path to an empty file, used for upload test. must be relative to this js file
url_ping:"empty.dat", //path to an empty file, used for ping test. must be relative to this js file
url_getIp:"getIP.php", //path to getIP.php relative to this js file, or a similar thing that outputs the client's ip
xhr_dlMultistream:10, //number of download streams to use (can be different if enable_quirks is active)
xhr_ulMultistream:3, //number of upload streams to use (can be different if enable_quirks is active)
xhr_dlUseBlob:false, //if set to true, it reduces ram usage but uses the hard drive (useful with large garbagePhp_chunkSize and/or high xhr_dlMultistream)
garbagePhp_chunkSize:20, //size of chunks sent by garbage.php (can be different if enable_quirks is active)
enable_quirks:true, //enable quirks for specific browsers. currently it overrides settings to optimize for specific browsers, unless they are already being overridden with the start command
allow_fetchAPI:false, //enables Fetch API. currently disabled because it leaks memory like no tomorrow
force_fetchAPI:false //when Fetch API is enabled, it will force usage on every browser that supports it
};
// test settings. can be overridden by sending specific values with the start command
var settings = {
time_ul: 15, // duration of upload test in seconds
time_dl: 15, // duration of download test in seconds
count_ping: 35, // number of pings to perform in ping test
url_dl: 'garbage.php', // path to a large file or garbage.php, used for download test. must be relative to this js file
url_ul: 'empty.dat', // path to an empty file, used for upload test. must be relative to this js file
url_ping: 'empty.dat', // path to an empty file, used for ping test. must be relative to this js file
url_getIp: 'getIP.php', // path to getIP.php relative to this js file, or a similar thing that outputs the client's ip
xhr_dlMultistream: 10, // number of download streams to use (can be different if enable_quirks is active)
xhr_ulMultistream: 3, // number of upload streams to use (can be different if enable_quirks is active)
xhr_dlUseBlob: false, // if set to true, it reduces ram usage but uses the hard drive (useful with large garbagePhp_chunkSize and/or high xhr_dlMultistream)
garbagePhp_chunkSize: 20, // size of chunks sent by garbage.php (can be different if enable_quirks is active)
enable_quirks: true, // enable quirks for specific browsers. currently it overrides settings to optimize for specific browsers, unless they are already being overridden with the start command
allow_fetchAPI: false, // enables Fetch API. currently disabled because it leaks memory like no tomorrow
force_fetchAPI: false // when Fetch API is enabled, it will force usage on every browser that supports it
}
var xhr=null, //array of currently active xhr requests
interval=null; //timer used in tests
var xhr = null // array of currently active xhr requests
var interval = null // timer used in tests
/*
when set to true (automatically) the download test will use the fetch api instead of xhr.
@ -40,7 +40,7 @@ var xhr=null, //array of currently active xhr requests
-allow_fetchAPI is true AND
-(we're on chrome that supports fetch api AND enable_quirks is true) OR (we're on any browser that supports fetch api AND force_fetchAPI is true)
*/
var useFetchAPI=false;
var useFetchAPI = false
/*
listener for commands from main thread to this worker.
@ -50,300 +50,301 @@ var useFetchAPI=false;
-start: starts the test. optionally, settings can be passed as JSON.
example: start {"time_ul":"10", "time_dl":"10", "count_ping":"50"}
*/
this.addEventListener('message', function(e){
var params=e.data.split(" ");
if(params[0]=="status"){ //return status
postMessage(testStatus+";"+dlStatus+";"+ulStatus+";"+pingStatus+";"+clientIp+";"+jitterStatus);
this.addEventListener('message', function (e) {
var params = e.data.split(' ')
if (params[0] === 'status') { // return status
postMessage(testStatus + ';' + dlStatus + ';' + ulStatus + ';' + pingStatus + ';' + clientIp + ';' + jitterStatus)
}
if(params[0]=="start"&&testStatus==0){ //start new test
testStatus=1;
try{
//parse settings, if present
var s=JSON.parse(e.data.substring(5));
if(typeof s.url_dl != "undefined") settings.url_dl=s.url_dl; //download url
if(typeof s.url_ul != "undefined") settings.url_ul=s.url_ul; //upload url
if(typeof s.url_ping != "undefined") settings.url_ping=s.url_ping; //ping url
if(typeof s.url_getIp != "undefined") settings.url_getIp=s.url_getIp; //url to getIP.php
if(typeof s.time_dl != "undefined") settings.time_dl=s.time_dl; //duration of download test
if(typeof s.time_ul != "undefined") settings.time_ul=s.time_ul; //duration of upload test
if(typeof s.enable_quirks != "undefined") settings.enable_quirks=s.enable_quirks; //enable quirks or not
if(typeof s.allow_fetchAPI != "undefined") settings.allow_fetchAPI=s.allow_fetchAPI; //allows fetch api to be used if supported
//quirks for specific browsers. more may be added in future releases
if(settings.enable_quirks){
var ua=navigator.userAgent;
if(/Firefox.(\d+\.\d+)/i.test(ua)){
//ff more precise with 1 upload stream
settings.xhr_ulMultistream=1;
if (params[0] === 'start' && testStatus === 0) { // start new test
testStatus = 1
try {
// parse settings, if present
var s = JSON.parse(e.data.substring(5))
if (typeof s.url_dl !== 'undefined') settings.url_dl = s.url_dl // download url
if (typeof s.url_ul !== 'undefined') settings.url_ul = s.url_ul // upload url
if (typeof s.url_ping !== 'undefined') settings.url_ping = s.url_ping // ping url
if (typeof s.url_getIp !== 'undefined') settings.url_getIp = s.url_getIp // url to getIP.php
if (typeof s.time_dl !== 'undefined') settings.time_dl = s.time_dl // duration of download test
if (typeof s.time_ul !== 'undefined') settings.time_ul = s.time_ul // duration of upload test
if (typeof s.enable_quirks !== 'undefined') settings.enable_quirks = s.enable_quirks // enable quirks or not
if (typeof s.allow_fetchAPI !== 'undefined') settings.allow_fetchAPI = s.allow_fetchAPI // allows fetch api to be used if supported
// quirks for specific browsers. more may be added in future releases
if (settings.enable_quirks) {
var ua = navigator.userAgent
if (/Firefox.(\d+\.\d+)/i.test(ua)) {
// ff more precise with 1 upload stream
settings.xhr_ulMultistream = 1
}
if(/Edge.(\d+\.\d+)/i.test(ua)){
//edge more precise with 3 download streams
settings.xhr_dlMultistream=3;
if (/Edge.(\d+\.\d+)/i.test(ua)) {
// edge more precise with 3 download streams
settings.xhr_dlMultistream = 3
}
if((/Safari.(\d+)/i.test(ua))&&!(/Chrome.(\d+)/i.test(ua))){
//safari more precise with 10 upload streams and 5mb chunks for download test
settings.xhr_ulMultistream=10;
settings.garbagePhp_chunkSize=5;
if ((/Safari.(\d+)/i.test(ua)) && !(/Chrome.(\d+)/i.test(ua))) {
// safari more precise with 10 upload streams and 5mb chunks for download test
settings.xhr_ulMultistream = 10
settings.garbagePhp_chunkSize = 5
}
if(/Chrome.(\d+)/i.test(ua)&&(!!self.fetch)){
//chrome can't handle large xhr very well, use fetch api if available and allowed
if(settings.allow_fetchAPI) useFetchAPI=true;
//chrome more precise with 5 streams
settings.xhr_dlMultistream=5;
if (/Chrome.(\d+)/i.test(ua) && (!!self.fetch)) {
// chrome can't handle large xhr very well, use fetch api if available and allowed
if (settings.allow_fetchAPI) useFetchAPI = true
// chrome more precise with 5 streams
settings.xhr_dlMultistream = 5
}
}
if(typeof s.count_ping != "undefined") settings.count_ping=s.count_ping; //number of pings for ping test
if(typeof s.xhr_dlMultistream != "undefined") settings.xhr_dlMultistream=s.xhr_dlMultistream; //number of download streams
if(typeof s.xhr_ulMultistream != "undefined") settings.xhr_ulMultistream=s.xhr_ulMultistream; //number of upload streams
if(typeof s.xhr_dlUseBlob != "undefined") settings.xhr_dlUseBlob=s.xhr_dlUseBlob; //use blob for download test
if(typeof s.garbagePhp_chunkSize != "undefined") settings.garbagePhp_chunkSize=s.garbagePhp_chunkSize; //size of garbage.php chunks
if(typeof s.force_fetchAPI != "undefined") settings.force_fetchAPI=s.force_fetchAPI; //use fetch api on all browsers that support it if enabled
if(settings.allow_fetchAPI&&settings.force_fetchAPI&&(!!self.fetch)) useFetchAPI=true;
}catch(e){}
//run the tests
console.log(settings);
console.log("Fetch API: "+useFetchAPI);
getIp(function(){dlTest(function(){testStatus=2;pingTest(function(){testStatus=3;ulTest(function(){testStatus=4;});});})});
if (typeof s.count_ping !== 'undefined') settings.count_ping = s.count_ping // number of pings for ping test
if (typeof s.xhr_dlMultistream !== 'undefined') settings.xhr_dlMultistream = s.xhr_dlMultistream // number of download streams
if (typeof s.xhr_ulMultistream !== 'undefined') settings.xhr_ulMultistream = s.xhr_ulMultistream // number of upload streams
if (typeof s.xhr_dlUseBlob !== 'undefined') settings.xhr_dlUseBlob = s.xhr_dlUseBlob // use blob for download test
if (typeof s.garbagePhp_chunkSize !== 'undefined') settings.garbagePhp_chunkSize = s.garbagePhp_chunkSize // size of garbage.php chunks
if (typeof s.force_fetchAPI !== 'undefined') settings.force_fetchAPI = s.force_fetchAPI // use fetch api on all browsers that support it if enabled
if (settings.allow_fetchAPI && settings.force_fetchAPI && (!!self.fetch)) useFetchAPI = true
} catch (e) { }
// run the tests
console.log(settings)
console.log('Fetch API: ' + useFetchAPI)
getIp(function () { dlTest(function () { testStatus = 2; pingTest(function () { testStatus = 3; ulTest(function () { testStatus = 4 }) }) }) })
}
if(params[0]=="abort"){ //abort command
clearRequests(); //stop all xhr activity
if(interval)clearInterval(interval); //clear timer if present
testStatus=5;dlStatus="";ulStatus="";pingStatus="";jitterStatus=""; //set test as aborted
if (params[0] === 'abort') { // abort command
clearRequests() // stop all xhr activity
if (interval) clearInterval(interval) // clear timer if present
testStatus = 5; dlStatus = ''; ulStatus = ''; pingStatus = ''; jitterStatus = '' // set test as aborted
}
});
//stops all XHR activity, aggressively
function clearRequests(){
if(xhr){
for(var i=0;i<xhr.length;i++){
if(useFetchAPI)try{xhr[i].cancelRequested=true;}catch(e){}
try{xhr[i].onprogress=null; xhr[i].onload=null; xhr[i].onerror=null;}catch(e){}
try{xhr[i].upload.onprogress=null; xhr[i].upload.onload=null; xhr[i].upload.onerror=null;}catch(e){}
try{xhr[i].abort();}catch(e){}
try{delete(xhr[i]);}catch(e){}
})
// stops all XHR activity, aggressively
function clearRequests () {
if (xhr) {
for (var i = 0; i < xhr.length; i++) {
if (useFetchAPI) try { xhr[i].cancelRequested = true } catch (e) { }
try { xhr[i].onprogress = null; xhr[i].onload = null; xhr[i].onerror = null } catch (e) { }
try { xhr[i].upload.onprogress = null; xhr[i].upload.onload = null; xhr[i].upload.onerror = null } catch (e) { }
try { xhr[i].abort() } catch (e) { }
try { delete (xhr[i]) } catch (e) { }
}
xhr=null;
xhr = null
}
}
//gets client's IP using url_getIp, then calls the done function
function getIp(done){
xhr=new XMLHttpRequest();
xhr.onload=function(){
clientIp=xhr.responseText;
done();
// gets client's IP using url_getIp, then calls the done function
function getIp (done) {
xhr = new XMLHttpRequest()
xhr.onload = function () {
clientIp = xhr.responseText
done()
}
xhr.onerror=function(){
done();
xhr.onerror = function () {
done()
}
xhr.open("GET",settings.url_getIp+"?r="+Math.random(),true);
xhr.send();
xhr.open('GET', settings.url_getIp + '?r=' + Math.random(), true)
xhr.send()
}
//download test, calls done function when it's over
var dlCalled=false; //used to prevent multiple accidental calls to dlTest
function dlTest(done){
if(dlCalled) return; else dlCalled=true; //dlTest already called?
var totLoaded=0.0, //total number of loaded bytes
startT=new Date().getTime(), //timestamp when test was started
failed=false; //set to true if a stream fails
xhr=[];
//function to create a download stream. streams are slightly delayed so that they will not end at the same time
var testStream=function(i,delay){
setTimeout(function(){
if(testStatus!=1) return; //delayed stream ended up starting after the end of the download test
if(useFetchAPI){
xhr[i]=fetch(settings.url_dl+"?r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize).then(function(response) {
var reader = response.body.getReader();
var consume=function() {
return reader.read().then(function(result){
if(result.done) testStream(i); else{
totLoaded+=result.value.length;
if(xhr[i].cancelRequested) reader.cancel();
// download test, calls done function when it's over
var dlCalled = false // used to prevent multiple accidental calls to dlTest
function dlTest (done) {
if (dlCalled) return; else dlCalled = true // dlTest already called?
var totLoaded = 0.0, // total number of loaded bytes
startT = new Date().getTime(), // timestamp when test was started
failed = false // set to true if a stream fails
xhr = []
// function to create a download stream. streams are slightly delayed so that they will not end at the same time
var testStream = function (i, delay) {
setTimeout(function () {
if (testStatus !== 1) return // delayed stream ended up starting after the end of the download test
if (useFetchAPI) {
xhr[i] = fetch(settings.url_dl + '?r=' + Math.random() + '&ckSize=' + settings.garbagePhp_chunkSize).then(function (response) {
var reader = response.body.getReader()
var consume = function () {
return reader.read().then(function (result) {
if (result.done) testStream(i); else {
totLoaded += result.value.length
if (xhr[i].cancelRequested) reader.cancel()
}
return consume();
}.bind(this));
}.bind(this);
return consume();
}.bind(this));
}else{
var prevLoaded=0; //number of bytes loaded last time onprogress was called
var x=new XMLHttpRequest();
xhr[i]=x;
xhr[i].onprogress=function(event){
if(testStatus!=1){try{x.abort();}catch(e){}} //just in case this XHR is still running after the download 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].onload=function(){
//the large file has been loaded entirely, start again
try{xhr[i].abort();}catch(e){} //reset the stream data to empty ram
testStream(i,0);
}.bind(this);
xhr[i].onerror=function(){
//error, abort
failed=true;
try{xhr[i].abort();}catch(e){}
delete(xhr[i]);
}.bind(this);
//send xhr
try{if(settings.xhr_dlUseBlob) xhr[i].responseType='blob'; else xhr[i].responseType='arraybuffer';}catch(e){}
xhr[i].open("GET",settings.url_dl+"?r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize,true); //random string to prevent caching
xhr[i].send();
return consume()
}.bind(this))
}.bind(this)
return consume()
}.bind(this))
} else {
var prevLoaded = 0 // number of bytes loaded last time onprogress was called
var x = new XMLHttpRequest()
xhr[i] = x
xhr[i].onprogress = function (event) {
if (testStatus !== 1) { try { x.abort() } catch (e) { } } // just in case this XHR is still running after the download 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].onload = function () {
// the large file has been loaded entirely, start again
try { xhr[i].abort() } catch (e) { } // reset the stream data to empty ram
testStream(i, 0)
}.bind(this)
xhr[i].onerror = function () {
// error, abort
failed = true
try { xhr[i].abort() } catch (e) { }
delete (xhr[i])
}.bind(this)
// send xhr
try { if (settings.xhr_dlUseBlob) xhr[i].responseType = 'blob'; else xhr[i].responseType = 'arraybuffer' } catch (e) { }
xhr[i].open('GET', settings.url_dl + '?r=' + Math.random() + '&ckSize=' + settings.garbagePhp_chunkSize, true) // random string to prevent caching
xhr[i].send()
}
}.bind(this),1+delay);
}.bind(this);
//open streams
for(var i=0;i<settings.xhr_dlMultistream;i++){
testStream(i,100*i);
}.bind(this), 1 + delay)
}.bind(this)
// open streams
for (var i = 0; i < settings.xhr_dlMultistream; i++) {
testStream(i, 100 * i)
}
//every 200ms, update dlStatus
interval=setInterval(function(){
var t=new Date().getTime()-startT;
if(t<200) return;
var speed=totLoaded/(t/1000.0);
dlStatus=((speed*8)/925000.0).toFixed(2); //925000 instead of 1048576 to account for overhead
if((t/1000.0)>settings.time_dl||failed){ //test is over, stop streams and timer
if(failed||isNaN(dlStatus)) dlStatus="Fail";
clearRequests();
clearInterval(interval);
done();
// every 200ms, update dlStatus
interval = setInterval(function () {
var t = new Date().getTime() - startT
if (t < 200) return
var speed = totLoaded / (t / 1000.0)
dlStatus = ((speed * 8) / 925000.0).toFixed(2) // 925000 instead of 1048576 to account for overhead
if ((t / 1000.0) > settings.time_dl || failed) { // test is over, stop streams and timer
if (failed || isNaN(dlStatus)) dlStatus = 'Fail'
clearRequests()
clearInterval(interval)
done()
}
}.bind(this),200);
}.bind(this), 200)
}
//upload test, calls done function whent it's over
//garbage data for upload test
var r=new ArrayBuffer(1048576);
try{r=new Float32Array(r);for(var i=0;i<r.length;i++)r[i]=Math.random();}catch(e){}
var req=[],reqsmall=[];
for(var i=0;i<20;i++) req.push(r);
req=new Blob(req);
r=new ArrayBuffer(262144);
try{r=new Float32Array(r);for(var i=0;i<r.length;i++)r[i]=Math.random();}catch(e){}
reqsmall.push(r);
reqsmall=new Blob(reqsmall);
var ulCalled=false; //used to prevent multiple accidental calls to ulTest
function ulTest(done){
if(ulCalled) return; else ulCalled=true; //ulTest already called?
var totLoaded=0.0, //total number of transmitted bytes
startT=new Date().getTime(), //timestamp when test was started
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
var prevLoaded=0; //number of bytes transmitted last time onprogress was called
var x=new XMLHttpRequest();
xhr[i]=x;
var ie11workaround;
try{
xhr[i].upload.onprogress;
ie11workaround=false;
}catch(e){
ie11workaround=true;
// upload test, calls done function whent it's over
// garbage data for upload test
var r = new ArrayBuffer(1048576)
try { r = new Float32Array(r); for (var i = 0; i < r.length; i++)r[i] = Math.random() } catch (e) { }
var req = []
var reqsmall = []
for (var i = 0; i < 20; i++) req.push(r)
req = new Blob(req)
r = new ArrayBuffer(262144)
try { r = new Float32Array(r); for (var i = 0; i < r.length; i++)r[i] = Math.random() } catch (e) { }
reqsmall.push(r)
reqsmall = new Blob(reqsmall)
var ulCalled = false // used to prevent multiple accidental calls to ulTest
function ulTest (done) {
if (ulCalled) return; else ulCalled = true // ulTest already called?
var totLoaded = 0.0 // total number of transmitted bytes
var startT = new Date().getTime() // timestamp when test was started
var 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
var prevLoaded = 0 // number of bytes transmitted last time onprogress was called
var x = new XMLHttpRequest()
xhr[i] = x
var ie11workaround
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(){
totLoaded+=262144;
testStream(i,0);
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 () {
totLoaded += 262144
testStream(i, 0)
}
xhr[i].onerror=function(){
//error, abort
failed=true;
try{xhr[i].abort();}catch(e){}
delete(xhr[i]);
xhr[i].onerror = function () {
// error, abort
failed = true
try { xhr[i].abort() } catch (e) { }
delete (xhr[i])
}
xhr[i].open("POST",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){
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
testStream(i,0);
}.bind(this);
xhr[i].upload.onerror=function(){
//error, abort
failed=true;
try{xhr[i].abort();}catch(e){}
delete(xhr[i]);
}.bind(this);
//send xhr
xhr[i].open("POST",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);
xhr[i].open('POST', 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) {
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
testStream(i, 0)
}.bind(this)
xhr[i].upload.onerror = function () {
// error, abort
failed = true
try { xhr[i].abort() } catch (e) { }
delete (xhr[i])
}.bind(this)
// send xhr
xhr[i].open('POST', 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,100*i);
}.bind(this), 1)
}.bind(this)
// open streams
for (var i = 0; i < settings.xhr_ulMultistream; i++) {
testStream(i, 100 * i)
}
//every 200ms, update ulStatus
interval=setInterval(function(){
var t=new Date().getTime()-startT;
if(t<200) return;
var speed=totLoaded/(t/1000.0);
ulStatus=((speed*8)/925000.0).toFixed(2); //925000 instead of 1048576 to account for overhead
if((t/1000.0)>settings.time_ul||failed){ //test is over, stop streams and timer
if(failed||isNaN(ulStatus)) ulStatus="Fail";
clearRequests();
clearInterval(interval);
done();
// every 200ms, update ulStatus
interval = setInterval(function () {
var t = new Date().getTime() - startT
if (t < 200) return
var speed = totLoaded / (t / 1000.0)
ulStatus = ((speed * 8) / 925000.0).toFixed(2) // 925000 instead of 1048576 to account for overhead
if ((t / 1000.0) > settings.time_ul || failed) { // test is over, stop streams and timer
if (failed || isNaN(ulStatus)) ulStatus = 'Fail'
clearRequests()
clearInterval(interval)
done()
}
}.bind(this),200);
}.bind(this), 200)
}
//ping+jitter test, function done is called when it's over
var ptCalled=false; //used to prevent multiple accidental calls to pingTest
function pingTest(done){
if(ptCalled) return; else ptCalled=true; //pingTest already called?
var prevT=null, //last time a pong was received
ping=0.0, //current ping value
jitter=0.0, //current jitter value
i=0, //counter of pongs received
prevInstspd=0; //last ping time, used for jitter calculation
xhr=[];
//ping function
var doPing=function(){
prevT=new Date().getTime();
xhr[0]=new XMLHttpRequest();
xhr[0].onload=function(){
//pong
if(i==0){
prevT=new Date().getTime(); //first pong
}else{
var instspd=(new Date().getTime()-prevT)/2;
var instjitter=Math.abs(instspd-prevInstspd);
if(i==1)ping=instspd; /*first ping, can't tell jiutter yet*/ else{
ping=ping*0.9+instspd*0.1; //ping, weighted average
jitter=instjitter>jitter?(jitter*0.2+instjitter*0.8):(jitter*0.9+instjitter*0.1); //update jitter, weighted average. spikes in ping values are given more weight.
// ping+jitter test, function done is called when it's over
var ptCalled = false // used to prevent multiple accidental calls to pingTest
function pingTest (done) {
if (ptCalled) return; else ptCalled = true // pingTest already called?
var prevT = null // last time a pong was received
var ping = 0.0 // current ping value
var jitter = 0.0 // current jitter value
var i = 0 // counter of pongs received
var prevInstspd = 0 // last ping time, used for jitter calculation
xhr = []
// ping function
var doPing = function () {
prevT = new Date().getTime()
xhr[0] = new XMLHttpRequest()
xhr[0].onload = function () {
// pong
if (i === 0) {
prevT = new Date().getTime() // first pong
} else {
var instspd = (new Date().getTime() - prevT) / 2
var instjitter = Math.abs(instspd - prevInstspd)
if (i === 1) ping = instspd; /* first ping, can't tell jiutter yet*/ else {
ping = ping * 0.9 + instspd * 0.1 // ping, weighted average
jitter = instjitter > jitter ? (jitter * 0.2 + instjitter * 0.8) : (jitter * 0.9 + instjitter * 0.1) // update jitter, weighted average. spikes in ping values are given more weight.
}
prevInstspd=instspd;
prevInstspd = instspd
}
pingStatus=ping.toFixed(2);
jitterStatus=jitter.toFixed(2);
i++;
if(i<settings.count_ping) doPing(); else done(); //more pings to do?
}.bind(this);
xhr[0].onerror=function(){
//a ping failed, cancel test
pingStatus="Fail";
jitterStatus="Fail";
clearRequests();
done();
}.bind(this);
//sent xhr
xhr[0].open("GET",settings.url_ping+"?r="+Math.random(),true); //random string to prevent caching
xhr[0].send();
}.bind(this);
doPing(); //start first ping
pingStatus = ping.toFixed(2)
jitterStatus = jitter.toFixed(2)
i++
if (i < settings.count_ping) doPing(); else done() // more pings to do?
}.bind(this)
xhr[0].onerror = function () {
// a ping failed, cancel test
pingStatus = 'Fail'
jitterStatus = 'Fail'
clearRequests()
done()
}.bind(this)
// sent xhr
xhr[0].open('GET', settings.url_ping + '?r=' + Math.random(), true) // random string to prevent caching
xhr[0].send()
}.bind(this)
doPing() // start first ping
}

File diff suppressed because one or more lines are too long