Skip to content

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,
},
},
},
},
aggs,
},
});
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;
});
}

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 };
}