github.com/thanos-io/thanos@v0.32.5/pkg/compactv2/compactor_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package compactv2 5 6 import ( 7 "bytes" 8 "context" 9 "math" 10 "os" 11 "path/filepath" 12 "sort" 13 "testing" 14 "time" 15 16 "github.com/go-kit/log" 17 "github.com/oklog/ulid" 18 "github.com/pkg/errors" 19 "github.com/prometheus/common/model" 20 "github.com/prometheus/prometheus/model/labels" 21 "github.com/prometheus/prometheus/model/relabel" 22 "github.com/prometheus/prometheus/storage" 23 "github.com/prometheus/prometheus/tsdb" 24 "github.com/prometheus/prometheus/tsdb/chunkenc" 25 "github.com/prometheus/prometheus/tsdb/chunks" 26 "github.com/prometheus/prometheus/tsdb/index" 27 "github.com/prometheus/prometheus/tsdb/tombstones" 28 29 "github.com/efficientgo/core/testutil" 30 31 "github.com/thanos-io/thanos/pkg/block" 32 "github.com/thanos-io/thanos/pkg/block/metadata" 33 ) 34 35 func TestCompactor_WriteSeries_e2e(t *testing.T) { 36 ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 37 defer cancel() 38 39 logger := log.NewLogfmtLogger(os.Stderr) 40 for _, tcase := range []struct { 41 name string 42 43 input [][]seriesSamples 44 modifiers []Modifier 45 dryRun bool 46 47 expected []seriesSamples 48 expectedErr error 49 expectedStats tsdb.BlockStats 50 expectedChanges string 51 }{ 52 { 53 name: "empty block", 54 expectedErr: errors.New("cannot write from no readers"), 55 }, 56 { 57 name: "1 blocks no modify", 58 input: [][]seriesSamples{ 59 { 60 {lset: labels.Labels{{Name: "a", Value: "1"}}, 61 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 62 {lset: labels.Labels{{Name: "a", Value: "2"}}, 63 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 64 {lset: labels.Labels{{Name: "a", Value: "3"}}, 65 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 66 }, 67 }, 68 expected: []seriesSamples{ 69 {lset: labels.Labels{{Name: "a", Value: "1"}}, 70 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 71 {lset: labels.Labels{{Name: "a", Value: "2"}}, 72 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 73 {lset: labels.Labels{{Name: "a", Value: "3"}}, 74 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 75 }, 76 expectedStats: tsdb.BlockStats{ 77 NumSamples: 18, 78 NumSeries: 3, 79 NumChunks: 4, 80 }, 81 }, 82 { 83 name: "2 blocks compact no modify", 84 input: [][]seriesSamples{ 85 { 86 {lset: labels.Labels{{Name: "a", Value: "1"}}, 87 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}}}, 88 {lset: labels.Labels{{Name: "a", Value: "2"}}, 89 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}}}, 90 {lset: labels.Labels{{Name: "a", Value: "3"}}, 91 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}}}, 92 }, 93 { 94 {lset: labels.Labels{{Name: "a", Value: "1"}}, 95 chunks: [][]sample{{{10, 10}, {11, 11}, {20, 20}}}}, 96 {lset: labels.Labels{{Name: "a", Value: "2"}}, 97 chunks: [][]sample{{{10, 11}, {11, 11}, {20, 20}}}}, 98 {lset: labels.Labels{{Name: "a", Value: "3"}}, 99 chunks: [][]sample{{{10, 12}, {11, 11}, {20, 20}}}}, 100 {lset: labels.Labels{{Name: "a", Value: "4"}}, 101 chunks: [][]sample{{{10, 12}, {11, 11}, {20, 20}}}}, 102 }, 103 }, 104 expected: []seriesSamples{ 105 {lset: labels.Labels{{Name: "a", Value: "1"}}, 106 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 10}, {11, 11}, {20, 20}}}}, 107 {lset: labels.Labels{{Name: "a", Value: "2"}}, 108 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 109 {lset: labels.Labels{{Name: "a", Value: "3"}}, 110 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 12}, {11, 11}, {20, 20}}}}, 111 {lset: labels.Labels{{Name: "a", Value: "4"}}, 112 chunks: [][]sample{{{10, 12}, {11, 11}, {20, 20}}}}, 113 }, 114 expectedStats: tsdb.BlockStats{ 115 NumSamples: 21, 116 NumSeries: 4, 117 NumChunks: 7, 118 }, 119 }, 120 { 121 name: "1 blocks + delete modifier, empty deletion request", 122 input: [][]seriesSamples{ 123 { 124 {lset: labels.Labels{{Name: "a", Value: "1"}}, 125 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 126 {lset: labels.Labels{{Name: "a", Value: "2"}}, 127 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 128 {lset: labels.Labels{{Name: "a", Value: "3"}}, 129 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 130 }, 131 }, 132 modifiers: []Modifier{WithDeletionModifier()}, 133 expected: []seriesSamples{ 134 {lset: labels.Labels{{Name: "a", Value: "1"}}, 135 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 136 {lset: labels.Labels{{Name: "a", Value: "2"}}, 137 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 138 {lset: labels.Labels{{Name: "a", Value: "3"}}, 139 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 140 }, 141 expectedStats: tsdb.BlockStats{ 142 NumSamples: 18, 143 NumSeries: 3, 144 NumChunks: 4, 145 }, 146 }, 147 { 148 name: "1 blocks + delete modifier, deletion request no deleting anything", 149 input: [][]seriesSamples{ 150 { 151 {lset: labels.Labels{{Name: "a", Value: "1"}}, 152 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 153 {lset: labels.Labels{{Name: "a", Value: "2"}}, 154 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 155 {lset: labels.Labels{{Name: "a", Value: "3"}}, 156 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 157 }, 158 }, 159 modifiers: []Modifier{WithDeletionModifier( 160 metadata.DeletionRequest{ 161 Matchers: []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "a", "0")}, 162 Intervals: tombstones.Intervals{{Mint: math.MinInt64, Maxt: math.MaxInt64}}, 163 }, metadata.DeletionRequest{ 164 Matchers: []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "a", "1")}, 165 Intervals: tombstones.Intervals{{Mint: math.MinInt64, Maxt: -1}}, 166 })}, 167 expected: []seriesSamples{ 168 {lset: labels.Labels{{Name: "a", Value: "1"}}, 169 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 170 {lset: labels.Labels{{Name: "a", Value: "2"}}, 171 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 172 {lset: labels.Labels{{Name: "a", Value: "3"}}, 173 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 174 }, 175 expectedStats: tsdb.BlockStats{ 176 NumSamples: 18, 177 NumSeries: 3, 178 NumChunks: 4, 179 }, 180 }, 181 { 182 name: "1 blocks + delete modifier, deletion request no deleting anything - by specifying no intervals.", 183 input: [][]seriesSamples{ 184 { 185 {lset: labels.Labels{{Name: "a", Value: "1"}}, 186 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 187 {lset: labels.Labels{{Name: "a", Value: "2"}}, 188 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 189 {lset: labels.Labels{{Name: "a", Value: "3"}}, 190 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 191 }, 192 }, 193 modifiers: []Modifier{WithDeletionModifier( 194 metadata.DeletionRequest{ 195 Matchers: []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "a", "0")}, 196 }, metadata.DeletionRequest{ 197 Matchers: []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "a", "1")}, 198 Intervals: tombstones.Intervals{{Mint: math.MinInt64, Maxt: -1}}, 199 })}, 200 expected: []seriesSamples{ 201 {lset: labels.Labels{{Name: "a", Value: "1"}}, 202 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 203 {lset: labels.Labels{{Name: "a", Value: "2"}}, 204 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 205 {lset: labels.Labels{{Name: "a", Value: "3"}}, 206 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 207 }, 208 expectedStats: tsdb.BlockStats{ 209 NumSamples: 18, 210 NumSeries: 3, 211 NumChunks: 4, 212 }, 213 }, 214 { 215 name: "1 blocks + delete modifier, delete second series", 216 input: [][]seriesSamples{ 217 { 218 {lset: labels.Labels{{Name: "a", Value: "1"}}, 219 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 220 {lset: labels.Labels{{Name: "a", Value: "2"}}, 221 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 222 {lset: labels.Labels{{Name: "a", Value: "3"}}, 223 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 224 }, 225 }, 226 modifiers: []Modifier{WithDeletionModifier( 227 metadata.DeletionRequest{ 228 Matchers: []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "a", "2")}, 229 }, metadata.DeletionRequest{ 230 Matchers: []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "a", "1")}, 231 Intervals: tombstones.Intervals{{Mint: math.MinInt64, Maxt: -1}}, 232 })}, 233 expected: []seriesSamples{ 234 {lset: labels.Labels{{Name: "a", Value: "1"}}, 235 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 236 {lset: labels.Labels{{Name: "a", Value: "3"}}, 237 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 238 }, 239 expectedChanges: "Deleted {a=\"2\"} [{0 20}]\n", 240 expectedStats: tsdb.BlockStats{ 241 NumSamples: 12, 242 NumSeries: 2, 243 NumChunks: 2, 244 }, 245 }, 246 { 247 name: "1 blocks + delete modifier, delete second series and part of first 3rd", 248 input: [][]seriesSamples{ 249 { 250 {lset: labels.Labels{{Name: "a", Value: "1"}}, 251 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 252 {lset: labels.Labels{{Name: "a", Value: "2"}}, 253 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 254 {lset: labels.Labels{{Name: "a", Value: "3"}}, 255 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 256 }, 257 }, 258 modifiers: []Modifier{WithDeletionModifier( 259 metadata.DeletionRequest{ 260 Matchers: []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "a", "2")}, 261 }, metadata.DeletionRequest{ 262 Matchers: []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "a", "1")}, 263 Intervals: tombstones.Intervals{{Mint: math.MinInt64, Maxt: -1}}, 264 }, metadata.DeletionRequest{ 265 Matchers: []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "a", "3")}, 266 Intervals: tombstones.Intervals{{Mint: 10, Maxt: 11}}, 267 })}, 268 expected: []seriesSamples{ 269 {lset: labels.Labels{{Name: "a", Value: "1"}}, 270 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 271 {lset: labels.Labels{{Name: "a", Value: "3"}}, 272 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {20, 20}}}}, 273 }, 274 expectedChanges: "Deleted {a=\"2\"} [{0 20}]\nDeleted {a=\"3\"} [{10 11}]\n", 275 expectedStats: tsdb.BlockStats{ 276 NumSamples: 10, 277 NumSeries: 2, 278 NumChunks: 2, 279 }, 280 }, 281 { 282 name: "1 blocks + delete modifier, deletion request contains multiple matchers, delete second series", 283 input: [][]seriesSamples{ 284 { 285 {lset: labels.Labels{{Name: "a", Value: "1"}, {Name: "b", Value: "1"}}, 286 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 287 {lset: labels.Labels{{Name: "a", Value: "1"}, {Name: "b", Value: "2"}}, 288 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 289 {lset: labels.Labels{{Name: "a", Value: "3"}}, 290 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 291 }, 292 }, 293 modifiers: []Modifier{WithDeletionModifier( 294 metadata.DeletionRequest{ 295 Matchers: []*labels.Matcher{ 296 labels.MustNewMatcher(labels.MatchEqual, "a", "1"), 297 labels.MustNewMatcher(labels.MatchEqual, "b", "2"), 298 }, 299 })}, 300 expected: []seriesSamples{ 301 {lset: labels.Labels{{Name: "a", Value: "1"}, {Name: "b", Value: "1"}}, 302 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 303 {lset: labels.Labels{{Name: "a", Value: "3"}}, 304 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 305 }, 306 expectedChanges: "Deleted {a=\"1\", b=\"2\"} [{0 20}]\n", 307 expectedStats: tsdb.BlockStats{ 308 NumSamples: 12, 309 NumSeries: 2, 310 NumChunks: 2, 311 }, 312 }, 313 { 314 name: "1 blocks + delete modifier. For deletion request, full match is required. Delete the first two series", 315 input: [][]seriesSamples{ 316 { 317 {lset: labels.Labels{{Name: "a", Value: "1"}, {Name: "b", Value: "2"}}, 318 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 319 {lset: labels.Labels{{Name: "a", Value: "1"}, {Name: "b", Value: "2"}, {Name: "foo", Value: "bar"}}, 320 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 321 {lset: labels.Labels{{Name: "a", Value: "1"}}, 322 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 323 {lset: labels.Labels{{Name: "b", Value: "2"}}, 324 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 325 {lset: labels.Labels{{Name: "c", Value: "1"}}, 326 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 327 }, 328 }, 329 modifiers: []Modifier{WithDeletionModifier( 330 metadata.DeletionRequest{ 331 Matchers: []*labels.Matcher{ 332 labels.MustNewMatcher(labels.MatchEqual, "a", "1"), 333 labels.MustNewMatcher(labels.MatchEqual, "b", "2"), 334 }, 335 })}, 336 expected: []seriesSamples{ 337 {lset: labels.Labels{{Name: "a", Value: "1"}}, 338 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 339 {lset: labels.Labels{{Name: "b", Value: "2"}}, 340 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 341 {lset: labels.Labels{{Name: "c", Value: "1"}}, 342 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 12}, {11, 11}, {20, 20}}}}, 343 }, 344 expectedChanges: "Deleted {a=\"1\", b=\"2\"} [{0 20}]\nDeleted {a=\"1\", b=\"2\", foo=\"bar\"} [{0 20}]\n", 345 expectedStats: tsdb.BlockStats{ 346 NumSamples: 18, 347 NumSeries: 3, 348 NumChunks: 3, 349 }, 350 }, 351 { 352 name: "1 blocks + delete modifier. Deletion request contains non-equal matchers.", 353 input: [][]seriesSamples{ 354 { 355 {lset: labels.Labels{{Name: "a", Value: "1"}}, 356 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 357 {lset: labels.Labels{{Name: "a", Value: "2"}}, 358 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 359 {lset: labels.Labels{{Name: "a", Value: "2"}, {Name: "foo", Value: "1"}}, 360 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 361 {lset: labels.Labels{{Name: "a", Value: "2"}, {Name: "foo", Value: "bar"}}, 362 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 363 {lset: labels.Labels{{Name: "a", Value: "3"}, {Name: "foo", Value: "baz"}}, 364 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 365 {lset: labels.Labels{{Name: "foo", Value: "bat"}}, 366 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 367 368 // Label a is present but with an empty value. 369 {lset: labels.Labels{{Name: "a", Value: ""}, {Name: "foo", Value: "bat"}}, 370 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 371 // Series with unrelated labels. 372 {lset: labels.Labels{{Name: "c", Value: "1"}}, 373 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 374 }, 375 }, 376 modifiers: []Modifier{WithDeletionModifier( 377 metadata.DeletionRequest{ 378 Matchers: []*labels.Matcher{ 379 labels.MustNewMatcher(labels.MatchNotEqual, "a", "1"), 380 labels.MustNewMatcher(labels.MatchRegexp, "foo", "^ba.$"), 381 }, 382 })}, 383 expected: []seriesSamples{ 384 {lset: labels.Labels{{Name: "a", Value: ""}, {Name: "foo", Value: "bat"}}, 385 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 386 {lset: labels.Labels{{Name: "a", Value: "1"}}, 387 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 388 {lset: labels.Labels{{Name: "a", Value: "2"}}, 389 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 390 {lset: labels.Labels{{Name: "a", Value: "2"}, {Name: "foo", Value: "1"}}, 391 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 392 {lset: labels.Labels{{Name: "c", Value: "1"}}, 393 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 394 {lset: labels.Labels{{Name: "foo", Value: "bat"}}, 395 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 11}, {11, 11}, {20, 20}}}}, 396 }, 397 expectedChanges: "Deleted {a=\"2\", foo=\"bar\"} [{0 20}]\nDeleted {a=\"3\", foo=\"baz\"} [{0 20}]\n", 398 expectedStats: tsdb.BlockStats{ 399 NumSamples: 36, 400 NumSeries: 6, 401 NumChunks: 12, 402 }, 403 }, 404 { 405 name: "1 block + relabel modifier, two chunks from the same series are merged into one larger chunk", 406 input: [][]seriesSamples{ 407 { 408 {lset: labels.Labels{{Name: "a", Value: "1"}}, 409 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 10}, {11, 11}, {20, 20}}}}, 410 }, 411 }, 412 // Not used in this test case. 413 modifiers: []Modifier{WithRelabelModifier( 414 &relabel.Config{ 415 Action: relabel.Drop, 416 Regex: relabel.MustNewRegexp("no-match"), 417 SourceLabels: model.LabelNames{"a"}, 418 }, 419 )}, 420 expected: []seriesSamples{ 421 {lset: labels.Labels{{Name: "a", Value: "1"}}, 422 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 423 }, 424 expectedStats: tsdb.BlockStats{ 425 NumSamples: 6, 426 NumSeries: 1, 427 NumChunks: 1, 428 }, 429 }, 430 { 431 name: "1 block + relabel modifier, delete first series", 432 input: [][]seriesSamples{ 433 { 434 {lset: labels.Labels{{Name: "a", Value: "1"}}, 435 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}}}}, 436 {lset: labels.Labels{{Name: "a", Value: "2"}}, 437 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 10}, {11, 11}, {20, 20}, {25, 25}}}}, 438 {lset: labels.Labels{{Name: "a", Value: "3"}}, 439 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 13}, {11, 11}, {20, 20}}}}, 440 }, 441 }, 442 modifiers: []Modifier{WithRelabelModifier( 443 &relabel.Config{ 444 Action: relabel.Drop, 445 Regex: relabel.MustNewRegexp("1"), 446 SourceLabels: model.LabelNames{"a"}, 447 }, 448 )}, 449 expected: []seriesSamples{ 450 {lset: labels.Labels{{Name: "a", Value: "2"}}, 451 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}, {25, 25}}}}, 452 {lset: labels.Labels{{Name: "a", Value: "3"}}, 453 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 13}, {11, 11}, {20, 20}}}}, 454 }, 455 expectedChanges: "Deleted {a=\"1\"} [{0 20}]\n", 456 expectedStats: tsdb.BlockStats{ 457 NumSamples: 13, 458 NumSeries: 2, 459 NumChunks: 2, 460 }, 461 }, 462 { 463 name: "1 block + relabel modifier, series reordered", 464 input: [][]seriesSamples{ 465 { 466 {lset: labels.Labels{{Name: "a", Value: "1"}}, 467 chunks: [][]sample{{{0, 0}, {1, -1}, {2, -2}, {10, -10}, {11, -11}, {20, -20}}}}, 468 {lset: labels.Labels{{Name: "a", Value: "2"}}, 469 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 10}, {11, 11}, {20, 20}, {25, 25}}}}, 470 }, 471 }, 472 // {a="1"} will be relabeled to {a="3"} while {a="2"} will be relabeled to {a="0"}. 473 modifiers: []Modifier{WithRelabelModifier( 474 &relabel.Config{ 475 Action: relabel.Replace, 476 Regex: relabel.MustNewRegexp("1"), 477 SourceLabels: model.LabelNames{"a"}, 478 TargetLabel: "a", 479 Replacement: "3", 480 }, 481 &relabel.Config{ 482 Action: relabel.Replace, 483 Regex: relabel.MustNewRegexp("2"), 484 SourceLabels: model.LabelNames{"a"}, 485 TargetLabel: "a", 486 Replacement: "0", 487 }, 488 )}, 489 expected: []seriesSamples{ 490 {lset: labels.Labels{{Name: "a", Value: "0"}}, 491 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}, {25, 25}}}}, 492 {lset: labels.Labels{{Name: "a", Value: "3"}}, 493 chunks: [][]sample{{{0, 0}, {1, -1}, {2, -2}, {10, -10}, {11, -11}, {20, -20}}}}, 494 }, 495 expectedChanges: "Relabelled {a=\"1\"} {a=\"3\"}\nRelabelled {a=\"2\"} {a=\"0\"}\n", 496 expectedStats: tsdb.BlockStats{ 497 NumSamples: 13, 498 NumSeries: 2, 499 NumChunks: 2, 500 }, 501 }, 502 { 503 name: "1 block + relabel modifier, series deleted because of no labels left after relabel", 504 input: [][]seriesSamples{ 505 { 506 {lset: labels.Labels{{Name: "a", Value: "1"}}, 507 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 10}, {11, 11}, {20, 20}, {25, 25}}}}, 508 }, 509 { 510 {lset: labels.Labels{{Name: "a", Value: "2"}}, 511 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 10}, {11, 11}, {20, 20}, {25, 25}}}}, 512 }, 513 }, 514 // Drop all label name "a". 515 modifiers: []Modifier{WithRelabelModifier( 516 &relabel.Config{ 517 Action: relabel.LabelDrop, 518 Regex: relabel.MustNewRegexp("a"), 519 }, 520 )}, 521 expected: nil, 522 expectedChanges: "Deleted {a=\"1\"} [{0 25}]\nDeleted {a=\"2\"} [{0 25}]\n", 523 expectedStats: tsdb.BlockStats{ 524 NumSamples: 0, 525 NumSeries: 0, 526 NumChunks: 0, 527 }, 528 }, 529 { 530 name: "1 block + relabel modifier, series 1 is deleted because of no labels left after relabel", 531 input: [][]seriesSamples{ 532 { 533 {lset: labels.Labels{{Name: "a", Value: "1"}}, 534 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 10}, {11, 11}, {20, 20}, {25, 25}}}}, 535 }, 536 { 537 {lset: labels.Labels{{Name: "a", Value: "2"}, {Name: "b", Value: "1"}}, 538 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}}, {{10, 10}, {11, 11}, {20, 20}, {25, 25}}}}, 539 }, 540 }, 541 // Drop all label name "a". 542 modifiers: []Modifier{WithRelabelModifier( 543 &relabel.Config{ 544 Action: relabel.LabelDrop, 545 Regex: relabel.MustNewRegexp("a"), 546 }, 547 )}, 548 expected: []seriesSamples{ 549 {lset: labels.Labels{{Name: "b", Value: "1"}}, 550 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {10, 10}, {11, 11}, {20, 20}, {25, 25}}}}, 551 }, 552 expectedChanges: "Deleted {a=\"1\"} [{0 25}]\nRelabelled {a=\"2\", b=\"1\"} {b=\"1\"}\n", 553 expectedStats: tsdb.BlockStats{ 554 NumSamples: 7, 555 NumSeries: 1, 556 NumChunks: 1, 557 }, 558 }, 559 { 560 name: "1 block + relabel modifier, series merged after relabeling", 561 input: [][]seriesSamples{ 562 { 563 {lset: labels.Labels{{Name: "a", Value: "1"}}, 564 chunks: [][]sample{{{1, 1}, {2, 2}, {10, 10}, {20, 20}}}}, 565 {lset: labels.Labels{{Name: "a", Value: "2"}}, 566 chunks: [][]sample{{{0, 0}, {2, 2}, {3, 3}}, {{4, 4}, {11, 11}, {20, 20}, {25, 25}}}}, 567 }, 568 }, 569 // Replace values of label name "a" with "0". 570 modifiers: []Modifier{WithRelabelModifier( 571 &relabel.Config{ 572 Action: relabel.Replace, 573 Regex: relabel.MustNewRegexp("1|2"), 574 SourceLabels: model.LabelNames{"a"}, 575 TargetLabel: "a", 576 Replacement: "0", 577 }, 578 )}, 579 expected: []seriesSamples{ 580 {lset: labels.Labels{{Name: "a", Value: "0"}}, 581 chunks: [][]sample{{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {10, 10}, {11, 11}, {20, 20}, {25, 25}}}}, 582 }, 583 expectedChanges: "Relabelled {a=\"1\"} {a=\"0\"}\nRelabelled {a=\"2\"} {a=\"0\"}\n", 584 expectedStats: tsdb.BlockStats{ 585 NumSamples: 9, 586 NumSeries: 1, 587 NumChunks: 1, 588 }, 589 }, 590 } { 591 t.Run(tcase.name, func(t *testing.T) { 592 tmpDir := t.TempDir() 593 594 chunkPool := chunkenc.NewPool() 595 596 changes := bytes.Buffer{} 597 changeLog := &changeLog{w: &changes} 598 var s *Compactor 599 if tcase.dryRun { 600 s = NewDryRun(tmpDir, logger, changeLog, chunkPool) 601 } else { 602 s = New(tmpDir, logger, changeLog, chunkPool) 603 } 604 605 var series int 606 var blocks []block.Reader 607 for _, b := range tcase.input { 608 series += len(b) 609 id := ulid.MustNew(uint64(len(blocks)+1), nil) 610 bdir := filepath.Join(tmpDir, id.String()) 611 testutil.Ok(t, os.MkdirAll(bdir, os.ModePerm)) 612 testutil.Ok(t, createBlockSeries(bdir, b)) 613 // Meta does not matter, but let's create for OpenBlock to work. 614 testutil.Ok(t, metadata.Meta{BlockMeta: tsdb.BlockMeta{Version: 1, ULID: id}}.WriteToDir(logger, bdir)) 615 block, err := tsdb.OpenBlock(logger, bdir, chunkPool) 616 testutil.Ok(t, err) 617 blocks = append(blocks, block) 618 } 619 620 id := ulid.MustNew(uint64(len(blocks)+1), nil) 621 d, err := block.NewDiskWriter(ctx, logger, filepath.Join(tmpDir, id.String())) 622 testutil.Ok(t, err) 623 p := NewProgressLogger(logger, series) 624 if tcase.expectedErr != nil { 625 err := s.WriteSeries(ctx, blocks, d, p, tcase.modifiers...) 626 testutil.NotOk(t, err) 627 testutil.Equals(t, tcase.expectedErr.Error(), err.Error()) 628 return 629 } 630 testutil.Ok(t, s.WriteSeries(ctx, blocks, d, p, tcase.modifiers...)) 631 632 testutil.Ok(t, os.MkdirAll(filepath.Join(tmpDir, id.String()), os.ModePerm)) 633 stats, err := d.Flush() 634 testutil.Ok(t, err) 635 636 testutil.Equals(t, tcase.expectedChanges, changes.String()) 637 testutil.Equals(t, tcase.expectedStats, stats) 638 testutil.Equals(t, tcase.expected, readBlockSeries(t, filepath.Join(tmpDir, id.String()))) 639 }) 640 } 641 } 642 643 type sample struct { 644 t int64 645 v float64 646 } 647 648 type seriesSamples struct { 649 lset labels.Labels 650 chunks [][]sample 651 } 652 653 func readBlockSeries(t *testing.T, bDir string) []seriesSamples { 654 indexr, err := index.NewFileReader(filepath.Join(bDir, block.IndexFilename)) 655 testutil.Ok(t, err) 656 defer indexr.Close() 657 658 chunkr, err := chunks.NewDirReader(filepath.Join(bDir, block.ChunksDirname), nil) 659 testutil.Ok(t, err) 660 defer chunkr.Close() 661 662 all, err := indexr.Postings(index.AllPostingsKey()) 663 testutil.Ok(t, err) 664 all = indexr.SortedPostings(all) 665 666 var builder labels.ScratchBuilder 667 var series []seriesSamples 668 var chks []chunks.Meta 669 for all.Next() { 670 s := seriesSamples{} 671 testutil.Ok(t, indexr.Series(all.At(), &builder, &chks)) 672 s.lset = builder.Labels() 673 674 for _, c := range chks { 675 c.Chunk, err = chunkr.Chunk(c) 676 testutil.Ok(t, err) 677 678 var chk []sample 679 iter := c.Chunk.Iterator(nil) 680 for iter.Next() != chunkenc.ValNone { 681 sa := sample{} 682 sa.t, sa.v = iter.At() 683 chk = append(chk, sa) 684 } 685 testutil.Ok(t, iter.Err()) 686 s.chunks = append(s.chunks, chk) 687 } 688 series = append(series, s) 689 } 690 testutil.Ok(t, all.Err()) 691 return series 692 } 693 694 func createBlockSeries(bDir string, inputSeries []seriesSamples) (err error) { 695 d, err := block.NewDiskWriter(context.TODO(), log.NewNopLogger(), bDir) 696 if err != nil { 697 return err 698 } 699 defer func() { 700 if err != nil { 701 _, _ = d.Flush() 702 _ = os.RemoveAll(bDir) 703 } 704 }() 705 706 sort.Slice(inputSeries, func(i, j int) bool { 707 return labels.Compare(inputSeries[i].lset, inputSeries[j].lset) < 0 708 }) 709 710 // Gather symbols. 711 symbols := map[string]struct{}{} 712 for _, input := range inputSeries { 713 for _, l := range input.lset { 714 symbols[l.Name] = struct{}{} 715 symbols[l.Value] = struct{}{} 716 } 717 } 718 719 symbolsSlice := make([]string, 0, len(symbols)) 720 for s := range symbols { 721 symbolsSlice = append(symbolsSlice, s) 722 } 723 sort.Strings(symbolsSlice) 724 for _, s := range symbolsSlice { 725 if err := d.AddSymbol(s); err != nil { 726 return err 727 } 728 } 729 var ref storage.SeriesRef 730 for _, input := range inputSeries { 731 var chks []chunks.Meta 732 for _, chk := range input.chunks { 733 x := chunkenc.NewXORChunk() 734 a, err := x.Appender() 735 if err != nil { 736 return err 737 } 738 for _, sa := range chk { 739 a.Append(sa.t, sa.v) 740 } 741 chks = append(chks, chunks.Meta{Chunk: x, MinTime: chk[0].t, MaxTime: chk[len(chk)-1].t}) 742 } 743 if err := d.WriteChunks(chks...); err != nil { 744 return errors.Wrap(err, "write chunks") 745 } 746 if err := d.AddSeries(ref, input.lset, chks...); err != nil { 747 return errors.Wrap(err, "add series") 748 } 749 ref++ 750 } 751 752 if _, err = d.Flush(); err != nil { 753 return errors.Wrap(err, "flush") 754 } 755 return nil 756 }