2025-04-01 10:38:02 +09:00

155 lines
4.2 KiB
Go

package traceql
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/tsdb/tempo/kinds/dataquery"
"github.com/grafana/tempo/pkg/tempopb"
v1 "github.com/grafana/tempo/pkg/tempopb/common/v1"
)
func TransformMetricsResponse(query string, resp tempopb.QueryRangeResponse) []*data.Frame {
// prealloc frames
frames := make([]*data.Frame, len(resp.Series))
var exemplarFrames []*data.Frame
for i, series := range resp.Series {
name, labels := transformLabelsAndGetName(series.Labels)
valueField := data.NewField(name, labels, []float64{})
valueField.Config = &data.FieldConfig{
DisplayName: name,
}
timeField := data.NewField("time", nil, []time.Time{})
frame := &data.Frame{
RefID: name,
Name: name,
Fields: []*data.Field{
timeField,
valueField,
},
Meta: &data.FrameMeta{
PreferredVisualization: data.VisTypeGraph,
Type: data.FrameTypeTimeSeriesMulti,
},
}
isHistogram := isHistogramQuery(query)
if isHistogram {
frame.Meta.PreferredVisualizationPluginID = "heatmap"
}
for _, sample := range series.Samples {
frame.AppendRow(time.UnixMilli(sample.GetTimestampMs()), sample.GetValue())
}
if len(series.Exemplars) > 0 {
exFrame := transformExemplarToFrame(name, series)
exemplarFrames = append(exemplarFrames, exFrame)
}
frames[i] = frame
}
return append(frames, exemplarFrames...)
}
func TransformInstantMetricsResponse(query *dataquery.TempoQuery, resp tempopb.QueryInstantResponse) []*data.Frame {
frames := make([]*data.Frame, len(resp.Series))
for i, series := range resp.Series {
name, labels := transformLabelsAndGetName(series.Labels)
labelKeys := make([]string, 0, len(labels))
labelFields := make([]*data.Field, 0, len(labels))
for key := range labels {
labelKeys = append(labelKeys, key)
labelFields = append(labelFields, data.NewField(key, nil, []string{}))
}
timeField := data.NewField("time", nil, []time.Time{})
valueField := data.NewField("value", labels, []float64{})
valueField.Config = &data.FieldConfig{
DisplayName: name,
}
frame := &data.Frame{
RefID: name,
Name: name,
Fields: append([]*data.Field{timeField}, append(labelFields, valueField)...),
Meta: &data.FrameMeta{
PreferredVisualization: data.VisTypeTable,
},
}
labelValues := make([]interface{}, len(labels))
for idx, key := range labelKeys {
labelValues[idx] = strings.Trim(labels[key], "\"")
}
row := append([]interface{}{time.Now()}, append(labelValues, series.GetValue())...)
frame.AppendRow(row...)
frames[i] = frame
}
return frames
}
func metricsValueToString(value *v1.AnyValue) (string, string) {
switch value.GetValue().(type) {
case *v1.AnyValue_DoubleValue:
res := strconv.FormatFloat(value.GetDoubleValue(), 'f', -1, 64)
return res, res
case *v1.AnyValue_IntValue:
res := strconv.FormatInt(value.GetIntValue(), 10)
return res, res
case *v1.AnyValue_StringValue:
// return the value wrapped in quotes since it's accurate and "1" is different from 1
// the second value is returned without quotes for display purposes
return fmt.Sprintf("\"%s\"", value.GetStringValue()), value.GetStringValue()
case *v1.AnyValue_BoolValue:
res := strconv.FormatBool(value.GetBoolValue())
return res, res
}
return "", ""
}
func transformLabelsAndGetName(seriesLabels []v1.KeyValue) (string, data.Labels) {
labels := make(data.Labels)
for _, label := range seriesLabels {
labels[label.GetKey()], _ = metricsValueToString(label.GetValue())
}
name := ""
if len(seriesLabels) > 0 {
if len(seriesLabels) == 1 {
_, name = metricsValueToString(seriesLabels[0].GetValue())
} else {
keys := make([]string, 0, len(labels))
for k := range labels {
keys = append(keys, k)
}
sort.Strings(keys)
var labelStrings []string
for _, key := range keys {
labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", key, labels[key]))
}
name = fmt.Sprintf("{%s}", strings.Join(labelStrings, ", "))
}
}
return name, labels
}
func isHistogramQuery(query string) bool {
match, _ := regexp.MatchString("\\|\\s*(histogram_over_time)\\s*\\(", query)
return match
}