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 }