[APM] Fix bucket size in alert previews (#111304)

* fixing preview charts

* using metrics

* fixing ts issues

* refactoring

* refactoring useServiceTransactionTypesFetcher

* addressing pr comments

* addressing pr comments

* addressing pr comments
This commit is contained in:
Cauê Marcondes 2021-09-09 12:57:29 -04:00 committed by GitHub
parent 6e9b1b57b8
commit c51c92cd7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 171 additions and 87 deletions

View file

@ -15,7 +15,7 @@ import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'
import { useFetcher } from '../../../hooks/use_fetcher';
import { ChartPreview } from '../chart_preview';
import { EnvironmentField, IsAboveField, ServiceField } from '../fields';
import { AlertMetadata, getAbsoluteTimeRange } from '../helper';
import { AlertMetadata, getIntervalAndTimeRange, TimeUnit } from '../helper';
import { ServiceAlertTrigger } from '../service_alert_trigger';
export interface AlertParams {
@ -54,14 +54,20 @@ export function ErrorCountAlertTrigger(props: Props) {
const { data } = useFetcher(
(callApmApi) => {
if (params.windowSize && params.windowUnit) {
const { interval, start, end } = getIntervalAndTimeRange({
windowSize: params.windowSize,
windowUnit: params.windowUnit as TimeUnit,
});
if (interval && start && end) {
return callApmApi({
endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count',
params: {
query: {
...getAbsoluteTimeRange(params.windowSize, params.windowUnit),
environment: params.environment,
serviceName: params.serviceName,
interval,
start,
end,
},
},
});

View file

@ -4,8 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import datemath from '@elastic/datemath';
import moment from 'moment';
export interface AlertMetadata {
environment: string;
@ -15,12 +14,25 @@ export interface AlertMetadata {
end?: string;
}
export function getAbsoluteTimeRange(windowSize: number, windowUnit: string) {
const now = new Date().toISOString();
export type TimeUnit = 's' | 'm' | 'h' | 'd';
const BUCKET_SIZE = 20;
export function getIntervalAndTimeRange({
windowSize,
windowUnit,
}: {
windowSize: number;
windowUnit: TimeUnit;
}) {
const end = Date.now();
const start =
end -
moment.duration(windowSize, windowUnit).asMilliseconds() * BUCKET_SIZE;
return {
start:
datemath.parse(`now-${windowSize}${windowUnit}`)?.toISOString() ?? now,
end: now,
interval: `${windowSize}${windowUnit}`,
start: new Date(start).toISOString(),
end: new Date(end).toISOString(),
};
}

View file

@ -29,7 +29,7 @@ import {
ServiceField,
TransactionTypeField,
} from '../fields';
import { AlertMetadata, getAbsoluteTimeRange } from '../helper';
import { AlertMetadata, getIntervalAndTimeRange, TimeUnit } from '../helper';
import { ServiceAlertTrigger } from '../service_alert_trigger';
import { PopoverExpression } from '../service_alert_trigger/popover_expression';
@ -77,9 +77,11 @@ export function TransactionDurationAlertTrigger(props: Props) {
createCallApmApi(services as CoreStart);
const transactionTypes = useServiceTransactionTypesFetcher(
metadata?.serviceName
);
const transactionTypes = useServiceTransactionTypesFetcher({
serviceName: metadata?.serviceName,
start: metadata?.start,
end: metadata?.end,
});
const params = defaults(
{
@ -104,16 +106,22 @@ export function TransactionDurationAlertTrigger(props: Props) {
const { data } = useFetcher(
(callApmApi) => {
if (params.windowSize && params.windowUnit) {
const { interval, start, end } = getIntervalAndTimeRange({
windowSize: params.windowSize,
windowUnit: params.windowUnit as TimeUnit,
});
if (interval && start && end) {
return callApmApi({
endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration',
params: {
query: {
...getAbsoluteTimeRange(params.windowSize, params.windowUnit),
aggregationType: params.aggregationType,
environment: params.environment,
serviceName: params.serviceName,
transactionType: params.transactionType,
interval,
start,
end,
},
},
});

View file

@ -48,9 +48,11 @@ interface Props {
export function TransactionDurationAnomalyAlertTrigger(props: Props) {
const { alertParams, metadata, setAlertParams, setAlertProperty } = props;
const transactionTypes = useServiceTransactionTypesFetcher(
metadata?.serviceName
);
const transactionTypes = useServiceTransactionTypesFetcher({
serviceName: metadata?.serviceName,
start: metadata?.start,
end: metadata?.end,
});
const params = defaults(
{

View file

@ -23,7 +23,7 @@ import {
ServiceField,
TransactionTypeField,
} from '../fields';
import { AlertMetadata, getAbsoluteTimeRange } from '../helper';
import { AlertMetadata, getIntervalAndTimeRange, TimeUnit } from '../helper';
import { ServiceAlertTrigger } from '../service_alert_trigger';
interface AlertParams {
@ -47,10 +47,11 @@ export function TransactionErrorRateAlertTrigger(props: Props) {
const { alertParams, metadata, setAlertParams, setAlertProperty } = props;
createCallApmApi(services as CoreStart);
const transactionTypes = useServiceTransactionTypesFetcher(
metadata?.serviceName
);
const transactionTypes = useServiceTransactionTypesFetcher({
serviceName: metadata?.serviceName,
start: metadata?.start,
end: metadata?.end,
});
const params = defaults(
{ ...omit(metadata, ['start', 'end']), ...alertParams },
@ -72,15 +73,21 @@ export function TransactionErrorRateAlertTrigger(props: Props) {
const { data } = useFetcher(
(callApmApi) => {
if (params.windowSize && params.windowUnit) {
const { interval, start, end } = getIntervalAndTimeRange({
windowSize: params.windowSize,
windowUnit: params.windowUnit as TimeUnit,
});
if (interval && start && end) {
return callApmApi({
endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate',
params: {
query: {
...getAbsoluteTimeRange(params.windowSize, params.windowUnit),
environment: params.environment,
serviceName: params.serviceName,
transactionType: params.transactionType,
interval,
start,
end,
},
},
});

View file

@ -51,7 +51,11 @@ export function ApmServiceContextProvider({
end,
});
const transactionTypes = useServiceTransactionTypesFetcher(serviceName);
const transactionTypes = useServiceTransactionTypesFetcher({
serviceName,
start,
end,
});
const transactionType = getTransactionType({
transactionType: query.transactionType,

View file

@ -5,19 +5,19 @@
* 2.0.
*/
import { useApmParams } from '../../hooks/use_apm_params';
import { useFetcher } from '../../hooks/use_fetcher';
import { useTimeRange } from '../../hooks/use_time_range';
const INITIAL_DATA = { transactionTypes: [] };
export function useServiceTransactionTypesFetcher(serviceName?: string) {
const {
query: { rangeFrom, rangeTo },
} = useApmParams('/services/{serviceName}');
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
export function useServiceTransactionTypesFetcher({
serviceName,
start,
end,
}: {
serviceName?: string;
start?: string;
end?: string;
}) {
const { data = INITIAL_DATA } = useFetcher(
(callApmApi) => {
if (serviceName && start && end) {

View file

@ -6,17 +6,19 @@
*/
import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types';
import { rangeQuery } from '../../../../../observability/server';
import {
PROCESSOR_EVENT,
SERVICE_NAME,
TRANSACTION_DURATION,
TRANSACTION_TYPE,
} from '../../../../common/elasticsearch_fieldnames';
import { ProcessorEvent } from '../../../../common/processor_event';
import { rangeQuery } from '../../../../../observability/server';
import { environmentQuery } from '../../../../common/utils/environment_query';
import { AlertParams } from '../../../routes/alerts/chart_preview';
import { getBucketSize } from '../../helpers/get_bucket_size';
import {
getDocumentTypeFilterForAggregatedTransactions,
getProcessorEventForAggregatedTransactions,
getSearchAggregatedTransactions,
getTransactionDurationFieldForAggregatedTransactions,
} from '../../helpers/aggregated_transactions';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
export async function getTransactionDurationChartPreview({
@ -26,43 +28,58 @@ export async function getTransactionDurationChartPreview({
alertParams: AlertParams;
setup: Setup & SetupTimeRange;
}) {
const searchAggregatedTransactions = await getSearchAggregatedTransactions({
...setup,
kuery: '',
});
const { apmEventClient, start, end } = setup;
const {
aggregationType,
environment,
serviceName,
transactionType,
interval,
} = alertParams;
const query = {
bool: {
filter: [
{ term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } },
...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []),
...(transactionType
? [{ term: { [TRANSACTION_TYPE]: transactionType } }]
: []),
...rangeQuery(start, end),
...environmentQuery(environment),
...getDocumentTypeFilterForAggregatedTransactions(
searchAggregatedTransactions
),
] as QueryDslQueryContainer[],
},
};
const { intervalString } = getBucketSize({ start, end, numBuckets: 20 });
const transactionDurationField = getTransactionDurationFieldForAggregatedTransactions(
searchAggregatedTransactions
);
const aggs = {
timeseries: {
date_histogram: {
field: '@timestamp',
fixed_interval: intervalString,
fixed_interval: interval,
min_doc_count: 0,
extended_bounds: {
min: start,
max: end,
},
},
aggs: {
agg:
aggregationType === 'avg'
? { avg: { field: TRANSACTION_DURATION } }
? { avg: { field: transactionDurationField } }
: {
percentiles: {
field: TRANSACTION_DURATION,
field: transactionDurationField,
percents: [aggregationType === '95th' ? 95 : 99],
},
},
@ -70,7 +87,13 @@ export async function getTransactionDurationChartPreview({
},
};
const params = {
apm: { events: [ProcessorEvent.transaction] },
apm: {
events: [
getProcessorEventForAggregatedTransactions(
searchAggregatedTransactions
),
],
},
body: { size: 0, query, aggs },
};
const resp = await apmEventClient.search(

View file

@ -10,7 +10,6 @@ import { ProcessorEvent } from '../../../../common/processor_event';
import { AlertParams } from '../../../routes/alerts/chart_preview';
import { rangeQuery } from '../../../../../observability/server';
import { environmentQuery } from '../../../../common/utils/environment_query';
import { getBucketSize } from '../../helpers/get_bucket_size';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
export async function getTransactionErrorCountChartPreview({
@ -21,7 +20,7 @@ export async function getTransactionErrorCountChartPreview({
alertParams: AlertParams;
}) {
const { apmEventClient, start, end } = setup;
const { serviceName, environment } = alertParams;
const { serviceName, environment, interval } = alertParams;
const query = {
bool: {
@ -33,13 +32,15 @@ export async function getTransactionErrorCountChartPreview({
},
};
const { intervalString } = getBucketSize({ start, end, numBuckets: 20 });
const aggs = {
timeseries: {
date_histogram: {
field: '@timestamp',
fixed_interval: intervalString,
fixed_interval: interval,
extended_bounds: {
min: start,
max: end,
},
},
},
};

View file

@ -5,16 +5,18 @@
* 2.0.
*/
import { rangeQuery } from '../../../../../observability/server';
import {
PROCESSOR_EVENT,
SERVICE_NAME,
TRANSACTION_TYPE,
} from '../../../../common/elasticsearch_fieldnames';
import { ProcessorEvent } from '../../../../common/processor_event';
import { AlertParams } from '../../../routes/alerts/chart_preview';
import { rangeQuery } from '../../../../../observability/server';
import { environmentQuery } from '../../../../common/utils/environment_query';
import { getBucketSize } from '../../helpers/get_bucket_size';
import { AlertParams } from '../../../routes/alerts/chart_preview';
import {
getDocumentTypeFilterForAggregatedTransactions,
getProcessorEventForAggregatedTransactions,
getSearchAggregatedTransactions,
} from '../../helpers/aggregated_transactions';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
import {
calculateFailedTransactionRate,
@ -28,41 +30,56 @@ export async function getTransactionErrorRateChartPreview({
setup: Setup & SetupTimeRange;
alertParams: AlertParams;
}) {
const { apmEventClient, start, end } = setup;
const { serviceName, environment, transactionType } = alertParams;
const searchAggregatedTransactions = await getSearchAggregatedTransactions({
...setup,
kuery: '',
});
const query = {
bool: {
filter: [
{ term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } },
...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []),
...(transactionType
? [{ term: { [TRANSACTION_TYPE]: transactionType } }]
: []),
...rangeQuery(start, end),
...environmentQuery(environment),
],
},
};
const { apmEventClient, start, end } = setup;
const { serviceName, environment, transactionType, interval } = alertParams;
const outcomes = getOutcomeAggregation();
const { intervalString } = getBucketSize({ start, end, numBuckets: 20 });
const aggs = {
outcomes,
timeseries: {
date_histogram: {
field: '@timestamp',
fixed_interval: intervalString,
},
aggs: { outcomes },
},
};
const params = {
apm: { events: [ProcessorEvent.transaction] },
body: { size: 0, query, aggs },
apm: {
events: [
getProcessorEventForAggregatedTransactions(
searchAggregatedTransactions
),
],
},
body: {
size: 0,
query: {
bool: {
filter: [
...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []),
...(transactionType
? [{ term: { [TRANSACTION_TYPE]: transactionType } }]
: []),
...rangeQuery(start, end),
...environmentQuery(environment),
...getDocumentTypeFilterForAggregatedTransactions(
searchAggregatedTransactions
),
],
},
},
aggs: {
outcomes,
timeseries: {
date_histogram: {
field: '@timestamp',
fixed_interval: interval,
extended_bounds: {
min: start,
max: end,
},
},
aggs: { outcomes },
},
},
},
};
const resp = await apmEventClient.search(

View file

@ -26,6 +26,9 @@ const alertParamsRt = t.intersection([
}),
environmentRt,
rangeRt,
t.type({
interval: t.string,
}),
]);
export type AlertParams = t.TypeOf<typeof alertParamsRt>;

View file

@ -24,6 +24,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
serviceName: 'opbeans-java',
transactionType: 'request' as string | undefined,
environment: 'ENVIRONMENT_ALL',
interval: '5m',
},
},
});