Skip to content

Commit 3bfd96f

Browse files
committed
add time range parameters to labels API
Signed-off-by: Ben Ye <[email protected]>
1 parent 76a6361 commit 3bfd96f

20 files changed

+622
-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

+51-30
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,9 @@ func (qapi *QueryAPI) parsePartialResponseParam(r *http.Request, defaultEnablePa
224224
}
225225

226226
func (qapi *QueryAPI) query(r *http.Request) (interface{}, []error, *api.ApiError) {
227-
var ts time.Time
228-
if t := r.FormValue("time"); t != "" {
229-
var err error
230-
ts, err = parseTime(t)
231-
if err != nil {
232-
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
233-
}
234-
} else {
235-
ts = qapi.baseAPI.Now()
227+
ts, err := parseTimeParam(r, "time", qapi.baseAPI.Now())
228+
if err != nil {
229+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
236230
}
237231

238232
ctx := r.Context()
@@ -425,12 +419,27 @@ func (qapi *QueryAPI) labelValues(r *http.Request) (interface{}, []error, *api.A
425419
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("invalid label name: %q", name)}
426420
}
427421

422+
start, err := parseTimeParam(r, "start", minTime)
423+
if err != nil {
424+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
425+
}
426+
end, err := parseTimeParam(r, "end", maxTime)
427+
if err != nil {
428+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
429+
}
430+
// Prometheus doesn't check this, do we need to?
431+
if end.Before(start) {
432+
err := errors.New("end timestamp must not be before start time")
433+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
434+
}
435+
428436
enablePartialResponse, apiErr := qapi.parsePartialResponseParam(r, qapi.enableQueryPartialResponse)
429437
if apiErr != nil {
430438
return nil, nil, apiErr
431439
}
432440

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

467-
var start time.Time
468-
if t := r.FormValue("start"); t != "" {
469-
var err error
470-
start, err = parseTime(t)
471-
if err != nil {
472-
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
473-
}
474-
} else {
475-
start = minTime
476+
start, err := parseTimeParam(r, "start", minTime)
477+
if err != nil {
478+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
476479
}
477-
478-
var end time.Time
479-
if t := r.FormValue("end"); t != "" {
480-
var err error
481-
end, err = parseTime(t)
482-
if err != nil {
483-
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
484-
}
485-
} else {
486-
end = maxTime
480+
end, err := parseTimeParam(r, "end", maxTime)
481+
if err != nil {
482+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
487483
}
488484

489485
var matcherSets [][]*labels.Matcher
@@ -540,6 +536,18 @@ func (qapi *QueryAPI) series(r *http.Request) (interface{}, []error, *api.ApiErr
540536
return metrics, set.Warnings(), nil
541537
}
542538

539+
func parseTimeParam(r *http.Request, paramName string, defaultValue time.Time) (time.Time, error) {
540+
val := r.FormValue(paramName)
541+
if val == "" {
542+
return defaultValue, nil
543+
}
544+
result, err := parseTime(val)
545+
if err != nil {
546+
return time.Time{}, errors.Wrapf(err, "Invalid time value for '%s'", paramName)
547+
}
548+
return result, nil
549+
}
550+
543551
func parseTime(s string) (time.Time, error) {
544552
if t, err := strconv.ParseFloat(s, 64); err == nil {
545553
s, ns := math.Modf(t)
@@ -568,6 +576,19 @@ func parseDuration(s string) (time.Duration, error) {
568576
func (qapi *QueryAPI) labelNames(r *http.Request) (interface{}, []error, *api.ApiError) {
569577
ctx := r.Context()
570578

579+
start, err := parseTimeParam(r, "start", minTime)
580+
if err != nil {
581+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
582+
}
583+
end, err := parseTimeParam(r, "end", maxTime)
584+
if err != nil {
585+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
586+
}
587+
if end.Before(start) {
588+
err := errors.New("end timestamp must not be before start time")
589+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: err}
590+
}
591+
571592
enablePartialResponse, apiErr := qapi.parsePartialResponseParam(r, qapi.enableQueryPartialResponse)
572593
if apiErr != nil {
573594
return nil, nil, apiErr
@@ -578,7 +599,7 @@ func (qapi *QueryAPI) labelNames(r *http.Request) (interface{}, []error, *api.Ap
578599
return nil, nil, apiErr
579600
}
580601

581-
q, err := qapi.queryableCreate(true, nil, storeMatchers, 0, enablePartialResponse, false).Querier(ctx, math.MinInt64, math.MaxInt64)
602+
q, err := qapi.queryableCreate(true, nil, storeMatchers, 0, enablePartialResponse, false).Querier(ctx, timestamp.FromTime(start), timestamp.FromTime(end))
582603
if err != nil {
583604
return nil, nil, &api.ApiError{Typ: api.ErrorExec, Err: err}
584605
}

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
@@ -333,7 +333,12 @@ func (q *querier) LabelValues(name string) ([]string, storage.Warnings, error) {
333333
span, ctx := tracing.StartSpan(q.ctx, "querier_label_values")
334334
defer span.Finish()
335335

336-
resp, err := q.proxy.LabelValues(ctx, &storepb.LabelValuesRequest{Label: name, PartialResponseDisabled: !q.partialResponse})
336+
resp, err := q.proxy.LabelValues(ctx, &storepb.LabelValuesRequest{
337+
Label: name,
338+
PartialResponseDisabled: !q.partialResponse,
339+
Start: q.mint,
340+
End: q.maxt,
341+
})
337342
if err != nil {
338343
return nil, nil, errors.Wrap(err, "proxy LabelValues()")
339344
}
@@ -351,7 +356,11 @@ func (q *querier) LabelNames() ([]string, storage.Warnings, error) {
351356
span, ctx := tracing.StartSpan(q.ctx, "querier_label_names")
352357
defer span.Finish()
353358

354-
resp, err := q.proxy.LabelNames(ctx, &storepb.LabelNamesRequest{PartialResponseDisabled: !q.partialResponse})
359+
resp, err := q.proxy.LabelNames(ctx, &storepb.LabelNamesRequest{
360+
PartialResponseDisabled: !q.partialResponse,
361+
Start: q.mint,
362+
End: q.maxt,
363+
})
355364
if err != nil {
356365
return nil, nil, errors.Wrap(err, "proxy LabelNames()")
357366
}

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.Names)
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.Names)
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)