Prometheus: Add resource for suggestions that include scopes/adhoc filters (#94001)
Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> Co-authored-by: Bogdan Matei <bogdan.matei@grafana.com>
This commit is contained in:
co-authored by
Dominik Prokop
Bogdan Matei
parent
a20ebbc8f8
commit
2a73b89374
@@ -3,14 +3,19 @@ package resource
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data/utils/maputil"
|
||||
"github.com/prometheus/prometheus/promql/parser"
|
||||
|
||||
"github.com/grafana/grafana/pkg/promlib/client"
|
||||
"github.com/grafana/grafana/pkg/promlib/models"
|
||||
"github.com/grafana/grafana/pkg/promlib/utils"
|
||||
)
|
||||
|
||||
@@ -84,3 +89,110 @@ func (r *Resource) DetectVersion(ctx context.Context, req *backend.CallResourceR
|
||||
|
||||
return r.Execute(ctx, newReq)
|
||||
}
|
||||
|
||||
func getSelectors(expr string) ([]string, error) {
|
||||
parsed, err := parser.ParseExpr(expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selectors := make([]string, 0)
|
||||
|
||||
parser.Inspect(parsed, func(node parser.Node, nodes []parser.Node) error {
|
||||
switch v := node.(type) {
|
||||
case *parser.VectorSelector:
|
||||
for _, matcher := range v.LabelMatchers {
|
||||
if matcher == nil {
|
||||
continue
|
||||
}
|
||||
if matcher.Name == "__name__" {
|
||||
selectors = append(selectors, matcher.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return selectors, nil
|
||||
}
|
||||
|
||||
// SuggestionRequest is the request body for the GetSuggestions resource.
|
||||
type SuggestionRequest struct {
|
||||
// LabelName, if provided, will result in label values being returned for the given label name.
|
||||
LabelName string `json:"labelName"`
|
||||
|
||||
Queries []string `json:"queries"`
|
||||
|
||||
Scopes []models.ScopeFilter `json:"scopes"`
|
||||
AdhocFilters []models.ScopeFilter `json:"adhocFilters"`
|
||||
|
||||
// Start and End are proxied directly to the prometheus endpoint (which is rfc3339 | unix_timestamp)
|
||||
Start string `json:"start"`
|
||||
End string `json:"end"`
|
||||
|
||||
// Limit is the maximum number of suggestions to return and is proxied directly to the prometheus endpoint.
|
||||
Limit int64 `json:"limit"`
|
||||
}
|
||||
|
||||
// GetSuggestions takes a Suggestion Request in the body of the resource request.
|
||||
// It builds a to call prometheus' labels endpoint (or label values endpoint if labelName is provided)
|
||||
// The match parameters for the endpoints are built from metrics extracted from the queries
|
||||
// combined with the scopes and adhoc filters provided in the request.
|
||||
// Queries must be valid raw promql.
|
||||
func (r *Resource) GetSuggestions(ctx context.Context, req *backend.CallResourceRequest) (*backend.CallResourceResponse, error) {
|
||||
sugReq := SuggestionRequest{}
|
||||
err := json.Unmarshal(req.Body, &sugReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling suggestion request: %v", err)
|
||||
}
|
||||
|
||||
selectorList := []string{}
|
||||
for _, query := range sugReq.Queries {
|
||||
s, err := getSelectors(query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing selectors: %v", err)
|
||||
}
|
||||
selectorList = append(selectorList, s...)
|
||||
}
|
||||
|
||||
slices.Sort(selectorList)
|
||||
selectorList = slices.Compact(selectorList)
|
||||
|
||||
matchers, err := models.FiltersToMatchers(sugReq.Scopes, sugReq.AdhocFilters)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error converting filters to matchers: %v", err)
|
||||
}
|
||||
|
||||
values := url.Values{}
|
||||
|
||||
for _, s := range selectorList {
|
||||
vs := parser.VectorSelector{Name: s, LabelMatchers: matchers}
|
||||
values.Add("match[]", vs.String())
|
||||
}
|
||||
|
||||
if sugReq.Start != "" {
|
||||
values.Add("start", sugReq.Start)
|
||||
}
|
||||
if sugReq.End != "" {
|
||||
values.Add("end", sugReq.End)
|
||||
}
|
||||
if sugReq.Limit > 0 {
|
||||
values.Add("limit", fmt.Sprintf("%d", sugReq.Limit))
|
||||
}
|
||||
|
||||
newReq := &backend.CallResourceRequest{
|
||||
PluginContext: req.PluginContext,
|
||||
}
|
||||
|
||||
if sugReq.LabelName != "" {
|
||||
// Get label values for the given name (key)
|
||||
newReq.Path = "/api/v1/label/" + sugReq.LabelName + "/values"
|
||||
newReq.URL = "/api/v1/label/" + sugReq.LabelName + "/values?" + values.Encode()
|
||||
} else {
|
||||
// Get Label names (keys)
|
||||
newReq.Path = "/api/v1/labels"
|
||||
newReq.URL = "/api/v1/labels?" + values.Encode()
|
||||
}
|
||||
|
||||
return r.Execute(ctx, newReq)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user