|
|
|
@ -28,7 +28,12 @@ import ( |
|
|
|
|
"github.com/opentracing/opentracing-go" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var slog log.Logger |
|
|
|
|
var ( |
|
|
|
|
slog log.Logger |
|
|
|
|
legendKeyFormat *regexp.Regexp |
|
|
|
|
longMetricNameFormat *regexp.Regexp |
|
|
|
|
shortMetricNameFormat *regexp.Regexp |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// StackdriverExecutor executes queries for the Stackdriver datasource
|
|
|
|
|
type StackdriverExecutor struct { |
|
|
|
@ -52,6 +57,9 @@ func NewStackdriverExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, |
|
|
|
|
func init() { |
|
|
|
|
slog = log.New("tsdb.stackdriver") |
|
|
|
|
tsdb.RegisterTsdbQueryEndpoint("stackdriver", NewStackdriverExecutor) |
|
|
|
|
legendKeyFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`) |
|
|
|
|
longMetricNameFormat = regexp.MustCompile(`([\w\d_]+)\.googleapis\.com/([\w\d_]+)/(.+)`) |
|
|
|
|
shortMetricNameFormat = regexp.MustCompile(`([\w\d_]+)\.googleapis\.com/(.+)`) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Query takes in the frontend queries, parses them into the Stackdriver query format
|
|
|
|
@ -132,11 +140,14 @@ func (e *StackdriverExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Stackd |
|
|
|
|
groupBysAsStrings = append(groupBysAsStrings, groupBy.(string)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
aliasBy := query.Model.Get("aliasBy").MustString() |
|
|
|
|
|
|
|
|
|
stackdriverQueries = append(stackdriverQueries, &StackdriverQuery{ |
|
|
|
|
Target: target, |
|
|
|
|
Params: params, |
|
|
|
|
RefID: query.RefId, |
|
|
|
|
GroupBys: groupBysAsStrings, |
|
|
|
|
AliasBy: aliasBy, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -260,14 +271,15 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta |
|
|
|
|
point := series.Points[i] |
|
|
|
|
points = append(points, tsdb.NewTimePoint(null.FloatFrom(point.Value.DoubleValue), float64((point.Interval.EndTime).Unix())*1000)) |
|
|
|
|
} |
|
|
|
|
metricName := series.Metric.Type |
|
|
|
|
|
|
|
|
|
defaultMetricName := series.Metric.Type |
|
|
|
|
|
|
|
|
|
for key, value := range series.Metric.Labels { |
|
|
|
|
if !containsLabel(metricLabels[key], value) { |
|
|
|
|
metricLabels[key] = append(metricLabels[key], value) |
|
|
|
|
} |
|
|
|
|
if len(query.GroupBys) == 0 || containsLabel(query.GroupBys, "metric.label."+key) { |
|
|
|
|
metricName += " " + value |
|
|
|
|
defaultMetricName += " " + value |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -277,10 +289,12 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if containsLabel(query.GroupBys, "resource.label."+key) { |
|
|
|
|
metricName += " " + value |
|
|
|
|
defaultMetricName += " " + value |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
metricName := formatLegendKeys(series.Metric.Type, defaultMetricName, series.Metric.Labels, series.Resource.Labels, query) |
|
|
|
|
|
|
|
|
|
queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{ |
|
|
|
|
Name: metricName, |
|
|
|
|
Points: points, |
|
|
|
@ -303,6 +317,74 @@ func containsLabel(labels []string, newLabel string) bool { |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func formatLegendKeys(metricType string, defaultMetricName string, metricLabels map[string]string, resourceLabels map[string]string, query *StackdriverQuery) string { |
|
|
|
|
if query.AliasBy == "" { |
|
|
|
|
return defaultMetricName |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
result := legendKeyFormat.ReplaceAllFunc([]byte(query.AliasBy), func(in []byte) []byte { |
|
|
|
|
metaPartName := strings.Replace(string(in), "{{", "", 1) |
|
|
|
|
metaPartName = strings.Replace(metaPartName, "}}", "", 1) |
|
|
|
|
metaPartName = strings.TrimSpace(metaPartName) |
|
|
|
|
|
|
|
|
|
if metaPartName == "metric.type" { |
|
|
|
|
return []byte(metricType) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
metricPart := replaceWithMetricPart(metaPartName, metricType) |
|
|
|
|
|
|
|
|
|
if metricPart != nil { |
|
|
|
|
return metricPart |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
metaPartName = strings.Replace(metaPartName, "metric.label.", "", 1) |
|
|
|
|
|
|
|
|
|
if val, exists := metricLabels[metaPartName]; exists { |
|
|
|
|
return []byte(val) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
metaPartName = strings.Replace(metaPartName, "resource.label.", "", 1) |
|
|
|
|
|
|
|
|
|
if val, exists := resourceLabels[metaPartName]; exists { |
|
|
|
|
return []byte(val) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return in |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
return string(result) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func replaceWithMetricPart(metaPartName string, metricType string) []byte { |
|
|
|
|
// https://cloud.google.com/monitoring/api/v3/metrics-details#label_names
|
|
|
|
|
longMatches := longMetricNameFormat.FindStringSubmatch(metricType) |
|
|
|
|
shortMatches := shortMetricNameFormat.FindStringSubmatch(metricType) |
|
|
|
|
|
|
|
|
|
if metaPartName == "metric.name" { |
|
|
|
|
if len(longMatches) > 0 { |
|
|
|
|
return []byte(longMatches[3]) |
|
|
|
|
} else if len(shortMatches) > 0 { |
|
|
|
|
return []byte(shortMatches[2]) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if metaPartName == "metric.category" { |
|
|
|
|
if len(longMatches) > 0 { |
|
|
|
|
return []byte(longMatches[2]) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if metaPartName == "metric.service" { |
|
|
|
|
if len(longMatches) > 0 { |
|
|
|
|
return []byte(longMatches[1]) |
|
|
|
|
} else if len(shortMatches) > 0 { |
|
|
|
|
return []byte(shortMatches[1]) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (e *StackdriverExecutor) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) { |
|
|
|
|
u, _ := url.Parse(dsInfo.Url) |
|
|
|
|
u.Path = path.Join(u.Path, "render") |
|
|
|
|