diff options
Diffstat (limited to 'weed/mount/access_pattern_test.go')
| -rw-r--r-- | weed/mount/access_pattern_test.go | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/weed/mount/access_pattern_test.go b/weed/mount/access_pattern_test.go new file mode 100644 index 000000000..f3c05d268 --- /dev/null +++ b/weed/mount/access_pattern_test.go @@ -0,0 +1,357 @@ +package mount + +import ( + "testing" + "time" +) + +func TestAccessPatternDetector_Sequential(t *testing.T) { + apd := NewAccessPatternDetector() + + inode := uint64(1) + + // Simulate sequential access pattern + info1 := apd.RecordAccess(inode, 0, 1024) + if info1.Pattern != RandomAccess { + t.Error("First access should be detected as random") + } + + info2 := apd.RecordAccess(inode, 1024, 1024) + if info2.ConsecutiveSeq != 1 { + t.Error("Second sequential access should increment counter") + } + + info3 := apd.RecordAccess(inode, 2048, 1024) + if info3.ConsecutiveSeq != 2 { + t.Error("Third sequential access should increment counter") + } + + info4 := apd.RecordAccess(inode, 3072, 1024) + if info4.Pattern != SequentialAccess { + t.Errorf("After %d sequential accesses, pattern should be Sequential, got: %v", + apd.sequentialThreshold+1, info4.Pattern) + } + + if info4.PrefetchSize <= 0 { + t.Error("Sequential access should set prefetch size") + } + + shouldPrefetch, prefetchSize := apd.ShouldPrefetch(inode) + if !shouldPrefetch { + t.Error("Should recommend prefetch for sequential access") + } + + if prefetchSize != info4.PrefetchSize { + t.Errorf("Prefetch size mismatch: expected %d, got %d", info4.PrefetchSize, prefetchSize) + } +} + +func TestAccessPatternDetector_Random(t *testing.T) { + apd := NewAccessPatternDetector() + + inode := uint64(2) + + // Simulate random access pattern + offsets := []int64{0, 5000, 1000, 10000, 2000} + + for _, offset := range offsets { + info := apd.RecordAccess(inode, offset, 1024) + if info.ConsecutiveSeq > 0 && info != apd.fileInfo[inode] { + // Reset should happen on non-sequential access + t.Error("Sequential counter should reset on random access") + } + } + + finalInfo := apd.fileInfo[inode] + if finalInfo.Pattern != RandomAccess { + t.Errorf("Pattern should remain RandomAccess, got: %v", finalInfo.Pattern) + } + + shouldPrefetch, _ := apd.ShouldPrefetch(inode) + if shouldPrefetch { + t.Error("Should not recommend prefetch for random access") + } +} + +func TestAccessPatternDetector_ModelAccess(t *testing.T) { + apd := NewAccessPatternDetector() + + inode := uint64(3) + + // Simulate model file loading (large sequential reads) + largeSize := 2 * 1024 * 1024 // 2MB + + apd.RecordAccess(inode, 0, largeSize) + apd.RecordAccess(inode, int64(largeSize), largeSize) + apd.RecordAccess(inode, int64(largeSize*2), largeSize) + + info := apd.RecordAccess(inode, int64(largeSize*3), largeSize) + + if info.Pattern != ModelAccess { + t.Errorf("Large sequential reads should be detected as ModelAccess, got: %v", info.Pattern) + } + + if info.Confidence < 0.9 { + t.Errorf("Model access should have high confidence, got: %.2f", info.Confidence) + } + + shouldPrefetch, prefetchSize := apd.ShouldPrefetch(inode) + if !shouldPrefetch { + t.Error("Should recommend prefetch for model access") + } + + if prefetchSize < 4*1024*1024 { // Should be at least 4MB for models + t.Errorf("Model access should have large prefetch size, got: %d", prefetchSize) + } +} + +func TestAccessPatternDetector_EpochAccess(t *testing.T) { + apd := NewAccessPatternDetector() + + inode := uint64(4) + + // Simulate many accesses first + for i := 0; i < 150; i++ { + apd.RecordAccess(inode, int64(i*1024), 1024) + } + + // Simulate gap (sleep not needed, just update last access time) + info := apd.fileInfo[inode] + info.LastAccessTime = time.Now().Add(-2 * time.Minute) + + // Access from beginning again (epoch restart) + epochInfo := apd.RecordAccess(inode, 0, 1024) + + if epochInfo.Pattern != EpochAccess { + t.Errorf("Restart from beginning should be detected as EpochAccess, got: %v", epochInfo.Pattern) + } + + shouldPrefetch, prefetchSize := apd.ShouldPrefetch(inode) + if !shouldPrefetch { + t.Error("Should recommend prefetch for epoch access") + } + + if prefetchSize < 256*1024 { // Should have reasonable prefetch size + t.Errorf("Epoch access should have decent prefetch size, got: %d", prefetchSize) + } +} + +func TestAccessPatternDetector_StridedAccess(t *testing.T) { + apd := NewAccessPatternDetector() + + inode := uint64(5) + + // Simulate strided access (e.g., reading every nth byte for image processing) + stride := int64(4096) + + apd.RecordAccess(inode, 0, 1024) + apd.RecordAccess(inode, 1024+stride, 1024) // Gap between reads + apd.RecordAccess(inode, 2048+stride*2, 1024) + info := apd.RecordAccess(inode, 3072+stride*3, 1024) + + // Note: Current simple implementation may not detect complex stride patterns + // This test validates the structure is in place + t.Logf("Strided access pattern: %v (confidence: %.2f)", info.Pattern, info.Confidence) +} + +func TestAccessPatternDetector_PatternTransition(t *testing.T) { + apd := NewAccessPatternDetector() + + inode := uint64(6) + + // Start with sequential + apd.RecordAccess(inode, 0, 1024) + apd.RecordAccess(inode, 1024, 1024) + apd.RecordAccess(inode, 2048, 1024) + info := apd.RecordAccess(inode, 3072, 1024) + + if info.Pattern != SequentialAccess { + t.Error("Should detect sequential pattern") + } + + // Break with random access + randomInfo := apd.RecordAccess(inode, 10000, 1024) + + if randomInfo.Pattern != RandomAccess { + t.Errorf("Pattern should transition to RandomAccess after break, got: %v", randomInfo.Pattern) + } + + if randomInfo.PrefetchSize != 0 { + t.Error("Prefetch size should be reset after pattern break") + } +} + +func TestAccessPatternDetector_MultipleFiles(t *testing.T) { + apd := NewAccessPatternDetector() + + // Test tracking multiple files simultaneously + file1 := uint64(10) + file2 := uint64(20) + + // File 1: Sequential pattern + apd.RecordAccess(file1, 0, 1024) + apd.RecordAccess(file1, 1024, 1024) + apd.RecordAccess(file1, 2048, 1024) + seq_info := apd.RecordAccess(file1, 3072, 1024) + + // File 2: Random pattern + apd.RecordAccess(file2, 5000, 1024) + apd.RecordAccess(file2, 1000, 1024) + random_info := apd.RecordAccess(file2, 8000, 1024) + + if seq_info.Pattern != SequentialAccess { + t.Error("File 1 should maintain sequential pattern") + } + + if random_info.Pattern != RandomAccess { + t.Error("File 2 should maintain random pattern") + } + + // Verify independent tracking + pattern1 := apd.GetPattern(file1) + pattern2 := apd.GetPattern(file2) + + if pattern1 != SequentialAccess || pattern2 != RandomAccess { + t.Error("Files should maintain independent patterns") + } +} + +func TestAccessPatternDetector_Metrics(t *testing.T) { + apd := NewAccessPatternDetector() + + // Generate some access patterns + file1 := uint64(100) + file2 := uint64(200) + + // Sequential accesses for file1 + for i := 0; i < 5; i++ { + apd.RecordAccess(file1, int64(i*1024), 1024) + } + + // Random accesses for file2 + offsets := []int64{0, 5000, 1000, 10000} + for _, offset := range offsets { + apd.RecordAccess(file2, offset, 1024) + } + + metrics := apd.GetMetrics() + + if metrics.TotalAccesses != 9 { + t.Errorf("Expected 9 total accesses, got: %d", metrics.TotalAccesses) + } + + if metrics.TotalFiles != 2 { + t.Errorf("Expected 2 files, got: %d", metrics.TotalFiles) + } + + if metrics.PatternCounts[SequentialAccess] != 1 { + t.Errorf("Expected 1 sequential file, got: %d", metrics.PatternCounts[SequentialAccess]) + } + + if metrics.PatternCounts[RandomAccess] != 1 { + t.Errorf("Expected 1 random file, got: %d", metrics.PatternCounts[RandomAccess]) + } +} + +func TestAccessPatternDetector_Cleanup(t *testing.T) { + apd := NewAccessPatternDetector() + + inode := uint64(999) + + // Create an access record + apd.RecordAccess(inode, 0, 1024) + + // Verify it exists + if len(apd.fileInfo) != 1 { + t.Error("Should have one file info entry") + } + + // Set old timestamp + info := apd.fileInfo[inode] + info.LastAccessTime = time.Now().Add(-2 * time.Hour) + + // Cleanup old entries + apd.CleanupOldEntries(1 * time.Hour) + + if len(apd.fileInfo) != 0 { + t.Error("Old entry should have been cleaned up") + } +} + +func TestAccessPatternDetector_Confidence(t *testing.T) { + apd := NewAccessPatternDetector() + apd.confidenceThreshold = 0.8 // High threshold for testing + + inode := uint64(888) + + // Start sequential access but don't reach high confidence + apd.RecordAccess(inode, 0, 1024) + apd.RecordAccess(inode, 1024, 1024) + apd.RecordAccess(inode, 2048, 1024) + info := apd.RecordAccess(inode, 3072, 1024) + + // Should be sequential but low confidence + if info.Pattern != SequentialAccess { + t.Error("Should detect sequential pattern") + } + + if info.Confidence >= 0.8 { + t.Errorf("Early sequential detection should have low confidence, got: %.2f", info.Confidence) + } + + // Should not recommend prefetch due to low confidence + shouldPrefetch, _ := apd.ShouldPrefetch(inode) + if shouldPrefetch { + t.Error("Should not prefetch with low confidence") + } + + // Continue sequential access to build confidence + for i := 4; i < 8; i++ { + apd.RecordAccess(inode, int64(i*1024), 1024) + } + + // Now should have high confidence + highConfInfo := apd.fileInfo[inode] + if highConfInfo.Confidence < 0.8 { + t.Errorf("Extended sequential access should have high confidence, got: %.2f", highConfInfo.Confidence) + } + + shouldPrefetch, _ = apd.ShouldPrefetch(inode) + if !shouldPrefetch { + t.Error("Should prefetch with high confidence") + } +} + +// Benchmark tests + +func BenchmarkAccessPatternDetector_RecordAccess(b *testing.B) { + apd := NewAccessPatternDetector() + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + inode := uint64(i % 100) // Cycle through 100 different files + offset := int64(i * 1024) + apd.RecordAccess(inode, offset, 1024) + } +} + +func BenchmarkAccessPatternDetector_ShouldPrefetch(b *testing.B) { + apd := NewAccessPatternDetector() + + // Setup some files with different patterns + for i := 0; i < 100; i++ { + inode := uint64(i) + // Create sequential pattern + for j := 0; j < 5; j++ { + apd.RecordAccess(inode, int64(j*1024), 1024) + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + inode := uint64(i % 100) + apd.ShouldPrefetch(inode) + } +} |
