From 43f55a5d08207ee8278d2aec6424bed03fc649b0 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Mon, 22 Jun 2020 23:13:21 +0200 Subject: [PATCH 1/6] [APM] Optimize service overview queries --- .../__snapshots__/queries.test.ts.snap | 324 ++++++++++++++--- .../get_services/get_services_items.ts | 334 +++++++++++++++--- .../apm/server/lib/services/queries.test.ts | 4 +- 3 files changed, 566 insertions(+), 96 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index 3f8d6b22cd000..aec4327abe33a 100644 --- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -113,76 +113,300 @@ Object { `; exports[`services queries fetches the service items 1`] = ` -Object { - "body": Object { - "aggs": Object { - "services": Object { - "aggs": Object { - "agents": Object { - "terms": Object { - "field": "agent.name", - "size": 1, +Array [ + Object { + "body": Object { + "aggs": Object { + "services": Object { + "aggs": Object { + "average": Object { + "avg": Object { + "field": "transaction.duration.us", + }, }, }, - "avg": Object { - "avg": Object { - "field": "transaction.duration.us", + "terms": Object { + "field": "service.name", + "size": 500, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "terms": Object { + "processor.event": Array [ + "transaction", + "error", + "metric", + ], + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, + }, + Object { + "term": Object { + "processor.event": "transaction", + }, + }, + ], + }, + }, + "size": 0, + }, + "index": Array [ + "myIndex", + "myIndex", + "myIndex", + ], + "size": 0, + }, + Object { + "body": Object { + "aggs": Object { + "services": Object { + "aggs": Object { + "agent_name": Object { + "top_hits": Object { + "_source": Array [ + "agent.name", + ], + "size": 1, + }, }, }, - "environments": Object { - "terms": Object { - "field": "service.environment", + "terms": Object { + "field": "service.name", + "size": 500, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "terms": Object { + "processor.event": Array [ + "transaction", + "error", + "metric", + ], + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, }, + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, + }, + Object { + "terms": Object { + "processor.event": Array [ + "metric", + "error", + "transaction", + ], + }, + }, + ], + }, + }, + "size": 0, + }, + "index": Array [ + "myIndex", + "myIndex", + "myIndex", + ], + }, + Object { + "body": Object { + "aggs": Object { + "services": Object { + "terms": Object { + "field": "service.name", + "size": 500, }, - "events": Object { - "terms": Object { - "field": "processor.event", + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "terms": Object { + "processor.event": Array [ + "transaction", + "error", + "metric", + ], + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, + }, + Object { + "term": Object { + "processor.event": "transaction", + }, }, + ], + }, + }, + "size": 0, + }, + "index": Array [ + "myIndex", + "myIndex", + "myIndex", + ], + }, + Object { + "body": Object { + "aggs": Object { + "services": Object { + "terms": Object { + "field": "service.name", }, }, - "terms": Object { - "field": "service.name", - "size": 500, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "terms": Object { + "processor.event": Array [ + "transaction", + "error", + "metric", + ], + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, + }, + Object { + "term": Object { + "processor.event": "error", + }, + }, + ], }, }, + "size": 0, }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "terms": Object { - "processor.event": Array [ - "transaction", - "error", - "metric", - ], + "index": Array [ + "myIndex", + "myIndex", + "myIndex", + ], + }, + Object { + "body": Object { + "aggs": Object { + "services": Object { + "aggs": Object { + "environments": Object { + "terms": Object { + "field": "service.environment", + }, }, }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, + "terms": Object { + "field": "service.name", + "size": 500, + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "terms": Object { + "processor.event": Array [ + "transaction", + "error", + "metric", + ], }, }, - }, - Object { - "term": Object { - "my.custom.ui.filter": "foo-bar", + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, }, - }, - ], + Object { + "term": Object { + "my.custom.ui.filter": "foo-bar", + }, + }, + Object { + "terms": Object { + "processor.event": Array [ + "transaction", + "error", + "metric", + ], + }, + }, + ], + }, }, + "size": 0, }, - "size": 0, + "index": Array [ + "myIndex", + "myIndex", + "myIndex", + ], }, - "index": Array [ - "myIndex", - "myIndex", - "myIndex", - ], -} +] `; exports[`services queries fetches the service transaction types 1`] = ` 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 acf052affabdb..36076d7b23174 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 @@ -3,7 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { ValuesType } from 'utility-types'; +import { uniq } from 'lodash'; import { mergeProjection } from '../../../../common/projections/util/merge_projection'; import { PROCESSOR_EVENT, @@ -19,72 +20,315 @@ import { } from '../../helpers/setup_request'; import { getServicesProjection } from '../../../../common/projections/services'; +const MAX_NUMBER_OF_SERVICES = 500; + export type ServiceListAPIResponse = PromiseReturnType; -export async function getServicesItems( - setup: Setup & SetupTimeRange & SetupUIFilters -) { - const { start, end, client } = setup; - const projection = getServicesProjection({ setup }); +const arrayUnionToCallable = ( + array: T +): Array> => { + return array; +}; + +type ServicesItemsSetup = Setup & SetupTimeRange & SetupUIFilters; + +type ServicesItemsProjection = ReturnType; + +interface AggregationParams { + setup: ServicesItemsSetup; + projection: ServicesItemsProjection; +} - const params = mergeProjection(projection, { - body: { +const getTransactionDurationAvg = async ({ + setup, + projection, +}: AggregationParams) => { + const { client } = setup; + + const response = await client.search( + mergeProjection(projection, { size: 0, - aggs: { - services: { - terms: { - ...projection.body.aggs.services.terms, - size: 500, + body: { + query: { + bool: { + filter: projection.body.query.bool.filter.concat({ + term: { + [PROCESSOR_EVENT]: 'transaction', + }, + }), }, - aggs: { - avg: { - avg: { field: TRANSACTION_DURATION }, + }, + aggs: { + services: { + terms: { + ...projection.body.aggs.services.terms, + size: MAX_NUMBER_OF_SERVICES, }, - agents: { - terms: { field: AGENT_NAME, size: 1 }, + aggs: { + average: { + avg: { + field: TRANSACTION_DURATION, + }, + }, + }, + }, + }, + }, + }) + ); + + const { aggregations } = response; + + if (!aggregations) { + return []; + } + + return aggregations.services.buckets.map((bucket) => ({ + name: bucket.key as string, + value: bucket.average.value, + })); +}; + +const getAgentName = async ({ setup, projection }: AggregationParams) => { + const response = await setup.client.search( + mergeProjection(projection, { + body: { + query: { + bool: { + filter: [ + ...projection.body.query.bool.filter, + { + terms: { + [PROCESSOR_EVENT]: ['metric', 'error', 'transaction'], + }, + }, + ], + }, + }, + aggs: { + services: { + terms: { + ...projection.body.aggs.services.terms, + size: MAX_NUMBER_OF_SERVICES, }, - events: { - terms: { field: PROCESSOR_EVENT }, + aggs: { + agent_name: { + top_hits: { + _source: [AGENT_NAME], + size: 1, + }, + }, }, - environments: { - terms: { field: SERVICE_ENVIRONMENT }, + }, + }, + size: 0, + }, + }) + ); + + const { aggregations } = response; + + if (!aggregations) { + return []; + } + + return aggregations.services.buckets.map((bucket) => ({ + name: bucket.key as string, + value: (bucket.agent_name.hits.hits[0]?._source as { + agent: { + name: string; + }; + }).agent.name, + })); +}; + +const getTransactionRate = async ({ setup, projection }: AggregationParams) => { + const response = await setup.client.search( + mergeProjection(projection, { + body: { + size: 0, + query: { + bool: { + filter: [ + ...projection.body.query.bool.filter, + { + term: { + [PROCESSOR_EVENT]: 'transaction', + }, + }, + ], + }, + }, + aggs: { + services: { + terms: { + ...projection.body.aggs.services.terms, + size: MAX_NUMBER_OF_SERVICES, }, }, }, }, - }, + }) + ); + + const { aggregations } = response; + + if (!aggregations) { + return []; + } + + const deltaAsMinutes = (setup.end - setup.start) / 1000 / 60; + + return arrayUnionToCallable(aggregations.services.buckets).map((bucket) => { + const transactionsPerMinute = bucket.doc_count / deltaAsMinutes; + return { + name: bucket.key as string, + value: transactionsPerMinute, + }; }); +}; + +const getErrorRate = async ({ setup, projection }: AggregationParams) => { + const response = await setup.client.search( + mergeProjection(projection, { + body: { + size: 0, + query: { + bool: { + filter: [ + ...projection.body.query.bool.filter, + { + term: { + [PROCESSOR_EVENT]: 'error', + }, + }, + ], + }, + }, + }, + }) + ); + + const { aggregations } = response; - const resp = await client.search(params); - const aggs = resp.aggregations; + if (!aggregations) { + return []; + } - const serviceBuckets = aggs?.services.buckets || []; + const deltaAsMinutes = (setup.end - setup.start) / 1000 / 60; - const items = serviceBuckets.map((bucket) => { - const eventTypes = bucket.events.buckets; + return aggregations.services.buckets.map((bucket) => { + const transactionsPerMinute = bucket.doc_count / deltaAsMinutes; + return { + name: bucket.key as string, + value: transactionsPerMinute, + }; + }); +}; + +const getEnvironments = async ({ setup, projection }: AggregationParams) => { + const response = await setup.client.search( + mergeProjection(projection, { + body: { + size: 0, + query: { + bool: { + filter: [ + ...projection.body.query.bool.filter, + { + terms: { + [PROCESSOR_EVENT]: ['transaction', 'error', 'metric'], + }, + }, + ], + }, + }, + aggs: { + services: { + terms: { + ...projection.body.aggs.services.terms, + size: MAX_NUMBER_OF_SERVICES, + }, + aggs: { + environments: { + terms: { + field: SERVICE_ENVIRONMENT, + }, + }, + }, + }, + }, + }, + }) + ); + + const { aggregations } = response; + + if (!aggregations) { + return []; + } + + return aggregations.services.buckets.map((bucket) => ({ + name: bucket.key as string, + value: bucket.environments.buckets.map((env) => env.key as string), + })); +}; + +export async function getServicesItems(setup: ServicesItemsSetup) { + const projection = getServicesProjection({ setup }); - const transactions = eventTypes.find((e) => e.key === 'transaction'); - const totalTransactions = transactions?.doc_count || 0; + const params = { + setup, + projection, + }; - const errors = eventTypes.find((e) => e.key === 'error'); - const totalErrors = errors?.doc_count || 0; + const [ + transactionDurationAvg, + agentName, + transactionRate, + errorRate, + environments, + ] = await Promise.all([ + getTransactionDurationAvg(params), + getAgentName(params), + getTransactionRate(params), + getErrorRate(params), + getEnvironments(params), + ]); - const deltaAsMinutes = (end - start) / 1000 / 60; - const transactionsPerMinute = totalTransactions / deltaAsMinutes; - const errorsPerMinute = totalErrors / deltaAsMinutes; + const allMetrics = [ + transactionDurationAvg, + agentName, + transactionRate, + errorRate, + environments, + ]; - const environmentsBuckets = bucket.environments.buckets; - const environments = environmentsBuckets.map( - (environmentBucket) => environmentBucket.key as string - ); + const serviceNames = uniq( + arrayUnionToCallable( + allMetrics.flatMap((metric) => + arrayUnionToCallable(metric).map((service) => service.name) + ) + ) + ); + const items = serviceNames.map((serviceName) => { return { - serviceName: bucket.key as string, - agentName: bucket.agents.buckets[0]?.key as string | undefined, - transactionsPerMinute, - errorsPerMinute, - avgResponseTime: bucket.avg.value, - environments, + serviceName, + agentName: + agentName.find((service) => service.name === serviceName)?.value ?? + null, + transactionsPerMinute: + transactionRate.find((service) => service.name === serviceName) + ?.value ?? 0, + errorsPerMinute: + errorRate.find((service) => service.name === serviceName)?.value ?? 0, + avgResponseTime: + transactionDurationAvg.find((service) => service.name === serviceName) + ?.value ?? null, + environments: + environments.find((service) => service.name === serviceName)?.value ?? + [], }; }); diff --git a/x-pack/plugins/apm/server/lib/services/queries.test.ts b/x-pack/plugins/apm/server/lib/services/queries.test.ts index d90cd8bf13908..b2fe7efeaf959 100644 --- a/x-pack/plugins/apm/server/lib/services/queries.test.ts +++ b/x-pack/plugins/apm/server/lib/services/queries.test.ts @@ -40,7 +40,9 @@ describe('services queries', () => { it('fetches the service items', async () => { mock = await inspectSearchParams((setup) => getServicesItems(setup)); - expect(mock.params).toMatchSnapshot(); + const allParams = mock.spy.mock.calls.map((call) => call[0]); + + expect(allParams).toMatchSnapshot(); }); it('fetches the legacy data status', async () => { From f774b47c139bc74e8c9b42e8ebbd61e388de19e6 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Sat, 27 Jun 2020 17:07:58 +0200 Subject: [PATCH 2/6] Review feedback --- .../common/utils/array_union_to_callable.ts | 14 + .../lib/services/get_services/get_metrics.ts | 281 ++++++++++++++++++ .../get_services/get_services_items.ts | 270 +---------------- 3 files changed, 305 insertions(+), 260 deletions(-) create mode 100644 x-pack/plugins/apm/common/utils/array_union_to_callable.ts create mode 100644 x-pack/plugins/apm/server/lib/services/get_services/get_metrics.ts diff --git a/x-pack/plugins/apm/common/utils/array_union_to_callable.ts b/x-pack/plugins/apm/common/utils/array_union_to_callable.ts new file mode 100644 index 0000000000000..23ea86006b888 --- /dev/null +++ b/x-pack/plugins/apm/common/utils/array_union_to_callable.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { ValuesType } from 'utility-types'; + +// work around a TypeScript limitation described in https://stackoverflow.com/posts/49511416 + +export const arrayUnionToCallable = ( + array: T +): Array> => { + return array; +}; diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_metrics.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_metrics.ts new file mode 100644 index 0000000000000..5ed7136b5bf3e --- /dev/null +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_metrics.ts @@ -0,0 +1,281 @@ +/* + * 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 { arrayUnionToCallable } from '../../../../common/utils/array_union_to_callable'; +import { + PROCESSOR_EVENT, + TRANSACTION_DURATION, + AGENT_NAME, + SERVICE_ENVIRONMENT, +} from '../../../../common/elasticsearch_fieldnames'; +import { mergeProjection } from '../../../../common/projections/util/merge_projection'; +import { ProcessorEvent } from '../../../../common/processor_event'; +import { + ServicesItemsSetup, + ServicesItemsProjection, +} from './get_services_items'; + +const MAX_NUMBER_OF_SERVICES = 500; + +const getDeltaAsMinutes = (setup: ServicesItemsSetup) => + (setup.end - setup.start) / 1000 / 60; + +interface AggregationParams { + setup: ServicesItemsSetup; + projection: ServicesItemsProjection; +} + +export const getTransactionDurationAvg = async ({ + setup, + projection, +}: AggregationParams) => { + const { client } = setup; + + const response = await client.search( + mergeProjection(projection, { + size: 0, + body: { + query: { + bool: { + filter: projection.body.query.bool.filter.concat({ + term: { + [PROCESSOR_EVENT]: ProcessorEvent.transaction, + }, + }), + }, + }, + aggs: { + services: { + terms: { + ...projection.body.aggs.services.terms, + size: MAX_NUMBER_OF_SERVICES, + }, + aggs: { + average: { + avg: { + field: TRANSACTION_DURATION, + }, + }, + }, + }, + }, + }, + }) + ); + + const { aggregations } = response; + + if (!aggregations) { + return []; + } + + return aggregations.services.buckets.map((bucket) => ({ + name: bucket.key as string, + value: bucket.average.value, + })); +}; + +export const getAgentName = async ({ + setup, + projection, +}: AggregationParams) => { + const response = await setup.client.search( + mergeProjection(projection, { + body: { + query: { + bool: { + filter: projection.body.query.bool.filter.concat({ + terms: { + [PROCESSOR_EVENT]: [ + ProcessorEvent.metric, + ProcessorEvent.error, + ProcessorEvent.transaction, + ], + }, + }), + }, + }, + aggs: { + services: { + terms: { + ...projection.body.aggs.services.terms, + size: MAX_NUMBER_OF_SERVICES, + }, + aggs: { + agent_name: { + top_hits: { + _source: [AGENT_NAME], + size: 1, + }, + }, + }, + }, + }, + size: 0, + }, + }) + ); + + const { aggregations } = response; + + if (!aggregations) { + return []; + } + + return aggregations.services.buckets.map((bucket) => ({ + name: bucket.key as string, + value: (bucket.agent_name.hits.hits[0]?._source as { + agent: { + name: string; + }; + }).agent.name, + })); +}; + +export const getTransactionRate = async ({ + setup, + projection, +}: AggregationParams) => { + const response = await setup.client.search( + mergeProjection(projection, { + body: { + size: 0, + query: { + bool: { + filter: [ + ...projection.body.query.bool.filter, + { + term: { + [PROCESSOR_EVENT]: ProcessorEvent.transaction, + }, + }, + ], + }, + }, + aggs: { + services: { + terms: { + ...projection.body.aggs.services.terms, + size: MAX_NUMBER_OF_SERVICES, + }, + }, + }, + }, + }) + ); + + const { aggregations } = response; + + if (!aggregations) { + return []; + } + + const deltaAsMinutes = getDeltaAsMinutes(setup); + + return arrayUnionToCallable(aggregations.services.buckets).map((bucket) => { + const transactionsPerMinute = bucket.doc_count / deltaAsMinutes; + return { + name: bucket.key as string, + value: transactionsPerMinute, + }; + }); +}; + +export const getErrorRate = async ({ + setup, + projection, +}: AggregationParams) => { + const response = await setup.client.search( + mergeProjection(projection, { + body: { + size: 0, + query: { + bool: { + filter: [ + ...projection.body.query.bool.filter, + { + term: { + [PROCESSOR_EVENT]: ProcessorEvent.error, + }, + }, + ], + }, + }, + }, + }) + ); + + const { aggregations } = response; + + if (!aggregations) { + return []; + } + + const deltaAsMinutes = getDeltaAsMinutes(setup); + + return aggregations.services.buckets.map((bucket) => { + const transactionsPerMinute = bucket.doc_count / deltaAsMinutes; + return { + name: bucket.key as string, + value: transactionsPerMinute, + }; + }); +}; + +export const getEnvironments = async ({ + setup, + projection, +}: AggregationParams) => { + const response = await setup.client.search( + mergeProjection(projection, { + body: { + size: 0, + query: { + bool: { + filter: [ + ...projection.body.query.bool.filter, + { + terms: { + [PROCESSOR_EVENT]: [ + ProcessorEvent.transaction, + ProcessorEvent.error, + ProcessorEvent.metric, + ], + }, + }, + ], + }, + }, + aggs: { + services: { + terms: { + ...projection.body.aggs.services.terms, + size: MAX_NUMBER_OF_SERVICES, + }, + aggs: { + environments: { + terms: { + field: SERVICE_ENVIRONMENT, + }, + }, + }, + }, + }, + }, + }) + ); + + const { aggregations } = response; + + if (!aggregations) { + return []; + } + + return aggregations.services.buckets.map((bucket) => ({ + name: bucket.key as string, + value: bucket.environments.buckets.map((env) => env.key as string), + })); +}; 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 36076d7b23174..fb3d7434b38c7 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 @@ -3,15 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ValuesType } from 'utility-types'; import { uniq } from 'lodash'; -import { mergeProjection } from '../../../../common/projections/util/merge_projection'; -import { - PROCESSOR_EVENT, - AGENT_NAME, - SERVICE_ENVIRONMENT, - TRANSACTION_DURATION, -} from '../../../../common/elasticsearch_fieldnames'; +import { arrayUnionToCallable } from '../../../../common/utils/array_union_to_callable'; import { PromiseReturnType } from '../../../../typings/common'; import { Setup, @@ -19,260 +12,17 @@ import { SetupUIFilters, } from '../../helpers/setup_request'; import { getServicesProjection } from '../../../../common/projections/services'; - -const MAX_NUMBER_OF_SERVICES = 500; +import { + getTransactionDurationAvg, + getAgentName, + getTransactionRate, + getErrorRate, + getEnvironments, +} from './get_metrics'; export type ServiceListAPIResponse = PromiseReturnType; - -const arrayUnionToCallable = ( - array: T -): Array> => { - return array; -}; - -type ServicesItemsSetup = Setup & SetupTimeRange & SetupUIFilters; - -type ServicesItemsProjection = ReturnType; - -interface AggregationParams { - setup: ServicesItemsSetup; - projection: ServicesItemsProjection; -} - -const getTransactionDurationAvg = async ({ - setup, - projection, -}: AggregationParams) => { - const { client } = setup; - - const response = await client.search( - mergeProjection(projection, { - size: 0, - body: { - query: { - bool: { - filter: projection.body.query.bool.filter.concat({ - term: { - [PROCESSOR_EVENT]: 'transaction', - }, - }), - }, - }, - aggs: { - services: { - terms: { - ...projection.body.aggs.services.terms, - size: MAX_NUMBER_OF_SERVICES, - }, - aggs: { - average: { - avg: { - field: TRANSACTION_DURATION, - }, - }, - }, - }, - }, - }, - }) - ); - - const { aggregations } = response; - - if (!aggregations) { - return []; - } - - return aggregations.services.buckets.map((bucket) => ({ - name: bucket.key as string, - value: bucket.average.value, - })); -}; - -const getAgentName = async ({ setup, projection }: AggregationParams) => { - const response = await setup.client.search( - mergeProjection(projection, { - body: { - query: { - bool: { - filter: [ - ...projection.body.query.bool.filter, - { - terms: { - [PROCESSOR_EVENT]: ['metric', 'error', 'transaction'], - }, - }, - ], - }, - }, - aggs: { - services: { - terms: { - ...projection.body.aggs.services.terms, - size: MAX_NUMBER_OF_SERVICES, - }, - aggs: { - agent_name: { - top_hits: { - _source: [AGENT_NAME], - size: 1, - }, - }, - }, - }, - }, - size: 0, - }, - }) - ); - - const { aggregations } = response; - - if (!aggregations) { - return []; - } - - return aggregations.services.buckets.map((bucket) => ({ - name: bucket.key as string, - value: (bucket.agent_name.hits.hits[0]?._source as { - agent: { - name: string; - }; - }).agent.name, - })); -}; - -const getTransactionRate = async ({ setup, projection }: AggregationParams) => { - const response = await setup.client.search( - mergeProjection(projection, { - body: { - size: 0, - query: { - bool: { - filter: [ - ...projection.body.query.bool.filter, - { - term: { - [PROCESSOR_EVENT]: 'transaction', - }, - }, - ], - }, - }, - aggs: { - services: { - terms: { - ...projection.body.aggs.services.terms, - size: MAX_NUMBER_OF_SERVICES, - }, - }, - }, - }, - }) - ); - - const { aggregations } = response; - - if (!aggregations) { - return []; - } - - const deltaAsMinutes = (setup.end - setup.start) / 1000 / 60; - - return arrayUnionToCallable(aggregations.services.buckets).map((bucket) => { - const transactionsPerMinute = bucket.doc_count / deltaAsMinutes; - return { - name: bucket.key as string, - value: transactionsPerMinute, - }; - }); -}; - -const getErrorRate = async ({ setup, projection }: AggregationParams) => { - const response = await setup.client.search( - mergeProjection(projection, { - body: { - size: 0, - query: { - bool: { - filter: [ - ...projection.body.query.bool.filter, - { - term: { - [PROCESSOR_EVENT]: 'error', - }, - }, - ], - }, - }, - }, - }) - ); - - const { aggregations } = response; - - if (!aggregations) { - return []; - } - - const deltaAsMinutes = (setup.end - setup.start) / 1000 / 60; - - return aggregations.services.buckets.map((bucket) => { - const transactionsPerMinute = bucket.doc_count / deltaAsMinutes; - return { - name: bucket.key as string, - value: transactionsPerMinute, - }; - }); -}; - -const getEnvironments = async ({ setup, projection }: AggregationParams) => { - const response = await setup.client.search( - mergeProjection(projection, { - body: { - size: 0, - query: { - bool: { - filter: [ - ...projection.body.query.bool.filter, - { - terms: { - [PROCESSOR_EVENT]: ['transaction', 'error', 'metric'], - }, - }, - ], - }, - }, - aggs: { - services: { - terms: { - ...projection.body.aggs.services.terms, - size: MAX_NUMBER_OF_SERVICES, - }, - aggs: { - environments: { - terms: { - field: SERVICE_ENVIRONMENT, - }, - }, - }, - }, - }, - }, - }) - ); - - const { aggregations } = response; - - if (!aggregations) { - return []; - } - - return aggregations.services.buckets.map((bucket) => ({ - name: bucket.key as string, - value: bucket.environments.buckets.map((env) => env.key as string), - })); -}; +export type ServicesItemsSetup = Setup & SetupTimeRange & SetupUIFilters; +export type ServicesItemsProjection = ReturnType; export async function getServicesItems(setup: ServicesItemsSetup) { const projection = getServicesProjection({ setup }); From 068c1703ccb543b18ea3b937ea5bf257a875692c Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Sat, 27 Jun 2020 22:11:22 +0200 Subject: [PATCH 3/6] Use correct indices/filters for service overview metrics --- .../apm/common/projections/services.ts | 28 ++++++--- .../__snapshots__/queries.test.ts.snap | 63 +------------------ .../lib/services/get_services/get_metrics.ts | 48 +++++++++----- .../get_services/get_services_items.ts | 4 +- 4 files changed, 58 insertions(+), 85 deletions(-) diff --git a/x-pack/plugins/apm/common/projections/services.ts b/x-pack/plugins/apm/common/projections/services.ts index 80a3471e9c30d..809caeeaf6088 100644 --- a/x-pack/plugins/apm/common/projections/services.ts +++ b/x-pack/plugins/apm/common/projections/services.ts @@ -16,25 +16,37 @@ import { rangeFilter } from '../utils/range_filter'; export function getServicesProjection({ setup, + noEvents, }: { setup: Setup & SetupTimeRange & SetupUIFilters; + noEvents?: boolean; }) { const { start, end, uiFiltersES, indices } = setup; return { - index: [ - indices['apm_oss.metricsIndices'], - indices['apm_oss.errorIndices'], - indices['apm_oss.transactionIndices'], - ], + ...(noEvents + ? {} + : { + index: [ + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'], + ], + }), body: { size: 0, query: { bool: { filter: [ - { - terms: { [PROCESSOR_EVENT]: ['transaction', 'error', 'metric'] }, - }, + ...(noEvents + ? [] + : [ + { + terms: { + [PROCESSOR_EVENT]: ['transaction', 'error', 'metric'], + }, + }, + ]), { range: rangeFilter(start, end) }, ...uiFiltersES, ], diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index aec4327abe33a..dc15a93373bfd 100644 --- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -134,15 +134,6 @@ Array [ "query": Object { "bool": Object { "filter": Array [ - Object { - "terms": Object { - "processor.event": Array [ - "transaction", - "error", - "metric", - ], - }, - }, Object { "range": Object { "@timestamp": Object { @@ -167,11 +158,7 @@ Array [ }, "size": 0, }, - "index": Array [ - "myIndex", - "myIndex", - "myIndex", - ], + "index": "myIndex", "size": 0, }, Object { @@ -197,15 +184,6 @@ Array [ "query": Object { "bool": Object { "filter": Array [ - Object { - "terms": Object { - "processor.event": Array [ - "transaction", - "error", - "metric", - ], - }, - }, Object { "range": Object { "@timestamp": Object { @@ -253,15 +231,6 @@ Array [ "query": Object { "bool": Object { "filter": Array [ - Object { - "terms": Object { - "processor.event": Array [ - "transaction", - "error", - "metric", - ], - }, - }, Object { "range": Object { "@timestamp": Object { @@ -286,11 +255,7 @@ Array [ }, "size": 0, }, - "index": Array [ - "myIndex", - "myIndex", - "myIndex", - ], + "index": "myIndex", }, Object { "body": Object { @@ -304,15 +269,6 @@ Array [ "query": Object { "bool": Object { "filter": Array [ - Object { - "terms": Object { - "processor.event": Array [ - "transaction", - "error", - "metric", - ], - }, - }, Object { "range": Object { "@timestamp": Object { @@ -337,11 +293,7 @@ Array [ }, "size": 0, }, - "index": Array [ - "myIndex", - "myIndex", - "myIndex", - ], + "index": "myIndex", }, Object { "body": Object { @@ -363,15 +315,6 @@ Array [ "query": Object { "bool": Object { "filter": Array [ - Object { - "terms": Object { - "processor.event": Array [ - "transaction", - "error", - "metric", - ], - }, - }, Object { "range": Object { "@timestamp": Object { diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_metrics.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_metrics.ts index 5ed7136b5bf3e..b950b84c4f64b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_metrics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_metrics.ts @@ -32,11 +32,12 @@ export const getTransactionDurationAvg = async ({ setup, projection, }: AggregationParams) => { - const { client } = setup; + const { client, indices } = setup; const response = await client.search( mergeProjection(projection, { size: 0, + index: indices['apm_oss.transactionIndices'], body: { query: { bool: { @@ -82,20 +83,30 @@ export const getAgentName = async ({ setup, projection, }: AggregationParams) => { - const response = await setup.client.search( + const { client, indices } = setup; + const response = await client.search( mergeProjection(projection, { + index: [ + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.transactionIndices'], + ], body: { + size: 0, query: { bool: { - filter: projection.body.query.bool.filter.concat({ - terms: { - [PROCESSOR_EVENT]: [ - ProcessorEvent.metric, - ProcessorEvent.error, - ProcessorEvent.transaction, - ], + filter: [ + ...projection.body.query.bool.filter, + { + terms: { + [PROCESSOR_EVENT]: [ + ProcessorEvent.metric, + ProcessorEvent.error, + ProcessorEvent.transaction, + ], + }, }, - }), + ], }, }, aggs: { @@ -114,7 +125,6 @@ export const getAgentName = async ({ }, }, }, - size: 0, }, }) ); @@ -139,8 +149,10 @@ export const getTransactionRate = async ({ setup, projection, }: AggregationParams) => { - const response = await setup.client.search( + const { client, indices } = setup; + const response = await client.search( mergeProjection(projection, { + index: indices['apm_oss.transactionIndices'], body: { size: 0, query: { @@ -188,8 +200,10 @@ export const getErrorRate = async ({ setup, projection, }: AggregationParams) => { - const response = await setup.client.search( + const { client, indices } = setup; + const response = await client.search( mergeProjection(projection, { + index: indices['apm_oss.errorIndices'], body: { size: 0, query: { @@ -229,8 +243,14 @@ export const getEnvironments = async ({ setup, projection, }: AggregationParams) => { - const response = await setup.client.search( + const { client, indices } = setup; + const response = await client.search( mergeProjection(projection, { + index: [ + indices['apm_oss.metricsIndices'], + indices['apm_oss.errorIndices'], + indices['apm_oss.metricsIndices'], + ], body: { size: 0, query: { 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 fb3d7434b38c7..f277ca7aae6bc 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 @@ -25,11 +25,9 @@ export type ServicesItemsSetup = Setup & SetupTimeRange & SetupUIFilters; export type ServicesItemsProjection = ReturnType; export async function getServicesItems(setup: ServicesItemsSetup) { - const projection = getServicesProjection({ setup }); - const params = { + projection: getServicesProjection({ setup, noEvents: true }), setup, - projection, }; const [ From 5e14e4277dd367b560e79296eb414a12bf2c418d Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Tue, 30 Jun 2020 19:49:15 +0200 Subject: [PATCH 4/6] use join utility function to merge requests --- .../common/utils/join_by_key/index.test.ts | 104 ++++++++++++++++++ .../apm/common/utils/join_by_key/index.ts | 48 ++++++++ x-pack/plugins/apm/common/utils/left_join.ts | 21 ---- .../get_services/get_services_items.ts | 69 ++++-------- ...metrics.ts => get_services_items_stats.ts} | 30 ++--- 5 files changed, 187 insertions(+), 85 deletions(-) create mode 100644 x-pack/plugins/apm/common/utils/join_by_key/index.test.ts create mode 100644 x-pack/plugins/apm/common/utils/join_by_key/index.ts delete mode 100644 x-pack/plugins/apm/common/utils/left_join.ts rename x-pack/plugins/apm/server/lib/services/get_services/{get_metrics.ts => get_services_items_stats.ts} (90%) diff --git a/x-pack/plugins/apm/common/utils/join_by_key/index.test.ts b/x-pack/plugins/apm/common/utils/join_by_key/index.test.ts new file mode 100644 index 0000000000000..458d21bfea58f --- /dev/null +++ b/x-pack/plugins/apm/common/utils/join_by_key/index.test.ts @@ -0,0 +1,104 @@ +/* + * 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 { joinByKey } from './'; + +describe('joinByKey', () => { + it('joins by a string key', () => { + const joined = joinByKey( + [ + { + serviceName: 'opbeans-node', + avg: 10, + }, + { + serviceName: 'opbeans-node', + count: 12, + }, + { + serviceName: 'opbeans-java', + avg: 11, + }, + { + serviceName: 'opbeans-java', + p95: 18, + }, + ], + 'serviceName' + ); + + expect(joined.length).toBe(2); + + expect(joined).toEqual([ + { + serviceName: 'opbeans-node', + avg: 10, + count: 12, + }, + { + serviceName: 'opbeans-java', + avg: 11, + p95: 18, + }, + ]); + }); + + it('joins by a record key', () => { + const joined = joinByKey( + [ + { + key: { + serviceName: 'opbeans-node', + transactionName: '/api/opbeans-node', + }, + avg: 10, + }, + { + key: { + serviceName: 'opbeans-node', + transactionName: '/api/opbeans-node', + }, + count: 12, + }, + { + key: { + serviceName: 'opbeans-java', + transactionName: '/api/opbeans-java', + }, + avg: 11, + }, + { + key: { + serviceName: 'opbeans-java', + transactionName: '/api/opbeans-java', + }, + p95: 18, + }, + ], + 'key' + ); + + expect(joined.length).toBe(2); + + expect(joined).toEqual([ + { + key: { + serviceName: 'opbeans-node', + transactionName: '/api/opbeans-node', + }, + avg: 10, + count: 12, + }, + { + key: { + serviceName: 'opbeans-java', + transactionName: '/api/opbeans-java', + }, + avg: 11, + p95: 18, + }, + ]); + }); +}); diff --git a/x-pack/plugins/apm/common/utils/join_by_key/index.ts b/x-pack/plugins/apm/common/utils/join_by_key/index.ts new file mode 100644 index 0000000000000..b49f536400514 --- /dev/null +++ b/x-pack/plugins/apm/common/utils/join_by_key/index.ts @@ -0,0 +1,48 @@ +/* + * 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 { UnionToIntersection, ValuesType } from 'utility-types'; +import { isEqual } from 'lodash'; + +/** + * Joins a list of records by a given key. Key can be any type of value, from + * strings to plain objects, as long as it is present in all records. `isEqual` + * is used for comparing keys. + * + * UnionToIntersection is needed to get all keys of union types, see below for + * example. + * + const agentNames = [{ serviceName: '', agentName: '' }]; + const transactionRates = [{ serviceName: '', transactionsPerMinute: 1 }]; + const flattened = joinByKey( + [...agentNames, ...transactionRates], + 'serviceName' + ); +*/ + +type JoinedReturnType< + T extends Record, + U extends UnionToIntersection, + V extends keyof T & keyof U +> = Array & Record>; + +export function joinByKey< + T extends Record, + U extends UnionToIntersection, + V extends keyof T & keyof U +>(items: T[], key: V): JoinedReturnType { + return items.reduce>((prev, current) => { + let item = prev.find((prevItem) => isEqual(prevItem[key], current[key])); + + if (!item) { + item = { ...current } as ValuesType>; + prev.push(item); + } else { + Object.assign(item, current); + } + + return prev; + }, []); +} diff --git a/x-pack/plugins/apm/common/utils/left_join.ts b/x-pack/plugins/apm/common/utils/left_join.ts deleted file mode 100644 index f3c4e48df755b..0000000000000 --- a/x-pack/plugins/apm/common/utils/left_join.ts +++ /dev/null @@ -1,21 +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 { Assign, Omit } from 'utility-types'; - -export function leftJoin< - TL extends object, - K extends keyof TL, - TR extends Pick ->(leftRecords: TL[], matchKey: K, rightRecords: TR[]) { - const rightLookup = new Map( - rightRecords.map((record) => [record[matchKey], record]) - ); - return leftRecords.map((record) => { - const matchProp = (record[matchKey] as unknown) as TR[K]; - const matchingRightRecord = rightLookup.get(matchProp); - return { ...record, ...matchingRightRecord }; - }) as Array>>>; -} 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 f277ca7aae6bc..14772e77fe1c2 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 @@ -3,8 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { uniq } from 'lodash'; -import { arrayUnionToCallable } from '../../../../common/utils/array_union_to_callable'; +import { joinByKey } from '../../../../common/utils/join_by_key'; import { PromiseReturnType } from '../../../../typings/common'; import { Setup, @@ -13,12 +12,12 @@ import { } from '../../helpers/setup_request'; import { getServicesProjection } from '../../../../common/projections/services'; import { - getTransactionDurationAvg, - getAgentName, - getTransactionRate, - getErrorRate, + getTransactionDurationAverages, + getAgentNames, + getTransactionRates, + getErrorRates, getEnvironments, -} from './get_metrics'; +} from './get_services_items_stats'; export type ServiceListAPIResponse = PromiseReturnType; export type ServicesItemsSetup = Setup & SetupTimeRange & SetupUIFilters; @@ -31,54 +30,26 @@ export async function getServicesItems(setup: ServicesItemsSetup) { }; const [ - transactionDurationAvg, - agentName, - transactionRate, - errorRate, + transactionDurationAverages, + agentNames, + transactionRates, + errorRates, environments, ] = await Promise.all([ - getTransactionDurationAvg(params), - getAgentName(params), - getTransactionRate(params), - getErrorRate(params), + getTransactionDurationAverages(params), + getAgentNames(params), + getTransactionRates(params), + getErrorRates(params), getEnvironments(params), ]); const allMetrics = [ - transactionDurationAvg, - agentName, - transactionRate, - errorRate, - environments, + ...transactionDurationAverages, + ...agentNames, + ...transactionRates, + ...errorRates, + ...environments, ]; - const serviceNames = uniq( - arrayUnionToCallable( - allMetrics.flatMap((metric) => - arrayUnionToCallable(metric).map((service) => service.name) - ) - ) - ); - - const items = serviceNames.map((serviceName) => { - return { - serviceName, - agentName: - agentName.find((service) => service.name === serviceName)?.value ?? - null, - transactionsPerMinute: - transactionRate.find((service) => service.name === serviceName) - ?.value ?? 0, - errorsPerMinute: - errorRate.find((service) => service.name === serviceName)?.value ?? 0, - avgResponseTime: - transactionDurationAvg.find((service) => service.name === serviceName) - ?.value ?? null, - environments: - environments.find((service) => service.name === serviceName)?.value ?? - [], - }; - }); - - return items; + return joinByKey(allMetrics, 'serviceName'); } diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_metrics.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts similarity index 90% rename from x-pack/plugins/apm/server/lib/services/get_services/get_metrics.ts rename to x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts index b950b84c4f64b..3f41fc95042d9 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_metrics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts @@ -28,7 +28,7 @@ interface AggregationParams { projection: ServicesItemsProjection; } -export const getTransactionDurationAvg = async ({ +export const getTransactionDurationAverages = async ({ setup, projection, }: AggregationParams) => { @@ -74,12 +74,12 @@ export const getTransactionDurationAvg = async ({ } return aggregations.services.buckets.map((bucket) => ({ - name: bucket.key as string, - value: bucket.average.value, + serviceName: bucket.key as string, + avgResponseTime: bucket.average.value, })); }; -export const getAgentName = async ({ +export const getAgentNames = async ({ setup, projection, }: AggregationParams) => { @@ -136,8 +136,8 @@ export const getAgentName = async ({ } return aggregations.services.buckets.map((bucket) => ({ - name: bucket.key as string, - value: (bucket.agent_name.hits.hits[0]?._source as { + serviceName: bucket.key as string, + agentName: (bucket.agent_name.hits.hits[0]?._source as { agent: { name: string; }; @@ -145,7 +145,7 @@ export const getAgentName = async ({ })); }; -export const getTransactionRate = async ({ +export const getTransactionRates = async ({ setup, projection, }: AggregationParams) => { @@ -190,13 +190,13 @@ export const getTransactionRate = async ({ return arrayUnionToCallable(aggregations.services.buckets).map((bucket) => { const transactionsPerMinute = bucket.doc_count / deltaAsMinutes; return { - name: bucket.key as string, - value: transactionsPerMinute, + serviceName: bucket.key as string, + transactionsPerMinute, }; }); }; -export const getErrorRate = async ({ +export const getErrorRates = async ({ setup, projection, }: AggregationParams) => { @@ -231,10 +231,10 @@ export const getErrorRate = async ({ const deltaAsMinutes = getDeltaAsMinutes(setup); return aggregations.services.buckets.map((bucket) => { - const transactionsPerMinute = bucket.doc_count / deltaAsMinutes; + const errorsPerMinute = bucket.doc_count / deltaAsMinutes; return { - name: bucket.key as string, - value: transactionsPerMinute, + serviceName: bucket.key as string, + errorsPerMinute, }; }); }; @@ -295,7 +295,7 @@ export const getEnvironments = async ({ } return aggregations.services.buckets.map((bucket) => ({ - name: bucket.key as string, - value: bucket.environments.buckets.map((env) => env.key as string), + serviceName: bucket.key as string, + environments: bucket.environments.buckets.map((env) => env.key as string), })); }; From 83326f239745f998482975452e906e39fdd8405a Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Wed, 1 Jul 2020 12:54:03 +0200 Subject: [PATCH 5/6] Query transaction indices in getEnvironments() --- .../lib/services/get_services/get_services_items_stats.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts index 3f41fc95042d9..4aa427339ee20 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts @@ -249,7 +249,7 @@ export const getEnvironments = async ({ index: [ indices['apm_oss.metricsIndices'], indices['apm_oss.errorIndices'], - indices['apm_oss.metricsIndices'], + indices['apm_oss.transactionIndices'], ], body: { size: 0, From a645758fd4968d297ee0674ac5852b504c184a15 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Thu, 2 Jul 2020 10:36:22 +0200 Subject: [PATCH 6/6] Set size of terms agg on error rate aggregation --- .../lib/services/__snapshots__/queries.test.ts.snap | 1 + .../lib/services/get_services/get_services_items_stats.ts | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index dc15a93373bfd..0fc1f89a3723b 100644 --- a/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -263,6 +263,7 @@ Array [ "services": Object { "terms": Object { "field": "service.name", + "size": 500, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts index 4aa427339ee20..c28bcad841ffd 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts @@ -218,6 +218,14 @@ export const getErrorRates = async ({ ], }, }, + aggs: { + services: { + terms: { + ...projection.body.aggs.services.terms, + size: MAX_NUMBER_OF_SERVICES, + }, + }, + }, }, }) );