Added time range parameters to labelNames API (#7288)

* add time range params to labelNames api

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* evaluate min/max time range when reading labels from the head

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* add time range params to labelValues api

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* fix test, add docs

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* add a test for head min max range

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* fix test to match comment

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* address CR comments

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* combine vars only used once

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* add time range params to labelNames api

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* evaluate min/max time range when reading labels from the head

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* add time range params to labelValues api

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* fix test, add docs

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* add a test for head min max range

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* fix test to match comment

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* address CR comments

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* combine vars only used once

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* fix test

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* restart ci

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>

* use range expectedLabelNames instead of range actualLabelNames in test

Signed-off-by: jessicagreben <Jessica.greben1+github@gmail.com>
This commit is contained in:
Jess G 2020-05-30 05:50:09 -07:00 committed by GitHub
parent 2209fa98b4
commit fdc49fae5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 316 additions and 7 deletions

View file

@ -268,6 +268,12 @@ GET /api/v1/labels
POST /api/v1/labels
```
URL query parameters:
- `start=<rfc3339 | unix_timestamp>`: Start timestamp. Optional.
- `end=<rfc3339 | unix_timestamp>`: End timestamp. Optional.
The `data` section of the JSON response is a list of string label names.
Here is an example.
@ -310,6 +316,12 @@ The following endpoint returns a list of label values for a provided label name:
GET /api/v1/label/<label_name>/values
```
URL query parameters:
- `start=<rfc3339 | unix_timestamp>`: Start timestamp. Optional.
- `end=<rfc3339 | unix_timestamp>`: End timestamp. Optional.
The `data` section of the JSON response is a list of string label values.
This example queries for all label values for the `job` label:

View file

@ -1539,9 +1539,16 @@ func (h *headIndexReader) Symbols() index.StringIter {
return index.NewStringListIter(res)
}
// LabelValues returns the possible label values
// LabelValues returns label values present in the head for the
// specific label name that are within the time range mint to maxt.
func (h *headIndexReader) LabelValues(name string) ([]string, error) {
h.head.symMtx.RLock()
if h.maxt < h.head.MinTime() || h.mint > h.head.MaxTime() {
h.head.symMtx.RUnlock()
return []string{}, nil
}
sl := make([]string, 0, len(h.head.values[name]))
for s := range h.head.values[name] {
sl = append(sl, s)
@ -1551,10 +1558,16 @@ func (h *headIndexReader) LabelValues(name string) ([]string, error) {
return sl, nil
}
// LabelNames returns all the unique label names present in the head.
// LabelNames returns all the unique label names present in the head
// that are within the time range mint to maxt.
func (h *headIndexReader) LabelNames() ([]string, error) {
h.head.symMtx.RLock()
defer h.head.symMtx.RUnlock()
if h.maxt < h.head.MinTime() || h.mint > h.head.MaxTime() {
return []string{}, nil
}
labelNames := make([]string, 0, len(h.head.values))
for name := range h.head.values {
if name == "" {

View file

@ -1811,3 +1811,63 @@ func newTestHead(t testing.TB, chunkRange int64, compressWAL bool) (*Head, *wal.
testutil.Ok(t, os.RemoveAll(dir))
}
}
func TestHeadLabelNamesValuesWithMinMaxRange(t *testing.T) {
head, _, closer := newTestHead(t, 1000, false)
defer closer()
defer func() {
testutil.Ok(t, head.Close())
}()
const (
firstSeriesTimestamp int64 = 100
secondSeriesTimestamp int64 = 200
lastSeriesTimestamp int64 = 300
)
var (
seriesTimestamps = []int64{firstSeriesTimestamp,
secondSeriesTimestamp,
lastSeriesTimestamp,
}
expectedLabelNames = []string{"a", "b", "c"}
expectedLabelValues = []string{"d", "e", "f"}
)
app := head.Appender()
for i, name := range expectedLabelNames {
_, err := app.Add(labels.Labels{{Name: name, Value: expectedLabelValues[i]}}, seriesTimestamps[i], 0)
testutil.Ok(t, err)
}
testutil.Ok(t, app.Commit())
testutil.Equals(t, head.MinTime(), firstSeriesTimestamp)
testutil.Equals(t, head.MaxTime(), lastSeriesTimestamp)
var testCases = []struct {
name string
mint int64
maxt int64
expectedNames []string
expectedValues []string
}{
{"maxt less than head min", head.MaxTime() - 10, head.MinTime() - 10, []string{}, []string{}},
{"mint less than head max", head.MaxTime() + 10, head.MinTime() + 10, []string{}, []string{}},
{"mint and maxt outside head", head.MaxTime() + 10, head.MinTime() - 10, []string{}, []string{}},
{"mint and maxt within head", head.MaxTime() - 10, head.MinTime() + 10, expectedLabelNames, expectedLabelValues},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
headIdxReader := head.indexRange(tt.mint, tt.maxt)
actualLabelNames, err := headIdxReader.LabelNames()
testutil.Ok(t, err)
testutil.Equals(t, tt.expectedNames, actualLabelNames)
if len(tt.expectedValues) > 0 {
for i, name := range expectedLabelNames {
actualLabelValue, err := headIdxReader.LabelValues(name)
testutil.Ok(t, err)
testutil.Equals(t, []string{tt.expectedValues[i]}, actualLabelValue)
}
}
})
}
}

View file

@ -64,7 +64,7 @@ func (q *querier) LabelNames() ([]string, storage.Warnings, error) {
func (q *querier) lvals(qs []storage.Querier, n string) ([]string, storage.Warnings, error) {
if len(qs) == 0 {
return nil, nil, nil
return []string{}, nil, nil
}
if len(qs) == 1 {
return qs[0].LabelValues(n)
@ -75,12 +75,12 @@ func (q *querier) lvals(qs []storage.Querier, n string) ([]string, storage.Warni
s1, w, err := q.lvals(qs[:l], n)
ws = append(ws, w...)
if err != nil {
return nil, ws, err
return []string{}, ws, err
}
s2, ws, err := q.lvals(qs[l:], n)
ws = append(ws, w...)
if err != nil {
return nil, ws, err
return []string{}, ws, err
}
return mergeStrings(s1, s2), ws, nil
}

View file

@ -482,7 +482,16 @@ func returnAPIError(err error) *apiError {
}
func (api *API) labelNames(r *http.Request) apiFuncResult {
q, err := api.Queryable.Querier(r.Context(), math.MinInt64, math.MaxInt64)
start, err := parseTimeParam(r, "start", minTime)
if err != nil {
return apiFuncResult{nil, &apiError{errorBadData, errors.Wrap(err, "invalid parameter 'start'")}, nil, nil}
}
end, err := parseTimeParam(r, "end", maxTime)
if err != nil {
return apiFuncResult{nil, &apiError{errorBadData, errors.Wrap(err, "invalid parameter 'end'")}, nil, nil}
}
q, err := api.Queryable.Querier(r.Context(), timestamp.FromTime(start), timestamp.FromTime(end))
if err != nil {
return apiFuncResult{nil, &apiError{errorExec, err}, nil, nil}
}
@ -502,7 +511,17 @@ func (api *API) labelValues(r *http.Request) (result apiFuncResult) {
if !model.LabelNameRE.MatchString(name) {
return apiFuncResult{nil, &apiError{errorBadData, errors.Errorf("invalid label name: %q", name)}, nil, nil}
}
q, err := api.Queryable.Querier(ctx, math.MinInt64, math.MaxInt64)
start, err := parseTimeParam(r, "start", minTime)
if err != nil {
return apiFuncResult{nil, &apiError{errorBadData, errors.Wrap(err, "invalid parameter 'start'")}, nil, nil}
}
end, err := parseTimeParam(r, "end", maxTime)
if err != nil {
return apiFuncResult{nil, &apiError{errorBadData, errors.Wrap(err, "invalid parameter 'end'")}, nil, nil}
}
q, err := api.Queryable.Querier(r.Context(), timestamp.FromTime(start), timestamp.FromTime(end))
if err != nil {
return apiFuncResult{nil, &apiError{errorExec, err}, nil, nil}
}

View file

@ -1474,11 +1474,216 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, testLabelAPI
},
errType: errorBadData,
},
// Start and end before LabelValues starts.
{
endpoint: api.labelValues,
params: map[string]string{
"name": "foo",
},
query: url.Values{
"start": []string{"-2"},
"end": []string{"-1"},
},
response: []string{},
},
// Start and end within LabelValues.
{
endpoint: api.labelValues,
params: map[string]string{
"name": "foo",
},
query: url.Values{
"start": []string{"1"},
"end": []string{"100"},
},
response: []string{
"bar",
"boo",
},
},
// Start before LabelValues, end within LabelValues.
{
endpoint: api.labelValues,
params: map[string]string{
"name": "foo",
},
query: url.Values{
"start": []string{"-1"},
"end": []string{"3"},
},
response: []string{
"bar",
"boo",
},
},
// Start before LabelValues starts, end after LabelValues ends.
{
endpoint: api.labelValues,
params: map[string]string{
"name": "foo",
},
query: url.Values{
"start": []string{"1969-12-31T00:00:00Z"},
"end": []string{"1970-02-01T00:02:03Z"},
},
response: []string{
"bar",
"boo",
},
},
// Start with bad data, end within LabelValues.
{
endpoint: api.labelValues,
params: map[string]string{
"name": "foo",
},
query: url.Values{
"start": []string{"boop"},
"end": []string{"1"},
},
errType: errorBadData,
},
// Start within LabelValues, end after.
{
endpoint: api.labelValues,
params: map[string]string{
"name": "foo",
},
query: url.Values{
"start": []string{"1"},
"end": []string{"100000000"},
},
response: []string{
"bar",
"boo",
},
},
// Start and end after LabelValues ends.
{
endpoint: api.labelValues,
params: map[string]string{
"name": "foo",
},
query: url.Values{
"start": []string{"148966367200.372"},
"end": []string{"148966367200.972"},
},
response: []string{},
},
// Only provide Start within LabelValues, don't provide an end time.
{
endpoint: api.labelValues,
params: map[string]string{
"name": "foo",
},
query: url.Values{
"start": []string{"2"},
},
response: []string{
"bar",
"boo",
},
},
// Only provide end within LabelValues, don't provide a start time.
{
endpoint: api.labelValues,
params: map[string]string{
"name": "foo",
},
query: url.Values{
"end": []string{"100"},
},
response: []string{
"bar",
"boo",
},
},
// Label names.
{
endpoint: api.labelNames,
response: []string{"__name__", "foo"},
},
// Start and end before Label names starts.
{
endpoint: api.labelNames,
query: url.Values{
"start": []string{"-2"},
"end": []string{"-1"},
},
response: []string{},
},
// Start and end within Label names.
{
endpoint: api.labelNames,
query: url.Values{
"start": []string{"1"},
"end": []string{"100"},
},
response: []string{"__name__", "foo"},
},
// Start before Label names, end within Label names.
{
endpoint: api.labelNames,
query: url.Values{
"start": []string{"-1"},
"end": []string{"10"},
},
response: []string{"__name__", "foo"},
},
// Start before Label names starts, end after Label names ends.
{
endpoint: api.labelNames,
query: url.Values{
"start": []string{"-1"},
"end": []string{"100000"},
},
response: []string{"__name__", "foo"},
},
// Start with bad data for Label names, end within Label names.
{
endpoint: api.labelNames,
query: url.Values{
"start": []string{"boop"},
"end": []string{"1"},
},
errType: errorBadData,
},
// Start within Label names, end after.
{
endpoint: api.labelNames,
query: url.Values{
"start": []string{"1"},
"end": []string{"1000000006"},
},
response: []string{"__name__", "foo"},
},
// Start and end after Label names ends.
{
endpoint: api.labelNames,
query: url.Values{
"start": []string{"148966367200.372"},
"end": []string{"148966367200.972"},
},
response: []string{},
},
// Only provide Start within Label names, don't provide an end time.
{
endpoint: api.labelNames,
query: url.Values{
"start": []string{"4"},
},
response: []string{"__name__", "foo"},
},
// Only provide End within Label names, don't provide a start time.
{
endpoint: api.labelNames,
query: url.Values{
"end": []string{"20"},
},
response: []string{"__name__", "foo"},
},
}...)
}