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 }