From 919dc0cbc603db274c1351f97848f91edde5f3ab Mon Sep 17 00:00:00 2001 From: Harry John Date: Mon, 16 Sep 2024 23:55:02 -0700 Subject: [PATCH] storage: Update LabelQuerier interface to return sorted label values (#14849) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change LabelQuerier.LabelValues() to return sorted values --------- Signed-off-by: 🌲 Harry 🌊 John 🏔 --- storage/interface.go | 2 +- tsdb/block_test.go | 28 ++++++++++++++++++++++++++++ tsdb/head_test.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/storage/interface.go b/storage/interface.go index 2f125e590..a4608e08c 100644 --- a/storage/interface.go +++ b/storage/interface.go @@ -157,7 +157,7 @@ type ChunkQuerier interface { // LabelQuerier provides querying access over labels. type LabelQuerier interface { - // LabelValues returns all potential values for a label name. + // LabelValues returns all potential values for a label name in sorted order. // It is not safe to use the strings beyond the lifetime of the querier. // If matchers are specified the returned result set is reduced // to label values of metrics matching the matchers. diff --git a/tsdb/block_test.go b/tsdb/block_test.go index f2569e35b..2273b812a 100644 --- a/tsdb/block_test.go +++ b/tsdb/block_test.go @@ -22,6 +22,7 @@ import ( "math/rand" "os" "path/filepath" + "slices" "sort" "strconv" "testing" @@ -310,6 +311,33 @@ func TestLabelValuesWithMatchers(t *testing.T) { } } +func TestBlockQuerierReturnsSortedLabelValues(t *testing.T) { + tmpdir := t.TempDir() + ctx := context.Background() + + var seriesEntries []storage.Series + for i := 100; i > 0; i-- { + seriesEntries = append(seriesEntries, storage.NewListSeries(labels.FromStrings( + "__name__", fmt.Sprintf("value%d", i), + ), []chunks.Sample{sample{100, 0, nil, nil}})) + } + + blockDir := createBlock(t, tmpdir, seriesEntries) + + // Check open err. + block, err := OpenBlock(nil, blockDir, nil) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, block.Close()) }) + + q, err := newBlockBaseQuerier(block, 0, 100) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, q.Close()) }) + + res, _, err := q.LabelValues(ctx, "__name__", nil) + require.NoError(t, err) + require.True(t, slices.IsSorted(res)) +} + // TestBlockSize ensures that the block size is calculated correctly. func TestBlockSize(t *testing.T) { tmpdir := t.TempDir() diff --git a/tsdb/head_test.go b/tsdb/head_test.go index c4e16ae65..ffe3c0494 100644 --- a/tsdb/head_test.go +++ b/tsdb/head_test.go @@ -23,6 +23,7 @@ import ( "path" "path/filepath" "reflect" + "slices" "sort" "strconv" "strings" @@ -2101,6 +2102,36 @@ func TestHead_LogRollback(t *testing.T) { } } +func TestHead_ReturnsSortedLabelValues(t *testing.T) { + h, _ := newTestHead(t, 1000, wlog.CompressionNone, false) + defer func() { + require.NoError(t, h.Close()) + }() + + h.initTime(0) + + app := h.appender() + for i := 100; i > 0; i-- { + for j := 0; j < 10; j++ { + lset := labels.FromStrings( + "__name__", fmt.Sprintf("metric_%d", i), + "label", fmt.Sprintf("value_%d", j), + ) + _, err := app.Append(0, lset, 2100, 1) + require.NoError(t, err) + } + } + + q, err := NewBlockQuerier(h, 1500, 2500) + require.NoError(t, err) + + res, _, err := q.LabelValues(context.Background(), "__name__", nil) + require.NoError(t, err) + + require.True(t, slices.IsSorted(res)) + require.NoError(t, q.Close()) +} + // TestWalRepair_DecodingError ensures that a repair is run for an error // when decoding a record. func TestWalRepair_DecodingError(t *testing.T) {