2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 07:57:21 +08:00

feat: 跑分插件

This commit is contained in:
耗子
2024-10-20 01:34:22 +08:00
parent 2be97a4543
commit 0d9c8b3a2d
16 changed files with 1048 additions and 12 deletions

View File

@@ -0,0 +1,18 @@
package benchmark
import (
"github.com/go-chi/chi/v5"
"github.com/TheTNB/panel/pkg/apploader"
"github.com/TheTNB/panel/pkg/types"
)
func init() {
apploader.Register(&types.App{
Slug: "benchmark",
Route: func(r chi.Router) {
service := NewService()
r.Post("/test", service.Test)
},
})
}

View File

@@ -0,0 +1,6 @@
package benchmark
type Test struct {
Name string `json:"name" validate:"required,oneof=image machine compile encryption compression physics json memory disk"`
Multi bool `json:"multi"`
}

View File

@@ -0,0 +1,686 @@
package benchmark
import (
"bytes"
"crypto/aes"
"crypto/cipher"
cryptorand "crypto/rand"
"encoding/json"
"fmt"
"image"
"image/color"
"io"
"math"
"math/big"
"math/rand/v2"
"net/http"
"os"
"runtime"
"strings"
"sync"
"time"
"github.com/klauspost/compress/zstd"
"github.com/TheTNB/panel/internal/service"
)
type Service struct {
}
func NewService() *Service {
return &Service{}
}
// Test 运行测试
func (s *Service) Test(w http.ResponseWriter, r *http.Request) {
req, err := service.Bind[Test](r)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
switch req.Name {
case "image":
result := s.imageProcessing(req.Multi)
service.Success(w, result)
case "machine":
result := s.machineLearning(req.Multi)
service.Success(w, result)
case "compile":
result := s.compileSimulationSingle(req.Multi)
service.Success(w, result)
case "encryption":
result := s.encryptionTest(req.Multi)
service.Success(w, result)
case "compression":
result := s.compressionTest(req.Multi)
service.Success(w, result)
case "physics":
result := s.physicsSimulation(req.Multi)
service.Success(w, result)
case "json":
result := s.jsonProcessing(req.Multi)
service.Success(w, result)
case "disk":
result := s.diskTestTask()
service.Success(w, result)
case "memory":
result := s.memoryTestTask()
service.Success(w, result)
default:
service.Error(w, http.StatusUnprocessableEntity, "未知测试类型")
}
}
// calculateCpuScore 计算CPU成绩
func (s *Service) calculateCpuScore(duration time.Duration) int {
score := int((10 / duration.Seconds()) * float64(3000))
if score < 0 {
score = 0
}
return score
}
// calculateScore 计算内存/硬盘成绩
func (s *Service) calculateScore(duration time.Duration) int {
score := int((10 / duration.Seconds()) * float64(10000))
if score < 0 {
score = 0
}
return score
}
// 图像处理
func (s *Service) imageProcessing(multi bool) int {
n := 1
if multi {
n = runtime.NumCPU()
}
start := time.Now()
if err := s.imageProcessingTask(n); err != nil {
return 0
}
duration := time.Since(start)
return s.calculateCpuScore(duration)
}
func (s *Service) imageProcessingTask(numThreads int) error {
img := image.NewRGBA(image.Rect(0, 0, 4000, 4000))
for x := 0; x < 4000; x++ {
for y := 0; y < 4000; y++ {
img.Set(x, y, color.RGBA{R: uint8(x % 256), G: uint8(y % 256), A: 255})
}
}
var wg sync.WaitGroup
dx := img.Bounds().Dx()
dy := img.Bounds().Dy()
chunkSize := dy / numThreads
for i := 0; i < numThreads; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
startY := i * chunkSize
endY := startY + chunkSize
if i == numThreads-1 {
endY = dy
}
for x := 1; x < dx-1; x++ {
for y := startY + 1; y < endY-1; y++ {
// 卷积操作(模糊)
rTotal, gTotal, bTotal := 0, 0, 0
for k := -1; k <= 1; k++ {
for l := -1; l <= 1; l++ {
r, g, b, _ := img.At(x+k, y+l).RGBA()
rTotal += int(r)
gTotal += int(g)
bTotal += int(b)
}
}
rAvg := uint8(rTotal / 9 / 256)
gAvg := uint8(gTotal / 9 / 256)
bAvg := uint8(bTotal / 9 / 256)
img.Set(x, y, color.RGBA{R: rAvg, G: gAvg, B: bAvg, A: 255})
}
}
}(i)
}
wg.Wait()
return nil
}
// 机器学习(矩阵乘法)
func (s *Service) machineLearning(multi bool) int {
n := 1
if multi {
n = runtime.NumCPU()
}
start := time.Now()
s.machineLearningTask(n)
duration := time.Since(start)
return s.calculateCpuScore(duration)
}
func (s *Service) machineLearningTask(numThreads int) {
size := 850
a := make([][]float64, size)
b := make([][]float64, size)
for i := 0; i < size; i++ {
a[i] = make([]float64, size)
b[i] = make([]float64, size)
for j := 0; j < size; j++ {
a[i][j] = rand.Float64()
b[i][j] = rand.Float64()
}
}
c := make([][]float64, size)
for i := 0; i < size; i++ {
c[i] = make([]float64, size)
}
var wg sync.WaitGroup
chunkSize := size / numThreads
for k := 0; k < numThreads; k++ {
wg.Add(1)
go func(k int) {
defer wg.Done()
start := k * chunkSize
end := start + chunkSize
if k == numThreads-1 {
end = size
}
for i := start; i < end; i++ {
for j := 0; j < size; j++ {
sum := 0.0
for l := 0; l < size; l++ {
sum += a[i][l] * b[l][j]
}
c[i][j] = sum
}
}
}(k)
}
wg.Wait()
}
// 数学问题(计算斐波那契数)
func (s *Service) compileSimulationSingle(multi bool) int {
n := 1
if multi {
n = runtime.NumCPU()
}
start := time.Now()
totalCalculations := 1000
fibNumber := 20000
calculationsPerThread := totalCalculations / n
remainder := totalCalculations % n
var wg sync.WaitGroup
for i := 0; i < n; i++ {
tasks := calculationsPerThread
if i < remainder {
tasks++ // 处理无法均分的剩余任务
}
wg.Add(1)
go func(tasks int) {
defer wg.Done()
for j := 0; j < tasks; j++ {
s.fib(fibNumber)
}
}(tasks)
}
wg.Wait()
duration := time.Since(start)
return s.calculateCpuScore(duration)
}
// 斐波那契函数
func (s *Service) fib(n int) *big.Int {
if n < 2 {
return big.NewInt(int64(n))
}
a := big.NewInt(0)
b := big.NewInt(1)
temp := big.NewInt(0)
for i := 2; i <= n; i++ {
temp.Add(a, b)
a.Set(b)
b.Set(temp)
}
return b
}
// AES加密
func (s *Service) encryptionTest(multi bool) int {
n := 1
if multi {
n = runtime.NumCPU()
}
start := time.Now()
if err := s.encryptionTestTask(n); err != nil {
return 0
}
duration := time.Since(start)
return s.calculateCpuScore(duration)
}
func (s *Service) encryptionTestTask(numThreads int) error {
key := []byte("abcdefghijklmnopqrstuvwxyz123456")
dataSize := 1 * 1024 * 1024 * 1024 // 1GB
plaintext := []byte(strings.Repeat("A", dataSize))
block, err := aes.NewCipher(key)
if err != nil {
return err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return err
}
chunkSize := dataSize / numThreads
var wg sync.WaitGroup
for i := 0; i < numThreads; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
start := i * chunkSize
end := start + chunkSize
if i == numThreads-1 {
end = dataSize
}
nonce := make([]byte, aesGCM.NonceSize())
if _, err = cryptorand.Read(nonce); err != nil {
return
}
aesGCM.Seal(nil, nonce, plaintext[start:end], nil)
}(i)
}
wg.Wait()
return nil
}
// 压缩/解压缩
func (s *Service) compressionTest(multi bool) int {
n := 1
if multi {
n = runtime.NumCPU()
}
start := time.Now()
s.compressionTestTask(n)
duration := time.Since(start)
return s.calculateCpuScore(duration)
}
func (s *Service) compressionTestTask(numThreads int) {
data := []byte(strings.Repeat("耗子面板", 50000000))
chunkSize := len(data) / numThreads
var wg sync.WaitGroup
compressedChunks := make([]bytes.Buffer, numThreads)
// 压缩
for i := 0; i < numThreads; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
start := i * chunkSize
end := start + chunkSize
if i == numThreads-1 {
end = len(data)
}
var buf bytes.Buffer
zw, _ := zstd.NewWriter(&buf)
_, _ = zw.Write(data[start:end])
_ = zw.Close()
compressedChunks[i] = buf
}(i)
}
wg.Wait()
// 解压缩
for i := 0; i < numThreads; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
zr, err := zstd.NewReader(&compressedChunks[i])
if err != nil {
return
}
_, err = io.Copy(io.Discard, zr)
if err != nil {
return
}
zr.Close()
}(i)
}
wg.Wait()
}
// 物理仿真N体问题
func (s *Service) physicsSimulation(multi bool) int {
n := 1
if multi {
n = runtime.NumCPU()
}
start := time.Now()
s.physicsSimulationTask(n)
duration := time.Since(start)
return s.calculateCpuScore(duration)
}
func (s *Service) physicsSimulationTask(numThreads int) {
const (
numBodies = 4000
steps = 30
)
type Body struct {
x, y, z, vx, vy, vz float64
}
bodies := make([]Body, numBodies)
for i := 0; i < numBodies; i++ {
bodies[i] = Body{
x: rand.Float64(),
y: rand.Float64(),
z: rand.Float64(),
vx: rand.Float64(),
vy: rand.Float64(),
vz: rand.Float64(),
}
}
chunkSize := numBodies / numThreads
for step := 0; step < steps; step++ {
var wg sync.WaitGroup
// 更新速度
for k := 0; k < numThreads; k++ {
wg.Add(1)
go func(k int) {
defer wg.Done()
start := k * chunkSize
end := start + chunkSize
if k == numThreads-1 {
end = numBodies
}
for i := start; i < end; i++ {
bi := &bodies[i]
for j := 0; j < numBodies; j++ {
if i == j {
continue
}
bj := &bodies[j]
dx := bj.x - bi.x
dy := bj.y - bi.y
dz := bj.z - bi.z
dist := math.Sqrt(dx*dx + dy*dy + dz*dz)
if dist == 0 {
continue
}
force := 1 / (dist * dist)
bi.vx += force * dx / dist
bi.vy += force * dy / dist
bi.vz += force * dz / dist
}
}
}(k)
}
wg.Wait()
// 更新位置
for k := 0; k < numThreads; k++ {
wg.Add(1)
go func(k int) {
defer wg.Done()
start := k * chunkSize
end := start + chunkSize
if k == numThreads-1 {
end = numBodies
}
for i := start; i < end; i++ {
bi := &bodies[i]
bi.x += bi.vx
bi.y += bi.vy
bi.z += bi.vz
}
}(k)
}
wg.Wait()
}
}
// JSON解析
func (s *Service) jsonProcessing(multi bool) int {
n := 1
if multi {
n = runtime.NumCPU()
}
start := time.Now()
s.jsonProcessingTask(n)
duration := time.Since(start)
return s.calculateCpuScore(duration)
}
func (s *Service) jsonProcessingTask(numThreads int) {
numElements := 1000000
elementsPerThread := numElements / numThreads
var wg sync.WaitGroup
for i := 0; i < numThreads; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
start := i * elementsPerThread
end := start + elementsPerThread
if i == numThreads-1 {
end = numElements
}
elements := make([]map[string]any, 0, end-start)
for j := start; j < end; j++ {
elements = append(elements, map[string]any{
"id": j,
"value": fmt.Sprintf("Value%d", j),
})
}
encoded, err := json.Marshal(elements)
if err != nil {
return
}
var parsed []map[string]any
err = json.Unmarshal(encoded, &parsed)
if err != nil {
return
}
}(i)
}
wg.Wait()
}
// 内存性能
func (s *Service) memoryTestTask() map[string]any {
results := make(map[string]any)
dataSize := 500 * 1024 * 1024 // 500 MB
data := make([]byte, dataSize)
_, _ = cryptorand.Read(data)
start := time.Now()
// 内存读写速度
results["bandwidth"] = s.memoryBandwidthTest(data)
// 内存访问延迟
data = data[:100*1024*1024] // 100 MB
results["latency"] = s.memoryLatencyTest(data)
duration := time.Since(start)
results["score"] = s.calculateScore(duration)
return results
}
func (s *Service) memoryBandwidthTest(data []byte) string {
dataSize := len(data)
startTime := time.Now()
for i := 0; i < dataSize; i++ {
data[i] ^= 0xFF
}
duration := time.Since(startTime).Seconds()
if duration == 0 {
return "N/A"
}
speed := float64(dataSize) / duration / (1024 * 1024)
return fmt.Sprintf("%.2f MB/s", speed)
}
func (s *Service) memoryLatencyTest(data []byte) string {
dataSize := len(data)
indices := rand.Perm(dataSize)
startTime := time.Now()
sum := byte(0)
for _, idx := range indices {
sum ^= data[idx]
}
duration := time.Since(startTime).Seconds()
if duration == 0 {
return "N/A"
}
avgLatency := duration * 1e9 / float64(dataSize)
return fmt.Sprintf("%.2f ns", avgLatency)
}
// 硬盘IO
func (s *Service) diskTestTask() map[string]any {
results := make(map[string]any)
blockSizes := []int64{4 * 1024, 64 * 1024, 512 * 1024, 1 * 1024 * 1024} // 4K, 64K, 512K, 1M
fileSize := int64(100 * 1024 * 1024) // 100MB 文件
start := time.Now()
for _, blockSize := range blockSizes {
result := s.diskIOTest(blockSize, fileSize)
results[fmt.Sprintf("%d", blockSize/1024)] = result
}
duration := time.Since(start)
results["score"] = s.calculateScore(duration)
return results
}
func (s *Service) diskIOTest(blockSize int64, fileSize int64) map[string]any {
result := make(map[string]any)
tempFile := fmt.Sprintf("tempfile_%d", blockSize)
defer os.Remove(tempFile)
// 写测试
writeSpeed, writeIOPS := s.diskWriteTest(tempFile, blockSize, fileSize)
// 读测试
readSpeed, readIOPS := s.diskReadTest(tempFile, blockSize, fileSize)
result["write_speed"] = fmt.Sprintf("%.2f MB/s", writeSpeed)
result["write_iops"] = fmt.Sprintf("%.2f IOPS", writeIOPS)
result["read_speed"] = fmt.Sprintf("%.2f MB/s", readSpeed)
result["read_iops"] = fmt.Sprintf("%.2f IOPS", readIOPS)
return result
}
func (s *Service) diskWriteTest(fileName string, blockSize int64, fileSize int64) (float64, float64) {
totalBlocks := fileSize / blockSize
data := make([]byte, blockSize)
_, _ = cryptorand.Read(data)
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_SYNC, 0644)
if err != nil {
return 0, 0
}
defer file.Close()
start := time.Now()
for i := int64(0); i < totalBlocks; i++ {
// 生成随机偏移
offset := rand.Int64N(fileSize - blockSize + 1)
_, err := file.WriteAt(data, offset)
if err != nil {
return 0, 0
}
}
_ = file.Sync()
duration := time.Since(start).Seconds()
if duration == 0 {
duration = 1
}
speed := float64(totalBlocks*blockSize) / duration / (1024 * 1024)
iops := float64(totalBlocks) / duration
return speed, iops
}
func (s *Service) diskReadTest(fileName string, blockSize int64, fileSize int64) (float64, float64) {
totalBlocks := fileSize / blockSize
data := make([]byte, blockSize)
file, err := os.OpenFile(fileName, os.O_RDONLY|os.O_SYNC, 0644)
if err != nil {
return 0, 0
}
defer file.Close()
start := time.Now()
for i := int64(0); i < totalBlocks; i++ {
// 生成随机偏移
offset := rand.Int64N(fileSize - blockSize + 1)
_, err := file.ReadAt(data, offset)
if err != nil && err != io.EOF {
return 0, 0
}
}
duration := time.Since(start).Seconds()
if duration == 0 {
duration = 1
}
speed := float64(totalBlocks*blockSize) / duration / (1024 * 1024)
iops := float64(totalBlocks) / duration
return speed, iops
}

View File

@@ -0,0 +1,9 @@
import type { AxiosResponse } from 'axios'
import { request } from '@/utils'
export default {
// 运行评分
test: (name: string, multi: boolean): Promise<AxiosResponse<any>> =>
request.post('/apps/benchmark/test', { name, multi })
}

View File

@@ -1,7 +1,8 @@
import type { AxiosResponse } from 'axios'
import { request } from '@/utils'
import type { AxiosResponse } from 'axios'
import type { RequestConfig } from '~/types/axios'
export default {
// 面板信息
panel: (): Promise<Response> => fetch('/api/dashboard/panel'),
@@ -11,7 +12,7 @@ export default {
homeApps: (): Promise<AxiosResponse<any>> => request.get('/dashboard/homeApps'),
// 实时信息
current: (nets: string[], disks: string[]): Promise<AxiosResponse<any>> =>
request.post('/dashboard/current', { nets, disks }),
request.post('/dashboard/current', { nets, disks }, { noNeedTip: true } as RequestConfig),
// 系统信息
systemInfo: (): Promise<AxiosResponse<any>> => request.get('/dashboard/systemInfo'),
// 统计信息

View File

@@ -29,7 +29,8 @@ const columns: any = [
render(row: any) {
return h(TheIcon, {
icon: row.icon,
size: 26
size: 26,
color: `var(--primary-color)`
})
}
},

View File

@@ -0,0 +1,288 @@
<script setup lang="ts">
import benchmark from '@/api/apps/benchmark'
import TheIcon from '@/components/custom/TheIcon.vue'
defineOptions({
name: 'apps-benchmark-index'
})
const inTest = ref(false)
const current = ref('CPU')
const progress = ref(0)
const tests = [
'image',
'machine',
'compile',
'encryption',
'compression',
'physics',
'json',
'memory',
'disk'
]
const cpu = ref({
image: {
single: 0,
multi: 0
},
machine: {
single: 0,
multi: 0
},
compile: {
single: 0,
multi: 0
},
encryption: {
single: 0,
multi: 0
},
compression: {
single: 0,
multi: 0
},
physics: {
single: 0,
multi: 0
},
json: {
single: 0,
multi: 0
}
})
const cpuTotal = computed(() => {
return {
single: Object.values(cpu.value).reduce((a, b) => a + b.single, 0),
multi: Object.values(cpu.value).reduce((a, b) => a + b.multi, 0)
}
})
const memory = ref({
score: 0,
bandwidth: '无结果',
latency: '无结果'
})
const disk = ref({
score: 0,
1024: {
read_iops: '无结果',
read_speed: '无结果',
write_iops: '无结果',
write_speed: '无结果'
},
4: {
read_iops: '无结果',
read_speed: '无结果',
write_iops: '无结果',
write_speed: '无结果'
},
512: {
read_iops: '无结果',
read_speed: '无结果',
write_iops: '无结果',
write_speed: '无结果'
},
64: {
read_iops: '无结果',
read_speed: '无结果',
write_iops: '无结果',
write_speed: '无结果'
}
})
const handleTest = async () => {
inTest.value = true
progress.value = 0
for (let i = 0; i < tests.length; i++) {
const test = tests[i]
current.value = test
if (test != 'memory' && test != 'disk') {
for (let j = 0; j < 2; j++) {
const { data } = await benchmark.test(test, j === 1)
cpu.value[test as keyof typeof cpu.value][j === 1 ? 'multi' : 'single'] = data
}
} else {
const { data } = await benchmark.test(test, false)
if (test === 'memory') {
memory.value = data
} else {
disk.value = data
}
}
progress.value = Math.round(((i + 1) / tests.length) * 100)
}
inTest.value = false
}
</script>
<template>
<common-page show-footer>
<n-flex vertical>
<n-alert type="warning">
跑分结果仅供参考受系统资源调度和缓存等因素影响可能与实际性能有所偏差
</n-alert>
<n-alert v-if="inTest" title="跑分中,可能需要较长时间..." type="info">
当前项目{{ current }}
</n-alert>
<n-progress v-if="inTest" :percentage="progress" color="var(--primary-color)" processing />
</n-flex>
<n-flex vertical items-center pt-40>
<div w-800>
<n-grid :cols="3">
<n-gi>
<n-popover trigger="hover">
<template #trigger>
<n-flex vertical items-center>
<div v-if="cpuTotal.single !== 0 && cpuTotal.multi !== 0">
单核
<n-number-animation :from="0" :to="cpuTotal.single" show-separator />
/ 多核
<n-number-animation :from="0" :to="cpuTotal.multi" show-separator />
</div>
<n-progress
type="circle"
:percentage="100"
:stroke-width="3"
color="var(--primary-color)"
>
<TheIcon :size="50" icon="bi:cpu" color="var(--primary-color)" />
</n-progress>
CPU
</n-flex>
</template>
<n-table :single-line="false" striped>
<tr>
<th>图像处理</th>
<td>单核 {{ cpu.image.single }} / 多核 {{ cpu.image.multi }}</td>
</tr>
<tr>
<th>机器学习</th>
<td>单核 {{ cpu.machine.single }} / 多核 {{ cpu.machine.multi }}</td>
</tr>
<tr>
<th>程序编译</th>
<td>单核 {{ cpu.compile.single }} / 多核 {{ cpu.compile.multi }}</td>
</tr>
<tr>
<th>AES 加密</th>
<td>单核 {{ cpu.encryption.single }} / 多核 {{ cpu.encryption.multi }}</td>
</tr>
<tr>
<th>压缩/解压缩</th>
<td>单核 {{ cpu.compression.single }} / 多核 {{ cpu.compression.multi }}</td>
</tr>
<tr>
<th>物理仿真</th>
<td>单核 {{ cpu.physics.single }} / 多核 {{ cpu.physics.multi }}</td>
</tr>
<tr>
<th>JSON 解析</th>
<td>单核 {{ cpu.json.single }} / 多核 {{ cpu.json.multi }}</td>
</tr>
</n-table>
</n-popover>
</n-gi>
<n-gi>
<n-popover trigger="hover">
<template #trigger>
<n-flex vertical items-center>
<div v-if="memory.score !== 0">
<n-number-animation :from="0" :to="memory.score" show-separator />
</div>
<n-progress
type="circle"
:percentage="100"
:stroke-width="3"
color="var(--primary-color)"
>
<TheIcon :size="50" icon="bi:memory" color="var(--primary-color)" />
</n-progress>
内存
</n-flex>
</template>
<n-table :single-line="false" striped>
<tr>
<th>内存带宽</th>
<td>{{ memory.bandwidth }}</td>
</tr>
<tr>
<th>内存延迟</th>
<td>{{ memory.latency }}</td>
</tr>
</n-table>
</n-popover>
</n-gi>
<n-gi>
<n-popover trigger="hover">
<template #trigger>
<n-flex vertical items-center>
<div v-if="disk.score !== 0">
<n-number-animation :from="0" :to="disk.score" show-separator />
</div>
<n-progress
type="circle"
:percentage="100"
:stroke-width="3"
color="var(--primary-color)"
>
<TheIcon :size="50" icon="bi:hdd-stack" color="var(--primary-color)" />
</n-progress>
硬盘
</n-flex>
</template>
<n-table :single-line="false" striped>
<tr>
<th>4KB 读取</th>
<td>速度 {{ disk['4'].read_speed }} / {{ disk['4'].read_iops }} IOPS</td>
</tr>
<tr>
<th>4KB 写入</th>
<td>速度 {{ disk['4'].write_speed }} / {{ disk['4'].write_iops }} IOPS</td>
</tr>
<tr>
<th>64KB 读取</th>
<td>速度 {{ disk['64'].read_speed }} / {{ disk['64'].read_iops }} IOPS</td>
</tr>
<tr>
<th>64KB 写入</th>
<td>速度 {{ disk['64'].write_speed }} / {{ disk['64'].write_iops }} IOPS</td>
</tr>
<tr>
<th>512KB 读取</th>
<td>速度 {{ disk['512'].read_speed }} / {{ disk['512'].read_iops }} IOPS</td>
</tr>
<tr>
<th>512KB 写入</th>
<td>速度 {{ disk['512'].write_speed }} / {{ disk['512'].write_iops }} IOPS</td>
</tr>
<tr>
<th>1MB 读取</th>
<td>速度 {{ disk['1024'].read_speed }} / {{ disk['1024'].read_iops }} IOPS</td>
</tr>
<tr>
<th>1MB 写入</th>
<td>速度 {{ disk['1024'].write_speed }} / {{ disk['1024'].write_iops }} IOPS</td>
</tr>
</n-table>
</n-popover>
</n-gi>
</n-grid>
</div>
<n-button
type="primary"
size="large"
:disabled="inTest"
:loading="inTest"
@click="handleTest"
w-200
mt-40
>
{{ inTest ? '跑分中...' : '开始跑分' }}
</n-button>
</n-flex>
</common-page>
</template>

View File

@@ -0,0 +1,23 @@
import type { RouteType } from '~/types/router'
const Layout = () => import('@/layout/IndexView.vue')
export default {
name: 'benchmark',
path: '/apps/benchmark',
component: Layout,
isHidden: true,
children: [
{
name: 'apps-benchmark-index',
path: '',
component: () => import('./IndexView.vue'),
meta: {
title: '耗子跑分',
icon: 'dashicons:performance',
role: ['admin'],
requireAuth: true
}
}
]
} as RouteType

View File

@@ -16,7 +16,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'certIndex.title',
icon: 'mdi:certificate',
icon: 'mdi:certificate-outline',
role: ['admin'],
requireAuth: true
}

View File

@@ -16,7 +16,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'cronIndex.title',
icon: 'mdi:clock-outline',
icon: 'mdi:timer-outline',
role: ['admin'],
requireAuth: true
}

View File

@@ -16,7 +16,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'fileIndex.title',
icon: 'mdi:file-tree',
icon: 'mdi:folder-open-outline',
role: ['admin'],
requireAuth: true
}

View File

@@ -674,7 +674,11 @@ if (import.meta.hot) {
<n-thing>
<template #avatar>
<div class="mt-8">
<TheIcon :size="30" :icon="item.icon" />
<TheIcon
:size="30"
:icon="item.icon"
color="var(--primary-color)"
/>
</div>
</template>
<template #header>

View File

@@ -17,7 +17,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: '仪表盘',
icon: 'mdi:monitor-dashboard',
icon: 'mdi:speedometer',
role: ['admin'],
requireAuth: true
}

View File

@@ -16,7 +16,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'monitorIndex.title',
icon: 'mdi:monitor',
icon: 'mdi:chart-line',
role: ['admin'],
requireAuth: true
}

View File

@@ -16,7 +16,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: '系统安全',
icon: 'mdi:server-security',
icon: 'mdi:shield-check-outline',
role: ['admin'],
requireAuth: true
}

View File

@@ -16,7 +16,7 @@ export default {
component: () => import('./IndexView.vue'),
meta: {
title: 'taskIndex.title',
icon: 'mdi:archive-sync-outline',
icon: 'mdi:table-sync',
role: ['admin'],
requireAuth: true
}