feat(slo): Add slo usage telemetry (#150393)

This commit is contained in:
Kevin Delemme 2023-02-10 11:28:23 -05:00 committed by GitHub
parent 1a3c9f0015
commit fe4239abe0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 325 additions and 1 deletions

View file

@ -67,7 +67,12 @@ export function SlosPage() {
defaultMessage: 'SLOs',
}),
rightSideItems: [
<EuiButton color="primary" fill onClick={handleClickCreateSlo}>
<EuiButton
color="primary"
fill
onClick={handleClickCreateSlo}
data-test-subj="slosPage-createNewSloButton"
>
{i18n.translate('xpack.observability.slos.sloList.pageHeader.createNewButtonLabel', {
defaultMessage: 'Create new SLO',
})}

View file

@ -0,0 +1,42 @@
/*
* 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 { savedObjectsRepositoryMock, ElasticsearchClientMock } from '@kbn/core/server/mocks';
import { CollectorFetchContext } from '@kbn/usage-collection-plugin/server';
import { fetcher } from './fetcher';
let savedObjectClient: ReturnType<typeof savedObjectsRepositoryMock.create>;
let closeMock: jest.Mock;
let esClient: ElasticsearchClientMock;
describe('SLO usage collector fetcher', () => {
beforeEach(() => {
savedObjectClient = savedObjectsRepositoryMock.create();
closeMock = jest.fn();
});
it('without any existing slo', async () => {
savedObjectClient.createPointInTimeFinder.mockReturnValue({
find: async function* find() {
return {
[Symbol.asyncIterator]: async () => {},
next: () => {},
};
},
close: closeMock,
});
const results = await fetcher({
soClient: savedObjectClient,
esClient,
} as CollectorFetchContext);
expect(closeMock).toHaveBeenCalled();
expect(results.slo.total).toEqual(0);
});
});

View file

@ -0,0 +1,80 @@
/*
* 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 { CollectorFetchContext } from '@kbn/usage-collection-plugin/server';
import { StoredSLO } from '../../domain/models';
import { SO_SLO_TYPE } from '../../saved_objects';
import { Usage } from './type';
export const fetcher = async (context: CollectorFetchContext) => {
const finder = context.soClient.createPointInTimeFinder<StoredSLO>({
type: SO_SLO_TYPE,
perPage: 100,
});
let usage: Usage['slo'] = {
total: 0,
by_status: {
enabled: 0,
disabled: 0,
},
by_sli_type: {},
by_rolling_duration: {},
by_calendar_aligned_duration: {},
by_budgeting_method: {
occurrences: 0,
timeslices: 0,
},
};
for await (const response of finder.find()) {
usage = response.saved_objects.reduce((acc, so) => {
return {
...acc,
total: acc.total + 1,
by_status: {
...acc.by_status,
...(so.attributes.enabled && { enabled: acc.by_status.enabled + 1 }),
...(!so.attributes.enabled && { disabled: acc.by_status.disabled + 1 }),
},
by_sli_type: {
...acc.by_sli_type,
[so.attributes.indicator.type]: (acc.by_sli_type[so.attributes.indicator.type] ?? 0) + 1,
},
by_rolling_duration: {
...acc.by_rolling_duration,
...('isRolling' in so.attributes.timeWindow && {
[so.attributes.timeWindow.duration]:
(acc.by_rolling_duration[so.attributes.timeWindow.duration] ?? 0) + 1,
}),
},
by_calendar_aligned_duration: {
...acc.by_calendar_aligned_duration,
...('calendar' in so.attributes.timeWindow && {
[so.attributes.timeWindow.duration]:
(acc.by_calendar_aligned_duration[so.attributes.timeWindow.duration] ?? 0) + 1,
}),
},
by_budgeting_method: {
...acc.by_budgeting_method,
...(so.attributes.budgetingMethod === 'occurrences' && {
occurrences: acc.by_budgeting_method.occurrences + 1,
}),
...(so.attributes.budgetingMethod === 'timeslices' && {
timeslices: acc.by_budgeting_method.timeslices + 1,
}),
},
};
}, usage);
}
await finder.close();
return {
slo: usage,
};
};

View file

@ -0,0 +1,87 @@
/*
* 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 { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { fetcher } from './fetcher';
import type { Usage } from './type';
export function registerSloUsageCollector(usageCollection?: UsageCollectionSetup): void {
if (!usageCollection) {
return;
}
const sloUsageCollector = usageCollection.makeUsageCollector<Usage>({
type: 'slo',
schema: {
slo: {
total: {
type: 'long',
_meta: {
description: 'The total number of slos in the cluster',
},
},
by_status: {
enabled: {
type: 'long',
_meta: {
description: 'The number of enabled slos in the cluster',
},
},
disabled: {
type: 'long',
_meta: {
description: 'The number of disabled slos in the cluster',
},
},
},
by_sli_type: {
DYNAMIC_KEY: {
type: 'long',
_meta: {
description: 'The number of slos by sli type in the cluster',
},
},
},
by_rolling_duration: {
DYNAMIC_KEY: {
type: 'long',
_meta: {
description: 'The number of slos by rolling duration in the cluster',
},
},
},
by_calendar_aligned_duration: {
DYNAMIC_KEY: {
type: 'long',
_meta: {
description: 'The number of slos by calendar aligned duration in the cluster',
},
},
},
by_budgeting_method: {
occurrences: {
type: 'long',
_meta: {
description: 'The number of slos by timeslices budgeting method in the cluster',
},
},
timeslices: {
type: 'long',
_meta: {
description: 'The number of slos by occurrences budgeting method in the cluster',
},
},
},
},
},
isReady: () => true,
fetch: fetcher,
});
// register usage collector
usageCollection.registerCollector(sloUsageCollector);
}

View file

@ -0,0 +1,29 @@
/*
* 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 interface Usage {
slo: {
total: number;
by_status: {
enabled: number;
disabled: number;
};
by_sli_type: {
[sli_type: string]: number;
};
by_rolling_duration: {
[duration: string]: number;
};
by_calendar_aligned_duration: {
[duration: string]: number;
};
by_budgeting_method: {
occurrences: number;
timeslices: number;
};
};
}

View file

@ -23,6 +23,7 @@ import { mappingFromFieldMap } from '@kbn/rule-registry-plugin/common/mapping_fr
import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/rule-registry-plugin/common/assets';
import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import {
kubernetesGuideId,
kubernetesGuideConfig,
@ -40,6 +41,7 @@ import { casesFeatureId, observabilityFeatureId } from '../common';
import { slo } from './saved_objects';
import { OBSERVABILITY_FEATURE_ID, RULE_REGISTRATION_CONTEXT } from './common/constants';
import { registerRuleTypes } from './lib/rules/register_rule_types';
import { registerSloUsageCollector } from './lib/collectors/register';
export type ObservabilityPluginSetup = ReturnType<ObservabilityPlugin['setup']>;
@ -49,6 +51,7 @@ interface PluginSetup {
spaces: SpacesPluginStart;
alerting: PluginSetupContract;
guidedOnboarding: GuidedOnboardingPluginSetup;
usageCollection?: UsageCollectionSetup;
}
export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
@ -174,6 +177,8 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
});
registerRuleTypes(plugins.alerting, this.logger, ruleDataClient);
registerSloUsageCollector(plugins.usageCollection);
}
const start = () => core.getStartServices().then(([coreStart]) => coreStart);

View file

@ -13204,6 +13204,82 @@
}
}
},
"slo": {
"properties": {
"slo": {
"properties": {
"total": {
"type": "long",
"_meta": {
"description": "The total number of slos in the cluster"
}
},
"by_status": {
"properties": {
"enabled": {
"type": "long",
"_meta": {
"description": "The number of enabled slos in the cluster"
}
},
"disabled": {
"type": "long",
"_meta": {
"description": "The number of disabled slos in the cluster"
}
}
}
},
"by_sli_type": {
"properties": {
"DYNAMIC_KEY": {
"type": "long",
"_meta": {
"description": "The number of slos by sli type in the cluster"
}
}
}
},
"by_rolling_duration": {
"properties": {
"DYNAMIC_KEY": {
"type": "long",
"_meta": {
"description": "The number of slos by rolling duration in the cluster"
}
}
}
},
"by_calendar_aligned_duration": {
"properties": {
"DYNAMIC_KEY": {
"type": "long",
"_meta": {
"description": "The number of slos by calendar aligned duration in the cluster"
}
}
}
},
"by_budgeting_method": {
"properties": {
"occurrences": {
"type": "long",
"_meta": {
"description": "The number of slos by timeslices budgeting method in the cluster"
}
},
"timeslices": {
"type": "long",
"_meta": {
"description": "The number of slos by occurrences budgeting method in the cluster"
}
}
}
}
}
}
}
},
"spaces": {
"properties": {
"usesFeatureControls": {