Skip to content

Commit 9c07baf

Browse files
committed
make the timeline minInterval configurable from the querier
1 parent c0df078 commit 9c07baf

File tree

7 files changed

+106
-16
lines changed

7 files changed

+106
-16
lines changed

pkg/api/api.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,8 @@ func (a *API) RegisterFeatureFlagsServiceHandler(svc capabilitiesv1connect.Featu
232232
capabilitiesv1connect.RegisterFeatureFlagsServiceHandler(a.server.HTTP, svc, a.connectOptionsAuthLogRecovery()...)
233233
}
234234

235-
func (a *API) RegisterPyroscopeHandlers(client querierv1connect.QuerierServiceClient) {
236-
handlers := querier.NewHTTPHandlers(client)
235+
func (a *API) RegisterPyroscopeHandlers(client querierv1connect.QuerierServiceClient, cfg querier.Config) {
236+
handlers := querier.NewHTTPHandlers(client, cfg)
237237
a.RegisterRoute("/pyroscope/render", http.HandlerFunc(handlers.Render), a.registerOptionsReadPath()...)
238238
a.RegisterRoute("/pyroscope/render-diff", http.HandlerFunc(handlers.RenderDiff), a.registerOptionsReadPath()...)
239239
a.RegisterRoute("/pyroscope/label-values", http.HandlerFunc(handlers.LabelValues), a.registerOptionsReadPath()...)

pkg/pyroscope/modules.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ func (f *Pyroscope) initQuerier() (services.Service, error) {
262262
}
263263

264264
if !f.isModuleActive(QueryFrontend) {
265-
f.API.RegisterPyroscopeHandlers(querierSvc)
265+
f.API.RegisterPyroscopeHandlers(querierSvc, f.Cfg.Querier)
266266
f.API.RegisterQuerierServiceHandler(querierSvc)
267267
}
268268

pkg/pyroscope/modules_experimental.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func (f *Pyroscope) initQueryFrontendV1() (services.Service, error) {
8282
}
8383
f.API.RegisterFrontendForQuerierHandler(f.frontend)
8484
f.API.RegisterQuerierServiceHandler(spanlogger.NewLogSpanParametersWrapper(f.frontend, queryFrontendLogger))
85-
f.API.RegisterPyroscopeHandlers(spanlogger.NewLogSpanParametersWrapper(f.frontend, queryFrontendLogger))
85+
f.API.RegisterPyroscopeHandlers(spanlogger.NewLogSpanParametersWrapper(f.frontend, queryFrontendLogger), f.Cfg.Querier)
8686
f.API.RegisterVCSServiceHandler(f.frontend)
8787
return f.frontend, nil
8888
}
@@ -104,7 +104,7 @@ func (f *Pyroscope) initQueryFrontendV2() (services.Service, error) {
104104
)
105105

106106
f.API.RegisterQuerierServiceHandler(spanlogger.NewLogSpanParametersWrapper(queryFrontend, queryFrontendLogger))
107-
f.API.RegisterPyroscopeHandlers(spanlogger.NewLogSpanParametersWrapper(queryFrontend, queryFrontendLogger))
107+
f.API.RegisterPyroscopeHandlers(spanlogger.NewLogSpanParametersWrapper(queryFrontend, queryFrontendLogger), f.Cfg.Querier)
108108
f.API.RegisterVCSServiceHandler(vcsService)
109109

110110
// New query frontend does not have any state.
@@ -148,7 +148,7 @@ func (f *Pyroscope) initQueryFrontendV12() (services.Service, error) {
148148

149149
f.API.RegisterFrontendForQuerierHandler(f.frontend)
150150
f.API.RegisterQuerierServiceHandler(spanlogger.NewLogSpanParametersWrapper(handler, queryFrontendLogger))
151-
f.API.RegisterPyroscopeHandlers(spanlogger.NewLogSpanParametersWrapper(handler, queryFrontendLogger))
151+
f.API.RegisterPyroscopeHandlers(spanlogger.NewLogSpanParametersWrapper(queryFrontend, queryFrontendLogger), f.Cfg.Querier)
152152
f.API.RegisterVCSServiceHandler(vcsService)
153153

154154
return f.frontend, nil

pkg/querier/http.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,16 @@ import (
3131
httputil "github.com/grafana/pyroscope/pkg/util/http"
3232
)
3333

34-
func NewHTTPHandlers(client querierv1connect.QuerierServiceClient) *QueryHandlers {
35-
return &QueryHandlers{client}
34+
func NewHTTPHandlers(client querierv1connect.QuerierServiceClient, cfg Config) *QueryHandlers {
35+
return &QueryHandlers{
36+
client: client,
37+
cfg: cfg,
38+
}
3639
}
3740

3841
type QueryHandlers struct {
3942
client querierv1connect.QuerierServiceClient
43+
cfg Config
4044
}
4145

4246
// LabelValues only returns the label values for the given label name.
@@ -186,7 +190,7 @@ func (q *QueryHandlers) Render(w http.ResponseWriter, req *http.Request) {
186190
return err
187191
})
188192

189-
timelineStep := timeline.CalcPointInterval(selectParams.Start, selectParams.End)
193+
timelineStep := timeline.CalcPointInterval(selectParams.Start, selectParams.End, q.cfg.MinInterval)
190194
var resSeries *connect.Response[querierv1.SelectSeriesResponse]
191195
g.Go(func() error {
192196
var err error

pkg/querier/querier.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@ import (
4747
type Config struct {
4848
PoolConfig clientpool.PoolConfig `yaml:"pool_config,omitempty"`
4949
QueryStoreAfter time.Duration `yaml:"query_store_after" category:"advanced"`
50+
MinInterval time.Duration `yaml:"min_interval" category:"advanced"`
5051
}
5152

5253
// RegisterFlags registers distributor-related flags.
5354
func (cfg *Config) RegisterFlags(fs *flag.FlagSet) {
5455
cfg.PoolConfig.RegisterFlagsWithPrefix("querier", fs)
5556
fs.DurationVar(&cfg.QueryStoreAfter, "querier.query-store-after", 4*time.Hour, "The time after which a metric should be queried from storage and not just ingesters. 0 means all queries are sent to store. If this option is enabled, the time range of the query sent to the store-gateway will be manipulated to ensure the query end is not more recent than 'now - query-store-after'.")
57+
fs.DurationVar(&cfg.MinInterval, "querier.min-interval", 5*time.Second, "The minimum interval between points in a query.")
5658
}
5759

5860
type Limits interface {

pkg/querier/timeline/calculator.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,30 @@ import (
77
)
88

99
var (
10-
DefaultRes int64 = 1500
11-
DefaultMinInterval = time.Second * 15
10+
DefaultRes int64 = 1500
1211
)
1312

1413
// CalcPointInterval calculates the appropriate interval between each point (aka step)
1514
// Note that its main usage is with SelectSeries, therefore its
1615
// * inputs are in ms
1716
// * output is in seconds
18-
func CalcPointInterval(fromMs int64, untilMs int64) float64 {
17+
func CalcPointInterval(fromMs int64, untilMs int64, minInterval time.Duration) float64 {
1918
resolution := DefaultRes
2019

2120
fromNano := fromMs * 1000000
2221
untilNano := untilMs * 1000000
2322
calculatedIntervalNano := time.Duration((untilNano - fromNano) / resolution)
2423

25-
if calculatedIntervalNano < DefaultMinInterval {
26-
return DefaultMinInterval.Seconds()
24+
if calculatedIntervalNano < minInterval {
25+
return minInterval.Seconds()
2726
}
2827

2928
return roundInterval(calculatedIntervalNano).Seconds()
3029
}
3130

3231
//nolint:gocyclo
3332
func roundInterval(interval time.Duration) time.Duration {
34-
// Notice that interval may be smaller than DefaultMinInterval, and therefore some branches may never be reached
33+
// Some branches may never be reached depending on the minimum interval configured
3534
// These branches are left in case the invariant changes
3635
switch {
3736
// 0.01s

pkg/querier/timeline/calculator_test.go

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
func Test_CalcPointInterval(t *testing.T) {
1313
TestDate := time.Date(2023, time.April, 18, 1, 2, 3, 4, time.UTC)
14+
defaultMinInterval := 15 * time.Second
1415

1516
testCases := []struct {
1617
name string
@@ -31,10 +32,94 @@ func Test_CalcPointInterval(t *testing.T) {
3132

3233
for _, tc := range testCases {
3334
t.Run(tc.name, func(t *testing.T) {
34-
got := timeline.CalcPointInterval(tc.start.UnixMilli(), tc.end.UnixMilli())
35+
got := timeline.CalcPointInterval(tc.start.UnixMilli(), tc.end.UnixMilli(), defaultMinInterval)
3536

3637
assert.Equal(t, float64(tc.want), got)
3738
})
3839
}
3940

4041
}
42+
43+
func Test_CalcPointInterval_WithCustomMinInterval(t *testing.T) {
44+
TestDate := time.Date(2023, time.April, 18, 1, 2, 3, 4, time.UTC)
45+
46+
testCases := []struct {
47+
name string
48+
start time.Time
49+
end time.Time
50+
minInterval time.Duration
51+
want float64
52+
}{
53+
{
54+
name: "1 second with 5s min interval",
55+
start: TestDate,
56+
end: TestDate.Add(1 * time.Second),
57+
minInterval: 5 * time.Second,
58+
want: 5.0,
59+
},
60+
{
61+
name: "1 second with 30s min interval",
62+
start: TestDate,
63+
end: TestDate.Add(1 * time.Second),
64+
minInterval: 30 * time.Second,
65+
want: 30.0,
66+
},
67+
{
68+
name: "1 hour with 5s min interval",
69+
start: TestDate,
70+
end: TestDate.Add(1 * time.Hour),
71+
minInterval: 5 * time.Second,
72+
want: 5.0,
73+
},
74+
{
75+
name: "1 hour with 1m min interval",
76+
start: TestDate,
77+
end: TestDate.Add(1 * time.Hour),
78+
minInterval: 1 * time.Minute,
79+
want: 60.0,
80+
},
81+
{
82+
name: "7 days with 1m min interval",
83+
start: TestDate,
84+
end: TestDate.Add(7 * 24 * time.Hour),
85+
minInterval: 1 * time.Minute,
86+
want: 300.0, // calculated interval is 5m, which is > 1m min
87+
},
88+
{
89+
name: "7 days with 10m min interval",
90+
start: TestDate,
91+
end: TestDate.Add(7 * 24 * time.Hour),
92+
minInterval: 10 * time.Minute,
93+
want: 600.0, // min interval enforced (10m)
94+
},
95+
{
96+
name: "30 days with default min interval",
97+
start: TestDate,
98+
end: TestDate.Add(30 * 24 * time.Hour),
99+
minInterval: 15 * time.Second,
100+
want: 1800.0, // calculated interval is 30m
101+
},
102+
{
103+
name: "30 days with 1h min interval",
104+
start: TestDate,
105+
end: TestDate.Add(30 * 24 * time.Hour),
106+
minInterval: 1 * time.Hour,
107+
want: 3600.0, // min interval enforced (1h)
108+
},
109+
{
110+
name: "1 year with 5m min interval",
111+
start: TestDate,
112+
end: TestDate.Add(365 * 24 * time.Hour),
113+
minInterval: 5 * time.Minute,
114+
want: 21600.0, // calculated interval is 6h
115+
},
116+
}
117+
118+
for _, tc := range testCases {
119+
t.Run(tc.name, func(t *testing.T) {
120+
got := timeline.CalcPointInterval(tc.start.UnixMilli(), tc.end.UnixMilli(), tc.minInterval)
121+
122+
assert.Equal(t, tc.want, got, "expected %v seconds, got %v seconds", tc.want, got)
123+
})
124+
}
125+
}

0 commit comments

Comments
 (0)