speedtest_worker.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. /*
  2. HTML5 Speedtest v4.3
  3. by Federico Dossena
  4. https://github.com/adolfintel/speedtest/
  5. GNU LGPLv3 License
  6. */
  7. // data reported to main thread
  8. var testStatus = 0 // 0=not started, 1=download test, 2=ping+jitter test, 3=upload test, 4=finished, 5=abort/error
  9. var dlStatus = '' // download speed in megabit/s with 2 decimal digits
  10. var ulStatus = '' // upload speed in megabit/s with 2 decimal digits
  11. var pingStatus = '' // ping in milliseconds with 2 decimal digits
  12. var jitterStatus = '' // jitter in milliseconds with 2 decimal digits
  13. var clientIp = '' // client's IP address as reported by getIP.php
  14. var log="" //telemetry log
  15. function tlog(s){log+=Date.now()+": "+s+"\n"}
  16. function twarn(s){log+=Date.now()+" WARN: "+s+"\n"; console.warn(s);}
  17. // test settings. can be overridden by sending specific values with the start command
  18. var settings = {
  19. time_ul: 15, // duration of upload test in seconds
  20. time_dl: 15, // duration of download test in seconds
  21. time_ulGraceTime: 3, //time to wait in seconds before actually measuring ul speed (wait for buffers to fill)
  22. time_dlGraceTime: 1.5, //time to wait in seconds before actually measuring dl speed (wait for TCP window to increase)
  23. count_ping: 35, // number of pings to perform in ping test
  24. url_dl: 'garbage.php', // path to a large file or garbage.php, used for download test. must be relative to this js file
  25. url_ul: 'empty.php', // path to an empty file, used for upload test. must be relative to this js file
  26. url_ping: 'empty.php', // path to an empty file, used for ping test. must be relative to this js file
  27. url_getIp: 'getIP.php', // path to getIP.php relative to this js file, or a similar thing that outputs the client's ip
  28. xhr_dlMultistream: 10, // number of download streams to use (can be different if enable_quirks is active)
  29. xhr_ulMultistream: 3, // number of upload streams to use (can be different if enable_quirks is active)
  30. xhr_ignoreErrors: 1, // 0=fail on errors, 1=attempt to restart a stream if it fails, 2=ignore all errors
  31. 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)
  32. garbagePhp_chunkSize: 20, // size of chunks sent by garbage.php (can be different if enable_quirks is active)
  33. 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
  34. 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)
  35. telemetry_level: 0, // 0=disabled, 1=basic (results only), 2=full (results+log)
  36. url_telemetry: 'telemetry.php' // path to the script that adds telemetry data to the database
  37. }
  38. var xhr = null // array of currently active xhr requests
  39. var interval = null // timer used in tests
  40. /*
  41. when set to true (automatically) the download test will use the fetch api instead of xhr.
  42. fetch api is used if
  43. -allow_fetchAPI is true AND
  44. -(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)
  45. */
  46. var useFetchAPI = false
  47. /*
  48. this function is used on URLs passed in the settings to determine whether we need a ? or an & as a separator
  49. */
  50. function url_sep (url) { return url.match(/\?/) ? '&' : '?'; }
  51. /*
  52. listener for commands from main thread to this worker.
  53. commands:
  54. -status: returns the current status as a string of values spearated by a semicolon (;) in this order: testStatus;dlStatus;ulStatus;pingStatus;clientIp;jitterStatus
  55. -abort: aborts the current test
  56. -start: starts the test. optionally, settings can be passed as JSON.
  57. example: start {"time_ul":"10", "time_dl":"10", "count_ping":"50"}
  58. */
  59. this.addEventListener('message', function (e) {
  60. var params = e.data.split(' ')
  61. if (params[0] === 'status') { // return status
  62. postMessage(testStatus + ';' + dlStatus + ';' + ulStatus + ';' + pingStatus + ';' + clientIp + ';' + jitterStatus)
  63. }
  64. if (params[0] === 'start' && testStatus === 0) { // start new test
  65. testStatus = 1
  66. try {
  67. // parse settings, if present
  68. var s = {}
  69. try{
  70. var ss = e.data.substring(5);
  71. if (ss) s = JSON.parse(ss);
  72. }catch(e){ console.warn("Error parsing custom settings JSON. Please check your syntax"); }
  73. if (typeof s.url_dl !== 'undefined') settings.url_dl = s.url_dl // download url
  74. if (typeof s.url_ul !== 'undefined') settings.url_ul = s.url_ul // upload url
  75. if (typeof s.url_ping !== 'undefined') settings.url_ping = s.url_ping // ping url
  76. if (typeof s.url_getIp !== 'undefined') settings.url_getIp = s.url_getIp // url to getIP.php
  77. if (typeof s.time_dl !== 'undefined') settings.time_dl = s.time_dl // duration of download test
  78. if (typeof s.time_ul !== 'undefined') settings.time_ul = s.time_ul // duration of upload test
  79. if (typeof s.enable_quirks !== 'undefined') settings.enable_quirks = s.enable_quirks // enable quirks or not
  80. // quirks for specific browsers. more may be added in future releases
  81. if (settings.enable_quirks) {
  82. var ua = navigator.userAgent
  83. if (/Firefox.(\d+\.\d+)/i.test(ua)) {
  84. // ff more precise with 1 upload stream
  85. settings.xhr_ulMultistream = 1
  86. }
  87. if (/Edge.(\d+\.\d+)/i.test(ua)) {
  88. // edge more precise with 3 download streams
  89. settings.xhr_dlMultistream = 3
  90. 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
  91. }
  92. if (/Chrome.(\d+)/i.test(ua) && (!!self.fetch)) {
  93. // chrome more precise with 5 streams
  94. settings.xhr_dlMultistream = 5
  95. }
  96. }
  97. if (typeof s.count_ping !== 'undefined') settings.count_ping = s.count_ping // number of pings for ping test
  98. if (typeof s.xhr_dlMultistream !== 'undefined') settings.xhr_dlMultistream = s.xhr_dlMultistream // number of download streams
  99. if (typeof s.xhr_ulMultistream !== 'undefined') settings.xhr_ulMultistream = s.xhr_ulMultistream // number of upload streams
  100. if (typeof s.xhr_ignoreErrors !== 'undefined') settings.xhr_ignoreErrors = s.xhr_ignoreErrors // what to do in case of errors during the test
  101. if (typeof s.xhr_dlUseBlob !== 'undefined') settings.xhr_dlUseBlob = s.xhr_dlUseBlob // use blob for download test
  102. if (typeof s.garbagePhp_chunkSize !== 'undefined') settings.garbagePhp_chunkSize = s.garbagePhp_chunkSize // size of garbage.php chunks
  103. if (typeof s.time_dlGraceTime !== 'undefined') settings.time_dlGraceTime = s.time_dlGraceTime // dl test grace time before measuring
  104. if (typeof s.time_ulGraceTime !== 'undefined') settings.time_ulGraceTime = s.time_ulGraceTime // ul test grace time before measuring
  105. if (typeof s.overheadCompensationFactor !== 'undefined') settings.overheadCompensationFactor = s.overheadCompensationFactor //custom overhead compensation factor (default assumes HTTP+TCP+IP+ETH with typical MTUs)
  106. if (typeof s.telemetry_level !== 'undefined') settings.telemetry_level = s.telemetry_level === "basic" ? 1 : s.telemetry_level === "full" ? 2 : 0; // telemetry level
  107. if (typeof s.url_telemetry !== 'undefined') settings.url_telemetry = s.url_telemetry // url to telemetry.php
  108. } catch (e) { twarn("Possible error in custom test settings. Some settings may not be applied. Exception: "+e) }
  109. // run the tests
  110. tlog(JSON.stringify(settings))
  111. getIp(function () { dlTest(function () { testStatus = 2; pingTest(function () { testStatus = 3; ulTest(function () { testStatus = 4; sendTelemetry() }) }) }) })
  112. }
  113. if (params[0] === 'abort') { // abort command
  114. tlog("manually aborted");
  115. clearRequests() // stop all xhr activity
  116. if (interval) clearInterval(interval) // clear timer if present
  117. if (settings.telemetry_level > 1) sendTelemetry()
  118. testStatus = 5; dlStatus = ''; ulStatus = ''; pingStatus = ''; jitterStatus = '' // set test as aborted
  119. }
  120. })
  121. // stops all XHR activity, aggressively
  122. function clearRequests () {
  123. tlog("stopping pending XHRs");
  124. if (xhr) {
  125. for (var i = 0; i < xhr.length; i++) {
  126. if (useFetchAPI) try { xhr[i].cancelRequested = true } catch (e) { }
  127. try { xhr[i].onprogress = null; xhr[i].onload = null; xhr[i].onerror = null } catch (e) { }
  128. try { xhr[i].upload.onprogress = null; xhr[i].upload.onload = null; xhr[i].upload.onerror = null } catch (e) { }
  129. try { xhr[i].abort() } catch (e) { }
  130. try { delete (xhr[i]) } catch (e) { }
  131. }
  132. xhr = null
  133. }
  134. }
  135. // gets client's IP using url_getIp, then calls the done function
  136. function getIp (done) {
  137. tlog("getIp");
  138. if (settings.url_getIp == "-1") {done(); return}
  139. xhr = new XMLHttpRequest()
  140. xhr.onload = function () {
  141. tlog("IP: "+xhr.responseText);
  142. clientIp = xhr.responseText
  143. done()
  144. }
  145. xhr.onerror = function () {
  146. tlog("getIp failed");
  147. done()
  148. }
  149. xhr.open('GET', settings.url_getIp + url_sep(settings.url_getIp) + 'r=' + Math.random(), true)
  150. xhr.send()
  151. }
  152. // download test, calls done function when it's over
  153. var dlCalled = false // used to prevent multiple accidental calls to dlTest
  154. function dlTest (done) {
  155. tlog("dlTest")
  156. if (dlCalled) return; else dlCalled = true // dlTest already called?
  157. if (settings.url_dl == "-1") {done(); return}
  158. var totLoaded = 0.0, // total number of loaded bytes
  159. startT = new Date().getTime(), // timestamp when test was started
  160. graceTimeDone = false, //set to true after the grace time is past
  161. failed = false // set to true if a stream fails
  162. xhr = []
  163. // function to create a download stream. streams are slightly delayed so that they will not end at the same time
  164. var testStream = function (i, delay) {
  165. setTimeout(function () {
  166. if (testStatus !== 1) return // delayed stream ended up starting after the end of the download test
  167. tlog("dl test stream started "+i+" "+delay)
  168. var prevLoaded = 0 // number of bytes loaded last time onprogress was called
  169. var x = new XMLHttpRequest()
  170. xhr[i] = x
  171. xhr[i].onprogress = function (event) {
  172. tlog("dl stream progress event "+i+" "+event.loaded);
  173. if (testStatus !== 1) { try { x.abort() } catch (e) { } } // just in case this XHR is still running after the download test
  174. // progress event, add number of new loaded bytes to totLoaded
  175. var loadDiff = event.loaded <= 0 ? 0 : (event.loaded - prevLoaded)
  176. if (isNaN(loadDiff) || !isFinite(loadDiff) || loadDiff < 0) return // just in case
  177. totLoaded += loadDiff
  178. prevLoaded = event.loaded
  179. }.bind(this)
  180. xhr[i].onload = function () {
  181. // the large file has been loaded entirely, start again
  182. tlog("dl stream finished "+i)
  183. try { xhr[i].abort() } catch (e) { } // reset the stream data to empty ram
  184. testStream(i, 0)
  185. }.bind(this)
  186. xhr[i].onerror = function () {
  187. // error
  188. tlog("dl stream failed "+i);
  189. if (settings.xhr_ignoreErrors === 0) failed=true //abort
  190. try { xhr[i].abort() } catch (e) { }
  191. delete (xhr[i])
  192. if (settings.xhr_ignoreErrors === 1) testStream(i, 100) //restart stream after 100ms
  193. }.bind(this)
  194. // send xhr
  195. try { if (settings.xhr_dlUseBlob) xhr[i].responseType = 'blob'; else xhr[i].responseType = 'arraybuffer' } catch (e) { }
  196. xhr[i].open('GET', settings.url_dl + url_sep(settings.url_dl) + 'r=' + Math.random() + '&ckSize=' + settings.garbagePhp_chunkSize, true) // random string to prevent caching
  197. xhr[i].send()
  198. }.bind(this), 1 + delay)
  199. }.bind(this)
  200. // open streams
  201. for (var i = 0; i < settings.xhr_dlMultistream; i++) {
  202. testStream(i, 100 * i)
  203. }
  204. // every 200ms, update dlStatus
  205. interval = setInterval(function () {
  206. tlog("DL: "+dlStatus+(graceTimeDone?"":" (in grace time)"))
  207. var t = new Date().getTime() - startT
  208. if (t < 200) return
  209. if (!graceTimeDone){
  210. if (t > 1000 * settings.time_dlGraceTime){
  211. if (totLoaded > 0){ // if the connection is so slow that we didn't get a single chunk yet, do not reset
  212. startT = new Date().getTime()
  213. totLoaded = 0.0;
  214. }
  215. graceTimeDone = true;
  216. }
  217. }else{
  218. var speed = totLoaded / (t / 1000.0)
  219. dlStatus = ((speed * 8 * settings.overheadCompensationFactor)/1048576).toFixed(2) // speed is multiplied by 8 to go from bytes to bits, overhead compensation is applied, then everything is divided by 1048576 to go to megabits/s
  220. if (((t / 1000.0) > settings.time_dl && dlStatus > 0) || failed) { // test is over, stop streams and timer
  221. if (failed || isNaN(dlStatus)) dlStatus = 'Fail'
  222. clearRequests()
  223. clearInterval(interval)
  224. tlog("dlTest finished "+dlStatus)
  225. done()
  226. }
  227. }
  228. }.bind(this), 200)
  229. }
  230. // upload test, calls done function whent it's over
  231. // garbage data for upload test
  232. var r = new ArrayBuffer(1048576)
  233. try { r = new Float32Array(r); for (var i = 0; i < r.length; i++)r[i] = Math.random() } catch (e) { }
  234. var req = []
  235. var reqsmall = []
  236. for (var i = 0; i < 20; i++) req.push(r)
  237. req = new Blob(req)
  238. r = new ArrayBuffer(262144)
  239. try { r = new Float32Array(r); for (var i = 0; i < r.length; i++)r[i] = Math.random() } catch (e) { }
  240. reqsmall.push(r)
  241. reqsmall = new Blob(reqsmall)
  242. var ulCalled = false // used to prevent multiple accidental calls to ulTest
  243. function ulTest (done) {
  244. tlog("ulTest");
  245. if (ulCalled) return; else ulCalled = true // ulTest already called?
  246. if (settings.url_ul == "-1") {done(); return}
  247. var totLoaded = 0.0, // total number of transmitted bytes
  248. startT = new Date().getTime(), // timestamp when test was started
  249. graceTimeDone = false, //set to true after the grace time is past
  250. failed = false // set to true if a stream fails
  251. xhr = []
  252. // function to create an upload stream. streams are slightly delayed so that they will not end at the same time
  253. var testStream = function (i, delay) {
  254. setTimeout(function () {
  255. if (testStatus !== 3) return // delayed stream ended up starting after the end of the upload test
  256. tlog("ul test stream started "+i+" "+delay)
  257. var prevLoaded = 0 // number of bytes transmitted last time onprogress was called
  258. var x = new XMLHttpRequest()
  259. xhr[i] = x
  260. var ie11workaround
  261. if (settings.forceIE11Workaround) ie11workaround = true; else {
  262. try {
  263. xhr[i].upload.onprogress
  264. ie11workaround = false
  265. } catch (e) {
  266. ie11workaround = true
  267. }
  268. }
  269. if (ie11workaround) {
  270. // 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
  271. xhr[i].onload = function () {
  272. tlog("ul stream progress event (ie11wa)")
  273. totLoaded += 262144
  274. testStream(i, 0)
  275. }
  276. xhr[i].onerror = function () {
  277. // error, abort
  278. tlog("ul stream failed (ie11wa)")
  279. if (settings.xhr_ignoreErrors === 0) failed = true //abort
  280. try { xhr[i].abort() } catch (e) { }
  281. delete (xhr[i])
  282. if (settings.xhr_ignoreErrors === 1) testStream(i,100); //restart stream after 100ms
  283. }
  284. xhr[i].open('POST', settings.url_ul + url_sep(settings.url_ul) + 'r=' + Math.random(), true) // random string to prevent caching
  285. xhr[i].setRequestHeader('Content-Encoding', 'identity') // disable compression (some browsers may refuse it, but data is incompressible anyway)
  286. xhr[i].send(reqsmall)
  287. } else {
  288. // REGULAR version, no workaround
  289. xhr[i].upload.onprogress = function (event) {
  290. tlog("ul stream progress event "+i+" "+event.loaded);
  291. if (testStatus !== 3) { try { x.abort() } catch (e) { } } // just in case this XHR is still running after the upload test
  292. // progress event, add number of new loaded bytes to totLoaded
  293. var loadDiff = event.loaded <= 0 ? 0 : (event.loaded - prevLoaded)
  294. if (isNaN(loadDiff) || !isFinite(loadDiff) || loadDiff < 0) return // just in case
  295. totLoaded += loadDiff
  296. prevLoaded = event.loaded
  297. }.bind(this)
  298. xhr[i].upload.onload = function () {
  299. // this stream sent all the garbage data, start again
  300. tlog("ul stream finished "+i);
  301. testStream(i, 0)
  302. }.bind(this)
  303. xhr[i].upload.onerror = function () {
  304. tlog("ul stream failed "+i);
  305. if (settings.xhr_ignoreErrors === 0) failed=true //abort
  306. try { xhr[i].abort() } catch (e) { }
  307. delete (xhr[i])
  308. if (settings.xhr_ignoreErrors === 1) testStream(i, 100) //restart stream after 100ms
  309. }.bind(this)
  310. // send xhr
  311. xhr[i].open('POST', settings.url_ul + url_sep(settings.url_ul) + 'r=' + Math.random(), true) // random string to prevent caching
  312. xhr[i].setRequestHeader('Content-Encoding', 'identity') // disable compression (some browsers may refuse it, but data is incompressible anyway)
  313. xhr[i].send(req)
  314. }
  315. }.bind(this), 1)
  316. }.bind(this)
  317. // open streams
  318. for (var i = 0; i < settings.xhr_ulMultistream; i++) {
  319. testStream(i, 100 * i)
  320. }
  321. // every 200ms, update ulStatus
  322. interval = setInterval(function () {
  323. tlog("UL: "+ulStatus+(graceTimeDone?"":" (in grace time)"))
  324. var t = new Date().getTime() - startT
  325. if (t < 200) return
  326. if (!graceTimeDone){
  327. if (t > 1000 * settings.time_ulGraceTime){
  328. if (totLoaded > 0){ // if the connection is so slow that we didn't get a single chunk yet, do not reset
  329. startT = new Date().getTime()
  330. totLoaded = 0.0;
  331. }
  332. graceTimeDone = true;
  333. }
  334. }else{
  335. var speed = totLoaded / (t / 1000.0)
  336. ulStatus = ((speed * 8 * settings.overheadCompensationFactor)/1048576).toFixed(2) // speed is multiplied by 8 to go from bytes to bits, overhead compensation is applied, then everything is divided by 1048576 to go to megabits/s
  337. if (((t / 1000.0) > settings.time_ul && ulStatus > 0) || failed) { // test is over, stop streams and timer
  338. if (failed || isNaN(ulStatus)) ulStatus = 'Fail'
  339. clearRequests()
  340. clearInterval(interval)
  341. tlog("ulTest finished "+ulStatus)
  342. done()
  343. }
  344. }
  345. }.bind(this), 200)
  346. }
  347. // ping+jitter test, function done is called when it's over
  348. var ptCalled = false // used to prevent multiple accidental calls to pingTest
  349. function pingTest (done) {
  350. tlog("pingTest");
  351. if (ptCalled) return; else ptCalled = true // pingTest already called?
  352. if (settings.url_ping == "-1") {done(); return}
  353. var prevT = null // last time a pong was received
  354. var ping = 0.0 // current ping value
  355. var jitter = 0.0 // current jitter value
  356. var i = 0 // counter of pongs received
  357. var prevInstspd = 0 // last ping time, used for jitter calculation
  358. xhr = []
  359. // ping function
  360. var doPing = function () {
  361. tlog("ping");
  362. prevT = new Date().getTime()
  363. xhr[0] = new XMLHttpRequest()
  364. xhr[0].onload = function () {
  365. // pong
  366. tlog("pong");
  367. if (i === 0) {
  368. prevT = new Date().getTime() // first pong
  369. } else {
  370. var instspd = (new Date().getTime() - prevT)
  371. var instjitter = Math.abs(instspd - prevInstspd)
  372. if (i === 1) ping = instspd; /* first ping, can't tell jitter yet*/ else {
  373. ping = ping * 0.9 + instspd * 0.1 // ping, weighted average
  374. 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.
  375. }
  376. prevInstspd = instspd
  377. }
  378. pingStatus = ping.toFixed(2)
  379. jitterStatus = jitter.toFixed(2)
  380. i++
  381. tlog("PING: "+pingStatus+" JITTER: "+jitterStatus)
  382. if (i < settings.count_ping) doPing(); else done() // more pings to do?
  383. }.bind(this)
  384. xhr[0].onerror = function () {
  385. // a ping failed, cancel test
  386. tlog("ping failed");
  387. if (settings.xhr_ignoreErrors === 0) { //abort
  388. pingStatus = 'Fail'
  389. jitterStatus = 'Fail'
  390. clearRequests()
  391. done()
  392. }
  393. if (settings.xhr_ignoreErrors === 1) doPing() //retry ping
  394. if (settings.xhr_ignoreErrors === 2){ //ignore failed ping
  395. i++
  396. if (i < settings.count_ping) doPing(); else done() // more pings to do?
  397. }
  398. }.bind(this)
  399. // sent xhr
  400. xhr[0].open('GET', settings.url_ping + url_sep(settings.url_ping) + 'r=' + Math.random(), true) // random string to prevent caching
  401. xhr[0].send()
  402. }.bind(this)
  403. doPing() // start first ping
  404. }
  405. // telemetry
  406. function sendTelemetry(){
  407. if (settings.telemetry_level < 1) return
  408. xhr = new XMLHttpRequest();
  409. xhr.onload = function () { console.log("TELEMETRY OL "+xhr.responseText) }
  410. xhr.onerror = function () { console.log("TELEMETRY ERROR "+xhr) }
  411. xhr.open('POST', settings.url_telemetry+"?r="+Math.random(), true);
  412. try{
  413. var fd = new FormData()
  414. fd.append('dl', dlStatus)
  415. fd.append('ul', ulStatus)
  416. fd.append('ping', pingStatus)
  417. fd.append('jitter', jitterStatus)
  418. fd.append('log', settings.telemetry_level>1?log:"")
  419. xhr.send(fd)
  420. }catch(ex){
  421. var postData = "dl="+encodeURIComponent(dlStatus)+"&ul="+encodeURIComponent(ulStatus)+"&ping="+encodeURIComponent(pingStatus)+"&jitter="+encodeURIComponent(jitterStatus)+"&log="+encodeURIComponent(settings.telemetry_level>1?log:"")
  422. xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
  423. xhr.send(postData)
  424. }
  425. }