diff --git a/x-pack/plugins/observability/server/assets/constants.ts b/x-pack/plugins/observability/server/assets/constants.ts index 78ec6611ef633..76543a27b6266 100644 --- a/x-pack/plugins/observability/server/assets/constants.ts +++ b/x-pack/plugins/observability/server/assets/constants.ts @@ -11,6 +11,7 @@ export const SLO_INDEX_TEMPLATE_NAME = '.slo-observability.sli'; export const SLO_RESOURCES_VERSION = 1; export const SLO_INGEST_PIPELINE_NAME = `${SLO_INDEX_TEMPLATE_NAME}.monthly`; export const SLO_DESTINATION_INDEX_NAME = `${SLO_INDEX_TEMPLATE_NAME}-v${SLO_RESOURCES_VERSION}`; +export const SLO_DESTINATION_INDEX_PATTERN = `${SLO_DESTINATION_INDEX_NAME}*`; export const getSLOTransformId = (sloId: string, sloRevision: number) => `slo-${sloId}-${sloRevision}`; diff --git a/x-pack/plugins/observability/server/domain/services/compute_error_budget.test.ts b/x-pack/plugins/observability/server/domain/services/compute_error_budget.test.ts deleted file mode 100644 index 846f11dbe236f..0000000000000 --- a/x-pack/plugins/observability/server/domain/services/compute_error_budget.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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 { oneMinute } from '../../services/slo/fixtures/duration'; -import { createSLO } from '../../services/slo/fixtures/slo'; -import { sevenDaysRolling, weeklyCalendarAligned } from '../../services/slo/fixtures/time_window'; -import { computeErrorBudget } from './compute_error_budget'; -import { toDateRange } from './date_range'; - -describe('computeErrorBudget', () => { - describe('for rolling time window', () => { - describe('for occurrences budgeting method', () => { - it('computes the error budget', () => { - const slo = createSLO({ - budgetingMethod: 'occurrences', - timeWindow: sevenDaysRolling(), - objective: { target: 0.95 }, - }); - const dateRange = toDateRange(slo.timeWindow); - const errorBudget = computeErrorBudget(slo, { - good: 97, - total: 100, - dateRange, - }); - - expect(errorBudget).toEqual({ - initial: 0.05, - consumed: 0.6, - remaining: 0.4, - isEstimated: false, - }); - }); - }); - - describe('for timeslices budgeting method', () => { - it('computes the error budget', () => { - const slo = createSLO({ - budgetingMethod: 'timeslices', - timeWindow: sevenDaysRolling(), - objective: { target: 0.95, timesliceTarget: 0.95, timesliceWindow: oneMinute() }, - }); - const dateRange = toDateRange(slo.timeWindow); - // 7 days sliced in 1m buckets = 10,080 slices - const errorBudget = computeErrorBudget(slo, { - good: 9987, - total: 10080, - dateRange, - }); - - expect(errorBudget).toEqual({ - initial: 0.05, - consumed: 0.184524, - remaining: 0.815476, - isEstimated: false, - }); - }); - }); - }); - - describe('for calendar aligned time window', () => { - describe('for occurrences budgeting method', () => { - beforeEach(() => { - jest.useFakeTimers({ now: new Date('2023-05-09') }); - }); - - it('computes the error budget with an estimation of total events', () => { - const slo = createSLO({ - budgetingMethod: 'occurrences', - timeWindow: weeklyCalendarAligned(), - objective: { target: 0.95 }, - }); - const dateRange = toDateRange(slo.timeWindow); - const errorBudget = computeErrorBudget(slo, { - good: 97, - total: 100, - dateRange, - }); - - expect(errorBudget).toEqual({ - initial: 0.05, - consumed: 0.171429, - remaining: 0.828571, - isEstimated: true, - }); - }); - }); - - describe('for timeslices budgeting method', () => { - it('computes the error budget', () => { - const slo = createSLO({ - budgetingMethod: 'timeslices', - timeWindow: weeklyCalendarAligned(), - objective: { target: 0.95, timesliceTarget: 0.95, timesliceWindow: oneMinute() }, - }); - const dateRange = toDateRange(slo.timeWindow); - // 2 days sliced in 1m buckets = 2,880 slices (slices we have data for) = total - // 7 days sliced in 1m buckets = 10,080 slices (all slices for the window) = window_total - const errorBudget = computeErrorBudget(slo, { - good: 2823, - total: 2880, - dateRange, - }); - - // error rate = (total - good) / window_total = (2880 - 2823) / 10080 = 0.00565476 - // consumed = error rate / error budget = 0.00565476 / 0.05 = 0.1130952 - expect(errorBudget).toEqual({ - initial: 0.05, - consumed: 0.113106, - remaining: 0.886894, - isEstimated: false, - }); - }); - }); - }); - - it("returns default values when total events is '0'", () => { - const slo = createSLO(); - const dateRange = toDateRange(slo.timeWindow); - const errorBudget = computeErrorBudget(slo, { good: 100, total: 0, dateRange }); - - expect(errorBudget).toEqual({ - initial: 0.001, // 0.1% - consumed: 0, // 0% consumed - remaining: 1, // 100% remaining - isEstimated: false, - }); - }); - - it("returns default values when 'good >= total' events", () => { - const slo = createSLO(); - const dateRange = toDateRange(slo.timeWindow); - const errorBudget = computeErrorBudget(slo, { good: 9999, total: 9, dateRange }); - - expect(errorBudget).toEqual({ - initial: 0.001, - consumed: 0, - remaining: 1, - isEstimated: false, - }); - }); - - it('computes the error budget with all good events', () => { - const slo = createSLO(); - const dateRange = toDateRange(slo.timeWindow); - const errorBudget = computeErrorBudget(slo, { good: 100, total: 100, dateRange }); - - expect(errorBudget).toEqual({ - initial: 0.001, - consumed: 0, - remaining: 1, - isEstimated: false, - }); - }); - - it('computes the error budget when exactly consumed', () => { - const slo = createSLO(); - const dateRange = toDateRange(slo.timeWindow); - const errorBudget = computeErrorBudget(slo, { good: 999, total: 1000, dateRange }); - - expect(errorBudget).toEqual({ - initial: 0.001, - consumed: 1, - remaining: 0, - isEstimated: false, - }); - }); - - it('computes the error budget with rounded values', () => { - const slo = createSLO(); - const dateRange = toDateRange(slo.timeWindow); - const errorBudget = computeErrorBudget(slo, { good: 770, total: 777, dateRange }); - - expect(errorBudget).toEqual({ - initial: 0.001, - consumed: 9.009009, // i.e. 900.90% consumed - remaining: -8.009009, // i.e. -800.90% remaining - isEstimated: false, - }); - }); - - it('computes the error budget with no good events', () => { - const slo = createSLO(); - const dateRange = toDateRange(slo.timeWindow); - const errorBudget = computeErrorBudget(slo, { good: 0, total: 100, dateRange }); - - expect(errorBudget).toEqual({ - initial: 0.001, - consumed: 1000, // i.e. 100,000% consumed - remaining: -999, - isEstimated: false, - }); - }); -}); diff --git a/x-pack/plugins/observability/server/domain/services/compute_error_budget.ts b/x-pack/plugins/observability/server/domain/services/compute_error_budget.ts deleted file mode 100644 index 3302ac35678ee..0000000000000 --- a/x-pack/plugins/observability/server/domain/services/compute_error_budget.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 moment from 'moment'; -import { - calendarAlignedTimeWindowSchema, - Duration, - occurrencesBudgetingMethodSchema, - rollingTimeWindowSchema, - timeslicesBudgetingMethodSchema, -} from '@kbn/slo-schema'; - -import { DateRange, ErrorBudget, IndicatorData, SLO, toMomentUnitOfTime } from '../models'; -import { toHighPrecision } from '../../utils/number'; - -// More details about calculus: https://github.com/elastic/kibana/issues/143980 -export function computeErrorBudget(slo: SLO, sliData: IndicatorData): ErrorBudget { - const { good, total } = sliData; - if (total === 0 || good >= total) { - const initialErrorBudget = 1 - slo.objective.target; - return toErrorBudget(initialErrorBudget, 0); - } - - if (rollingTimeWindowSchema.is(slo.timeWindow)) { - return computeForRolling(slo, sliData); - } - - if (calendarAlignedTimeWindowSchema.is(slo.timeWindow)) { - if (timeslicesBudgetingMethodSchema.is(slo.budgetingMethod)) { - return computeForCalendarAlignedWithTimeslices(slo, sliData); - } - - if (occurrencesBudgetingMethodSchema.is(slo.budgetingMethod)) { - return computeForCalendarAlignedWithOccurrences(slo, sliData); - } - } - - throw new Error('Invalid slo time window'); -} - -function computeForRolling(slo: SLO, sliData: IndicatorData) { - const { good, total } = sliData; - const initialErrorBudget = 1 - slo.objective.target; - const consumedErrorBudget = (total - good) / (total * initialErrorBudget); - return toErrorBudget(initialErrorBudget, consumedErrorBudget); -} - -function computeForCalendarAlignedWithOccurrences(slo: SLO, sliData: IndicatorData) { - const { good, total, dateRange } = sliData; - const initialErrorBudget = 1 - slo.objective.target; - const now = moment(); - - const durationCalendarPeriod = moment(dateRange.to).diff(dateRange.from, 'minutes'); - const durationSinceBeginning = now.isAfter(dateRange.to) - ? durationCalendarPeriod - : moment(now).diff(dateRange.from, 'minutes'); - - const totalEventsEstimatedAtPeriodEnd = Math.round( - (total / durationSinceBeginning) * durationCalendarPeriod - ); - - const consumedErrorBudget = - (total - good) / (totalEventsEstimatedAtPeriodEnd * initialErrorBudget); - return toErrorBudget(initialErrorBudget, consumedErrorBudget, true); -} - -function computeForCalendarAlignedWithTimeslices(slo: SLO, sliData: IndicatorData) { - const { good, total, dateRange } = sliData; - const initialErrorBudget = 1 - slo.objective.target; - - const totalSlices = computeTotalSlicesFromDateRange(dateRange, slo.objective.timesliceWindow!); - const consumedErrorBudget = (total - good) / (totalSlices * initialErrorBudget); - - return toErrorBudget(initialErrorBudget, consumedErrorBudget); -} - -export function computeTotalSlicesFromDateRange(dateRange: DateRange, timesliceWindow: Duration) { - const dateRangeDurationInUnit = moment(dateRange.to).diff( - dateRange.from, - toMomentUnitOfTime(timesliceWindow.unit) - ); - const totalSlices = Math.ceil(dateRangeDurationInUnit / timesliceWindow!.value); - return totalSlices; -} - -export function toErrorBudget( - initial: number, - consumed: number, - isEstimated: boolean = false -): ErrorBudget { - return { - initial: toHighPrecision(initial), - consumed: toHighPrecision(consumed), - remaining: toHighPrecision(1 - consumed), - isEstimated, - }; -} diff --git a/x-pack/plugins/observability/server/domain/services/compute_sli.test.ts b/x-pack/plugins/observability/server/domain/services/compute_sli.test.ts index 5ecb7cafc805e..f2c1a08d47c46 100644 --- a/x-pack/plugins/observability/server/domain/services/compute_sli.test.ts +++ b/x-pack/plugins/observability/server/domain/services/compute_sli.test.ts @@ -9,18 +9,18 @@ import { computeSLI } from './compute_sli'; describe('computeSLI', () => { it('returns -1 when no total events', () => { - expect(computeSLI({ good: 100, total: 0 })).toEqual(-1); + expect(computeSLI(100, 0)).toEqual(-1); }); it('returns the sli value', () => { - expect(computeSLI({ good: 100, total: 1000 })).toEqual(0.1); + expect(computeSLI(100, 1000)).toEqual(0.1); }); it('returns 1 when good is greater than total events', () => { - expect(computeSLI({ good: 9999, total: 9 })).toEqual(1); + expect(computeSLI(9999, 9)).toEqual(1); }); it('returns rounds the value to 6 digits', () => { - expect(computeSLI({ good: 33, total: 90 })).toEqual(0.366667); + expect(computeSLI(33, 90)).toEqual(0.366667); }); }); diff --git a/x-pack/plugins/observability/server/domain/services/compute_sli.ts b/x-pack/plugins/observability/server/domain/services/compute_sli.ts index d944e427564fa..eb7d0493f5534 100644 --- a/x-pack/plugins/observability/server/domain/services/compute_sli.ts +++ b/x-pack/plugins/observability/server/domain/services/compute_sli.ts @@ -5,13 +5,11 @@ * 2.0. */ -import { IndicatorData } from '../models'; import { toHighPrecision } from '../../utils/number'; const NO_DATA = -1; -export function computeSLI(sliData: Pick): number { - const { good, total } = sliData; +export function computeSLI(good: number, total: number): number { if (total === 0) { return NO_DATA; } diff --git a/x-pack/plugins/observability/server/domain/services/compute_summary_status.ts b/x-pack/plugins/observability/server/domain/services/compute_summary_status.ts index 76217973e7ccd..3aaaffe180b6a 100644 --- a/x-pack/plugins/observability/server/domain/services/compute_summary_status.ts +++ b/x-pack/plugins/observability/server/domain/services/compute_summary_status.ts @@ -12,7 +12,7 @@ export function computeSummaryStatus(slo: SLO, sliValue: number, errorBudget: Er return 'NO_DATA'; } - if (slo.objective.target <= sliValue) { + if (sliValue >= slo.objective.target) { return 'HEALTHY'; } else { return errorBudget.remaining > 0 ? 'DEGRADING' : 'VIOLATED'; diff --git a/x-pack/plugins/observability/server/domain/services/error_budget.ts b/x-pack/plugins/observability/server/domain/services/error_budget.ts new file mode 100644 index 0000000000000..74165c5eec560 --- /dev/null +++ b/x-pack/plugins/observability/server/domain/services/error_budget.ts @@ -0,0 +1,22 @@ +/* + * 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 { toHighPrecision } from '../../utils/number'; +import { ErrorBudget } from '../models'; + +export function toErrorBudget( + initial: number, + consumed: number, + isEstimated: boolean = false +): ErrorBudget { + return { + initial: toHighPrecision(initial), + consumed: toHighPrecision(consumed), + remaining: toHighPrecision(1 - consumed), + isEstimated, + }; +} diff --git a/x-pack/plugins/observability/server/domain/services/index.ts b/x-pack/plugins/observability/server/domain/services/index.ts index 7dd7cf7d85344..212647f27b172 100644 --- a/x-pack/plugins/observability/server/domain/services/index.ts +++ b/x-pack/plugins/observability/server/domain/services/index.ts @@ -6,7 +6,7 @@ */ export * from './compute_burn_rate'; -export * from './compute_error_budget'; +export * from './error_budget'; export * from './compute_sli'; export * from './compute_summary_status'; export * from './date_range'; diff --git a/x-pack/plugins/observability/server/services/slo/__snapshots__/historical_summary_client.test.ts.snap b/x-pack/plugins/observability/server/services/slo/__snapshots__/historical_summary_client.test.ts.snap index 94237416e64ce..f2c4518c18ecb 100644 --- a/x-pack/plugins/observability/server/services/slo/__snapshots__/historical_summary_client.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/__snapshots__/historical_summary_client.test.ts.snap @@ -4,10 +4,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 1, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -18,10 +18,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.003226, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.996774, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -32,10 +32,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.006452, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.993548, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -46,10 +46,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.009678, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.990322, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -60,10 +60,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.012904, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.987096, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -74,10 +74,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.016129, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.983871, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -88,10 +88,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.019355, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.980645, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -102,10 +102,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.022581, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.977419, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -116,10 +116,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.025806, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.974194, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -130,10 +130,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.029033, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.970967, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -144,10 +144,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.032258, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.967742, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -158,10 +158,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.035485, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.964515, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -172,10 +172,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.03871, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.96129, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -186,10 +186,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.041937, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.958063, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -200,10 +200,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.045163, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.954837, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -214,10 +214,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.048387, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.951613, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -228,10 +228,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.051614, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.948386, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -242,10 +242,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.054839, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.945161, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -256,10 +256,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.058066, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.941934, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -270,10 +270,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.06129, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.93871, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -284,10 +284,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.064516, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.935484, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -298,10 +298,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.067744, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.932256, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -312,10 +312,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.070969, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.929031, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -326,10 +326,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.074196, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.925804, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -340,10 +340,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.077419, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.922581, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -354,10 +354,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.080645, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.919355, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -368,10 +368,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.083873, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.916127, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -382,10 +382,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.087101, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.912899, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -396,10 +396,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.090324, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.909676, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -410,10 +410,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.09355, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.90645, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -424,10 +424,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.096774, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.903226, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -438,10 +438,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.1, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.9, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -452,10 +452,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.103227, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.896773, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -466,10 +466,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.106455, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.893545, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -480,10 +480,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.109678, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.890322, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -494,10 +494,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.112906, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.887094, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -508,10 +508,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.116133, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.883867, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -522,10 +522,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.119359, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.880641, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -536,10 +536,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.122584, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.877416, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -550,10 +550,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.125813, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.874187, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -564,10 +564,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.129032, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.870968, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -578,10 +578,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.132263, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.867737, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -592,10 +592,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.13549, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.86451, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -606,10 +606,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.138714, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.861286, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -620,10 +620,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.141941, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.858059, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -634,10 +634,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.145164, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.854836, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -648,10 +648,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.14839, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.85161, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -662,10 +662,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.151619, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.848381, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -676,10 +676,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.154843, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.845157, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -690,10 +690,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.158069, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.841931, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -704,10 +704,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.16129, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.83871, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -718,10 +718,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.164522, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.835478, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -732,10 +732,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.167748, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.832252, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -746,10 +746,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.170967, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.829033, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -760,10 +760,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.174198, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.825802, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -774,10 +774,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.177421, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.822579, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -788,10 +788,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.180647, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.819353, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -802,10 +802,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.183874, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.816126, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -816,10 +816,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.187104, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.812896, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -830,10 +830,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.190325, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.809675, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -844,10 +844,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.193548, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.806452, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -858,10 +858,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.196784, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.803216, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -872,10 +872,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.2, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.8, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -886,10 +886,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.203228, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.796772, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -900,10 +900,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.206458, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.793542, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -914,10 +914,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.209679, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.790321, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -928,10 +928,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.212912, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.787088, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -942,10 +942,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.216136, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.783864, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -956,10 +956,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.219361, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.780639, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -970,10 +970,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.222587, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.777413, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -984,10 +984,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.225815, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.774185, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -998,10 +998,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.229032, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.770968, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1012,10 +1012,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.232262, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.767738, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1026,10 +1026,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.235494, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.764506, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1040,10 +1040,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.238714, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.761286, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1054,10 +1054,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.241935, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.758065, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1068,10 +1068,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.245171, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.754829, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1082,10 +1082,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.248394, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.751606, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1096,10 +1096,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.251619, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.748381, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1110,10 +1110,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.254845, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.745155, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1124,10 +1124,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.258071, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.741929, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1138,10 +1138,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.261299, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.738701, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1152,10 +1152,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.264528, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.735472, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1166,10 +1166,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.267743, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.732257, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1180,10 +1180,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.270974, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.729026, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1194,10 +1194,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.274206, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.725794, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1208,10 +1208,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.277423, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.722577, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1222,10 +1222,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.280657, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.719343, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1236,10 +1236,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.283876, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.716124, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1250,10 +1250,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.287097, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.712903, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1264,10 +1264,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.290333, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.709667, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1278,10 +1278,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.293555, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.706445, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1292,10 +1292,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.296777, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.703223, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1306,10 +1306,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.3, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.7, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1320,10 +1320,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.30324, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.69676, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1334,10 +1334,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.306464, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.693536, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1348,10 +1348,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.30969, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.69031, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1362,10 +1362,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.312916, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.687084, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1376,10 +1376,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.316142, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.683858, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1390,10 +1390,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.31937, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.68063, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1404,10 +1404,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.322581, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.677419, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1418,10 +1418,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.325809, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.674191, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1432,10 +1432,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.329038, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.670962, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1446,10 +1446,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.332268, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.667732, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1460,10 +1460,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.335499, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.664501, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1474,10 +1474,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.338712, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.661288, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1488,10 +1488,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.341944, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.658056, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", @@ -1502,10 +1502,10 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns th Object { "date": Any, "errorBudget": Object { - "consumed": 0.345177, + "consumed": 0.6, "initial": 0.05, "isEstimated": true, - "remaining": 0.654823, + "remaining": 0.4, }, "sliValue": 0.97, "status": "HEALTHY", diff --git a/x-pack/plugins/observability/server/services/slo/historical_summary_client.ts b/x-pack/plugins/observability/server/services/slo/historical_summary_client.ts index 5a2e8dd575303..e2de4b1146947 100644 --- a/x-pack/plugins/observability/server/services/slo/historical_summary_client.ts +++ b/x-pack/plugins/observability/server/services/slo/historical_summary_client.ts @@ -9,6 +9,7 @@ import { MsearchMultisearchBody } from '@elastic/elasticsearch/lib/api/typesWith import { ElasticsearchClient } from '@kbn/core/server'; import { calendarAlignedTimeWindowSchema, + Duration, occurrencesBudgetingMethodSchema, rollingTimeWindowSchema, timeslicesBudgetingMethodSchema, @@ -16,13 +17,11 @@ import { } from '@kbn/slo-schema'; import { assertNever } from '@kbn/std'; import moment from 'moment'; - -import { SLO_DESTINATION_INDEX_NAME } from '../../assets/constants'; +import { SLO_DESTINATION_INDEX_PATTERN } from '../../assets/constants'; import { DateRange, HistoricalSummary, SLO, SLOId } from '../../domain/models'; import { computeSLI, computeSummaryStatus, - computeTotalSlicesFromDateRange, toDateRange, toErrorBudget, } from '../../domain/services'; @@ -59,7 +58,7 @@ export class DefaultHistoricalSummaryClient implements HistoricalSummaryClient { }, {}); const searches = sloList.flatMap((slo) => [ - { index: `${SLO_DESTINATION_INDEX_NAME}*` }, + { index: SLO_DESTINATION_INDEX_PATTERN }, generateSearchQuery(slo, dateRangeBySlo[slo.id]), ]); @@ -98,11 +97,9 @@ export class DefaultHistoricalSummaryClient implements HistoricalSummaryClient { } if (occurrencesBudgetingMethodSchema.is(slo.budgetingMethod)) { - const dateRange = dateRangeBySlo[slo.id]; historicalSummaryBySlo[slo.id] = handleResultForCalendarAlignedAndOccurrences( slo, - buckets, - dateRange + buckets ); continue; } @@ -119,29 +116,15 @@ export class DefaultHistoricalSummaryClient implements HistoricalSummaryClient { function handleResultForCalendarAlignedAndOccurrences( slo: SLO, - buckets: DailyAggBucket[], - dateRange: DateRange + buckets: DailyAggBucket[] ): HistoricalSummary[] { const initialErrorBudget = 1 - slo.objective.target; return buckets.map((bucket: DailyAggBucket): HistoricalSummary => { const good = bucket.cumulative_good?.value ?? 0; const total = bucket.cumulative_total?.value ?? 0; - const sliValue = computeSLI({ good, total }); - - const durationCalendarPeriod = moment(dateRange.to).diff(dateRange.from, 'minutes'); - const bucketDate = moment(bucket.key_as_string); - const durationSinceBeginning = bucketDate.isSameOrAfter(dateRange.to) - ? durationCalendarPeriod - : moment(bucketDate).diff(dateRange.from, 'minutes'); - - const totalEventsEstimatedAtPeriodEnd = Math.round( - (total / durationSinceBeginning) * durationCalendarPeriod - ); - - const consumedErrorBudget = - (total - good) / (totalEventsEstimatedAtPeriodEnd * initialErrorBudget); - + const sliValue = computeSLI(good, total); + const consumedErrorBudget = sliValue < 0 ? 0 : (1 - sliValue) / initialErrorBudget; const errorBudget = toErrorBudget(initialErrorBudget, consumedErrorBudget, true); return { @@ -163,7 +146,7 @@ function handleResultForCalendarAlignedAndTimeslices( return buckets.map((bucket: DailyAggBucket): HistoricalSummary => { const good = bucket.cumulative_good?.value ?? 0; const total = bucket.cumulative_total?.value ?? 0; - const sliValue = computeSLI({ good, total }); + const sliValue = computeSLI(good, total); const totalSlices = computeTotalSlicesFromDateRange(dateRange, slo.objective.timesliceWindow!); const consumedErrorBudget = (total - good) / (totalSlices * initialErrorBudget); const errorBudget = toErrorBudget(initialErrorBudget, consumedErrorBudget); @@ -190,8 +173,8 @@ function handleResultForRolling(slo: SLO, buckets: DailyAggBucket[]): Historical .map((bucket: DailyAggBucket): HistoricalSummary => { const good = bucket.cumulative_good?.value ?? 0; const total = bucket.cumulative_total?.value ?? 0; - const sliValue = computeSLI({ good, total }); - const consumedErrorBudget = total === 0 ? 0 : (total - good) / (total * initialErrorBudget); + const sliValue = computeSLI(good, total); + const consumedErrorBudget = sliValue < 0 ? 0 : (1 - sliValue) / initialErrorBudget; const errorBudget = toErrorBudget(initialErrorBudget, consumedErrorBudget); return { @@ -305,6 +288,14 @@ function getDateRange(slo: SLO) { assertNever(slo.timeWindow); } +function computeTotalSlicesFromDateRange(dateRange: DateRange, timesliceWindow: Duration) { + const dateRangeDurationInUnit = moment(dateRange.to).diff( + dateRange.from, + toMomentUnitOfTime(timesliceWindow.unit) + ); + return Math.ceil(dateRangeDurationInUnit / timesliceWindow!.value); +} + export function getFixedIntervalAndBucketsPerDay(durationInDays: number): { fixedInterval: string; bucketsPerDay: number; diff --git a/x-pack/plugins/observability/server/services/slo/sli_client.ts b/x-pack/plugins/observability/server/services/slo/sli_client.ts index fe0959b57224a..fe03e4140ed10 100644 --- a/x-pack/plugins/observability/server/services/slo/sli_client.ts +++ b/x-pack/plugins/observability/server/services/slo/sli_client.ts @@ -13,13 +13,12 @@ import { MsearchMultisearchBody, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ElasticsearchClient } from '@kbn/core/server'; -import { assertNever } from '@kbn/std'; import { occurrencesBudgetingMethodSchema, timeslicesBudgetingMethodSchema } from '@kbn/slo-schema'; - -import { SLO_DESTINATION_INDEX_NAME } from '../../assets/constants'; +import { assertNever } from '@kbn/std'; +import { SLO_DESTINATION_INDEX_PATTERN } from '../../assets/constants'; +import { DateRange, Duration, IndicatorData, SLO } from '../../domain/models'; import { toDateRange } from '../../domain/services/date_range'; import { InternalQueryError } from '../../errors'; -import { DateRange, Duration, IndicatorData, SLO } from '../../domain/models'; export interface SLIClient { fetchSLIDataFrom( @@ -56,7 +55,7 @@ export class DefaultSLIClient implements SLIClient { if (occurrencesBudgetingMethodSchema.is(slo.budgetingMethod)) { const result = await this.esClient.search({ ...commonQuery(slo, longestDateRange), - index: `${SLO_DESTINATION_INDEX_NAME}*`, + index: SLO_DESTINATION_INDEX_PATTERN, aggs: toLookbackWindowsAggregationsQuery(sortedLookbackWindows), }); @@ -66,7 +65,7 @@ export class DefaultSLIClient implements SLIClient { if (timeslicesBudgetingMethodSchema.is(slo.budgetingMethod)) { const result = await this.esClient.search({ ...commonQuery(slo, longestDateRange), - index: `${SLO_DESTINATION_INDEX_NAME}*`, + index: SLO_DESTINATION_INDEX_PATTERN, aggs: toLookbackWindowsSlicedAggregationsQuery(slo, sortedLookbackWindows), }); @@ -159,8 +158,8 @@ function handleWindowedResult( } const indicatorDataPerLookbackWindow: Record = {}; - lookbackWindows.forEach((lookbackWindow) => { - const windowAggBuckets = aggregations[lookbackWindow.name]?.buckets; + for (const lookbackWindow of lookbackWindows) { + const windowAggBuckets = aggregations[lookbackWindow.name]?.buckets ?? []; if (!Array.isArray(windowAggBuckets) || windowAggBuckets.length === 0) { throw new InternalQueryError('Invalid aggregation bucket response'); } @@ -176,7 +175,7 @@ function handleWindowedResult( total, dateRange: { from: new Date(bucket.from_as_string!), to: new Date(bucket.to_as_string!) }, }; - }); + } return indicatorDataPerLookbackWindow; } diff --git a/x-pack/plugins/observability/server/services/slo/summary_client.ts b/x-pack/plugins/observability/server/services/slo/summary_client.ts index 94fa69fb12371..fe01e22608260 100644 --- a/x-pack/plugins/observability/server/services/slo/summary_client.ts +++ b/x-pack/plugins/observability/server/services/slo/summary_client.ts @@ -7,11 +7,18 @@ import { MsearchMultisearchBody } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ElasticsearchClient } from '@kbn/core/server'; -import { occurrencesBudgetingMethodSchema, timeslicesBudgetingMethodSchema } from '@kbn/slo-schema'; -import { SLO_DESTINATION_INDEX_NAME } from '../../assets/constants'; -import { toDateRange } from '../../domain/services/date_range'; +import { + calendarAlignedTimeWindowSchema, + Duration, + occurrencesBudgetingMethodSchema, + timeslicesBudgetingMethodSchema, + toMomentUnitOfTime, +} from '@kbn/slo-schema'; +import moment from 'moment'; +import { SLO_DESTINATION_INDEX_PATTERN } from '../../assets/constants'; import { DateRange, SLO, SLOId, Summary } from '../../domain/models'; -import { computeErrorBudget, computeSLI, computeSummaryStatus } from '../../domain/services'; +import { computeSLI, computeSummaryStatus, toErrorBudget } from '../../domain/services'; +import { toDateRange } from '../../domain/services/date_range'; export interface SummaryClient { fetchSummary(sloList: SLO[]): Promise>; @@ -26,7 +33,7 @@ export class DefaultSummaryClient implements SummaryClient { return acc; }, {}); const searches = sloList.flatMap((slo) => [ - { index: `${SLO_DESTINATION_INDEX_NAME}*` }, + { index: SLO_DESTINATION_INDEX_PATTERN }, generateSearchQuery(slo, dateRangeBySlo[slo.id]), ]); @@ -45,12 +52,31 @@ export class DefaultSummaryClient implements SummaryClient { const good = aggregations?.good?.value ?? 0; const total = aggregations?.total?.value ?? 0; - const sliValue = computeSLI({ good, total }); - const errorBudget = computeErrorBudget(slo, { - dateRange: dateRangeBySlo[slo.id], - good, - total, - }); + const sliValue = computeSLI(good, total); + const initialErrorBudget = 1 - slo.objective.target; + let errorBudget; + + if ( + calendarAlignedTimeWindowSchema.is(slo.timeWindow) && + timeslicesBudgetingMethodSchema.is(slo.budgetingMethod) + ) { + const totalSlices = computeTotalSlicesFromDateRange( + dateRangeBySlo[slo.id], + slo.objective.timesliceWindow! + ); + const consumedErrorBudget = + sliValue < 0 ? 0 : (total - good) / (totalSlices * initialErrorBudget); + + errorBudget = toErrorBudget(initialErrorBudget, consumedErrorBudget); + } else { + const consumedErrorBudget = sliValue < 0 ? 0 : (1 - sliValue) / initialErrorBudget; + errorBudget = toErrorBudget( + initialErrorBudget, + consumedErrorBudget, + calendarAlignedTimeWindowSchema.is(slo.timeWindow) + ); + } + summaryBySlo[slo.id] = { sliValue, errorBudget, @@ -62,6 +88,14 @@ export class DefaultSummaryClient implements SummaryClient { } } +function computeTotalSlicesFromDateRange(dateRange: DateRange, timesliceWindow: Duration) { + const dateRangeDurationInUnit = moment(dateRange.to).diff( + dateRange.from, + toMomentUnitOfTime(timesliceWindow.unit) + ); + return Math.ceil(dateRangeDurationInUnit / timesliceWindow!.value); +} + function generateSearchQuery(slo: SLO, dateRange: DateRange): MsearchMultisearchBody { return { size: 0,