package testcase import ( "context" "fmt" "math/rand" "os" "path/filepath" "time" "plp-test/internal/config" "plp-test/internal/model" "plp-test/internal/utils" "github.com/sirupsen/logrus" ) // RandomWriteTest 随机写入测试 type RandomWriteTest struct { *BaseTestCase testDir string blockSize int totalBlocks int writtenBlocks int verifiedBlocks int writeSequence []int // 记录写入顺序 blocks map[int]*model.TestBlock writeLatencies []float64 readLatencies []float64 } // NewRandomWriteTest 创建随机写入测试 func NewRandomWriteTest(cfg *config.Config, logger *logrus.Logger) *RandomWriteTest { baseTest := NewBaseTestCase( "random", "测试在Open-CAS环境中进行随机写入时的数据完整性", cfg, logger, ) return &RandomWriteTest{ BaseTestCase: baseTest, blockSize: utils.KBToBytes(float64(cfg.Test.BlockSize)), totalBlocks: utils.MBToBytes(float64(cfg.Test.DataSizeMB)) / utils.KBToBytes(float64(cfg.Test.BlockSize)), blocks: make(map[int]*model.TestBlock), writeSequence: make([]int, 0), writeLatencies: make([]float64, 0), readLatencies: make([]float64, 0), } } // Setup 设置测试环境 func (t *RandomWriteTest) Setup(ctx context.Context, recovery bool) error { if err := t.BaseTestCase.Setup(ctx); err != nil { return err } t.setMessage("创建Open-CAS缓存实例") id := t.config.Server.CacheInstanceID nvme := t.config.Server.DevicesNVMe hdd := t.config.Server.DevicesHDD // 创建缓存实例 err := t.casManager.CreateCacheInstance(id, nvme, hdd, "wb") // 使用write-back模式 if err != nil { t.setStatus(StatusFailed) return fmt.Errorf("创建缓存实例失败: %v", err) } // 获取缓存设备路径 cacheDevice := fmt.Sprintf("/dev/cas%s-1", id) t.setMessage(fmt.Sprintf("格式化缓存设备 %s", cacheDevice)) // 格式化缓存设备 err = t.casManager.FormatDevice(cacheDevice, "ext4") if err != nil { t.setStatus(StatusFailed) return fmt.Errorf("格式化缓存设备失败: %v", err) } // 挂载缓存设备 mountPoint := t.config.Server.MountPoint t.setMessage(fmt.Sprintf("挂载缓存设备到 %s", mountPoint)) err = t.casManager.MountDevice(cacheDevice, mountPoint) if err != nil { t.setStatus(StatusFailed) return fmt.Errorf("挂载缓存设备失败: %v", err) } // 创建测试目录 t.testDir = filepath.Join(mountPoint, "random_test") t.setMessage(fmt.Sprintf("创建测试目录 %s", t.testDir)) err = utils.CreateDirIfNotExist(t.testDir) if err != nil { t.setStatus(StatusFailed) return fmt.Errorf("创建测试目录失败: %v", err) } // 生成随机的写入顺序 rand.Seed(time.Now().UnixNano()) indices := make([]int, t.totalBlocks) for i := 0; i < t.totalBlocks; i++ { indices[i] = i } // 洗牌算法生成随机序列 for i := range indices { j := rand.Intn(i + 1) indices[i], indices[j] = indices[j], indices[i] } t.writeSequence = indices t.setProgress(10) return nil } // Run 运行测试 func (t *RandomWriteTest) Run(ctx context.Context) (*model.TestResult, error) { t.setMessage("开始随机写入测试") startTime := time.Now() var totalBytesWritten int // 写入阶段 t.setMessage("写入阶段") for i, blockIdx := range t.writeSequence { select { case <-ctx.Done(): t.setStatus(StatusAborted) return nil, ctx.Err() default: // 生成随机数据 data, err := utils.GenerateRandomData(t.blockSize * 1024) if err != nil { t.setStatus(StatusFailed) return nil, fmt.Errorf("生成随机数据失败: %v", err) } // 创建测试数据块 block := model.NewTestBlock(data, blockIdx) t.blocks[blockIdx] = block // 写入文件 filePath := filepath.Join(t.testDir, fmt.Sprintf("block_%d.dat", blockIdx)) writeStart := time.Now() err = os.WriteFile(filePath, data, 0644) if err != nil { t.setStatus(StatusFailed) return nil, fmt.Errorf("写入文件 %s 失败: %v", filePath, err) } // 记录写入延迟 writeLatency := time.Since(writeStart).Seconds() * 1000 // 毫秒 t.writeLatencies = append(t.writeLatencies, writeLatency) t.writtenBlocks++ totalBytesWritten += len(data) // 更新进度 progress := float64(i+1) / float64(t.totalBlocks) * 50 // 写入阶段占50% t.setProgress(progress) // 每隔一定数量的块,强制刷新缓存 if i > 0 && i%100 == 0 { t.logger.Infof("已写入 %d 个数据块,强制刷新缓存", i) t.casManager.FlushCache(t.config.Server.CacheInstanceID) } } } // 验证阶段 - 按随机顺序读取 t.setMessage("验证阶段") // 再次生成随机顺序,用于随机读取 readIndices := make([]int, len(t.writeSequence)) copy(readIndices, t.writeSequence) rand.Shuffle(len(readIndices), func(i, j int) { readIndices[i], readIndices[j] = readIndices[j], readIndices[i] }) for i, blockIdx := range readIndices { select { case <-ctx.Done(): t.setStatus(StatusAborted) return nil, ctx.Err() default: block, ok := t.blocks[blockIdx] if !ok { t.setStatus(StatusFailed) return nil, fmt.Errorf("找不到索引为 %d 的数据块", blockIdx) } filePath := filepath.Join(t.testDir, fmt.Sprintf("block_%d.dat", blockIdx)) readStart := time.Now() data, err := os.ReadFile(filePath) if err != nil { t.setStatus(StatusFailed) return nil, fmt.Errorf("读取文件 %s 失败: %v", filePath, err) } // 记录读取延迟 readLatency := time.Since(readStart).Seconds() * 1000 // 毫秒 t.readLatencies = append(t.readLatencies, readLatency) // 验证数据完整性 if string(data) != string(block.Data) { t.setStatus(StatusFailed) return nil, fmt.Errorf("数据验证失败,块 %d 内容不匹配", blockIdx) } t.verifiedBlocks++ // 更新进度 progress := 50 + float64(i+1)/float64(len(readIndices))*50 // 验证阶段占50% t.setProgress(progress) } } // 计算性能指标 duration := time.Since(startTime) writeSpeedMBs := utils.BytesToMB(totalBytesWritten) / duration.Seconds() // 计算平均延迟 var totalWriteLatency, totalReadLatency float64 for _, latency := range t.writeLatencies { totalWriteLatency += latency } for _, latency := range t.readLatencies { totalReadLatency += latency } avgWriteLatency := totalWriteLatency / float64(len(t.writeLatencies)) avgReadLatency := totalReadLatency / float64(len(t.readLatencies)) // 计算IOPS operations := t.writtenBlocks + t.verifiedBlocks iops := utils.CalculateIOPS(operations, duration) t.setProgress(100) t.setStatus(StatusCompleted) t.setMessage("随机写入测试完成") // 构造测试结果 result := t.getTestResult() result.BlocksWritten = t.writtenBlocks result.BlocksVerified = t.verifiedBlocks result.DataWrittenMB = utils.BytesToMB(totalBytesWritten) result.WriteSpeedMBs = writeSpeedMBs result.Metrics = model.TestMetrics{ WriteLatencyMs: avgWriteLatency, ReadLatencyMs: avgReadLatency, IOPS: iops, DataIntegrityLoss: 0, // 没有数据完整性丢失 } return result, nil } // Cleanup 清理测试环境 func (t *RandomWriteTest) Cleanup(ctx context.Context) error { if err := t.BaseTestCase.Cleanup(ctx); err != nil { return err } t.setMessage("卸载缓存设备") err := t.casManager.UnmountDevice(t.config.Server.MountPoint) if err != nil { t.logger.Warnf("卸载缓存设备失败: %v", err) } t.setMessage("停止缓存实例") err = t.casManager.StopCacheInstance(t.config.Server.CacheInstanceID) if err != nil { t.logger.Warnf("停止缓存实例失败: %v", err) } return nil }