"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultHistoricalSummaryClient = void 0; exports.getFixedIntervalAndBucketsPerDay = getFixedIntervalAndBucketsPerDay; var _sloSchema = require("@kbn/slo-schema"); var _std = require("@kbn/std"); var _moment = _interopRequireDefault(require("moment")); var _constants = require("../../assets/constants"); var _services = require("../../domain/services"); /* * 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. */ class DefaultHistoricalSummaryClient { constructor(esClient) { this.esClient = esClient; } async fetch(list) { const dateRangeBySlo = list.reduce((acc, { sloId, slo }) => { acc[sloId] = getDateRange(slo); return acc; }, {}); const searches = list.flatMap(({ sloId, instanceId, slo }) => [{ index: _constants.SLO_DESTINATION_INDEX_PATTERN }, generateSearchQuery(slo, instanceId, dateRangeBySlo[sloId])]); const historicalSummary = []; if (searches.length === 0) { return historicalSummary; } const result = await this.esClient.msearch({ searches }); for (let i = 0; i < result.responses.length; i++) { var _result$responses$i$a, _result$responses$i$a2; const { slo, sloId, instanceId } = list[i]; if ('error' in result.responses[i]) { // handle errorneous responses with an empty historical summary data historicalSummary.push({ sloId, instanceId, data: [] }); continue; } // @ts-ignore typing msearch is hard, we cast the response to what it is supposed to be. const buckets = ((_result$responses$i$a = result.responses[i].aggregations) === null || _result$responses$i$a === void 0 ? void 0 : (_result$responses$i$a2 = _result$responses$i$a.daily) === null || _result$responses$i$a2 === void 0 ? void 0 : _result$responses$i$a2.buckets) || []; if (_sloSchema.rollingTimeWindowSchema.is(slo.timeWindow)) { historicalSummary.push({ sloId, instanceId, data: handleResultForRolling(slo, buckets) }); continue; } if (_sloSchema.calendarAlignedTimeWindowSchema.is(slo.timeWindow)) { if (_sloSchema.timeslicesBudgetingMethodSchema.is(slo.budgetingMethod)) { const dateRange = dateRangeBySlo[sloId]; historicalSummary.push({ sloId, instanceId, data: handleResultForCalendarAlignedAndTimeslices(slo, buckets, dateRange) }); continue; } if (_sloSchema.occurrencesBudgetingMethodSchema.is(slo.budgetingMethod)) { historicalSummary.push({ sloId, instanceId, data: handleResultForCalendarAlignedAndOccurrences(slo, buckets) }); continue; } (0, _std.assertNever)(slo.budgetingMethod); } (0, _std.assertNever)(slo.timeWindow); } return historicalSummary; } } exports.DefaultHistoricalSummaryClient = DefaultHistoricalSummaryClient; function handleResultForCalendarAlignedAndOccurrences(slo, buckets) { const initialErrorBudget = 1 - slo.objective.target; return buckets.map(bucket => { var _bucket$cumulative_go, _bucket$cumulative_go2, _bucket$cumulative_to, _bucket$cumulative_to2; const good = (_bucket$cumulative_go = (_bucket$cumulative_go2 = bucket.cumulative_good) === null || _bucket$cumulative_go2 === void 0 ? void 0 : _bucket$cumulative_go2.value) !== null && _bucket$cumulative_go !== void 0 ? _bucket$cumulative_go : 0; const total = (_bucket$cumulative_to = (_bucket$cumulative_to2 = bucket.cumulative_total) === null || _bucket$cumulative_to2 === void 0 ? void 0 : _bucket$cumulative_to2.value) !== null && _bucket$cumulative_to !== void 0 ? _bucket$cumulative_to : 0; const sliValue = (0, _services.computeSLI)(good, total); const consumedErrorBudget = sliValue < 0 ? 0 : (1 - sliValue) / initialErrorBudget; const errorBudget = (0, _services.toErrorBudget)(initialErrorBudget, consumedErrorBudget, true); return { date: new Date(bucket.key_as_string), errorBudget, sliValue, status: (0, _services.computeSummaryStatus)(slo, sliValue, errorBudget) }; }); } function handleResultForCalendarAlignedAndTimeslices(slo, buckets, dateRange) { const initialErrorBudget = 1 - slo.objective.target; return buckets.map(bucket => { var _bucket$cumulative_go3, _bucket$cumulative_go4, _bucket$cumulative_to3, _bucket$cumulative_to4; const good = (_bucket$cumulative_go3 = (_bucket$cumulative_go4 = bucket.cumulative_good) === null || _bucket$cumulative_go4 === void 0 ? void 0 : _bucket$cumulative_go4.value) !== null && _bucket$cumulative_go3 !== void 0 ? _bucket$cumulative_go3 : 0; const total = (_bucket$cumulative_to3 = (_bucket$cumulative_to4 = bucket.cumulative_total) === null || _bucket$cumulative_to4 === void 0 ? void 0 : _bucket$cumulative_to4.value) !== null && _bucket$cumulative_to3 !== void 0 ? _bucket$cumulative_to3 : 0; const sliValue = (0, _services.computeSLI)(good, total); const totalSlices = computeTotalSlicesFromDateRange(dateRange, slo.objective.timesliceWindow); const consumedErrorBudget = (total - good) / (totalSlices * initialErrorBudget); const errorBudget = (0, _services.toErrorBudget)(initialErrorBudget, consumedErrorBudget); return { date: new Date(bucket.key_as_string), errorBudget, sliValue, status: (0, _services.computeSummaryStatus)(slo, sliValue, errorBudget) }; }); } function handleResultForRolling(slo, buckets) { const initialErrorBudget = 1 - slo.objective.target; const rollingWindowDurationInDays = _moment.default.duration(slo.timeWindow.duration.value, (0, _sloSchema.toMomentUnitOfTime)(slo.timeWindow.duration.unit)).asDays(); const { bucketsPerDay } = getFixedIntervalAndBucketsPerDay(rollingWindowDurationInDays); return buckets.slice(-bucketsPerDay * rollingWindowDurationInDays).map(bucket => { var _bucket$cumulative_go5, _bucket$cumulative_go6, _bucket$cumulative_to5, _bucket$cumulative_to6; const good = (_bucket$cumulative_go5 = (_bucket$cumulative_go6 = bucket.cumulative_good) === null || _bucket$cumulative_go6 === void 0 ? void 0 : _bucket$cumulative_go6.value) !== null && _bucket$cumulative_go5 !== void 0 ? _bucket$cumulative_go5 : 0; const total = (_bucket$cumulative_to5 = (_bucket$cumulative_to6 = bucket.cumulative_total) === null || _bucket$cumulative_to6 === void 0 ? void 0 : _bucket$cumulative_to6.value) !== null && _bucket$cumulative_to5 !== void 0 ? _bucket$cumulative_to5 : 0; const sliValue = (0, _services.computeSLI)(good, total); const consumedErrorBudget = sliValue < 0 ? 0 : (1 - sliValue) / initialErrorBudget; const errorBudget = (0, _services.toErrorBudget)(initialErrorBudget, consumedErrorBudget); return { date: new Date(bucket.key_as_string), errorBudget, sliValue, status: (0, _services.computeSummaryStatus)(slo, sliValue, errorBudget) }; }); } function generateSearchQuery(slo, instanceId, dateRange) { const unit = (0, _sloSchema.toMomentUnitOfTime)(slo.timeWindow.duration.unit); const timeWindowDurationInDays = _moment.default.duration(slo.timeWindow.duration.value, unit).asDays(); const { fixedInterval, bucketsPerDay } = getFixedIntervalAndBucketsPerDay(timeWindowDurationInDays); const extraFilterByInstanceId = !!slo.groupBy && slo.groupBy !== _sloSchema.ALL_VALUE && instanceId !== _sloSchema.ALL_VALUE ? [{ term: { 'slo.instanceId': instanceId } }] : []; return { size: 0, query: { bool: { filter: [{ term: { 'slo.id': slo.id } }, { term: { 'slo.revision': slo.revision } }, { range: { '@timestamp': { gte: dateRange.from.toISOString(), lte: dateRange.to.toISOString() } } }, ...extraFilterByInstanceId] } }, aggs: { daily: { date_histogram: { field: '@timestamp', fixed_interval: fixedInterval, extended_bounds: { min: dateRange.from.toISOString(), max: 'now/d' } }, aggs: { ...(_sloSchema.occurrencesBudgetingMethodSchema.is(slo.budgetingMethod) && { good: { sum: { field: 'slo.numerator' } }, total: { sum: { field: 'slo.denominator' } } }), ...(_sloSchema.timeslicesBudgetingMethodSchema.is(slo.budgetingMethod) && { good: { sum: { field: 'slo.isGoodSlice' } }, total: { value_count: { field: 'slo.isGoodSlice' } } }), cumulative_good: { moving_fn: { buckets_path: 'good', window: timeWindowDurationInDays * bucketsPerDay, shift: 1, script: 'MovingFunctions.sum(values)', gap_policy: 'insert_zeros' } }, cumulative_total: { moving_fn: { buckets_path: 'total', window: timeWindowDurationInDays * bucketsPerDay, shift: 1, script: 'MovingFunctions.sum(values)', gap_policy: 'insert_zeros' } } } } } }; } function getDateRange(slo) { if (_sloSchema.rollingTimeWindowSchema.is(slo.timeWindow)) { const unit = (0, _sloSchema.toMomentUnitOfTime)(slo.timeWindow.duration.unit); const now = (0, _moment.default)(); return { from: now.clone().subtract(slo.timeWindow.duration.value * 2, unit).startOf('day').toDate(), to: now.startOf('minute').toDate() }; } if (_sloSchema.calendarAlignedTimeWindowSchema.is(slo.timeWindow)) { return (0, _services.toDateRange)(slo.timeWindow); } (0, _std.assertNever)(slo.timeWindow); } function computeTotalSlicesFromDateRange(dateRange, timesliceWindow) { const dateRangeDurationInUnit = (0, _moment.default)(dateRange.to).diff(dateRange.from, (0, _sloSchema.toMomentUnitOfTime)(timesliceWindow.unit)); return Math.ceil(dateRangeDurationInUnit / timesliceWindow.value); } function getFixedIntervalAndBucketsPerDay(durationInDays) { if (durationInDays <= 7) { return { fixedInterval: '1h', bucketsPerDay: 24 }; } if (durationInDays <= 30) { return { fixedInterval: '4h', bucketsPerDay: 6 }; } if (durationInDays <= 90) { return { fixedInterval: '12h', bucketsPerDay: 2 }; } return { fixedInterval: '1d', bucketsPerDay: 1 }; }