by Federico Dossena
Version 5.0 https://github.com/adolfintel/speedtest/
HTML5 Speedtest is a Free and Open Source speedtest that you can host on your server(s), and users can run in their browser.
Features:
Browser support: The test supports any browser that supports XHR Level 2 and Web Workers. JavaScript must be enabled.
The following browsers are officially supported:
Client side, the test can use up to 500MB of RAM on very fast connections.
These guides cover a simple single server installation of the Speedtest.
More guides will be added later
Server side, you'll need:
Let's install the speedtest.
Put all files on your web server via FTP or by copying them directly. You can install it in the root, or in a subdirectory.
Important: The speedtest needs write permissions in the installation folder!
The speedtest uses ipinfo.io to detect ISP and distance from server. This is completely optional and can be disabled if you want (see Speedtest settings), but it is enabled by default, and if you expect more than ~500 tests per day, you will need to sign up to ipinfo.io and edit backend/getIP_ipInfo_apikey.php to set your access token.
IpInfo.io has kindly offered free access to their APIs for users of this project; if you're interested, contact me at [email protected] and provide a description of what you intend to do with the project, and you'll get the API key.
The test supports storing test results and can generate shareable images that users can embed in forum signatures and such.
To use this function, you will need a database. The test supports MySQL, PostgreSQL and SQLite as backends.
This step is only required for MySQL and PostgreSQL. If you want to use SQLite, skip to the next step.
Log into your database using phpMyAdmin or a similar software and create a new database. Inside the results folder you will find telemetry_mysql.sql and telemetry_postgresql.sql, which are templates for MySQL and PostgreSQL respectively. Import the one you need, and you will see a speedtest_users table in the database. You can delete the templates afterwards.
Open results/telemetry_settings.php in a text editor. Set $db_type to either mysql,postgresql or sqlite.
If you chose to use SQLite, you might want to change $Sqlite_db_file to another path where you want the database to be stored. Just make sure that the file cannot be downloaded by users. Sqlite doesn't require any additional configuration, you can skip the rest of this section.
If you chose to use MySQL, you must set 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_mysql.sql
If you chose to use PostgreSQL, you must set your database credentials:
$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
This feature generates an image that can be share by the user containing the download, upload, ping, jitter and ISP (if enabled).
By default, the telemetry generates a progressive ID for each test. Even if no sensitive information is leaked, you might not want users to be able to guess other test IDs. To avoid this, you can turn on ID obfuscation, which turns IDs into a reversible hash, much like YouTube video IDs.
To enable ID obfuscation, edit results/telemetry_settings.php and set $enable_id_obfuscation to true. From now on, all test IDs will be obfuscated using a unique salt. The IDs in the database are still progressive, but users will only know their obfuscated versions and won't be able to easily guess other IDs.
Important: ID obfuscation currently only works on 64-bit PHP!
A basic front-end for visualizing and searching tests by ID is available in results/stats.php.
A login is required to access the interface. Important: change the default password in results/telemetry_settings.php.
Now that the test is installed, rename one of the examples to index.html and delete the other examples.
The best starting point for most people is example-singleServer-pretty.html. If you want to use telemetry and results sharing, use example-singleServer-full.html instead.
If you're not using telemetry and results sharing, you can delete the results folder too.
Details about the examples and how to make custom UIs will be discussed later.
Telemetry contains personal information (according to GDPR defintion), therefore it is important to treat this data respectfully of national and international laws, especially if you plan to offer the service in the European Union.
example-singleServer-full.html and example-multipleServers-full.html both contain a privacy policy for the service: you MUST read it, change it if necessary, and add your email address for data deletion requests. Failure to comply with GDPR regulations can get you in serious trouble.
The speedtest can automatically choose between multiple test points and use the one with the lowest ping in a list.
Note that this is an advanced use case and it is recommended that you already know how to use the speedtest with a single server.
We must distinguish 2 types of servers:
This is the server that your users will first connect to. It hosts the UI, the JS files, and optionally telemetry and results sharing stuff.
Requirements:
To install the speedtest frontend, copy the following files to your web server:
speedtest.jsspeedtest_worker.jsresults foldermultipleServers examples (the best starting points are example-multipleServers-pretty.html if you don't want to use telemetry and results sharing, example-multipleServers-full.html if you want to use them). Rename the example you choose to index.htmlImportant: The speedtest needs write permissions in the installation folder!
Edit index.html, you will see a list of servers:
var SPEEDTEST_SERVERS=[
{
name:"Speedtest Demo Server 1", //user friendly name for the server
server:"//mpotdemo.fdossena.com/", //URL to the server. // at the beginning will be replaced with http:// or https:// automatically
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:"Speedtest Demo Server 2",
server:"//mpotdemo2.fdossena.com/",
dlURL:"garbage.php",
ulURL:"empty.php",
pingURL:"empty.php",
getIpURL:"getIP.php"
}
//add other servers here, comma separated
];
Replace the demo servers with your test points. Each server in the list is an object containing:
name: user friendly name for this test pointserver: URL to the server. If your server only supports HTTP or HTTPS, put http:// or https:// at the beginning, respectively; if it supports both, put // at the beginning and it will be replaced automaticallydlURL: path to the download test on this server (garbage.php or replacement)ulURL: path to the upload test on this server (empty.php or replacement)pingURL: path to the ping test on this server (empty.php or replacement)getIpURL: path to getIP on this server (getIP.php or replacement)None of these parameters can be omitted.
Important: You can't mix HTTP with HTTPS; if the frontend uses HTTP, you won't be able to connect to HTTPS backends, and viceversa.
Important: For HTTPS, all your servers must have valid certificates or the browser will refuse to connect
Important: Don't use my demo servers, they're slow!
Telemetry is stored on the frontend server. The setup procedure is the same as the single server version.
These are the servers that will actually be used to perform the test.
Requirements:
To install a backend, simply copy all the files in the backend folder to your backend server.
Important: The speedtest needs write permissions in the installation folder!
The speedtest uses ipinfo.io to detect ISP and distance from server. This is completely optional and can be disabled if you want (see Speedtest settings), but it is enabled by default, and if you expect more than ~500 tests per day, you will need to sign up to ipinfo.io and edit getIP_ipInfo_apikey.php to set your access token.
IpInfo.io has kindly offered free access to their APIs for users of this project; if you're interested, contact me at [email protected] and provide a description of what you intend to do with the project, and you'll get the API key.
This section explains how to use speedtest.js in your webpages.
The best way to learn is by looking at the provided examples.
Single server:
example-singleServer-basic.html: The most basic configuration possible. Runs the test with the default settings when the page is loaded and displays the results with no fancy graphics.example-singleServer-pretty.html: A more sophisticated example with a nicer layout and a start/stop button. This is the best starting point for most usersexample-singleServer-progressBar.html: Same as example-singleServer-pretty.html but adds a progress indicatorexample-singleServer-customSettings.html: Same as example-singleServer-pretty.html but configures the test so that it only performs download and upload tests, and with a fixed length instead of automaticexample-singleServer-gauges.html: The most sophisticated example, with the same functionality as example-singleServer-pretty.html but adds gauges. This is also a good starting point, but the gauges may slow down underpowered devicesexample-singleServer-chart.html: Shows how to use the test with the Chart.js libraryexample-singleServer-full.html: The most complete example. Based on example-singleServer-gauges.html, also enables telemetry and results sharingMultiple servers:
example-multipleServers-pretty.html: Same as example-singleServer-pretty.html but with multiple test points. Server selection is fully automaticexample-multipleServers-full.html: Same as example-singleServer-full.html but with multiple test points. Server selection is automatic but the server can be changed afterwards by the userTo use the speedtest in your page, first you need to load it:
<script type="text/javascript" src="speedtest.js"></script>
After loading, you can initialize the test:
var s=new Speedtest();
Now, you can set up event handlers to update your UI:
s.onupdate=function(data){
//update your UI here
}
s.onend=function(aborted){
//end of the test
if(aborted){
//something to do if the test was aborted instead of ending normally
}
}
The onupdate event handler will be called periodically by the test with data coming from the speedtest worker thread. The data argument is an object containing the following:
-1 = Test not started yet0 = Test starting1 = Download test in progress2 = Ping + Jitter test in progress3 = Upload test in progress4 = Test finished5 = Test abortedThe onend event handler will be called at the end of the test (onupdate will be called first), with a boolean telling you if the test was aborted (either manually or because of an error) or if it ended normally.
Before starting the test, you can change some of the settings from their default values. You might want to do this to better adapt the speedtest to a specific scenario, such as a satellite connection. To change a setting, use
s.setParameter("parameter_name",value);
For instance, to enable telemetry we can use:
s.setParameter("telemetry_level","basic");
And now the test results will be stored and we will get our test ID at the end of the test (along with the other data)
Main parameters:
15>=515>=10true10>=3, <30garbage.phpempty.phpempty.phpgetIP.phptelemetry/telemetry.phpnonebasic: send results onlyfull: send results and timing information, even for aborted testsdebug: same as full but also sends debug information. Not recommended.I: get IPD: download testU: upload testP: ping + jitter test_: delay 1 secondIP_D_Uisp=true to the request to url_getIp. getIP.php accomplishes this using ipinfo.io
truedistance 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 kilometersmi: estimate distance in mileskmAdvanced parameters: (Seriously, don't change these unless you know what you're doing)
JSON.stringify. This string will be added to the database entry for this test.true100>=1010246>=33>=120300>=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>=1truefalse on Firefox because its performance API implementation is inaccuratefalse1.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 speedIf you want to use more than one test server, this is the time to add all your test points and select the best one. Skip this part if you don't want to use this feature.
The best way to do this is to declare an array with all your servers, and give it to the speedtest:
var SPEEDTEST_SERVERS=[
server1,
server2,
...
];
s.addTestPoints(SPEEDTEST_SERVERS);
Each server in the list is an object containing:
name: user friendly name for this test pointserver: URL to the server. If your server only supports HTTP or HTTPS, put http:// or https:// at the beginning, respectively; if it supports both, put // at the beginning and it will be replaced automaticallydlURL: path to the download test on this server (garbage.php or replacement)ulURL: path to the upload test on this server (empty.php or replacement)pingURL: path to the ping test on this server (empty.php or replacement)getIpURL: path to getIP on this server (getIP.php or replacement)None of these parameters can be omitted.
Example:
{
name:"Milano, IT",
server:"http://backend1.myspeedtest.net/",
dlURL:"garbage.php",
ulURL:"empty.php",
pingURL:"empty.php",
getIpURL:"getIP.php"
}
Now, we can run the server selector:
s.selectServer(function(server){
//do something
})
The selectServer function is asynchronous in order to avoid freeing the UI, and it will run a callback function when it is done choosing the server with the lowest ping.
The server argument is the selected server, and you can display it in the UI if you want. You cannot start the test until the selection is done!
You can also set the test point manually (for instance, from a combobox in the UI):
s.setSelectedServer(server)
where server is the server that you want to use.
Finally, we can run the test:
s.start();
During the test, your onupdate event handler will be called periodically with data that you can use to update your UI. Your onend handler will be called at the end of the test.
You can abort the test at any time:
s.abort();
When the test is finished, you can run it again if you want, or you can just destroy s.
The purpose of this section is to help developers who want to make changes to the inner workings of the speedtest.
It will be divided into 4 sections: speedtest.js, speedtest_worker.js, the backend files and the results files.
speedtest.jsThis is the main interface between your webpage and the speedtest. It hides the speedtest web worker to the page, and provides many convenient functions to control the test.
You can think of this as a finite state machine. These are the states (use getState() to see them):
setParameter("parameter",value) function. From here you can either start the test using start() (goes to state 3) or you can add multiple test points using addTestPoint(server) or addTestPoints(serverList) (goes to state 1). Additionally, this is the perfect moment to set up callbacks for the onupdate(data) and onend(aborted) events.1: here you can add test points. You only need to do this if you want to use multiple test points. A server is defined as an object like this:
{
name: "User friendly name",
server:"http://yourBackend.com/", <---- URL to your server. You can specify http:// or https://. If your server supports both, just write // without the protocol
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
}
While in state 1, you can only add test points, you cannot change the test settings. When you're done, use selectServer(callback) to select the test point with the lowest ping. This is asynchronous, when it's done, it will call your callback function and move to state 2. Calling setSelectedServer(server) will manually select a server and move to state 2.
2: test point selected, ready to start the test. Use start() to begin, this will move to state 3
3: test running. Here, your onupdate event calback will be called periodically, with data coming from the worker about speed and progress. A data object will be passed to your onupdate function, with the following items:
- `dlStatus`: download speed in mbps
- `ulStatus`: upload speed in mbps
- `pingStatus`: ping in ms
- `jitterStatus`: jitter in ms
- `dlProgress`: progress of the download test as a float 0-1
- `ulProgress`: progress of the upload test as a float 0-1
- `pingProgress`: progress of the ping/jitter test as a float 0-1
- `testState`: state of the test (-1=not started, 0=starting, 1=download test, 2=ping+jitter test, 3=upload test, 4=finished, 5=aborted)
- `clientIp`: IP address of the client performing the test (and optionally ISP and distance)
At the end of the test, the onend function will be called, with a boolean specifying whether the test was aborted or if it ended normally.
The test can be aborted at any time with abort().
At the end of the test, it will move to state 4
4: test finished. You can run it again by calling start() if you want.
Returns the state of the test: 0=adding settings, 1=adding servers, 2=server selection done, 3=test running, 4=done
Change one of the test settings from their defaults.
Invalid values or nonexistant parameters will be ignored by the speedtest worker.
Add a test point (multiple points of test)
server: the server to be added as an object. Must contain the following elements:
{
name: "User friendly name",
server:"http://yourBackend.com/", URL to your server. You can specify http:// or https://. If your server supports both, just write // without the protocol
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
}
Note that this will add mpot:true to the parameters sent to the speedtest worker.
Same as addTestPoint, but you can pass an array of servers
Returns the selected server (multiple points of test)
Manually selects one of the test points (multiple points of test)
Automatically selects a server from the list of added test points. The server with the lowest ping will be chosen. (multiple points of test)
The selector checks multiple servers in parallel (default: 6 streams) to speed things up if the list of servers is long.
The process is asynchronous and the passed result callback function will be called when it's done, then the test can be started.
Starts the test.
Note (multiple points of test): the selected server will be added to the telemetry_extra string. If this string was already set, then telemetry_extra will be a JSON string containing both the server and the original string
During the test, the onupdate(data) callback function will be called periodically with data from the worker.
At the end of the test, the onend(aborted) function will be called with a boolean telling you if the test was aborted or if it ended normally.
Aborts the test while it's running.
speedtest_worker.jsThis is where the actual speedtest code is. It receives the settings from the main thread, runs the test, and reports back the results.
The worker accepts 3 commands:
start: starts the test. Optionally, test settings can be passed as a JSON string after the word start and a spacestatus: returns the current status as a JSON string. The status string contents are the ones described in the Event handlers section in the section about making a custom front-end.abort: aborts the testIn addition to the parameters listed in the Test settings section in the section about making a custom front-end, there is one additional setting:
mpot: set this to true to run the test with multiple points of test. This will add cors=true to all requests (all responses will contain CORS headers) and enable some extra quirks.
Default: falseThe download test is performed by transferring large blobs of garbage data using XHR from the server to the client.
The test uses multiple streams. If a stream finishes the download, it is restarted. The amount of downloaded data for each stream is tracked using the XHR Level 2 onprogress event.
The test streams are not perfectly synchronized because we don't want them to finish all at the same time if they do.
Every 200ms, a timer updates the dlStatus string with the current speed and calculates a "bonus" time by which to shorten the test depending on how high the speed is, (when time_auto is set to true).
See the code for more implementation details.
This works similarly to the download test, but in reverse. A large blob of garbage data is generated and it is sent to the server repeatedly using multiple streams.
To keep track of the amount of transferred data, the XHR Level 2 upload.onprogress event is used.
This test has a couple of complications:
upload.onprogress event. For this, we use a small blobs instead of a large one and we keep track of progress using the onload event. This is referred to as IE11 Workaround (but the same bug was also found in some versions of Edge and Safari)mpot is set to true, an empty request must first be sent in order to load the CORS headers before the test can startSee the code for more implementation details.
The Ping/Jitter test is NOT an ICMP ping. This is a common misconception. You cannot use ICMP over HTTP, and certainly not in a browser.
This test works by creating a persistent HTTP connection to the server, and then repeatedly downloading an empty file, and measuring how long it takes between the request and the response.
Timing can be measured as a simple timestamp difference or with the Performance API if available.
Jitter is the variance in ping times.
See the code for more implementation details.
backend filesgarbage.phpUses OpenSSL to generate a stream of incompressible garbage data for the download test.
If accepts a ckSize GET parameter, which specifies how much garbage data to generate in megabytes (4-1024).
empty.phpAn empty file used for the upload and ping test. It only sends headers to create the connection.
getIP.phpReturns client IP, ISP and distance from the server.
GET parameters:
isp: if set, fetches ISP info from ipinfo.iodistance: if set, calculates distance from server. You can specify km or mi for the format.If isp is set, the output is a JSON string containing:
processedString: string that can be displayed to the userrawIspInfo: info about the client as a JSON string, straight from ipinfo.ioIf isp is not set, the output is just a string containing the client's IP address.
Note: if your server is behind some proxy, firewall, VPN, etc., the client's IP address may not be detected properly. If this happens, you must analyze the traffic coming from the client to find the name of the HTTP header that contains the original IP address. getIP.php contains some of these headers but not all of them.
All these files will send the following CORS headers if the GET parameter cors=true is passed to them:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Encoding, Content-Type
results filestelemetry.phpThis file stores telemetry information into the database.
Data is passed as POST parameters:
ispinfo: ISP info (if enabled, empty strng otherwise)extra: the telemetry_extra string passed to the worker (if set, empty string otherwise)dl: download speedul: upload speedping: ping timejitter: jitter valuelog: telemetry log (if telemetry_level is set to full or higher, empty string otherwise)index.phpGenerates a shareable results image for a given test ID.
GET parameters:
id: ID of the test you want to displayThe looks of this image can be customized by editing the variables in this file.
idObfuscation.phpContains the implementation of ID obfuscation and deobfuscation.
See the code for the implementation details, it's basically a bunch of bitwise operations.
stats.phpSimple UI to display and search test results. Not required to run the test.
If for some reason you can't or don't want to use PHP, the speedtest can run with other backends, or even no backend (with limited functionality).
You will need replacements for backend/garbage.php and backend/empty.php and optionally backend/getIP.php, and the test needs to know where to find them:
//Speedtest initialization
var s=new Speedtest();
...
//Custom backend
s.setParameter("url_dl","URL to your garbage.php replacement");
s.setParameter("url_ul","URL to your empty.php replacement");
s.setParameter("url_ping","URL to your empty.php replacement");
s.setParameter("url_getIp","URL to your getIP.php replacement");
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.
A symlink to /dev/urandom is also ok.
If you want to make your own backend, see the section on the implementation details of garbage.php.
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.
An empty file can be used for this.
If you want to make your own backend, see the section on the implementation details of empty.php.
getIP.phpYour replacement can simply respond with the client's IP as plaintext or do something more fancy.
If you want to make your own backend, see the section on the implementation details of getIP.php.
The speedtest can run, albeit with limited functionality, using only a web server as backend, with no PHP or other server-side scripting.
You will be able to run the download and upload test, but no IP, ISP and distance detection, no telemetry and results sharing, and only a single point of test.
To do this, you will need:
garbage.php: a large incompressible file, like this. We'll call this backend/garbage.datempty.php: an empty file will do. We'll call this backend/empty.datNow you need to configure the test to use them. Look for s=new Speedtest() and right below it, put the following:
s.setParameter("url_dl","backend/garbage.dat");
s.setParameter("url_ul","backend/empty.dat");
s.setParameter("url_ping","backend/empty.dat");
s.setParameter("test_order","P_D_U");
This will point to our static files and set the test to only do ping/jitter, download and uplod tests.
These are the most common issues reported by users, and how to fix them. If you still need help, contact me at [email protected].
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
Edit getIP.php and replace lines 14-23 with what is more appropriate in your scenario.
Example: $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
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 and no real solution is known.
This is not a speedtest related issue, as it can be replicated in virtually any HTTP file upload/download.
Go to your domain's DNS settings and change "DNS and HTTP proxy (CDN)" to "DNS only", and wait for the settings to be applied (can take a few minutes).
This is a configuration issue. Make a file called web.config in wwwroot and adapt the following code:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<cors enabled="true" failUnlistedOrigins="false">
<add origin="*">
<allowHeaders allowAllRequestedHeaders="true" />
<allowMethods>
<add method="GET" />
<add method="POST" />
<add method="PUT" />
<add method="DELETE" />
<add method="OPTIONS" />
</allowMethods>
<exposeHeaders>
</exposeHeaders>
</add>
</cors>
</system.webServer>
</configuration>
ID obfuscation only works on 64-bit PHP (requires PHP_INT_SIZE to be 8).
Note that older versions of PHP 5 on Windows use PHP_INT_SIZE of 4, even if they're 64 bit. If you're in this situation, update your PHP install.
Also, make sure that the web server has write permission on the results folder.
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, and potentially even the download and upload tests on very fast connections.
Since this is an open source project, you can modify it.
If you made some changes that you think should make it into the main project, send a Pull Request on GitHub, or contact me at [email protected].
We don't require you to use a specific coding convention, write the code however you want and we'll change the formatting if necessary.
Donations are also appreciated: you can donate with PayPal or Liberapay.
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 [email protected] for other licensing models.