2024-02-19 19:49:34 +01:00
|
|
|
<script>
|
|
|
|
import { onMount } from "svelte";
|
|
|
|
import Button from "../layout/Button.svelte";
|
|
|
|
import { formatDataVolume, formatDataVolumeBits } from "../util/Formatting.svelte";
|
|
|
|
import ProgressBar from "../util/ProgressBar.svelte";
|
|
|
|
import { copy_text } from "../util/Util.svelte";
|
|
|
|
|
|
|
|
let running = false
|
|
|
|
let data_received = 0
|
2024-02-20 00:09:09 +01:00
|
|
|
const update_interval = 100
|
2024-02-19 19:49:34 +01:00
|
|
|
let latency = 0
|
2024-02-20 13:52:54 +01:00
|
|
|
const start = async (dur = 10000) => {
|
2024-02-19 19:49:34 +01:00
|
|
|
if (running) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
running = true
|
|
|
|
data_received = 0
|
|
|
|
|
2024-02-20 00:09:09 +01:00
|
|
|
const latency_start = Date.now()
|
2024-02-19 19:49:34 +01:00
|
|
|
|
|
|
|
// Start a request for 10 GB of random data. We omit credentials so the
|
|
|
|
// server does fetch the API key from the database which increases latency
|
2024-02-20 00:09:09 +01:00
|
|
|
const req = await fetch(
|
2024-02-19 19:49:34 +01:00
|
|
|
window.api_endpoint+"/misc/speedtest?limit="+10e9,
|
|
|
|
{credentials: "omit"},
|
|
|
|
)
|
|
|
|
|
|
|
|
// Measure request latency
|
|
|
|
latency = Date.now() - latency_start
|
|
|
|
|
2024-02-20 00:09:09 +01:00
|
|
|
const reader = req.body.getReader();
|
2024-02-19 19:49:34 +01:00
|
|
|
|
2024-02-20 13:52:54 +01:00
|
|
|
measure_speed(() => reader.cancel(), dur)
|
2024-02-19 19:49:34 +01:00
|
|
|
|
|
|
|
while(true) {
|
2024-02-20 00:09:09 +01:00
|
|
|
const {done, value} = await reader.read()
|
2024-02-19 19:49:34 +01:00
|
|
|
if (done) {
|
|
|
|
break;
|
|
|
|
}
|
2024-02-20 00:09:09 +01:00
|
|
|
data_received += value.byteLength;
|
2024-02-19 19:49:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
running = false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Average speed for the whole test
|
|
|
|
let speed = 0
|
|
|
|
let result_link = ""
|
|
|
|
|
2024-02-20 13:32:38 +01:00
|
|
|
let progress_duration = 0
|
|
|
|
let progress_unchanged = 0
|
|
|
|
|
|
|
|
const measure_speed = (stop, test_duration) => {
|
2024-02-20 00:09:09 +01:00
|
|
|
speed = 0
|
|
|
|
result_link = ""
|
|
|
|
|
2024-02-20 13:32:38 +01:00
|
|
|
// Updates per second
|
|
|
|
const ups = (1000/update_interval)
|
|
|
|
|
|
|
|
// This slice contains the speed measurements for four seconds of the test.
|
|
|
|
// This value is averaged and if the average is higher than the previously
|
|
|
|
// calculated average then it is saved. The resulting speed is the highest
|
|
|
|
// speed that was sustained for four seconds at any point in the test
|
|
|
|
const hist = new Uint32Array(ups*2)
|
2024-02-19 19:49:34 +01:00
|
|
|
let idx = 0
|
2024-02-20 13:32:38 +01:00
|
|
|
|
|
|
|
// This var measures for how many ticks the max speed has not changed. When
|
|
|
|
// the speed has not changed for a third of the test duration the test is
|
|
|
|
// considered over
|
|
|
|
let unchanged = 0
|
|
|
|
const unchanged_limit = (test_duration/3)/update_interval
|
|
|
|
|
2024-02-19 19:49:34 +01:00
|
|
|
let previous_transferred = 0
|
2024-02-20 00:09:09 +01:00
|
|
|
const start = Date.now()
|
2024-02-19 19:49:34 +01:00
|
|
|
|
2024-02-20 00:09:09 +01:00
|
|
|
console.debug("Test duration", test_duration, "interval", update_interval, "history", hist.length)
|
2024-02-19 19:49:34 +01:00
|
|
|
|
|
|
|
let measure = async () => {
|
2024-02-20 00:09:09 +01:00
|
|
|
// Update the speed measurement
|
|
|
|
hist[idx%hist.length] = data_received - previous_transferred
|
2024-02-19 19:49:34 +01:00
|
|
|
previous_transferred = data_received
|
|
|
|
idx++
|
|
|
|
|
2024-02-20 00:09:09 +01:00
|
|
|
// Calculate the average of all the speed measurements
|
2024-02-20 13:32:38 +01:00
|
|
|
const sum = hist.reduce((acc, val) => {
|
|
|
|
if (val !== 0) {
|
|
|
|
acc.sum += val
|
|
|
|
acc.count++
|
|
|
|
}
|
|
|
|
return acc
|
|
|
|
}, {sum: 0, count: 0})
|
|
|
|
const new_speed = (sum.sum/sum.count)*ups
|
2024-02-20 00:09:09 +01:00
|
|
|
if (new_speed > speed) {
|
|
|
|
speed = new_speed
|
2024-02-20 13:32:38 +01:00
|
|
|
unchanged = 0
|
|
|
|
} else {
|
|
|
|
unchanged++
|
2024-02-20 00:09:09 +01:00
|
|
|
}
|
2024-02-19 19:49:34 +01:00
|
|
|
|
2024-02-20 13:32:38 +01:00
|
|
|
// Update the duration of the test. Used for calculating progress and
|
|
|
|
// clock drift
|
|
|
|
const current_duration = Date.now() - start
|
2024-02-19 19:49:34 +01:00
|
|
|
|
2024-02-20 13:32:38 +01:00
|
|
|
// Update the progress bar
|
|
|
|
progress_unchanged = unchanged/unchanged_limit
|
|
|
|
progress_duration = current_duration/test_duration
|
|
|
|
|
|
|
|
if (idx < test_duration/update_interval && unchanged < unchanged_limit) {
|
2024-02-19 19:49:34 +01:00
|
|
|
// We have to manually calculate and subtract drift, because in my
|
|
|
|
// tests with setInterval the clock would drift like 200ms in a
|
|
|
|
// single test which significantly impacts results
|
|
|
|
setTimeout(measure, update_interval - (current_duration-(idx*update_interval)))
|
|
|
|
} else {
|
2024-02-20 00:09:09 +01:00
|
|
|
// Test is done, break the reader out of the counting loop
|
2024-02-20 13:32:38 +01:00
|
|
|
await stop()
|
2024-02-19 19:49:34 +01:00
|
|
|
|
2024-02-20 13:32:38 +01:00
|
|
|
console.debug(
|
|
|
|
"Done! Test ran for", current_duration,
|
|
|
|
"result did not change for", unchanged*update_interval,
|
|
|
|
)
|
|
|
|
progress_unchanged = 0
|
|
|
|
progress_duration = 0
|
2024-02-20 00:09:09 +01:00
|
|
|
|
|
|
|
// Update the URL so the results can be shared
|
2024-02-19 19:49:34 +01:00
|
|
|
history.replaceState(
|
|
|
|
undefined,
|
|
|
|
undefined,
|
|
|
|
"#s="+speed+"&l="+latency+"&t="+data_received,
|
|
|
|
)
|
|
|
|
result_link = window.location.href
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-20 00:09:09 +01:00
|
|
|
// Start the measurement loop
|
2024-02-19 19:49:34 +01:00
|
|
|
setTimeout(measure, update_interval)
|
|
|
|
}
|
|
|
|
|
|
|
|
onMount(() => {
|
2024-02-20 00:09:09 +01:00
|
|
|
// Parse the results saved in the URL, if any
|
2024-02-19 19:49:34 +01:00
|
|
|
if (window.location.hash[0] === "#") {
|
2024-02-20 00:09:09 +01:00
|
|
|
const hash = window.location.hash.replace("#", "");
|
|
|
|
const result = hash.replace("#", "").split('&').reduce((res, item) => {
|
|
|
|
const parts = item.split('=')
|
|
|
|
const n = Number(parts[1])
|
2024-02-19 19:49:34 +01:00
|
|
|
if (n !== NaN) {
|
|
|
|
res[parts[0]] = n
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
if (result.s && result.l && result.t) {
|
|
|
|
speed = result.s
|
|
|
|
latency = result.l
|
|
|
|
data_received = result.t
|
|
|
|
result_link = window.location.href
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<section class="highlight_border">
|
|
|
|
<div style="text-align: center">
|
2024-02-20 13:52:54 +01:00
|
|
|
<Button icon="speed" label="Start test" click={() => start(15000)} disabled={running} highlight={!running}/>
|
|
|
|
<Button icon="speed" label="Long test" click={() => start(30000)} disabled={running}/>
|
2024-02-19 19:49:34 +01:00
|
|
|
<Button
|
|
|
|
highlight_on_click
|
|
|
|
disabled={result_link === ""}
|
|
|
|
icon="content_copy"
|
|
|
|
label="Copy test result"
|
|
|
|
click={e => copy_text(result_link)}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
2024-02-20 13:32:38 +01:00
|
|
|
<!-- This progress bar shows either the progress for the test duration, or
|
|
|
|
when the test will time out. Whichever is higher -->
|
|
|
|
<ProgressBar
|
|
|
|
animation="linear"
|
|
|
|
speed={update_interval}
|
|
|
|
used={Math.max(progress_unchanged, progress_duration)}
|
|
|
|
total={1}
|
|
|
|
/>
|
2024-02-19 19:49:34 +01:00
|
|
|
|
|
|
|
<div class="speed_stats">
|
|
|
|
<div class="highlight_shaded">{formatDataVolume(speed, 4)}/s</div>
|
|
|
|
<div class="highlight_shaded">{formatDataVolumeBits(speed, 4)}ps</div>
|
|
|
|
<div class="highlight_shaded">Latency {latency}ms</div>
|
|
|
|
<div class="highlight_shaded">Received {formatDataVolume(data_received, 3)}</div>
|
|
|
|
</div>
|
2024-02-20 00:09:09 +01:00
|
|
|
<!-- Progress bar starts at log10(6) because the we want the lowest speed
|
|
|
|
shown to be 1 Mbps (1e6 bits) -->
|
|
|
|
<ProgressBar animation="linear" speed={update_interval} used={Math.log10(speed*8)-6} total={4}/>
|
2024-02-19 19:49:34 +01:00
|
|
|
|
|
|
|
<div class="speed_grid">
|
|
|
|
<div>↑</div>
|
|
|
|
<div>↑</div>
|
|
|
|
<div>↑</div>
|
|
|
|
<div>↑</div>
|
|
|
|
<div>↑</div>
|
|
|
|
</div>
|
|
|
|
<div class="speed_grid">
|
|
|
|
<div>1 Mb</div>
|
|
|
|
<div>10 Mb</div>
|
|
|
|
<div>100 Mb</div>
|
|
|
|
<div>1 Gb</div>
|
|
|
|
<div>10 Gb</div>
|
|
|
|
</div>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
<style>
|
|
|
|
.speed_stats {
|
|
|
|
display: flex;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
gap: 4px;
|
|
|
|
font-size: 1.5em;
|
|
|
|
}
|
|
|
|
.speed_stats > * {
|
|
|
|
flex: 1 0 9em;
|
|
|
|
}
|
|
|
|
.speed_grid {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
line-height: 1em;
|
|
|
|
}
|
|
|
|
.speed_grid > * {
|
|
|
|
flex: 0 0 auto;
|
|
|
|
}
|
|
|
|
</style>
|