Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[APM] Use status_code field to calculate error rate #71109

Merged
merged 10 commits into from
Jul 14, 2020

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions x-pack/plugins/apm/common/elasticsearch_fieldnames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const AGENT_VERSION = 'agent.version';

export const URL_FULL = 'url.full';
export const HTTP_REQUEST_METHOD = 'http.request.method';
export const HTTP_RESPONSE_STATUS_CODE = 'http.response.status_code';
export const USER_ID = 'user.id';
export const USER_AGENT_ORIGINAL = 'user_agent.original';
export const USER_AGENT_NAME = 'user_agent.name';
Expand Down
102 changes: 53 additions & 49 deletions x-pack/plugins/apm/server/lib/errors/get_error_rate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,106 +4,110 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
ERROR_GROUP_ID,
HTTP_RESPONSE_STATUS_CODE,
PROCESSOR_EVENT,
SERVICE_NAME,
} from '../../../common/elasticsearch_fieldnames';
import { ProcessorEvent } from '../../../common/processor_event';
import { rangeFilter } from '../../../common/utils/range_filter';
import { getMetricsDateHistogramParams } from '../helpers/metrics';
import {
Setup,
SetupTimeRange,
SetupUIFilters,
} from '../helpers/setup_request';
import { rangeFilter } from '../../../common/utils/range_filter';

export async function getErrorRate({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add an api test for GET /api/apm/services/{serviceName}/errors/rate to cover this?

serviceName,
groupId,
setup,
}: {
serviceName: string;
groupId?: string;
setup: Setup & SetupTimeRange & SetupUIFilters;
}) {
const { start, end, uiFiltersES, client, indices } = setup;

const filter = [
{ term: { [SERVICE_NAME]: serviceName } },
{ term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } },
{ range: rangeFilter(start, end) },
...uiFiltersES,
];

const aggs = {
response_times: {
const must = [{ exists: { field: HTTP_RESPONSE_STATUS_CODE } }];

const dateHistogramAggs = {
histogram: {
date_histogram: getMetricsDateHistogramParams(start, end),
},
};
sorenlouv marked this conversation as resolved.
Show resolved Hide resolved

const getTransactionBucketAggregation = async () => {
const resp = await client.search({
const getTransactionsCount = async () => {
const transactionsCountParams = {
index: indices['apm_oss.transactionIndices'],
body: {
size: 0,
query: {
bool: {
filter: [
...filter,
{ term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } },
],
},
},
aggs,
query: { bool: { must, filter } },
aggs: dateHistogramAggs,
},
});
};

const resp = await client.search(transactionsCountParams);
const transactionsCountByTimestamp: Record<number, number> = {};
if (resp.aggregations) {
resp.aggregations.histogram.buckets.forEach(
(bucket) =>
(transactionsCountByTimestamp[bucket.key] = bucket.doc_count)
);
}
return {
totalHits: resp.hits.total.value,
responseTimeBuckets: resp.aggregations?.response_times.buckets,
transactionsCountByTimestamp,
noHits: resp.hits.total.value === 0,
};
};
const getErrorBucketAggregation = async () => {
const groupIdFilter = groupId
? [{ term: { [ERROR_GROUP_ID]: groupId } }]
: [];
const resp = await client.search({
index: indices['apm_oss.errorIndices'],

const getErroneousTransactionsCount = async () => {
const erroneousTransactionsCountParams = {
index: indices['apm_oss.transactionIndices'],
body: {
size: 0,
query: {
bool: {
must,
filter: [
...filter,
...groupIdFilter,
{ term: { [PROCESSOR_EVENT]: ProcessorEvent.error } },
{
range: {
[HTTP_RESPONSE_STATUS_CODE]: {
gte: 400, // everything equals or above 400 should be treated as an error
},
},
},
],
},
},
aggs,
aggs: dateHistogramAggs,
},
});
sorenlouv marked this conversation as resolved.
Show resolved Hide resolved
return resp.aggregations?.response_times.buckets;
};
const resp = await client.search(erroneousTransactionsCountParams);

return resp.aggregations?.histogram.buckets;
};

const [transactions, errorResponseTimeBuckets] = await Promise.all([
getTransactionBucketAggregation(),
getErrorBucketAggregation(),
const [transactionsCount, erroneousTransactionsCount] = await Promise.all([
getTransactionsCount(),
getErroneousTransactionsCount(),
]);

const transactionCountByTimestamp: Record<number, number> = {};
if (transactions?.responseTimeBuckets) {
transactions.responseTimeBuckets.forEach((bucket) => {
transactionCountByTimestamp[bucket.key] = bucket.doc_count;
});
}
const { transactionsCountByTimestamp, noHits } = transactionsCount;

const errorRates = errorResponseTimeBuckets?.map((bucket) => {
const { key, doc_count: errorCount } = bucket;
const relativeRate = errorCount / transactionCountByTimestamp[key];
return { x: key, y: relativeRate };
});
const errorRates =
erroneousTransactionsCount?.map(({ key, doc_count: errorCount }) => {
const transactionsTotalCount = transactionsCountByTimestamp[key];
return {
x: key,
y: errorCount / transactionsTotalCount,
};
}) || [];

return {
noHits: transactions?.totalHits === 0,
errorRates,
};
return { noHits, errorRates };
}
11 changes: 2 additions & 9 deletions x-pack/plugins/apm/server/routes/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,12 @@ export const errorRateRoute = createRoute(() => ({
path: t.type({
serviceName: t.string,
}),
query: t.intersection([
t.partial({
groupId: t.string,
}),
uiFiltersRt,
rangeRt,
]),
query: t.intersection([uiFiltersRt, rangeRt]),
},
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
const { params } = context;
const { serviceName } = params.path;
const { groupId } = params.query;
return getErrorRate({ serviceName, groupId, setup });
return getErrorRate({ serviceName, setup });
cauemarcondes marked this conversation as resolved.
Show resolved Hide resolved
},
}));