diff --git a/cache.go b/cache.go index db88d2f..bf02c74 100644 --- a/cache.go +++ b/cache.go @@ -8,6 +8,7 @@ import ( "runtime" "sync" "time" + "unsafe" ) type Item struct { @@ -1034,6 +1035,57 @@ func (c *cache) LoadFile(fname string) error { return fp.Close() } +// Return current size of all values +// WARNING: this function assumes all values can be converted to a buffer +// of bytes to gather their length +func (c *cache) Size() (size int) { + c.mu.RLock() + defer c.mu.RUnlock() + size = 0 + now := time.Now().UnixNano() + for _, v := range c.items { + // "Inlining" of Expired + if v.Expiration > 0 && now > v.Expiration { + continue + } + switch value := v.Object.(type) { + case bool: + size += int(unsafe.Sizeof(value)) + case int: + size += int(unsafe.Sizeof(value)) + case int8: + size += int(unsafe.Sizeof(value)) + case int16: + size += int(unsafe.Sizeof(value)) + case int32: // rune + size += int(unsafe.Sizeof(value)) + case int64: + size += int(unsafe.Sizeof(value)) + case uint: + size += int(unsafe.Sizeof(value)) + case uint8: // byte + size += int(unsafe.Sizeof(value)) + case uint16: + size += int(unsafe.Sizeof(value)) + case uint32: + size += int(unsafe.Sizeof(value)) + case uint64: + size += int(unsafe.Sizeof(value)) + case uintptr: + size += int(unsafe.Sizeof(value)) + case float32: + size += int(unsafe.Sizeof(value)) + case float64: + size += int(unsafe.Sizeof(value)) + case string: + size += int(unsafe.Sizeof(value)) + size += len(value) + } + } + return +} + + // Copies all unexpired items in the cache into a new map and returns it. func (c *cache) Items() map[string]Item { c.mu.RLock() diff --git a/cache_test.go b/cache_test.go index de3e9d6..c0cd84c 100644 --- a/cache_test.go +++ b/cache_test.go @@ -2,12 +2,15 @@ package cache import ( "bytes" + "fmt" "io/ioutil" + "math/rand" "runtime" "strconv" "sync" "testing" "time" + "unsafe" ) type TestStruct struct { @@ -66,6 +69,17 @@ func TestCache(t *testing.T) { } else if c2 := x.(float64); c2+1.2 != 4.7 { t.Error("c2 (which should be 3.5) plus 1.2 does not equal 4.7; value:", c2) } + + // in Go we have to have a variable with a type to be able to get + // the size of the type + var a_size = 1 + var b_size = "b" + var c_size = 3.5 + size := tc.Size() + expected_size := int(unsafe.Sizeof(a_size)) + int(unsafe.Sizeof(b_size)) + len(b_size) + int(unsafe.Sizeof(c_size)) + if size != expected_size { + t.Error("size (which should be", expected_size, "); value:", size) + } } func TestCacheTimes(t *testing.T) { @@ -1769,3 +1783,59 @@ func TestGetWithExpiration(t *testing.T) { t.Error("expiration for e is in the past") } } + +// countBytes from the KVStores +func countBytes(items map[string]Item) int64 { + sum := int64(0) + for _, v := range items { + if buff, ok := v.Object.([]byte); ok { + sum += int64(len(buff)) + } + } + return sum +} + +func TestSizeSpeedTest(t *testing.T) { + if testing.Short() { + t.Skip() + fmt.Println("warning: Skipping slow TestSizeSpeed() -- do not use `-short` to not skip this speed tests.") + return + } + + tc := New(DefaultExpiration, 0) + + fmt.Print("Generate large cache ") + for i := 0; i < 100000; i++ { + if i % 1000 == 999 { + fmt.Print(".") + } + key := "" + key_size := rand.Uint32() % 15 + 5 + for j := uint32(0); j < key_size; j++ { + key += string(rune(rand.Uint32() % 26 + 65)) + } + value := "" + value_size := rand.Uint32() % 55 + 5 + for j := uint32(0); j < value_size; j++ { + value += string(rune(rand.Uint32() % 26 + 65)) + } + tc.Set(key, value, DefaultExpiration) + } + fmt.Println() + + fmt.Print("Time 1,000 Size() calls: ") + start := time.Now() + for i := 0; i < 1000; i++ { + tc.Size() + } + end := time.Now() + fmt.Println("Size() x 1,000 took:", end.Sub(start)) + + fmt.Print("Time 1,000 countBytes(Items()) calls: ") + start = time.Now() + for i := 0; i < 1000; i++ { + countBytes(tc.Items()) + } + end = time.Now() + fmt.Println("Size() x 1,000 took:", end.Sub(start)) +}