feat: 优化块生成

This commit is contained in:
td-zhangshun 2025-04-23 15:22:10 +08:00 committed by Netlops
parent dc927febf0
commit 4559bb2cd6
4 changed files with 123 additions and 16 deletions

View File

@ -4,11 +4,11 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
"syscall"
"time" "time"
"plp-test/internal/config" "plp-test/internal/config"
@ -140,8 +140,12 @@ func (t *PowerLossTest) Run(ctx context.Context) (*model.TestResult, error) {
startTime := time.Now() startTime := time.Now()
var totalBytesWritten int var totalBytesWritten int
// 第一阶段 - 持续写入数据,直到手动断电 // 第一阶段 - 在内存中预先生成所有数据块
t.setMessage("写入数据 (请在适当时手动断电)") t.setMessage("在内存中预生成数据块")
// 预先在内存中生成所有数据块
blocksInMemory := make([]*model.TestBlock, t.totalBlocks)
checksumMap := make(map[int]string)
for i := 0; i < t.totalBlocks; i++ { for i := 0; i < t.totalBlocks; i++ {
select { select {
@ -159,6 +163,54 @@ func (t *PowerLossTest) Run(ctx context.Context) (*model.TestResult, error) {
// 创建测试数据块 // 创建测试数据块
block := model.NewTestBlock(data, i) block := model.NewTestBlock(data, i)
// 存储到内存中
blocksInMemory[i] = block
checksumMap[i] = block.Checksum
// 更新进度
if i > 0 && i%100 == 0 {
progress := float64(i+1) / float64(t.totalBlocks) * 30 // 前30%进度用于生成
t.setProgress(progress)
t.setMessage(fmt.Sprintf("已在内存中生成 %d/%d 个数据块", i, t.totalBlocks))
}
}
}
// 将校验和映射持久化到文件
t.setMessage("持久化校验和映射到文件")
checksumFilePath := filepath.Join(t.testDir, "checksums.json")
checksumData, err := json.Marshal(checksumMap)
if err != nil {
t.setStatus(StatusFailed)
return nil, fmt.Errorf("序列化校验和映射失败: %v", err)
}
err = os.WriteFile(checksumFilePath, checksumData, 0644)
if err != nil {
t.setStatus(StatusFailed)
return nil, fmt.Errorf("保存校验和映射文件失败: %v", err)
}
// 确保校验和映射已落盘
t.setMessage("同步校验和映射到磁盘")
_, err = utils.ExecuteCommand("sync")
if err != nil {
t.logger.Warnf("执行sync命令失败: %v", err)
}
t.setProgress(35)
t.setMessage("校验和映射已保存,准备断电测试,开始写入数据块...")
time.Sleep(3 * time.Second) // 给用户一些时间准备
// 第二阶段 - 写入数据块到磁盘
t.setMessage("写入数据块到磁盘 (请在适当时手动断电)")
for i, block := range blocksInMemory {
select {
case <-ctx.Done():
t.setStatus(StatusAborted)
return nil, ctx.Err()
default:
// 添加到数据块列表 // 添加到数据块列表
t.blocksMu.Lock() t.blocksMu.Lock()
t.blocks = append(t.blocks, block) t.blocks = append(t.blocks, block)
@ -175,22 +227,22 @@ func (t *PowerLossTest) Run(ctx context.Context) (*model.TestResult, error) {
// 写入文件 // 写入文件
filePath := filepath.Join(t.testDir, fmt.Sprintf("block_%d.dat", i)) filePath := filepath.Join(t.testDir, fmt.Sprintf("block_%d.dat", i))
// direct IO 直接写入磁盘,必须使用syscall.O_DIRECTLinux 2.6.29 及以上版本支持 // direct IO 直接写入磁盘,使用跨平台的DirectIOFlag
file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|syscall.O_DIRECT, 0644) file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|DirectIOFlag, 0644)
if err != nil { if err != nil {
t.setStatus(StatusFailed) t.setStatus(StatusFailed)
return nil, fmt.Errorf("写入文件 %s 失败: %v", filePath, err) return nil, fmt.Errorf("写入文件 %s 失败: %v", filePath, err)
} }
defer file.Close() defer file.Close()
_, err = file.Write(data) _, err = file.Write(block.Data)
if err != nil { if err != nil {
t.setStatus(StatusFailed) t.setStatus(StatusFailed)
return nil, fmt.Errorf("写入文件 %s 失败: %v", filePath, err) return nil, fmt.Errorf("写入文件 %s 失败: %v", filePath, err)
} }
t.writtenBlocks++ t.writtenBlocks++
totalBytesWritten += len(data) totalBytesWritten += len(block.Data)
t.setMessage(fmt.Sprintf("同步数据到磁盘 (已写入 %d/%d 块)", i, t.totalBlocks)) t.setMessage(fmt.Sprintf("同步数据到磁盘 (已写入 %d/%d 块)", i, t.totalBlocks))
_, err = utils.ExecuteCommand("sync") _, err = utils.ExecuteCommand("sync")
@ -199,13 +251,12 @@ func (t *PowerLossTest) Run(ctx context.Context) (*model.TestResult, error) {
} }
// 更新进度 // 更新进度
progress := float64(i+1) / float64(t.totalBlocks) * 100 progress := 35 + float64(i+1)/float64(t.totalBlocks)*65 // 余下65%进度用于写入
t.setProgress(progress) t.setProgress(progress)
// 每写入一定数量的打印输出下当前进度到日志 // 每写入一定数量的打印输出下当前进度到日志
if i > 0 && i%100 == 0 { if i > 0 && i%100 == 0 {
t.setMessage(fmt.Sprintf("已写入 %d/%d 块数据, 共 %.2f MB", i, t.totalBlocks, float64(i*t.blockSize)/(1024*1024))) t.setMessage(fmt.Sprintf("已写入 %d/%d 块数据, 共 %.2f MB", i, t.totalBlocks, float64(i*t.blockSize)/(1024*1024)))
// time.Sleep(1 * time.Second)
} }
} }
} }
@ -215,7 +266,7 @@ func (t *PowerLossTest) Run(ctx context.Context) (*model.TestResult, error) {
// 完成所有数据写入后,同步到磁盘 // 完成所有数据写入后,同步到磁盘
t.setMessage("同步所有数据到磁盘") t.setMessage("同步所有数据到磁盘")
_, err := utils.ExecuteCommand("sync") _, err = utils.ExecuteCommand("sync")
if err != nil { if err != nil {
t.logger.Warnf("执行sync命令失败: %v", err) t.logger.Warnf("执行sync命令失败: %v", err)
} }
@ -248,6 +299,26 @@ func (t *PowerLossTest) CheckIntegrity() *model.IntegrityInfo {
t.integrityInfo.CorruptedBlocks = 0 t.integrityInfo.CorruptedBlocks = 0
t.integrityInfo.MissingBlocks = 0 t.integrityInfo.MissingBlocks = 0
// 尝试从文件加载校验和映射
checksumMap := make(map[int]string)
checksumFilePath := filepath.Join(t.testDir, "checksums.json")
if utils.FileExists(checksumFilePath) {
t.setMessage("从文件加载校验和映射")
data, err := os.ReadFile(checksumFilePath)
if err == nil {
err = json.Unmarshal(data, &checksumMap)
if err != nil {
t.logger.Warnf("解析校验和映射文件失败: %v将使用内存中的校验和", err)
} else {
t.logger.Infof("从文件成功加载了 %d 个校验和", len(checksumMap))
}
} else {
t.logger.Warnf("读取校验和映射文件失败: %v将使用内存中的校验和", err)
}
} else {
t.logger.Warnf("校验和映射文件不存在,将使用内存中的校验和")
}
// 为所有块创建状态记录 // 为所有块创建状态记录
for i := 0; i < t.totalBlocks; i++ { for i := 0; i < t.totalBlocks; i++ {
filePath := filepath.Join(t.testDir, fmt.Sprintf("block_%d.dat", i)) filePath := filepath.Join(t.testDir, fmt.Sprintf("block_%d.dat", i))
@ -280,14 +351,20 @@ func (t *PowerLossTest) CheckIntegrity() *model.IntegrityInfo {
hash := sha256.Sum256(data) hash := sha256.Sum256(data)
checksum := hex.EncodeToString(hash[:]) checksum := hex.EncodeToString(hash[:])
var blockChecksum string // 获取期望的校验和 - 优先使用文件中的校验和映射
var expectedChecksum string
if storedChecksum, ok := checksumMap[i]; ok {
expectedChecksum = storedChecksum
} else {
// 回退到内存中的校验和
t.blocksMu.RLock() t.blocksMu.RLock()
if i < len(t.blocks) && t.blocks[i] != nil { if i < len(t.blocks) && t.blocks[i] != nil {
blockChecksum = t.blocks[i].Checksum expectedChecksum = t.blocks[i].Checksum
} }
t.blocksMu.RUnlock() t.blocksMu.RUnlock()
}
if blockChecksum != "" && checksum != blockChecksum { if expectedChecksum != "" && checksum != expectedChecksum {
// 数据损坏 // 数据损坏
t.integrityInfo.CorruptedBlocks++ t.integrityInfo.CorruptedBlocks++
t.integrityInfo.BlocksMap[i] = model.BlockStatus{ t.integrityInfo.BlocksMap[i] = model.BlockStatus{

View File

@ -0,0 +1,4 @@
package testcase
// DirectIOFlag 根据当前操作系统设置的直接IO标志
var DirectIOFlag int

View File

@ -0,0 +1,12 @@
//go:build darwin
// +build darwin
package testcase
// DarwinOpenFileFlag MacOS不支持O_DIRECT使用0代替
const DarwinOpenFileFlag = 0
func init() {
// MacOS上设置为0不支持O_DIRECT
DirectIOFlag = 0
}

View File

@ -0,0 +1,14 @@
//go:build linux
// +build linux
package testcase
import "syscall"
// LinuxOpenFileFlag 定义Linux特有的O_DIRECT标志
const LinuxOpenFileFlag = syscall.O_DIRECT
func init() {
// Linux上设置O_DIRECT标志
DirectIOFlag = syscall.O_DIRECT
}