Skip to content

Commit 05ce575

Browse files
authored
Closes #80629, with proper timeout messaging and docs for user to work around the scalability issue. (#82083) (#82093)
1 parent 123d09c commit 05ce575

6 files changed

Lines changed: 105 additions & 22 deletions

File tree

docs/settings/apm-settings.asciidoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ Changing these settings may disable features of the APM App.
4848
| `xpack.apm.enabled`
4949
| Set to `false` to disable the APM app. Defaults to `true`.
5050

51+
| `xpack.apm.serviceMapFingerprintBucketSize`
52+
| Maximum number of unique transaction combinations sampled for generating service map focused on a specific service. Defaults to `100`.
53+
54+
| `xpack.apm.serviceMapFingerprintGlobalBucketSize`
55+
| Maximum number of unique transaction combinations sampled for generating the global service map. Defaults to `100`.
56+
5157
| `xpack.apm.ui.enabled` {ess-icon}
5258
| Set to `false` to hide the APM app from the main menu. Defaults to `true`.
5359

x-pack/plugins/apm/common/service_map.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,5 @@ export function isSpanGroupingSupported(type?: string, subtype?: string) {
9191
nongroupedSubType === 'all' || nongroupedSubType === subtype
9292
);
9393
}
94+
95+
export const SERVICE_MAP_TIMEOUT_ERROR = 'ServiceMapTimeoutError';

x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useTrackPageview } from '../../../../../observability/public';
1010
import {
1111
invalidLicenseMessage,
1212
isActivePlatinumLicense,
13+
SERVICE_MAP_TIMEOUT_ERROR,
1314
} from '../../../../common/service_map';
1415
import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher';
1516
import { useLicense } from '../../../hooks/useLicense';
@@ -22,6 +23,7 @@ import { Cytoscape } from './Cytoscape';
2223
import { getCytoscapeDivStyle } from './cytoscape_options';
2324
import { EmptyBanner } from './EmptyBanner';
2425
import { EmptyPrompt } from './empty_prompt';
26+
import { TimeoutPrompt } from './timeout_prompt';
2527
import { Popover } from './Popover';
2628
import { useRefDimensions } from './useRefDimensions';
2729

@@ -61,7 +63,7 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
6163
const license = useLicense();
6264
const { urlParams } = useUrlParams();
6365

64-
const { data = { elements: [] }, status } = useFetcher(() => {
66+
const { data = { elements: [] }, status, error } = useFetcher(() => {
6567
// When we don't have a license or a valid license, don't make the request.
6668
if (!license || !isActivePlatinumLicense(license)) {
6769
return;
@@ -109,6 +111,20 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
109111
);
110112
}
111113

114+
if (
115+
status === FETCH_STATUS.FAILURE &&
116+
error &&
117+
'body' in error &&
118+
error.body.statusCode === 500 &&
119+
error.body.message === SERVICE_MAP_TIMEOUT_ERROR
120+
) {
121+
return (
122+
<PromptContainer>
123+
<TimeoutPrompt isGlobalServiceMap={!serviceName} />
124+
</PromptContainer>
125+
);
126+
}
127+
112128
return (
113129
<div
114130
data-test-subj="ServiceMap"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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 { EuiEmptyPrompt } from '@elastic/eui';
8+
import { i18n } from '@kbn/i18n';
9+
import React from 'react';
10+
import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink';
11+
12+
export function TimeoutPrompt({
13+
isGlobalServiceMap,
14+
}: {
15+
isGlobalServiceMap: boolean;
16+
}) {
17+
return (
18+
<EuiEmptyPrompt
19+
iconType="alert"
20+
iconColor="subdued"
21+
title={
22+
<h2>
23+
{i18n.translate('xpack.apm.serviceMap.timeoutPromptTitle', {
24+
defaultMessage: 'Service map timeout',
25+
})}
26+
</h2>
27+
}
28+
body={
29+
<p>
30+
{i18n.translate('xpack.apm.serviceMap.timeoutPromptDescription', {
31+
defaultMessage: `Timed out while fetching data for service map. Limit the scope by selecting a smaller time range, or use configuration setting '{configName}' with a reduced value.`,
32+
values: {
33+
configName: isGlobalServiceMap
34+
? 'xpack.apm.serviceMapFingerprintGlobalBucketSize'
35+
: 'xpack.apm.serviceMapFingerprintBucketSize',
36+
},
37+
})}
38+
</p>
39+
}
40+
actions={<ApmSettingsDocLink />}
41+
/>
42+
);
43+
}
44+
45+
function ApmSettingsDocLink() {
46+
return (
47+
<ElasticDocsLink section="/kibana" path="/apm-settings-in-kibana.html">
48+
{i18n.translate('xpack.apm.serviceMap.timeoutPrompt.docsLink', {
49+
defaultMessage: 'Learn more about APM settings in the docs',
50+
})}
51+
</ElasticDocsLink>
52+
);
53+
}

x-pack/plugins/apm/public/hooks/useFetcher.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export enum FETCH_STATUS {
2121
export interface FetcherResult<Data> {
2222
data?: Data;
2323
status: FETCH_STATUS;
24-
error?: Error;
24+
error?: IHttpFetchError;
2525
}
2626

2727
// fetcher functions can return undefined OR a promise. Previously we had a more simple type

x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66
import { uniq, take, sortBy } from 'lodash';
7+
import Boom from 'boom';
78
import { ProcessorEvent } from '../../../common/processor_event';
89
import { Setup, SetupTimeRange } from '../helpers/setup_request';
910
import { rangeFilter } from '../../../common/utils/range_filter';
@@ -15,6 +16,7 @@ import {
1516
SPAN_DESTINATION_SERVICE_RESOURCE,
1617
} from '../../../common/elasticsearch_fieldnames';
1718
import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es';
19+
import { SERVICE_MAP_TIMEOUT_ERROR } from '../../../common/service_map';
1820

1921
const MAX_TRACES_TO_INSPECT = 1000;
2022

@@ -122,26 +124,30 @@ export async function getTraceSampleIds({
122124
},
123125
};
124126

125-
const tracesSampleResponse = await apmEventClient.search(params);
127+
try {
128+
const tracesSampleResponse = await apmEventClient.search(params);
129+
// make sure at least one trace per composite/connection bucket
130+
// is queried
131+
const traceIdsWithPriority =
132+
tracesSampleResponse.aggregations?.connections.buckets.flatMap((bucket) =>
133+
bucket.sample.trace_ids.buckets.map((sampleDocBucket, index) => ({
134+
traceId: sampleDocBucket.key as string,
135+
priority: index,
136+
}))
137+
) || [];
126138

127-
// make sure at least one trace per composite/connection bucket
128-
// is queried
129-
const traceIdsWithPriority =
130-
tracesSampleResponse.aggregations?.connections.buckets.flatMap((bucket) =>
131-
bucket.sample.trace_ids.buckets.map((sampleDocBucket, index) => ({
132-
traceId: sampleDocBucket.key as string,
133-
priority: index,
134-
}))
135-
) || [];
139+
const traceIds = take(
140+
uniq(
141+
sortBy(traceIdsWithPriority, 'priority').map(({ traceId }) => traceId)
142+
),
143+
MAX_TRACES_TO_INSPECT
144+
);
136145

137-
const traceIds = take(
138-
uniq(
139-
sortBy(traceIdsWithPriority, 'priority').map(({ traceId }) => traceId)
140-
),
141-
MAX_TRACES_TO_INSPECT
142-
);
143-
144-
return {
145-
traceIds,
146-
};
146+
return { traceIds };
147+
} catch (error) {
148+
if ('displayName' in error && error.displayName === 'RequestTimeout') {
149+
throw Boom.internal(SERVICE_MAP_TIMEOUT_ERROR);
150+
}
151+
throw error;
152+
}
147153
}

0 commit comments

Comments
 (0)