grafana/pkg/tsdb/loki/frame.go

137 lines
3.4 KiB
Go

package loki
import (
"fmt"
"sort"
"strings"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
)
// we adjust the dataframes to be the way frontend & alerting
// wants them.
func adjustFrame(frame *data.Frame, query *lokiQuery) *data.Frame {
labels := getFrameLabels(frame)
timeFields, nonTimeFields := partitionFields(frame)
isMetricFrame := nonTimeFields[0].Type() != data.FieldTypeString
isMetricRange := isMetricFrame && query.QueryType == QueryTypeRange
name := formatName(labels, query)
frame.Name = name
if frame.Meta == nil {
frame.Meta = &data.FrameMeta{}
}
if isMetricRange {
frame.Meta.ExecutedQueryString = "Expr: " + query.Expr + "\n" + "Step: " + query.Step.String()
} else {
frame.Meta.ExecutedQueryString = "Expr: " + query.Expr
}
for _, field := range timeFields {
field.Name = "time"
if isMetricRange {
if field.Config == nil {
field.Config = &data.FieldConfig{}
}
field.Config.Interval = float64(query.Step.Milliseconds())
}
}
for _, field := range nonTimeFields {
field.Name = "value"
if field.Config == nil {
field.Config = &data.FieldConfig{}
}
field.Config.DisplayNameFromDS = name
}
// for streams-dataframes, we need to send to the browser the nanosecond-precision timestamp too.
// usually timestamps become javascript-date-objects in the browser automatically, which only
// have millisecond-precision.
// so we send a separate timestamp-as-string field too.
if !isMetricFrame {
stringTimeField := makeStringTimeField(timeFields[0])
frame.Fields = append(frame.Fields, stringTimeField)
}
return frame
}
func makeStringTimeField(field *data.Field) *data.Field {
length := field.Len()
stringTimestamps := make([]string, length)
for i := 0; i < length; i++ {
if v, ok := field.ConcreteAt(i); ok {
nsNumber := v.(time.Time).UnixNano()
stringTimestamps[i] = fmt.Sprintf("%d", nsNumber)
}
}
return data.NewField("tsNs", field.Labels.Copy(), stringTimestamps)
}
func formatNamePrometheusStyle(labels map[string]string) string {
var parts []string
for k, v := range labels {
parts = append(parts, fmt.Sprintf("%s=%q", k, v))
}
sort.Strings(parts)
return fmt.Sprintf("{%s}", strings.Join(parts, ", "))
}
//If legend (using of name or pattern instead of time series name) is used, use that name/pattern for formatting
func formatName(labels map[string]string, query *lokiQuery) string {
if query.LegendFormat == "" {
return formatNamePrometheusStyle(labels)
}
result := legendFormat.ReplaceAllFunc([]byte(query.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{}
})
return string(result)
}
func getFrameLabels(frame *data.Frame) map[string]string {
labels := make(map[string]string)
for _, field := range frame.Fields {
for k, v := range field.Labels {
labels[k] = v
}
}
return labels
}
func partitionFields(frame *data.Frame) ([]*data.Field, []*data.Field) {
var timeFields []*data.Field
var nonTimeFields []*data.Field
for _, field := range frame.Fields {
if field.Type() == data.FieldTypeTime {
timeFields = append(timeFields, field)
} else {
nonTimeFields = append(nonTimeFields, field)
}
}
return timeFields, nonTimeFields
}