[APM] Show warning if transaction groups are dropped (#148625)

Closes https://github.com/elastic/kibana/issues/146648.

### Changes
- `transaction_detail_link.tsx` prevent navigation to tx details when
bucket name is other and display the warning tooltip
- `xpack.apm.ui.transactionGroupBucketSize` ui setting was removed and
now we have a hardcoded limit of `1000` buckets when querying
transactions.

**After this change**


https://user-images.githubusercontent.com/1313018/214321647-e0ae59d6-0761-4d9e-a0fe-ef21397aeff7.mov


- When kibana limit has been reached
<img width="1391" alt="image"
src="https://user-images.githubusercontent.com/1313018/214321902-05fbfa6c-7c99-4b21-a67b-7e134c14ab73.png">

- When having only `_other`
<img width="1390" alt="image"
src="https://user-images.githubusercontent.com/1313018/214332887-d6fd59a2-d1ef-4b61-b6ed-79a6d458f0a0.png">

### Test instructions
1. Checkout PR branch
2. Execute Synthtrace scenario
    a. For reaching kibana limit callout
        ```
node scripts/synthtrace --clean other_transaction_group_bucket.ts
--scenarioOpts.txGroups=1001
        ```
    b. For `_other` callout
        ```
node scripts/synthtrace --clean other_transaction_group_bucket.ts
        ```

---------

Co-authored-by: Achyut Jhunjhunwala <achyut.jhunjhunwala@elastic.co>
This commit is contained in:
Yngrid Coello 2023-01-30 15:40:25 +01:00 committed by GitHub
parent 1a0538872c
commit 3d3a885ad7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 357 additions and 238 deletions

View file

@ -93,13 +93,19 @@ Instead, we should strip away the unique information and group our transactions
In this case, that means naming all blog transactions, `/blog`, and all documentation transactions, `/guide`.
If you feel like you'd be losing valuable information by following this naming convention, don't fret!
You can always add additional metadata to your transactions using {apm-guide-ref-v}/metadata.html#labels-fields[labels] (indexed) or
{apm-guide-ref-v}/metadata.html#custom-fields[custom context] (non-indexed).
You can always add additional metadata to your transactions using {apm-guide-ref}/metadata.html#labels-fields[labels] (indexed) or
{apm-guide-ref}/metadata.html#custom-fields[custom context] (non-indexed).
After ensuring you've correctly named your transactions,
you might still see an error in the APM app related to too many transaction names.
If this is the case, you can increase the default number of transaction groups displayed in the APM app by configuring
<<apm-settings-kb,`xpack.apm.ui.transactionGroupBucketSize`>>.
you might still see errors in the APM app related to transaction group limit reached:
`The number of transaction groups has been reached. Current APM server capacity for handling unique transaction groups has been reached. There are at least X transactions missing in this list. Please decrease the number of transaction groups in your service or increase the memory allocated to APM server.`
You will see this warning if an agent is creating too many transaction groups. This could indicate incorrect instrumentation which will have to be fixed in your application. Alternatively you can increase the memory of the APM server.
`Number of transaction groups exceed the allowed maximum(1,000) that are displayed. The maximum number of transaction groups displayed in Kibana has been reached. Try narrowing down results by using the query bar..`
You will see this warning if your results have more than `1000` unique transaction groups. Alternatively you can use the query bar to reduce the number of unique transaction groups in your results.
**More information**

View file

@ -68,9 +68,6 @@ Maximum number of traces per request for generating the global service map. Defa
`xpack.apm.ui.enabled` {ess-icon}::
Set to `false` to hide the APM app from the main menu. Defaults to `true`.
`xpack.apm.ui.transactionGroupBucketSize` {ess-icon}::
Number of top transaction groups displayed in the APM app. Defaults to `1000`.
`xpack.apm.ui.maxTraceItems` {ess-icon}::
Maximum number of child items displayed when viewing trace details. Defaults to `1000`.

View file

@ -130,6 +130,7 @@ export type ApmFields = Fields<{
'processor.name': string;
'session.id': string;
'trace.id': string;
'transaction.aggregation.overflow_count': number;
'transaction.duration.us': number;
'transaction.id': string;
'transaction.name': string;

View file

@ -0,0 +1,81 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { apm, ApmFields } from '@kbn/apm-synthtrace-client';
import { range as lodashRange } from 'lodash';
import { Scenario } from '../cli/scenario';
const scenario: Scenario<ApmFields> = async ({ logger, scenarioOpts }) => {
const { services: numServices = 10, txGroups: numTxGroups = 10 } = scenarioOpts ?? {};
return {
generate: ({ range }) => {
const TRANSACTION_TYPES = ['request'];
const ENVIRONMENTS = ['production', 'development'];
const MIN_DURATION = 10;
const MAX_DURATION = 1000;
const MAX_BUCKETS = 50;
const BUCKET_SIZE = (MAX_DURATION - MIN_DURATION) / MAX_BUCKETS;
const instances = lodashRange(0, numServices).flatMap((serviceId) => {
const serviceName = `service-${serviceId}`;
const services = ENVIRONMENTS.map((env) => apm.service(serviceName, env, 'go'));
return lodashRange(0, 2).flatMap((serviceNodeId) =>
services.map((service) => service.instance(`${serviceName}-${serviceNodeId}`))
);
});
const transactionGroupRange = [
...lodashRange(0, numTxGroups).map((groupId) => `transaction-${groupId}`),
'_other',
];
return range
.interval('1m')
.rate(1)
.generator((timestamp, timestampIndex) => {
return logger.perf(
'generate_events_for_timestamp ' + new Date(timestamp).toISOString(),
() => {
const events = instances.flatMap((instance) =>
transactionGroupRange.flatMap((groupId, groupIndex) => {
const duration = Math.round(
(timestampIndex % MAX_BUCKETS) * BUCKET_SIZE + MIN_DURATION
);
if (groupId === '_other') {
return instance
.transaction(groupId)
.timestamp(timestamp)
.duration(duration)
.defaults({
'transaction.aggregation.overflow_count': 10,
});
}
return instance
.transaction(groupId, TRANSACTION_TYPES[groupIndex % TRANSACTION_TYPES.length])
.timestamp(timestamp)
.duration(duration)
.success();
})
);
return events;
}
);
});
},
};
};
export default scenario;

View file

@ -164,7 +164,6 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.apm.serviceMapEnabled (boolean)',
'xpack.apm.ui.enabled (boolean)',
'xpack.apm.ui.maxTraceItems (number)',
'xpack.apm.ui.transactionGroupBucketSize (number)',
'xpack.cases.markdownPlugins.lens (boolean)',
'xpack.ccr.ui.enabled (boolean)',
'xpack.cloud.base_url (string)',

View file

@ -272,6 +272,8 @@ exports[`Error TRANSACTION_ID 1`] = `"transaction id"`;
exports[`Error TRANSACTION_NAME 1`] = `undefined`;
exports[`Error TRANSACTION_OVERFLOW_COUNT 1`] = `undefined`;
exports[`Error TRANSACTION_PAGE_URL 1`] = `undefined`;
exports[`Error TRANSACTION_RESULT 1`] = `undefined`;
@ -547,6 +549,8 @@ exports[`Span TRANSACTION_ID 1`] = `"transaction id"`;
exports[`Span TRANSACTION_NAME 1`] = `undefined`;
exports[`Span TRANSACTION_OVERFLOW_COUNT 1`] = `undefined`;
exports[`Span TRANSACTION_PAGE_URL 1`] = `undefined`;
exports[`Span TRANSACTION_RESULT 1`] = `undefined`;
@ -840,6 +844,8 @@ exports[`Transaction TRANSACTION_ID 1`] = `"transaction id"`;
exports[`Transaction TRANSACTION_NAME 1`] = `"transaction name"`;
exports[`Transaction TRANSACTION_OVERFLOW_COUNT 1`] = `undefined`;
exports[`Transaction TRANSACTION_PAGE_URL 1`] = `undefined`;
exports[`Transaction TRANSACTION_RESULT 1`] = `"transaction result"`;

View file

@ -56,6 +56,8 @@ export const TRANSACTION_SAMPLED = 'transaction.sampled';
export const TRANSACTION_PAGE_URL = 'transaction.page.url';
export const TRANSACTION_FAILURE_COUNT = 'transaction.failure_count';
export const TRANSACTION_SUCCESS_COUNT = 'transaction.success_count';
export const TRANSACTION_OVERFLOW_COUNT =
'transaction.aggregation.overflow_count';
// for transaction metrics
export const TRANSACTION_ROOT = 'transaction.root';

View file

@ -78,7 +78,7 @@ export function MobileTransactionOverview() {
<TransactionsTable
hideViewTransactionsLink
numberOfTransactionsPerPage={25}
showAggregationAccurateCallout
showMaxTransactionGroupsExceededWarning
environment={environment}
kuery={kueryWithMobileFilters}
start={start}

View file

@ -70,7 +70,7 @@ export function TransactionOverview() {
<TransactionsTable
hideViewTransactionsLink
numberOfTransactionsPerPage={25}
showAggregationAccurateCallout
showMaxTransactionGroupsExceededWarning
environment={environment}
kuery={kuery}
start={start}

View file

@ -1,72 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { TransactionDetailLink } from './transaction_detail_link';
import { createMemoryHistory } from 'history';
import React from 'react';
import { Router } from 'react-router-dom';
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
import { render } from '@testing-library/react';
const history = createMemoryHistory();
function Wrapper({ children }: { children: React.ReactElement }) {
return (
<MockApmPluginContextWrapper>
<Router history={history}>{children}</Router>
</MockApmPluginContextWrapper>
);
}
describe('TransactionDetailLink', () => {
function getHref(container: HTMLElement) {
return ((container as HTMLDivElement).children[0] as HTMLAnchorElement)
.href;
}
describe('With comparison in the url', () => {
it('returns comparison defined in the url', () => {
const { container } = render(
<Wrapper>
<TransactionDetailLink
serviceName="foo"
transactionName="bar"
transactionType="request"
comparisonEnabled
offset="1w"
traceId="baz"
transactionId="123"
>
Transaction
</TransactionDetailLink>
</Wrapper>
);
expect(getHref(container)).toEqual(
'http://localhost/basepath/app/apm/services/foo/transactions/view?traceId=baz&transactionId=123&transactionName=bar&transactionType=request&comparisonEnabled=true&offset=1w'
);
});
});
describe('use default comparison', () => {
it('returns default comparison', () => {
const { container } = render(
<Wrapper>
<TransactionDetailLink
serviceName="foo"
transactionName="bar"
transactionType="request"
traceId="baz"
transactionId="123"
>
Transaction
</TransactionDetailLink>
</Wrapper>
);
expect(getHref(container)).toEqual(
'http://localhost/basepath/app/apm/services/foo/transactions/view?traceId=baz&transactionId=123&transactionName=bar&transactionType=request&comparisonEnabled=true&offset=1d'
);
});
});
});

View file

@ -1,72 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { useLocation } from 'react-router-dom';
import { EuiLink } from '@elastic/eui';
import { pickBy, identity } from 'lodash';
import { getLegacyApmHref, APMLinkExtendProps } from './apm_link';
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
import { pickKeys } from '../../../../../common/utils/pick_keys';
import { APMQueryParams } from '../url_helpers';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { getComparisonEnabled } from '../../time_comparison/get_comparison_enabled';
interface Props extends APMLinkExtendProps {
serviceName: string;
traceId?: string;
transactionId?: string;
transactionName: string;
transactionType: string;
latencyAggregationType?: string;
environment?: string;
comparisonEnabled?: boolean;
offset?: string;
}
const persistedFilters: Array<keyof APMQueryParams> = [
'transactionResult',
'serviceVersion',
];
export function TransactionDetailLink({
serviceName,
traceId,
transactionId,
transactionName,
transactionType,
latencyAggregationType,
environment,
comparisonEnabled,
offset = '1d',
...rest
}: Props) {
const { urlParams } = useLegacyUrlParams();
const { core } = useApmPluginContext();
const defaultComparisonEnabled = getComparisonEnabled({
core,
urlComparisonEnabled: comparisonEnabled,
});
const location = useLocation();
const href = getLegacyApmHref({
basePath: core.http.basePath,
path: `/services/${serviceName}/transactions/view`,
query: {
traceId,
transactionId,
transactionName,
transactionType,
comparisonEnabled: defaultComparisonEnabled,
offset,
...pickKeys(urlParams as APMQueryParams, ...persistedFilters),
...pickBy({ latencyAggregationType, environment }, identity),
},
search: location.search,
});
return <EuiLink href={href} {...rest} />;
}

View file

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Location } from 'history';
import React from 'react';
import { getRenderedHref } from '../../../../../utils/test_helpers';
import { TransactionDetailLink } from '.';
describe('TransactionDetailLink', () => {
describe('With comparison in the url', () => {
it('returns comparison defined in the url', async () => {
const href = await getRenderedHref(
() => (
<TransactionDetailLink
serviceName="foo"
transactionName="bar"
transactionType="request"
comparisonEnabled
offset="1w"
traceId="baz"
transactionId="123"
>
Transaction
</TransactionDetailLink>
),
{} as Location
);
expect(href).toMatchInlineSnapshot(
'"/basepath/app/apm/services/foo/transactions/view?traceId=baz&transactionId=123&transactionName=bar&transactionType=request&comparisonEnabled=true&offset=1w"'
);
});
});
describe('use default comparison', () => {
it('returns default comparison', async () => {
const href = await getRenderedHref(
() => (
<TransactionDetailLink
serviceName="foo"
transactionName="bar"
transactionType="request"
traceId="baz"
transactionId="123"
>
Transaction
</TransactionDetailLink>
),
{} as Location
);
expect(href).toMatchInlineSnapshot(
'"/basepath/app/apm/services/foo/transactions/view?traceId=baz&transactionId=123&transactionName=bar&transactionType=request&comparisonEnabled=true&offset=1d"'
);
});
});
});

View file

@ -0,0 +1,116 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { identity, pickBy } from 'lodash';
import React from 'react';
import { useLocation } from 'react-router-dom';
import { pickKeys } from '../../../../../../common/utils/pick_keys';
import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context';
import { useLegacyUrlParams } from '../../../../../context/url_params_context/use_url_params';
import { unit } from '../../../../../utils/style';
import { PopoverTooltip } from '../../../popover_tooltip';
import { getComparisonEnabled } from '../../../time_comparison/get_comparison_enabled';
import { TruncateWithTooltip } from '../../../truncate_with_tooltip';
import { APMQueryParams } from '../../url_helpers';
import { APMLinkExtendProps, getLegacyApmHref } from '../apm_link';
export const txGroupsDroppedBucketName = '_other';
interface Props extends APMLinkExtendProps {
serviceName: string;
traceId?: string;
transactionId?: string;
transactionName: string;
transactionType: string;
latencyAggregationType?: string;
environment?: string;
comparisonEnabled?: boolean;
offset?: string;
overflowCount?: number;
}
const persistedFilters: Array<keyof APMQueryParams> = [
'transactionResult',
'serviceVersion',
];
export function TransactionDetailLink({
serviceName,
traceId,
transactionId,
transactionName,
transactionType,
latencyAggregationType,
environment,
comparisonEnabled,
offset = '1d',
overflowCount = 0,
...rest
}: Props) {
const { urlParams } = useLegacyUrlParams();
const { core } = useApmPluginContext();
const defaultComparisonEnabled = getComparisonEnabled({
core,
urlComparisonEnabled: comparisonEnabled,
});
const location = useLocation();
const href = getLegacyApmHref({
basePath: core.http.basePath,
path: `/services/${serviceName}/transactions/view`,
query: {
traceId,
transactionId,
transactionName,
transactionType,
comparisonEnabled: defaultComparisonEnabled,
offset,
...pickKeys(urlParams as APMQueryParams, ...persistedFilters),
...pickBy({ latencyAggregationType, environment }, identity),
},
search: location.search,
});
if (transactionName !== txGroupsDroppedBucketName) {
return (
<TruncateWithTooltip
text={transactionName}
content={<EuiLink href={href} {...rest} />}
/>
);
}
return (
<EuiFlexGroup alignItems="center" gutterSize="xs">
<EuiFlexItem grow={false} style={{ fontStyle: 'italic' }}>
{i18n.translate('xpack.apm.transactionDetail.remainingServices', {
defaultMessage: 'Remaining Transactions',
})}
</EuiFlexItem>
<EuiFlexItem>
<PopoverTooltip
ariaLabel={i18n.translate('xpack.apm.transactionDetail.tooltip', {
defaultMessage: 'Max transaction groups reached tooltip',
})}
iconType="alert"
>
<EuiText style={{ width: `${unit * 28}px` }} size="s">
<FormattedMessage
defaultMessage="Current APM server capacity for handling unique transaction groups has been reached. There are at least {overflowCount, plural, one {1 transaction} other {# transactions}} missing in this list. Please decrease the number of transaction groups in your service or increase the memory allocated to APM server."
id="xpack.apm.transactionDetail.maxGroups.message"
values={{
overflowCount,
}}
/>
</EuiText>
</PopoverTooltip>
</EuiFlexItem>
</EuiFlexGroup>
);
}

View file

@ -11,9 +11,14 @@ import React, { useState } from 'react';
interface PopoverTooltipProps {
ariaLabel?: string;
children: React.ReactNode;
iconType?: string;
}
export function PopoverTooltip({ ariaLabel, children }: PopoverTooltipProps) {
export function PopoverTooltip({
ariaLabel,
iconType,
children,
}: PopoverTooltipProps) {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
return (
@ -30,7 +35,7 @@ export function PopoverTooltip({ ariaLabel, children }: PopoverTooltipProps) {
}}
size="xs"
color="primary"
iconType="questionInCircle"
iconType={iconType ?? 'questionInCircle'}
style={{ height: 'auto' }}
/>
}

View file

@ -16,7 +16,6 @@ import {
import { i18n } from '@kbn/i18n';
import React from 'react';
import { ValuesType } from 'utility-types';
import { isTimeComparison } from '../time_comparison/get_comparison_options';
import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
import {
asMillisecondDuration,
@ -31,13 +30,13 @@ import {
import { ImpactBar } from '../impact_bar';
import { TransactionDetailLink } from '../links/apm/transaction_detail_link';
import { ListMetric } from '../list_metric';
import { TruncateWithTooltip } from '../truncate_with_tooltip';
import { isTimeComparison } from '../time_comparison/get_comparison_options';
import { getLatencyColumnLabel } from './get_latency_column_label';
type TransactionGroupMainStatistics =
APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics'>;
type ServiceTransactionGroupItem = ValuesType<
export type ServiceTransactionGroupItem = ValuesType<
TransactionGroupMainStatistics['transactionGroups']
>;
type TransactionGroupDetailedStatistics =
@ -51,6 +50,7 @@ export function getColumns({
comparisonEnabled,
shouldShowSparkPlots = true,
offset,
transactionOverflowCount,
}: {
serviceName: string;
latencyAggregationType?: LatencyAggregationType;
@ -59,6 +59,7 @@ export function getColumns({
comparisonEnabled?: boolean;
shouldShowSparkPlots?: boolean;
offset?: string;
transactionOverflowCount: number;
}): Array<EuiBasicTableColumn<ServiceTransactionGroupItem>> {
return [
{
@ -71,21 +72,17 @@ export function getColumns({
width: '30%',
render: (_, { name, transactionType: type }) => {
return (
<TruncateWithTooltip
text={name}
content={
<TransactionDetailLink
serviceName={serviceName}
transactionName={name}
transactionType={type}
latencyAggregationType={latencyAggregationType}
comparisonEnabled={comparisonEnabled}
offset={offset}
>
{name}
</TransactionDetailLink>
}
/>
<TransactionDetailLink
serviceName={serviceName}
transactionName={name}
transactionType={type}
latencyAggregationType={latencyAggregationType}
comparisonEnabled={comparisonEnabled}
offset={offset}
overflowCount={transactionOverflowCount}
>
{name}
</TransactionDetailLink>
);
},
},

View file

@ -7,34 +7,33 @@
import {
EuiBasicTable,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { orderBy } from 'lodash';
import React, { useMemo, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { EuiCallOut } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiCode } from '@elastic/eui';
import { useHistory } from 'react-router-dom';
import { APIReturnType } from '../../../services/rest/create_call_apm_api';
import { v4 as uuidv4 } from 'uuid';
import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
import { useBreakpoints } from '../../../hooks/use_breakpoints';
import {
FETCH_STATUS,
isPending,
useFetcher,
} from '../../../hooks/use_fetcher';
import { APIReturnType } from '../../../services/rest/create_call_apm_api';
import { txGroupsDroppedBucketName } from '../links/apm/transaction_detail_link';
import { TransactionOverviewLink } from '../links/apm/transaction_overview_link';
import { OverviewTableContainer } from '../overview_table_container';
import { getColumns } from './get_columns';
import { ElasticDocsLink } from '../links/elastic_docs_link';
import { useBreakpoints } from '../../../hooks/use_breakpoints';
import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
import { fromQuery, toQuery } from '../links/url_helpers';
import { OverviewTableContainer } from '../overview_table_container';
import { isTimeComparison } from '../time_comparison/get_comparison_options';
import { getColumns } from './get_columns';
type ApiResponse =
APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics'>;
@ -50,8 +49,8 @@ const INITIAL_STATE: InitialState = {
requestId: '',
mainStatisticsData: {
transactionGroups: [],
isAggregationAccurate: true,
bucketSize: 0,
maxTransactionGroupsExceeded: true,
transactionOverflowCount: 0,
transactionGroupsTotalItems: 0,
},
};
@ -68,7 +67,7 @@ interface Props {
isSingleColumn?: boolean;
numberOfTransactionsPerPage?: number;
showPerPageOptions?: boolean;
showAggregationAccurateCallout?: boolean;
showMaxTransactionGroupsExceededWarning?: boolean;
environment: string;
fixedHeight?: boolean;
kuery: string;
@ -83,7 +82,7 @@ export function TransactionsTable({
isSingleColumn = true,
numberOfTransactionsPerPage = 5,
showPerPageOptions = true,
showAggregationAccurateCallout = false,
showMaxTransactionGroupsExceededWarning = false,
environment,
kuery,
start,
@ -154,8 +153,12 @@ export function TransactionsTable({
).then((response) => {
const currentPageTransactionGroups = orderBy(
response.transactionGroups,
field,
direction
[
(transactionItem) =>
transactionItem.name === txGroupsDroppedBucketName ? -1 : 0,
field,
],
['asc', direction]
).slice(index * size, (index + 1) * size);
return {
@ -193,8 +196,8 @@ export function TransactionsTable({
requestId,
mainStatisticsData: {
transactionGroups,
isAggregationAccurate,
bucketSize,
maxTransactionGroupsExceeded,
transactionOverflowCount,
transactionGroupsTotalItems,
},
} = data;
@ -254,6 +257,7 @@ export function TransactionsTable({
comparisonEnabled,
shouldShowSparkPlots,
offset,
transactionOverflowCount,
});
const isLoading = status === FETCH_STATUS.LOADING;
@ -306,40 +310,24 @@ export function TransactionsTable({
)}
</EuiFlexGroup>
</EuiFlexItem>
{showAggregationAccurateCallout && !isAggregationAccurate && (
{showMaxTransactionGroupsExceededWarning && maxTransactionGroupsExceeded && (
<EuiFlexItem>
<EuiCallOut
title={i18n.translate(
'xpack.apm.transactionsTable.cardinalityWarning.title',
'xpack.apm.transactionsCallout.cardinalityWarning.title',
{
defaultMessage:
'This view shows a subset of reported transactions.',
'Number of transaction groups exceed the allowed maximum(1,000) that are displayed.',
}
)}
color="danger"
color="warning"
iconType="alert"
>
<p>
<FormattedMessage
id="xpack.apm.transactionsTable.cardinalityWarning.body"
defaultMessage="The number of unique transaction names exceeds the configured value of {bucketSize}. Try reconfiguring your agents to group similar transactions or increase the value of {codeBlock}"
values={{
bucketSize,
codeBlock: (
<EuiCode>xpack.apm.ui.transactionGroupBucketSize</EuiCode>
),
}}
id="xpack.apm.transactionsCallout.transactionGroupLimit.exceeded"
defaultMessage="The maximum number of transaction groups displayed in Kibana has been reached. Try narrowing down results by using the query bar."
/>
<ElasticDocsLink
section="/kibana"
path="/troubleshooting.html#troubleshooting-too-many-transactions"
>
{i18n.translate(
'xpack.apm.transactionsTable.cardinalityWarning.docsLink',
{ defaultMessage: 'Learn more in the docs' }
)}
</ElasticDocsLink>
</p>
</EuiCallOut>
</EuiFlexItem>

View file

@ -28,7 +28,6 @@ const configSchema = schema.object({
serviceMapMaxTracesPerRequest: schema.number({ defaultValue: 50 }),
ui: schema.object({
enabled: schema.boolean({ defaultValue: true }),
transactionGroupBucketSize: schema.number({ defaultValue: 1000 }),
maxTraceItems: schema.number({ defaultValue: 5000 }),
}),
searchAggregatedTransactions: schema.oneOf(
@ -66,6 +65,9 @@ export const config: PluginConfigDescriptor<APMConfig> = {
unusedFromRoot,
}) => [
unused('indices.sourcemap', { level: 'warning' }),
unused('ui.transactionGroupBucketSize', {
level: 'warning',
}),
rename('autocreateApmIndexPattern', 'autoCreateApmDataView', {
level: 'warning',
}),

View file

@ -10,6 +10,7 @@ import {
EVENT_OUTCOME,
SERVICE_NAME,
TRANSACTION_NAME,
TRANSACTION_OVERFLOW_COUNT,
TRANSACTION_TYPE,
} from '../../../common/es_fields/apm';
import { EventOutcome } from '../../../common/event_outcome';
@ -29,6 +30,8 @@ import { calculateFailedTransactionRate } from '../../lib/helpers/transaction_er
import { APMConfig } from '../..';
import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
const txGroupsDroppedBucketName = '_other';
export type ServiceOverviewTransactionGroupSortField =
| 'name'
| 'latency'
@ -59,8 +62,6 @@ export async function getServiceTransactionGroups({
start: number;
end: number;
}) {
const bucketSize = config.ui.transactionGroupBucketSize;
const field = getDurationFieldForTransactions(searchAggregatedTransactions);
const response = await apmEventClient.search(
@ -78,7 +79,14 @@ export async function getServiceTransactionGroups({
bool: {
filter: [
{ term: { [SERVICE_NAME]: serviceName } },
{ term: { [TRANSACTION_TYPE]: transactionType } },
{
bool: {
should: [
{ term: { [TRANSACTION_NAME]: txGroupsDroppedBucketName } },
{ term: { [TRANSACTION_TYPE]: transactionType } },
],
},
},
...getDocumentTypeFilterForTransactions(
searchAggregatedTransactions
),
@ -90,10 +98,15 @@ export async function getServiceTransactionGroups({
},
aggs: {
total_duration: { sum: { field } },
transaction_overflow_count: {
sum: {
field: TRANSACTION_OVERFLOW_COUNT,
},
},
transaction_groups: {
terms: {
field: TRANSACTION_NAME,
size: bucketSize,
size: 1000,
order: { _count: 'desc' },
},
aggs: {
@ -146,9 +159,9 @@ export async function getServiceTransactionGroups({
...transactionGroup,
transactionType,
})),
isAggregationAccurate:
(response.aggregations?.transaction_groups.sum_other_doc_count ?? 0) ===
0,
bucketSize,
maxTransactionGroupsExceeded:
(response.aggregations?.transaction_groups.sum_other_doc_count ?? 0) > 0,
transactionOverflowCount:
response.aggregations?.transaction_overflow_count.value ?? 0,
};
}

View file

@ -53,8 +53,8 @@ const transactionGroupsMainStatisticsRoute = createApmServerRoute({
errorRate: number;
impact: number;
}>;
isAggregationAccurate: boolean;
bucketSize: number;
transactionOverflowCount: number;
maxTransactionGroupsExceeded: boolean;
}> => {
const { params, config } = resources;
const apmEventClient = await getApmEventClient(resources);

View file

@ -78,7 +78,6 @@ export async function inspectSearchParams(
case 'ui':
return {
enabled: true,
transactionGroupBucketSize: 1000,
maxTraceItems: 5000,
};
case 'metricsInterval':

View file

@ -6735,7 +6735,6 @@
"xpack.apm.transactionDetails.errorCount": "{errorCount, number} {errorCount, plural, one {erreur} other {erreurs}}",
"xpack.apm.transactionDetails.transFlyout.callout.agentDroppedSpansMessage": "L'agent APM qui a signalé cette transaction a abandonné {dropped} intervalles ou plus, d'après sa configuration.",
"xpack.apm.transactionRateLabel": "{displayedValue} tpm",
"xpack.apm.transactionsTable.cardinalityWarning.body": "Le nombre de noms de transactions uniques dépasse la valeur configurée de {bucketSize}. Essayez de reconfigurer vos agents de façon à regrouper les transactions similaires ou augmentez la valeur de {codeBlock}",
"xpack.apm.tutorial.config_otel.description1": "(1) Les agents et SDK OpenTelemetry doivent prendre en charge les variables {otelExporterOtlpEndpoint}, {otelExporterOtlpHeaders} et {otelResourceAttributes}. Certains composants instables peuvent ne pas encore répondre à cette exigence.",
"xpack.apm.tutorial.config_otel.description3": "La liste exhaustive des variables d'environnement, les paramètres de ligne de commande et les extraits de code de configuration (conformes à la spécification OpenTelemetry) se trouvent dans le {otelInstrumentationGuide}. Certains clients OpenTelemetry instables peuvent ne pas prendre en charge toutes les fonctionnalités et nécessitent peut-être d'autres mécanismes de configuration.",
"xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment": "Définir l'URL personnalisée du serveur APM (par défaut : {defaultApmServerUrl})",
@ -7975,8 +7974,6 @@
"xpack.apm.transactions.latency.chart.95thPercentileLabel": "95e centile",
"xpack.apm.transactions.latency.chart.99thPercentileLabel": "99e centile",
"xpack.apm.transactions.latency.chart.averageLabel": "Moyenne",
"xpack.apm.transactionsTable.cardinalityWarning.docsLink": "En savoir plus dans la documentation",
"xpack.apm.transactionsTable.cardinalityWarning.title": "Cette vue présente un sous-ensemble de transactions signalées.",
"xpack.apm.transactionsTable.errorMessage": "Impossible de récupérer",
"xpack.apm.transactionsTable.linkText": "Afficher les transactions",
"xpack.apm.transactionsTable.title": "Transactions",

View file

@ -6725,7 +6725,6 @@
"xpack.apm.transactionDetails.errorCount": "{errorCount, number} {errorCount, plural, other {エラー}}",
"xpack.apm.transactionDetails.transFlyout.callout.agentDroppedSpansMessage": "このトランザクションを報告した APM エージェントが、構成に基づき {dropped} 個以上のスパンをドロップしました。",
"xpack.apm.transactionRateLabel": "{displayedValue} tpm",
"xpack.apm.transactionsTable.cardinalityWarning.body": "一意のトランザクション名の数が構成された値{bucketSize}を超えています。エージェントを再構成し、類似したトランザクションをグループ化するか、{codeBlock}の値を増やしてください。",
"xpack.apm.tutorial.config_otel.description1": "(1) OpenTelemetryエージェントとSDKは、{otelExporterOtlpEndpoint}、{otelExporterOtlpHeaders}、および{otelResourceAttributes}変数をサポートする必要があります。一部の不安定なコンポーネントは、まだこの要件を満たしていない場合があります。",
"xpack.apm.tutorial.config_otel.description3": "環境変数、コマンドラインパラメーター、構成コードスニペットOpenTelemetry仕様に準拠の網羅的な一覧は、{otelInstrumentationGuide}をご覧ください。一部の不安定なOpenTelemetryクライアントでは、一部の機能がサポートされておらず、別の構成メカニズムが必要になる場合があります。",
"xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment": "カスタム APM Server URLデフォルト{defaultApmServerUrl})を設定します",
@ -7964,8 +7963,6 @@
"xpack.apm.transactions.latency.chart.95thPercentileLabel": "95 パーセンタイル",
"xpack.apm.transactions.latency.chart.99thPercentileLabel": "99 パーセンタイル",
"xpack.apm.transactions.latency.chart.averageLabel": "平均",
"xpack.apm.transactionsTable.cardinalityWarning.docsLink": "詳細はドキュメントをご覧ください",
"xpack.apm.transactionsTable.cardinalityWarning.title": "このビューには、報告されたトランザクションのサブセットが表示されます。",
"xpack.apm.transactionsTable.errorMessage": "取得できませんでした",
"xpack.apm.transactionsTable.linkText": "トランザクションを表示",
"xpack.apm.transactionsTable.title": "トランザクション",

View file

@ -6738,7 +6738,6 @@
"xpack.apm.transactionDetails.errorCount": "{errorCount, number} 个 {errorCount, plural, other {错误}}",
"xpack.apm.transactionDetails.transFlyout.callout.agentDroppedSpansMessage": "报告此事务的 APM 代理基于其配置丢弃了 {dropped} 个跨度。",
"xpack.apm.transactionRateLabel": "{displayedValue} tpm",
"xpack.apm.transactionsTable.cardinalityWarning.body": "唯一事务名称的数目超过 {bucketSize} 的已配置值。尝试重新配置您的代理以对类似的事务分组或增大 {codeBlock} 的值",
"xpack.apm.tutorial.config_otel.description1": "(1) OpenTelemetry 代理和 SDK 必须支持 {otelExporterOtlpEndpoint}、{otelExporterOtlpHeaders} 和 {otelResourceAttributes} 变量;某些不稳定的组件可能尚未遵循此要求。",
"xpack.apm.tutorial.config_otel.description3": "{otelInstrumentationGuide}中提供了环境变量、命令行参数和配置代码片段(根据 OpenTelemetry 规范)的详细列表。某些不稳定的 OpenTelemetry 客户端可能不支持所有功能,并可能需要备选配置机制。",
"xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment": "设置定制 APM Server URL默认值{defaultApmServerUrl}",
@ -7978,8 +7977,6 @@
"xpack.apm.transactions.latency.chart.95thPercentileLabel": "第 95 个百分位",
"xpack.apm.transactions.latency.chart.99thPercentileLabel": "第 99 个百分位",
"xpack.apm.transactions.latency.chart.averageLabel": "平均值",
"xpack.apm.transactionsTable.cardinalityWarning.docsLink": "在文档中了解详情",
"xpack.apm.transactionsTable.cardinalityWarning.title": "此视图显示已报告事务的子集。",
"xpack.apm.transactionsTable.errorMessage": "无法提取",
"xpack.apm.transactionsTable.linkText": "查看事务",
"xpack.apm.transactionsTable.title": "事务",

View file

@ -46,7 +46,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const transctionsGroupsPrimaryStatistics =
response.body as TransactionsGroupsPrimaryStatistics;
expect(transctionsGroupsPrimaryStatistics.transactionGroups).to.empty();
expect(transctionsGroupsPrimaryStatistics.isAggregationAccurate).to.be(true);
expect(transctionsGroupsPrimaryStatistics.maxTransactionGroupsExceeded).to.be(false);
});
}
);