diff --git a/x-pack/plugins/apm/public/components/app/Correlations/correlations_table.tsx b/x-pack/plugins/apm/public/components/app/Correlations/correlations_table.tsx index 57d949d82c0feb..52a7628779c9c6 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/correlations_table.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/correlations_table.tsx @@ -6,12 +6,15 @@ */ import React from 'react'; -import { EuiIcon, EuiLink } from '@elastic/eui'; +import { + EuiIcon, + EuiLink, + EuiBasicTable, + EuiBasicTableColumn, + EuiBadge, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useHistory } from 'react-router-dom'; -import { EuiBasicTable } from '@elastic/eui'; -import { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiCode } from '@elastic/eui'; import { asInteger, asPercent } from '../../../../common/utils/formatters'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; @@ -30,6 +33,7 @@ interface Props { status: FETCH_STATUS; cardinalityColumnName: string; setSelectedSignificantTerm: (term: T | null) => void; + onFilter: () => void; } export function CorrelationsTable({ @@ -37,6 +41,7 @@ export function CorrelationsTable({ status, cardinalityColumnName, setSelectedSignificantTerm, + onFilter, }: Props) { const history = useHistory(); const columns: Array> = [ @@ -48,7 +53,7 @@ export function CorrelationsTable({ { defaultMessage: 'Score' } ), render: (_: any, term: T) => { - return {Math.round(term.score)}; + return {Math.round(term.score)}; }, }, { @@ -96,6 +101,7 @@ export function CorrelationsTable({ )}"`, }, }); + onFilter(); }, }, { @@ -117,6 +123,7 @@ export function CorrelationsTable({ )}"`, }, }); + onFilter(); }, }, ], diff --git a/x-pack/plugins/apm/public/components/app/Correlations/custom_fields.tsx b/x-pack/plugins/apm/public/components/app/Correlations/custom_fields.tsx new file mode 100644 index 00000000000000..777acd87bad424 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/Correlations/custom_fields.tsx @@ -0,0 +1,125 @@ +/* + * 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, + EuiAccordion, + EuiComboBox, + EuiFormRow, + EuiLink, + EuiFieldNumber, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +interface Props { + fieldNames: string[]; + defaultFieldNames: string[]; + setFieldNames: (fieldNames: string[]) => void; + setDurationPercentile?: (value: number) => void; + showThreshold?: boolean; + durationPercentile?: number; +} + +export function CustomFields({ + fieldNames, + setFieldNames, + defaultFieldNames, + setDurationPercentile = () => {}, + showThreshold = false, + durationPercentile = 50, +}: Props) { + return ( + + + {showThreshold && ( + + + { + const value = parseInt(e.currentTarget.value, 10); + if (isValidPercentile(value)) { + setDurationPercentile(value); + } + }} + prepend="%" + /> + + + )} + + + {i18n.translate( + 'xpack.apm.correlations.customize.fieldHelpText', + { + defaultMessage: 'Fields to analyse for correlations.', + } + )} +   + { + setFieldNames(defaultFieldNames); + }} + > + {i18n.translate( + 'xpack.apm.correlations.customize.fieldResetDefault', + { defaultMessage: 'Reset to default' } + )} + + + } + > + ({ label }))} + onChange={(options) => { + const nextFieldNames = options.map((option) => option.label); + setFieldNames(nextFieldNames); + }} + onCreateOption={(term) => { + const nextFieldNames = [...fieldNames, term]; + setFieldNames(nextFieldNames); + }} + /> + + + + + ); +} + +function isValidPercentile(value: number) { + return !isNaN(value) && value >= 0 && value <= 100; +} diff --git a/x-pack/plugins/apm/public/components/app/Correlations/error_correlations.tsx b/x-pack/plugins/apm/public/components/app/Correlations/error_correlations.tsx index 4ec2564d61cdeb..bcaa3a1b4a0054 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/error_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/error_correlations.tsx @@ -17,13 +17,7 @@ import { } from '@elastic/charts'; import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; -import { - EuiTitle, - EuiFlexGroup, - EuiFlexItem, - EuiComboBox, - EuiAccordion, -} from '@elastic/eui'; +import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; @@ -32,6 +26,8 @@ import { px } from '../../../style/variables'; import { CorrelationsTable } from './correlations_table'; import { ChartContainer } from '../../shared/charts/chart_container'; import { useTheme } from '../../../hooks/use_theme'; +import { CustomFields } from './custom_fields'; +import { useDefaultFieldNames } from './useDefaultFieldNames'; type CorrelationsApiResponse = NonNullable< APIReturnType<'GET /api/apm/correlations/failed_transactions'> @@ -41,26 +37,31 @@ type SignificantTerm = NonNullable< CorrelationsApiResponse['significantTerms'] >[0]; -const initialFieldNames = [ - 'transaction.name', - 'user.username', - 'user.id', - 'host.ip', - 'user_agent.name', - 'kubernetes.pod.uuid', - 'kubernetes.pod.name', - 'url.domain', - 'container.id', - 'service.node.name', -].map((label) => ({ label })); +// const initialFieldNames = [ +// 'transaction.name', +// 'user.username', +// 'user.id', +// 'host.ip', +// 'user_agent.name', +// 'kubernetes.pod.uuid', +// 'kubernetes.pod.name', +// 'url.domain', +// 'container.id', +// 'service.node.name', +// ].map((label) => ({ label })); + +interface Props { + onClose: () => void; +} -export function ErrorCorrelations() { +export function ErrorCorrelations({ onClose }: Props) { const [ selectedSignificantTerm, setSelectedSignificantTerm, ] = useState(null); - const [fieldNames, setFieldNames] = useState(initialFieldNames); + const defaultFieldNames = useDefaultFieldNames(); + const [fieldNames, setFieldNames] = useState(defaultFieldNames); const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); const { transactionName, transactionType, start, end } = urlParams; @@ -78,7 +79,7 @@ export function ErrorCorrelations() { start, end, uiFilters: JSON.stringify(uiFilters), - fieldNames: fieldNames.map((field) => field.label).join(','), + fieldNames: fieldNames.join(','), }, }, }); @@ -99,7 +100,17 @@ export function ErrorCorrelations() { <> - + +

+ Orbiting this at a distance of roughly ninety-two million miles is + an utterly insignificant little blue green planet whose ape- + descended life forms are so amazingly primitive that they still + think digital watches are a pretty neat idea. +

+
+
+ +

{i18n.translate('xpack.apm.correlations.error.chart.title', { defaultMessage: 'Error rate over time', @@ -123,29 +134,15 @@ export function ErrorCorrelations() { significantTerms={data?.significantTerms} status={status} setSelectedSignificantTerm={setSelectedSignificantTerm} + onFilter={onClose} /> - - - setFieldNames((names) => [...names, { label: term }]) - } - /> - + diff --git a/x-pack/plugins/apm/public/components/app/Correlations/index.tsx b/x-pack/plugins/apm/public/components/app/Correlations/index.tsx index 85e4ab84aec08d..fca40dd77fa0d3 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/index.tsx @@ -28,7 +28,7 @@ import { enableCorrelations } from '../../../../common/ui_settings_keys'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { LatencyCorrelations } from './latency_correlations'; import { ErrorCorrelations } from './error_correlations'; -import { ThroughputCorrelations } from './throughput_correlations'; +// import { ThroughputCorrelations } from './throughput_correlations'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { createHref } from '../../shared/Links/url_helpers'; import { useLicenseContext } from '../../../context/license/use_license_context'; @@ -40,13 +40,13 @@ const latencyTab = { }), component: LatencyCorrelations, }; -const throughputTab = { - key: 'throughput', - label: i18n.translate('xpack.apm.correlations.tabs.throughputLabel', { - defaultMessage: 'Throughput', - }), - component: ThroughputCorrelations, -}; +// const throughputTab = { +// key: 'throughput', +// label: i18n.translate('xpack.apm.correlations.tabs.throughputLabel', { +// defaultMessage: 'Throughput', +// }), +// component: ThroughputCorrelations, +// }; const errorRateTab = { key: 'errorRate', label: i18n.translate('xpack.apm.correlations.tabs.errorRateLabel', { @@ -54,7 +54,8 @@ const errorRateTab = { }), component: ErrorCorrelations, }; -const tabs = [latencyTab, throughputTab, errorRateTab]; +// const tabs = [latencyTab, throughputTab, errorRateTab]; +const tabs = [latencyTab, errorRateTab]; export function Correlations() { const { uiSettings } = useApmPluginContext().core; @@ -165,7 +166,7 @@ export function Correlations() { ))} - + setIsFlyoutVisible(false)} /> diff --git a/x-pack/plugins/apm/public/components/app/Correlations/latency_correlations.tsx b/x-pack/plugins/apm/public/components/app/Correlations/latency_correlations.tsx index d798ee66ea82fb..51ce08cf92f7cd 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/latency_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/latency_correlations.tsx @@ -15,15 +15,7 @@ import { } from '@elastic/charts'; import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; -import { - EuiTitle, - EuiFlexGroup, - EuiFlexItem, - EuiComboBox, - EuiAccordion, - EuiFormRow, - EuiFieldNumber, -} from '@elastic/eui'; +import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { getDurationFormatter } from '../../../../common/utils/formatters'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; @@ -32,6 +24,8 @@ import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { CorrelationsTable } from './correlations_table'; import { ChartContainer } from '../../shared/charts/chart_container'; import { useTheme } from '../../../hooks/use_theme'; +import { CustomFields } from './custom_fields'; +import { useDefaultFieldNames } from './useDefaultFieldNames'; type CorrelationsApiResponse = NonNullable< APIReturnType<'GET /api/apm/correlations/slow_transactions'> @@ -41,25 +35,18 @@ type SignificantTerm = NonNullable< CorrelationsApiResponse['significantTerms'] >[0]; -const initialFieldNames = [ - 'user.username', - 'user.id', - 'host.ip', - 'user_agent.name', - 'kubernetes.pod.uuid', - 'kubernetes.pod.name', - 'url.domain', - 'container.id', - 'service.node.name', -].map((label) => ({ label })); +interface Props { + onClose: () => void; +} -export function LatencyCorrelations() { +export function LatencyCorrelations({ onClose }: Props) { const [ selectedSignificantTerm, setSelectedSignificantTerm, ] = useState(null); - const [fieldNames, setFieldNames] = useState(initialFieldNames); - const [durationPercentile, setDurationPercentile] = useState('50'); + const defaultFieldNames = useDefaultFieldNames(); + const [fieldNames, setFieldNames] = useState(defaultFieldNames); + const [durationPercentile, setDurationPercentile] = useState(50); const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); const { transactionName, transactionType, start, end } = urlParams; @@ -77,8 +64,8 @@ export function LatencyCorrelations() { start, end, uiFilters: JSON.stringify(uiFilters), - durationPercentile, - fieldNames: fieldNames.map((field) => field.label).join(','), + durationPercentile: durationPercentile.toString(10), + fieldNames: fieldNames.join(','), }, }, }); @@ -99,10 +86,20 @@ export function LatencyCorrelations() { return ( <> + + +

+ Orbiting this at a distance of roughly ninety-two million miles is + an utterly insignificant little blue green planet whose ape- + descended life forms are so amazingly primitive that they still + think digital watches are a pretty neat idea. +

+
+
- +

{i18n.translate( 'xpack.apm.correlations.latency.chart.title', @@ -127,60 +124,18 @@ export function LatencyCorrelations() { significantTerms={data?.significantTerms} status={status} setSelectedSignificantTerm={setSelectedSignificantTerm} + onFilter={onClose} /> - - - - - - setDurationPercentile(e.currentTarget.value) - } - /> - - - - - { - setFieldNames((names) => [...names, { label: term }]); - }} - /> - - - - + diff --git a/x-pack/plugins/apm/public/components/app/Correlations/throughput_correlations.tsx b/x-pack/plugins/apm/public/components/app/Correlations/throughput_correlations.tsx index b6dfbbd6fbb7c9..b11f98e7421064 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/throughput_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/throughput_correlations.tsx @@ -1,7 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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 { diff --git a/x-pack/plugins/apm/public/components/app/Correlations/useDefaultFieldNames.ts b/x-pack/plugins/apm/public/components/app/Correlations/useDefaultFieldNames.ts new file mode 100644 index 00000000000000..a9dcfad1f64ffb --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/Correlations/useDefaultFieldNames.ts @@ -0,0 +1,28 @@ +/* + * 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 { isRumAgentName } from '../../../../common/agent_name'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; + +export function useDefaultFieldNames() { + const { agentName } = useApmServiceContext(); + const isRumAgent = isRumAgentName(agentName); + return isRumAgent + ? [ + // 'labels', + 'user_agent.name', + 'user_agent.os.name', + 'url.original', + // 'user fields' + ] + : [ + // 'labels', + 'service.version', + 'service.node.name', + 'host.ip', + ]; +} diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 2214dc2a5e034f..799802b5bbb68f 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -58,7 +58,11 @@ export function ServiceOverview({ return ( - + {isRumAgent && ( diff --git a/x-pack/plugins/apm/public/components/shared/search_bar.tsx b/x-pack/plugins/apm/public/components/shared/search_bar.tsx index 34ba1d86264c17..eb5dbd086bddd4 100644 --- a/x-pack/plugins/apm/public/components/shared/search_bar.tsx +++ b/x-pack/plugins/apm/public/components/shared/search_bar.tsx @@ -13,6 +13,7 @@ import { DatePicker } from './DatePicker'; import { KueryBar } from './KueryBar'; import { TimeComparison } from './time_comparison'; import { useBreakPoints } from '../../hooks/use_break_points'; +import { Correlations } from '../app/correlations'; const SearchBarFlexGroup = styled(EuiFlexGroup)` margin: ${({ theme }) => @@ -22,17 +23,27 @@ const SearchBarFlexGroup = styled(EuiFlexGroup)` interface Props { prepend?: React.ReactNode | string; showTimeComparison?: boolean; + showCorrelations?: boolean; } function getRowDirection(showColumn: boolean) { return showColumn ? 'column' : 'row'; } -export function SearchBar({ prepend, showTimeComparison = false }: Props) { +export function SearchBar({ + prepend, + showTimeComparison = false, + showCorrelations = false, +}: Props) { const { isMedium, isLarge } = useBreakPoints(); const itemsStyle = { marginBottom: isLarge ? px(unit) : 0 }; return ( + {showCorrelations && ( + + + + )} diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts index 686629b0d7d180..577fad940ba245 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts @@ -121,7 +121,7 @@ export async function getErrorRateTimeSeries({ topSigTerms: TopSigTerm[]; }) { const { start, end, apmEventClient } = setup; - const { intervalString } = getBucketSize({ start, end, numBuckets: 30 }); + const { intervalString } = getBucketSize({ start, end, numBuckets: 15 }); if (isEmpty(topSigTerms)) { return {}; diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts index 9601700df4a5a7..47cd5199792430 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts +++ b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isEmpty } from 'lodash'; +import { isEmpty, dropRightWhile } from 'lodash'; import { AggregationOptionsByType } from '../../../../../../typings/elasticsearch/aggregations'; import { ESFilter } from '../../../../../../typings/elasticsearch'; import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; @@ -39,8 +39,8 @@ export async function getLatencyDistribution({ return {}; } - const intervalBuckets = 20; - const distributionInterval = roundtoTenth(maxLatency / intervalBuckets); + const intervalBuckets = 15; + const distributionInterval = Math.floor(maxLatency / intervalBuckets); const distributionAgg = { // filter out outliers not included in the significant term docs @@ -107,7 +107,14 @@ export async function getLatencyDistribution({ function formatDistribution(distribution: Agg['distribution']) { const total = distribution.doc_count; - return distribution.dist_filtered_by_latency.buckets.map((bucket) => ({ + + // remove trailing buckets that are empty and out of bounds of the desired number of buckets + const buckets = dropRightWhile( + distribution.dist_filtered_by_latency.buckets, + (bucket, index) => bucket.doc_count === 0 && index > intervalBuckets - 1 + ); + + return buckets.map((bucket) => ({ x: bucket.key, y: (bucket.doc_count / total) * 100, })); @@ -129,7 +136,3 @@ export async function getLatencyDistribution({ }), }; } - -function roundtoTenth(v: number) { - return Math.pow(10, Math.round(Math.log10(v))); -}