mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-15 10:04:07 -08:00
135 lines
3.7 KiB
Go
135 lines
3.7 KiB
Go
|
package hashcache
|
||
|
|
||
|
import (
|
||
|
"crypto/rand"
|
||
|
"fmt"
|
||
|
"runtime"
|
||
|
"strconv"
|
||
|
"sync"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/oklog/ulid"
|
||
|
"github.com/stretchr/testify/require"
|
||
|
)
|
||
|
|
||
|
func TestSeriesHashCache(t *testing.T) {
|
||
|
// Set the max cache size to store at most 1 entry per generation,
|
||
|
// so that we test the GC logic too.
|
||
|
c := NewSeriesHashCache(numGenerations * approxBytesPerEntry)
|
||
|
|
||
|
block1 := c.GetBlockCache("1")
|
||
|
assertFetch(t, block1, 1, 0, false)
|
||
|
block1.Store(1, 100)
|
||
|
assertFetch(t, block1, 1, 100, true)
|
||
|
|
||
|
block2 := c.GetBlockCache("2")
|
||
|
assertFetch(t, block2, 1, 0, false)
|
||
|
block2.Store(1, 1000)
|
||
|
assertFetch(t, block2, 1, 1000, true)
|
||
|
|
||
|
block3 := c.GetBlockCache("3")
|
||
|
assertFetch(t, block1, 1, 100, true)
|
||
|
assertFetch(t, block2, 1, 1000, true)
|
||
|
assertFetch(t, block3, 1, 0, false)
|
||
|
|
||
|
// Get again the block caches.
|
||
|
block1 = c.GetBlockCache("1")
|
||
|
block2 = c.GetBlockCache("2")
|
||
|
block3 = c.GetBlockCache("3")
|
||
|
|
||
|
assertFetch(t, block1, 1, 100, true)
|
||
|
assertFetch(t, block2, 1, 1000, true)
|
||
|
assertFetch(t, block3, 1, 0, false)
|
||
|
}
|
||
|
|
||
|
func TestSeriesHashCache_MeasureApproximateSizePerEntry(t *testing.T) {
|
||
|
// This test measures the approximate size (in bytes) per cache entry.
|
||
|
// We only take in account the memory used by the map, which is the largest amount.
|
||
|
const numEntries = 100000
|
||
|
c := NewSeriesHashCache(1024 * 1024 * 1024)
|
||
|
b := c.GetBlockCache(ulid.MustNew(0, rand.Reader).String())
|
||
|
|
||
|
before := runtime.MemStats{}
|
||
|
runtime.ReadMemStats(&before)
|
||
|
|
||
|
// Preallocate the map in order to not account for re-allocations
|
||
|
// since we want to measure the heap utilization and not allocations.
|
||
|
b.generations[0].hashes = make(map[uint64]uint64, numEntries)
|
||
|
|
||
|
for i := uint64(0); i < numEntries; i++ {
|
||
|
b.Store(i, i)
|
||
|
}
|
||
|
|
||
|
after := runtime.MemStats{}
|
||
|
runtime.ReadMemStats(&after)
|
||
|
|
||
|
t.Logf("approximate size per entry: %d bytes", (after.TotalAlloc-before.TotalAlloc)/numEntries)
|
||
|
}
|
||
|
|
||
|
func TestSeriesHashCache_Concurrency(t *testing.T) {
|
||
|
const (
|
||
|
concurrency = 100
|
||
|
numIterations = 10000
|
||
|
numBlocks = 10
|
||
|
)
|
||
|
|
||
|
// Set the max cache size to store at most 10 entries per generation,
|
||
|
// so that we stress test the GC too.
|
||
|
c := NewSeriesHashCache(10 * numGenerations * approxBytesPerEntry)
|
||
|
|
||
|
wg := sync.WaitGroup{}
|
||
|
wg.Add(concurrency)
|
||
|
|
||
|
for i := 0; i < concurrency; i++ {
|
||
|
go func() {
|
||
|
defer wg.Done()
|
||
|
|
||
|
for n := 0; n < numIterations; n++ {
|
||
|
blockID := strconv.Itoa(n % numBlocks)
|
||
|
|
||
|
blockCache := c.GetBlockCache(blockID)
|
||
|
blockCache.Store(uint64(n), uint64(n))
|
||
|
actual, ok := blockCache.Fetch(uint64(n))
|
||
|
|
||
|
require.True(t, ok)
|
||
|
require.Equal(t, uint64(n), actual)
|
||
|
}
|
||
|
}()
|
||
|
}
|
||
|
|
||
|
wg.Wait()
|
||
|
}
|
||
|
|
||
|
func BenchmarkSeriesHashCache_StoreAndFetch(b *testing.B) {
|
||
|
for _, numBlocks := range []int{1, 10, 100, 1000, 10000} {
|
||
|
b.Run(fmt.Sprintf("blocks=%d", numBlocks), func(b *testing.B) {
|
||
|
c := NewSeriesHashCache(1024 * 1024)
|
||
|
|
||
|
// In this benchmark we assume the usage pattern is calling Fetch() and Store() will be
|
||
|
// orders of magnitude more frequent than GetBlockCache(), so we call GetBlockCache() just
|
||
|
// once per block.
|
||
|
blockCaches := make([]*BlockSeriesHashCache, numBlocks)
|
||
|
for idx := 0; idx < numBlocks; idx++ {
|
||
|
blockCaches[idx] = c.GetBlockCache(strconv.Itoa(idx))
|
||
|
}
|
||
|
|
||
|
// In this benchmark we assume the ratio between Store() and Fetch() is 1:10.
|
||
|
storeOps := (b.N / 10) + 1
|
||
|
|
||
|
for n := 0; n < b.N; n++ {
|
||
|
if n < storeOps {
|
||
|
blockCaches[n%numBlocks].Store(uint64(n), uint64(n))
|
||
|
} else {
|
||
|
blockCaches[n%numBlocks].Fetch(uint64(n % storeOps))
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func assertFetch(t *testing.T, c *BlockSeriesHashCache, seriesID, expectedValue uint64, expectedOk bool) {
|
||
|
actualValue, actualOk := c.Fetch(seriesID)
|
||
|
require.Equal(t, expectedValue, actualValue)
|
||
|
require.Equal(t, expectedOk, actualOk)
|
||
|
}
|