Skip to content
This repository has been archived by the owner on Jan 12, 2024. It is now read-only.

Commit

Permalink
feat!: Change dashboard tile title parsing logic (#844)
Browse files Browse the repository at this point in the history
* Move SLO definition parsing code to `dashboard` package

Signed-off-by: Arthur Pitman <[email protected]>

* Use shared key value parsing

Signed-off-by: Arthur Pitman <[email protected]>

* Replace `$` and `.` with `_` in indicator names

Signed-off-by: Arthur Pitman <[email protected]>

* Allow display name to be set

Signed-off-by: Arthur Pitman <[email protected]>

* Move cleanIndicatorName function

Signed-off-by: Arthur Pitman <[email protected]>

* Add display name and indicator name logic

Signed-off-by: Arthur Pitman <[email protected]>

* Propagate display name in success case

Signed-off-by: Arthur Pitman <[email protected]>

* Minimal documentation update

Signed-off-by: Arthur Pitman <[email protected]>

* Add dashboard tile title examples to documentation

Signed-off-by: Arthur Pitman <[email protected]>

* Support `exclude` key in dashboard tile titles

Signed-off-by: Arthur Pitman <[email protected]>

* Improve tile examples in documentation

Signed-off-by: Arthur Pitman <[email protected]>

* Fix test

Signed-off-by: Arthur Pitman <[email protected]>

* Ensure SLI names are lower case

Signed-off-by: Arthur Pitman <[email protected]>
  • Loading branch information
arthurpitman authored Jul 14, 2022
1 parent 0577fa0 commit 48721ea
Show file tree
Hide file tree
Showing 42 changed files with 1,617 additions and 548 deletions.
Binary file added documentation/images/tile-example-customized.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added documentation/images/tile-example-exclude.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
102 changes: 94 additions & 8 deletions documentation/slis-via-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,107 @@ In response to a `sh.keptn.event.get-sli.triggered` event, the dynatrace-servic

## Defining SLIs and SLOs

The base name of the SLI as well as the properties of the SLO must be set by appending `;`-separated `<key>=<value>` pairs to the tile's title. The following keys are supported:
By default, the tile's title is taken as the display name of the SLO. A clean version of this name (lower case, with spaces, `/`, `%`, `$` and `.` replaced with `_`) is used as the base-name of the associated SLI. The properties of the SLO can be further customized by appending `;`-separated `<key>=<value>` pairs to the tile's title. The following keys are supported:

| Key | Description | Required | Example |
|---|---|---|---|
| `sli` | Use `<value>` as the base-name of the SLI | Yes | `sli=response_time` |
| `pass` | Add `<value>` as a pass criterion to the SLO | No | `pass=<200` |
| `warning` | Add `<value>` as a warning criterion to the SLO | No | `warning=<300` |
| `key` | Mark SLI as a key SLI | No | `key=true` |
| `weight` | Set the weight of the SLO to `<value>` | No | `weight=2` |
| Key | Description | Example |
|---|---|---|
| `sli` | Use `<value>` as the base-name of the SLI | `sli=response_time` |
| `pass` | Add `<value>` as a pass criterion to the SLO | `pass=<200` |
| `warning` | Add `<value>` as a warning criterion to the SLO | `warning=<300` |
| `key` | Mark SLI as a key SLI | `key=true` |
| `weight` | Set the weight of the SLO to `<value>` | `weight=2` |
| `exclude` | Set to `true` to exclude this tile | `exclude=true` |

Consult [the Keptn documentation](https://keptn.sh/docs/0.11.x/quality_gates/slo/#objectives) for more details on configuring objectives.

**Note:**
In case dynatrace-service could not parse the tile title correctly it will stop the processing of this tile and return an error for the concerned SLI.

### Examples
* **Informational SLO**

To retrieve the value of an SLI purely for informational purposes, giving the tile a title is sufficient. As it does not include a pass criterion it will not be included in an evaluation. For example, the title `Av Response Time (Info)`

![Example of an informational SLO](images/tile-example-infomational.png "Example of an informational SLO")

results in an SLO objective:

```{yaml}
- sli: av_response_time_(info)
displayName: Av Response Time (Info)
pass: []
warning: []
weight: 1
key_sli: false
```

* **Display name and pass criterion**

To include an SLO as part of an evaluation, simply append a pass criterion. For example, the title `Av Response Time; pass=<=75000`

![Example of an SLO with display name and pass criterion](images/tile-example-display-name-pass.png "Example of an SLO with display name and pass criterion")

results in an SLO objective:

```{yaml}
- sli: av_response_time
displayName: Av Response Time
pass:
- criteria:
- <=75000
warning: []
weight: 1
key_sli: false
```

* **SLI name and pass criterion**

Alternatively, create an SLO by providing an SLI name and a pass criterion. For example, the title `sli=service_rt_av; pass=<=75000`

![Example of an SLO with SLI name and pass criterion](images/tile-example-sli-name-pass.png "Example of an SLO with SLI name and pass criterion")

results in an SLO objective:

```{yaml}
- sli: service_rt_av
displayName: service_rt_av
pass:
- criteria:
- <=75000
warning: []
weight: 1
key_sli: false
```

* **Custom SLI name with pass and warning criteria**

The SLI name as well as the properties of the SLO may be customized. For example, the title `Av Svc Resp. Time; sli=srt_av; pass=<=70000; warning=<=80000; key=true; weight=2`

![Example of a customized SLO](images/tile-example-customized.png "Example of a customized SLO")

results in an SLO objective:

```{yaml}
- sli: srt_av
displayName: Av Svc Resp. Time
pass:
- criteria:
- <=70000
warning:
- criteria:
- <=80000
weight: 2
key_sli: true
```

* **Excluded tile**

To exclude a tile, simply add the key-value pair `exclude=true`. For example, the title `Av Response Time; exclude=true`

![Example of an excluded tile](images/tile-example-exclude.png "Example of an excluded tile")

will ensure that the tile is not processed.

### Logical AND/OR operators for pass and warning criteria

The Keptn SLO objective definition for `pass` and `warning` criteria allows logical **AND** as well as **OR** operators.
Expand Down
164 changes: 0 additions & 164 deletions internal/common/common.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package common

import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"time"

keptnv2 "github.com/keptn/go-utils/pkg/lib/v0_2_0"

"github.com/keptn-contrib/dynatrace-service/internal/adapter"

keptncommon "github.com/keptn/go-utils/pkg/lib"
)

const ProblemURLLabel = "Problem URL"
Expand Down Expand Up @@ -75,163 +71,3 @@ func ReplaceKeptnPlaceholders(input string, keptnEvent adapter.EventContentAdapt
func TimestampToUnixMillisecondsString(time time.Time) string {
return strconv.FormatInt(time.Unix()*1000, 10)
}

// SLODefinitionError represents an error that occurred while parsing an SLO definition
type SLODefinitionError struct {
tileTitle string
sliName string
errors []error
}

func (err *SLODefinitionError) Error() string {
var errStrings = make([]string, len(err.errors))
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
}

type duplicateKeyError struct {
key string
}

func (err *duplicateKeyError) Error() string {
return fmt.Sprintf("duplicate key '%s' in SLO definition", err.key)
}

const (
sloDefSli = "sli"
sloDefPass = "pass"
sloDefWarning = "warning"
sloDefKey = "key"
sloDefWeight = "weight"
)

// ParseSLOFromString 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
// can also take a value like
// "KQG;project=myproject;pass=90%;warning=75%;"
// This will return a SLO object or an error if parsing was not possible
func ParseSLOFromString(customName string) (*keptncommon.SLO, error) {
result := &keptncommon.SLO{
Weight: 1,
KeySLI: false,
}
var errs []error

nameValueSplits := strings.Split(customName, ";")

// let's iterate through all name-value pairs which are separated through ";" to extract keys such as warning, pass, weight, key, sli
keyFound := make(map[string]bool)
for i := 0; i < len(nameValueSplits); i++ {

nameValueDividerIndex := strings.Index(nameValueSplits[i], "=")
if nameValueDividerIndex < 0 {
continue
}

// for each name=value pair we get the name as first part of the string until the first =
// the value is the after that =
nameString := strings.ToLower(nameValueSplits[i][:nameValueDividerIndex])
valueString := strings.TrimSpace(nameValueSplits[i][nameValueDividerIndex+1:])
var err error
switch nameString {
case sloDefSli:
if keyFound[sloDefSli] {
errs = append(errs, &duplicateKeyError{key: sloDefSli})
break
}
result.SLI = valueString
if valueString == "" {
errs = append(errs, fmt.Errorf("sli name is empty"))
}
keyFound[sloDefSli] = true
case sloDefPass:
passCriteria, err := parseSLOCriteriaString(valueString)
if err != nil {
errs = append(errs, fmt.Errorf("invalid definition for '%s': %w", sloDefPass, err))
break
}
result.Pass = append(result.Pass, passCriteria)
case sloDefWarning:
warningCriteria, err := parseSLOCriteriaString(valueString)
if err != nil {
errs = append(errs, fmt.Errorf("invalid definition for '%s': %w", sloDefWarning, err))
break
}
result.Warning = append(result.Warning, warningCriteria)
case sloDefKey:
if keyFound[sloDefKey] {
errs = append(errs, &duplicateKeyError{key: sloDefKey})
break
}
result.KeySLI, err = strconv.ParseBool(valueString)
if err != nil {
errs = append(errs, fmt.Errorf("invalid definition for '%s': not a boolean value: %v", sloDefKey, valueString))
}
keyFound[sloDefKey] = true
case sloDefWeight:
if keyFound[sloDefWeight] {
errs = append(errs, &duplicateKeyError{key: sloDefWeight})
break
}
result.Weight, err = strconv.Atoi(valueString)
if err != nil {
errs = append(errs, fmt.Errorf("invalid definition for '%s': not an integer value: %v", sloDefWeight, valueString))
}
keyFound[sloDefWeight] = true
}
}

if len(errs) > 0 {
return nil, &SLODefinitionError{
sliName: result.SLI,
tileTitle: customName,
errors: errs,
}
}

return result, nil
}

func parseSLOCriteriaString(criteria string) (*keptncommon.SLOCriteria, error) {
criteriaChunks := strings.Split(criteria, ",")
var invalidCriteria []string
for _, criterion := range criteriaChunks {
if criterionIsNotValid(criterion) {
invalidCriteria = append(invalidCriteria, criterion)
}
}

if len(invalidCriteria) > 0 {
return nil, fmt.Errorf("invalid criteria value(s): %s", strings.Join(invalidCriteria, ","))
}

return &keptncommon.SLOCriteria{Criteria: criteriaChunks}, nil
}

func criterionIsNotValid(criterion string) bool {
pattern := regexp.MustCompile("^(<|<=|=|>|>=)([+-]?\\d+|[+-]?\\d+\\.\\d+)([%]?)$")

return !pattern.MatchString(criterion)
}

// CleanIndicatorName makes sure we have a valid indicator name by getting rid of special characters
func CleanIndicatorName(indicatorName string) string {
indicatorName = strings.ReplaceAll(indicatorName, " ", "_")
indicatorName = strings.ReplaceAll(indicatorName, "/", "_")
indicatorName = strings.ReplaceAll(indicatorName, "%", "_")

return indicatorName
}
Loading

0 comments on commit 48721ea

Please sign in to comment.