diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index 615dc98c066b..e47326a1d206 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -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-*`. diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index ce64d5fbf842..6e2cc28495e1 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -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 diff --git a/src/legacy/core_plugins/apm_oss/index.js b/src/legacy/core_plugins/apm_oss/index.js index 6c0c6d0e5fe5..0c281ec939bb 100644 --- a/src/legacy/core_plugins/apm_oss/index.js +++ b/src/legacy/core_plugins/apm_oss/index.js @@ -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}`)))); } }); diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts index aac946a36b45..d39df6084935 100644 --- a/x-pack/legacy/plugins/apm/index.ts +++ b/x-pack/legacy/plugins/apm/index.ts @@ -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 diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx index d3852014c373..e5be12509e3c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/TransactionTabs.tsx @@ -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} /> ) : ( diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/index.tsx index 1c89cbf9f6e6..d53b4077d975 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/index.tsx @@ -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 { @@ -126,12 +129,23 @@ export class Waterfall extends Component { }; 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 ( + {exceedsMax ? ( + + ) : null} { 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'); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts index 80a8474c2930..7835d47468b7 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts @@ -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': diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx index cd437325e57f..2f34cc86c5cf 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/index.tsx @@ -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} /> ); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx index 659eca2c06f0..d9e024c5560e 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx @@ -97,13 +97,15 @@ interface Props { urlParams: IUrlParams; location: Location; waterfall: IWaterfall; + exceedsMax: boolean; } export const Transaction: React.SFC = ({ transaction, urlParams, location, - waterfall + waterfall, + exceedsMax }) => { return ( @@ -149,6 +151,7 @@ export const Transaction: React.SFC = ({ location={location} urlParams={urlParams} waterfall={waterfall} + exceedsMax={exceedsMax} /> ); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/index.tsx index e08285ae9b96..f242690beab0 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -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} /> )} diff --git a/x-pack/legacy/plugins/apm/public/hooks/useWaterfall.ts b/x-pack/legacy/plugins/apm/public/hooks/useWaterfall.ts index 050041724a90..fd2ed152c79f 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useWaterfall.ts +++ b/x-pack/legacy/plugins/apm/public/hooks/useWaterfall.ts @@ -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 }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts index 11599d09c1d6..00eeefb4b4fc 100644 --- a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts +++ b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts @@ -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('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(params); - return resp.hits.hits.map(hit => hit._source); + return { + items: resp.hits.hits.map(hit => hit._source), + exceedsMax: resp.hits.total > maxTraceItems + }; }