plp-test/internal/testcase/random_write-test.go
2025-04-23 10:33:06 +08:00

277 lines
7.5 KiB
Go

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) 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
}