All checks were successful
Go Tests / Run Tests (1.24.2) (push) Successful in 9m48s
237 lines
6.1 KiB
Go
237 lines
6.1 KiB
Go
package stats
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestCollector_TrackOperation(t *testing.T) {
|
|
collector := NewCollector()
|
|
|
|
// Track operations
|
|
collector.TrackOperation(OpPut)
|
|
collector.TrackOperation(OpPut)
|
|
collector.TrackOperation(OpGet)
|
|
|
|
// Get stats
|
|
stats := collector.GetStats()
|
|
|
|
// Verify counts
|
|
if stats["put_ops"].(uint64) != 2 {
|
|
t.Errorf("Expected 2 put operations, got %v", stats["put_ops"])
|
|
}
|
|
|
|
if stats["get_ops"].(uint64) != 1 {
|
|
t.Errorf("Expected 1 get operation, got %v", stats["get_ops"])
|
|
}
|
|
|
|
// Verify last operation times exist
|
|
if _, exists := stats["last_put_time"]; !exists {
|
|
t.Errorf("Expected last_put_time to exist in stats")
|
|
}
|
|
|
|
if _, exists := stats["last_get_time"]; !exists {
|
|
t.Errorf("Expected last_get_time to exist in stats")
|
|
}
|
|
}
|
|
|
|
func TestCollector_TrackOperationWithLatency(t *testing.T) {
|
|
collector := NewCollector()
|
|
|
|
// Track operations with latency
|
|
collector.TrackOperationWithLatency(OpGet, 100)
|
|
collector.TrackOperationWithLatency(OpGet, 200)
|
|
collector.TrackOperationWithLatency(OpGet, 300)
|
|
|
|
// Get stats
|
|
stats := collector.GetStats()
|
|
|
|
// Check latency stats
|
|
latencyStats, ok := stats["get_latency"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("Expected get_latency to be a map, got %T", stats["get_latency"])
|
|
}
|
|
|
|
if count := latencyStats["count"].(uint64); count != 3 {
|
|
t.Errorf("Expected 3 latency records, got %v", count)
|
|
}
|
|
|
|
if avg := latencyStats["avg_ns"].(uint64); avg != 200 {
|
|
t.Errorf("Expected average latency 200ns, got %v", avg)
|
|
}
|
|
|
|
if min := latencyStats["min_ns"].(uint64); min != 100 {
|
|
t.Errorf("Expected min latency 100ns, got %v", min)
|
|
}
|
|
|
|
if max := latencyStats["max_ns"].(uint64); max != 300 {
|
|
t.Errorf("Expected max latency 300ns, got %v", max)
|
|
}
|
|
}
|
|
|
|
func TestCollector_ConcurrentAccess(t *testing.T) {
|
|
collector := NewCollector()
|
|
const numGoroutines = 10
|
|
const opsPerGoroutine = 1000
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(numGoroutines)
|
|
|
|
// Launch goroutines to track operations concurrently
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < opsPerGoroutine; j++ {
|
|
// Mix different operations
|
|
switch j % 3 {
|
|
case 0:
|
|
collector.TrackOperation(OpPut)
|
|
case 1:
|
|
collector.TrackOperation(OpGet)
|
|
case 2:
|
|
collector.TrackOperationWithLatency(OpDelete, uint64(j))
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Get stats
|
|
stats := collector.GetStats()
|
|
|
|
// There should be approximately opsPerGoroutine * numGoroutines / 3 operations of each type
|
|
expectedOps := uint64(numGoroutines * opsPerGoroutine / 3)
|
|
|
|
// Allow for small variations due to concurrent execution
|
|
// Use 99% of expected as minimum threshold
|
|
minThreshold := expectedOps * 99 / 100
|
|
|
|
if ops := stats["put_ops"].(uint64); ops < minThreshold {
|
|
t.Errorf("Expected approximately %d put operations, got %v (below threshold %d)",
|
|
expectedOps, ops, minThreshold)
|
|
}
|
|
|
|
if ops := stats["get_ops"].(uint64); ops < minThreshold {
|
|
t.Errorf("Expected approximately %d get operations, got %v (below threshold %d)",
|
|
expectedOps, ops, minThreshold)
|
|
}
|
|
|
|
if ops := stats["delete_ops"].(uint64); ops < minThreshold {
|
|
t.Errorf("Expected approximately %d delete operations, got %v (below threshold %d)",
|
|
expectedOps, ops, minThreshold)
|
|
}
|
|
}
|
|
|
|
func TestCollector_GetStatsFiltered(t *testing.T) {
|
|
collector := NewCollector()
|
|
|
|
// Track different operations
|
|
collector.TrackOperation(OpPut)
|
|
collector.TrackOperation(OpGet)
|
|
collector.TrackOperation(OpGet)
|
|
collector.TrackOperation(OpDelete)
|
|
collector.TrackError("io_error")
|
|
collector.TrackError("network_error")
|
|
|
|
// Filter by "get" prefix
|
|
getStats := collector.GetStatsFiltered("get")
|
|
|
|
// Should only contain get_ops and related stats
|
|
if len(getStats) == 0 {
|
|
t.Errorf("Expected non-empty filtered stats")
|
|
}
|
|
|
|
if _, exists := getStats["get_ops"]; !exists {
|
|
t.Errorf("Expected get_ops in filtered stats")
|
|
}
|
|
|
|
if _, exists := getStats["put_ops"]; exists {
|
|
t.Errorf("Did not expect put_ops in get-filtered stats")
|
|
}
|
|
|
|
// Filter by "error" prefix
|
|
errorStats := collector.GetStatsFiltered("error")
|
|
|
|
if _, exists := errorStats["errors"]; !exists {
|
|
t.Errorf("Expected errors in error-filtered stats")
|
|
}
|
|
}
|
|
|
|
func TestCollector_TrackBytes(t *testing.T) {
|
|
collector := NewCollector()
|
|
|
|
// Track read and write bytes
|
|
collector.TrackBytes(true, 1000) // write
|
|
collector.TrackBytes(false, 500) // read
|
|
|
|
stats := collector.GetStats()
|
|
|
|
if bytesWritten := stats["total_bytes_written"].(uint64); bytesWritten != 1000 {
|
|
t.Errorf("Expected 1000 bytes written, got %v", bytesWritten)
|
|
}
|
|
|
|
if bytesRead := stats["total_bytes_read"].(uint64); bytesRead != 500 {
|
|
t.Errorf("Expected 500 bytes read, got %v", bytesRead)
|
|
}
|
|
}
|
|
|
|
func TestCollector_TrackMemTableSize(t *testing.T) {
|
|
collector := NewCollector()
|
|
|
|
// Track memtable size
|
|
collector.TrackMemTableSize(2048)
|
|
|
|
stats := collector.GetStats()
|
|
|
|
if size := stats["memtable_size"].(uint64); size != 2048 {
|
|
t.Errorf("Expected memtable size 2048, got %v", size)
|
|
}
|
|
|
|
// Update memtable size
|
|
collector.TrackMemTableSize(4096)
|
|
|
|
stats = collector.GetStats()
|
|
|
|
if size := stats["memtable_size"].(uint64); size != 4096 {
|
|
t.Errorf("Expected updated memtable size 4096, got %v", size)
|
|
}
|
|
}
|
|
|
|
func TestCollector_RecoveryStats(t *testing.T) {
|
|
collector := NewCollector()
|
|
|
|
// Start recovery
|
|
startTime := collector.StartRecovery()
|
|
|
|
// Simulate some work
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// Finish recovery
|
|
collector.FinishRecovery(startTime, 5, 1000, 2)
|
|
|
|
stats := collector.GetStats()
|
|
recoveryStats, ok := stats["recovery"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("Expected recovery stats to be a map")
|
|
}
|
|
|
|
if filesRecovered := recoveryStats["wal_files_recovered"].(uint64); filesRecovered != 5 {
|
|
t.Errorf("Expected 5 files recovered, got %v", filesRecovered)
|
|
}
|
|
|
|
if entriesRecovered := recoveryStats["wal_entries_recovered"].(uint64); entriesRecovered != 1000 {
|
|
t.Errorf("Expected 1000 entries recovered, got %v", entriesRecovered)
|
|
}
|
|
|
|
if corruptedEntries := recoveryStats["wal_corrupted_entries"].(uint64); corruptedEntries != 2 {
|
|
t.Errorf("Expected 2 corrupted entries, got %v", corruptedEntries)
|
|
}
|
|
|
|
if _, exists := recoveryStats["wal_recovery_duration_ms"]; !exists {
|
|
t.Errorf("Expected recovery duration to be recorded")
|
|
}
|
|
}
|