From 4559bb2cd6250f88d8329ae27ab3436b36a47b24 Mon Sep 17 00:00:00 2001 From: td-zhangshun Date: Wed, 23 Apr 2025 15:22:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=9D=97=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/testcase/power_loss-test.go | 109 +++++++++++++++++++++++---- internal/testcase/syscall_common.go | 4 + internal/testcase/syscall_darwin.go | 12 +++ internal/testcase/syscall_linux.go | 14 ++++ 4 files changed, 123 insertions(+), 16 deletions(-) create mode 100644 internal/testcase/syscall_common.go create mode 100644 internal/testcase/syscall_darwin.go create mode 100644 internal/testcase/syscall_linux.go diff --git a/internal/testcase/power_loss-test.go b/internal/testcase/power_loss-test.go index ff22337..c27f774 100644 --- a/internal/testcase/power_loss-test.go +++ b/internal/testcase/power_loss-test.go @@ -4,11 +4,11 @@ import ( "context" "crypto/sha256" "encoding/hex" + "encoding/json" "fmt" "os" "path/filepath" "sync" - "syscall" "time" "plp-test/internal/config" @@ -140,8 +140,12 @@ func (t *PowerLossTest) Run(ctx context.Context) (*model.TestResult, error) { startTime := time.Now() 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++ { select { @@ -159,6 +163,54 @@ func (t *PowerLossTest) Run(ctx context.Context) (*model.TestResult, error) { // 创建测试数据块 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.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)) - // direct IO 直接写入磁盘,必须使用syscall.O_DIRECT,Linux 2.6.29 及以上版本支持 - file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|syscall.O_DIRECT, 0644) + // direct IO 直接写入磁盘,使用跨平台的DirectIOFlag + file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|DirectIOFlag, 0644) if err != nil { t.setStatus(StatusFailed) return nil, fmt.Errorf("写入文件 %s 失败: %v", filePath, err) } defer file.Close() - _, err = file.Write(data) + _, err = file.Write(block.Data) if err != nil { t.setStatus(StatusFailed) return nil, fmt.Errorf("写入文件 %s 失败: %v", filePath, err) } t.writtenBlocks++ - totalBytesWritten += len(data) + totalBytesWritten += len(block.Data) t.setMessage(fmt.Sprintf("同步数据到磁盘 (已写入 %d/%d 块)", i, t.totalBlocks)) _, 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) // 每写入一定数量的打印输出下当前进度到日志 if i > 0 && i%100 == 0 { 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("同步所有数据到磁盘") - _, err := utils.ExecuteCommand("sync") + _, err = utils.ExecuteCommand("sync") if err != nil { t.logger.Warnf("执行sync命令失败: %v", err) } @@ -248,6 +299,26 @@ func (t *PowerLossTest) CheckIntegrity() *model.IntegrityInfo { t.integrityInfo.CorruptedBlocks = 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++ { 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) checksum := hex.EncodeToString(hash[:]) - var blockChecksum string - t.blocksMu.RLock() - if i < len(t.blocks) && t.blocks[i] != nil { - blockChecksum = t.blocks[i].Checksum + // 获取期望的校验和 - 优先使用文件中的校验和映射 + var expectedChecksum string + if storedChecksum, ok := checksumMap[i]; ok { + expectedChecksum = storedChecksum + } else { + // 回退到内存中的校验和 + t.blocksMu.RLock() + if i < len(t.blocks) && t.blocks[i] != nil { + expectedChecksum = t.blocks[i].Checksum + } + t.blocksMu.RUnlock() } - t.blocksMu.RUnlock() - if blockChecksum != "" && checksum != blockChecksum { + if expectedChecksum != "" && checksum != expectedChecksum { // 数据损坏 t.integrityInfo.CorruptedBlocks++ t.integrityInfo.BlocksMap[i] = model.BlockStatus{ diff --git a/internal/testcase/syscall_common.go b/internal/testcase/syscall_common.go new file mode 100644 index 0000000..757bb83 --- /dev/null +++ b/internal/testcase/syscall_common.go @@ -0,0 +1,4 @@ +package testcase + +// DirectIOFlag 根据当前操作系统设置的直接IO标志 +var DirectIOFlag int diff --git a/internal/testcase/syscall_darwin.go b/internal/testcase/syscall_darwin.go new file mode 100644 index 0000000..feec122 --- /dev/null +++ b/internal/testcase/syscall_darwin.go @@ -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 +} diff --git a/internal/testcase/syscall_linux.go b/internal/testcase/syscall_linux.go new file mode 100644 index 0000000..21ed178 --- /dev/null +++ b/internal/testcase/syscall_linux.go @@ -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 +}