1
0
Fork 0
mirror of https://github.com/chrislusf/seaweedfs synced 2025-09-10 05:12:47 +02:00
seaweedfs/weed/mount/ml/cache_policy_test.go
2025-08-30 15:32:00 -07:00

549 lines
13 KiB
Go

package ml
import (
"testing"
"time"
)
func TestMLCachePolicy_Basic(t *testing.T) {
policy := NewMLCachePolicy()
// Test basic eviction score calculation
entry := &CacheEntry{
Inode: 1,
Size: 1024,
LastAccess: time.Now(),
AccessCount: 5,
CacheLevel: 0,
Pattern: RandomAccess,
FileType: MLFileUnknown,
IsHot: false,
}
score := policy.CalculateEvictionScore(entry)
if score <= 0 {
t.Error("Eviction score should be positive")
}
shouldEvict := policy.ShouldEvict(entry)
t.Logf("Basic entry eviction: score=%.3f, shouldEvict=%v", score, shouldEvict)
}
func TestMLCachePolicy_ModelFileBoost(t *testing.T) {
policy := NewMLCachePolicy()
// Create two identical entries, one is a model file
baseEntry := &CacheEntry{
Inode: 1,
Size: 10 * 1024 * 1024, // 10MB
LastAccess: time.Now().Add(-5 * time.Minute),
AccessCount: 3,
CacheLevel: 0,
Pattern: SequentialAccess,
FileType: MLFileUnknown,
IsModel: false,
}
modelEntry := &CacheEntry{
Inode: 2,
Size: 10 * 1024 * 1024, // 10MB
LastAccess: time.Now().Add(-5 * time.Minute),
AccessCount: 3,
CacheLevel: 0,
Pattern: SequentialAccess,
FileType: MLFileModel,
IsModel: true,
}
baseScore := policy.CalculateEvictionScore(baseEntry)
modelScore := policy.CalculateEvictionScore(modelEntry)
if modelScore <= baseScore {
t.Errorf("Model file should have higher score than regular file: model=%.3f, base=%.3f",
modelScore, baseScore)
}
// Model files should be less likely to be evicted
baseShouldEvict := policy.ShouldEvict(baseEntry)
modelShouldEvict := policy.ShouldEvict(modelEntry)
if modelShouldEvict && !baseShouldEvict {
t.Error("Model file should not be evicted if regular file is not evicted")
}
t.Logf("Model vs Base eviction: model=%.3f (evict=%v), base=%.3f (evict=%v)",
modelScore, modelShouldEvict, baseScore, baseShouldEvict)
}
func TestMLCachePolicy_TrainingDataBoost(t *testing.T) {
policy := NewMLCachePolicy()
regularEntry := &CacheEntry{
Inode: 1,
Size: 1024,
LastAccess: time.Now().Add(-2 * time.Minute),
AccessCount: 10,
FileType: MLFileUnknown,
IsTrainingData: false,
}
trainingEntry := &CacheEntry{
Inode: 2,
Size: 1024,
LastAccess: time.Now().Add(-2 * time.Minute),
AccessCount: 10,
FileType: MLFileDataset,
IsTrainingData: true,
}
regularScore := policy.CalculateEvictionScore(regularEntry)
trainingScore := policy.CalculateEvictionScore(trainingEntry)
if trainingScore <= regularScore {
t.Errorf("Training data should have higher score: training=%.3f, regular=%.3f",
trainingScore, regularScore)
}
}
func TestMLCachePolicy_AccessPatternBoost(t *testing.T) {
policy := NewMLCachePolicy()
randomEntry := &CacheEntry{
Inode: 1,
Size: 1024,
LastAccess: time.Now(),
AccessCount: 5,
Pattern: RandomAccess,
FileType: MLFileDataset,
}
sequentialEntry := &CacheEntry{
Inode: 2,
Size: 1024,
LastAccess: time.Now(),
AccessCount: 5,
Pattern: SequentialAccess,
FileType: MLFileDataset,
}
modelAccessEntry := &CacheEntry{
Inode: 3,
Size: 1024,
LastAccess: time.Now(),
AccessCount: 5,
Pattern: ModelAccess,
FileType: MLFileModel,
}
randomScore := policy.CalculateEvictionScore(randomEntry)
sequentialScore := policy.CalculateEvictionScore(sequentialEntry)
modelScore := policy.CalculateEvictionScore(modelAccessEntry)
if sequentialScore <= randomScore {
t.Errorf("Sequential access should have higher score than random: seq=%.3f, random=%.3f",
sequentialScore, randomScore)
}
if modelScore <= sequentialScore {
t.Errorf("Model access should have highest score: model=%.3f, seq=%.3f",
modelScore, sequentialScore)
}
t.Logf("Pattern comparison: random=%.3f, sequential=%.3f, model=%.3f",
randomScore, sequentialScore, modelScore)
}
func TestMLCachePolicy_SizePreference(t *testing.T) {
policy := NewMLCachePolicy()
smallEntry := &CacheEntry{
Inode: 1,
Size: 1024, // 1KB
LastAccess: time.Now().Add(-5 * time.Minute),
AccessCount: 3,
FileType: MLFileUnknown,
}
largeEntry := &CacheEntry{
Inode: 2,
Size: 50 * 1024 * 1024, // 50MB
LastAccess: time.Now().Add(-5 * time.Minute),
AccessCount: 3,
FileType: MLFileUnknown,
}
smallScore := policy.CalculateEvictionScore(smallEntry)
largeScore := policy.CalculateEvictionScore(largeEntry)
if smallScore <= largeScore {
t.Errorf("Small files should have higher score than large files: small=%.3f, large=%.3f",
smallScore, largeScore)
}
}
func TestMLCachePolicy_RecencyDecay(t *testing.T) {
policy := NewMLCachePolicy()
// Create entries with different access times
recentEntry := &CacheEntry{
Inode: 1,
Size: 1024,
LastAccess: time.Now(),
AccessCount: 5,
FileType: MLFileUnknown,
}
oldEntry := &CacheEntry{
Inode: 2,
Size: 1024,
LastAccess: time.Now().Add(-20 * time.Minute),
AccessCount: 5,
FileType: MLFileUnknown,
}
recentScore := policy.CalculateEvictionScore(recentEntry)
oldScore := policy.CalculateEvictionScore(oldEntry)
if recentScore <= oldScore {
t.Errorf("Recent access should have higher score: recent=%.3f, old=%.3f",
recentScore, oldScore)
}
}
func TestMLCachePolicy_EpochRelevance(t *testing.T) {
policy := NewMLCachePolicy()
lowRelevanceEntry := &CacheEntry{
Inode: 1,
Size: 1024,
LastAccess: time.Now(),
AccessCount: 5,
FileType: MLFileDataset,
EpochRelevance: 0.2,
}
highRelevanceEntry := &CacheEntry{
Inode: 2,
Size: 1024,
LastAccess: time.Now(),
AccessCount: 5,
FileType: MLFileDataset,
EpochRelevance: 0.9,
}
lowScore := policy.CalculateEvictionScore(lowRelevanceEntry)
highScore := policy.CalculateEvictionScore(highRelevanceEntry)
if highScore <= lowScore {
t.Errorf("High epoch relevance should have higher score: high=%.3f, low=%.3f",
highScore, lowScore)
}
}
func TestMLCachePolicy_DifferentThresholds(t *testing.T) {
policy := NewMLCachePolicy()
// Create entries for different file types with same base score
unknownEntry := &CacheEntry{
Inode: 1,
Size: 1024,
LastAccess: time.Now().Add(-15 * time.Minute), // Old enough to potentially evict
AccessCount: 2,
FileType: MLFileUnknown,
}
modelEntry := &CacheEntry{
Inode: 2,
Size: 1024,
LastAccess: time.Now().Add(-15 * time.Minute),
AccessCount: 2,
FileType: MLFileModel,
IsModel: true,
}
datasetEntry := &CacheEntry{
Inode: 3,
Size: 1024,
LastAccess: time.Now().Add(-15 * time.Minute),
AccessCount: 2,
FileType: MLFileDataset,
Pattern: SequentialAccess,
}
unknownShouldEvict := policy.ShouldEvict(unknownEntry)
modelShouldEvict := policy.ShouldEvict(modelEntry)
datasetShouldEvict := policy.ShouldEvict(datasetEntry)
// Models should be least likely to be evicted
if modelShouldEvict && (!unknownShouldEvict || !datasetShouldEvict) {
t.Error("Model files should be least likely to be evicted")
}
t.Logf("Eviction by type: unknown=%v, model=%v, dataset=%v",
unknownShouldEvict, modelShouldEvict, datasetShouldEvict)
}
func TestMLCachePolicy_SetWeights(t *testing.T) {
policy := NewMLCachePolicy()
// Test setting custom weights
policy.SetWeights(0.4, 0.3, 0.1, 0.2)
if policy.accessFrequencyWeight != 0.4 {
t.Errorf("Expected frequency weight 0.4, got %.2f", policy.accessFrequencyWeight)
}
if policy.recencyWeight != 0.3 {
t.Errorf("Expected recency weight 0.3, got %.2f", policy.recencyWeight)
}
if policy.sizeWeight != 0.1 {
t.Errorf("Expected size weight 0.1, got %.2f", policy.sizeWeight)
}
if policy.mlWeight != 0.2 {
t.Errorf("Expected ML weight 0.2, got %.2f", policy.mlWeight)
}
// Test weight normalization
policy.SetWeights(2.0, 2.0, 1.0, 1.0) // Total = 6.0
expectedFreq := 2.0 / 6.0
if abs(policy.accessFrequencyWeight-expectedFreq) > 0.001 {
t.Errorf("Expected normalized frequency weight %.3f, got %.3f",
expectedFreq, policy.accessFrequencyWeight)
}
}
func TestMLCachePolicy_SetMLBoosts(t *testing.T) {
policy := NewMLCachePolicy()
// Test setting custom boost factors
policy.SetMLBoosts(2.0, 3.0, 1.5, 1.8)
if policy.trainingDataBoost != 2.0 {
t.Errorf("Expected training data boost 2.0, got %.2f", policy.trainingDataBoost)
}
if policy.modelFileBoost != 3.0 {
t.Errorf("Expected model file boost 3.0, got %.2f", policy.modelFileBoost)
}
if policy.sequentialBoost != 1.5 {
t.Errorf("Expected sequential boost 1.5, got %.2f", policy.sequentialBoost)
}
if policy.epochRelevanceBoost != 1.8 {
t.Errorf("Expected epoch relevance boost 1.8, got %.2f", policy.epochRelevanceBoost)
}
}
func TestMLCachePolicy_Metrics(t *testing.T) {
policy := NewMLCachePolicy()
// Simulate some evictions
entries := []*CacheEntry{
{FileType: MLFileModel, IsModel: true},
{FileType: MLFileDataset, IsTrainingData: true},
{FileType: MLFileUnknown},
}
for _, entry := range entries {
entry.LastAccess = time.Now().Add(-30 * time.Minute) // Old enough to evict
entry.AccessCount = 1
entry.Size = 1024
if policy.ShouldEvict(entry) {
// Eviction counters are updated in ShouldEvict
}
}
metrics := policy.GetEvictionMetrics()
if metrics.TotalEvictions == 0 {
t.Error("Should have some total evictions")
}
// Verify weight configuration in metrics
if metrics.AccessFrequencyWeight != policy.accessFrequencyWeight {
t.Error("Metrics should reflect current weight configuration")
}
}
func TestMLCachePolicy_HotChunkPreference(t *testing.T) {
policy := NewMLCachePolicy()
coldEntry := &CacheEntry{
Inode: 1,
Size: 1024,
LastAccess: time.Now(),
AccessCount: 5,
IsHot: false,
FileType: MLFileDataset,
}
hotEntry := &CacheEntry{
Inode: 2,
Size: 1024,
LastAccess: time.Now(),
AccessCount: 5,
IsHot: true,
FileType: MLFileDataset,
}
coldScore := policy.CalculateEvictionScore(coldEntry)
hotScore := policy.CalculateEvictionScore(hotEntry)
if hotScore <= coldScore {
t.Errorf("Hot chunk should have higher score: hot=%.3f, cold=%.3f", hotScore, coldScore)
}
}
func TestMLCachePolicy_RecencyThresholds(t *testing.T) {
policy := NewMLCachePolicy()
// Test hot threshold
hotEntry := &CacheEntry{
Inode: 1,
Size: 1024,
LastAccess: time.Now().Add(-30 * time.Second), // Within hot threshold
AccessCount: 1,
}
// Test cold threshold
coldEntry := &CacheEntry{
Inode: 2,
Size: 1024,
LastAccess: time.Now().Add(-15 * time.Minute), // Beyond cold threshold
AccessCount: 1,
}
// Test middle
middleEntry := &CacheEntry{
Inode: 3,
Size: 1024,
LastAccess: time.Now().Add(-5 * time.Minute), // Between thresholds
AccessCount: 1,
}
hotScore := policy.calculateRecencyScore(time.Since(hotEntry.LastAccess))
coldScore := policy.calculateRecencyScore(time.Since(coldEntry.LastAccess))
middleScore := policy.calculateRecencyScore(time.Since(middleEntry.LastAccess))
if hotScore != 1.0 {
t.Errorf("Hot entry should have score 1.0, got %.3f", hotScore)
}
if coldScore != 0.1 {
t.Errorf("Cold entry should have score 0.1, got %.3f", coldScore)
}
if middleScore <= coldScore || middleScore >= hotScore {
t.Errorf("Middle entry should have score between hot and cold: %.3f not in (%.3f, %.3f)",
middleScore, coldScore, hotScore)
}
}
func TestMLCachePolicy_SizeScore(t *testing.T) {
policy := NewMLCachePolicy()
smallSize := uint64(1024) // 1KB
largeSize := uint64(100 * 1024 * 1024) // 100MB
smallScore := policy.calculateSizeScore(smallSize)
largeScore := policy.calculateSizeScore(largeSize)
if smallScore <= largeScore {
t.Errorf("Small files should have higher size score: small=%.3f, large=%.3f",
smallScore, largeScore)
}
// Large files should still have reasonable score (not too low)
if largeScore < 0.2 {
t.Errorf("Large files should have reasonable score, got %.3f", largeScore)
}
}
func TestMLCachePolicy_AccessFrequencyScore(t *testing.T) {
policy := NewMLCachePolicy()
lowAccessEntry := &CacheEntry{
AccessCount: 1,
FileType: MLFileUnknown,
Pattern: RandomAccess,
}
highAccessEntry := &CacheEntry{
AccessCount: 100,
FileType: MLFileUnknown,
Pattern: RandomAccess,
}
lowScore := policy.calculateAccessFrequencyScore(lowAccessEntry)
highScore := policy.calculateAccessFrequencyScore(highAccessEntry)
if highScore <= lowScore {
t.Errorf("High access count should have higher score: high=%.3f, low=%.3f",
highScore, lowScore)
}
}
// Helper function
func abs(x float64) float64 {
if x < 0 {
return -x
}
return x
}
// Benchmark tests
func BenchmarkMLCachePolicy_CalculateEvictionScore(b *testing.B) {
policy := NewMLCachePolicy()
entry := &CacheEntry{
Inode: 1,
Size: 1024,
LastAccess: time.Now().Add(-5 * time.Minute),
AccessCount: 10,
FileType: MLFileDataset,
Pattern: SequentialAccess,
IsTrainingData: true,
EpochRelevance: 0.8,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
policy.CalculateEvictionScore(entry)
}
}
func BenchmarkMLCachePolicy_ShouldEvict(b *testing.B) {
policy := NewMLCachePolicy()
entry := &CacheEntry{
Inode: 1,
Size: 1024,
LastAccess: time.Now().Add(-5 * time.Minute),
AccessCount: 10,
FileType: MLFileDataset,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
policy.ShouldEvict(entry)
}
}