Skip to content

Commit

Permalink
[APM] Error rate on service list page is not in sync with the value a…
Browse files Browse the repository at this point in the history
…t the transaction page (elastic#80814) (elastic#81148)

* fixing error rate

* addressing pr comments

* addressing pr comments

* fixing TS issue

* fixing api test
  • Loading branch information
cauemarcondes authored Oct 20, 2020
1 parent 530c2ec commit 6b29f9f
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { callApmApi } from '../../../../services/rest/createCallApmApi';
// @ts-expect-error
import CustomPlot from '../CustomPlot';

const tickFormatY = (y?: number) => {
const tickFormatY = (y?: number | null) => {
return asPercent(y || 0, 1);
};

Expand Down Expand Up @@ -56,7 +56,7 @@ export function ErroneousTransactionsRateChart() {
[syncedChartsProps]
);

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

return (
Expand Down
65 changes: 65 additions & 0 deletions x-pack/plugins/apm/server/lib/helpers/transaction_error_rate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { EVENT_OUTCOME } from '../../../common/elasticsearch_fieldnames';
import { EventOutcome } from '../../../common/event_outcome';
import {
AggregationOptionsByType,
AggregationResultOf,
} from '../../../typings/elasticsearch/aggregations';
import { getTransactionDurationFieldForAggregatedTransactions } from './aggregated_transactions';

export function getOutcomeAggregation({
searchAggregatedTransactions,
}: {
searchAggregatedTransactions: boolean;
}) {
return {
terms: { field: EVENT_OUTCOME },
aggs: {
count: {
value_count: {
field: getTransactionDurationFieldForAggregatedTransactions(
searchAggregatedTransactions
),
},
},
},
};
}

export function calculateTransactionErrorPercentage(
outcomeResponse: AggregationResultOf<
ReturnType<typeof getOutcomeAggregation>,
{}
>
) {
const outcomes = Object.fromEntries(
outcomeResponse.buckets.map(({ key, count }) => [key, count.value])
);

const failedTransactions = outcomes[EventOutcome.failure] ?? 0;
const successfulTransactions = outcomes[EventOutcome.success] ?? 0;

return failedTransactions / (successfulTransactions + failedTransactions);
}

export function getTransactionErrorRateTimeSeries(
buckets: AggregationResultOf<
{
date_histogram: AggregationOptionsByType['date_histogram'];
aggs: { outcomes: ReturnType<typeof getOutcomeAggregation> };
},
{}
>['buckets']
) {
return buckets.map((dateBucket) => {
return {
x: dateBucket.key,
y: calculateTransactionErrorPercentage(dateBucket.outcomes),
};
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('getServiceMapServiceNodeInfo', () => {
it('returns data', async () => {
jest.spyOn(getErrorRateModule, 'getErrorRate').mockResolvedValueOnce({
average: 0.5,
erroneousTransactionsRate: [],
transactionErrorRate: [],
noHits: false,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ import {
getMLJobIds,
getServiceAnomalies,
} from '../../service_map/get_service_anomalies';
import { AggregationResultOf } from '../../../../typings/elasticsearch/aggregations';
import {
calculateTransactionErrorPercentage,
getOutcomeAggregation,
getTransactionErrorRateTimeSeries,
} from '../../helpers/transaction_error_rate';

function getDateHistogramOpts(start: number, end: number) {
return {
Expand Down Expand Up @@ -261,20 +265,7 @@ export const getTransactionErrorRates = async ({
}: AggregationParams) => {
const { apmEventClient, start, end } = setup;

const outcomes = {
terms: {
field: EVENT_OUTCOME,
},
aggs: {
count: {
value_count: {
field: getTransactionDurationFieldForAggregatedTransactions(
searchAggregatedTransactions
),
},
},
},
};
const outcomes = getOutcomeAggregation({ searchAggregatedTransactions });

const response = await apmEventClient.search(
mergeProjection(projection, {
Expand Down Expand Up @@ -326,21 +317,6 @@ export const getTransactionErrorRates = async ({
return [];
}

function calculateTransactionErrorPercentage(
outcomeResponse: AggregationResultOf<typeof outcomes, {}>
) {
const successfulTransactions =
outcomeResponse.buckets.find(
(bucket) => bucket.key === EventOutcome.success
)?.count.value ?? 0;
const failedTransactions =
outcomeResponse.buckets.find(
(bucket) => bucket.key === EventOutcome.failure
)?.count.value ?? 0;

return failedTransactions / (successfulTransactions + failedTransactions);
}

return aggregations.services.buckets.map((serviceBucket) => {
const transactionErrorRate = calculateTransactionErrorPercentage(
serviceBucket.outcomes
Expand All @@ -349,12 +325,9 @@ export const getTransactionErrorRates = async ({
serviceName: serviceBucket.key as string,
transactionErrorRate: {
value: transactionErrorRate,
timeseries: serviceBucket.timeseries.buckets.map((dateBucket) => {
return {
x: dateBucket.key,
y: calculateTransactionErrorPercentage(dateBucket.outcomes),
};
}),
timeseries: getTransactionErrorRateTimeSeries(
serviceBucket.timeseries.buckets
),
},
};
});
Expand Down
78 changes: 31 additions & 47 deletions x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,25 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { mean } from 'lodash';
import { EventOutcome } from '../../../common/event_outcome';

import { Coordinate } from '../../../typings/timeseries';

import {
EVENT_OUTCOME,
SERVICE_NAME,
TRANSACTION_NAME,
TRANSACTION_TYPE,
SERVICE_NAME,
EVENT_OUTCOME,
} from '../../../common/elasticsearch_fieldnames';
import { EventOutcome } from '../../../common/event_outcome';
import { rangeFilter } from '../../../common/utils/range_filter';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions';
import { getBucketSize } from '../helpers/get_bucket_size';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
import {
getProcessorEventForAggregatedTransactions,
getTransactionDurationFieldForAggregatedTransactions,
} from '../helpers/aggregated_transactions';
calculateTransactionErrorPercentage,
getOutcomeAggregation,
getTransactionErrorRateTimeSeries,
} from '../helpers/transaction_error_rate';

export async function getErrorRate({
serviceName,
Expand All @@ -31,7 +35,11 @@ export async function getErrorRate({
transactionName?: string;
setup: Setup & SetupTimeRange;
searchAggregatedTransactions: boolean;
}) {
}): Promise<{
noHits: boolean;
transactionErrorRate: Coordinate[];
average: number | null;
}> {
const { start, end, esFilter, apmEventClient } = setup;

const transactionNamefilter = transactionName
Expand All @@ -52,6 +60,8 @@ export async function getErrorRate({
...esFilter,
];

const outcomes = getOutcomeAggregation({ searchAggregatedTransactions });

const params = {
apm: {
events: [
Expand All @@ -64,28 +74,16 @@ export async function getErrorRate({
size: 0,
query: { bool: { filter } },
aggs: {
total_transactions: {
outcomes,
timeseries: {
date_histogram: {
field: '@timestamp',
fixed_interval: getBucketSize(start, end).intervalString,
min_doc_count: 0,
extended_bounds: { min: start, max: end },
},
aggs: {
[EVENT_OUTCOME]: {
terms: {
field: EVENT_OUTCOME,
},
aggs: {
count: {
value_count: {
field: getTransactionDurationFieldForAggregatedTransactions(
searchAggregatedTransactions
),
},
},
},
},
outcomes,
},
},
},
Expand All @@ -96,31 +94,17 @@ export async function getErrorRate({

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

const erroneousTransactionsRate =
resp.aggregations?.total_transactions.buckets.map((bucket) => {
const successful =
bucket[EVENT_OUTCOME].buckets.find(
(eventOutcomeBucket) =>
eventOutcomeBucket.key === EventOutcome.success
)?.count.value ?? 0;

const failed =
bucket[EVENT_OUTCOME].buckets.find(
(eventOutcomeBucket) =>
eventOutcomeBucket.key === EventOutcome.failure
)?.count.value ?? 0;
if (!resp.aggregations) {
return { noHits, transactionErrorRate: [], average: null };
}

return {
x: bucket.key,
y: failed / (successful + failed),
};
}) || [];
const transactionErrorRate = getTransactionErrorRateTimeSeries(
resp.aggregations.timeseries.buckets
);

const average = mean(
erroneousTransactionsRate
.map((errorRate) => errorRate.y)
.filter((y) => isFinite(y))
const average = calculateTransactionErrorPercentage(
resp.aggregations.outcomes
);

return { noHits, erroneousTransactionsRate, average };
return { noHits, transactionErrorRate, average };
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {

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

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

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

expect(errorRateResponse.erroneousTransactionsRate.length).to.be.greaterThan(0);
expect(errorRateResponse.transactionErrorRate.length).to.be.greaterThan(0);

const nonNullDataPoints = errorRateResponse.erroneousTransactionsRate.filter(
const nonNullDataPoints = errorRateResponse.transactionErrorRate.filter(
({ y }) => y !== null
);

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

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

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

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

it('has the correct calculation for average', () => {
expectSnapshot(errorRateResponse.average).toMatchInline(`0.200076804915515`);
expectSnapshot(errorRateResponse.average).toMatchInline(`0.152173913043478`);
});

it('has the correct error rate', () => {
expectSnapshot(errorRateResponse.erroneousTransactionsRate).toMatch();
expectSnapshot(errorRateResponse.transactionErrorRate).toMatch();
});
});
});
Expand Down

0 comments on commit 6b29f9f

Please sign in to comment.