mirror of
https://github.com/grafana/grafana.git
synced 2025-09-27 07:54:17 +08:00
GoogleCloudMonitoring: Refactor SLO queries (#59421)
* GoogleCloudMonitoring: Refactor metricType input * Remove preprocessor in favor of secondary inputs * GoogleCloudMonitoring: Refactor SLO queries * GoogleCloudMonitoring: Refactor util functions (#59482) * GoogleCloudMonitoring: Remove unnecessary util functions * GoogleCloudMonitoring: Refactor run function (#59505)
This commit is contained in:

committed by
GitHub

parent
4ec08c4317
commit
2e64ea652e
@ -7,14 +7,13 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-google-sdk-go/pkg/utils"
|
||||
"github.com/huandu/xstrings"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
|
||||
@ -25,7 +24,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -33,11 +31,9 @@ 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]+")
|
||||
cloudMonitoringUnitMappings = map[string]string{
|
||||
"bit": "bits",
|
||||
"By": "bytes",
|
||||
@ -333,33 +329,6 @@ func migrateRequest(req *backend.QueryDataRequest) error {
|
||||
q.JSON = b
|
||||
}
|
||||
|
||||
// SloQuery was merged into timeSeriesList
|
||||
if rawQuery["sloQuery"] != nil {
|
||||
if rawQuery["timeSeriesList"] == nil {
|
||||
rawQuery["timeSeriesList"] = &timeSeriesList{}
|
||||
}
|
||||
tsl := rawQuery["timeSeriesList"].(*timeSeriesList)
|
||||
sloq := rawQuery["sloQuery"].(map[string]interface{})
|
||||
if sloq["projectName"] != nil {
|
||||
tsl.ProjectName = sloq["projectName"].(string)
|
||||
}
|
||||
if sloq["alignmentPeriod"] != nil {
|
||||
tsl.AlignmentPeriod = sloq["alignmentPeriod"].(string)
|
||||
}
|
||||
if sloq["perSeriesAligner"] != nil {
|
||||
tsl.PerSeriesAligner = sloq["perSeriesAligner"].(string)
|
||||
}
|
||||
rawQuery["timeSeriesList"] = tsl
|
||||
b, err := json.Marshal(rawQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if q.QueryType == "" {
|
||||
q.QueryType = sloQueryType
|
||||
}
|
||||
q.JSON = b
|
||||
}
|
||||
|
||||
req.Queries[i] = q
|
||||
}
|
||||
|
||||
@ -437,16 +406,7 @@ func (s *Service) buildQueryExecutors(logger log.Logger, req *backend.QueryDataR
|
||||
return nil, fmt.Errorf("could not unmarshal CloudMonitoringQuery json: %w", err)
|
||||
}
|
||||
|
||||
params := url.Values{}
|
||||
params.Add("interval.startTime", startTime.UTC().Format(time.RFC3339))
|
||||
params.Add("interval.endTime", endTime.UTC().Format(time.RFC3339))
|
||||
|
||||
var queryInterface cloudMonitoringQueryExecutor
|
||||
cmtsf := &cloudMonitoringTimeSeriesList{
|
||||
refID: query.RefID,
|
||||
logger: logger,
|
||||
aliasBy: q.AliasBy,
|
||||
}
|
||||
switch query.QueryType {
|
||||
case metricQueryType, annotationQueryType:
|
||||
if q.TimeSeriesQuery != nil {
|
||||
@ -458,33 +418,33 @@ func (s *Service) buildQueryExecutors(logger log.Logger, req *backend.QueryDataR
|
||||
timeRange: req.Queries[0].TimeRange,
|
||||
}
|
||||
} else if q.TimeSeriesList != nil {
|
||||
cmtsf := &cloudMonitoringTimeSeriesList{
|
||||
refID: query.RefID,
|
||||
logger: logger,
|
||||
aliasBy: q.AliasBy,
|
||||
}
|
||||
if q.TimeSeriesList.View == "" {
|
||||
q.TimeSeriesList.View = "FULL"
|
||||
}
|
||||
cmtsf.parameters = q.TimeSeriesList
|
||||
params.Add("filter", buildFilterString(q.TimeSeriesList.Filters))
|
||||
params.Add("view", q.TimeSeriesList.View)
|
||||
setMetricAggParams(¶ms, q.TimeSeriesList, durationSeconds, query.Interval.Milliseconds())
|
||||
cmtsf.setParams(startTime, endTime, durationSeconds, query.Interval.Milliseconds())
|
||||
queryInterface = cmtsf
|
||||
} else {
|
||||
return nil, fmt.Errorf("missing query info")
|
||||
}
|
||||
case sloQueryType:
|
||||
cmtsf.sloQ = q.SloQuery
|
||||
cmtsf.parameters = q.TimeSeriesList
|
||||
params.Add("filter", buildSLOFilterExpression(q.TimeSeriesList.ProjectName, q.SloQuery))
|
||||
setSloAggParams(¶ms, q.SloQuery, q.TimeSeriesList.AlignmentPeriod, durationSeconds, query.Interval.Milliseconds())
|
||||
queryInterface = cmtsf
|
||||
cmslo := &cloudMonitoringSLO{
|
||||
refID: query.RefID,
|
||||
logger: logger,
|
||||
aliasBy: q.AliasBy,
|
||||
parameters: q.SloQuery,
|
||||
}
|
||||
cmslo.setParams(startTime, endTime, durationSeconds, query.Interval.Milliseconds())
|
||||
queryInterface = cmslo
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognized query type %q", query.QueryType)
|
||||
}
|
||||
|
||||
cmtsf.params = params
|
||||
|
||||
if setting.Env == setting.Dev {
|
||||
logger.Debug("CloudMonitoring request", "params", params)
|
||||
}
|
||||
|
||||
cloudMonitoringQueryExecutors = append(cloudMonitoringQueryExecutors, queryInterface)
|
||||
}
|
||||
|
||||
@ -501,7 +461,7 @@ func interpolateFilterWildcards(value string) string {
|
||||
value = strings.Replace(value, "*", "", 1)
|
||||
value = fmt.Sprintf(`ends_with("%s")`, value)
|
||||
case matches == 1 && strings.HasSuffix(value, "*"):
|
||||
value = reverse(strings.Replace(reverse(value), "*", "", 1))
|
||||
value = xstrings.Reverse(strings.Replace(xstrings.Reverse(value), "*", "", 1))
|
||||
value = fmt.Sprintf(`starts_with("%s")`, value)
|
||||
case matches != 0:
|
||||
value = string(wildcardRegexRe.ReplaceAllFunc([]byte(value), func(in []byte) []byte {
|
||||
@ -515,87 +475,6 @@ func interpolateFilterWildcards(value string) string {
|
||||
return value
|
||||
}
|
||||
|
||||
func buildFilterString(filterParts []string) string {
|
||||
filterString := ""
|
||||
for i, part := range filterParts {
|
||||
mod := i % 4
|
||||
switch {
|
||||
case part == "AND":
|
||||
filterString += " "
|
||||
case mod == 2:
|
||||
operator := filterParts[i-1]
|
||||
switch {
|
||||
case operator == "=~" || operator == "!=~":
|
||||
filterString = reverse(strings.Replace(reverse(filterString), "~", "", 1))
|
||||
filterString += fmt.Sprintf(`monitoring.regex.full_match("%s")`, part)
|
||||
case strings.Contains(part, "*"):
|
||||
filterString += interpolateFilterWildcards(part)
|
||||
default:
|
||||
filterString += fmt.Sprintf(`"%s"`, part)
|
||||
}
|
||||
default:
|
||||
filterString += part
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Trim(filterString, " ")
|
||||
}
|
||||
|
||||
func buildSLOFilterExpression(projectName string, q *sloQuery) string {
|
||||
sloName := fmt.Sprintf("projects/%s/services/%s/serviceLevelObjectives/%s", projectName, q.ServiceId, q.SloId)
|
||||
|
||||
if q.SelectorName == "select_slo_burn_rate" {
|
||||
return fmt.Sprintf(`%s("%s", "%s")`, q.SelectorName, sloName, q.LookbackPeriod)
|
||||
} else {
|
||||
return fmt.Sprintf(`%s("%s")`, q.SelectorName, sloName)
|
||||
}
|
||||
}
|
||||
|
||||
func setMetricAggParams(params *url.Values, query *timeSeriesList, durationSeconds int, intervalMs int64) {
|
||||
if query.CrossSeriesReducer == "" {
|
||||
query.CrossSeriesReducer = crossSeriesReducerDefault
|
||||
}
|
||||
|
||||
if query.PerSeriesAligner == "" {
|
||||
query.PerSeriesAligner = perSeriesAlignerDefault
|
||||
}
|
||||
|
||||
alignmentPeriod := calculateAlignmentPeriod(query.AlignmentPeriod, intervalMs, durationSeconds)
|
||||
params.Add("aggregation.alignmentPeriod", alignmentPeriod)
|
||||
if query.CrossSeriesReducer != "" {
|
||||
params.Add("aggregation.crossSeriesReducer", query.CrossSeriesReducer)
|
||||
}
|
||||
if query.PerSeriesAligner != "" {
|
||||
params.Add("aggregation.perSeriesAligner", query.PerSeriesAligner)
|
||||
}
|
||||
for _, groupBy := range query.GroupBys {
|
||||
params.Add("aggregation.groupByFields", groupBy)
|
||||
}
|
||||
|
||||
if query.SecondaryAlignmentPeriod != "" {
|
||||
secondaryAlignmentPeriod := calculateAlignmentPeriod(query.AlignmentPeriod, intervalMs, durationSeconds)
|
||||
params.Add("secondaryAggregation.alignmentPeriod", secondaryAlignmentPeriod)
|
||||
}
|
||||
if query.SecondaryCrossSeriesReducer != "" {
|
||||
params.Add("secondaryAggregation.crossSeriesReducer", query.SecondaryCrossSeriesReducer)
|
||||
}
|
||||
if query.SecondaryPerSeriesAligner != "" {
|
||||
params.Add("secondaryAggregation.perSeriesAligner", query.SecondaryPerSeriesAligner)
|
||||
}
|
||||
for _, groupBy := range query.SecondaryGroupBys {
|
||||
params.Add("secondaryAggregation.groupByFields", groupBy)
|
||||
}
|
||||
}
|
||||
|
||||
func setSloAggParams(params *url.Values, query *sloQuery, alignmentPeriod string, durationSeconds int, intervalMs int64) {
|
||||
params.Add("aggregation.alignmentPeriod", calculateAlignmentPeriod(alignmentPeriod, intervalMs, durationSeconds))
|
||||
if query.SelectorName == "select_slo_health" {
|
||||
params.Add("aggregation.perSeriesAligner", "ALIGN_MEAN")
|
||||
} else {
|
||||
params.Add("aggregation.perSeriesAligner", "ALIGN_NEXT_OLDER")
|
||||
}
|
||||
}
|
||||
|
||||
func calculateAlignmentPeriod(alignmentPeriod string, intervalMs int64, durationSeconds int) string {
|
||||
if alignmentPeriod == "grafana-auto" || alignmentPeriod == "" {
|
||||
alignmentPeriodValue := int(math.Max(float64(intervalMs)/1000, 60.0))
|
||||
@ -618,12 +497,12 @@ func calculateAlignmentPeriod(alignmentPeriod string, intervalMs int64, duration
|
||||
}
|
||||
|
||||
func formatLegendKeys(metricType string, defaultMetricName string, labels map[string]string,
|
||||
additionalLabels map[string]string, query *cloudMonitoringTimeSeriesList) string {
|
||||
if query.aliasBy == "" {
|
||||
additionalLabels map[string]string, query cloudMonitoringQueryExecutor) string {
|
||||
if query.getAliasBy() == "" {
|
||||
return defaultMetricName
|
||||
}
|
||||
|
||||
result := legendKeyFormat.ReplaceAllFunc([]byte(query.aliasBy), func(in []byte) []byte {
|
||||
result := legendKeyFormat.ReplaceAllFunc([]byte(query.getAliasBy()), func(in []byte) []byte {
|
||||
metaPartName := strings.Replace(string(in), "{{", "", 1)
|
||||
metaPartName = strings.Replace(metaPartName, "}}", "", 1)
|
||||
metaPartName = strings.TrimSpace(metaPartName)
|
||||
@ -646,20 +525,8 @@ func formatLegendKeys(metricType string, defaultMetricName string, labels map[st
|
||||
return []byte(val)
|
||||
}
|
||||
|
||||
if metaPartName == "project" && query.parameters.ProjectName != "" {
|
||||
return []byte(query.parameters.ProjectName)
|
||||
}
|
||||
|
||||
if metaPartName == "service" && query.sloQ.ServiceId != "" {
|
||||
return []byte(query.sloQ.ServiceId)
|
||||
}
|
||||
|
||||
if metaPartName == "slo" && query.sloQ.SloId != "" {
|
||||
return []byte(query.sloQ.SloId)
|
||||
}
|
||||
|
||||
if metaPartName == "selector" && query.sloQ.SelectorName != "" {
|
||||
return []byte(query.sloQ.SelectorName)
|
||||
if query.getParameter(metaPartName) != "" {
|
||||
return []byte(query.getParameter(metaPartName))
|
||||
}
|
||||
|
||||
return in
|
||||
@ -709,27 +576,11 @@ func calcBucketBound(bucketOptions cloudMonitoringBucketOptions, n int) string {
|
||||
return bucketBound
|
||||
}
|
||||
|
||||
func (s *Service) createRequest(logger log.Logger, dsInfo *datasourceInfo, proxyPass string, body io.Reader) (*http.Request, error) {
|
||||
u, err := url.Parse(dsInfo.url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (s *Service) ensureProject(ctx context.Context, dsInfo datasourceInfo, projectName string) (string, error) {
|
||||
if projectName != "" {
|
||||
return projectName, nil
|
||||
}
|
||||
u.Path = path.Join(u.Path, "render")
|
||||
|
||||
method := http.MethodGet
|
||||
if body != nil {
|
||||
method = http.MethodPost
|
||||
}
|
||||
req, err := http.NewRequest(method, dsInfo.services[cloudMonitor].url, body)
|
||||
if err != nil {
|
||||
logger.Error("Failed to create request", "error", err)
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.URL.Path = proxyPass
|
||||
|
||||
return req, nil
|
||||
return s.getDefaultProject(ctx, dsInfo)
|
||||
}
|
||||
|
||||
func (s *Service) getDefaultProject(ctx context.Context, dsInfo datasourceInfo) (string, error) {
|
||||
|
Reference in New Issue
Block a user