github.com/grafana/pyroscope@v1.18.0/pkg/compactor/job_sorting_test.go (about)

     1  // SPDX-License-Identifier: AGPL-3.0-only
     2  // Provenance-includes-location: https://github.com/grafana/mimir/blob/main/pkg/compactor/job_sorting_test.go
     3  // Provenance-includes-license: Apache-2.0
     4  // Provenance-includes-copyright: The Cortex Authors.
     5  
     6  package compactor
     7  
     8  import (
     9  	"testing"
    10  
    11  	"github.com/oklog/ulid/v2"
    12  	"github.com/prometheus/common/model"
    13  	"github.com/stretchr/testify/assert"
    14  
    15  	"github.com/grafana/pyroscope/pkg/phlaredb/block"
    16  )
    17  
    18  func TestSortJobsBySmallestRangeOldestBlocksFirst(t *testing.T) {
    19  	block1 := ulid.MustNew(1, nil)
    20  	block2 := ulid.MustNew(2, nil)
    21  	block3 := ulid.MustNew(3, nil)
    22  	block4 := ulid.MustNew(4, nil)
    23  	block5 := ulid.MustNew(5, nil)
    24  	block6 := ulid.MustNew(6, nil)
    25  
    26  	tests := map[string]struct {
    27  		input    []*Job
    28  		expected []*Job
    29  	}{
    30  		"should do nothing on empty input": {
    31  			input:    nil,
    32  			expected: nil,
    33  		},
    34  		"should sort jobs by smallest range, oldest blocks first": {
    35  			input: []*Job{
    36  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block5, 40, 60), mockMetaWithMinMax(block6, 40, 80)}},
    37  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block3, 10, 20), mockMetaWithMinMax(block4, 20, 30)}},
    38  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block1, 10, 20), mockMetaWithMinMax(block2, 10, 20)}},
    39  			},
    40  			expected: []*Job{
    41  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block1, 10, 20), mockMetaWithMinMax(block2, 10, 20)}},
    42  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block3, 10, 20), mockMetaWithMinMax(block4, 20, 30)}},
    43  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block5, 40, 60), mockMetaWithMinMax(block6, 40, 80)}},
    44  			},
    45  		},
    46  		"split jobs are always sorted first": {
    47  			input: []*Job{
    48  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block5, 40, 60), mockMetaWithMinMax(block6, 40, 80)}},
    49  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block3, 10, 20), mockMetaWithMinMax(block4, 20, 30)}, useSplitting: false},
    50  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block3, 10, 20), mockMetaWithMinMax(block4, 20, 30)}, useSplitting: true},
    51  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block1, 10, 20), mockMetaWithMinMax(block2, 10, 20)}},
    52  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block4, 5, 50)}, useSplitting: true}, // Big splitting block. Should be sorted by minTime only.
    53  			},
    54  			expected: []*Job{
    55  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block4, 5, 50)}, useSplitting: true},
    56  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block3, 10, 20), mockMetaWithMinMax(block4, 20, 30)}, useSplitting: true}, // Split job is first.
    57  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block1, 10, 20), mockMetaWithMinMax(block2, 10, 20)}},
    58  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block3, 10, 20), mockMetaWithMinMax(block4, 20, 30)}, useSplitting: false},
    59  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block5, 40, 60), mockMetaWithMinMax(block6, 40, 80)}},
    60  			},
    61  		},
    62  	}
    63  
    64  	for testName, testData := range tests {
    65  		t.Run(testName, func(t *testing.T) {
    66  			assert.Equal(t, testData.expected, sortJobsBySmallestRangeOldestBlocksFirst(testData.input))
    67  		})
    68  	}
    69  }
    70  
    71  func TestSortJobsByNewestBlocksFirst(t *testing.T) {
    72  	block1 := ulid.MustNew(1, nil)
    73  	block2 := ulid.MustNew(2, nil)
    74  	block3 := ulid.MustNew(3, nil)
    75  	block4 := ulid.MustNew(4, nil)
    76  	block5 := ulid.MustNew(5, nil)
    77  	block6 := ulid.MustNew(6, nil)
    78  	block7 := ulid.MustNew(7, nil)
    79  
    80  	tests := map[string]struct {
    81  		input    []*Job
    82  		expected []*Job
    83  	}{
    84  		"should do nothing on empty input": {
    85  			input:    nil,
    86  			expected: nil,
    87  		},
    88  		"should sort jobs by newest blocks first": {
    89  			input: []*Job{
    90  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block1, 10, 20), mockMetaWithMinMax(block2, 10, 20)}},
    91  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block3, 10, 20), mockMetaWithMinMax(block4, 20, 30)}},
    92  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block5, 40, 60), mockMetaWithMinMax(block6, 40, 80)}},
    93  			},
    94  			expected: []*Job{
    95  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block5, 40, 60), mockMetaWithMinMax(block6, 40, 80)}},
    96  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block3, 10, 20), mockMetaWithMinMax(block4, 20, 30)}},
    97  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block1, 10, 20), mockMetaWithMinMax(block2, 10, 20)}},
    98  			},
    99  		},
   100  		"should give precedence to smaller time ranges in case of multiple jobs with the same max time": {
   101  			input: []*Job{
   102  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block1, 10, 20), mockMetaWithMinMax(block2, 20, 30), mockMetaWithMinMax(block3, 30, 40)}},
   103  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block4, 30, 40), mockMetaWithMinMax(block5, 30, 40)}},
   104  			},
   105  			expected: []*Job{
   106  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block4, 30, 40), mockMetaWithMinMax(block5, 30, 40)}},
   107  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block1, 10, 20), mockMetaWithMinMax(block2, 20, 30), mockMetaWithMinMax(block3, 30, 40)}},
   108  			},
   109  		},
   110  		"should give precedence to newest blocks over smaller time ranges": {
   111  			input: []*Job{
   112  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block1, 10, 20), mockMetaWithMinMax(block2, 20, 30), mockMetaWithMinMax(block3, 30, 40)}},
   113  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block6, 10, 20), mockMetaWithMinMax(block7, 10, 20)}},
   114  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block4, 10, 30), mockMetaWithMinMax(block5, 20, 30)}},
   115  			},
   116  			expected: []*Job{
   117  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block1, 10, 20), mockMetaWithMinMax(block2, 20, 30), mockMetaWithMinMax(block3, 30, 40)}},
   118  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block4, 10, 30), mockMetaWithMinMax(block5, 20, 30)}},
   119  				{metasByMinTime: []*block.Meta{mockMetaWithMinMax(block6, 10, 20), mockMetaWithMinMax(block7, 10, 20)}},
   120  			},
   121  		},
   122  	}
   123  
   124  	for testName, testData := range tests {
   125  		t.Run(testName, func(t *testing.T) {
   126  			actual := sortJobsByNewestBlocksFirst(testData.input)
   127  			assert.Equal(t, testData.expected, actual)
   128  
   129  			// Print for debugging.
   130  			t.Log("sorted jobs:")
   131  			for _, job := range actual {
   132  				t.Logf("- %s", job.String())
   133  			}
   134  		})
   135  	}
   136  }
   137  
   138  func mockMetaWithMinMax(id ulid.ULID, minTime, maxTime int64) *block.Meta {
   139  	return &block.Meta{
   140  		ULID:    id,
   141  		MinTime: model.Time(minTime),
   142  		MaxTime: model.Time(maxTime),
   143  	}
   144  }