SQL Expressions: Change metric conversion to full long (#102728)

When querying metric data (non-table data) with SQL Expressions, we need to convert the data to table format. This is alternative format which does not have the same issues with sparse data.

There is now a __metric_name__ column and one __value__ column. Also a __display_name__ column if there is DisplayNameFromDS metadata.

---------

Co-authored-by: Adam Simpson <adam@adamsimpson.net>
This commit is contained in:
Kyle Brandt
2025-03-24 16:04:43 -04:00
committed by GitHub
parent 5dd0aa2c73
commit f4849eabc7
16 changed files with 1613 additions and 372 deletions
+192
View File
@@ -0,0 +1,192 @@
package expr
import (
"sort"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/require"
)
func TestConvertFromFullLongToNumericMulti(t *testing.T) {
t.Run("SingleRowNoLabels", func(t *testing.T) {
input := data.NewFrame("",
data.NewField(SQLMetricFieldName, nil, []string{"cpu"}),
data.NewField(SQLValueFieldName, nil, []*float64{fp(3.14)}),
)
input.Meta = &data.FrameMeta{Type: numericFullLongType}
out, err := ConvertFromFullLongToNumericMulti(data.Frames{input})
require.NoError(t, err)
require.Len(t, out, 1)
expected := data.NewFrame("",
data.NewField("cpu", nil, []*float64{fp(3.14)}),
)
expected.Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
if diff := cmp.Diff(expected, out[0], data.FrameTestCompareOptions()...); diff != "" {
require.FailNowf(t, "Mismatch (-want +got):\n%s", diff)
}
})
t.Run("TwoRowsWithLabelsAndDisplay", func(t *testing.T) {
input := data.NewFrame("",
data.NewField(SQLMetricFieldName, nil, []string{"cpu", "cpu"}),
data.NewField(SQLValueFieldName, nil, []*float64{fp(1.0), fp(2.0)}),
data.NewField(SQLDisplayFieldName, nil, []*string{sp("CPU A"), sp("CPU A")}),
data.NewField("host", nil, []*string{sp("a"), sp("a")}),
)
input.Meta = &data.FrameMeta{Type: numericFullLongType}
out, err := ConvertFromFullLongToNumericMulti(data.Frames{input})
require.NoError(t, err)
require.Len(t, out, 1)
expected := data.NewFrame("",
func() *data.Field {
f := data.NewField("cpu", data.Labels{"host": "a"}, []*float64{fp(1.0), fp(2.0)})
f.Config = &data.FieldConfig{DisplayNameFromDS: "CPU A"}
return f
}(),
)
expected.Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
if diff := cmp.Diff(expected, out[0], data.FrameTestCompareOptions()...); diff != "" {
require.FailNowf(t, "Mismatch (-want +got):\n%s", diff)
}
})
t.Run("SkipsNullValues", func(t *testing.T) {
input := data.NewFrame("",
data.NewField(SQLMetricFieldName, nil, []string{"cpu", "cpu"}),
data.NewField(SQLValueFieldName, nil, []*float64{fp(1.0), nil}),
)
input.Meta = &data.FrameMeta{Type: numericFullLongType}
out, err := ConvertFromFullLongToNumericMulti(data.Frames{input})
require.NoError(t, err)
require.Len(t, out, 1)
expected := data.NewFrame("",
data.NewField("cpu", nil, []*float64{fp(1.0)}),
)
expected.Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
if diff := cmp.Diff(expected, out[0], data.FrameTestCompareOptions()...); diff != "" {
require.FailNowf(t, "Mismatch (-want +got):\n%s", diff)
}
})
}
func TestConvertNumericMultiRoundTripToFullLongAndBack(t *testing.T) {
t.Run("TwoFieldsWithSparseLabels", func(t *testing.T) {
input := data.Frames{
data.NewFrame("",
data.NewField("cpu", data.Labels{"host": "a"}, []*float64{fp(1.0)}),
),
data.NewFrame("",
data.NewField("cpu", data.Labels{"host": "b", "env": "prod"}, []*float64{fp(2.0)}),
),
}
for _, f := range input {
f.Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
}
fullLong, err := ConvertToFullLong(input)
require.NoError(t, err)
require.Len(t, fullLong, 1)
roundTrip, err := ConvertFromFullLongToNumericMulti(fullLong)
require.NoError(t, err)
expected := data.Frames{
data.NewFrame("",
data.NewField("cpu", data.Labels{"host": "a"}, []*float64{fp(1.0)}),
),
data.NewFrame("",
data.NewField("cpu", data.Labels{"host": "b", "env": "prod"}, []*float64{fp(2.0)}),
),
}
for _, f := range expected {
f.Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
}
sortFramesByMetricDisplayAndLabels(expected)
sortFramesByMetricDisplayAndLabels(roundTrip)
require.Len(t, roundTrip, len(expected))
for i := range expected {
if diff := cmp.Diff(expected[i], roundTrip[i], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Mismatch on frame %d (-want +got):\n%s", i, diff)
}
}
})
t.Run("PreservesDisplayName", func(t *testing.T) {
input := data.Frames{
data.NewFrame("",
func() *data.Field {
f := data.NewField("cpu", data.Labels{"host": "a"}, []*float64{fp(1.0)})
f.Config = &data.FieldConfig{DisplayNameFromDS: "CPU A"}
return f
}(),
),
}
input[0].Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
fullLong, err := ConvertToFullLong(input)
require.NoError(t, err)
require.Len(t, fullLong, 1)
roundTrip, err := ConvertFromFullLongToNumericMulti(fullLong)
require.NoError(t, err)
expected := data.Frames{
data.NewFrame("",
func() *data.Field {
f := data.NewField("cpu", data.Labels{"host": "a"}, []*float64{fp(1.0)})
f.Config = &data.FieldConfig{DisplayNameFromDS: "CPU A"}
return f
}(),
),
}
expected[0].Meta = &data.FrameMeta{Type: data.FrameTypeNumericMulti}
sortFramesByMetricDisplayAndLabels(expected)
sortFramesByMetricDisplayAndLabels(roundTrip)
require.Len(t, roundTrip, 1)
if diff := cmp.Diff(expected[0], roundTrip[0], data.FrameTestCompareOptions()...); diff != "" {
t.Errorf("Mismatch (-want +got):\n%s", diff)
}
})
}
func sortFramesByMetricDisplayAndLabels(frames data.Frames) {
sort.Slice(frames, func(i, j int) bool {
fi := frames[i].Fields[0]
fj := frames[j].Fields[0]
// 1. Metric name
if fi.Name != fj.Name {
return fi.Name < fj.Name
}
// 2. Display name (if set)
var di, dj string
if fi.Config != nil {
di = fi.Config.DisplayNameFromDS
}
if fj.Config != nil {
dj = fj.Config.DisplayNameFromDS
}
if di != dj {
return di < dj
}
// 3. Labels fingerprint
return fi.Labels.Fingerprint() < fj.Labels.Fingerprint()
})
}