mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-20 15:16:42 +00:00
app/vmselect: allow passing match[]
, start
and time
to /api/v1/label/<label_name>/values
`/api/v1/label/<label_name>/values?match[]=q` emulates emulates `label_values(q, <label_name>)` call in Grafana templating.
This commit is contained in:
parent
47e4b50112
commit
99e048c9df
1 changed files with 80 additions and 3 deletions
|
@ -6,12 +6,15 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
|
||||||
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||||
"github.com/VictoriaMetrics/metrics"
|
"github.com/VictoriaMetrics/metrics"
|
||||||
"github.com/valyala/quicktemplate"
|
"github.com/valyala/quicktemplate"
|
||||||
|
@ -230,9 +233,39 @@ var deleteDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/
|
||||||
func LabelValuesHandler(labelName string, w http.ResponseWriter, r *http.Request) error {
|
func LabelValuesHandler(labelName string, w http.ResponseWriter, r *http.Request) error {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
deadline := getDeadline(r)
|
deadline := getDeadline(r)
|
||||||
labelValues, err := netstorage.GetLabelValues(labelName, deadline)
|
|
||||||
if err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
return fmt.Errorf(`cannot obtain label values for %q: %s`, labelName, err)
|
return fmt.Errorf("cannot parse form values: %s", err)
|
||||||
|
}
|
||||||
|
var labelValues []string
|
||||||
|
if len(r.Form["match[]"]) == 0 && len(r.Form["start"]) == 0 && len(r.Form["end"]) == 0 {
|
||||||
|
var err error
|
||||||
|
labelValues, err = netstorage.GetLabelValues(labelName, deadline)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(`cannot obtain label values for %q: %s`, labelName, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Extended functionality that allows filtering by label filters and time range
|
||||||
|
// i.e. /api/v1/label/foo/values?match[]=foobar{baz="abc"}&start=...&end=...
|
||||||
|
// is equivalent to `label_values(foobar{baz="abc"}, foo)` call on the selected
|
||||||
|
// time range in Grafana templating.
|
||||||
|
matches := r.Form["match[]"]
|
||||||
|
if len(matches) == 0 {
|
||||||
|
matches = []string{fmt.Sprintf("{%s!=''}", labelName)}
|
||||||
|
}
|
||||||
|
ct := currentTime()
|
||||||
|
end, err := getTime(r, "end", ct)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
start, err := getTime(r, "start", end-defaultStep)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
labelValues, err = labelValuesWithMatches(labelName, matches, start, end, deadline)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot obtain label values for %q, match[]=%q, start=%d, end=%d: %s", labelName, matches, start, end, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
@ -241,6 +274,50 @@ func LabelValuesHandler(labelName string, w http.ResponseWriter, r *http.Request
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func labelValuesWithMatches(labelName string, matches []string, start, end int64, deadline netstorage.Deadline) ([]string, error) {
|
||||||
|
if len(matches) == 0 {
|
||||||
|
logger.Panicf("BUG: matches must be non-empty")
|
||||||
|
}
|
||||||
|
tagFilterss, err := getTagFilterssFromMatches(matches)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if start >= end {
|
||||||
|
start = end - defaultStep
|
||||||
|
}
|
||||||
|
sq := &storage.SearchQuery{
|
||||||
|
MinTimestamp: start,
|
||||||
|
MaxTimestamp: end,
|
||||||
|
TagFilterss: tagFilterss,
|
||||||
|
}
|
||||||
|
rss, err := netstorage.ProcessSearchQuery(sq, false, deadline)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot fetch data for %q: %s", sq, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := make(map[string]struct{})
|
||||||
|
var mLock sync.Mutex
|
||||||
|
err = rss.RunParallel(func(rs *netstorage.Result, workerID uint) {
|
||||||
|
labelValue := rs.MetricName.GetTagValue(labelName)
|
||||||
|
if len(labelValue) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mLock.Lock()
|
||||||
|
m[string(labelValue)] = struct{}{}
|
||||||
|
mLock.Unlock()
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error when data fetching: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
labelValues := make([]string, 0, len(m))
|
||||||
|
for labelValue := range m {
|
||||||
|
labelValues = append(labelValues, labelValue)
|
||||||
|
}
|
||||||
|
sort.Strings(labelValues)
|
||||||
|
return labelValues, nil
|
||||||
|
}
|
||||||
|
|
||||||
var labelValuesDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/label/{}/values"}`)
|
var labelValuesDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/label/{}/values"}`)
|
||||||
|
|
||||||
// LabelsCountHandler processes /api/v1/labels/count request.
|
// LabelsCountHandler processes /api/v1/labels/count request.
|
||||||
|
|
Loading…
Reference in a new issue