by Federico Dossena
Version 4.5, November 1, 2017 https://github.com/adolfintel/speedtest/
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:
If this looks good, let's proceed and see how to use the test.
To install the test on your server, upload the following files:
speedtest_worker.min.jsgarbage.phpgetIP.phpempty.phpLater we'll see how to use the test without PHP, and how to configure the telemetry if you want to use it.
Important: keep all the files together; all paths are relative to the js file
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 shownexample-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 usersexample-progressBar.html: A modified version of example-pretty.html with a progress indicatorexample-customSettings.html: A modified version of example-pretty.html showing how the test can be started with custom parametersexample-customSettings2.html: A modified version of example-pretty.html showing how to make a custom test with only download and uploadexample-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 Piexample-chart.html: The old example5.html, showing how to use the test with the Chart.js libraryexample-telemetry.html: A modified version of example-pretty.html with basic telemetry turned on. See the section on Telemetry for detailsThe 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:
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:
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.
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:
var w = new Worker("speedtest_worker.min.js")
Important: use the minified version, it's smaller!
First, we set up a timer that fetches the status of the worker continuously:
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.
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]
var dlProgress = data[6]
var ulProgress = data[7]
var pingProgress = data[8]
if (testState >= 4) {
clearInterval(timer) // test is finished or aborted
}
// .. update your page here ..
}
The response from the worker is composed of values separated by ; (semicolon) in this
format:
testState;dlStatus;ulStatus;pingStatus;clientIp;jitterStatus;dlProgress;ulProgress;pingProgress
-1 = Test not started yet0 = Test starting1 = Download test in progress2 = Ping + Jitter test in progress3 = Upload test in progress4 = Test finished5 = Test abortedNote: 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.
To start the test with the default settings, which is usually the best choice, send the start command to the worker:
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:
w.postMessage('start {"param1": "value1", "param2": "value2", ...}')
or this:
var params{
param1:value1,
param2:value2,
...
}
w.postMessage('start '+JSON.stringify(params))
15>=515>=1035>=20garbage.phpempty.phpempty.phpgetIP.phpI: get IPD: download testU: upload testP: ping + jitter test_: delay 1 secondID_U_PID_P_U on Firefox if enable_quirks is true because Firefox does not stop upload XHRs right away, it takes 2-3 seconds.true20>=1010010>=33>=1300>=100, <=7000: Fail test on error (behaviour of previous versions of this test)1: Restart a stream/ping when it fails2: Ignore all errors111.5>=03>=1truefalse1.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 overhead1514 / 1440: TCP+IPv6+ETH, ignoring HTTP overhead1: ignore overheads. This measures the speed at which you actually download and upload files rather than the raw connection speedThe test can be aborted at any time by sending an abort command to the worker:
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!
__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.
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.
garbage.phpA replacement for garbage.php must generate incompressible garbage data.
A large file (10-100 Mbytes) is a possible replacement. You can get one here.
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.
empty.phpYour 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.
getIP.phpYour replacement must simply respond with the client's IP as plaintext. Nothing fancy.
You need to start the test with your replacements like this:
w.postMessage('start {"url_dl": "newGarbageURL", "url_ul": "newEmptyURL", "url_ping": "newEmptyURL", "url_getIp": "newIpURL"}')
Telemetry currently requires PHP and either MySQL or SQLite.
To set up the telemetry, we need to do 4 things:
telemetry.phptelemetry.php to add your database settingsThis step is only for MySQL. Skip this if you want to use SQLite.
Log into your database using phpMyAdmin or a similar software and import telemetry.sql into an empty database.
If you see a table called speedtest_users, empty, you did it right.
telemetry.phpOpen telemetry.php with notepad or a similar text editor.
Set your preferred database, $db_type="mysql"; or $db_type="sqlite";
If you choose to use MySQL, you must also add your database credentials:
$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.sql
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, User Agent, Preferred language, Test resultsfull: same as above, but also collects a log (10-150 Kb each, not recommended)Example:
w.postMessage('start {"telemetry_level":"basic"}')
Also, see example-telemetry.html
At the moment there is no front-end to see the telemetry data; you can connect to the database and see the collected results in the speedtest_users table.
These are the most common issues reported by users, and how to fix them. If you still need help, contact me at dosse91@paranoici.org.
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).
Check your server's maximum POST size, make sure it's at least 20Mbytes, possibly more
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.
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.
Make sure your server is sending the Connection:keep-alive header
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.
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.
On some Linux systems with hardware acceleration turned off, the page rendering makes the browser lag, reducing the accuracy of the ping/jitter test
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 speedtest_worker.js > speedtest_worker.min.js
Pull requests are much appreciated. If you don't use github (or git), simply contact me at dosse91@paranoici.org.
Important: please add your name to modified versions to distinguish them from the main project.
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 dosse91@paranoici.org for other licensing models.