mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 21:02:29 +08:00

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>
401 lines
9.3 KiB
Go
401 lines
9.3 KiB
Go
package expr
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
)
|
|
|
|
const (
|
|
SQLMetricFieldName = "__metric_name__"
|
|
SQLValueFieldName = "__value__"
|
|
SQLDisplayFieldName = "__display_name__"
|
|
|
|
// These are not types in the SDK or dataplane contract yet.
|
|
numericFullLongType = "numeric_full_long"
|
|
timeseriesFullLongType = "time_series_full_long"
|
|
)
|
|
|
|
func ConvertToFullLong(frames data.Frames) (data.Frames, error) {
|
|
if len(frames) == 0 {
|
|
return frames, nil
|
|
}
|
|
|
|
var inputType data.FrameType
|
|
if frames[0].Meta != nil && frames[0].Meta.Type != "" {
|
|
inputType = frames[0].Meta.Type
|
|
} else {
|
|
return nil, fmt.Errorf("input frame missing FrameMeta.Type")
|
|
}
|
|
|
|
if !supportedToLongConversion(inputType) {
|
|
return nil, fmt.Errorf("unsupported input dataframe type %s for full long conversion", inputType)
|
|
}
|
|
|
|
switch inputType {
|
|
case data.FrameTypeNumericMulti:
|
|
return convertNumericMultiToFullLong(frames)
|
|
case data.FrameTypeNumericWide:
|
|
return convertNumericWideToFullLong(frames)
|
|
case data.FrameTypeTimeSeriesMulti:
|
|
return convertTimeSeriesMultiToFullLong(frames)
|
|
case data.FrameTypeTimeSeriesWide:
|
|
return convertTimeSeriesWideToFullLong(frames)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported input type %s for full long conversion", inputType)
|
|
}
|
|
}
|
|
|
|
func convertNumericMultiToFullLong(frames data.Frames) (data.Frames, error) {
|
|
wide := convertNumericMultiToNumericWide(frames)
|
|
return convertNumericWideToFullLong(wide)
|
|
}
|
|
|
|
func convertNumericWideToFullLong(frames data.Frames) (data.Frames, error) {
|
|
if len(frames) != 1 {
|
|
return nil, fmt.Errorf("expected exactly one frame for wide format, but got %d", len(frames))
|
|
}
|
|
inputFrame := frames[0]
|
|
if inputFrame.Rows() > 1 {
|
|
return nil, fmt.Errorf("expected no more than one row in the frame, but got %d", inputFrame.Rows())
|
|
}
|
|
|
|
var (
|
|
metricCol = make([]string, 0, len(inputFrame.Fields))
|
|
valueCol = make([]*float64, 0, len(inputFrame.Fields))
|
|
displayCol = make([]*string, 0, len(inputFrame.Fields))
|
|
hasDisplayCol bool
|
|
)
|
|
|
|
labelKeySet := map[string]struct{}{}
|
|
for _, field := range inputFrame.Fields {
|
|
if !field.Type().Numeric() {
|
|
continue
|
|
}
|
|
val, err := field.FloatAt(0)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
v := val
|
|
valueCol = append(valueCol, &v)
|
|
metricCol = append(metricCol, field.Name)
|
|
|
|
// Display name
|
|
var d *string
|
|
if field.Config != nil && field.Config.DisplayNameFromDS != "" {
|
|
s := field.Config.DisplayNameFromDS
|
|
d = &s
|
|
hasDisplayCol = true
|
|
}
|
|
displayCol = append(displayCol, d)
|
|
|
|
for k := range field.Labels {
|
|
labelKeySet[k] = struct{}{}
|
|
}
|
|
}
|
|
|
|
labelKeys := make([]string, 0, len(labelKeySet))
|
|
|
|
labelValues := make(map[string][]*string)
|
|
for k := range labelKeySet {
|
|
labelKeys = append(labelKeys, k)
|
|
labelValues[k] = make([]*string, 0, len(valueCol))
|
|
}
|
|
sort.Strings(labelKeys)
|
|
|
|
for _, field := range inputFrame.Fields {
|
|
if !field.Type().Numeric() {
|
|
continue
|
|
}
|
|
for _, k := range labelKeys {
|
|
var val *string
|
|
if field.Labels != nil {
|
|
if v, ok := field.Labels[k]; ok {
|
|
val = &v
|
|
}
|
|
}
|
|
labelValues[k] = append(labelValues[k], val)
|
|
}
|
|
}
|
|
|
|
fields := []*data.Field{
|
|
data.NewField(SQLMetricFieldName, nil, metricCol),
|
|
data.NewField(SQLValueFieldName, nil, valueCol),
|
|
}
|
|
if hasDisplayCol {
|
|
fields = append(fields, data.NewField(SQLDisplayFieldName, nil, displayCol))
|
|
}
|
|
for _, k := range labelKeys {
|
|
fields = append(fields, data.NewField(k, nil, labelValues[k]))
|
|
}
|
|
|
|
out := data.NewFrame("", fields...)
|
|
out.Meta = &data.FrameMeta{Type: numericFullLongType}
|
|
return data.Frames{out}, nil
|
|
}
|
|
|
|
func convertTimeSeriesMultiToFullLong(frames data.Frames) (data.Frames, error) {
|
|
type row struct {
|
|
t time.Time
|
|
value *float64
|
|
metric string
|
|
display *string
|
|
labels data.Labels
|
|
}
|
|
|
|
var rows []row
|
|
labelKeysSet := map[string]struct{}{}
|
|
hasDisplayCol := false
|
|
|
|
for _, frame := range frames {
|
|
var timeField *data.Field
|
|
for _, f := range frame.Fields {
|
|
if f.Type() == data.FieldTypeTime {
|
|
timeField = f
|
|
break
|
|
}
|
|
}
|
|
if timeField == nil {
|
|
return nil, fmt.Errorf("missing time field")
|
|
}
|
|
for _, f := range frame.Fields {
|
|
if !f.Type().Numeric() {
|
|
continue
|
|
}
|
|
var display *string
|
|
if f.Config != nil && f.Config.DisplayNameFromDS != "" {
|
|
s := f.Config.DisplayNameFromDS
|
|
display = &s
|
|
hasDisplayCol = true
|
|
}
|
|
for i := 0; i < f.Len(); i++ {
|
|
t := timeField.At(i).(time.Time)
|
|
v, err := f.FloatAt(i)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
val := v
|
|
rows = append(rows, row{
|
|
t: t,
|
|
value: &val,
|
|
metric: f.Name,
|
|
display: display,
|
|
labels: f.Labels,
|
|
})
|
|
for k := range f.Labels {
|
|
labelKeysSet[k] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
labelKeys := make([]string, 0, len(labelKeysSet))
|
|
for k := range labelKeysSet {
|
|
labelKeys = append(labelKeys, k)
|
|
}
|
|
sort.Strings(labelKeys)
|
|
sort.SliceStable(rows, func(i, j int) bool {
|
|
if rows[i].t.Equal(rows[j].t) {
|
|
return rows[i].metric < rows[j].metric
|
|
}
|
|
return rows[i].t.Before(rows[j].t)
|
|
})
|
|
|
|
times := make([]time.Time, len(rows))
|
|
values := make([]*float64, len(rows))
|
|
metrics := make([]string, len(rows))
|
|
var displays []*string
|
|
if hasDisplayCol {
|
|
displays = make([]*string, len(rows))
|
|
}
|
|
labels := make(map[string][]*string)
|
|
for _, k := range labelKeys {
|
|
labels[k] = make([]*string, len(rows))
|
|
}
|
|
|
|
for i, r := range rows {
|
|
times[i] = r.t
|
|
values[i] = r.value
|
|
metrics[i] = r.metric
|
|
if hasDisplayCol {
|
|
displays[i] = r.display
|
|
}
|
|
for _, k := range labelKeys {
|
|
if v, ok := r.labels[k]; ok {
|
|
labels[k][i] = &v
|
|
}
|
|
}
|
|
}
|
|
|
|
fields := []*data.Field{
|
|
data.NewField("time", nil, times),
|
|
data.NewField(SQLValueFieldName, nil, values),
|
|
data.NewField(SQLMetricFieldName, nil, metrics),
|
|
}
|
|
if hasDisplayCol {
|
|
fields = append(fields, data.NewField(SQLDisplayFieldName, nil, displays))
|
|
}
|
|
for _, k := range labelKeys {
|
|
fields = append(fields, data.NewField(k, nil, labels[k]))
|
|
}
|
|
|
|
out := data.NewFrame("", fields...)
|
|
out.Meta = &data.FrameMeta{Type: timeseriesFullLongType}
|
|
return data.Frames{out}, nil
|
|
}
|
|
|
|
func convertTimeSeriesWideToFullLong(frames data.Frames) (data.Frames, error) {
|
|
if len(frames) != 1 {
|
|
return nil, fmt.Errorf("expected exactly one frame for wide format, but got %d", len(frames))
|
|
}
|
|
frame := frames[0]
|
|
|
|
var timeField *data.Field
|
|
for _, f := range frame.Fields {
|
|
if f.Type() == data.FieldTypeTime {
|
|
timeField = f
|
|
break
|
|
}
|
|
}
|
|
if timeField == nil {
|
|
return nil, fmt.Errorf("time field not found in TimeSeriesWide frame")
|
|
}
|
|
|
|
type row struct {
|
|
t time.Time
|
|
value *float64
|
|
metric string
|
|
display *string
|
|
labels data.Labels
|
|
}
|
|
|
|
var (
|
|
rows []row
|
|
labelKeysSet = map[string]struct{}{}
|
|
hasDisplayCol bool
|
|
)
|
|
|
|
// Collect all label keys
|
|
for _, f := range frame.Fields {
|
|
if !f.Type().Numeric() {
|
|
continue
|
|
}
|
|
for k := range f.Labels {
|
|
labelKeysSet[k] = struct{}{}
|
|
}
|
|
}
|
|
|
|
labelKeys := make([]string, 0, len(labelKeysSet))
|
|
for k := range labelKeysSet {
|
|
labelKeys = append(labelKeys, k)
|
|
}
|
|
sort.Strings(labelKeys)
|
|
|
|
timeLen := timeField.Len()
|
|
for _, f := range frame.Fields {
|
|
if !f.Type().Numeric() {
|
|
continue
|
|
}
|
|
var display *string
|
|
if f.Config != nil && f.Config.DisplayNameFromDS != "" {
|
|
s := f.Config.DisplayNameFromDS
|
|
display = &s
|
|
hasDisplayCol = true
|
|
}
|
|
for i := 0; i < timeLen; i++ {
|
|
t := timeField.At(i).(time.Time)
|
|
v, err := f.FloatAt(i)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
val := v
|
|
rows = append(rows, row{
|
|
t: t,
|
|
value: &val,
|
|
metric: f.Name,
|
|
display: display,
|
|
labels: f.Labels,
|
|
})
|
|
}
|
|
}
|
|
|
|
sort.SliceStable(rows, func(i, j int) bool {
|
|
if rows[i].t.Equal(rows[j].t) {
|
|
return rows[i].metric < rows[j].metric
|
|
}
|
|
return rows[i].t.Before(rows[j].t)
|
|
})
|
|
|
|
times := make([]time.Time, len(rows))
|
|
values := make([]*float64, len(rows))
|
|
metrics := make([]string, len(rows))
|
|
var displays []*string
|
|
if hasDisplayCol {
|
|
displays = make([]*string, len(rows))
|
|
}
|
|
labels := make(map[string][]*string)
|
|
for _, k := range labelKeys {
|
|
labels[k] = make([]*string, len(rows))
|
|
}
|
|
|
|
for i, r := range rows {
|
|
times[i] = r.t
|
|
values[i] = r.value
|
|
metrics[i] = r.metric
|
|
if hasDisplayCol {
|
|
displays[i] = r.display
|
|
}
|
|
for _, k := range labelKeys {
|
|
if v, ok := r.labels[k]; ok {
|
|
labels[k][i] = &v
|
|
}
|
|
}
|
|
}
|
|
|
|
fields := []*data.Field{
|
|
data.NewField("time", nil, times),
|
|
data.NewField(SQLValueFieldName, nil, values),
|
|
data.NewField(SQLMetricFieldName, nil, metrics),
|
|
}
|
|
if hasDisplayCol {
|
|
fields = append(fields, data.NewField(SQLDisplayFieldName, nil, displays))
|
|
}
|
|
for _, k := range labelKeys {
|
|
fields = append(fields, data.NewField(k, nil, labels[k]))
|
|
}
|
|
|
|
out := data.NewFrame("", fields...)
|
|
out.Meta = &data.FrameMeta{Type: timeseriesFullLongType}
|
|
return data.Frames{out}, nil
|
|
}
|
|
|
|
func supportedToLongConversion(inputType data.FrameType) bool {
|
|
switch inputType {
|
|
case data.FrameTypeNumericMulti, data.FrameTypeNumericWide:
|
|
return true
|
|
case data.FrameTypeTimeSeriesMulti, data.FrameTypeTimeSeriesWide:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func convertNumericMultiToNumericWide(frames data.Frames) data.Frames {
|
|
if len(frames) == 0 {
|
|
return nil
|
|
}
|
|
|
|
out := data.NewFrame("")
|
|
for _, frame := range frames {
|
|
for _, field := range frame.Fields {
|
|
if field.Type().Numeric() {
|
|
out.Fields = append(out.Fields, field)
|
|
}
|
|
}
|
|
}
|
|
out.Meta = &data.FrameMeta{Type: data.FrameTypeNumericWide}
|
|
return data.Frames{out}
|
|
}
|