Cloud Monitoring: Convert datasource to use Dataframes (#29830)
* Convert Cloud Monitoring (Stackdriver) Datasource to use Dataframes #29830 * add deeplink into config * omggggggggggggggg this deeplink works! * move unit to the backend part * remove unit from frontend * only set the config fields[1] for deeplink and unit * refactory + fix some test * remove frontend test for unit * adding backend test for unit mapping * resolve review * rewrtie unit logic to do exactly the same as frontend filter * refactory
This commit is contained in:
@@ -15,8 +15,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/api/pluginproxy"
|
||||
"github.com/grafana/grafana/pkg/components/null"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
@@ -34,11 +34,26 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
matchAllCap = regexp.MustCompile("(.)([A-Z][a-z]*)")
|
||||
legendKeyFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
|
||||
metricNameFormat = regexp.MustCompile(`([\w\d_]+)\.(googleapis\.com|io)/(.+)`)
|
||||
wildcardRegexRe = regexp.MustCompile(`[-\/^$+?.()|[\]{}]`)
|
||||
alignmentPeriodRe = regexp.MustCompile("[0-9]+")
|
||||
matchAllCap = regexp.MustCompile("(.)([A-Z][a-z]*)")
|
||||
legendKeyFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`)
|
||||
metricNameFormat = regexp.MustCompile(`([\w\d_]+)\.(googleapis\.com|io)/(.+)`)
|
||||
wildcardRegexRe = regexp.MustCompile(`[-\/^$+?.()|[\]{}]`)
|
||||
alignmentPeriodRe = regexp.MustCompile("[0-9]+")
|
||||
cloudMonitoringUnitMappings = map[string]string{
|
||||
"bit": "bits",
|
||||
"By": "bytes",
|
||||
"s": "s",
|
||||
"min": "m",
|
||||
"h": "h",
|
||||
"d": "d",
|
||||
"us": "µs",
|
||||
"ms": "ms",
|
||||
"ns": "ns",
|
||||
"percent": "percent",
|
||||
"MiBy": "mbytes",
|
||||
"By/s": "Bps",
|
||||
"GBy": "decgbytes",
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -202,17 +217,13 @@ func (e *CloudMonitoringExecutor) executeTimeSeriesQuery(ctx context.Context, ts
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unit := e.resolvePanelUnitFromQueries(queries)
|
||||
|
||||
for _, query := range queries {
|
||||
queryRes, resp, err := e.executeQuery(ctx, query, tsdbQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = e.parseResponse(queryRes, resp, query)
|
||||
if err != nil {
|
||||
queryRes.Error = err
|
||||
}
|
||||
|
||||
result.Results[query.RefID] = queryRes
|
||||
|
||||
resourceType := ""
|
||||
for _, s := range resp.TimeSeries {
|
||||
@@ -221,16 +232,48 @@ func (e *CloudMonitoringExecutor) executeTimeSeriesQuery(ctx context.Context, ts
|
||||
break
|
||||
}
|
||||
query.Params.Set("resourceType", resourceType)
|
||||
dl := ""
|
||||
if len(resp.TimeSeries) > 0 {
|
||||
dl = query.buildDeepLink()
|
||||
|
||||
err = e.parseResponse(queryRes, resp, query)
|
||||
if err != nil {
|
||||
queryRes.Error = err
|
||||
}
|
||||
queryRes.Meta.Set("deepLink", dl)
|
||||
|
||||
if len(unit) > 0 {
|
||||
frames, _ := queryRes.Dataframes.Decoded()
|
||||
for i := range frames {
|
||||
if frames[i].Fields[1].Config == nil {
|
||||
frames[i].Fields[1].Config = &data.FieldConfig{}
|
||||
}
|
||||
frames[i].Fields[1].Config.Unit = unit
|
||||
}
|
||||
queryRes.Dataframes = tsdb.NewDecodedDataFrames(frames)
|
||||
}
|
||||
result.Results[query.RefID] = queryRes
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (e *CloudMonitoringExecutor) resolvePanelUnitFromQueries(queries []*cloudMonitoringQuery) string {
|
||||
if len(queries) == 0 {
|
||||
return ""
|
||||
}
|
||||
unit := queries[0].Unit
|
||||
if len(queries) > 1 {
|
||||
for _, query := range queries[1:] {
|
||||
if query.Unit != unit {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(unit) > 0 {
|
||||
if val, ok := cloudMonitoringUnitMappings[unit]; ok {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e *CloudMonitoringExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*cloudMonitoringQuery, error) {
|
||||
cloudMonitoringQueries := []*cloudMonitoringQuery{}
|
||||
|
||||
@@ -286,7 +329,7 @@ func (e *CloudMonitoringExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*cl
|
||||
target = params.Encode()
|
||||
sq.Target = target
|
||||
sq.Params = params
|
||||
|
||||
sq.Unit = q.MetricQuery.Unit
|
||||
if setting.Env == setting.Dev {
|
||||
slog.Debug("CloudMonitoring request", "params", params)
|
||||
}
|
||||
@@ -507,9 +550,8 @@ func (e *CloudMonitoringExecutor) unmarshalResponse(res *http.Response) (cloudMo
|
||||
}
|
||||
|
||||
func handleDistributionSeries(series timeSeries, defaultMetricName string, seriesLabels map[string]string,
|
||||
query *cloudMonitoringQuery, queryRes *tsdb.QueryResult) {
|
||||
points := make([]tsdb.TimePoint, 0)
|
||||
for i := len(series.Points) - 1; i >= 0; i-- {
|
||||
query *cloudMonitoringQuery, queryRes *tsdb.QueryResult, frame *data.Frame) {
|
||||
for i := 0; i < len(series.Points); i++ {
|
||||
point := series.Points[i]
|
||||
value := point.Value.DoubleValue
|
||||
|
||||
@@ -527,27 +569,27 @@ func handleDistributionSeries(series timeSeries, defaultMetricName string, serie
|
||||
value = 0
|
||||
}
|
||||
}
|
||||
|
||||
points = append(points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.Interval.EndTime).Unix())*1000))
|
||||
frame.SetRow(len(series.Points)-1-i, point.Interval.EndTime, value)
|
||||
}
|
||||
|
||||
metricName := formatLegendKeys(series.Metric.Type, defaultMetricName, seriesLabels, nil, query)
|
||||
|
||||
queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
|
||||
Name: metricName,
|
||||
Points: points,
|
||||
})
|
||||
dataField := frame.Fields[1]
|
||||
dataField.Name = metricName
|
||||
}
|
||||
|
||||
func (e *CloudMonitoringExecutor) parseResponse(queryRes *tsdb.QueryResult, data cloudMonitoringResponse, query *cloudMonitoringQuery) error {
|
||||
func (e *CloudMonitoringExecutor) parseResponse(queryRes *tsdb.QueryResult, cmr cloudMonitoringResponse, query *cloudMonitoringQuery) error {
|
||||
labels := make(map[string]map[string]bool)
|
||||
|
||||
for _, series := range data.TimeSeries {
|
||||
seriesLabels := make(map[string]string)
|
||||
frames := data.Frames{}
|
||||
for _, series := range cmr.TimeSeries {
|
||||
seriesLabels := data.Labels{}
|
||||
defaultMetricName := series.Metric.Type
|
||||
|
||||
labels["resource.type"] = map[string]bool{series.Resource.Type: true}
|
||||
seriesLabels["resource.type"] = series.Resource.Type
|
||||
|
||||
frame := data.NewFrameOfFieldTypes("", len(series.Points), data.FieldTypeTime, data.FieldTypeFloat64)
|
||||
frame.RefID = query.RefID
|
||||
|
||||
for key, value := range series.Metric.Labels {
|
||||
if _, ok := labels["metric.label."+key]; !ok {
|
||||
labels["metric.label."+key] = map[string]bool{}
|
||||
@@ -602,10 +644,11 @@ func (e *CloudMonitoringExecutor) parseResponse(queryRes *tsdb.QueryResult, data
|
||||
|
||||
// reverse the order to be ascending
|
||||
if series.ValueType != "DISTRIBUTION" {
|
||||
handleDistributionSeries(series, defaultMetricName, seriesLabels, query, queryRes)
|
||||
handleDistributionSeries(
|
||||
series, defaultMetricName, seriesLabels, query, queryRes, frame)
|
||||
frames = append(frames, frame)
|
||||
} else {
|
||||
buckets := make(map[int]*tsdb.TimeSeries)
|
||||
|
||||
buckets := make(map[int]*data.Frame)
|
||||
for i := len(series.Points) - 1; i >= 0; i-- {
|
||||
point := series.Points[i]
|
||||
if len(point.Value.DistributionValue.BucketCounts) == 0 {
|
||||
@@ -622,34 +665,56 @@ func (e *CloudMonitoringExecutor) parseResponse(queryRes *tsdb.QueryResult, data
|
||||
// https://cloud.google.com/monitoring/api/ref_v3/rest/v3/TimeSeries#Distribution
|
||||
bucketBound := calcBucketBound(point.Value.DistributionValue.BucketOptions, i)
|
||||
additionalLabels := map[string]string{"bucket": bucketBound}
|
||||
buckets[i] = &tsdb.TimeSeries{
|
||||
Name: formatLegendKeys(series.Metric.Type, defaultMetricName, nil, additionalLabels, query),
|
||||
Points: make([]tsdb.TimePoint, 0),
|
||||
|
||||
timeField := data.NewField(data.TimeSeriesTimeFieldName, nil, []time.Time{})
|
||||
valueField := data.NewField(data.TimeSeriesValueFieldName, nil, []float64{})
|
||||
|
||||
frameName := formatLegendKeys(series.Metric.Type, defaultMetricName, nil, additionalLabels, query)
|
||||
valueField.Name = frameName
|
||||
buckets[i] = &data.Frame{
|
||||
Name: frameName,
|
||||
Fields: []*data.Field{
|
||||
timeField,
|
||||
valueField,
|
||||
},
|
||||
RefID: query.RefID,
|
||||
}
|
||||
|
||||
if maxKey < i {
|
||||
maxKey = i
|
||||
}
|
||||
}
|
||||
buckets[i].Points = append(buckets[i].Points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.Interval.EndTime).Unix())*1000))
|
||||
buckets[i].AppendRow(point.Interval.EndTime, value)
|
||||
}
|
||||
|
||||
// fill empty bucket
|
||||
for i := 0; i < maxKey; i++ {
|
||||
if _, ok := buckets[i]; !ok {
|
||||
bucketBound := calcBucketBound(point.Value.DistributionValue.BucketOptions, i)
|
||||
additionalLabels := map[string]string{"bucket": bucketBound}
|
||||
buckets[i] = &tsdb.TimeSeries{
|
||||
Name: formatLegendKeys(series.Metric.Type, defaultMetricName, seriesLabels, additionalLabels, query),
|
||||
Points: make([]tsdb.TimePoint, 0),
|
||||
additionalLabels := data.Labels{"bucket": bucketBound}
|
||||
timeField := data.NewField(data.TimeSeriesTimeFieldName, nil, []time.Time{})
|
||||
valueField := data.NewField(data.TimeSeriesValueFieldName, nil, []float64{})
|
||||
frameName := formatLegendKeys(series.Metric.Type, defaultMetricName, seriesLabels, additionalLabels, query)
|
||||
valueField.Name = frameName
|
||||
buckets[i] = &data.Frame{
|
||||
Name: frameName,
|
||||
Fields: []*data.Field{
|
||||
timeField,
|
||||
valueField,
|
||||
},
|
||||
RefID: query.RefID,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(buckets); i++ {
|
||||
queryRes.Series = append(queryRes.Series, buckets[i])
|
||||
frames = append(frames, buckets[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(cmr.TimeSeries) > 0 {
|
||||
frames = addConfigData(frames, query)
|
||||
}
|
||||
|
||||
queryRes.Dataframes = tsdb.NewDecodedDataFrames(frames)
|
||||
|
||||
labelsByKey := make(map[string][]string)
|
||||
for key, values := range labels {
|
||||
@@ -660,10 +725,25 @@ func (e *CloudMonitoringExecutor) parseResponse(queryRes *tsdb.QueryResult, data
|
||||
|
||||
queryRes.Meta.Set("labels", labelsByKey)
|
||||
queryRes.Meta.Set("groupBys", query.GroupBys)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addConfigData(frames data.Frames, query *cloudMonitoringQuery) data.Frames {
|
||||
dl := query.buildDeepLink()
|
||||
for i := range frames {
|
||||
if frames[i].Fields[1].Config == nil {
|
||||
frames[i].Fields[1].Config = &data.FieldConfig{}
|
||||
}
|
||||
deepLink := data.DataLink{
|
||||
Title: "View in Metrics Explorer",
|
||||
TargetBlank: true,
|
||||
URL: dl,
|
||||
}
|
||||
frames[i].Fields[1].Config.Links = append(frames[i].Fields[1].Config.Links, deepLink)
|
||||
}
|
||||
return frames
|
||||
}
|
||||
|
||||
func toSnakeCase(str string) string {
|
||||
return strings.ToLower(matchAllCap.ReplaceAllString(str, "${1}_${2}"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user