feat(slo): Add more fields into the rollup and summary data (#162075)

This commit is contained in:
Kevin Delemme 2023-07-18 16:02:50 -04:00
parent d9f098f210
commit b760b2cbed
34 changed files with 3133 additions and 410 deletions

View file

@ -16,6 +16,31 @@ export const getSLOMappingsTemplate = (name: string) => ({
type: 'date',
format: 'date_optional_time||epoch_millis',
},
// APM service and transaction specific fields
service: {
properties: {
name: {
type: 'keyword',
ignore_above: 256,
},
environment: {
type: 'keyword',
ignore_above: 256,
},
},
},
transaction: {
properties: {
name: {
type: 'keyword',
ignore_above: 256,
},
type: {
type: 'keyword',
ignore_above: 256,
},
},
},
slo: {
properties: {
id: {
@ -29,6 +54,26 @@ export const getSLOMappingsTemplate = (name: string) => ({
type: 'keyword',
ignore_above: 256,
},
name: {
type: 'keyword',
ignore_above: 256,
},
description: {
type: 'keyword',
ignore_above: 256,
},
tags: {
type: 'keyword',
ignore_above: 256,
},
indicator: {
properties: {
type: {
type: 'keyword',
ignore_above: 256,
},
},
},
objective: {
properties: {
target: {

View file

@ -12,6 +12,31 @@ export const getSLOSummaryMappingsTemplate = (name: string) => ({
template: {
mappings: {
properties: {
// APM service and transaction specific fields
service: {
properties: {
name: {
type: 'keyword',
ignore_above: 256,
},
environment: {
type: 'keyword',
ignore_above: 256,
},
},
},
transaction: {
properties: {
name: {
type: 'keyword',
ignore_above: 256,
},
type: {
type: 'keyword',
ignore_above: 256,
},
},
},
slo: {
properties: {
id: {
@ -25,6 +50,26 @@ export const getSLOSummaryMappingsTemplate = (name: string) => ({
type: 'keyword',
ignore_above: 256,
},
name: {
type: 'keyword',
ignore_above: 256,
},
description: {
type: 'keyword',
ignore_above: 256,
},
tags: {
type: 'keyword',
ignore_above: 256,
},
indicator: {
properties: {
type: {
type: 'keyword',
ignore_above: 256,
},
},
},
budgetingMethod: {
type: 'keyword',
},

View file

@ -315,8 +315,9 @@ const getSloDiagnosisRoute = createObservabilityServerRoute({
handler: async ({ context, params }) => {
const esClient = (await context.core).elasticsearch.client.asCurrentUser;
const soClient = (await context.core).savedObjects.client;
const repository = new KibanaSavedObjectsSLORepository(soClient);
return getSloDiagnosis(params.path.id, { esClient, soClient });
return getSloDiagnosis(params.path.id, { esClient, repository });
},
});

View file

@ -9,7 +9,11 @@ import { rulesClientMock } from '@kbn/alerting-plugin/server/rules_client.mock';
import { RulesClientApi } from '@kbn/alerting-plugin/server/types';
import { ElasticsearchClient } from '@kbn/core/server';
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
import { getSLOTransformId, SLO_DESTINATION_INDEX_PATTERN } from '../../assets/constants';
import {
getSLOTransformId,
SLO_DESTINATION_INDEX_PATTERN,
SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
} from '../../assets/constants';
import { DeleteSLO } from './delete_slo';
import { createAPMTransactionErrorRateIndicator, createSLO } from './fixtures/slo';
import { createSLORepositoryMock, createTransformManagerMock } from './mocks';
@ -45,7 +49,9 @@ describe('DeleteSLO', () => {
expect(mockTransformManager.uninstall).toHaveBeenCalledWith(
getSLOTransformId(slo.id, slo.revision)
);
expect(mockEsClient.deleteByQuery).toHaveBeenCalledWith(
expect(mockEsClient.deleteByQuery).toHaveBeenCalledTimes(2);
expect(mockEsClient.deleteByQuery).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
index: SLO_DESTINATION_INDEX_PATTERN,
query: {
@ -55,6 +61,17 @@ describe('DeleteSLO', () => {
},
})
);
expect(mockEsClient.deleteByQuery).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
query: {
match: {
'slo.id': slo.id,
},
},
})
);
expect(mockRulesClient.bulkDeleteRules).toHaveBeenCalledWith({
filter: `alert.attributes.params.sloId:${slo.id}`,
});

View file

@ -7,7 +7,11 @@
import { RulesClientApi } from '@kbn/alerting-plugin/server/types';
import { ElasticsearchClient } from '@kbn/core/server';
import { getSLOTransformId, SLO_DESTINATION_INDEX_PATTERN } from '../../assets/constants';
import {
getSLOTransformId,
SLO_DESTINATION_INDEX_PATTERN,
SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
} from '../../assets/constants';
import { SLORepository } from './slo_repository';
import { TransformManager } from './transform_manager';
@ -27,6 +31,7 @@ export class DeleteSLO {
await this.transformManager.uninstall(sloTransformId);
await this.deleteRollupData(slo.id);
await this.deleteSummaryData(slo.id);
await this.deleteAssociatedRules(slo.id);
await this.repository.deleteById(slo.id);
}
@ -43,6 +48,17 @@ export class DeleteSLO {
});
}
private async deleteSummaryData(sloId: string): Promise<void> {
await this.esClient.deleteByQuery({
index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
wait_for_completion: false,
query: {
match: {
'slo.id': sloId,
},
},
});
}
private async deleteAssociatedRules(sloId: string): Promise<void> {
try {
await this.rulesClient.bulkDeleteRules({

View file

@ -6,18 +6,20 @@
*/
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
import { LicensingApiRequestHandlerContext } from '@kbn/licensing-plugin/server';
import {
getSLOTransformId,
SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME,
SLO_COMPONENT_TEMPLATE_SETTINGS_NAME,
SLO_INDEX_TEMPLATE_NAME,
SLO_INGEST_PIPELINE_NAME,
SLO_SUMMARY_COMPONENT_TEMPLATE_MAPPINGS_NAME,
SLO_SUMMARY_COMPONENT_TEMPLATE_SETTINGS_NAME,
SLO_SUMMARY_INDEX_TEMPLATE_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../assets/constants';
import { StoredSLO } from '../../domain/models';
import { SO_SLO_TYPE } from '../../saved_objects';
import { SLO } from '../../domain/models';
import { SLORepository } from './slo_repository';
const OK = 'OK';
const NOT_OK = 'NOT_OK';
@ -30,11 +32,19 @@ export async function getGlobalDiagnosis(
const licenseInfo = licensing.license.toJSON();
const userPrivileges = await esClient.security.getUserPrivileges();
const sloResources = await getSloResourcesDiagnosis(esClient);
const sloSummaryResources = await getSloSummaryResourcesDiagnosis(esClient);
const sloSummaryTransformsStats = await esClient.transform.getTransformStats({
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}*`,
allow_no_match: true,
});
return {
licenseAndFeatures: licenseInfo,
userPrivileges,
sloResources,
sloSummaryResources,
sloSummaryTransformsStats,
};
} catch (error) {
throw error;
@ -43,42 +53,36 @@ export async function getGlobalDiagnosis(
export async function getSloDiagnosis(
sloId: string,
services: { esClient: ElasticsearchClient; soClient: SavedObjectsClientContract }
services: { esClient: ElasticsearchClient; repository: SLORepository }
) {
const { esClient, soClient } = services;
const { esClient, repository } = services;
const sloResources = await getSloResourcesDiagnosis(esClient);
const sloSummaryResources = await getSloSummaryResourcesDiagnosis(esClient);
let sloSavedObject;
let slo: SLO | undefined;
try {
sloSavedObject = await soClient.get<StoredSLO>(SO_SLO_TYPE, sloId);
slo = await repository.findById(sloId);
} catch (err) {
// noop
}
const sloTransformStats = await esClient.transform.getTransformStats({
transform_id: getSLOTransformId(sloId, sloSavedObject?.attributes.revision ?? 1),
transform_id: getSLOTransformId(sloId, slo?.revision ?? 1),
allow_no_match: true,
});
let dataSample;
if (sloSavedObject?.attributes.indicator.params.index) {
const slo = sloSavedObject.attributes;
const sortField =
'timestampField' in slo.indicator.params
? slo.indicator.params.timestampField ?? '@timestamp'
: '@timestamp';
dataSample = await esClient.search({
index: slo.indicator.params.index,
sort: { [sortField]: 'desc' },
size: 5,
});
}
const sloSummaryTransformsStats = await esClient.transform.getTransformStats({
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}*`,
allow_no_match: true,
});
return {
sloResources,
sloSavedObject: sloSavedObject ?? NOT_OK,
sloSummaryResources,
slo: slo ?? NOT_OK,
sloTransformStats,
dataSample: dataSample ?? NOT_OK,
sloSummaryTransformsStats,
};
}
@ -116,3 +120,29 @@ async function getSloResourcesDiagnosis(esClient: ElasticsearchClient) {
}
}
}
async function getSloSummaryResourcesDiagnosis(esClient: ElasticsearchClient) {
try {
const indexTemplateExists = await esClient.indices.existsIndexTemplate({
name: SLO_SUMMARY_INDEX_TEMPLATE_NAME,
});
const mappingsTemplateExists = await esClient.cluster.existsComponentTemplate({
name: SLO_SUMMARY_COMPONENT_TEMPLATE_MAPPINGS_NAME,
});
const settingsTemplateExists = await esClient.cluster.existsComponentTemplate({
name: SLO_SUMMARY_COMPONENT_TEMPLATE_SETTINGS_NAME,
});
return {
[SLO_SUMMARY_INDEX_TEMPLATE_NAME]: indexTemplateExists ? OK : NOT_OK,
[SLO_SUMMARY_COMPONENT_TEMPLATE_MAPPINGS_NAME]: mappingsTemplateExists ? OK : NOT_OK,
[SLO_SUMMARY_COMPONENT_TEMPLATE_SETTINGS_NAME]: settingsTemplateExists ? OK : NOT_OK,
};
} catch (err) {
if (err.meta.statusCode === 403) {
throw new Error('Insufficient permissions to access Elasticsearch Cluster', { cause: err });
}
}
}

View file

@ -0,0 +1,103 @@
/*
* 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 {
ElasticsearchClientMock,
elasticsearchServiceMock,
loggingSystemMock,
} from '@kbn/core/server/mocks';
import { MockedLogger } from '@kbn/logging-mocks';
import { DefaultSummaryTransformInstaller } from './summary_transform_installer';
import { ALL_TRANSFORM_TEMPLATES } from './templates';
describe('Summary Transform Installer', () => {
let esClientMock: ElasticsearchClientMock;
let loggerMock: jest.Mocked<MockedLogger>;
beforeEach(() => {
esClientMock = elasticsearchServiceMock.createElasticsearchClient();
loggerMock = loggingSystemMock.createLogger();
});
it('skips the installation when latest version already installed', async () => {
esClientMock.transform.getTransform.mockResolvedValue({
count: ALL_TRANSFORM_TEMPLATES.length,
// @ts-ignore
transforms: ALL_TRANSFORM_TEMPLATES.map((transform) => ({
id: transform.transform_id,
_meta: transform._meta,
})),
});
const installer = new DefaultSummaryTransformInstaller(esClientMock, loggerMock);
await installer.installAndStart();
expect(esClientMock.transform.stopTransform).not.toHaveBeenCalled();
expect(esClientMock.transform.deleteTransform).not.toHaveBeenCalled();
expect(esClientMock.transform.putTransform).not.toHaveBeenCalled();
expect(esClientMock.transform.startTransform).not.toHaveBeenCalled();
});
it('installs every summary transforms when none are already installed', async () => {
esClientMock.transform.getTransform.mockResolvedValue({ count: 0, transforms: [] });
const installer = new DefaultSummaryTransformInstaller(esClientMock, loggerMock);
await installer.installAndStart();
const nbOfTransforms = ALL_TRANSFORM_TEMPLATES.length;
expect(esClientMock.transform.stopTransform).not.toHaveBeenCalled();
expect(esClientMock.transform.deleteTransform).not.toHaveBeenCalled();
expect(esClientMock.transform.putTransform).toHaveBeenCalledTimes(nbOfTransforms);
expect(esClientMock.transform.startTransform).toHaveBeenCalledTimes(nbOfTransforms);
});
it('desinstalls previous summary transforms prior to installing the new ones', async () => {
esClientMock.transform.getTransform.mockResolvedValue({
count: ALL_TRANSFORM_TEMPLATES.length,
// @ts-ignore
transforms: ALL_TRANSFORM_TEMPLATES.map((transform) => ({
id: transform.transform_id,
_meta: { ...transform._meta, version: -1 },
})),
});
const installer = new DefaultSummaryTransformInstaller(esClientMock, loggerMock);
await installer.installAndStart();
const nbOfTransforms = ALL_TRANSFORM_TEMPLATES.length;
expect(esClientMock.transform.stopTransform).toHaveBeenCalledTimes(nbOfTransforms);
expect(esClientMock.transform.deleteTransform).toHaveBeenCalledTimes(nbOfTransforms);
expect(esClientMock.transform.putTransform).toHaveBeenCalledTimes(nbOfTransforms);
expect(esClientMock.transform.startTransform).toHaveBeenCalledTimes(nbOfTransforms);
});
it('installs only the missing summary transforms', async () => {
const occurrencesSummaryTransforms = ALL_TRANSFORM_TEMPLATES.filter((transform) =>
transform.transform_id.includes('-occurrences-')
);
esClientMock.transform.getTransform.mockResolvedValue({
count: occurrencesSummaryTransforms.length,
// @ts-ignore
transforms: occurrencesSummaryTransforms.map((transform) => ({
id: transform.transform_id,
_meta: transform._meta,
})),
});
const installer = new DefaultSummaryTransformInstaller(esClientMock, loggerMock);
await installer.installAndStart();
const nbOfTransforms = ALL_TRANSFORM_TEMPLATES.length - occurrencesSummaryTransforms.length;
expect(esClientMock.transform.stopTransform).not.toHaveBeenCalled();
expect(esClientMock.transform.deleteTransform).not.toHaveBeenCalled();
expect(esClientMock.transform.putTransform).toHaveBeenCalledTimes(nbOfTransforms);
expect(esClientMock.transform.startTransform).toHaveBeenCalledTimes(nbOfTransforms);
expect(esClientMock.transform.putTransform.mock.calls).toMatchSnapshot();
});
});

View file

@ -0,0 +1,84 @@
/*
* 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.
*/
export const groupBy = {
'slo.id': {
terms: {
field: 'slo.id',
},
},
'slo.revision': {
terms: {
field: 'slo.revision',
},
},
'slo.instanceId': {
terms: {
field: 'slo.instanceId',
},
},
'slo.name': {
terms: {
field: 'slo.name',
},
},
'slo.description': {
terms: {
field: 'slo.description',
},
},
'slo.tags': {
terms: {
field: 'slo.tags',
},
},
'slo.indicator.type': {
terms: {
field: 'slo.indicator.type',
},
},
'slo.budgetingMethod': {
terms: {
field: 'slo.budgetingMethod',
},
},
'slo.timeWindow.duration': {
terms: {
field: 'slo.timeWindow.duration',
},
},
'slo.timeWindow.type': {
terms: {
field: 'slo.timeWindow.type',
},
},
// optional fields: only specified for APM indicators. Must include missing_bucket:true
'service.name': {
terms: {
field: 'service.name',
missing_bucket: true,
},
},
'service.environment': {
terms: {
field: 'service.environment',
missing_bucket: true,
},
},
'transaction.name': {
terms: {
field: 'transaction.name',
missing_bucket: true,
},
},
'transaction.type': {
terms: {
field: 'transaction.type',
missing_bucket: true,
},
},
};

View file

@ -12,6 +12,7 @@ import {
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
import { groupBy } from './common';
export const SUMMARY_OCCURRENCES_30D_ROLLING: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-30d-rolling`,
@ -51,38 +52,7 @@ export const SUMMARY_OCCURRENCES_30D_ROLLING: TransformPutTransformRequest = {
},
},
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',
},
},
},
group_by: groupBy,
aggregations: {
goodEvents: {
sum: {

View file

@ -12,6 +12,7 @@ import {
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
import { groupBy } from './common';
export const SUMMARY_OCCURRENCES_7D_ROLLING: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-7d-rolling`,
@ -51,38 +52,7 @@ export const SUMMARY_OCCURRENCES_7D_ROLLING: TransformPutTransformRequest = {
},
},
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',
},
},
},
group_by: groupBy,
aggregations: {
goodEvents: {
sum: {

View file

@ -12,6 +12,7 @@ import {
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
import { groupBy } from './common';
export const SUMMARY_OCCURRENCES_90D_ROLLING: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-90d-rolling`,
@ -51,38 +52,7 @@ export const SUMMARY_OCCURRENCES_90D_ROLLING: TransformPutTransformRequest = {
},
},
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',
},
},
},
group_by: groupBy,
aggregations: {
goodEvents: {
sum: {

View file

@ -12,6 +12,7 @@ import {
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
import { groupBy } from './common';
export const SUMMARY_OCCURRENCES_MONTHLY_ALIGNED: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-monthly-aligned`,
@ -51,38 +52,7 @@ export const SUMMARY_OCCURRENCES_MONTHLY_ALIGNED: TransformPutTransformRequest =
},
},
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',
},
},
},
group_by: groupBy,
aggregations: {
_objectiveTarget: {
max: {

View file

@ -12,6 +12,7 @@ import {
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
import { groupBy } from './common';
export const SUMMARY_OCCURRENCES_WEEKLY_ALIGNED: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}occurrences-weekly-aligned`,
@ -51,38 +52,7 @@ export const SUMMARY_OCCURRENCES_WEEKLY_ALIGNED: TransformPutTransformRequest =
},
},
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',
},
},
},
group_by: groupBy,
aggregations: {
_objectiveTarget: {
max: {

View file

@ -12,6 +12,7 @@ import {
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
import { groupBy } from './common';
export const SUMMARY_TIMESLICES_30D_ROLLING: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-30d-rolling`,
@ -51,38 +52,7 @@ export const SUMMARY_TIMESLICES_30D_ROLLING: TransformPutTransformRequest = {
},
},
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',
},
},
},
group_by: groupBy,
aggregations: {
goodEvents: {
sum: {

View file

@ -12,6 +12,7 @@ import {
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
import { groupBy } from './common';
export const SUMMARY_TIMESLICES_7D_ROLLING: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-7d-rolling`,
@ -51,38 +52,7 @@ export const SUMMARY_TIMESLICES_7D_ROLLING: TransformPutTransformRequest = {
},
},
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',
},
},
},
group_by: groupBy,
aggregations: {
goodEvents: {
sum: {

View file

@ -12,6 +12,7 @@ import {
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
import { groupBy } from './common';
export const SUMMARY_TIMESLICES_90D_ROLLING: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-90d-rolling`,
@ -51,38 +52,7 @@ export const SUMMARY_TIMESLICES_90D_ROLLING: TransformPutTransformRequest = {
},
},
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',
},
},
},
group_by: groupBy,
aggregations: {
goodEvents: {
sum: {

View file

@ -12,6 +12,7 @@ import {
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
import { groupBy } from './common';
export const SUMMARY_TIMESLICES_MONTHLY_ALIGNED: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-monthly-aligned`,
@ -51,38 +52,7 @@ export const SUMMARY_TIMESLICES_MONTHLY_ALIGNED: TransformPutTransformRequest =
},
},
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',
},
},
},
group_by: groupBy,
aggregations: {
_sliceDurationInSeconds: {
max: {

View file

@ -12,6 +12,7 @@ import {
SLO_SUMMARY_DESTINATION_INDEX_NAME,
SLO_SUMMARY_TRANSFORM_NAME_PREFIX,
} from '../../../../assets/constants';
import { groupBy } from './common';
export const SUMMARY_TIMESLICES_WEEKLY_ALIGNED: TransformPutTransformRequest = {
transform_id: `${SLO_SUMMARY_TRANSFORM_NAME_PREFIX}timeslices-weekly-aligned`,
@ -51,38 +52,7 @@ export const SUMMARY_TIMESLICES_WEEKLY_ALIGNED: TransformPutTransformRequest = {
},
},
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',
},
},
},
group_by: groupBy,
aggregations: {
_sliceDurationInSeconds: {
max: {

View file

@ -139,6 +139,442 @@ Object {
}
`;
exports[`APM Transaction Duration Transform Generator groups by the 'service.environment' 1`] = `
Object {
"bool": Object {
"filter": Array [
Object {
"terms": Object {
"processor.event": Array [
"metric",
],
},
},
Object {
"term": Object {
"metricset.name": "transaction",
},
},
Object {
"exists": Object {
"field": "transaction.duration.histogram",
},
},
Object {
"range": Object {
"@timestamp": Object {
"gte": "now-7d",
},
},
},
Object {
"match": Object {
"service.environment": "production",
},
},
],
},
}
`;
exports[`APM Transaction Duration Transform Generator groups by the 'service.environment' 2`] = `
Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
"service.environment": Object {
"terms": Object {
"field": "service.environment",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
},
},
"slo.revision": Object {
"terms": Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
},
},
"slo.timeWindow.type": Object {
"terms": Object {
"field": "slo.timeWindow.type",
},
},
}
`;
exports[`APM Transaction Duration Transform Generator groups by the 'service.name' 1`] = `
Object {
"bool": Object {
"filter": Array [
Object {
"terms": Object {
"processor.event": Array [
"metric",
],
},
},
Object {
"term": Object {
"metricset.name": "transaction",
},
},
Object {
"exists": Object {
"field": "transaction.duration.histogram",
},
},
Object {
"range": Object {
"@timestamp": Object {
"gte": "now-7d",
},
},
},
Object {
"match": Object {
"service.name": "my-service",
},
},
],
},
}
`;
exports[`APM Transaction Duration Transform Generator groups by the 'service.name' 2`] = `
Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
"service.name": Object {
"terms": Object {
"field": "service.name",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
},
},
"slo.revision": Object {
"terms": Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
},
},
"slo.timeWindow.type": Object {
"terms": Object {
"field": "slo.timeWindow.type",
},
},
}
`;
exports[`APM Transaction Duration Transform Generator groups by the 'transaction.name' 1`] = `
Object {
"bool": Object {
"filter": Array [
Object {
"terms": Object {
"processor.event": Array [
"metric",
],
},
},
Object {
"term": Object {
"metricset.name": "transaction",
},
},
Object {
"exists": Object {
"field": "transaction.duration.histogram",
},
},
Object {
"range": Object {
"@timestamp": Object {
"gte": "now-7d",
},
},
},
Object {
"match": Object {
"transaction.name": "GET /foo",
},
},
],
},
}
`;
exports[`APM Transaction Duration Transform Generator groups by the 'transaction.name' 2`] = `
Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
},
},
"slo.revision": Object {
"terms": Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
},
},
"slo.timeWindow.type": Object {
"terms": Object {
"field": "slo.timeWindow.type",
},
},
"transaction.name": Object {
"terms": Object {
"field": "transaction.name",
},
},
}
`;
exports[`APM Transaction Duration Transform Generator groups by the 'transaction.type' 1`] = `
Object {
"bool": Object {
"filter": Array [
Object {
"terms": Object {
"processor.event": Array [
"metric",
],
},
},
Object {
"term": Object {
"metricset.name": "transaction",
},
},
Object {
"exists": Object {
"field": "transaction.duration.histogram",
},
},
Object {
"range": Object {
"@timestamp": Object {
"gte": "now-7d",
},
},
},
Object {
"match": Object {
"transaction.type": "request",
},
},
],
},
}
`;
exports[`APM Transaction Duration Transform Generator groups by the 'transaction.type' 2`] = `
Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
},
},
"slo.revision": Object {
"terms": Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
},
},
"slo.timeWindow.type": Object {
"terms": Object {
"field": "slo.timeWindow.type",
},
},
"transaction.type": Object {
"terms": Object {
"field": "transaction.type",
},
},
}
`;
exports[`APM Transaction Duration Transform Generator returns the expected transform params for timeslices slo 1`] = `
Object {
"_meta": Object {
@ -194,21 +630,46 @@ Object {
"fixed_interval": "2m",
},
},
"service.environment": Object {
"terms": Object {
"field": "service.environment",
},
},
"service.name": Object {
"terms": Object {
"field": "service.name",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.sliceDurationInSeconds": Object {
"terms": Object {
"field": "slo.objective.sliceDurationInSeconds",
@ -224,6 +685,11 @@ Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
@ -234,6 +700,16 @@ Object {
"field": "slo.timeWindow.type",
},
},
"transaction.name": Object {
"terms": Object {
"field": "transaction.name",
},
},
"transaction.type": Object {
"terms": Object {
"field": "transaction.type",
},
},
},
},
"settings": Object {
@ -298,18 +774,36 @@ Object {
},
"type": "keyword",
},
"slo.description": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.id": Object {
"script": Object {
"source": Any<String>,
},
"type": "keyword",
},
"slo.indicator.type": Object {
"script": Object {
"source": "emit('sli.apm.transactionDuration')",
},
"type": "keyword",
},
"slo.instanceId": Object {
"script": Object {
"source": "emit('*')",
},
"type": "keyword",
},
"slo.name": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.objective.sliceDurationInSeconds": Object {
"script": Object {
"source": "emit(120)",
@ -328,6 +822,12 @@ Object {
},
"type": "long",
},
"slo.tags": Object {
"script": Object {
"source": "emit('critical,k8s')",
},
"type": "keyword",
},
"slo.timeWindow.duration": Object {
"script": Object {
"source": "emit('7d')",
@ -398,21 +898,46 @@ Object {
"fixed_interval": "1m",
},
},
"service.environment": Object {
"terms": Object {
"field": "service.environment",
},
},
"service.name": Object {
"terms": Object {
"field": "service.name",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
@ -423,6 +948,11 @@ Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
@ -433,6 +963,16 @@ Object {
"field": "slo.timeWindow.type",
},
},
"transaction.name": Object {
"terms": Object {
"field": "transaction.name",
},
},
"transaction.type": Object {
"terms": Object {
"field": "transaction.type",
},
},
},
},
"settings": Object {
@ -497,18 +1037,36 @@ Object {
},
"type": "keyword",
},
"slo.description": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.id": Object {
"script": Object {
"source": Any<String>,
},
"type": "keyword",
},
"slo.indicator.type": Object {
"script": Object {
"source": "emit('sli.apm.transactionDuration')",
},
"type": "keyword",
},
"slo.instanceId": Object {
"script": Object {
"source": "emit('*')",
},
"type": "keyword",
},
"slo.name": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.objective.target": Object {
"script": Object {
"source": "emit(0.999)",
@ -521,6 +1079,12 @@ Object {
},
"type": "long",
},
"slo.tags": Object {
"script": Object {
"source": "emit('critical,k8s')",
},
"type": "keyword",
},
"slo.timeWindow.duration": Object {
"script": Object {
"source": "emit('7d')",

View file

@ -131,6 +131,426 @@ Object {
}
`;
exports[`APM Transaction Error Rate Transform Generator groups by the 'service.environment' 1`] = `
Object {
"bool": Object {
"filter": Array [
Object {
"term": Object {
"metricset.name": "transaction",
},
},
Object {
"terms": Object {
"event.outcome": Array [
"success",
"failure",
],
},
},
Object {
"range": Object {
"@timestamp": Object {
"gte": "now-7d",
},
},
},
Object {
"match": Object {
"service.environment": "production",
},
},
],
},
}
`;
exports[`APM Transaction Error Rate Transform Generator groups by the 'service.environment' 2`] = `
Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
"service.environment": Object {
"terms": Object {
"field": "service.environment",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
},
},
"slo.revision": Object {
"terms": Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
},
},
"slo.timeWindow.type": Object {
"terms": Object {
"field": "slo.timeWindow.type",
},
},
}
`;
exports[`APM Transaction Error Rate Transform Generator groups by the 'service.name' 1`] = `
Object {
"bool": Object {
"filter": Array [
Object {
"term": Object {
"metricset.name": "transaction",
},
},
Object {
"terms": Object {
"event.outcome": Array [
"success",
"failure",
],
},
},
Object {
"range": Object {
"@timestamp": Object {
"gte": "now-7d",
},
},
},
Object {
"match": Object {
"service.name": "my-service",
},
},
],
},
}
`;
exports[`APM Transaction Error Rate Transform Generator groups by the 'service.name' 2`] = `
Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
"service.name": Object {
"terms": Object {
"field": "service.name",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
},
},
"slo.revision": Object {
"terms": Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
},
},
"slo.timeWindow.type": Object {
"terms": Object {
"field": "slo.timeWindow.type",
},
},
}
`;
exports[`APM Transaction Error Rate Transform Generator groups by the 'transaction.name' 1`] = `
Object {
"bool": Object {
"filter": Array [
Object {
"term": Object {
"metricset.name": "transaction",
},
},
Object {
"terms": Object {
"event.outcome": Array [
"success",
"failure",
],
},
},
Object {
"range": Object {
"@timestamp": Object {
"gte": "now-7d",
},
},
},
Object {
"match": Object {
"transaction.name": "GET /foo",
},
},
],
},
}
`;
exports[`APM Transaction Error Rate Transform Generator groups by the 'transaction.name' 2`] = `
Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
},
},
"slo.revision": Object {
"terms": Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
},
},
"slo.timeWindow.type": Object {
"terms": Object {
"field": "slo.timeWindow.type",
},
},
"transaction.name": Object {
"terms": Object {
"field": "transaction.name",
},
},
}
`;
exports[`APM Transaction Error Rate Transform Generator groups by the 'transaction.type' 1`] = `
Object {
"bool": Object {
"filter": Array [
Object {
"term": Object {
"metricset.name": "transaction",
},
},
Object {
"terms": Object {
"event.outcome": Array [
"success",
"failure",
],
},
},
Object {
"range": Object {
"@timestamp": Object {
"gte": "now-7d",
},
},
},
Object {
"match": Object {
"transaction.type": "request",
},
},
],
},
}
`;
exports[`APM Transaction Error Rate Transform Generator groups by the 'transaction.type' 2`] = `
Object {
"@timestamp": Object {
"date_histogram": Object {
"field": "@timestamp",
"fixed_interval": "1m",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
},
},
"slo.revision": Object {
"terms": Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
},
},
"slo.timeWindow.type": Object {
"terms": Object {
"field": "slo.timeWindow.type",
},
},
"transaction.type": Object {
"terms": Object {
"field": "transaction.type",
},
},
}
`;
exports[`APM Transaction Error Rate Transform Generator returns the expected transform params for timeslices slo 1`] = `
Object {
"_meta": Object {
@ -179,21 +599,46 @@ Object {
"fixed_interval": "2m",
},
},
"service.environment": Object {
"terms": Object {
"field": "service.environment",
},
},
"service.name": Object {
"terms": Object {
"field": "service.name",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.sliceDurationInSeconds": Object {
"terms": Object {
"field": "slo.objective.sliceDurationInSeconds",
@ -209,6 +654,11 @@ Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
@ -219,6 +669,16 @@ Object {
"field": "slo.timeWindow.type",
},
},
"transaction.name": Object {
"terms": Object {
"field": "transaction.name",
},
},
"transaction.type": Object {
"terms": Object {
"field": "transaction.type",
},
},
},
},
"settings": Object {
@ -279,18 +739,36 @@ Object {
},
"type": "keyword",
},
"slo.description": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.id": Object {
"script": Object {
"source": Any<String>,
},
"type": "keyword",
},
"slo.indicator.type": Object {
"script": Object {
"source": "emit('sli.apm.transactionErrorRate')",
},
"type": "keyword",
},
"slo.instanceId": Object {
"script": Object {
"source": "emit('*')",
},
"type": "keyword",
},
"slo.name": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.objective.sliceDurationInSeconds": Object {
"script": Object {
"source": "emit(120)",
@ -309,6 +787,12 @@ Object {
},
"type": "long",
},
"slo.tags": Object {
"script": Object {
"source": "emit('critical,k8s')",
},
"type": "keyword",
},
"slo.timeWindow.duration": Object {
"script": Object {
"source": "emit('7d')",
@ -372,21 +856,46 @@ Object {
"fixed_interval": "1m",
},
},
"service.environment": Object {
"terms": Object {
"field": "service.environment",
},
},
"service.name": Object {
"terms": Object {
"field": "service.name",
},
},
"slo.budgetingMethod": Object {
"terms": Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
@ -397,6 +906,11 @@ Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
@ -407,6 +921,16 @@ Object {
"field": "slo.timeWindow.type",
},
},
"transaction.name": Object {
"terms": Object {
"field": "transaction.name",
},
},
"transaction.type": Object {
"terms": Object {
"field": "transaction.type",
},
},
},
},
"settings": Object {
@ -467,18 +991,36 @@ Object {
},
"type": "keyword",
},
"slo.description": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.id": Object {
"script": Object {
"source": Any<String>,
},
"type": "keyword",
},
"slo.indicator.type": Object {
"script": Object {
"source": "emit('sli.apm.transactionErrorRate')",
},
"type": "keyword",
},
"slo.instanceId": Object {
"script": Object {
"source": "emit('*')",
},
"type": "keyword",
},
"slo.name": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.objective.target": Object {
"script": Object {
"source": "emit(0.999)",
@ -491,6 +1033,12 @@ Object {
},
"type": "long",
},
"slo.tags": Object {
"script": Object {
"source": "emit('critical,k8s')",
},
"type": "keyword",
},
"slo.timeWindow.duration": Object {
"script": Object {
"source": "emit('7d')",

View file

@ -143,16 +143,31 @@ Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.sliceDurationInSeconds": Object {
"terms": Object {
"field": "slo.objective.sliceDurationInSeconds",
@ -168,6 +183,11 @@ Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
@ -204,18 +224,36 @@ Object {
},
"type": "keyword",
},
"slo.description": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.id": Object {
"script": Object {
"source": Any<String>,
},
"type": "keyword",
},
"slo.indicator.type": Object {
"script": Object {
"source": "emit('sli.histogram.custom')",
},
"type": "keyword",
},
"slo.instanceId": Object {
"script": Object {
"source": "emit('*')",
},
"type": "keyword",
},
"slo.name": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.objective.sliceDurationInSeconds": Object {
"script": Object {
"source": "emit(120)",
@ -234,6 +272,12 @@ Object {
},
"type": "long",
},
"slo.tags": Object {
"script": Object {
"source": "emit('critical,k8s')",
},
"type": "keyword",
},
"slo.timeWindow.duration": Object {
"script": Object {
"source": "emit('7d')",
@ -333,16 +377,31 @@ Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
@ -353,6 +412,11 @@ Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
@ -389,18 +453,36 @@ Object {
},
"type": "keyword",
},
"slo.description": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.id": Object {
"script": Object {
"source": Any<String>,
},
"type": "keyword",
},
"slo.indicator.type": Object {
"script": Object {
"source": "emit('sli.histogram.custom')",
},
"type": "keyword",
},
"slo.instanceId": Object {
"script": Object {
"source": "emit('*')",
},
"type": "keyword",
},
"slo.name": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.objective.target": Object {
"script": Object {
"source": "emit(0.999)",
@ -413,6 +495,12 @@ Object {
},
"type": "long",
},
"slo.tags": Object {
"script": Object {
"source": "emit('critical,k8s')",
},
"type": "keyword",
},
"slo.timeWindow.duration": Object {
"script": Object {
"source": "emit('7d')",

View file

@ -158,16 +158,31 @@ Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.sliceDurationInSeconds": Object {
"terms": Object {
"field": "slo.objective.sliceDurationInSeconds",
@ -183,6 +198,11 @@ Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
@ -219,18 +239,36 @@ Object {
},
"type": "keyword",
},
"slo.description": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.id": Object {
"script": Object {
"source": Any<String>,
},
"type": "keyword",
},
"slo.indicator.type": Object {
"script": Object {
"source": "emit('sli.kql.custom')",
},
"type": "keyword",
},
"slo.instanceId": Object {
"script": Object {
"source": "emit('*')",
},
"type": "keyword",
},
"slo.name": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.objective.sliceDurationInSeconds": Object {
"script": Object {
"source": "emit(120)",
@ -249,6 +287,12 @@ Object {
},
"type": "long",
},
"slo.tags": Object {
"script": Object {
"source": "emit('critical,k8s')",
},
"type": "keyword",
},
"slo.timeWindow.duration": Object {
"script": Object {
"source": "emit('7d')",
@ -322,16 +366,31 @@ Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
@ -342,6 +401,11 @@ Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
@ -378,18 +442,36 @@ Object {
},
"type": "keyword",
},
"slo.description": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.id": Object {
"script": Object {
"source": Any<String>,
},
"type": "keyword",
},
"slo.indicator.type": Object {
"script": Object {
"source": "emit('sli.kql.custom')",
},
"type": "keyword",
},
"slo.instanceId": Object {
"script": Object {
"source": "emit('*')",
},
"type": "keyword",
},
"slo.name": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.objective.target": Object {
"script": Object {
"source": "emit(0.999)",
@ -402,6 +484,12 @@ Object {
},
"type": "long",
},
"slo.tags": Object {
"script": Object {
"source": "emit('critical,k8s')",
},
"type": "keyword",
},
"slo.timeWindow.duration": Object {
"script": Object {
"source": "emit('7d')",

View file

@ -167,16 +167,31 @@ Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.sliceDurationInSeconds": Object {
"terms": Object {
"field": "slo.objective.sliceDurationInSeconds",
@ -192,6 +207,11 @@ Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
@ -228,18 +248,36 @@ Object {
},
"type": "keyword",
},
"slo.description": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.id": Object {
"script": Object {
"source": Any<String>,
},
"type": "keyword",
},
"slo.indicator.type": Object {
"script": Object {
"source": "emit('sli.metric.custom')",
},
"type": "keyword",
},
"slo.instanceId": Object {
"script": Object {
"source": "emit('*')",
},
"type": "keyword",
},
"slo.name": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.objective.sliceDurationInSeconds": Object {
"script": Object {
"source": "emit(120)",
@ -258,6 +296,12 @@ Object {
},
"type": "long",
},
"slo.tags": Object {
"script": Object {
"source": "emit('critical,k8s')",
},
"type": "keyword",
},
"slo.timeWindow.duration": Object {
"script": Object {
"source": "emit('7d')",
@ -369,16 +413,31 @@ Object {
"field": "slo.budgetingMethod",
},
},
"slo.description": Object {
"terms": Object {
"field": "slo.description",
},
},
"slo.id": Object {
"terms": Object {
"field": "slo.id",
},
},
"slo.indicator.type": Object {
"terms": Object {
"field": "slo.indicator.type",
},
},
"slo.instanceId": Object {
"terms": Object {
"field": "slo.instanceId",
},
},
"slo.name": Object {
"terms": Object {
"field": "slo.name",
},
},
"slo.objective.target": Object {
"terms": Object {
"field": "slo.objective.target",
@ -389,6 +448,11 @@ Object {
"field": "slo.revision",
},
},
"slo.tags": Object {
"terms": Object {
"field": "slo.tags",
},
},
"slo.timeWindow.duration": Object {
"terms": Object {
"field": "slo.timeWindow.duration",
@ -425,18 +489,36 @@ Object {
},
"type": "keyword",
},
"slo.description": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.id": Object {
"script": Object {
"source": Any<String>,
},
"type": "keyword",
},
"slo.indicator.type": Object {
"script": Object {
"source": "emit('sli.metric.custom')",
},
"type": "keyword",
},
"slo.instanceId": Object {
"script": Object {
"source": "emit('*')",
},
"type": "keyword",
},
"slo.name": Object {
"script": Object {
"source": "emit('irrelevant')",
},
"type": "keyword",
},
"slo.objective.target": Object {
"script": Object {
"source": "emit(0.999)",
@ -449,6 +531,12 @@ Object {
},
"type": "long",
},
"slo.tags": Object {
"script": Object {
"source": "emit('critical,k8s')",
},
"type": "keyword",
},
"slo.timeWindow.duration": Object {
"script": Object {
"source": "emit('7d')",

View file

@ -15,28 +15,28 @@ import { ApmTransactionDurationTransformGenerator } from './apm_transaction_dura
const generator = new ApmTransactionDurationTransformGenerator();
describe('APM Transaction Duration Transform Generator', () => {
it('returns the expected transform params with every specified indicator params', async () => {
const anSLO = createSLO({ indicator: createAPMTransactionDurationIndicator() });
const transform = generator.getTransformParams(anSLO);
it('returns the expected transform params with every specified indicator params', () => {
const slo = createSLO({ indicator: createAPMTransactionDurationIndicator() });
const transform = generator.getTransformParams(slo);
expect(transform).toMatchSnapshot({
transform_id: expect.any(String),
source: { runtime_mappings: { 'slo.id': { script: { source: expect.any(String) } } } },
});
expect(transform.transform_id).toEqual(`slo-${anSLO.id}-${anSLO.revision}`);
expect(transform.transform_id).toEqual(`slo-${slo.id}-${slo.revision}`);
expect(transform.source.runtime_mappings!['slo.id']).toMatchObject({
script: { source: `emit('${anSLO.id}')` },
script: { source: `emit('${slo.id}')` },
});
expect(transform.source.runtime_mappings!['slo.revision']).toMatchObject({
script: { source: `emit(${anSLO.revision})` },
script: { source: `emit(${slo.revision})` },
});
});
it('returns the expected transform params for timeslices slo', async () => {
const anSLO = createSLOWithTimeslicesBudgetingMethod({
it('returns the expected transform params for timeslices slo', () => {
const slo = createSLOWithTimeslicesBudgetingMethod({
indicator: createAPMTransactionDurationIndicator(),
});
const transform = generator.getTransformParams(anSLO);
const transform = generator.getTransformParams(slo);
expect(transform).toMatchSnapshot({
transform_id: expect.any(String),
@ -44,8 +44,8 @@ describe('APM Transaction Duration Transform Generator', () => {
});
});
it("does not include the query filter when params are '*'", async () => {
const anSLO = createSLO({
it("does not include the query filter when params are '*'", () => {
const slo = createSLO({
indicator: createAPMTransactionDurationIndicator({
environment: '*',
service: '*',
@ -53,32 +53,96 @@ describe('APM Transaction Duration Transform Generator', () => {
transactionType: '*',
}),
});
const transform = generator.getTransformParams(anSLO);
const transform = generator.getTransformParams(slo);
expect(transform.source.query).toMatchSnapshot();
});
it('uses the provided index params as source index', async () => {
it('uses the provided index params as source index', () => {
const index = 'my-custom-apm-index*';
const anSLO = createSLO({
const slo = createSLO({
indicator: createAPMTransactionDurationIndicator({
index,
}),
});
const transform = generator.getTransformParams(anSLO);
const transform = generator.getTransformParams(slo);
expect(transform.source.index).toEqual(index);
});
it('adds the custom kql filter to the query', async () => {
it('adds the custom kql filter to the query', () => {
const filter = `"my.field" : "value" and ("foo" >= 12 or "bar" <= 100)`;
const anSLO = createSLO({
const slo = createSLO({
indicator: createAPMTransactionDurationIndicator({
filter,
}),
});
const transform = generator.getTransformParams(anSLO);
const transform = generator.getTransformParams(slo);
expect(transform.source.query).toMatchSnapshot();
});
it("groups by the 'service.name'", () => {
const slo = createSLO({
indicator: createAPMTransactionDurationIndicator({
service: 'my-service',
environment: '*',
transactionName: '*',
transactionType: '*',
}),
});
const transform = generator.getTransformParams(slo);
expect(transform.source.query).toMatchSnapshot();
expect(transform.pivot?.group_by).toMatchSnapshot();
});
it("groups by the 'service.environment'", () => {
const slo = createSLO({
indicator: createAPMTransactionDurationIndicator({
service: '*',
environment: 'production',
transactionName: '*',
transactionType: '*',
}),
});
const transform = generator.getTransformParams(slo);
expect(transform.source.query).toMatchSnapshot();
expect(transform.pivot?.group_by).toMatchSnapshot();
});
it("groups by the 'transaction.name'", () => {
const slo = createSLO({
indicator: createAPMTransactionDurationIndicator({
service: '*',
environment: '*',
transactionName: 'GET /foo',
transactionType: '*',
}),
});
const transform = generator.getTransformParams(slo);
expect(transform.source.query).toMatchSnapshot();
expect(transform.pivot?.group_by).toMatchSnapshot();
});
it("groups by the 'transaction.type'", () => {
const slo = createSLO({
indicator: createAPMTransactionDurationIndicator({
service: '*',
environment: '*',
transactionName: '*',
transactionType: 'request',
}),
});
const transform = generator.getTransformParams(slo);
expect(transform.source.query).toMatchSnapshot();
expect(transform.pivot?.group_by).toMatchSnapshot();
});
});

View file

@ -11,17 +11,17 @@ import {
apmTransactionDurationIndicatorSchema,
timeslicesBudgetingMethodSchema,
} from '@kbn/slo-schema';
import { InvalidTransformError } from '../../../errors';
import { getElastichsearchQueryOrThrow, TransformGenerator } from '.';
import {
getSLOTransformId,
SLO_DESTINATION_INDEX_NAME,
SLO_INGEST_PIPELINE_NAME,
getSLOTransformId,
} from '../../../assets/constants';
import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template';
import { SLO, APMTransactionDurationIndicator } from '../../../domain/models';
import { getElastichsearchQueryOrThrow, TransformGenerator } from '.';
import { Query } from './types';
import { APMTransactionDurationIndicator, SLO } from '../../../domain/models';
import { InvalidTransformError } from '../../../errors';
import { parseIndex } from './common';
import { Query } from './types';
export class ApmTransactionDurationTransformGenerator extends TransformGenerator {
public getTransformParams(slo: SLO): TransformPutTransformRequest {
@ -34,7 +34,7 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator
this.buildDescription(slo),
this.buildSource(slo, slo.indicator),
this.buildDestination(),
this.buildGroupBy(slo),
this.buildGroupBy(slo, slo.indicator),
this.buildAggregations(slo, slo.indicator),
this.buildSettings(slo)
);
@ -44,6 +44,29 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator
return getSLOTransformId(slo.id, slo.revision);
}
private buildGroupBy(slo: SLO, indicator: APMTransactionDurationIndicator) {
// These groupBy fields must match the fields from the source query, otherwise
// the transform will create permutations for each value present in the source.
// E.g. if environment is not specified in the source query, but we include it in the groupBy,
// we'll output documents for each environment value
const extraGroupByFields = {
...(indicator.params.service !== ALL_VALUE && {
'service.name': { terms: { field: 'service.name' } },
}),
...(indicator.params.environment !== ALL_VALUE && {
'service.environment': { terms: { field: 'service.environment' } },
}),
...(indicator.params.transactionName !== ALL_VALUE && {
'transaction.name': { terms: { field: 'transaction.name' } },
}),
...(indicator.params.transactionType !== ALL_VALUE && {
'transaction.type': { terms: { field: 'transaction.type' } },
}),
};
return this.buildCommonGroupBy(slo, '@timestamp', extraGroupByFields);
}
private buildSource(slo: SLO, indicator: APMTransactionDurationIndicator) {
const queryFilter: Query[] = [
{
@ -54,6 +77,7 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator
},
},
];
if (indicator.params.service !== ALL_VALUE) {
queryFilter.push({
match: {

View file

@ -16,27 +16,27 @@ const generator = new ApmTransactionErrorRateTransformGenerator();
describe('APM Transaction Error Rate Transform Generator', () => {
it('returns the expected transform params with every specified indicator params', async () => {
const anSLO = createSLO({ indicator: createAPMTransactionErrorRateIndicator() });
const transform = generator.getTransformParams(anSLO);
const slo = createSLO({ indicator: createAPMTransactionErrorRateIndicator() });
const transform = generator.getTransformParams(slo);
expect(transform).toMatchSnapshot({
transform_id: expect.any(String),
source: { runtime_mappings: { 'slo.id': { script: { source: expect.any(String) } } } },
});
expect(transform.transform_id).toEqual(`slo-${anSLO.id}-${anSLO.revision}`);
expect(transform.transform_id).toEqual(`slo-${slo.id}-${slo.revision}`);
expect(transform.source.runtime_mappings!['slo.id']).toMatchObject({
script: { source: `emit('${anSLO.id}')` },
script: { source: `emit('${slo.id}')` },
});
expect(transform.source.runtime_mappings!['slo.revision']).toMatchObject({
script: { source: `emit(${anSLO.revision})` },
script: { source: `emit(${slo.revision})` },
});
});
it('returns the expected transform params for timeslices slo', async () => {
const anSLO = createSLOWithTimeslicesBudgetingMethod({
const slo = createSLOWithTimeslicesBudgetingMethod({
indicator: createAPMTransactionErrorRateIndicator(),
});
const transform = generator.getTransformParams(anSLO);
const transform = generator.getTransformParams(slo);
expect(transform).toMatchSnapshot({
transform_id: expect.any(String),
@ -45,7 +45,7 @@ describe('APM Transaction Error Rate Transform Generator', () => {
});
it("does not include the query filter when params are '*'", async () => {
const anSLO = createSLO({
const slo = createSLO({
indicator: createAPMTransactionErrorRateIndicator({
environment: '*',
service: '*',
@ -53,32 +53,96 @@ describe('APM Transaction Error Rate Transform Generator', () => {
transactionType: '*',
}),
});
const transform = generator.getTransformParams(anSLO);
const transform = generator.getTransformParams(slo);
expect(transform.source.query).toMatchSnapshot();
});
it('uses the provided index params as source index', async () => {
const index = 'my-custom-apm-index*';
const anSLO = createSLO({
const slo = createSLO({
indicator: createAPMTransactionErrorRateIndicator({
index,
}),
});
const transform = generator.getTransformParams(anSLO);
const transform = generator.getTransformParams(slo);
expect(transform.source.index).toEqual(index);
});
it('adds the custom kql filter to the query', async () => {
const filter = `"my.field" : "value" and ("foo" >= 12 or "bar" <= 100)`;
const anSLO = createSLO({
const slo = createSLO({
indicator: createAPMTransactionErrorRateIndicator({
filter,
}),
});
const transform = generator.getTransformParams(anSLO);
const transform = generator.getTransformParams(slo);
expect(transform.source.query).toMatchSnapshot();
});
it("groups by the 'service.name'", () => {
const slo = createSLO({
indicator: createAPMTransactionErrorRateIndicator({
service: 'my-service',
environment: '*',
transactionName: '*',
transactionType: '*',
}),
});
const transform = generator.getTransformParams(slo);
expect(transform.source.query).toMatchSnapshot();
expect(transform.pivot?.group_by).toMatchSnapshot();
});
it("groups by the 'service.environment'", () => {
const slo = createSLO({
indicator: createAPMTransactionErrorRateIndicator({
service: '*',
environment: 'production',
transactionName: '*',
transactionType: '*',
}),
});
const transform = generator.getTransformParams(slo);
expect(transform.source.query).toMatchSnapshot();
expect(transform.pivot?.group_by).toMatchSnapshot();
});
it("groups by the 'transaction.name'", () => {
const slo = createSLO({
indicator: createAPMTransactionErrorRateIndicator({
service: '*',
environment: '*',
transactionName: 'GET /foo',
transactionType: '*',
}),
});
const transform = generator.getTransformParams(slo);
expect(transform.source.query).toMatchSnapshot();
expect(transform.pivot?.group_by).toMatchSnapshot();
});
it("groups by the 'transaction.type'", () => {
const slo = createSLO({
indicator: createAPMTransactionErrorRateIndicator({
service: '*',
environment: '*',
transactionName: '*',
transactionType: 'request',
}),
});
const transform = generator.getTransformParams(slo);
expect(transform.source.query).toMatchSnapshot();
expect(transform.pivot?.group_by).toMatchSnapshot();
});
});

View file

@ -11,18 +11,17 @@ import {
apmTransactionErrorRateIndicatorSchema,
timeslicesBudgetingMethodSchema,
} from '@kbn/slo-schema';
import { InvalidTransformError } from '../../../errors';
import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template';
import { getElastichsearchQueryOrThrow, TransformGenerator } from '.';
import {
getSLOTransformId,
SLO_DESTINATION_INDEX_NAME,
SLO_INGEST_PIPELINE_NAME,
getSLOTransformId,
} from '../../../assets/constants';
import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template';
import { APMTransactionErrorRateIndicator, SLO } from '../../../domain/models';
import { Query } from './types';
import { InvalidTransformError } from '../../../errors';
import { parseIndex } from './common';
import { Query } from './types';
export class ApmTransactionErrorRateTransformGenerator extends TransformGenerator {
public getTransformParams(slo: SLO): TransformPutTransformRequest {
@ -35,7 +34,7 @@ export class ApmTransactionErrorRateTransformGenerator extends TransformGenerato
this.buildDescription(slo),
this.buildSource(slo, slo.indicator),
this.buildDestination(),
this.buildGroupBy(slo),
this.buildGroupBy(slo, slo.indicator),
this.buildAggregations(slo),
this.buildSettings(slo)
);
@ -45,6 +44,29 @@ export class ApmTransactionErrorRateTransformGenerator extends TransformGenerato
return getSLOTransformId(slo.id, slo.revision);
}
private buildGroupBy(slo: SLO, indicator: APMTransactionErrorRateIndicator) {
// These groupBy fields must match the fields from the source query, otherwise
// the transform will create permutations for each value present in the source.
// E.g. if environment is not specified in the source query, but we include it in the groupBy,
// we'll output documents for each environment value
const extraGroupByFields = {
...(indicator.params.service !== ALL_VALUE && {
'service.name': { terms: { field: 'service.name' } },
}),
...(indicator.params.environment !== ALL_VALUE && {
'service.environment': { terms: { field: 'service.environment' } },
}),
...(indicator.params.transactionName !== ALL_VALUE && {
'transaction.name': { terms: { field: 'transaction.name' } },
}),
...(indicator.params.transactionType !== ALL_VALUE && {
'transaction.type': { terms: { field: 'transaction.type' } },
}),
};
return this.buildCommonGroupBy(slo, '@timestamp', extraGroupByFields);
}
private buildSource(slo: SLO, indicator: APMTransactionErrorRateIndicator) {
const queryFilter: Query[] = [
{

View file

@ -34,7 +34,7 @@ export class HistogramTransformGenerator extends TransformGenerator {
this.buildDescription(slo),
this.buildSource(slo, slo.indicator),
this.buildDestination(),
this.buildGroupBy(slo, slo.indicator.params.timestampField),
this.buildCommonGroupBy(slo, slo.indicator.params.timestampField),
this.buildAggregations(slo, slo.indicator),
this.buildSettings(slo, slo.indicator.params.timestampField)
);

View file

@ -29,7 +29,7 @@ export class KQLCustomTransformGenerator extends TransformGenerator {
this.buildDescription(slo),
this.buildSource(slo, slo.indicator),
this.buildDestination(),
this.buildGroupBy(slo, slo.indicator.params.timestampField),
this.buildCommonGroupBy(slo, slo.indicator.params.timestampField),
this.buildAggregations(slo, slo.indicator),
this.buildSettings(slo, slo.indicator.params.timestampField)
);

View file

@ -32,7 +32,7 @@ export class MetricCustomTransformGenerator extends TransformGenerator {
this.buildDescription(slo),
this.buildSource(slo, slo.indicator),
this.buildDestination(),
this.buildGroupBy(slo, slo.indicator.params.timestampField),
this.buildCommonGroupBy(slo, slo.indicator.params.timestampField),
this.buildAggregations(slo, slo.indicator),
this.buildSettings(slo, slo.indicator.params.timestampField)
);

View file

@ -5,8 +5,10 @@
* 2.0.
*/
import { MappingRuntimeFieldType } from '@elastic/elasticsearch/lib/api/types';
import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import {
MappingRuntimeFields,
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';
@ -14,54 +16,78 @@ import { SLO } from '../../../domain/models';
export abstract class TransformGenerator {
public abstract getTransformParams(slo: SLO): TransformPutTransformRequest;
public buildCommonRuntimeMappings(slo: SLO) {
public buildCommonRuntimeMappings(slo: SLO): MappingRuntimeFields {
return {
'slo.id': {
type: 'keyword' as MappingRuntimeFieldType,
type: 'keyword',
script: {
source: `emit('${slo.id}')`,
},
},
'slo.revision': {
type: 'long' as MappingRuntimeFieldType,
type: 'long',
script: {
source: `emit(${slo.revision})`,
},
},
'slo.instanceId': {
type: 'keyword' as MappingRuntimeFieldType,
type: 'keyword',
script: {
source: `emit('${ALL_VALUE}')`,
},
},
'slo.name': {
type: 'keyword',
script: {
source: `emit('${slo.name}')`,
},
},
'slo.description': {
type: 'keyword',
script: {
source: `emit('${slo.description}')`,
},
},
'slo.tags': {
type: 'keyword',
script: {
source: `emit('${slo.tags}')`,
},
},
'slo.indicator.type': {
type: 'keyword',
script: {
source: `emit('${slo.indicator.type}')`,
},
},
'slo.objective.target': {
type: 'double' as MappingRuntimeFieldType,
type: 'double',
script: {
source: `emit(${slo.objective.target})`,
},
},
...(slo.objective.timesliceWindow && {
'slo.objective.sliceDurationInSeconds': {
type: 'long' as MappingRuntimeFieldType,
type: 'long',
script: {
source: `emit(${slo.objective.timesliceWindow!.asSeconds()})`,
},
},
}),
'slo.budgetingMethod': {
type: 'keyword' as MappingRuntimeFieldType,
type: 'keyword',
script: {
source: `emit('${slo.budgetingMethod}')`,
},
},
'slo.timeWindow.duration': {
type: 'keyword' as MappingRuntimeFieldType,
type: 'keyword',
script: {
source: `emit('${slo.timeWindow.duration.format()}')`,
},
},
'slo.timeWindow.type': {
type: 'keyword' as MappingRuntimeFieldType,
type: 'keyword',
script: {
source: `emit('${slo.timeWindow.type}')`,
},
@ -73,7 +99,11 @@ export abstract class TransformGenerator {
return `Rolled-up SLI data for SLO: ${slo.name}`;
}
public buildGroupBy(slo: SLO, sourceIndexTimestampField: string | undefined = '@timestamp') {
public buildCommonGroupBy(
slo: SLO,
sourceIndexTimestampField: string | undefined = '@timestamp',
extraGroupByFields = {}
) {
let fixedInterval = '1m';
if (timeslicesBudgetingMethodSchema.is(slo.budgetingMethod)) {
fixedInterval = slo.objective.timesliceWindow!.format();
@ -83,6 +113,10 @@ export abstract class TransformGenerator {
'slo.id': { terms: { field: 'slo.id' } },
'slo.revision': { terms: { field: 'slo.revision' } },
'slo.instanceId': { terms: { field: 'slo.instanceId' } },
'slo.name': { terms: { field: 'slo.name' } },
'slo.description': { terms: { field: 'slo.description' } },
'slo.tags': { terms: { field: 'slo.tags' } },
'slo.indicator.type': { terms: { field: 'slo.indicator.type' } },
'slo.objective.target': { terms: { field: 'slo.objective.target' } },
...(slo.objective.timesliceWindow && {
'slo.objective.sliceDurationInSeconds': {
@ -92,7 +126,8 @@ export abstract class TransformGenerator {
'slo.budgetingMethod': { terms: { field: 'slo.budgetingMethod' } },
'slo.timeWindow.duration': { terms: { field: 'slo.timeWindow.duration' } },
'slo.timeWindow.type': { terms: { field: 'slo.timeWindow.type' } },
// timestamp field defined in the destination index
...extraGroupByFields,
// @timestamp field defined in the destination index
'@timestamp': {
date_histogram: {
field: sourceIndexTimestampField, // timestamp field defined in the source index

View file

@ -8,7 +8,11 @@
import { ElasticsearchClient } from '@kbn/core/server';
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
import { getSLOTransformId } from '../../assets/constants';
import {
getSLOTransformId,
SLO_DESTINATION_INDEX_PATTERN,
SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
} from '../../assets/constants';
import { SLO } from '../../domain/models';
import { fiveMinute, oneMinute } from './fixtures/duration';
import {
@ -132,8 +136,26 @@ describe('UpdateSLO', () => {
const transformId = getSLOTransformId(originalSlo.id, originalSlo.revision);
expect(mockTransformManager.stop).toBeCalledWith(transformId);
expect(mockTransformManager.uninstall).toBeCalledWith(transformId);
expect(mockEsClient.deleteByQuery).toBeCalledWith(
expect(mockEsClient.deleteByQuery).toHaveBeenCalledTimes(2);
expect(mockEsClient.deleteByQuery).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
index: SLO_DESTINATION_INDEX_PATTERN,
query: {
bool: {
filter: [
{ term: { 'slo.id': originalSlo.id } },
{ term: { 'slo.revision': originalSlo.revision } },
],
},
},
})
);
expect(mockEsClient.deleteByQuery).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
query: {
bool: {
filter: [

View file

@ -7,7 +7,11 @@
import { ElasticsearchClient } from '@kbn/core/server';
import { UpdateSLOParams, UpdateSLOResponse, updateSLOResponseSchema } from '@kbn/slo-schema';
import { getSLOTransformId, SLO_DESTINATION_INDEX_PATTERN } from '../../assets/constants';
import {
getSLOTransformId,
SLO_DESTINATION_INDEX_PATTERN,
SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
} from '../../assets/constants';
import { SLO } from '../../domain/models';
import { validateSLO } from '../../domain/services';
import { SLORepository } from './slo_repository';
@ -26,6 +30,7 @@ export class UpdateSLO {
updatedAt: new Date(),
revision: originalSlo.revision + 1,
});
validateSLO(updatedSlo);
await this.deleteObsoleteSLORevisionData(originalSlo);
@ -41,6 +46,7 @@ export class UpdateSLO {
await this.transformManager.stop(originalSloTransformId);
await this.transformManager.uninstall(originalSloTransformId);
await this.deleteRollupData(originalSlo.id, originalSlo.revision);
await this.deleteSummaryData(originalSlo.id, originalSlo.revision);
}
private async deleteRollupData(sloId: string, sloRevision: number): Promise<void> {
@ -55,6 +61,18 @@ export class UpdateSLO {
});
}
private async deleteSummaryData(sloId: string, sloRevision: number): Promise<void> {
await this.esClient.deleteByQuery({
index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
wait_for_completion: false,
query: {
bool: {
filter: [{ term: { 'slo.id': sloId } }, { term: { 'slo.revision': sloRevision } }],
},
},
});
}
private toResponse(slo: SLO): UpdateSLOResponse {
return updateSLOResponseSchema.encode(slo);
}