From 1a7cdac2c0167ea3b6aab4a28ebc00f1e29d4f9e Mon Sep 17 00:00:00 2001 From: Arthur Pitman Date: Sun, 16 Oct 2022 22:06:27 +0200 Subject: [PATCH 1/7] Add `MetricsUnitsClient` Signed-off-by: Arthur Pitman --- internal/dynatrace/metrics_units_client.go | 81 ++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 internal/dynatrace/metrics_units_client.go diff --git a/internal/dynatrace/metrics_units_client.go b/internal/dynatrace/metrics_units_client.go new file mode 100644 index 000000000..1e34d36fd --- /dev/null +++ b/internal/dynatrace/metrics_units_client.go @@ -0,0 +1,81 @@ +package dynatrace + +import ( + "context" + "encoding/json" + "net/url" + "strconv" +) + +// MetricsUnitsPath is the base endpoint for Metrics Units API +const MetricsUnitsPath = "/api/v2/units" + +// UnitConversionResult is the result of a conversion. +type UnitConversionResult struct { + UnitID string `json:"unitId"` + ResultValue float64 `json:"resultValue"` +} + +const ( + valueKey = "value" + targetUnitKey = "targetUnit" +) + +// MetricsUnitsClientConvertRequest encapsulates the request for the MetricsUnitsClient's Convert method. +type MetricsUnitsClientConvertRequest struct { + sourceUnitID string + value float64 + targetUnitID string +} + +// NewMetricsUnitsClientConvertRequest creates a new MetricsUnitsClientConvertRequest. +func NewMetricsUnitsClientConvertRequest(sourceUnitID string, value float64, targetUnitID string) MetricsUnitsClientConvertRequest { + return MetricsUnitsClientConvertRequest{ + sourceUnitID: sourceUnitID, + value: value, + targetUnitID: targetUnitID, + } +} + +// RequestString encodes MetricsUnitsClientConvertRequest into a request string. +func (q *MetricsUnitsClientConvertRequest) RequestString() string { + queryParameters := newQueryParameters() + queryParameters.add(valueKey, strconv.FormatFloat(q.value, 'f', -1, 64)) + queryParameters.add(targetUnitKey, q.targetUnitID) + + return MetricsUnitsPath + "/" + url.PathEscape(q.sourceUnitID) + "/convert?" + queryParameters.encode() +} + +// MetricsUnitsClientInterface defines functions for the Dynatrace Metrics Units endpoint. +type MetricsUnitsClientInterface interface { + // Convert converts a value between the specified units. + Convert(ctx context.Context, request MetricsUnitsClientConvertRequest) (float64, error) +} + +// MetricsUnitsClient is a client for interacting with Dynatrace Metrics Units endpoint. +type MetricsUnitsClient struct { + client ClientInterface +} + +// NewMetricsUnitsClient creates a new MetricsUnitsClient +func NewMetricsUnitsClient(client ClientInterface) *MetricsUnitsClient { + return &MetricsUnitsClient{ + client: client, + } +} + +// Convert converts a value between the specified units. +func (c *MetricsUnitsClient) Convert(ctx context.Context, request MetricsUnitsClientConvertRequest) (float64, error) { + body, err := c.client.Get(ctx, request.RequestString()) + if err != nil { + return 0, err + } + + var result UnitConversionResult + err = json.Unmarshal(body, &result) + if err != nil { + return 0, err + } + + return result.ResultValue, nil +} From b2ec263b9a9647e3bb60b9003b275f7a548b23d0 Mon Sep 17 00:00:00 2001 From: Arthur Pitman Date: Sun, 16 Oct 2022 22:59:42 +0200 Subject: [PATCH 2/7] Add `ConvertUnitMetricsProcessingDecorator` Signed-off-by: Arthur Pitman --- internal/dynatrace/metrics_processing.go | 52 ++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/internal/dynatrace/metrics_processing.go b/internal/dynatrace/metrics_processing.go index 11275595c..ada955287 100644 --- a/internal/dynatrace/metrics_processing.go +++ b/internal/dynatrace/metrics_processing.go @@ -357,3 +357,55 @@ func (p *RetryForSingleValueMetricsProcessingDecorator) modifyQuery(ctx context. return metrics.NewQuery("("+metricSelector+"):fold()", existingQuery.GetEntitySelector(), existingQuery.GetResolution(), existingQuery.GetMZSelector()) } + +// ConvertUnitMetricsProcessingDecorator decorates MetricsProcessing by converting the unit of the results. +type ConvertUnitMetricsProcessingDecorator struct { + metricsClient MetricsClientInterface + unitsClient MetricsUnitsClientInterface + targetUnitID string + metricsProcessing MetricsProcessingInterface +} + +// NewConvertUnitMetricsProcessingDecorator creates a new ConvertUnitMetricsProcessingDecorator using the specified client interfaces, target unit ID and underlying metrics processing interface. +func NewConvertUnitMetricsProcessingDecorator(metricsClient MetricsClientInterface, + unitsClient MetricsUnitsClientInterface, + targetUnitID string, + metricsProcessing MetricsProcessingInterface) *ConvertUnitMetricsProcessingDecorator { + return &ConvertUnitMetricsProcessingDecorator{ + metricsClient: metricsClient, + unitsClient: unitsClient, + targetUnitID: targetUnitID, + metricsProcessing: metricsProcessing, + } +} + +// ProcessRequest queries and processes metrics using the specified request. +func (p *ConvertUnitMetricsProcessingDecorator) ProcessRequest(ctx context.Context, request MetricsClientQueryRequest) (*MetricsProcessingResults, error) { + result, err := p.metricsProcessing.ProcessRequest(ctx, request) + + if err != nil { + return nil, err + } + + if p.targetUnitID == "" { + return result, nil + } + + metricSelector := result.request.query.GetMetricSelector() + metricDefinition, err := p.metricsClient.GetMetricDefinitionByID(ctx, metricSelector) + if err != nil { + return nil, err + } + + sourceUnitID := metricDefinition.Unit + convertedResults := make([]MetricsProcessingResult, len(result.Results())) + for i, r := range result.results { + v, err := p.unitsClient.Convert(ctx, NewMetricsUnitsClientConvertRequest(sourceUnitID, r.value, p.targetUnitID)) + if err != nil { + return nil, err + } + + convertedResults[i] = newMetricsProcessingResult(r.Name(), v) + } + return newMetricsProcessingResults(result.Request(), convertedResults, result.Warnings()), nil +} From 5bbe176bb947c8806c4d11df2448559f1d47f9ee Mon Sep 17 00:00:00 2001 From: Arthur Pitman Date: Wed, 19 Oct 2022 13:44:10 +0200 Subject: [PATCH 3/7] Remove unneeded Data Explorer tile fields Signed-off-by: Arthur Pitman --- internal/dynatrace/dashboard.go | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/internal/dynatrace/dashboard.go b/internal/dynatrace/dashboard.go index be4de6c66..6c616b7c3 100644 --- a/internal/dynatrace/dashboard.go +++ b/internal/dynatrace/dashboard.go @@ -133,26 +133,7 @@ type TileFilter struct { // DataExplorerQuery Query Definition for DATA_EXPLORER dashboard tile type DataExplorerQuery struct { - ID string `json:"id"` - Metric string `json:"metric"` - SpaceAggregation string `json:"spaceAggregation,omitempty"` - TimeAggregation string `json:"timeAggregation"` - SplitBy []string `json:"splitBy"` - FilterBy *DataExplorerFilter `json:"filterBy,omitempty"` -} - -type DataExplorerFilter struct { - Filter string `json:"filter,omitempty"` - FilterType string `json:"filterType,omitempty"` - FilterOperator string `json:"filterOperator,omitempty"` - EntityAttribute string `json:"entityAttribute,omitempty"` - NestedFilters []DataExplorerFilter `json:"nestedFilters"` - Criteria []DataExplorerCriterion `json:"criteria"` -} - -type DataExplorerCriterion struct { - Value string `json:"value"` - Evaluator string `json:"evaluator"` + ID string `json:"id"` } type FilterConfig struct { From d9247295f509af7c02edc040e31b29fb32a4500b Mon Sep 17 00:00:00 2001 From: Arthur Pitman Date: Wed, 19 Oct 2022 16:16:03 +0200 Subject: [PATCH 4/7] Rename several dashboard types Signed-off-by: Arthur Pitman --- internal/dynatrace/dashboard.go | 72 +++++++++---------- .../sli/dashboard/data_explorer_thresholds.go | 4 +- .../data_explorer_tile_processing.go | 4 +- ...trics_from_dashboard_data_explorer_test.go | 32 ++++----- 4 files changed, 56 insertions(+), 56 deletions(-) diff --git a/internal/dynatrace/dashboard.go b/internal/dynatrace/dashboard.go index 6c616b7c3..a02f8dbd1 100644 --- a/internal/dynatrace/dashboard.go +++ b/internal/dynatrace/dashboard.go @@ -74,47 +74,47 @@ type ManagementZoneEntry struct { } type Tile struct { - Name string `json:"name"` - TileType string `json:"tileType"` - Configured bool `json:"configured"` - Query string `json:"query,omitempty"` - Type string `json:"type,omitempty"` - CustomName string `json:"customName,omitempty"` - Markdown string `json:"markdown,omitempty"` - ChartVisible bool `json:"chartVisible,omitempty"` - Bounds Bounds `json:"bounds"` - TileFilter TileFilter `json:"tileFilter"` - Queries []DataExplorerQuery `json:"queries,omitempty"` - AssignedEntities []string `json:"assignedEntities,omitempty"` - ExcludeMaintenanceWindows bool `json:"excludeMaintenanceWindows,omitempty"` - FilterConfig *FilterConfig `json:"filterConfig,omitempty"` - VisualConfig *VisualConfig `json:"visualConfig,omitempty"` - MetricExpressions []string `json:"metricExpressions,omitempty"` -} - -// VisualConfig is the visual configuration for a dashboard tile. -type VisualConfig struct { - Type string `json:"type,omitempty"` - Thresholds []Threshold `json:"thresholds,omitempty"` - Rules []VisualConfigRule `json:"rules,omitempty"` -} - -// SingleValueVisualConfigType is the single value visualization type for VisualConfigs -const SingleValueVisualConfigType = "SINGLE_VALUE" - -// VisualConfigRule is a rule for the visual configuration. -type VisualConfigRule struct { + Name string `json:"name"` + TileType string `json:"tileType"` + Configured bool `json:"configured"` + Query string `json:"query,omitempty"` + Type string `json:"type,omitempty"` + CustomName string `json:"customName,omitempty"` + Markdown string `json:"markdown,omitempty"` + ChartVisible bool `json:"chartVisible,omitempty"` + Bounds Bounds `json:"bounds"` + TileFilter TileFilter `json:"tileFilter"` + Queries []DataExplorerQuery `json:"queries,omitempty"` + AssignedEntities []string `json:"assignedEntities,omitempty"` + ExcludeMaintenanceWindows bool `json:"excludeMaintenanceWindows,omitempty"` + FilterConfig *FilterConfig `json:"filterConfig,omitempty"` + VisualConfig *VisualizationConfiguration `json:"visualConfig,omitempty"` + MetricExpressions []string `json:"metricExpressions,omitempty"` +} + +// VisualizationConfiguration is the visual configuration for a dashboard tile. +type VisualizationConfiguration struct { + Type string `json:"type,omitempty"` + Thresholds []VisualizationThreshold `json:"thresholds,omitempty"` + Rules []VisualizationRule `json:"rules,omitempty"` +} + +// SingleValueVisualizationConfigurationType is the single value visualization type for VisualConfigs +const SingleValueVisualizationConfigurationType = "SINGLE_VALUE" + +// VisualizationRule is a rule for the visual configuration. +type VisualizationRule struct { UnitTransform string `json:"unitTransform,omitempty"` } -// Threshold is a threshold configuration for a Data Explorer tile. -type Threshold struct { - Visible bool `json:"visible"` - Rules []ThresholdRule `json:"rules,omitempty"` +// VisualizationThreshold is a threshold configuration for a Data Explorer tile. +type VisualizationThreshold struct { + Visible bool `json:"visible"` + Rules []VisualizationThresholdRule `json:"rules,omitempty"` } -// ThresholdRule is a rule for a threshold. -type ThresholdRule struct { +// VisualizationThresholdRule is a rule for a threshold. +type VisualizationThresholdRule struct { Value *float64 `json:"value,omitempty"` Color string `json:"color"` } diff --git a/internal/sli/dashboard/data_explorer_thresholds.go b/internal/sli/dashboard/data_explorer_thresholds.go index 720ce3d3e..f4676237c 100644 --- a/internal/sli/dashboard/data_explorer_thresholds.go +++ b/internal/sli/dashboard/data_explorer_thresholds.go @@ -176,7 +176,7 @@ func tryGetThresholdPassAndWarningCriteria(tile *dynatrace.Tile) (*passAndWarnin } // areThresholdsEnabled returns true if a user has set thresholds that will be displayed, i.e. if thresholds are visible and at least one value has been set. -func areThresholdsEnabled(threshold *dynatrace.Threshold) bool { +func areThresholdsEnabled(threshold *dynatrace.VisualizationThreshold) bool { if !threshold.Visible { return false } @@ -191,7 +191,7 @@ func areThresholdsEnabled(threshold *dynatrace.Threshold) bool { } // convertThresholdRulesToThresholdConfiguration checks that the threshold rules are complete and returns them as a threshold configuration or returns an error. -func convertThresholdRulesToThresholdConfiguration(rules []dynatrace.ThresholdRule) (*thresholdConfiguration, error) { +func convertThresholdRulesToThresholdConfiguration(rules []dynatrace.VisualizationThresholdRule) (*thresholdConfiguration, error) { var errs []error if len(rules) != 3 { diff --git a/internal/sli/dashboard/data_explorer_tile_processing.go b/internal/sli/dashboard/data_explorer_tile_processing.go index 4a14ededd..2ccbeb371 100644 --- a/internal/sli/dashboard/data_explorer_tile_processing.go +++ b/internal/sli/dashboard/data_explorer_tile_processing.go @@ -103,7 +103,7 @@ func validateDataExplorerTile(tile *dynatrace.Tile) error { return validateDataExplorerVisualConfigurationRule(tile.VisualConfig.Rules[0]) } -func validateDataExplorerVisualConfigurationRule(rule dynatrace.VisualConfigRule) error { +func validateDataExplorerVisualConfigurationRule(rule dynatrace.VisualizationRule) error { if rule.UnitTransform != "" { return fmt.Errorf("Data Explorer query unit must be set to 'Auto' rather than '%s'", rule.UnitTransform) } @@ -115,7 +115,7 @@ func (p *DataExplorerTileProcessing) createMetricsQueryProcessingForTile(tile *d return NewMetricsQueryProcessing(p.client) } - if tile.VisualConfig.Type == dynatrace.SingleValueVisualConfigType { + if tile.VisualConfig.Type == dynatrace.SingleValueVisualizationConfigurationType { return NewMetricsQueryProcessingThatAllowsOnlyOneResult(p.client) } diff --git a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go index 063ee4342..4f65bfc88 100644 --- a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go +++ b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go @@ -363,7 +363,7 @@ func TestRetrieveMetricsFromDashboardDataExplorerTile_TileThresholdsWork(t *test tests := []struct { name string tileName string - thresholds dynatrace.Threshold + thresholds dynatrace.VisualizationThreshold expectedSLO *keptnapi.SLO }{ { @@ -434,40 +434,40 @@ func TestRetrieveMetricsFromDashboardDataExplorerTile_TileThresholdsWork(t *test } } -func createPassThresholdRule(value float64) dynatrace.ThresholdRule { +func createPassThresholdRule(value float64) dynatrace.VisualizationThresholdRule { return createPassThresholdRuleWithPointer(&value) } -func createPassThresholdRuleWithPointer(value *float64) dynatrace.ThresholdRule { - return dynatrace.ThresholdRule{Value: value, Color: "#7dc540"} +func createPassThresholdRuleWithPointer(value *float64) dynatrace.VisualizationThresholdRule { + return dynatrace.VisualizationThresholdRule{Value: value, Color: "#7dc540"} } -func createWarnThresholdRule(value float64) dynatrace.ThresholdRule { +func createWarnThresholdRule(value float64) dynatrace.VisualizationThresholdRule { return createWarnThresholdRuleWithPointer(&value) } -func createWarnThresholdRuleWithPointer(value *float64) dynatrace.ThresholdRule { - return dynatrace.ThresholdRule{Value: value, Color: "#f5d30f"} +func createWarnThresholdRuleWithPointer(value *float64) dynatrace.VisualizationThresholdRule { + return dynatrace.VisualizationThresholdRule{Value: value, Color: "#f5d30f"} } -func createFailThresholdRule(value float64) dynatrace.ThresholdRule { +func createFailThresholdRule(value float64) dynatrace.VisualizationThresholdRule { return createFailThresholdRuleWithPointer(&value) } -func createFailThresholdRuleWithPointer(value *float64) dynatrace.ThresholdRule { - return dynatrace.ThresholdRule{Value: value, Color: "#dc172a"} +func createFailThresholdRuleWithPointer(value *float64) dynatrace.VisualizationThresholdRule { + return dynatrace.VisualizationThresholdRule{Value: value, Color: "#dc172a"} } -func createVisibleThresholds(rule1 dynatrace.ThresholdRule, rule2 dynatrace.ThresholdRule, rule3 dynatrace.ThresholdRule) dynatrace.Threshold { - return dynatrace.Threshold{ - Rules: []dynatrace.ThresholdRule{rule1, rule2, rule3}, +func createVisibleThresholds(rule1 dynatrace.VisualizationThresholdRule, rule2 dynatrace.VisualizationThresholdRule, rule3 dynatrace.VisualizationThresholdRule) dynatrace.VisualizationThreshold { + return dynatrace.VisualizationThreshold{ + Rules: []dynatrace.VisualizationThresholdRule{rule1, rule2, rule3}, Visible: true, } } -func createNotVisibleThresholds(rule1 dynatrace.ThresholdRule, rule2 dynatrace.ThresholdRule, rule3 dynatrace.ThresholdRule) dynatrace.Threshold { - return dynatrace.Threshold{ - Rules: []dynatrace.ThresholdRule{rule1, rule2, rule3}, +func createNotVisibleThresholds(rule1 dynatrace.VisualizationThresholdRule, rule2 dynatrace.VisualizationThresholdRule, rule3 dynatrace.VisualizationThresholdRule) dynatrace.VisualizationThreshold { + return dynatrace.VisualizationThreshold{ + Rules: []dynatrace.VisualizationThresholdRule{rule1, rule2, rule3}, Visible: false, } } From b6186ce93724743a3c2232ae0ea716aad3625d41 Mon Sep 17 00:00:00 2001 From: Arthur Pitman Date: Thu, 20 Oct 2022 11:01:27 +0200 Subject: [PATCH 5/7] Collect multiple Data Explorer configuration problems into a single error Signed-off-by: Arthur Pitman --- internal/dynatrace/dashboard.go | 4 +- .../custom_charting_tile_processing.go | 11 +- .../sli/dashboard/data_explorer_thresholds.go | 55 +++--- .../data_explorer_tile_processing.go | 172 +++++++++++++----- .../sli/dashboard/slo_definition_parsing.go | 51 +++--- .../dashboard/slo_definition_parsing_test.go | 32 +++- .../sli/dashboard/usql_tile_processing.go | 11 +- ...trics_from_dashboard_data_explorer_test.go | 13 +- .../dashboard.json | 130 +++++++++++++ 9 files changed, 367 insertions(+), 112 deletions(-) create mode 100644 internal/sli/testdata/dashboards/data_explorer/multiple_tile_configuration_problems/dashboard.json diff --git a/internal/dynatrace/dashboard.go b/internal/dynatrace/dashboard.go index a02f8dbd1..36d342012 100644 --- a/internal/dynatrace/dashboard.go +++ b/internal/dynatrace/dashboard.go @@ -105,6 +105,7 @@ const SingleValueVisualizationConfigurationType = "SINGLE_VALUE" // VisualizationRule is a rule for the visual configuration. type VisualizationRule struct { UnitTransform string `json:"unitTransform,omitempty"` + Matcher string `json:"matcher,omitempty"` } // VisualizationThreshold is a threshold configuration for a Data Explorer tile. @@ -133,7 +134,8 @@ type TileFilter struct { // DataExplorerQuery Query Definition for DATA_EXPLORER dashboard tile type DataExplorerQuery struct { - ID string `json:"id"` + ID string `json:"id"` + Enabled bool `json:"enabled"` } type FilterConfig struct { diff --git a/internal/sli/dashboard/custom_charting_tile_processing.go b/internal/sli/dashboard/custom_charting_tile_processing.go index 131773993..19493501b 100644 --- a/internal/sli/dashboard/custom_charting_tile_processing.go +++ b/internal/sli/dashboard/custom_charting_tile_processing.go @@ -43,12 +43,7 @@ func (p *CustomChartingTileProcessing) Process(ctx context.Context, tile *dynatr } sloDefinitionParsingResult, err := parseSLODefinition(tile.FilterConfig.CustomName) - var sloDefError *sloDefinitionError - if errors.As(err, &sloDefError) { - return []TileResult{newFailedTileResultFromError(sloDefError.sliNameOrTileTitle(), "Custom charting tile title parsing error", err)} - } - - if sloDefinitionParsingResult.exclude { + if (err == nil) && (sloDefinitionParsingResult.exclude) { log.WithField("tile.FilterConfig.CustomName", tile.FilterConfig.CustomName).Debug("Tile excluded as name includes exclude=true") return nil } @@ -59,6 +54,10 @@ func (p *CustomChartingTileProcessing) Process(ctx context.Context, tile *dynatr return nil } + if err != nil { + return []TileResult{newFailedTileResultFromSLODefinition(sloDefinition, "Custom charting tile title parsing error: "+err.Error())} + } + // get the tile specific management zone filter that might be needed by different tile processors // Check for tile management zone filter - this would overwrite the dashboardManagementZoneFilter tileManagementZoneFilter := NewManagementZoneFilter(dashboardFilter, tile.TileFilter.ManagementZone) diff --git a/internal/sli/dashboard/data_explorer_thresholds.go b/internal/sli/dashboard/data_explorer_thresholds.go index f4676237c..46db389e1 100644 --- a/internal/sli/dashboard/data_explorer_thresholds.go +++ b/internal/sli/dashboard/data_explorer_thresholds.go @@ -1,7 +1,6 @@ package dashboard import ( - "errors" "fmt" "strings" @@ -90,16 +89,16 @@ type threshold struct { value float64 } -type thresholdParsingErrors struct { +type thresholdParsingError struct { errors []error } -func (err *thresholdParsingErrors) Error() string { +func (err *thresholdParsingError) Error() string { var errStrings = make([]string, len(err.errors)) for i, e := range err.errors { errStrings[i] = e.Error() } - return strings.Join(errStrings, "; ") + return fmt.Sprintf("error parsing thresholds: %s", strings.Join(errStrings, "; ")) } type incorrectThresholdRuleCountError struct { @@ -116,7 +115,7 @@ type invalidThresholdColorError struct { } func (err *invalidThresholdColorError) Error() string { - return fmt.Sprintf("invalid color %s at position %d ", err.color, err.position) + return fmt.Sprintf("invalid color %s at position %d", err.color, err.position) } type missingThresholdValueError struct { @@ -124,7 +123,7 @@ type missingThresholdValueError struct { } func (err *missingThresholdValueError) Error() string { - return fmt.Sprintf("missing value at position %d ", err.position) + return fmt.Sprintf("missing value at position %d", err.position) } type strictlyMonotonicallyIncreasingConstraintError struct { @@ -149,34 +148,48 @@ func (err *invalidThresholdColorSequenceError) Error() string { // tryGetThresholdPassAndWarningCriteria tries to get pass and warning criteria defined using the thresholds placed on a Data Explorer tile. // It returns either the criteria and no error (conversion succeeded), nil for the criteria and no error (no threshold set), or nil for the criteria and an error (conversion failed). func tryGetThresholdPassAndWarningCriteria(tile *dynatrace.Tile) (*passAndWarningCriteria, error) { - if tile.VisualConfig == nil { + thresholdRules, err := getThresholdRulesFromTile(tile) + if err != nil { + return nil, err + } + + if thresholdRules == nil { return nil, nil } - visualConfig := tile.VisualConfig - if len(visualConfig.Thresholds) == 0 { + thresholdConfiguration, err := convertThresholdRulesToThresholdConfiguration(thresholdRules) + if err != nil { + return nil, err + } + + return convertThresholdConfigurationToPassAndWarningCriteria(*thresholdConfiguration) +} + +func getThresholdRulesFromTile(tile *dynatrace.Tile) ([]dynatrace.VisualizationThresholdRule, error) { + if tile.VisualConfig == nil { return nil, nil } - if len(visualConfig.Thresholds) > 1 { - return nil, errors.New("too many threshold configurations") + visibleThresholdRules := make([][]dynatrace.VisualizationThresholdRule, 0, len(tile.VisualConfig.Thresholds)) + for _, t := range tile.VisualConfig.Thresholds { + if areThresholdsVisible(t) { + visibleThresholdRules = append(visibleThresholdRules, t.Rules) + } } - t := &visualConfig.Thresholds[0] - if !areThresholdsEnabled(t) { + if len(visibleThresholdRules) == 0 { return nil, nil } - thresholdConfiguration, err := convertThresholdRulesToThresholdConfiguration(t.Rules) - if err != nil { - return nil, err + if len(visibleThresholdRules) > 1 { + return nil, fmt.Errorf("Data Explorer tile has %d visible thresholds but only one is supported", len(visibleThresholdRules)) } - return convertThresholdConfigurationToPassAndWarningCriteria(*thresholdConfiguration) + return visibleThresholdRules[0], nil } -// areThresholdsEnabled returns true if a user has set thresholds that will be displayed, i.e. if thresholds are visible and at least one value has been set. -func areThresholdsEnabled(threshold *dynatrace.VisualizationThreshold) bool { +// areThresholdsVisible returns true if a user has set thresholds that will be displayed, i.e. if thresholds are visible and at least one value has been set. +func areThresholdsVisible(threshold dynatrace.VisualizationThreshold) bool { if !threshold.Visible { return false } @@ -212,7 +225,7 @@ func convertThresholdRulesToThresholdConfiguration(rules []dynatrace.Visualizati } if len(errs) > 0 { - return nil, &thresholdParsingErrors{errors: errs} + return nil, &thresholdParsingError{errors: errs} } return &thresholdConfiguration{ @@ -243,7 +256,7 @@ func convertThresholdConfigurationToPassAndWarningCriteria(t thresholdConfigurat } if len(errs) > 0 { - return nil, &thresholdParsingErrors{errors: errs} + return nil, &thresholdParsingError{errors: errs} } return sloCriteria, nil diff --git a/internal/sli/dashboard/data_explorer_tile_processing.go b/internal/sli/dashboard/data_explorer_tile_processing.go index 2ccbeb371..065888e18 100644 --- a/internal/sli/dashboard/data_explorer_tile_processing.go +++ b/internal/sli/dashboard/data_explorer_tile_processing.go @@ -36,27 +36,80 @@ func NewDataExplorerTileProcessing(client dynatrace.ClientInterface, eventData a // Process processes the specified Data Explorer dashboard tile. func (p *DataExplorerTileProcessing) Process(ctx context.Context, tile *dynatrace.Tile, dashboardFilter *dynatrace.DashboardFilter) []TileResult { - sloDefinitionParsingResult, err := parseSLODefinition(tile.Name) - var sloDefError *sloDefinitionError - if errors.As(err, &sloDefError) { - return []TileResult{newFailedTileResultFromError(sloDefError.sliNameOrTileTitle(), "Data Explorer tile title parsing error", err)} + validatedDataExplorerTile, err := newDataExplorerTileValidator(tile, dashboardFilter).tryValidate() + var validationErr *dataExplorerTileValidationError + if errors.As(err, &validationErr) { + return []TileResult{newFailedTileResultFromSLODefinition(validationErr.sloDefinition, err.Error())} } - if sloDefinitionParsingResult.exclude { - log.WithField("tileName", tile.Name).Debug("Tile excluded as name includes exclude=true") - return nil + if validatedDataExplorerTile == nil { + return []TileResult{} + } + + return p.createMetricsQueryProcessing(validatedDataExplorerTile).Process(ctx, validatedDataExplorerTile.sloDefinition, validatedDataExplorerTile.query, p.timeframe) +} + +func (p *DataExplorerTileProcessing) createMetricsQueryProcessing(validatedTile *validatedDataExplorerTile) *MetricsQueryProcessing { + if validatedTile.singleValueVisualization { + return NewMetricsQueryProcessingThatAllowsOnlyOneResult(p.client) + } + + return NewMetricsQueryProcessing(p.client) +} + +type dataExplorerTileValidationError struct { + sloDefinition keptnapi.SLO + errors []error +} + +func (err *dataExplorerTileValidationError) Error() string { + var errStrings = make([]string, len(err.errors)) + for i, e := range err.errors { + errStrings[i] = e.Error() + } + return fmt.Sprintf("error validating Data Explorer tile: %s", strings.Join(errStrings, "; ")) +} + +type dataExplorerTileValidator struct { + tile *dynatrace.Tile + dashboardFilter *dynatrace.DashboardFilter +} + +func newDataExplorerTileValidator(tile *dynatrace.Tile, dashboardFilter *dynatrace.DashboardFilter) *dataExplorerTileValidator { + return &dataExplorerTileValidator{ + tile: tile, + dashboardFilter: dashboardFilter, + } +} + +func (v *dataExplorerTileValidator) tryValidate() (*validatedDataExplorerTile, error) { + sloDefinitionParsingResult, err := parseSLODefinition(v.tile.Name) + if (err == nil) && (sloDefinitionParsingResult.exclude) { + log.WithField("tileName", v.tile.Name).Debug("Tile excluded as name includes exclude=true") + return nil, nil } sloDefinition := sloDefinitionParsingResult.sloDefinition + if sloDefinition.SLI == "" { - log.WithField("tileName", tile.Name).Debug("Omitted Data Explorer tile as no SLI name could be derived") - return nil + log.WithField("tileName", v.tile.Name).Debug("Omitted Data Explorer tile as no SLI name could be derived") + return nil, nil + } + + var errs []error + if err != nil { + errs = append(errs, err) + } + + queryID, err := getQueryID(v.tile.Queries) + if err != nil { + errs = append(errs, err) } if (len(sloDefinition.Pass) == 0) && (len(sloDefinition.Warning) == 0) { - criteria, err := tryGetThresholdPassAndWarningCriteria(tile) + criteria, err := tryGetThresholdPassAndWarningCriteria(v.tile) if err != nil { - return []TileResult{newFailedTileResultFromSLODefinition(sloDefinition, fmt.Sprintf("Invalid Data Explorer tile thresholds: %s", err.Error()))} + errs = append(errs, err) } if criteria != nil { @@ -65,64 +118,85 @@ func (p *DataExplorerTileProcessing) Process(ctx context.Context, tile *dynatrac } } - err = validateDataExplorerTile(tile) + query, err := createMetricsQueryForMetricExpressions(v.tile.MetricExpressions, NewManagementZoneFilter(v.dashboardFilter, v.tile.TileFilter.ManagementZone)) if err != nil { - return []TileResult{newFailedTileResultFromSLODefinition(sloDefinition, err.Error())} + log.WithError(err).Warn("createMetricsQueryForMetricExpressions returned an error, SLI will not be used") + errs = append(errs, err) } - // get the tile specific management zone filter that might be needed by different tile processors - // Check for tile management zone filter - this would overwrite the dashboardManagementZoneFilter - managementZoneFilter := NewManagementZoneFilter(dashboardFilter, tile.TileFilter.ManagementZone) + // temporarily require unit set to Auto + targetUnitID := getUnitTransform(v.tile.VisualConfig, queryID) + if targetUnitID != "" { + err := fmt.Errorf("Data Explorer query unit must be set to 'Auto' rather than '%s'", targetUnitID) + errs = append(errs, err) + } - query, err := p.createMetricsQueryForMetricExpressions(tile.MetricExpressions, managementZoneFilter) - if err != nil { - log.WithError(err).Warn("generateMetricQueryFromMetricExpressions returned an error, SLI will not be used") - return []TileResult{newFailedTileResultFromSLODefinition(sloDefinition, "Data Explorer tile could not be converted to a metrics query: "+err.Error())} + if len(errs) > 0 { + return nil, &dataExplorerTileValidationError{ + sloDefinition: sloDefinition, + errors: errs, + } } - return p.createMetricsQueryProcessingForTile(tile).Process(ctx, sloDefinition, *query, p.timeframe) + return &validatedDataExplorerTile{ + sloDefinition: sloDefinition, + targetUnitID: targetUnitID, + singleValueVisualization: isSingleValueVisualizationType(v.tile.VisualConfig), + query: *query, + }, nil } -func validateDataExplorerTile(tile *dynatrace.Tile) error { - if len(tile.Queries) != 1 { - return fmt.Errorf("Data Explorer tile must have exactly one query") +// getQueryID gets the single enabled query ID or returns an error. +func getQueryID(queries []dynatrace.DataExplorerQuery) (string, error) { + if len(queries) == 0 { + return "", errors.New("Data Explorer tile has no query") } - if tile.VisualConfig == nil { - return nil + enabledQueryIDs := make([]string, 0, len(queries)) + for _, q := range queries { + if q.Enabled { + enabledQueryIDs = append(enabledQueryIDs, q.ID) + } } - if len(tile.VisualConfig.Rules) == 0 { - return nil + if len(enabledQueryIDs) == 0 { + return "", errors.New("Data Explorer tile has no query enabled") } - if len(tile.VisualConfig.Rules) > 1 { - return fmt.Errorf("Data Explorer tile must have exactly one visual configuration rule") + if len(enabledQueryIDs) > 1 { + return "", fmt.Errorf("Data Explorer tile has %d queries enabled but only one is supported", len(enabledQueryIDs)) } - return validateDataExplorerVisualConfigurationRule(tile.VisualConfig.Rules[0]) + return enabledQueryIDs[0], nil } -func validateDataExplorerVisualConfigurationRule(rule dynatrace.VisualizationRule) error { - if rule.UnitTransform != "" { - return fmt.Errorf("Data Explorer query unit must be set to 'Auto' rather than '%s'", rule.UnitTransform) +func getUnitTransform(visualConfig *dynatrace.VisualizationConfiguration, queryID string) string { + if visualConfig == nil { + return "" } - return nil -} -func (p *DataExplorerTileProcessing) createMetricsQueryProcessingForTile(tile *dynatrace.Tile) *MetricsQueryProcessing { - if tile.VisualConfig == nil { - return NewMetricsQueryProcessing(p.client) + queryMatcher := createQueryMatcher(queryID) + for _, r := range visualConfig.Rules { + if r.Matcher == queryMatcher { + return r.UnitTransform + } } + return "" +} - if tile.VisualConfig.Type == dynatrace.SingleValueVisualizationConfigurationType { - return NewMetricsQueryProcessingThatAllowsOnlyOneResult(p.client) +func createQueryMatcher(queryID string) string { + return queryID + ":" +} + +func isSingleValueVisualizationType(visualConfig *dynatrace.VisualizationConfiguration) bool { + if visualConfig == nil { + return false } - return NewMetricsQueryProcessing(p.client) + return visualConfig.Type == dynatrace.SingleValueVisualizationConfigurationType } -func (p *DataExplorerTileProcessing) createMetricsQueryForMetricExpressions(metricExpressions []string, managementZoneFilter *ManagementZoneFilter) (*metrics.Query, error) { +func createMetricsQueryForMetricExpressions(metricExpressions []string, managementZoneFilter *ManagementZoneFilter) (*metrics.Query, error) { if len(metricExpressions) == 0 { return nil, errors.New("Data Explorer tile has no metric expressions") } @@ -131,16 +205,15 @@ func (p *DataExplorerTileProcessing) createMetricsQueryForMetricExpressions(metr log.WithField("metricExpressions", metricExpressions).Warn("processMetricExpressions found more than 2 metric expressions") } - return p.createMetricsQueryForMetricExpression(metricExpressions[0], managementZoneFilter) + return createMetricsQueryForMetricExpression(metricExpressions[0], managementZoneFilter) } -func (p *DataExplorerTileProcessing) createMetricsQueryForMetricExpression(metricExpression string, managementZoneFilter *ManagementZoneFilter) (*metrics.Query, error) { +func createMetricsQueryForMetricExpression(metricExpression string, managementZoneFilter *ManagementZoneFilter) (*metrics.Query, error) { pieces := strings.SplitN(metricExpression, "&", 2) if len(pieces) != 2 { return nil, fmt.Errorf("metric expression does not contain two components: %s", metricExpression) } - // TODO: 2022-08-24: support resolutions other than auto, encoded as null, assumed here to be the same as resolution inf. resolution, err := parseResolutionKeyValuePair(pieces[0]) if err != nil { return nil, fmt.Errorf("could not parse resolution metric expression component: %w", err) @@ -167,3 +240,10 @@ func parseResolutionKeyValuePair(keyValuePair string) (string, error) { return resolution, nil } + +type validatedDataExplorerTile struct { + sloDefinition keptnapi.SLO + targetUnitID string + singleValueVisualization bool + query metrics.Query +} diff --git a/internal/sli/dashboard/slo_definition_parsing.go b/internal/sli/dashboard/slo_definition_parsing.go index 0c998d8f2..addb07809 100644 --- a/internal/sli/dashboard/slo_definition_parsing.go +++ b/internal/sli/dashboard/slo_definition_parsing.go @@ -24,14 +24,18 @@ type sloDefinitionParsingResult struct { } // parseSLODefinition takes a value such as -// Example 1: Some description;sli=teststep_rt;pass=<500ms,<+10%;warning=<1000ms,<+20%;weight=1;key=true -// Example 2: Response time (P95);sli=svc_rt_p95;pass=<+10%,<600 -// Example 3: Host Disk Queue Length (max);sli=host_disk_queue;pass=<=0;warning=<1;key=false +// +// Example 1: Some description;sli=teststep_rt;pass=<500ms,<+10%;warning=<1000ms,<+20%;weight=1;key=true +// Example 2: Response time (P95);sli=svc_rt_p95;pass=<+10%,<600 +// Example 3: Host Disk Queue Length (max);sli=host_disk_queue;pass=<=0;warning=<1;key=false +// // can also take a value like -// "KQG;project=myproject;pass=90%;warning=75%;" +// +// "KQG;project=myproject;pass=90%;warning=75%;" +// // This will return a SLO object or an error if parsing was not possible -func parseSLODefinition(sloDefinition string) (*sloDefinitionParsingResult, error) { - result := &sloDefinitionParsingResult{ +func parseSLODefinition(sloDefinition string) (sloDefinitionParsingResult, error) { + result := sloDefinitionParsingResult{ sloDefinition: keptncommon.SLO{ Weight: 1, KeySLI: false, @@ -50,7 +54,6 @@ func parseSLODefinition(sloDefinition string) (*sloDefinitionParsingResult, erro continue } - var err error switch strings.ToLower(kv.key) { case sloDefSli: if keyFound[sloDefSli] { @@ -92,10 +95,12 @@ func parseSLODefinition(sloDefinition string) (*sloDefinitionParsingResult, erro } keyFound[sloDefKey] = true - result.sloDefinition.KeySLI, err = strconv.ParseBool(kv.value) + val, err := strconv.ParseBool(kv.value) if err != nil { errs = append(errs, fmt.Errorf("invalid definition for '%s': not a boolean value: %v", sloDefKey, kv.value)) + break } + result.sloDefinition.KeySLI = val case sloDefWeight: if keyFound[sloDefWeight] { @@ -104,10 +109,12 @@ func parseSLODefinition(sloDefinition string) (*sloDefinitionParsingResult, erro } keyFound[sloDefWeight] = true - result.sloDefinition.Weight, err = strconv.Atoi(kv.value) + val, err := strconv.Atoi(kv.value) if err != nil { errs = append(errs, fmt.Errorf("invalid definition for '%s': not an integer value: %v", sloDefWeight, kv.value)) + break } + result.sloDefinition.Weight = val case sloDefExclude: if keyFound[sloDefExclude] { @@ -116,10 +123,12 @@ func parseSLODefinition(sloDefinition string) (*sloDefinitionParsingResult, erro } keyFound[sloDefExclude] = true - result.exclude, err = strconv.ParseBool(kv.value) + val, err := strconv.ParseBool(kv.value) if err != nil { errs = append(errs, fmt.Errorf("invalid definition for '%s': not a boolean value: %v", sloDefExclude, kv.value)) + break } + result.exclude = val } } @@ -128,10 +137,9 @@ func parseSLODefinition(sloDefinition string) (*sloDefinitionParsingResult, erro } if len(errs) > 0 { - return nil, &sloDefinitionError{ - sliName: result.sloDefinition.SLI, - tileTitle: sloDefinition, - errors: errs, + + return result, &sloDefinitionError{ + errors: errs, } } @@ -162,9 +170,7 @@ func criterionIsNotValid(criterion string) bool { // sloDefinitionError represents an error that occurred while parsing an SLO definition type sloDefinitionError struct { - tileTitle string - sliName string - errors []error + errors []error } func (err *sloDefinitionError) Error() string { @@ -172,16 +178,7 @@ func (err *sloDefinitionError) Error() string { for i, e := range err.errors { errStrings[i] = e.Error() } - return strings.Join(errStrings, ";") -} - -// sliNameOrTileTitle returns the SLI name or the tile title, if the SLI name is empty -func (err *sloDefinitionError) sliNameOrTileTitle() string { - if err.sliName != "" { - return err.sliName - } - - return err.tileTitle + return fmt.Sprintf("error parsing SLO definition: %s", strings.Join(errStrings, "; ")) } // cleanIndicatorName makes sure we have a valid indicator name by forcing lower case and getting rid of special characters. diff --git a/internal/sli/dashboard/slo_definition_parsing_test.go b/internal/sli/dashboard/slo_definition_parsing_test.go index 1eb1cbcb0..ec51634f8 100644 --- a/internal/sli/dashboard/slo_definition_parsing_test.go +++ b/internal/sli/dashboard/slo_definition_parsing_test.go @@ -11,7 +11,7 @@ func TestParseSLODefinition_SuccessCases(t *testing.T) { tests := []struct { name string sloString string - want *sloDefinitionParsingResult + want sloDefinitionParsingResult }{ { name: "just some SLI - so no error", @@ -88,132 +88,156 @@ func TestParseSLODefinition_ErrorCases(t *testing.T) { tests := []struct { name string sloString string + want sloDefinitionParsingResult errMessages []string }{ { name: "invalid pass criterion - ms suffix", sloString: "Some description;sli=teststep_rt;pass=<500ms,<+10%;warning=<1000,<+20%;weight=1;key=true", + want: createSLODefinitionParsingResult(false, "teststep_rt", "Some description", [][]string{}, [][]string{{"<1000", "<+20%"}}, 1, true), errMessages: []string{"pass", "<500ms"}, }, { name: "invalid pass and warning criteria - ms suffixes", sloString: "Some description;sli=teststep_rt;pass=<500ms,<+10%;warning=<1000ms,<+20%;weight=1;key=true", + want: createSLODefinitionParsingResult(false, "teststep_rt", "Some description", [][]string{}, [][]string{}, 1, true), errMessages: []string{"pass", "<500ms", "warning", "<1000ms"}, }, { name: "invalid pass criterion - wrong operator", sloString: "sli=some_sli_name;pass=<<500", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"pass", "<<500"}, }, { name: "invalid pass criterion - wrong decimal notation", sloString: "sli=some_sli_name;pass=<500.", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"pass", "<500."}, }, { name: "invalid pass criterion - wrong decimal notation with percent", sloString: "sli=some_sli_name;pass=<500.%", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"pass", "<500.%"}, }, { name: "invalid warning criterion - wrong decimal notation with percent and wrong type", sloString: "sli=some_sli_name;warning=<500.%,yes", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"warning", "<500.%", "yes"}, }, { name: "invalid warning criterion - some string", sloString: "sli=some_sli_name;warning=yes!", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"warning", "yes!"}, }, { name: "invalid warning criterion - wrong operator", sloString: "sli=some_sli_name;warning=<<500", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"warning", "<<500"}, }, { name: "invalid warning criterion - wrong decimal notation", sloString: "sli=some_sli_name;warning=<500.", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"warning", "<500."}, }, { name: "invalid warning criterion - wrong decimal notation with percent", sloString: "sli=some_sli_name;warning=<500.%", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"warning", "<500.%"}, }, { name: "invalid warning criterion - wrong decimal notation with percent and wrong type", sloString: "sli=some_sli_name;warning=<500.%,yes", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"warning", "<500.%", "yes"}, }, { name: "invalid warning criterion - some string", sloString: "sli=some_sli_name;warning=no!", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"warning", "no!"}, }, { name: "invalid weight - not an int", sloString: "sli=some_sli_name;weight=3.14", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"weight", "3.14"}, }, { name: "invalid keySli - not a bool", sloString: "sli=some_sli_name;key=yes", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"key", "yes"}, }, { name: "invalid exclude - not a bool", sloString: "sli=some_sli_name;exclude=enable", + want: createSLODefinitionParsingResult(false, "some_sli_name", "some_sli_name", [][]string{}, [][]string{}, 1, false), errMessages: []string{"exclude", "enable"}, }, { name: "sli name is empty", sloString: "sli=;pass=<600", + want: createSLODefinitionParsingResult(false, "", "", [][]string{{"<600"}}, [][]string{}, 1, false), errMessages: []string{"sli", "is empty"}, }, { name: "sli name is empty - only space", sloString: "sli= ;pass=<600", + want: createSLODefinitionParsingResult(false, "", "", [][]string{{"<600"}}, [][]string{}, 1, false), errMessages: []string{"sli", "is empty"}, }, { name: "duplicate sli name", sloString: "sli=first_name;pass=<600;sli=last_name", + want: createSLODefinitionParsingResult(false, "first_name", "first_name", [][]string{{"<600"}}, [][]string{}, 1, false), errMessages: []string{"'sli'", "duplicate key"}, }, { name: "duplicate key", sloString: "sli=first_name;key=true;pass=<600;key=false", + want: createSLODefinitionParsingResult(false, "first_name", "first_name", [][]string{{"<600"}}, [][]string{}, 1, true), errMessages: []string{"'key'", "duplicate key"}, }, { name: "duplicate weight", sloString: "sli=first_name;weight=7;pass=<600;weight=3", + want: createSLODefinitionParsingResult(false, "first_name", "first_name", [][]string{{"<600"}}, [][]string{}, 7, false), errMessages: []string{"'weight'", "duplicate key"}, }, { name: "duplicate exclude", sloString: "sli=first_name;exclude=true;pass=<600;exclude=false", + want: createSLODefinitionParsingResult(true, "first_name", "first_name", [][]string{{"<600"}}, [][]string{}, 1, false), errMessages: []string{"'exclude'", "duplicate key"}, }, { name: "duplication for sli, key, weight, exclude", sloString: "sli=first_name;weight=7;key=false;exclude=false;sli=last_name;pass=<600;weight=3;key=true;exclude=true", + want: createSLODefinitionParsingResult(false, "first_name", "first_name", [][]string{{"<600"}}, [][]string{}, 7, false), errMessages: []string{"'weight'", "'key'", "'sli'", "'exclude'", "duplicate key"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := parseSLODefinition(tt.sloString) + got, err := parseSLODefinition(tt.sloString) if assert.Error(t, err) { for _, errMessage := range tt.errMessages { assert.Contains(t, err.Error(), errMessage) } } + assert.EqualValues(t, tt.want, got) }) } } -func createSLODefinitionParsingResult(exclude bool, indicatorName string, displayName string, pass [][]string, warning [][]string, weight int, isKey bool) *sloDefinitionParsingResult { +func createSLODefinitionParsingResult(exclude bool, indicatorName string, displayName string, pass [][]string, warning [][]string, weight int, isKey bool) sloDefinitionParsingResult { var passCriteria []*keptnapi.SLOCriteria for _, criteria := range pass { passCriteria = append(passCriteria, &keptnapi.SLOCriteria{Criteria: criteria}) @@ -224,7 +248,7 @@ func createSLODefinitionParsingResult(exclude bool, indicatorName string, displa warningCriteria = append(warningCriteria, &keptnapi.SLOCriteria{Criteria: criteria}) } - return &sloDefinitionParsingResult{ + return sloDefinitionParsingResult{ exclude: exclude, sloDefinition: keptnapi.SLO{ SLI: indicatorName, diff --git a/internal/sli/dashboard/usql_tile_processing.go b/internal/sli/dashboard/usql_tile_processing.go index 803816ad9..11dbb4bcf 100644 --- a/internal/sli/dashboard/usql_tile_processing.go +++ b/internal/sli/dashboard/usql_tile_processing.go @@ -37,12 +37,7 @@ func NewUSQLTileProcessing(client dynatrace.ClientInterface, eventData adapter.E // TODO: 2022-03-07: Investigate if all error and warning cases are covered. E.g. what happens if a query returns no results? func (p *USQLTileProcessing) Process(ctx context.Context, tile *dynatrace.Tile) []TileResult { sloDefinitionParsingResult, err := parseSLODefinition(tile.CustomName) - var sloDefError *sloDefinitionError - if errors.As(err, &sloDefError) { - return []TileResult{newFailedTileResultFromError(sloDefError.sliNameOrTileTitle(), "User Sessions Query tile title parsing error", err)} - } - - if sloDefinitionParsingResult.exclude { + if (err == nil) && (sloDefinitionParsingResult.exclude) { log.WithField("tile.CustomName", tile.Name).Debug("Tile excluded as name includes exclude=true") return nil } @@ -53,6 +48,10 @@ func (p *USQLTileProcessing) Process(ctx context.Context, tile *dynatrace.Tile) return nil } + if err != nil { + return []TileResult{newFailedTileResultFromSLODefinition(sloDefinition, "User Sessions Query tile title parsing error: "+err.Error())} + } + query, err := usql.NewQuery(tile.Query) if err != nil { return []TileResult{newFailedTileResultFromSLODefinition(sloDefinition, "error creating USQL query: "+err.Error())} diff --git a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go index 4f65bfc88..8a156b880 100644 --- a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go +++ b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go @@ -475,10 +475,21 @@ func createNotVisibleThresholds(rule1 dynatrace.VisualizationThresholdRule, rule // TestRetrieveMetricsFromDashboardDataExplorerTile_UnitTransformIsNotAuto tests that unit transforms other than auto are not allowed. // This is will result in a SLIResult with failure, as this is not allowed. func TestRetrieveMetricsFromDashboardDataExplorerTile_UnitTransformIsNotAuto(t *testing.T) { - handler := createHandlerForEarlyFailureDataExplorerTest(t, "./testdata/dashboards/data_explorer/unit_transform_is_not_auto/") + const testDataFolder = "./testdata/dashboards/data_explorer/unit_transform_is_not_auto/" + + handler := createHandlerForEarlyFailureDataExplorerTest(t, testDataFolder) runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventFailureAssertionsFunc, createFailedSLIResultAssertionsFunc("srt", "must be set to 'Auto'")) } +// TestRetrieveMetricsFromDashboardDataExplorerTile_MultipleTileConfigurationProblems tests that a Data Explorer tile with multiple configuration problems results in an error that includes all these problems. +// This is will result in a SLIResult with failure, as this is not allowed. +func TestRetrieveMetricsFromDashboardDataExplorerTile_MultipleTileConfigurationProblems(t *testing.T) { + const testDataFolder = "./testdata/dashboards/data_explorer/multiple_tile_configuration_problems/" + + handler := createHandlerForEarlyFailureDataExplorerTest(t, testDataFolder) + runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventFailureAssertionsFunc, createFailedSLIResultAssertionsFunc("srt", "error parsing SLO definition", "tile has 2 queries enabled but only one is supported", "tile has no metric expressions")) +} + func createExpectedServiceResponseTimeSLO(passCriteria []*keptnapi.SLOCriteria, warningCriteria []*keptnapi.SLOCriteria) *keptnapi.SLO { return &keptnapi.SLO{ SLI: "srt", diff --git a/internal/sli/testdata/dashboards/data_explorer/multiple_tile_configuration_problems/dashboard.json b/internal/sli/testdata/dashboards/data_explorer/multiple_tile_configuration_problems/dashboard.json new file mode 100644 index 000000000..77401b472 --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/multiple_tile_configuration_problems/dashboard.json @@ -0,0 +1,130 @@ +{ + "metadata": { + "configurationVersions": [ + 5 + ], + "clusterVersion": "1.245.0.20220804-195908" + }, + "id": "12345678-1111-4444-8888-123456789012", + "dashboardMetadata": { + "name": "", + "shared": false, + "owner": "", + "popularity": 1 + }, + "tiles": [ + { + "name": "Service response time; sli=srt; pass=<30; weight=4.2; key=true; key=false", + "tileType": "DATA_EXPLORER", + "configured": true, + "bounds": { + "top": 266, + "left": 0, + "width": 1140, + "height": 190 + }, + "tileFilter": {}, + "customName": "Data explorer results", + "queries": [ + { + "id": "A", + "metric": "builtin:service.response.time", + "spaceAggregation": "AVG", + "timeAggregation": "DEFAULT", + "splitBy": [], + "filterBy": { + "nestedFilters": [], + "criteria": [] + }, + "enabled": true + }, + { + "id": "B", + "metric": "builtin:service.response.time", + "spaceAggregation": "AVG", + "timeAggregation": "DEFAULT", + "splitBy": [], + "filterBy": { + "nestedFilters": [], + "criteria": [] + }, + "enabled": true + } + ], + "visualConfig": { + "type": "GRAPH_CHART", + "global": { + "hideLegend": false + }, + "rules": [ + { + "matcher": "A:", + "properties": { + "color": "DEFAULT" + }, + "seriesOverrides": [] + } + ], + "axes": { + "xAxis": { + "displayName": "", + "visible": true + }, + "yAxes": [ + { + "displayName": "", + "visible": true, + "min": "AUTO", + "max": "AUTO", + "position": "LEFT", + "queryIds": [ + "A" + ], + "defaultAxis": true + } + ] + }, + "heatmapSettings": { + "yAxis": "VALUE" + }, + "thresholds": [ + { + "axisTarget": "LEFT", + "rules": [ + { + "value": 0, + "color": "#14a8f5" + }, + { + "value": 4100000, + "color": "#ffe11c" + }, + { + "value": 5000000, + "color": "#048855" + } + ], + "queryId": "", + "visible": true + } + ], + "tableSettings": { + "isThresholdBackgroundAppliedToCell": false + }, + "graphChartSettings": { + "connectNulls": false + }, + "honeycombSettings": { + "showHive": true, + "showLegend": true, + "showLabels": false + } + }, + "queriesSettings": { + "resolution": "" + }, + "metricExpressions": [ + ] + } + ] +} From 575003b71d19b0fb55b8352dce9adfe6a2cc5281 Mon Sep 17 00:00:00 2001 From: Arthur Pitman Date: Thu, 20 Oct 2022 13:32:59 +0200 Subject: [PATCH 6/7] Enable units support in Data Explorer and Custom Charting Signed-off-by: Arthur Pitman --- internal/dynatrace/dashboard.go | 9 +- .../custom_charting_tile_processing.go | 10 +- .../data_explorer_tile_processing.go | 13 +- .../sli/dashboard/metrics_query_processing.go | 26 +- ...ics_from_dashboard_custom_charting_test.go | 38 +++ ...trics_from_dashboard_data_explorer_test.go | 37 ++- internal/sli/test_helper_test.go | 7 + .../unit_transform_error/dashboard.json | 52 +++ .../metrics_get_by_id_base.json | 54 +++ .../metrics_get_by_id_full.json | 46 +++ .../metrics_get_by_query_first.json | 310 ++++++++++++++++++ .../metrics_get_by_query_second.json | 24 ++ .../metrics_units_convert_error.json | 6 + .../dashboard.json | 52 +++ .../metrics_get_by_id_base.json | 54 +++ .../metrics_get_by_id_full.json | 46 +++ .../metrics_get_by_query_first.json | 310 ++++++++++++++++++ .../metrics_get_by_query_second.json | 24 ++ .../metrics_units_convert1.json | 4 + .../unit_transform_error/dashboard.json | 116 +++++++ .../metrics_get_by_id.json | 46 +++ .../metrics_get_by_query1.json | 310 ++++++++++++++++++ .../metrics_get_by_query2.json | 24 ++ .../metrics_units_convert_error.json | 6 + .../unit_transform_is_not_auto/dashboard.json | 122 ------- .../dashboard.json | 116 +++++++ .../metrics_get_by_id.json | 46 +++ .../metrics_get_by_query1.json | 310 ++++++++++++++++++ .../metrics_get_by_query2.json | 24 ++ .../metrics_units_convert1.json | 4 + internal/test/dynatrace_test_file_updater.go | 8 +- 31 files changed, 2096 insertions(+), 158 deletions(-) create mode 100644 internal/sli/testdata/dashboards/custom_charting/unit_transform_error/dashboard.json create mode 100644 internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_id_base.json create mode 100644 internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_id_full.json create mode 100644 internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_query_first.json create mode 100644 internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_query_second.json create mode 100644 internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_units_convert_error.json create mode 100644 internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/dashboard.json create mode 100644 internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_id_base.json create mode 100644 internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_id_full.json create mode 100644 internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_query_first.json create mode 100644 internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_query_second.json create mode 100644 internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_units_convert1.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/unit_transform_error/dashboard.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_get_by_id.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_get_by_query1.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_get_by_query2.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_units_convert_error.json delete mode 100644 internal/sli/testdata/dashboards/data_explorer/unit_transform_is_not_auto/dashboard.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/dashboard.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_get_by_id.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_get_by_query1.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_get_by_query2.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_units_convert1.json diff --git a/internal/dynatrace/dashboard.go b/internal/dynatrace/dashboard.go index 36d342012..469077682 100644 --- a/internal/dynatrace/dashboard.go +++ b/internal/dynatrace/dashboard.go @@ -193,10 +193,11 @@ FiltersPerEntityType struct { */ type ChartConfig struct { - LegendShown bool `json:"legendShown"` - Type string `json:"type"` - Series []Series `json:"series"` - ResultMetadata ResultMetadata `json:"resultMetadata"` + LegendShown bool `json:"legendShown"` + Type string `json:"type"` + Series []Series `json:"series"` + ResultMetadata ResultMetadata `json:"resultMetadata"` + LeftAxisCustomUnit string `json:"leftAxisCustomUnit,omitempty"` } type Series struct { diff --git a/internal/sli/dashboard/custom_charting_tile_processing.go b/internal/sli/dashboard/custom_charting_tile_processing.go index 19493501b..dcb0c0794 100644 --- a/internal/sli/dashboard/custom_charting_tile_processing.go +++ b/internal/sli/dashboard/custom_charting_tile_processing.go @@ -62,20 +62,22 @@ func (p *CustomChartingTileProcessing) Process(ctx context.Context, tile *dynatr // Check for tile management zone filter - this would overwrite the dashboardManagementZoneFilter tileManagementZoneFilter := NewManagementZoneFilter(dashboardFilter, tile.TileFilter.ManagementZone) - if len(tile.FilterConfig.ChartConfig.Series) != 1 { + chartConfig := tile.FilterConfig.ChartConfig + targetUnitID := chartConfig.LeftAxisCustomUnit + if len(chartConfig.Series) != 1 { return []TileResult{newFailedTileResultFromSLODefinition(sloDefinition, "Custom charting tile must have exactly one series")} } - return p.processSeries(ctx, sloDefinition, &tile.FilterConfig.ChartConfig.Series[0], tileManagementZoneFilter, tile.FilterConfig.FiltersPerEntityType) + return p.processSeries(ctx, sloDefinition, &chartConfig.Series[0], targetUnitID, tileManagementZoneFilter, tile.FilterConfig.FiltersPerEntityType) } -func (p *CustomChartingTileProcessing) processSeries(ctx context.Context, sloDefinition keptnapi.SLO, series *dynatrace.Series, tileManagementZoneFilter *ManagementZoneFilter, filtersPerEntityType map[string]dynatrace.FilterMap) []TileResult { +func (p *CustomChartingTileProcessing) processSeries(ctx context.Context, sloDefinition keptnapi.SLO, series *dynatrace.Series, targetUnitID string, tileManagementZoneFilter *ManagementZoneFilter, filtersPerEntityType map[string]dynatrace.FilterMap) []TileResult { metricsQuery, err := p.generateMetricQueryFromChartSeries(ctx, series, tileManagementZoneFilter, filtersPerEntityType) if err != nil { return []TileResult{newFailedTileResultFromSLODefinition(sloDefinition, "Custom charting tile could not be converted to a metric query: "+err.Error())} } - return NewMetricsQueryProcessing(p.client).Process(ctx, sloDefinition, *metricsQuery, p.timeframe) + return NewMetricsQueryProcessing(p.client, targetUnitID).Process(ctx, sloDefinition, *metricsQuery, p.timeframe) } func (p *CustomChartingTileProcessing) generateMetricQueryFromChartSeries(ctx context.Context, series *dynatrace.Series, tileManagementZoneFilter *ManagementZoneFilter, filtersPerEntityType map[string]dynatrace.FilterMap) (*metrics.Query, error) { diff --git a/internal/sli/dashboard/data_explorer_tile_processing.go b/internal/sli/dashboard/data_explorer_tile_processing.go index 065888e18..8924adffa 100644 --- a/internal/sli/dashboard/data_explorer_tile_processing.go +++ b/internal/sli/dashboard/data_explorer_tile_processing.go @@ -51,10 +51,10 @@ func (p *DataExplorerTileProcessing) Process(ctx context.Context, tile *dynatrac func (p *DataExplorerTileProcessing) createMetricsQueryProcessing(validatedTile *validatedDataExplorerTile) *MetricsQueryProcessing { if validatedTile.singleValueVisualization { - return NewMetricsQueryProcessingThatAllowsOnlyOneResult(p.client) + return NewMetricsQueryProcessingThatAllowsOnlyOneResult(p.client, validatedTile.targetUnitID) } - return NewMetricsQueryProcessing(p.client) + return NewMetricsQueryProcessing(p.client, validatedTile.targetUnitID) } type dataExplorerTileValidationError struct { @@ -124,13 +124,6 @@ func (v *dataExplorerTileValidator) tryValidate() (*validatedDataExplorerTile, e errs = append(errs, err) } - // temporarily require unit set to Auto - targetUnitID := getUnitTransform(v.tile.VisualConfig, queryID) - if targetUnitID != "" { - err := fmt.Errorf("Data Explorer query unit must be set to 'Auto' rather than '%s'", targetUnitID) - errs = append(errs, err) - } - if len(errs) > 0 { return nil, &dataExplorerTileValidationError{ sloDefinition: sloDefinition, @@ -140,7 +133,7 @@ func (v *dataExplorerTileValidator) tryValidate() (*validatedDataExplorerTile, e return &validatedDataExplorerTile{ sloDefinition: sloDefinition, - targetUnitID: targetUnitID, + targetUnitID: getUnitTransform(v.tile.VisualConfig, queryID), singleValueVisualization: isSingleValueVisualizationType(v.tile.VisualConfig), query: *query, }, nil diff --git a/internal/sli/dashboard/metrics_query_processing.go b/internal/sli/dashboard/metrics_query_processing.go index 8e4c6a00f..0bc926638 100644 --- a/internal/sli/dashboard/metrics_query_processing.go +++ b/internal/sli/dashboard/metrics_query_processing.go @@ -14,17 +14,35 @@ type MetricsQueryProcessing struct { metricsProcessing dynatrace.MetricsProcessingInterface } -func NewMetricsQueryProcessing(client dynatrace.ClientInterface) *MetricsQueryProcessing { +func NewMetricsQueryProcessing(client dynatrace.ClientInterface, targetUnitID string) *MetricsQueryProcessing { metricsClient := dynatrace.NewMetricsClient(client) + unitsClient := dynatrace.NewMetricsUnitsClient(client) return &MetricsQueryProcessing{ - metricsProcessing: dynatrace.NewRetryForSingleValueMetricsProcessingDecorator(metricsClient, dynatrace.NewMetricsProcessing(metricsClient)), + metricsProcessing: dynatrace.NewConvertUnitMetricsProcessingDecorator( + metricsClient, + unitsClient, + targetUnitID, + dynatrace.NewRetryForSingleValueMetricsProcessingDecorator( + metricsClient, + dynatrace.NewMetricsProcessing(metricsClient), + ), + ), } } -func NewMetricsQueryProcessingThatAllowsOnlyOneResult(client dynatrace.ClientInterface) *MetricsQueryProcessing { +func NewMetricsQueryProcessingThatAllowsOnlyOneResult(client dynatrace.ClientInterface, targetUnitID string) *MetricsQueryProcessing { metricsClient := dynatrace.NewMetricsClient(client) + unitsClient := dynatrace.NewMetricsUnitsClient(client) return &MetricsQueryProcessing{ - metricsProcessing: dynatrace.NewRetryForSingleValueMetricsProcessingDecorator(metricsClient, dynatrace.NewMetricsProcessingThatAllowsOnlyOneResult(metricsClient)), + metricsProcessing: dynatrace.NewConvertUnitMetricsProcessingDecorator( + metricsClient, + unitsClient, + targetUnitID, + dynatrace.NewRetryForSingleValueMetricsProcessingDecorator( + metricsClient, + dynatrace.NewMetricsProcessingThatAllowsOnlyOneResult(metricsClient), + ), + ), } } diff --git a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_custom_charting_test.go b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_custom_charting_test.go index 774e874e4..87c1e8a25 100644 --- a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_custom_charting_test.go +++ b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_custom_charting_test.go @@ -309,6 +309,44 @@ func TestRetrieveMetricsFromDashboardCustomChartingTile_ExcludedTile(t *testing. runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventSuccessAssertionsFunc, sliResultsAssertionsFuncs...) } +// TestRetrieveMetricsFromDashboardCustomChartingTile_UnitTransformMilliseconds tests a custom charting tile with units set to milliseconds. +// This is will result in a SLIResult with success, as this is supported. +func TestRetrieveMetricsFromDashboardCustomChartingTile_UnitTransformMilliseconds(t *testing.T) { + const testDataFolder = "./testdata/dashboards/custom_charting/unit_transform_milliseconds/" + + handler, expectedMetricsRequest := createHandlerForSuccessfulCustomChartingTest(t, successfulCustomChartingTestHandlerConfiguration{ + testDataFolder: testDataFolder, + baseMetricSelector: "builtin:service.response.time", + fullMetricSelector: "builtin:service.response.time:splitBy():avg:names", + entitySelector: "type(SERVICE)", + }) + + handler.AddExact(buildMetricsUnitsConvertRequest("MicroSecond", 54896.48858596068, "MilliSecond"), filepath.Join(testDataFolder, "metrics_units_convert1.json")) + + sliResultsAssertionsFuncs := []func(t *testing.T, actual sliResult){ + createSuccessfulSLIResultAssertionsFunc("service_response_time", 54.89648858596068, expectedMetricsRequest), + } + + runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventSuccessAssertionsFunc, sliResultsAssertionsFuncs...) +} + +// TestRetrieveMetricsFromDashboardCustomChartingTile_UnitTransformError tests a custom charting tile with invalid units generates the expected error. +func TestRetrieveMetricsFromDashboardCustomChartingTile_UnitTransformError(t *testing.T) { + const testDataFolder = "./testdata/dashboards/custom_charting/unit_transform_error/" + + requestBuilder := newMetricsV2QueryRequestBuilder("builtin:service.response.time:splitBy():avg:names").copyWithEntitySelector("type(SERVICE)") + + handler, _ := createHandlerForSuccessfulCustomChartingTest(t, successfulCustomChartingTestHandlerConfiguration{ + testDataFolder: testDataFolder, + baseMetricSelector: "builtin:service.response.time", + fullMetricSelector: requestBuilder.metricSelector(), + entitySelector: "type(SERVICE)", + }) + + handler.AddExactError(buildMetricsUnitsConvertRequest("MicroSecond", 54896.48858596068, "Byte"), 400, filepath.Join(testDataFolder, "metrics_units_convert_error.json")) + runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventFailureAssertionsFunc, createFailedSLIResultWithQueryAssertionsFunc("service_response_time", requestBuilder.build())) +} + type successfulCustomChartingTestHandlerConfiguration struct { testDataFolder string baseMetricSelector string diff --git a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go index 8a156b880..555d2ffef 100644 --- a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go +++ b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go @@ -472,15 +472,6 @@ func createNotVisibleThresholds(rule1 dynatrace.VisualizationThresholdRule, rule } } -// TestRetrieveMetricsFromDashboardDataExplorerTile_UnitTransformIsNotAuto tests that unit transforms other than auto are not allowed. -// This is will result in a SLIResult with failure, as this is not allowed. -func TestRetrieveMetricsFromDashboardDataExplorerTile_UnitTransformIsNotAuto(t *testing.T) { - const testDataFolder = "./testdata/dashboards/data_explorer/unit_transform_is_not_auto/" - - handler := createHandlerForEarlyFailureDataExplorerTest(t, testDataFolder) - runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventFailureAssertionsFunc, createFailedSLIResultAssertionsFunc("srt", "must be set to 'Auto'")) -} - // TestRetrieveMetricsFromDashboardDataExplorerTile_MultipleTileConfigurationProblems tests that a Data Explorer tile with multiple configuration problems results in an error that includes all these problems. // This is will result in a SLIResult with failure, as this is not allowed. func TestRetrieveMetricsFromDashboardDataExplorerTile_MultipleTileConfigurationProblems(t *testing.T) { @@ -490,6 +481,34 @@ func TestRetrieveMetricsFromDashboardDataExplorerTile_MultipleTileConfigurationP runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventFailureAssertionsFunc, createFailedSLIResultAssertionsFunc("srt", "error parsing SLO definition", "tile has 2 queries enabled but only one is supported", "tile has no metric expressions")) } +// TestRetrieveMetricsFromDashboardDataExplorerTile_UnitTransformMilliseconds tests that unit transform works as expected. +func TestRetrieveMetricsFromDashboardDataExplorerTile_UnitTransformMilliseconds(t *testing.T) { + const testDataFolder = "./testdata/dashboards/data_explorer/unit_transform_milliseconds/" + + handler, expectedMetricsRequest := createHandlerForSuccessfulDataExplorerTestWithResolutionInf(t, + testDataFolder, + newMetricsV2QueryRequestBuilder("(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names"), + ) + handler.AddExact(buildMetricsUnitsConvertRequest("MicroSecond", 54896.48858596068, "MilliSecond"), filepath.Join(testDataFolder, "metrics_units_convert1.json")) + + sliResultsAssertionsFuncs := []func(t *testing.T, actual sliResult){ + createSuccessfulSLIResultAssertionsFunc("srt_milliseconds", 54.89648858596068, expectedMetricsRequest), + } + + runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventSuccessAssertionsFunc, sliResultsAssertionsFuncs...) +} + +// TestRetrieveMetricsFromDashboardDataExplorerTile_UnitTransformError tests that a unit transform with an invalid unit generates the expected error. +func TestRetrieveMetricsFromDashboardDataExplorerTile_UnitTransformError(t *testing.T) { + const testDataFolder = "./testdata/dashboards/data_explorer/unit_transform_error/" + + requestBuilder := newMetricsV2QueryRequestBuilder("(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names") + handler, _ := createHandlerForSuccessfulDataExplorerTestWithResolutionInf(t, testDataFolder, requestBuilder) + handler.AddExactError(buildMetricsUnitsConvertRequest("MicroSecond", 54896.48858596068, "Byte"), 400, filepath.Join(testDataFolder, "metrics_units_convert_error.json")) + + runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventFailureAssertionsFunc, createFailedSLIResultWithQueryAssertionsFunc("srt_bytes", requestBuilder.build(), "Cannot convert MicroSecond to Byte")) +} + func createExpectedServiceResponseTimeSLO(passCriteria []*keptnapi.SLOCriteria, warningCriteria []*keptnapi.SLOCriteria) *keptnapi.SLO { return &keptnapi.SLO{ SLI: "srt", diff --git a/internal/sli/test_helper_test.go b/internal/sli/test_helper_test.go index ba8b81efc..c7edcf7dd 100644 --- a/internal/sli/test_helper_test.go +++ b/internal/sli/test_helper_test.go @@ -7,6 +7,7 @@ import ( "net/http" "net/url" "path/filepath" + "strconv" "testing" cloudevents "github.com/cloudevents/sdk-go/v2" @@ -77,6 +78,12 @@ func buildMetricsV2DefinitionRequestString(metricID string) string { return fmt.Sprintf("%s/%s", dynatrace.MetricsPath, url.PathEscape(metricID)) } +// buildMetricsUnitsConvertRequest builds a Metrics Units convert request string with the specified source unit ID, value and target unit ID for use in testing. +func buildMetricsUnitsConvertRequest(sourceUnitID string, value float64, targetUnitID string) string { + vs := strconv.FormatFloat(value, 'f', -1, 64) + return fmt.Sprintf("%s/%s/convert?targetUnit=%s&value=%s", dynatrace.MetricsUnitsPath, url.PathEscape(sourceUnitID), targetUnitID, vs) +} + // buildProblemsV2Request builds a Problems V2 request string with the specified problem selector for use in testing. func buildProblemsV2Request(problemSelector string) string { return fmt.Sprintf("%s?from=%s&problemSelector=%s&to=%s", dynatrace.ProblemsV2Path, convertTimeStringToUnixMillisecondsString(testSLIStart), url.QueryEscape(problemSelector), convertTimeStringToUnixMillisecondsString(testSLIEnd)) diff --git a/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/dashboard.json b/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/dashboard.json new file mode 100644 index 000000000..700b686a1 --- /dev/null +++ b/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/dashboard.json @@ -0,0 +1,52 @@ +{ + "metadata": { + "configurationVersions": [ + 5 + ], + "clusterVersion": "1.231.0.20211103-072326" + }, + "id": "12345678-1111-4444-8888-123456789012", + "dashboardMetadata": { + "name": "Test-369", + "shared": true, + "owner": "" + }, + "tiles": [ + { + "name": "", + "tileType": "CUSTOM_CHARTING", + "configured": true, + "bounds": { + "top": 608, + "left": 38, + "width": 532, + "height": 190 + }, + "tileFilter": {}, + "filterConfig": { + "type": "MIXED", + "customName": "Valid chart with SLI with pass;SLI=service_response_time;pass=<30", + "defaultName": "Custom chart", + "chartConfig": { + "legendShown": true, + "type": "TIMESERIES", + "series": [ + { + "metric": "builtin:service.response.time", + "aggregation": "AVG", + "type": "LINE", + "entityType": "SERVICE", + "dimensions": [], + "sortAscending": false, + "sortColumn": true, + "aggregationRate": "TOTAL" + } + ], + "resultMetadata": {}, + "leftAxisCustomUnit": "Byte" + }, + "filtersPerEntityType": {} + } + } + ] +} diff --git a/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_id_base.json b/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_id_base.json new file mode 100644 index 000000000..76965298d --- /dev/null +++ b/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_id_base.json @@ -0,0 +1,54 @@ +{ + "metricId": "builtin:service.response.time", + "displayName": "Response time", + "description": "", + "unit": "MicroSecond", + "dduBillable": false, + "created": 0, + "lastWritten": 1666265323048, + "entityType": [ + "SERVICE" + ], + "aggregationTypes": [ + "auto", + "avg", + "count", + "max", + "median", + "min", + "percentile", + "sum" + ], + "transformations": [ + "filter", + "fold", + "limit", + "merge", + "names", + "parents", + "timeshift", + "sort", + "last", + "splitBy", + "lastReal", + "setUnit" + ], + "defaultAggregation": { + "type": "avg" + }, + "dimensionDefinitions": [ + { + "key": "dt.entity.service", + "name": "Service", + "displayName": "Service", + "index": 0, + "type": "ENTITY" + } + ], + "tags": [], + "metricValueType": { + "type": "unknown" + }, + "scalar": false, + "resolutionInfSupported": true +} diff --git a/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_id_full.json b/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_id_full.json new file mode 100644 index 000000000..8006c11fe --- /dev/null +++ b/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_id_full.json @@ -0,0 +1,46 @@ +{ + "metricId": "builtin:service.response.time:splitBy():avg:names", + "displayName": "Response time", + "description": "", + "unit": "MicroSecond", + "dduBillable": false, + "created": 0, + "lastWritten": 1666265323048, + "entityType": [ + "SERVICE" + ], + "aggregationTypes": [ + "auto", + "value" + ], + "transformations": [ + "fold", + "limit", + "timeshift", + "rate", + "sort", + "last", + "splitBy", + "default", + "delta", + "lastReal", + "smooth", + "rollup", + "partition", + "toUnit", + "setUnit" + ], + "defaultAggregation": { + "type": "value" + }, + "dimensionDefinitions": [], + "tags": [], + "metricValueType": { + "type": "unknown" + }, + "scalar": false, + "resolutionInfSupported": true, + "warnings": [ + "The field dimensionCardinalities is only supported for untransformed single metric keys and was ignored." + ] +} diff --git a/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_query_first.json b/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_query_first.json new file mode 100644 index 000000000..14a17ba75 --- /dev/null +++ b/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_query_first.json @@ -0,0 +1,310 @@ +{ + "totalCount": 1, + "nextPageKey": null, + "resolution": "10m", + "result": [ + { + "metricId": "builtin:service.response.time:splitBy():avg:names", + "dataPointCountRatio": 0.0699408, + "dimensionCountRatio": 0.04857, + "data": [ + { + "dimensions": [], + "dimensionMap": {}, + "timestamps": [ + 1664323800000, + 1664324400000, + 1664325000000, + 1664325600000, + 1664326200000, + 1664326800000, + 1664327400000, + 1664328000000, + 1664328600000, + 1664329200000, + 1664329800000, + 1664330400000, + 1664331000000, + 1664331600000, + 1664332200000, + 1664332800000, + 1664333400000, + 1664334000000, + 1664334600000, + 1664335200000, + 1664335800000, + 1664336400000, + 1664337000000, + 1664337600000, + 1664338200000, + 1664338800000, + 1664339400000, + 1664340000000, + 1664340600000, + 1664341200000, + 1664341800000, + 1664342400000, + 1664343000000, + 1664343600000, + 1664344200000, + 1664344800000, + 1664345400000, + 1664346000000, + 1664346600000, + 1664347200000, + 1664347800000, + 1664348400000, + 1664349000000, + 1664349600000, + 1664350200000, + 1664350800000, + 1664351400000, + 1664352000000, + 1664352600000, + 1664353200000, + 1664353800000, + 1664354400000, + 1664355000000, + 1664355600000, + 1664356200000, + 1664356800000, + 1664357400000, + 1664358000000, + 1664358600000, + 1664359200000, + 1664359800000, + 1664360400000, + 1664361000000, + 1664361600000, + 1664362200000, + 1664362800000, + 1664363400000, + 1664364000000, + 1664364600000, + 1664365200000, + 1664365800000, + 1664366400000, + 1664367000000, + 1664367600000, + 1664368200000, + 1664368800000, + 1664369400000, + 1664370000000, + 1664370600000, + 1664371200000, + 1664371800000, + 1664372400000, + 1664373000000, + 1664373600000, + 1664374200000, + 1664374800000, + 1664375400000, + 1664376000000, + 1664376600000, + 1664377200000, + 1664377800000, + 1664378400000, + 1664379000000, + 1664379600000, + 1664380200000, + 1664380800000, + 1664381400000, + 1664382000000, + 1664382600000, + 1664383200000, + 1664383800000, + 1664384400000, + 1664385000000, + 1664385600000, + 1664386200000, + 1664386800000, + 1664387400000, + 1664388000000, + 1664388600000, + 1664389200000, + 1664389800000, + 1664390400000, + 1664391000000, + 1664391600000, + 1664392200000, + 1664392800000, + 1664393400000, + 1664394000000, + 1664394600000, + 1664395200000, + 1664395800000, + 1664396400000, + 1664397000000, + 1664397600000, + 1664398200000, + 1664398800000, + 1664399400000, + 1664400000000, + 1664400600000, + 1664401200000, + 1664401800000, + 1664402400000, + 1664403000000, + 1664403600000, + 1664404200000, + 1664404800000, + 1664405400000, + 1664406000000, + 1664406600000, + 1664407200000, + 1664407800000, + 1664408400000, + 1664409000000, + 1664409600000 + ], + "values": [ + 54820.03373164987, + 55218.99471220384, + 55071.49928878831, + 54949.78353754613, + 54792.09863487236, + 54758.23014110582, + 54839.84122407719, + 55080.79677446355, + 54553.826345367015, + 54700.14292835205, + 54737.2003722548, + 55002.27033954363, + 54777.74025995784, + 54920.93069442279, + 54564.00216313528, + 55127.85502136979, + 54649.187273431846, + 54903.09771335987, + 54811.92754307572, + 54954.78607277203, + 55320.74452442934, + 54811.32374927372, + 54789.12016856007, + 54832.67417526022, + 54882.66109509589, + 54839.12680134036, + 54963.65066827593, + 54833.89919248517, + 54726.41233535216, + 54711.6734476709, + 55050.82096058733, + 54804.508598504384, + 55043.67518606438, + 54909.71511835754, + 54832.34011665316, + 54965.59893223934, + 54768.34111642939, + 54769.87290857302, + 54838.65544370555, + 54639.0661843702, + 54871.37265354548, + 54877.451343791385, + 54884.69594899173, + 54830.335242511755, + 54909.95975180857, + 54729.30936347974, + 54857.07455554992, + 54773.180566804505, + 57040.51818064292, + 54815.08510582958, + 54638.81806222, + 54931.655559810424, + 54822.79794409753, + 54943.88083062903, + 59189.65722406022, + 54863.39737403222, + 54865.96204806309, + 54941.49568657858, + 55013.6698386867, + 54927.511166520126, + 55034.0434218988, + 54726.585503515416, + 55179.36971275004, + 55140.22470784515, + 54873.353515106246, + 55046.39285477691, + 55219.21159741783, + 53953.047819327396, + 54398.93323329027, + 54638.48781541484, + 55437.86906753935, + 54777.13448401533, + 54614.82884669915, + 54741.899303641876, + 54811.067369581295, + 54589.01308088208, + 55234.24117269804, + 54332.28349304332, + 54490.19518987563, + 54634.571387632306, + 54716.22231328921, + 56106.07520870239, + 54878.24152910459, + 55026.275312892016, + 54081.654492523165, + 55144.45957110088, + 56071.58167898117, + 54385.870900343165, + 54458.36845208812, + 54230.56669550621, + 54258.731741534466, + 55081.29456308478, + 55865.60168431374, + 55196.5338318562, + 53852.42383215121, + 55496.39855469356, + 55323.17160213487, + 54835.549739796435, + 55074.18764280705, + 54714.90320689123, + 55315.876354608066, + 54894.23055589334, + 54762.26818212387, + 54519.16418366507, + 55263.627200541814, + 54644.019993779904, + 54705.427177621474, + 55111.50943922037, + 54662.1410122232, + 54391.73053502808, + 53794.626921183415, + 54720.05095351137, + 54882.17257639208, + 54895.636923092046, + 54843.223935575785, + 55041.561428220484, + 54674.520597537055, + 54655.125680576966, + 54893.71445497061, + 54744.62396520675, + 54977.05859708377, + 55160.030160401315, + 54764.61432756854, + 54725.26721258407, + 55183.882339578675, + 55129.065580386196, + 54626.46311949491, + 54849.14344749185, + 54963.18046961137, + 54816.06156961105, + 54733.57320804991, + 55447.00928208658, + 55124.80110455526, + 55027.30869873211, + 54987.10183916898, + 55062.99573491732, + 53716.04621333344, + 55011.03937116556, + 54764.56758589279, + 54588.83623561774, + 54533.389405348695, + 54877.71688140235, + 54616.27774406533, + 55079.69618901161 + ] + } + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_query_second.json b/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_query_second.json new file mode 100644 index 000000000..4268f0c6f --- /dev/null +++ b/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_get_by_query_second.json @@ -0,0 +1,24 @@ +{ + "totalCount": 1, + "nextPageKey": null, + "resolution": "Inf", + "result": [ + { + "metricId": "builtin:service.response.time:splitBy():avg:names", + "dataPointCountRatio": 2.4285E-4, + "dimensionCountRatio": 0.04857, + "data": [ + { + "dimensions": [], + "dimensionMap": {}, + "timestamps": [ + 1664409600000 + ], + "values": [ + 54896.48858596068 + ] + } + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_units_convert_error.json b/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_units_convert_error.json new file mode 100644 index 000000000..72cecf571 --- /dev/null +++ b/internal/sli/testdata/dashboards/custom_charting/unit_transform_error/metrics_units_convert_error.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": 400, + "message": "Cannot convert MicroSecond to Byte." + } +} diff --git a/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/dashboard.json b/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/dashboard.json new file mode 100644 index 000000000..8229dde9e --- /dev/null +++ b/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/dashboard.json @@ -0,0 +1,52 @@ +{ + "metadata": { + "configurationVersions": [ + 5 + ], + "clusterVersion": "1.231.0.20211103-072326" + }, + "id": "12345678-1111-4444-8888-123456789012", + "dashboardMetadata": { + "name": "Test-369", + "shared": true, + "owner": "" + }, + "tiles": [ + { + "name": "", + "tileType": "CUSTOM_CHARTING", + "configured": true, + "bounds": { + "top": 608, + "left": 38, + "width": 532, + "height": 190 + }, + "tileFilter": {}, + "filterConfig": { + "type": "MIXED", + "customName": "Valid chart with SLI with pass;SLI=service_response_time;pass=<30", + "defaultName": "Custom chart", + "chartConfig": { + "legendShown": true, + "type": "TIMESERIES", + "series": [ + { + "metric": "builtin:service.response.time", + "aggregation": "AVG", + "type": "LINE", + "entityType": "SERVICE", + "dimensions": [], + "sortAscending": false, + "sortColumn": true, + "aggregationRate": "TOTAL" + } + ], + "resultMetadata": {}, + "leftAxisCustomUnit": "MilliSecond" + }, + "filtersPerEntityType": {} + } + } + ] +} diff --git a/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_id_base.json b/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_id_base.json new file mode 100644 index 000000000..d72ca250a --- /dev/null +++ b/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_id_base.json @@ -0,0 +1,54 @@ +{ + "metricId": "builtin:service.response.time", + "displayName": "Response time", + "description": "", + "unit": "MicroSecond", + "dduBillable": false, + "created": 0, + "lastWritten": 1666261537089, + "entityType": [ + "SERVICE" + ], + "aggregationTypes": [ + "auto", + "avg", + "count", + "max", + "median", + "min", + "percentile", + "sum" + ], + "transformations": [ + "filter", + "fold", + "limit", + "merge", + "names", + "parents", + "timeshift", + "sort", + "last", + "splitBy", + "lastReal", + "setUnit" + ], + "defaultAggregation": { + "type": "avg" + }, + "dimensionDefinitions": [ + { + "key": "dt.entity.service", + "name": "Service", + "displayName": "Service", + "index": 0, + "type": "ENTITY" + } + ], + "tags": [], + "metricValueType": { + "type": "unknown" + }, + "scalar": false, + "resolutionInfSupported": true +} diff --git a/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_id_full.json b/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_id_full.json new file mode 100644 index 000000000..e795ab0f2 --- /dev/null +++ b/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_id_full.json @@ -0,0 +1,46 @@ +{ + "metricId": "builtin:service.response.time:splitBy():avg:names", + "displayName": "Response time", + "description": "", + "unit": "MicroSecond", + "dduBillable": false, + "created": 0, + "lastWritten": 1666261597089, + "entityType": [ + "SERVICE" + ], + "aggregationTypes": [ + "auto", + "value" + ], + "transformations": [ + "fold", + "limit", + "timeshift", + "rate", + "sort", + "last", + "splitBy", + "default", + "delta", + "lastReal", + "smooth", + "rollup", + "partition", + "toUnit", + "setUnit" + ], + "defaultAggregation": { + "type": "value" + }, + "dimensionDefinitions": [], + "tags": [], + "metricValueType": { + "type": "unknown" + }, + "scalar": false, + "resolutionInfSupported": true, + "warnings": [ + "The field dimensionCardinalities is only supported for untransformed single metric keys and was ignored." + ] +} diff --git a/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_query_first.json b/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_query_first.json new file mode 100644 index 000000000..14a17ba75 --- /dev/null +++ b/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_query_first.json @@ -0,0 +1,310 @@ +{ + "totalCount": 1, + "nextPageKey": null, + "resolution": "10m", + "result": [ + { + "metricId": "builtin:service.response.time:splitBy():avg:names", + "dataPointCountRatio": 0.0699408, + "dimensionCountRatio": 0.04857, + "data": [ + { + "dimensions": [], + "dimensionMap": {}, + "timestamps": [ + 1664323800000, + 1664324400000, + 1664325000000, + 1664325600000, + 1664326200000, + 1664326800000, + 1664327400000, + 1664328000000, + 1664328600000, + 1664329200000, + 1664329800000, + 1664330400000, + 1664331000000, + 1664331600000, + 1664332200000, + 1664332800000, + 1664333400000, + 1664334000000, + 1664334600000, + 1664335200000, + 1664335800000, + 1664336400000, + 1664337000000, + 1664337600000, + 1664338200000, + 1664338800000, + 1664339400000, + 1664340000000, + 1664340600000, + 1664341200000, + 1664341800000, + 1664342400000, + 1664343000000, + 1664343600000, + 1664344200000, + 1664344800000, + 1664345400000, + 1664346000000, + 1664346600000, + 1664347200000, + 1664347800000, + 1664348400000, + 1664349000000, + 1664349600000, + 1664350200000, + 1664350800000, + 1664351400000, + 1664352000000, + 1664352600000, + 1664353200000, + 1664353800000, + 1664354400000, + 1664355000000, + 1664355600000, + 1664356200000, + 1664356800000, + 1664357400000, + 1664358000000, + 1664358600000, + 1664359200000, + 1664359800000, + 1664360400000, + 1664361000000, + 1664361600000, + 1664362200000, + 1664362800000, + 1664363400000, + 1664364000000, + 1664364600000, + 1664365200000, + 1664365800000, + 1664366400000, + 1664367000000, + 1664367600000, + 1664368200000, + 1664368800000, + 1664369400000, + 1664370000000, + 1664370600000, + 1664371200000, + 1664371800000, + 1664372400000, + 1664373000000, + 1664373600000, + 1664374200000, + 1664374800000, + 1664375400000, + 1664376000000, + 1664376600000, + 1664377200000, + 1664377800000, + 1664378400000, + 1664379000000, + 1664379600000, + 1664380200000, + 1664380800000, + 1664381400000, + 1664382000000, + 1664382600000, + 1664383200000, + 1664383800000, + 1664384400000, + 1664385000000, + 1664385600000, + 1664386200000, + 1664386800000, + 1664387400000, + 1664388000000, + 1664388600000, + 1664389200000, + 1664389800000, + 1664390400000, + 1664391000000, + 1664391600000, + 1664392200000, + 1664392800000, + 1664393400000, + 1664394000000, + 1664394600000, + 1664395200000, + 1664395800000, + 1664396400000, + 1664397000000, + 1664397600000, + 1664398200000, + 1664398800000, + 1664399400000, + 1664400000000, + 1664400600000, + 1664401200000, + 1664401800000, + 1664402400000, + 1664403000000, + 1664403600000, + 1664404200000, + 1664404800000, + 1664405400000, + 1664406000000, + 1664406600000, + 1664407200000, + 1664407800000, + 1664408400000, + 1664409000000, + 1664409600000 + ], + "values": [ + 54820.03373164987, + 55218.99471220384, + 55071.49928878831, + 54949.78353754613, + 54792.09863487236, + 54758.23014110582, + 54839.84122407719, + 55080.79677446355, + 54553.826345367015, + 54700.14292835205, + 54737.2003722548, + 55002.27033954363, + 54777.74025995784, + 54920.93069442279, + 54564.00216313528, + 55127.85502136979, + 54649.187273431846, + 54903.09771335987, + 54811.92754307572, + 54954.78607277203, + 55320.74452442934, + 54811.32374927372, + 54789.12016856007, + 54832.67417526022, + 54882.66109509589, + 54839.12680134036, + 54963.65066827593, + 54833.89919248517, + 54726.41233535216, + 54711.6734476709, + 55050.82096058733, + 54804.508598504384, + 55043.67518606438, + 54909.71511835754, + 54832.34011665316, + 54965.59893223934, + 54768.34111642939, + 54769.87290857302, + 54838.65544370555, + 54639.0661843702, + 54871.37265354548, + 54877.451343791385, + 54884.69594899173, + 54830.335242511755, + 54909.95975180857, + 54729.30936347974, + 54857.07455554992, + 54773.180566804505, + 57040.51818064292, + 54815.08510582958, + 54638.81806222, + 54931.655559810424, + 54822.79794409753, + 54943.88083062903, + 59189.65722406022, + 54863.39737403222, + 54865.96204806309, + 54941.49568657858, + 55013.6698386867, + 54927.511166520126, + 55034.0434218988, + 54726.585503515416, + 55179.36971275004, + 55140.22470784515, + 54873.353515106246, + 55046.39285477691, + 55219.21159741783, + 53953.047819327396, + 54398.93323329027, + 54638.48781541484, + 55437.86906753935, + 54777.13448401533, + 54614.82884669915, + 54741.899303641876, + 54811.067369581295, + 54589.01308088208, + 55234.24117269804, + 54332.28349304332, + 54490.19518987563, + 54634.571387632306, + 54716.22231328921, + 56106.07520870239, + 54878.24152910459, + 55026.275312892016, + 54081.654492523165, + 55144.45957110088, + 56071.58167898117, + 54385.870900343165, + 54458.36845208812, + 54230.56669550621, + 54258.731741534466, + 55081.29456308478, + 55865.60168431374, + 55196.5338318562, + 53852.42383215121, + 55496.39855469356, + 55323.17160213487, + 54835.549739796435, + 55074.18764280705, + 54714.90320689123, + 55315.876354608066, + 54894.23055589334, + 54762.26818212387, + 54519.16418366507, + 55263.627200541814, + 54644.019993779904, + 54705.427177621474, + 55111.50943922037, + 54662.1410122232, + 54391.73053502808, + 53794.626921183415, + 54720.05095351137, + 54882.17257639208, + 54895.636923092046, + 54843.223935575785, + 55041.561428220484, + 54674.520597537055, + 54655.125680576966, + 54893.71445497061, + 54744.62396520675, + 54977.05859708377, + 55160.030160401315, + 54764.61432756854, + 54725.26721258407, + 55183.882339578675, + 55129.065580386196, + 54626.46311949491, + 54849.14344749185, + 54963.18046961137, + 54816.06156961105, + 54733.57320804991, + 55447.00928208658, + 55124.80110455526, + 55027.30869873211, + 54987.10183916898, + 55062.99573491732, + 53716.04621333344, + 55011.03937116556, + 54764.56758589279, + 54588.83623561774, + 54533.389405348695, + 54877.71688140235, + 54616.27774406533, + 55079.69618901161 + ] + } + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_query_second.json b/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_query_second.json new file mode 100644 index 000000000..4268f0c6f --- /dev/null +++ b/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_get_by_query_second.json @@ -0,0 +1,24 @@ +{ + "totalCount": 1, + "nextPageKey": null, + "resolution": "Inf", + "result": [ + { + "metricId": "builtin:service.response.time:splitBy():avg:names", + "dataPointCountRatio": 2.4285E-4, + "dimensionCountRatio": 0.04857, + "data": [ + { + "dimensions": [], + "dimensionMap": {}, + "timestamps": [ + 1664409600000 + ], + "values": [ + 54896.48858596068 + ] + } + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_units_convert1.json b/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_units_convert1.json new file mode 100644 index 000000000..eb19c9b79 --- /dev/null +++ b/internal/sli/testdata/dashboards/custom_charting/unit_transform_milliseconds/metrics_units_convert1.json @@ -0,0 +1,4 @@ +{ + "unitId": "MilliSecond", + "resultValue": 54.89648858596068 +} diff --git a/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/dashboard.json b/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/dashboard.json new file mode 100644 index 000000000..69affeda9 --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/dashboard.json @@ -0,0 +1,116 @@ +{ + "metadata": { + "configurationVersions": [ + 5 + ], + "clusterVersion": "1.232.0.20211118-204216" + }, + "id": "12345678-1111-4444-8888-123456789012", + "dashboardMetadata": { + "name": "Management Zone Test", + "shared": false, + "owner": "", + "popularity": 1 + }, + "tiles": [ + { + "name": "SRT Bytes", + "tileType": "DATA_EXPLORER", + "configured": true, + "bounds": { + "top": 76, + "left": 114, + "width": 304, + "height": 304 + }, + "tileFilter": {}, + "customName": "Data explorer results", + "queries": [ + { + "id": "A", + "metric": "builtin:service.response.time", + "spaceAggregation": "AVG", + "timeAggregation": "DEFAULT", + "splitBy": [], + "filterBy": { + "nestedFilters": [], + "criteria": [] + }, + "enabled": true + } + ], + "visualConfig": { + "type": "GRAPH_CHART", + "global": { + "hideLegend": false + }, + "rules": [ + { + "matcher": "A:", + "unitTransform": "Byte", + "valueFormat": "auto", + "properties": { + "color": "DEFAULT", + "seriesType": "LINE" + }, + "seriesOverrides": [] + } + ], + "axes": { + "xAxis": { + "displayName": "", + "visible": true + }, + "yAxes": [ + { + "displayName": "", + "visible": true, + "min": "AUTO", + "max": "AUTO", + "position": "LEFT", + "queryIds": [ + "A" + ], + "defaultAxis": true + } + ] + }, + "heatmapSettings": { + "yAxis": "VALUE" + }, + "thresholds": [ + { + "axisTarget": "LEFT", + "rules": [ + { + "color": "#7dc540" + }, + { + "color": "#f5d30f" + }, + { + "color": "#dc172a" + } + ], + "queryId": "", + "visible": true + } + ], + "tableSettings": { + "isThresholdBackgroundAppliedToCell": false + }, + "graphChartSettings": { + "connectNulls": false + }, + "honeycombSettings": { + "showHive": true, + "showLegend": true, + "showLabels": false + } + }, + "metricExpressions": [ + "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_get_by_id.json b/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_get_by_id.json new file mode 100644 index 000000000..cd0c350cb --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_get_by_id.json @@ -0,0 +1,46 @@ +{ + "metricId": "(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names", + "displayName": "Response time", + "description": "", + "unit": "MicroSecond", + "dduBillable": false, + "created": 0, + "lastWritten": null, + "entityType": [ + "SERVICE" + ], + "aggregationTypes": [ + "auto", + "value" + ], + "transformations": [ + "fold", + "limit", + "timeshift", + "rate", + "sort", + "last", + "splitBy", + "default", + "delta", + "lastReal", + "smooth", + "rollup", + "partition", + "toUnit", + "setUnit" + ], + "defaultAggregation": { + "type": "value" + }, + "dimensionDefinitions": [], + "tags": [], + "metricValueType": { + "type": "unknown" + }, + "scalar": false, + "resolutionInfSupported": true, + "warnings": [ + "The field dimensionCardinalities is only supported for untransformed single metric keys and was ignored." + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_get_by_query1.json b/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_get_by_query1.json new file mode 100644 index 000000000..a441fe6b0 --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_get_by_query1.json @@ -0,0 +1,310 @@ +{ + "totalCount": 1, + "nextPageKey": null, + "resolution": "10m", + "result": [ + { + "metricId": "(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names", + "dataPointCountRatio": 0.0699408, + "dimensionCountRatio": 0.04857, + "data": [ + { + "dimensions": [], + "dimensionMap": {}, + "timestamps": [ + 1664323800000, + 1664324400000, + 1664325000000, + 1664325600000, + 1664326200000, + 1664326800000, + 1664327400000, + 1664328000000, + 1664328600000, + 1664329200000, + 1664329800000, + 1664330400000, + 1664331000000, + 1664331600000, + 1664332200000, + 1664332800000, + 1664333400000, + 1664334000000, + 1664334600000, + 1664335200000, + 1664335800000, + 1664336400000, + 1664337000000, + 1664337600000, + 1664338200000, + 1664338800000, + 1664339400000, + 1664340000000, + 1664340600000, + 1664341200000, + 1664341800000, + 1664342400000, + 1664343000000, + 1664343600000, + 1664344200000, + 1664344800000, + 1664345400000, + 1664346000000, + 1664346600000, + 1664347200000, + 1664347800000, + 1664348400000, + 1664349000000, + 1664349600000, + 1664350200000, + 1664350800000, + 1664351400000, + 1664352000000, + 1664352600000, + 1664353200000, + 1664353800000, + 1664354400000, + 1664355000000, + 1664355600000, + 1664356200000, + 1664356800000, + 1664357400000, + 1664358000000, + 1664358600000, + 1664359200000, + 1664359800000, + 1664360400000, + 1664361000000, + 1664361600000, + 1664362200000, + 1664362800000, + 1664363400000, + 1664364000000, + 1664364600000, + 1664365200000, + 1664365800000, + 1664366400000, + 1664367000000, + 1664367600000, + 1664368200000, + 1664368800000, + 1664369400000, + 1664370000000, + 1664370600000, + 1664371200000, + 1664371800000, + 1664372400000, + 1664373000000, + 1664373600000, + 1664374200000, + 1664374800000, + 1664375400000, + 1664376000000, + 1664376600000, + 1664377200000, + 1664377800000, + 1664378400000, + 1664379000000, + 1664379600000, + 1664380200000, + 1664380800000, + 1664381400000, + 1664382000000, + 1664382600000, + 1664383200000, + 1664383800000, + 1664384400000, + 1664385000000, + 1664385600000, + 1664386200000, + 1664386800000, + 1664387400000, + 1664388000000, + 1664388600000, + 1664389200000, + 1664389800000, + 1664390400000, + 1664391000000, + 1664391600000, + 1664392200000, + 1664392800000, + 1664393400000, + 1664394000000, + 1664394600000, + 1664395200000, + 1664395800000, + 1664396400000, + 1664397000000, + 1664397600000, + 1664398200000, + 1664398800000, + 1664399400000, + 1664400000000, + 1664400600000, + 1664401200000, + 1664401800000, + 1664402400000, + 1664403000000, + 1664403600000, + 1664404200000, + 1664404800000, + 1664405400000, + 1664406000000, + 1664406600000, + 1664407200000, + 1664407800000, + 1664408400000, + 1664409000000, + 1664409600000 + ], + "values": [ + 54820.03373164987, + 55218.99471220384, + 55071.49928878831, + 54949.78353754613, + 54792.09863487236, + 54758.23014110582, + 54839.84122407719, + 55080.79677446355, + 54553.826345367015, + 54700.14292835205, + 54737.2003722548, + 55002.27033954363, + 54777.74025995784, + 54920.93069442279, + 54564.00216313528, + 55127.85502136979, + 54649.187273431846, + 54903.09771335987, + 54811.92754307572, + 54954.78607277203, + 55320.74452442934, + 54811.32374927372, + 54789.12016856007, + 54832.67417526022, + 54882.66109509589, + 54839.12680134036, + 54963.65066827593, + 54833.89919248517, + 54726.41233535216, + 54711.6734476709, + 55050.82096058733, + 54804.508598504384, + 55043.67518606438, + 54909.71511835754, + 54832.34011665316, + 54965.59893223934, + 54768.34111642939, + 54769.87290857302, + 54838.65544370555, + 54639.0661843702, + 54871.37265354548, + 54877.451343791385, + 54884.69594899173, + 54830.335242511755, + 54909.95975180857, + 54729.30936347974, + 54857.07455554992, + 54773.180566804505, + 57040.51818064292, + 54815.08510582958, + 54638.81806222, + 54931.655559810424, + 54822.79794409753, + 54943.88083062903, + 59189.65722406022, + 54863.39737403222, + 54865.96204806309, + 54941.49568657858, + 55013.6698386867, + 54927.511166520126, + 55034.0434218988, + 54726.585503515416, + 55179.36971275004, + 55140.22470784515, + 54873.353515106246, + 55046.39285477691, + 55219.21159741783, + 53953.047819327396, + 54398.93323329027, + 54638.48781541484, + 55437.86906753935, + 54777.13448401533, + 54614.82884669915, + 54741.899303641876, + 54811.067369581295, + 54589.01308088208, + 55234.24117269804, + 54332.28349304332, + 54490.19518987563, + 54634.571387632306, + 54716.22231328921, + 56106.07520870239, + 54878.24152910459, + 55026.275312892016, + 54081.654492523165, + 55144.45957110088, + 56071.58167898117, + 54385.870900343165, + 54458.36845208812, + 54230.56669550621, + 54258.731741534466, + 55081.29456308478, + 55865.60168431374, + 55196.5338318562, + 53852.42383215121, + 55496.39855469356, + 55323.17160213487, + 54835.549739796435, + 55074.18764280705, + 54714.90320689123, + 55315.876354608066, + 54894.23055589334, + 54762.26818212387, + 54519.16418366507, + 55263.627200541814, + 54644.019993779904, + 54705.427177621474, + 55111.50943922037, + 54662.1410122232, + 54391.73053502808, + 53794.626921183415, + 54720.05095351137, + 54882.17257639208, + 54895.636923092046, + 54843.223935575785, + 55041.561428220484, + 54674.520597537055, + 54655.125680576966, + 54893.71445497061, + 54744.62396520675, + 54977.05859708377, + 55160.030160401315, + 54764.61432756854, + 54725.26721258407, + 55183.882339578675, + 55129.065580386196, + 54626.46311949491, + 54849.14344749185, + 54963.18046961137, + 54816.06156961105, + 54733.57320804991, + 55447.00928208658, + 55124.80110455526, + 55027.30869873211, + 54987.10183916898, + 55062.99573491732, + 53716.04621333344, + 55011.03937116556, + 54764.56758589279, + 54588.83623561774, + 54533.389405348695, + 54877.71688140235, + 54616.27774406533, + 55079.69618901161 + ] + } + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_get_by_query2.json b/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_get_by_query2.json new file mode 100644 index 000000000..ff5a9bdce --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_get_by_query2.json @@ -0,0 +1,24 @@ +{ + "totalCount": 1, + "nextPageKey": null, + "resolution": "Inf", + "result": [ + { + "metricId": "(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names", + "dataPointCountRatio": 2.4285E-4, + "dimensionCountRatio": 0.04857, + "data": [ + { + "dimensions": [], + "dimensionMap": {}, + "timestamps": [ + 1664409600000 + ], + "values": [ + 54896.48858596068 + ] + } + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_units_convert_error.json b/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_units_convert_error.json new file mode 100644 index 000000000..72cecf571 --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/unit_transform_error/metrics_units_convert_error.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": 400, + "message": "Cannot convert MicroSecond to Byte." + } +} diff --git a/internal/sli/testdata/dashboards/data_explorer/unit_transform_is_not_auto/dashboard.json b/internal/sli/testdata/dashboards/data_explorer/unit_transform_is_not_auto/dashboard.json deleted file mode 100644 index d7e092279..000000000 --- a/internal/sli/testdata/dashboards/data_explorer/unit_transform_is_not_auto/dashboard.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "metadata": { - "configurationVersions": [ - 5 - ], - "clusterVersion": "1.245.0.20220804-195908" - }, - "id": "12345678-1111-4444-8888-123456789012", - "dashboardMetadata": { - "name": "", - "shared": false, - "owner": "", - "popularity": 1 - }, - "tiles": [ - { - "name": "Service Response Time; sli=srt", - "tileType": "DATA_EXPLORER", - "configured": true, - "bounds": { - "top": 266, - "left": 0, - "width": 1140, - "height": 190 - }, - "tileFilter": {}, - "customName": "Data explorer results", - "queries": [ - { - "id": "A", - "metric": "builtin:service.response.time", - "spaceAggregation": "AVG", - "timeAggregation": "DEFAULT", - "splitBy": [], - "filterBy": { - "nestedFilters": [], - "criteria": [] - }, - "enabled": true - } - ], - "visualConfig": { - "type": "GRAPH_CHART", - "global": { - "hideLegend": false - }, - "rules": [ - { - "matcher": "A:", - "unitTransform": "MilliSecond", - "valueFormat": "auto", - "properties": { - "color": "DEFAULT", - "seriesType": "LINE" - }, - "seriesOverrides": [] - } - ], - "axes": { - "xAxis": { - "displayName": "", - "visible": true - }, - "yAxes": [ - { - "displayName": "", - "visible": true, - "min": "AUTO", - "max": "AUTO", - "position": "LEFT", - "queryIds": [ - "A" - ], - "defaultAxis": true - } - ] - }, - "heatmapSettings": { - "yAxis": "VALUE" - }, - "thresholds": [ - { - "axisTarget": "LEFT", - "rules": [ - { - "value": 0, - "color": "#7dc540" - }, - { - "value": 68000, - "color": "#f5d30f" - }, - { - "value": 69000, - "color": "#dc172a" - } - ], - "queryId": "", - "visible": true - } - ], - "tableSettings": { - "isThresholdBackgroundAppliedToCell": false - }, - "graphChartSettings": { - "connectNulls": false - }, - "honeycombSettings": { - "showHive": true, - "showLegend": true, - "showLabels": false - } - }, - "queriesSettings": { - "resolution": "" - }, - "metricExpressions": [ - "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" - ] - } - ] -} diff --git a/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/dashboard.json b/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/dashboard.json new file mode 100644 index 000000000..7b5d3f102 --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/dashboard.json @@ -0,0 +1,116 @@ +{ + "metadata": { + "configurationVersions": [ + 5 + ], + "clusterVersion": "1.232.0.20211118-204216" + }, + "id": "12345678-1111-4444-8888-123456789012", + "dashboardMetadata": { + "name": "Management Zone Test", + "shared": false, + "owner": "", + "popularity": 1 + }, + "tiles": [ + { + "name": "SRT Milliseconds", + "tileType": "DATA_EXPLORER", + "configured": true, + "bounds": { + "top": 76, + "left": 114, + "width": 304, + "height": 304 + }, + "tileFilter": {}, + "customName": "Data explorer results", + "queries": [ + { + "id": "A", + "metric": "builtin:service.response.time", + "spaceAggregation": "AVG", + "timeAggregation": "DEFAULT", + "splitBy": [], + "filterBy": { + "nestedFilters": [], + "criteria": [] + }, + "enabled": true + } + ], + "visualConfig": { + "type": "GRAPH_CHART", + "global": { + "hideLegend": false + }, + "rules": [ + { + "matcher": "A:", + "unitTransform": "MilliSecond", + "valueFormat": "auto", + "properties": { + "color": "DEFAULT", + "seriesType": "LINE" + }, + "seriesOverrides": [] + } + ], + "axes": { + "xAxis": { + "displayName": "", + "visible": true + }, + "yAxes": [ + { + "displayName": "", + "visible": true, + "min": "AUTO", + "max": "AUTO", + "position": "LEFT", + "queryIds": [ + "A" + ], + "defaultAxis": true + } + ] + }, + "heatmapSettings": { + "yAxis": "VALUE" + }, + "thresholds": [ + { + "axisTarget": "LEFT", + "rules": [ + { + "color": "#7dc540" + }, + { + "color": "#f5d30f" + }, + { + "color": "#dc172a" + } + ], + "queryId": "", + "visible": true + } + ], + "tableSettings": { + "isThresholdBackgroundAppliedToCell": false + }, + "graphChartSettings": { + "connectNulls": false + }, + "honeycombSettings": { + "showHive": true, + "showLegend": true, + "showLabels": false + } + }, + "metricExpressions": [ + "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_get_by_id.json b/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_get_by_id.json new file mode 100644 index 000000000..cd0c350cb --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_get_by_id.json @@ -0,0 +1,46 @@ +{ + "metricId": "(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names", + "displayName": "Response time", + "description": "", + "unit": "MicroSecond", + "dduBillable": false, + "created": 0, + "lastWritten": null, + "entityType": [ + "SERVICE" + ], + "aggregationTypes": [ + "auto", + "value" + ], + "transformations": [ + "fold", + "limit", + "timeshift", + "rate", + "sort", + "last", + "splitBy", + "default", + "delta", + "lastReal", + "smooth", + "rollup", + "partition", + "toUnit", + "setUnit" + ], + "defaultAggregation": { + "type": "value" + }, + "dimensionDefinitions": [], + "tags": [], + "metricValueType": { + "type": "unknown" + }, + "scalar": false, + "resolutionInfSupported": true, + "warnings": [ + "The field dimensionCardinalities is only supported for untransformed single metric keys and was ignored." + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_get_by_query1.json b/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_get_by_query1.json new file mode 100644 index 000000000..a441fe6b0 --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_get_by_query1.json @@ -0,0 +1,310 @@ +{ + "totalCount": 1, + "nextPageKey": null, + "resolution": "10m", + "result": [ + { + "metricId": "(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names", + "dataPointCountRatio": 0.0699408, + "dimensionCountRatio": 0.04857, + "data": [ + { + "dimensions": [], + "dimensionMap": {}, + "timestamps": [ + 1664323800000, + 1664324400000, + 1664325000000, + 1664325600000, + 1664326200000, + 1664326800000, + 1664327400000, + 1664328000000, + 1664328600000, + 1664329200000, + 1664329800000, + 1664330400000, + 1664331000000, + 1664331600000, + 1664332200000, + 1664332800000, + 1664333400000, + 1664334000000, + 1664334600000, + 1664335200000, + 1664335800000, + 1664336400000, + 1664337000000, + 1664337600000, + 1664338200000, + 1664338800000, + 1664339400000, + 1664340000000, + 1664340600000, + 1664341200000, + 1664341800000, + 1664342400000, + 1664343000000, + 1664343600000, + 1664344200000, + 1664344800000, + 1664345400000, + 1664346000000, + 1664346600000, + 1664347200000, + 1664347800000, + 1664348400000, + 1664349000000, + 1664349600000, + 1664350200000, + 1664350800000, + 1664351400000, + 1664352000000, + 1664352600000, + 1664353200000, + 1664353800000, + 1664354400000, + 1664355000000, + 1664355600000, + 1664356200000, + 1664356800000, + 1664357400000, + 1664358000000, + 1664358600000, + 1664359200000, + 1664359800000, + 1664360400000, + 1664361000000, + 1664361600000, + 1664362200000, + 1664362800000, + 1664363400000, + 1664364000000, + 1664364600000, + 1664365200000, + 1664365800000, + 1664366400000, + 1664367000000, + 1664367600000, + 1664368200000, + 1664368800000, + 1664369400000, + 1664370000000, + 1664370600000, + 1664371200000, + 1664371800000, + 1664372400000, + 1664373000000, + 1664373600000, + 1664374200000, + 1664374800000, + 1664375400000, + 1664376000000, + 1664376600000, + 1664377200000, + 1664377800000, + 1664378400000, + 1664379000000, + 1664379600000, + 1664380200000, + 1664380800000, + 1664381400000, + 1664382000000, + 1664382600000, + 1664383200000, + 1664383800000, + 1664384400000, + 1664385000000, + 1664385600000, + 1664386200000, + 1664386800000, + 1664387400000, + 1664388000000, + 1664388600000, + 1664389200000, + 1664389800000, + 1664390400000, + 1664391000000, + 1664391600000, + 1664392200000, + 1664392800000, + 1664393400000, + 1664394000000, + 1664394600000, + 1664395200000, + 1664395800000, + 1664396400000, + 1664397000000, + 1664397600000, + 1664398200000, + 1664398800000, + 1664399400000, + 1664400000000, + 1664400600000, + 1664401200000, + 1664401800000, + 1664402400000, + 1664403000000, + 1664403600000, + 1664404200000, + 1664404800000, + 1664405400000, + 1664406000000, + 1664406600000, + 1664407200000, + 1664407800000, + 1664408400000, + 1664409000000, + 1664409600000 + ], + "values": [ + 54820.03373164987, + 55218.99471220384, + 55071.49928878831, + 54949.78353754613, + 54792.09863487236, + 54758.23014110582, + 54839.84122407719, + 55080.79677446355, + 54553.826345367015, + 54700.14292835205, + 54737.2003722548, + 55002.27033954363, + 54777.74025995784, + 54920.93069442279, + 54564.00216313528, + 55127.85502136979, + 54649.187273431846, + 54903.09771335987, + 54811.92754307572, + 54954.78607277203, + 55320.74452442934, + 54811.32374927372, + 54789.12016856007, + 54832.67417526022, + 54882.66109509589, + 54839.12680134036, + 54963.65066827593, + 54833.89919248517, + 54726.41233535216, + 54711.6734476709, + 55050.82096058733, + 54804.508598504384, + 55043.67518606438, + 54909.71511835754, + 54832.34011665316, + 54965.59893223934, + 54768.34111642939, + 54769.87290857302, + 54838.65544370555, + 54639.0661843702, + 54871.37265354548, + 54877.451343791385, + 54884.69594899173, + 54830.335242511755, + 54909.95975180857, + 54729.30936347974, + 54857.07455554992, + 54773.180566804505, + 57040.51818064292, + 54815.08510582958, + 54638.81806222, + 54931.655559810424, + 54822.79794409753, + 54943.88083062903, + 59189.65722406022, + 54863.39737403222, + 54865.96204806309, + 54941.49568657858, + 55013.6698386867, + 54927.511166520126, + 55034.0434218988, + 54726.585503515416, + 55179.36971275004, + 55140.22470784515, + 54873.353515106246, + 55046.39285477691, + 55219.21159741783, + 53953.047819327396, + 54398.93323329027, + 54638.48781541484, + 55437.86906753935, + 54777.13448401533, + 54614.82884669915, + 54741.899303641876, + 54811.067369581295, + 54589.01308088208, + 55234.24117269804, + 54332.28349304332, + 54490.19518987563, + 54634.571387632306, + 54716.22231328921, + 56106.07520870239, + 54878.24152910459, + 55026.275312892016, + 54081.654492523165, + 55144.45957110088, + 56071.58167898117, + 54385.870900343165, + 54458.36845208812, + 54230.56669550621, + 54258.731741534466, + 55081.29456308478, + 55865.60168431374, + 55196.5338318562, + 53852.42383215121, + 55496.39855469356, + 55323.17160213487, + 54835.549739796435, + 55074.18764280705, + 54714.90320689123, + 55315.876354608066, + 54894.23055589334, + 54762.26818212387, + 54519.16418366507, + 55263.627200541814, + 54644.019993779904, + 54705.427177621474, + 55111.50943922037, + 54662.1410122232, + 54391.73053502808, + 53794.626921183415, + 54720.05095351137, + 54882.17257639208, + 54895.636923092046, + 54843.223935575785, + 55041.561428220484, + 54674.520597537055, + 54655.125680576966, + 54893.71445497061, + 54744.62396520675, + 54977.05859708377, + 55160.030160401315, + 54764.61432756854, + 54725.26721258407, + 55183.882339578675, + 55129.065580386196, + 54626.46311949491, + 54849.14344749185, + 54963.18046961137, + 54816.06156961105, + 54733.57320804991, + 55447.00928208658, + 55124.80110455526, + 55027.30869873211, + 54987.10183916898, + 55062.99573491732, + 53716.04621333344, + 55011.03937116556, + 54764.56758589279, + 54588.83623561774, + 54533.389405348695, + 54877.71688140235, + 54616.27774406533, + 55079.69618901161 + ] + } + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_get_by_query2.json b/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_get_by_query2.json new file mode 100644 index 000000000..ff5a9bdce --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_get_by_query2.json @@ -0,0 +1,24 @@ +{ + "totalCount": 1, + "nextPageKey": null, + "resolution": "Inf", + "result": [ + { + "metricId": "(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names", + "dataPointCountRatio": 2.4285E-4, + "dimensionCountRatio": 0.04857, + "data": [ + { + "dimensions": [], + "dimensionMap": {}, + "timestamps": [ + 1664409600000 + ], + "values": [ + 54896.48858596068 + ] + } + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_units_convert1.json b/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_units_convert1.json new file mode 100644 index 000000000..eb19c9b79 --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/unit_transform_milliseconds/metrics_units_convert1.json @@ -0,0 +1,4 @@ +{ + "unitId": "MilliSecond", + "resultValue": 54.89648858596068 +} diff --git a/internal/test/dynatrace_test_file_updater.go b/internal/test/dynatrace_test_file_updater.go index 10fa00fd2..af4c894a5 100644 --- a/internal/test/dynatrace_test_file_updater.go +++ b/internal/test/dynatrace_test_file_updater.go @@ -90,15 +90,13 @@ func (h *dynatraceTestFileUpdater) tryUpdateTestFileUsingGet(url string, filenam } func shouldUpdateFileForURL(url string) bool { - if strings.HasPrefix(url, "/api/v2/metrics") || + return strings.HasPrefix(url, "/api/v2/metrics") || + strings.HasPrefix(url, "/api/v2/units") || strings.HasPrefix(url, "/api/v2/slo") || strings.HasPrefix(url, "/api/v2/problems") || strings.HasPrefix(url, "/api/v1/userSessionQueryLanguage/table") || - strings.HasPrefix(url, "/api/v2/securityProblems") { - return true - } + strings.HasPrefix(url, "/api/v2/securityProblems") - return false } func createClient() *http.Client { From 99ed02a6a8d51cd84e96967aa2891200a77beb3a Mon Sep 17 00:00:00 2001 From: Arthur Pitman Date: Thu, 20 Oct 2022 16:22:29 +0200 Subject: [PATCH 7/7] Improve handling of multiple visualization rules for a single query Signed-off-by: Arthur Pitman --- .../data_explorer_tile_processing.go | 44 ++- ...trics_from_dashboard_data_explorer_test.go | 25 ++ .../dashboard.json | 126 +++++++ .../dashboard.json | 126 +++++++ .../metrics_get_by_id.json | 46 +++ .../metrics_get_by_query1.json | 310 ++++++++++++++++++ .../metrics_get_by_query2.json | 24 ++ .../metrics_units_convert1.json | 4 + 8 files changed, 699 insertions(+), 6 deletions(-) create mode 100644 internal/sli/testdata/dashboards/data_explorer/error_two_matching_visual_config_rules/dashboard.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/dashboard.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_get_by_id.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_get_by_query1.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_get_by_query2.json create mode 100644 internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_units_convert1.json diff --git a/internal/sli/dashboard/data_explorer_tile_processing.go b/internal/sli/dashboard/data_explorer_tile_processing.go index 8924adffa..59eb368d8 100644 --- a/internal/sli/dashboard/data_explorer_tile_processing.go +++ b/internal/sli/dashboard/data_explorer_tile_processing.go @@ -124,6 +124,11 @@ func (v *dataExplorerTileValidator) tryValidate() (*validatedDataExplorerTile, e errs = append(errs, err) } + targetUnitID, err := getUnitTransform(v.tile.VisualConfig, queryID) + if err != nil { + errs = append(errs, err) + } + if len(errs) > 0 { return nil, &dataExplorerTileValidationError{ sloDefinition: sloDefinition, @@ -133,7 +138,7 @@ func (v *dataExplorerTileValidator) tryValidate() (*validatedDataExplorerTile, e return &validatedDataExplorerTile{ sloDefinition: sloDefinition, - targetUnitID: getUnitTransform(v.tile.VisualConfig, queryID), + targetUnitID: targetUnitID, singleValueVisualization: isSingleValueVisualizationType(v.tile.VisualConfig), query: *query, }, nil @@ -163,18 +168,45 @@ func getQueryID(queries []dynatrace.DataExplorerQuery) (string, error) { return enabledQueryIDs[0], nil } -func getUnitTransform(visualConfig *dynatrace.VisualizationConfiguration, queryID string) string { +func getUnitTransform(visualConfig *dynatrace.VisualizationConfiguration, queryID string) (string, error) { if visualConfig == nil { - return "" + return "", nil + } + + if queryID == "" { + return "", nil + } + + matchingRule, err := tryGetMatchingVisualizationRule(visualConfig.Rules, queryID) + if err != nil { + return "", fmt.Errorf("could not get unit transform: %w", err) } + if matchingRule == nil { + return "", nil + } + + return matchingRule.UnitTransform, nil +} + +func tryGetMatchingVisualizationRule(rules []dynatrace.VisualizationRule, queryID string) (*dynatrace.VisualizationRule, error) { queryMatcher := createQueryMatcher(queryID) - for _, r := range visualConfig.Rules { + var matchingRules []dynatrace.VisualizationRule + for _, r := range rules { if r.Matcher == queryMatcher { - return r.UnitTransform + matchingRules = append(matchingRules, r) } } - return "" + + if len(matchingRules) == 0 { + return nil, nil + } + + if len(matchingRules) > 1 { + return nil, fmt.Errorf("expected one visualization rule for query '%s' but found %d", queryID, len(matchingRules)) + } + + return &matchingRules[0], nil } func createQueryMatcher(queryID string) string { diff --git a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go index 555d2ffef..b0bdd6d20 100644 --- a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go +++ b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go @@ -509,6 +509,31 @@ func TestRetrieveMetricsFromDashboardDataExplorerTile_UnitTransformError(t *test runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventFailureAssertionsFunc, createFailedSLIResultWithQueryAssertionsFunc("srt_bytes", requestBuilder.build(), "Cannot convert MicroSecond to Byte")) } +// TestRetrieveMetricsFromDashboardDataExplorerTile_PickCorrectVisualConfigRule tests that the visual config rule corresponding to the query is used and others are ignored. +func TestRetrieveMetricsFromDashboardDataExplorerTile_PickCorrectVisualConfigRule(t *testing.T) { + const testDataFolder = "./testdata/dashboards/data_explorer/pick_correct_visual_config_rule/" + + handler, expectedMetricsRequest := createHandlerForSuccessfulDataExplorerTestWithResolutionInf(t, + testDataFolder, + newMetricsV2QueryRequestBuilder("(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names"), + ) + handler.AddExact(buildMetricsUnitsConvertRequest("MicroSecond", 54896.48858596068, "MilliSecond"), filepath.Join(testDataFolder, "metrics_units_convert1.json")) + + sliResultsAssertionsFuncs := []func(t *testing.T, actual sliResult){ + createSuccessfulSLIResultAssertionsFunc("srt_milliseconds", 54.89648858596068, expectedMetricsRequest), + } + + runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventSuccessAssertionsFunc, sliResultsAssertionsFuncs...) +} + +// TestRetrieveMetricsFromDashboardDataExplorerTile_TwoMatchingVisualConfigRulesProducesError tests that two matchings visual config rules result in the expected error +func TestRetrieveMetricsFromDashboardDataExplorerTile_TwoMatchingVisualConfigRulesProducesError(t *testing.T) { + const testDataFolder = "./testdata/dashboards/data_explorer/error_two_matching_visual_config_rules/" + + handler := createHandlerForEarlyFailureDataExplorerTest(t, testDataFolder) + runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventFailureAssertionsFunc, createFailedSLIResultAssertionsFunc("srt", "expected one visualization rule for query", "found 2")) +} + func createExpectedServiceResponseTimeSLO(passCriteria []*keptnapi.SLOCriteria, warningCriteria []*keptnapi.SLOCriteria) *keptnapi.SLO { return &keptnapi.SLO{ SLI: "srt", diff --git a/internal/sli/testdata/dashboards/data_explorer/error_two_matching_visual_config_rules/dashboard.json b/internal/sli/testdata/dashboards/data_explorer/error_two_matching_visual_config_rules/dashboard.json new file mode 100644 index 000000000..8b4465064 --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/error_two_matching_visual_config_rules/dashboard.json @@ -0,0 +1,126 @@ +{ + "metadata": { + "configurationVersions": [ + 5 + ], + "clusterVersion": "1.232.0.20211118-204216" + }, + "id": "12345678-1111-4444-8888-123456789012", + "dashboardMetadata": { + "name": "Management Zone Test", + "shared": false, + "owner": "", + "popularity": 1 + }, + "tiles": [ + { + "name": "SRT", + "tileType": "DATA_EXPLORER", + "configured": true, + "bounds": { + "top": 76, + "left": 114, + "width": 304, + "height": 304 + }, + "tileFilter": {}, + "customName": "Data explorer results", + "queries": [ + { + "id": "A", + "metric": "builtin:service.response.time", + "spaceAggregation": "AVG", + "timeAggregation": "DEFAULT", + "splitBy": [], + "filterBy": { + "nestedFilters": [], + "criteria": [] + }, + "enabled": true + } + ], + "visualConfig": { + "type": "GRAPH_CHART", + "global": { + "hideLegend": false + }, + "rules": [ + { + "matcher": "A:", + "unitTransform": "MilliSecond", + "valueFormat": "auto", + "properties": { + "color": "DEFAULT", + "seriesType": "LINE" + }, + "seriesOverrides": [] + }, + { + "matcher": "A:", + "unitTransform": "Byte", + "valueFormat": "auto", + "properties": { + "color": "DEFAULT", + "seriesType": "LINE" + }, + "seriesOverrides": [] + } + ], + "axes": { + "xAxis": { + "displayName": "", + "visible": true + }, + "yAxes": [ + { + "displayName": "", + "visible": true, + "min": "AUTO", + "max": "AUTO", + "position": "LEFT", + "queryIds": [ + "A" + ], + "defaultAxis": true + } + ] + }, + "heatmapSettings": { + "yAxis": "VALUE" + }, + "thresholds": [ + { + "axisTarget": "LEFT", + "rules": [ + { + "color": "#7dc540" + }, + { + "color": "#f5d30f" + }, + { + "color": "#dc172a" + } + ], + "queryId": "", + "visible": true + } + ], + "tableSettings": { + "isThresholdBackgroundAppliedToCell": false + }, + "graphChartSettings": { + "connectNulls": false + }, + "honeycombSettings": { + "showHive": true, + "showLegend": true, + "showLabels": false + } + }, + "metricExpressions": [ + "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/dashboard.json b/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/dashboard.json new file mode 100644 index 000000000..d1fc31490 --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/dashboard.json @@ -0,0 +1,126 @@ +{ + "metadata": { + "configurationVersions": [ + 5 + ], + "clusterVersion": "1.232.0.20211118-204216" + }, + "id": "12345678-1111-4444-8888-123456789012", + "dashboardMetadata": { + "name": "Management Zone Test", + "shared": false, + "owner": "", + "popularity": 1 + }, + "tiles": [ + { + "name": "SRT Milliseconds", + "tileType": "DATA_EXPLORER", + "configured": true, + "bounds": { + "top": 76, + "left": 114, + "width": 304, + "height": 304 + }, + "tileFilter": {}, + "customName": "Data explorer results", + "queries": [ + { + "id": "A", + "metric": "builtin:service.response.time", + "spaceAggregation": "AVG", + "timeAggregation": "DEFAULT", + "splitBy": [], + "filterBy": { + "nestedFilters": [], + "criteria": [] + }, + "enabled": true + } + ], + "visualConfig": { + "type": "GRAPH_CHART", + "global": { + "hideLegend": false + }, + "rules": [ + { + "matcher": "A:", + "unitTransform": "MilliSecond", + "valueFormat": "auto", + "properties": { + "color": "DEFAULT", + "seriesType": "LINE" + }, + "seriesOverrides": [] + }, + { + "matcher": "^A:", + "unitTransform": "Byte", + "valueFormat": "auto", + "properties": { + "color": "DEFAULT", + "seriesType": "LINE" + }, + "seriesOverrides": [] + } + ], + "axes": { + "xAxis": { + "displayName": "", + "visible": true + }, + "yAxes": [ + { + "displayName": "", + "visible": true, + "min": "AUTO", + "max": "AUTO", + "position": "LEFT", + "queryIds": [ + "A" + ], + "defaultAxis": true + } + ] + }, + "heatmapSettings": { + "yAxis": "VALUE" + }, + "thresholds": [ + { + "axisTarget": "LEFT", + "rules": [ + { + "color": "#7dc540" + }, + { + "color": "#f5d30f" + }, + { + "color": "#dc172a" + } + ], + "queryId": "", + "visible": true + } + ], + "tableSettings": { + "isThresholdBackgroundAppliedToCell": false + }, + "graphChartSettings": { + "connectNulls": false + }, + "honeycombSettings": { + "showHive": true, + "showLegend": true, + "showLabels": false + } + }, + "metricExpressions": [ + "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_get_by_id.json b/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_get_by_id.json new file mode 100644 index 000000000..cd0c350cb --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_get_by_id.json @@ -0,0 +1,46 @@ +{ + "metricId": "(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names", + "displayName": "Response time", + "description": "", + "unit": "MicroSecond", + "dduBillable": false, + "created": 0, + "lastWritten": null, + "entityType": [ + "SERVICE" + ], + "aggregationTypes": [ + "auto", + "value" + ], + "transformations": [ + "fold", + "limit", + "timeshift", + "rate", + "sort", + "last", + "splitBy", + "default", + "delta", + "lastReal", + "smooth", + "rollup", + "partition", + "toUnit", + "setUnit" + ], + "defaultAggregation": { + "type": "value" + }, + "dimensionDefinitions": [], + "tags": [], + "metricValueType": { + "type": "unknown" + }, + "scalar": false, + "resolutionInfSupported": true, + "warnings": [ + "The field dimensionCardinalities is only supported for untransformed single metric keys and was ignored." + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_get_by_query1.json b/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_get_by_query1.json new file mode 100644 index 000000000..a441fe6b0 --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_get_by_query1.json @@ -0,0 +1,310 @@ +{ + "totalCount": 1, + "nextPageKey": null, + "resolution": "10m", + "result": [ + { + "metricId": "(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names", + "dataPointCountRatio": 0.0699408, + "dimensionCountRatio": 0.04857, + "data": [ + { + "dimensions": [], + "dimensionMap": {}, + "timestamps": [ + 1664323800000, + 1664324400000, + 1664325000000, + 1664325600000, + 1664326200000, + 1664326800000, + 1664327400000, + 1664328000000, + 1664328600000, + 1664329200000, + 1664329800000, + 1664330400000, + 1664331000000, + 1664331600000, + 1664332200000, + 1664332800000, + 1664333400000, + 1664334000000, + 1664334600000, + 1664335200000, + 1664335800000, + 1664336400000, + 1664337000000, + 1664337600000, + 1664338200000, + 1664338800000, + 1664339400000, + 1664340000000, + 1664340600000, + 1664341200000, + 1664341800000, + 1664342400000, + 1664343000000, + 1664343600000, + 1664344200000, + 1664344800000, + 1664345400000, + 1664346000000, + 1664346600000, + 1664347200000, + 1664347800000, + 1664348400000, + 1664349000000, + 1664349600000, + 1664350200000, + 1664350800000, + 1664351400000, + 1664352000000, + 1664352600000, + 1664353200000, + 1664353800000, + 1664354400000, + 1664355000000, + 1664355600000, + 1664356200000, + 1664356800000, + 1664357400000, + 1664358000000, + 1664358600000, + 1664359200000, + 1664359800000, + 1664360400000, + 1664361000000, + 1664361600000, + 1664362200000, + 1664362800000, + 1664363400000, + 1664364000000, + 1664364600000, + 1664365200000, + 1664365800000, + 1664366400000, + 1664367000000, + 1664367600000, + 1664368200000, + 1664368800000, + 1664369400000, + 1664370000000, + 1664370600000, + 1664371200000, + 1664371800000, + 1664372400000, + 1664373000000, + 1664373600000, + 1664374200000, + 1664374800000, + 1664375400000, + 1664376000000, + 1664376600000, + 1664377200000, + 1664377800000, + 1664378400000, + 1664379000000, + 1664379600000, + 1664380200000, + 1664380800000, + 1664381400000, + 1664382000000, + 1664382600000, + 1664383200000, + 1664383800000, + 1664384400000, + 1664385000000, + 1664385600000, + 1664386200000, + 1664386800000, + 1664387400000, + 1664388000000, + 1664388600000, + 1664389200000, + 1664389800000, + 1664390400000, + 1664391000000, + 1664391600000, + 1664392200000, + 1664392800000, + 1664393400000, + 1664394000000, + 1664394600000, + 1664395200000, + 1664395800000, + 1664396400000, + 1664397000000, + 1664397600000, + 1664398200000, + 1664398800000, + 1664399400000, + 1664400000000, + 1664400600000, + 1664401200000, + 1664401800000, + 1664402400000, + 1664403000000, + 1664403600000, + 1664404200000, + 1664404800000, + 1664405400000, + 1664406000000, + 1664406600000, + 1664407200000, + 1664407800000, + 1664408400000, + 1664409000000, + 1664409600000 + ], + "values": [ + 54820.03373164987, + 55218.99471220384, + 55071.49928878831, + 54949.78353754613, + 54792.09863487236, + 54758.23014110582, + 54839.84122407719, + 55080.79677446355, + 54553.826345367015, + 54700.14292835205, + 54737.2003722548, + 55002.27033954363, + 54777.74025995784, + 54920.93069442279, + 54564.00216313528, + 55127.85502136979, + 54649.187273431846, + 54903.09771335987, + 54811.92754307572, + 54954.78607277203, + 55320.74452442934, + 54811.32374927372, + 54789.12016856007, + 54832.67417526022, + 54882.66109509589, + 54839.12680134036, + 54963.65066827593, + 54833.89919248517, + 54726.41233535216, + 54711.6734476709, + 55050.82096058733, + 54804.508598504384, + 55043.67518606438, + 54909.71511835754, + 54832.34011665316, + 54965.59893223934, + 54768.34111642939, + 54769.87290857302, + 54838.65544370555, + 54639.0661843702, + 54871.37265354548, + 54877.451343791385, + 54884.69594899173, + 54830.335242511755, + 54909.95975180857, + 54729.30936347974, + 54857.07455554992, + 54773.180566804505, + 57040.51818064292, + 54815.08510582958, + 54638.81806222, + 54931.655559810424, + 54822.79794409753, + 54943.88083062903, + 59189.65722406022, + 54863.39737403222, + 54865.96204806309, + 54941.49568657858, + 55013.6698386867, + 54927.511166520126, + 55034.0434218988, + 54726.585503515416, + 55179.36971275004, + 55140.22470784515, + 54873.353515106246, + 55046.39285477691, + 55219.21159741783, + 53953.047819327396, + 54398.93323329027, + 54638.48781541484, + 55437.86906753935, + 54777.13448401533, + 54614.82884669915, + 54741.899303641876, + 54811.067369581295, + 54589.01308088208, + 55234.24117269804, + 54332.28349304332, + 54490.19518987563, + 54634.571387632306, + 54716.22231328921, + 56106.07520870239, + 54878.24152910459, + 55026.275312892016, + 54081.654492523165, + 55144.45957110088, + 56071.58167898117, + 54385.870900343165, + 54458.36845208812, + 54230.56669550621, + 54258.731741534466, + 55081.29456308478, + 55865.60168431374, + 55196.5338318562, + 53852.42383215121, + 55496.39855469356, + 55323.17160213487, + 54835.549739796435, + 55074.18764280705, + 54714.90320689123, + 55315.876354608066, + 54894.23055589334, + 54762.26818212387, + 54519.16418366507, + 55263.627200541814, + 54644.019993779904, + 54705.427177621474, + 55111.50943922037, + 54662.1410122232, + 54391.73053502808, + 53794.626921183415, + 54720.05095351137, + 54882.17257639208, + 54895.636923092046, + 54843.223935575785, + 55041.561428220484, + 54674.520597537055, + 54655.125680576966, + 54893.71445497061, + 54744.62396520675, + 54977.05859708377, + 55160.030160401315, + 54764.61432756854, + 54725.26721258407, + 55183.882339578675, + 55129.065580386196, + 54626.46311949491, + 54849.14344749185, + 54963.18046961137, + 54816.06156961105, + 54733.57320804991, + 55447.00928208658, + 55124.80110455526, + 55027.30869873211, + 54987.10183916898, + 55062.99573491732, + 53716.04621333344, + 55011.03937116556, + 54764.56758589279, + 54588.83623561774, + 54533.389405348695, + 54877.71688140235, + 54616.27774406533, + 55079.69618901161 + ] + } + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_get_by_query2.json b/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_get_by_query2.json new file mode 100644 index 000000000..ff5a9bdce --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_get_by_query2.json @@ -0,0 +1,24 @@ +{ + "totalCount": 1, + "nextPageKey": null, + "resolution": "Inf", + "result": [ + { + "metricId": "(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names", + "dataPointCountRatio": 2.4285E-4, + "dimensionCountRatio": 0.04857, + "data": [ + { + "dimensions": [], + "dimensionMap": {}, + "timestamps": [ + 1664409600000 + ], + "values": [ + 54896.48858596068 + ] + } + ] + } + ] +} diff --git a/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_units_convert1.json b/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_units_convert1.json new file mode 100644 index 000000000..eb19c9b79 --- /dev/null +++ b/internal/sli/testdata/dashboards/data_explorer/pick_correct_visual_config_rule/metrics_units_convert1.json @@ -0,0 +1,4 @@ +{ + "unitId": "MilliSecond", + "resultValue": 54.89648858596068 +}