diff --git a/internal/dynatrace/dashboard.go b/internal/dynatrace/dashboard.go index be4de6c66..469077682 100644 --- a/internal/dynatrace/dashboard.go +++ b/internal/dynatrace/dashboard.go @@ -74,47 +74,48 @@ 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"` + Matcher string `json:"matcher,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"` } @@ -133,26 +134,8 @@ 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"` + Enabled bool `json:"enabled"` } type FilterConfig struct { @@ -210,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/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 +} 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 +} diff --git a/internal/sli/dashboard/custom_charting_tile_processing.go b/internal/sli/dashboard/custom_charting_tile_processing.go index 131773993..dcb0c0794 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,24 +54,30 @@ 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) - 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_thresholds.go b/internal/sli/dashboard/data_explorer_thresholds.go index 720ce3d3e..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.Threshold) 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 } @@ -191,7 +204,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 { @@ -212,7 +225,7 @@ func convertThresholdRulesToThresholdConfiguration(rules []dynatrace.ThresholdRu } 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 4a14ededd..59eb368d8 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, validatedTile.targetUnitID) + } + + return NewMetricsQueryProcessing(p.client, validatedTile.targetUnitID) +} + +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,110 @@ 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) - - query, err := p.createMetricsQueryForMetricExpressions(tile.MetricExpressions, managementZoneFilter) + targetUnitID, err := getUnitTransform(v.tile.VisualConfig, queryID) 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())} + errs = append(errs, err) } - return p.createMetricsQueryProcessingForTile(tile).Process(ctx, sloDefinition, *query, p.timeframe) + if len(errs) > 0 { + return nil, &dataExplorerTileValidationError{ + sloDefinition: sloDefinition, + errors: errs, + } + } + + 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.VisualConfigRule) 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, error) { + if visualConfig == nil { + 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 nil + + return matchingRule.UnitTransform, nil } -func (p *DataExplorerTileProcessing) createMetricsQueryProcessingForTile(tile *dynatrace.Tile) *MetricsQueryProcessing { - if tile.VisualConfig == nil { - return NewMetricsQueryProcessing(p.client) +func tryGetMatchingVisualizationRule(rules []dynatrace.VisualizationRule, queryID string) (*dynatrace.VisualizationRule, error) { + queryMatcher := createQueryMatcher(queryID) + var matchingRules []dynatrace.VisualizationRule + for _, r := range rules { + if r.Matcher == queryMatcher { + matchingRules = append(matchingRules, r) + } } - if tile.VisualConfig.Type == dynatrace.SingleValueVisualConfigType { - return NewMetricsQueryProcessingThatAllowsOnlyOneResult(p.client) + if len(matchingRules) == 0 { + return nil, nil } - return NewMetricsQueryProcessing(p.client) + 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 { + return queryID + ":" } -func (p *DataExplorerTileProcessing) createMetricsQueryForMetricExpressions(metricExpressions []string, managementZoneFilter *ManagementZoneFilter) (*metrics.Query, error) { +func isSingleValueVisualizationType(visualConfig *dynatrace.VisualizationConfiguration) bool { + if visualConfig == nil { + return false + } + + return visualConfig.Type == dynatrace.SingleValueVisualizationConfigurationType +} + +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 +230,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 +265,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/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/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_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 063ee4342..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 @@ -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,49 +434,104 @@ 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, } } -// TestRetrieveMetricsFromDashboardDataExplorerTile_UnitTransformIsNotAuto tests that unit transforms other than auto are not allowed. +// 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_UnitTransformIsNotAuto(t *testing.T) { - handler := createHandlerForEarlyFailureDataExplorerTest(t, "./testdata/dashboards/data_explorer/unit_transform_is_not_auto/") - runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventFailureAssertionsFunc, createFailedSLIResultAssertionsFunc("srt", "must be set to 'Auto'")) +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")) +} + +// 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")) +} + +// 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 { 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/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/unit_transform_is_not_auto/dashboard.json b/internal/sli/testdata/dashboards/data_explorer/multiple_tile_configuration_problems/dashboard.json similarity index 74% rename from internal/sli/testdata/dashboards/data_explorer/unit_transform_is_not_auto/dashboard.json rename to internal/sli/testdata/dashboards/data_explorer/multiple_tile_configuration_problems/dashboard.json index d7e092279..77401b472 100644 --- a/internal/sli/testdata/dashboards/data_explorer/unit_transform_is_not_auto/dashboard.json +++ b/internal/sli/testdata/dashboards/data_explorer/multiple_tile_configuration_problems/dashboard.json @@ -14,7 +14,7 @@ }, "tiles": [ { - "name": "Service Response Time; sli=srt", + "name": "Service response time; sli=srt; pass=<30; weight=4.2; key=true; key=false", "tileType": "DATA_EXPLORER", "configured": true, "bounds": { @@ -37,6 +37,18 @@ "criteria": [] }, "enabled": true + }, + { + "id": "B", + "metric": "builtin:service.response.time", + "spaceAggregation": "AVG", + "timeAggregation": "DEFAULT", + "splitBy": [], + "filterBy": { + "nestedFilters": [], + "criteria": [] + }, + "enabled": true } ], "visualConfig": { @@ -47,14 +59,11 @@ "rules": [ { "matcher": "A:", - "unitTransform": "MilliSecond", - "valueFormat": "auto", "properties": { - "color": "DEFAULT", - "seriesType": "LINE" + "color": "DEFAULT" }, "seriesOverrides": [] - } + } ], "axes": { "xAxis": { @@ -82,22 +91,22 @@ { "axisTarget": "LEFT", "rules": [ - { - "value": 0, - "color": "#7dc540" - }, - { - "value": 68000, - "color": "#f5d30f" - }, - { - "value": 69000, - "color": "#dc172a" - } + { + "value": 0, + "color": "#14a8f5" + }, + { + "value": 4100000, + "color": "#ffe11c" + }, + { + "value": 5000000, + "color": "#048855" + } ], "queryId": "", "visible": true - } + } ], "tableSettings": { "isThresholdBackgroundAppliedToCell": false @@ -114,8 +123,7 @@ "queriesSettings": { "resolution": "" }, - "metricExpressions": [ - "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" + "metricExpressions": [ ] } ] 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 +} 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_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 {