mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[8.12] [SLOs] Use named keys to reference range aggregations for APM Latency and Histogram Metric (#173548) (#174505)
# Backport This will backport the following commits from `main` to `8.12`: - [[SLOs] Use named keys to reference range aggregations for APM Latency and Histogram Metric (#173548)](https://github.com/elastic/kibana/pull/173548) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Shahzad","email":"shahzad31comp@gmail.com"},"sourceCommit":{"committedDate":"2024-01-05T19:24:58Z","message":"[SLOs] Use named keys to reference range aggregations for APM Latency and Histogram Metric (#173548)\n\n## Summary\n\nFixes https://github.com/elastic/kibana/issues/171329\n\nit now works for value over 9999\n\nFix APM SLI Preview value calculation !!\n\n\n<img width=\"1726\" alt=\"image\"\nsrc=\"d875e2cf
-6195-455f-9b28-35c49c6d5eee\">","sha":"57e50a6f47f7c4cb084fddb75b1b2031db10913f","branchLabelMapping":{"^v8.13.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:prev-minor","Team:obs-ux-management","v8.13.0"],"number":173548,"url":"https://github.com/elastic/kibana/pull/173548","mergeCommit":{"message":"[SLOs] Use named keys to reference range aggregations for APM Latency and Histogram Metric (#173548)\n\n## Summary\n\nFixes https://github.com/elastic/kibana/issues/171329\n\nit now works for value over 9999\n\nFix APM SLI Preview value calculation !!\n\n\n<img width=\"1726\" alt=\"image\"\nsrc=\"d875e2cf
-6195-455f-9b28-35c49c6d5eee\">","sha":"57e50a6f47f7c4cb084fddb75b1b2031db10913f"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.13.0","labelRegex":"^v8.13.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/173548","number":173548,"mergeCommit":{"message":"[SLOs] Use named keys to reference range aggregations for APM Latency and Histogram Metric (#173548)\n\n## Summary\n\nFixes https://github.com/elastic/kibana/issues/171329\n\nit now works for value over 9999\n\nFix APM SLI Preview value calculation !!\n\n\n<img width=\"1726\" alt=\"image\"\nsrc=\"d875e2cf
-6195-455f-9b28-35c49c6d5eee\">","sha":"57e50a6f47f7c4cb084fddb75b1b2031db10913f"}}]}] BACKPORT--> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
222317c0e1
commit
c310ce2afa
8 changed files with 83 additions and 60 deletions
|
@ -11,6 +11,7 @@ Object {
|
|||
"ranges": Array [
|
||||
Object {
|
||||
"from": 0,
|
||||
"key": "target",
|
||||
"to": 100,
|
||||
},
|
||||
],
|
||||
|
@ -24,7 +25,7 @@ Object {
|
|||
"goodEvents": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"value": "_good>total['0.0-100.0']>_count",
|
||||
"value": "_good>total['target']>_count",
|
||||
},
|
||||
"script": "params.value",
|
||||
},
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { HistogramIndicator } from '@kbn/slo-schema';
|
||||
import { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { getElastichsearchQueryOrThrow } from '../transform_generators/common';
|
||||
|
||||
type HistogramIndicatorDef =
|
||||
|
@ -15,7 +16,7 @@ type HistogramIndicatorDef =
|
|||
export class GetHistogramIndicatorAggregation {
|
||||
constructor(private indicator: HistogramIndicator) {}
|
||||
|
||||
private buildAggregation(type: 'good' | 'total', indicator: HistogramIndicatorDef) {
|
||||
private buildAggregation(indicator: HistogramIndicatorDef): AggregationsAggregationContainer {
|
||||
const filter = indicator.filter
|
||||
? getElastichsearchQueryOrThrow(indicator.filter)
|
||||
: { match_all: {} };
|
||||
|
@ -43,15 +44,6 @@ export class GetHistogramIndicatorAggregation {
|
|||
throw new Error('Invalid Range: "from" should be less that "to".');
|
||||
}
|
||||
|
||||
const range: { from?: number; to?: number } = {};
|
||||
if (indicator.from != null) {
|
||||
range.from = indicator.from;
|
||||
}
|
||||
|
||||
if (indicator.to != null) {
|
||||
range.to = indicator.to;
|
||||
}
|
||||
|
||||
return {
|
||||
filter,
|
||||
aggs: {
|
||||
|
@ -59,24 +51,23 @@ export class GetHistogramIndicatorAggregation {
|
|||
range: {
|
||||
field: indicator.field,
|
||||
keyed: true,
|
||||
ranges: [range],
|
||||
ranges: [
|
||||
{
|
||||
key: 'target',
|
||||
from: indicator.from,
|
||||
to: indicator.to,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private formatNumberAsFloatString(value: number) {
|
||||
return value % 1 === 0 ? `${value}.0` : `${value}`;
|
||||
}
|
||||
|
||||
private buildRangeKey(from: number | undefined, to: number | undefined) {
|
||||
const fromString = from != null ? this.formatNumberAsFloatString(from) : '*';
|
||||
const toString = to != null ? this.formatNumberAsFloatString(to) : '*';
|
||||
return `${fromString}-${toString}`;
|
||||
}
|
||||
|
||||
private buildBucketScript(type: 'good' | 'total', indicator: HistogramIndicatorDef) {
|
||||
private buildBucketScript(
|
||||
type: 'good' | 'total',
|
||||
indicator: HistogramIndicatorDef
|
||||
): AggregationsAggregationContainer {
|
||||
if (indicator.aggregation === 'value_count') {
|
||||
return {
|
||||
bucket_script: {
|
||||
|
@ -87,11 +78,10 @@ export class GetHistogramIndicatorAggregation {
|
|||
},
|
||||
};
|
||||
}
|
||||
const rangeKey = this.buildRangeKey(indicator.from, indicator.to);
|
||||
return {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
value: `_${type}>total['${rangeKey}']>_count`,
|
||||
value: `_${type}>total['target']>_count`,
|
||||
},
|
||||
script: 'params.value',
|
||||
},
|
||||
|
@ -101,7 +91,7 @@ export class GetHistogramIndicatorAggregation {
|
|||
public execute({ type, aggregationKey }: { type: 'good' | 'total'; aggregationKey: string }) {
|
||||
const indicatorDef = this.indicator.params[type];
|
||||
return {
|
||||
[`_${type}`]: this.buildAggregation(type, indicatorDef),
|
||||
[`_${type}`]: this.buildAggregation(indicatorDef),
|
||||
[aggregationKey]: this.buildBucketScript(type, indicatorDef),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { calculateAuto } from '@kbn/calculate-auto';
|
||||
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
|
||||
import {
|
||||
ALL_VALUE,
|
||||
|
@ -20,6 +19,9 @@ import {
|
|||
} from '@kbn/slo-schema';
|
||||
import { assertNever } from '@kbn/std';
|
||||
import moment from 'moment';
|
||||
import { ElasticsearchClient } from '@kbn/core/server';
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { typedSearch } from '../../utils/queries';
|
||||
import { APMTransactionDurationIndicator } from '../../domain/models';
|
||||
import { computeSLI } from '../../domain/services';
|
||||
import { InvalidQueryError } from '../../errors';
|
||||
|
@ -43,7 +45,7 @@ export class GetPreviewData {
|
|||
indicator: APMTransactionDurationIndicator,
|
||||
options: Options
|
||||
): Promise<GetPreviewDataResponse> {
|
||||
const filter = [];
|
||||
const filter: estypes.QueryDslQueryContainer[] = [];
|
||||
if (indicator.params.service !== ALL_VALUE)
|
||||
filter.push({
|
||||
match: { 'service.name': indicator.params.service },
|
||||
|
@ -61,11 +63,11 @@ export class GetPreviewData {
|
|||
match: { 'transaction.type': indicator.params.transactionType },
|
||||
});
|
||||
if (!!indicator.params.filter)
|
||||
filter.push(getElastichsearchQueryOrThrow(indicator.params.filter));
|
||||
filter.push(getElasticsearchQueryOrThrow(indicator.params.filter));
|
||||
|
||||
const truncatedThreshold = Math.trunc(indicator.params.threshold * 1000);
|
||||
|
||||
const result = await this.esClient.search({
|
||||
const result = await typedSearch(this.esClient, {
|
||||
index: indicator.params.index,
|
||||
size: 0,
|
||||
query: {
|
||||
|
@ -89,13 +91,14 @@ export class GetPreviewData {
|
|||
_good: {
|
||||
range: {
|
||||
field: 'transaction.duration.histogram',
|
||||
ranges: [{ to: truncatedThreshold }],
|
||||
keyed: true,
|
||||
ranges: [{ to: truncatedThreshold, key: 'target' }],
|
||||
},
|
||||
},
|
||||
good: {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
_good: `_good['*-${truncatedThreshold}.0']>_count`,
|
||||
_good: `_good['target']>_count`,
|
||||
},
|
||||
script: 'params._good',
|
||||
},
|
||||
|
@ -110,17 +113,21 @@ export class GetPreviewData {
|
|||
},
|
||||
});
|
||||
|
||||
// @ts-ignore buckets is not improperly typed
|
||||
return result.aggregations?.perMinute.buckets.map((bucket) => ({
|
||||
date: bucket.key_as_string,
|
||||
sliValue:
|
||||
!!bucket.good && !!bucket.total ? computeSLI(bucket.good.value, bucket.total.value) : null,
|
||||
events: {
|
||||
good: bucket.good?.value ?? 0,
|
||||
bad: (bucket.total?.value ?? 0) - (bucket.good?.value ?? 0),
|
||||
total: bucket.total?.value ?? 0,
|
||||
},
|
||||
}));
|
||||
return (
|
||||
result.aggregations?.perMinute.buckets.map((bucket) => {
|
||||
const good = (bucket.good?.value as number) ?? 0;
|
||||
const total = bucket.total?.value ?? 0;
|
||||
return {
|
||||
date: bucket.key_as_string,
|
||||
sliValue: computeSLI(good, total),
|
||||
events: {
|
||||
good,
|
||||
total,
|
||||
bad: total - good,
|
||||
},
|
||||
};
|
||||
}) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
private async getAPMTransactionErrorPreviewData(
|
||||
|
@ -145,7 +152,7 @@ export class GetPreviewData {
|
|||
match: { 'transaction.type': indicator.params.transactionType },
|
||||
});
|
||||
if (!!indicator.params.filter)
|
||||
filter.push(getElastichsearchQueryOrThrow(indicator.params.filter));
|
||||
filter.push(getElasticsearchQueryOrThrow(indicator.params.filter));
|
||||
|
||||
const result = await this.esClient.search({
|
||||
index: indicator.params.index,
|
||||
|
@ -208,7 +215,7 @@ export class GetPreviewData {
|
|||
options: Options
|
||||
): Promise<GetPreviewDataResponse> {
|
||||
const getHistogramIndicatorAggregations = new GetHistogramIndicatorAggregation(indicator);
|
||||
const filterQuery = getElastichsearchQueryOrThrow(indicator.params.filter);
|
||||
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter);
|
||||
const timestampField = indicator.params.timestampField;
|
||||
const result = await this.esClient.search({
|
||||
index: indicator.params.index,
|
||||
|
@ -259,7 +266,7 @@ export class GetPreviewData {
|
|||
options: Options
|
||||
): Promise<GetPreviewDataResponse> {
|
||||
const timestampField = indicator.params.timestampField;
|
||||
const filterQuery = getElastichsearchQueryOrThrow(indicator.params.filter);
|
||||
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter);
|
||||
const getCustomMetricIndicatorAggregation = new GetCustomMetricIndicatorAggregation(indicator);
|
||||
const result = await this.esClient.search({
|
||||
index: indicator.params.index,
|
||||
|
@ -310,7 +317,7 @@ export class GetPreviewData {
|
|||
options: Options
|
||||
): Promise<GetPreviewDataResponse> {
|
||||
const timestampField = indicator.params.timestampField;
|
||||
const filterQuery = getElastichsearchQueryOrThrow(indicator.params.filter);
|
||||
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter);
|
||||
const getCustomMetricIndicatorAggregation = new GetTimesliceMetricIndicatorAggregation(
|
||||
indicator
|
||||
);
|
||||
|
@ -349,9 +356,9 @@ export class GetPreviewData {
|
|||
indicator: KQLCustomIndicator,
|
||||
options: Options
|
||||
): Promise<GetPreviewDataResponse> {
|
||||
const filterQuery = getElastichsearchQueryOrThrow(indicator.params.filter);
|
||||
const goodQuery = getElastichsearchQueryOrThrow(indicator.params.good);
|
||||
const totalQuery = getElastichsearchQueryOrThrow(indicator.params.total);
|
||||
const filterQuery = getElasticsearchQueryOrThrow(indicator.params.filter);
|
||||
const goodQuery = getElasticsearchQueryOrThrow(indicator.params.good);
|
||||
const totalQuery = getElasticsearchQueryOrThrow(indicator.params.total);
|
||||
const timestampField = indicator.params.timestampField;
|
||||
const result = await this.esClient.search({
|
||||
index: indicator.params.index,
|
||||
|
@ -429,7 +436,7 @@ export class GetPreviewData {
|
|||
}
|
||||
}
|
||||
|
||||
function getElastichsearchQueryOrThrow(kuery: string | undefined = '') {
|
||||
function getElasticsearchQueryOrThrow(kuery: string | undefined = '') {
|
||||
try {
|
||||
return toElasticsearchQuery(fromKueryExpression(kuery));
|
||||
} catch (err) {
|
||||
|
|
|
@ -433,8 +433,10 @@ Object {
|
|||
"_numerator": Object {
|
||||
"range": Object {
|
||||
"field": "transaction.duration.histogram",
|
||||
"keyed": true,
|
||||
"ranges": Array [
|
||||
Object {
|
||||
"key": "target",
|
||||
"to": 500000,
|
||||
},
|
||||
],
|
||||
|
@ -457,7 +459,7 @@ Object {
|
|||
"slo.numerator": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"numerator": "_numerator['*-500000.0']>_count",
|
||||
"numerator": "_numerator['target']>_count",
|
||||
},
|
||||
"script": "params.numerator",
|
||||
},
|
||||
|
@ -612,8 +614,10 @@ Object {
|
|||
"_numerator": Object {
|
||||
"range": Object {
|
||||
"field": "transaction.duration.histogram",
|
||||
"keyed": true,
|
||||
"ranges": Array [
|
||||
Object {
|
||||
"key": "target",
|
||||
"to": 500000,
|
||||
},
|
||||
],
|
||||
|
@ -627,7 +631,7 @@ Object {
|
|||
"slo.numerator": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"numerator": "_numerator['*-500000.0']>_count",
|
||||
"numerator": "_numerator['target']>_count",
|
||||
},
|
||||
"script": "params.numerator",
|
||||
},
|
||||
|
|
|
@ -26,7 +26,7 @@ exports[`Histogram Transform Generator aggregates using the numerator equation 1
|
|||
Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"value": "_good>total['0.0-100.0']>_count",
|
||||
"value": "_good>total['target']>_count",
|
||||
},
|
||||
"script": "params.value",
|
||||
},
|
||||
|
@ -37,7 +37,7 @@ exports[`Histogram Transform Generator aggregates using the numerator equation w
|
|||
Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"value": "_good>total['0.0-100.0']>_count",
|
||||
"value": "_good>total['target']>_count",
|
||||
},
|
||||
"script": "params.value",
|
||||
},
|
||||
|
@ -96,6 +96,7 @@ Object {
|
|||
"ranges": Array [
|
||||
Object {
|
||||
"from": 0,
|
||||
"key": "target",
|
||||
"to": 100,
|
||||
},
|
||||
],
|
||||
|
@ -138,7 +139,7 @@ Object {
|
|||
"slo.numerator": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"value": "_good>total['0.0-100.0']>_count",
|
||||
"value": "_good>total['target']>_count",
|
||||
},
|
||||
"script": "params.value",
|
||||
},
|
||||
|
@ -257,6 +258,7 @@ Object {
|
|||
"ranges": Array [
|
||||
Object {
|
||||
"from": 0,
|
||||
"key": "target",
|
||||
"to": 100,
|
||||
},
|
||||
],
|
||||
|
@ -290,7 +292,7 @@ Object {
|
|||
"slo.numerator": Object {
|
||||
"bucket_script": Object {
|
||||
"buckets_path": Object {
|
||||
"value": "_good>total['0.0-100.0']>_count",
|
||||
"value": "_good>total['target']>_count",
|
||||
},
|
||||
"script": "params.value",
|
||||
},
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
apmTransactionDurationIndicatorSchema,
|
||||
timeslicesBudgetingMethodSchema,
|
||||
} from '@kbn/slo-schema';
|
||||
import { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { getElastichsearchQueryOrThrow, TransformGenerator } from '.';
|
||||
import {
|
||||
getSLOTransformId,
|
||||
|
@ -137,7 +138,10 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator
|
|||
};
|
||||
}
|
||||
|
||||
private buildAggregations(slo: SLO, indicator: APMTransactionDurationIndicator) {
|
||||
private buildAggregations(
|
||||
slo: SLO,
|
||||
indicator: APMTransactionDurationIndicator
|
||||
): Record<string, AggregationsAggregationContainer> {
|
||||
// threshold is in ms (milliseconds), but apm data is stored in us (microseconds)
|
||||
const truncatedThreshold = Math.trunc(indicator.params.threshold * 1000);
|
||||
|
||||
|
@ -145,9 +149,11 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator
|
|||
_numerator: {
|
||||
range: {
|
||||
field: 'transaction.duration.histogram',
|
||||
keyed: true,
|
||||
ranges: [
|
||||
{
|
||||
to: truncatedThreshold,
|
||||
key: 'target',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -155,7 +161,7 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator
|
|||
'slo.numerator': {
|
||||
bucket_script: {
|
||||
buckets_path: {
|
||||
numerator: `_numerator['*-${truncatedThreshold}.0']>_count`,
|
||||
numerator: `_numerator['target']>_count`,
|
||||
},
|
||||
script: 'params.numerator',
|
||||
},
|
||||
|
|
|
@ -8,6 +8,8 @@ import { reject } from 'lodash';
|
|||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
|
||||
import { ESSearchResponse } from '@kbn/es-types';
|
||||
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
|
||||
export function isUndefinedOrNull(value: any): value is undefined | null {
|
||||
return value === undefined || value === null;
|
||||
|
@ -68,3 +70,13 @@ export function kqlQuery(kql?: string): estypes.QueryDslQueryContainer[] {
|
|||
const ast = fromKueryExpression(kql);
|
||||
return [toElasticsearchQuery(ast)];
|
||||
}
|
||||
|
||||
export async function typedSearch<
|
||||
DocumentSource extends unknown,
|
||||
TParams extends estypes.SearchRequest
|
||||
>(
|
||||
esClient: ElasticsearchClient,
|
||||
params: TParams
|
||||
): Promise<ESSearchResponse<DocumentSource, TParams>> {
|
||||
return (await esClient.search(params)) as unknown as ESSearchResponse<DocumentSource, TParams>;
|
||||
}
|
||||
|
|
|
@ -96,7 +96,8 @@
|
|||
"@kbn/lens-embeddable-utils",
|
||||
"@kbn/serverless",
|
||||
"@kbn/dashboard-plugin",
|
||||
"@kbn/calculate-auto"
|
||||
"@kbn/calculate-auto",
|
||||
"@kbn/es-types"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue