CloudMonitor: Improve detail of MQL series labels (#59747)

* Improve consistency between MQL and builder queries

* Review

* Review and fix test
This commit is contained in:
Andreas Christou 2022-12-07 15:32:54 +00:00 committed by GitHub
parent 1aa94165d9
commit 43afb85056
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 97 additions and 83 deletions

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"net/url" "net/url"
"strconv"
"strings" "strings"
"time" "time"
@ -20,62 +19,12 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesList) run(ctx context.Context,
return runTimeSeriesRequest(ctx, timeSeriesFilter.logger, req, s, dsInfo, tracer, timeSeriesFilter.parameters.ProjectName, timeSeriesFilter.params, nil) return runTimeSeriesRequest(ctx, timeSeriesFilter.logger, req, s, dsInfo, tracer, timeSeriesFilter.parameters.ProjectName, timeSeriesFilter.params, nil)
} }
func extractTimeSeriesLabels(series timeSeries, groupBys []string) (data.Labels, string) {
seriesLabels := data.Labels{}
defaultMetricName := series.Metric.Type
seriesLabels["resource.type"] = series.Resource.Type
groupBysMap := make(map[string]bool)
for _, groupBy := range groupBys {
groupBysMap[groupBy] = true
}
for key, value := range series.Metric.Labels {
seriesLabels["metric.label."+key] = value
if len(groupBys) == 0 || groupBysMap["metric.label."+key] {
defaultMetricName += " " + value
}
}
for key, value := range series.Resource.Labels {
seriesLabels["resource.label."+key] = value
if groupBysMap["resource.label."+key] {
defaultMetricName += " " + value
}
}
for labelType, labelTypeValues := range series.MetaData {
for labelKey, labelValue := range labelTypeValues {
key := xstrings.ToSnakeCase(fmt.Sprintf("metadata.%s.%s", labelType, labelKey))
switch v := labelValue.(type) {
case string:
seriesLabels[key] = v
case bool:
strVal := strconv.FormatBool(v)
seriesLabels[key] = strVal
case []interface{}:
for _, v := range v {
strVal := v.(string)
if len(seriesLabels[key]) > 0 {
strVal = fmt.Sprintf("%s, %s", seriesLabels[key], strVal)
}
seriesLabels[key] = strVal
}
}
}
}
return seriesLabels, defaultMetricName
}
func parseTimeSeriesResponse(queryRes *backend.DataResponse, func parseTimeSeriesResponse(queryRes *backend.DataResponse,
response cloudMonitoringResponse, executedQueryString string, query cloudMonitoringQueryExecutor, params url.Values, groupBys []string) error { response cloudMonitoringResponse, executedQueryString string, query cloudMonitoringQueryExecutor, params url.Values, groupBys []string) error {
frames := data.Frames{} frames := data.Frames{}
for _, series := range response.TimeSeries { for _, series := range response.TimeSeries {
seriesLabels, defaultMetricName := extractTimeSeriesLabels(series, groupBys) seriesLabels, defaultMetricName := series.getLabels(groupBys)
frame := data.NewFrameOfFieldTypes("", len(series.Points), data.FieldTypeTime, data.FieldTypeFloat64) frame := data.NewFrameOfFieldTypes("", len(series.Points), data.FieldTypeTime, data.FieldTypeFloat64)
frame.RefID = query.getRefID() frame.RefID = query.getRefID()
frame.Meta = &data.FrameMeta{ frame.Meta = &data.FrameMeta{

View File

@ -3,13 +3,11 @@ package cloudmonitoring
import ( import (
"context" "context"
"fmt" "fmt"
"strconv"
"strings" "strings"
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/huandu/xstrings"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/tsdb/intervalv2" "github.com/grafana/grafana/pkg/tsdb/intervalv2"
@ -42,26 +40,6 @@ func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) run(ctx context.Context,
return runTimeSeriesRequest(ctx, timeSeriesQuery.logger, req, s, dsInfo, tracer, timeSeriesQuery.parameters.ProjectName, nil, requestBody) return runTimeSeriesRequest(ctx, timeSeriesQuery.logger, req, s, dsInfo, tracer, timeSeriesQuery.parameters.ProjectName, nil, requestBody)
} }
func extractTimeSeriesDataLabels(response cloudMonitoringResponse, series timeSeriesData) map[string]string {
seriesLabels := make(map[string]string)
for n, d := range response.TimeSeriesDescriptor.LabelDescriptors {
key := xstrings.ToSnakeCase(d.Key)
key = strings.Replace(key, ".", ".label.", 1)
labelValue := series.LabelValues[n]
switch d.ValueType {
case "BOOL":
strVal := strconv.FormatBool(labelValue.BoolValue)
seriesLabels[key] = strVal
case "INT64":
seriesLabels[key] = labelValue.Int64Value
default:
seriesLabels[key] = labelValue.StringValue
}
}
return seriesLabels
}
func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *backend.DataResponse, func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *backend.DataResponse,
response cloudMonitoringResponse, executedQueryString string) error { response cloudMonitoringResponse, executedQueryString string) error {
frames := data.Frames{} frames := data.Frames{}
@ -69,7 +47,7 @@ func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *b
for _, series := range response.TimeSeriesData { for _, series := range response.TimeSeriesData {
frame := data.NewFrameOfFieldTypes("", len(series.PointData), data.FieldTypeTime, data.FieldTypeFloat64) frame := data.NewFrameOfFieldTypes("", len(series.PointData), data.FieldTypeTime, data.FieldTypeFloat64)
frame.RefID = timeSeriesQuery.refID frame.RefID = timeSeriesQuery.refID
seriesLabels := extractTimeSeriesDataLabels(response, series) seriesLabels, defaultMetricName := series.getLabels(response.TimeSeriesDescriptor.LabelDescriptors)
for n, d := range response.TimeSeriesDescriptor.PointDescriptors { for n, d := range response.TimeSeriesDescriptor.PointDescriptors {
// If more than 1 pointdescriptor was returned, three aggregations are returned per time series - min, mean and max. // If more than 1 pointdescriptor was returned, three aggregations are returned per time series - min, mean and max.
@ -81,7 +59,6 @@ func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *b
} }
seriesLabels["metric.name"] = d.Key seriesLabels["metric.name"] = d.Key
defaultMetricName := d.Key
customFrameMeta := map[string]interface{}{} customFrameMeta := map[string]interface{}{}
customFrameMeta["labels"] = seriesLabels customFrameMeta["labels"] = seriesLabels

View File

@ -5,6 +5,8 @@ import (
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
gdata "github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -32,7 +34,7 @@ func TestTimeSeriesQuery(t *testing.T) {
} }
err = query.parseResponse(res, data, "") err = query.parseResponse(res, data, "")
frames := res.Frames frames := res.Frames
assert.Equal(t, "value.usage.mean", frames[0].Fields[1].Name) assert.Equal(t, "grafana-prod asia-northeast1-c 6724404429462225363 200", frames[0].Fields[1].Name)
assert.Equal(t, 843302441.9, frames[0].Fields[1].At(0)) assert.Equal(t, 843302441.9, frames[0].Fields[1].At(0))
}) })
@ -105,7 +107,7 @@ func TestTimeSeriesQuery(t *testing.T) {
frames := res.Frames frames := res.Frames
custom, ok := frames[0].Meta.Custom.(map[string]interface{}) custom, ok := frames[0].Meta.Custom.(map[string]interface{})
require.True(t, ok) require.True(t, ok)
labels, ok := custom["labels"].(map[string]string) labels, ok := custom["labels"].(gdata.Labels)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, "6724404429462225363", labels["resource.label.instance_id"]) assert.Equal(t, "6724404429462225363", labels["resource.label.instance_id"])
}) })

View File

@ -2,10 +2,15 @@ package cloudmonitoring
import ( import (
"context" "context"
"fmt"
"net/url" "net/url"
"strconv"
"strings"
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/huandu/xstrings"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
@ -139,14 +144,16 @@ type point interface {
} }
type timeSeriesDescriptor struct { type timeSeriesDescriptor struct {
LabelDescriptors []struct { LabelDescriptors []LabelDescriptor `json:"labelDescriptors"`
Key string `json:"key"`
ValueType string `json:"valueType"`
Description string `json:"description"`
} `json:"labelDescriptors"`
PointDescriptors []timeSeriesPointDescriptor `json:"pointDescriptors"` PointDescriptors []timeSeriesPointDescriptor `json:"pointDescriptors"`
} }
type LabelDescriptor struct {
Key string `json:"key"`
ValueType string `json:"valueType"`
Description string `json:"description"`
}
type timeSeriesPointDescriptor struct { type timeSeriesPointDescriptor struct {
Key string `json:"key"` Key string `json:"key"`
ValueType string `json:"valueType"` ValueType string `json:"valueType"`
@ -178,6 +185,35 @@ func (ts timeSeriesData) getPoint(index int) point {
return &ts.PointData[index] return &ts.PointData[index]
} }
func (ts timeSeriesData) getLabels(labelDescriptors []LabelDescriptor) (data.Labels, string) {
seriesLabels := make(map[string]string)
defaultMetricName := ""
for n, d := range labelDescriptors {
key := xstrings.ToSnakeCase(d.Key)
key = strings.Replace(key, ".", ".label.", 1)
labelValue := ts.LabelValues[n]
switch d.ValueType {
case "BOOL":
strVal := strconv.FormatBool(labelValue.BoolValue)
seriesLabels[key] = strVal
case "INT64":
seriesLabels[key] = labelValue.Int64Value
default:
seriesLabels[key] = labelValue.StringValue
}
if strings.Contains(key, "metric.label") || strings.Contains(key, "resource.label") {
defaultMetricName += seriesLabels[key] + " "
}
}
defaultMetricName = strings.Trim(defaultMetricName, " ")
return seriesLabels, defaultMetricName
}
type timeSeriesDataIterator struct { type timeSeriesDataIterator struct {
timeSeriesData timeSeriesData
timeSeriesPointDescriptor timeSeriesPointDescriptor
@ -250,6 +286,56 @@ func (ts timeSeries) valueType() string {
return ts.ValueType return ts.ValueType
} }
func (ts timeSeries) getLabels(groupBys []string) (data.Labels, string) {
seriesLabels := data.Labels{}
defaultMetricName := ts.Metric.Type
seriesLabels["resource.type"] = ts.Resource.Type
groupBysMap := make(map[string]bool)
for _, groupBy := range groupBys {
groupBysMap[groupBy] = true
}
for key, value := range ts.Metric.Labels {
seriesLabels["metric.label."+key] = value
if len(groupBys) == 0 || groupBysMap["metric.label."+key] {
defaultMetricName += " " + value
}
}
for key, value := range ts.Resource.Labels {
seriesLabels["resource.label."+key] = value
if groupBysMap["resource.label."+key] {
defaultMetricName += " " + value
}
}
for labelType, labelTypeValues := range ts.MetaData {
for labelKey, labelValue := range labelTypeValues {
key := xstrings.ToSnakeCase(fmt.Sprintf("metadata.%s.%s", labelType, labelKey))
switch v := labelValue.(type) {
case string:
seriesLabels[key] = v
case bool:
strVal := strconv.FormatBool(v)
seriesLabels[key] = strVal
case []interface{}:
for _, v := range v {
strVal := v.(string)
if len(seriesLabels[key]) > 0 {
strVal = fmt.Sprintf("%s, %s", seriesLabels[key], strVal)
}
seriesLabels[key] = strVal
}
}
}
}
return seriesLabels, defaultMetricName
}
type timeSeriesPoint struct { type timeSeriesPoint struct {
Interval struct { Interval struct {
StartTime time.Time `json:"startTime"` StartTime time.Time `json:"startTime"`