Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions internal/app/generate/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func TestIntegrationAppServiceGenerate(t *testing.T) {
Rules: []rulefmt.Rule{
{
Record: "slo:sli_error:ratio_rate5m",
Expr: "(rate(my_metric{error=\"true\"}[5m]))\n/\n(rate(my_metric[5m]))\n",
Expr: "(rate(my_metric{error=\"true\"}[5m]))\n/\n((rate(my_metric[5m])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand All @@ -193,7 +193,7 @@ func TestIntegrationAppServiceGenerate(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate30m",
Expr: "(rate(my_metric{error=\"true\"}[30m]))\n/\n(rate(my_metric[30m]))\n",
Expr: "(rate(my_metric{error=\"true\"}[30m]))\n/\n((rate(my_metric[30m])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand All @@ -206,7 +206,7 @@ func TestIntegrationAppServiceGenerate(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate1h",
Expr: "(rate(my_metric{error=\"true\"}[1h]))\n/\n(rate(my_metric[1h]))\n",
Expr: "(rate(my_metric{error=\"true\"}[1h]))\n/\n((rate(my_metric[1h])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand All @@ -219,7 +219,7 @@ func TestIntegrationAppServiceGenerate(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate2h",
Expr: "(rate(my_metric{error=\"true\"}[2h]))\n/\n(rate(my_metric[2h]))\n",
Expr: "(rate(my_metric{error=\"true\"}[2h]))\n/\n((rate(my_metric[2h])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand All @@ -232,7 +232,7 @@ func TestIntegrationAppServiceGenerate(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate6h",
Expr: "(rate(my_metric{error=\"true\"}[6h]))\n/\n(rate(my_metric[6h]))\n",
Expr: "(rate(my_metric{error=\"true\"}[6h]))\n/\n((rate(my_metric[6h])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand All @@ -245,7 +245,7 @@ func TestIntegrationAppServiceGenerate(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate1d",
Expr: "(rate(my_metric{error=\"true\"}[1d]))\n/\n(rate(my_metric[1d]))\n",
Expr: "(rate(my_metric{error=\"true\"}[1d]))\n/\n((rate(my_metric[1d])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand All @@ -258,7 +258,7 @@ func TestIntegrationAppServiceGenerate(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate3d",
Expr: "(rate(my_metric{error=\"true\"}[3d]))\n/\n(rate(my_metric[3d]))\n",
Expr: "(rate(my_metric{error=\"true\"}[3d]))\n/\n((rate(my_metric[3d])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand Down Expand Up @@ -593,7 +593,7 @@ or
Rules: []rulefmt.Rule{
{
Record: "slo:sli_error:ratio_rate5m",
Expr: "(rate(my_metric{error=\"true\"}[5m]))\n/\n(rate(my_metric[5m]))\n",
Expr: "(rate(my_metric{error=\"true\"}[5m]))\n/\n((rate(my_metric[5m])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand All @@ -606,7 +606,7 @@ or
},
{
Record: "slo:sli_error:ratio_rate30m",
Expr: "(rate(my_metric{error=\"true\"}[30m]))\n/\n(rate(my_metric[30m]))\n",
Expr: "(rate(my_metric{error=\"true\"}[30m]))\n/\n((rate(my_metric[30m])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand All @@ -619,7 +619,7 @@ or
},
{
Record: "slo:sli_error:ratio_rate1h",
Expr: "(rate(my_metric{error=\"true\"}[1h]))\n/\n(rate(my_metric[1h]))\n",
Expr: "(rate(my_metric{error=\"true\"}[1h]))\n/\n((rate(my_metric[1h])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand All @@ -632,7 +632,7 @@ or
},
{
Record: "slo:sli_error:ratio_rate2h",
Expr: "(rate(my_metric{error=\"true\"}[2h]))\n/\n(rate(my_metric[2h]))\n",
Expr: "(rate(my_metric{error=\"true\"}[2h]))\n/\n((rate(my_metric[2h])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand All @@ -645,7 +645,7 @@ or
},
{
Record: "slo:sli_error:ratio_rate6h",
Expr: "(rate(my_metric{error=\"true\"}[6h]))\n/\n(rate(my_metric[6h]))\n",
Expr: "(rate(my_metric{error=\"true\"}[6h]))\n/\n((rate(my_metric[6h])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand All @@ -658,7 +658,7 @@ or
},
{
Record: "slo:sli_error:ratio_rate1d",
Expr: "(rate(my_metric{error=\"true\"}[1d]))\n/\n(rate(my_metric[1d]))\n",
Expr: "(rate(my_metric{error=\"true\"}[1d]))\n/\n((rate(my_metric[1d])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand All @@ -671,7 +671,7 @@ or
},
{
Record: "slo:sli_error:ratio_rate3d",
Expr: "(rate(my_metric{error=\"true\"}[3d]))\n/\n(rate(my_metric[3d]))\n",
Expr: "(rate(my_metric{error=\"true\"}[3d]))\n/\n((rate(my_metric[3d])) > 0)\n",
Labels: map[string]string{
"test_label": "label_1",
"extra_k1": "extra_v1",
Expand Down
4 changes: 3 additions & 1 deletion internal/plugin/slo/core/sli_rules_v1/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,11 @@ func rawSLIRecordGenerator(slo model.PromSLO, window time.Duration, alerts model
}

func eventsSLIRecordGenerator(slo model.PromSLO, window time.Duration, alerts model.MWMBAlertGroup) (*rulefmt.Rule, error) {
// Wrap the denominator with ((...) > 0) so a zero total produces an absent result
// rather than +Inf, which would otherwise poison sum_over_time for up to 30 days.
const sliExprTplFmt = `(%s)
/
(%s)
((%s) > 0)
`
// Generate our first level of template by assembling the error and total expressions.
sliExprTpl := fmt.Sprintf(sliExprTplFmt, slo.SLI.Events.ErrorQuery, slo.SLI.Events.TotalQuery)
Expand Down
36 changes: 18 additions & 18 deletions internal/plugin/slo/core/sli_rules_v1/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
expRules: []rulefmt.Rule{
{
Record: "slo:sli_error:ratio_rate5m",
Expr: "(rate(my_metric[5m]{error=\"true\"}))\n/\n(rate(my_metric[5m]))\n",
Expr: "(rate(my_metric[5m]{error=\"true\"}))\n/\n((rate(my_metric[5m])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -122,7 +122,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate30m",
Expr: "(rate(my_metric[30m]{error=\"true\"}))\n/\n(rate(my_metric[30m]))\n",
Expr: "(rate(my_metric[30m]{error=\"true\"}))\n/\n((rate(my_metric[30m])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -133,7 +133,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate1h",
Expr: "(rate(my_metric[1h]{error=\"true\"}))\n/\n(rate(my_metric[1h]))\n",
Expr: "(rate(my_metric[1h]{error=\"true\"}))\n/\n((rate(my_metric[1h])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -144,7 +144,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate2h",
Expr: "(rate(my_metric[2h]{error=\"true\"}))\n/\n(rate(my_metric[2h]))\n",
Expr: "(rate(my_metric[2h]{error=\"true\"}))\n/\n((rate(my_metric[2h])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -155,7 +155,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate6h",
Expr: "(rate(my_metric[6h]{error=\"true\"}))\n/\n(rate(my_metric[6h]))\n",
Expr: "(rate(my_metric[6h]{error=\"true\"}))\n/\n((rate(my_metric[6h])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -166,7 +166,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate1d",
Expr: "(rate(my_metric[1d]{error=\"true\"}))\n/\n(rate(my_metric[1d]))\n",
Expr: "(rate(my_metric[1d]{error=\"true\"}))\n/\n((rate(my_metric[1d])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -177,7 +177,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate3d",
Expr: "(rate(my_metric[3d]{error=\"true\"}))\n/\n(rate(my_metric[3d]))\n",
Expr: "(rate(my_metric[3d]{error=\"true\"}))\n/\n((rate(my_metric[3d])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand Down Expand Up @@ -221,7 +221,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
expRules: []rulefmt.Rule{
{
Record: "slo:sli_error:ratio_rate5m",
Expr: "(rate(my_metric[5m]{error=\"true\"}))\n/\n(rate(my_metric[5m]))\n",
Expr: "(rate(my_metric[5m]{error=\"true\"}))\n/\n((rate(my_metric[5m])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -232,7 +232,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate30m",
Expr: "(rate(my_metric[30m]{error=\"true\"}))\n/\n(rate(my_metric[30m]))\n",
Expr: "(rate(my_metric[30m]{error=\"true\"}))\n/\n((rate(my_metric[30m])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -243,7 +243,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate1h",
Expr: "(rate(my_metric[1h]{error=\"true\"}))\n/\n(rate(my_metric[1h]))\n",
Expr: "(rate(my_metric[1h]{error=\"true\"}))\n/\n((rate(my_metric[1h])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -254,7 +254,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate2h",
Expr: "(rate(my_metric[2h]{error=\"true\"}))\n/\n(rate(my_metric[2h]))\n",
Expr: "(rate(my_metric[2h]{error=\"true\"}))\n/\n((rate(my_metric[2h])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -265,7 +265,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate6h",
Expr: "(rate(my_metric[6h]{error=\"true\"}))\n/\n(rate(my_metric[6h]))\n",
Expr: "(rate(my_metric[6h]{error=\"true\"}))\n/\n((rate(my_metric[6h])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -276,7 +276,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate1d",
Expr: "(rate(my_metric[1d]{error=\"true\"}))\n/\n(rate(my_metric[1d]))\n",
Expr: "(rate(my_metric[1d]{error=\"true\"}))\n/\n((rate(my_metric[1d])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -287,7 +287,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate3d",
Expr: "(rate(my_metric[3d]{error=\"true\"}))\n/\n(rate(my_metric[3d]))\n",
Expr: "(rate(my_metric[3d]{error=\"true\"}))\n/\n((rate(my_metric[3d])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -298,7 +298,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate30d",
Expr: "(rate(my_metric[30d]{error=\"true\"}))\n/\n(rate(my_metric[30d]))\n",
Expr: "(rate(my_metric[30d]{error=\"true\"}))\n/\n((rate(my_metric[30d])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand Down Expand Up @@ -445,7 +445,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
expRules: []rulefmt.Rule{
{
Record: "slo:sli_error:ratio_rate1h",
Expr: "(rate(my_metric[1h]{error=\"true\"}))\n/\n(rate(my_metric[1h]))\n",
Expr: "(rate(my_metric[1h]{error=\"true\"}))\n/\n((rate(my_metric[1h])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -456,7 +456,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate2h",
Expr: "(rate(my_metric[2h]{error=\"true\"}))\n/\n(rate(my_metric[2h]))\n",
Expr: "(rate(my_metric[2h]{error=\"true\"}))\n/\n((rate(my_metric[2h])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand All @@ -467,7 +467,7 @@ func TestGenerateSLIRecordingRules(t *testing.T) {
},
{
Record: "slo:sli_error:ratio_rate3h",
Expr: "(rate(my_metric[3h]{error=\"true\"}))\n/\n(rate(my_metric[3h]))\n",
Expr: "(rate(my_metric[3h]{error=\"true\"}))\n/\n((rate(my_metric[3h])) > 0)\n",
Labels: map[string]string{
"kind": "test",
"sloth_service": "test-svc",
Expand Down
5 changes: 3 additions & 2 deletions pkg/common/validation/slo.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,9 @@ func ValidateSLO(slo model.PromSLO, dialect SLODialectValidator) error {
return fmt.Errorf("time window is required")
}

if slo.Objective <= 0 || slo.Objective > 100 {
return fmt.Errorf("objective must >0 and <=100")
// Objective=100 yields a zero error budget, causing division by zero in burn rate calculations.
if slo.Objective <= 0 || slo.Objective >= 100 {
return fmt.Errorf("objective must >0 and <100")
}

for k, v := range slo.Labels {
Expand Down
15 changes: 12 additions & 3 deletions pkg/common/validation/slo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func TestModelValidationSpecForPrometheusBackend(t *testing.T) {
s.Objective = -1
return s
},
expErrMessage: `objective must >0 and <=100`,
expErrMessage: `objective must >0 and <100`,
},

"SLO Objective shouldn't be 0.": {
Expand All @@ -317,7 +317,16 @@ func TestModelValidationSpecForPrometheusBackend(t *testing.T) {
s.Objective = 0
return s
},
expErrMessage: `objective must >0 and <=100`,
expErrMessage: `objective must >0 and <100`,
},

"SLO Objective shouldn't be 100 because error budget would be zero causing infinite burn rates.": {
slo: func() model.PromSLO {
s := getGoodSLO()
s.Objective = 100
return s
},
expErrMessage: `objective must >0 and <100`,
},

"SLO Objective shouldn't be greater than 100.": {
Expand All @@ -326,7 +335,7 @@ func TestModelValidationSpecForPrometheusBackend(t *testing.T) {
s.Objective = 100.0001
return s
},
expErrMessage: `objective must >0 and <=100`,
expErrMessage: `objective must >0 and <100`,
},

"SLO Labels should be valid prometheus keys.": {
Expand Down
Loading