Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
feat: add bandwidth and latency testing functionality with progress tracking
Browse files- src/app.css +28 -0
- src/lib/index.ts +12 -7
- src/routes/+page.svelte +54 -10
src/app.css
CHANGED
|
@@ -1,2 +1,30 @@
|
|
| 1 |
@import 'tailwindcss';
|
| 2 |
@plugin '@tailwindcss/typography';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
@import 'tailwindcss';
|
| 2 |
@plugin '@tailwindcss/typography';
|
| 3 |
+
|
| 4 |
+
.progress-bar {
|
| 5 |
+
height: 9px;
|
| 6 |
+
border-radius: 4px;
|
| 7 |
+
background: linear-gradient(90deg, #4f46e5 0%, #10b981 50%, #f59e0b 100%);
|
| 8 |
+
transition: width 0.3s ease;
|
| 9 |
+
}
|
| 10 |
+
.wave {
|
| 11 |
+
position: relative;
|
| 12 |
+
overflow: hidden;
|
| 13 |
+
}
|
| 14 |
+
.wave::after {
|
| 15 |
+
content: "";
|
| 16 |
+
position: absolute;
|
| 17 |
+
top: 0;
|
| 18 |
+
left: 0;
|
| 19 |
+
right: 0;
|
| 20 |
+
bottom: 0;
|
| 21 |
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
| 22 |
+
animation: wave 2s linear infinite;
|
| 23 |
+
}
|
| 24 |
+
@keyframes wave {
|
| 25 |
+
0% { transform: translateX(-100%); }
|
| 26 |
+
100% { transform: translateX(100%); }
|
| 27 |
+
}
|
| 28 |
+
.glow {
|
| 29 |
+
box-shadow: 0 0 15px rgba(79, 70, 229, 0.5);
|
| 30 |
+
}
|
src/lib/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ interface BandwidthCallback {
|
|
| 7 |
totalBytes: number,
|
| 8 |
bytesPerSecond: number,
|
| 9 |
done: boolean
|
| 10 |
-
):
|
| 11 |
}
|
| 12 |
|
| 13 |
export async function bandwidthTest(
|
|
@@ -18,8 +18,8 @@ export async function bandwidthTest(
|
|
| 18 |
performance.clearResourceTimings();
|
| 19 |
// start timer
|
| 20 |
const startTime = performance.now();
|
| 21 |
-
|
| 22 |
-
const url = 'https://cdn-test-cloudfront.hf.co/15mb.json';
|
| 23 |
const response = await fetch(url);
|
| 24 |
if (!response.ok) {
|
| 25 |
throw new Error(`Network response was not ok: ${response.status}`);
|
|
@@ -37,15 +37,15 @@ export async function bandwidthTest(
|
|
| 37 |
// }, 2000);
|
| 38 |
const latency = performance.now() - startTime;
|
| 39 |
onLatency(latency);
|
| 40 |
-
const contentLengthHeader = response.headers.get('
|
| 41 |
-
const totalBytes = contentLengthHeader ? parseInt(contentLengthHeader, 10) :
|
| 42 |
const reader = response.body.getReader();
|
| 43 |
let loadedBytes = 0;
|
| 44 |
let lastTimestamp = performance.now();
|
| 45 |
let lastLoaded = 0;
|
| 46 |
const REPORT_INTERVAL_MS = 500;
|
| 47 |
onProgress(latency, loadedBytes, totalBytes, 0, false);
|
| 48 |
-
let bytesPerSecond= 0;
|
| 49 |
while (true) {
|
| 50 |
const { done, value } = await reader.read();
|
| 51 |
if (done) {
|
|
@@ -71,7 +71,12 @@ export async function bandwidthTest(
|
|
| 71 |
|
| 72 |
// Invoke callback
|
| 73 |
const elapsedMs = performance.now() - startTime;
|
| 74 |
-
onProgress(elapsedMs, loadedBytes, totalBytes, bytesPerSecond, false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
// Reset our “last” markers
|
| 77 |
lastLoaded = loadedBytes;
|
|
|
|
| 7 |
totalBytes: number,
|
| 8 |
bytesPerSecond: number,
|
| 9 |
done: boolean
|
| 10 |
+
): boolean;
|
| 11 |
}
|
| 12 |
|
| 13 |
export async function bandwidthTest(
|
|
|
|
| 18 |
performance.clearResourceTimings();
|
| 19 |
// start timer
|
| 20 |
const startTime = performance.now();
|
| 21 |
+
const url = 'https://cdn-test-cloudfront.hf.co/5gb.safetensors';
|
| 22 |
+
// const url = 'https://cdn-test-cloudfront.hf.co/15mb.json';
|
| 23 |
const response = await fetch(url);
|
| 24 |
if (!response.ok) {
|
| 25 |
throw new Error(`Network response was not ok: ${response.status}`);
|
|
|
|
| 37 |
// }, 2000);
|
| 38 |
const latency = performance.now() - startTime;
|
| 39 |
onLatency(latency);
|
| 40 |
+
const contentLengthHeader = response.headers.get('content-length');
|
| 41 |
+
const totalBytes = contentLengthHeader ? parseInt(contentLengthHeader, 10) : 1e99;
|
| 42 |
const reader = response.body.getReader();
|
| 43 |
let loadedBytes = 0;
|
| 44 |
let lastTimestamp = performance.now();
|
| 45 |
let lastLoaded = 0;
|
| 46 |
const REPORT_INTERVAL_MS = 500;
|
| 47 |
onProgress(latency, loadedBytes, totalBytes, 0, false);
|
| 48 |
+
let bytesPerSecond = 0;
|
| 49 |
while (true) {
|
| 50 |
const { done, value } = await reader.read();
|
| 51 |
if (done) {
|
|
|
|
| 71 |
|
| 72 |
// Invoke callback
|
| 73 |
const elapsedMs = performance.now() - startTime;
|
| 74 |
+
const stop = onProgress(elapsedMs, loadedBytes, totalBytes, bytesPerSecond, false);
|
| 75 |
+
if (stop) {
|
| 76 |
+
// stop the test
|
| 77 |
+
console.log(`Stopping bandwidth test at ${loadedBytes} bytes after ${elapsedMs} ms`);
|
| 78 |
+
break;
|
| 79 |
+
}
|
| 80 |
|
| 81 |
// Reset our “last” markers
|
| 82 |
lastLoaded = loadedBytes;
|
src/routes/+page.svelte
CHANGED
|
@@ -5,11 +5,13 @@
|
|
| 5 |
|
| 6 |
Chart.register(...registerables);
|
| 7 |
|
| 8 |
-
let currentBandwidth = $state(
|
| 9 |
let currentLatency = $state(0);
|
|
|
|
| 10 |
let bandwidthMeasurements: number[] = $state([]);
|
| 11 |
let timeMeasurements: string[] = $state([]);
|
| 12 |
-
let
|
|
|
|
| 13 |
let mbps = (bw / 1000000 * 8); // convert Bps to Mbps
|
| 14 |
// update the bandwidth state
|
| 15 |
currentBandwidth = mbps.toFixed(2);
|
|
@@ -21,14 +23,38 @@
|
|
| 21 |
bandwidthMeasurements.shift();
|
| 22 |
timeMeasurements.shift();
|
| 23 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
};
|
| 25 |
let latencyCallback = (latency: number) => {
|
| 26 |
// update the latency state
|
| 27 |
currentLatency = latency;
|
| 28 |
-
console.log('Latency:', latency);
|
| 29 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
//counter(callback);
|
| 31 |
-
bandwidthTest(bandwidthCallback, latencyCallback);
|
| 32 |
|
| 33 |
let canvas: HTMLCanvasElement;
|
| 34 |
|
|
@@ -97,6 +123,22 @@
|
|
| 97 |
speedChart.destroy();
|
| 98 |
};
|
| 99 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
</script>
|
| 101 |
<!-- Main Card -->
|
| 102 |
<div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8 transition-all duration-300 hover:shadow-xl">
|
|
@@ -104,8 +146,8 @@
|
|
| 104 |
<div class="flex items-center justify-between mb-6">
|
| 105 |
<h2 class="text-xl font-semibold text-gray-800">Connection Statistics</h2>
|
| 106 |
<div id="connection-status" class="flex items-center">
|
| 107 |
-
<span class="h-3 w-3 rounded-full
|
| 108 |
-
<span class="text-sm text-gray-500">
|
| 109 |
</div>
|
| 110 |
</div>
|
| 111 |
|
|
@@ -132,22 +174,24 @@
|
|
| 132 |
<div class="mb-6">
|
| 133 |
<div class="flex justify-between mb-2">
|
| 134 |
<span class="text-sm font-medium text-gray-700">Test Progress</span>
|
| 135 |
-
<span id="progress-percent" class="text-sm font-medium text-gray-700">0%</span>
|
| 136 |
</div>
|
| 137 |
<div class="w-full bg-gray-200 rounded-full h-2.5">
|
| 138 |
-
<div id="progress-bar" class="progress-bar h-2.5 rounded-full
|
|
|
|
| 139 |
</div>
|
| 140 |
</div>
|
| 141 |
<!-- Test Controls -->
|
| 142 |
<div class="flex flex-col sm:flex-row justify-center gap-4">
|
| 143 |
<button id="start-test"
|
| 144 |
-
class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-3 px-6 rounded-lg transition-all flex items-center justify-center glow"
|
|
|
|
| 145 |
<i class="fas fa-play mr-2"></i>
|
| 146 |
Start Test
|
| 147 |
</button>
|
| 148 |
<button id="stop-test"
|
| 149 |
class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-3 px-6 rounded-lg transition-all flex items-center justify-center"
|
| 150 |
-
disabled>
|
| 151 |
<i class="fas fa-stop mr-2"></i>
|
| 152 |
Stop Test
|
| 153 |
</button>
|
|
|
|
| 5 |
|
| 6 |
Chart.register(...registerables);
|
| 7 |
|
| 8 |
+
let currentBandwidth = $state('0');
|
| 9 |
let currentLatency = $state(0);
|
| 10 |
+
let progress = $state(0);
|
| 11 |
let bandwidthMeasurements: number[] = $state([]);
|
| 12 |
let timeMeasurements: string[] = $state([]);
|
| 13 |
+
let testStatus = $state('Idle');
|
| 14 |
+
let bandwidthCallback = (elapsedMs: number, loadedBytes: number, totalBytes: number, bw: number, done: boolean) => {
|
| 15 |
let mbps = (bw / 1000000 * 8); // convert Bps to Mbps
|
| 16 |
// update the bandwidth state
|
| 17 |
currentBandwidth = mbps.toFixed(2);
|
|
|
|
| 23 |
bandwidthMeasurements.shift();
|
| 24 |
timeMeasurements.shift();
|
| 25 |
}
|
| 26 |
+
// update the progress state
|
| 27 |
+
progress = (loadedBytes / totalBytes) * 100;
|
| 28 |
+
if (testStatus == 'Stopped') {
|
| 29 |
+
return true;
|
| 30 |
+
}
|
| 31 |
+
if (done) {
|
| 32 |
+
testStatus = 'Completed';
|
| 33 |
+
progress = 100;
|
| 34 |
+
} else {
|
| 35 |
+
testStatus = 'Running';
|
| 36 |
+
}
|
| 37 |
+
return false;
|
| 38 |
};
|
| 39 |
let latencyCallback = (latency: number) => {
|
| 40 |
// update the latency state
|
| 41 |
currentLatency = latency;
|
|
|
|
| 42 |
};
|
| 43 |
+
|
| 44 |
+
const startTest = () => {
|
| 45 |
+
testStatus = 'Running';
|
| 46 |
+
progress = 0;
|
| 47 |
+
bandwidthMeasurements = [];
|
| 48 |
+
timeMeasurements = [];
|
| 49 |
+
currentBandwidth = '0';
|
| 50 |
+
currentLatency = 0;
|
| 51 |
+
bandwidthTest(bandwidthCallback, latencyCallback);
|
| 52 |
+
};
|
| 53 |
+
const stopTest = () => {
|
| 54 |
+
testStatus = 'Stopped';
|
| 55 |
+
}
|
| 56 |
//counter(callback);
|
| 57 |
+
//bandwidthTest(bandwidthCallback, latencyCallback);
|
| 58 |
|
| 59 |
let canvas: HTMLCanvasElement;
|
| 60 |
|
|
|
|
| 123 |
speedChart.destroy();
|
| 124 |
};
|
| 125 |
};
|
| 126 |
+
const getStatusClass = () => {
|
| 127 |
+
switch (testStatus) {
|
| 128 |
+
case 'Idle':
|
| 129 |
+
return 'bg-gray-300';
|
| 130 |
+
case 'Running':
|
| 131 |
+
return 'bg-blue-500';
|
| 132 |
+
case 'Completed':
|
| 133 |
+
return 'bg-green-500';
|
| 134 |
+
case 'Error':
|
| 135 |
+
return 'bg-red-500';
|
| 136 |
+
case 'Stopped':
|
| 137 |
+
return 'bg-red-500';
|
| 138 |
+
default:
|
| 139 |
+
return 'bg-gray-300';
|
| 140 |
+
}
|
| 141 |
+
};
|
| 142 |
</script>
|
| 143 |
<!-- Main Card -->
|
| 144 |
<div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8 transition-all duration-300 hover:shadow-xl">
|
|
|
|
| 146 |
<div class="flex items-center justify-between mb-6">
|
| 147 |
<h2 class="text-xl font-semibold text-gray-800">Connection Statistics</h2>
|
| 148 |
<div id="connection-status" class="flex items-center">
|
| 149 |
+
<span class="h-3 w-3 rounded-full {getStatusClass()} mr-2"></span>
|
| 150 |
+
<span class="text-sm text-gray-500">{testStatus}</span>
|
| 151 |
</div>
|
| 152 |
</div>
|
| 153 |
|
|
|
|
| 174 |
<div class="mb-6">
|
| 175 |
<div class="flex justify-between mb-2">
|
| 176 |
<span class="text-sm font-medium text-gray-700">Test Progress</span>
|
| 177 |
+
<span id="progress-percent" class="text-sm font-medium text-gray-700">{progress.toFixed(0)}%</span>
|
| 178 |
</div>
|
| 179 |
<div class="w-full bg-gray-200 rounded-full h-2.5">
|
| 180 |
+
<div id="progress-bar" class="progress-bar h-2.5 rounded-full {progress<100 && progress >0? 'wave':''}"
|
| 181 |
+
style="width: {progress}%"></div>
|
| 182 |
</div>
|
| 183 |
</div>
|
| 184 |
<!-- Test Controls -->
|
| 185 |
<div class="flex flex-col sm:flex-row justify-center gap-4">
|
| 186 |
<button id="start-test"
|
| 187 |
+
class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-3 px-6 rounded-lg transition-all flex items-center justify-center glow"
|
| 188 |
+
onclick={startTest}>
|
| 189 |
<i class="fas fa-play mr-2"></i>
|
| 190 |
Start Test
|
| 191 |
</button>
|
| 192 |
<button id="stop-test"
|
| 193 |
class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-3 px-6 rounded-lg transition-all flex items-center justify-center"
|
| 194 |
+
disabled={testStatus!=='Running'} onclick={stopTest}>
|
| 195 |
<i class="fas fa-stop mr-2"></i>
|
| 196 |
Stop Test
|
| 197 |
</button>
|