github.com/grafana/pyroscope@v1.18.0/pkg/frontend/frontend_diff_test.go (about)

     1  package frontend
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"connectrpc.com/connect"
     9  	"github.com/grafana/dskit/user"
    10  	"github.com/opentracing/opentracing-go"
    11  	"github.com/pkg/errors"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1"
    15  	"github.com/grafana/pyroscope/pkg/model"
    16  	"github.com/grafana/pyroscope/pkg/util/connectgrpc"
    17  	"github.com/grafana/pyroscope/pkg/util/httpgrpc"
    18  )
    19  
    20  type mockLimits struct{}
    21  
    22  func (m *mockLimits) QuerySplitDuration(_ string) time.Duration {
    23  	return time.Hour
    24  }
    25  
    26  func (m *mockLimits) QuerySanitizeOnMerge(_ string) bool {
    27  	return true
    28  }
    29  
    30  func (m *mockLimits) MaxQueryParallelism(_ string) int {
    31  	return 100
    32  }
    33  
    34  func (m *mockLimits) MaxQueryLength(_ string) time.Duration {
    35  	return time.Hour
    36  }
    37  
    38  func (m *mockLimits) MaxQueryLookback(_ string) time.Duration {
    39  	return time.Hour * 24
    40  }
    41  
    42  func (m *mockLimits) QueryAnalysisEnabled(_ string) bool {
    43  	return true
    44  }
    45  
    46  func (m *mockLimits) MaxFlameGraphNodesDefault(_ string) int {
    47  	return 10_000
    48  }
    49  
    50  func (m *mockLimits) MaxFlameGraphNodesMax(_ string) int {
    51  	return 100_000
    52  }
    53  
    54  func (m *mockLimits) MaxFlameGraphNodesOnSelectMergeProfile(_ string) bool {
    55  	return true
    56  }
    57  
    58  func (m *mockLimits) SymbolizerEnabled(s string) bool { return true }
    59  
    60  type mockRoundTripper struct {
    61  	callback func(ctx context.Context, req *httpgrpc.HTTPRequest) (*httpgrpc.HTTPResponse, error)
    62  }
    63  
    64  func (m *mockRoundTripper) RoundTripGRPC(ctx context.Context, req *httpgrpc.HTTPRequest) (*httpgrpc.HTTPResponse, error) {
    65  	if m.callback != nil {
    66  		return m.callback(ctx, req)
    67  	}
    68  	return &httpgrpc.HTTPResponse{}, errors.New("not implemented")
    69  }
    70  
    71  func Test_Frontend_Diff(t *testing.T) {
    72  	frontend := Frontend{
    73  		limits: &mockLimits{},
    74  	}
    75  
    76  	ctx := user.InjectOrgID(context.Background(), "test")
    77  	_, ctx = opentracing.StartSpanFromContext(ctx, "test")
    78  	now := time.Now().UnixMilli()
    79  
    80  	profileType := "memory:inuse_space:bytes:space:byte"
    81  
    82  	t.Run("Diff outside of the query window", func(t *testing.T) {
    83  		resp, err := frontend.Diff(
    84  			ctx,
    85  			connect.NewRequest(&querierv1.DiffRequest{
    86  				Left: &querierv1.SelectMergeStacktracesRequest{
    87  					ProfileTypeID: profileType,
    88  					LabelSelector: "{}",
    89  					Start:         1,
    90  					End:           1000,
    91  				},
    92  				Right: &querierv1.SelectMergeStacktracesRequest{
    93  					ProfileTypeID: profileType,
    94  					LabelSelector: "{}",
    95  					Start:         2000,
    96  					End:           3000,
    97  				},
    98  			}),
    99  		)
   100  		require.NoError(t, err)
   101  		require.NotNil(t, resp)
   102  	})
   103  
   104  	t.Run("Failing left hand side", func(t *testing.T) {
   105  		frontend.GRPCRoundTripper = &mockRoundTripper{callback: func(ctx context.Context, req *httpgrpc.HTTPRequest) (*httpgrpc.HTTPResponse, error) {
   106  			return connectgrpc.HandleUnary[querierv1.SelectMergeStacktracesRequest, querierv1.SelectMergeStacktracesResponse](ctx, req, func(ctx context.Context, req *connect.Request[querierv1.SelectMergeStacktracesRequest]) (*connect.Response[querierv1.SelectMergeStacktracesResponse], error) {
   107  				if req.Msg.Start == now {
   108  					return nil, errors.New("left fails")
   109  				}
   110  
   111  				return connect.NewResponse(&querierv1.SelectMergeStacktracesResponse{
   112  					Flamegraph: &querierv1.FlameGraph{},
   113  				}), nil
   114  			})
   115  		}}
   116  
   117  		_, err := frontend.Diff(
   118  			ctx,
   119  			connect.NewRequest(&querierv1.DiffRequest{
   120  				Left: &querierv1.SelectMergeStacktracesRequest{
   121  					ProfileTypeID: profileType,
   122  					LabelSelector: "{}",
   123  					Start:         now + 0000,
   124  					End:           now + 1000,
   125  				},
   126  				Right: &querierv1.SelectMergeStacktracesRequest{
   127  					ProfileTypeID: profileType,
   128  					LabelSelector: "{}",
   129  					Start:         now + 2000,
   130  					End:           now + 3000,
   131  				},
   132  			}),
   133  		)
   134  		require.ErrorContains(t, err, "left fails")
   135  	})
   136  
   137  	t.Run("simple diff", func(t *testing.T) {
   138  		frontend.GRPCRoundTripper = &mockRoundTripper{callback: func(ctx context.Context, req *httpgrpc.HTTPRequest) (*httpgrpc.HTTPResponse, error) {
   139  			return connectgrpc.HandleUnary[querierv1.SelectMergeStacktracesRequest, querierv1.SelectMergeStacktracesResponse](ctx, req, func(ctx context.Context, req *connect.Request[querierv1.SelectMergeStacktracesRequest]) (*connect.Response[querierv1.SelectMergeStacktracesResponse], error) {
   140  
   141  				s := new(model.Tree)
   142  				s.InsertStack(1, "foo", "bar")
   143  
   144  				if req.Msg.Start == now {
   145  					//left
   146  					s.InsertStack(1, "foo", "bar", "baz")
   147  				} else {
   148  					//right
   149  					s.InsertStack(2, "foo", "bar", "buz")
   150  				}
   151  
   152  				return connect.NewResponse(&querierv1.SelectMergeStacktracesResponse{
   153  					Flamegraph: model.NewFlameGraph(s, -1),
   154  				}), nil
   155  			})
   156  		}}
   157  
   158  		resp, err := frontend.Diff(
   159  			ctx,
   160  			connect.NewRequest(&querierv1.DiffRequest{
   161  				Left: &querierv1.SelectMergeStacktracesRequest{
   162  					ProfileTypeID: profileType,
   163  					LabelSelector: "{}",
   164  					Start:         now + 0000,
   165  					End:           now + 1000,
   166  				},
   167  				Right: &querierv1.SelectMergeStacktracesRequest{
   168  					ProfileTypeID: profileType,
   169  					LabelSelector: "{}",
   170  					Start:         now + 2000,
   171  					End:           now + 3000,
   172  				},
   173  			}),
   174  		)
   175  		require.NoError(t, err)
   176  		require.Equal(
   177  			t,
   178  			&querierv1.FlameGraphDiff{
   179  				Names: []string{"total", "foo", "bar", "buz", "baz"},
   180  				Total: 5,
   181  				Levels: []*querierv1.Level{
   182  					{Values: []int64{0, 2, 0, 0, 3, 0, 0}},
   183  					{Values: []int64{0, 2, 0, 0, 3, 0, 1}},
   184  					{Values: []int64{0, 2, 1, 0, 3, 1, 2}},
   185  					{Values: []int64{1, 1, 1, 1, 0, 0, 4, 0, 0, 0, 0, 2, 2, 3}},
   186  				},
   187  				LeftTicks:  2,
   188  				RightTicks: 3,
   189  				MaxSelf:    2,
   190  			},
   191  			resp.Msg.Flamegraph,
   192  		)
   193  	})
   194  
   195  }