From ca779a5ae2fca28f75d505c2bde9fda646eec817 Mon Sep 17 00:00:00 2001 From: dosse91 Date: Thu, 24 Aug 2017 15:28:28 +0200 Subject: [PATCH] Added optional telemetry + example; Updated documentation; Minor changes and fixes --- README.md | 11 ++++- doc.md | 50 ++++++++++++++++--- example8_telemetry.html | 105 ++++++++++++++++++++++++++++++++++++++++ speedtest_worker.js | 85 +++++++++++++++++++++++++++----- speedtest_worker.min.js | 2 +- telemetry.php | 23 +++++++++ telemetry.sql | 66 +++++++++++++++++++++++++ 7 files changed, 319 insertions(+), 23 deletions(-) create mode 100644 example8_telemetry.html create mode 100644 telemetry.php create mode 100644 telemetry.sql diff --git a/README.md b/README.md index 0eea20e..59a1fcf 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,16 @@ This is a very lightweight Speedtest implemented in Javascript, using XMLHttpReq ## Compatibility Only modern browsers are supported (IE11, latest Edge, latest Chrome, latest Firefox, latest Safari) +## Features +* Download +* Upload +* Ping +* Jitter +* IP Address +* Telemetry (optional) + ## Requirements - - A reasonably fast web server. PHP is optional but recommended (see doc.md for details) - - Some way to generate garbage data (PHP script included, see doc.md for other solutions) + - A reasonably fast web server with PHP (see doc.md for details and use without PHP) - Your server must accept large POST requests (up to 20 Megabytes), otherwise the upload test will fail - It's also better if your server does not use compression, but it's not mandatory diff --git a/doc.md b/doc.md index 6b8863b..e35a536 100644 --- a/doc.md +++ b/doc.md @@ -1,7 +1,7 @@ # HTML5 Speedtest > by Federico Dossena -> Version 4.2.9, July 19 2017 +> Version 4.3, August 24 2017 > [https://github.com/adolfintel/speedtest/](https://github.com/adolfintel/speedtest/) @@ -35,14 +35,16 @@ To install the test on your server, upload the following files: * `garbage.php` * `getIP.php` * `empty.php` +* one of the examples -You may also want to upload one of the examples to test it. -Later we'll see how to use the test without PHP. +Later 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 ## Usage +You can modify one of the examples or start from scratch. If you are just editing one of the example, skip to the "test parameters" section. + To run the test, you need to do 3 things: * Create the worker @@ -246,15 +248,49 @@ You need to start the test with your replacements like this: ```js w.postMessage('start {"url_dl": "newGarbageURL", "url_ul": "newEmptyURL", "url_ping": "newEmptyURL", "url_getIp": "newIpURL"}') ``` +## Telemetry +Telemetry currently requires PHP and MySQL. +To set up the telemetry, we need to do 4 things: +* copy `telemetry.php` +* edit `telemetry.php` to add your database access credentials +* create the database +* enable telemetry +### Creating the database +At the moment, only MySQL is supported. +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. + +### Configuring `telemetry.php` +Open telemetry.php with notepad or a similar text editor, and insert your database access 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 +``` + +### Enabling telemetry +Edit your test page; where you start the worker, you need to specify the `telemetry_level`. +There are 3 levels: +* `none`: telemetry is disabled (default) +* `basic`: telemetry collects IP, User Agent, Preferred language, Test results +* `full`: same as above, but also collects a log (10-150 Kb each, not recommended) + +Example: +``` +w.postMessage('start {"telemetry_level":"basic"}') +``` + +Also, see example8_telemetry.html + +### See the results +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. ## Known bugs and limitations * 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 -* __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 +* __IE11, Edge:__ the upload test is not precise on very fast connections * __IE11:__ the upload test may not work over HTTPS -* __Safari:__ works, but needs more testing and tweaking for very fast connections * __Firefox:__ on some Linux systems with hardware acceleration turned off, the page rendering makes the browser lag, reducing the accuracy of the ping/jitter test ## Making changes diff --git a/example8_telemetry.html b/example8_telemetry.html new file mode 100644 index 0000000..579035d --- /dev/null +++ b/example8_telemetry.html @@ -0,0 +1,105 @@ + + + + + + + Speedtest + + + + + + +
+
+
+

+ Speedtest + + +

Basic telemetry is active; results will be saved in your database. If the results don't appear, check the settings in telemetry.php

+

+

+ Your IP: +

+
+
+

Download

+

+

Mbit/s

+
+
+

Upload

+

+

Mbit/s

+
+
+

Ping

+

+

ms

+
+
+

Jitter

+

+

ms

+
+
+
+ + + + diff --git a/speedtest_worker.js b/speedtest_worker.js index 5c0c709..e263547 100644 --- a/speedtest_worker.js +++ b/speedtest_worker.js @@ -1,5 +1,5 @@ /* - HTML5 Speedtest v4.2.8 + HTML5 Speedtest v4.3 by Federico Dossena https://github.com/adolfintel/speedtest/ GNU LGPLv3 License @@ -13,6 +13,10 @@ 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 +var log="" //telemetry log +function tlog(s){log+=Date.now()+": "+s+"\n"} +function twarn(s){log+=Date.now()+" WARN: "+s+"\n"; console.warn(s);} + // test settings. can be overridden by sending specific values with the start command var settings = { time_ul: 15, // duration of upload test in seconds @@ -30,7 +34,9 @@ var settings = { 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 - overheadCompensationFactor: 1048576/925000 //compensation for HTTP+TCP+IP+ETH overhead. 925000 is how much data is actually carried over 1048576 (1mb) bytes downloaded/uploaded. This default value assumes HTTP+TCP+IPv4+ETH with typical MTUs over the Internet. You may want to change this if you're going through your local network with a different MTU or if you're going over IPv6 (see doc.md for some other values) + overheadCompensationFactor: 1048576/925000, //compensation for HTTP+TCP+IP+ETH overhead. 925000 is how much data is actually carried over 1048576 (1mb) bytes downloaded/uploaded. This default value assumes HTTP+TCP+IPv4+ETH with typical MTUs over the Internet. You may want to change this if you're going through your local network with a different MTU or if you're going over IPv6 (see doc.md for some other values) + telemetry_level: 0, // 0=disabled, 1=basic (results only), 2=full (results+log) + url_telemetry: 'telemetry.php' // path to the script that adds telemetry data to the database } var xhr = null // array of currently active xhr requests @@ -88,13 +94,13 @@ this.addEventListener('message', function (e) { if (/Edge.(\d+\.\d+)/i.test(ua)) { // edge more precise with 3 download streams settings.xhr_dlMultistream = 3 + settings.forceIE11Workaround = true //Edge 15 introduced a bug that causes onprogress events to not get fired, so for Edge, we have to use the "small chunks" workaround that reduces accuracy } if (/Chrome.(\d+)/i.test(ua) && (!!self.fetch)) { // 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 @@ -104,19 +110,24 @@ this.addEventListener('message', function (e) { if (typeof s.time_dlGraceTime !== 'undefined') settings.time_dlGraceTime = s.time_dlGraceTime // dl test grace time before measuring if (typeof s.time_ulGraceTime !== 'undefined') settings.time_ulGraceTime = s.time_ulGraceTime // ul test grace time before measuring if (typeof s.overheadCompensationFactor !== 'undefined') settings.overheadCompensationFactor = s.overheadCompensationFactor //custom overhead compensation factor (default assumes HTTP+TCP+IP+ETH with typical MTUs) - } catch (e) { console.warn("Possible error in custom test settings. Some settings may not be applied. Exception: "+e) } + if (typeof s.telemetry_level !== 'undefined') settings.telemetry_level = s.telemetry_level === "basic" ? 1 : s.telemetry_level === "full" ? 2 : 0; // telemetry level + if (typeof s.url_telemetry !== 'undefined') settings.url_telemetry = s.url_telemetry // url to telemetry.php + } catch (e) { twarn("Possible error in custom test settings. Some settings may not be applied. Exception: "+e) } // run the tests - console.log(settings) - getIp(function () { dlTest(function () { testStatus = 2; pingTest(function () { testStatus = 3; ulTest(function () { testStatus = 4 }) }) }) }) + tlog(JSON.stringify(settings)) + getIp(function () { dlTest(function () { testStatus = 2; pingTest(function () { testStatus = 3; ulTest(function () { testStatus = 4; sendTelemetry() }) }) }) }) } if (params[0] === 'abort') { // abort command + tlog("manually aborted"); clearRequests() // stop all xhr activity if (interval) clearInterval(interval) // clear timer if present - testStatus = 5; dlStatus = ''; ulStatus = ''; pingStatus = ''; jitterStatus = '' // set test as aborted + if (settings.telemetry_level > 1) sendTelemetry() + testStatus = 5; dlStatus = ''; ulStatus = ''; pingStatus = ''; jitterStatus = '' // set test as aborted } }) // stops all XHR activity, aggressively function clearRequests () { + tlog("stopping pending XHRs"); if (xhr) { for (var i = 0; i < xhr.length; i++) { if (useFetchAPI) try { xhr[i].cancelRequested = true } catch (e) { } @@ -130,13 +141,16 @@ function clearRequests () { } // gets client's IP using url_getIp, then calls the done function function getIp (done) { + tlog("getIp"); if (settings.url_getIp == "-1") {done(); return} xhr = new XMLHttpRequest() xhr.onload = function () { + tlog("IP: "+xhr.responseText); clientIp = xhr.responseText done() } xhr.onerror = function () { + tlog("getIp failed"); done() } xhr.open('GET', settings.url_getIp + url_sep(settings.url_getIp) + 'r=' + Math.random(), true) @@ -145,6 +159,7 @@ function getIp (done) { // download test, calls done function when it's over var dlCalled = false // used to prevent multiple accidental calls to dlTest function dlTest (done) { + tlog("dlTest") if (dlCalled) return; else dlCalled = true // dlTest already called? if (settings.url_dl == "-1") {done(); return} var totLoaded = 0.0, // total number of loaded bytes @@ -156,10 +171,12 @@ function dlTest (done) { var testStream = function (i, delay) { setTimeout(function () { if (testStatus !== 1) return // delayed stream ended up starting after the end of the download test + tlog("dl test stream started "+i+" "+delay) var prevLoaded = 0 // number of bytes loaded last time onprogress was called var x = new XMLHttpRequest() xhr[i] = x xhr[i].onprogress = function (event) { + tlog("dl stream progress event "+i+" "+event.loaded); 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) @@ -169,11 +186,13 @@ function dlTest (done) { }.bind(this) xhr[i].onload = function () { // the large file has been loaded entirely, start again + tlog("dl stream finished "+i) try { xhr[i].abort() } catch (e) { } // reset the stream data to empty ram testStream(i, 0) }.bind(this) xhr[i].onerror = function () { // error + tlog("dl stream failed "+i); if (settings.xhr_ignoreErrors === 0) failed=true //abort try { xhr[i].abort() } catch (e) { } delete (xhr[i]) @@ -191,6 +210,7 @@ function dlTest (done) { } // every 200ms, update dlStatus interval = setInterval(function () { + tlog("DL: "+dlStatus+(graceTimeDone?"":" (in grace time)")) var t = new Date().getTime() - startT if (t < 200) return if (!graceTimeDone){ @@ -208,6 +228,7 @@ function dlTest (done) { if (failed || isNaN(dlStatus)) dlStatus = 'Fail' clearRequests() clearInterval(interval) + tlog("dlTest finished "+dlStatus) done() } } @@ -227,6 +248,7 @@ reqsmall.push(r) reqsmall = new Blob(reqsmall) var ulCalled = false // used to prevent multiple accidental calls to ulTest function ulTest (done) { + tlog("ulTest"); if (ulCalled) return; else ulCalled = true // ulTest already called? if (settings.url_ul == "-1") {done(); return} var totLoaded = 0.0, // total number of transmitted bytes @@ -238,24 +260,29 @@ function ulTest (done) { var testStream = function (i, delay) { setTimeout(function () { if (testStatus !== 3) return // delayed stream ended up starting after the end of the upload test + tlog("ul test stream started "+i+" "+delay) var prevLoaded = 0 // number of bytes transmitted last time onprogress was called var x = new XMLHttpRequest() xhr[i] = x var ie11workaround - try { - xhr[i].upload.onprogress - ie11workaround = false - } catch (e) { - ie11workaround = true + if (settings.forceIE11Workaround) ie11workaround = true; else { + try { + xhr[i].upload.onprogress + ie11workaround = false + } catch (e) { + ie11workaround = true + } } if (ie11workaround) { // IE11 workarond: xhr.upload does not work properly, therefore we send a bunch of small 256k requests and use the onload event as progress. This is not precise, especially on fast connections xhr[i].onload = function () { + tlog("ul stream progress event (ie11wa)") totLoaded += 262144 testStream(i, 0) } xhr[i].onerror = function () { // error, abort + tlog("ul stream failed (ie11wa)") if (settings.xhr_ignoreErrors === 0) failed = true //abort try { xhr[i].abort() } catch (e) { } delete (xhr[i]) @@ -267,6 +294,7 @@ function ulTest (done) { } else { // REGULAR version, no workaround xhr[i].upload.onprogress = function (event) { + tlog("ul stream progress event "+i+" "+event.loaded); if (testStatus !== 3) { try { x.abort() } catch (e) { } } // just in case this XHR is still running after the upload test // progress event, add number of new loaded bytes to totLoaded var loadDiff = event.loaded <= 0 ? 0 : (event.loaded - prevLoaded) @@ -276,9 +304,11 @@ function ulTest (done) { }.bind(this) xhr[i].upload.onload = function () { // this stream sent all the garbage data, start again + tlog("ul stream finished "+i); testStream(i, 0) }.bind(this) xhr[i].upload.onerror = function () { + tlog("ul stream failed "+i); if (settings.xhr_ignoreErrors === 0) failed=true //abort try { xhr[i].abort() } catch (e) { } delete (xhr[i]) @@ -297,6 +327,7 @@ function ulTest (done) { } // every 200ms, update ulStatus interval = setInterval(function () { + tlog("UL: "+ulStatus+(graceTimeDone?"":" (in grace time)")) var t = new Date().getTime() - startT if (t < 200) return if (!graceTimeDone){ @@ -314,6 +345,7 @@ function ulTest (done) { if (failed || isNaN(ulStatus)) ulStatus = 'Fail' clearRequests() clearInterval(interval) + tlog("ulTest finished "+ulStatus) done() } } @@ -322,6 +354,7 @@ function ulTest (done) { // 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) { + tlog("pingTest"); if (ptCalled) return; else ptCalled = true // pingTest already called? if (settings.url_ping == "-1") {done(); return} var prevT = null // last time a pong was received @@ -332,10 +365,12 @@ function pingTest (done) { xhr = [] // ping function var doPing = function () { + tlog("ping"); prevT = new Date().getTime() xhr[0] = new XMLHttpRequest() xhr[0].onload = function () { // pong + tlog("pong"); if (i === 0) { prevT = new Date().getTime() // first pong } else { @@ -350,10 +385,12 @@ function pingTest (done) { pingStatus = ping.toFixed(2) jitterStatus = jitter.toFixed(2) i++ + tlog("PING: "+pingStatus+" JITTER: "+jitterStatus) if (i < settings.count_ping) doPing(); else done() // more pings to do? }.bind(this) xhr[0].onerror = function () { // a ping failed, cancel test + tlog("ping failed"); if (settings.xhr_ignoreErrors === 0) { //abort pingStatus = 'Fail' jitterStatus = 'Fail' @@ -361,7 +398,6 @@ function pingTest (done) { done() } if (settings.xhr_ignoreErrors === 1) doPing() //retry ping - if (settings.xhr_ignoreErrors === 2){ //ignore failed ping i++ if (i < settings.count_ping) doPing(); else done() // more pings to do? @@ -373,3 +409,26 @@ function pingTest (done) { }.bind(this) doPing() // start first ping } +// telemetry +function sendTelemetry(){ + if (settings.telemetry_level < 1) return + xhr = new XMLHttpRequest(); + xhr.onload = function () { console.log("TELEMETRY OL "+xhr.responseText) } + xhr.onerror = function () { console.log("TELEMETRY ERROR "+xhr) } + xhr.open('POST', settings.url_telemetry+"?r="+Math.random(), true); + try{ + var fd = new FormData() + fd.append('dl', dlStatus) + fd.append('ul', ulStatus) + fd.append('ping', pingStatus) + fd.append('jitter', jitterStatus) + fd.append('log', settings.telemetry_level>1?log:"") + xhr.send(fd) + }catch(ex){ + var postData = "dl="+encodeURIComponent(dlStatus)+"&ul="+encodeURIComponent(ulStatus)+"&ping="+encodeURIComponent(pingStatus)+"&jitter="+encodeURIComponent(jitterStatus)+"&log="+encodeURIComponent(settings.telemetry_level>1?log:"") + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded") + xhr.send(postData) + } + + +} \ No newline at end of file diff --git a/speedtest_worker.min.js b/speedtest_worker.min.js index ae33a00..f7a2a8c 100644 --- a/speedtest_worker.min.js +++ b/speedtest_worker.min.js @@ -1 +1 @@ -function url_sep(url){return url.match(/\?/)?"&":"?"}function clearRequests(){if(xhr){for(var i=0;isettings.time_dl&&dlStatus>0||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),done())):t>1e3*settings.time_dlGraceTime&&(totLoaded>0&&(startT=(new Date).getTime(),totLoaded=0),graceTimeDone=!0))}.bind(this),200)}else done()}function ulTest(done){if(!ulCalled)if(ulCalled=!0,"-1"!=settings.url_ul){var totLoaded=0,startT=(new Date).getTime(),graceTimeDone=!1,failed=!1;xhr=[];for(var testStream=function(i,delay){setTimeout(function(){if(3===testStatus){var prevLoaded=0,x=new XMLHttpRequest;xhr[i]=x;var ie11workaround;try{xhr[i].upload.onprogress,ie11workaround=!1}catch(e){ie11workaround=!0}ie11workaround?(xhr[i].onload=function(){totLoaded+=262144,testStream(i,0)},xhr[i].onerror=function(){0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,100)},xhr[i].open("POST",settings.url_ul+url_sep(settings.url_ul)+"r="+Math.random(),!0),xhr[i].setRequestHeader("Content-Encoding","identity"),xhr[i].send(reqsmall)):(xhr[i].upload.onprogress=function(event){if(3!==testStatus)try{x.abort()}catch(e){}var loadDiff=event.loaded<=0?0:event.loaded-prevLoaded;isNaN(loadDiff)||!isFinite(loadDiff)||loadDiff<0||(totLoaded+=loadDiff,prevLoaded=event.loaded)}.bind(this),xhr[i].upload.onload=function(){testStream(i,0)}.bind(this),xhr[i].upload.onerror=function(){0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,100)}.bind(this),xhr[i].open("POST",settings.url_ul+url_sep(settings.url_ul)+"r="+Math.random(),!0),xhr[i].setRequestHeader("Content-Encoding","identity"),xhr[i].send(req))}}.bind(this),1)}.bind(this),i=0;isettings.time_ul&&ulStatus>0||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),done())):t>1e3*settings.time_ulGraceTime&&(totLoaded>0&&(startT=(new Date).getTime(),totLoaded=0),graceTimeDone=!0))}.bind(this),200)}else done()}function pingTest(done){if(!ptCalled)if(ptCalled=!0,"-1"!=settings.url_ping){var prevT=null,ping=0,jitter=0,i=0,prevInstspd=0;xhr=[];var doPing=function(){prevT=(new Date).getTime(),xhr[0]=new XMLHttpRequest,xhr[0].onload=function(){if(0===i)prevT=(new Date).getTime();else{var instspd=(new Date).getTime()-prevT,instjitter=Math.abs(instspd-prevInstspd);1===i?ping=instspd:(ping=.9*ping+.1*instspd,jitter=instjitter>jitter?.2*jitter+.8*instjitter:.9*jitter+.1*instjitter),prevInstspd=instspd}pingStatus=ping.toFixed(2),jitterStatus=jitter.toFixed(2),++iloadDiff||(totLoaded+=loadDiff,prevLoaded=event.loaded)}.bind(this),xhr[i].onload=function(){tlog("dl stream finished "+i);try{xhr[i].abort()}catch(e){}testStream(i,0)}.bind(this),xhr[i].onerror=function(){tlog("dl stream failed "+i),0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,100)}.bind(this);try{settings.xhr_dlUseBlob?xhr[i].responseType="blob":xhr[i].responseType="arraybuffer"}catch(e){}xhr[i].open("GET",settings.url_dl+url_sep(settings.url_dl)+"r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize,!0),xhr[i].send()}}.bind(this),1+delay)}.bind(this),i=0;it))if(graceTimeDone){var speed=totLoaded/(t/1e3);dlStatus=(8*speed*settings.overheadCompensationFactor/1048576).toFixed(2),(t/1e3>settings.time_dl&&dlStatus>0||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),tlog("dlTest finished "+dlStatus),done())}else t>1e3*settings.time_dlGraceTime&&(totLoaded>0&&(startT=(new Date).getTime(),totLoaded=0),graceTimeDone=!0)}.bind(this),200)}}function ulTest(done){if(tlog("ulTest"),!ulCalled){if(ulCalled=!0,"-1"==settings.url_ul)return void done();var totLoaded=0,startT=(new Date).getTime(),graceTimeDone=!1,failed=!1;xhr=[];for(var testStream=function(i,delay){setTimeout(function(){if(3===testStatus){tlog("ul test stream started "+i+" "+delay);var prevLoaded=0,x=new XMLHttpRequest;xhr[i]=x;var ie11workaround;if(settings.forceIE11Workaround)ie11workaround=!0;else try{xhr[i].upload.onprogress,ie11workaround=!1}catch(e){ie11workaround=!0}ie11workaround?(xhr[i].onload=function(){tlog("ul stream progress event (ie11wa)"),totLoaded+=262144,testStream(i,0)},xhr[i].onerror=function(){tlog("ul stream failed (ie11wa)"),0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,100)},xhr[i].open("POST",settings.url_ul+url_sep(settings.url_ul)+"r="+Math.random(),!0),xhr[i].setRequestHeader("Content-Encoding","identity"),xhr[i].send(reqsmall)):(xhr[i].upload.onprogress=function(event){if(tlog("ul stream progress event "+i+" "+event.loaded),3!==testStatus)try{x.abort()}catch(e){}var loadDiff=event.loaded<=0?0:event.loaded-prevLoaded;isNaN(loadDiff)||!isFinite(loadDiff)||0>loadDiff||(totLoaded+=loadDiff,prevLoaded=event.loaded)}.bind(this),xhr[i].upload.onload=function(){tlog("ul stream finished "+i),testStream(i,0)}.bind(this),xhr[i].upload.onerror=function(){tlog("ul stream failed "+i),0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,100)}.bind(this),xhr[i].open("POST",settings.url_ul+url_sep(settings.url_ul)+"r="+Math.random(),!0),xhr[i].setRequestHeader("Content-Encoding","identity"),xhr[i].send(req))}}.bind(this),1)}.bind(this),i=0;it))if(graceTimeDone){var speed=totLoaded/(t/1e3);ulStatus=(8*speed*settings.overheadCompensationFactor/1048576).toFixed(2),(t/1e3>settings.time_ul&&ulStatus>0||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),tlog("ulTest finished "+ulStatus),done())}else t>1e3*settings.time_ulGraceTime&&(totLoaded>0&&(startT=(new Date).getTime(),totLoaded=0),graceTimeDone=!0)}.bind(this),200)}}function pingTest(done){if(tlog("pingTest"),!ptCalled){if(ptCalled=!0,"-1"==settings.url_ping)return void done();var prevT=null,ping=0,jitter=0,i=0,prevInstspd=0;xhr=[];var doPing=function(){tlog("ping"),prevT=(new Date).getTime(),xhr[0]=new XMLHttpRequest,xhr[0].onload=function(){if(tlog("pong"),0===i)prevT=(new Date).getTime();else{var instspd=(new Date).getTime()-prevT,instjitter=Math.abs(instspd-prevInstspd);1===i?ping=instspd:(ping=.9*ping+.1*instspd,jitter=instjitter>jitter?.2*jitter+.8*instjitter:.9*jitter+.1*instjitter),prevInstspd=instspd}pingStatus=ping.toFixed(2),jitterStatus=jitter.toFixed(2),i++,tlog("PING: "+pingStatus+" JITTER: "+jitterStatus),i1?log:""),xhr.send(fd)}catch(ex){var postData="dl="+encodeURIComponent(dlStatus)+"&ul="+encodeURIComponent(ulStatus)+"&ping="+encodeURIComponent(pingStatus)+"&jitter="+encodeURIComponent(jitterStatus)+"&log="+encodeURIComponent(settings.telemetry_level>1?log:"");xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),xhr.send(postData)}}}var testStatus=0,dlStatus="",ulStatus="",pingStatus="",jitterStatus="",clientIp="",log="",settings={time_ul:15,time_dl:15,time_ulGraceTime:3,time_dlGraceTime:1.5,count_ping:35,url_dl:"garbage.php",url_ul:"empty.php",url_ping:"empty.php",url_getIp:"getIP.php",xhr_dlMultistream:10,xhr_ulMultistream:3,xhr_ignoreErrors:1,xhr_dlUseBlob:!1,garbagePhp_chunkSize:20,enable_quirks:!0,overheadCompensationFactor:1048576/925e3,telemetry_level:0,url_telemetry:"telemetry.php"},xhr=null,interval=null,useFetchAPI=!1;this.addEventListener("message",function(e){var params=e.data.split(" ");if("status"===params[0]&&postMessage(testStatus+";"+dlStatus+";"+ulStatus+";"+pingStatus+";"+clientIp+";"+jitterStatus),"start"===params[0]&&0===testStatus){testStatus=1;try{var s={};try{var ss=e.data.substring(5);ss&&(s=JSON.parse(ss))}catch(e){console.warn("Error parsing custom settings JSON. Please check your syntax")}if("undefined"!=typeof s.url_dl&&(settings.url_dl=s.url_dl),"undefined"!=typeof s.url_ul&&(settings.url_ul=s.url_ul),"undefined"!=typeof s.url_ping&&(settings.url_ping=s.url_ping),"undefined"!=typeof s.url_getIp&&(settings.url_getIp=s.url_getIp),"undefined"!=typeof s.time_dl&&(settings.time_dl=s.time_dl),"undefined"!=typeof s.time_ul&&(settings.time_ul=s.time_ul),"undefined"!=typeof s.enable_quirks&&(settings.enable_quirks=s.enable_quirks),settings.enable_quirks){var ua=navigator.userAgent;/Firefox.(\d+\.\d+)/i.test(ua)&&(settings.xhr_ulMultistream=1),/Edge.(\d+\.\d+)/i.test(ua)&&(settings.xhr_dlMultistream=3,settings.forceIE11Workaround=!0),/Chrome.(\d+)/i.test(ua)&&self.fetch&&(settings.xhr_dlMultistream=5)}"undefined"!=typeof s.count_ping&&(settings.count_ping=s.count_ping),"undefined"!=typeof s.xhr_dlMultistream&&(settings.xhr_dlMultistream=s.xhr_dlMultistream),"undefined"!=typeof s.xhr_ulMultistream&&(settings.xhr_ulMultistream=s.xhr_ulMultistream),"undefined"!=typeof s.xhr_ignoreErrors&&(settings.xhr_ignoreErrors=s.xhr_ignoreErrors),"undefined"!=typeof s.xhr_dlUseBlob&&(settings.xhr_dlUseBlob=s.xhr_dlUseBlob),"undefined"!=typeof s.garbagePhp_chunkSize&&(settings.garbagePhp_chunkSize=s.garbagePhp_chunkSize),"undefined"!=typeof s.time_dlGraceTime&&(settings.time_dlGraceTime=s.time_dlGraceTime),"undefined"!=typeof s.time_ulGraceTime&&(settings.time_ulGraceTime=s.time_ulGraceTime),"undefined"!=typeof s.overheadCompensationFactor&&(settings.overheadCompensationFactor=s.overheadCompensationFactor),"undefined"!=typeof s.telemetry_level&&(settings.telemetry_level="basic"===s.telemetry_level?1:"full"===s.telemetry_level?2:0),"undefined"!=typeof s.url_telemetry&&(settings.url_telemetry=s.url_telemetry)}catch(e){twarn("Possible error in custom test settings. Some settings may not be applied. Exception: "+e)}tlog(JSON.stringify(settings)),getIp(function(){dlTest(function(){testStatus=2,pingTest(function(){testStatus=3,ulTest(function(){testStatus=4,sendTelemetry()})})})})}"abort"===params[0]&&(tlog("manually aborted"),clearRequests(),interval&&clearInterval(interval),settings.telemetry_level>1&&sendTelemetry(),testStatus=5,dlStatus="",ulStatus="",pingStatus="",jitterStatus="")});var dlCalled=!1,r=new ArrayBuffer(1048576);try{r=new Float32Array(r);for(var i=0;ii;i++)req.push(r);req=new Blob(req),r=new ArrayBuffer(262144);try{r=new Float32Array(r);for(var i=0;iprepare("INSERT INTO speedtest_users (ip,ua,lang,dl,ul,ping,jitter,log) VALUES (?,?,?,?,?,?,?,?)") or die("2"); +$stmt->bind_param("ssssssss",$ip,$ua,$lang,$dl,$ul,$ping,$jitter,$log) or die("3"); +$stmt->execute() or die("4"); +$stmt->close() or die("5"); +$conn->close() or die("6"); + +?> \ No newline at end of file diff --git a/telemetry.sql b/telemetry.sql new file mode 100644 index 0000000..160b4fd --- /dev/null +++ b/telemetry.sql @@ -0,0 +1,66 @@ +-- phpMyAdmin SQL Dump +-- version 4.7.0 +-- https://www.phpmyadmin.net/ +-- +-- Host: 127.0.0.1 +-- Generation Time: Aug 24, 2017 at 02:16 PM +-- Server version: 10.1.25-MariaDB +-- PHP Version: 7.1.7 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET AUTOCOMMIT = 0; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `speedtest_telemetry` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `speedtest_users` +-- + +CREATE TABLE `speedtest_users` ( + `id` int(11) NOT NULL, + `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `ip` text NOT NULL, + `ua` text NOT NULL, + `lang` text NOT NULL, + `dl` text, + `ul` text, + `ping` text, + `jitter` text, + `log` longtext +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `speedtest_users` +-- +ALTER TABLE `speedtest_users` + ADD PRIMARY KEY (`id`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `speedtest_users` +-- +ALTER TABLE `speedtest_users` + MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;