mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-23 12:31:07 +01:00
app/vmselect: add ability to pass match[]
, start
and end
to /api/v1/labels
This makes the `/api/v1/labels` handler consistent with already existing functionality for `/api/v1/label/.../values`. See https://github.com/prometheus/prometheus/issues/6178 for more details.
This commit is contained in:
parent
02f6566ce1
commit
96ff8d9adb
12
README.md
12
README.md
@ -405,6 +405,18 @@ VictoriaMetrics supports the following handlers from [Prometheus querying API](h
|
|||||||
|
|
||||||
These handlers can be queried from Prometheus-compatible clients such as Grafana or curl.
|
These handlers can be queried from Prometheus-compatible clients such as Grafana or curl.
|
||||||
|
|
||||||
|
VictoriaMetrics accepts additional args for `/api/v1/labels` and `/api/v1/label/.../values` handlers.
|
||||||
|
See [this feature request](https://github.com/prometheus/prometheus/issues/6178) for details:
|
||||||
|
|
||||||
|
* Any number [time series selectors](https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors) via `match[]` query arg.
|
||||||
|
* Optional `start` and `end` query args for limiting the time range for the selected labels or label values.
|
||||||
|
|
||||||
|
Additionally VictoriaMetrics provides the following handlers:
|
||||||
|
|
||||||
|
* `/api/v1/series/count` - it returns the total number of time series in the database. Note that this handler scans all the inverted index,
|
||||||
|
so it can be slow if the database contains tens of millions of time series.
|
||||||
|
* `/api/v1/labels/count` - it returns a list of `label: values_count` entries. It can be used for determining labels with the maximum number of values.
|
||||||
|
|
||||||
|
|
||||||
### How to build from sources
|
### How to build from sources
|
||||||
|
|
||||||
|
@ -432,13 +432,10 @@ func GetLabelEntries(deadline Deadline) ([]storage.TagEntry, error) {
|
|||||||
// Sort labelEntries by the number of label values in each entry.
|
// Sort labelEntries by the number of label values in each entry.
|
||||||
sort.Slice(labelEntries, func(i, j int) bool {
|
sort.Slice(labelEntries, func(i, j int) bool {
|
||||||
a, b := labelEntries[i].Values, labelEntries[j].Values
|
a, b := labelEntries[i].Values, labelEntries[j].Values
|
||||||
if len(a) < len(b) {
|
if len(a) != len(b) {
|
||||||
return true
|
return len(a) > len(b)
|
||||||
}
|
}
|
||||||
if len(a) > len(b) {
|
return labelEntries[i].Key > labelEntries[j].Key
|
||||||
return false
|
|
||||||
}
|
|
||||||
return labelEntries[i].Key < labelEntries[j].Key
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return labelEntries, nil
|
return labelEntries, nil
|
||||||
|
@ -285,6 +285,13 @@ func labelValuesWithMatches(labelName string, matches []string, start, end int64
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for i, tfs := range tagFilterss {
|
||||||
|
// Add `labelName!=''` tag filter in order to filter out series without the labelName.
|
||||||
|
tagFilterss[i] = append(tfs, storage.TagFilter{
|
||||||
|
Key: []byte(labelName),
|
||||||
|
IsNegative: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
if start >= end {
|
if start >= end {
|
||||||
end = start + defaultStep
|
end = start + defaultStep
|
||||||
}
|
}
|
||||||
@ -346,9 +353,37 @@ var labelsCountDuration = metrics.NewSummary(`vm_request_duration_seconds{path="
|
|||||||
func LabelsHandler(w http.ResponseWriter, r *http.Request) error {
|
func LabelsHandler(w http.ResponseWriter, r *http.Request) error {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
deadline := getDeadline(r)
|
deadline := getDeadline(r)
|
||||||
labels, err := netstorage.GetLabels(deadline)
|
|
||||||
if err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
return fmt.Errorf("cannot obtain labels: %s", err)
|
return fmt.Errorf("cannot parse form values: %s", err)
|
||||||
|
}
|
||||||
|
var labels []string
|
||||||
|
if len(r.Form["match[]"]) == 0 && len(r.Form["start"]) == 0 && len(r.Form["end"]) == 0 {
|
||||||
|
var err error
|
||||||
|
labels, err = netstorage.GetLabels(deadline)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot obtain labels: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Extended functionality that allows filtering by label filters and time range
|
||||||
|
// i.e. /api/v1/labels?match[]=foobar{baz="abc"}&start=...&end=...
|
||||||
|
matches := r.Form["match[]"]
|
||||||
|
if len(matches) == 0 {
|
||||||
|
matches = []string{"{__name__!=''}"}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
labels, err = labelsWithMatches(matches, start, end, deadline)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot obtain labels for match[]=%q, start=%d, end=%d: %s", matches, start, end, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@ -357,6 +392,51 @@ func LabelsHandler(w http.ResponseWriter, r *http.Request) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func labelsWithMatches(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 {
|
||||||
|
end = start + 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) {
|
||||||
|
mLock.Lock()
|
||||||
|
tags := rs.MetricName.Tags
|
||||||
|
for i := range tags {
|
||||||
|
t := &tags[i]
|
||||||
|
m[string(t.Key)] = struct{}{}
|
||||||
|
}
|
||||||
|
m["__name__"] = struct{}{}
|
||||||
|
mLock.Unlock()
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error when data fetching: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := make([]string, 0, len(m))
|
||||||
|
for label := range m {
|
||||||
|
labels = append(labels, label)
|
||||||
|
}
|
||||||
|
sort.Strings(labels)
|
||||||
|
return labels, nil
|
||||||
|
}
|
||||||
|
|
||||||
var labelsDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/labels"}`)
|
var labelsDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/api/v1/labels"}`)
|
||||||
|
|
||||||
// SeriesCountHandler processes /api/v1/series/count request.
|
// SeriesCountHandler processes /api/v1/series/count request.
|
||||||
|
Loading…
Reference in New Issue
Block a user