mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -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
|
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.
|
The `data` section of the JSON response is a list of string label names.
|
||||||
|
|
||||||
Here is an example.
|
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
|
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.
|
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:
|
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)
|
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) {
|
func (h *headIndexReader) LabelValues(name string) ([]string, error) {
|
||||||
h.head.symMtx.RLock()
|
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]))
|
sl := make([]string, 0, len(h.head.values[name]))
|
||||||
for s := range h.head.values[name] {
|
for s := range h.head.values[name] {
|
||||||
sl = append(sl, s)
|
sl = append(sl, s)
|
||||||
|
@ -1551,10 +1558,16 @@ func (h *headIndexReader) LabelValues(name string) ([]string, error) {
|
||||||
return sl, nil
|
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) {
|
func (h *headIndexReader) LabelNames() ([]string, error) {
|
||||||
h.head.symMtx.RLock()
|
h.head.symMtx.RLock()
|
||||||
defer h.head.symMtx.RUnlock()
|
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))
|
labelNames := make([]string, 0, len(h.head.values))
|
||||||
for name := range h.head.values {
|
for name := range h.head.values {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
|
|
|
@ -1811,3 +1811,63 @@ func newTestHead(t testing.TB, chunkRange int64, compressWAL bool) (*Head, *wal.
|
||||||
testutil.Ok(t, os.RemoveAll(dir))
|
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) {
|
func (q *querier) lvals(qs []storage.Querier, n string) ([]string, storage.Warnings, error) {
|
||||||
if len(qs) == 0 {
|
if len(qs) == 0 {
|
||||||
return nil, nil, nil
|
return []string{}, nil, nil
|
||||||
}
|
}
|
||||||
if len(qs) == 1 {
|
if len(qs) == 1 {
|
||||||
return qs[0].LabelValues(n)
|
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)
|
s1, w, err := q.lvals(qs[:l], n)
|
||||||
ws = append(ws, w...)
|
ws = append(ws, w...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ws, err
|
return []string{}, ws, err
|
||||||
}
|
}
|
||||||
s2, ws, err := q.lvals(qs[l:], n)
|
s2, ws, err := q.lvals(qs[l:], n)
|
||||||
ws = append(ws, w...)
|
ws = append(ws, w...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ws, err
|
return []string{}, ws, err
|
||||||
}
|
}
|
||||||
return mergeStrings(s1, s2), ws, nil
|
return mergeStrings(s1, s2), ws, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -482,7 +482,16 @@ func returnAPIError(err error) *apiError {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) labelNames(r *http.Request) apiFuncResult {
|
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 {
|
if err != nil {
|
||||||
return apiFuncResult{nil, &apiError{errorExec, err}, nil, 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) {
|
if !model.LabelNameRE.MatchString(name) {
|
||||||
return apiFuncResult{nil, &apiError{errorBadData, errors.Errorf("invalid label name: %q", name)}, nil, nil}
|
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 {
|
if err != nil {
|
||||||
return apiFuncResult{nil, &apiError{errorExec, err}, nil, 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,
|
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.
|
// Label names.
|
||||||
{
|
{
|
||||||
endpoint: api.labelNames,
|
endpoint: api.labelNames,
|
||||||
response: []string{"__name__", "foo"},
|
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