[APM] Always get the root transaction when fetching trace items (#42508) (#42812)

* [APM] Always get the root transaction when fetching trace items
Fixes #33210

* code tweaks

* displays message notifying user that trace items exceeds maximum displayed

* remove getTraceRoot query by adjusting the score and order of trace
items with no parent.id

* add `apm_oss.maxTraceItems` config options to control the number of displayed trace items

* changed config `apm_oss.maxTraceItems` to `xpack.apm.ui.maxTraceItems`

* added missing configs to apm settings doc and docker template

* minor code tweak
This commit is contained in:
Oliver Gupte 2019-08-07 06:58:51 -07:00 committed by GitHub
parent 4535f2d686
commit 4301be4821
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 100 additions and 35 deletions

View file

@ -21,6 +21,10 @@ xpack.apm.ui.enabled:: Set to `false` to hide the APM plugin {kib} from the menu
xpack.apm.ui.transactionGroupBucketSize:: Number of top transaction groups displayed in APM plugin in Kibana. Defaults to `100`.
xpack.apm.ui.maxTraceItems:: Max number of child items displayed when viewing trace details. Defaults to `1000`.
apm_oss.apmAgentConfigurationIndex:: Index containing agent configuration settings. Defaults to `.apm-agent-configuration`.
apm_oss.indexPattern:: Index pattern is used for integrations with Machine Learning and Kuery Bar. It must match all apm indices. Defaults to `apm-*`.
apm_oss.errorIndices:: Matcher for indices containing error documents. Defaults to `apm-*`.

View file

@ -82,6 +82,14 @@ kibana_vars=(
vega.enableExternalUrls
xpack.apm.enabled
xpack.apm.ui.enabled
xpack.apm.ui.maxTraceItems
apm_oss.apmAgentConfigurationIndex
apm_oss.indexPattern
apm_oss.errorIndices
apm_oss.onboardingIndices
apm_oss.spanIndices
apm_oss.transactionIndices
apm_oss.metricsIndices
xpack.canvas.enabled
xpack.graph.enabled
xpack.grokdebugger.enabled

View file

@ -38,7 +38,7 @@ export default function apmOss(kibana) {
spanIndices: Joi.string().default('apm-*'),
metricsIndices: Joi.string().default('apm-*'),
onboardingIndices: Joi.string().default('apm-*'),
apmAgentConfigurationIndex: Joi.string().default('.apm-agent-configuration')
apmAgentConfigurationIndex: Joi.string().default('.apm-agent-configuration'),
}).default();
},
@ -49,7 +49,7 @@ export default function apmOss(kibana) {
'transactionIndices',
'spanIndices',
'metricsIndices',
'onboardingIndices'
'onboardingIndices',
].map(type => server.config().get(`apm_oss.${type}`))));
}
});

View file

@ -59,7 +59,8 @@ export const apm: LegacyPluginInitializer = kibana => {
// display menu item
ui: Joi.object({
enabled: Joi.boolean().default(true),
transactionGroupBucketSize: Joi.number().default(100)
transactionGroupBucketSize: Joi.number().default(100),
maxTraceItems: Joi.number().default(1000)
}).default(),
// enable plugin

View file

@ -35,13 +35,15 @@ interface Props {
transaction: Transaction;
urlParams: IUrlParams;
waterfall: IWaterfall;
exceedsMax: boolean;
}
export function TransactionTabs({
location,
transaction,
urlParams,
waterfall
waterfall,
exceedsMax
}: Props) {
const tabs = [timelineTab, metadataTab];
const currentTab =
@ -79,6 +81,7 @@ export function TransactionTabs({
location={location}
urlParams={urlParams}
waterfall={waterfall}
exceedsMax={exceedsMax}
/>
) : (
<TransactionMetadata transaction={transaction} />

View file

@ -9,6 +9,8 @@ import React, { Component } from 'react';
// @ts-ignore
import { StickyContainer } from 'react-sticky';
import styled from 'styled-components';
import { EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { IUrlParams } from '../../../../../../context/UrlParamsContext/types';
// @ts-ignore
import Timeline from '../../../../../shared/charts/Timeline';
@ -47,6 +49,7 @@ interface Props {
waterfall: IWaterfall;
location: Location;
serviceColors: IServiceColors;
exceedsMax: boolean;
}
export class Waterfall extends Component<Props> {
@ -126,12 +129,23 @@ export class Waterfall extends Component<Props> {
};
public render() {
const { waterfall } = this.props;
const { waterfall, exceedsMax } = this.props;
const itemContainerHeight = 58; // TODO: This is a nasty way to calculate the height of the svg element. A better approach should be found
const waterfallHeight = itemContainerHeight * waterfall.orderedItems.length;
return (
<Container>
{exceedsMax ? (
<EuiCallOut
color="warning"
size="s"
iconType="alert"
title={i18n.translate('xpack.apm.waterfall.exceedsMax', {
defaultMessage:
'Number of items in this trace exceed what is displayed'
})}
/>
) : null}
<StickyContainer>
<Timeline
agentMarks={this.props.agentMarks}

View file

@ -17,6 +17,17 @@ import {
describe('waterfall_helpers', () => {
describe('getWaterfall', () => {
const hits = [
{
processor: { event: 'transaction' },
trace: { id: 'myTraceId' },
service: { name: 'opbeans-node' },
transaction: {
duration: { us: 49660 },
name: 'GET /api',
id: 'myTransactionId1'
},
timestamp: { us: 1549324795784006 }
} as Transaction,
{
parent: { id: 'mySpanIdA' },
processor: { event: 'span' },
@ -80,17 +91,6 @@ describe('waterfall_helpers', () => {
id: 'myTransactionId2'
},
timestamp: { us: 1549324795823304 }
} as Transaction,
{
processor: { event: 'transaction' },
trace: { id: 'myTraceId' },
service: { name: 'opbeans-node' },
transaction: {
duration: { us: 49660 },
name: 'GET /api',
id: 'myTransactionId1'
},
timestamp: { us: 1549324795784006 }
} as Transaction
];
@ -101,7 +101,10 @@ describe('waterfall_helpers', () => {
myTransactionId2: 3
};
const waterfall = getWaterfall(
{ trace: hits, errorsPerTransaction },
{
trace: { items: hits, exceedsMax: false },
errorsPerTransaction
},
entryTransactionId
);
expect(waterfall.orderedItems.length).toBe(6);
@ -116,7 +119,10 @@ describe('waterfall_helpers', () => {
myTransactionId2: 3
};
const waterfall = getWaterfall(
{ trace: hits, errorsPerTransaction },
{
trace: { items: hits, exceedsMax: false },
errorsPerTransaction
},
entryTransactionId
);
expect(waterfall.orderedItems.length).toBe(4);
@ -131,7 +137,10 @@ describe('waterfall_helpers', () => {
myTransactionId2: 3
};
const waterfall = getWaterfall(
{ trace: hits, errorsPerTransaction },
{
trace: { items: hits, exceedsMax: false },
errorsPerTransaction
},
entryTransactionId
);
const transaction = waterfall.getTransactionById('myTransactionId2');

View file

@ -6,14 +6,14 @@
import theme from '@elastic/eui/dist/eui_theme_light.json';
import {
first,
flatten,
groupBy,
indexBy,
isEmpty,
sortBy,
uniq,
zipObject
zipObject,
isEmpty,
first
} from 'lodash';
import { idx } from '@kbn/elastic-idx';
import { TraceAPIResponse } from '../../../../../../../../server/lib/traces/get_trace';
@ -22,7 +22,7 @@ import { Span } from '../../../../../../../../typings/es_schemas/ui/Span';
import { Transaction } from '../../../../../../../../typings/es_schemas/ui/Transaction';
interface IWaterfallIndex {
[key: string]: IWaterfallItem;
[key: string]: IWaterfallItem | undefined;
}
interface IWaterfallGroup {
@ -234,7 +234,7 @@ export function getWaterfall(
{ trace, errorsPerTransaction }: TraceAPIResponse,
entryTransactionId?: Transaction['transaction']['id']
): IWaterfall {
if (isEmpty(trace) || !entryTransactionId) {
if (isEmpty(trace.items) || !entryTransactionId) {
return {
services: [],
duration: 0,
@ -246,7 +246,7 @@ export function getWaterfall(
};
}
const waterfallItems = trace.map(traceItem => {
const waterfallItems = trace.items.map(traceItem => {
const docType = traceItem.processor.event;
switch (docType) {
case 'span':

View file

@ -18,13 +18,15 @@ interface Props {
transaction: Transaction;
location: Location;
waterfall: IWaterfall;
exceedsMax: boolean;
}
export function WaterfallContainer({
location,
urlParams,
transaction,
waterfall
waterfall,
exceedsMax
}: Props) {
const agentMarks = getAgentMarks(transaction);
if (!waterfall) {
@ -40,6 +42,7 @@ export function WaterfallContainer({
serviceColors={waterfall.serviceColors}
urlParams={urlParams}
waterfall={waterfall}
exceedsMax={exceedsMax}
/>
</div>
);

View file

@ -97,13 +97,15 @@ interface Props {
urlParams: IUrlParams;
location: Location;
waterfall: IWaterfall;
exceedsMax: boolean;
}
export const Transaction: React.SFC<Props> = ({
transaction,
urlParams,
location,
waterfall
waterfall,
exceedsMax
}) => {
return (
<EuiPanel paddingSize="m">
@ -149,6 +151,7 @@ export const Transaction: React.SFC<Props> = ({
location={location}
urlParams={urlParams}
waterfall={waterfall}
exceedsMax={exceedsMax}
/>
</EuiPanel>
);

View file

@ -31,7 +31,7 @@ export function TransactionDetails() {
const { data: transactionChartsData } = useTransactionCharts();
const { data: waterfall } = useWaterfall(urlParams);
const { data: waterfall, exceedsMax } = useWaterfall(urlParams);
const transaction = waterfall.getTransactionById(urlParams.transactionId);
const { transactionName } = urlParams;
@ -81,6 +81,7 @@ export function TransactionDetails() {
transaction={transaction}
urlParams={urlParams}
waterfall={waterfall}
exceedsMax={exceedsMax}
/>
)}
</div>

View file

@ -10,7 +10,11 @@ import { loadTrace } from '../services/rest/apm/traces';
import { IUrlParams } from '../context/UrlParamsContext/types';
import { useFetcher } from './useFetcher';
const INITIAL_DATA = { trace: [], errorsPerTransaction: {} };
const INITIAL_DATA = {
root: undefined,
trace: { items: [], exceedsMax: false },
errorsPerTransaction: {}
};
export function useWaterfall(urlParams: IUrlParams) {
const { traceId, start, end, transactionId } = urlParams;
@ -25,5 +29,5 @@ export function useWaterfall(urlParams: IUrlParams) {
transactionId
]);
return { data: waterfall, status, error };
return { data: waterfall, status, error, exceedsMax: data.trace.exceedsMax };
}

View file

@ -7,7 +7,10 @@
import { SearchParams } from 'elasticsearch';
import {
PROCESSOR_EVENT,
TRACE_ID
TRACE_ID,
PARENT_ID,
TRANSACTION_DURATION,
SPAN_DURATION
} from '../../../common/elasticsearch_fieldnames';
import { Span } from '../../../typings/es_schemas/ui/Span';
import { Transaction } from '../../../typings/es_schemas/ui/Transaction';
@ -16,6 +19,7 @@ import { Setup } from '../helpers/setup_request';
export async function getTraceItems(traceId: string, setup: Setup) {
const { start, end, client, config } = setup;
const maxTraceItems = config.get<number>('xpack.apm.ui.maxTraceItems');
const params: SearchParams = {
index: [
@ -23,20 +27,31 @@ export async function getTraceItems(traceId: string, setup: Setup) {
config.get('apm_oss.transactionIndices')
],
body: {
size: 1000,
size: maxTraceItems,
query: {
bool: {
filter: [
{ term: { [TRACE_ID]: traceId } },
{ terms: { [PROCESSOR_EVENT]: ['span', 'transaction'] } },
{ range: rangeFilter(start, end) }
]
],
should: {
exists: { field: PARENT_ID }
}
}
}
},
sort: [
{ _score: { order: 'asc' } },
{ [TRANSACTION_DURATION]: { order: 'desc' } },
{ [SPAN_DURATION]: { order: 'desc' } }
]
}
};
const resp = await client.search<Transaction | Span>(params);
return resp.hits.hits.map(hit => hit._source);
return {
items: resp.hits.hits.map(hit => hit._source),
exceedsMax: resp.hits.total > maxTraceItems
};
}