mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 07:57:21 +08:00
feat: 跑分插件
This commit is contained in:
18
internal/apps/benchmark/init.go
Normal file
18
internal/apps/benchmark/init.go
Normal 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)
|
||||
},
|
||||
})
|
||||
}
|
||||
6
internal/apps/benchmark/request.go
Normal file
6
internal/apps/benchmark/request.go
Normal 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"`
|
||||
}
|
||||
686
internal/apps/benchmark/service.go
Normal file
686
internal/apps/benchmark/service.go
Normal 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
|
||||
}
|
||||
9
web/src/api/apps/benchmark/index.ts
Normal file
9
web/src/api/apps/benchmark/index.ts
Normal 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 })
|
||||
}
|
||||
@@ -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'),
|
||||
// 统计信息
|
||||
|
||||
@@ -29,7 +29,8 @@ const columns: any = [
|
||||
render(row: any) {
|
||||
return h(TheIcon, {
|
||||
icon: row.icon,
|
||||
size: 26
|
||||
size: 26,
|
||||
color: `var(--primary-color)`
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
288
web/src/views/apps/benchmark/IndexView.vue
Normal file
288
web/src/views/apps/benchmark/IndexView.vue
Normal 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>
|
||||
23
web/src/views/apps/benchmark/route.ts
Normal file
23
web/src/views/apps/benchmark/route.ts
Normal 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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -17,7 +17,7 @@ export default {
|
||||
component: () => import('./IndexView.vue'),
|
||||
meta: {
|
||||
title: '仪表盘',
|
||||
icon: 'mdi:monitor-dashboard',
|
||||
icon: 'mdi:speedometer',
|
||||
role: ['admin'],
|
||||
requireAuth: true
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user