grafana/pkg/tsdb/prometheus/querydata/response.go

135 lines
3.4 KiB
Go

package querydata
import (
"context"
"fmt"
"net/http"
"sort"
"strings"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/tsdb/prometheus/models"
"github.com/grafana/grafana/pkg/util/converter"
jsoniter "github.com/json-iterator/go"
)
func (s *QueryData) parseResponse(ctx context.Context, q *models.Query, res *http.Response) (*backend.DataResponse, error) {
defer func() {
if err := res.Body.Close(); err != nil {
s.log.Error("Failed to close response body", "err", err)
}
}()
iter := jsoniter.Parse(jsoniter.ConfigDefault, res.Body, 1024)
r := converter.ReadPrometheusStyleResult(iter, converter.Options{
MatrixWideSeries: s.enableWideSeries,
VectorWideSeries: s.enableWideSeries,
})
if r == nil {
return nil, fmt.Errorf("received empty response from prometheus")
}
// The ExecutedQueryString can be viewed in QueryInspector in UI
for _, frame := range r.Frames {
if s.enableWideSeries {
addMetadataToWideFrame(q, frame)
} else {
addMetadataToMultiFrame(q, frame)
}
}
return r, nil
}
func addMetadataToMultiFrame(q *models.Query, frame *data.Frame) {
if frame.Meta == nil {
frame.Meta = &data.FrameMeta{}
}
frame.Meta.ExecutedQueryString = executedQueryString(q)
if len(frame.Fields) < 2 {
return
}
frame.Name = getName(q, frame.Fields[1])
frame.Fields[0].Config = &data.FieldConfig{Interval: float64(q.Step.Milliseconds())}
if frame.Name != "" {
frame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: frame.Name}
}
}
func addMetadataToWideFrame(q *models.Query, frame *data.Frame) {
if frame.Meta == nil {
frame.Meta = &data.FrameMeta{}
}
frame.Meta.ExecutedQueryString = executedQueryString(q)
if len(frame.Fields) < 2 {
return
}
frame.Fields[0].Config = &data.FieldConfig{Interval: float64(q.Step.Milliseconds())}
for _, f := range frame.Fields {
if f.Name != data.TimeSeriesTimeFieldName {
f.Name = getName(q, f)
}
}
}
// this is based on the logic from the String() function in github.com/prometheus/common/model.go
func metricNameFromLabels(f *data.Field) string {
labels := f.Labels
metricName, hasName := labels["__name__"]
numLabels := len(labels) - 1
if !hasName {
numLabels = len(labels)
}
labelStrings := make([]string, 0, numLabels)
for label, value := range labels {
if label != "__name__" {
labelStrings = append(labelStrings, fmt.Sprintf("%s=%q", label, value))
}
}
switch numLabels {
case 0:
if hasName {
return metricName
}
return "{}"
default:
sort.Strings(labelStrings)
return fmt.Sprintf("%s{%s}", metricName, strings.Join(labelStrings, ", "))
}
}
func executedQueryString(q *models.Query) string {
return "Expr: " + q.Expr + "\n" + "Step: " + q.Step.String()
}
func getName(q *models.Query, field *data.Field) string {
labels := field.Labels
legend := metricNameFromLabels(field)
if q.LegendFormat == legendFormatAuto && len(labels) > 0 {
return ""
}
if q.LegendFormat != "" {
result := legendFormatRegexp.ReplaceAllFunc([]byte(q.LegendFormat), func(in []byte) []byte {
labelName := strings.Replace(string(in), "{{", "", 1)
labelName = strings.Replace(labelName, "}}", "", 1)
labelName = strings.TrimSpace(labelName)
if val, exists := labels[labelName]; exists {
return []byte(val)
}
return []byte{}
})
legend = string(result)
}
// If legend is empty brackets, use query expression
if legend == "{}" {
return q.Expr
}
return legend
}