Skip to content

Commit b793dff

Browse files
[APM] Error rate on service list page is not in sync with the value at the transaction page (#80814) (#81149)
* fixing error rate * addressing pr comments * addressing pr comments * fixing TS issue * fixing api test
1 parent 7a3531b commit b793dff

File tree

6 files changed

+117
-95
lines changed

6 files changed

+117
-95
lines changed

x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { callApmApi } from '../../../../services/rest/createCallApmApi';
1717
// @ts-expect-error
1818
import CustomPlot from '../CustomPlot';
1919

20-
const tickFormatY = (y?: number) => {
20+
const tickFormatY = (y?: number | null) => {
2121
return asPercent(y || 0, 1);
2222
};
2323

@@ -56,7 +56,7 @@ export function ErroneousTransactionsRateChart() {
5656
[syncedChartsProps]
5757
);
5858

59-
const errorRates = data?.erroneousTransactionsRate || [];
59+
const errorRates = data?.transactionErrorRate || [];
6060
const maxRate = max(errorRates.map((errorRate) => errorRate.y));
6161

6262
return (
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { EVENT_OUTCOME } from '../../../common/elasticsearch_fieldnames';
8+
import { EventOutcome } from '../../../common/event_outcome';
9+
import {
10+
AggregationOptionsByType,
11+
AggregationResultOf,
12+
} from '../../../typings/elasticsearch/aggregations';
13+
import { getTransactionDurationFieldForAggregatedTransactions } from './aggregated_transactions';
14+
15+
export function getOutcomeAggregation({
16+
searchAggregatedTransactions,
17+
}: {
18+
searchAggregatedTransactions: boolean;
19+
}) {
20+
return {
21+
terms: { field: EVENT_OUTCOME },
22+
aggs: {
23+
count: {
24+
value_count: {
25+
field: getTransactionDurationFieldForAggregatedTransactions(
26+
searchAggregatedTransactions
27+
),
28+
},
29+
},
30+
},
31+
};
32+
}
33+
34+
export function calculateTransactionErrorPercentage(
35+
outcomeResponse: AggregationResultOf<
36+
ReturnType<typeof getOutcomeAggregation>,
37+
{}
38+
>
39+
) {
40+
const outcomes = Object.fromEntries(
41+
outcomeResponse.buckets.map(({ key, count }) => [key, count.value])
42+
);
43+
44+
const failedTransactions = outcomes[EventOutcome.failure] ?? 0;
45+
const successfulTransactions = outcomes[EventOutcome.success] ?? 0;
46+
47+
return failedTransactions / (successfulTransactions + failedTransactions);
48+
}
49+
50+
export function getTransactionErrorRateTimeSeries(
51+
buckets: AggregationResultOf<
52+
{
53+
date_histogram: AggregationOptionsByType['date_histogram'];
54+
aggs: { outcomes: ReturnType<typeof getOutcomeAggregation> };
55+
},
56+
{}
57+
>['buckets']
58+
) {
59+
return buckets.map((dateBucket) => {
60+
return {
61+
x: dateBucket.key,
62+
y: calculateTransactionErrorPercentage(dateBucket.outcomes),
63+
};
64+
});
65+
}

x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('getServiceMapServiceNodeInfo', () => {
4444
it('returns data', async () => {
4545
jest.spyOn(getErrorRateModule, 'getErrorRate').mockResolvedValueOnce({
4646
average: 0.5,
47-
erroneousTransactionsRate: [],
47+
transactionErrorRate: [],
4848
noHits: false,
4949
});
5050

x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts

Lines changed: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ import {
2828
getMLJobIds,
2929
getServiceAnomalies,
3030
} from '../../service_map/get_service_anomalies';
31-
import { AggregationResultOf } from '../../../../typings/elasticsearch/aggregations';
31+
import {
32+
calculateTransactionErrorPercentage,
33+
getOutcomeAggregation,
34+
getTransactionErrorRateTimeSeries,
35+
} from '../../helpers/transaction_error_rate';
3236

3337
function getDateHistogramOpts(start: number, end: number) {
3438
return {
@@ -261,20 +265,7 @@ export const getTransactionErrorRates = async ({
261265
}: AggregationParams) => {
262266
const { apmEventClient, start, end } = setup;
263267

264-
const outcomes = {
265-
terms: {
266-
field: EVENT_OUTCOME,
267-
},
268-
aggs: {
269-
count: {
270-
value_count: {
271-
field: getTransactionDurationFieldForAggregatedTransactions(
272-
searchAggregatedTransactions
273-
),
274-
},
275-
},
276-
},
277-
};
268+
const outcomes = getOutcomeAggregation({ searchAggregatedTransactions });
278269

279270
const response = await apmEventClient.search(
280271
mergeProjection(projection, {
@@ -326,21 +317,6 @@ export const getTransactionErrorRates = async ({
326317
return [];
327318
}
328319

329-
function calculateTransactionErrorPercentage(
330-
outcomeResponse: AggregationResultOf<typeof outcomes, {}>
331-
) {
332-
const successfulTransactions =
333-
outcomeResponse.buckets.find(
334-
(bucket) => bucket.key === EventOutcome.success
335-
)?.count.value ?? 0;
336-
const failedTransactions =
337-
outcomeResponse.buckets.find(
338-
(bucket) => bucket.key === EventOutcome.failure
339-
)?.count.value ?? 0;
340-
341-
return failedTransactions / (successfulTransactions + failedTransactions);
342-
}
343-
344320
return aggregations.services.buckets.map((serviceBucket) => {
345321
const transactionErrorRate = calculateTransactionErrorPercentage(
346322
serviceBucket.outcomes
@@ -349,12 +325,9 @@ export const getTransactionErrorRates = async ({
349325
serviceName: serviceBucket.key as string,
350326
transactionErrorRate: {
351327
value: transactionErrorRate,
352-
timeseries: serviceBucket.timeseries.buckets.map((dateBucket) => {
353-
return {
354-
x: dateBucket.key,
355-
y: calculateTransactionErrorPercentage(dateBucket.outcomes),
356-
};
357-
}),
328+
timeseries: getTransactionErrorRateTimeSeries(
329+
serviceBucket.timeseries.buckets
330+
),
358331
},
359332
};
360333
});

x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts

Lines changed: 31 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,25 @@
33
* or more contributor license agreements. Licensed under the Elastic License;
44
* you may not use this file except in compliance with the Elastic License.
55
*/
6-
import { mean } from 'lodash';
7-
import { EventOutcome } from '../../../common/event_outcome';
6+
7+
import { Coordinate } from '../../../typings/timeseries';
8+
89
import {
10+
EVENT_OUTCOME,
11+
SERVICE_NAME,
912
TRANSACTION_NAME,
1013
TRANSACTION_TYPE,
11-
SERVICE_NAME,
12-
EVENT_OUTCOME,
1314
} from '../../../common/elasticsearch_fieldnames';
15+
import { EventOutcome } from '../../../common/event_outcome';
1416
import { rangeFilter } from '../../../common/utils/range_filter';
15-
import { Setup, SetupTimeRange } from '../helpers/setup_request';
17+
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
1618
import { getBucketSize } from '../helpers/get_bucket_size';
19+
import { Setup, SetupTimeRange } from '../helpers/setup_request';
1720
import {
18-
getProcessorEventForAggregatedTransactions,
19-
getTransactionDurationFieldForAggregatedTransactions,
20-
} from '../helpers/aggregated_transactions';
21+
calculateTransactionErrorPercentage,
22+
getOutcomeAggregation,
23+
getTransactionErrorRateTimeSeries,
24+
} from '../helpers/transaction_error_rate';
2125

2226
export async function getErrorRate({
2327
serviceName,
@@ -31,7 +35,11 @@ export async function getErrorRate({
3135
transactionName?: string;
3236
setup: Setup & SetupTimeRange;
3337
searchAggregatedTransactions: boolean;
34-
}) {
38+
}): Promise<{
39+
noHits: boolean;
40+
transactionErrorRate: Coordinate[];
41+
average: number | null;
42+
}> {
3543
const { start, end, esFilter, apmEventClient } = setup;
3644

3745
const transactionNamefilter = transactionName
@@ -52,6 +60,8 @@ export async function getErrorRate({
5260
...esFilter,
5361
];
5462

63+
const outcomes = getOutcomeAggregation({ searchAggregatedTransactions });
64+
5565
const params = {
5666
apm: {
5767
events: [
@@ -64,28 +74,16 @@ export async function getErrorRate({
6474
size: 0,
6575
query: { bool: { filter } },
6676
aggs: {
67-
total_transactions: {
77+
outcomes,
78+
timeseries: {
6879
date_histogram: {
6980
field: '@timestamp',
7081
fixed_interval: getBucketSize(start, end).intervalString,
7182
min_doc_count: 0,
7283
extended_bounds: { min: start, max: end },
7384
},
7485
aggs: {
75-
[EVENT_OUTCOME]: {
76-
terms: {
77-
field: EVENT_OUTCOME,
78-
},
79-
aggs: {
80-
count: {
81-
value_count: {
82-
field: getTransactionDurationFieldForAggregatedTransactions(
83-
searchAggregatedTransactions
84-
),
85-
},
86-
},
87-
},
88-
},
86+
outcomes,
8987
},
9088
},
9189
},
@@ -96,31 +94,17 @@ export async function getErrorRate({
9694

9795
const noHits = resp.hits.total.value === 0;
9896

99-
const erroneousTransactionsRate =
100-
resp.aggregations?.total_transactions.buckets.map((bucket) => {
101-
const successful =
102-
bucket[EVENT_OUTCOME].buckets.find(
103-
(eventOutcomeBucket) =>
104-
eventOutcomeBucket.key === EventOutcome.success
105-
)?.count.value ?? 0;
106-
107-
const failed =
108-
bucket[EVENT_OUTCOME].buckets.find(
109-
(eventOutcomeBucket) =>
110-
eventOutcomeBucket.key === EventOutcome.failure
111-
)?.count.value ?? 0;
97+
if (!resp.aggregations) {
98+
return { noHits, transactionErrorRate: [], average: null };
99+
}
112100

113-
return {
114-
x: bucket.key,
115-
y: failed / (successful + failed),
116-
};
117-
}) || [];
101+
const transactionErrorRate = getTransactionErrorRateTimeSeries(
102+
resp.aggregations.timeseries.buckets
103+
);
118104

119-
const average = mean(
120-
erroneousTransactionsRate
121-
.map((errorRate) => errorRate.y)
122-
.filter((y) => isFinite(y))
105+
const average = calculateTransactionErrorPercentage(
106+
resp.aggregations.outcomes
123107
);
124108

125-
return { noHits, erroneousTransactionsRate, average };
109+
return { noHits, transactionErrorRate, average };
126110
}

x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
3131

3232
expect(response.body.noHits).to.be(true);
3333

34-
expect(response.body.erroneousTransactionsRate.length).to.be(0);
34+
expect(response.body.transactionErrorRate.length).to.be(0);
3535
expect(response.body.average).to.be(null);
3636
});
3737
});
@@ -41,7 +41,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
4141

4242
describe('returns the transaction error rate', () => {
4343
let errorRateResponse: {
44-
erroneousTransactionsRate: Array<{ x: number; y: number | null }>;
44+
transactionErrorRate: Array<{ x: number; y: number | null }>;
4545
average: number;
4646
};
4747
before(async () => {
@@ -54,9 +54,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
5454
it('returns some data', () => {
5555
expect(errorRateResponse.average).to.be.greaterThan(0);
5656

57-
expect(errorRateResponse.erroneousTransactionsRate.length).to.be.greaterThan(0);
57+
expect(errorRateResponse.transactionErrorRate.length).to.be.greaterThan(0);
5858

59-
const nonNullDataPoints = errorRateResponse.erroneousTransactionsRate.filter(
59+
const nonNullDataPoints = errorRateResponse.transactionErrorRate.filter(
6060
({ y }) => y !== null
6161
);
6262

@@ -65,26 +65,26 @@ export default function ApiTest({ getService }: FtrProviderContext) {
6565

6666
it('has the correct start date', () => {
6767
expectSnapshot(
68-
new Date(first(errorRateResponse.erroneousTransactionsRate)?.x ?? NaN).toISOString()
68+
new Date(first(errorRateResponse.transactionErrorRate)?.x ?? NaN).toISOString()
6969
).toMatchInline(`"2020-09-29T14:30:00.000Z"`);
7070
});
7171

7272
it('has the correct end date', () => {
7373
expectSnapshot(
74-
new Date(last(errorRateResponse.erroneousTransactionsRate)?.x ?? NaN).toISOString()
74+
new Date(last(errorRateResponse.transactionErrorRate)?.x ?? NaN).toISOString()
7575
).toMatchInline(`"2020-09-29T15:00:00.000Z"`);
7676
});
7777

7878
it('has the correct number of buckets', () => {
79-
expectSnapshot(errorRateResponse.erroneousTransactionsRate.length).toMatchInline(`61`);
79+
expectSnapshot(errorRateResponse.transactionErrorRate.length).toMatchInline(`61`);
8080
});
8181

8282
it('has the correct calculation for average', () => {
83-
expectSnapshot(errorRateResponse.average).toMatchInline(`0.200076804915515`);
83+
expectSnapshot(errorRateResponse.average).toMatchInline(`0.152173913043478`);
8484
});
8585

8686
it('has the correct error rate', () => {
87-
expectSnapshot(errorRateResponse.erroneousTransactionsRate).toMatch();
87+
expectSnapshot(errorRateResponse.transactionErrorRate).toMatch();
8888
});
8989
});
9090
});

0 commit comments

Comments
 (0)