mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-24 21:24:05 -08:00
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:
parent
2209fa98b4
commit
fdc49fae5b
|
@ -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:
|
||||
|
|
17
tsdb/head.go
17
tsdb/head.go
|
@ -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 == "" {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
|
|
@ -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"},
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue