diff --git a/x-pack/plugins/apm/common/search_strategies/constants.ts b/x-pack/plugins/apm/common/search_strategies/constants.ts new file mode 100644 index 0000000000000..b1bd321e1c914 --- /dev/null +++ b/x-pack/plugins/apm/common/search_strategies/constants.ts @@ -0,0 +1,14 @@ +/* + * 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. + */ + +export const APM_SEARCH_STRATEGIES = { + APM_FAILED_TRANSACTIONS_CORRELATIONS: 'apmFailedTransactionsCorrelations', + APM_LATENCY_CORRELATIONS: 'apmLatencyCorrelations', +} as const; +export type ApmSearchStrategies = typeof APM_SEARCH_STRATEGIES[keyof typeof APM_SEARCH_STRATEGIES]; + +export const DEFAULT_PERCENTILE_THRESHOLD = 95; diff --git a/x-pack/plugins/apm/common/search_strategies/correlations/types.ts b/x-pack/plugins/apm/common/search_strategies/correlations/types.ts deleted file mode 100644 index 886c5fd6161d8..0000000000000 --- a/x-pack/plugins/apm/common/search_strategies/correlations/types.ts +++ /dev/null @@ -1,63 +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. - */ - -export interface HistogramItem { - key: number; - doc_count: number; -} - -export interface ResponseHitSource { - [s: string]: unknown; -} - -export interface ResponseHit { - _source: ResponseHitSource; -} - -export interface SearchServiceParams { - environment: string; - kuery: string; - serviceName?: string; - transactionName?: string; - transactionType?: string; - start?: string; - end?: string; - percentileThreshold?: number; - analyzeCorrelations?: boolean; -} - -export interface SearchServiceFetchParams extends SearchServiceParams { - index: string; - includeFrozen?: boolean; -} - -export interface SearchServiceValue { - histogram: HistogramItem[]; - value: string; - field: string; - correlation: number; - ksTest: number; - duplicatedFields?: string[]; -} - -export interface AsyncSearchProviderProgress { - started: number; - loadedHistogramStepsize: number; - loadedOverallHistogram: number; - loadedFieldCanditates: number; - loadedFieldValuePairs: number; - loadedHistograms: number; -} - -export interface SearchServiceRawResponse { - ccsWarning: boolean; - log: string[]; - overallHistogram?: HistogramItem[]; - percentileThresholdValue?: number; - took: number; - values: SearchServiceValue[]; -} diff --git a/x-pack/plugins/apm/common/search_strategies/failure_correlations/constants.ts b/x-pack/plugins/apm/common/search_strategies/failed_transactions_correlations/constants.ts similarity index 86% rename from x-pack/plugins/apm/common/search_strategies/failure_correlations/constants.ts rename to x-pack/plugins/apm/common/search_strategies/failed_transactions_correlations/constants.ts index a80918f0e399e..09e3e22a1d352 100644 --- a/x-pack/plugins/apm/common/search_strategies/failure_correlations/constants.ts +++ b/x-pack/plugins/apm/common/search_strategies/failed_transactions_correlations/constants.ts @@ -7,9 +7,6 @@ import { i18n } from '@kbn/i18n'; -export const FAILED_TRANSACTIONS_CORRELATION_SEARCH_STRATEGY = - 'apmFailedTransactionsCorrelationsSearchStrategy'; - export const FAILED_TRANSACTIONS_IMPACT_THRESHOLD = { HIGH: i18n.translate( 'xpack.apm.correlations.failedTransactions.highImpactText', diff --git a/x-pack/plugins/apm/common/search_strategies/failed_transactions_correlations/types.ts b/x-pack/plugins/apm/common/search_strategies/failed_transactions_correlations/types.ts new file mode 100644 index 0000000000000..dca07e52107e7 --- /dev/null +++ b/x-pack/plugins/apm/common/search_strategies/failed_transactions_correlations/types.ts @@ -0,0 +1,34 @@ +/* + * 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 { + FieldValuePair, + RawResponseBase, + SearchStrategyClientParams, +} from '../types'; + +import { FAILED_TRANSACTIONS_IMPACT_THRESHOLD } from './constants'; + +export interface FailedTransactionsCorrelation extends FieldValuePair { + doc_count: number; + bg_count: number; + score: number; + pValue: number | null; + normalizedScore: number; + failurePercentage: number; + successPercentage: number; +} + +export type FailedTransactionsCorrelationsImpactThreshold = typeof FAILED_TRANSACTIONS_IMPACT_THRESHOLD[keyof typeof FAILED_TRANSACTIONS_IMPACT_THRESHOLD]; + +export type FailedTransactionsCorrelationsParams = SearchStrategyClientParams; + +export interface FailedTransactionsCorrelationsRawResponse + extends RawResponseBase { + log: string[]; + failedTransactionsCorrelations: FailedTransactionsCorrelation[]; +} diff --git a/x-pack/plugins/apm/common/search_strategies/failure_correlations/types.ts b/x-pack/plugins/apm/common/search_strategies/failure_correlations/types.ts deleted file mode 100644 index 2b0d2b5642e0c..0000000000000 --- a/x-pack/plugins/apm/common/search_strategies/failure_correlations/types.ts +++ /dev/null @@ -1,28 +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 { FAILED_TRANSACTIONS_IMPACT_THRESHOLD } from './constants'; - -export interface FailedTransactionsCorrelationValue { - key: string; - doc_count: number; - bg_count: number; - score: number; - pValue: number | null; - fieldName: string; - fieldValue: string; - normalizedScore: number; - failurePercentage: number; - successPercentage: number; -} - -export type FailureCorrelationImpactThreshold = typeof FAILED_TRANSACTIONS_IMPACT_THRESHOLD[keyof typeof FAILED_TRANSACTIONS_IMPACT_THRESHOLD]; - -export interface CorrelationsTerm { - fieldName: string; - fieldValue: string; -} diff --git a/x-pack/plugins/apm/common/search_strategies/latency_correlations/types.ts b/x-pack/plugins/apm/common/search_strategies/latency_correlations/types.ts new file mode 100644 index 0000000000000..29f419da350ef --- /dev/null +++ b/x-pack/plugins/apm/common/search_strategies/latency_correlations/types.ts @@ -0,0 +1,40 @@ +/* + * 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 { + FieldValuePair, + HistogramItem, + RawResponseBase, + SearchStrategyClientParams, +} from '../types'; + +export interface LatencyCorrelation extends FieldValuePair { + correlation: number; + histogram: HistogramItem[]; + ksTest: number; +} + +export interface LatencyCorrelationSearchServiceProgress { + started: number; + loadedHistogramStepsize: number; + loadedOverallHistogram: number; + loadedFieldCandidates: number; + loadedFieldValuePairs: number; + loadedHistograms: number; +} + +export interface LatencyCorrelationsParams extends SearchStrategyClientParams { + percentileThreshold: number; + analyzeCorrelations: boolean; +} + +export interface LatencyCorrelationsRawResponse extends RawResponseBase { + log: string[]; + overallHistogram?: HistogramItem[]; + percentileThresholdValue?: number; + latencyCorrelations?: LatencyCorrelation[]; +} diff --git a/x-pack/plugins/apm/common/search_strategies/types.ts b/x-pack/plugins/apm/common/search_strategies/types.ts new file mode 100644 index 0000000000000..d7c6eab1f07c1 --- /dev/null +++ b/x-pack/plugins/apm/common/search_strategies/types.ts @@ -0,0 +1,50 @@ +/* + * 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. + */ + +export interface FieldValuePair { + fieldName: string; + // For dynamic fieldValues we only identify fields as `string`, + // but for example `http.response.status_code` which is part of + // of the list of predefined field candidates is of type long/number. + fieldValue: string | number; +} + +export interface HistogramItem { + key: number; + doc_count: number; +} + +export interface ResponseHitSource { + [s: string]: unknown; +} + +export interface ResponseHit { + _source: ResponseHitSource; +} + +export interface RawResponseBase { + ccsWarning: boolean; + took: number; +} + +export interface SearchStrategyClientParams { + environment: string; + kuery: string; + serviceName?: string; + transactionName?: string; + transactionType?: string; + start?: string; + end?: string; +} + +export interface SearchStrategyServerParams { + index: string; + includeFrozen?: boolean; +} + +export type SearchStrategyParams = SearchStrategyClientParams & + SearchStrategyServerParams; 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 f7e62b76a61c0..c700533ca4f45 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 @@ -14,28 +14,23 @@ import type { Criteria } from '@elastic/eui/src/components/basic_table/basic_tab import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { useUiTracker } from '../../../../../observability/public'; import { useTheme } from '../../../hooks/use_theme'; -import type { CorrelationsTerm } from '../../../../common/search_strategies/failure_correlations/types'; +import type { FieldValuePair } from '../../../../common/search_strategies/types'; const PAGINATION_SIZE_OPTIONS = [5, 10, 20, 50]; -export type SelectedCorrelationTerm = Pick< - T, - 'fieldName' | 'fieldValue' ->; - -interface Props { +interface CorrelationsTableProps { significantTerms?: T[]; status: FETCH_STATUS; percentageColumnName?: string; setSelectedSignificantTerm: (term: T | null) => void; - selectedTerm?: { fieldName: string; fieldValue: string }; + selectedTerm?: FieldValuePair; onFilter?: () => void; columns: Array>; onTableChange: (c: Criteria) => void; sorting?: EuiTableSortingType; } -export function CorrelationsTable({ +export function CorrelationsTable({ significantTerms, status, setSelectedSignificantTerm, @@ -43,7 +38,7 @@ export function CorrelationsTable({ selectedTerm, onTableChange, sorting, -}: Props) { +}: CorrelationsTableProps) { const euiTheme = useTheme(); const trackApmEvent = useUiTracker({ app: 'apm' }); const trackSelectSignificantCorrelationTerm = useCallback( diff --git a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx index 4fb7bf5d6fcfb..fc1d9a3324b24 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx @@ -6,6 +6,9 @@ */ import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { orderBy } from 'lodash'; + import { EuiBasicTableColumn, EuiFlexGroup, @@ -18,33 +21,35 @@ import { EuiBadge, EuiToolTip, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useHistory } from 'react-router-dom'; -import { orderBy } from 'lodash'; import type { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; import type { Direction } from '@elastic/eui/src/services/sort/sort_direction'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; + +import { i18n } from '@kbn/i18n'; +import { + enableInspectEsQueries, + useUiTracker, +} from '../../../../../observability/public'; + +import { asPercent } from '../../../../common/utils/formatters'; +import { FailedTransactionsCorrelation } from '../../../../common/search_strategies/failed_transactions_correlations/types'; +import { APM_SEARCH_STRATEGIES } from '../../../../common/search_strategies/constants'; + import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { useSearchStrategy } from '../../../hooks/use_search_strategy'; + +import { ImpactBar } from '../../shared/ImpactBar'; +import { createHref, push } from '../../shared/Links/url_helpers'; +import { Summary } from '../../shared/Summary'; + import { CorrelationsTable } from './correlations_table'; -import { enableInspectEsQueries } from '../../../../../observability/public'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { FailedTransactionsCorrelationsHelpPopover } from './failed_transactions_correlations_help_popover'; -import { ImpactBar } from '../../shared/ImpactBar'; import { isErrorMessage } from './utils/is_error_message'; -import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { getFailedTransactionsCorrelationImpactLabel } from './utils/get_failed_transactions_correlation_impact_label'; -import { createHref, push } from '../../shared/Links/url_helpers'; -import { useUiTracker } from '../../../../../observability/public'; -import { useFailedTransactionsCorrelationsFetcher } from '../../../hooks/use_failed_transactions_correlations_fetcher'; -import { useApmParams } from '../../../hooks/use_apm_params'; import { CorrelationsLog } from './correlations_log'; import { CorrelationsEmptyStatePrompt } from './empty_state_prompt'; import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning'; import { CorrelationsProgressControls } from './progress_controls'; -import type { FailedTransactionsCorrelationValue } from '../../../../common/search_strategies/failure_correlations/types'; -import { Summary } from '../../shared/Summary'; -import { asPercent } from '../../../../common/utils/formatters'; -import { useTimeRange } from '../../../hooks/use_time_range'; export function FailedTransactionsCorrelations({ onFilter, @@ -56,78 +61,28 @@ export function FailedTransactionsCorrelations({ } = useApmPluginContext(); const trackApmEvent = useUiTracker({ app: 'apm' }); - const { serviceName, transactionType } = useApmServiceContext(); - - const { - query: { kuery, environment, rangeFrom, rangeTo }, - } = useApmParams('/services/:serviceName'); - - const { urlParams } = useUrlParams(); - const { transactionName } = urlParams; - - const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const inspectEnabled = uiSettings.get(enableInspectEsQueries); - const result = useFailedTransactionsCorrelationsFetcher(); - - const { - ccsWarning, - log, - error, - isRunning, - progress, - startFetch, - cancelFetch, - } = result; - - const startFetchHandler = useCallback(() => { - startFetch({ - environment, - kuery, - serviceName, - transactionName, - transactionType, - start, - end, - }); - }, [ - startFetch, - environment, - serviceName, - transactionName, - transactionType, - kuery, - start, - end, - ]); - - useEffect(() => { - startFetchHandler(); - return cancelFetch; - }, [cancelFetch, startFetchHandler]); + const { progress, response, startFetch, cancelFetch } = useSearchStrategy( + APM_SEARCH_STRATEGIES.APM_FAILED_TRANSACTIONS_CORRELATIONS + ); + const progressNormalized = progress.loaded / progress.total; const [ selectedSignificantTerm, setSelectedSignificantTerm, - ] = useState(null); + ] = useState(null); - const selectedTerm = useMemo(() => { - if (selectedSignificantTerm) return selectedSignificantTerm; - return result?.values && - Array.isArray(result.values) && - result.values.length > 0 - ? result?.values[0] - : undefined; - }, [selectedSignificantTerm, result]); + const selectedTerm = + selectedSignificantTerm ?? response.failedTransactionsCorrelations?.[0]; const history = useHistory(); const failedTransactionsCorrelationsColumns: Array< - EuiBasicTableColumn + EuiBasicTableColumn > = useMemo(() => { const percentageColumns: Array< - EuiBasicTableColumn + EuiBasicTableColumn > = inspectEnabled ? [ { @@ -159,7 +114,7 @@ export function FailedTransactionsCorrelations({ ), - render: (failurePercentage: number) => + render: (_, { failurePercentage }) => asPercent(failurePercentage, 1), sortable: true, }, @@ -193,7 +148,7 @@ export function FailedTransactionsCorrelations({ ), - render: (successPercentage: number) => + render: (_, { successPercentage }) => asPercent(successPercentage, 1), sortable: true, }, @@ -213,7 +168,7 @@ export function FailedTransactionsCorrelations({ )} ), - render: (normalizedScore: number) => { + render: (_, { normalizedScore }) => { return ( <> @@ -235,7 +190,7 @@ export function FailedTransactionsCorrelations({ )} ), - render: (pValue: number) => { + render: (_, { pValue }) => { const label = getFailedTransactionsCorrelationImpactLabel(pValue); return label ? ( {label.impact} @@ -252,12 +207,12 @@ export function FailedTransactionsCorrelations({ sortable: true, }, { - field: 'key', + field: 'fieldValue', name: i18n.translate( 'xpack.apm.correlations.failedTransactions.correlationsTable.fieldValueLabel', { defaultMessage: 'Field value' } ), - render: (fieldValue: string) => String(fieldValue).slice(0, 50), + render: (_, { fieldValue }) => String(fieldValue).slice(0, 50), sortable: true, }, ...percentageColumns, @@ -275,7 +230,7 @@ export function FailedTransactionsCorrelations({ ), icon: 'plusInCircle', type: 'icon', - onClick: (term: FailedTransactionsCorrelationValue) => { + onClick: (term: FailedTransactionsCorrelation) => { push(history, { query: { kuery: `${term.fieldName}:"${term.fieldValue}"`, @@ -296,7 +251,7 @@ export function FailedTransactionsCorrelations({ ), icon: 'minusInCircle', type: 'icon', - onClick: (term: FailedTransactionsCorrelationValue) => { + onClick: (term: FailedTransactionsCorrelation) => { push(history, { query: { kuery: `not ${term.fieldName}:"${term.fieldValue}"`, @@ -311,13 +266,13 @@ export function FailedTransactionsCorrelations({ 'xpack.apm.correlations.correlationsTable.actionsLabel', { defaultMessage: 'Filter' } ), - render: (_: unknown, term: FailedTransactionsCorrelationValue) => { + render: (_, { fieldName, fieldValue }) => { return ( <> @@ -327,7 +282,7 @@ export function FailedTransactionsCorrelations({ @@ -337,11 +292,11 @@ export function FailedTransactionsCorrelations({ ); }, }, - ] as Array>; + ] as Array>; }, [history, onFilter, trackApmEvent, inspectEnabled]); useEffect(() => { - if (isErrorMessage(error)) { + if (isErrorMessage(progress.error)) { notifications.toasts.addDanger({ title: i18n.translate( 'xpack.apm.correlations.failedTransactions.errorTitle', @@ -350,13 +305,13 @@ export function FailedTransactionsCorrelations({ 'An error occurred performing correlations on failed transactions', } ), - text: error.toString(), + text: progress.error.toString(), }); } - }, [error, notifications.toasts]); + }, [progress.error, notifications.toasts]); const [sortField, setSortField] = useState< - keyof FailedTransactionsCorrelationValue + keyof FailedTransactionsCorrelation >('normalizedScore'); const [sortDirection, setSortDirection] = useState('desc'); @@ -367,28 +322,32 @@ export function FailedTransactionsCorrelations({ setSortDirection(currentSortDirection); }, []); - const { sorting, correlationTerms } = useMemo(() => { - if (!Array.isArray(result.values)) { - return { correlationTerms: [], sorting: undefined }; - } - const orderedTerms = orderBy( - result.values, - // The smaller the p value the higher the impact - // So we want to sort by the normalized score here - // which goes from 0 -> 1 - sortField === 'pValue' ? 'normalizedScore' : sortField, - sortDirection - ); - return { - correlationTerms: orderedTerms, - sorting: { - sort: { - field: sortField, - direction: sortDirection, - }, - } as EuiTableSortingType, - }; - }, [result?.values, sortField, sortDirection]); + const sorting: EuiTableSortingType = { + sort: { field: sortField, direction: sortDirection }, + }; + + const correlationTerms = useMemo( + () => + orderBy( + response.failedTransactionsCorrelations, + // The smaller the p value the higher the impact + // So we want to sort by the normalized score here + // which goes from 0 -> 1 + sortField === 'pValue' ? 'normalizedScore' : sortField, + sortDirection + ), + [response.failedTransactionsCorrelations, sortField, sortDirection] + ); + + const showCorrelationsTable = + progress.isRunning || correlationTerms.length > 0; + + const showCorrelationsEmptyStatePrompt = + correlationTerms.length < 1 && + (progressNormalized === 1 || !progress.isRunning); + + const showSummaryBadge = + inspectEnabled && (progress.isRunning || correlationTerms.length > 0); return (
@@ -456,54 +415,53 @@ export function FailedTransactionsCorrelations({ - {ccsWarning && ( + {response.ccsWarning && ( <> + {/* Failed transactions correlations uses ES aggs that are available since 7.15 */} )} - {inspectEnabled && - selectedTerm?.pValue != null && - (isRunning || correlationTerms.length > 0) ? ( + {showSummaryBadge && selectedTerm?.pValue && ( <> - {`${selectedTerm.fieldName}: ${selectedTerm.key}`} + {`${selectedTerm.fieldName}: ${selectedTerm.fieldValue}`} , <>{`p-value: ${selectedTerm.pValue.toPrecision(3)}`}, ]} /> - ) : null} + )}
- {(isRunning || correlationTerms.length > 0) && ( - + {showCorrelationsTable && ( + columns={failedTransactionsCorrelationsColumns} significantTerms={correlationTerms} - status={isRunning ? FETCH_STATUS.LOADING : FETCH_STATUS.SUCCESS} + status={ + progress.isRunning ? FETCH_STATUS.LOADING : FETCH_STATUS.SUCCESS + } setSelectedSignificantTerm={setSelectedSignificantTerm} selectedTerm={selectedTerm} onTableChange={onTableChange} sorting={sorting} /> )} - {correlationTerms.length < 1 && (progress === 1 || !isRunning) && ( - - )} + {showCorrelationsEmptyStatePrompt && }
- {inspectEnabled && } + {inspectEnabled && }
); } diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.test.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.test.tsx index b0da5b6d60d74..c1fb1beb1918e 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.test.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.test.tsx @@ -18,7 +18,7 @@ import { dataPluginMock } from 'src/plugins/data/public/mocks'; import type { IKibanaSearchResponse } from 'src/plugins/data/public'; import { EuiThemeProvider } from 'src/plugins/kibana_react/common'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; -import type { SearchServiceRawResponse } from '../../../../common/search_strategies/correlations/types'; +import type { LatencyCorrelationsRawResponse } from '../../../../common/search_strategies/latency_correlations/types'; import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { @@ -34,7 +34,7 @@ function Wrapper({ dataSearchResponse, }: { children?: ReactNode; - dataSearchResponse: IKibanaSearchResponse; + dataSearchResponse: IKibanaSearchResponse; }) { const mockDataSearch = jest.fn(() => of(dataSearchResponse)); @@ -97,7 +97,12 @@ describe('correlations', () => { @@ -115,7 +120,12 @@ describe('correlations', () => { 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 ad8a56a3ac6f9..1a769adb621df 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 @@ -7,6 +7,8 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useHistory } from 'react-router-dom'; +import { orderBy } from 'lodash'; + import { EuiIcon, EuiBasicTableColumn, @@ -16,103 +18,61 @@ import { EuiTitle, EuiToolTip, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { Direction } from '@elastic/eui/src/services/sort/sort_direction'; -import { orderBy } from 'lodash'; import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; -import { FETCH_STATUS } from '../../../hooks/use_fetcher'; -import { useTransactionLatencyCorrelationsFetcher } from '../../../hooks/use_transaction_latency_correlations_fetcher'; -import { TransactionDistributionChart } from '../../shared/charts/transaction_distribution_chart'; -import { CorrelationsTable } from './correlations_table'; -import { push } from '../../shared/Links/url_helpers'; + +import { i18n } from '@kbn/i18n'; + import { enableInspectEsQueries, useUiTracker, } from '../../../../../observability/public'; + import { asPreciseDecimal } from '../../../../common/utils/formatters'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { + APM_SEARCH_STRATEGIES, + DEFAULT_PERCENTILE_THRESHOLD, +} from '../../../../common/search_strategies/constants'; +import { LatencyCorrelation } from '../../../../common/search_strategies/latency_correlations/types'; + +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { useSearchStrategy } from '../../../hooks/use_search_strategy'; + +import { TransactionDistributionChart } from '../../shared/charts/transaction_distribution_chart'; +import { push } from '../../shared/Links/url_helpers'; + +import { CorrelationsTable } from './correlations_table'; import { LatencyCorrelationsHelpPopover } from './latency_correlations_help_popover'; -import { useApmParams } from '../../../hooks/use_apm_params'; import { isErrorMessage } from './utils/is_error_message'; +import { getOverallHistogram } from './utils/get_overall_histogram'; import { CorrelationsLog } from './correlations_log'; import { CorrelationsEmptyStatePrompt } from './empty_state_prompt'; import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning'; import { CorrelationsProgressControls } from './progress_controls'; -import { useTimeRange } from '../../../hooks/use_time_range'; - -const DEFAULT_PERCENTILE_THRESHOLD = 95; - -interface MlCorrelationsTerms { - correlation: number; - ksTest: number; - fieldName: string; - fieldValue: string; - duplicatedFields?: string[]; -} export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { const { core: { notifications, uiSettings }, } = useApmPluginContext(); - const { serviceName, transactionType } = useApmServiceContext(); - - const { - query: { kuery, environment, rangeFrom, rangeTo }, - } = useApmParams('/services/:serviceName/transactions/view'); - - const { urlParams } = useUrlParams(); - - const { transactionName } = urlParams; - - const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const displayLog = uiSettings.get(enableInspectEsQueries); - const { - ccsWarning, - log, - error, - histograms, - percentileThresholdValue, - isRunning, - progress, - startFetch, - cancelFetch, - overallHistogram, - } = useTransactionLatencyCorrelationsFetcher(); - - const startFetchHandler = useCallback(() => { - startFetch({ - environment, - kuery, - serviceName, - transactionName, - transactionType, - start, - end, + const { progress, response, startFetch, cancelFetch } = useSearchStrategy( + APM_SEARCH_STRATEGIES.APM_LATENCY_CORRELATIONS, + { percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD, - }); - }, [ - startFetch, - environment, - serviceName, - transactionName, - transactionType, - kuery, - start, - end, - ]); - - useEffect(() => { - startFetchHandler(); - return cancelFetch; - }, [cancelFetch, startFetchHandler]); + analyzeCorrelations: true, + } + ); + const progressNormalized = progress.loaded / progress.total; + const { overallHistogram, hasData, status } = getOverallHistogram( + response, + progress.isRunning + ); useEffect(() => { - if (isErrorMessage(error)) { + if (isErrorMessage(progress.error)) { notifications.toasts.addDanger({ title: i18n.translate( 'xpack.apm.correlations.latencyCorrelations.errorTitle', @@ -120,34 +80,31 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { defaultMessage: 'An error occurred fetching correlations', } ), - text: error.toString(), + text: progress.error.toString(), }); } - }, [error, notifications.toasts]); + }, [progress.error, notifications.toasts]); const [ selectedSignificantTerm, setSelectedSignificantTerm, - ] = useState(null); - - const selectedHistogram = useMemo(() => { - let selected = histograms.length > 0 ? histograms[0] : undefined; + ] = useState(null); - if (histograms.length > 0 && selectedSignificantTerm !== null) { - selected = histograms.find( + const selectedHistogram = useMemo( + () => + response.latencyCorrelations?.find( (h) => - h.field === selectedSignificantTerm.fieldName && - h.value === selectedSignificantTerm.fieldValue - ); - } - return selected; - }, [histograms, selectedSignificantTerm]); + h.fieldName === selectedSignificantTerm?.fieldName && + h.fieldValue === selectedSignificantTerm?.fieldValue + ) ?? response.latencyCorrelations?.[0], + [response.latencyCorrelations, selectedSignificantTerm] + ); const history = useHistory(); const trackApmEvent = useUiTracker({ app: 'apm' }); const mlCorrelationColumns: Array< - EuiBasicTableColumn + EuiBasicTableColumn > = useMemo( () => [ { @@ -179,7 +136,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { ), - render: (correlation: number) => { + render: (_, { correlation }) => { return
{asPreciseDecimal(correlation, 2)}
; }, sortable: true, @@ -198,7 +155,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { 'xpack.apm.correlations.latencyCorrelations.correlationsTable.fieldValueLabel', { defaultMessage: 'Field value' } ), - render: (fieldValue: string) => String(fieldValue).slice(0, 50), + render: (_, { fieldValue }) => String(fieldValue).slice(0, 50), sortable: true, }, { @@ -215,7 +172,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { ), icon: 'plusInCircle', type: 'icon', - onClick: (term: MlCorrelationsTerms) => { + onClick: (term: LatencyCorrelation) => { push(history, { query: { kuery: `${term.fieldName}:"${term.fieldValue}"`, @@ -236,7 +193,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { ), icon: 'minusInCircle', type: 'icon', - onClick: (term: MlCorrelationsTerms) => { + onClick: (term: LatencyCorrelation) => { push(history, { query: { kuery: `not ${term.fieldName}:"${term.fieldValue}"`, @@ -256,7 +213,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { [history, onFilter, trackApmEvent] ); - const [sortField, setSortField] = useState( + const [sortField, setSortField] = useState( 'correlation' ); const [sortDirection, setSortDirection] = useState('desc'); @@ -268,34 +225,19 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { setSortDirection(currentSortDirection); }, []); - const { histogramTerms, sorting } = useMemo(() => { - if (!Array.isArray(histograms)) { - return { histogramTerms: [], sorting: undefined }; - } - const orderedTerms = orderBy( - histograms.map((d) => { - return { - fieldName: d.field, - fieldValue: d.value, - ksTest: d.ksTest, - correlation: d.correlation, - duplicatedFields: d.duplicatedFields, - }; - }), - sortField, - sortDirection - ); - - return { - histogramTerms: orderedTerms, - sorting: { - sort: { - field: sortField, - direction: sortDirection, - }, - } as EuiTableSortingType, - }; - }, [histograms, sortField, sortDirection]); + const sorting: EuiTableSortingType = { + sort: { field: sortField, direction: sortDirection }, + }; + + const histogramTerms = useMemo( + () => orderBy(response.latencyCorrelations ?? [], sortField, sortDirection), + [response.latencyCorrelations, sortField, sortDirection] + ); + + const showCorrelationsTable = progress.isRunning || histogramTerms.length > 0; + const showCorrelationsEmptyStatePrompt = + histogramTerms.length < 1 && + (progressNormalized === 1 || !progress.isRunning); return (
@@ -321,9 +263,11 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { @@ -342,15 +286,16 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { - {ccsWarning && ( + {response.ccsWarning && ( <> + {/* Latency correlations uses ES aggs that are available since 7.14 */} )} @@ -358,29 +303,22 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) {
- {(isRunning || histogramTerms.length > 0) && ( - + {showCorrelationsTable && ( + columns={mlCorrelationColumns} significantTerms={histogramTerms} - status={isRunning ? FETCH_STATUS.LOADING : FETCH_STATUS.SUCCESS} - setSelectedSignificantTerm={setSelectedSignificantTerm} - selectedTerm={ - selectedHistogram !== undefined - ? { - fieldName: selectedHistogram.field, - fieldValue: selectedHistogram.value, - } - : undefined + status={ + progress.isRunning ? FETCH_STATUS.LOADING : FETCH_STATUS.SUCCESS } + setSelectedSignificantTerm={setSelectedSignificantTerm} + selectedTerm={selectedHistogram} onTableChange={onTableChange} sorting={sorting} /> )} - {histogramTerms.length < 1 && (progress === 1 || !isRunning) && ( - - )} + {showCorrelationsEmptyStatePrompt && }
- {displayLog && } + {displayLog && }
); } diff --git a/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.test.ts b/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.test.ts index edb7c8c16e267..e4c08b42b2420 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.test.ts +++ b/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.test.ts @@ -6,7 +6,7 @@ */ import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label'; -import { FAILED_TRANSACTIONS_IMPACT_THRESHOLD } from '../../../../../common/search_strategies/failure_correlations/constants'; +import { FAILED_TRANSACTIONS_IMPACT_THRESHOLD } from '../../../../../common/search_strategies/failed_transactions_correlations/constants'; const EXPECTED_RESULT = { HIGH: { diff --git a/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.ts b/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.ts index 5a806aba5371e..cbfaee88ff6f4 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.ts +++ b/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.ts @@ -5,12 +5,22 @@ * 2.0. */ -import { FailureCorrelationImpactThreshold } from '../../../../../common/search_strategies/failure_correlations/types'; -import { FAILED_TRANSACTIONS_IMPACT_THRESHOLD } from '../../../../../common/search_strategies/failure_correlations/constants'; +import { + FailedTransactionsCorrelation, + FailedTransactionsCorrelationsImpactThreshold, +} from '../../../../../common/search_strategies/failed_transactions_correlations/types'; +import { FAILED_TRANSACTIONS_IMPACT_THRESHOLD } from '../../../../../common/search_strategies/failed_transactions_correlations/constants'; export function getFailedTransactionsCorrelationImpactLabel( - pValue: number -): { impact: FailureCorrelationImpactThreshold; color: string } | null { + pValue: FailedTransactionsCorrelation['pValue'] +): { + impact: FailedTransactionsCorrelationsImpactThreshold; + color: string; +} | null { + if (pValue === null) { + return null; + } + // The lower the p value, the higher the impact if (pValue >= 0 && pValue < 1e-6) return { diff --git a/x-pack/plugins/apm/public/components/app/correlations/utils/get_overall_histogram.test.ts b/x-pack/plugins/apm/public/components/app/correlations/utils/get_overall_histogram.test.ts new file mode 100644 index 0000000000000..c323b69594013 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/correlations/utils/get_overall_histogram.test.ts @@ -0,0 +1,56 @@ +/* + * 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 type { LatencyCorrelationsRawResponse } from '../../../../../common/search_strategies/latency_correlations/types'; + +import { getOverallHistogram } from './get_overall_histogram'; + +describe('getOverallHistogram', () => { + it('returns "loading" when undefined and running', () => { + const { overallHistogram, hasData, status } = getOverallHistogram( + {} as LatencyCorrelationsRawResponse, + true + ); + expect(overallHistogram).toStrictEqual(undefined); + expect(hasData).toBe(false); + expect(status).toBe('loading'); + }); + + it('returns "success" when undefined and not running', () => { + const { overallHistogram, hasData, status } = getOverallHistogram( + {} as LatencyCorrelationsRawResponse, + false + ); + expect(overallHistogram).toStrictEqual([]); + expect(hasData).toBe(false); + expect(status).toBe('success'); + }); + + it('returns "success" when not undefined and still running', () => { + const { overallHistogram, hasData, status } = getOverallHistogram( + { + overallHistogram: [{ key: 1, doc_count: 1234 }], + } as LatencyCorrelationsRawResponse, + true + ); + expect(overallHistogram).toStrictEqual([{ key: 1, doc_count: 1234 }]); + expect(hasData).toBe(true); + expect(status).toBe('success'); + }); + + it('returns "success" when not undefined and not running', () => { + const { overallHistogram, hasData, status } = getOverallHistogram( + { + overallHistogram: [{ key: 1, doc_count: 1234 }], + } as LatencyCorrelationsRawResponse, + false + ); + expect(overallHistogram).toStrictEqual([{ key: 1, doc_count: 1234 }]); + expect(hasData).toBe(true); + expect(status).toBe('success'); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/correlations/utils/get_overall_histogram.ts b/x-pack/plugins/apm/public/components/app/correlations/utils/get_overall_histogram.ts new file mode 100644 index 0000000000000..3a90eb4b89123 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/correlations/utils/get_overall_histogram.ts @@ -0,0 +1,30 @@ +/* + * 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 type { LatencyCorrelationsRawResponse } from '../../../../../common/search_strategies/latency_correlations/types'; + +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; + +// `isRunning` refers to the search strategy as whole which might still be in the process +// of fetching more data such as correlation results. That's why we have to determine +// the `status` of the data for the latency chart separately. +export function getOverallHistogram( + data: LatencyCorrelationsRawResponse, + isRunning: boolean +) { + const overallHistogram = + data.overallHistogram === undefined && !isRunning + ? [] + : data.overallHistogram; + const hasData = + Array.isArray(overallHistogram) && overallHistogram.length > 0; + const status = Array.isArray(overallHistogram) + ? FETCH_STATUS.SUCCESS + : FETCH_STATUS.LOADING; + + return { overallHistogram, hasData, status }; +} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx index 5a9977b373c33..9a38e0fcf6289 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx @@ -16,7 +16,7 @@ import { dataPluginMock } from 'src/plugins/data/public/mocks'; import type { IKibanaSearchResponse } from 'src/plugins/data/public'; import { EuiThemeProvider } from 'src/plugins/kibana_react/common'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; -import type { SearchServiceRawResponse } from '../../../../../common/search_strategies/correlations/types'; +import type { LatencyCorrelationsRawResponse } from '../../../../../common/search_strategies/latency_correlations/types'; import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider'; import { ApmPluginContextValue } from '../../../../context/apm_plugin/apm_plugin_context'; import { @@ -32,7 +32,7 @@ function Wrapper({ dataSearchResponse, }: { children?: ReactNode; - dataSearchResponse: IKibanaSearchResponse; + dataSearchResponse: IKibanaSearchResponse; }) { const mockDataSearch = jest.fn(() => of(dataSearchResponse)); @@ -101,18 +101,22 @@ describe('transaction_details/distribution', () => { describe('TransactionDistribution', () => { it('shows loading indicator when the service is running and returned no results yet', async () => { - const onHasData = jest.fn(); render( ); @@ -120,23 +124,26 @@ describe('transaction_details/distribution', () => { await waitFor(() => { expect(screen.getByTestId('apmCorrelationsChart')).toBeInTheDocument(); expect(screen.getByTestId('loading')).toBeInTheDocument(); - expect(onHasData).toHaveBeenLastCalledWith(false); }); }); it("doesn't show loading indicator when the service isn't running", async () => { - const onHasData = jest.fn(); render( ); @@ -144,7 +151,6 @@ describe('transaction_details/distribution', () => { await waitFor(() => { expect(screen.getByTestId('apmCorrelationsChart')).toBeInTheDocument(); expect(screen.queryByTestId('loading')).toBeNull(); // it doesn't exist - expect(onHasData).toHaveBeenLastCalledWith(false); }); }); }); diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx index 2da61bc0fc555..acd8c5f4d57d3 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { BrushEndListener, XYBrushArea } from '@elastic/charts'; import { EuiBadge, @@ -18,20 +18,24 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; -import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; -import { useTransactionDistributionFetcher } from '../../../../hooks/use_transaction_distribution_fetcher'; import { - OnHasData, - TransactionDistributionChart, -} from '../../../shared/charts/transaction_distribution_chart'; + APM_SEARCH_STRATEGIES, + DEFAULT_PERCENTILE_THRESHOLD, +} from '../../../../../common/search_strategies/constants'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useSearchStrategy } from '../../../../hooks/use_search_strategy'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; + +import { TransactionDistributionChart } from '../../../shared/charts/transaction_distribution_chart'; import { useUiTracker } from '../../../../../../observability/public'; -import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; -import { useApmParams } from '../../../../hooks/use_apm_params'; import { isErrorMessage } from '../../correlations/utils/is_error_message'; -import { useTimeRange } from '../../../../hooks/use_time_range'; +import { getOverallHistogram } from '../../correlations/utils/get_overall_histogram'; + +import type { TabContentProps } from '../types'; +import { useWaterfallFetcher } from '../use_waterfall_fetcher'; +import { WaterfallWithSummary } from '../waterfall_with_summary'; -const DEFAULT_PERCENTILE_THRESHOLD = 95; // Enforce min height so it's consistent across all tabs on the same level // to prevent "flickering" behavior const MIN_TAB_TITLE_HEIGHT = 56; @@ -51,45 +55,32 @@ export function getFormattedSelection(selection: Selection): string { } interface TransactionDistributionProps { - markerCurrentTransaction?: number; onChartSelection: BrushEndListener; onClearSelection: () => void; - onHasData: OnHasData; selection?: Selection; + traceSamples: TabContentProps['traceSamples']; } export function TransactionDistribution({ - markerCurrentTransaction, onChartSelection, onClearSelection, - onHasData, selection, + traceSamples, }: TransactionDistributionProps) { const { core: { notifications }, } = useApmPluginContext(); - const { serviceName, transactionType } = useApmServiceContext(); - - const { - query: { kuery, environment, rangeFrom, rangeTo }, - } = useApmParams('/services/:serviceName/transactions/view'); - - const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const { urlParams } = useUrlParams(); - const { transactionName } = urlParams; - - const [showSelection, setShowSelection] = useState(false); + const { + waterfall, + exceedsMax, + status: waterfallStatus, + } = useWaterfallFetcher(); - const onTransactionDistributionHasData: OnHasData = useCallback( - (hasData) => { - setShowSelection(hasData); - onHasData(hasData); - }, - [onHasData] - ); + const markerCurrentTransaction = + waterfall.entryWaterfallTransaction?.doc.transaction.duration.us; const emptySelectionText = i18n.translate( 'xpack.apm.transactionDetails.emptySelectionText', @@ -105,43 +96,20 @@ export function TransactionDistribution({ } ); - const { - error, - percentileThresholdValue, - startFetch, - cancelFetch, - transactionDistribution, - } = useTransactionDistributionFetcher(); - - const startFetchHandler = useCallback(() => { - startFetch({ - environment, - kuery, - serviceName, - transactionName, - transactionType, - start, - end, + const { progress, response } = useSearchStrategy( + APM_SEARCH_STRATEGIES.APM_LATENCY_CORRELATIONS, + { percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD, - }); - }, [ - startFetch, - environment, - serviceName, - transactionName, - transactionType, - kuery, - start, - end, - ]); - - useEffect(() => { - startFetchHandler(); - return cancelFetch; - }, [cancelFetch, startFetchHandler]); + analyzeCorrelations: false, + } + ); + const { overallHistogram, hasData, status } = getOverallHistogram( + response, + progress.isRunning + ); useEffect(() => { - if (isErrorMessage(error)) { + if (isErrorMessage(progress.error)) { notifications.toasts.addDanger({ title: i18n.translate( 'xpack.apm.transactionDetails.distribution.errorTitle', @@ -149,10 +117,10 @@ export function TransactionDistribution({ defaultMessage: 'An error occurred fetching the distribution', } ), - text: error.toString(), + text: progress.error.toString(), }); } - }, [error, notifications.toasts]); + }, [progress.error, notifications.toasts]); const trackApmEvent = useUiTracker({ app: 'apm' }); @@ -183,7 +151,7 @@ export function TransactionDistribution({ - {showSelection && !selection && ( + {hasData && !selection && ( )} - {showSelection && selection && ( + {hasData && selection && ( + + {hasData && ( + <> + + + + + )} ); } diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/trace_samples_tab.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/trace_samples_tab.tsx index ea02cfea5a941..ad629b7a2d132 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/trace_samples_tab.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/trace_samples_tab.tsx @@ -5,19 +5,12 @@ * 2.0. */ -import React, { useState } from 'react'; - -import { EuiSpacer } from '@elastic/eui'; +import React from 'react'; import { i18n } from '@kbn/i18n'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { FETCH_STATUS } from '../../../hooks/use_fetcher'; - import { TransactionDistribution } from './distribution'; -import { useWaterfallFetcher } from './use_waterfall_fetcher'; import type { TabContentProps } from './types'; -import { WaterfallWithSummary } from './waterfall_with_summary'; function TraceSamplesTab({ selectSampleFromChartSelection, @@ -26,49 +19,17 @@ function TraceSamplesTab({ sampleRangeTo, traceSamples, }: TabContentProps) { - const { urlParams } = useUrlParams(); - - const { - waterfall, - exceedsMax, - status: waterfallStatus, - } = useWaterfallFetcher(); - - const [ - transactionDistributionHasData, - setTransactionDistributionHasData, - ] = useState(false); - return ( - <> - - - {transactionDistributionHasData && ( - <> - - - - - )} - + ); } diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx index a58a2887b1576..ee5ae0d4dc840 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_distribution_chart/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { AnnotationDomainType, AreaSeries, @@ -30,31 +30,28 @@ import { i18n } from '@kbn/i18n'; import { useChartTheme } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; -import { HistogramItem } from '../../../../../common/search_strategies/correlations/types'; +import type { + FieldValuePair, + HistogramItem, +} from '../../../../../common/search_strategies/types'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; -import { ChartContainer, ChartContainerProps } from '../chart_container'; - -export type TransactionDistributionChartLoadingState = Pick< - ChartContainerProps, - 'hasData' | 'status' ->; - -export type OnHasData = (hasData: boolean) => void; +import { ChartContainer } from '../chart_container'; interface TransactionDistributionChartProps { - field?: string; - value?: string; + fieldName?: FieldValuePair['fieldName']; + fieldValue?: FieldValuePair['fieldValue']; + hasData: boolean; histogram?: HistogramItem[]; markerCurrentTransaction?: number; markerValue: number; markerPercentile: number; overallHistogram?: HistogramItem[]; onChartSelection?: BrushEndListener; - onHasData?: OnHasData; selection?: [number, number]; + status: FETCH_STATUS; } const getAnnotationsStyle = (color = 'gray'): LineAnnotationStyle => ({ @@ -103,16 +100,17 @@ const xAxisTickFormat: TickFormatter = (d) => getDurationFormatter(d, 0.9999)(d).formatted; export function TransactionDistributionChart({ - field: fieldName, - value: fieldValue, + fieldName, + fieldValue, + hasData, histogram: originalHistogram, markerCurrentTransaction, markerValue, markerPercentile, overallHistogram, onChartSelection, - onHasData, selection, + status, }: TransactionDistributionChartProps) { const chartTheme = useChartTheme(); const euiTheme = useTheme(); @@ -163,34 +161,12 @@ export function TransactionDistributionChart({ ] : undefined; - const chartLoadingState: TransactionDistributionChartLoadingState = useMemo( - () => ({ - hasData: - Array.isArray(patchedOverallHistogram) && - patchedOverallHistogram.length > 0, - status: Array.isArray(patchedOverallHistogram) - ? FETCH_STATUS.SUCCESS - : FETCH_STATUS.LOADING, - }), - [patchedOverallHistogram] - ); - - useEffect(() => { - if (onHasData) { - onHasData(chartLoadingState.hasData); - } - }, [chartLoadingState, onHasData]); - return (
- + { - const { - services: { data }, - } = useKibana(); - - const [ - fetchState, - setFetchState, - ] = useState({ - isComplete: false, - isRunning: false, - loaded: 0, - ccsWarning: false, - values: [], - log: [], - total: 100, - }); - - const abortCtrl = useRef(new AbortController()); - const searchSubscription$ = useRef(); - - function setResponse(response: IKibanaSearchResponse) { - setFetchState((prevState) => ({ - ...prevState, - isRunning: response.isRunning || false, - ccsWarning: response.rawResponse?.ccsWarning ?? false, - values: response.rawResponse?.values ?? [], - log: response.rawResponse?.log ?? [], - loaded: response.loaded!, - total: response.total!, - timeTook: response.rawResponse.took, - })); - } - - const startFetch = useCallback( - (params: SearchServiceParams) => { - setFetchState((prevState) => ({ - ...prevState, - error: undefined, - isComplete: false, - })); - searchSubscription$.current?.unsubscribe(); - abortCtrl.current.abort(); - abortCtrl.current = new AbortController(); - - const req = { params }; - - // Submit the search request using the `data.search` service. - searchSubscription$.current = data.search - .search>(req, { - strategy: FAILED_TRANSACTIONS_CORRELATION_SEARCH_STRATEGY, - abortSignal: abortCtrl.current.signal, - }) - .subscribe({ - next: (res: IKibanaSearchResponse) => { - setResponse(res); - if (isCompleteResponse(res)) { - searchSubscription$.current?.unsubscribe(); - setFetchState((prevState) => ({ - ...prevState, - isRunnning: false, - isComplete: true, - })); - } else if (isErrorResponse(res)) { - searchSubscription$.current?.unsubscribe(); - setFetchState((prevState) => ({ - ...prevState, - error: (res as unknown) as Error, - isRunning: false, - })); - } - }, - error: (error: Error) => { - setFetchState((prevState) => ({ - ...prevState, - error, - isRunning: false, - })); - }, - }); - }, - [data.search, setFetchState] - ); - - const cancelFetch = useCallback(() => { - searchSubscription$.current?.unsubscribe(); - searchSubscription$.current = undefined; - abortCtrl.current.abort(); - setFetchState((prevState) => ({ - ...prevState, - isRunning: false, - })); - }, [setFetchState]); - - return { - ...fetchState, - progress: fetchState.loaded / fetchState.total, - startFetch, - cancelFetch, - }; -}; diff --git a/x-pack/plugins/apm/public/hooks/use_search_strategy.ts b/x-pack/plugins/apm/public/hooks/use_search_strategy.ts new file mode 100644 index 0000000000000..6f6c9bf151c00 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_search_strategy.ts @@ -0,0 +1,208 @@ +/* + * 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 { useCallback, useEffect, useReducer, useRef } from 'react'; +import type { Subscription } from 'rxjs'; + +import { + IKibanaSearchRequest, + IKibanaSearchResponse, + isCompleteResponse, + isErrorResponse, +} from '../../../../../src/plugins/data/public'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; + +import type { SearchStrategyClientParams } from '../../common/search_strategies/types'; +import type { RawResponseBase } from '../../common/search_strategies/types'; +import type { LatencyCorrelationsRawResponse } from '../../common/search_strategies/latency_correlations/types'; +import type { FailedTransactionsCorrelationsRawResponse } from '../../common/search_strategies/failed_transactions_correlations/types'; +import { + ApmSearchStrategies, + APM_SEARCH_STRATEGIES, +} from '../../common/search_strategies/constants'; +import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; + +import { ApmPluginStartDeps } from '../plugin'; + +import { useApmParams } from './use_apm_params'; +import { useTimeRange } from './use_time_range'; + +interface SearchStrategyProgress { + error?: Error; + isRunning: boolean; + loaded: number; + total: number; +} + +const getInitialRawResponse = < + TRawResponse extends RawResponseBase +>(): TRawResponse => + ({ + ccsWarning: false, + took: 0, + } as TRawResponse); + +const getInitialProgress = (): SearchStrategyProgress => ({ + isRunning: false, + loaded: 0, + total: 100, +}); + +const getReducer = () => (prev: T, update: Partial): T => ({ + ...prev, + ...update, +}); + +interface SearchStrategyReturnBase { + progress: SearchStrategyProgress; + startFetch: () => void; + cancelFetch: () => void; +} + +// Function overload for Latency Correlations +export function useSearchStrategy( + searchStrategyName: typeof APM_SEARCH_STRATEGIES.APM_LATENCY_CORRELATIONS, + options: { + percentileThreshold: number; + analyzeCorrelations: boolean; + } +): { + response: LatencyCorrelationsRawResponse; +} & SearchStrategyReturnBase; + +// Function overload for Failed Transactions Correlations +export function useSearchStrategy( + searchStrategyName: typeof APM_SEARCH_STRATEGIES.APM_FAILED_TRANSACTIONS_CORRELATIONS +): { + response: FailedTransactionsCorrelationsRawResponse; +} & SearchStrategyReturnBase; + +export function useSearchStrategy< + TRawResponse extends RawResponseBase, + TOptions = unknown +>(searchStrategyName: ApmSearchStrategies, options?: TOptions): unknown { + const { + services: { data }, + } = useKibana(); + + const { serviceName, transactionType } = useApmServiceContext(); + const { + query: { kuery, environment, rangeFrom, rangeTo }, + } = useApmParams('/services/:serviceName/transactions/view'); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { urlParams } = useUrlParams(); + const { transactionName } = urlParams; + + const [rawResponse, setRawResponse] = useReducer( + getReducer(), + getInitialRawResponse() + ); + + const [fetchState, setFetchState] = useReducer( + getReducer(), + getInitialProgress() + ); + + const abortCtrl = useRef(new AbortController()); + const searchSubscription$ = useRef(); + const optionsRef = useRef(options); + + const startFetch = useCallback(() => { + searchSubscription$.current?.unsubscribe(); + abortCtrl.current.abort(); + abortCtrl.current = new AbortController(); + setFetchState({ + ...getInitialProgress(), + error: undefined, + }); + + const request = { + params: { + environment, + serviceName, + transactionName, + transactionType, + kuery, + start, + end, + ...(optionsRef.current ? { ...optionsRef.current } : {}), + }, + }; + + // Submit the search request using the `data.search` service. + searchSubscription$.current = data.search + .search< + IKibanaSearchRequest, + IKibanaSearchResponse + >(request, { + strategy: searchStrategyName, + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response: IKibanaSearchResponse) => { + setRawResponse(response.rawResponse); + setFetchState({ + isRunning: response.isRunning || false, + loaded: response.loaded, + total: response.total, + }); + + if (isCompleteResponse(response)) { + searchSubscription$.current?.unsubscribe(); + setFetchState({ + isRunning: false, + }); + } else if (isErrorResponse(response)) { + searchSubscription$.current?.unsubscribe(); + setFetchState({ + error: (response as unknown) as Error, + isRunning: false, + }); + } + }, + error: (error: Error) => { + setFetchState({ + error, + isRunning: false, + }); + }, + }); + }, [ + searchStrategyName, + data.search, + environment, + serviceName, + transactionName, + transactionType, + kuery, + start, + end, + ]); + + const cancelFetch = useCallback(() => { + searchSubscription$.current?.unsubscribe(); + searchSubscription$.current = undefined; + abortCtrl.current.abort(); + setFetchState({ + isRunning: false, + }); + }, []); + + // auto-update + useEffect(() => { + startFetch(); + return cancelFetch; + }, [startFetch, cancelFetch]); + + return { + progress: fetchState, + response: rawResponse, + startFetch, + cancelFetch, + }; +} diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts deleted file mode 100644 index 2ff1b83ef1782..0000000000000 --- a/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts +++ /dev/null @@ -1,160 +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 { useCallback, useRef, useState } from 'react'; -import type { Subscription } from 'rxjs'; -import { - IKibanaSearchRequest, - IKibanaSearchResponse, - isCompleteResponse, - isErrorResponse, -} from '../../../../../src/plugins/data/public'; -import type { - SearchServiceParams, - SearchServiceRawResponse, -} from '../../common/search_strategies/correlations/types'; -import { useKibana } from '../../../../../src/plugins/kibana_react/public'; -import { ApmPluginStartDeps } from '../plugin'; - -interface TransactionDistributionFetcherState { - error?: Error; - isComplete: boolean; - isRunning: boolean; - loaded: number; - ccsWarning: SearchServiceRawResponse['ccsWarning']; - log: SearchServiceRawResponse['log']; - transactionDistribution?: SearchServiceRawResponse['overallHistogram']; - percentileThresholdValue?: SearchServiceRawResponse['percentileThresholdValue']; - timeTook?: number; - total: number; -} - -export function useTransactionDistributionFetcher() { - const { - services: { data }, - } = useKibana(); - - const [ - fetchState, - setFetchState, - ] = useState({ - isComplete: false, - isRunning: false, - loaded: 0, - ccsWarning: false, - log: [], - total: 100, - }); - - const abortCtrl = useRef(new AbortController()); - const searchSubscription$ = useRef(); - - function setResponse( - response: IKibanaSearchResponse - ) { - setFetchState((prevState) => ({ - ...prevState, - isRunning: response.isRunning || false, - ccsWarning: response.rawResponse?.ccsWarning ?? false, - histograms: response.rawResponse?.values ?? [], - log: response.rawResponse?.log ?? [], - loaded: response.loaded!, - total: response.total!, - timeTook: response.rawResponse.took, - // only set percentileThresholdValue and overallHistogram once it's repopulated on a refresh, - // otherwise the consuming chart would flicker with an empty state on reload. - ...(response.rawResponse?.percentileThresholdValue !== undefined && - response.rawResponse?.overallHistogram !== undefined - ? { - transactionDistribution: response.rawResponse?.overallHistogram, - percentileThresholdValue: - response.rawResponse?.percentileThresholdValue, - } - : {}), - // if loading is done but didn't return any data for the overall histogram, - // set it to an empty array so the consuming chart component knows loading is done. - ...(!response.isRunning && - response.rawResponse?.overallHistogram === undefined - ? { transactionDistribution: [] } - : {}), - })); - } - - const startFetch = useCallback( - (params: Omit) => { - setFetchState((prevState) => ({ - ...prevState, - error: undefined, - isComplete: false, - })); - searchSubscription$.current?.unsubscribe(); - abortCtrl.current.abort(); - abortCtrl.current = new AbortController(); - - const searchServiceParams: SearchServiceParams = { - ...params, - analyzeCorrelations: false, - }; - const req = { params: searchServiceParams }; - - // Submit the search request using the `data.search` service. - searchSubscription$.current = data.search - .search< - IKibanaSearchRequest, - IKibanaSearchResponse - >(req, { - strategy: 'apmCorrelationsSearchStrategy', - abortSignal: abortCtrl.current.signal, - }) - .subscribe({ - next: (res: IKibanaSearchResponse) => { - setResponse(res); - if (isCompleteResponse(res)) { - searchSubscription$.current?.unsubscribe(); - setFetchState((prevState) => ({ - ...prevState, - isRunnning: false, - isComplete: true, - })); - } else if (isErrorResponse(res)) { - searchSubscription$.current?.unsubscribe(); - setFetchState((prevState) => ({ - ...prevState, - error: (res as unknown) as Error, - isRunning: false, - })); - } - }, - error: (error: Error) => { - setFetchState((prevState) => ({ - ...prevState, - error, - isRunning: false, - })); - }, - }); - }, - [data.search, setFetchState] - ); - - const cancelFetch = useCallback(() => { - searchSubscription$.current?.unsubscribe(); - searchSubscription$.current = undefined; - abortCtrl.current.abort(); - setFetchState((prevState) => ({ - ...prevState, - isRunning: false, - })); - }, [setFetchState]); - - return { - ...fetchState, - progress: fetchState.loaded / fetchState.total, - startFetch, - cancelFetch, - }; -} diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_latency_correlations_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_latency_correlations_fetcher.ts deleted file mode 100644 index 0b035c6af2354..0000000000000 --- a/x-pack/plugins/apm/public/hooks/use_transaction_latency_correlations_fetcher.ts +++ /dev/null @@ -1,167 +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 { useCallback, useRef, useState } from 'react'; -import type { Subscription } from 'rxjs'; -import { - IKibanaSearchRequest, - IKibanaSearchResponse, - isCompleteResponse, - isErrorResponse, -} from '../../../../../src/plugins/data/public'; -import type { - SearchServiceParams, - SearchServiceRawResponse, -} from '../../common/search_strategies/correlations/types'; -import { useKibana } from '../../../../../src/plugins/kibana_react/public'; -import { ApmPluginStartDeps } from '../plugin'; - -interface TransactionLatencyCorrelationsFetcherState { - error?: Error; - isComplete: boolean; - isRunning: boolean; - loaded: number; - ccsWarning: SearchServiceRawResponse['ccsWarning']; - histograms: SearchServiceRawResponse['values']; - log: SearchServiceRawResponse['log']; - overallHistogram?: SearchServiceRawResponse['overallHistogram']; - percentileThresholdValue?: SearchServiceRawResponse['percentileThresholdValue']; - timeTook?: number; - total: number; -} - -export const useTransactionLatencyCorrelationsFetcher = () => { - const { - services: { data }, - } = useKibana(); - - const [ - fetchState, - setFetchState, - ] = useState({ - isComplete: false, - isRunning: false, - loaded: 0, - ccsWarning: false, - histograms: [], - log: [], - total: 100, - }); - - const abortCtrl = useRef(new AbortController()); - const searchSubscription$ = useRef(); - - function setResponse( - response: IKibanaSearchResponse - ) { - setFetchState((prevState) => ({ - ...prevState, - isRunning: response.isRunning || false, - ccsWarning: response.rawResponse?.ccsWarning ?? false, - histograms: response.rawResponse?.values ?? [], - log: response.rawResponse?.log ?? [], - loaded: response.loaded!, - total: response.total!, - timeTook: response.rawResponse.took, - // only set percentileThresholdValue and overallHistogram once it's repopulated on a refresh, - // otherwise the consuming chart would flicker with an empty state on reload. - ...(response.rawResponse?.percentileThresholdValue !== undefined && - response.rawResponse?.overallHistogram !== undefined - ? { - overallHistogram: response.rawResponse?.overallHistogram, - percentileThresholdValue: - response.rawResponse?.percentileThresholdValue, - } - : {}), - // if loading is done but didn't return any data for the overall histogram, - // set it to an empty array so the consuming chart component knows loading is done. - ...(!response.isRunning && - response.rawResponse?.overallHistogram === undefined - ? { overallHistogram: [] } - : {}), - })); - } - - const startFetch = useCallback( - (params: Omit) => { - setFetchState((prevState) => ({ - ...prevState, - error: undefined, - isComplete: false, - })); - searchSubscription$.current?.unsubscribe(); - abortCtrl.current.abort(); - abortCtrl.current = new AbortController(); - - const searchServiceParams: SearchServiceParams = { - ...params, - analyzeCorrelations: true, - }; - const req = { params: searchServiceParams }; - - // Submit the search request using the `data.search` service. - searchSubscription$.current = data.search - .search< - IKibanaSearchRequest, - IKibanaSearchResponse - >(req, { - strategy: 'apmCorrelationsSearchStrategy', - abortSignal: abortCtrl.current.signal, - }) - .subscribe({ - next: (res: IKibanaSearchResponse) => { - setResponse(res); - if (isCompleteResponse(res)) { - searchSubscription$.current?.unsubscribe(); - setFetchState((prevState) => ({ - ...prevState, - isRunnning: false, - isComplete: true, - })); - } else if (isErrorResponse(res)) { - searchSubscription$.current?.unsubscribe(); - setFetchState((prevState) => ({ - ...prevState, - error: (res as unknown) as Error, - isRunning: false, - })); - } - }, - error: (error: Error) => { - setFetchState((prevState) => ({ - ...prevState, - error, - isRunning: false, - })); - }, - }); - }, - [data.search, setFetchState] - ); - - const cancelFetch = useCallback(() => { - searchSubscription$.current?.unsubscribe(); - searchSubscription$.current = undefined; - abortCtrl.current.abort(); - setFetchState((prevState) => ({ - ...prevState, - // If we didn't receive data for the overall histogram yet - // set it to an empty array to indicate loading stopped. - ...(prevState.overallHistogram === undefined - ? { overallHistogram: [] } - : {}), - isRunning: false, - })); - }, [setFetchState]); - - return { - ...fetchState, - progress: fetchState.loaded / fetchState.total, - startFetch, - cancelFetch, - }; -}; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/constants.ts b/x-pack/plugins/apm/server/lib/search_strategies/constants.ts similarity index 97% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/constants.ts rename to x-pack/plugins/apm/server/lib/search_strategies/constants.ts index 6b96b6b9d2131..5500e336c3542 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/constants.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/constants.ts @@ -79,3 +79,5 @@ export const SIGNIFICANT_VALUE_DIGITS = 3; export const CORRELATION_THRESHOLD = 0.3; export const KS_TEST_THRESHOLD = 0.1; + +export const ERROR_CORRELATION_THRESHOLD = 0.02; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service_log.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service_log.test.ts deleted file mode 100644 index bbeb8435e61bf..0000000000000 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service_log.test.ts +++ /dev/null @@ -1,30 +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 { asyncSearchServiceLogProvider } from './async_search_service_log'; - -describe('async search service', () => { - describe('asyncSearchServiceLogProvider', () => { - it('adds and retrieves messages from the log', async () => { - const { addLogMessage, getLogMessages } = asyncSearchServiceLogProvider(); - - const mockDate = new Date(1392202800000); - // @ts-ignore ignore the mockImplementation callback error - const spy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate); - - addLogMessage('the first message'); - addLogMessage('the second message'); - - expect(getLogMessages()).toEqual([ - '2014-02-12T11:00:00.000Z: the first message', - '2014-02-12T11:00:00.000Z: the second message', - ]); - - spy.mockRestore(); - }); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.ts deleted file mode 100644 index 7f67147a75580..0000000000000 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.ts +++ /dev/null @@ -1,130 +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 uuid from 'uuid'; -import { of } from 'rxjs'; - -import type { ISearchStrategy } from '../../../../../../../src/plugins/data/server'; -import { - IKibanaSearchRequest, - IKibanaSearchResponse, -} from '../../../../../../../src/plugins/data/common'; - -import type { - SearchServiceParams, - SearchServiceRawResponse, - SearchServiceValue, -} from '../../../../common/search_strategies/correlations/types'; - -import type { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices'; - -import { asyncSearchServiceProvider } from './async_search_service'; - -export type PartialSearchRequest = IKibanaSearchRequest; -export type PartialSearchResponse = IKibanaSearchResponse<{ - values: SearchServiceValue[]; -}>; - -export const apmCorrelationsSearchStrategyProvider = ( - getApmIndices: () => Promise, - includeFrozen: boolean -): ISearchStrategy => { - const asyncSearchServiceMap = new Map< - string, - ReturnType - >(); - - return { - search: (request, options, deps) => { - if (request.params === undefined) { - throw new Error('Invalid request parameters.'); - } - - // The function to fetch the current state of the async search service. - // This will be either an existing service for a follow up fetch or a new one for new requests. - let getAsyncSearchServiceState: ReturnType< - typeof asyncSearchServiceProvider - >; - - // If the request includes an ID, we require that the async search service already exists - // otherwise we throw an error. The client should never poll a service that's been cancelled or finished. - // This also avoids instantiating async search services when the service gets called with random IDs. - if (typeof request.id === 'string') { - const existingGetAsyncSearchServiceState = asyncSearchServiceMap.get( - request.id - ); - - if (typeof existingGetAsyncSearchServiceState === 'undefined') { - throw new Error( - `AsyncSearchService with ID '${request.id}' does not exist.` - ); - } - - getAsyncSearchServiceState = existingGetAsyncSearchServiceState; - } else { - getAsyncSearchServiceState = asyncSearchServiceProvider( - deps.esClient.asCurrentUser, - getApmIndices, - request.params, - includeFrozen - ); - } - - // Reuse the request's id or create a new one. - const id = request.id ?? uuid(); - - const { - ccsWarning, - error, - log, - isRunning, - loaded, - started, - total, - values, - percentileThresholdValue, - overallHistogram, - } = getAsyncSearchServiceState(); - - if (error instanceof Error) { - asyncSearchServiceMap.delete(id); - throw error; - } else if (isRunning) { - asyncSearchServiceMap.set(id, getAsyncSearchServiceState); - } else { - asyncSearchServiceMap.delete(id); - } - - const took = Date.now() - started; - - const rawResponse: SearchServiceRawResponse = { - ccsWarning, - log, - took, - values, - percentileThresholdValue, - overallHistogram, - }; - - return of({ - id, - loaded, - total, - isRunning, - isPartial: isRunning, - rawResponse, - }); - }, - cancel: async (id, options, deps) => { - const getAsyncSearchServiceState = asyncSearchServiceMap.get(id); - if (getAsyncSearchServiceState !== undefined) { - getAsyncSearchServiceState().cancel(); - asyncSearchServiceMap.delete(id); - } - }, - }; -}; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/current_time_as_string.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/current_time_as_string.test.ts deleted file mode 100644 index 2034c29b01d94..0000000000000 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/current_time_as_string.test.ts +++ /dev/null @@ -1,24 +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 { currentTimeAsString } from './current_time_as_string'; - -describe('aggregation utils', () => { - describe('currentTimeAsString', () => { - it('returns the current time as a string', () => { - const mockDate = new Date(1392202800000); - // @ts-ignore ignore the mockImplementation callback error - const spy = jest.spyOn(global, 'Date').mockImplementation(() => mockDate); - - const timeString = currentTimeAsString(); - - expect(timeString).toEqual('2014-02-12T11:00:00.000Z'); - - spy.mockRestore(); - }); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/current_time_as_string.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/current_time_as_string.ts deleted file mode 100644 index f454b8c8274f1..0000000000000 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/current_time_as_string.ts +++ /dev/null @@ -1,8 +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. - */ - -export const currentTimeAsString = () => new Date().toISOString(); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/async_search_service.ts b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service.ts similarity index 60% rename from x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/async_search_service.ts rename to x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service.ts index 89fcda926d547..12f7902b51488 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/async_search_service.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service.ts @@ -5,32 +5,58 @@ * 2.0. */ -import type { ElasticsearchClient } from 'src/core/server'; import { chunk } from 'lodash'; -import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types'; -import type { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices'; -import { asyncSearchServiceLogProvider } from '../correlations/async_search_service_log'; -import { asyncErrorCorrelationsSearchServiceStateProvider } from './async_search_service_state'; -import { fetchTransactionDurationFieldCandidates } from '../correlations/queries'; -import type { SearchServiceFetchParams } from '../../../../common/search_strategies/correlations/types'; -import { fetchFailedTransactionsCorrelationPValues } from './queries/query_failure_correlation'; -import { ERROR_CORRELATION_THRESHOLD } from './constants'; + +import type { ElasticsearchClient } from 'src/core/server'; + +import type { ISearchStrategy } from '../../../../../../../src/plugins/data/server'; +import { + IKibanaSearchRequest, + IKibanaSearchResponse, +} from '../../../../../../../src/plugins/data/common'; + import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames'; +import type { SearchStrategyParams } from '../../../../common/search_strategies/types'; +import type { + FailedTransactionsCorrelationsParams, + FailedTransactionsCorrelationsRawResponse, +} from '../../../../common/search_strategies/failed_transactions_correlations/types'; +import type { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices'; +import { searchServiceLogProvider } from '../search_service_log'; +import { + fetchFailedTransactionsCorrelationPValues, + fetchTransactionDurationFieldCandidates, +} from '../queries'; +import type { SearchServiceProvider } from '../search_strategy_provider'; + +import { failedTransactionsCorrelationsSearchServiceStateProvider } from './failed_transactions_correlations_search_service_state'; -export const asyncErrorCorrelationSearchServiceProvider = ( +import { ERROR_CORRELATION_THRESHOLD } from '../constants'; + +export type FailedTransactionsCorrelationsSearchServiceProvider = SearchServiceProvider< + FailedTransactionsCorrelationsParams, + FailedTransactionsCorrelationsRawResponse +>; + +export type FailedTransactionsCorrelationsSearchStrategy = ISearchStrategy< + IKibanaSearchRequest, + IKibanaSearchResponse +>; + +export const failedTransactionsCorrelationsSearchServiceProvider: FailedTransactionsCorrelationsSearchServiceProvider = ( esClient: ElasticsearchClient, getApmIndices: () => Promise, - searchServiceParams: SearchServiceParams, + searchServiceParams: FailedTransactionsCorrelationsParams, includeFrozen: boolean ) => { - const { addLogMessage, getLogMessages } = asyncSearchServiceLogProvider(); + const { addLogMessage, getLogMessages } = searchServiceLogProvider(); - const state = asyncErrorCorrelationsSearchServiceStateProvider(); + const state = failedTransactionsCorrelationsSearchServiceStateProvider(); async function fetchErrorCorrelations() { try { const indices = await getApmIndices(); - const params: SearchServiceFetchParams = { + const params: SearchStrategyParams = { ...searchServiceParams, index: indices['apm_oss.transactionIndices'], includeFrozen, @@ -63,7 +89,7 @@ export const asyncErrorCorrelationSearchServiceProvider = ( results.forEach((result, idx) => { if (result.status === 'fulfilled') { - state.addValues( + state.addFailedTransactionsCorrelations( result.value.filter( (record) => record && @@ -87,7 +113,7 @@ export const asyncErrorCorrelationSearchServiceProvider = ( } finally { fieldCandidatesFetchedCount += batches[i].length; state.setProgress({ - loadedErrorCorrelations: + loadedFailedTransactionsCorrelations: fieldCandidatesFetchedCount / fieldCandidates.length, }); } @@ -103,7 +129,7 @@ export const asyncErrorCorrelationSearchServiceProvider = ( addLogMessage( `Identified ${ - state.getState().values.length + state.getState().failedTransactionsCorrelations.length } significant correlations relating to failed transactions.` ); @@ -116,18 +142,23 @@ export const asyncErrorCorrelationSearchServiceProvider = ( const { ccsWarning, error, isRunning, progress } = state.getState(); return { - ccsWarning, - error, - log: getLogMessages(), - isRunning, - loaded: Math.round(state.getOverallProgress() * 100), - started: progress.started, - total: 100, - values: state.getValuesSortedByScore(), cancel: () => { addLogMessage(`Service cancelled.`); state.setIsCancelled(true); }, + error, + meta: { + loaded: Math.round(state.getOverallProgress() * 100), + total: 100, + isRunning, + isPartial: isRunning, + }, + rawResponse: { + ccsWarning, + log: getLogMessages(), + took: Date.now() - progress.started, + failedTransactionsCorrelations: state.getFailedTransactionsCorrelationsSortedByScore(), + }, }; }; }; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/async_search_service_state.ts b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service_state.ts similarity index 52% rename from x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/async_search_service_state.ts rename to x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service_state.ts index fb0c6fea4879a..13cf752618537 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/async_search_service_state.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/failed_transactions_correlations_search_service_state.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { FailedTransactionsCorrelationValue } from '../../../../common/search_strategies/failure_correlations/types'; +import { FailedTransactionsCorrelation } from '../../../../common/search_strategies/failed_transactions_correlations/types'; interface Progress { started: number; loadedFieldCandidates: number; - loadedErrorCorrelations: number; + loadedFailedTransactionsCorrelations: number; } -export const asyncErrorCorrelationsSearchServiceStateProvider = () => { +export const failedTransactionsCorrelationsSearchServiceStateProvider = () => { let ccsWarning = false; function setCcsWarning(d: boolean) { ccsWarning = d; @@ -36,12 +36,12 @@ export const asyncErrorCorrelationsSearchServiceStateProvider = () => { let progress: Progress = { started: Date.now(), loadedFieldCandidates: 0, - loadedErrorCorrelations: 0, + loadedFailedTransactionsCorrelations: 0, }; function getOverallProgress() { return ( progress.loadedFieldCandidates * 0.025 + - progress.loadedErrorCorrelations * (1 - 0.025) + progress.loadedFailedTransactionsCorrelations * (1 - 0.025) ); } function setProgress(d: Partial>) { @@ -51,16 +51,18 @@ export const asyncErrorCorrelationsSearchServiceStateProvider = () => { }; } - const values: FailedTransactionsCorrelationValue[] = []; - function addValue(d: FailedTransactionsCorrelationValue) { - values.push(d); + const failedTransactionsCorrelations: FailedTransactionsCorrelation[] = []; + function addFailedTransactionsCorrelation(d: FailedTransactionsCorrelation) { + failedTransactionsCorrelations.push(d); } - function addValues(d: FailedTransactionsCorrelationValue[]) { - values.push(...d); + function addFailedTransactionsCorrelations( + d: FailedTransactionsCorrelation[] + ) { + failedTransactionsCorrelations.push(...d); } - function getValuesSortedByScore() { - return values.sort((a, b) => b.score - a.score); + function getFailedTransactionsCorrelationsSortedByScore() { + return failedTransactionsCorrelations.sort((a, b) => b.score - a.score); } function getState() { @@ -70,16 +72,16 @@ export const asyncErrorCorrelationsSearchServiceStateProvider = () => { isCancelled, isRunning, progress, - values, + failedTransactionsCorrelations, }; } return { - addValue, - addValues, + addFailedTransactionsCorrelation, + addFailedTransactionsCorrelations, getOverallProgress, getState, - getValuesSortedByScore, + getFailedTransactionsCorrelationsSortedByScore, setCcsWarning, setError, setIsCancelled, @@ -88,6 +90,6 @@ export const asyncErrorCorrelationsSearchServiceStateProvider = () => { }; }; -export type AsyncSearchServiceState = ReturnType< - typeof asyncErrorCorrelationsSearchServiceStateProvider +export type FailedTransactionsCorrelationsSearchServiceState = ReturnType< + typeof failedTransactionsCorrelationsSearchServiceStateProvider >; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/index.ts b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/index.ts index f7e24ac6e1335..ec91165cb481b 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/index.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/index.ts @@ -5,5 +5,8 @@ * 2.0. */ -export { apmFailedTransactionsCorrelationsSearchStrategyProvider } from './search_strategy'; -export { FAILED_TRANSACTIONS_CORRELATION_SEARCH_STRATEGY } from '../../../../common/search_strategies/failure_correlations/constants'; +export { + failedTransactionsCorrelationsSearchServiceProvider, + FailedTransactionsCorrelationsSearchServiceProvider, + FailedTransactionsCorrelationsSearchStrategy, +} from './failed_transactions_correlations_search_service'; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/search_strategy.ts b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/search_strategy.ts deleted file mode 100644 index 415f19e892741..0000000000000 --- a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/search_strategy.ts +++ /dev/null @@ -1,120 +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 uuid from 'uuid'; -import { of } from 'rxjs'; - -import type { ISearchStrategy } from '../../../../../../../src/plugins/data/server'; -import { - IKibanaSearchRequest, - IKibanaSearchResponse, -} from '../../../../../../../src/plugins/data/common'; - -import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types'; -import type { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices'; - -import { asyncErrorCorrelationSearchServiceProvider } from './async_search_service'; -import { FailedTransactionsCorrelationValue } from '../../../../common/search_strategies/failure_correlations/types'; - -export type PartialSearchRequest = IKibanaSearchRequest; -export type PartialSearchResponse = IKibanaSearchResponse<{ - values: FailedTransactionsCorrelationValue[]; -}>; - -export const apmFailedTransactionsCorrelationsSearchStrategyProvider = ( - getApmIndices: () => Promise, - includeFrozen: boolean -): ISearchStrategy => { - const asyncSearchServiceMap = new Map< - string, - ReturnType - >(); - - return { - search: (request, options, deps) => { - if (request.params === undefined) { - throw new Error('Invalid request parameters.'); - } - - // The function to fetch the current state of the async search service. - // This will be either an existing service for a follow up fetch or a new one for new requests. - let getAsyncSearchServiceState: ReturnType< - typeof asyncErrorCorrelationSearchServiceProvider - >; - - // If the request includes an ID, we require that the async search service already exists - // otherwise we throw an error. The client should never poll a service that's been cancelled or finished. - // This also avoids instantiating async search services when the service gets called with random IDs. - if (typeof request.id === 'string') { - const existingGetAsyncSearchServiceState = asyncSearchServiceMap.get( - request.id - ); - - if (typeof existingGetAsyncSearchServiceState === 'undefined') { - throw new Error( - `AsyncSearchService with ID '${request.id}' does not exist.` - ); - } - - getAsyncSearchServiceState = existingGetAsyncSearchServiceState; - } else { - getAsyncSearchServiceState = asyncErrorCorrelationSearchServiceProvider( - deps.esClient.asCurrentUser, - getApmIndices, - request.params, - includeFrozen - ); - } - - // Reuse the request's id or create a new one. - const id = request.id ?? uuid(); - - const { - ccsWarning, - error, - log, - isRunning, - loaded, - started, - total, - values, - } = getAsyncSearchServiceState(); - - if (error instanceof Error) { - asyncSearchServiceMap.delete(id); - throw error; - } else if (isRunning) { - asyncSearchServiceMap.set(id, getAsyncSearchServiceState); - } else { - asyncSearchServiceMap.delete(id); - } - - const took = Date.now() - started; - - return of({ - id, - loaded, - total, - isRunning, - isPartial: isRunning, - rawResponse: { - ccsWarning, - log, - took, - values, - }, - }); - }, - cancel: async (id, options, deps) => { - const getAsyncSearchServiceState = asyncSearchServiceMap.get(id); - if (getAsyncSearchServiceState !== undefined) { - getAsyncSearchServiceState().cancel(); - asyncSearchServiceMap.delete(id); - } - }, - }; -}; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/constants.ts b/x-pack/plugins/apm/server/lib/search_strategies/index.ts similarity index 77% rename from x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/constants.ts rename to x-pack/plugins/apm/server/lib/search_strategies/index.ts index 711c5f736d774..b4668138eefab 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/constants.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export const ERROR_CORRELATION_THRESHOLD = 0.02; +export { registerSearchStrategies } from './register_search_strategies'; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/index.ts b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/index.ts similarity index 58% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/index.ts rename to x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/index.ts index 5ba7b4d7c957a..073bb122896ff 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/index.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/index.ts @@ -5,4 +5,8 @@ * 2.0. */ -export { apmCorrelationsSearchStrategyProvider } from './search_strategy'; +export { + latencyCorrelationsSearchServiceProvider, + LatencyCorrelationsSearchServiceProvider, + LatencyCorrelationsSearchStrategy, +} from './latency_correlations_search_service'; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service.ts b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts similarity index 72% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service.ts rename to x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts index e9986bd9f0cf5..b623f6c73f896 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service.ts @@ -7,11 +7,21 @@ import { range } from 'lodash'; import type { ElasticsearchClient } from 'src/core/server'; + +import type { ISearchStrategy } from '../../../../../../../src/plugins/data/server'; +import { + IKibanaSearchRequest, + IKibanaSearchResponse, +} from '../../../../../../../src/plugins/data/common'; + +import type { SearchStrategyServerParams } from '../../../../common/search_strategies/types'; import type { - SearchServiceParams, - SearchServiceFetchParams, -} from '../../../../common/search_strategies/correlations/types'; + LatencyCorrelationsParams, + LatencyCorrelationsRawResponse, +} from '../../../../common/search_strategies/latency_correlations/types'; + import type { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices'; + import { fetchTransactionDurationFieldCandidates, fetchTransactionDurationFieldValuePairs, @@ -20,23 +30,37 @@ import { fetchTransactionDurationHistograms, fetchTransactionDurationHistogramRangeSteps, fetchTransactionDurationRanges, -} from './queries'; -import { computeExpectationsAndRanges } from './utils'; -import { asyncSearchServiceLogProvider } from './async_search_service_log'; -import { asyncSearchServiceStateProvider } from './async_search_service_state'; +} from '../queries'; +import { computeExpectationsAndRanges } from '../utils'; +import { searchServiceLogProvider } from '../search_service_log'; +import type { SearchServiceProvider } from '../search_strategy_provider'; + +import { latencyCorrelationsSearchServiceStateProvider } from './latency_correlations_search_service_state'; + +export type LatencyCorrelationsSearchServiceProvider = SearchServiceProvider< + LatencyCorrelationsParams, + LatencyCorrelationsRawResponse +>; + +export type LatencyCorrelationsSearchStrategy = ISearchStrategy< + IKibanaSearchRequest, + IKibanaSearchResponse +>; -export const asyncSearchServiceProvider = ( +export const latencyCorrelationsSearchServiceProvider: LatencyCorrelationsSearchServiceProvider = ( esClient: ElasticsearchClient, getApmIndices: () => Promise, - searchServiceParams: SearchServiceParams, + searchServiceParams: LatencyCorrelationsParams, includeFrozen: boolean ) => { - const { addLogMessage, getLogMessages } = asyncSearchServiceLogProvider(); + const { addLogMessage, getLogMessages } = searchServiceLogProvider(); - const state = asyncSearchServiceStateProvider(); + const state = latencyCorrelationsSearchServiceStateProvider(); async function fetchCorrelations() { - let params: SearchServiceFetchParams | undefined; + let params: + | (LatencyCorrelationsParams & SearchStrategyServerParams) + | undefined; try { const indices = await getApmIndices(); @@ -71,7 +95,7 @@ export const asyncSearchServiceProvider = ( state.setProgress({ loadedHistogramStepsize: 1, loadedOverallHistogram: 1, - loadedFieldCanditates: 1, + loadedFieldCandidates: 1, loadedFieldValuePairs: 1, loadedHistograms: 1, }); @@ -115,7 +139,7 @@ export const asyncSearchServiceProvider = ( state.setProgress({ loadedHistogramStepsize: 1, loadedOverallHistogram: 1, - loadedFieldCanditates: 1, + loadedFieldCandidates: 1, loadedFieldValuePairs: 1, loadedHistograms: 1, }); @@ -148,7 +172,7 @@ export const asyncSearchServiceProvider = ( addLogMessage(`Identified ${fieldCandidates.length} fieldCandidates.`); - state.setProgress({ loadedFieldCanditates: 1 }); + state.setProgress({ loadedFieldCandidates: 1 }); const fieldValuePairs = await fetchTransactionDurationFieldValuePairs( esClient, @@ -190,7 +214,7 @@ export const asyncSearchServiceProvider = ( fieldValuePairs )) { if (item !== undefined) { - state.addValue(item); + state.addLatencyCorrelation(item); } loadedHistograms++; state.setProgress({ @@ -200,7 +224,7 @@ export const asyncSearchServiceProvider = ( addLogMessage( `Identified ${ - state.getState().values.length + state.getState().latencyCorrelations.length } significant correlations out of ${ fieldValuePairs.length } field/value pairs.` @@ -216,6 +240,11 @@ export const asyncSearchServiceProvider = ( state.setIsRunning(false); } + function cancel() { + addLogMessage(`Service cancelled.`); + state.setIsCancelled(true); + } + fetchCorrelations(); return () => { @@ -229,19 +258,21 @@ export const asyncSearchServiceProvider = ( } = state.getState(); return { - ccsWarning, + cancel, error, - log: getLogMessages(), - isRunning, - loaded: Math.round(state.getOverallProgress() * 100), - overallHistogram, - started: progress.started, - total: 100, - values: state.getValuesSortedByCorrelation(), - percentileThresholdValue, - cancel: () => { - addLogMessage(`Service cancelled.`); - state.setIsCancelled(true); + meta: { + loaded: Math.round(state.getOverallProgress() * 100), + total: 100, + isRunning, + isPartial: isRunning, + }, + rawResponse: { + ccsWarning, + log: getLogMessages(), + took: Date.now() - progress.started, + latencyCorrelations: state.getLatencyCorrelationsSortedByCorrelation(), + percentileThresholdValue, + overallHistogram, }, }; }; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service_state.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service_state.test.ts similarity index 82% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service_state.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service_state.test.ts index cfa1bf2a5ad71..ce9014004f4b0 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service_state.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service_state.test.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { asyncSearchServiceStateProvider } from './async_search_service_state'; +import { latencyCorrelationsSearchServiceStateProvider } from './latency_correlations_search_service_state'; -describe('async search service', () => { - describe('asyncSearchServiceStateProvider', () => { +describe('search service', () => { + describe('latencyCorrelationsSearchServiceStateProvider', () => { it('initializes with default state', () => { - const state = asyncSearchServiceStateProvider(); + const state = latencyCorrelationsSearchServiceStateProvider(); const defaultState = state.getState(); const defaultProgress = state.getOverallProgress(); @@ -19,7 +19,7 @@ describe('async search service', () => { expect(defaultState.isCancelled).toBe(false); expect(defaultState.isRunning).toBe(true); expect(defaultState.overallHistogram).toBe(undefined); - expect(defaultState.progress.loadedFieldCanditates).toBe(0); + expect(defaultState.progress.loadedFieldCandidates).toBe(0); expect(defaultState.progress.loadedFieldValuePairs).toBe(0); expect(defaultState.progress.loadedHistogramStepsize).toBe(0); expect(defaultState.progress.loadedHistograms).toBe(0); @@ -30,7 +30,7 @@ describe('async search service', () => { }); it('returns updated state', () => { - const state = asyncSearchServiceStateProvider(); + const state = latencyCorrelationsSearchServiceStateProvider(); state.setCcsWarning(true); state.setError(new Error('the-error-message')); @@ -49,7 +49,7 @@ describe('async search service', () => { expect(updatedState.overallHistogram).toEqual([ { key: 1392202800000, doc_count: 1234 }, ]); - expect(updatedState.progress.loadedFieldCanditates).toBe(0); + expect(updatedState.progress.loadedFieldCandidates).toBe(0); expect(updatedState.progress.loadedFieldValuePairs).toBe(0); expect(updatedState.progress.loadedHistogramStepsize).toBe(0); expect(updatedState.progress.loadedHistograms).toBe(0.5); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service_state.ts b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service_state.ts similarity index 64% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service_state.ts rename to x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service_state.ts index d0aac8987e070..53f357ed1135f 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service_state.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/latency_correlations/latency_correlations_search_service_state.ts @@ -5,14 +5,13 @@ * 2.0. */ +import type { HistogramItem } from '../../../../common/search_strategies/types'; import type { - AsyncSearchProviderProgress, - SearchServiceValue, -} from '../../../../common/search_strategies/correlations/types'; + LatencyCorrelationSearchServiceProgress, + LatencyCorrelation, +} from '../../../../common/search_strategies/latency_correlations/types'; -import { HistogramItem } from './queries'; - -export const asyncSearchServiceStateProvider = () => { +export const latencyCorrelationsSearchServiceStateProvider = () => { let ccsWarning = false; function setCcsWarning(d: boolean) { ccsWarning = d; @@ -46,11 +45,11 @@ export const asyncSearchServiceStateProvider = () => { percentileThresholdValue = d; } - let progress: AsyncSearchProviderProgress = { + let progress: LatencyCorrelationSearchServiceProgress = { started: Date.now(), loadedHistogramStepsize: 0, loadedOverallHistogram: 0, - loadedFieldCanditates: 0, + loadedFieldCandidates: 0, loadedFieldValuePairs: 0, loadedHistograms: 0, }; @@ -58,13 +57,13 @@ export const asyncSearchServiceStateProvider = () => { return ( progress.loadedHistogramStepsize * 0.025 + progress.loadedOverallHistogram * 0.025 + - progress.loadedFieldCanditates * 0.025 + + progress.loadedFieldCandidates * 0.025 + progress.loadedFieldValuePairs * 0.025 + progress.loadedHistograms * 0.9 ); } function setProgress( - d: Partial> + d: Partial> ) { progress = { ...progress, @@ -72,13 +71,13 @@ export const asyncSearchServiceStateProvider = () => { }; } - const values: SearchServiceValue[] = []; - function addValue(d: SearchServiceValue) { - values.push(d); + const latencyCorrelations: LatencyCorrelation[] = []; + function addLatencyCorrelation(d: LatencyCorrelation) { + latencyCorrelations.push(d); } - function getValuesSortedByCorrelation() { - return values.sort((a, b) => b.correlation - a.correlation); + function getLatencyCorrelationsSortedByCorrelation() { + return latencyCorrelations.sort((a, b) => b.correlation - a.correlation); } function getState() { @@ -90,16 +89,16 @@ export const asyncSearchServiceStateProvider = () => { overallHistogram, percentileThresholdValue, progress, - values, + latencyCorrelations, }; } return { - addValue, + addLatencyCorrelation, getIsCancelled, getOverallProgress, getState, - getValuesSortedByCorrelation, + getLatencyCorrelationsSortedByCorrelation, setCcsWarning, setError, setIsCancelled, @@ -110,6 +109,6 @@ export const asyncSearchServiceStateProvider = () => { }; }; -export type AsyncSearchServiceState = ReturnType< - typeof asyncSearchServiceStateProvider +export type LatencyCorrelationsSearchServiceState = ReturnType< + typeof latencyCorrelationsSearchServiceStateProvider >; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_prioritized_field_value_pairs.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_prioritized_field_value_pairs.test.ts similarity index 64% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_prioritized_field_value_pairs.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/get_prioritized_field_value_pairs.test.ts index dc11b4860a8b6..cb1500e70babc 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_prioritized_field_value_pairs.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_prioritized_field_value_pairs.test.ts @@ -11,13 +11,13 @@ describe('correlations', () => { describe('getPrioritizedFieldValuePairs', () => { it('returns fields without prioritization in the same order', () => { const fieldValuePairs = [ - { field: 'the-field-1', value: 'the-value-1' }, - { field: 'the-field-2', value: 'the-value-2' }, + { fieldName: 'the-field-1', fieldValue: 'the-value-1' }, + { fieldName: 'the-field-2', fieldValue: 'the-value-2' }, ]; const prioritziedFieldValuePairs = getPrioritizedFieldValuePairs( fieldValuePairs ); - expect(prioritziedFieldValuePairs.map((d) => d.field)).toEqual([ + expect(prioritziedFieldValuePairs.map((d) => d.fieldName)).toEqual([ 'the-field-1', 'the-field-2', ]); @@ -25,13 +25,13 @@ describe('correlations', () => { it('returns fields with already sorted prioritization in the same order', () => { const fieldValuePairs = [ - { field: 'service.version', value: 'the-value-1' }, - { field: 'the-field-2', value: 'the-value-2' }, + { fieldName: 'service.version', fieldValue: 'the-value-1' }, + { fieldName: 'the-field-2', fieldValue: 'the-value-2' }, ]; const prioritziedFieldValuePairs = getPrioritizedFieldValuePairs( fieldValuePairs ); - expect(prioritziedFieldValuePairs.map((d) => d.field)).toEqual([ + expect(prioritziedFieldValuePairs.map((d) => d.fieldName)).toEqual([ 'service.version', 'the-field-2', ]); @@ -39,13 +39,13 @@ describe('correlations', () => { it('returns fields with unsorted prioritization in the corrected order', () => { const fieldValuePairs = [ - { field: 'the-field-1', value: 'the-value-1' }, - { field: 'service.version', value: 'the-value-2' }, + { fieldName: 'the-field-1', fieldValue: 'the-value-1' }, + { fieldName: 'service.version', fieldValue: 'the-value-2' }, ]; const prioritziedFieldValuePairs = getPrioritizedFieldValuePairs( fieldValuePairs ); - expect(prioritziedFieldValuePairs.map((d) => d.field)).toEqual([ + expect(prioritziedFieldValuePairs.map((d) => d.fieldName)).toEqual([ 'service.version', 'the-field-1', ]); @@ -53,14 +53,14 @@ describe('correlations', () => { it('considers prefixes when sorting', () => { const fieldValuePairs = [ - { field: 'the-field-1', value: 'the-value-1' }, - { field: 'service.version', value: 'the-value-2' }, - { field: 'cloud.the-field-3', value: 'the-value-3' }, + { fieldName: 'the-field-1', fieldValue: 'the-value-1' }, + { fieldName: 'service.version', fieldValue: 'the-value-2' }, + { fieldName: 'cloud.the-field-3', fieldValue: 'the-value-3' }, ]; const prioritziedFieldValuePairs = getPrioritizedFieldValuePairs( fieldValuePairs ); - expect(prioritziedFieldValuePairs.map((d) => d.field)).toEqual([ + expect(prioritziedFieldValuePairs.map((d) => d.fieldName)).toEqual([ 'service.version', 'cloud.the-field-3', 'the-field-1', diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_prioritized_field_value_pairs.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_prioritized_field_value_pairs.ts similarity index 67% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_prioritized_field_value_pairs.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/get_prioritized_field_value_pairs.ts index ddfd87c83f9f3..6338422b022da 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_prioritized_field_value_pairs.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_prioritized_field_value_pairs.ts @@ -8,19 +8,19 @@ import { FIELDS_TO_ADD_AS_CANDIDATE } from '../constants'; import { hasPrefixToInclude } from '../utils'; -import type { FieldValuePairs } from './query_field_value_pairs'; +import type { FieldValuePair } from '../../../../common/search_strategies/types'; export const getPrioritizedFieldValuePairs = ( - fieldValuePairs: FieldValuePairs + fieldValuePairs: FieldValuePair[] ) => { const prioritizedFields = [...FIELDS_TO_ADD_AS_CANDIDATE]; return fieldValuePairs.sort((a, b) => { - const hasPrefixA = hasPrefixToInclude(a.field); - const hasPrefixB = hasPrefixToInclude(b.field); + const hasPrefixA = hasPrefixToInclude(a.fieldName); + const hasPrefixB = hasPrefixToInclude(b.fieldName); - const includesA = prioritizedFields.includes(a.field); - const includesB = prioritizedFields.includes(b.field); + const includesA = prioritizedFields.includes(a.fieldName); + const includesB = prioritizedFields.includes(b.fieldName); if ((includesA || hasPrefixA) && !includesB && !hasPrefixB) { return -1; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_query_with_params.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.test.ts similarity index 97% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_query_with_params.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.test.ts index 3be3438b2d18f..b8bce75320942 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_query_with_params.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { getQueryWithParams } from './get_query_with_params'; describe('correlations', () => { diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_query_with_params.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.ts similarity index 72% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_query_with_params.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.ts index 8bd9f3d4e582c..445f432f2d5ad 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_query_with_params.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_query_with_params.ts @@ -10,22 +10,25 @@ import { getOrElse } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import * as t from 'io-ts'; import { failure } from 'io-ts/lib/PathReporter'; -import type { SearchServiceFetchParams } from '../../../../../common/search_strategies/correlations/types'; -import { rangeRt } from '../../../../routes/default_api_types'; -import { getCorrelationsFilters } from '../../../correlations/get_filters'; -import { Setup, SetupTimeRange } from '../../../helpers/setup_request'; +import type { + FieldValuePair, + SearchStrategyParams, +} from '../../../../common/search_strategies/types'; +import { rangeRt } from '../../../routes/default_api_types'; +import { getCorrelationsFilters } from '../../correlations/get_filters'; +import { Setup, SetupTimeRange } from '../../helpers/setup_request'; export const getTermsQuery = ( - fieldName: string | undefined, - fieldValue: string | undefined + fieldName: FieldValuePair['fieldName'] | undefined, + fieldValue: FieldValuePair['fieldValue'] | undefined ) => { return fieldName && fieldValue ? [{ term: { [fieldName]: fieldValue } }] : []; }; interface QueryParams { - params: SearchServiceFetchParams; - fieldName?: string; - fieldValue?: string; + params: SearchStrategyParams; + fieldName?: FieldValuePair['fieldName']; + fieldValue?: FieldValuePair['fieldValue']; } export const getQueryWithParams = ({ params, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_request_base.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_request_base.test.ts similarity index 92% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_request_base.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/get_request_base.test.ts index b95db6d2691f1..fd5f52207d4c5 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_request_base.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_request_base.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { getRequestBase } from './get_request_base'; describe('correlations', () => { diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_request_base.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_request_base.ts similarity index 76% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_request_base.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/get_request_base.ts index e2cdbab830e0d..fb1639b5d5f4a 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_request_base.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/get_request_base.ts @@ -5,12 +5,12 @@ * 2.0. */ -import type { SearchServiceFetchParams } from '../../../../../common/search_strategies/correlations/types'; +import type { SearchStrategyParams } from '../../../../common/search_strategies/types'; export const getRequestBase = ({ index, includeFrozen, -}: SearchServiceFetchParams) => ({ +}: SearchStrategyParams) => ({ index, // matches APM's event client settings ignore_throttled: includeFrozen === undefined ? true : !includeFrozen, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/index.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/index.ts similarity index 84% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/index.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/index.ts index c33b131d9cbd7..e691b81e4adcf 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/index.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +export { fetchFailedTransactionsCorrelationPValues } from './query_failure_correlation'; export { fetchTransactionDurationFieldCandidates } from './query_field_candidates'; export { fetchTransactionDurationFieldValuePairs } from './query_field_value_pairs'; export { fetchTransactionDurationFractions } from './query_fractions'; @@ -12,4 +13,4 @@ export { fetchTransactionDurationPercentiles } from './query_percentiles'; export { fetchTransactionDurationCorrelation } from './query_correlation'; export { fetchTransactionDurationHistograms } from './query_histograms_generator'; export { fetchTransactionDurationHistogramRangeSteps } from './query_histogram_range_steps'; -export { fetchTransactionDurationRanges, HistogramItem } from './query_ranges'; +export { fetchTransactionDurationRanges } from './query_ranges'; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_correlation.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_correlation.test.ts similarity index 97% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_correlation.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_correlation.test.ts index 5245af6cdadcd..d3d14260df65c 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_correlation.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_correlation.test.ts @@ -8,7 +8,7 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { fetchTransactionDurationCorrelation, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_correlation.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_correlation.ts similarity index 84% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_correlation.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_correlation.ts index 823abe936e223..6e2981032d67d 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_correlation.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_correlation.ts @@ -9,24 +9,16 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { TRANSACTION_DURATION } from '../../../../../common/elasticsearch_fieldnames'; -import type { SearchServiceFetchParams } from '../../../../../common/search_strategies/correlations/types'; +import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; +import type { + FieldValuePair, + ResponseHit, + SearchStrategyParams, +} from '../../../../common/search_strategies/types'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; -export interface HistogramItem { - key: number; - doc_count: number; -} - -interface ResponseHitSource { - [s: string]: unknown; -} -interface ResponseHit { - _source: ResponseHitSource; -} - export interface BucketCorrelation { buckets_path: string; function: { @@ -41,13 +33,13 @@ export interface BucketCorrelation { } export const getTransactionDurationCorrelationRequest = ( - params: SearchServiceFetchParams, + params: SearchStrategyParams, expectations: number[], ranges: estypes.AggregationsAggregationRange[], fractions: number[], totalDocCount: number, - fieldName?: string, - fieldValue?: string + fieldName?: FieldValuePair['fieldName'], + fieldValue?: FieldValuePair['fieldValue'] ): estypes.SearchRequest => { const query = getQueryWithParams({ params, fieldName, fieldValue }); @@ -96,13 +88,13 @@ export const getTransactionDurationCorrelationRequest = ( export const fetchTransactionDurationCorrelation = async ( esClient: ElasticsearchClient, - params: SearchServiceFetchParams, + params: SearchStrategyParams, expectations: number[], ranges: estypes.AggregationsAggregationRange[], fractions: number[], totalDocCount: number, - fieldName?: string, - fieldValue?: string + fieldName?: FieldValuePair['fieldName'], + fieldValue?: FieldValuePair['fieldValue'] ): Promise<{ ranges: unknown[]; correlation: number | null; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/queries/query_failure_correlation.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_failure_correlation.ts similarity index 76% rename from x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/queries/query_failure_correlation.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_failure_correlation.ts index 81fe6697d1fb1..bc8ab4be97c11 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/queries/query_failure_correlation.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_failure_correlation.ts @@ -6,17 +6,14 @@ */ import { estypes } from '@elastic/elasticsearch'; import { ElasticsearchClient } from 'kibana/server'; -import { SearchServiceFetchParams } from '../../../../../common/search_strategies/correlations/types'; -import { - getQueryWithParams, - getTermsQuery, -} from '../../correlations/queries/get_query_with_params'; -import { getRequestBase } from '../../correlations/queries/get_request_base'; -import { EVENT_OUTCOME } from '../../../../../common/elasticsearch_fieldnames'; -import { EventOutcome } from '../../../../../common/event_outcome'; +import { SearchStrategyParams } from '../../../../common/search_strategies/types'; +import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames'; +import { EventOutcome } from '../../../../common/event_outcome'; +import { getQueryWithParams, getTermsQuery } from './get_query_with_params'; +import { getRequestBase } from './get_request_base'; export const getFailureCorrelationRequest = ( - params: SearchServiceFetchParams, + params: SearchStrategyParams, fieldName: string ): estypes.SearchRequest => { const query = getQueryWithParams({ @@ -62,7 +59,7 @@ export const getFailureCorrelationRequest = ( export const fetchFailedTransactionsCorrelationPValues = async ( esClient: ElasticsearchClient, - params: SearchServiceFetchParams, + params: SearchStrategyParams, fieldName: string ) => { const resp = await esClient.search( @@ -77,26 +74,26 @@ export const fetchFailedTransactionsCorrelationPValues = async ( const overallResult = resp.body.aggregations .failure_p_value as estypes.AggregationsSignificantTermsAggregate<{ - key: string; + key: string | number; doc_count: number; bg_count: number; score: number; }>; const result = overallResult.buckets.map((bucket) => { - const score = bucket.score; - // Scale the score into a value from 0 - 1 // using a concave piecewise linear function in -log(p-value) const normalizedScore = - 0.5 * Math.min(Math.max((score - 3.912) / 2.995, 0), 1) + - 0.25 * Math.min(Math.max((score - 6.908) / 6.908, 0), 1) + - 0.25 * Math.min(Math.max((score - 13.816) / 101.314, 0), 1); + 0.5 * Math.min(Math.max((bucket.score - 3.912) / 2.995, 0), 1) + + 0.25 * Math.min(Math.max((bucket.score - 6.908) / 6.908, 0), 1) + + 0.25 * Math.min(Math.max((bucket.score - 13.816) / 101.314, 0), 1); return { - ...bucket, fieldName, fieldValue: bucket.key, - pValue: Math.exp(-score), + doc_count: bucket.doc_count, + bg_count: bucket.doc_count, + score: bucket.score, + pValue: Math.exp(-bucket.score), normalizedScore, // Percentage of time the term appears in failed transactions failurePercentage: bucket.doc_count / overallResult.doc_count, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_field_candidates.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.test.ts similarity index 96% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_field_candidates.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.test.ts index 688af72e8f6d3..150348e2a7aa2 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_field_candidates.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.test.ts @@ -8,9 +8,9 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import { hasPrefixToInclude } from '../utils/has_prefix_to_include'; +import { hasPrefixToInclude } from '../utils'; import { fetchTransactionDurationFieldCandidates, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_field_candidates.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.ts similarity index 90% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_field_candidates.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.ts index aeb67a4d6884b..390243295c4f0 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_field_candidates.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_candidates.ts @@ -9,7 +9,7 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import type { SearchServiceFetchParams } from '../../../../../common/search_strategies/correlations/types'; +import type { SearchStrategyParams } from '../../../../common/search_strategies/types'; import { FIELD_PREFIX_TO_EXCLUDE_AS_CANDIDATE, @@ -21,7 +21,6 @@ import { hasPrefixToInclude } from '../utils'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; -import type { FieldName } from './query_field_value_pairs'; export const shouldBeExcluded = (fieldName: string) => { return ( @@ -33,7 +32,7 @@ export const shouldBeExcluded = (fieldName: string) => { }; export const getRandomDocsRequest = ( - params: SearchServiceFetchParams + params: SearchStrategyParams ): estypes.SearchRequest => ({ ...getRequestBase(params), body: { @@ -52,8 +51,8 @@ export const getRandomDocsRequest = ( export const fetchTransactionDurationFieldCandidates = async ( esClient: ElasticsearchClient, - params: SearchServiceFetchParams -): Promise<{ fieldCandidates: FieldName[] }> => { + params: SearchStrategyParams +): Promise<{ fieldCandidates: string[] }> => { const { index } = params; // Get all fields with keyword mapping const respMapping = await esClient.fieldCaps({ diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_field_value_pairs.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.test.ts similarity index 72% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_field_value_pairs.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.test.ts index a20720944f19b..1fff8cde5bbb3 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_field_value_pairs.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.test.ts @@ -8,10 +8,10 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import { asyncSearchServiceLogProvider } from '../async_search_service_log'; -import { asyncSearchServiceStateProvider } from '../async_search_service_state'; +import { searchServiceLogProvider } from '../search_service_log'; +import { latencyCorrelationsSearchServiceStateProvider } from '../latency_correlations/latency_correlations_search_service_state'; import { fetchTransactionDurationFieldValuePairs, @@ -62,8 +62,8 @@ describe('query_field_value_pairs', () => { search: esClientSearchMock, } as unknown) as ElasticsearchClient; - const { addLogMessage, getLogMessages } = asyncSearchServiceLogProvider(); - const state = asyncSearchServiceStateProvider(); + const { addLogMessage, getLogMessages } = searchServiceLogProvider(); + const state = latencyCorrelationsSearchServiceStateProvider(); const resp = await fetchTransactionDurationFieldValuePairs( esClientMock, @@ -77,12 +77,12 @@ describe('query_field_value_pairs', () => { expect(progress.loadedFieldValuePairs).toBe(1); expect(resp).toEqual([ - { field: 'myFieldCandidate1', value: 'myValue1' }, - { field: 'myFieldCandidate1', value: 'myValue2' }, - { field: 'myFieldCandidate2', value: 'myValue1' }, - { field: 'myFieldCandidate2', value: 'myValue2' }, - { field: 'myFieldCandidate3', value: 'myValue1' }, - { field: 'myFieldCandidate3', value: 'myValue2' }, + { fieldName: 'myFieldCandidate1', fieldValue: 'myValue1' }, + { fieldName: 'myFieldCandidate1', fieldValue: 'myValue2' }, + { fieldName: 'myFieldCandidate2', fieldValue: 'myValue1' }, + { fieldName: 'myFieldCandidate2', fieldValue: 'myValue2' }, + { fieldName: 'myFieldCandidate3', fieldValue: 'myValue1' }, + { fieldName: 'myFieldCandidate3', fieldValue: 'myValue2' }, ]); expect(esClientSearchMock).toHaveBeenCalledTimes(3); expect(getLogMessages()).toEqual([]); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_field_value_pairs.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.ts similarity index 72% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_field_value_pairs.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.ts index 33adff4af7a52..aa7d9f341a345 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_field_value_pairs.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_field_value_pairs.ts @@ -9,26 +9,21 @@ import type { ElasticsearchClient } from 'src/core/server'; import type { estypes } from '@elastic/elasticsearch'; -import type { SearchServiceFetchParams } from '../../../../../common/search_strategies/correlations/types'; +import type { + FieldValuePair, + SearchStrategyParams, +} from '../../../../common/search_strategies/types'; -import type { AsyncSearchServiceLog } from '../async_search_service_log'; -import type { AsyncSearchServiceState } from '../async_search_service_state'; +import type { SearchServiceLog } from '../search_service_log'; +import type { LatencyCorrelationsSearchServiceState } from '../latency_correlations/latency_correlations_search_service_state'; import { TERMS_SIZE } from '../constants'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; -export type FieldName = string; - -interface FieldValuePair { - field: FieldName; - value: string; -} -export type FieldValuePairs = FieldValuePair[]; - export const getTermsAggRequest = ( - params: SearchServiceFetchParams, - fieldName: FieldName + params: SearchStrategyParams, + fieldName: string ): estypes.SearchRequest => ({ ...getRequestBase(params), body: { @@ -47,10 +42,10 @@ export const getTermsAggRequest = ( const fetchTransactionDurationFieldTerms = async ( esClient: ElasticsearchClient, - params: SearchServiceFetchParams, + params: SearchStrategyParams, fieldName: string, - addLogMessage: AsyncSearchServiceLog['addLogMessage'] -): Promise => { + addLogMessage: SearchServiceLog['addLogMessage'] +): Promise => { try { const resp = await esClient.search(getTermsAggRequest(params, fieldName)); @@ -67,8 +62,8 @@ const fetchTransactionDurationFieldTerms = async ( }>)?.buckets; if (buckets?.length >= 1) { return buckets.map((d) => ({ - field: fieldName, - value: d.key, + fieldName, + fieldValue: d.key, })); } } catch (e) { @@ -82,8 +77,8 @@ const fetchTransactionDurationFieldTerms = async ( }; async function fetchInSequence( - fieldCandidates: FieldName[], - fn: (fieldCandidate: string) => Promise + fieldCandidates: string[], + fn: (fieldCandidate: string) => Promise ) { const results = []; @@ -96,11 +91,11 @@ async function fetchInSequence( export const fetchTransactionDurationFieldValuePairs = async ( esClient: ElasticsearchClient, - params: SearchServiceFetchParams, - fieldCandidates: FieldName[], - state: AsyncSearchServiceState, - addLogMessage: AsyncSearchServiceLog['addLogMessage'] -): Promise => { + params: SearchStrategyParams, + fieldCandidates: string[], + state: LatencyCorrelationsSearchServiceState, + addLogMessage: SearchServiceLog['addLogMessage'] +): Promise => { let fieldValuePairsProgress = 1; return await fetchInSequence( diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_fractions.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_fractions.test.ts similarity index 96% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_fractions.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_fractions.test.ts index 73df48a0d8170..fdf383453e17f 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_fractions.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_fractions.test.ts @@ -8,7 +8,7 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { fetchTransactionDurationFractions, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_fractions.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_fractions.ts similarity index 87% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_fractions.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_fractions.ts index 35e59054ad01f..25e5f62564b04 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_fractions.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_fractions.ts @@ -8,14 +8,14 @@ import { ElasticsearchClient } from 'kibana/server'; import { estypes } from '@elastic/elasticsearch'; -import { SearchServiceFetchParams } from '../../../../../common/search_strategies/correlations/types'; -import { TRANSACTION_DURATION } from '../../../../../common/elasticsearch_fieldnames'; +import { SearchStrategyParams } from '../../../../common/search_strategies/types'; +import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; export const getTransactionDurationRangesRequest = ( - params: SearchServiceFetchParams, + params: SearchStrategyParams, ranges: estypes.AggregationsAggregationRange[] ): estypes.SearchRequest => ({ ...getRequestBase(params), @@ -38,7 +38,7 @@ export const getTransactionDurationRangesRequest = ( */ export const fetchTransactionDurationFractions = async ( esClient: ElasticsearchClient, - params: SearchServiceFetchParams, + params: SearchStrategyParams, ranges: estypes.AggregationsAggregationRange[] ): Promise<{ fractions: number[]; totalDocCount: number }> => { const resp = await esClient.search( diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram.test.ts similarity index 97% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram.test.ts index 9b2a4807d4863..e6faeb16247fb 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram.test.ts @@ -8,7 +8,7 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { fetchTransactionDurationHistogram, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram.ts similarity index 78% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram.ts index 18fc18af1472e..1dac98d785f3c 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram.ts @@ -9,21 +9,22 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { TRANSACTION_DURATION } from '../../../../../common/elasticsearch_fieldnames'; +import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; import type { + FieldValuePair, HistogramItem, ResponseHit, - SearchServiceFetchParams, -} from '../../../../../common/search_strategies/correlations/types'; + SearchStrategyParams, +} from '../../../../common/search_strategies/types'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; export const getTransactionDurationHistogramRequest = ( - params: SearchServiceFetchParams, + params: SearchStrategyParams, interval: number, - fieldName?: string, - fieldValue?: string + fieldName?: FieldValuePair['fieldName'], + fieldValue?: FieldValuePair['fieldValue'] ): estypes.SearchRequest => ({ ...getRequestBase(params), body: { @@ -39,10 +40,10 @@ export const getTransactionDurationHistogramRequest = ( export const fetchTransactionDurationHistogram = async ( esClient: ElasticsearchClient, - params: SearchServiceFetchParams, + params: SearchStrategyParams, interval: number, - fieldName?: string, - fieldValue?: string + fieldName?: FieldValuePair['fieldName'], + fieldValue?: FieldValuePair['fieldValue'] ): Promise => { const resp = await esClient.search( getTransactionDurationHistogramRequest( diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_interval.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_interval.test.ts similarity index 97% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_interval.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_interval.test.ts index bb76769fe94b5..7b0d00d0d9b57 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_interval.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_interval.test.ts @@ -8,7 +8,7 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { fetchTransactionDurationHistogramInterval, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_interval.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_interval.ts similarity index 85% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_interval.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_interval.ts index cc50c8d4d860a..7a8752e45c6f2 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_interval.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_interval.ts @@ -9,8 +9,8 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { TRANSACTION_DURATION } from '../../../../../common/elasticsearch_fieldnames'; -import type { SearchServiceFetchParams } from '../../../../../common/search_strategies/correlations/types'; +import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; +import type { SearchStrategyParams } from '../../../../common/search_strategies/types'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; @@ -18,7 +18,7 @@ import { getRequestBase } from './get_request_base'; const HISTOGRAM_INTERVALS = 1000; export const getHistogramIntervalRequest = ( - params: SearchServiceFetchParams + params: SearchStrategyParams ): estypes.SearchRequest => ({ ...getRequestBase(params), body: { @@ -33,7 +33,7 @@ export const getHistogramIntervalRequest = ( export const fetchTransactionDurationHistogramInterval = async ( esClient: ElasticsearchClient, - params: SearchServiceFetchParams + params: SearchStrategyParams ): Promise => { const resp = await esClient.search(getHistogramIntervalRequest(params)); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_range_steps.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.test.ts similarity index 97% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_range_steps.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.test.ts index 52cfe6168232d..88d4f1a57adeb 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_range_steps.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.test.ts @@ -8,7 +8,7 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { fetchTransactionDurationHistogramRangeSteps, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_range_steps.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.ts similarity index 88% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_range_steps.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.ts index 116b5d1645601..31ab7392155bc 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_range_steps.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histogram_range_steps.ts @@ -11,8 +11,8 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { TRANSACTION_DURATION } from '../../../../../common/elasticsearch_fieldnames'; -import type { SearchServiceFetchParams } from '../../../../../common/search_strategies/correlations/types'; +import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; +import type { SearchStrategyParams } from '../../../../common/search_strategies/types'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; @@ -26,7 +26,7 @@ const getHistogramRangeSteps = (min: number, max: number, steps: number) => { }; export const getHistogramIntervalRequest = ( - params: SearchServiceFetchParams + params: SearchStrategyParams ): estypes.SearchRequest => ({ ...getRequestBase(params), body: { @@ -41,7 +41,7 @@ export const getHistogramIntervalRequest = ( export const fetchTransactionDurationHistogramRangeSteps = async ( esClient: ElasticsearchClient, - params: SearchServiceFetchParams + params: SearchStrategyParams ): Promise => { const steps = 100; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histograms_generator.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histograms_generator.test.ts similarity index 82% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histograms_generator.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_histograms_generator.test.ts index 22876684bec7e..c80b2533d7e32 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histograms_generator.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histograms_generator.test.ts @@ -8,10 +8,10 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import { asyncSearchServiceLogProvider } from '../async_search_service_log'; -import { asyncSearchServiceStateProvider } from '../async_search_service_state'; +import { searchServiceLogProvider } from '../search_service_log'; +import { latencyCorrelationsSearchServiceStateProvider } from '../latency_correlations/latency_correlations_search_service_state'; import { fetchTransactionDurationHistograms } from './query_histograms_generator'; @@ -30,9 +30,9 @@ const totalDocCount = 1234; const histogramRangeSteps = [1, 2, 4, 5]; const fieldValuePairs = [ - { field: 'the-field-name-1', value: 'the-field-value-1' }, - { field: 'the-field-name-2', value: 'the-field-value-2' }, - { field: 'the-field-name-2', value: 'the-field-value-3' }, + { fieldName: 'the-field-name-1', fieldValue: 'the-field-value-1' }, + { fieldName: 'the-field-name-2', fieldValue: 'the-field-value-2' }, + { fieldName: 'the-field-name-2', fieldValue: 'the-field-value-3' }, ]; describe('query_histograms_generator', () => { @@ -50,8 +50,8 @@ describe('query_histograms_generator', () => { search: esClientSearchMock, } as unknown) as ElasticsearchClient; - const state = asyncSearchServiceStateProvider(); - const { addLogMessage, getLogMessages } = asyncSearchServiceLogProvider(); + const state = latencyCorrelationsSearchServiceStateProvider(); + const { addLogMessage, getLogMessages } = searchServiceLogProvider(); let loadedHistograms = 0; const items = []; @@ -104,8 +104,8 @@ describe('query_histograms_generator', () => { search: esClientSearchMock, } as unknown) as ElasticsearchClient; - const state = asyncSearchServiceStateProvider(); - const { addLogMessage, getLogMessages } = asyncSearchServiceLogProvider(); + const state = latencyCorrelationsSearchServiceStateProvider(); + const { addLogMessage, getLogMessages } = searchServiceLogProvider(); let loadedHistograms = 0; const items = []; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histograms_generator.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histograms_generator.ts similarity index 78% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histograms_generator.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_histograms_generator.ts index c4869aac187c6..a07abd356db6d 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histograms_generator.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_histograms_generator.ts @@ -9,29 +9,30 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import type { SearchServiceFetchParams } from '../../../../../common/search_strategies/correlations/types'; +import type { + FieldValuePair, + SearchStrategyParams, +} from '../../../../common/search_strategies/types'; -import type { AsyncSearchServiceLog } from '../async_search_service_log'; -import type { AsyncSearchServiceState } from '../async_search_service_state'; +import type { SearchServiceLog } from '../search_service_log'; +import type { LatencyCorrelationsSearchServiceState } from '../latency_correlations/latency_correlations_search_service_state'; import { CORRELATION_THRESHOLD, KS_TEST_THRESHOLD } from '../constants'; import { getPrioritizedFieldValuePairs } from './get_prioritized_field_value_pairs'; import { fetchTransactionDurationCorrelation } from './query_correlation'; import { fetchTransactionDurationRanges } from './query_ranges'; -import type { FieldValuePairs } from './query_field_value_pairs'; - export async function* fetchTransactionDurationHistograms( esClient: ElasticsearchClient, - addLogMessage: AsyncSearchServiceLog['addLogMessage'], - params: SearchServiceFetchParams, - state: AsyncSearchServiceState, + addLogMessage: SearchServiceLog['addLogMessage'], + params: SearchStrategyParams, + state: LatencyCorrelationsSearchServiceState, expectations: number[], ranges: estypes.AggregationsAggregationRange[], fractions: number[], histogramRangeSteps: number[], totalDocCount: number, - fieldValuePairs: FieldValuePairs + fieldValuePairs: FieldValuePair[] ) { for (const item of getPrioritizedFieldValuePairs(fieldValuePairs)) { if (params === undefined || item === undefined || state.getIsCancelled()) { @@ -49,8 +50,8 @@ export async function* fetchTransactionDurationHistograms( ranges, fractions, totalDocCount, - item.field, - item.value + item.fieldName, + item.fieldValue ); if (state.getIsCancelled()) { @@ -68,8 +69,8 @@ export async function* fetchTransactionDurationHistograms( esClient, params, histogramRangeSteps, - item.field, - item.value + item.fieldName, + item.fieldValue ); yield { ...item, @@ -85,7 +86,7 @@ export async function* fetchTransactionDurationHistograms( // just add the error to the internal log and check if we'd want to set the // cross-cluster search compatibility warning to true. addLogMessage( - `Failed to fetch correlation/kstest for '${item.field}/${item.value}'`, + `Failed to fetch correlation/kstest for '${item.fieldName}/${item.fieldValue}'`, JSON.stringify(e) ); if (params?.index.includes(':')) { diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_percentiles.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_percentiles.test.ts similarity index 97% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_percentiles.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_percentiles.test.ts index cab2e283935d6..1a5d518b7e47a 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_percentiles.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_percentiles.test.ts @@ -8,7 +8,7 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { fetchTransactionDurationPercentiles, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_percentiles.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_percentiles.ts similarity index 80% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_percentiles.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_percentiles.ts index bd230687314e6..8d9e2ed88ba37 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_percentiles.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_percentiles.ts @@ -9,30 +9,22 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { TRANSACTION_DURATION } from '../../../../../common/elasticsearch_fieldnames'; -import type { SearchServiceFetchParams } from '../../../../../common/search_strategies/correlations/types'; +import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; +import type { + FieldValuePair, + ResponseHit, + SearchStrategyParams, +} from '../../../../common/search_strategies/types'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; import { SIGNIFICANT_VALUE_DIGITS } from '../constants'; -export interface HistogramItem { - key: number; - doc_count: number; -} - -interface ResponseHitSource { - [s: string]: unknown; -} -interface ResponseHit { - _source: ResponseHitSource; -} - export const getTransactionDurationPercentilesRequest = ( - params: SearchServiceFetchParams, + params: SearchStrategyParams, percents?: number[], - fieldName?: string, - fieldValue?: string + fieldName?: FieldValuePair['fieldName'], + fieldValue?: FieldValuePair['fieldValue'] ): estypes.SearchRequest => { const query = getQueryWithParams({ params, fieldName, fieldValue }); @@ -59,10 +51,10 @@ export const getTransactionDurationPercentilesRequest = ( export const fetchTransactionDurationPercentiles = async ( esClient: ElasticsearchClient, - params: SearchServiceFetchParams, + params: SearchStrategyParams, percents?: number[], - fieldName?: string, - fieldValue?: string + fieldName?: FieldValuePair['fieldName'], + fieldValue?: FieldValuePair['fieldValue'] ): Promise<{ totalDocs: number; percentiles: Record }> => { const resp = await esClient.search( getTransactionDurationPercentilesRequest( diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_ranges.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_ranges.test.ts similarity index 97% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_ranges.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_ranges.test.ts index 839d6a33cfe05..64b746b72534a 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_ranges.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_ranges.test.ts @@ -8,7 +8,7 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { fetchTransactionDurationRanges, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_ranges.ts b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_ranges.ts similarity index 78% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_ranges.ts rename to x-pack/plugins/apm/server/lib/search_strategies/queries/query_ranges.ts index 6f662363d0c42..e15962f2979ba 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_ranges.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/queries/query_ranges.ts @@ -9,29 +9,21 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from 'src/core/server'; -import { TRANSACTION_DURATION } from '../../../../../common/elasticsearch_fieldnames'; -import type { SearchServiceFetchParams } from '../../../../../common/search_strategies/correlations/types'; +import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; +import type { + FieldValuePair, + ResponseHit, + SearchStrategyParams, +} from '../../../../common/search_strategies/types'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; -export interface HistogramItem { - key: number; - doc_count: number; -} - -interface ResponseHitSource { - [s: string]: unknown; -} -interface ResponseHit { - _source: ResponseHitSource; -} - export const getTransactionDurationRangesRequest = ( - params: SearchServiceFetchParams, + params: SearchStrategyParams, rangesSteps: number[], - fieldName?: string, - fieldValue?: string + fieldName?: FieldValuePair['fieldName'], + fieldValue?: FieldValuePair['fieldValue'] ): estypes.SearchRequest => { const query = getQueryWithParams({ params, fieldName, fieldValue }); @@ -66,10 +58,10 @@ export const getTransactionDurationRangesRequest = ( export const fetchTransactionDurationRanges = async ( esClient: ElasticsearchClient, - params: SearchServiceFetchParams, + params: SearchStrategyParams, rangesSteps: number[], - fieldName?: string, - fieldValue?: string + fieldName?: FieldValuePair['fieldName'], + fieldValue?: FieldValuePair['fieldValue'] ): Promise> => { const resp = await esClient.search( getTransactionDurationRangesRequest( diff --git a/x-pack/plugins/apm/server/lib/search_strategies/register_search_strategies.ts b/x-pack/plugins/apm/server/lib/search_strategies/register_search_strategies.ts new file mode 100644 index 0000000000000..713c5e390ca8b --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/register_search_strategies.ts @@ -0,0 +1,40 @@ +/* + * 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 { PluginSetup as DataPluginSetup } from 'src/plugins/data/server'; + +import { APM_SEARCH_STRATEGIES } from '../../../common/search_strategies/constants'; + +import type { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices'; + +import { failedTransactionsCorrelationsSearchServiceProvider } from './failed_transactions_correlations'; +import { latencyCorrelationsSearchServiceProvider } from './latency_correlations'; +import { searchStrategyProvider } from './search_strategy_provider'; + +export const registerSearchStrategies = ( + registerSearchStrategy: DataPluginSetup['search']['registerSearchStrategy'], + getApmIndices: () => Promise, + includeFrozen: boolean +) => { + registerSearchStrategy( + APM_SEARCH_STRATEGIES.APM_LATENCY_CORRELATIONS, + searchStrategyProvider( + latencyCorrelationsSearchServiceProvider, + getApmIndices, + includeFrozen + ) + ); + + registerSearchStrategy( + APM_SEARCH_STRATEGIES.APM_FAILED_TRANSACTIONS_CORRELATIONS, + searchStrategyProvider( + failedTransactionsCorrelationsSearchServiceProvider, + getApmIndices, + includeFrozen + ) + ); +}; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/search_service_log.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/search_service_log.test.ts new file mode 100644 index 0000000000000..5b887f15a584e --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/search_service_log.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { + searchServiceLogProvider, + currentTimeAsString, +} from './search_service_log'; + +describe('search service', () => { + describe('currentTimeAsString', () => { + it('returns the current time as a string', () => { + const mockDate = new Date(1392202800000); + // @ts-ignore ignore the mockImplementation callback error + const spy = jest.spyOn(global, 'Date').mockReturnValue(mockDate); + + const timeString = currentTimeAsString(); + + expect(timeString).toEqual('2014-02-12T11:00:00.000Z'); + + spy.mockRestore(); + }); + }); + + describe('searchServiceLogProvider', () => { + it('adds and retrieves messages from the log', async () => { + const { addLogMessage, getLogMessages } = searchServiceLogProvider(); + + const mockDate = new Date(1392202800000); + // @ts-ignore ignore the mockImplementation callback error + const spy = jest.spyOn(global, 'Date').mockReturnValue(mockDate); + + addLogMessage('the first message'); + addLogMessage('the second message'); + + expect(getLogMessages()).toEqual([ + '2014-02-12T11:00:00.000Z: the first message', + '2014-02-12T11:00:00.000Z: the second message', + ]); + + spy.mockRestore(); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service_log.ts b/x-pack/plugins/apm/server/lib/search_strategies/search_service_log.ts similarity index 78% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service_log.ts rename to x-pack/plugins/apm/server/lib/search_strategies/search_service_log.ts index e69d2f55b6c56..73a59021b01ed 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service_log.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/search_service_log.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { currentTimeAsString } from './utils'; - interface LogMessage { timestamp: string; message: string; error?: string; } -export const asyncSearchServiceLogProvider = () => { +export const currentTimeAsString = () => new Date().toISOString(); + +export const searchServiceLogProvider = () => { const log: LogMessage[] = []; function addLogMessage(message: string, error?: string) { @@ -31,6 +31,4 @@ export const asyncSearchServiceLogProvider = () => { return { addLogMessage, getLogMessages }; }; -export type AsyncSearchServiceLog = ReturnType< - typeof asyncSearchServiceLogProvider ->; +export type SearchServiceLog = ReturnType; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.test.ts similarity index 84% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.test.ts index b5ab4a072be6c..b45b95666326f 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.test.ts @@ -8,14 +8,16 @@ import type { estypes } from '@elastic/elasticsearch'; import { SearchStrategyDependencies } from 'src/plugins/data/server'; -import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import type { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices'; +import { IKibanaSearchRequest } from '../../../../../../src/plugins/data/common'; -import { - apmCorrelationsSearchStrategyProvider, - PartialSearchRequest, -} from './search_strategy'; +import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; +import type { LatencyCorrelationsParams } from '../../../common/search_strategies/latency_correlations/types'; + +import type { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices'; + +import { latencyCorrelationsSearchServiceProvider } from './latency_correlations'; +import { searchStrategyProvider } from './search_strategy_provider'; // helper to trigger promises in the async search service const flushPromises = () => new Promise(setImmediate); @@ -106,7 +108,8 @@ const getApmIndicesMock = async () => describe('APM Correlations search strategy', () => { describe('strategy interface', () => { it('returns a custom search strategy with a `search` and `cancel` function', async () => { - const searchStrategy = await apmCorrelationsSearchStrategyProvider( + const searchStrategy = await searchStrategyProvider( + latencyCorrelationsSearchServiceProvider, getApmIndicesMock, false ); @@ -120,7 +123,9 @@ describe('APM Correlations search strategy', () => { let mockClientSearch: jest.Mock; let mockGetApmIndicesMock: jest.Mock; let mockDeps: SearchStrategyDependencies; - let params: Required['params']; + let params: Required< + IKibanaSearchRequest + >['params']; beforeEach(() => { mockClientFieldCaps = jest.fn(clientFieldCapsMock); @@ -139,13 +144,16 @@ describe('APM Correlations search strategy', () => { end: '2021', environment: ENVIRONMENT_ALL.value, kuery: '', + percentileThreshold: 95, + analyzeCorrelations: true, }; }); describe('async functionality', () => { describe('when no params are provided', () => { it('throws an error', async () => { - const searchStrategy = await apmCorrelationsSearchStrategyProvider( + const searchStrategy = await searchStrategyProvider( + latencyCorrelationsSearchServiceProvider, mockGetApmIndicesMock, false ); @@ -160,7 +168,8 @@ describe('APM Correlations search strategy', () => { describe('when no ID is provided', () => { it('performs a client search with params', async () => { - const searchStrategy = await apmCorrelationsSearchStrategyProvider( + const searchStrategy = await searchStrategyProvider( + latencyCorrelationsSearchServiceProvider, mockGetApmIndicesMock, false ); @@ -178,6 +187,7 @@ describe('APM Correlations search strategy', () => { percentiles: { field: 'transaction.duration.us', hdr: { number_of_significant_value_digits: 3 }, + percents: [95], }, }, }, @@ -206,7 +216,8 @@ describe('APM Correlations search strategy', () => { describe('when an ID with params is provided', () => { it('retrieves the current request', async () => { - const searchStrategy = await apmCorrelationsSearchStrategyProvider( + const searchStrategy = await searchStrategyProvider( + latencyCorrelationsSearchServiceProvider, mockGetApmIndicesMock, false ); @@ -232,7 +243,8 @@ describe('APM Correlations search strategy', () => { mockClientSearch .mockReset() .mockRejectedValueOnce(new Error('client error')); - const searchStrategy = await apmCorrelationsSearchStrategyProvider( + const searchStrategy = await searchStrategyProvider( + latencyCorrelationsSearchServiceProvider, mockGetApmIndicesMock, false ); @@ -250,7 +262,8 @@ describe('APM Correlations search strategy', () => { it('triggers the subscription only once', async () => { expect.assertions(2); - const searchStrategy = await apmCorrelationsSearchStrategyProvider( + const searchStrategy = await searchStrategyProvider( + latencyCorrelationsSearchServiceProvider, mockGetApmIndicesMock, false ); @@ -267,7 +280,8 @@ describe('APM Correlations search strategy', () => { describe('response', () => { it('sends an updated response on consecutive search calls', async () => { - const searchStrategy = await apmCorrelationsSearchStrategyProvider( + const searchStrategy = await searchStrategyProvider( + latencyCorrelationsSearchServiceProvider, mockGetApmIndicesMock, false ); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.ts b/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.ts new file mode 100644 index 0000000000000..c0376852b2505 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/search_strategy_provider.ts @@ -0,0 +1,151 @@ +/* + * 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 uuid from 'uuid'; +import { of } from 'rxjs'; + +import type { ElasticsearchClient } from 'src/core/server'; + +import type { ISearchStrategy } from '../../../../../../src/plugins/data/server'; +import { + IKibanaSearchRequest, + IKibanaSearchResponse, +} from '../../../../../../src/plugins/data/common'; + +import type { SearchStrategyClientParams } from '../../../common/search_strategies/types'; +import type { RawResponseBase } from '../../../common/search_strategies/types'; +import type { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices'; + +import type { + LatencyCorrelationsSearchServiceProvider, + LatencyCorrelationsSearchStrategy, +} from './latency_correlations'; +import type { + FailedTransactionsCorrelationsSearchServiceProvider, + FailedTransactionsCorrelationsSearchStrategy, +} from './failed_transactions_correlations'; + +interface SearchServiceState { + cancel: () => void; + error: Error; + meta: { + loaded: number; + total: number; + isRunning: boolean; + isPartial: boolean; + }; + rawResponse: TRawResponse; +} + +type GetSearchServiceState< + TRawResponse extends RawResponseBase +> = () => SearchServiceState; + +export type SearchServiceProvider< + TSearchStrategyClientParams extends SearchStrategyClientParams, + TRawResponse extends RawResponseBase +> = ( + esClient: ElasticsearchClient, + getApmIndices: () => Promise, + searchServiceParams: TSearchStrategyClientParams, + includeFrozen: boolean +) => GetSearchServiceState; + +// Failed Transactions Correlations function overload +export function searchStrategyProvider( + searchServiceProvider: FailedTransactionsCorrelationsSearchServiceProvider, + getApmIndices: () => Promise, + includeFrozen: boolean +): FailedTransactionsCorrelationsSearchStrategy; + +// Latency Correlations function overload +export function searchStrategyProvider( + searchServiceProvider: LatencyCorrelationsSearchServiceProvider, + getApmIndices: () => Promise, + includeFrozen: boolean +): LatencyCorrelationsSearchStrategy; + +export function searchStrategyProvider< + TSearchStrategyClientParams extends SearchStrategyClientParams, + TRawResponse extends RawResponseBase +>( + searchServiceProvider: SearchServiceProvider< + TSearchStrategyClientParams, + TRawResponse + >, + getApmIndices: () => Promise, + includeFrozen: boolean +): ISearchStrategy< + IKibanaSearchRequest, + IKibanaSearchResponse +> { + const searchServiceMap = new Map< + string, + GetSearchServiceState + >(); + + return { + search: (request, options, deps) => { + if (request.params === undefined) { + throw new Error('Invalid request parameters.'); + } + + // The function to fetch the current state of the search service. + // This will be either an existing service for a follow up fetch or a new one for new requests. + let getSearchServiceState: GetSearchServiceState; + + // If the request includes an ID, we require that the search service already exists + // otherwise we throw an error. The client should never poll a service that's been cancelled or finished. + // This also avoids instantiating search services when the service gets called with random IDs. + if (typeof request.id === 'string') { + const existingGetSearchServiceState = searchServiceMap.get(request.id); + + if (typeof existingGetSearchServiceState === 'undefined') { + throw new Error( + `SearchService with ID '${request.id}' does not exist.` + ); + } + + getSearchServiceState = existingGetSearchServiceState; + } else { + getSearchServiceState = searchServiceProvider( + deps.esClient.asCurrentUser, + getApmIndices, + request.params as TSearchStrategyClientParams, + includeFrozen + ); + } + + // Reuse the request's id or create a new one. + const id = request.id ?? uuid(); + + const { error, meta, rawResponse } = getSearchServiceState(); + + if (error instanceof Error) { + searchServiceMap.delete(id); + throw error; + } else if (meta.isRunning) { + searchServiceMap.set(id, getSearchServiceState); + } else { + searchServiceMap.delete(id); + } + + return of({ + id, + ...meta, + rawResponse, + }); + }, + cancel: async (id, options, deps) => { + const getSearchServiceState = searchServiceMap.get(id); + if (getSearchServiceState !== undefined) { + getSearchServiceState().cancel(); + searchServiceMap.delete(id); + } + }, + }; +} diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/compute_expectations_and_ranges.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/utils/compute_expectations_and_ranges.test.ts similarity index 100% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/compute_expectations_and_ranges.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/utils/compute_expectations_and_ranges.test.ts diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/compute_expectations_and_ranges.ts b/x-pack/plugins/apm/server/lib/search_strategies/utils/compute_expectations_and_ranges.ts similarity index 100% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/compute_expectations_and_ranges.ts rename to x-pack/plugins/apm/server/lib/search_strategies/utils/compute_expectations_and_ranges.ts diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/has_prefix_to_include.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/utils/has_prefix_to_include.test.ts similarity index 100% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/has_prefix_to_include.test.ts rename to x-pack/plugins/apm/server/lib/search_strategies/utils/has_prefix_to_include.test.ts diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/has_prefix_to_include.ts b/x-pack/plugins/apm/server/lib/search_strategies/utils/has_prefix_to_include.ts similarity index 100% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/has_prefix_to_include.ts rename to x-pack/plugins/apm/server/lib/search_strategies/utils/has_prefix_to_include.ts diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/index.ts b/x-pack/plugins/apm/server/lib/search_strategies/utils/index.ts similarity index 86% rename from x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/index.ts rename to x-pack/plugins/apm/server/lib/search_strategies/utils/index.ts index 000fd57c718b7..727bc6cd787a0 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/index.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/utils/index.ts @@ -6,5 +6,4 @@ */ export { computeExpectationsAndRanges } from './compute_expectations_and_ranges'; -export { currentTimeAsString } from './current_time_as_string'; export { hasPrefixToInclude } from './has_prefix_to_include'; diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 14c8bd9087b89..1c6d1cdef37ca 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -28,7 +28,7 @@ import { registerFleetPolicyCallbacks } from './lib/fleet/register_fleet_policy_ import { createApmTelemetry } from './lib/apm_telemetry'; import { createApmEventClient } from './lib/helpers/create_es_client/create_apm_event_client'; import { getInternalSavedObjectsClient } from './lib/helpers/get_internal_saved_objects_client'; -import { apmCorrelationsSearchStrategyProvider } from './lib/search_strategies/correlations'; +import { registerSearchStrategies } from './lib/search_strategies'; import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index'; import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices'; import { createApmCustomLinkIndex } from './lib/settings/custom_link/create_custom_link_index'; @@ -51,10 +51,6 @@ import { TRANSACTION_TYPE, } from '../common/elasticsearch_fieldnames'; import { tutorialProvider } from './tutorial'; -import { - apmFailedTransactionsCorrelationsSearchStrategyProvider, - FAILED_TRANSACTIONS_CORRELATION_SEARCH_STRATEGY, -} from './lib/search_strategies/failed_transactions_correlations'; export class APMPlugin implements @@ -217,22 +213,10 @@ export class APMPlugin .asScopedToClient(savedObjectsClient) .get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN); - // Register APM latency correlations search strategy - plugins.data.search.registerSearchStrategy( - 'apmCorrelationsSearchStrategy', - apmCorrelationsSearchStrategyProvider( - boundGetApmIndices, - includeFrozen - ) - ); - - // Register APM failed transactions correlations search strategy - plugins.data.search.registerSearchStrategy( - FAILED_TRANSACTIONS_CORRELATION_SEARCH_STRATEGY, - apmFailedTransactionsCorrelationsSearchStrategyProvider( - boundGetApmIndices, - includeFrozen - ) + registerSearchStrategies( + plugins.data.search.registerSearchStrategy, + boundGetApmIndices, + includeFrozen ); })(); }); diff --git a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts index 0205940d77724..4b484502d5826 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts @@ -6,9 +6,14 @@ */ import expect from '@kbn/expect'; + +import { IKibanaSearchRequest } from '../../../../../src/plugins/data/common'; + +import type { FailedTransactionsCorrelationsParams } from '../../../../plugins/apm/common/search_strategies/failed_transactions_correlations/types'; +import { APM_SEARCH_STRATEGIES } from '../../../../plugins/apm/common/search_strategies/constants'; + import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; -import { PartialSearchRequest } from '../../../../plugins/apm/server/lib/search_strategies/correlations/search_strategy'; import { parseBfetchResponse } from '../../common/utils/parse_b_fetch'; export default function ApiTest({ getService }: FtrProviderContext) { @@ -16,7 +21,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('legacySupertestAsApmReadUser'); const getRequestBody = () => { - const partialSearchRequest: PartialSearchRequest = { + const request: IKibanaSearchRequest = { params: { environment: 'ENVIRONMENT_ALL', start: '2020', @@ -28,8 +33,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { return { batch: [ { - request: partialSearchRequest, - options: { strategy: 'apmFailedTransactionsCorrelationsSearchStrategy' }, + request, + options: { strategy: APM_SEARCH_STRATEGIES.APM_FAILED_TRANSACTIONS_CORRELATIONS }, }, ], }; @@ -117,9 +122,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(typeof finalRawResponse?.took).to.be('number'); - expect(finalRawResponse?.values.length).to.eql( + expect(finalRawResponse?.failedTransactionsCorrelations.length).to.eql( 0, - `Expected 0 identified correlations, got ${finalRawResponse?.values.length}.` + `Expected 0 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations.length}.` ); }); }); @@ -209,9 +214,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(finalRawResponse?.percentileThresholdValue).to.be(undefined); expect(finalRawResponse?.overallHistogram).to.be(undefined); - expect(finalRawResponse?.values.length).to.eql( + expect(finalRawResponse?.failedTransactionsCorrelations.length).to.eql( 43, - `Expected 43 identified correlations, got ${finalRawResponse?.values.length}.` + `Expected 43 identified correlations, got ${finalRawResponse?.failedTransactionsCorrelations.length}.` ); expect(finalRawResponse?.log.map((d: string) => d.split(': ')[1])).to.eql([ @@ -220,7 +225,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'Identified 43 significant correlations relating to failed transactions.', ]); - const sortedCorrelations = finalRawResponse?.values.sort(); + const sortedCorrelations = finalRawResponse?.failedTransactionsCorrelations.sort(); const correlation = sortedCorrelations[0]; expect(typeof correlation).to.be('object'); diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency.ts b/x-pack/test/apm_api_integration/tests/correlations/latency.ts index 21cd855f4ed85..a51c0c8b9d151 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/latency.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/latency.ts @@ -6,9 +6,14 @@ */ import expect from '@kbn/expect'; + +import { IKibanaSearchRequest } from '../../../../../src/plugins/data/common'; + +import type { LatencyCorrelationsParams } from '../../../../plugins/apm/common/search_strategies/latency_correlations/types'; +import { APM_SEARCH_STRATEGIES } from '../../../../plugins/apm/common/search_strategies/constants'; + import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; -import { PartialSearchRequest } from '../../../../plugins/apm/server/lib/search_strategies/correlations/search_strategy'; import { parseBfetchResponse } from '../../common/utils/parse_b_fetch'; export default function ApiTest({ getService }: FtrProviderContext) { @@ -16,21 +21,22 @@ export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('legacySupertestAsApmReadUser'); const getRequestBody = () => { - const partialSearchRequest: PartialSearchRequest = { + const request: IKibanaSearchRequest = { params: { environment: 'ENVIRONMENT_ALL', start: '2020', end: '2021', - percentileThreshold: 95, kuery: '', + percentileThreshold: 95, + analyzeCorrelations: true, }, }; return { batch: [ { - request: partialSearchRequest, - options: { strategy: 'apmCorrelationsSearchStrategy' }, + request, + options: { strategy: APM_SEARCH_STRATEGIES.APM_LATENCY_CORRELATIONS }, }, ], }; @@ -122,7 +128,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(typeof finalRawResponse?.took).to.be('number'); expect(finalRawResponse?.percentileThresholdValue).to.be(undefined); expect(finalRawResponse?.overallHistogram).to.be(undefined); - expect(finalRawResponse?.values.length).to.be(0); + expect(finalRawResponse?.latencyCorrelations.length).to.be(0); expect(finalRawResponse?.log.map((d: string) => d.split(': ')[1])).to.eql([ 'Fetched 95th percentile value of undefined based on 0 documents.', 'Abort service since percentileThresholdValue could not be determined.', @@ -176,7 +182,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const { rawResponse } = result; expect(typeof rawResponse?.took).to.be('number'); - expect(rawResponse?.values).to.eql([]); + expect(rawResponse?.latencyCorrelations).to.eql([]); // follow up request body including search strategy ID const reqBody = getRequestBody(); @@ -230,9 +236,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(finalRawResponse?.percentileThresholdValue).to.be(1309695.875); expect(finalRawResponse?.overallHistogram.length).to.be(101); - expect(finalRawResponse?.values.length).to.eql( + expect(finalRawResponse?.latencyCorrelations.length).to.eql( 13, - `Expected 13 identified correlations, got ${finalRawResponse?.values.length}.` + `Expected 13 identified correlations, got ${finalRawResponse?.latencyCorrelations.length}.` ); expect(finalRawResponse?.log.map((d: string) => d.split(': ')[1])).to.eql([ 'Fetched 95th percentile value of 1309695.875 based on 1244 documents.', @@ -245,10 +251,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'Identified 13 significant correlations out of 379 field/value pairs.', ]); - const correlation = finalRawResponse?.values[0]; + const correlation = finalRawResponse?.latencyCorrelations[0]; expect(typeof correlation).to.be('object'); - expect(correlation?.field).to.be('transaction.result'); - expect(correlation?.value).to.be('success'); + expect(correlation?.fieldName).to.be('transaction.result'); + expect(correlation?.fieldValue).to.be('success'); expect(correlation?.correlation).to.be(0.6275246559191225); expect(correlation?.ksTest).to.be(4.806503252860024e-13); expect(correlation?.histogram.length).to.be(101);