From 8538a87ce04c01a53bed4eca82863aaf60a73893 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Wed, 13 Nov 2024 13:39:45 -0500 Subject: [PATCH] fix(slo): Use correct calendar period (#199873) (cherry picked from commit 3cbdcc3609269dcf109fe787359b2504203da229) --- .../__snapshots__/occurrences.test.ts.snap | 745 ++++++++++++++++++ .../timeslices_calendar_aligned.test.ts.snap | 273 +++++++ .../timeslices_rolling.test.ts.snap | 249 ++++++ .../generators/occurrences.test.ts | 52 ++ .../generators/occurrences.ts | 15 +- .../timeslices_calendar_aligned.test.ts | 23 + .../generators/timeslices_rolling.test.ts | 23 + 7 files changed, 1379 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/__snapshots__/occurrences.test.ts.snap create mode 100644 x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/__snapshots__/timeslices_calendar_aligned.test.ts.snap create mode 100644 x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/__snapshots__/timeslices_rolling.test.ts.snap create mode 100644 x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/occurrences.test.ts create mode 100644 x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_calendar_aligned.test.ts create mode 100644 x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_rolling.test.ts diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/__snapshots__/occurrences.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/__snapshots__/occurrences.test.ts.snap new file mode 100644 index 000000000000..114ae4de393f --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/__snapshots__/occurrences.test.ts.snap @@ -0,0 +1,745 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Summary Transform Generator for 'Occurrences' SLO generates the correct transform for a 30days rolling SLO 1`] = ` +Object { + "_meta": Object { + "managed": true, + "managed_by": "observability", + "version": 3.3, + }, + "defer_validation": true, + "description": "Summarise the rollup data of SLO: irrelevant [id: irrelevant, revision: 1].", + "dest": Object { + "index": ".slo-observability.summary-v3.3", + "pipeline": ".slo-observability.summary.pipeline-irrelevant-1", + }, + "frequency": "1m", + "pivot": Object { + "aggregations": Object { + "errorBudgetConsumed": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetInitial": "errorBudgetInitial", + "sliValue": "sliValue", + }, + "script": "if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }", + }, + }, + "errorBudgetInitial": Object { + "bucket_script": Object { + "buckets_path": Object {}, + "script": "1 - 0.999", + }, + }, + "errorBudgetRemaining": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetConsumed": "errorBudgetConsumed", + }, + "script": "1 - params.errorBudgetConsumed", + }, + }, + "fiveMinuteBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.numerator", + }, + }, + "totalEvents": Object { + "sum": Object { + "field": "slo.denominator", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-480s/m", + "lte": "now-180s/m", + }, + }, + }, + }, + "goodEvents": Object { + "sum": Object { + "field": "slo.numerator", + }, + }, + "latestSliTimestamp": Object { + "max": Object { + "field": "@timestamp", + }, + }, + "oneDayBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.numerator", + }, + }, + "totalEvents": Object { + "sum": Object { + "field": "slo.denominator", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-86580s/m", + "lte": "now-180s/m", + }, + }, + }, + }, + "oneHourBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.numerator", + }, + }, + "totalEvents": Object { + "sum": Object { + "field": "slo.denominator", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-3780s/m", + "lte": "now-180s/m", + }, + }, + }, + }, + "sliValue": Object { + "bucket_script": Object { + "buckets_path": Object { + "goodEvents": "goodEvents", + "totalEvents": "totalEvents", + }, + "script": "if (params.totalEvents == 0) { return -1 } else if (params.goodEvents >= params.totalEvents) { return 1 } else { return params.goodEvents / params.totalEvents }", + }, + }, + "statusCode": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetRemaining": "errorBudgetRemaining", + "sliValue": "sliValue", + }, + "script": Object { + "source": "if (params.sliValue == -1) { return 0 } else if (params.sliValue >= 0.999) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }", + }, + }, + }, + "totalEvents": Object { + "sum": Object { + "field": "slo.denominator", + }, + }, + }, + "group_by": Object { + "monitor.config_id": Object { + "terms": Object { + "field": "monitor.config_id", + "missing_bucket": true, + }, + }, + "monitor.name": Object { + "terms": Object { + "field": "monitor.name", + "missing_bucket": true, + }, + }, + "observer.geo.name": Object { + "terms": Object { + "field": "observer.geo.name", + "missing_bucket": true, + }, + }, + "observer.name": Object { + "terms": Object { + "field": "observer.name", + "missing_bucket": true, + }, + }, + "service.environment": Object { + "terms": Object { + "field": "service.environment", + "missing_bucket": true, + }, + }, + "service.name": Object { + "terms": Object { + "field": "service.name", + "missing_bucket": true, + }, + }, + "slo.id": Object { + "terms": Object { + "field": "slo.id", + }, + }, + "slo.instanceId": Object { + "terms": Object { + "field": "slo.instanceId", + }, + }, + "slo.revision": Object { + "terms": Object { + "field": "slo.revision", + }, + }, + "transaction.name": Object { + "terms": Object { + "field": "transaction.name", + "missing_bucket": true, + }, + }, + "transaction.type": Object { + "terms": Object { + "field": "transaction.type", + "missing_bucket": true, + }, + }, + }, + }, + "settings": Object { + "deduce_mappings": false, + "unattended": true, + }, + "source": Object { + "index": ".slo-observability.sli-v3.3*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-30d/m", + "lte": "now/m", + }, + }, + }, + Object { + "term": Object { + "slo.id": "irrelevant", + }, + }, + Object { + "term": Object { + "slo.revision": 1, + }, + }, + ], + }, + }, + }, + "sync": Object { + "time": Object { + "delay": "65s", + "field": "event.ingested", + }, + }, + "transform_id": "slo-summary-irrelevant-1", +} +`; + +exports[`Summary Transform Generator for 'Occurrences' SLO generates the correct transform for a monthly calendar aligned SLO 1`] = ` +Object { + "_meta": Object { + "managed": true, + "managed_by": "observability", + "version": 3.3, + }, + "defer_validation": true, + "description": "Summarise the rollup data of SLO: irrelevant [id: irrelevant, revision: 1].", + "dest": Object { + "index": ".slo-observability.summary-v3.3", + "pipeline": ".slo-observability.summary.pipeline-irrelevant-1", + }, + "frequency": "1m", + "pivot": Object { + "aggregations": Object { + "errorBudgetConsumed": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetInitial": "errorBudgetInitial", + "sliValue": "sliValue", + }, + "script": "if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }", + }, + }, + "errorBudgetInitial": Object { + "bucket_script": Object { + "buckets_path": Object {}, + "script": "1 - 0.999", + }, + }, + "errorBudgetRemaining": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetConsumed": "errorBudgetConsumed", + }, + "script": "1 - params.errorBudgetConsumed", + }, + }, + "fiveMinuteBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.numerator", + }, + }, + "totalEvents": Object { + "sum": Object { + "field": "slo.denominator", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-480s/m", + "lte": "now-180s/m", + }, + }, + }, + }, + "goodEvents": Object { + "sum": Object { + "field": "slo.numerator", + }, + }, + "latestSliTimestamp": Object { + "max": Object { + "field": "@timestamp", + }, + }, + "oneDayBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.numerator", + }, + }, + "totalEvents": Object { + "sum": Object { + "field": "slo.denominator", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-86580s/m", + "lte": "now-180s/m", + }, + }, + }, + }, + "oneHourBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.numerator", + }, + }, + "totalEvents": Object { + "sum": Object { + "field": "slo.denominator", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-3780s/m", + "lte": "now-180s/m", + }, + }, + }, + }, + "sliValue": Object { + "bucket_script": Object { + "buckets_path": Object { + "goodEvents": "goodEvents", + "totalEvents": "totalEvents", + }, + "script": "if (params.totalEvents == 0) { return -1 } else if (params.goodEvents >= params.totalEvents) { return 1 } else { return params.goodEvents / params.totalEvents }", + }, + }, + "statusCode": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetRemaining": "errorBudgetRemaining", + "sliValue": "sliValue", + }, + "script": Object { + "source": "if (params.sliValue == -1) { return 0 } else if (params.sliValue >= 0.999) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }", + }, + }, + }, + "totalEvents": Object { + "sum": Object { + "field": "slo.denominator", + }, + }, + }, + "group_by": Object { + "monitor.config_id": Object { + "terms": Object { + "field": "monitor.config_id", + "missing_bucket": true, + }, + }, + "monitor.name": Object { + "terms": Object { + "field": "monitor.name", + "missing_bucket": true, + }, + }, + "observer.geo.name": Object { + "terms": Object { + "field": "observer.geo.name", + "missing_bucket": true, + }, + }, + "observer.name": Object { + "terms": Object { + "field": "observer.name", + "missing_bucket": true, + }, + }, + "service.environment": Object { + "terms": Object { + "field": "service.environment", + "missing_bucket": true, + }, + }, + "service.name": Object { + "terms": Object { + "field": "service.name", + "missing_bucket": true, + }, + }, + "slo.id": Object { + "terms": Object { + "field": "slo.id", + }, + }, + "slo.instanceId": Object { + "terms": Object { + "field": "slo.instanceId", + }, + }, + "slo.revision": Object { + "terms": Object { + "field": "slo.revision", + }, + }, + "transaction.name": Object { + "terms": Object { + "field": "transaction.name", + "missing_bucket": true, + }, + }, + "transaction.type": Object { + "terms": Object { + "field": "transaction.type", + "missing_bucket": true, + }, + }, + }, + }, + "settings": Object { + "deduce_mappings": false, + "unattended": true, + }, + "source": Object { + "index": ".slo-observability.sli-v3.3*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now/M", + "lte": "now/m", + }, + }, + }, + Object { + "term": Object { + "slo.id": "irrelevant", + }, + }, + Object { + "term": Object { + "slo.revision": 1, + }, + }, + ], + }, + }, + }, + "sync": Object { + "time": Object { + "delay": "65s", + "field": "event.ingested", + }, + }, + "transform_id": "slo-summary-irrelevant-1", +} +`; + +exports[`Summary Transform Generator for 'Occurrences' SLO generates the correct transform for a weekly calendar aligned SLO 1`] = ` +Object { + "_meta": Object { + "managed": true, + "managed_by": "observability", + "version": 3.3, + }, + "defer_validation": true, + "description": "Summarise the rollup data of SLO: irrelevant [id: irrelevant, revision: 1].", + "dest": Object { + "index": ".slo-observability.summary-v3.3", + "pipeline": ".slo-observability.summary.pipeline-irrelevant-1", + }, + "frequency": "1m", + "pivot": Object { + "aggregations": Object { + "errorBudgetConsumed": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetInitial": "errorBudgetInitial", + "sliValue": "sliValue", + }, + "script": "if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }", + }, + }, + "errorBudgetInitial": Object { + "bucket_script": Object { + "buckets_path": Object {}, + "script": "1 - 0.999", + }, + }, + "errorBudgetRemaining": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetConsumed": "errorBudgetConsumed", + }, + "script": "1 - params.errorBudgetConsumed", + }, + }, + "fiveMinuteBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.numerator", + }, + }, + "totalEvents": Object { + "sum": Object { + "field": "slo.denominator", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-480s/m", + "lte": "now-180s/m", + }, + }, + }, + }, + "goodEvents": Object { + "sum": Object { + "field": "slo.numerator", + }, + }, + "latestSliTimestamp": Object { + "max": Object { + "field": "@timestamp", + }, + }, + "oneDayBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.numerator", + }, + }, + "totalEvents": Object { + "sum": Object { + "field": "slo.denominator", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-86580s/m", + "lte": "now-180s/m", + }, + }, + }, + }, + "oneHourBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.numerator", + }, + }, + "totalEvents": Object { + "sum": Object { + "field": "slo.denominator", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-3780s/m", + "lte": "now-180s/m", + }, + }, + }, + }, + "sliValue": Object { + "bucket_script": Object { + "buckets_path": Object { + "goodEvents": "goodEvents", + "totalEvents": "totalEvents", + }, + "script": "if (params.totalEvents == 0) { return -1 } else if (params.goodEvents >= params.totalEvents) { return 1 } else { return params.goodEvents / params.totalEvents }", + }, + }, + "statusCode": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetRemaining": "errorBudgetRemaining", + "sliValue": "sliValue", + }, + "script": Object { + "source": "if (params.sliValue == -1) { return 0 } else if (params.sliValue >= 0.999) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }", + }, + }, + }, + "totalEvents": Object { + "sum": Object { + "field": "slo.denominator", + }, + }, + }, + "group_by": Object { + "monitor.config_id": Object { + "terms": Object { + "field": "monitor.config_id", + "missing_bucket": true, + }, + }, + "monitor.name": Object { + "terms": Object { + "field": "monitor.name", + "missing_bucket": true, + }, + }, + "observer.geo.name": Object { + "terms": Object { + "field": "observer.geo.name", + "missing_bucket": true, + }, + }, + "observer.name": Object { + "terms": Object { + "field": "observer.name", + "missing_bucket": true, + }, + }, + "service.environment": Object { + "terms": Object { + "field": "service.environment", + "missing_bucket": true, + }, + }, + "service.name": Object { + "terms": Object { + "field": "service.name", + "missing_bucket": true, + }, + }, + "slo.id": Object { + "terms": Object { + "field": "slo.id", + }, + }, + "slo.instanceId": Object { + "terms": Object { + "field": "slo.instanceId", + }, + }, + "slo.revision": Object { + "terms": Object { + "field": "slo.revision", + }, + }, + "transaction.name": Object { + "terms": Object { + "field": "transaction.name", + "missing_bucket": true, + }, + }, + "transaction.type": Object { + "terms": Object { + "field": "transaction.type", + "missing_bucket": true, + }, + }, + }, + }, + "settings": Object { + "deduce_mappings": false, + "unattended": true, + }, + "source": Object { + "index": ".slo-observability.sli-v3.3*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now/w", + "lte": "now/m", + }, + }, + }, + Object { + "term": Object { + "slo.id": "irrelevant", + }, + }, + Object { + "term": Object { + "slo.revision": 1, + }, + }, + ], + }, + }, + }, + "sync": Object { + "time": Object { + "delay": "65s", + "field": "event.ingested", + }, + }, + "transform_id": "slo-summary-irrelevant-1", +} +`; diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/__snapshots__/timeslices_calendar_aligned.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/__snapshots__/timeslices_calendar_aligned.test.ts.snap new file mode 100644 index 000000000000..ea635841aea6 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/__snapshots__/timeslices_calendar_aligned.test.ts.snap @@ -0,0 +1,273 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Summary Transform Generator for 'Timeslices' and 'CalendarAligned' SLO generates the correct transform for a 7 days SLO 1`] = ` +Object { + "_meta": Object { + "managed": true, + "managed_by": "observability", + "version": 3.3, + }, + "defer_validation": true, + "description": "Summarise the rollup data of SLO: irrelevant [id: irrelevant, revision: 1].", + "dest": Object { + "index": ".slo-observability.summary-v3.3", + "pipeline": ".slo-observability.summary.pipeline-irrelevant-1", + }, + "frequency": "1m", + "pivot": Object { + "aggregations": Object { + "_totalSlicesInPeriod": Object { + "bucket_script": Object { + "buckets_path": Object {}, + "script": Object { + "source": " + if (false == true) { + return Math.ceil(7 * 24 * 60 * 60 / 120); + } else { + Date d = new Date(); + Instant instant = Instant.ofEpochMilli(d.getTime()); + LocalDateTime now = LocalDateTime.ofInstant(instant, ZoneOffset.UTC); + LocalDateTime startOfMonth = now + .withDayOfMonth(1) + .withHour(0) + .withMinute(0) + .withSecond(0); + LocalDateTime startOfNextMonth = startOfMonth.plusMonths(1); + double sliceDurationInMinutes = 120 / 60; + + return Math.ceil(Duration.between(startOfMonth, startOfNextMonth).toMinutes() / sliceDurationInMinutes); + } + ", + }, + }, + }, + "errorBudgetConsumed": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetInitial": "errorBudgetInitial", + "sliValue": "sliValue", + }, + "script": "if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }", + }, + }, + "errorBudgetInitial": Object { + "bucket_script": Object { + "buckets_path": Object {}, + "script": "1 - 0.98", + }, + }, + "errorBudgetRemaining": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetConsumed": "errorBudgetConsumed", + }, + "script": "1 - params.errorBudgetConsumed", + }, + }, + "fiveMinuteBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.isGoodSlice", + }, + }, + "totalEvents": Object { + "value_count": Object { + "field": "slo.isGoodSlice", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-540s/m", + "lte": "now-240s/m", + }, + }, + }, + }, + "goodEvents": Object { + "sum": Object { + "field": "slo.isGoodSlice", + }, + }, + "latestSliTimestamp": Object { + "max": Object { + "field": "@timestamp", + }, + }, + "oneDayBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.isGoodSlice", + }, + }, + "totalEvents": Object { + "value_count": Object { + "field": "slo.isGoodSlice", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-86640s/m", + "lte": "now-240s/m", + }, + }, + }, + }, + "oneHourBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.isGoodSlice", + }, + }, + "totalEvents": Object { + "value_count": Object { + "field": "slo.isGoodSlice", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-3840s/m", + "lte": "now-240s/m", + }, + }, + }, + }, + "sliValue": Object { + "bucket_script": Object { + "buckets_path": Object { + "goodEvents": "goodEvents", + "totalEvents": "totalEvents", + "totalSlicesInPeriod": "_totalSlicesInPeriod", + }, + "script": "if (params.totalEvents == 0) { return -1 } else if (params.goodEvents >= params.totalEvents) { return 1 } else { return 1 - (params.totalEvents - params.goodEvents) / params.totalSlicesInPeriod }", + }, + }, + "statusCode": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetRemaining": "errorBudgetRemaining", + "sliValue": "sliValue", + }, + "script": "if (params.sliValue == -1) { return 0 } else if (params.sliValue >= 0.98) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }", + }, + }, + "totalEvents": Object { + "value_count": Object { + "field": "slo.isGoodSlice", + }, + }, + }, + "group_by": Object { + "monitor.config_id": Object { + "terms": Object { + "field": "monitor.config_id", + "missing_bucket": true, + }, + }, + "monitor.name": Object { + "terms": Object { + "field": "monitor.name", + "missing_bucket": true, + }, + }, + "observer.geo.name": Object { + "terms": Object { + "field": "observer.geo.name", + "missing_bucket": true, + }, + }, + "observer.name": Object { + "terms": Object { + "field": "observer.name", + "missing_bucket": true, + }, + }, + "service.environment": Object { + "terms": Object { + "field": "service.environment", + "missing_bucket": true, + }, + }, + "service.name": Object { + "terms": Object { + "field": "service.name", + "missing_bucket": true, + }, + }, + "slo.id": Object { + "terms": Object { + "field": "slo.id", + }, + }, + "slo.instanceId": Object { + "terms": Object { + "field": "slo.instanceId", + }, + }, + "slo.revision": Object { + "terms": Object { + "field": "slo.revision", + }, + }, + "transaction.name": Object { + "terms": Object { + "field": "transaction.name", + "missing_bucket": true, + }, + }, + "transaction.type": Object { + "terms": Object { + "field": "transaction.type", + "missing_bucket": true, + }, + }, + }, + }, + "settings": Object { + "deduce_mappings": false, + "unattended": true, + }, + "source": Object { + "index": ".slo-observability.sli-v3.3*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now/M", + "lte": "now/m", + }, + }, + }, + Object { + "term": Object { + "slo.id": "irrelevant", + }, + }, + Object { + "term": Object { + "slo.revision": 1, + }, + }, + ], + }, + }, + }, + "sync": Object { + "time": Object { + "delay": "65s", + "field": "event.ingested", + }, + }, + "transform_id": "slo-summary-irrelevant-1", +} +`; diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/__snapshots__/timeslices_rolling.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/__snapshots__/timeslices_rolling.test.ts.snap new file mode 100644 index 000000000000..d01bc36872fb --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/__snapshots__/timeslices_rolling.test.ts.snap @@ -0,0 +1,249 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Summary Transform Generator for 'Timeslices' and 'Rolling' SLO generates the correct transform for a 7 days SLO 1`] = ` +Object { + "_meta": Object { + "managed": true, + "managed_by": "observability", + "version": 3.3, + }, + "defer_validation": true, + "description": "Summarise the rollup data of SLO: irrelevant [id: irrelevant, revision: 1].", + "dest": Object { + "index": ".slo-observability.summary-v3.3", + "pipeline": ".slo-observability.summary.pipeline-irrelevant-1", + }, + "frequency": "1m", + "pivot": Object { + "aggregations": Object { + "errorBudgetConsumed": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetInitial": "errorBudgetInitial", + "sliValue": "sliValue", + }, + "script": "if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }", + }, + }, + "errorBudgetInitial": Object { + "bucket_script": Object { + "buckets_path": Object {}, + "script": "1 - 0.98", + }, + }, + "errorBudgetRemaining": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetConsumed": "errorBudgetConsumed", + }, + "script": "1 - params.errorBudgetConsumed", + }, + }, + "fiveMinuteBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.isGoodSlice", + }, + }, + "totalEvents": Object { + "value_count": Object { + "field": "slo.isGoodSlice", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-540s/m", + "lte": "now-240s/m", + }, + }, + }, + }, + "goodEvents": Object { + "sum": Object { + "field": "slo.isGoodSlice", + }, + }, + "latestSliTimestamp": Object { + "max": Object { + "field": "@timestamp", + }, + }, + "oneDayBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.isGoodSlice", + }, + }, + "totalEvents": Object { + "value_count": Object { + "field": "slo.isGoodSlice", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-86640s/m", + "lte": "now-240s/m", + }, + }, + }, + }, + "oneHourBurnRate": Object { + "aggs": Object { + "goodEvents": Object { + "sum": Object { + "field": "slo.isGoodSlice", + }, + }, + "totalEvents": Object { + "value_count": Object { + "field": "slo.isGoodSlice", + }, + }, + }, + "filter": Object { + "range": Object { + "@timestamp": Object { + "gte": "now-3840s/m", + "lte": "now-240s/m", + }, + }, + }, + }, + "sliValue": Object { + "bucket_script": Object { + "buckets_path": Object { + "goodEvents": "goodEvents", + "totalEvents": "totalEvents", + }, + "script": "if (params.totalEvents == 0) { return -1 } else if (params.goodEvents >= params.totalEvents) { return 1 } else { return 1 - (params.totalEvents - params.goodEvents) / 5040 }", + }, + }, + "statusCode": Object { + "bucket_script": Object { + "buckets_path": Object { + "errorBudgetRemaining": "errorBudgetRemaining", + "sliValue": "sliValue", + }, + "script": Object { + "source": "if (params.sliValue == -1) { return 0 } else if (params.sliValue >= 0.98) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }", + }, + }, + }, + "totalEvents": Object { + "value_count": Object { + "field": "slo.isGoodSlice", + }, + }, + }, + "group_by": Object { + "monitor.config_id": Object { + "terms": Object { + "field": "monitor.config_id", + "missing_bucket": true, + }, + }, + "monitor.name": Object { + "terms": Object { + "field": "monitor.name", + "missing_bucket": true, + }, + }, + "observer.geo.name": Object { + "terms": Object { + "field": "observer.geo.name", + "missing_bucket": true, + }, + }, + "observer.name": Object { + "terms": Object { + "field": "observer.name", + "missing_bucket": true, + }, + }, + "service.environment": Object { + "terms": Object { + "field": "service.environment", + "missing_bucket": true, + }, + }, + "service.name": Object { + "terms": Object { + "field": "service.name", + "missing_bucket": true, + }, + }, + "slo.id": Object { + "terms": Object { + "field": "slo.id", + }, + }, + "slo.instanceId": Object { + "terms": Object { + "field": "slo.instanceId", + }, + }, + "slo.revision": Object { + "terms": Object { + "field": "slo.revision", + }, + }, + "transaction.name": Object { + "terms": Object { + "field": "transaction.name", + "missing_bucket": true, + }, + }, + "transaction.type": Object { + "terms": Object { + "field": "transaction.type", + "missing_bucket": true, + }, + }, + }, + }, + "settings": Object { + "deduce_mappings": false, + "unattended": true, + }, + "source": Object { + "index": ".slo-observability.sli-v3.3*", + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-7d/m", + "lte": "now/m", + }, + }, + }, + Object { + "term": Object { + "slo.id": "irrelevant", + }, + }, + Object { + "term": Object { + "slo.revision": 1, + }, + }, + ], + }, + }, + }, + "sync": Object { + "time": Object { + "delay": "65s", + "field": "event.ingested", + }, + }, + "transform_id": "slo-summary-irrelevant-1", +} +`; diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/occurrences.test.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/occurrences.test.ts new file mode 100644 index 000000000000..08dc5231d297 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/occurrences.test.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createSLO } from '../../fixtures/slo'; +import { + monthlyCalendarAligned, + thirtyDaysRolling, + weeklyCalendarAligned, +} from '../../fixtures/time_window'; +import { generateSummaryTransformForOccurrences } from './occurrences'; + +describe("Summary Transform Generator for 'Occurrences' SLO", () => { + it('generates the correct transform for a weekly calendar aligned SLO', async () => { + const slo = createSLO({ + id: 'irrelevant', + budgetingMethod: 'occurrences', + timeWindow: weeklyCalendarAligned(), + }); + + const transform = generateSummaryTransformForOccurrences(slo); + + expect(transform).toMatchSnapshot(); + }); + + it('generates the correct transform for a monthly calendar aligned SLO', async () => { + const slo = createSLO({ + id: 'irrelevant', + budgetingMethod: 'occurrences', + timeWindow: monthlyCalendarAligned(), + }); + + const transform = generateSummaryTransformForOccurrences(slo); + + expect(transform).toMatchSnapshot(); + }); + + it('generates the correct transform for a 30days rolling SLO', async () => { + const slo = createSLO({ + id: 'irrelevant', + budgetingMethod: 'occurrences', + timeWindow: thirtyDaysRolling(), + }); + + const transform = generateSummaryTransformForOccurrences(slo); + + expect(transform).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/occurrences.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/occurrences.ts index 1dd2c0758096..ab5377e38b12 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/occurrences.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/occurrences.ts @@ -6,6 +6,7 @@ */ import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; +import { calendarAlignedTimeWindowSchema, DurationUnit } from '@kbn/slo-schema'; import { getSLOSummaryPipelineId, getSLOSummaryTransformId, @@ -20,6 +21,18 @@ import { buildBurnRateAgg } from './utils'; export function generateSummaryTransformForOccurrences( slo: SLODefinition ): TransformPutTransformRequest { + const isCalendarAligned = calendarAlignedTimeWindowSchema.is(slo.timeWindow); + let isWeeklyAligned = false; + if (isCalendarAligned) { + isWeeklyAligned = slo.timeWindow.duration.unit === DurationUnit.Week; + } + + const rangeLowerBound = isCalendarAligned + ? isWeeklyAligned + ? 'now/w' + : 'now/M' + : `now-${slo.timeWindow.duration.format()}/m`; + return { transform_id: getSLOSummaryTransformId(slo.id, slo.revision), dest: { @@ -34,7 +47,7 @@ export function generateSummaryTransformForOccurrences( { range: { '@timestamp': { - gte: `now-${slo.timeWindow.duration.format()}/m`, + gte: rangeLowerBound, lte: 'now/m', }, }, diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_calendar_aligned.test.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_calendar_aligned.test.ts new file mode 100644 index 000000000000..32628d619d53 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_calendar_aligned.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createSLOWithTimeslicesBudgetingMethod } from '../../fixtures/slo'; +import { monthlyCalendarAligned } from '../../fixtures/time_window'; +import { generateSummaryTransformForTimeslicesAndCalendarAligned } from './timeslices_calendar_aligned'; + +describe("Summary Transform Generator for 'Timeslices' and 'CalendarAligned' SLO", () => { + it('generates the correct transform for a 7 days SLO', async () => { + const slo = createSLOWithTimeslicesBudgetingMethod({ + id: 'irrelevant', + timeWindow: monthlyCalendarAligned(), + }); + + const transform = generateSummaryTransformForTimeslicesAndCalendarAligned(slo); + + expect(transform).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_rolling.test.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_rolling.test.ts new file mode 100644 index 000000000000..867a40deeced --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_rolling.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createSLOWithTimeslicesBudgetingMethod } from '../../fixtures/slo'; +import { sevenDaysRolling } from '../../fixtures/time_window'; +import { generateSummaryTransformForTimeslicesAndRolling } from './timeslices_rolling'; + +describe("Summary Transform Generator for 'Timeslices' and 'Rolling' SLO", () => { + it('generates the correct transform for a 7 days SLO', async () => { + const slo = createSLOWithTimeslicesBudgetingMethod({ + id: 'irrelevant', + timeWindow: sevenDaysRolling(), + }); + + const transform = generateSummaryTransformForTimeslicesAndRolling(slo); + + expect(transform).toMatchSnapshot(); + }); +});