github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/ingester/active_series_test.go (about) 1 package ingester 2 3 import ( 4 "bytes" 5 "fmt" 6 "math" 7 "strconv" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/prometheus/prometheus/pkg/labels" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 16 "github.com/cortexproject/cortex/pkg/ingester/client" 17 ) 18 19 func copyFn(l labels.Labels) labels.Labels { return l } 20 21 func TestActiveSeries_UpdateSeries(t *testing.T) { 22 ls1 := []labels.Label{{Name: "a", Value: "1"}} 23 ls2 := []labels.Label{{Name: "a", Value: "2"}} 24 25 c := NewActiveSeries() 26 assert.Equal(t, 0, c.Active()) 27 28 c.UpdateSeries(ls1, time.Now(), copyFn) 29 assert.Equal(t, 1, c.Active()) 30 31 c.UpdateSeries(ls1, time.Now(), copyFn) 32 assert.Equal(t, 1, c.Active()) 33 34 c.UpdateSeries(ls2, time.Now(), copyFn) 35 assert.Equal(t, 2, c.Active()) 36 } 37 38 func TestActiveSeries_ShouldCorrectlyHandleFingerprintCollisions(t *testing.T) { 39 metric := labels.NewBuilder(labels.FromStrings("__name__", "logs")) 40 ls1 := metric.Set("_", "ypfajYg2lsv").Labels() 41 ls2 := metric.Set("_", "KiqbryhzUpn").Labels() 42 43 require.True(t, client.Fingerprint(ls1) == client.Fingerprint(ls2)) 44 45 c := NewActiveSeries() 46 c.UpdateSeries(ls1, time.Now(), copyFn) 47 c.UpdateSeries(ls2, time.Now(), copyFn) 48 49 assert.Equal(t, 2, c.Active()) 50 } 51 52 func TestActiveSeries_Purge(t *testing.T) { 53 series := [][]labels.Label{ 54 {{Name: "a", Value: "1"}}, 55 {{Name: "a", Value: "2"}}, 56 // The two following series have the same Fingerprint 57 {{Name: "_", Value: "ypfajYg2lsv"}, {Name: "__name__", Value: "logs"}}, 58 {{Name: "_", Value: "KiqbryhzUpn"}, {Name: "__name__", Value: "logs"}}, 59 } 60 61 // Run the same test for increasing TTL values 62 for ttl := 0; ttl < len(series); ttl++ { 63 c := NewActiveSeries() 64 65 for i := 0; i < len(series); i++ { 66 c.UpdateSeries(series[i], time.Unix(int64(i), 0), copyFn) 67 } 68 69 c.Purge(time.Unix(int64(ttl+1), 0)) 70 // call purge twice, just to hit "quick" path. It doesn't really do anything. 71 c.Purge(time.Unix(int64(ttl+1), 0)) 72 73 exp := len(series) - (ttl + 1) 74 assert.Equal(t, exp, c.Active()) 75 } 76 } 77 78 func TestActiveSeries_PurgeOpt(t *testing.T) { 79 metric := labels.NewBuilder(labels.FromStrings("__name__", "logs")) 80 ls1 := metric.Set("_", "ypfajYg2lsv").Labels() 81 ls2 := metric.Set("_", "KiqbryhzUpn").Labels() 82 83 c := NewActiveSeries() 84 85 now := time.Now() 86 c.UpdateSeries(ls1, now.Add(-2*time.Minute), copyFn) 87 c.UpdateSeries(ls2, now, copyFn) 88 c.Purge(now) 89 90 assert.Equal(t, 1, c.Active()) 91 92 c.UpdateSeries(ls1, now.Add(-1*time.Minute), copyFn) 93 c.UpdateSeries(ls2, now, copyFn) 94 c.Purge(now) 95 96 assert.Equal(t, 1, c.Active()) 97 98 // This will *not* update the series, since there is already newer timestamp. 99 c.UpdateSeries(ls2, now.Add(-1*time.Minute), copyFn) 100 c.Purge(now) 101 102 assert.Equal(t, 1, c.Active()) 103 } 104 105 var activeSeriesTestGoroutines = []int{50, 100, 500} 106 107 func BenchmarkActiveSeriesTest_single_series(b *testing.B) { 108 for _, num := range activeSeriesTestGoroutines { 109 b.Run(fmt.Sprintf("%d", num), func(b *testing.B) { 110 benchmarkActiveSeriesConcurrencySingleSeries(b, num) 111 }) 112 } 113 } 114 115 func benchmarkActiveSeriesConcurrencySingleSeries(b *testing.B, goroutines int) { 116 series := labels.Labels{ 117 {Name: "a", Value: "a"}, 118 } 119 120 c := NewActiveSeries() 121 122 wg := &sync.WaitGroup{} 123 start := make(chan struct{}) 124 max := int(math.Ceil(float64(b.N) / float64(goroutines))) 125 126 for i := 0; i < goroutines; i++ { 127 wg.Add(1) 128 go func() { 129 defer wg.Done() 130 <-start 131 132 now := time.Now() 133 134 for ix := 0; ix < max; ix++ { 135 now = now.Add(time.Duration(ix) * time.Millisecond) 136 c.UpdateSeries(series, now, copyFn) 137 } 138 }() 139 } 140 141 b.ResetTimer() 142 close(start) 143 wg.Wait() 144 } 145 146 func BenchmarkActiveSeries_UpdateSeries(b *testing.B) { 147 c := NewActiveSeries() 148 149 // Prepare series 150 nameBuf := bytes.Buffer{} 151 for i := 0; i < 50; i++ { 152 nameBuf.WriteString("abcdefghijklmnopqrstuvzyx") 153 } 154 name := nameBuf.String() 155 156 series := make([]labels.Labels, b.N) 157 for s := 0; s < b.N; s++ { 158 series[s] = labels.Labels{{Name: name, Value: name + strconv.Itoa(s)}} 159 } 160 161 now := time.Now().UnixNano() 162 163 b.ResetTimer() 164 for ix := 0; ix < b.N; ix++ { 165 c.UpdateSeries(series[ix], time.Unix(0, now+int64(ix)), copyFn) 166 } 167 } 168 169 func BenchmarkActiveSeries_Purge_once(b *testing.B) { 170 benchmarkPurge(b, false) 171 } 172 173 func BenchmarkActiveSeries_Purge_twice(b *testing.B) { 174 benchmarkPurge(b, true) 175 } 176 177 func benchmarkPurge(b *testing.B, twice bool) { 178 const numSeries = 10000 179 const numExpiresSeries = numSeries / 25 180 181 now := time.Now() 182 c := NewActiveSeries() 183 184 series := [numSeries]labels.Labels{} 185 for s := 0; s < numSeries; s++ { 186 series[s] = labels.Labels{{Name: "a", Value: strconv.Itoa(s)}} 187 } 188 189 for i := 0; i < b.N; i++ { 190 b.StopTimer() 191 192 // Prepare series 193 for ix, s := range series { 194 if ix < numExpiresSeries { 195 c.UpdateSeries(s, now.Add(-time.Minute), copyFn) 196 } else { 197 c.UpdateSeries(s, now, copyFn) 198 } 199 } 200 201 assert.Equal(b, numSeries, c.Active()) 202 b.StartTimer() 203 204 // Purge everything 205 c.Purge(now) 206 assert.Equal(b, numSeries-numExpiresSeries, c.Active()) 207 208 if twice { 209 c.Purge(now) 210 assert.Equal(b, numSeries-numExpiresSeries, c.Active()) 211 } 212 } 213 }