feat(slo): Add summary transform for rolling slo (#161698)

This commit is contained in:
Kevin Delemme 2023-07-14 08:58:29 -04:00
parent 39af36782e
commit b1cfbbd673
23 changed files with 2099 additions and 82 deletions

View file

@ -37,8 +37,8 @@ export function SloTimeWindowBadge({ slo }: Props) {
const unitMoment = toMomentUnitOfTime(unit);
const now = moment.utc();
const periodStart = now.clone().startOf(unitMoment!);
const periodEnd = now.clone().endOf(unitMoment!);
const periodStart = now.clone().startOf(unitMoment!).add(1, 'day');
const periodEnd = now.clone().endOf(unitMoment!).add(1, 'day');
const totalDurationInDays = periodEnd.diff(periodStart, 'days') + 1;
const elapsedDurationInDays = now.diff(periodStart, 'days') + 1;

View file

@ -25,5 +25,9 @@ export const SLO_SUMMARY_COMPONENT_TEMPLATE_SETTINGS_NAME = '.slo-observability.
export const SLO_SUMMARY_INDEX_TEMPLATE_NAME = '.slo-observability.summary';
export const SLO_SUMMARY_INDEX_TEMPLATE_PATTERN = `${SLO_SUMMARY_INDEX_TEMPLATE_NAME}-*`;
export const SLO_SUMMARY_TRANSFORM_NAME_PREFIX = 'slo-summary-';
export const SLO_SUMMARY_DESTINATION_INDEX_NAME = `${SLO_SUMMARY_INDEX_TEMPLATE_NAME}-v${SLO_RESOURCES_VERSION}`;
export const SLO_SUMMARY_DESTINATION_INDEX_PATTERN = `${SLO_SUMMARY_DESTINATION_INDEX_NAME}*`;
export const getSLOTransformId = (sloId: string, sloRevision: number) =>
`slo-${sloId}-${sloRevision}`;

View file

@ -17,8 +17,8 @@ describe('toDateRange', () => {
it('computes the date range for weekly calendar', () => {
const timeWindow = aCalendarTimeWindow(oneWeek());
expect(toDateRange(timeWindow, NOW)).toEqual({
from: new Date('2022-08-07T00:00:00.000Z'),
to: new Date('2022-08-13T23:59:59.999Z'),
from: new Date('2022-08-08T00:00:00.000Z'),
to: new Date('2022-08-14T23:59:59.999Z'),
});
});

View file

@ -15,6 +15,14 @@ import type { TimeWindow } from '../models/time_window';
export const toDateRange = (timeWindow: TimeWindow, currentDate: Date = new Date()): DateRange => {
if (calendarAlignedTimeWindowSchema.is(timeWindow)) {
const unit = toMomentUnitOfTime(timeWindow.duration.unit);
if (unit === 'weeks') {
// moment startOf(week) returns sunday, but we want to stay consistent with es "now/w" date math which returns monday.
const from = moment.utc(currentDate).startOf(unit).add(1, 'day');
const to = moment.utc(currentDate).endOf(unit).add(1, 'day');
return { from: from.toDate(), to: to.toDate() };
}
const from = moment.utc(currentDate).startOf(unit);
const to = moment.utc(currentDate).endOf(unit);

View file

@ -5,47 +5,47 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import {
PluginInitializerContext,
Plugin,
CoreSetup,
DEFAULT_APP_CATEGORIES,
Logger,
} from '@kbn/core/server';
import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects';
import { PluginSetupContract, PluginStartContract } from '@kbn/alerting-plugin/server';
import { RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server';
import { PluginSetupContract as FeaturesSetup } from '@kbn/features-plugin/server';
import {
createUICapabilities as createCasesUICapabilities,
getApiTags as getCasesApiTags,
} from '@kbn/cases-plugin/common';
import { SharePluginSetup } from '@kbn/share-plugin/server';
import { SpacesPluginSetup } from '@kbn/spaces-plugin/server';
import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { CloudSetup } from '@kbn/cloud-plugin/server';
import {
kubernetesGuideId,
kubernetesGuideConfig,
} from '../common/guided_onboarding/kubernetes_guide_config';
CoreSetup,
DEFAULT_APP_CATEGORIES,
Logger,
Plugin,
PluginInitializerContext,
} from '@kbn/core/server';
import { PluginSetupContract as FeaturesSetup } from '@kbn/features-plugin/server';
import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects';
import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server';
import { i18n } from '@kbn/i18n';
import { RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server';
import { SharePluginSetup } from '@kbn/share-plugin/server';
import { SpacesPluginSetup } from '@kbn/spaces-plugin/server';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { ObservabilityConfig } from '.';
import { casesFeatureId, observabilityFeatureId, sloFeatureId } from '../common';
import { SLO_BURN_RATE_RULE_TYPE_ID } from '../common/constants';
import {
kubernetesGuideConfig,
kubernetesGuideId,
} from '../common/guided_onboarding/kubernetes_guide_config';
import { AlertsLocatorDefinition } from '../common/locators/alerts';
import {
AnnotationsAPI,
bootstrapAnnotations,
ScopedAnnotationsClientFactory,
AnnotationsAPI,
} from './lib/annotations/bootstrap_annotations';
import { uiSettings } from './ui_settings';
import { registerRoutes } from './routes/register_routes';
import { getObservabilityServerRouteRepository } from './routes/get_global_observability_server_route_repository';
import { compositeSlo, slo, SO_COMPOSITE_SLO_TYPE, SO_SLO_TYPE } from './saved_objects';
import { AlertsLocatorDefinition } from '../common/locators/alerts';
import { casesFeatureId, observabilityFeatureId, sloFeatureId } from '../common';
import { registerRuleTypes } from './lib/rules/register_rule_types';
import { SLO_BURN_RATE_RULE_TYPE_ID } from '../common/constants';
import { registerSloUsageCollector } from './lib/collectors/register';
import { registerRuleTypes } from './lib/rules/register_rule_types';
import { getObservabilityServerRouteRepository } from './routes/get_global_observability_server_route_repository';
import { registerRoutes } from './routes/register_routes';
import { compositeSlo, slo, SO_COMPOSITE_SLO_TYPE, SO_SLO_TYPE } from './saved_objects';
import { threshold } from './saved_objects/threshold';
import { uiSettings } from './ui_settings';
export type ObservabilityPluginSetup = ReturnType<ObservabilityPlugin['setup']>;

View file

@ -5,19 +5,20 @@
* 2.0.
*/
import { forbidden, failedDependency } from '@hapi/boom';
import { failedDependency, forbidden } from '@hapi/boom';
import {
createSLOParamsSchema,
deleteSLOParamsSchema,
fetchHistoricalSummaryParamsSchema,
findSLOParamsSchema,
getSLOBurnRatesParamsSchema,
getPreviewDataParamsSchema,
getSLOBurnRatesParamsSchema,
getSLODiagnosisParamsSchema,
getSLOParamsSchema,
manageSLOParamsSchema,
updateSLOParamsSchema,
} from '@kbn/slo-schema';
import type { IndicatorTypes } from '../../domain/models';
import {
CreateSLO,
DefaultResourceInstaller,
@ -29,6 +30,13 @@ import {
KibanaSavedObjectsSLORepository,
UpdateSLO,
} from '../../services/slo';
import { FetchHistoricalSummary } from '../../services/slo/fetch_historical_summary';
import { getBurnRates } from '../../services/slo/get_burn_rates';
import { getGlobalDiagnosis, getSloDiagnosis } from '../../services/slo/get_diagnosis';
import { GetPreviewData } from '../../services/slo/get_preview_data';
import { DefaultHistoricalSummaryClient } from '../../services/slo/historical_summary_client';
import { ManageSLO } from '../../services/slo/manage_slo';
import { DefaultSummaryTransformInstaller } from '../../services/slo/summary_transform/summary_transform_installer';
import {
ApmTransactionDurationTransformGenerator,
ApmTransactionErrorRateTransformGenerator,
@ -37,15 +45,8 @@ import {
MetricCustomTransformGenerator,
TransformGenerator,
} from '../../services/slo/transform_generators';
import { createObservabilityServerRoute } from '../create_observability_server_route';
import { DefaultHistoricalSummaryClient } from '../../services/slo/historical_summary_client';
import { FetchHistoricalSummary } from '../../services/slo/fetch_historical_summary';
import type { IndicatorTypes } from '../../domain/models';
import type { ObservabilityRequestHandlerContext } from '../../types';
import { ManageSLO } from '../../services/slo/manage_slo';
import { getGlobalDiagnosis, getSloDiagnosis } from '../../services/slo/get_diagnosis';
import { getBurnRates } from '../../services/slo/get_burn_rates';
import { GetPreviewData } from '../../services/slo/get_preview_data';
import { createObservabilityServerRoute } from '../create_observability_server_route';
const transformGenerators: Record<IndicatorTypes, TransformGenerator> = {
'sli.apm.transactionDuration': new ApmTransactionDurationTransformGenerator(),
@ -74,12 +75,22 @@ const createSLORoute = createObservabilityServerRoute({
}
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
const esInternalClient = (await context.core).elasticsearch.client.asInternalUser;
const soClient = (await context.core).savedObjects.client;
const resourceInstaller = new DefaultResourceInstaller(esClient, logger);
const sloResourceInstaller = new DefaultResourceInstaller(esInternalClient, logger);
const sloSummaryInstaller = new DefaultSummaryTransformInstaller(esInternalClient, logger);
try {
await sloResourceInstaller.ensureCommonResourcesInstalled();
await sloSummaryInstaller.installAndStart();
} catch (error) {
logger.error('Failed to install SLO common resources and summary transforms', { error });
throw error;
}
const repository = new KibanaSavedObjectsSLORepository(soClient);
const transformManager = new DefaultTransformManager(transformGenerators, esClient, logger);
const createSLO = new CreateSLO(resourceInstaller, repository, transformManager);
const createSLO = new CreateSLO(repository, transformManager);
const response = await createSLO.execute(params.body);

View file

@ -8,26 +8,19 @@
import { CreateSLO } from './create_slo';
import { fiveMinute, oneMinute } from './fixtures/duration';
import { createAPMTransactionErrorRateIndicator, createSLOParams } from './fixtures/slo';
import {
createResourceInstallerMock,
createSLORepositoryMock,
createTransformManagerMock,
} from './mocks';
import { ResourceInstaller } from './resource_installer';
import { createSLORepositoryMock, createTransformManagerMock } from './mocks';
import { SLORepository } from './slo_repository';
import { TransformManager } from './transform_manager';
describe('CreateSLO', () => {
let mockResourceInstaller: jest.Mocked<ResourceInstaller>;
let mockRepository: jest.Mocked<SLORepository>;
let mockTransformManager: jest.Mocked<TransformManager>;
let createSLO: CreateSLO;
beforeEach(() => {
mockResourceInstaller = createResourceInstallerMock();
mockRepository = createSLORepositoryMock();
mockTransformManager = createTransformManagerMock();
createSLO = new CreateSLO(mockResourceInstaller, mockRepository, mockTransformManager);
createSLO = new CreateSLO(mockRepository, mockTransformManager);
});
describe('happy path', () => {
@ -37,7 +30,6 @@ describe('CreateSLO', () => {
const response = await createSLO.execute(sloParams);
expect(mockResourceInstaller.ensureCommonResourcesInstalled).toHaveBeenCalled();
expect(mockRepository.save).toHaveBeenCalledWith(
expect.objectContaining({
...sloParams,
@ -73,7 +65,6 @@ describe('CreateSLO', () => {
await createSLO.execute(sloParams);
expect(mockResourceInstaller.ensureCommonResourcesInstalled).toHaveBeenCalled();
expect(mockRepository.save).toHaveBeenCalledWith(
expect.objectContaining({
...sloParams,

View file

@ -5,30 +5,21 @@
* 2.0.
*/
import { v1 as uuidv1 } from 'uuid';
import { CreateSLOParams, CreateSLOResponse } from '@kbn/slo-schema';
import { v1 as uuidv1 } from 'uuid';
import { Duration, DurationUnit, SLO } from '../../domain/models';
import { ResourceInstaller } from './resource_installer';
import { validateSLO } from '../../domain/services';
import { SLORepository } from './slo_repository';
import { TransformManager } from './transform_manager';
import { validateSLO } from '../../domain/services';
export class CreateSLO {
constructor(
private resourceInstaller: ResourceInstaller,
private repository: SLORepository,
private transformManager: TransformManager
) {}
constructor(private repository: SLORepository, private transformManager: TransformManager) {}
public async execute(params: CreateSLOParams): Promise<CreateSLOResponse> {
const slo = this.toSLO(params);
validateSLO(slo);
await this.resourceInstaller.ensureCommonResourcesInstalled();
await this.repository.save(slo, { throwOnConflict: true });
let sloTransformId;
try {
sloTransformId = await this.transformManager.install(slo);

View file

@ -24,6 +24,8 @@ import {
SLO_SUMMARY_COMPONENT_TEMPLATE_MAPPINGS_NAME,
SLO_SUMMARY_COMPONENT_TEMPLATE_SETTINGS_NAME,
SLO_SUMMARY_INDEX_TEMPLATE_PATTERN,
SLO_DESTINATION_INDEX_NAME,
SLO_SUMMARY_DESTINATION_INDEX_NAME,
} from '../../assets/constants';
import { getSLOMappingsTemplate } from '../../assets/component_templates/slo_mappings_template';
import { getSLOSettingsTemplate } from '../../assets/component_templates/slo_settings_template';
@ -32,6 +34,7 @@ import { getSLOPipelineTemplate } from '../../assets/ingest_templates/slo_pipeli
import { getSLOSummaryMappingsTemplate } from '../../assets/component_templates/slo_summary_mappings_template';
import { getSLOSummarySettingsTemplate } from '../../assets/component_templates/slo_summary_settings_template';
import { getSLOSummaryIndexTemplate } from '../../assets/index_templates/slo_summary_index_templates';
import { retryTransientEsErrors } from '../../utils/retry';
export interface ResourceInstaller {
ensureCommonResourcesInstalled(): Promise<void>;
@ -44,11 +47,12 @@ export class DefaultResourceInstaller implements ResourceInstaller {
const alreadyInstalled = await this.areResourcesAlreadyInstalled();
if (alreadyInstalled) {
this.logger.debug('SLO resources already installed - skipping.');
this.logger.info('SLO resources already installed - skipping');
return;
}
try {
this.logger.info('Installing SLO shared resources');
await Promise.all([
this.createOrUpdateComponentTemplate(
getSLOMappingsTemplate(SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME)
@ -82,6 +86,11 @@ export class DefaultResourceInstaller implements ResourceInstaller {
)
);
await this.execute(() => this.esClient.indices.create({ index: SLO_DESTINATION_INDEX_NAME }));
await this.execute(() =>
this.esClient.indices.create({ index: SLO_SUMMARY_DESTINATION_INDEX_NAME })
);
await this.createOrUpdateIngestPipelineTemplate(
getSLOPipelineTemplate(SLO_INGEST_PIPELINE_NAME, SLO_INGEST_PIPELINE_INDEX_NAME_PREFIX)
);
@ -94,9 +103,12 @@ export class DefaultResourceInstaller implements ResourceInstaller {
private async areResourcesAlreadyInstalled(): Promise<boolean> {
let indexTemplateExists = false;
try {
const { index_templates: indexTemplates } = await this.esClient.indices.getIndexTemplate({
name: SLO_INDEX_TEMPLATE_NAME,
});
const { index_templates: indexTemplates } = await this.execute(() =>
this.esClient.indices.getIndexTemplate({
name: SLO_INDEX_TEMPLATE_NAME,
})
);
const sloIndexTemplate = indexTemplates.find(
(template) => template.name === SLO_INDEX_TEMPLATE_NAME
);
@ -109,9 +121,11 @@ export class DefaultResourceInstaller implements ResourceInstaller {
let summaryIndexTemplateExists = false;
try {
const { index_templates: indexTemplates } = await this.esClient.indices.getIndexTemplate({
name: SLO_SUMMARY_INDEX_TEMPLATE_NAME,
});
const { index_templates: indexTemplates } = await this.execute(() =>
this.esClient.indices.getIndexTemplate({
name: SLO_SUMMARY_INDEX_TEMPLATE_NAME,
})
);
const sloSummaryIndexTemplate = indexTemplates.find(
(template) => template.name === SLO_SUMMARY_INDEX_TEMPLATE_NAME
);
@ -124,7 +138,9 @@ export class DefaultResourceInstaller implements ResourceInstaller {
let ingestPipelineExists = false;
try {
const pipeline = await this.esClient.ingest.getPipeline({ id: SLO_INGEST_PIPELINE_NAME });
const pipeline = await this.execute(() =>
this.esClient.ingest.getPipeline({ id: SLO_INGEST_PIPELINE_NAME })
);
ingestPipelineExists =
// @ts-ignore _meta is not defined on the type
@ -138,16 +154,20 @@ export class DefaultResourceInstaller implements ResourceInstaller {
private async createOrUpdateComponentTemplate(template: ClusterPutComponentTemplateRequest) {
this.logger.debug(`Installing SLO component template ${template.name}`);
return this.esClient.cluster.putComponentTemplate(template);
return this.execute(() => this.esClient.cluster.putComponentTemplate(template));
}
private async createOrUpdateIndexTemplate(template: IndicesPutIndexTemplateRequest) {
this.logger.debug(`Installing SLO index template ${template.name}`);
return this.esClient.indices.putIndexTemplate(template);
return this.execute(() => this.esClient.indices.putIndexTemplate(template));
}
private async createOrUpdateIngestPipelineTemplate(template: IngestPutPipelineRequest) {
this.logger.debug(`Installing SLO ingest pipeline template ${template.id}`);
await this.esClient.ingest.putPipeline(template);
return this.execute(() => this.esClient.ingest.putPipeline(template));
}
private async execute<T>(esCall: () => Promise<T>): Promise<T> {
return await retryTransientEsErrors(esCall, { logger: this.logger });
}
}

View file

@ -0,0 +1,105 @@
/*
* 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types';
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
import {
SLO_RESOURCES_VERSION,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../assets/constants';
import { retryTransientEsErrors } from '../../../utils/retry';
import { ALL_TRANSFORM_TEMPLATES } from './templates';
export interface SummaryTransformInstaller {
installAndStart(): Promise<void>;
}
export class DefaultSummaryTransformInstaller implements SummaryTransformInstaller {
constructor(private esClient: ElasticsearchClient, private logger: Logger) {}
public async installAndStart(): Promise<void> {
const allTransformIds = ALL_TRANSFORM_TEMPLATES.map((transform) => transform.transform_id);
const summaryTransforms = await this.execute(() =>
this.esClient.transform.getTransform(
{ transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}*`, allow_no_match: true },
{ ignore: [404] }
)
);
const alreadyInstalled =
summaryTransforms.count === allTransformIds.length &&
summaryTransforms.transforms.every(
(transform) => transform._meta?.version === SLO_RESOURCES_VERSION
) &&
summaryTransforms.transforms.every((transform) => allTransformIds.includes(transform.id));
if (alreadyInstalled) {
this.logger.info(`SLO summary transforms already installed - skipping`);
return;
}
for (const transformTemplate of ALL_TRANSFORM_TEMPLATES) {
const transformId = transformTemplate.transform_id;
const transform = summaryTransforms.transforms.find((t) => t.id === transformId);
const transformAlreadyInstalled =
!!transform && transform._meta?.version === SLO_RESOURCES_VERSION;
const previousTransformAlreadyInstalled =
!!transform && transform._meta?.version !== SLO_RESOURCES_VERSION;
if (transformAlreadyInstalled) {
this.logger.info(`SLO summary transform: ${transformId} already installed - skipping`);
continue;
}
if (previousTransformAlreadyInstalled) {
await this.deletePreviousTransformVersion(transformId);
}
await this.installTransform(transformId, transformTemplate);
await this.startTransform(transformId);
}
this.logger.info(`All SLO summary transforms installed and started`);
}
private async installTransform(
transformId: string,
transformTemplate: TransformPutTransformRequest
) {
this.logger.info(`Installing SLO summary transform: ${transformId}`);
await this.execute(() =>
this.esClient.transform.putTransform(transformTemplate, { ignore: [409] })
);
}
private async deletePreviousTransformVersion(transformId: string) {
this.logger.info(`Deleting previous SLO summary transform: ${transformId}`);
await this.execute(() =>
this.esClient.transform.stopTransform(
{ transform_id: transformId, allow_no_match: true, force: true },
{ ignore: [409, 404] }
)
);
await this.execute(() =>
this.esClient.transform.deleteTransform(
{ transform_id: transformId, force: true },
{ ignore: [409, 404] }
)
);
}
private async startTransform(transformId: string) {
this.logger.info(`Starting SLO summary transform: ${transformId} - noop if already running`);
await this.execute(() =>
this.esClient.transform.startTransform({ transform_id: transformId }, { ignore: [409] })
);
}
private async execute<T>(esCall: () => Promise<T>): Promise<T> {
return await retryTransientEsErrors(esCall, { logger: this.logger });
}
}

View file

@ -0,0 +1,30 @@
/*
* 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 { SUMMARY_OCCURRENCES_7D_ROLLING } from './summary_occurrences_7d_rolling';
import { SUMMARY_OCCURRENCES_30D_ROLLING } from './summary_occurrences_30d_rolling';
import { SUMMARY_OCCURRENCES_90D_ROLLING } from './summary_occurrences_90d_rolling';
import { SUMMARY_TIMESLICES_7D_ROLLING } from './summary_timeslices_7d_rolling';
import { SUMMARY_TIMESLICES_30D_ROLLING } from './summary_timeslices_30d_rolling';
import { SUMMARY_TIMESLICES_90D_ROLLING } from './summary_timeslices_90d_rolling';
import { SUMMARY_OCCURRENCES_WEEKLY_ALIGNED } from './summary_occurrences_weekly_aligned';
import { SUMMARY_OCCURRENCES_MONTHLY_ALIGNED } from './summary_occurrences_monthly_aligned';
import { SUMMARY_TIMESLICES_WEEKLY_ALIGNED } from './summary_timeslices_weekly_aligned';
import { SUMMARY_TIMESLICES_MONTHLY_ALIGNED } from './summary_timeslices_monthly_aligned';
export const ALL_TRANSFORM_TEMPLATES = [
SUMMARY_OCCURRENCES_7D_ROLLING,
SUMMARY_OCCURRENCES_30D_ROLLING,
SUMMARY_OCCURRENCES_90D_ROLLING,
SUMMARY_OCCURRENCES_WEEKLY_ALIGNED,
SUMMARY_OCCURRENCES_MONTHLY_ALIGNED,
SUMMARY_TIMESLICES_7D_ROLLING,
SUMMARY_TIMESLICES_30D_ROLLING,
SUMMARY_TIMESLICES_90D_ROLLING,
SUMMARY_TIMESLICES_WEEKLY_ALIGNED,
SUMMARY_TIMESLICES_MONTHLY_ALIGNED,
];

View file

@ -0,0 +1,170 @@
/*
* 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types';
import {
SLO_DESTINATION_INDEX_PATTERN,
SLO_RESOURCES_VERSION,
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
export const SUMMARY_OCCURRENCES_30D_ROLLING: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-30d-rolling`,
dest: {
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
},
source: {
index: SLO_DESTINATION_INDEX_PATTERN,
query: {
bool: {
filter: [
{
range: {
'@timestamp': {
gte: 'now-30d/m',
lte: 'now/m',
},
},
},
{
term: {
'slo.budgetingMethod': 'occurrences',
},
},
{
term: {
'slo.timeWindow.type': 'rolling',
},
},
{
term: {
'slo.timeWindow.duration': '30d',
},
},
],
},
},
},
pivot: {
group_by: {
'slo.id': {
terms: {
field: 'slo.id',
},
},
'slo.revision': {
terms: {
field: 'slo.revision',
},
},
'slo.instanceId': {
terms: {
field: 'slo.instanceId',
},
},
'slo.budgetingMethod': {
terms: {
field: 'slo.budgetingMethod',
},
},
'slo.timeWindow.duration': {
terms: {
field: 'slo.timeWindow.duration',
},
},
'slo.timeWindow.type': {
terms: {
field: 'slo.timeWindow.type',
},
},
},
aggregations: {
goodEvents: {
sum: {
field: 'slo.numerator',
},
},
totalEvents: {
sum: {
field: 'slo.denominator',
},
},
_objectiveTarget: {
max: {
field: 'slo.objective.target',
},
},
sliValue: {
bucket_script: {
buckets_path: {
goodEvents: 'goodEvents',
totalEvents: 'totalEvents',
},
script:
'if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }',
},
},
errorBudgetInitial: {
bucket_script: {
buckets_path: {
objectiveTarget: '_objectiveTarget',
},
script: '1 - params.objectiveTarget',
},
},
errorBudgetConsumed: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
errorBudgetInitial: 'errorBudgetInitial',
},
script:
'if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }',
},
},
errorBudgetRemaining: {
bucket_script: {
buckets_path: {
errorBudgetConsummed: 'errorBudgetConsumed',
},
script: '1 - params.errorBudgetConsummed',
},
},
status: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
objectiveTarget: '_objectiveTarget',
errorBudgetRemaining: 'errorBudgetRemaining',
},
script: {
source:
'if (params.sliValue == -1) { return 0 } else if (params.sliValue >= params.objectiveTarget) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }',
},
},
},
},
},
description:
'Summarize every SLO with occurrences budgeting method and a 30 days rolling time window',
frequency: '1m',
sync: {
time: {
field: '@timestamp',
delay: '60s',
},
},
settings: {
deduce_mappings: false,
},
_meta: {
version: SLO_RESOURCES_VERSION,
managed: true,
managed_by: 'observability',
},
};

View file

@ -0,0 +1,170 @@
/*
* 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types';
import {
SLO_DESTINATION_INDEX_PATTERN,
SLO_RESOURCES_VERSION,
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
export const SUMMARY_OCCURRENCES_7D_ROLLING: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-7d-rolling`,
dest: {
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
},
source: {
index: SLO_DESTINATION_INDEX_PATTERN,
query: {
bool: {
filter: [
{
range: {
'@timestamp': {
gte: 'now-7d/m',
lte: 'now/m',
},
},
},
{
term: {
'slo.budgetingMethod': 'occurrences',
},
},
{
term: {
'slo.timeWindow.type': 'rolling',
},
},
{
term: {
'slo.timeWindow.duration': '7d',
},
},
],
},
},
},
pivot: {
group_by: {
'slo.id': {
terms: {
field: 'slo.id',
},
},
'slo.revision': {
terms: {
field: 'slo.revision',
},
},
'slo.instanceId': {
terms: {
field: 'slo.instanceId',
},
},
'slo.budgetingMethod': {
terms: {
field: 'slo.budgetingMethod',
},
},
'slo.timeWindow.duration': {
terms: {
field: 'slo.timeWindow.duration',
},
},
'slo.timeWindow.type': {
terms: {
field: 'slo.timeWindow.type',
},
},
},
aggregations: {
goodEvents: {
sum: {
field: 'slo.numerator',
},
},
totalEvents: {
sum: {
field: 'slo.denominator',
},
},
_objectiveTarget: {
max: {
field: 'slo.objective.target',
},
},
sliValue: {
bucket_script: {
buckets_path: {
goodEvents: 'goodEvents',
totalEvents: 'totalEvents',
},
script:
'if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }',
},
},
errorBudgetInitial: {
bucket_script: {
buckets_path: {
objectiveTarget: '_objectiveTarget',
},
script: '1 - params.objectiveTarget',
},
},
errorBudgetConsumed: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
errorBudgetInitial: 'errorBudgetInitial',
},
script:
'if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }',
},
},
errorBudgetRemaining: {
bucket_script: {
buckets_path: {
errorBudgetConsummed: 'errorBudgetConsumed',
},
script: '1 - params.errorBudgetConsummed',
},
},
status: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
objectiveTarget: '_objectiveTarget',
errorBudgetRemaining: 'errorBudgetRemaining',
},
script: {
source:
'if (params.sliValue == -1) { return 0 } else if (params.sliValue >= params.objectiveTarget) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }',
},
},
},
},
},
description:
'Summarize every SLO with occurrences budgeting method and a 7 days rolling time window',
frequency: '1m',
sync: {
time: {
field: '@timestamp',
delay: '60s',
},
},
settings: {
deduce_mappings: false,
},
_meta: {
version: SLO_RESOURCES_VERSION,
managed: true,
managed_by: 'observability',
},
};

View file

@ -0,0 +1,170 @@
/*
* 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types';
import {
SLO_DESTINATION_INDEX_PATTERN,
SLO_RESOURCES_VERSION,
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
export const SUMMARY_OCCURRENCES_90D_ROLLING: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-90d-rolling`,
dest: {
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
},
source: {
index: SLO_DESTINATION_INDEX_PATTERN,
query: {
bool: {
filter: [
{
range: {
'@timestamp': {
gte: 'now-90d/m',
lte: 'now/m',
},
},
},
{
term: {
'slo.budgetingMethod': 'occurrences',
},
},
{
term: {
'slo.timeWindow.type': 'rolling',
},
},
{
term: {
'slo.timeWindow.duration': '90d',
},
},
],
},
},
},
pivot: {
group_by: {
'slo.id': {
terms: {
field: 'slo.id',
},
},
'slo.revision': {
terms: {
field: 'slo.revision',
},
},
'slo.instanceId': {
terms: {
field: 'slo.instanceId',
},
},
'slo.budgetingMethod': {
terms: {
field: 'slo.budgetingMethod',
},
},
'slo.timeWindow.duration': {
terms: {
field: 'slo.timeWindow.duration',
},
},
'slo.timeWindow.type': {
terms: {
field: 'slo.timeWindow.type',
},
},
},
aggregations: {
goodEvents: {
sum: {
field: 'slo.numerator',
},
},
totalEvents: {
sum: {
field: 'slo.denominator',
},
},
_objectiveTarget: {
max: {
field: 'slo.objective.target',
},
},
sliValue: {
bucket_script: {
buckets_path: {
goodEvents: 'goodEvents',
totalEvents: 'totalEvents',
},
script:
'if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }',
},
},
errorBudgetInitial: {
bucket_script: {
buckets_path: {
objectiveTarget: '_objectiveTarget',
},
script: '1 - params.objectiveTarget',
},
},
errorBudgetConsumed: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
errorBudgetInitial: 'errorBudgetInitial',
},
script:
'if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }',
},
},
errorBudgetRemaining: {
bucket_script: {
buckets_path: {
errorBudgetConsummed: 'errorBudgetConsumed',
},
script: '1 - params.errorBudgetConsummed',
},
},
status: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
objectiveTarget: '_objectiveTarget',
errorBudgetRemaining: 'errorBudgetRemaining',
},
script: {
source:
'if (params.sliValue == -1) { return 0 } else if (params.sliValue >= params.objectiveTarget) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }',
},
},
},
},
},
description:
'Summarize every SLO with occurrences budgeting method and a 90 days rolling time window',
frequency: '1m',
sync: {
time: {
field: '@timestamp',
delay: '60s',
},
},
settings: {
deduce_mappings: false,
},
_meta: {
version: SLO_RESOURCES_VERSION,
managed: true,
managed_by: 'observability',
},
};

View file

@ -0,0 +1,168 @@
/*
* 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types';
import {
SLO_DESTINATION_INDEX_PATTERN,
SLO_RESOURCES_VERSION,
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
export const SUMMARY_OCCURRENCES_MONTHLY_ALIGNED: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-monthly-aligned`,
dest: {
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
},
source: {
index: SLO_DESTINATION_INDEX_PATTERN,
query: {
bool: {
filter: [
{
range: {
'@timestamp': {
gte: 'now/M',
lte: 'now/m',
},
},
},
{
term: {
'slo.budgetingMethod': 'occurrences',
},
},
{
term: {
'slo.timeWindow.type': 'calendarAligned',
},
},
{
term: {
'slo.timeWindow.duration': '1M',
},
},
],
},
},
},
pivot: {
group_by: {
'slo.id': {
terms: {
field: 'slo.id',
},
},
'slo.revision': {
terms: {
field: 'slo.revision',
},
},
'slo.instanceId': {
terms: {
field: 'slo.instanceId',
},
},
'slo.budgetingMethod': {
terms: {
field: 'slo.budgetingMethod',
},
},
'slo.timeWindow.duration': {
terms: {
field: 'slo.timeWindow.duration',
},
},
'slo.timeWindow.type': {
terms: {
field: 'slo.timeWindow.type',
},
},
},
aggregations: {
_objectiveTarget: {
max: {
field: 'slo.objective.target',
},
},
goodEvents: {
sum: {
field: 'slo.numerator',
},
},
totalEvents: {
sum: {
field: 'slo.denominator',
},
},
sliValue: {
bucket_script: {
buckets_path: {
goodEvents: 'goodEvents',
totalEvents: 'totalEvents',
},
script:
'if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }',
},
},
errorBudgetInitial: {
bucket_script: {
buckets_path: {
objective: '_objectiveTarget',
},
script: '1 - params.objective',
},
},
errorBudgetConsumed: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
errorBudgetInitial: 'errorBudgetInitial',
},
script:
'if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }',
},
},
errorBudgetRemaining: {
bucket_script: {
buckets_path: {
errorBudgetConsumed: 'errorBudgetConsumed',
},
script: '1 - params.errorBudgetConsumed',
},
},
status: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
objective: '_objectiveTarget',
errorBudgetRemaining: 'errorBudgetRemaining',
},
script:
'if (params.sliValue == -1) { return 0 } else if (params.sliValue >= params.objective) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }',
},
},
},
},
description:
'Summarize every SLO with occurrences budgeting method and a monthly calendar aligned time window',
frequency: '1m',
sync: {
time: {
field: '@timestamp',
delay: '60s',
},
},
settings: {
deduce_mappings: false,
},
_meta: {
version: SLO_RESOURCES_VERSION,
managed: true,
managed_by: 'observability',
},
};

View file

@ -0,0 +1,168 @@
/*
* 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types';
import {
SLO_DESTINATION_INDEX_PATTERN,
SLO_RESOURCES_VERSION,
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
export const SUMMARY_OCCURRENCES_WEEKLY_ALIGNED: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-weekly-aligned`,
dest: {
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
},
source: {
index: SLO_DESTINATION_INDEX_PATTERN,
query: {
bool: {
filter: [
{
range: {
'@timestamp': {
gte: 'now/w',
lte: 'now/m',
},
},
},
{
term: {
'slo.budgetingMethod': 'occurrences',
},
},
{
term: {
'slo.timeWindow.type': 'calendarAligned',
},
},
{
term: {
'slo.timeWindow.duration': '1w',
},
},
],
},
},
},
pivot: {
group_by: {
'slo.id': {
terms: {
field: 'slo.id',
},
},
'slo.revision': {
terms: {
field: 'slo.revision',
},
},
'slo.instanceId': {
terms: {
field: 'slo.instanceId',
},
},
'slo.budgetingMethod': {
terms: {
field: 'slo.budgetingMethod',
},
},
'slo.timeWindow.duration': {
terms: {
field: 'slo.timeWindow.duration',
},
},
'slo.timeWindow.type': {
terms: {
field: 'slo.timeWindow.type',
},
},
},
aggregations: {
_objectiveTarget: {
max: {
field: 'slo.objective.target',
},
},
goodEvents: {
sum: {
field: 'slo.numerator',
},
},
totalEvents: {
sum: {
field: 'slo.denominator',
},
},
sliValue: {
bucket_script: {
buckets_path: {
goodEvents: 'goodEvents',
totalEvents: 'totalEvents',
},
script:
'if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }',
},
},
errorBudgetInitial: {
bucket_script: {
buckets_path: {
objective: '_objectiveTarget',
},
script: '1 - params.objective',
},
},
errorBudgetConsumed: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
errorBudgetInitial: 'errorBudgetInitial',
},
script:
'if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }',
},
},
errorBudgetRemaining: {
bucket_script: {
buckets_path: {
errorBudgetConsumed: 'errorBudgetConsumed',
},
script: '1 - params.errorBudgetConsumed',
},
},
status: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
objective: '_objectiveTarget',
errorBudgetRemaining: 'errorBudgetRemaining',
},
script:
'if (params.sliValue == -1) { return 0 } else if (params.sliValue >= params.objective) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }',
},
},
},
},
description:
'Summarize every SLO with occurrences budgeting method and a weekly calendar aligned time window',
frequency: '1m',
sync: {
time: {
field: '@timestamp',
delay: '60s',
},
},
settings: {
deduce_mappings: false,
},
_meta: {
version: SLO_RESOURCES_VERSION,
managed: true,
managed_by: 'observability',
},
};

View file

@ -0,0 +1,170 @@
/*
* 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types';
import {
SLO_DESTINATION_INDEX_PATTERN,
SLO_RESOURCES_VERSION,
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
export const SUMMARY_TIMESLICES_30D_ROLLING: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-30d-rolling`,
dest: {
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
},
source: {
index: SLO_DESTINATION_INDEX_PATTERN,
query: {
bool: {
filter: [
{
range: {
'@timestamp': {
gte: 'now-30d/m',
lte: 'now/m',
},
},
},
{
term: {
'slo.budgetingMethod': 'timeslices',
},
},
{
term: {
'slo.timeWindow.type': 'rolling',
},
},
{
term: {
'slo.timeWindow.duration': '30d',
},
},
],
},
},
},
pivot: {
group_by: {
'slo.id': {
terms: {
field: 'slo.id',
},
},
'slo.revision': {
terms: {
field: 'slo.revision',
},
},
'slo.instanceId': {
terms: {
field: 'slo.instanceId',
},
},
'slo.budgetingMethod': {
terms: {
field: 'slo.budgetingMethod',
},
},
'slo.timeWindow.duration': {
terms: {
field: 'slo.timeWindow.duration',
},
},
'slo.timeWindow.type': {
terms: {
field: 'slo.timeWindow.type',
},
},
},
aggregations: {
goodEvents: {
sum: {
field: 'slo.isGoodSlice',
},
},
totalEvents: {
value_count: {
field: 'slo.isGoodSlice',
},
},
_objectiveTarget: {
max: {
field: 'slo.objective.target',
},
},
sliValue: {
bucket_script: {
buckets_path: {
goodEvents: 'goodEvents',
totalEvents: 'totalEvents',
},
script:
'if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }',
},
},
errorBudgetInitial: {
bucket_script: {
buckets_path: {
objectiveTarget: '_objectiveTarget',
},
script: '1 - params.objectiveTarget',
},
},
errorBudgetConsumed: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
errorBudgetInitial: 'errorBudgetInitial',
},
script:
'if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }',
},
},
errorBudgetRemaining: {
bucket_script: {
buckets_path: {
errorBudgetConsummed: 'errorBudgetConsumed',
},
script: '1 - params.errorBudgetConsummed',
},
},
status: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
objectiveTarget: '_objectiveTarget',
errorBudgetRemaining: 'errorBudgetRemaining',
},
script: {
source:
'if (params.sliValue == -1) { return 0 } else if (params.sliValue >= params.objectiveTarget) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }',
},
},
},
},
},
description:
'Summarize every SLO with timeslices budgeting method and a 30 days rolling time window',
frequency: '1m',
sync: {
time: {
field: '@timestamp',
delay: '60s',
},
},
settings: {
deduce_mappings: false,
},
_meta: {
version: SLO_RESOURCES_VERSION,
managed: true,
managed_by: 'observability',
},
};

View file

@ -0,0 +1,170 @@
/*
* 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types';
import {
SLO_DESTINATION_INDEX_PATTERN,
SLO_RESOURCES_VERSION,
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
export const SUMMARY_TIMESLICES_7D_ROLLING: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-7d-rolling`,
dest: {
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
},
source: {
index: SLO_DESTINATION_INDEX_PATTERN,
query: {
bool: {
filter: [
{
range: {
'@timestamp': {
gte: 'now-7d/m',
lte: 'now/m',
},
},
},
{
term: {
'slo.budgetingMethod': 'timeslices',
},
},
{
term: {
'slo.timeWindow.type': 'rolling',
},
},
{
term: {
'slo.timeWindow.duration': '7d',
},
},
],
},
},
},
pivot: {
group_by: {
'slo.id': {
terms: {
field: 'slo.id',
},
},
'slo.revision': {
terms: {
field: 'slo.revision',
},
},
'slo.instanceId': {
terms: {
field: 'slo.instanceId',
},
},
'slo.budgetingMethod': {
terms: {
field: 'slo.budgetingMethod',
},
},
'slo.timeWindow.duration': {
terms: {
field: 'slo.timeWindow.duration',
},
},
'slo.timeWindow.type': {
terms: {
field: 'slo.timeWindow.type',
},
},
},
aggregations: {
goodEvents: {
sum: {
field: 'slo.isGoodSlice',
},
},
totalEvents: {
value_count: {
field: 'slo.isGoodSlice',
},
},
_objectiveTarget: {
max: {
field: 'slo.objective.target',
},
},
sliValue: {
bucket_script: {
buckets_path: {
goodEvents: 'goodEvents',
totalEvents: 'totalEvents',
},
script:
'if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }',
},
},
errorBudgetInitial: {
bucket_script: {
buckets_path: {
objectiveTarget: '_objectiveTarget',
},
script: '1 - params.objectiveTarget',
},
},
errorBudgetConsumed: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
errorBudgetInitial: 'errorBudgetInitial',
},
script:
'if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }',
},
},
errorBudgetRemaining: {
bucket_script: {
buckets_path: {
errorBudgetConsummed: 'errorBudgetConsumed',
},
script: '1 - params.errorBudgetConsummed',
},
},
status: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
objectiveTarget: '_objectiveTarget',
errorBudgetRemaining: 'errorBudgetRemaining',
},
script: {
source:
'if (params.sliValue == -1) { return 0 } else if (params.sliValue >= params.objectiveTarget) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }',
},
},
},
},
},
description:
'Summarize every SLO with timeslices budgeting method and a 7 days rolling time window',
frequency: '1m',
sync: {
time: {
field: '@timestamp',
delay: '60s',
},
},
settings: {
deduce_mappings: false,
},
_meta: {
version: SLO_RESOURCES_VERSION,
managed: true,
managed_by: 'observability',
},
};

View file

@ -0,0 +1,170 @@
/*
* 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types';
import {
SLO_DESTINATION_INDEX_PATTERN,
SLO_RESOURCES_VERSION,
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
export const SUMMARY_TIMESLICES_90D_ROLLING: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-90d-rolling`,
dest: {
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
},
source: {
index: SLO_DESTINATION_INDEX_PATTERN,
query: {
bool: {
filter: [
{
range: {
'@timestamp': {
gte: 'now-90d/m',
lte: 'now/m',
},
},
},
{
term: {
'slo.budgetingMethod': 'timeslices',
},
},
{
term: {
'slo.timeWindow.type': 'rolling',
},
},
{
term: {
'slo.timeWindow.duration': '90d',
},
},
],
},
},
},
pivot: {
group_by: {
'slo.id': {
terms: {
field: 'slo.id',
},
},
'slo.revision': {
terms: {
field: 'slo.revision',
},
},
'slo.instanceId': {
terms: {
field: 'slo.instanceId',
},
},
'slo.budgetingMethod': {
terms: {
field: 'slo.budgetingMethod',
},
},
'slo.timeWindow.duration': {
terms: {
field: 'slo.timeWindow.duration',
},
},
'slo.timeWindow.type': {
terms: {
field: 'slo.timeWindow.type',
},
},
},
aggregations: {
goodEvents: {
sum: {
field: 'slo.isGoodSlice',
},
},
totalEvents: {
value_count: {
field: 'slo.isGoodSlice',
},
},
_objectiveTarget: {
max: {
field: 'slo.objective.target',
},
},
sliValue: {
bucket_script: {
buckets_path: {
goodEvents: 'goodEvents',
totalEvents: 'totalEvents',
},
script:
'if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }',
},
},
errorBudgetInitial: {
bucket_script: {
buckets_path: {
objectiveTarget: '_objectiveTarget',
},
script: '1 - params.objectiveTarget',
},
},
errorBudgetConsumed: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
errorBudgetInitial: 'errorBudgetInitial',
},
script:
'if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }',
},
},
errorBudgetRemaining: {
bucket_script: {
buckets_path: {
errorBudgetConsummed: 'errorBudgetConsumed',
},
script: '1 - params.errorBudgetConsummed',
},
},
status: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
objectiveTarget: '_objectiveTarget',
errorBudgetRemaining: 'errorBudgetRemaining',
},
script: {
source:
'if (params.sliValue == -1) { return 0 } else if (params.sliValue >= params.objectiveTarget) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }',
},
},
},
},
},
description:
'Summarize every SLO with timeslices budgeting method and a 90 days rolling time window',
frequency: '1m',
sync: {
time: {
field: '@timestamp',
delay: '60s',
},
},
settings: {
deduce_mappings: false,
},
_meta: {
version: SLO_RESOURCES_VERSION,
managed: true,
managed_by: 'observability',
},
};

View file

@ -0,0 +1,198 @@
/*
* 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types';
import {
SLO_DESTINATION_INDEX_PATTERN,
SLO_RESOURCES_VERSION,
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
export const SUMMARY_TIMESLICES_MONTHLY_ALIGNED: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-monthly-aligned`,
dest: {
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
},
source: {
index: SLO_DESTINATION_INDEX_PATTERN,
query: {
bool: {
filter: [
{
range: {
'@timestamp': {
gte: 'now/M',
lte: 'now/m',
},
},
},
{
term: {
'slo.budgetingMethod': 'timeslices',
},
},
{
term: {
'slo.timeWindow.type': 'calendarAligned',
},
},
{
term: {
'slo.timeWindow.duration': '1M',
},
},
],
},
},
},
pivot: {
group_by: {
'slo.id': {
terms: {
field: 'slo.id',
},
},
'slo.revision': {
terms: {
field: 'slo.revision',
},
},
'slo.instanceId': {
terms: {
field: 'slo.instanceId',
},
},
'slo.budgetingMethod': {
terms: {
field: 'slo.budgetingMethod',
},
},
'slo.timeWindow.duration': {
terms: {
field: 'slo.timeWindow.duration',
},
},
'slo.timeWindow.type': {
terms: {
field: 'slo.timeWindow.type',
},
},
},
aggregations: {
_sliceDurationInSeconds: {
max: {
field: 'slo.objective.sliceDurationInSeconds',
},
},
_totalSlicesInPeriod: {
bucket_script: {
buckets_path: {
sliceDurationInSeconds: '_sliceDurationInSeconds',
},
script: {
source: `
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 = params.sliceDurationInSeconds / 60;
return Math.ceil(Duration.between(startOfMonth, startOfNextMonth).toMinutes() / sliceDurationInMinutes);
`,
},
},
},
_objectiveTarget: {
max: {
field: 'slo.objective.target',
},
},
goodEvents: {
sum: {
field: 'slo.isGoodSlice',
},
},
totalEvents: {
value_count: {
field: 'slo.isGoodSlice',
},
},
sliValue: {
bucket_script: {
buckets_path: {
goodEvents: 'goodEvents',
totalEvents: 'totalEvents',
},
script:
'if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }',
},
},
errorBudgetInitial: {
bucket_script: {
buckets_path: {
objective: '_objectiveTarget',
},
script: '1 - params.objective',
},
},
errorBudgetConsumed: {
bucket_script: {
buckets_path: {
goodEvents: 'goodEvents',
totalEvents: 'totalEvents',
totalSlicesInPeriod: '_totalSlicesInPeriod',
errorBudgetInitial: 'errorBudgetInitial',
},
script:
'if (params.totalEvents == 0) { return 0 } else { return (params.totalEvents - params.goodEvents) / (params.totalSlicesInPeriod * params.errorBudgetInitial) }',
},
},
errorBudgetRemaining: {
bucket_script: {
buckets_path: {
errorBudgetConsumed: 'errorBudgetConsumed',
},
script: '1 - params.errorBudgetConsumed',
},
},
status: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
objective: '_objectiveTarget',
errorBudgetRemaining: 'errorBudgetRemaining',
},
script:
'if (params.sliValue == -1) { return 0 } else if (params.sliValue >= params.objective) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }',
},
},
},
},
description:
'Summarize every SLO with timeslices budgeting method and a monthly calendar aligned time window',
frequency: '1m',
sync: {
time: {
field: '@timestamp',
delay: '60s',
},
},
settings: {
deduce_mappings: false,
},
_meta: {
version: SLO_RESOURCES_VERSION,
managed: true,
managed_by: 'observability',
},
};

View file

@ -0,0 +1,183 @@
/*
* 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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types';
import {
SLO_DESTINATION_INDEX_PATTERN,
SLO_RESOURCES_VERSION,
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
export const SUMMARY_TIMESLICES_WEEKLY_ALIGNED: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-weekly-aligned`,
dest: {
index: SLO_SUMMARY_DESTINATION_INDEX_NAME,
},
source: {
index: SLO_DESTINATION_INDEX_PATTERN,
query: {
bool: {
filter: [
{
range: {
'@timestamp': {
gte: 'now/w',
lte: 'now/m',
},
},
},
{
term: {
'slo.budgetingMethod': 'timeslices',
},
},
{
term: {
'slo.timeWindow.type': 'calendarAligned',
},
},
{
term: {
'slo.timeWindow.duration': '1w',
},
},
],
},
},
},
pivot: {
group_by: {
'slo.id': {
terms: {
field: 'slo.id',
},
},
'slo.revision': {
terms: {
field: 'slo.revision',
},
},
'slo.instanceId': {
terms: {
field: 'slo.instanceId',
},
},
'slo.budgetingMethod': {
terms: {
field: 'slo.budgetingMethod',
},
},
'slo.timeWindow.duration': {
terms: {
field: 'slo.timeWindow.duration',
},
},
'slo.timeWindow.type': {
terms: {
field: 'slo.timeWindow.type',
},
},
},
aggregations: {
_sliceDurationInSeconds: {
max: {
field: 'slo.objective.sliceDurationInSeconds',
},
},
_totalSlicesInPeriod: {
bucket_script: {
buckets_path: {
sliceDurationInSeconds: '_sliceDurationInSeconds',
},
script: 'Math.ceil(7 * 24 * 60 * 60 / params.sliceDurationInSeconds)',
},
},
_objectiveTarget: {
max: {
field: 'slo.objective.target',
},
},
goodEvents: {
sum: {
field: 'slo.isGoodSlice',
},
},
totalEvents: {
value_count: {
field: 'slo.isGoodSlice',
},
},
sliValue: {
bucket_script: {
buckets_path: {
goodEvents: 'goodEvents',
totalEvents: 'totalEvents',
},
script:
'if (params.totalEvents == 0) { return -1 } else { return params.goodEvents / params.totalEvents }',
},
},
errorBudgetInitial: {
bucket_script: {
buckets_path: {
objective: '_objectiveTarget',
},
script: '1 - params.objective',
},
},
errorBudgetConsumed: {
bucket_script: {
buckets_path: {
goodEvents: 'goodEvents',
totalEvents: 'totalEvents',
totalSlicesInPeriod: '_totalSlicesInPeriod',
errorBudgetInitial: 'errorBudgetInitial',
},
script:
'if (params.totalEvents == 0) { return 0 } else { return (params.totalEvents - params.goodEvents) / (params.totalSlicesInPeriod * params.errorBudgetInitial) }',
},
},
errorBudgetRemaining: {
bucket_script: {
buckets_path: {
errorBudgetConsumed: 'errorBudgetConsumed',
},
script: '1 - params.errorBudgetConsumed',
},
},
status: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
objective: '_objectiveTarget',
errorBudgetRemaining: 'errorBudgetRemaining',
},
script:
'if (params.sliValue == -1) { return 0 } else if (params.sliValue >= params.objective) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }',
},
},
},
},
description:
'Summarize every SLO with timeslices budgeting method and a weekly calendar aligned time window',
frequency: '1m',
sync: {
time: {
field: '@timestamp',
delay: '60s',
},
},
settings: {
deduce_mappings: false,
},
_meta: {
version: SLO_RESOURCES_VERSION,
managed: true,
managed_by: 'observability',
},
};

View file

@ -64,11 +64,11 @@ Object {
"_meta": Object {
"managed": true,
"managed_by": "observability",
"version": 1,
"version": 2,
},
"description": "Rolled-up SLI data for SLO: irrelevant",
"dest": Object {
"index": ".slo-observability.sli-v1",
"index": ".slo-observability.sli-v2",
"pipeline": ".slo-observability.sli.monthly",
},
"frequency": "1m",
@ -138,16 +138,46 @@ Object {
"fixed_interval": "2m",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.objective.sliceDurationInSeconds": Object {
"terms": Object {
"field": "slo.objective.sliceDurationInSeconds",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
},
},
"slo.revision": Object {
"terms": Object {
"field": "slo.revision",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
},
},
"slo.timeWindow.type": Object {
"terms": Object {
"field": "slo.timeWindow.type",
},
},
},
},
"settings": Object {
@ -168,18 +198,54 @@ Object {
},
},
"runtime_mappings": Object {
"slo.budgetingMethod": Object {
"script": Object {
"source": "emit('timeslices')",
},
"type": "keyword",
},
"slo.id": Object {
"script": Object {
"source": Any<String>,
},
"type": "keyword",
},
"slo.instanceId": Object {
"script": Object {
"source": "emit('*')",
},
"type": "keyword",
},
"slo.objective.sliceDurationInSeconds": Object {
"script": Object {
"source": "emit(120)",
},
"type": "long",
},
"slo.objective.target": Object {
"script": Object {
"source": "emit(0.98)",
},
"type": "double",
},
"slo.revision": Object {
"script": Object {
"source": "emit(1)",
},
"type": "long",
},
"slo.timeWindow.duration": Object {
"script": Object {
"source": "emit('7d')",
},
"type": "keyword",
},
"slo.timeWindow.type": Object {
"script": Object {
"source": "emit('rolling')",
},
"type": "keyword",
},
},
},
"sync": Object {
@ -197,11 +263,11 @@ Object {
"_meta": Object {
"managed": true,
"managed_by": "observability",
"version": 1,
"version": 2,
},
"description": "Rolled-up SLI data for SLO: irrelevant",
"dest": Object {
"index": ".slo-observability.sli-v1",
"index": ".slo-observability.sli-v2",
"pipeline": ".slo-observability.sli.monthly",
},
"frequency": "1m",
@ -262,16 +328,41 @@ Object {
"fixed_interval": "1m",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
},
},
"slo.revision": Object {
"terms": Object {
"field": "slo.revision",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
},
},
"slo.timeWindow.type": Object {
"terms": Object {
"field": "slo.timeWindow.type",
},
},
},
},
"settings": Object {
@ -292,18 +383,48 @@ Object {
},
},
"runtime_mappings": Object {
"slo.budgetingMethod": Object {
"script": Object {
"source": "emit('occurrences')",
},
"type": "keyword",
},
"slo.id": Object {
"script": Object {
"source": Any<String>,
},
"type": "keyword",
},
"slo.instanceId": Object {
"script": Object {
"source": "emit('*')",
},
"type": "keyword",
},
"slo.objective.target": Object {
"script": Object {
"source": "emit(0.999)",
},
"type": "double",
},
"slo.revision": Object {
"script": Object {
"source": "emit(1)",
},
"type": "long",
},
"slo.timeWindow.duration": Object {
"script": Object {
"source": "emit('7d')",
},
"type": "keyword",
},
"slo.timeWindow.type": Object {
"script": Object {
"source": "emit('rolling')",
},
"type": "keyword",
},
},
},
"sync": Object {

View file

@ -8,7 +8,6 @@
import { MappingRuntimeFieldType } from '@elastic/elasticsearch/lib/api/types';
import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { ALL_VALUE, timeslicesBudgetingMethodSchema } from '@kbn/slo-schema';
import { TransformSettings } from '../../../assets/transform_templates/slo_transform_template';
import { SLO } from '../../../domain/models';