Skip to content

Commit 0da6da5

Browse files
committed
add time range parameters to labels API
Signed-off-by: Ben Ye <[email protected]>
1 parent 13d8efc commit 0da6da5

20 files changed

+623
-124
lines changed

.bingo/Variables.mk

+3-3
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,13 @@ $(MINIO): .bingo/minio.mod
100100
@echo "(re)installing $(GOBIN)/minio-v0.0.0-20200527010300-cccf2de129da"
101101
@cd .bingo && $(GO) build -modfile=minio.mod -o=$(GOBIN)/minio-v0.0.0-20200527010300-cccf2de129da "github.com/minio/minio"
102102

103-
PROMETHEUS_ARRAY := $(GOBIN)/prometheus-v2.4.3+incompatible $(GOBIN)/prometheus-v1.8.2-0.20200507164740-ecee9c8abfd1
103+
PROMETHEUS_ARRAY := $(GOBIN)/prometheus-v2.4.3+incompatible $(GOBIN)/prometheus-v1.8.2-0.20200724121523-657ba532e42f
104104
$(PROMETHEUS_ARRAY): .bingo/prometheus.mod .bingo/prometheus.1.mod
105105
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
106106
@echo "(re)installing $(GOBIN)/prometheus-v2.4.3+incompatible"
107107
@cd .bingo && $(GO) build -modfile=prometheus.mod -o=$(GOBIN)/prometheus-v2.4.3+incompatible "github.com/prometheus/prometheus/cmd/prometheus"
108-
@echo "(re)installing $(GOBIN)/prometheus-v1.8.2-0.20200507164740-ecee9c8abfd1"
109-
@cd .bingo && $(GO) build -modfile=prometheus.1.mod -o=$(GOBIN)/prometheus-v1.8.2-0.20200507164740-ecee9c8abfd1 "github.com/prometheus/prometheus/cmd/prometheus"
108+
@echo "(re)installing $(GOBIN)/prometheus-v1.8.2-0.20200724121523-657ba532e42f"
109+
@cd .bingo && $(GO) build -modfile=prometheus.1.mod -o=$(GOBIN)/prometheus-v1.8.2-0.20200724121523-657ba532e42f "github.com/prometheus/prometheus/cmd/prometheus"
110110

111111
PROMTOOL := $(GOBIN)/promtool-v1.8.2-0.20200522113006-f4dd45609a05
112112
$(PROMTOOL): .bingo/promtool.mod

.bingo/prometheus.1.mod

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
22

33
go 1.14
44

5-
require github.com/prometheus/prometheus v1.8.2-0.20200507164740-ecee9c8abfd1 // cmd/prometheus
5+
require github.com/prometheus/prometheus v1.8.2-0.20200724121523-657ba532e42f // cmd/prometheus
66

77
replace (
88
// Mitigation for: https://github.com/Azure/go-autorest/issues/414
99
github.com/Azure/go-autorest => github.com/Azure/go-autorest v12.3.0+incompatible
10-
k8s.io/api => k8s.io/api v0.17.5
11-
k8s.io/apimachinery => k8s.io/apimachinery v0.17.5
12-
k8s.io/client-go => k8s.io/client-go v0.17.5
10+
k8s.io/api => k8s.io/api v0.18.6
11+
k8s.io/apimachinery => k8s.io/apimachinery v0.18.6
12+
k8s.io/client-go => k8s.io/client-go v0.18.6
1313
k8s.io/klog => github.com/simonpasquier/klog-gokit v0.1.0
1414
k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30
1515
)

.bingo/variables.env

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ LICHE="${gobin}/liche-v0.0.0-20181124191719-2a2e6e56f6c6"
3636

3737
MINIO="${gobin}/minio-v0.0.0-20200527010300-cccf2de129da"
3838

39-
PROMETHEUS_ARRAY="${gobin}/prometheus-v2.4.3+incompatible${gobin}/prometheus-v1.8.2-0.20200507164740-ecee9c8abfd1"
39+
PROMETHEUS_ARRAY="${gobin}/prometheus-v2.4.3+incompatible${gobin}/prometheus-v1.8.2-0.20200724121523-657ba532e42f"
4040

4141
PROMTOOL="${gobin}/promtool-v1.8.2-0.20200522113006-f4dd45609a05"
4242

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ We use *breaking* word for marking changes that are not backward compatible (rel
3030
- [#2926](https://github.com/thanos-io/thanos/pull/2926) API: Add new blocks HTTP API to serve blocks metadata. The status endpoints (`/api/v1/status/flags`, `/api/v1/status/runtimeinfo` and `/api/v1/status/buildinfo`) are now available on all components with a HTTP API.
3131
- [#2892](https://github.com/thanos-io/thanos/pull/2892) Receive: Receiver fails when the initial upload fails.
3232
- [#2865](https://github.com/thanos-io/thanos/pull/2865) ui: Migrate Thanos Ruler UI to React
33+
- [#2964](https://github.com/thanos-io/thanos/pull/2964) Query: Add time range parameters to label APIs. Add `start` and `end` fields to Store API `LabelNamesRequest` and `LabelValuesRequest`.
3334

3435
### Changed
3536

pkg/api/query/v1.go

+52-30
Original file line numberDiff line numberDiff line change
@@ -203,15 +203,9 @@ func (qapi *QueryAPI) parsePartialResponseParam(r *http.Request, defaultEnablePa
203203
}
204204

205205
func (qapi *QueryAPI) query(r *http.Request) (interface{}, []error, *api.ApiError) {
206-
var ts time.Time
207-
if t := r.FormValue("time"); t != "" {
208-
var err error
209-
ts, err = parseTime(t)
210-
if err != nil {
211-
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
212-
}
213-
} else {
214-
ts = qapi.baseAPI.Now()
206+
ts, err := parseTimeParam(r, "time", qapi.baseAPI.Now())
207+
if err != nil {
208+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
215209
}
216210

217211
ctx := r.Context()
@@ -394,12 +388,27 @@ func (qapi *QueryAPI) labelValues(r *http.Request) (interface{}, []error, *api.A
394388
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("invalid label name: %q", name)}
395389
}
396390

391+
start, err := parseTimeParam(r, "start", minTime)
392+
if err != nil {
393+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
394+
}
395+
end, err := parseTimeParam(r, "end", maxTime)
396+
if err != nil {
397+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
398+
}
399+
// Prometheus doesn't check this, do we need to?
400+
if end.Before(start) {
401+
err := errors.New("end timestamp must not be before start time")
402+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
403+
}
404+
397405
enablePartialResponse, apiErr := qapi.parsePartialResponseParam(r, qapi.enableQueryPartialResponse)
398406
if apiErr != nil {
399407
return nil, nil, apiErr
400408
}
401409

402-
q, err := qapi.queryableCreate(true, nil, 0, enablePartialResponse, false).Querier(ctx, math.MinInt64, math.MaxInt64)
410+
q, err := qapi.queryableCreate(true, nil, 0, enablePartialResponse, false).
411+
Querier(ctx, timestamp.FromTime(start), timestamp.FromTime(end))
403412
if err != nil {
404413
return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}
405414
}
@@ -433,26 +442,13 @@ func (qapi *QueryAPI) series(r *http.Request) (interface{}, []error, *api.ApiErr
433442
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.New("no match[] parameter provided")}
434443
}
435444

436-
var start time.Time
437-
if t := r.FormValue("start"); t != "" {
438-
var err error
439-
start, err = parseTime(t)
440-
if err != nil {
441-
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
442-
}
443-
} else {
444-
start = minTime
445+
start, err := parseTimeParam(r, "start", minTime)
446+
if err != nil {
447+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
445448
}
446-
447-
var end time.Time
448-
if t := r.FormValue("end"); t != "" {
449-
var err error
450-
end, err = parseTime(t)
451-
if err != nil {
452-
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
453-
}
454-
} else {
455-
end = maxTime
449+
end, err := parseTimeParam(r, "end", maxTime)
450+
if err != nil {
451+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
456452
}
457453

458454
var matcherSets [][]*labels.Matcher
@@ -504,6 +500,18 @@ func (qapi *QueryAPI) series(r *http.Request) (interface{}, []error, *api.ApiErr
504500
return metrics, set.Warnings(), nil
505501
}
506502

503+
func parseTimeParam(r *http.Request, paramName string, defaultValue time.Time) (time.Time, error) {
504+
val := r.FormValue(paramName)
505+
if val == "" {
506+
return defaultValue, nil
507+
}
508+
result, err := parseTime(val)
509+
if err != nil {
510+
return time.Time{}, errors.Wrapf(err, "Invalid time value for '%s'", paramName)
511+
}
512+
return result, nil
513+
}
514+
507515
func parseTime(s string) (time.Time, error) {
508516
if t, err := strconv.ParseFloat(s, 64); err == nil {
509517
s, ns := math.Modf(t)
@@ -532,12 +540,26 @@ func parseDuration(s string) (time.Duration, error) {
532540
func (qapi *QueryAPI) labelNames(r *http.Request) (interface{}, []error, *api.ApiError) {
533541
ctx := r.Context()
534542

543+
start, err := parseTimeParam(r, "start", minTime)
544+
if err != nil {
545+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
546+
}
547+
end, err := parseTimeParam(r, "end", maxTime)
548+
if err != nil {
549+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
550+
}
551+
if end.Before(start) {
552+
err := errors.New("end timestamp must not be before start time")
553+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
554+
}
555+
535556
enablePartialResponse, apiErr := qapi.parsePartialResponseParam(r, qapi.enableQueryPartialResponse)
536557
if apiErr != nil {
537558
return nil, nil, apiErr
538559
}
539560

540-
q, err := qapi.queryableCreate(true, nil, 0, enablePartialResponse, false).Querier(ctx, math.MinInt64, math.MaxInt64)
561+
q, err := qapi.queryableCreate(true, nil, 0, enablePartialResponse, false).
562+
Querier(ctx, timestamp.FromTime(start), timestamp.FromTime(end))
541563
if err != nil {
542564
return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}
543565
}

pkg/promclient/promclient.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -587,9 +587,14 @@ func (c *Client) SeriesInGRPC(ctx context.Context, base *url.URL, matchers []sto
587587

588588
// LabelNames returns all known label names. It uses gRPC errors.
589589
// NOTE: This method is tested in pkg/store/prometheus_test.go against Prometheus.
590-
func (c *Client) LabelNamesInGRPC(ctx context.Context, base *url.URL) ([]string, error) {
590+
func (c *Client) LabelNamesInGRPC(ctx context.Context, base *url.URL, startTime, endTime int64) ([]string, error) {
591591
u := *base
592592
u.Path = path.Join(u.Path, "/api/v1/labels")
593+
q := u.Query()
594+
595+
q.Add("start", formatTime(timestamp.Time(startTime)))
596+
q.Add("end", formatTime(timestamp.Time(endTime)))
597+
u.RawQuery = q.Encode()
593598

594599
var m struct {
595600
Data []string `json:"data"`
@@ -599,9 +604,14 @@ func (c *Client) LabelNamesInGRPC(ctx context.Context, base *url.URL) ([]string,
599604

600605
// LabelValuesInGRPC returns all known label values for a given label name. It uses gRPC errors.
601606
// NOTE: This method is tested in pkg/store/prometheus_test.go against Prometheus.
602-
func (c *Client) LabelValuesInGRPC(ctx context.Context, base *url.URL, label string) ([]string, error) {
607+
func (c *Client) LabelValuesInGRPC(ctx context.Context, base *url.URL, label string, startTime, endTime int64) ([]string, error) {
603608
u := *base
604609
u.Path = path.Join(u.Path, "/api/v1/label/", label, "/values")
610+
q := u.Query()
611+
612+
q.Add("start", formatTime(timestamp.Time(startTime)))
613+
q.Add("end", formatTime(timestamp.Time(endTime)))
614+
u.RawQuery = q.Encode()
605615

606616
var m struct {
607617
Data []string `json:"data"`

pkg/query/querier.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,12 @@ func (q *querier) LabelValues(name string) ([]string, storage.Warnings, error) {
324324
span, ctx := tracing.StartSpan(q.ctx, "querier_label_values")
325325
defer span.Finish()
326326

327-
resp, err := q.proxy.LabelValues(ctx, &storepb.LabelValuesRequest{Label: name, PartialResponseDisabled: !q.partialResponse})
327+
resp, err := q.proxy.LabelValues(ctx, &storepb.LabelValuesRequest{
328+
Label: name,
329+
PartialResponseDisabled: !q.partialResponse,
330+
Start: q.mint,
331+
End: q.maxt,
332+
})
328333
if err != nil {
329334
return nil, nil, errors.Wrap(err, "proxy LabelValues()")
330335
}
@@ -342,7 +347,11 @@ func (q *querier) LabelNames() ([]string, storage.Warnings, error) {
342347
span, ctx := tracing.StartSpan(q.ctx, "querier_label_names")
343348
defer span.Finish()
344349

345-
resp, err := q.proxy.LabelNames(ctx, &storepb.LabelNamesRequest{PartialResponseDisabled: !q.partialResponse})
350+
resp, err := q.proxy.LabelNames(ctx, &storepb.LabelNamesRequest{
351+
PartialResponseDisabled: !q.partialResponse,
352+
Start: q.mint,
353+
End: q.maxt,
354+
})
346355
if err != nil {
347356
return nil, nil, errors.Wrap(err, "proxy LabelNames()")
348357
}

pkg/store/bucket.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -1046,7 +1046,7 @@ func chunksSize(chks []storepb.AggrChunk) (size int) {
10461046
}
10471047

10481048
// LabelNames implements the storepb.StoreServer interface.
1049-
func (s *BucketStore) LabelNames(ctx context.Context, _ *storepb.LabelNamesRequest) (*storepb.LabelNamesResponse, error) {
1049+
func (s *BucketStore) LabelNames(ctx context.Context, r *storepb.LabelNamesRequest) (*storepb.LabelNamesResponse, error) {
10501050
g, gctx := errgroup.WithContext(ctx)
10511051

10521052
s.mtx.RLock()
@@ -1055,6 +1055,9 @@ func (s *BucketStore) LabelNames(ctx context.Context, _ *storepb.LabelNamesReque
10551055
var sets [][]string
10561056

10571057
for _, b := range s.blocks {
1058+
if b.meta.MinTime > r.End || b.meta.MaxTime < r.Start {
1059+
continue
1060+
}
10581061
indexr := b.indexReader(gctx)
10591062
g.Go(func() error {
10601063
defer runutil.CloseWithLogOnErr(s.logger, indexr, "label names")
@@ -1091,6 +1094,9 @@ func (s *BucketStore) LabelValues(ctx context.Context, req *storepb.LabelValuesR
10911094
var sets [][]string
10921095

10931096
for _, b := range s.blocks {
1097+
if b.meta.MinTime > req.End || b.meta.MaxTime < req.Start {
1098+
continue
1099+
}
10941100
indexr := b.indexReader(gctx)
10951101
g.Go(func() error {
10961102
defer runutil.CloseWithLogOnErr(s.logger, indexr, "label values")

pkg/store/bucket_e2e_test.go

+72-2
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,11 @@ func testBucketStore_e2e(t *testing.T, ctx context.Context, s *storeSuite) {
191191
testutil.Equals(t, s.minTime, mint)
192192
testutil.Equals(t, s.maxTime, maxt)
193193

194-
vals, err := s.store.LabelValues(ctx, &storepb.LabelValuesRequest{Label: "a"})
194+
vals, err := s.store.LabelValues(ctx, &storepb.LabelValuesRequest{
195+
Label: "a",
196+
Start: timestamp.FromTime(minTime),
197+
End: timestamp.FromTime(maxTime),
198+
})
195199
testutil.Ok(t, err)
196200
testutil.Equals(t, []string{"1", "2"}, vals.Values)
197201

@@ -381,7 +385,7 @@ func testBucketStore_e2e(t *testing.T, ctx context.Context, s *storeSuite) {
381385
MaxTime: maxt,
382386
},
383387
},
384-
// Test no-chunk option.
388+
// Test skip-chunk option.
385389
{
386390
req: &storepb.SeriesRequest{
387391
Matchers: []storepb.LabelMatcher{
@@ -599,3 +603,69 @@ func TestBucketStore_Series_ChunksLimiter_e2e(t *testing.T) {
599603
})
600604
}
601605
}
606+
607+
func TestBucketStore_LabelNames_e2e(t *testing.T) {
608+
objtesting.ForeachStore(t, func(t *testing.T, bkt objstore.Bucket) {
609+
ctx, cancel := context.WithCancel(context.Background())
610+
defer cancel()
611+
612+
dir, err := ioutil.TempDir("", "test_bucketstore_label_names_e2e")
613+
testutil.Ok(t, err)
614+
defer func() { testutil.Ok(t, os.RemoveAll(dir)) }()
615+
616+
s := prepareStoreWithTestBlocks(t, dir, bkt, false, 0, emptyRelabelConfig, allowAllFilterConf)
617+
618+
mint, maxt := s.store.TimeRange()
619+
testutil.Equals(t, s.minTime, mint)
620+
testutil.Equals(t, s.maxTime, maxt)
621+
622+
vals, err := s.store.LabelNames(ctx, &storepb.LabelNamesRequest{
623+
Start: timestamp.FromTime(minTime),
624+
End: timestamp.FromTime(maxTime),
625+
})
626+
testutil.Ok(t, err)
627+
testutil.Equals(t, []string{"a", "b", "c"}, vals)
628+
629+
// Outside the time range.
630+
vals, err = s.store.LabelNames(ctx, &storepb.LabelNamesRequest{
631+
Start: timestamp.FromTime(time.Now().Add(-24 * time.Hour)),
632+
End: timestamp.FromTime(time.Now().Add(-23 * time.Hour)),
633+
})
634+
testutil.Ok(t, err)
635+
testutil.Equals(t, []string(nil), vals)
636+
})
637+
}
638+
639+
func TestBucketStore_LabelValues_e2e(t *testing.T) {
640+
objtesting.ForeachStore(t, func(t *testing.T, bkt objstore.Bucket) {
641+
ctx, cancel := context.WithCancel(context.Background())
642+
defer cancel()
643+
644+
dir, err := ioutil.TempDir("", "test_bucketstore_label_values_e2e")
645+
testutil.Ok(t, err)
646+
defer func() { testutil.Ok(t, os.RemoveAll(dir)) }()
647+
648+
s := prepareStoreWithTestBlocks(t, dir, bkt, false, 0, emptyRelabelConfig, allowAllFilterConf)
649+
650+
mint, maxt := s.store.TimeRange()
651+
testutil.Equals(t, s.minTime, mint)
652+
testutil.Equals(t, s.maxTime, maxt)
653+
654+
vals, err := s.store.LabelValues(ctx, &storepb.LabelValuesRequest{
655+
Label: "a",
656+
Start: timestamp.FromTime(minTime),
657+
End: timestamp.FromTime(maxTime),
658+
})
659+
testutil.Ok(t, err)
660+
testutil.Equals(t, []string{"1", "2"}, vals.Values)
661+
662+
// Outside the time range.
663+
vals, err = s.store.LabelValues(ctx, &storepb.LabelValuesRequest{
664+
Label: "a",
665+
Start: timestamp.FromTime(time.Now().Add(-24 * time.Hour)),
666+
End: timestamp.FromTime(time.Now().Add(-23 * time.Hour)),
667+
})
668+
testutil.Ok(t, err)
669+
testutil.Equals(t, []string(nil), vals.Values)
670+
})
671+
}

pkg/store/prometheus.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -519,8 +519,8 @@ Outer:
519519
}
520520

521521
// LabelNames returns all known label names.
522-
func (p *PrometheusStore) LabelNames(ctx context.Context, _ *storepb.LabelNamesRequest) (*storepb.LabelNamesResponse, error) {
523-
lbls, err := p.client.LabelNamesInGRPC(ctx, p.base)
522+
func (p *PrometheusStore) LabelNames(ctx context.Context, r *storepb.LabelNamesRequest) (*storepb.LabelNamesResponse, error) {
523+
lbls, err := p.client.LabelNamesInGRPC(ctx, p.base, r.Start, r.End)
524524
if err != nil {
525525
return nil, err
526526
}
@@ -536,7 +536,7 @@ func (p *PrometheusStore) LabelValues(ctx context.Context, r *storepb.LabelValue
536536
return &storepb.LabelValuesResponse{Values: []string{l}}, nil
537537
}
538538

539-
vals, err := p.client.LabelValuesInGRPC(ctx, p.base, r.Label)
539+
vals, err := p.client.LabelValuesInGRPC(ctx, p.base, r.Label, r.Start, r.End)
540540
if err != nil {
541541
return nil, err
542542
}

0 commit comments

Comments
 (0)