From 22b3729a2979003a349c1019a15485dc5a57e013 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Thu, 21 Jan 2021 17:44:06 +0100 Subject: [PATCH] new metrics api --- .../index.tsx | 74 ++++++++++-- .../shared/charts/spark_plot/index.tsx | 17 +-- ..._timeseries_data_for_transaction_groups.ts | 2 +- .../get_service_transaction_groups/index.ts | 33 ++---- .../merge_transaction_group_data.ts | 90 -------------- .../index.ts | 110 ++++++++++++++++++ .../apm/server/routes/create_apm_api.ts | 2 + .../plugins/apm/server/routes/transactions.ts | 48 ++++++++ 8 files changed, 244 insertions(+), 132 deletions(-) delete mode 100644 x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/merge_transaction_group_data.ts create mode 100644 x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index 43c813d201ec7..8f82e0a2f16aa 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -12,7 +12,7 @@ import { EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { ValuesType } from 'utility-types'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; import { @@ -40,6 +40,8 @@ type ServiceTransactionGroupItem = ValuesType< APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/overview'>['transactionGroups'] >; +type TransactionGroupMetrics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/metrics'>; + interface Props { serviceName: string; } @@ -84,9 +86,11 @@ function getLatencyAggregationTypeLabel(latencyAggregationType?: string) { function getColumns({ serviceName, latencyAggregationType, + transactionsMetricsData, }: { serviceName: string; latencyAggregationType?: string; + transactionsMetricsData: TransactionGroupMetrics; }): Array> { return [ { @@ -119,12 +123,15 @@ function getColumns({ field: 'latency', name: getLatencyAggregationTypeLabel(latencyAggregationType), width: px(unit * 10), - render: (_, { latency }) => { + render: (_, { latency, name }) => { + const timeseries = transactionsMetricsData + ? transactionsMetricsData[name]?.latency.timeseries + : undefined; return ( ); @@ -137,12 +144,15 @@ function getColumns({ { defaultMessage: 'Throughput' } ), width: px(unit * 10), - render: (_, { throughput }) => { + render: (_, { throughput, name }) => { + const timeseries = transactionsMetricsData + ? transactionsMetricsData[name]?.throughput.timeseries + : undefined; return ( ); @@ -157,12 +167,15 @@ function getColumns({ } ), width: px(unit * 8), - render: (_, { errorRate }) => { + render: (_, { errorRate, name }) => { + const timeseries = transactionsMetricsData + ? transactionsMetricsData[name]?.errorRate.timeseries + : undefined; return ( ); @@ -265,7 +278,47 @@ export function ServiceOverviewTransactionsTable(props: Props) { tableOptions: { pageIndex, sort }, } = data; - const columns = getColumns({ serviceName, latencyAggregationType }); + const transactionNames = useMemo(() => items.map(({ name }) => name), [ + items, + ]); + + const { + data: transactionsMetricsData, + status: transactionsMetricsStatus, + } = useFetcher(() => { + if (transactionNames.length && start && end && transactionType) { + return callApmApi({ + endpoint: + 'GET /api/apm/services/{serviceName}/transactions/groups/metrics', + params: { + path: { serviceName }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + numBuckets: 20, + transactionType, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + transactionNames: transactionNames.join(), + }, + }, + }); + } + }, [ + serviceName, + start, + end, + uiFilters, + transactionType, + latencyAggregationType, + transactionNames, + ]); + + const columns = getColumns({ + serviceName, + latencyAggregationType, + transactionsMetricsData, + }); return ( @@ -319,7 +372,10 @@ export function ServiceOverviewTransactionsTable(props: Props) { pageSizeOptions: [PAGE_SIZE], hidePerPageOptions: true, }} - loading={status === FETCH_STATUS.LOADING} + loading={ + status === FETCH_STATUS.LOADING || + transactionsMetricsStatus === FETCH_STATUS.LOADING + } onChange={(newTableOptions: { page?: { index: number; diff --git a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx index 7b5ff4fd56e91..59e48f13ad1c8 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx @@ -15,6 +15,7 @@ import { Settings, } from '@elastic/charts'; import { merge } from 'lodash'; +import { Coordinate } from '../../../../../typings/timeseries'; import { useChartTheme } from '../../../../../../observability/public'; import { px, unit } from '../../../../style/variables'; import { useTheme } from '../../../../hooks/use_theme'; @@ -38,7 +39,7 @@ export function SparkPlot({ compact, }: { color: Color; - series?: Array<{ x: number; y: number | null }> | null; + series?: Coordinate[] | null; valueLabel: React.ReactNode; compact?: boolean; }) { @@ -57,18 +58,18 @@ export function SparkPlot({ const colorValue = theme.eui[color]; + const chartSize = { + height: px(24), + width: compact ? px(unit * 3) : px(unit * 4), + }; + return ( {!series || series.every((point) => point.y === null) ? ( - + ) : ( - + group.name); - - const timeseriesData = await getTimeseriesDataForTransactionGroups({ - apmEventClient, - start, - end, - esFilter, - numBuckets, - searchAggregatedTransactions, - serviceName, - transactionNames, - transactionType, - latencyAggregationType, - }); - return { - transactionGroups: mergeTransactionGroupData({ - transactionGroups, - timeseriesData, - start, - end, - latencyAggregationType, - transactionType, + transactionGroups: transactionGroups.map((transactionGroup) => { + return { + name: transactionGroup.name, + transactionType, + latency: { value: transactionGroup.latency }, + throughput: { value: transactionGroup.throughput }, + errorRate: { value: transactionGroup.errorRate }, + impact: transactionGroup.impact, + }; }), totalTransactionGroups, isAggregationAccurate, diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/merge_transaction_group_data.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/merge_transaction_group_data.ts deleted file mode 100644 index a8794e3c09a40..0000000000000 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/merge_transaction_group_data.ts +++ /dev/null @@ -1,90 +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 { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames'; -import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; -import { getLatencyValue } from '../../helpers/latency_aggregation_type'; -import { TransactionGroupTimeseriesData } from './get_timeseries_data_for_transaction_groups'; -import { TransactionGroupWithoutTimeseriesData } from './get_transaction_groups_for_page'; - -export function mergeTransactionGroupData({ - start, - end, - transactionGroups, - timeseriesData, - latencyAggregationType, - transactionType, -}: { - start: number; - end: number; - transactionGroups: TransactionGroupWithoutTimeseriesData[]; - timeseriesData: TransactionGroupTimeseriesData; - latencyAggregationType: LatencyAggregationType; - transactionType: string; -}) { - const deltaAsMinutes = (end - start) / 1000 / 60; - - return transactionGroups.map((transactionGroup) => { - const groupBucket = timeseriesData.find( - ({ key }) => key === transactionGroup.name - ); - - const timeseriesBuckets = groupBucket?.timeseries.buckets ?? []; - - return timeseriesBuckets.reduce( - (acc, point) => { - return { - ...acc, - latency: { - ...acc.latency, - timeseries: acc.latency.timeseries.concat({ - x: point.key, - y: getLatencyValue({ - latencyAggregationType, - aggregation: point.latency, - }), - }), - }, - throughput: { - ...acc.throughput, - timeseries: acc.throughput.timeseries.concat({ - x: point.key, - y: point.transaction_count.value / deltaAsMinutes, - }), - }, - errorRate: { - ...acc.errorRate, - timeseries: acc.errorRate.timeseries.concat({ - x: point.key, - y: - point.transaction_count.value > 0 - ? (point[EVENT_OUTCOME].transaction_count.value ?? 0) / - point.transaction_count.value - : null, - }), - }, - }; - }, - { - name: transactionGroup.name, - transactionType, - latency: { - value: transactionGroup.latency, - timeseries: [] as Array<{ x: number; y: number | null }>, - }, - throughput: { - value: transactionGroup.throughput, - timeseries: [] as Array<{ x: number; y: number }>, - }, - errorRate: { - value: transactionGroup.errorRate, - timeseries: [] as Array<{ x: number; y: number | null }>, - }, - impact: transactionGroup.impact, - } - ); - }); -} diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts new file mode 100644 index 0000000000000..e508ccbc851b4 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups_metrics/index.ts @@ -0,0 +1,110 @@ +/* + * 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 { Coordinate } from '../../../../typings/timeseries'; +import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames'; +import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; +import { getLatencyValue } from '../../helpers/latency_aggregation_type'; +import { Setup, SetupTimeRange } from '../../helpers/setup_request'; +import { getTimeseriesDataForTransactionGroups } from '../get_service_transaction_groups/get_timeseries_data_for_transaction_groups'; + +export async function getServiceTransactionGroupsMetrics({ + serviceName, + transactionNames, + setup, + numBuckets, + searchAggregatedTransactions, + transactionType, + latencyAggregationType, +}: { + serviceName: string; + transactionNames: string[]; + setup: Setup & SetupTimeRange; + numBuckets: number; + searchAggregatedTransactions: boolean; + transactionType: string; + latencyAggregationType: LatencyAggregationType; +}): Promise< + Record< + string, + { + latency: { timeseries: Coordinate[] }; + throughput: { timeseries: Coordinate[] }; + errorRate: { timeseries: Coordinate[] }; + } + > +> { + const { apmEventClient, start, end, esFilter } = setup; + + const buckets = await getTimeseriesDataForTransactionGroups({ + apmEventClient, + start, + end, + esFilter, + numBuckets, + searchAggregatedTransactions, + serviceName, + transactionNames, + transactionType, + latencyAggregationType, + }); + const deltaAsMinutes = (end - start) / 1000 / 60; + + return buckets.reduce((bucketAcc, bucket) => { + const transactionName = bucket.key; + return { + ...bucketAcc, + [transactionName]: bucket.timeseries.buckets.reduce( + (acc, timeseriesBucket) => { + const x = timeseriesBucket.key; + return { + ...acc, + latency: { + timeseries: [ + ...acc.latency.timeseries, + { + x, + y: getLatencyValue({ + latencyAggregationType, + aggregation: timeseriesBucket.latency, + }), + }, + ], + }, + throughput: { + timeseries: [ + ...acc.throughput.timeseries, + { + x, + y: timeseriesBucket.transaction_count.value / deltaAsMinutes, + }, + ], + }, + errorRate: { + timeseries: [ + ...acc.errorRate.timeseries, + { + x, + y: + timeseriesBucket.transaction_count.value > 0 + ? (timeseriesBucket[EVENT_OUTCOME].transaction_count + .value ?? 0) / + timeseriesBucket.transaction_count.value + : null, + }, + ], + }, + }; + }, + { + latency: { timeseries: [] as Coordinate[] }, + throughput: { timeseries: [] as Coordinate[] }, + errorRate: { timeseries: [] as Coordinate[] }, + } + ), + }; + }, {}); +} diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index aeaa6f90a7426..d4de99865ed33 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -62,6 +62,7 @@ import { transactionGroupsOverviewRoute, transactionLatencyChatsRoute, transactionThroughputChatsRoute, + transactionGroupsMetricsRoute, } from './transactions'; import { errorGroupsLocalFiltersRoute, @@ -172,6 +173,7 @@ const createApmApi = () => { .add(transactionGroupsOverviewRoute) .add(transactionLatencyChatsRoute) .add(transactionThroughputChatsRoute) + .add(transactionGroupsMetricsRoute) // UI filters .add(errorGroupsLocalFiltersRoute) diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index 4a8d722547fb0..201493dea24bd 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -23,6 +23,7 @@ import { LatencyAggregationType, latencyAggregationTypeRt, } from '../../common/latency_aggregation_types'; +import { getServiceTransactionGroupsMetrics } from '../lib/services/get_service_transaction_groups_metrics'; /** * Returns a list of transactions grouped by name @@ -118,6 +119,53 @@ export const transactionGroupsOverviewRoute = createRoute({ }, }); +export const transactionGroupsMetricsRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups/metrics', + params: t.type({ + path: t.type({ serviceName: t.string }), + query: t.intersection([ + rangeRt, + uiFiltersRt, + t.type({ + transactionNames: t.string, + numBuckets: toNumberRt, + transactionType: t.string, + latencyAggregationType: latencyAggregationTypeRt, + }), + ]), + }), + options: { + tags: ['access:apm'], + }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + + const searchAggregatedTransactions = await getSearchAggregatedTransactions( + setup + ); + + const { + path: { serviceName }, + query: { + transactionNames, + latencyAggregationType, + numBuckets, + transactionType, + }, + } = context.params; + + return getServiceTransactionGroupsMetrics({ + setup, + serviceName, + transactionNames: transactionNames.split(','), + searchAggregatedTransactions, + transactionType, + numBuckets, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, + }); + }, +}); + export const transactionLatencyChatsRoute = createRoute({ endpoint: 'GET /api/apm/services/{serviceName}/transactions/charts/latency', params: t.type({