From bcbaf255675c785b2ecdb93efa10b3a407dfe0fe Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 30 Jun 2021 17:00:28 +0200 Subject: [PATCH 1/7] unit tests for APM correlations. --- .../correlations/async_search_service.ts | 6 +- .../get_query_with_params.test.ts | 92 ++++++++ .../correlations/get_query_with_params.ts | 4 + .../correlations/query_correlation.test.ts | 57 +++++ .../correlations/query_correlation.ts | 2 +- .../query_field_candidates.test.ts | 70 ++++++ .../correlations/query_field_candidates.ts | 4 +- .../query_field_value_pairs.test.ts | 19 ++ .../correlations/query_fractions.test.ts | 31 +++ .../correlations/query_histogram.test.ts | 45 ++++ .../query_histogram_interval.test.ts | 46 ++++ .../query_histogram_rangesteps.test.ts | 46 ++++ .../correlations/query_percentiles.test.ts | 44 ++++ .../correlations/query_percentiles.ts | 4 +- .../correlations/query_ranges.test.ts | 66 ++++++ .../correlations/query_ranges.ts | 4 +- .../correlations/search_strategy.test.ts | 221 ++++++++++++++++++ .../utils/aggregation_utils.test.ts | 49 ++++ .../correlations/utils/aggregation_utils.ts | 6 +- .../correlations/utils/math_utils.test.ts | 26 +++ 20 files changed, 831 insertions(+), 11 deletions(-) create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.test.ts create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.test.ts create mode 100644 x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/math_utils.test.ts 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/correlations/async_search_service.ts index 5820fd952c4491..7a511fc60fd064 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/async_search_service.ts @@ -9,7 +9,7 @@ import { shuffle, range } from 'lodash'; import type { ElasticsearchClient } from 'src/core/server'; import { fetchTransactionDurationFieldCandidates } from './query_field_candidates'; import { fetchTransactionDurationFieldValuePairs } from './query_field_value_pairs'; -import { fetchTransactionDurationPecentiles } from './query_percentiles'; +import { fetchTransactionDurationPercentiles } from './query_percentiles'; import { fetchTransactionDurationCorrelation } from './query_correlation'; import { fetchTransactionDurationHistogramRangesteps } from './query_histogram_rangesteps'; import { fetchTransactionDurationRanges, HistogramItem } from './query_ranges'; @@ -59,7 +59,7 @@ export const asyncSearchServiceProvider = ( const fetchCorrelations = async () => { try { // 95th percentile to be displayed as a marker in the log log chart - const percentileThreshold = await fetchTransactionDurationPecentiles( + const percentileThreshold = await fetchTransactionDurationPercentiles( esClient, params, params.percentileThreshold ? [params.percentileThreshold] : undefined @@ -93,7 +93,7 @@ export const asyncSearchServiceProvider = ( // Create an array of ranges [2, 4, 6, ..., 98] const percents = Array.from(range(2, 100, 2)); - const percentilesRecords = await fetchTransactionDurationPecentiles( + const percentilesRecords = await fetchTransactionDurationPercentiles( esClient, params, percents diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.test.ts new file mode 100644 index 00000000000000..7bf392a3be8686 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.test.ts @@ -0,0 +1,92 @@ +/* + * 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 { getQueryWithParams } from './get_query_with_params'; + +describe('correlations', () => { + describe('getQueryWithParams()', () => { + it('returns the most basic query filtering on processor.event=transaction', () => { + const query = getQueryWithParams({ params: { index: 'apm-*' } }); + expect(query).toEqual({ + bool: { + filter: [{ term: { 'processor.event': 'transaction' } }], + }, + }); + }); + + it('returns a query considering additional params', () => { + const query = getQueryWithParams({ + params: { + index: 'apm-*', + serviceName: 'actualServiceName', + transactionName: 'actualTransactionName', + start: '01-01-2021', + end: '31-01-2021', + environment: 'dev', + percentileThresholdValue: 75, + }, + }); + expect(query).toEqual({ + bool: { + filter: [ + { term: { 'processor.event': 'transaction' } }, + { + term: { + 'service.name': 'actualServiceName', + }, + }, + { + term: { + 'transaction.name': 'actualTransactionName', + }, + }, + { + range: { + '@timestamp': { + gte: '01-01-2021', + lte: '31-01-2021', + }, + }, + }, + { + term: { + 'service.environment': 'dev', + }, + }, + { + range: { + 'transaction.duration.us': { + gte: 75, + }, + }, + }, + ], + }, + }); + }); + + it('returns a query considering a custom field/value pair', () => { + const query = getQueryWithParams({ + params: { index: 'apm-*' }, + fieldName: 'actualFieldName', + fieldValue: 'actualFieldValue', + }); + expect(query).toEqual({ + bool: { + filter: [ + { term: { 'processor.event': 'transaction' } }, + { + term: { + actualFieldName: 'actualFieldValue', + }, + }, + ], + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.ts index e7cf8173b5bac6..08ba4b23fec35b 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.ts @@ -43,6 +43,10 @@ const getRangeQuery = ( start?: string, end?: string ): estypes.QueryDslQueryContainer[] => { + if (start === undefined && end === undefined) { + return []; + } + return [ { range: { diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts new file mode 100644 index 00000000000000..39a32c363da451 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts @@ -0,0 +1,57 @@ +/* + * 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 { + getTransactionDurationCorrelationRequest, + BucketCorrelation, +} from './query_correlation'; + +describe('query_correlation', () => { + describe('getTransactionDurationCorrelationRequest()', () => { + it('applies options to the returned query with aggregations for correlations and k-test', () => { + const index = 'apm-*'; + const expectations = [1, 3, 5]; + const ranges = [ + { to: 1 }, + { from: 1, to: 3 }, + { from: 3, to: 5 }, + { from: 5 }, + ]; + const fractions = [1, 2, 4, 5]; + const totalDocCount = 1234; + + const query = getTransactionDurationCorrelationRequest( + { index }, + expectations, + ranges, + fractions, + totalDocCount + ); + + expect(query.index).toBe(index); + + expect(query?.body?.aggs?.latency_ranges?.range?.field).toBe( + 'transaction.duration.us' + ); + expect(query?.body?.aggs?.latency_ranges?.range?.ranges).toEqual(ranges); + + expect( + (query?.body?.aggs?.transaction_duration_correlation as { + bucket_correlation: BucketCorrelation; + })?.bucket_correlation.function.count_correlation.indicator + ).toEqual({ + fractions, + expectations, + doc_count: totalDocCount, + }); + + expect( + (query?.body?.aggs?.ks_test as any)?.bucket_count_ks_test?.fractions + ).toEqual([0, ...fractions]); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.ts index 9894ac54eccb66..3fdb47a1a3813e 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.ts @@ -26,7 +26,7 @@ interface ResponseHit { _source: ResponseHitSource; } -interface BucketCorrelation { +export interface BucketCorrelation { buckets_path: string; function: { count_correlation: { diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts new file mode 100644 index 00000000000000..10b68e329525f6 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts @@ -0,0 +1,70 @@ +/* + * 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 { + getRandomDocsRequest, + hasPrefixToInclude, + shouldBeExcluded, +} from './query_field_candidates'; + +describe('query_field_candidates', () => { + describe('shouldBeExcluded()', () => { + it('does not exclude a completely custom field name', () => { + expect(shouldBeExcluded('myFieldName')).toBe(false); + }); + + it(`excludes a field if it's one of FIELDS_TO_EXCLUDE_AS_CANDIDATE`, () => { + expect(shouldBeExcluded('transaction.type')).toBe(true); + }); + + it(`excludes a field if it's prefixed with one of FIELD_PREFIX_TO_EXCLUDE_AS_CANDIDATE`, () => { + expect(shouldBeExcluded('observer.myFieldName')).toBe(true); + }); + }); + + describe('hasPrefixToInclude()', () => { + it('identifies if a field name is prefixed to be included', () => { + expect(hasPrefixToInclude('myFieldName')).toBe(false); + expect(hasPrefixToInclude('somePrefix.myFieldName')).toBe(false); + expect(hasPrefixToInclude('cloud.myFieldName')).toBe(true); + expect(hasPrefixToInclude('labels.myFieldName')).toBe(true); + expect(hasPrefixToInclude('user_agent.myFieldName')).toBe(true); + }); + }); + + describe('getRandomDocsRequest()', () => { + it('returns the most basic request body for a sample of random documents', () => { + const req = getRandomDocsRequest({ index: 'apm-*' }); + + expect(req).toEqual({ + body: { + _source: false, + fields: ['*'], + query: { + function_score: { + query: { + bool: { + filter: [ + { + term: { + 'processor.event': 'transaction', + }, + }, + ], + }, + }, + random_score: {}, + }, + }, + size: 1000, + track_total_hits: true, + }, + index: 'apm-*', + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.ts index 4f1840971da7d2..def1bb566c9c80 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.ts @@ -21,7 +21,7 @@ import { POPULATED_DOC_COUNT_SAMPLE_SIZE, } from './constants'; -const shouldBeExcluded = (fieldName: string) => { +export const shouldBeExcluded = (fieldName: string) => { return ( FIELDS_TO_EXCLUDE_AS_CANDIDATE.has(fieldName) || FIELD_PREFIX_TO_EXCLUDE_AS_CANDIDATE.some((prefix) => @@ -30,7 +30,7 @@ const shouldBeExcluded = (fieldName: string) => { ); }; -const hasPrefixToInclude = (fieldName: string) => { +export const hasPrefixToInclude = (fieldName: string) => { return FIELD_PREFIX_TO_ADD_AS_CANDIDATE.some((prefix) => fieldName.startsWith(prefix) ); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts new file mode 100644 index 00000000000000..bffe3306a3cc6b --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts @@ -0,0 +1,19 @@ +/* + * 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 { getTermsAggRequest } from './query_field_value_pairs'; + +describe('query_field_value_pairs', () => { + describe('getTermsAggRequest()', () => { + it('returns the most basic request body for a terms aggregation', () => { + const req = getTermsAggRequest({ index: 'apm-*' }, 'myFieldName'); + expect(req?.body?.aggs?.attribute_terms?.terms?.field).toBe( + 'myFieldName' + ); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts new file mode 100644 index 00000000000000..e2a069d61b5550 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts @@ -0,0 +1,31 @@ +/* + * 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 { getTransactionDurationRangesRequest } from './query_fractions'; + +describe('query_fractions', () => { + describe('getTransactionDurationRangesRequest()', () => { + it('returns the request body for the transaction duration ranges aggregation', () => { + const ranges = [ + { to: 1 }, + { from: 1, to: 3 }, + { from: 3, to: 5 }, + { from: 5 }, + ]; + + const req = getTransactionDurationRangesRequest( + { index: 'apm-*' }, + ranges + ); + + expect(req?.body?.aggs?.latency_ranges?.range?.field).toBe( + 'transaction.duration.us' + ); + expect(req?.body?.aggs?.latency_ranges?.range?.ranges).toEqual(ranges); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts new file mode 100644 index 00000000000000..e24ce4e3b0b1d0 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts @@ -0,0 +1,45 @@ +/* + * 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 { getTransactionDurationHistogramRequest } from './query_histogram'; + +describe('query_histogram', () => { + describe('getHistogramIntervalRequest()', () => { + it('returns the request body for the histogram request', () => { + const req = getTransactionDurationHistogramRequest( + { index: 'apm-*' }, + 100 + ); + + expect(req).toEqual({ + body: { + aggs: { + transaction_duration_histogram: { + histogram: { + field: 'transaction.duration.us', + interval: 100, + }, + }, + }, + query: { + bool: { + filter: [ + { + term: { + 'processor.event': 'transaction', + }, + }, + ], + }, + }, + size: 0, + }, + index: 'apm-*', + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts new file mode 100644 index 00000000000000..d4f12ac0bcd46c --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts @@ -0,0 +1,46 @@ +/* + * 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 { getHistogramIntervalRequest } from './query_histogram_interval'; + +describe('query_histogram_interval', () => { + describe('getHistogramIntervalRequest()', () => { + it('returns the request body for the transaction duration ranges aggregation', () => { + const req = getHistogramIntervalRequest({ index: 'apm-*' }); + + expect(req).toEqual({ + body: { + aggs: { + transaction_duration_max: { + max: { + field: 'transaction.duration.us', + }, + }, + transaction_duration_min: { + min: { + field: 'transaction.duration.us', + }, + }, + }, + query: { + bool: { + filter: [ + { + term: { + 'processor.event': 'transaction', + }, + }, + ], + }, + }, + size: 0, + }, + index: 'apm-*', + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts new file mode 100644 index 00000000000000..0e4cfb9ad50e68 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts @@ -0,0 +1,46 @@ +/* + * 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 { getHistogramIntervalRequest } from './query_histogram_rangesteps'; + +describe('query_histogram_rangesteps', () => { + describe('getHistogramIntervalRequest()', () => { + it('returns the request body for the histogram interval request', () => { + const req = getHistogramIntervalRequest({ index: 'apm-*' }); + + expect(req).toEqual({ + body: { + aggs: { + transaction_duration_max: { + max: { + field: 'transaction.duration.us', + }, + }, + transaction_duration_min: { + min: { + field: 'transaction.duration.us', + }, + }, + }, + query: { + bool: { + filter: [ + { + term: { + 'processor.event': 'transaction', + }, + }, + ], + }, + }, + size: 0, + }, + index: 'apm-*', + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts new file mode 100644 index 00000000000000..eb65ab46bd1416 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts @@ -0,0 +1,44 @@ +/* + * 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 { getTransactionDurationPercentilesRequest } from './query_percentiles'; + +describe('query_percentiles', () => { + describe('getTransactionDurationPercentilesRequest()', () => { + it('returns the request body for the duration percentiles request', () => { + const req = getTransactionDurationPercentilesRequest({ index: 'apm-*' }); + + expect(req).toEqual({ + body: { + aggs: { + transaction_duration_percentiles: { + percentiles: { + field: 'transaction.duration.us', + hdr: { + number_of_significant_value_digits: 3, + }, + }, + }, + }, + query: { + bool: { + filter: [ + { + term: { + 'processor.event': 'transaction', + }, + }, + ], + }, + }, + size: 0, + }, + index: 'apm-*', + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.ts index 013c1ba3cbc239..18dcefb59a11a5 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.ts @@ -55,7 +55,7 @@ export const getTransactionDurationPercentilesRequest = ( }; }; -export const fetchTransactionDurationPecentiles = async ( +export const fetchTransactionDurationPercentiles = async ( esClient: ElasticsearchClient, params: SearchServiceParams, percents?: number[], @@ -73,7 +73,7 @@ export const fetchTransactionDurationPecentiles = async ( if (resp.body.aggregations === undefined) { throw new Error( - 'fetchTransactionDurationPecentiles failed, did not return aggregations.' + 'fetchTransactionDurationPercentiles failed, did not return aggregations.' ); } return ( diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts new file mode 100644 index 00000000000000..8698d7e086d6ec --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts @@ -0,0 +1,66 @@ +/* + * 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 { getTransactionDurationRangesRequest } from './query_ranges'; + +describe('query_ranges', () => { + describe('getTransactionDurationRangesRequest()', () => { + it('returns the request body for the duration percentiles request', () => { + const rangeSteps = [1, 3, 5]; + + const req = getTransactionDurationRangesRequest( + { index: 'apm-*' }, + rangeSteps + ); + + expect(req).toEqual({ + body: { + aggs: { + logspace_ranges: { + range: { + field: 'transaction.duration.us', + ranges: [ + { + to: 0, + }, + { + from: 0, + to: 1, + }, + { + from: 1, + to: 3, + }, + { + from: 3, + to: 5, + }, + { + from: 5, + }, + ], + }, + }, + }, + query: { + bool: { + filter: [ + { + term: { + 'processor.event': 'transaction', + }, + }, + ], + }, + }, + size: 0, + }, + index: 'apm-*', + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.ts index 88256f79150fc4..9074e7e0809bf7 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.ts @@ -42,7 +42,9 @@ export const getTransactionDurationRangesRequest = ( }, [{ to: 0 }] as Array<{ from?: number; to?: number }> ); - ranges.push({ from: ranges[ranges.length - 1].to }); + if (ranges.length > 0) { + ranges.push({ from: ranges[ranges.length - 1].to }); + } return { index: params.index, 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/correlations/search_strategy.test.ts new file mode 100644 index 00000000000000..9fac51ac708af8 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts @@ -0,0 +1,221 @@ +/* + * 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 { estypes } from '@elastic/elasticsearch'; + +import { SearchStrategyDependencies } from 'src/plugins/data/server'; + +import { + apmCorrelationsSearchStrategyProvider, + PartialSearchRequest, +} from './search_strategy'; + +// helper to trigger promises in the async search service +const flushPromises = () => new Promise(setImmediate); + +const clientFieldCapsMock = () => ({ body: { fields: [] } }); + +// minimal client mock to fulfill search requirements of the async search service to succeed +const clientSearchMock = ( + req: estypes.SearchRequest +): { body: estypes.SearchResponse } => { + let aggregations: + | { + transaction_duration_percentiles: estypes.AggregationsTDigestPercentilesAggregate; + } + | { + transaction_duration_min: estypes.AggregationsValueAggregate; + transaction_duration_max: estypes.AggregationsValueAggregate; + } + | { + logspace_ranges: estypes.AggregationsMultiBucketAggregate<{ + from: number; + doc_count: number; + }>; + } + | { + latency_ranges: estypes.AggregationsMultiBucketAggregate<{ + doc_count: number; + }>; + } + | undefined; + + // fetchTransactionDurationPercentiles + if (req?.body?.aggs?.transaction_duration_percentiles !== undefined) { + aggregations = { transaction_duration_percentiles: { values: {} } }; + } + + // fetchTransactionDurationHistogramInterval + if ( + req?.body?.aggs?.transaction_duration_min !== undefined && + req?.body?.aggs?.transaction_duration_max !== undefined + ) { + aggregations = { + transaction_duration_min: { value: 0 }, + transaction_duration_max: { value: 1234 }, + }; + } + + // fetchTransactionDurationCorrelation + if (req?.body?.aggs?.logspace_ranges !== undefined) { + aggregations = { logspace_ranges: { buckets: [] } }; + } + + // fetchTransactionDurationFractions + if (req?.body?.aggs?.latency_ranges !== undefined) { + aggregations = { latency_ranges: { buckets: [] } }; + } + + return { + body: { + _shards: { + failed: 0, + successful: 1, + total: 1, + }, + took: 162, + timed_out: false, + hits: { + hits: [], + total: { + value: 0, + relation: 'eq', + }, + }, + ...(aggregations !== undefined ? { aggregations } : {}), + }, + }; +}; + +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(); + expect(typeof searchStrategy.search).toBe('function'); + expect(typeof searchStrategy.cancel).toBe('function'); + }); + }); + + describe('search()', () => { + let mockClientFieldCaps: jest.Mock; + let mockClientSearch: jest.Mock; + let mockDeps: SearchStrategyDependencies; + let params: Required['params']; + + beforeEach(() => { + mockClientFieldCaps = jest.fn(clientFieldCapsMock); + mockClientSearch = jest.fn(clientSearchMock); + mockDeps = ({ + esClient: { + asCurrentUser: { + fieldCaps: mockClientFieldCaps, + search: mockClientSearch, + }, + }, + } as unknown) as SearchStrategyDependencies; + params = { + index: 'apm-*', + }; + }); + + describe('async functionality', () => { + it('throws an error if no params are provided', async () => { + const searchStrategy = await apmCorrelationsSearchStrategyProvider(); + expect(() => searchStrategy.search({}, {}, mockDeps)).toThrow( + 'Invalid request parameters.' + ); + }); + + it('performs a client search with params when no ID is provided', async () => { + const searchStrategy = await apmCorrelationsSearchStrategyProvider(); + await searchStrategy.search({ params }, {}, mockDeps).toPromise(); + const [[request]] = mockClientSearch.mock.calls; + + expect(request.index).toEqual('apm-*'); + expect(request.body).toEqual( + expect.objectContaining({ + aggs: { + transaction_duration_percentiles: { + percentiles: { + field: 'transaction.duration.us', + hdr: { number_of_significant_value_digits: 3 }, + }, + }, + }, + query: { + bool: { + filter: [{ term: { 'processor.event': 'transaction' } }], + }, + }, + size: 0, + }) + ); + }); + + it('retrieves the current request if an id is provided', async () => { + const searchStrategy = await apmCorrelationsSearchStrategyProvider(); + const response = await searchStrategy + .search({ id: 'my-search-id', params }, {}, mockDeps) + .toPromise(); + + expect(response).toEqual( + expect.objectContaining({ id: 'my-search-id' }) + ); + }); + + it('does not emit an error if the client throws', async () => { + mockClientSearch + .mockReset() + .mockRejectedValueOnce(new Error('client error')); + const searchStrategy = await apmCorrelationsSearchStrategyProvider(); + const response = await searchStrategy + .search({ params }, {}, mockDeps) + .toPromise(); + + expect(response).toEqual(expect.objectContaining({ isRunning: true })); + }); + + it('triggers the subscription only once', async () => { + expect.assertions(1); + const searchStrategy = await apmCorrelationsSearchStrategyProvider(); + searchStrategy + .search({ params }, {}, mockDeps) + .subscribe((response) => { + expect(response).toEqual( + expect.objectContaining({ loaded: 0, isRunning: true }) + ); + }); + }); + }); + + describe('response', () => { + it('sends an updated response on consecutive search calls', async () => { + const searchStrategy = await apmCorrelationsSearchStrategyProvider(); + + const response1 = await searchStrategy + .search({ params }, {}, mockDeps) + .toPromise(); + + expect(typeof response1.id).toEqual('string'); + expect(response1).toEqual( + expect.objectContaining({ loaded: 0, isRunning: true }) + ); + + await flushPromises(); + + const response2 = await searchStrategy + .search({ id: response1.id, params }, {}, mockDeps) + .toPromise(); + + expect(response2.id).toEqual(response1.id); + expect(response2).toEqual( + expect.objectContaining({ loaded: 10, isRunning: false }) + ); + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.test.ts new file mode 100644 index 00000000000000..8a1f4411a6aa43 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.test.ts @@ -0,0 +1,49 @@ +/* + * 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 { computeExpectationsAndRanges } from './aggregation_utils'; + +describe('aggregation utils', () => { + describe('computeExpectationsAndRanges()', () => { + it('returns expectations and ranges based on given percentiles #1', async () => { + const { expectations, ranges } = computeExpectationsAndRanges([0, 1]); + expect(expectations).toEqual([0, 0.5, 1]); + expect(ranges).toEqual([{ to: 0 }, { from: 0, to: 1 }, { from: 1 }]); + }); + it('returns expectations and ranges based on given percentiles #2', async () => { + const { expectations, ranges } = computeExpectationsAndRanges([1, 3, 5]); + expect(expectations).toEqual([1, 2, 4, 5]); + expect(ranges).toEqual([ + { to: 1 }, + { from: 1, to: 3 }, + { from: 3, to: 5 }, + { from: 5 }, + ]); + }); + it('returns expectations and ranges with adjusted fractions', async () => { + const { expectations, ranges } = computeExpectationsAndRanges([ + 1, + 3, + 3, + 5, + ]); + expect(expectations).toEqual([ + 1, + 2.333333333333333, + 3.666666666666667, + 5, + ]); + expect(ranges).toEqual([ + { to: 1 }, + { from: 1, to: 3 }, + { from: 3, to: 3 }, + { from: 3, to: 5 }, + { from: 5 }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.ts index 34e5ae2795d589..8d83b8fc29b058 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.ts @@ -31,14 +31,16 @@ export const computeExpectationsAndRanges = ( const ranges = percentiles.reduce((p, to) => { const from = p[p.length - 1]?.to; - if (from) { + if (from !== undefined) { p.push({ from, to }); } else { p.push({ to }); } return p; }, [] as Array<{ from?: number; to?: number }>); - ranges.push({ from: ranges[ranges.length - 1].to }); + if (ranges.length > 0) { + ranges.push({ from: ranges[ranges.length - 1].to }); + } const expectations = [tempPercentiles[0]]; for (let i = 1; i < tempPercentiles.length; i++) { diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/math_utils.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/math_utils.test.ts new file mode 100644 index 00000000000000..a5a6bc99d362e3 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/math_utils.test.ts @@ -0,0 +1,26 @@ +/* + * 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 { getRandomInt } from './math_utils'; + +describe('math utils', () => { + describe('getRandomInt()', () => { + it('returns a random integer within the given range', () => { + const min = 0.9; + const max = 11.1; + const randomInt = getRandomInt(min, max); + expect(Number.isInteger(randomInt)).toBe(true); + expect(randomInt > min).toBe(true); + expect(randomInt < max).toBe(true); + }); + + it('returns 1 if given range only allows this integer', () => { + const randomInt = getRandomInt(0.9, 1.1); + expect(randomInt).toBe(1); + }); + }); +}); From 700bd031eece6286388f2b7b9d3b0bfa2f5bf3dd Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 30 Jun 2021 17:12:25 +0200 Subject: [PATCH 2/7] remove leading 0 from fractions array. --- .../search_strategies/correlations/query_correlation.test.ts | 2 +- .../lib/search_strategies/correlations/query_correlation.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts index 39a32c363da451..c3eb16709aad3a 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts @@ -51,7 +51,7 @@ describe('query_correlation', () => { expect( (query?.body?.aggs?.ks_test as any)?.bucket_count_ks_test?.fractions - ).toEqual([0, ...fractions]); + ).toEqual(fractions); }); }); }); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.ts index 3fdb47a1a3813e..f63c36f90d728a 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.ts @@ -80,8 +80,7 @@ export const getTransactionDurationCorrelationRequest = ( // KS test p value = ks_test.less ks_test: { bucket_count_ks_test: { - // Remove 0 after https://github.com/elastic/elasticsearch/pull/74624 is merged - fractions: [0, ...fractions], + fractions, buckets_path: 'latency_ranges>_count', alternative: ['less', 'greater', 'two_sided'], }, From 2642aab8c3e54a574a049ed5935aafb69a689f37 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 1 Jul 2021 11:31:44 +0200 Subject: [PATCH 3/7] unit tests for fetch functions. --- .../correlations/query_correlation.test.ts | 72 +++++++++++++--- .../query_field_candidates.test.ts | 84 ++++++++++++++++++- .../query_field_value_pairs.test.ts | 67 ++++++++++++++- .../correlations/query_field_value_pairs.ts | 2 +- .../correlations/query_fractions.test.ts | 58 ++++++++++--- .../correlations/query_histogram.test.ts | 59 +++++++++++-- .../query_histogram_interval.test.ts | 48 ++++++++++- .../query_histogram_rangesteps.test.ts | 50 ++++++++++- .../correlations/query_percentiles.test.ts | 55 +++++++++++- .../correlations/query_ranges.test.ts | 74 ++++++++++++++-- 10 files changed, 513 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts index c3eb16709aad3a..ed40a7cbfd8d95 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts @@ -5,34 +5,34 @@ * 2.0. */ +import type { estypes } from '@elastic/elasticsearch'; + +import type { ElasticsearchClient } from 'src/core/server'; + import { + fetchTransactionDurationCorrelation, getTransactionDurationCorrelationRequest, BucketCorrelation, } from './query_correlation'; +const params = { index: 'apm-*' }; +const expectations = [1, 3, 5]; +const ranges = [{ to: 1 }, { from: 1, to: 3 }, { from: 3, to: 5 }, { from: 5 }]; +const fractions = [1, 2, 4, 5]; +const totalDocCount = 1234; + describe('query_correlation', () => { describe('getTransactionDurationCorrelationRequest()', () => { it('applies options to the returned query with aggregations for correlations and k-test', () => { - const index = 'apm-*'; - const expectations = [1, 3, 5]; - const ranges = [ - { to: 1 }, - { from: 1, to: 3 }, - { from: 3, to: 5 }, - { from: 5 }, - ]; - const fractions = [1, 2, 4, 5]; - const totalDocCount = 1234; - const query = getTransactionDurationCorrelationRequest( - { index }, + params, expectations, ranges, fractions, totalDocCount ); - expect(query.index).toBe(index); + expect(query.index).toBe(params.index); expect(query?.body?.aggs?.latency_ranges?.range?.field).toBe( 'transaction.duration.us' @@ -54,4 +54,50 @@ describe('query_correlation', () => { ).toEqual(fractions); }); }); + + describe('fetchTransactionDurationCorrelation()', () => { + it('returns the data from the aggregations', async () => { + const latencyRangesBuckets = [{ to: 1 }, { from: 1, to: 2 }, { from: 2 }]; + const transactionDurationCorrelationValue = 0.45; + const KsTestLess = 0.01; + + const esClientSearchMock = jest.fn((req: estypes.SearchRequest): { + body: estypes.SearchResponse; + } => { + return { + body: ({ + aggregations: { + latency_ranges: { + buckets: latencyRangesBuckets, + }, + transaction_duration_correlation: { + value: transactionDurationCorrelationValue, + }, + ks_test: { less: KsTestLess }, + }, + } as unknown) as estypes.SearchResponse, + }; + }); + + const esClientMock = ({ + search: esClientSearchMock, + } as unknown) as ElasticsearchClient; + + const resp = await fetchTransactionDurationCorrelation( + esClientMock, + params, + expectations, + ranges, + fractions, + totalDocCount + ); + + expect(resp).toEqual({ + correlation: transactionDurationCorrelationValue, + ksTest: KsTestLess, + ranges: latencyRangesBuckets, + }); + expect(esClientSearchMock).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts index 10b68e329525f6..4648a065b035b8 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts @@ -5,12 +5,19 @@ * 2.0. */ +import type { estypes } from '@elastic/elasticsearch'; + +import type { ElasticsearchClient } from 'src/core/server'; + import { + fetchTransactionDurationFieldCandidates, getRandomDocsRequest, hasPrefixToInclude, shouldBeExcluded, } from './query_field_candidates'; +const params = { index: 'apm-*' }; + describe('query_field_candidates', () => { describe('shouldBeExcluded()', () => { it('does not exclude a completely custom field name', () => { @@ -38,7 +45,7 @@ describe('query_field_candidates', () => { describe('getRandomDocsRequest()', () => { it('returns the most basic request body for a sample of random documents', () => { - const req = getRandomDocsRequest({ index: 'apm-*' }); + const req = getRandomDocsRequest(params); expect(req).toEqual({ body: { @@ -63,8 +70,81 @@ describe('query_field_candidates', () => { size: 1000, track_total_hits: true, }, - index: 'apm-*', + index: params.index, + }); + }); + }); + + describe('fetchTransactionDurationFieldCandidates()', () => { + it('returns field candidates and total hits', async () => { + const totalHits = 1234; + + const esClientFieldCapsMock = jest.fn(() => ({ + body: { + fields: { + myIpFieldName: { ip: {} }, + myKeywordFieldName: { keyword: {} }, + myUnpopulatedKeywordFieldName: { keyword: {} }, + myNumericFieldName: { number: {} }, + }, + }, + })); + const esClientSearchMock = jest.fn((req: estypes.SearchRequest): { + body: estypes.SearchResponse; + } => { + return { + body: ({ + hits: { + hits: [ + { + fields: { + myIpFieldName: '1.1.1.1', + myKeywordFieldName: 'myKeywordFieldValue', + myNumericFieldName: 1234, + }, + }, + ], + total: { value: totalHits }, + }, + } as unknown) as estypes.SearchResponse, + }; + }); + + const esClientMock = ({ + fieldCaps: esClientFieldCapsMock, + search: esClientSearchMock, + } as unknown) as ElasticsearchClient; + + const resp = await fetchTransactionDurationFieldCandidates( + esClientMock, + params + ); + + expect(resp).toEqual({ + fieldCandidates: [ + // default field candidates + 'service.version', + 'service.node.name', + 'service.framework.version', + 'service.language.version', + 'service.runtime.version', + 'kubernetes.pod.name', + 'kubernetes.pod.uid', + 'container.id', + 'source.ip', + 'client.ip', + 'host.ip', + 'service.environment', + 'process.args', + 'http.response.status_code', + // field candidates identified by sample documents + 'myIpFieldName', + 'myKeywordFieldName', + ], + totalHits, }); + expect(esClientFieldCapsMock).toHaveBeenCalledTimes(1); + expect(esClientSearchMock).toHaveBeenCalledTimes(1); }); }); }); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts index bffe3306a3cc6b..ba63a0a8c9551f 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts @@ -5,15 +5,74 @@ * 2.0. */ -import { getTermsAggRequest } from './query_field_value_pairs'; +import type { estypes } from '@elastic/elasticsearch'; + +import type { ElasticsearchClient } from 'src/core/server'; + +import type { AsyncSearchProviderProgress } from '../../../../common/search_strategies/correlations/types'; + +import { + fetchTransactionDurationFieldValuePairs, + getTermsAggRequest, +} from './query_field_value_pairs'; + +const params = { index: 'apm-*' }; describe('query_field_value_pairs', () => { describe('getTermsAggRequest()', () => { it('returns the most basic request body for a terms aggregation', () => { - const req = getTermsAggRequest({ index: 'apm-*' }, 'myFieldName'); - expect(req?.body?.aggs?.attribute_terms?.terms?.field).toBe( - 'myFieldName' + const fieldName = 'myFieldName'; + const req = getTermsAggRequest(params, fieldName); + expect(req?.body?.aggs?.attribute_terms?.terms?.field).toBe(fieldName); + }); + }); + + describe('fetchTransactionDurationFieldValuePairs()', () => { + it('returns field/value pairs for field candidates', async () => { + const fieldCandidates = [ + 'myFieldCandidate1', + 'myFieldCandidate2', + 'myFieldCandidate3', + ]; + const progress = { + loadedFieldValuePairs: 0, + } as AsyncSearchProviderProgress; + + const esClientSearchMock = jest.fn((req: estypes.SearchRequest): { + body: estypes.SearchResponse; + } => { + return { + body: ({ + aggregations: { + attribute_terms: { + buckets: [{ key: 'myValue1' }, { key: 'myValue2' }], + }, + }, + } as unknown) as estypes.SearchResponse, + }; + }); + + const esClientMock = ({ + search: esClientSearchMock, + } as unknown) as ElasticsearchClient; + + const resp = await fetchTransactionDurationFieldValuePairs( + esClientMock, + params, + fieldCandidates, + progress ); + + 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' }, + ]); + expect(esClientSearchMock).toHaveBeenCalledTimes(3); }); }); }); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.ts index 703a203c892072..8fde9d3ab13783 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.ts @@ -52,7 +52,7 @@ export const fetchTransactionDurationFieldValuePairs = async ( ): Promise => { const fieldValuePairs: FieldValuePairs = []; - let fieldValuePairsProgress = 0; + let fieldValuePairsProgress = 1; for (let i = 0; i < fieldCandidates.length; i++) { const fieldName = fieldCandidates[i]; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts index e2a069d61b5550..677145ebde2788 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts @@ -5,22 +5,22 @@ * 2.0. */ -import { getTransactionDurationRangesRequest } from './query_fractions'; +import type { estypes } from '@elastic/elasticsearch'; + +import type { ElasticsearchClient } from 'src/core/server'; + +import { + fetchTransactionDurationFractions, + getTransactionDurationRangesRequest, +} from './query_fractions'; + +const params = { index: 'apm-*' }; +const ranges = [{ to: 1 }, { from: 1, to: 3 }, { from: 3, to: 5 }, { from: 5 }]; describe('query_fractions', () => { describe('getTransactionDurationRangesRequest()', () => { it('returns the request body for the transaction duration ranges aggregation', () => { - const ranges = [ - { to: 1 }, - { from: 1, to: 3 }, - { from: 3, to: 5 }, - { from: 5 }, - ]; - - const req = getTransactionDurationRangesRequest( - { index: 'apm-*' }, - ranges - ); + const req = getTransactionDurationRangesRequest(params, ranges); expect(req?.body?.aggs?.latency_ranges?.range?.field).toBe( 'transaction.duration.us' @@ -28,4 +28,38 @@ describe('query_fractions', () => { expect(req?.body?.aggs?.latency_ranges?.range?.ranges).toEqual(ranges); }); }); + + describe('fetchTransactionDurationFractions()', () => { + it('computes the actual percentile bucket counts and actual fractions', async () => { + const esClientSearchMock = jest.fn((req: estypes.SearchRequest): { + body: estypes.SearchResponse; + } => { + return { + body: ({ + aggregations: { + latency_ranges: { + buckets: [{ doc_count: 1 }, { doc_count: 2 }], + }, + }, + } as unknown) as estypes.SearchResponse, + }; + }); + + const esClientMock = ({ + search: esClientSearchMock, + } as unknown) as ElasticsearchClient; + + const resp = await fetchTransactionDurationFractions( + esClientMock, + params, + ranges + ); + + expect(resp).toEqual({ + fractions: [0.3333333333333333, 0.6666666666666666], + totalDocCount: 3, + }); + expect(esClientSearchMock).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts index e24ce4e3b0b1d0..8069a454b6a2c1 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts @@ -5,15 +5,22 @@ * 2.0. */ -import { getTransactionDurationHistogramRequest } from './query_histogram'; +import type { estypes } from '@elastic/elasticsearch'; + +import type { ElasticsearchClient } from 'src/core/server'; + +import { + fetchTransactionDurationHistogram, + getTransactionDurationHistogramRequest, +} from './query_histogram'; + +const params = { index: 'apm-*' }; +const interval = 100; describe('query_histogram', () => { - describe('getHistogramIntervalRequest()', () => { + describe('getTransactionDurationHistogramRequest()', () => { it('returns the request body for the histogram request', () => { - const req = getTransactionDurationHistogramRequest( - { index: 'apm-*' }, - 100 - ); + const req = getTransactionDurationHistogramRequest(params, interval); expect(req).toEqual({ body: { @@ -21,7 +28,7 @@ describe('query_histogram', () => { transaction_duration_histogram: { histogram: { field: 'transaction.duration.us', - interval: 100, + interval, }, }, }, @@ -42,4 +49,42 @@ describe('query_histogram', () => { }); }); }); + + describe('fetchTransactionDurationHistogram()', () => { + it('returns the buckets from the histogram aggregation', async () => { + const histogramBucket = [ + { + key: 0.0, + doc_count: 1, + }, + ]; + + const esClientSearchMock = jest.fn((req: estypes.SearchRequest): { + body: estypes.SearchResponse; + } => { + return { + body: ({ + aggregations: { + transaction_duration_histogram: { + buckets: histogramBucket, + }, + }, + } as unknown) as estypes.SearchResponse, + }; + }); + + const esClientMock = ({ + search: esClientSearchMock, + } as unknown) as ElasticsearchClient; + + const resp = await fetchTransactionDurationHistogram( + esClientMock, + params, + interval + ); + + expect(resp).toEqual(histogramBucket); + expect(esClientSearchMock).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts index d4f12ac0bcd46c..548677a2a7f7d0 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts @@ -5,12 +5,21 @@ * 2.0. */ -import { getHistogramIntervalRequest } from './query_histogram_interval'; +import type { estypes } from '@elastic/elasticsearch'; + +import type { ElasticsearchClient } from 'src/core/server'; + +import { + fetchTransactionDurationHistogramInterval, + getHistogramIntervalRequest, +} from './query_histogram_interval'; + +const params = { index: 'apm-*' }; describe('query_histogram_interval', () => { describe('getHistogramIntervalRequest()', () => { it('returns the request body for the transaction duration ranges aggregation', () => { - const req = getHistogramIntervalRequest({ index: 'apm-*' }); + const req = getHistogramIntervalRequest(params); expect(req).toEqual({ body: { @@ -39,8 +48,41 @@ describe('query_histogram_interval', () => { }, size: 0, }, - index: 'apm-*', + index: params.index, }); }); }); + + describe('fetchTransactionDurationHistogramInterval()', () => { + it('fetches the interval duration for histograms', async () => { + const esClientSearchMock = jest.fn((req: estypes.SearchRequest): { + body: estypes.SearchResponse; + } => { + return { + body: ({ + aggregations: { + transaction_duration_max: { + value: 10000, + }, + transaction_duration_min: { + value: 10, + }, + }, + } as unknown) as estypes.SearchResponse, + }; + }); + + const esClientMock = ({ + search: esClientSearchMock, + } as unknown) as ElasticsearchClient; + + const resp = await fetchTransactionDurationHistogramInterval( + esClientMock, + params + ); + + expect(resp).toEqual(10); + expect(esClientSearchMock).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts index 0e4cfb9ad50e68..461884bd00b4cf 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts @@ -5,12 +5,21 @@ * 2.0. */ -import { getHistogramIntervalRequest } from './query_histogram_rangesteps'; +import type { estypes } from '@elastic/elasticsearch'; + +import type { ElasticsearchClient } from 'src/core/server'; + +import { + fetchTransactionDurationHistogramRangesteps, + getHistogramIntervalRequest, +} from './query_histogram_rangesteps'; + +const params = { index: 'apm-*' }; describe('query_histogram_rangesteps', () => { describe('getHistogramIntervalRequest()', () => { it('returns the request body for the histogram interval request', () => { - const req = getHistogramIntervalRequest({ index: 'apm-*' }); + const req = getHistogramIntervalRequest(params); expect(req).toEqual({ body: { @@ -39,8 +48,43 @@ describe('query_histogram_rangesteps', () => { }, size: 0, }, - index: 'apm-*', + index: params.index, }); }); }); + + describe('fetchTransactionDurationHistogramRangesteps()', () => { + it('fetches the range steps for the log histogram', async () => { + const esClientSearchMock = jest.fn((req: estypes.SearchRequest): { + body: estypes.SearchResponse; + } => { + return { + body: ({ + aggregations: { + transaction_duration_max: { + value: 10000, + }, + transaction_duration_min: { + value: 10, + }, + }, + } as unknown) as estypes.SearchResponse, + }; + }); + + const esClientMock = ({ + search: esClientSearchMock, + } as unknown) as ElasticsearchClient; + + const resp = await fetchTransactionDurationHistogramRangesteps( + esClientMock, + params + ); + + expect(resp.length).toEqual(100); + expect(resp[0]).toEqual(9.260965422132594); + expect(resp[99]).toEqual(18521.930844265193); + expect(esClientSearchMock).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts index eb65ab46bd1416..e8126378cd3411 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts @@ -5,12 +5,21 @@ * 2.0. */ -import { getTransactionDurationPercentilesRequest } from './query_percentiles'; +import type { estypes } from '@elastic/elasticsearch'; + +import type { ElasticsearchClient } from 'src/core/server'; + +import { + fetchTransactionDurationPercentiles, + getTransactionDurationPercentilesRequest, +} from './query_percentiles'; + +const params = { index: 'apm-*' }; describe('query_percentiles', () => { describe('getTransactionDurationPercentilesRequest()', () => { it('returns the request body for the duration percentiles request', () => { - const req = getTransactionDurationPercentilesRequest({ index: 'apm-*' }); + const req = getTransactionDurationPercentilesRequest(params); expect(req).toEqual({ body: { @@ -37,8 +46,48 @@ describe('query_percentiles', () => { }, size: 0, }, - index: 'apm-*', + index: params.index, }); }); }); + + describe('fetchTransactionDurationPercentiles()', () => { + it('fetches the percentiles', async () => { + const percentilesValues = { + '1.0': 5.0, + '5.0': 25.0, + '25.0': 165.0, + '50.0': 445.0, + '75.0': 725.0, + '95.0': 945.0, + '99.0': 985.0, + }; + + const esClientSearchMock = jest.fn((req: estypes.SearchRequest): { + body: estypes.SearchResponse; + } => { + return { + body: ({ + aggregations: { + transaction_duration_percentiles: { + values: percentilesValues, + }, + }, + } as unknown) as estypes.SearchResponse, + }; + }); + + const esClientMock = ({ + search: esClientSearchMock, + } as unknown) as ElasticsearchClient; + + const resp = await fetchTransactionDurationPercentiles( + esClientMock, + params + ); + + expect(resp).toEqual(percentilesValues); + expect(esClientSearchMock).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts index 8698d7e086d6ec..c9ea46b3f61fe0 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts @@ -5,17 +5,22 @@ * 2.0. */ -import { getTransactionDurationRangesRequest } from './query_ranges'; +import type { estypes } from '@elastic/elasticsearch'; + +import type { ElasticsearchClient } from 'src/core/server'; + +import { + fetchTransactionDurationRanges, + getTransactionDurationRangesRequest, +} from './query_ranges'; + +const params = { index: 'apm-*' }; +const rangeSteps = [1, 3, 5]; describe('query_ranges', () => { describe('getTransactionDurationRangesRequest()', () => { it('returns the request body for the duration percentiles request', () => { - const rangeSteps = [1, 3, 5]; - - const req = getTransactionDurationRangesRequest( - { index: 'apm-*' }, - rangeSteps - ); + const req = getTransactionDurationRangesRequest(params, rangeSteps); expect(req).toEqual({ body: { @@ -59,8 +64,61 @@ describe('query_ranges', () => { }, size: 0, }, - index: 'apm-*', + index: params.index, }); }); }); + + describe('fetchTransactionDurationRanges()', () => { + it('fetches the percentiles', async () => { + const logspaceRangesBuckets = [ + { + key: '*-100.0', + to: 100.0, + doc_count: 2, + }, + { + key: '100.0-200.0', + from: 100.0, + to: 200.0, + doc_count: 2, + }, + { + key: '200.0-*', + from: 200.0, + doc_count: 3, + }, + ]; + + const esClientSearchMock = jest.fn((req: estypes.SearchRequest): { + body: estypes.SearchResponse; + } => { + return { + body: ({ + aggregations: { + logspace_ranges: { + buckets: logspaceRangesBuckets, + }, + }, + } as unknown) as estypes.SearchResponse, + }; + }); + + const esClientMock = ({ + search: esClientSearchMock, + } as unknown) as ElasticsearchClient; + + const resp = await fetchTransactionDurationRanges( + esClientMock, + params, + rangeSteps + ); + + expect(resp).toEqual([ + { doc_count: 2, key: 100 }, + { doc_count: 3, key: 200 }, + ]); + expect(esClientSearchMock).toHaveBeenCalledTimes(1); + }); + }); }); From f42dda2d551509a1e26c94ce2e0972fe604fcc3d Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 5 Jul 2021 14:35:38 +0200 Subject: [PATCH 4/7] remove track_total_hits from field candidates query. --- .../correlations/query_field_candidates.test.ts | 5 ----- .../search_strategies/correlations/query_field_candidates.ts | 5 +---- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts index 4648a065b035b8..6b5876f416fffc 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts @@ -68,7 +68,6 @@ describe('query_field_candidates', () => { }, }, size: 1000, - track_total_hits: true, }, index: params.index, }); @@ -77,8 +76,6 @@ describe('query_field_candidates', () => { describe('fetchTransactionDurationFieldCandidates()', () => { it('returns field candidates and total hits', async () => { - const totalHits = 1234; - const esClientFieldCapsMock = jest.fn(() => ({ body: { fields: { @@ -104,7 +101,6 @@ describe('query_field_candidates', () => { }, }, ], - total: { value: totalHits }, }, } as unknown) as estypes.SearchResponse, }; @@ -141,7 +137,6 @@ describe('query_field_candidates', () => { 'myIpFieldName', 'myKeywordFieldName', ], - totalHits, }); expect(esClientFieldCapsMock).toHaveBeenCalledTimes(1); expect(esClientSearchMock).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.ts index def1bb566c9c80..0fbdfef405e0d1 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.ts @@ -50,8 +50,6 @@ export const getRandomDocsRequest = ( random_score: {}, }, }, - // Required value for later correlation queries - track_total_hits: true, size: POPULATED_DOC_COUNT_SAMPLE_SIZE, }, }); @@ -59,7 +57,7 @@ export const getRandomDocsRequest = ( export const fetchTransactionDurationFieldCandidates = async ( esClient: ElasticsearchClient, params: SearchServiceParams -): Promise<{ fieldCandidates: Field[]; totalHits: number }> => { +): Promise<{ fieldCandidates: Field[] }> => { const { index } = params; // Get all fields with keyword mapping const respMapping = await esClient.fieldCaps({ @@ -100,6 +98,5 @@ export const fetchTransactionDurationFieldCandidates = async ( return { fieldCandidates: [...finalFieldCandidates], - totalHits: (resp.body.hits.total as estypes.SearchTotalHits).value, }; }; From 16ef5609c745d062777940c654555d10c8332563 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 5 Jul 2021 14:39:31 +0200 Subject: [PATCH 5/7] simpliy aggs check code. --- .../correlations/search_strategy.test.ts | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) 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/correlations/search_strategy.test.ts index 9fac51ac708af8..20e505840f0fdd 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts @@ -44,30 +44,33 @@ const clientSearchMock = ( } | undefined; - // fetchTransactionDurationPercentiles - if (req?.body?.aggs?.transaction_duration_percentiles !== undefined) { - aggregations = { transaction_duration_percentiles: { values: {} } }; - } - - // fetchTransactionDurationHistogramInterval - if ( - req?.body?.aggs?.transaction_duration_min !== undefined && - req?.body?.aggs?.transaction_duration_max !== undefined - ) { - aggregations = { - transaction_duration_min: { value: 0 }, - transaction_duration_max: { value: 1234 }, - }; - } + if (req?.body?.aggs !== undefined) { + const aggs = req.body.aggs; + // fetchTransactionDurationPercentiles + if (aggs.transaction_duration_percentiles !== undefined) { + aggregations = { transaction_duration_percentiles: { values: {} } }; + } + + // fetchTransactionDurationHistogramInterval + if ( + aggs.transaction_duration_min !== undefined && + aggs.transaction_duration_max !== undefined + ) { + aggregations = { + transaction_duration_min: { value: 0 }, + transaction_duration_max: { value: 1234 }, + }; + } - // fetchTransactionDurationCorrelation - if (req?.body?.aggs?.logspace_ranges !== undefined) { - aggregations = { logspace_ranges: { buckets: [] } }; - } + // fetchTransactionDurationCorrelation + if (aggs.logspace_ranges !== undefined) { + aggregations = { logspace_ranges: { buckets: [] } }; + } - // fetchTransactionDurationFractions - if (req?.body?.aggs?.latency_ranges !== undefined) { - aggregations = { latency_ranges: { buckets: [] } }; + // fetchTransactionDurationFractions + if (aggs.latency_ranges !== undefined) { + aggregations = { latency_ranges: { buckets: [] } }; + } } return { From 02a1fd2cd6f16c3ccf1ecb445c8023126ee5eaed Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 5 Jul 2021 14:43:17 +0200 Subject: [PATCH 6/7] remove parenthesis. --- .../correlations/get_query_with_params.test.ts | 2 +- .../correlations/query_correlation.test.ts | 4 ++-- .../correlations/query_field_candidates.test.ts | 8 ++++---- .../correlations/query_field_value_pairs.test.ts | 4 ++-- .../correlations/query_fractions.test.ts | 4 ++-- .../correlations/query_histogram.test.ts | 4 ++-- .../correlations/query_histogram_interval.test.ts | 4 ++-- .../correlations/query_histogram_rangesteps.test.ts | 4 ++-- .../correlations/query_percentiles.test.ts | 4 ++-- .../search_strategies/correlations/query_ranges.test.ts | 4 ++-- .../correlations/search_strategy.test.ts | 2 +- .../correlations/utils/aggregation_utils.test.ts | 2 +- .../correlations/utils/math_utils.test.ts | 2 +- 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.test.ts index 7bf392a3be8686..12e897ab3eec92 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/get_query_with_params.test.ts @@ -8,7 +8,7 @@ import { getQueryWithParams } from './get_query_with_params'; describe('correlations', () => { - describe('getQueryWithParams()', () => { + describe('getQueryWithParams', () => { it('returns the most basic query filtering on processor.event=transaction', () => { const query = getQueryWithParams({ params: { index: 'apm-*' } }); expect(query).toEqual({ diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts index ed40a7cbfd8d95..24741ebaa2daef 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_correlation.test.ts @@ -22,7 +22,7 @@ const fractions = [1, 2, 4, 5]; const totalDocCount = 1234; describe('query_correlation', () => { - describe('getTransactionDurationCorrelationRequest()', () => { + describe('getTransactionDurationCorrelationRequest', () => { it('applies options to the returned query with aggregations for correlations and k-test', () => { const query = getTransactionDurationCorrelationRequest( params, @@ -55,7 +55,7 @@ describe('query_correlation', () => { }); }); - describe('fetchTransactionDurationCorrelation()', () => { + describe('fetchTransactionDurationCorrelation', () => { it('returns the data from the aggregations', async () => { const latencyRangesBuckets = [{ to: 1 }, { from: 1, to: 2 }, { from: 2 }]; const transactionDurationCorrelationValue = 0.45; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts index 6b5876f416fffc..89bdd4280d3249 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_candidates.test.ts @@ -19,7 +19,7 @@ import { const params = { index: 'apm-*' }; describe('query_field_candidates', () => { - describe('shouldBeExcluded()', () => { + describe('shouldBeExcluded', () => { it('does not exclude a completely custom field name', () => { expect(shouldBeExcluded('myFieldName')).toBe(false); }); @@ -33,7 +33,7 @@ describe('query_field_candidates', () => { }); }); - describe('hasPrefixToInclude()', () => { + describe('hasPrefixToInclude', () => { it('identifies if a field name is prefixed to be included', () => { expect(hasPrefixToInclude('myFieldName')).toBe(false); expect(hasPrefixToInclude('somePrefix.myFieldName')).toBe(false); @@ -43,7 +43,7 @@ describe('query_field_candidates', () => { }); }); - describe('getRandomDocsRequest()', () => { + describe('getRandomDocsRequest', () => { it('returns the most basic request body for a sample of random documents', () => { const req = getRandomDocsRequest(params); @@ -74,7 +74,7 @@ describe('query_field_candidates', () => { }); }); - describe('fetchTransactionDurationFieldCandidates()', () => { + describe('fetchTransactionDurationFieldCandidates', () => { it('returns field candidates and total hits', async () => { const esClientFieldCapsMock = jest.fn(() => ({ body: { diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts index ba63a0a8c9551f..ea5a1f55bc9246 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_field_value_pairs.test.ts @@ -19,7 +19,7 @@ import { const params = { index: 'apm-*' }; describe('query_field_value_pairs', () => { - describe('getTermsAggRequest()', () => { + describe('getTermsAggRequest', () => { it('returns the most basic request body for a terms aggregation', () => { const fieldName = 'myFieldName'; const req = getTermsAggRequest(params, fieldName); @@ -27,7 +27,7 @@ describe('query_field_value_pairs', () => { }); }); - describe('fetchTransactionDurationFieldValuePairs()', () => { + describe('fetchTransactionDurationFieldValuePairs', () => { it('returns field/value pairs for field candidates', async () => { const fieldCandidates = [ 'myFieldCandidate1', diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts index 677145ebde2788..6052841d277c3d 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_fractions.test.ts @@ -18,7 +18,7 @@ const params = { index: 'apm-*' }; const ranges = [{ to: 1 }, { from: 1, to: 3 }, { from: 3, to: 5 }, { from: 5 }]; describe('query_fractions', () => { - describe('getTransactionDurationRangesRequest()', () => { + describe('getTransactionDurationRangesRequest', () => { it('returns the request body for the transaction duration ranges aggregation', () => { const req = getTransactionDurationRangesRequest(params, ranges); @@ -29,7 +29,7 @@ describe('query_fractions', () => { }); }); - describe('fetchTransactionDurationFractions()', () => { + describe('fetchTransactionDurationFractions', () => { it('computes the actual percentile bucket counts and actual fractions', async () => { const esClientSearchMock = jest.fn((req: estypes.SearchRequest): { body: estypes.SearchResponse; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts index 8069a454b6a2c1..2be94463522605 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram.test.ts @@ -18,7 +18,7 @@ const params = { index: 'apm-*' }; const interval = 100; describe('query_histogram', () => { - describe('getTransactionDurationHistogramRequest()', () => { + describe('getTransactionDurationHistogramRequest', () => { it('returns the request body for the histogram request', () => { const req = getTransactionDurationHistogramRequest(params, interval); @@ -50,7 +50,7 @@ describe('query_histogram', () => { }); }); - describe('fetchTransactionDurationHistogram()', () => { + describe('fetchTransactionDurationHistogram', () => { it('returns the buckets from the histogram aggregation', async () => { const histogramBucket = [ { diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts index 548677a2a7f7d0..9ed529ccabddbe 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_interval.test.ts @@ -17,7 +17,7 @@ import { const params = { index: 'apm-*' }; describe('query_histogram_interval', () => { - describe('getHistogramIntervalRequest()', () => { + describe('getHistogramIntervalRequest', () => { it('returns the request body for the transaction duration ranges aggregation', () => { const req = getHistogramIntervalRequest(params); @@ -53,7 +53,7 @@ describe('query_histogram_interval', () => { }); }); - describe('fetchTransactionDurationHistogramInterval()', () => { + describe('fetchTransactionDurationHistogramInterval', () => { it('fetches the interval duration for histograms', async () => { const esClientSearchMock = jest.fn((req: estypes.SearchRequest): { body: estypes.SearchResponse; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts index 461884bd00b4cf..bb366ea29fed48 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_histogram_rangesteps.test.ts @@ -17,7 +17,7 @@ import { const params = { index: 'apm-*' }; describe('query_histogram_rangesteps', () => { - describe('getHistogramIntervalRequest()', () => { + describe('getHistogramIntervalRequest', () => { it('returns the request body for the histogram interval request', () => { const req = getHistogramIntervalRequest(params); @@ -53,7 +53,7 @@ describe('query_histogram_rangesteps', () => { }); }); - describe('fetchTransactionDurationHistogramRangesteps()', () => { + describe('fetchTransactionDurationHistogramRangesteps', () => { it('fetches the range steps for the log histogram', async () => { const esClientSearchMock = jest.fn((req: estypes.SearchRequest): { body: estypes.SearchResponse; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts index e8126378cd3411..0c319aee0fb2b7 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_percentiles.test.ts @@ -17,7 +17,7 @@ import { const params = { index: 'apm-*' }; describe('query_percentiles', () => { - describe('getTransactionDurationPercentilesRequest()', () => { + describe('getTransactionDurationPercentilesRequest', () => { it('returns the request body for the duration percentiles request', () => { const req = getTransactionDurationPercentilesRequest(params); @@ -51,7 +51,7 @@ describe('query_percentiles', () => { }); }); - describe('fetchTransactionDurationPercentiles()', () => { + describe('fetchTransactionDurationPercentiles', () => { it('fetches the percentiles', async () => { const percentilesValues = { '1.0': 5.0, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts index c9ea46b3f61fe0..9451928e47ded3 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/query_ranges.test.ts @@ -18,7 +18,7 @@ const params = { index: 'apm-*' }; const rangeSteps = [1, 3, 5]; describe('query_ranges', () => { - describe('getTransactionDurationRangesRequest()', () => { + describe('getTransactionDurationRangesRequest', () => { it('returns the request body for the duration percentiles request', () => { const req = getTransactionDurationRangesRequest(params, rangeSteps); @@ -69,7 +69,7 @@ describe('query_ranges', () => { }); }); - describe('fetchTransactionDurationRanges()', () => { + describe('fetchTransactionDurationRanges', () => { it('fetches the percentiles', async () => { const logspaceRangesBuckets = [ { 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/correlations/search_strategy.test.ts index 20e505840f0fdd..c93a36bf350450 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts @@ -103,7 +103,7 @@ describe('APM Correlations search strategy', () => { }); }); - describe('search()', () => { + describe('search', () => { let mockClientFieldCaps: jest.Mock; let mockClientSearch: jest.Mock; let mockDeps: SearchStrategyDependencies; diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.test.ts index 8a1f4411a6aa43..63de0a59d4894a 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/aggregation_utils.test.ts @@ -8,7 +8,7 @@ import { computeExpectationsAndRanges } from './aggregation_utils'; describe('aggregation utils', () => { - describe('computeExpectationsAndRanges()', () => { + describe('computeExpectationsAndRanges', () => { it('returns expectations and ranges based on given percentiles #1', async () => { const { expectations, ranges } = computeExpectationsAndRanges([0, 1]); expect(expectations).toEqual([0, 0.5, 1]); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/math_utils.test.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/math_utils.test.ts index a5a6bc99d362e3..ed4107b9d602a1 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/math_utils.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/utils/math_utils.test.ts @@ -8,7 +8,7 @@ import { getRandomInt } from './math_utils'; describe('math utils', () => { - describe('getRandomInt()', () => { + describe('getRandomInt', () => { it('returns a random integer within the given range', () => { const min = 0.9; const max = 11.1; From 44d5cc587def5e57484885dd84ed7e1be629b884 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 5 Jul 2021 14:48:23 +0200 Subject: [PATCH 7/7] restructure tests. --- .../correlations/search_strategy.test.ts | 100 ++++++++++-------- 1 file changed, 55 insertions(+), 45 deletions(-) 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/correlations/search_strategy.test.ts index c93a36bf350450..6d4bfcdde99943 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/search_strategy.test.ts @@ -126,60 +126,70 @@ describe('APM Correlations search strategy', () => { }); describe('async functionality', () => { - it('throws an error if no params are provided', async () => { - const searchStrategy = await apmCorrelationsSearchStrategyProvider(); - expect(() => searchStrategy.search({}, {}, mockDeps)).toThrow( - 'Invalid request parameters.' - ); + describe('when no params are provided', () => { + it('throws an error', async () => { + const searchStrategy = await apmCorrelationsSearchStrategyProvider(); + expect(() => searchStrategy.search({}, {}, mockDeps)).toThrow( + 'Invalid request parameters.' + ); + }); }); - it('performs a client search with params when no ID is provided', async () => { - const searchStrategy = await apmCorrelationsSearchStrategyProvider(); - await searchStrategy.search({ params }, {}, mockDeps).toPromise(); - const [[request]] = mockClientSearch.mock.calls; - - expect(request.index).toEqual('apm-*'); - expect(request.body).toEqual( - expect.objectContaining({ - aggs: { - transaction_duration_percentiles: { - percentiles: { - field: 'transaction.duration.us', - hdr: { number_of_significant_value_digits: 3 }, + describe('when no ID is provided', () => { + it('performs a client search with params', async () => { + const searchStrategy = await apmCorrelationsSearchStrategyProvider(); + await searchStrategy.search({ params }, {}, mockDeps).toPromise(); + const [[request]] = mockClientSearch.mock.calls; + + expect(request.index).toEqual('apm-*'); + expect(request.body).toEqual( + expect.objectContaining({ + aggs: { + transaction_duration_percentiles: { + percentiles: { + field: 'transaction.duration.us', + hdr: { number_of_significant_value_digits: 3 }, + }, }, }, - }, - query: { - bool: { - filter: [{ term: { 'processor.event': 'transaction' } }], + query: { + bool: { + filter: [{ term: { 'processor.event': 'transaction' } }], + }, }, - }, - size: 0, - }) - ); + size: 0, + }) + ); + }); }); - it('retrieves the current request if an id is provided', async () => { - const searchStrategy = await apmCorrelationsSearchStrategyProvider(); - const response = await searchStrategy - .search({ id: 'my-search-id', params }, {}, mockDeps) - .toPromise(); - - expect(response).toEqual( - expect.objectContaining({ id: 'my-search-id' }) - ); + describe('when an ID with params is provided', () => { + it('retrieves the current request', async () => { + const searchStrategy = await apmCorrelationsSearchStrategyProvider(); + const response = await searchStrategy + .search({ id: 'my-search-id', params }, {}, mockDeps) + .toPromise(); + + expect(response).toEqual( + expect.objectContaining({ id: 'my-search-id' }) + ); + }); }); - it('does not emit an error if the client throws', async () => { - mockClientSearch - .mockReset() - .mockRejectedValueOnce(new Error('client error')); - const searchStrategy = await apmCorrelationsSearchStrategyProvider(); - const response = await searchStrategy - .search({ params }, {}, mockDeps) - .toPromise(); - - expect(response).toEqual(expect.objectContaining({ isRunning: true })); + describe('if the client throws', () => { + it('does not emit an error', async () => { + mockClientSearch + .mockReset() + .mockRejectedValueOnce(new Error('client error')); + const searchStrategy = await apmCorrelationsSearchStrategyProvider(); + const response = await searchStrategy + .search({ params }, {}, mockDeps) + .toPromise(); + + expect(response).toEqual( + expect.objectContaining({ isRunning: true }) + ); + }); }); it('triggers the subscription only once', async () => {