diff --git a/x-pack/plugins/apm/public/hooks/useTransactionList.ts b/x-pack/plugins/apm/public/hooks/useTransactionList.ts index f100e3de4fdbf..423d4c69953f2 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionList.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionList.ts @@ -22,14 +22,20 @@ const getRelativeImpact = ( ); function getWithRelativeImpact(items: TransactionListAPIResponse) { - const impacts = items.map(({ impact }) => impact); + const impacts = items + .map(({ impact }) => impact) + .filter(impact => impact !== null) as number[]; + const impactMin = Math.min(...impacts); const impactMax = Math.max(...impacts); return items.map(item => { return { ...item, - impactRelative: getRelativeImpact(item.impact, impactMin, impactMax) + impactRelative: + item.impact !== null + ? getRelativeImpact(item.impact, impactMin, impactMax) + : null }; }); } diff --git a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts index 14849371c4ceb..233bc143a8df8 100644 --- a/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts +++ b/x-pack/plugins/apm/server/lib/errors/distribution/get_buckets.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BucketAgg, ESFilter } from 'elasticsearch'; +import { ESFilter } from 'elasticsearch'; import { ERROR_GROUP_ID, PROCESSOR_EVENT, @@ -61,13 +61,8 @@ export async function getBuckets({ } }; - interface Aggs { - distribution: { - buckets: Array>; - }; - } + const resp = await client.search(params); - const resp = await client.search(params); const buckets = resp.aggregations.distribution.buckets.map(bucket => ({ key: bucket.key, count: bucket.doc_count diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts index a76578f7d07c6..33a93fa986db3 100644 --- a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts +++ b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams } from 'elasticsearch'; import { idx } from '@kbn/elastic-idx'; import { ERROR_CULPRIT, @@ -37,7 +36,10 @@ export async function getErrorGroups({ }) { const { start, end, uiFiltersES, client, config } = setup; - const params: SearchParams = { + // sort buckets by last occurrence of error + const sortByLatestOccurrence = sortField === 'latestOccurrenceAt'; + + const params = { index: config.get('apm_oss.errorIndices'), body: { size: 0, @@ -56,7 +58,11 @@ export async function getErrorGroups({ terms: { field: ERROR_GROUP_ID, size: 500, - order: { _count: sortDirection } + order: sortByLatestOccurrence + ? { + max_timestamp: sortDirection + } + : { _count: sortDirection } }, aggs: { sample: { @@ -72,24 +78,22 @@ export async function getErrorGroups({ sort: [{ '@timestamp': 'desc' }], size: 1 } - } + }, + ...(sortByLatestOccurrence + ? { + max_timestamp: { + max: { + field: '@timestamp' + } + } + } + : {}) } } } } }; - // sort buckets by last occurrence of error - if (sortField === 'latestOccurrenceAt') { - params.body.aggs.error_groups.terms.order = { - max_timestamp: sortDirection - }; - - params.body.aggs.error_groups.aggs.max_timestamp = { - max: { field: '@timestamp' } - }; - } - interface SampleError { '@timestamp': APMError['@timestamp']; error: { @@ -105,44 +109,27 @@ export async function getErrorGroups({ }; } - interface Bucket { - key: string; - doc_count: number; - sample: { - hits: { - total: number; - max_score: number | null; - hits: Array<{ - _source: SampleError; - }>; - }; - }; - } - - interface Aggs { - error_groups: { - buckets: Bucket[]; - }; - } + const resp = await client.search(params); - const resp = await client.search(params); - const buckets = idx(resp, _ => _.aggregations.error_groups.buckets) || []; + // aggregations can be undefined when no matching indices are found. + // this is an exception rather than the rule so the ES type does not account for this. + const hits = (idx(resp, _ => _.aggregations.error_groups.buckets) || []).map( + bucket => { + const source = bucket.sample.hits.hits[0]._source as SampleError; + const message = + idx(source, _ => _.error.log.message) || + idx(source, _ => _.error.exception[0].message); - const hits = buckets.map(bucket => { - const source = bucket.sample.hits.hits[0]._source; - const message = - idx(source, _ => _.error.log.message) || - idx(source, _ => _.error.exception[0].message); - - return { - message, - occurrenceCount: bucket.doc_count, - culprit: idx(source, _ => _.error.culprit), - groupId: idx(source, _ => _.error.grouping_key), - latestOccurrenceAt: source['@timestamp'], - handled: idx(source, _ => _.error.exception[0].handled) - }; - }); + return { + message, + occurrenceCount: bucket.doc_count, + culprit: idx(source, _ => _.error.culprit), + groupId: idx(source, _ => _.error.grouping_key), + latestOccurrenceAt: source['@timestamp'], + handled: idx(source, _ => _.error.exception[0].handled) + }; + } + ); return hits; } diff --git a/x-pack/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts b/x-pack/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts index a40477deaea43..25b74cafae69e 100644 --- a/x-pack/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts +++ b/x-pack/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams } from 'elasticsearch'; import { PROCESSOR_EVENT, TRACE_ID, @@ -17,25 +16,14 @@ export interface ErrorsPerTransaction { [transactionId: string]: number; } -interface TraceErrorsAggBucket { - key: string; - doc_count: number; -} - -interface TraceErrorsAggResponse { - transactions: { - buckets: TraceErrorsAggBucket[]; - }; -} - export async function getTraceErrorsPerTransaction( traceId: string, setup: Setup ): Promise { const { start, end, client, config } = setup; - const params: SearchParams = { - index: [config.get('apm_oss.errorIndices')], + const params = { + index: config.get('apm_oss.errorIndices'), body: { size: 0, query: { @@ -57,7 +45,7 @@ export async function getTraceErrorsPerTransaction( } }; - const resp = await client.search(params); + const resp = await client.search(params); return resp.aggregations.transactions.buckets.reduce( (acc, bucket) => ({ diff --git a/x-pack/plugins/apm/server/lib/helpers/es_client.ts b/x-pack/plugins/apm/server/lib/helpers/es_client.ts index 208e33893e589..420c7087c8032 100644 --- a/x-pack/plugins/apm/server/lib/helpers/es_client.ts +++ b/x-pack/plugins/apm/server/lib/helpers/es_client.ts @@ -90,10 +90,10 @@ export function getESClient(req: Legacy.Request) { const query = (req.query as unknown) as APMRequestQuery; return { - search: async ( - params: SearchParams, + search: async ( + params: U, apmOptions?: APMOptions - ): Promise> => { + ): Promise> => { const nextParams = await getParamsForSearchRequest( req, params, @@ -112,7 +112,7 @@ export function getESClient(req: Legacy.Request) { } return cluster.callWithRequest(req, 'search', nextParams) as Promise< - AggregationSearchResponse + AggregationSearchResponse >; }, index: (params: IndexDocumentParams) => { diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/fetcher.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/fetcher.ts deleted file mode 100644 index dd2826e4ea9c5..0000000000000 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/fetcher.ts +++ /dev/null @@ -1,64 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { - SERVICE_AGENT_NAME, - PROCESSOR_EVENT, - SERVICE_NAME, - METRIC_JAVA_HEAP_MEMORY_MAX, - METRIC_JAVA_HEAP_MEMORY_COMMITTED, - METRIC_JAVA_HEAP_MEMORY_USED -} from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../helpers/setup_request'; -import { MetricsAggs, MetricSeriesKeys, AggValue } from '../../../types'; -import { getMetricsDateHistogramParams } from '../../../../helpers/metrics'; -import { rangeFilter } from '../../../../helpers/range_filter'; - -export interface HeapMemoryMetrics extends MetricSeriesKeys { - heapMemoryMax: AggValue; - heapMemoryCommitted: AggValue; - heapMemoryUsed: AggValue; -} - -export async function fetch(setup: Setup, serviceName: string) { - const { start, end, uiFiltersES, client, config } = setup; - - const aggs = { - heapMemoryMax: { avg: { field: METRIC_JAVA_HEAP_MEMORY_MAX } }, - heapMemoryCommitted: { - avg: { field: METRIC_JAVA_HEAP_MEMORY_COMMITTED } - }, - heapMemoryUsed: { avg: { field: METRIC_JAVA_HEAP_MEMORY_USED } } - }; - - const params = { - index: config.get('apm_oss.metricsIndices'), - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [PROCESSOR_EVENT]: 'metric' } }, - { term: { [SERVICE_AGENT_NAME]: 'java' } }, - { - range: rangeFilter(start, end) - }, - ...uiFiltersES - ] - } - }, - aggs: { - timeseriesData: { - date_histogram: getMetricsDateHistogramParams(start, end), - aggs - }, - ...aggs - } - } - }; - - return client.search>(params); -} diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts index 50c3d05f76105..48c7ee29a16a3 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts @@ -6,49 +6,62 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; +import { + METRIC_JAVA_HEAP_MEMORY_MAX, + METRIC_JAVA_HEAP_MEMORY_COMMITTED, + METRIC_JAVA_HEAP_MEMORY_USED, + SERVICE_AGENT_NAME +} from '../../../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../../../helpers/setup_request'; -import { fetch, HeapMemoryMetrics } from './fetcher'; +import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; import { ChartBase } from '../../../types'; -import { transformDataToMetricsChart } from '../../../transform_metrics_chart'; -// TODO: i18n for titles +const series = { + heapMemoryUsed: { + title: i18n.translate('xpack.apm.agentMetrics.java.heapMemorySeriesUsed', { + defaultMessage: 'Avg. used' + }), + color: theme.euiColorVis0 + }, + heapMemoryCommitted: { + title: i18n.translate( + 'xpack.apm.agentMetrics.java.heapMemorySeriesCommitted', + { + defaultMessage: 'Avg. committed' + } + ), + color: theme.euiColorVis1 + }, + heapMemoryMax: { + title: i18n.translate('xpack.apm.agentMetrics.java.heapMemorySeriesMax', { + defaultMessage: 'Avg. limit' + }), + color: theme.euiColorVis2 + } +}; -const chartBase: ChartBase = { +const chartBase: ChartBase = { title: i18n.translate('xpack.apm.agentMetrics.java.heapMemoryChartTitle', { defaultMessage: 'Heap Memory' }), key: 'heap_memory_area_chart', type: 'area', yUnit: 'bytes', - series: { - heapMemoryUsed: { - title: i18n.translate( - 'xpack.apm.agentMetrics.java.heapMemorySeriesUsed', - { - defaultMessage: 'Avg. used' - } - ), - color: theme.euiColorVis0 - }, - heapMemoryCommitted: { - title: i18n.translate( - 'xpack.apm.agentMetrics.java.heapMemorySeriesCommitted', - { - defaultMessage: 'Avg. committed' - } - ), - color: theme.euiColorVis1 - }, - heapMemoryMax: { - title: i18n.translate('xpack.apm.agentMetrics.java.heapMemorySeriesMax', { - defaultMessage: 'Avg. limit' - }), - color: theme.euiColorVis2 - } - } + series }; export async function getHeapMemoryChart(setup: Setup, serviceName: string) { - const result = await fetch(setup, serviceName); - return transformDataToMetricsChart(result, chartBase); + return fetchAndTransformMetrics({ + setup, + serviceName, + chartBase, + aggs: { + heapMemoryMax: { avg: { field: METRIC_JAVA_HEAP_MEMORY_MAX } }, + heapMemoryCommitted: { + avg: { field: METRIC_JAVA_HEAP_MEMORY_COMMITTED } + }, + heapMemoryUsed: { avg: { field: METRIC_JAVA_HEAP_MEMORY_USED } } + }, + additionalFilters: [{ term: { [SERVICE_AGENT_NAME]: 'java' } }] + }); } diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/fetcher.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/fetcher.ts deleted file mode 100644 index 84ee3a3864734..0000000000000 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/fetcher.ts +++ /dev/null @@ -1,65 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { - SERVICE_AGENT_NAME, - PROCESSOR_EVENT, - SERVICE_NAME, - METRIC_JAVA_NON_HEAP_MEMORY_MAX, - METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED, - METRIC_JAVA_NON_HEAP_MEMORY_USED -} from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../helpers/setup_request'; -import { MetricsAggs, MetricSeriesKeys, AggValue } from '../../../types'; -import { getMetricsDateHistogramParams } from '../../../../helpers/metrics'; -import { rangeFilter } from '../../../../helpers/range_filter'; - -export interface NonHeapMemoryMetrics extends MetricSeriesKeys { - nonHeapMemoryCommitted: AggValue; - nonHeapMemoryUsed: AggValue; -} - -export async function fetch(setup: Setup, serviceName: string) { - const { start, end, uiFiltersES, client, config } = setup; - - const aggs = { - nonHeapMemoryMax: { avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_MAX } }, - nonHeapMemoryCommitted: { - avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED } - }, - nonHeapMemoryUsed: { - avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_USED } - } - }; - - const params = { - index: config.get('apm_oss.metricsIndices'), - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [PROCESSOR_EVENT]: 'metric' } }, - { term: { [SERVICE_AGENT_NAME]: 'java' } }, - { - range: rangeFilter(start, end) - }, - ...uiFiltersES - ] - } - }, - aggs: { - timeseriesData: { - date_histogram: getMetricsDateHistogramParams(start, end), - aggs - }, - ...aggs - } - } - }; - - return client.search>(params); -} diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts index 2490449c3cc7c..446d9258b4310 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts @@ -6,41 +6,61 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; +import { + METRIC_JAVA_NON_HEAP_MEMORY_MAX, + METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED, + METRIC_JAVA_NON_HEAP_MEMORY_USED, + SERVICE_AGENT_NAME +} from '../../../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../../../helpers/setup_request'; -import { fetch, NonHeapMemoryMetrics } from './fetcher'; import { ChartBase } from '../../../types'; -import { transformDataToMetricsChart } from '../../../transform_metrics_chart'; +import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; -const chartBase: ChartBase = { +const series = { + nonHeapMemoryUsed: { + title: i18n.translate( + 'xpack.apm.agentMetrics.java.nonHeapMemorySeriesUsed', + { + defaultMessage: 'Avg. used' + } + ), + color: theme.euiColorVis0 + }, + nonHeapMemoryCommitted: { + title: i18n.translate( + 'xpack.apm.agentMetrics.java.nonHeapMemorySeriesCommitted', + { + defaultMessage: 'Avg. committed' + } + ), + color: theme.euiColorVis1 + } +}; + +const chartBase: ChartBase = { title: i18n.translate('xpack.apm.agentMetrics.java.nonHeapMemoryChartTitle', { defaultMessage: 'Non-Heap Memory' }), key: 'non_heap_memory_area_chart', type: 'area', yUnit: 'bytes', - series: { - nonHeapMemoryUsed: { - title: i18n.translate( - 'xpack.apm.agentMetrics.java.nonHeapMemorySeriesUsed', - { - defaultMessage: 'Avg. used' - } - ), - color: theme.euiColorVis0 - }, - nonHeapMemoryCommitted: { - title: i18n.translate( - 'xpack.apm.agentMetrics.java.nonHeapMemorySeriesCommitted', - { - defaultMessage: 'Avg. committed' - } - ), - color: theme.euiColorVis1 - } - } + series }; export async function getNonHeapMemoryChart(setup: Setup, serviceName: string) { - const result = await fetch(setup, serviceName); - return transformDataToMetricsChart(result, chartBase); + return fetchAndTransformMetrics({ + setup, + serviceName, + chartBase, + aggs: { + nonHeapMemoryMax: { avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_MAX } }, + nonHeapMemoryCommitted: { + avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED } + }, + nonHeapMemoryUsed: { + avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_USED } + } + }, + additionalFilters: [{ term: { [SERVICE_AGENT_NAME]: 'java' } }] + }); } diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/fetcher.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/fetcher.ts deleted file mode 100644 index a74208cc8218c..0000000000000 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/fetcher.ts +++ /dev/null @@ -1,55 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import { - SERVICE_AGENT_NAME, - PROCESSOR_EVENT, - SERVICE_NAME, - METRIC_JAVA_THREAD_COUNT -} from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../helpers/setup_request'; -import { MetricsAggs, MetricSeriesKeys, AggValue } from '../../../types'; -import { getMetricsDateHistogramParams } from '../../../../helpers/metrics'; -import { rangeFilter } from '../../../../helpers/range_filter'; - -export interface ThreadCountMetrics extends MetricSeriesKeys { - threadCount: AggValue; -} - -export async function fetch(setup: Setup, serviceName: string) { - const { start, end, uiFiltersES, client, config } = setup; - - const aggs = { - threadCount: { avg: { field: METRIC_JAVA_THREAD_COUNT } }, - threadCountMax: { max: { field: METRIC_JAVA_THREAD_COUNT } } - }; - - const params = { - index: config.get('apm_oss.metricsIndices'), - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [PROCESSOR_EVENT]: 'metric' } }, - { term: { [SERVICE_AGENT_NAME]: 'java' } }, - { range: rangeFilter(start, end) }, - ...uiFiltersES - ] - } - }, - aggs: { - timeseriesData: { - date_histogram: getMetricsDateHistogramParams(start, end), - aggs - }, - ...aggs - } - } - }; - - return client.search>(params); -} diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts index db6158ff99487..5f53cde44d622 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts @@ -6,35 +6,48 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; +import { + METRIC_JAVA_THREAD_COUNT, + SERVICE_AGENT_NAME +} from '../../../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../../../helpers/setup_request'; -import { fetch, ThreadCountMetrics } from './fetcher'; import { ChartBase } from '../../../types'; -import { transformDataToMetricsChart } from '../../../transform_metrics_chart'; +import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; -const chartBase: ChartBase = { +const series = { + threadCount: { + title: i18n.translate('xpack.apm.agentMetrics.java.threadCount', { + defaultMessage: 'Avg. count' + }), + color: theme.euiColorVis0 + }, + threadCountMax: { + title: i18n.translate('xpack.apm.agentMetrics.java.threadCountMax', { + defaultMessage: 'Max count' + }), + color: theme.euiColorVis1 + } +}; + +const chartBase: ChartBase = { title: i18n.translate('xpack.apm.agentMetrics.java.threadCountChartTitle', { defaultMessage: 'Thread Count' }), key: 'thread_count_line_chart', type: 'linemark', yUnit: 'number', - series: { - threadCount: { - title: i18n.translate('xpack.apm.agentMetrics.java.threadCount', { - defaultMessage: 'Avg. count' - }), - color: theme.euiColorVis0 - }, - threadCountMax: { - title: i18n.translate('xpack.apm.agentMetrics.java.threadCountMax', { - defaultMessage: 'Max count' - }), - color: theme.euiColorVis1 - } - } + series }; export async function getThreadCountChart(setup: Setup, serviceName: string) { - const result = await fetch(setup, serviceName); - return transformDataToMetricsChart(result, chartBase); + return fetchAndTransformMetrics({ + setup, + serviceName, + chartBase, + aggs: { + threadCount: { avg: { field: METRIC_JAVA_THREAD_COUNT } }, + threadCountMax: { max: { field: METRIC_JAVA_THREAD_COUNT } } + }, + additionalFilters: [{ term: { [SERVICE_AGENT_NAME]: 'java' } }] + }); } diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/fetcher.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/fetcher.ts deleted file mode 100644 index 0da5859666a51..0000000000000 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/fetcher.ts +++ /dev/null @@ -1,62 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - METRIC_PROCESS_CPU_PERCENT, - METRIC_SYSTEM_CPU_PERCENT, - PROCESSOR_EVENT, - SERVICE_NAME -} from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../helpers/setup_request'; -import { MetricsAggs, MetricSeriesKeys, AggValue } from '../../../types'; -import { getMetricsDateHistogramParams } from '../../../../helpers/metrics'; -import { rangeFilter } from '../../../../helpers/range_filter'; - -export interface CPUMetrics extends MetricSeriesKeys { - systemCPUAverage: AggValue; - systemCPUMax: AggValue; - processCPUAverage: AggValue; - processCPUMax: AggValue; -} - -export async function fetch(setup: Setup, serviceName: string) { - const { start, end, uiFiltersES, client, config } = setup; - - const aggs = { - systemCPUAverage: { avg: { field: METRIC_SYSTEM_CPU_PERCENT } }, - systemCPUMax: { max: { field: METRIC_SYSTEM_CPU_PERCENT } }, - processCPUAverage: { avg: { field: METRIC_PROCESS_CPU_PERCENT } }, - processCPUMax: { max: { field: METRIC_PROCESS_CPU_PERCENT } } - }; - - const params = { - index: config.get('apm_oss.metricsIndices'), - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [PROCESSOR_EVENT]: 'metric' } }, - { - range: rangeFilter(start, end) - }, - ...uiFiltersES - ] - } - }, - aggs: { - timeseriesData: { - date_histogram: getMetricsDateHistogramParams(start, end), - aggs - }, - ...aggs - } - } - }; - - return client.search>(params); -} diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts index f7fe92d578e93..67f69456e9e1b 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts @@ -6,47 +6,63 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; +import { + METRIC_SYSTEM_CPU_PERCENT, + METRIC_PROCESS_CPU_PERCENT +} from '../../../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../../../helpers/setup_request'; -import { fetch, CPUMetrics } from './fetcher'; import { ChartBase } from '../../../types'; -import { transformDataToMetricsChart } from '../../../transform_metrics_chart'; +import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; -const chartBase: ChartBase = { +const series = { + systemCPUMax: { + title: i18n.translate('xpack.apm.chart.cpuSeries.systemMaxLabel', { + defaultMessage: 'System max' + }), + color: theme.euiColorVis1 + }, + systemCPUAverage: { + title: i18n.translate('xpack.apm.chart.cpuSeries.systemAverageLabel', { + defaultMessage: 'System average' + }), + color: theme.euiColorVis0 + }, + processCPUMax: { + title: i18n.translate('xpack.apm.chart.cpuSeries.processMaxLabel', { + defaultMessage: 'Process max' + }), + color: theme.euiColorVis7 + }, + processCPUAverage: { + title: i18n.translate('xpack.apm.chart.cpuSeries.processAverageLabel', { + defaultMessage: 'Process average' + }), + color: theme.euiColorVis5 + } +}; + +const chartBase: ChartBase = { title: i18n.translate('xpack.apm.serviceDetails.metrics.cpuUsageChartTitle', { defaultMessage: 'CPU usage' }), key: 'cpu_usage_chart', type: 'linemark', yUnit: 'percent', - series: { - systemCPUMax: { - title: i18n.translate('xpack.apm.chart.cpuSeries.systemMaxLabel', { - defaultMessage: 'System max' - }), - color: theme.euiColorVis1 - }, - systemCPUAverage: { - title: i18n.translate('xpack.apm.chart.cpuSeries.systemAverageLabel', { - defaultMessage: 'System average' - }), - color: theme.euiColorVis0 - }, - processCPUMax: { - title: i18n.translate('xpack.apm.chart.cpuSeries.processMaxLabel', { - defaultMessage: 'Process max' - }), - color: theme.euiColorVis7 - }, - processCPUAverage: { - title: i18n.translate('xpack.apm.chart.cpuSeries.processAverageLabel', { - defaultMessage: 'Process average' - }), - color: theme.euiColorVis5 - } - } + series }; export async function getCPUChartData(setup: Setup, serviceName: string) { - const result = await fetch(setup, serviceName); - return transformDataToMetricsChart(result, chartBase); + const metricsChart = await fetchAndTransformMetrics({ + setup, + serviceName, + chartBase, + aggs: { + systemCPUAverage: { avg: { field: METRIC_SYSTEM_CPU_PERCENT } }, + systemCPUMax: { max: { field: METRIC_SYSTEM_CPU_PERCENT } }, + processCPUAverage: { avg: { field: METRIC_PROCESS_CPU_PERCENT } }, + processCPUMax: { max: { field: METRIC_PROCESS_CPU_PERCENT } } + } + }); + + return metricsChart; } diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/fetcher.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/fetcher.ts deleted file mode 100644 index 96b3160600111..0000000000000 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/fetcher.ts +++ /dev/null @@ -1,73 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - PROCESSOR_EVENT, - SERVICE_NAME, - METRIC_SYSTEM_FREE_MEMORY, - METRIC_SYSTEM_TOTAL_MEMORY -} from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../helpers/setup_request'; -import { MetricsAggs, MetricSeriesKeys, AggValue } from '../../../types'; -import { getMetricsDateHistogramParams } from '../../../../helpers/metrics'; -import { rangeFilter } from '../../../../helpers/range_filter'; - -export interface MemoryMetrics extends MetricSeriesKeys { - memoryUsedAvg: AggValue; - memoryUsedMax: AggValue; -} - -const percentUsedScript = { - lang: 'expression', - source: `1 - doc['${METRIC_SYSTEM_FREE_MEMORY}'] / doc['${METRIC_SYSTEM_TOTAL_MEMORY}']` -}; - -export async function fetch(setup: Setup, serviceName: string) { - const { start, end, uiFiltersES, client, config } = setup; - - const aggs = { - memoryUsedAvg: { avg: { script: percentUsedScript } }, - memoryUsedMax: { max: { script: percentUsedScript } } - }; - - const params = { - index: config.get('apm_oss.metricsIndices'), - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [PROCESSOR_EVENT]: 'metric' } }, - { - range: rangeFilter(start, end) - }, - { - exists: { - field: METRIC_SYSTEM_FREE_MEMORY - } - }, - { - exists: { - field: METRIC_SYSTEM_TOTAL_MEMORY - } - }, - ...uiFiltersES - ] - } - }, - aggs: { - timeseriesData: { - date_histogram: getMetricsDateHistogramParams(start, end), - aggs - }, - ...aggs - } - } - }; - - return client.search>(params); -} diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts index fe9637ab34e69..e372a62a7ce05 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts @@ -5,12 +5,28 @@ */ import { i18n } from '@kbn/i18n'; +import { + METRIC_SYSTEM_FREE_MEMORY, + METRIC_SYSTEM_TOTAL_MEMORY +} from '../../../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../../../helpers/setup_request'; -import { fetch, MemoryMetrics } from './fetcher'; import { ChartBase } from '../../../types'; -import { transformDataToMetricsChart } from '../../../transform_metrics_chart'; +import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; -const chartBase: ChartBase = { +const series = { + memoryUsedMax: { + title: i18n.translate('xpack.apm.chart.memorySeries.systemMaxLabel', { + defaultMessage: 'Max' + }) + }, + memoryUsedAvg: { + title: i18n.translate('xpack.apm.chart.memorySeries.systemAverageLabel', { + defaultMessage: 'Average' + }) + } +}; + +const chartBase: ChartBase = { title: i18n.translate( 'xpack.apm.serviceDetails.metrics.memoryUsageChartTitle', { @@ -20,21 +36,34 @@ const chartBase: ChartBase = { key: 'memory_usage_chart', type: 'linemark', yUnit: 'percent', - series: { - memoryUsedMax: { - title: i18n.translate('xpack.apm.chart.memorySeries.systemMaxLabel', { - defaultMessage: 'Max' - }) - }, - memoryUsedAvg: { - title: i18n.translate('xpack.apm.chart.memorySeries.systemAverageLabel', { - defaultMessage: 'Average' - }) - } - } + series +}; + +const percentUsedScript = { + lang: 'expression', + source: `1 - doc['${METRIC_SYSTEM_FREE_MEMORY}'] / doc['${METRIC_SYSTEM_TOTAL_MEMORY}']` }; export async function getMemoryChartData(setup: Setup, serviceName: string) { - const result = await fetch(setup, serviceName); - return transformDataToMetricsChart(result, chartBase); + return fetchAndTransformMetrics({ + setup, + serviceName, + chartBase, + aggs: { + memoryUsedAvg: { avg: { script: percentUsedScript } }, + memoryUsedMax: { max: { script: percentUsedScript } } + }, + additionalFilters: [ + { + exists: { + field: METRIC_SYSTEM_FREE_MEMORY + } + }, + { + exists: { + field: METRIC_SYSTEM_TOTAL_MEMORY + } + } + ] + }); } diff --git a/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts new file mode 100644 index 0000000000000..eefa1e0ef201a --- /dev/null +++ b/x-pack/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + PROCESSOR_EVENT, + SERVICE_NAME +} from '../../../common/elasticsearch_fieldnames'; +import { Setup } from '../helpers/setup_request'; +import { getMetricsDateHistogramParams } from '../helpers/metrics'; +import { rangeFilter } from '../helpers/range_filter'; +import { ChartBase } from './types'; +import { transformDataToMetricsChart } from './transform_metrics_chart'; + +interface Aggs { + [key: string]: { + min?: any; + max?: any; + sum?: any; + avg?: any; + }; +} + +interface Filter { + exists?: { + field: string; + }; + term?: { + [key: string]: string; + }; +} + +export async function fetchAndTransformMetrics({ + setup, + serviceName, + chartBase, + aggs, + additionalFilters = [] +}: { + setup: Setup; + serviceName: string; + chartBase: ChartBase; + aggs: T; + additionalFilters?: Filter[]; +}) { + const { start, end, uiFiltersES, client, config } = setup; + + const params = { + index: config.get('apm_oss.metricsIndices'), + body: { + size: 0, + query: { + bool: { + filter: [ + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [PROCESSOR_EVENT]: 'metric' } }, + { + range: rangeFilter(start, end) + }, + ...additionalFilters, + ...uiFiltersES + ] + } + }, + aggs: { + timeseriesData: { + date_histogram: getMetricsDateHistogramParams(start, end), + aggs + }, + ...aggs + } + } + }; + + const response = await client.search(params); + + return transformDataToMetricsChart(response, chartBase); +} diff --git a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.test.ts b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.test.ts index e6fff34b37bc4..e077105e9b2e5 100644 --- a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.test.ts +++ b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.test.ts @@ -3,22 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { AggregationSearchResponse } from 'elasticsearch'; -import { MetricsAggs, MetricSeriesKeys, AggValue } from './types'; import { transformDataToMetricsChart } from './transform_metrics_chart'; import { ChartType, YUnit } from '../../../typings/timeseries'; test('transformDataToMetricsChart should transform an ES result into a chart object', () => { - interface TestKeys extends MetricSeriesKeys { - a: AggValue; - b: AggValue; - c: AggValue; - } - - type R = AggregationSearchResponse>; - const response = { - hits: { total: 5000 } as R['hits'], + hits: { total: 5000 }, aggregations: { a: { value: 1000 }, b: { value: 1000 }, @@ -29,24 +19,27 @@ test('transformDataToMetricsChart should transform an ES result into a chart obj a: { value: 10 }, b: { value: 10 }, c: { value: 10 }, - key: 1 - } as R['aggregations']['timeseriesData']['buckets'][0], + key: 1, + doc_count: 0 + }, { a: { value: 20 }, b: { value: 20 }, c: { value: 20 }, - key: 2 - } as R['aggregations']['timeseriesData']['buckets'][0], + key: 2, + doc_count: 0 + }, { a: { value: 30 }, b: { value: 30 }, c: { value: 30 }, - key: 3 - } as R['aggregations']['timeseriesData']['buckets'][0] + key: 3, + doc_count: 0 + } ] } - } as R['aggregations'] - } as R; + } + } as any; const chartBase = { title: 'Test Chart Title', diff --git a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts index 9936b6883a1c7..1acac008c8bf8 100644 --- a/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/transform_metrics_chart.ts @@ -3,9 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { AggregationSearchResponse } from 'elasticsearch'; import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { ChartBase, MetricsAggs, MetricSeriesKeys } from './types'; +import { AggregationSearchResponse, AggregatedValue } from 'elasticsearch'; +import { ChartBase } from './types'; const colors = [ theme.euiColorVis0, @@ -20,9 +20,33 @@ const colors = [ export type GenericMetricsChart = ReturnType< typeof transformDataToMetricsChart >; -export function transformDataToMetricsChart( - result: AggregationSearchResponse>, - chartBase: ChartBase + +interface AggregatedParams { + body: { + aggs: { + timeseriesData: { + date_histogram: any; + aggs: { + min?: any; + max?: any; + sum?: any; + avg?: any; + }; + }; + } & { + [key: string]: { + min?: any; + max?: any; + sum?: any; + avg?: any; + }; + }; + }; +} + +export function transformDataToMetricsChart( + result: AggregationSearchResponse, + chartBase: ChartBase ) { const { aggregations, hits } = result; const { timeseriesData } = aggregations; @@ -32,20 +56,24 @@ export function transformDataToMetricsChart( key: chartBase.key, yUnit: chartBase.yUnit, totalHits: hits.total, - series: Object.keys(chartBase.series).map((seriesKey, i) => ({ - title: chartBase.series[seriesKey].title, - key: seriesKey, - type: chartBase.type, - color: chartBase.series[seriesKey].color || colors[i], - overallValue: aggregations[seriesKey].value, - data: timeseriesData.buckets.map(bucket => { - const { value } = bucket[seriesKey]; - const y = value === null || isNaN(value) ? null : value; - return { - x: bucket.key, - y - }; - }) - })) + series: Object.keys(chartBase.series).map((seriesKey, i) => { + const agg = aggregations[seriesKey]; + + return { + title: chartBase.series[seriesKey].title, + key: seriesKey, + type: chartBase.type, + color: chartBase.series[seriesKey].color || colors[i], + overallValue: agg.value, + data: timeseriesData.buckets.map(bucket => { + const { value } = bucket[seriesKey] as AggregatedValue; + const y = value === null || isNaN(value) ? null : value; + return { + x: bucket.key, + y + }; + }) + }; + }) }; } diff --git a/x-pack/plugins/apm/server/lib/metrics/types.ts b/x-pack/plugins/apm/server/lib/metrics/types.ts index f234b44733444..622b37162d9dc 100644 --- a/x-pack/plugins/apm/server/lib/metrics/types.ts +++ b/x-pack/plugins/apm/server/lib/metrics/types.ts @@ -3,38 +3,17 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { ChartType, YUnit } from '../../../typings/timeseries'; -export interface AggValue { - value: number | null; -} - -export interface MetricSeriesKeys { - [key: string]: AggValue; -} - -export interface ChartBase { +export interface ChartBase { title: string; key: string; type: ChartType; yUnit: YUnit; series: { - [key in keyof T]: { + [key: string]: { title: string; color?: string; - } + }; }; } - -export type MetricsAggs = { - timeseriesData: { - buckets: Array< - { - key_as_string: string; // timestamp as string - key: number; // timestamp as epoch milliseconds - doc_count: number; - } & T - >; - }; -} & T; diff --git a/x-pack/plugins/apm/server/lib/services/get_service.ts b/x-pack/plugins/apm/server/lib/services/get_service.ts index 7cbb1825ef7eb..d39397fedd154 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service.ts @@ -3,8 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { BucketAgg } from 'elasticsearch'; import { idx } from '@kbn/elastic-idx'; import { PROCESSOR_EVENT, @@ -48,19 +46,11 @@ export async function getService(serviceName: string, setup: Setup) { } }; - interface Aggs { - types: { - buckets: BucketAgg[]; - }; - agents: { - buckets: BucketAgg[]; - }; - } - - const { aggregations } = await client.search(params); + const { aggregations } = await client.search(params); const buckets = idx(aggregations, _ => _.types.buckets) || []; const types = buckets.map(bucket => bucket.key); - const agentName = idx(aggregations, _ => _.agents.buckets[0].key); + const agentName = idx(aggregations, _ => _.agents.buckets[0].key) || ''; + return { serviceName, types, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts index 35747a88bb806..75410b70e0139 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BucketAgg } from 'elasticsearch'; import { idx } from '@kbn/elastic-idx'; import { PROCESSOR_EVENT, @@ -65,33 +64,14 @@ export async function getServicesItems(setup: Setup) { } }; - interface ServiceBucket extends BucketAgg { - avg: { - value: number; - }; - agents: { - buckets: BucketAgg[]; - }; - events: { - buckets: BucketAgg[]; - }; - environments: { - buckets: BucketAgg[]; - }; - } - - interface Aggs extends BucketAgg { - services: { - buckets: ServiceBucket[]; - }; - } - - const resp = await client.search(params); + const resp = await client.search(params); const aggs = resp.aggregations; + const serviceBuckets = idx(aggs, _ => _.services.buckets) || []; const items = serviceBuckets.map(bucket => { const eventTypes = bucket.events.buckets; + const transactions = eventTypes.find(e => e.key === 'transaction'); const totalTransactions = idx(transactions, _ => _.doc_count) || 0; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts index 13c3c3bfb2539..b909d5ff62a74 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -4,42 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams } from 'elasticsearch'; import { TRANSACTION_DURATION, TRANSACTION_NAME } from '../../../common/elasticsearch_fieldnames'; import { PromiseReturnType, StringMap } from '../../../typings/common'; -import { Transaction } from '../../../typings/es_schemas/ui/Transaction'; import { Setup } from '../helpers/setup_request'; -interface Bucket { - key: string; - doc_count: number; - avg: { value: number }; - p95: { values: { '95.0': number } }; - sum: { value: number }; - sample: { - hits: { - total: number; - max_score: number | null; - hits: Array<{ - _source: Transaction; - }>; - }; - }; -} - -interface Aggs { - transactions: { - buckets: Bucket[]; - }; -} - export type ESResponse = PromiseReturnType; export function transactionGroupsFetcher(setup: Setup, bodyQuery: StringMap) { const { client, config } = setup; - const params: SearchParams = { + const params = { index: config.get('apm_oss.transactionIndices'), body: { size: 0, @@ -72,5 +47,5 @@ export function transactionGroupsFetcher(setup: Setup, bodyQuery: StringMap) { } }; - return client.search(params); + return client.search(params); } diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts b/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts index dbef10672c988..62d212d07d2a7 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts @@ -6,16 +6,23 @@ import moment from 'moment'; import { idx } from '@kbn/elastic-idx'; +import { Transaction } from '../../../typings/es_schemas/ui/Transaction'; import { ESResponse } from './fetcher'; function calculateRelativeImpacts(transactionGroups: ITransactionGroup[]) { - const values = transactionGroups.map(({ impact }) => impact); + const values = transactionGroups + .map(({ impact }) => impact) + .filter(value => value !== null) as number[]; + const max = Math.max(...values); const min = Math.min(...values); return transactionGroups.map(bucket => ({ ...bucket, - impact: ((bucket.impact - min) / (max - min)) * 100 || 0 + impact: + bucket.impact !== null + ? ((bucket.impact - min) / (max - min)) * 100 || 0 + : 0 })); } @@ -27,7 +34,7 @@ function getTransactionGroup( const averageResponseTime = bucket.avg.value; const transactionsPerMinute = bucket.doc_count / minutes; const impact = bucket.sum.value; - const sample = bucket.sample.hits.hits[0]._source; + const sample = bucket.sample.hits.hits[0]._source as Transaction; return { name: bucket.key, diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts index 6fcf50b148318..b8af24b840d99 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts @@ -8,28 +8,11 @@ import { getMlIndex } from '../../../../../common/ml_job_constants'; import { PromiseReturnType } from '../../../../../typings/common'; import { Setup } from '../../../helpers/setup_request'; -export interface ESBucket { - key_as_string: string; // timestamp as string - key: number; // timestamp - doc_count: number; - anomaly_score: { - value: number | null; - }; - lower: { - value: number | null; - }; - upper: { - value: number | null; - }; -} - -interface Aggs { - ml_avg_response_times: { - buckets: ESBucket[]; - }; -} +export type ESResponse = Exclude< + PromiseReturnType, + undefined +>; -export type ESResponse = PromiseReturnType; export async function anomalySeriesFetcher({ serviceName, transactionType, @@ -91,7 +74,8 @@ export async function anomalySeriesFetcher({ }; try { - return await client.search(params); + const response = await client.search(params); + return response; } catch (err) { const isHttpError = 'statusCode' in err; if (isHttpError) { diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts index f3227140c692b..7b5b77e2a2ddc 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts @@ -55,10 +55,12 @@ export async function getAnomalySeries({ setup }); - return anomalySeriesTransform( - esResponse, - mlBucketSize, - bucketSize, - timeSeriesDates - ); + return esResponse + ? anomalySeriesTransform( + esResponse, + mlBucketSize, + bucketSize, + timeSeriesDates + ) + : undefined; } diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock-responses/mlAnomalyResponse.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock-responses/mlAnomalyResponse.ts index eaea107e5ef2d..c04cf95526d4b 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock-responses/mlAnomalyResponse.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock-responses/mlAnomalyResponse.ts @@ -6,7 +6,7 @@ import { ESResponse } from '../fetcher'; -export const mlAnomalyResponse: ESResponse = { +export const mlAnomalyResponse: ESResponse = ({ took: 3, timed_out: false, _shards: { @@ -124,4 +124,4 @@ export const mlAnomalyResponse: ESResponse = { ] } } -}; +} as unknown) as ESResponse; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts index bbcab297f3a93..eab68a2bda974 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts @@ -5,7 +5,7 @@ */ import { idx } from '@kbn/elastic-idx'; -import { ESBucket, ESResponse } from './fetcher'; +import { ESResponse } from './fetcher'; import { mlAnomalyResponse } from './mock-responses/mlAnomalyResponse'; import { anomalySeriesTransform, replaceFirstAndLastBucket } from './transform'; @@ -46,7 +46,7 @@ describe('anomalySeriesTransform', () => { key: 20000, anomaly_score: { value: 90 } } - ] as ESBucket[]); + ]); const getMlBucketSize = 5; const bucketSize = 5; @@ -72,7 +72,7 @@ describe('anomalySeriesTransform', () => { key: 5000, anomaly_score: { value: 90 } } - ] as ESBucket[]); + ]); const getMlBucketSize = 10; const bucketSize = 5; @@ -112,7 +112,7 @@ describe('anomalySeriesTransform', () => { upper: { value: 45 }, lower: { value: 40 } } - ] as ESBucket[]); + ]); const mlBucketSize = 10; const bucketSize = 5; @@ -151,7 +151,7 @@ describe('anomalySeriesTransform', () => { upper: { value: 25 }, lower: { value: 20 } } - ] as ESBucket[]); + ]); const getMlBucketSize = 10; const bucketSize = 5; @@ -190,7 +190,7 @@ describe('anomalySeriesTransform', () => { upper: { value: null }, lower: { value: null } } - ] as ESBucket[]); + ]); const getMlBucketSize = 10; const bucketSize = 5; @@ -234,10 +234,10 @@ describe('replaceFirstAndLastBucket', () => { lower: 30, upper: 40 } - ] as any; + ]; const timeSeriesDates = [10, 15]; - expect(replaceFirstAndLastBucket(buckets, timeSeriesDates)).toEqual([ + expect(replaceFirstAndLastBucket(buckets as any, timeSeriesDates)).toEqual([ { x: 10, lower: 10, upper: 20 }, { x: 15, lower: 30, upper: 40 } ]); @@ -271,8 +271,8 @@ describe('replaceFirstAndLastBucket', () => { }); }); -function getESResponse(buckets: ESBucket[]): ESResponse { - return { +function getESResponse(buckets: any): ESResponse { + return ({ took: 3, timed_out: false, _shards: { @@ -288,7 +288,7 @@ function getESResponse(buckets: ESBucket[]): ESResponse { }, aggregations: { ml_avg_response_times: { - buckets: buckets.map(bucket => { + buckets: buckets.map((bucket: any) => { return { ...bucket, lower: { value: idx(bucket, _ => _.lower.value) || null }, @@ -300,5 +300,5 @@ function getESResponse(buckets: ESBucket[]): ESResponse { }) } } - }; + } as unknown) as ESResponse; } diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts index 211918fc2417d..27cb122addfb1 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts @@ -7,10 +7,12 @@ import { first, last } from 'lodash'; import { idx } from '@kbn/elastic-idx'; import { Coordinate, RectCoordinate } from '../../../../../typings/timeseries'; -import { ESBucket, ESResponse } from './fetcher'; +import { ESResponse } from './fetcher'; type IBucket = ReturnType; -function getBucket(bucket: ESBucket) { +function getBucket( + bucket: ESResponse['aggregations']['ml_avg_response_times']['buckets'][0] +) { return { x: bucket.key, anomalyScore: bucket.anomaly_score.value, @@ -28,10 +30,6 @@ export function anomalySeriesTransform( bucketSize: number, timeSeriesDates: number[] ) { - if (!response) { - return; - } - const buckets = ( idx(response, _ => _.aggregations.ml_avg_response_times.buckets) || [] ).map(getBucket); diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts index ceb19b865177d..8ccf1af568535 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ESFilter, SearchParams } from 'elasticsearch'; +import { ESFilter } from 'elasticsearch'; import { PROCESSOR_EVENT, SERVICE_NAME, @@ -18,53 +18,6 @@ import { getBucketSize } from '../../../helpers/get_bucket_size'; import { rangeFilter } from '../../../helpers/range_filter'; import { Setup } from '../../../helpers/setup_request'; -interface ResponseTimeBucket { - key_as_string: string; - key: number; - doc_count: number; - avg: { - value: number | null; - }; - pct: { - values: { - '95.0': number | 'NaN'; - '99.0': number | 'NaN'; - }; - }; -} - -interface TransactionResultBucket { - /** - * transaction result eg. 2xx - */ - key: string; - doc_count: number; - timeseries: { - buckets: Array<{ - key_as_string: string; - /** - * timestamp in ms - */ - key: number; - doc_count: number; - }>; - }; -} - -interface Aggs { - response_times: { - buckets: ResponseTimeBucket[]; - }; - transaction_results: { - doc_count_error_upper_bound: number; - sum_other_doc_count: number; - buckets: TransactionResultBucket[]; - }; - overall_avg_duration: { - value: number; - }; -} - export type ESResponse = PromiseReturnType; export function timeseriesFetcher({ serviceName, @@ -96,8 +49,8 @@ export function timeseriesFetcher({ filter.push({ term: { [TRANSACTION_TYPE]: transactionType } }); } - const params: SearchParams = { - index: config.get('apm_oss.transactionIndices'), + const params = { + index: config.get('apm_oss.transactionIndices'), body: { size: 0, query: { bool: { filter } }, @@ -134,5 +87,5 @@ export function timeseriesFetcher({ } }; - return client.search(params); + return client.search(params); } diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/mock-responses/timeseries_response.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/mock-responses/timeseries_response.ts index 075ede23fb38e..8f70f007a3f1d 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/mock-responses/timeseries_response.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/mock-responses/timeseries_response.ts @@ -6,7 +6,7 @@ import { ESResponse } from '../fetcher'; -export const timeseriesResponse: ESResponse = { +export const timeseriesResponse = ({ took: 368, timed_out: false, _shards: { @@ -2826,4 +2826,4 @@ export const timeseriesResponse: ESResponse = { value: 32861.15660262639 } } -}; +} as unknown) as ESResponse; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.test.ts index a584cd70e2f8b..1da800ae21ab2 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.test.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.test.ts @@ -96,7 +96,7 @@ describe('getTpmBuckets', () => { } ]; const bucketSize = 10; - expect(getTpmBuckets(buckets, bucketSize)).toEqual([ + expect(getTpmBuckets(buckets as any, bucketSize)).toEqual([ { dataPoints: [ { x: 0, y: 0 }, diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts index 6d639eb7f9ffd..e3978217f260b 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts @@ -10,19 +10,7 @@ import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { Coordinate } from '../../../../../typings/timeseries'; import { ESResponse } from './fetcher'; -export interface ApmTimeSeriesResponse { - totalHits: number; - responseTimes: { - avg: Coordinate[]; - p95: Coordinate[]; - p99: Coordinate[]; - }; - tpmBuckets: Array<{ - key: string; - dataPoints: Coordinate[]; - }>; - overallAvgDuration?: number; -} +export type ApmTimeSeriesResponse = ReturnType; export function timeseriesTransformer({ timeseriesResponse, @@ -30,7 +18,7 @@ export function timeseriesTransformer({ }: { timeseriesResponse: ESResponse; bucketSize: number; -}): ApmTimeSeriesResponse { +}) { const aggs = timeseriesResponse.aggregations; const overallAvgDuration = idx(aggs, _ => _.overall_avg_duration.value); const responseTimeBuckets = idx(aggs, _ => _.response_times.buckets); diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/calculate_bucket_size.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/calculate_bucket_size.ts index 3e2a285f864e9..20f69a0bd4d8c 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/calculate_bucket_size.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/calculate_bucket_size.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams } from 'elasticsearch'; import { PROCESSOR_EVENT, SERVICE_NAME, @@ -22,8 +21,8 @@ export async function calculateBucketSize( ) { const { start, end, uiFiltersES, client, config } = setup; - const params: SearchParams = { - index: config.get('apm_oss.transactionIndices'), + const params = { + index: config.get('apm_oss.transactionIndices'), body: { size: 0, query: { @@ -56,13 +55,8 @@ export async function calculateBucketSize( } }; - interface Aggs { - stats: { - max: number; - }; - } + const resp = await client.search(params); - const resp = await client.search(params); const minBucketSize: number = config.get('xpack.apm.minimumBucketSize'); const bucketTargetCount: number = config.get('xpack.apm.bucketTargetCount'); const max = resp.aggregations.stats.max; diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts index e67d7db075df1..d265aa5173d2f 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchResponse } from 'elasticsearch'; import { PROCESSOR_EVENT, SERVICE_NAME, @@ -15,29 +14,9 @@ import { TRANSACTION_SAMPLED, TRANSACTION_TYPE } from '../../../../../common/elasticsearch_fieldnames'; -import { PromiseReturnType } from '../../../../../typings/common'; -import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; import { rangeFilter } from '../../../helpers/range_filter'; import { Setup } from '../../../helpers/setup_request'; -interface Bucket { - key: number; - doc_count: number; - sample: SearchResponse<{ - transaction: Pick; - trace: { - id: string; - }; - }>; -} - -interface Aggs { - distribution: { - buckets: Bucket[]; - }; -} - -export type ESResponse = PromiseReturnType; export function bucketFetcher( serviceName: string, transactionName: string, @@ -95,5 +74,5 @@ export function bucketFetcher( } }; - return client.search(params); + return client.search(params); } diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/transform.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/transform.ts index 07e5004655e7f..c17b388cabb19 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/transform.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/transform.ts @@ -6,7 +6,11 @@ import { isEmpty } from 'lodash'; import { idx } from '@kbn/elastic-idx'; -import { ESResponse } from './fetcher'; +import { PromiseReturnType } from '../../../../../typings/common'; +import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; +import { bucketFetcher } from './fetcher'; + +type DistributionBucketResponse = PromiseReturnType; function getDefaultSample(buckets: IBucket[]) { const samples = buckets @@ -23,9 +27,12 @@ function getDefaultSample(buckets: IBucket[]) { export type IBucket = ReturnType; function getBucket( - bucket: ESResponse['aggregations']['distribution']['buckets'][0] + bucket: DistributionBucketResponse['aggregations']['distribution']['buckets'][0] ) { - const sampleSource = idx(bucket, _ => _.sample.hits.hits[0]._source); + const sampleSource = idx(bucket, _ => _.sample.hits.hits[0]._source) as + | Transaction + | undefined; + const isSampled = idx(sampleSource, _ => _.transaction.sampled); const sample = { traceId: idx(sampleSource, _ => _.trace.id), @@ -39,7 +46,7 @@ function getBucket( }; } -export function bucketTransformer(response: ESResponse) { +export function bucketTransformer(response: DistributionBucketResponse) { const buckets = response.aggregations.distribution.buckets.map(getBucket); return { diff --git a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts b/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts index 71658da91bb37..93a82bf6db85b 100644 --- a/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts +++ b/x-pack/plugins/apm/server/lib/ui_filters/get_environments.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BucketAgg, ESFilter } from 'elasticsearch'; +import { ESFilter } from 'elasticsearch'; import { idx } from '@kbn/elastic-idx'; import { PROCESSOR_EVENT, @@ -57,13 +57,7 @@ export async function getEnvironments(setup: Setup, serviceName?: string) { } }; - interface Aggs extends BucketAgg { - environments: { - buckets: BucketAgg[]; - }; - } - - const resp = await client.search(params); + const resp = await client.search(params); const aggs = resp.aggregations; const environmentsBuckets = idx(aggs, _ => _.environments.buckets) || []; diff --git a/x-pack/plugins/apm/typings/common.ts b/x-pack/plugins/apm/typings/common.ts index 6abbcc5413bd2..42c9d90f809aa 100644 --- a/x-pack/plugins/apm/typings/common.ts +++ b/x-pack/plugins/apm/typings/common.ts @@ -20,3 +20,9 @@ export type PromiseReturnType = Func extends ( ) => Promise ? Value : Func; + +export type IndexAsString = { + [k: string]: Map[keyof Map]; +} & Map; + +export type Omit = Pick>; diff --git a/x-pack/plugins/apm/typings/elasticsearch.ts b/x-pack/plugins/apm/typings/elasticsearch.ts index 1fa52da1e26da..4ba936f480155 100644 --- a/x-pack/plugins/apm/typings/elasticsearch.ts +++ b/x-pack/plugins/apm/typings/elasticsearch.ts @@ -4,25 +4,115 @@ * you may not use this file except in compliance with the Elastic License. */ -import { StringMap } from './common'; +import { StringMap, IndexAsString } from './common'; declare module 'elasticsearch' { // extending SearchResponse to be able to have typed aggregations - export interface AggregationSearchResponse - extends SearchResponse { - aggregations: Aggs; - } - export interface BucketAgg { - key: T; - doc_count: number; - } + type AggregationType = + | 'date_histogram' + | 'histogram' + | 'terms' + | 'avg' + | 'top_hits' + | 'max' + | 'min' + | 'percentiles' + | 'sum' + | 'extended_stats'; + + type AggOptions = AggregationOptionMap & { + [key: string]: any; + }; + + // eslint-disable-next-line @typescript-eslint/prefer-interface + export type AggregationOptionMap = { + aggs?: { + [aggregationName: string]: { + [T in AggregationType]?: AggOptions & AggregationOptionMap + }; + }; + }; + + // eslint-disable-next-line @typescript-eslint/prefer-interface + type BucketAggregation = { + buckets: Array< + { + key: KeyType; + key_as_string: string; + doc_count: number; + } & (SubAggregationMap extends { aggs: any } + ? AggregationResultMap + : {}) + >; + }; - export interface TermsAggsBucket { - key: string; - doc_count: number; + interface AggregatedValue { + value: number | null; } + type AggregationResultMap = IndexAsString< + { + [AggregationName in keyof AggregationOption]: { + avg: AggregatedValue; + max: AggregatedValue; + min: AggregatedValue; + sum: AggregatedValue; + terms: BucketAggregation; + date_histogram: BucketAggregation< + AggregationOption[AggregationName], + number + >; + histogram: BucketAggregation< + AggregationOption[AggregationName], + number + >; + top_hits: { + hits: { + total: number; + max_score: number | null; + hits: Array<{ + _source: AggregationOption[AggregationName] extends { + Mapping: any; + } + ? AggregationOption[AggregationName]['Mapping'] + : never; + }>; + }; + }; + percentiles: { + values: { + [key: string]: number; + }; + }; + extended_stats: { + count: number; + min: number; + max: number; + avg: number; + sum: number; + sum_of_squares: number; + variance: number; + std_deviation: number; + std_deviation_bounds: { + upper: number; + lower: number; + }; + }; + }[AggregationType & keyof AggregationOption[AggregationName]] + } + >; + + export type AggregationSearchResponse = Pick< + SearchResponse, + Exclude, 'aggregations'> + > & + (SearchParams extends { body: Required } + ? { + aggregations: AggregationResultMap; + } + : {}); + export interface ESFilter { [key: string]: { [key: string]: string | number | StringMap | ESFilter[];