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: 37 additions & 65 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,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import {
ERROR_GROUP_ID,
PROCESSOR_EVENT,
SERVICE_NAME,
HTTP_RESPONSE_STATUS_CODE,
} from '../../../common/elasticsearch_fieldnames';
import { ProcessorEvent } from '../../../common/processor_event';
import { getMetricsDateHistogramParams } from '../helpers/metrics';
Expand All @@ -17,6 +17,9 @@ import {
} from '../helpers/setup_request';
import { rangeFilter } from '../../../common/utils/range_filter';

// Regex for 5xx and 4xx
const errorStatusCodeRegex = /5\d{2}|4\d{2}/;

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,
Expand All @@ -30,80 +33,49 @@ export async function getErrorRate({

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

const aggs = {
response_times: {
date_histogram: getMetricsDateHistogramParams(start, end),
},
};

const getTransactionBucketAggregation = async () => {
const resp = await client.search({
index: indices['apm_oss.transactionIndices'],
body: {
size: 0,
query: {
bool: {
filter: [
...filter,
{ term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } },
],
},
},
aggs,
const params = {
index: indices['apm_oss.transactionIndices'],
body: {
size: 0,
query: {
bool: { filter },
},
});
return {
totalHits: resp.hits.total.value,
responseTimeBuckets: resp.aggregations?.response_times.buckets,
};
};
const getErrorBucketAggregation = async () => {
const groupIdFilter = groupId
? [{ term: { [ERROR_GROUP_ID]: groupId } }]
: [];
const resp = await client.search({
index: indices['apm_oss.errorIndices'],
body: {
size: 0,
query: {
bool: {
filter: [
...filter,
...groupIdFilter,
{ term: { [PROCESSOR_EVENT]: ProcessorEvent.error } },
],
aggs: {
histogram: {
date_histogram: getMetricsDateHistogramParams(start, end),
aggs: {
statusAggregation: {
terms: {
field: HTTP_RESPONSE_STATUS_CODE,
size: 10,
sorenlouv marked this conversation as resolved.
Show resolved Hide resolved
},
},
},
},
aggs,
},
});
sorenlouv marked this conversation as resolved.
Show resolved Hide resolved
return resp.aggregations?.response_times.buckets;
},
};
const resp = await client.search(params);
const noHits = resp.hits.total.value === 0;

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

const transactionCountByTimestamp: Record<number, number> = {};
if (transactions?.responseTimeBuckets) {
transactions.responseTimeBuckets.forEach((bucket) => {
transactionCountByTimestamp[bucket.key] = bucket.doc_count;
const errorRates = resp.aggregations?.histogram.buckets.map((bucket) => {
let errorCount = 0;
let total = 0;
bucket.statusAggregation.buckets.forEach(({ key, doc_count: count }) => {
if (errorStatusCodeRegex.test(key.toString())) {
errorCount += count;
}
total += count;
});
sorenlouv marked this conversation as resolved.
Show resolved Hide resolved
}

const errorRates = errorResponseTimeBuckets?.map((bucket) => {
const { key, doc_count: errorCount } = bucket;
const relativeRate = errorCount / transactionCountByTimestamp[key];
return { x: key, y: relativeRate };
return {
x: bucket.key,
y: noHits ? null : errorCount / total,
};
});

return {
noHits: transactions?.totalHits === 0,
errorRates,
};
return { noHits, errorRates };
}