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 }