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  }