upgrade api

This commit is contained in:
Huakun Shen 2025-03-23 08:04:07 -04:00
parent d14e17eb3d
commit 41f95a7af7
No known key found for this signature in database
18 changed files with 904 additions and 2084 deletions

4
.gitignore vendored
View File

@ -23,3 +23,7 @@ extensions_support/
.pnpm-store
dist/
speedtest/index.js
test.txt
target/

8
build.ts Normal file
View File

@ -0,0 +1,8 @@
import { $ } from "bun";
await Bun.build({
entrypoints: ["speedtest/index.ts"],
outdir: "speedtest",
minify: true,
});
await $`vite build`;

View File

@ -3,6 +3,6 @@
"dev": "deno run --watch main.ts"
},
"imports": {
"@kunkun/api": "jsr:@kunkun/api@^0.0.52"
"@kunkun/api": "jsr:@kunkun/api@^0.1.7"
}
}

48
deno-src/deno.lock generated
View File

@ -1,45 +1,71 @@
{
"version": "4",
"specifiers": {
"jsr:@kunkun/api@^0.0.39": "0.0.39",
"jsr:@kunkun/api@~0.1.7": "0.1.7",
"npm:@types/node@*": "22.5.4",
"npm:kkrpc@^0.0.10": "0.0.10_typescript@5.6.3"
"npm:kkrpc@~0.2.2": "0.2.2_typescript@5.8.2"
},
"jsr": {
"@kunkun/api@0.0.39": {
"integrity": "af1f0728083a6553279a4a7ce12ca83a6affe7dcda09b041376934e6c26e979e",
"@kunkun/api@0.1.7": {
"integrity": "05522131be509dce77900dfe6ba49fe478deffe73fff18970f6996b2b7c2f0f7",
"dependencies": [
"npm:kkrpc"
]
}
},
"npm": {
"@tauri-apps/api@2.3.0": {
"integrity": "sha512-33Z+0lX2wgZbx1SPFfqvzI6su63hCBkbzv+5NexeYjIx7WA9htdOKoRR7Dh3dJyltqS5/J8vQFyybiRoaL0hlA=="
},
"@tauri-apps/plugin-shell@2.2.0": {
"integrity": "sha512-iC3Ic1hLmasoboG7BO+7p+AriSoqAwKrIk+Hpk+S/bjTQdXqbl2GbdclghI4gM32X0bls7xHzIFqhRdrlvJeaA==",
"dependencies": [
"@tauri-apps/api"
]
},
"@types/node@22.5.4": {
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"dependencies": [
"undici-types"
]
},
"kkrpc@0.0.10_typescript@5.6.3": {
"integrity": "sha512-lkQKVnN9f6JrS4ybKbGkV4mtuGhWYLTnaWx60ysytEap+sP5jcTbAuJlSrY6JqlwaohiS0X3ZbvJ2rAXYRdTng==",
"copy-anything@3.0.5": {
"integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
"dependencies": [
"is-what"
]
},
"is-what@4.1.16": {
"integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="
},
"kkrpc@0.2.2_typescript@5.8.2": {
"integrity": "sha512-EliGFPRf+dplMiqNipPUUj89WX9vEWfQkQU05ztbMfdK/SSgnHBbvm7QySGlEIlUb9Y55dSXPkROuxjHz2JbfA==",
"dependencies": [
"@tauri-apps/plugin-shell",
"superjson",
"typescript",
"ws"
]
},
"typescript@5.6.3": {
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="
"superjson@2.2.2": {
"integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
"dependencies": [
"copy-anything"
]
},
"typescript@5.8.2": {
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="
},
"undici-types@6.19.8": {
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
},
"ws@8.18.0": {
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="
"ws@8.18.1": {
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="
}
},
"workspace": {
"dependencies": [
"jsr:@kunkun/api@^0.0.39"
"jsr:@kunkun/api@~0.1.7"
]
}
}

View File

@ -1,89 +1,93 @@
import { expose } from "@kunkun/api/runtime/deno"
import { API, Progress } from "../src/types.ts"
import { expose } from "@kunkun/api/runtime/deno";
import { API, Progress } from "../src/types.ts";
const oneMB = 1024 * 1024
const oneMB = 1024 * 1024;
export async function sequentialWriteTest(
options: {
filePath: string
sizeInMB: number
rounds: number
bufferSizeMB: number
keepTheFile?: boolean
},
callback?: (progress: Progress) => void
options: {
filePath: string;
sizeInMB: number;
rounds: number;
bufferSizeMB: number;
keepTheFile?: boolean;
},
callback?: (progress: Progress) => void
): Promise<Progress> {
const { filePath, sizeInMB, rounds, bufferSizeMB } = options
const data = new Uint8Array(bufferSizeMB * oneMB) // 1MB buffer
let start = performance.now()
let totalMB = 0
let totalDuration = 0
for (let round = 0; round < rounds; round++) {
const file = await Deno.open(filePath, { write: true, create: true })
const writer = file.writable.getWriter()
// console.error("sequentialWriteTest", options);
const { filePath, sizeInMB, rounds, bufferSizeMB } = options;
const data = new Uint8Array(bufferSizeMB * oneMB); // 1MB buffer
let start = performance.now();
let totalMB = 0;
let totalDuration = 0;
for (let round = 0; round < rounds; round++) {
const file = await Deno.open(filePath, { write: true, create: true });
const writer = file.writable.getWriter();
start = performance.now()
for (let i = 0; i < Math.floor(sizeInMB / bufferSizeMB); i++) {
await writer.write(data)
totalMB += bufferSizeMB
}
const roundEnd = performance.now()
totalDuration += (roundEnd - start) / 1000
callback?.({ totalMB, totalDuration })
await writer.close()
// if keepTheFile, do not remove the file in the last round
const isLastRound = round === rounds - 1
if (!isLastRound && !options.keepTheFile) {
Deno.removeSync(filePath)
}
}
start = performance.now();
for (let i = 0; i < Math.floor(sizeInMB / bufferSizeMB); i++) {
await writer.write(data);
totalMB += bufferSizeMB;
}
const roundEnd = performance.now();
totalDuration += (roundEnd - start) / 1000;
callback?.({ totalMB, totalDuration });
await writer.close();
// if keepTheFile, do not remove the file in the last round
const isLastRound = round === rounds - 1;
if (!isLastRound && !options.keepTheFile) {
Deno.removeSync(filePath);
}
}
return { totalDuration, totalMB }
return { totalDuration, totalMB };
}
export async function createEmptyFile(filePath: string, sizeInMB: number): Promise<void> {
if (await fileExists(filePath)) {
await Deno.remove(filePath)
}
const file = await Deno.open(filePath, { write: true, create: true })
const writer = file.writable.getWriter()
for (let i = 0; i < sizeInMB; i++) {
await writer.write(new Uint8Array(oneMB))
}
await writer.close()
export async function createEmptyFile(
filePath: string,
sizeInMB: number
): Promise<void> {
if (await fileExists(filePath)) {
await Deno.remove(filePath);
}
const file = await Deno.open(filePath, { write: true, create: true });
const writer = file.writable.getWriter();
for (let i = 0; i < sizeInMB; i++) {
await writer.write(new Uint8Array(oneMB));
}
await writer.close();
}
// Sequential Read
export async function sequentialReadTest(
filePath: string,
options: { deleteAfter: boolean } = { deleteAfter: true }
filePath: string,
options: { deleteAfter: boolean } = { deleteAfter: true }
): Promise<Progress> {
const file = await Deno.open(filePath, { read: true })
const buffer = new Uint8Array(oneMB) // 1MB buffer
const start = performance.now()
let totalMB = 0
while ((await file.read(buffer)) !== null) {
totalMB += 1
}
const totalDuration = (performance.now() - start) / 1000
file.close()
if (options.deleteAfter) {
Deno.removeSync(filePath)
}
return { totalMB, totalDuration }
const file = await Deno.open(filePath, { read: true });
const buffer = new Uint8Array(oneMB); // 1MB buffer
const start = performance.now();
let totalMB = 0;
while ((await file.read(buffer)) !== null) {
totalMB += 1;
}
const totalDuration = (performance.now() - start) / 1000;
file.close();
if (options.deleteAfter) {
Deno.removeSync(filePath);
}
return { totalMB, totalDuration };
}
export function fileExists(filePath: string): boolean {
try {
Deno.statSync(filePath)
return true
} catch (_error) {
return false
}
try {
Deno.statSync(filePath);
return true;
} catch (_error) {
return false;
}
}
expose({
sequentialWriteTest,
sequentialReadTest,
createEmptyFile
} satisfies API)
sequentialWriteTest,
sequentialReadTest,
createEmptyFile,
} satisfies API);

View File

@ -1,9 +0,0 @@
{
"tasks": {
"dev": "deno run --watch main.ts"
},
"imports": {
"@std/assert": "jsr:@std/assert@1",
"@valibot/valibot": "jsr:@valibot/valibot@^0.42.1"
}
}

1544
deno.lock generated

File diff suppressed because it is too large Load Diff

52
dev.ts
View File

@ -1,31 +1,27 @@
import { $ } from "bun"
import { DiskSpeedTestInput } from "./src/model.ts"
import { sequentialReadTest, sequentialWriteTest } from "./speedtest/lib.ts";
const input: DiskSpeedTestInput = {
targetPath: "./testfile.dat",
sequential: {
stressFileSizeMB: 2000
},
random: {
stressFileSizeMB: 1000,
iterations: 1000,
blockSize: 4096
}
}
const encoded = btoa(JSON.stringify(input))
// sequential
;(async () => {
const res =
await $`deno run --allow-read --allow-write deno-scripts/sequential.ts ${encoded}`.quiet()
const stdoutSplit = res.stdout.toString("utf-8").split("\n")
console.log(JSON.parse(stdoutSplit[stdoutSplit.length - 2]))
})()
const testPath = "./test.txt";
// const testPath = "/Volumes/Portable2TB/test.txt";
// random
;(async () => {
const res = await $`deno run --allow-read --allow-write deno-scripts/random.ts ${encoded}`.quiet()
console.log("stdout", res.stdout.toString("utf-8"))
const writeResult = await sequentialWriteTest(
{
filePath: testPath,
sizeInMB: 1000,
rounds: 10,
bufferSizeMB: 1,
keepTheFile: true,
},
(progress) => {
console.log(progress);
}
);
console.log(writeResult);
console.log(writeResult.totalMB / writeResult.totalDuration);
const stdoutSplit = res.stdout.toString("utf-8").split("\n")
console.log(JSON.parse(stdoutSplit[stdoutSplit.length - 2]))
})()
const readResult = await sequentialReadTest({
filePath: testPath,
rounds: 3,
deleteAfter: false,
});
console.log(readResult);
console.log("read speed: ", readResult.totalMB / readResult.totalDuration);

View File

@ -3,7 +3,7 @@
"name": "kunkun-ext-disk-speed",
"license": "MIT",
"repository": "https://github.com/kunkunsh/kunkun-ext-disk-speed",
"version": "0.0.6",
"version": "0.0.7",
"type": "module",
"kunkun": {
"name": "Disk Speed",
@ -31,7 +31,7 @@
"permission": "shell:deno:spawn",
"allow": [
{
"path": "$EXTENSION/deno-src/lib.ts",
"path": "$EXTENSION/speedtest/index.js",
"read": "*",
"write": "*"
}
@ -57,17 +57,18 @@
},
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "bun build.ts",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json"
},
"dependencies": {
"@iconify/svelte": "^4.0.2",
"@kksh/api": "^0.1.1",
"@kksh/api": "^0.1.7",
"@kksh/svelte": "0.1.7",
"bits-ui": "^0.21.16",
"clsx": "^2.1.1",
"echarts": "^5.5.1",
"kkrpc": "^0.2.2",
"lucide-svelte": "^0.416.0",
"tailwind-merge": "^2.4.0",
"tailwind-variants": "^0.2.1",
@ -89,9 +90,7 @@
"files": [
"dist",
".gitignore",
"deno-src",
"deno.json",
"deno.lock"
"speedtest/index.js"
],
"packageManager": "pnpm@9.15.3"
"packageManager": "pnpm@10.6.5"
}

715
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

7
speedtest-rs/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "speedtest-rs"
version = "0.1.0"

6
speedtest-rs/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "speedtest-rs"
version = "0.1.0"
edition = "2024"
[dependencies]

64
speedtest-rs/src/main.rs Normal file
View File

@ -0,0 +1,64 @@
use std::fs::File;
use std::io::{self, BufReader, Read};
use std::time::{Instant, Duration};
use std::path::Path;
fn read_file_speed_test(path: &Path) -> io::Result<(u64, Duration)> {
let file = File::open(path)?;
let file_size = file.metadata()?.len();
let mut reader = BufReader::with_capacity(1024 * 1024, file); // 1MB buffer
let mut buffer = [0; 1024 * 1024]; // 1MB chunks
let mut total_read = 0;
let start = Instant::now();
loop {
match reader.read(&mut buffer)? {
0 => break, // EOF
bytes_read => {
total_read += bytes_read as u64;
}
}
}
let duration = start.elapsed();
Ok((total_read, duration))
}
fn format_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} bytes", bytes)
}
}
fn main() {
let file_path = Path::new("/Volumes/Portable2TB/test.txt");
println!("Starting disk read speed test on: {}", file_path.display());
match read_file_speed_test(file_path) {
Ok((bytes_read, duration)) => {
let size = format_size(bytes_read);
let seconds = duration.as_secs_f64();
let speed = bytes_read as f64 / seconds / (1024.0 * 1024.0);
println!("Read {} in {:.2} seconds", size, seconds);
println!("Read speed: {:.2} MB/s", speed);
},
Err(e) => {
eprintln!("Error testing read speed: {}", e);
}
}
}

13
speedtest/index.ts Normal file
View File

@ -0,0 +1,13 @@
import { API } from "../src/types.ts";
import { expose } from "@kksh/api/runtime/deno";
import {
sequentialWriteTest,
sequentialReadTest,
createEmptyFile,
} from "./lib.ts";
expose({
sequentialWriteTest,
sequentialReadTest,
createEmptyFile,
} satisfies API);

89
speedtest/lib.ts Normal file
View File

@ -0,0 +1,89 @@
const oneMB = 1024 * 1024;
export type Progress = { totalMB: number; totalDuration: number };
export async function sequentialWriteTest(
options: {
filePath: string;
sizeInMB: number;
rounds: number;
bufferSizeMB: number;
keepTheFile?: boolean;
},
callback?: (progress: Progress) => void
): Promise<Progress> {
// console.error("sequentialWriteTest", options);
const { filePath, sizeInMB, rounds, bufferSizeMB } = options;
const data = new Uint8Array(bufferSizeMB * oneMB); // 1MB buffer
let start = performance.now();
let totalMB = 0;
let totalDuration = 0;
for (let round = 0; round < rounds; round++) {
const file = await Deno.open(filePath, { write: true, create: true });
const writer = file.writable.getWriter();
start = performance.now();
for (let i = 0; i < Math.floor(sizeInMB / bufferSizeMB); i++) {
await writer.write(data);
totalMB += bufferSizeMB;
}
const roundEnd = performance.now();
totalDuration += (roundEnd - start) / 1000;
callback?.({ totalMB, totalDuration });
await writer.close();
// if keepTheFile, do not remove the file in the last round
const isLastRound = round === rounds - 1;
if (!isLastRound && !options.keepTheFile) {
Deno.removeSync(filePath);
}
}
return { totalDuration, totalMB };
}
export async function createEmptyFile(
filePath: string,
sizeInMB: number
): Promise<void> {
if (await fileExists(filePath)) {
await Deno.remove(filePath);
}
const file = await Deno.open(filePath, { write: true, create: true });
const writer = file.writable.getWriter();
for (let i = 0; i < sizeInMB; i++) {
await writer.write(new Uint8Array(oneMB));
}
await writer.close();
}
// Sequential Read
export async function sequentialReadTest(
options: { filePath: string; rounds: number; deleteAfter: boolean } = {
filePath: "",
rounds: 1,
deleteAfter: true,
}
): Promise<Progress> {
const { filePath, rounds, deleteAfter } = options;
const file = await Deno.open(filePath, { read: true });
const buffer = new Uint8Array(oneMB); // 1MB buffer
const start = performance.now();
let totalMB = 0;
while ((await file.read(buffer)) !== null) {
totalMB += 1;
}
const totalDuration = (performance.now() - start) / 1000;
file.close();
if (options.deleteAfter) {
Deno.removeSync(filePath);
}
return { totalMB, totalDuration };
}
export function fileExists(filePath: string): boolean {
try {
Deno.statSync(filePath);
return true;
} catch (_error) {
return false;
}
}

View File

@ -1,133 +1,142 @@
<script lang="ts">
import { open, path, shell, toast, ui } from "@kksh/api/ui/custom"
import { Button, ModeWatcher, ThemeWrapper, updateTheme } from "@kksh/svelte"
import SpeedGauge from "$lib/components/SpeedGauge.svelte"
import StressSelect from "$lib/components/StressSelect.svelte"
import TargetDirSelect from "$lib/components/TargetDirSelect.svelte"
import { stress, targetDir } from "$lib/store"
import { onMount } from "svelte"
import { get } from "svelte/store"
import { type API } from "./types"
import { open, path, shell, toast, ui } from "@kksh/api/ui/custom";
import { Button, ModeWatcher, ThemeWrapper, updateTheme } from "@kksh/svelte";
import SpeedGauge from "$lib/components/SpeedGauge.svelte";
import StressSelect from "$lib/components/StressSelect.svelte";
import TargetDirSelect from "$lib/components/TargetDirSelect.svelte";
import { stress, targetDir } from "$lib/store";
import { onMount } from "svelte";
import { get } from "svelte/store";
import { type API } from "./types";
let readSpeedMBps = $state(0)
let writeSpeedMBps = $state(0)
let running = $state(false)
let readSpeedMBps = $state(0);
let writeSpeedMBps = $state(0);
let running = $state(false);
onMount(() => {
ui.registerDragRegion()
ui.showBackButton({ right: 0.5, bottom: 0.5 })
onMount(() => {
ui.registerDragRegion();
ui.showBackButton({ right: 0.5, bottom: 0.5 });
updateTheme({
theme: "neutral",
radius: 0.5,
lightMode: "dark"
})
})
updateTheme({
theme: "neutral",
radius: 0.5,
lightMode: "dark",
});
});
async function startSpeedTest() {
running = true
const _targetDir = get(targetDir)
if (!_targetDir) {
toast.error("Target directory is not set")
return
}
async function startSpeedTest() {
running = true;
readSpeedMBps;
writeSpeedMBps;
const _targetDir = get(targetDir);
if (!_targetDir) {
toast.error("Target directory is not set");
return;
}
const { rpcChannel, process, command } = await shell.createDenoRpcChannel<
{},
API
>(
"$EXTENSION/speedtest/index.js",
[],
{
allowAllRead: true,
allowAllWrite: true,
},
{}
);
const { rpcChannel, process } = await shell.createDenoRpcChannel<{}, API>(
"$EXTENSION/deno-src/lib.ts",
[],
{
allowAllRead: true,
allowAllWrite: true
},
{}
)
const api = rpcChannel.getAPI();
const testFileName = "kk-disk-speed-test";
const api = rpcChannel.getAPI()
const testFileName = "kk-disk-speed-test"
const testFilePath = await path.join(_targetDir, testFileName);
const testFilePath = await path.join(_targetDir, testFileName)
const writeResult = await api.sequentialWriteTest(
{
filePath: testFilePath,
sizeInMB: get(stress) * 1024,
rounds: 3,
bufferSizeMB: 1,
keepTheFile: true,
},
({ totalMB, totalDuration }) => {
writeSpeedMBps = totalMB / totalDuration;
}
);
const readResult = await api.sequentialReadTest({
filePath: testFilePath,
rounds: 3,
deleteAfter: true,
});
writeSpeedMBps = writeResult.totalMB / writeResult.totalDuration;
const writeResult = await api.sequentialWriteTest(
{
filePath: testFilePath,
sizeInMB: get(stress) * 1024,
rounds: 3,
bufferSizeMB: 1,
keepTheFile: true
},
({ totalMB, totalDuration }) => {
writeSpeedMBps = totalMB / totalDuration
}
)
const readResult = await api.sequentialReadTest(testFilePath, {
deleteAfter: true
})
writeSpeedMBps = writeResult.totalMB / writeResult.totalDuration
console.log("writeDuration", writeResult)
readSpeedMBps = readResult.totalMB / readResult.totalDuration;
readSpeedMBps = readResult.totalMB / readResult.totalDuration
console.log("readDuration", readResult)
process
.kill()
.then(() => {
console.log("process killed")
})
.catch((err) => {
console.error("error killing process", err)
toast.error(`Error killing process ${process.pid}`)
})
.finally(() => {
running = false
})
}
process
.kill()
.then(() => {
console.log("process killed");
})
.catch((err) => {
console.error("error killing process", err);
toast.error(`Error killing process ${process.pid}`);
})
.finally(() => {
running = false;
});
}
</script>
<svelte:window
on:keydown={(e) => {
if (e.key === "Escape") {
ui.goBack()
}
}}
on:keydown={(e) => {
if (e.key === "Escape") {
ui.goBack();
}
}}
/>
<ModeWatcher />
<ThemeWrapper>
<main class="container flex flex-col gap-4 pt-10">
<div class="absolute left-0 top-0 h-10 w-screen" data-kunkun-drag-region></div>
<div class="flex items-center gap-4">
<strong>Stress</strong>
<StressSelect />
</div>
<div class="flex items-center gap-4">
<strong>Target Directory</strong>
<TargetDirSelect />
{#if $targetDir}
<button
onclick={() => {
if ($targetDir) {
open.folder($targetDir)
}
}}
>
<pre>{$targetDir}</pre>
</button>
{:else}
<pre class="text-red-500">Pick a target directory to test</pre>
{/if}
</div>
<Button disabled={!$targetDir || running} on:click={startSpeedTest}>Start Speed Test</Button>
<!-- <div class="flex items-center gap-4">
<strong>Write Speed</strong>
<pre>{writeSpeedMBps} MB/s</pre>
</div>
<div class="flex items-center gap-4">
<strong>Read Speed</strong>
<pre>{readSpeedMBps} MB/s</pre>
</div> -->
<div class="grid h-96 w-full grid-cols-2">
<SpeedGauge speedInMBps={writeSpeedMBps} title="Write Speed" class="h-full w-full" />
<SpeedGauge speedInMBps={readSpeedMBps} title="Read Speed" class="h-full w-full" />
</div>
</main>
<main class="container flex flex-col gap-4 pt-10">
<div
class="absolute left-0 top-0 h-10 w-screen"
data-kunkun-drag-region
></div>
<div class="flex items-center gap-4">
<strong>Stress</strong>
<StressSelect />
</div>
<div class="flex items-center gap-4">
<strong>Target Directory</strong>
<TargetDirSelect />
{#if $targetDir}
<button
onclick={() => {
if ($targetDir) {
open.folder($targetDir);
}
}}
>
<pre>{$targetDir}</pre>
</button>
{:else}
<pre class="text-red-500">Pick a target directory to test</pre>
{/if}
</div>
<Button disabled={!$targetDir || running} on:click={startSpeedTest}>
Start Speed Test
</Button>
<div class="grid h-96 w-full grid-cols-2">
<SpeedGauge
speedInMBps={writeSpeedMBps}
title="Write Speed"
class="h-full w-full"
/>
<SpeedGauge
speedInMBps={readSpeedMBps}
title="Read Speed"
class="h-full w-full"
/>
</div>
</main>
</ThemeWrapper>

View File

@ -1,4 +1,4 @@
import { writable } from "svelte/store"
import { writable } from "svelte/store";
export const stress = writable(1)
export const targetDir = writable<string | undefined>(undefined)
export const stress = writable(1);
export const targetDir = writable<string | undefined>("/Volumes/Portable2TB");

View File

@ -1,15 +1,12 @@
export type Progress = { totalMB: number; totalDuration: number }
import type {
createEmptyFile,
sequentialReadTest,
sequentialWriteTest,
} from "../speedtest/lib.ts";
export interface API {
sequentialWriteTest: (
options: {
filePath: string
sizeInMB: number
rounds: number
bufferSizeMB: number
keepTheFile?: boolean
},
callback?: (progress: Progress) => void
) => Promise<Progress>
sequentialReadTest: (filePath: string, options: { deleteAfter: boolean }) => Promise<Progress>
createEmptyFile: (filePath: string, sizeInMB: number) => Promise<void>
sequentialWriteTest: typeof sequentialWriteTest;
sequentialReadTest: typeof sequentialReadTest;
createEmptyFile: typeof createEmptyFile;
}
export type { Progress } from "../speedtest/lib.ts";