github.com/grafana/pyroscope@v1.18.0/pkg/querier/analyze_query_test.go (about) 1 package querier 2 3 import ( 4 "slices" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9 10 ingestv1 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1" 11 querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" 12 ) 13 14 func Test_getDataFromPlan(t *testing.T) { 15 tests := []struct { 16 name string 17 plan blockPlan 18 verifyIngesterQueryScope func(t *testing.T, scope *queryScope) 19 verifyStoreGatewayQueryScope func(t *testing.T, scope *queryScope) 20 wantDeduplicationNeeded bool 21 }{ 22 { 23 name: "empty plan", 24 plan: blockPlan{}, 25 verifyIngesterQueryScope: func(t *testing.T, scope *queryScope) { 26 require.Equal(t, &queryScope{ 27 QueryScope: &querierv1.QueryScope{ 28 ComponentType: "Short term storage", 29 }, 30 }, scope) 31 }, 32 verifyStoreGatewayQueryScope: func(t *testing.T, scope *queryScope) { 33 require.Equal(t, &queryScope{ 34 QueryScope: &querierv1.QueryScope{ 35 ComponentType: "Long term storage", 36 }, 37 }, scope) 38 }, 39 wantDeduplicationNeeded: false, 40 }, 41 { 42 name: "plan with ingesters only", 43 plan: blockPlan{ 44 "replica 1": &blockPlanEntry{ 45 BlockHints: &ingestv1.BlockHints{Ulids: []string{"block A", "block B"}, Deduplication: true}, 46 InstanceTypes: []instanceType{ingesterInstance}, 47 }, 48 "replica 2": &blockPlanEntry{ 49 BlockHints: &ingestv1.BlockHints{Ulids: []string{"block C", "block D"}, Deduplication: true}, 50 InstanceTypes: []instanceType{ingesterInstance}, 51 }, 52 }, 53 verifyIngesterQueryScope: func(t *testing.T, scope *queryScope) { 54 require.Equal(t, uint64(2), scope.ComponentCount) 55 require.Equal(t, uint64(4), scope.BlockCount) 56 for _, block := range []string{"block A", "block B", "block C", "block D"} { 57 require.True(t, slices.Contains(scope.blockIds, block)) 58 } 59 }, 60 verifyStoreGatewayQueryScope: func(t *testing.T, scope *queryScope) { 61 require.Equal(t, uint64(0), scope.ComponentCount) 62 }, 63 wantDeduplicationNeeded: true, 64 }, 65 { 66 name: "plan with ingesters and store gateways", 67 plan: blockPlan{ 68 "replica 1": &blockPlanEntry{ 69 BlockHints: &ingestv1.BlockHints{Ulids: []string{"block A", "block B"}, Deduplication: true}, 70 InstanceTypes: []instanceType{ingesterInstance}, 71 }, 72 "replica 2": &blockPlanEntry{ 73 BlockHints: &ingestv1.BlockHints{Ulids: []string{"block C", "block D"}, Deduplication: true}, 74 InstanceTypes: []instanceType{ingesterInstance}, 75 }, 76 "replica 3": &blockPlanEntry{ 77 BlockHints: &ingestv1.BlockHints{Ulids: []string{"block E", "block F"}, Deduplication: true}, 78 InstanceTypes: []instanceType{storeGatewayInstance}, 79 }, 80 }, 81 verifyIngesterQueryScope: func(t *testing.T, scope *queryScope) { 82 require.Equal(t, uint64(2), scope.ComponentCount) 83 require.Equal(t, uint64(4), scope.BlockCount) 84 for _, block := range []string{"block A", "block B", "block C", "block D"} { 85 require.True(t, slices.Contains(scope.blockIds, block)) 86 } 87 }, 88 verifyStoreGatewayQueryScope: func(t *testing.T, scope *queryScope) { 89 require.Equal(t, uint64(1), scope.ComponentCount) 90 require.Equal(t, uint64(2), scope.BlockCount) 91 for _, block := range []string{"block E", "block F"} { 92 require.True(t, slices.Contains(scope.blockIds, block)) 93 } 94 }, 95 wantDeduplicationNeeded: true, 96 }, 97 { 98 name: "plan with a single replica with dual instance types (standalone binary)", 99 plan: blockPlan{ 100 "replica 1": &blockPlanEntry{ 101 BlockHints: &ingestv1.BlockHints{Ulids: []string{"block A"}, Deduplication: true}, 102 InstanceTypes: []instanceType{ingesterInstance, storeGatewayInstance}, 103 }, 104 }, 105 verifyIngesterQueryScope: func(t *testing.T, scope *queryScope) { 106 require.Equal(t, uint64(1), scope.ComponentCount) 107 require.Equal(t, uint64(1), scope.BlockCount) 108 for _, block := range []string{"block A"} { 109 require.True(t, slices.Contains(scope.blockIds, block)) 110 } 111 }, 112 verifyStoreGatewayQueryScope: func(t *testing.T, scope *queryScope) { 113 require.Equal(t, uint64(1), scope.ComponentCount) 114 require.Equal(t, uint64(1), scope.BlockCount) 115 for _, block := range []string{"block A"} { 116 require.True(t, slices.Contains(scope.blockIds, block)) 117 } 118 }, 119 wantDeduplicationNeeded: true, 120 }, 121 } 122 for _, tt := range tests { 123 t.Run(tt.name, func(t *testing.T) { 124 gotIngesterQueryScope, gotStoreGatewayQueryScope, gotDeduplicationNeeded := getDataFromPlan(tt.plan) 125 tt.verifyIngesterQueryScope(t, gotIngesterQueryScope) 126 tt.verifyStoreGatewayQueryScope(t, gotStoreGatewayQueryScope) 127 assert.Equalf(t, tt.wantDeduplicationNeeded, gotDeduplicationNeeded, "getDataFromPlan(%v)", tt.plan) 128 }) 129 } 130 } 131 132 func Test_addBlockStatsToQueryScope(t *testing.T) { 133 type args struct { 134 blockStatsFromReplicas []ResponseFromReplica[*ingestv1.GetBlockStatsResponse] 135 queryScope *queryScope 136 } 137 tests := []struct { 138 name string 139 args args 140 verifyExpectations func(t *testing.T, s *queryScope) 141 }{ 142 { 143 name: "with empty block stats", 144 args: args{ 145 blockStatsFromReplicas: []ResponseFromReplica[*ingestv1.GetBlockStatsResponse]{}, 146 queryScope: &queryScope{QueryScope: &querierv1.QueryScope{}}, 147 }, 148 verifyExpectations: func(t *testing.T, s *queryScope) { 149 assert.Equalf(t, uint64(0), s.SeriesCount, "addBlockStatsToQueryScope(%v)", s) 150 assert.Equalf(t, uint64(0), s.ProfileCount, "addBlockStatsToQueryScope(%v)", s) 151 assert.Equalf(t, uint64(0), s.SampleCount, "addBlockStatsToQueryScope(%v)", s) 152 assert.Equalf(t, uint64(0), s.IndexBytes, "addBlockStatsToQueryScope(%v)", s) 153 assert.Equalf(t, uint64(0), s.ProfileBytes, "addBlockStatsToQueryScope(%v)", s) 154 assert.Equalf(t, uint64(0), s.SymbolBytes, "addBlockStatsToQueryScope(%v)", s) 155 }, 156 }, 157 { 158 name: "with valid block stats", 159 args: args{ 160 blockStatsFromReplicas: []ResponseFromReplica[*ingestv1.GetBlockStatsResponse]{ 161 { 162 addr: "replica 1", 163 response: &ingestv1.GetBlockStatsResponse{ 164 BlockStats: []*ingestv1.BlockStats{ 165 { 166 SeriesCount: 50, 167 ProfileCount: 100, 168 SampleCount: 2000, 169 IndexBytes: 1024, 170 ProfileBytes: 4096, 171 SymbolBytes: 65536, 172 }, 173 { 174 SeriesCount: 100, 175 ProfileCount: 200, 176 SampleCount: 4000, 177 IndexBytes: 2048, 178 ProfileBytes: 8192, 179 SymbolBytes: 131072, 180 }, 181 }, 182 }, 183 }, 184 { 185 addr: "replica 2", 186 response: &ingestv1.GetBlockStatsResponse{ 187 BlockStats: []*ingestv1.BlockStats{ 188 { 189 SeriesCount: 50, 190 ProfileCount: 100, 191 SampleCount: 2000, 192 IndexBytes: 1024, 193 ProfileBytes: 4096, 194 SymbolBytes: 65536, 195 }, 196 }, 197 }, 198 }, 199 }, 200 queryScope: &queryScope{QueryScope: &querierv1.QueryScope{}}, 201 }, 202 verifyExpectations: func(t *testing.T, s *queryScope) { 203 assert.Equalf(t, uint64(200), s.SeriesCount, "addBlockStatsToQueryScope(%v)", s) 204 assert.Equalf(t, uint64(400), s.ProfileCount, "addBlockStatsToQueryScope(%v)", s) 205 assert.Equalf(t, uint64(8000), s.SampleCount, "addBlockStatsToQueryScope(%v)", s) 206 assert.Equalf(t, uint64(4096), s.IndexBytes, "addBlockStatsToQueryScope(%v)", s) 207 assert.Equalf(t, uint64(16384), s.ProfileBytes, "addBlockStatsToQueryScope(%v)", s) 208 assert.Equalf(t, uint64(262144), s.SymbolBytes, "addBlockStatsToQueryScope(%v)", s) 209 }, 210 }, 211 } 212 for _, tt := range tests { 213 t.Run(tt.name, func(t *testing.T) { 214 addBlockStatsToQueryScope(tt.args.blockStatsFromReplicas, tt.args.queryScope) 215 tt.verifyExpectations(t, tt.args.queryScope) 216 }) 217 } 218 } 219 220 func Test_createResponse(t *testing.T) { 221 type args struct { 222 ingesterQueryScope *queryScope 223 storeGatewayQueryScope *queryScope 224 } 225 tests := []struct { 226 name string 227 args args 228 want *querierv1.AnalyzeQueryResponse 229 }{ 230 { 231 name: "happy path", 232 args: args{ 233 ingesterQueryScope: &queryScope{ 234 QueryScope: &querierv1.QueryScope{ 235 IndexBytes: 1024, 236 ProfileBytes: 2048, 237 SymbolBytes: 4096, 238 }, 239 }, 240 storeGatewayQueryScope: &queryScope{ 241 QueryScope: &querierv1.QueryScope{ 242 IndexBytes: 256, 243 ProfileBytes: 512, 244 SymbolBytes: 1024, 245 }, 246 }, 247 }, 248 want: &querierv1.AnalyzeQueryResponse{ 249 QueryScopes: []*querierv1.QueryScope{ 250 { 251 IndexBytes: 1024, 252 ProfileBytes: 2048, 253 SymbolBytes: 4096, 254 }, 255 { 256 IndexBytes: 256, 257 ProfileBytes: 512, 258 SymbolBytes: 1024, 259 }, 260 }, 261 QueryImpact: &querierv1.QueryImpact{ 262 TotalBytesInTimeRange: 8960, 263 }, 264 }, 265 }, 266 } 267 for _, tt := range tests { 268 t.Run(tt.name, func(t *testing.T) { 269 assert.Equalf(t, tt.want, createResponse(tt.args.ingesterQueryScope, tt.args.storeGatewayQueryScope), "createResponse(%v, %v)", tt.args.ingesterQueryScope, tt.args.storeGatewayQueryScope) 270 }) 271 } 272 } 273 274 func Test_createMatchersFromQuery(t *testing.T) { 275 tests := []struct { 276 name string 277 query string 278 want []string 279 wantErr bool 280 }{ 281 { 282 name: "empty query", 283 query: "", 284 want: []string{"{}"}, 285 wantErr: false, 286 }, 287 { 288 name: "query with a profile type", 289 query: "process_cpu:cpu:nanoseconds:cpu:nanoseconds{}", 290 want: []string{"{__profile_type__=\"process_cpu:cpu:nanoseconds:cpu:nanoseconds\"}"}, 291 wantErr: false, 292 }, 293 { 294 name: "query with labels", 295 query: "process_cpu:cpu:nanoseconds:cpu:nanoseconds{namespace=\"dev\"}", 296 want: []string{"{namespace=\"dev\",__profile_type__=\"process_cpu:cpu:nanoseconds:cpu:nanoseconds\"}"}, 297 wantErr: false, 298 }, 299 } 300 for _, tt := range tests { 301 t.Run(tt.name, func(t *testing.T) { 302 got, err := createMatchersFromQuery(tt.query) 303 if (err != nil) != tt.wantErr { 304 t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr) 305 return 306 } 307 assert.Equalf(t, tt.want, got, "createMatchersFromQuery(%v)", tt.query) 308 }) 309 } 310 }