diff --git a/.mockery.yaml b/.mockery.yaml index e03e72fc0f..906adff998 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -14,3 +14,6 @@ packages: github.com/grafana/pyroscope/api/gen/proto/go/querier/v1/querierv1connect: interfaces: QuerierServiceClient: + github.com/grafana/pyroscope/pkg/frontend: + interfaces: + Limits: diff --git a/pkg/experiment/query_backend/backend.go b/pkg/experiment/query_backend/backend.go index 1a0df9c487..91a729122a 100644 --- a/pkg/experiment/query_backend/backend.go +++ b/pkg/experiment/query_backend/backend.go @@ -138,9 +138,9 @@ func (q *QueryBackend) read( } func (q *QueryBackend) withThrottling(fn func() (*queryv1.InvokeResponse, error)) (*queryv1.InvokeResponse, error) { + defer q.running.Dec() if q.running.Inc() > q.concurrency { return nil, status.Error(codes.ResourceExhausted, "all minions are busy, please try later") } - defer q.running.Dec() return fn() } diff --git a/pkg/frontend/read_path/query_frontend/compat.go b/pkg/frontend/read_path/query_frontend/compat.go index 4277e36d55..fab57194ea 100644 --- a/pkg/frontend/read_path/query_frontend/compat.go +++ b/pkg/frontend/read_path/query_frontend/compat.go @@ -17,7 +17,6 @@ import ( querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" queryv1 "github.com/grafana/pyroscope/api/gen/proto/go/query/v1" typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" - metastoreclient "github.com/grafana/pyroscope/pkg/experiment/metastore/client" phlaremodel "github.com/grafana/pyroscope/pkg/model" ) @@ -55,7 +54,7 @@ func isProfileTypeQuery(labels, matchers []string) bool { func listProfileTypesFromMetadataAsSeriesLabels( ctx context.Context, - client *metastoreclient.Client, + client metastorev1.MetastoreServiceClient, tenants []string, startTime int64, endTime int64, @@ -72,7 +71,7 @@ func listProfileTypesFromMetadataAsSeriesLabels( func listProfileTypesFromMetadata( ctx context.Context, - client *metastoreclient.Client, + client metastorev1.MetastoreServiceClient, tenants []string, startTime int64, endTime int64, diff --git a/pkg/frontend/read_path/query_frontend/query_frontend.go b/pkg/frontend/read_path/query_frontend/query_frontend.go index 191ff0ded0..c36bf717b4 100644 --- a/pkg/frontend/read_path/query_frontend/query_frontend.go +++ b/pkg/frontend/read_path/query_frontend/query_frontend.go @@ -11,7 +11,6 @@ import ( metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1" "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1/querierv1connect" queryv1 "github.com/grafana/pyroscope/api/gen/proto/go/query/v1" - metastoreclient "github.com/grafana/pyroscope/pkg/experiment/metastore/client" querybackend "github.com/grafana/pyroscope/pkg/experiment/query_backend" querybackendclient "github.com/grafana/pyroscope/pkg/experiment/query_backend/client" queryplan "github.com/grafana/pyroscope/pkg/experiment/query_backend/query_plan" @@ -23,14 +22,14 @@ var _ querierv1connect.QuerierServiceClient = (*QueryFrontend)(nil) type QueryFrontend struct { logger log.Logger limits frontend.Limits - metastore *metastoreclient.Client + metastore metastorev1.MetastoreServiceClient querybackend *querybackendclient.Client } func NewQueryFrontend( logger log.Logger, limits frontend.Limits, - metastore *metastoreclient.Client, + metastore metastorev1.MetastoreServiceClient, querybackend *querybackendclient.Client, ) *QueryFrontend { return &QueryFrontend{ diff --git a/pkg/frontend/read_path/query_frontend/query_profile_types.go b/pkg/frontend/read_path/query_frontend/query_profile_types.go new file mode 100644 index 0000000000..bf9babd124 --- /dev/null +++ b/pkg/frontend/read_path/query_frontend/query_profile_types.go @@ -0,0 +1,72 @@ +package query_frontend + +import ( + "context" + "sort" + + "connectrpc.com/connect" + "github.com/grafana/dskit/tenant" + + metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1" + querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" + typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" + phlaremodel "github.com/grafana/pyroscope/pkg/model" + "github.com/grafana/pyroscope/pkg/validation" +) + +func (q *QueryFrontend) ProfileTypes( + ctx context.Context, + req *connect.Request[querierv1.ProfileTypesRequest], +) (*connect.Response[querierv1.ProfileTypesResponse], error) { + + tenants, err := tenant.TenantIDs(ctx) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } + empty, err := validation.SanitizeTimeRange(q.limits, tenants, &req.Msg.Start, &req.Msg.End) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, err) + } + if empty { + return connect.NewResponse(&querierv1.ProfileTypesResponse{}), nil + } + + md, err := q.metastore.QueryMetadata(ctx, &metastorev1.QueryMetadataRequest{ + TenantId: tenants, + StartTime: req.Msg.Start, + EndTime: req.Msg.End, + Query: "{}", + }) + + if err != nil { + return nil, err + } + + pTypesFromMetadata := make(map[string]*typesv1.ProfileType) + for _, b := range md.Blocks { + for _, d := range b.Datasets { + for _, pType := range d.ProfileTypes { + if _, ok := pTypesFromMetadata[pType]; !ok { + profileType, err := phlaremodel.ParseProfileTypeSelector(pType) + if err != nil { + return nil, err + } + pTypesFromMetadata[pType] = profileType + } + } + } + } + + var profileTypes []*typesv1.ProfileType + for _, pType := range pTypesFromMetadata { + profileTypes = append(profileTypes, pType) + } + + sort.Slice(profileTypes, func(i, j int) bool { + return profileTypes[i].ID < profileTypes[j].ID + }) + + return connect.NewResponse(&querierv1.ProfileTypesResponse{ + ProfileTypes: profileTypes, + }), nil +} diff --git a/pkg/frontend/read_path/query_frontend/query_profile_types_test.go b/pkg/frontend/read_path/query_frontend/query_profile_types_test.go new file mode 100644 index 0000000000..1927661ce0 --- /dev/null +++ b/pkg/frontend/read_path/query_frontend/query_profile_types_test.go @@ -0,0 +1,72 @@ +package query_frontend + +import ( + "context" + "testing" + "time" + + "connectrpc.com/connect" + "github.com/go-kit/log" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1" + querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" + "github.com/grafana/pyroscope/pkg/tenant" + "github.com/grafana/pyroscope/pkg/test/mocks/mockfrontend" + "github.com/grafana/pyroscope/pkg/test/mocks/mockmetastorev1" +) + +func TestQueryFrontend_ProfileTypes(t *testing.T) { + metaClient := mockmetastorev1.NewMockMetastoreServiceClient(t) + limits := mockfrontend.NewMockLimits(t) + f := NewQueryFrontend(log.NewNopLogger(), limits, metaClient, nil) + require.NotNil(t, f) + + limits.On("MaxQueryLookback", mock.Anything).Return(24 * time.Hour) + limits.On("MaxQueryLength", mock.Anything).Return(2 * time.Hour) + metaClient.On("QueryMetadata", mock.Anything, mock.Anything).Maybe().Return(&metastorev1.QueryMetadataResponse{ + Blocks: []*metastorev1.BlockMeta{ + { + Datasets: []*metastorev1.Dataset{ + { + ProfileTypes: []string{ + "memory:inuse_space:bytes:space:byte", + "process_cpu:cpu:nanoseconds:cpu:nanoseconds", + "mutex:delay:nanoseconds:mutex:count", + }, + }, + { + ProfileTypes: []string{ + "memory:alloc_in_new_tlab_objects:count:space:bytes", + "process_cpu:cpu:nanoseconds:cpu:nanoseconds", + }, + }, + }, + }, + { + Datasets: []*metastorev1.Dataset{ + { + ProfileTypes: []string{ + "mutex:contentions:count:mutex:count", + "mutex:delay:nanoseconds:mutex:count", + }, + }, + }, + }, + }, + }, nil) + + ctx := tenant.InjectTenantID(context.Background(), "tenant") + types, err := f.ProfileTypes(ctx, connect.NewRequest(&querierv1.ProfileTypesRequest{ + Start: time.Now().Add(-time.Hour).UnixMilli(), + End: time.Now().UnixMilli(), + })) + require.NoError(t, err) + require.Equal(t, 5, len(types.Msg.ProfileTypes)) + require.Equal(t, "memory:alloc_in_new_tlab_objects:count:space:bytes", types.Msg.ProfileTypes[0].ID) + require.Equal(t, "memory:inuse_space:bytes:space:byte", types.Msg.ProfileTypes[1].ID) + require.Equal(t, "mutex:contentions:count:mutex:count", types.Msg.ProfileTypes[2].ID) + require.Equal(t, "mutex:delay:nanoseconds:mutex:count", types.Msg.ProfileTypes[3].ID) + require.Equal(t, "process_cpu:cpu:nanoseconds:cpu:nanoseconds", types.Msg.ProfileTypes[4].ID) +} diff --git a/pkg/frontend/read_path/query_frontend/query_stubs.go b/pkg/frontend/read_path/query_frontend/query_stubs.go index e0831cc6df..775bb31e5e 100644 --- a/pkg/frontend/read_path/query_frontend/query_stubs.go +++ b/pkg/frontend/read_path/query_frontend/query_stubs.go @@ -24,10 +24,3 @@ func (q *QueryFrontend) GetProfileStats( ) (*connect.Response[typesv1.GetProfileStatsResponse], error) { return connect.NewResponse(&typesv1.GetProfileStatsResponse{}), nil } - -func (q *QueryFrontend) ProfileTypes( - context.Context, - *connect.Request[querierv1.ProfileTypesRequest], -) (*connect.Response[querierv1.ProfileTypesResponse], error) { - return connect.NewResponse(&querierv1.ProfileTypesResponse{}), nil -} diff --git a/pkg/frontend/read_path/query_service_handler.go b/pkg/frontend/read_path/query_service_handler.go index 6a4fb9617f..8d8d29dc32 100644 --- a/pkg/frontend/read_path/query_service_handler.go +++ b/pkg/frontend/read_path/query_service_handler.go @@ -2,6 +2,7 @@ package read_path import ( "context" + "slices" "connectrpc.com/connect" "golang.org/x/sync/errgroup" @@ -203,10 +204,16 @@ func (r *Router) GetProfileStats( func (r *Router) ProfileTypes( ctx context.Context, - req *connect.Request[querierv1.ProfileTypesRequest], + c *connect.Request[querierv1.ProfileTypesRequest], ) (*connect.Response[querierv1.ProfileTypesResponse], error) { - if r.frontend != nil { - return r.frontend.ProfileTypes(ctx, req) - } - return connect.NewResponse(&querierv1.ProfileTypesResponse{}), nil + return Query[querierv1.ProfileTypesRequest, querierv1.ProfileTypesResponse](ctx, r, c, + func(a, b *querierv1.ProfileTypesResponse) (*querierv1.ProfileTypesResponse, error) { + pTypes := a.ProfileTypes + for _, pType := range b.ProfileTypes { + if !slices.Contains(pTypes, pType) { + pTypes = append(pTypes, pType) + } + } + return &querierv1.ProfileTypesResponse{ProfileTypes: pTypes}, nil + }) } diff --git a/pkg/test/mocks/mockfrontend/mock_limits.go b/pkg/test/mocks/mockfrontend/mock_limits.go new file mode 100644 index 0000000000..1ed23d0fd4 --- /dev/null +++ b/pkg/test/mocks/mockfrontend/mock_limits.go @@ -0,0 +1,358 @@ +// Code generated by mockery. DO NOT EDIT. + +package mockfrontend + +import ( + time "time" + + mock "github.com/stretchr/testify/mock" +) + +// MockLimits is an autogenerated mock type for the Limits type +type MockLimits struct { + mock.Mock +} + +type MockLimits_Expecter struct { + mock *mock.Mock +} + +func (_m *MockLimits) EXPECT() *MockLimits_Expecter { + return &MockLimits_Expecter{mock: &_m.Mock} +} + +// MaxFlameGraphNodesDefault provides a mock function with given fields: _a0 +func (_m *MockLimits) MaxFlameGraphNodesDefault(_a0 string) int { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for MaxFlameGraphNodesDefault") + } + + var r0 int + if rf, ok := ret.Get(0).(func(string) int); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// MockLimits_MaxFlameGraphNodesDefault_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MaxFlameGraphNodesDefault' +type MockLimits_MaxFlameGraphNodesDefault_Call struct { + *mock.Call +} + +// MaxFlameGraphNodesDefault is a helper method to define mock.On call +// - _a0 string +func (_e *MockLimits_Expecter) MaxFlameGraphNodesDefault(_a0 interface{}) *MockLimits_MaxFlameGraphNodesDefault_Call { + return &MockLimits_MaxFlameGraphNodesDefault_Call{Call: _e.mock.On("MaxFlameGraphNodesDefault", _a0)} +} + +func (_c *MockLimits_MaxFlameGraphNodesDefault_Call) Run(run func(_a0 string)) *MockLimits_MaxFlameGraphNodesDefault_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLimits_MaxFlameGraphNodesDefault_Call) Return(_a0 int) *MockLimits_MaxFlameGraphNodesDefault_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockLimits_MaxFlameGraphNodesDefault_Call) RunAndReturn(run func(string) int) *MockLimits_MaxFlameGraphNodesDefault_Call { + _c.Call.Return(run) + return _c +} + +// MaxFlameGraphNodesMax provides a mock function with given fields: _a0 +func (_m *MockLimits) MaxFlameGraphNodesMax(_a0 string) int { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for MaxFlameGraphNodesMax") + } + + var r0 int + if rf, ok := ret.Get(0).(func(string) int); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// MockLimits_MaxFlameGraphNodesMax_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MaxFlameGraphNodesMax' +type MockLimits_MaxFlameGraphNodesMax_Call struct { + *mock.Call +} + +// MaxFlameGraphNodesMax is a helper method to define mock.On call +// - _a0 string +func (_e *MockLimits_Expecter) MaxFlameGraphNodesMax(_a0 interface{}) *MockLimits_MaxFlameGraphNodesMax_Call { + return &MockLimits_MaxFlameGraphNodesMax_Call{Call: _e.mock.On("MaxFlameGraphNodesMax", _a0)} +} + +func (_c *MockLimits_MaxFlameGraphNodesMax_Call) Run(run func(_a0 string)) *MockLimits_MaxFlameGraphNodesMax_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLimits_MaxFlameGraphNodesMax_Call) Return(_a0 int) *MockLimits_MaxFlameGraphNodesMax_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockLimits_MaxFlameGraphNodesMax_Call) RunAndReturn(run func(string) int) *MockLimits_MaxFlameGraphNodesMax_Call { + _c.Call.Return(run) + return _c +} + +// MaxQueryLength provides a mock function with given fields: tenantID +func (_m *MockLimits) MaxQueryLength(tenantID string) time.Duration { + ret := _m.Called(tenantID) + + if len(ret) == 0 { + panic("no return value specified for MaxQueryLength") + } + + var r0 time.Duration + if rf, ok := ret.Get(0).(func(string) time.Duration); ok { + r0 = rf(tenantID) + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + +// MockLimits_MaxQueryLength_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MaxQueryLength' +type MockLimits_MaxQueryLength_Call struct { + *mock.Call +} + +// MaxQueryLength is a helper method to define mock.On call +// - tenantID string +func (_e *MockLimits_Expecter) MaxQueryLength(tenantID interface{}) *MockLimits_MaxQueryLength_Call { + return &MockLimits_MaxQueryLength_Call{Call: _e.mock.On("MaxQueryLength", tenantID)} +} + +func (_c *MockLimits_MaxQueryLength_Call) Run(run func(tenantID string)) *MockLimits_MaxQueryLength_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLimits_MaxQueryLength_Call) Return(_a0 time.Duration) *MockLimits_MaxQueryLength_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockLimits_MaxQueryLength_Call) RunAndReturn(run func(string) time.Duration) *MockLimits_MaxQueryLength_Call { + _c.Call.Return(run) + return _c +} + +// MaxQueryLookback provides a mock function with given fields: tenantID +func (_m *MockLimits) MaxQueryLookback(tenantID string) time.Duration { + ret := _m.Called(tenantID) + + if len(ret) == 0 { + panic("no return value specified for MaxQueryLookback") + } + + var r0 time.Duration + if rf, ok := ret.Get(0).(func(string) time.Duration); ok { + r0 = rf(tenantID) + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + +// MockLimits_MaxQueryLookback_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MaxQueryLookback' +type MockLimits_MaxQueryLookback_Call struct { + *mock.Call +} + +// MaxQueryLookback is a helper method to define mock.On call +// - tenantID string +func (_e *MockLimits_Expecter) MaxQueryLookback(tenantID interface{}) *MockLimits_MaxQueryLookback_Call { + return &MockLimits_MaxQueryLookback_Call{Call: _e.mock.On("MaxQueryLookback", tenantID)} +} + +func (_c *MockLimits_MaxQueryLookback_Call) Run(run func(tenantID string)) *MockLimits_MaxQueryLookback_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLimits_MaxQueryLookback_Call) Return(_a0 time.Duration) *MockLimits_MaxQueryLookback_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockLimits_MaxQueryLookback_Call) RunAndReturn(run func(string) time.Duration) *MockLimits_MaxQueryLookback_Call { + _c.Call.Return(run) + return _c +} + +// MaxQueryParallelism provides a mock function with given fields: _a0 +func (_m *MockLimits) MaxQueryParallelism(_a0 string) int { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for MaxQueryParallelism") + } + + var r0 int + if rf, ok := ret.Get(0).(func(string) int); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// MockLimits_MaxQueryParallelism_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MaxQueryParallelism' +type MockLimits_MaxQueryParallelism_Call struct { + *mock.Call +} + +// MaxQueryParallelism is a helper method to define mock.On call +// - _a0 string +func (_e *MockLimits_Expecter) MaxQueryParallelism(_a0 interface{}) *MockLimits_MaxQueryParallelism_Call { + return &MockLimits_MaxQueryParallelism_Call{Call: _e.mock.On("MaxQueryParallelism", _a0)} +} + +func (_c *MockLimits_MaxQueryParallelism_Call) Run(run func(_a0 string)) *MockLimits_MaxQueryParallelism_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLimits_MaxQueryParallelism_Call) Return(_a0 int) *MockLimits_MaxQueryParallelism_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockLimits_MaxQueryParallelism_Call) RunAndReturn(run func(string) int) *MockLimits_MaxQueryParallelism_Call { + _c.Call.Return(run) + return _c +} + +// QueryAnalysisEnabled provides a mock function with given fields: _a0 +func (_m *MockLimits) QueryAnalysisEnabled(_a0 string) bool { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for QueryAnalysisEnabled") + } + + var r0 bool + if rf, ok := ret.Get(0).(func(string) bool); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// MockLimits_QueryAnalysisEnabled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryAnalysisEnabled' +type MockLimits_QueryAnalysisEnabled_Call struct { + *mock.Call +} + +// QueryAnalysisEnabled is a helper method to define mock.On call +// - _a0 string +func (_e *MockLimits_Expecter) QueryAnalysisEnabled(_a0 interface{}) *MockLimits_QueryAnalysisEnabled_Call { + return &MockLimits_QueryAnalysisEnabled_Call{Call: _e.mock.On("QueryAnalysisEnabled", _a0)} +} + +func (_c *MockLimits_QueryAnalysisEnabled_Call) Run(run func(_a0 string)) *MockLimits_QueryAnalysisEnabled_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLimits_QueryAnalysisEnabled_Call) Return(_a0 bool) *MockLimits_QueryAnalysisEnabled_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockLimits_QueryAnalysisEnabled_Call) RunAndReturn(run func(string) bool) *MockLimits_QueryAnalysisEnabled_Call { + _c.Call.Return(run) + return _c +} + +// QuerySplitDuration provides a mock function with given fields: _a0 +func (_m *MockLimits) QuerySplitDuration(_a0 string) time.Duration { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for QuerySplitDuration") + } + + var r0 time.Duration + if rf, ok := ret.Get(0).(func(string) time.Duration); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + +// MockLimits_QuerySplitDuration_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QuerySplitDuration' +type MockLimits_QuerySplitDuration_Call struct { + *mock.Call +} + +// QuerySplitDuration is a helper method to define mock.On call +// - _a0 string +func (_e *MockLimits_Expecter) QuerySplitDuration(_a0 interface{}) *MockLimits_QuerySplitDuration_Call { + return &MockLimits_QuerySplitDuration_Call{Call: _e.mock.On("QuerySplitDuration", _a0)} +} + +func (_c *MockLimits_QuerySplitDuration_Call) Run(run func(_a0 string)) *MockLimits_QuerySplitDuration_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockLimits_QuerySplitDuration_Call) Return(_a0 time.Duration) *MockLimits_QuerySplitDuration_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockLimits_QuerySplitDuration_Call) RunAndReturn(run func(string) time.Duration) *MockLimits_QuerySplitDuration_Call { + _c.Call.Return(run) + return _c +} + +// NewMockLimits creates a new instance of MockLimits. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockLimits(t interface { + mock.TestingT + Cleanup(func()) +}) *MockLimits { + mock := &MockLimits{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +}