github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/querier/astmapper/shard_summer_test.go (about)

     1  package astmapper
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/prometheus/prometheus/pkg/labels"
     8  	"github.com/prometheus/prometheus/promql/parser"
     9  	"github.com/stretchr/testify/require"
    10  )
    11  
    12  // orSquasher is a custom squasher which mimics the intuitive but less efficient OR'ing of sharded vectors.
    13  // It's helpful for tests because of its intuitive & human readable output.
    14  func orSquasher(nodes ...parser.Node) (parser.Expr, error) {
    15  	combined := nodes[0]
    16  	for i := 1; i < len(nodes); i++ {
    17  		combined = &parser.BinaryExpr{
    18  			Op:  parser.LOR,
    19  			LHS: combined.(parser.Expr),
    20  			RHS: nodes[i].(parser.Expr),
    21  		}
    22  	}
    23  	return combined.(parser.Expr), nil
    24  }
    25  
    26  func TestShardSummer(t *testing.T) {
    27  	var testExpr = []struct {
    28  		shards   int
    29  		input    string
    30  		expected string
    31  	}{
    32  		{
    33  			shards: 3,
    34  			input:  `sum(rate(bar1{baz="blip"}[1m]))`,
    35  			expected: `sum without(__cortex_shard__) (
    36  			  sum by(__cortex_shard__) (rate(bar1{__cortex_shard__="0_of_3",baz="blip"}[1m])) or
    37  			  sum by(__cortex_shard__) (rate(bar1{__cortex_shard__="1_of_3",baz="blip"}[1m])) or
    38  			  sum by(__cortex_shard__) (rate(bar1{__cortex_shard__="2_of_3",baz="blip"}[1m]))
    39  			)`,
    40  		},
    41  		{
    42  			shards: 3,
    43  			input:  `sum by(foo) (rate(bar1{baz="blip"}[1m]))`,
    44  			expected: `sum by(foo) (
    45  			  sum by(foo, __cortex_shard__) (rate(bar1{__cortex_shard__="0_of_3",baz="blip"}[1m])) or
    46  			  sum by(foo, __cortex_shard__) (rate(bar1{__cortex_shard__="1_of_3",baz="blip"}[1m])) or
    47  			  sum by(foo, __cortex_shard__) (rate(bar1{__cortex_shard__="2_of_3",baz="blip"}[1m]))
    48  			)`,
    49  		},
    50  		{
    51  			shards: 2,
    52  			input: `sum(
    53  				sum by (foo) (rate(bar1{baz="blip"}[1m]))
    54  				/
    55  				sum by (foo) (rate(foo{baz="blip"}[1m]))
    56  			)`,
    57  			expected: `sum(
    58  			  sum by(foo) (
    59  				sum by(foo, __cortex_shard__) (rate(bar1{__cortex_shard__="0_of_2",baz="blip"}[1m])) or
    60  				sum by(foo, __cortex_shard__) (rate(bar1{__cortex_shard__="1_of_2",baz="blip"}[1m]))
    61  			  )
    62  			  /
    63  			  sum by(foo) (
    64  				sum by(foo, __cortex_shard__) (rate(foo{__cortex_shard__="0_of_2",baz="blip"}[1m])) or
    65  				sum by(foo, __cortex_shard__) (rate(foo{__cortex_shard__="1_of_2",baz="blip"}[1m]))
    66  			  )
    67  			)`,
    68  		},
    69  		// This nested sum example is nonsensical, but should not try to shard nested aggregations.
    70  		// Instead it only maps the subAggregation but not the outer one.
    71  		{
    72  			shards: 2,
    73  			input:  `sum(sum by(foo) (rate(bar1{baz="blip"}[1m])))`,
    74  			expected: `sum(
    75  			  sum by(foo) (
    76  			    sum by(foo, __cortex_shard__) (rate(bar1{__cortex_shard__="0_of_2",baz="blip"}[1m])) or
    77  			    sum by(foo, __cortex_shard__) (rate(bar1{__cortex_shard__="1_of_2",baz="blip"}[1m]))
    78  			  )
    79  			)`,
    80  		},
    81  		// without
    82  		{
    83  			shards: 2,
    84  			input:  `sum without(foo) (rate(bar1{baz="blip"}[1m]))`,
    85  			expected: `sum without(__cortex_shard__) (
    86  			  sum without(foo) (rate(bar1{__cortex_shard__="0_of_2",baz="blip"}[1m])) or
    87  			  sum without(foo) (rate(bar1{__cortex_shard__="1_of_2",baz="blip"}[1m]))
    88  			)`,
    89  		},
    90  		// multiple dimensions
    91  		{
    92  			shards: 2,
    93  			input:  `sum by(foo, bom) (rate(bar1{baz="blip"}[1m]))`,
    94  			expected: `sum by(foo, bom) (
    95  			  sum by(foo, bom, __cortex_shard__) (rate(bar1{__cortex_shard__="0_of_2",baz="blip"}[1m])) or
    96  			  sum by(foo, bom, __cortex_shard__) (rate(bar1{__cortex_shard__="1_of_2",baz="blip"}[1m]))
    97  			)`,
    98  		},
    99  		// sharding histogram inputs
   100  		{
   101  			shards: 2,
   102  			input:  `histogram_quantile(0.9, sum(rate(alertmanager_http_request_duration_seconds_bucket[10m])) by (job, le))`,
   103  			expected: `histogram_quantile(
   104  				    0.9,
   105  				    sum by(job, le) (
   106  				      sum by(job, le, __cortex_shard__) (rate(alertmanager_http_request_duration_seconds_bucket{__cortex_shard__="0_of_2"}[10m])) or
   107  				      sum by(job, le, __cortex_shard__) (rate(alertmanager_http_request_duration_seconds_bucket{__cortex_shard__="1_of_2"}[10m]))
   108  				    )
   109  				  )`,
   110  		},
   111  		{
   112  			// Disallow sharding nested aggregations as they may merge series in a non-associative manner.
   113  			shards:   2,
   114  			input:    `sum(count(foo{}))`,
   115  			expected: `sum(count(foo{}))`,
   116  		},
   117  	}
   118  
   119  	for i, c := range testExpr {
   120  		t.Run(fmt.Sprintf("[%d]", i), func(t *testing.T) {
   121  
   122  			summer, err := NewShardSummer(c.shards, orSquasher, nil)
   123  			require.Nil(t, err)
   124  			expr, err := parser.ParseExpr(c.input)
   125  			require.Nil(t, err)
   126  			res, err := summer.Map(expr)
   127  			require.Nil(t, err)
   128  
   129  			expected, err := parser.ParseExpr(c.expected)
   130  			require.Nil(t, err)
   131  
   132  			require.Equal(t, expected.String(), res.String())
   133  		})
   134  	}
   135  }
   136  
   137  func TestShardSummerWithEncoding(t *testing.T) {
   138  	for i, c := range []struct {
   139  		shards   int
   140  		input    string
   141  		expected string
   142  	}{
   143  		{
   144  			shards:   3,
   145  			input:    `sum(rate(bar1{baz="blip"}[1m]))`,
   146  			expected: `sum without(__cortex_shard__) (__embedded_queries__{__cortex_queries__="{\"Concat\":[\"sum by(__cortex_shard__) (rate(bar1{__cortex_shard__=\\\"0_of_3\\\",baz=\\\"blip\\\"}[1m]))\",\"sum by(__cortex_shard__) (rate(bar1{__cortex_shard__=\\\"1_of_3\\\",baz=\\\"blip\\\"}[1m]))\",\"sum by(__cortex_shard__) (rate(bar1{__cortex_shard__=\\\"2_of_3\\\",baz=\\\"blip\\\"}[1m]))\"]}"})`,
   147  		},
   148  	} {
   149  		t.Run(fmt.Sprintf("[%d]", i), func(t *testing.T) {
   150  			summer, err := NewShardSummer(c.shards, VectorSquasher, nil)
   151  			require.Nil(t, err)
   152  			expr, err := parser.ParseExpr(c.input)
   153  			require.Nil(t, err)
   154  			res, err := summer.Map(expr)
   155  			require.Nil(t, err)
   156  
   157  			expected, err := parser.ParseExpr(c.expected)
   158  			require.Nil(t, err)
   159  
   160  			require.Equal(t, expected.String(), res.String())
   161  		})
   162  	}
   163  }
   164  
   165  func TestParseShard(t *testing.T) {
   166  	var testExpr = []struct {
   167  		input  string
   168  		output ShardAnnotation
   169  		err    bool
   170  	}{
   171  		{
   172  			input:  "lsdjf",
   173  			output: ShardAnnotation{},
   174  			err:    true,
   175  		},
   176  		{
   177  			input:  "a_of_3",
   178  			output: ShardAnnotation{},
   179  			err:    true,
   180  		},
   181  		{
   182  			input:  "3_of_3",
   183  			output: ShardAnnotation{},
   184  			err:    true,
   185  		},
   186  		{
   187  			input: "1_of_2",
   188  			output: ShardAnnotation{
   189  				Shard: 1,
   190  				Of:    2,
   191  			},
   192  		},
   193  	}
   194  
   195  	for _, c := range testExpr {
   196  		t.Run(fmt.Sprint(c.input), func(t *testing.T) {
   197  			shard, err := ParseShard(c.input)
   198  			if c.err {
   199  				require.NotNil(t, err)
   200  			} else {
   201  				require.Nil(t, err)
   202  				require.Equal(t, c.output, shard)
   203  			}
   204  		})
   205  	}
   206  
   207  }
   208  
   209  func TestShardFromMatchers(t *testing.T) {
   210  	var testExpr = []struct {
   211  		input []*labels.Matcher
   212  		shard *ShardAnnotation
   213  		idx   int
   214  		err   bool
   215  	}{
   216  		{
   217  			input: []*labels.Matcher{
   218  				{},
   219  				{
   220  					Name: ShardLabel,
   221  					Type: labels.MatchEqual,
   222  					Value: ShardAnnotation{
   223  						Shard: 10,
   224  						Of:    16,
   225  					}.String(),
   226  				},
   227  				{},
   228  			},
   229  			shard: &ShardAnnotation{
   230  				Shard: 10,
   231  				Of:    16,
   232  			},
   233  			idx: 1,
   234  			err: false,
   235  		},
   236  		{
   237  			input: []*labels.Matcher{
   238  				{
   239  					Name:  ShardLabel,
   240  					Type:  labels.MatchEqual,
   241  					Value: "invalid-fmt",
   242  				},
   243  			},
   244  			shard: nil,
   245  			idx:   0,
   246  			err:   true,
   247  		},
   248  		{
   249  			input: []*labels.Matcher{},
   250  			shard: nil,
   251  			idx:   0,
   252  			err:   false,
   253  		},
   254  	}
   255  
   256  	for i, c := range testExpr {
   257  		t.Run(fmt.Sprint(i), func(t *testing.T) {
   258  			shard, idx, err := ShardFromMatchers(c.input)
   259  			if c.err {
   260  				require.NotNil(t, err)
   261  			} else {
   262  				require.Nil(t, err)
   263  				require.Equal(t, c.shard, shard)
   264  				require.Equal(t, c.idx, idx)
   265  			}
   266  		})
   267  	}
   268  
   269  }