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  }