diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts index 67cd33415f28f..e487684909633 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts @@ -15,80 +15,83 @@ import { import { ProcessorEvent } from '../../../../common/processor_event'; import { rangeFilter } from '../../../../common/utils/range_filter'; import { AlertParams } from '../../../routes/alerts/chart_preview'; +import { withApmSpan } from '../../../utils/with_apm_span'; import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; -export async function getTransactionDurationChartPreview({ +export function getTransactionDurationChartPreview({ alertParams, setup, }: { alertParams: AlertParams; setup: Setup & SetupTimeRange; }) { - const { apmEventClient, start, end } = setup; - const { - aggregationType, - environment, - serviceName, - transactionType, - } = alertParams; + return withApmSpan('get_transaction_duration_chart_preview', async () => { + const { apmEventClient, start, end } = setup; + const { + aggregationType, + environment, + serviceName, + transactionType, + } = alertParams; - const query = { - bool: { - filter: [ - { range: rangeFilter(start, end) }, - { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, - ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), - ...(transactionType - ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] - : []), - ...getEnvironmentUiFilterES(environment), - ], - }, - }; + const query = { + bool: { + filter: [ + { range: rangeFilter(start, end) }, + { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, + ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), + ...(transactionType + ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] + : []), + ...getEnvironmentUiFilterES(environment), + ], + }, + }; - const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); + const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); - const aggs = { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - }, - aggs: { - agg: - aggregationType === 'avg' - ? { avg: { field: TRANSACTION_DURATION } } - : { - percentiles: { - field: TRANSACTION_DURATION, - percents: [aggregationType === '95th' ? 95 : 99], + const aggs = { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + }, + aggs: { + agg: + aggregationType === 'avg' + ? { avg: { field: TRANSACTION_DURATION } } + : { + percentiles: { + field: TRANSACTION_DURATION, + percents: [aggregationType === '95th' ? 95 : 99], + }, }, - }, + }, }, - }, - }; - const params = { - apm: { events: [ProcessorEvent.transaction] }, - body: { size: 0, query, aggs }, - }; - const resp = await apmEventClient.search(params); + }; + const params = { + apm: { events: [ProcessorEvent.transaction] }, + body: { size: 0, query, aggs }, + }; + const resp = await apmEventClient.search(params); - if (!resp.aggregations) { - return []; - } + if (!resp.aggregations) { + return []; + } - return resp.aggregations.timeseries.buckets.map((bucket) => { - const percentilesKey = aggregationType === '95th' ? '95.0' : '99.0'; - const x = bucket.key; - const y = - aggregationType === 'avg' - ? (bucket.agg as MetricsAggregationResponsePart).value - : (bucket.agg as { values: Record }).values[ - percentilesKey - ]; + return resp.aggregations.timeseries.buckets.map((bucket) => { + const percentilesKey = aggregationType === '95th' ? '95.0' : '99.0'; + const x = bucket.key; + const y = + aggregationType === 'avg' + ? (bucket.agg as MetricsAggregationResponsePart).value + : (bucket.agg as { values: Record }).values[ + percentilesKey + ]; - return { x, y }; + return { x, y }; + }); }); } diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts index ae1d634928ed9..05ad743af0997 100644 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts @@ -9,56 +9,59 @@ import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; import { ProcessorEvent } from '../../../../common/processor_event'; import { rangeFilter } from '../../../../common/utils/range_filter'; import { AlertParams } from '../../../routes/alerts/chart_preview'; +import { withApmSpan } from '../../../utils/with_apm_span'; import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; -export async function getTransactionErrorCountChartPreview({ +export function getTransactionErrorCountChartPreview({ setup, alertParams, }: { setup: Setup & SetupTimeRange; alertParams: AlertParams; }) { - const { apmEventClient, start, end } = setup; - const { serviceName, environment } = alertParams; + return withApmSpan('get_transaction_error_count_chart_preview', async () => { + const { apmEventClient, start, end } = setup; + const { serviceName, environment } = alertParams; - const query = { - bool: { - filter: [ - { range: rangeFilter(start, end) }, - ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), - ...getEnvironmentUiFilterES(environment), - ], - }, - }; + const query = { + bool: { + filter: [ + { range: rangeFilter(start, end) }, + ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), + ...getEnvironmentUiFilterES(environment), + ], + }, + }; - const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); + const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); - const aggs = { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, + const aggs = { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + }, }, - }, - }; + }; - const params = { - apm: { events: [ProcessorEvent.error] }, - body: { size: 0, query, aggs }, - }; + const params = { + apm: { events: [ProcessorEvent.error] }, + body: { size: 0, query, aggs }, + }; - const resp = await apmEventClient.search(params); + const resp = await apmEventClient.search(params); - if (!resp.aggregations) { - return []; - } + if (!resp.aggregations) { + return []; + } - return resp.aggregations.timeseries.buckets.map((bucket) => { - return { - x: bucket.key, - y: bucket.doc_count, - }; + return resp.aggregations.timeseries.buckets.map((bucket) => { + return { + x: bucket.key, + y: bucket.doc_count, + }; + }); }); } diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts index 3b8e104fbf81d..909bb59a13816 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts @@ -18,6 +18,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { APM_ML_JOB_GROUP, ML_MODULE_ID_APM_TRANSACTION } from './constants'; import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { withApmSpan } from '../../utils/with_apm_span'; export async function createAnomalyDetectionJobs( setup: Setup, @@ -30,32 +31,36 @@ export async function createAnomalyDetectionJobs( throw Boom.notImplemented(ML_ERRORS.ML_NOT_AVAILABLE); } - const mlCapabilities = await ml.mlSystem.mlCapabilities(); + const mlCapabilities = await withApmSpan('get_ml_capabilites', () => + ml.mlSystem.mlCapabilities() + ); if (!mlCapabilities.mlFeatureEnabledInSpace) { throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE); } - logger.info( - `Creating ML anomaly detection jobs for environments: [${environments}].` - ); - - const indexPatternName = indices['apm_oss.transactionIndices']; - const responses = await Promise.all( - environments.map((environment) => - createAnomalyDetectionJob({ ml, environment, indexPatternName }) - ) - ); - const jobResponses = responses.flatMap((response) => response.jobs); - const failedJobs = jobResponses.filter(({ success }) => !success); + return withApmSpan('create_anomaly_detection_jobs', async () => { + logger.info( + `Creating ML anomaly detection jobs for environments: [${environments}].` + ); - if (failedJobs.length > 0) { - const errors = failedJobs.map(({ id, error }) => ({ id, error })); - throw new Error( - `An error occurred while creating ML jobs: ${JSON.stringify(errors)}` + const indexPatternName = indices['apm_oss.transactionIndices']; + const responses = await Promise.all( + environments.map((environment) => + createAnomalyDetectionJob({ ml, environment, indexPatternName }) + ) ); - } + const jobResponses = responses.flatMap((response) => response.jobs); + const failedJobs = jobResponses.filter(({ success }) => !success); + + if (failedJobs.length > 0) { + const errors = failedJobs.map(({ id, error }) => ({ id, error })); + throw new Error( + `An error occurred while creating ML jobs: ${JSON.stringify(errors)}` + ); + } - return jobResponses; + return jobResponses; + }); } async function createAnomalyDetectionJob({ @@ -67,34 +72,36 @@ async function createAnomalyDetectionJob({ environment: string; indexPatternName: string; }) { - const randomToken = uuid().substr(-4); + return withApmSpan('create_anomaly_detection_job', async () => { + const randomToken = uuid().substr(-4); - return ml.modules.setup({ - moduleId: ML_MODULE_ID_APM_TRANSACTION, - prefix: `${APM_ML_JOB_GROUP}-${snakeCase(environment)}-${randomToken}-`, - groups: [APM_ML_JOB_GROUP], - indexPatternName, - applyToAllSpaces: true, - query: { - bool: { - filter: [ - { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, - { exists: { field: TRANSACTION_DURATION } }, - ...getEnvironmentUiFilterES(environment), - ], + return ml.modules.setup({ + moduleId: ML_MODULE_ID_APM_TRANSACTION, + prefix: `${APM_ML_JOB_GROUP}-${snakeCase(environment)}-${randomToken}-`, + groups: [APM_ML_JOB_GROUP], + indexPatternName, + applyToAllSpaces: true, + query: { + bool: { + filter: [ + { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, + { exists: { field: TRANSACTION_DURATION } }, + ...getEnvironmentUiFilterES(environment), + ], + }, }, - }, - startDatafeed: true, - jobOverrides: [ - { - custom_settings: { - job_tags: { - environment, - // identifies this as an APM ML job & facilitates future migrations - apm_ml_version: 2, + startDatafeed: true, + jobOverrides: [ + { + custom_settings: { + job_tags: { + environment, + // identifies this as an APM ML job & facilitates future migrations + apm_ml_version: 2, + }, }, }, - }, - ], + ], + }); }); } diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts index 632a9398ff6ac..75b2e8289c7a8 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts @@ -10,27 +10,35 @@ import Boom from '@hapi/boom'; import { ML_ERRORS } from '../../../common/anomaly_detection'; import { Setup } from '../helpers/setup_request'; import { getMlJobsWithAPMGroup } from './get_ml_jobs_with_apm_group'; +import { withApmSpan } from '../../utils/with_apm_span'; -export async function getAnomalyDetectionJobs(setup: Setup, logger: Logger) { +export function getAnomalyDetectionJobs(setup: Setup, logger: Logger) { const { ml } = setup; if (!ml) { throw Boom.notImplemented(ML_ERRORS.ML_NOT_AVAILABLE); } - const mlCapabilities = await ml.mlSystem.mlCapabilities(); - if (!mlCapabilities.mlFeatureEnabledInSpace) { - throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE); - } + return withApmSpan('get_anomaly_detection_jobs', async () => { + const mlCapabilities = await withApmSpan('get_ml_capabilities', () => + ml.mlSystem.mlCapabilities() + ); + + if (!mlCapabilities.mlFeatureEnabledInSpace) { + throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE); + } - const response = await getMlJobsWithAPMGroup(ml.anomalyDetectors); - return response.jobs - .filter((job) => (job.custom_settings?.job_tags?.apm_ml_version ?? 0) >= 2) - .map((job) => { - const environment = job.custom_settings?.job_tags?.environment ?? ''; - return { - job_id: job.job_id, - environment, - }; - }); + const response = await getMlJobsWithAPMGroup(ml.anomalyDetectors); + return response.jobs + .filter( + (job) => (job.custom_settings?.job_tags?.apm_ml_version ?? 0) >= 2 + ) + .map((job) => { + const environment = job.custom_settings?.job_tags?.environment ?? ''; + return { + job_id: job.job_id, + environment, + }; + }); + }); } diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts index 48f40e387ffae..c189d24efc23a 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts @@ -7,27 +7,32 @@ import Boom from '@hapi/boom'; import { ML_ERRORS } from '../../../common/anomaly_detection'; +import { withApmSpan } from '../../utils/with_apm_span'; import { Setup } from '../helpers/setup_request'; import { getMlJobsWithAPMGroup } from './get_ml_jobs_with_apm_group'; // Determine whether there are any legacy ml jobs. // A legacy ML job has a job id that ends with "high_mean_response_time" and created_by=ml-module-apm-transaction -export async function hasLegacyJobs(setup: Setup) { +export function hasLegacyJobs(setup: Setup) { const { ml } = setup; if (!ml) { throw Boom.notImplemented(ML_ERRORS.ML_NOT_AVAILABLE); } - const mlCapabilities = await ml.mlSystem.mlCapabilities(); - if (!mlCapabilities.mlFeatureEnabledInSpace) { - throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE); - } + return withApmSpan('has_legacy_jobs', async () => { + const mlCapabilities = await withApmSpan('get_ml_capabilities', () => + ml.mlSystem.mlCapabilities() + ); + if (!mlCapabilities.mlFeatureEnabledInSpace) { + throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE); + } - const response = await getMlJobsWithAPMGroup(ml.anomalyDetectors); - return response.jobs.some( - (job) => - job.job_id.endsWith('high_mean_response_time') && - job.custom_settings?.created_by === 'ml-module-apm-transaction' - ); + const response = await getMlJobsWithAPMGroup(ml.anomalyDetectors); + return response.jobs.some( + (job) => + job.job_id.endsWith('high_mean_response_time') && + job.custom_settings?.created_by === 'ml-module-apm-transaction' + ); + }); } diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts index 686629b0d7d18..ecebf5b5715a1 100644 --- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_failed_transactions/index.ts @@ -27,6 +27,7 @@ import { getOutcomeAggregation, getTransactionErrorRateTimeSeries, } from '../../helpers/transaction_error_rate'; +import { withApmSpan } from '../../../utils/with_apm_span'; export async function getCorrelationsForFailedTransactions({ serviceName, @@ -41,74 +42,76 @@ export async function getCorrelationsForFailedTransactions({ fieldNames: string[]; setup: Setup & SetupTimeRange; }) { - const { start, end, esFilter, apmEventClient } = setup; - - const backgroundFilters: ESFilter[] = [ - ...esFilter, - { range: rangeFilter(start, end) }, - ]; - - if (serviceName) { - backgroundFilters.push({ term: { [SERVICE_NAME]: serviceName } }); - } - - if (transactionType) { - backgroundFilters.push({ term: { [TRANSACTION_TYPE]: transactionType } }); - } - - if (transactionName) { - backgroundFilters.push({ term: { [TRANSACTION_NAME]: transactionName } }); - } - - const params = { - apm: { events: [ProcessorEvent.transaction] }, - track_total_hits: true, - body: { - size: 0, - query: { - bool: { filter: backgroundFilters }, - }, - aggs: { - failed_transactions: { - filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, - - // significant term aggs - aggs: fieldNames.reduce((acc, fieldName) => { - return { - ...acc, - [fieldName]: { - significant_terms: { - size: 10, - field: fieldName, - background_filter: { bool: { filter: backgroundFilters } }, + return withApmSpan('get_correlations_for_failed_transactions', async () => { + const { start, end, esFilter, apmEventClient } = setup; + + const backgroundFilters: ESFilter[] = [ + ...esFilter, + { range: rangeFilter(start, end) }, + ]; + + if (serviceName) { + backgroundFilters.push({ term: { [SERVICE_NAME]: serviceName } }); + } + + if (transactionType) { + backgroundFilters.push({ term: { [TRANSACTION_TYPE]: transactionType } }); + } + + if (transactionName) { + backgroundFilters.push({ term: { [TRANSACTION_NAME]: transactionName } }); + } + + const params = { + apm: { events: [ProcessorEvent.transaction] }, + track_total_hits: true, + body: { + size: 0, + query: { + bool: { filter: backgroundFilters }, + }, + aggs: { + failed_transactions: { + filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, + + // significant term aggs + aggs: fieldNames.reduce((acc, fieldName) => { + return { + ...acc, + [fieldName]: { + significant_terms: { + size: 10, + field: fieldName, + background_filter: { bool: { filter: backgroundFilters } }, + }, }, - }, - }; - }, {} as Record), + }; + }, {} as Record), + }, }, }, - }, - }; - - const response = await apmEventClient.search(params); - if (!response.aggregations) { - return {}; - } - - const failedTransactionCount = - response.aggregations?.failed_transactions.doc_count; - const totalTransactionCount = response.hits.total.value; - const avgErrorRate = (failedTransactionCount / totalTransactionCount) * 100; - const sigTermAggs = omit( - response.aggregations?.failed_transactions, - 'doc_count' - ); - - const topSigTerms = processSignificantTermAggs({ - sigTermAggs, - thresholdPercentage: avgErrorRate, + }; + + const response = await apmEventClient.search(params); + if (!response.aggregations) { + return {}; + } + + const failedTransactionCount = + response.aggregations?.failed_transactions.doc_count; + const totalTransactionCount = response.hits.total.value; + const avgErrorRate = (failedTransactionCount / totalTransactionCount) * 100; + const sigTermAggs = omit( + response.aggregations?.failed_transactions, + 'doc_count' + ); + + const topSigTerms = processSignificantTermAggs({ + sigTermAggs, + thresholdPercentage: avgErrorRate, + }); + return getErrorRateTimeSeries({ setup, backgroundFilters, topSigTerms }); }); - return getErrorRateTimeSeries({ setup, backgroundFilters, topSigTerms }); } export async function getErrorRateTimeSeries({ @@ -120,71 +123,73 @@ export async function getErrorRateTimeSeries({ backgroundFilters: ESFilter[]; topSigTerms: TopSigTerm[]; }) { - const { start, end, apmEventClient } = setup; - const { intervalString } = getBucketSize({ start, end, numBuckets: 30 }); - - if (isEmpty(topSigTerms)) { - return {}; - } - - const timeseriesAgg = { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - min_doc_count: 0, - extended_bounds: { min: start, max: end }, - }, - aggs: { - outcomes: getOutcomeAggregation(), - }, - }; - - const perTermAggs = topSigTerms.reduce( - (acc, term, index) => { - acc[`term_${index}`] = { - filter: { term: { [term.fieldName]: term.fieldValue } }, - aggs: { timeseries: timeseriesAgg }, - }; - return acc; - }, - {} as { - [key: string]: { - filter: AggregationOptionsByType['filter']; - aggs: { timeseries: typeof timeseriesAgg }; - }; + return withApmSpan('get_error_rate_timeseries', async () => { + const { start, end, apmEventClient } = setup; + const { intervalString } = getBucketSize({ start, end, numBuckets: 30 }); + + if (isEmpty(topSigTerms)) { + return {}; } - ); - - const params = { - // TODO: add support for metrics - apm: { events: [ProcessorEvent.transaction] }, - body: { - size: 0, - query: { bool: { filter: backgroundFilters } }, - aggs: merge({ timeseries: timeseriesAgg }, perTermAggs), - }, - }; - - const response = await apmEventClient.search(params); - const { aggregations } = response; - - if (!aggregations) { - return {}; - } - - return { - overall: { - timeseries: getTransactionErrorRateTimeSeries( - aggregations.timeseries.buckets - ), - }, - significantTerms: topSigTerms.map((topSig, index) => { - const agg = aggregations[`term_${index}`]!; - - return { - ...topSig, - timeseries: getTransactionErrorRateTimeSeries(agg.timeseries.buckets), - }; - }), - }; + + const timeseriesAgg = { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { min: start, max: end }, + }, + aggs: { + outcomes: getOutcomeAggregation(), + }, + }; + + const perTermAggs = topSigTerms.reduce( + (acc, term, index) => { + acc[`term_${index}`] = { + filter: { term: { [term.fieldName]: term.fieldValue } }, + aggs: { timeseries: timeseriesAgg }, + }; + return acc; + }, + {} as { + [key: string]: { + filter: AggregationOptionsByType['filter']; + aggs: { timeseries: typeof timeseriesAgg }; + }; + } + ); + + const params = { + // TODO: add support for metrics + apm: { events: [ProcessorEvent.transaction] }, + body: { + size: 0, + query: { bool: { filter: backgroundFilters } }, + aggs: merge({ timeseries: timeseriesAgg }, perTermAggs), + }, + }; + + const response = await apmEventClient.search(params); + const { aggregations } = response; + + if (!aggregations) { + return {}; + } + + return { + overall: { + timeseries: getTransactionErrorRateTimeSeries( + aggregations.timeseries.buckets + ), + }, + significantTerms: topSigTerms.map((topSig, index) => { + const agg = aggregations[`term_${index}`]!; + + return { + ...topSig, + timeseries: getTransactionErrorRateTimeSeries(agg.timeseries.buckets), + }; + }), + }; + }); } diff --git a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts index 44158433ce5a2..112700d0b6583 100644 --- a/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts +++ b/x-pack/plugins/apm/server/lib/environments/get_all_environments.ts @@ -13,6 +13,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; +import { withApmSpan } from '../../utils/with_apm_span'; export async function getAllEnvironments({ serviceName, @@ -25,52 +26,59 @@ export async function getAllEnvironments({ searchAggregatedTransactions: boolean; includeMissing?: boolean; }) { - const { apmEventClient, config } = setup; - const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; + const spanName = serviceName + ? 'get_all_environments_for_all_services' + : 'get_all_environments_for_service'; + return withApmSpan(spanName, async () => { + const { apmEventClient, config } = setup; + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; - // omit filter for service.name if "All" option is selected - const serviceNameFilter = serviceName - ? [{ term: { [SERVICE_NAME]: serviceName } }] - : []; + // omit filter for service.name if "All" option is selected + const serviceNameFilter = serviceName + ? [{ term: { [SERVICE_NAME]: serviceName } }] + : []; - const params = { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ProcessorEvent.error, - ProcessorEvent.metric, - ], - }, - body: { - // use timeout + min_doc_count to return as early as possible - // if filter is not defined to prevent timeouts - ...(!serviceName ? { timeout: '1ms' } : {}), - size: 0, - query: { - bool: { - filter: [...serviceNameFilter], - }, + const params = { + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ProcessorEvent.error, + ProcessorEvent.metric, + ], }, - aggs: { - environments: { - terms: { - field: SERVICE_ENVIRONMENT, - size: maxServiceEnvironments, - ...(!serviceName ? { min_doc_count: 0 } : {}), - missing: includeMissing ? ENVIRONMENT_NOT_DEFINED.value : undefined, + body: { + // use timeout + min_doc_count to return as early as possible + // if filter is not defined to prevent timeouts + ...(!serviceName ? { timeout: '1ms' } : {}), + size: 0, + query: { + bool: { + filter: [...serviceNameFilter], + }, + }, + aggs: { + environments: { + terms: { + field: SERVICE_ENVIRONMENT, + size: maxServiceEnvironments, + ...(!serviceName ? { min_doc_count: 0 } : {}), + missing: includeMissing + ? ENVIRONMENT_NOT_DEFINED.value + : undefined, + }, }, }, }, - }, - }; + }; - const resp = await apmEventClient.search(params); + const resp = await apmEventClient.search(params); - const environments = - resp.aggregations?.environments.buckets.map( - (bucket) => bucket.key as string - ) || []; - return environments; + const environments = + resp.aggregations?.environments.buckets.map( + (bucket) => bucket.key as string + ) || []; + return environments; + }); } diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts index 730423d326901..7989f57046ae7 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_rate_chart.ts @@ -7,6 +7,7 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; +import { withApmSpan } from '../../../../../utils/with_apm_span'; import { METRIC_JAVA_GC_COUNT } from '../../../../../../common/elasticsearch_fieldnames'; import { Setup, SetupTimeRange } from '../../../../helpers/setup_request'; import { fetchAndTransformGcMetrics } from './fetch_and_transform_gc_metrics'; @@ -40,13 +41,15 @@ function getGcRateChart({ serviceName: string; serviceNodeName?: string; }) { - return fetchAndTransformGcMetrics({ - setup, - serviceName, - serviceNodeName, - chartBase, - fieldName: METRIC_JAVA_GC_COUNT, - }); + return withApmSpan('get_gc_rate_charts', () => + fetchAndTransformGcMetrics({ + setup, + serviceName, + serviceNodeName, + chartBase, + fieldName: METRIC_JAVA_GC_COUNT, + }) + ); } export { getGcRateChart }; diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts index ed9f135ea8b35..446894f82b75e 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/gc/get_gc_time_chart.ts @@ -7,6 +7,7 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; +import { withApmSpan } from '../../../../../utils/with_apm_span'; import { METRIC_JAVA_GC_TIME } from '../../../../../../common/elasticsearch_fieldnames'; import { Setup, SetupTimeRange } from '../../../../helpers/setup_request'; import { fetchAndTransformGcMetrics } from './fetch_and_transform_gc_metrics'; @@ -40,13 +41,15 @@ function getGcTimeChart({ serviceName: string; serviceNodeName?: string; }) { - return fetchAndTransformGcMetrics({ - setup, - serviceName, - serviceNodeName, - chartBase, - fieldName: METRIC_JAVA_GC_TIME, - }); + return withApmSpan('get_gc_time_charts', () => + fetchAndTransformGcMetrics({ + setup, + serviceName, + serviceNodeName, + chartBase, + fieldName: METRIC_JAVA_GC_TIME, + }) + ); } export { getGcTimeChart }; 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 3e34fd407dd42..2b7bb9ea8da6e 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 @@ -7,6 +7,7 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; +import { withApmSpan } from '../../../../../utils/with_apm_span'; import { METRIC_JAVA_HEAP_MEMORY_MAX, METRIC_JAVA_HEAP_MEMORY_COMMITTED, @@ -51,7 +52,7 @@ const chartBase: ChartBase = { series, }; -export async function getHeapMemoryChart({ +export function getHeapMemoryChart({ setup, serviceName, serviceNodeName, @@ -60,18 +61,20 @@ export async function getHeapMemoryChart({ serviceName: string; serviceNodeName?: string; }) { - return fetchAndTransformMetrics({ - setup, - serviceName, - serviceNodeName, - chartBase, - aggs: { - heapMemoryMax: { avg: { field: METRIC_JAVA_HEAP_MEMORY_MAX } }, - heapMemoryCommitted: { - avg: { field: METRIC_JAVA_HEAP_MEMORY_COMMITTED }, + return withApmSpan('get_heap_memory_charts', () => + fetchAndTransformMetrics({ + setup, + serviceName, + serviceNodeName, + 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 } }, }, - heapMemoryUsed: { avg: { field: METRIC_JAVA_HEAP_MEMORY_USED } }, - }, - additionalFilters: [{ term: { [AGENT_NAME]: 'java' } }], - }); + additionalFilters: [{ term: { [AGENT_NAME]: 'java' } }], + }) + ); } diff --git a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/index.ts b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/index.ts index d5751085e26c0..e137720000262 100644 --- a/x-pack/plugins/apm/server/lib/metrics/by_agent/java/index.ts +++ b/x-pack/plugins/apm/server/lib/metrics/by_agent/java/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { withApmSpan } from '../../../../utils/with_apm_span'; import { getHeapMemoryChart } from './heap_memory'; import { Setup, SetupTimeRange } from '../../../helpers/setup_request'; import { getNonHeapMemoryChart } from './non_heap_memory'; @@ -14,7 +15,7 @@ import { getMemoryChartData } from '../shared/memory'; import { getGcRateChart } from './gc/get_gc_rate_chart'; import { getGcTimeChart } from './gc/get_gc_time_chart'; -export async function getJavaMetricsCharts({ +export function getJavaMetricsCharts({ setup, serviceName, serviceNodeName, @@ -23,15 +24,17 @@ export async function getJavaMetricsCharts({ serviceName: string; serviceNodeName?: string; }) { - const charts = await Promise.all([ - getCPUChartData({ setup, serviceName, serviceNodeName }), - getMemoryChartData({ setup, serviceName, serviceNodeName }), - getHeapMemoryChart({ setup, serviceName, serviceNodeName }), - getNonHeapMemoryChart({ setup, serviceName, serviceNodeName }), - getThreadCountChart({ setup, serviceName, serviceNodeName }), - getGcRateChart({ setup, serviceName, serviceNodeName }), - getGcTimeChart({ setup, serviceName, serviceNodeName }), - ]); + return withApmSpan('get_java_system_metric_charts', async () => { + const charts = await Promise.all([ + getCPUChartData({ setup, serviceName, serviceNodeName }), + getMemoryChartData({ setup, serviceName, serviceNodeName }), + getHeapMemoryChart({ setup, serviceName, serviceNodeName }), + getNonHeapMemoryChart({ setup, serviceName, serviceNodeName }), + getThreadCountChart({ setup, serviceName, serviceNodeName }), + getGcRateChart({ setup, serviceName, serviceNodeName }), + getGcTimeChart({ setup, serviceName, serviceNodeName }), + ]); - return { charts }; + return { charts }; + }); } 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 0f38c39b00d21..a3e253d2c81d6 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 @@ -7,6 +7,7 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; +import { withApmSpan } from '../../../../../utils/with_apm_span'; import { METRIC_JAVA_NON_HEAP_MEMORY_MAX, METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED, @@ -57,20 +58,22 @@ export async function getNonHeapMemoryChart({ serviceName: string; serviceNodeName?: string; }) { - return fetchAndTransformMetrics({ - setup, - serviceName, - serviceNodeName, - chartBase, - aggs: { - nonHeapMemoryMax: { avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_MAX } }, - nonHeapMemoryCommitted: { - avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED }, + return withApmSpan('get_non_heap_memory_charts', () => + fetchAndTransformMetrics({ + setup, + serviceName, + serviceNodeName, + 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 }, + }, }, - nonHeapMemoryUsed: { - avg: { field: METRIC_JAVA_NON_HEAP_MEMORY_USED }, - }, - }, - additionalFilters: [{ term: { [AGENT_NAME]: 'java' } }], - }); + additionalFilters: [{ term: { [AGENT_NAME]: 'java' } }], + }) + ); } 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 670a94f53515a..e176c156ad05a 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 @@ -7,6 +7,7 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; +import { withApmSpan } from '../../../../../utils/with_apm_span'; import { METRIC_JAVA_THREAD_COUNT, AGENT_NAME, @@ -49,15 +50,17 @@ export async function getThreadCountChart({ serviceName: string; serviceNodeName?: string; }) { - return fetchAndTransformMetrics({ - setup, - serviceName, - serviceNodeName, - chartBase, - aggs: { - threadCount: { avg: { field: METRIC_JAVA_THREAD_COUNT } }, - threadCountMax: { max: { field: METRIC_JAVA_THREAD_COUNT } }, - }, - additionalFilters: [{ term: { [AGENT_NAME]: 'java' } }], - }); + return withApmSpan('get_thread_count_charts', () => + fetchAndTransformMetrics({ + setup, + serviceName, + serviceNodeName, + chartBase, + aggs: { + threadCount: { avg: { field: METRIC_JAVA_THREAD_COUNT } }, + threadCountMax: { max: { field: METRIC_JAVA_THREAD_COUNT } }, + }, + additionalFilters: [{ term: { [AGENT_NAME]: 'java' } }], + }) + ); } 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 6ea8d942aad43..e7f576b73c5ae 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 @@ -7,6 +7,7 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; +import { withApmSpan } from '../../../../../utils/with_apm_span'; import { METRIC_SYSTEM_CPU_PERCENT, METRIC_PROCESS_CPU_PERCENT, @@ -52,7 +53,7 @@ const chartBase: ChartBase = { series, }; -export async function getCPUChartData({ +export function getCPUChartData({ setup, serviceName, serviceNodeName, @@ -61,18 +62,18 @@ export async function getCPUChartData({ serviceName: string; serviceNodeName?: string; }) { - const metricsChart = await fetchAndTransformMetrics({ - setup, - serviceName, - serviceNodeName, - 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; + return withApmSpan('get_cpu_metric_charts', () => + fetchAndTransformMetrics({ + setup, + serviceName, + serviceNodeName, + 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 } }, + }, + }) + ); } 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 5c31ffca509bc..0f7954d86d3e2 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 @@ -6,6 +6,7 @@ */ import { i18n } from '@kbn/i18n'; +import { withApmSpan } from '../../../../../utils/with_apm_span'; import { METRIC_CGROUP_MEMORY_LIMIT_BYTES, METRIC_CGROUP_MEMORY_USAGE_BYTES, @@ -78,36 +79,44 @@ export async function getMemoryChartData({ serviceName: string; serviceNodeName?: string; }) { - const cgroupResponse = await fetchAndTransformMetrics({ - setup, - serviceName, - serviceNodeName, - chartBase, - aggs: { - memoryUsedAvg: { avg: { script: percentCgroupMemoryUsedScript } }, - memoryUsedMax: { max: { script: percentCgroupMemoryUsedScript } }, - }, - additionalFilters: [ - { exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES } }, - ], - }); + return withApmSpan('get_memory_metrics_charts', async () => { + const cgroupResponse = await withApmSpan( + 'get_cgroup_memory_metrics_charts', + () => + fetchAndTransformMetrics({ + setup, + serviceName, + serviceNodeName, + chartBase, + aggs: { + memoryUsedAvg: { avg: { script: percentCgroupMemoryUsedScript } }, + memoryUsedMax: { max: { script: percentCgroupMemoryUsedScript } }, + }, + additionalFilters: [ + { exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES } }, + ], + }) + ); - if (cgroupResponse.noHits) { - return await fetchAndTransformMetrics({ - setup, - serviceName, - serviceNodeName, - chartBase, - aggs: { - memoryUsedAvg: { avg: { script: percentSystemMemoryUsedScript } }, - memoryUsedMax: { max: { script: percentSystemMemoryUsedScript } }, - }, - additionalFilters: [ - { exists: { field: METRIC_SYSTEM_FREE_MEMORY } }, - { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, - ], - }); - } + if (cgroupResponse.noHits) { + return await withApmSpan('get_system_memory_metrics_charts', () => + fetchAndTransformMetrics({ + setup, + serviceName, + serviceNodeName, + chartBase, + aggs: { + memoryUsedAvg: { avg: { script: percentSystemMemoryUsedScript } }, + memoryUsedMax: { max: { script: percentSystemMemoryUsedScript } }, + }, + additionalFilters: [ + { exists: { field: METRIC_SYSTEM_FREE_MEMORY } }, + { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, + ], + }) + ); + } - return cgroupResponse; + return cgroupResponse; + }); } diff --git a/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts b/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts index 15c3ccfaec232..4af57a685bf83 100644 --- a/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts +++ b/x-pack/plugins/apm/server/lib/observability_overview/get_service_count.ts @@ -10,37 +10,40 @@ import { rangeFilter } from '../../../common/utils/range_filter'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; +import { withApmSpan } from '../../utils/with_apm_span'; -export async function getServiceCount({ +export function getServiceCount({ setup, searchAggregatedTransactions, }: { setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; }) { - const { apmEventClient, start, end } = setup; + return withApmSpan('observability_overview_get_service_count', async () => { + const { apmEventClient, start, end } = setup; - const params = { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ProcessorEvent.error, - ProcessorEvent.metric, - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [{ range: rangeFilter(start, end) }], + const params = { + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ProcessorEvent.error, + ProcessorEvent.metric, + ], + }, + body: { + size: 0, + query: { + bool: { + filter: [{ range: rangeFilter(start, end) }], + }, }, + aggs: { serviceCount: { cardinality: { field: SERVICE_NAME } } }, }, - aggs: { serviceCount: { cardinality: { field: SERVICE_NAME } } }, - }, - }; + }; - const { aggregations } = await apmEventClient.search(params); - return aggregations?.serviceCount.value || 0; + const { aggregations } = await apmEventClient.search(params); + return aggregations?.serviceCount.value || 0; + }); } diff --git a/x-pack/plugins/apm/server/lib/observability_overview/get_transaction_coordinates.ts b/x-pack/plugins/apm/server/lib/observability_overview/get_transaction_coordinates.ts index 6e6bd7ca6ef3d..87394567afc50 100644 --- a/x-pack/plugins/apm/server/lib/observability_overview/get_transaction_coordinates.ts +++ b/x-pack/plugins/apm/server/lib/observability_overview/get_transaction_coordinates.ts @@ -10,8 +10,9 @@ import { Coordinates } from '../../../../observability/typings/common'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { calculateThroughput } from '../helpers/calculate_throughput'; +import { withApmSpan } from '../../utils/with_apm_span'; -export async function getTransactionCoordinates({ +export function getTransactionCoordinates({ setup, bucketSize, searchAggregatedTransactions, @@ -20,39 +21,44 @@ export async function getTransactionCoordinates({ bucketSize: string; searchAggregatedTransactions: boolean; }): Promise { - const { apmEventClient, start, end } = setup; + return withApmSpan( + 'observability_overview_get_transaction_distribution', + async () => { + const { apmEventClient, start, end } = setup; - const { aggregations } = await apmEventClient.search({ - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [{ range: rangeFilter(start, end) }], + const { aggregations } = await apmEventClient.search({ + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], }, - }, - aggs: { - distribution: { - date_histogram: { - field: '@timestamp', - fixed_interval: bucketSize, - min_doc_count: 0, + body: { + size: 0, + query: { + bool: { + filter: [{ range: rangeFilter(start, end) }], + }, + }, + aggs: { + distribution: { + date_histogram: { + field: '@timestamp', + fixed_interval: bucketSize, + min_doc_count: 0, + }, + }, }, }, - }, - }, - }); + }); - return ( - aggregations?.distribution.buckets.map((bucket) => ({ - x: bucket.key, - y: calculateThroughput({ start, end, value: bucket.doc_count }), - })) || [] + return ( + aggregations?.distribution.buckets.map((bucket) => ({ + x: bucket.key, + y: calculateThroughput({ start, end, value: bucket.doc_count }), + })) || [] + ); + } ); } diff --git a/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts b/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts index dc9186fa2b354..abdc8da78502c 100644 --- a/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts +++ b/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts @@ -6,28 +6,31 @@ */ import { ProcessorEvent } from '../../../common/processor_event'; +import { withApmSpan } from '../../utils/with_apm_span'; import { Setup } from '../helpers/setup_request'; -export async function hasData({ setup }: { setup: Setup }) { - const { apmEventClient } = setup; - try { - const params = { - apm: { - events: [ - ProcessorEvent.transaction, - ProcessorEvent.error, - ProcessorEvent.metric, - ], - }, - terminateAfter: 1, - body: { - size: 0, - }, - }; +export function hasData({ setup }: { setup: Setup }) { + return withApmSpan('observability_overview_has_apm_data', async () => { + const { apmEventClient } = setup; + try { + const params = { + apm: { + events: [ + ProcessorEvent.transaction, + ProcessorEvent.error, + ProcessorEvent.metric, + ], + }, + terminateAfter: 1, + body: { + size: 0, + }, + }; - const response = await apmEventClient.search(params); - return response.hits.total.value > 0; - } catch (e) { - return false; - } + const response = await apmEventClient.search(params); + return response.hits.total.value > 0; + } catch (e) { + return false; + } + }); } diff --git a/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts b/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts index 7aa019464754d..939ebbb1f7941 100644 --- a/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/fetch_service_paths_from_trace_ids.ts @@ -14,42 +14,44 @@ import { ServiceConnectionNode, } from '../../../common/service_map'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { withApmSpan } from '../../utils/with_apm_span'; export async function fetchServicePathsFromTraceIds( setup: Setup & SetupTimeRange, traceIds: string[] ) { - const { apmEventClient } = setup; - - // make sure there's a range so ES can skip shards - const dayInMs = 24 * 60 * 60 * 1000; - const start = setup.start - dayInMs; - const end = setup.end + dayInMs; - - const serviceMapParams = { - apm: { - events: [ProcessorEvent.span, ProcessorEvent.transaction], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { - terms: { - [TRACE_ID]: traceIds, + return withApmSpan('get_service_paths_from_trace_ids', async () => { + const { apmEventClient } = setup; + + // make sure there's a range so ES can skip shards + const dayInMs = 24 * 60 * 60 * 1000; + const start = setup.start - dayInMs; + const end = setup.end + dayInMs; + + const serviceMapParams = { + apm: { + events: [ProcessorEvent.span, ProcessorEvent.transaction], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + { + terms: { + [TRACE_ID]: traceIds, + }, }, - }, - { range: rangeFilter(start, end) }, - ], + { range: rangeFilter(start, end) }, + ], + }, }, - }, - aggs: { - service_map: { - scripted_metric: { - init_script: { - lang: 'painless', - source: `state.eventsById = new HashMap(); + aggs: { + service_map: { + scripted_metric: { + init_script: { + lang: 'painless', + source: `state.eventsById = new HashMap(); String[] fieldsToCopy = new String[] { 'parent.id', @@ -63,10 +65,10 @@ export async function fetchServicePathsFromTraceIds( 'agent.name' }; state.fieldsToCopy = fieldsToCopy;`, - }, - map_script: { - lang: 'painless', - source: `def id; + }, + map_script: { + lang: 'painless', + source: `def id; if (!doc['span.id'].empty) { id = doc['span.id'].value; } else { @@ -83,14 +85,14 @@ export async function fetchServicePathsFromTraceIds( } state.eventsById[id] = copy`, - }, - combine_script: { - lang: 'painless', - source: `return state.eventsById;`, - }, - reduce_script: { - lang: 'painless', - source: ` + }, + combine_script: { + lang: 'painless', + source: `return state.eventsById;`, + }, + reduce_script: { + lang: 'painless', + source: ` def getDestination ( def event ) { def destination = new HashMap(); destination['span.destination.service.resource'] = event['span.destination.service.resource']; @@ -206,28 +208,29 @@ export async function fetchServicePathsFromTraceIds( response.discoveredServices = discoveredServices; return response;`, + }, }, }, }, }, - }, - }; - - const serviceMapFromTraceIdsScriptResponse = await apmEventClient.search( - serviceMapParams - ); - - return serviceMapFromTraceIdsScriptResponse as { - aggregations?: { - service_map: { - value: { - paths: ConnectionNode[][]; - discoveredServices: Array<{ - from: ExternalConnectionNode; - to: ServiceConnectionNode; - }>; + }; + + const serviceMapFromTraceIdsScriptResponse = await apmEventClient.search( + serviceMapParams + ); + + return serviceMapFromTraceIdsScriptResponse as { + aggregations?: { + service_map: { + value: { + paths: ConnectionNode[][]; + discoveredServices: Array<{ + from: ExternalConnectionNode; + to: ServiceConnectionNode; + }>; + }; }; }; }; - }; + }); } diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts index 5b7589925d110..951484308db19 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts @@ -15,6 +15,7 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { getServicesProjection } from '../../projections/services'; import { mergeProjection } from '../../projections/util/merge_projection'; +import { withApmSpan } from '../../utils/with_apm_span'; import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { @@ -38,137 +39,147 @@ async function getConnectionData({ serviceName, environment, }: IEnvOptions) { - const { traceIds } = await getTraceSampleIds({ - setup, - serviceName, - environment, - }); + return withApmSpan('get_service_map_connections', async () => { + const { traceIds } = await getTraceSampleIds({ + setup, + serviceName, + environment, + }); + + const chunks = chunk( + traceIds, + setup.config['xpack.apm.serviceMapMaxTracesPerRequest'] + ); - const chunks = chunk( - traceIds, - setup.config['xpack.apm.serviceMapMaxTracesPerRequest'] - ); - - const init = { - connections: [], - discoveredServices: [], - }; - - if (!traceIds.length) { - return init; - } - - const chunkedResponses = await Promise.all( - chunks.map((traceIdsChunk) => - getServiceMapFromTraceIds({ - setup, - serviceName, - environment, - traceIds: traceIdsChunk, - }) - ) - ); - - return chunkedResponses.reduce((prev, current) => { - return { - connections: prev.connections.concat(current.connections), - discoveredServices: prev.discoveredServices.concat( - current.discoveredServices - ), + const init = { + connections: [], + discoveredServices: [], }; + + if (!traceIds.length) { + return init; + } + + const chunkedResponses = await withApmSpan( + 'get_service_paths_from_all_trace_ids', + () => + Promise.all( + chunks.map((traceIdsChunk) => + getServiceMapFromTraceIds({ + setup, + serviceName, + environment, + traceIds: traceIdsChunk, + }) + ) + ) + ); + + return chunkedResponses.reduce((prev, current) => { + return { + connections: prev.connections.concat(current.connections), + discoveredServices: prev.discoveredServices.concat( + current.discoveredServices + ), + }; + }); }); } async function getServicesData(options: IEnvOptions) { - const { setup, searchAggregatedTransactions } = options; + return withApmSpan('get_service_stats_for_service_map', async () => { + const { setup, searchAggregatedTransactions } = options; - const projection = getServicesProjection({ - setup: { ...setup, esFilter: [] }, - searchAggregatedTransactions, - }); + const projection = getServicesProjection({ + setup: { ...setup, esFilter: [] }, + searchAggregatedTransactions, + }); - let { filter } = projection.body.query.bool; + let { filter } = projection.body.query.bool; - if (options.serviceName) { - filter = filter.concat({ - term: { - [SERVICE_NAME]: options.serviceName, - }, - }); - } - - if (options.environment) { - filter = filter.concat(getEnvironmentUiFilterES(options.environment)); - } - - const params = mergeProjection(projection, { - body: { - size: 0, - query: { - bool: { - ...projection.body.query.bool, - filter, + if (options.serviceName) { + filter = filter.concat({ + term: { + [SERVICE_NAME]: options.serviceName, }, - }, - aggs: { - services: { - terms: { - field: projection.body.aggs.services.terms.field, - size: 500, + }); + } + + if (options.environment) { + filter = filter.concat(getEnvironmentUiFilterES(options.environment)); + } + + const params = mergeProjection(projection, { + body: { + size: 0, + query: { + bool: { + ...projection.body.query.bool, + filter, }, - aggs: { - agent_name: { - terms: { - field: AGENT_NAME, + }, + aggs: { + services: { + terms: { + field: projection.body.aggs.services.terms.field, + size: 500, + }, + aggs: { + agent_name: { + terms: { + field: AGENT_NAME, + }, }, }, }, }, }, - }, - }); + }); - const { apmEventClient } = setup; + const { apmEventClient } = setup; - const response = await apmEventClient.search(params); + const response = await apmEventClient.search(params); - return ( - response.aggregations?.services.buckets.map((bucket) => { - return { - [SERVICE_NAME]: bucket.key as string, - [AGENT_NAME]: - (bucket.agent_name.buckets[0]?.key as string | undefined) || '', - [SERVICE_ENVIRONMENT]: options.environment || null, - }; - }) || [] - ); + return ( + response.aggregations?.services.buckets.map((bucket) => { + return { + [SERVICE_NAME]: bucket.key as string, + [AGENT_NAME]: + (bucket.agent_name.buckets[0]?.key as string | undefined) || '', + [SERVICE_ENVIRONMENT]: options.environment || null, + }; + }) || [] + ); + }); } export type ConnectionsResponse = PromiseReturnType; export type ServicesResponse = PromiseReturnType; export type ServiceMapAPIResponse = PromiseReturnType; -export async function getServiceMap(options: IEnvOptions) { - const { logger } = options; - const anomaliesPromise = getServiceAnomalies( - options - - // always catch error to avoid breaking service maps if there is a problem with ML - ).catch((error) => { - logger.warn(`Unable to retrieve anomalies for service maps.`); - logger.error(error); - return DEFAULT_ANOMALIES; - }); +export function getServiceMap(options: IEnvOptions) { + return withApmSpan('get_service_map', async () => { + const { logger } = options; + const anomaliesPromise = getServiceAnomalies( + options + + // always catch error to avoid breaking service maps if there is a problem with ML + ).catch((error) => { + logger.warn(`Unable to retrieve anomalies for service maps.`); + logger.error(error); + return DEFAULT_ANOMALIES; + }); - const [connectionData, servicesData, anomalies] = await Promise.all([ - getConnectionData(options), - getServicesData(options), - anomaliesPromise, - ]); + const [connectionData, servicesData, anomalies] = await Promise.all([ + getConnectionData(options), + getServicesData(options), + anomaliesPromise, + ]); - return transformServiceMapResponses({ - ...connectionData, - services: servicesData, - anomalies, + return transformServiceMapResponses({ + ...connectionData, + services: servicesData, + anomalies, + }); }); } diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts index 71be5f5392b6f..213702bf06f4c 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts @@ -20,6 +20,7 @@ import { TRANSACTION_REQUEST, } from '../../../common/transaction_types'; import { rangeFilter } from '../../../common/utils/range_filter'; +import { withApmSpan } from '../../utils/with_apm_span'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, @@ -49,46 +50,48 @@ interface TaskParameters { setup: Setup; } -export async function getServiceMapServiceNodeInfo({ +export function getServiceMapServiceNodeInfo({ serviceName, setup, searchAggregatedTransactions, }: Options & { serviceName: string }) { - const { start, end, uiFilters } = setup; - - const filter: ESFilter[] = [ - { range: rangeFilter(start, end) }, - { term: { [SERVICE_NAME]: serviceName } }, - ...getEnvironmentUiFilterES(uiFilters.environment), - ]; - - const minutes = Math.abs((end - start) / (1000 * 60)); - const taskParams = { - environment: uiFilters.environment, - filter, - searchAggregatedTransactions, - minutes, - serviceName, - setup, - }; - - const [ - errorStats, - transactionStats, - cpuStats, - memoryStats, - ] = await Promise.all([ - getErrorStats(taskParams), - getTransactionStats(taskParams), - getCpuStats(taskParams), - getMemoryStats(taskParams), - ]); - return { - ...errorStats, - transactionStats, - ...cpuStats, - ...memoryStats, - }; + return withApmSpan('get_service_map_node_stats', async () => { + const { start, end, uiFilters } = setup; + + const filter: ESFilter[] = [ + { range: rangeFilter(start, end) }, + { term: { [SERVICE_NAME]: serviceName } }, + ...getEnvironmentUiFilterES(uiFilters.environment), + ]; + + const minutes = Math.abs((end - start) / (1000 * 60)); + const taskParams = { + environment: uiFilters.environment, + filter, + searchAggregatedTransactions, + minutes, + serviceName, + setup, + }; + + const [ + errorStats, + transactionStats, + cpuStats, + memoryStats, + ] = await Promise.all([ + getErrorStats(taskParams), + getTransactionStats(taskParams), + getCpuStats(taskParams), + getMemoryStats(taskParams), + ]); + return { + ...errorStats, + transactionStats, + ...cpuStats, + ...memoryStats, + }; + }); } async function getErrorStats({ @@ -102,20 +105,22 @@ async function getErrorStats({ environment?: string; searchAggregatedTransactions: boolean; }) { - const setupWithBlankUiFilters = { - ...setup, - uiFilters: { environment }, - esFilter: getEnvironmentUiFilterES(environment), - }; - const { noHits, average } = await getErrorRate({ - setup: setupWithBlankUiFilters, - serviceName, - searchAggregatedTransactions, + return withApmSpan('get_error_rate_for_service_map_node', async () => { + const setupWithBlankUiFilters = { + ...setup, + uiFilters: { environment }, + esFilter: getEnvironmentUiFilterES(environment), + }; + const { noHits, average } = await getErrorRate({ + setup: setupWithBlankUiFilters, + serviceName, + searchAggregatedTransactions, + }); + return { avgErrorRate: noHits ? null : average }; }); - return { avgErrorRate: noHits ? null : average }; } -async function getTransactionStats({ +function getTransactionStats({ setup, filter, minutes, @@ -124,95 +129,67 @@ async function getTransactionStats({ avgTransactionDuration: number | null; avgRequestsPerMinute: number | null; }> { - const { apmEventClient } = setup; - - const params = { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - ...filter, - ...getDocumentTypeFilterForAggregatedTransactions( - searchAggregatedTransactions - ), - { - terms: { - [TRANSACTION_TYPE]: [ - TRANSACTION_REQUEST, - TRANSACTION_PAGE_LOAD, - ], + return withApmSpan('get_transaction_stats_for_service_map_node', async () => { + const { apmEventClient } = setup; + + const params = { + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + ...filter, + ...getDocumentTypeFilterForAggregatedTransactions( + searchAggregatedTransactions + ), + { + terms: { + [TRANSACTION_TYPE]: [ + TRANSACTION_REQUEST, + TRANSACTION_PAGE_LOAD, + ], + }, }, - }, - ], + ], + }, }, - }, - track_total_hits: true, - aggs: { - duration: { - avg: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), + track_total_hits: true, + aggs: { + duration: { + avg: { + field: getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ), + }, }, }, }, - }, - }; - const response = await apmEventClient.search(params); + }; + const response = await apmEventClient.search(params); - const totalRequests = response.hits.total.value; + const totalRequests = response.hits.total.value; - return { - avgTransactionDuration: response.aggregations?.duration.value ?? null, - avgRequestsPerMinute: totalRequests > 0 ? totalRequests / minutes : null, - }; + return { + avgTransactionDuration: response.aggregations?.duration.value ?? null, + avgRequestsPerMinute: totalRequests > 0 ? totalRequests / minutes : null, + }; + }); } -async function getCpuStats({ +function getCpuStats({ setup, filter, }: TaskParameters): Promise<{ avgCpuUsage: number | null }> { - const { apmEventClient } = setup; - - const response = await apmEventClient.search({ - apm: { - events: [ProcessorEvent.metric], - }, - body: { - size: 0, - query: { - bool: { - filter: [...filter, { exists: { field: METRIC_SYSTEM_CPU_PERCENT } }], - }, - }, - aggs: { avgCpuUsage: { avg: { field: METRIC_SYSTEM_CPU_PERCENT } } }, - }, - }); + return withApmSpan('get_avg_cpu_usage_for_service_map_node', async () => { + const { apmEventClient } = setup; - return { avgCpuUsage: response.aggregations?.avgCpuUsage.value ?? null }; -} - -async function getMemoryStats({ - setup, - filter, -}: TaskParameters): Promise<{ avgMemoryUsage: number | null }> { - const { apmEventClient } = setup; - - const getAvgMemoryUsage = async ({ - additionalFilters, - script, - }: { - additionalFilters: ESFilter[]; - script: typeof percentCgroupMemoryUsedScript; - }) => { const response = await apmEventClient.search({ apm: { events: [ProcessorEvent.metric], @@ -221,34 +198,72 @@ async function getMemoryStats({ size: 0, query: { bool: { - filter: [...filter, ...additionalFilters], + filter: [ + ...filter, + { exists: { field: METRIC_SYSTEM_CPU_PERCENT } }, + ], }, }, - aggs: { - avgMemoryUsage: { avg: { script } }, - }, + aggs: { avgCpuUsage: { avg: { field: METRIC_SYSTEM_CPU_PERCENT } } }, }, }); - return response.aggregations?.avgMemoryUsage.value ?? null; - }; - - let avgMemoryUsage = await getAvgMemoryUsage({ - additionalFilters: [ - { exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES } }, - ], - script: percentCgroupMemoryUsedScript, + return { avgCpuUsage: response.aggregations?.avgCpuUsage.value ?? null }; }); +} - if (!avgMemoryUsage) { - avgMemoryUsage = await getAvgMemoryUsage({ +function getMemoryStats({ + setup, + filter, +}: TaskParameters): Promise<{ avgMemoryUsage: number | null }> { + return withApmSpan('get_memory_stats_for_service_map_node', async () => { + const { apmEventClient } = setup; + + const getAvgMemoryUsage = ({ + additionalFilters, + script, + }: { + additionalFilters: ESFilter[]; + script: typeof percentCgroupMemoryUsedScript; + }) => { + return withApmSpan('get_avg_memory_for_service_map_node', async () => { + const response = await apmEventClient.search({ + apm: { + events: [ProcessorEvent.metric], + }, + body: { + size: 0, + query: { + bool: { + filter: [...filter, ...additionalFilters], + }, + }, + aggs: { + avgMemoryUsage: { avg: { script } }, + }, + }, + }); + return response.aggregations?.avgMemoryUsage.value ?? null; + }); + }; + + let avgMemoryUsage = await getAvgMemoryUsage({ additionalFilters: [ - { exists: { field: METRIC_SYSTEM_FREE_MEMORY } }, - { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, + { exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES } }, ], - script: percentSystemMemoryUsedScript, + script: percentCgroupMemoryUsedScript, }); - } - return { avgMemoryUsage }; + if (!avgMemoryUsage) { + avgMemoryUsage = await getAvgMemoryUsage({ + additionalFilters: [ + { exists: { field: METRIC_SYSTEM_FREE_MEMORY } }, + { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, + ], + script: percentSystemMemoryUsedScript, + }); + } + + return { avgMemoryUsage }; + }); } diff --git a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts index aa9105d2edb30..deb9104a83905 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts @@ -17,12 +17,13 @@ import { import { ProcessorEvent } from '../../../common/processor_event'; import { SERVICE_MAP_TIMEOUT_ERROR } from '../../../common/service_map'; import { rangeFilter } from '../../../common/utils/range_filter'; +import { withApmSpan } from '../../utils/with_apm_span'; import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; const MAX_TRACES_TO_INSPECT = 1000; -export async function getTraceSampleIds({ +export function getTraceSampleIds({ serviceName, environment, setup, @@ -31,90 +32,92 @@ export async function getTraceSampleIds({ environment?: string; setup: Setup & SetupTimeRange; }) { - const { start, end, apmEventClient, config } = setup; + return withApmSpan('get_trace_sample_ids', async () => { + const { start, end, apmEventClient, config } = setup; - const rangeQuery = { range: rangeFilter(start, end) }; + const rangeQuery = { range: rangeFilter(start, end) }; - const query = { - bool: { - filter: [ - { - exists: { - field: SPAN_DESTINATION_SERVICE_RESOURCE, + const query = { + bool: { + filter: [ + { + exists: { + field: SPAN_DESTINATION_SERVICE_RESOURCE, + }, }, - }, - rangeQuery, - ] as ESFilter[], - }, - } as { bool: { filter: ESFilter[]; must_not?: ESFilter[] | ESFilter } }; + rangeQuery, + ] as ESFilter[], + }, + } as { bool: { filter: ESFilter[]; must_not?: ESFilter[] | ESFilter } }; - if (serviceName) { - query.bool.filter.push({ term: { [SERVICE_NAME]: serviceName } }); - } + if (serviceName) { + query.bool.filter.push({ term: { [SERVICE_NAME]: serviceName } }); + } - query.bool.filter.push(...getEnvironmentUiFilterES(environment)); + query.bool.filter.push(...getEnvironmentUiFilterES(environment)); - const fingerprintBucketSize = serviceName - ? config['xpack.apm.serviceMapFingerprintBucketSize'] - : config['xpack.apm.serviceMapFingerprintGlobalBucketSize']; + const fingerprintBucketSize = serviceName + ? config['xpack.apm.serviceMapFingerprintBucketSize'] + : config['xpack.apm.serviceMapFingerprintGlobalBucketSize']; - const traceIdBucketSize = serviceName - ? config['xpack.apm.serviceMapTraceIdBucketSize'] - : config['xpack.apm.serviceMapTraceIdGlobalBucketSize']; + const traceIdBucketSize = serviceName + ? config['xpack.apm.serviceMapTraceIdBucketSize'] + : config['xpack.apm.serviceMapTraceIdGlobalBucketSize']; - const samplerShardSize = traceIdBucketSize * 10; + const samplerShardSize = traceIdBucketSize * 10; - const params = { - apm: { - events: [ProcessorEvent.span], - }, - body: { - size: 0, - query, - aggs: { - connections: { - composite: { - sources: [ - { - [SPAN_DESTINATION_SERVICE_RESOURCE]: { - terms: { - field: SPAN_DESTINATION_SERVICE_RESOURCE, + const params = { + apm: { + events: [ProcessorEvent.span], + }, + body: { + size: 0, + query, + aggs: { + connections: { + composite: { + sources: [ + { + [SPAN_DESTINATION_SERVICE_RESOURCE]: { + terms: { + field: SPAN_DESTINATION_SERVICE_RESOURCE, + }, }, }, - }, - { - [SERVICE_NAME]: { - terms: { - field: SERVICE_NAME, + { + [SERVICE_NAME]: { + terms: { + field: SERVICE_NAME, + }, }, }, - }, - { - [SERVICE_ENVIRONMENT]: { - terms: { - field: SERVICE_ENVIRONMENT, - missing_bucket: true, + { + [SERVICE_ENVIRONMENT]: { + terms: { + field: SERVICE_ENVIRONMENT, + missing_bucket: true, + }, }, }, - }, - ], - size: fingerprintBucketSize, - }, - aggs: { - sample: { - sampler: { - shard_size: samplerShardSize, - }, - aggs: { - trace_ids: { - terms: { - field: TRACE_ID, - size: traceIdBucketSize, - execution_hint: 'map' as const, - // remove bias towards large traces by sorting on trace.id - // which will be random-esque - order: { - _key: 'desc' as const, + ], + size: fingerprintBucketSize, + }, + aggs: { + sample: { + sampler: { + shard_size: samplerShardSize, + }, + aggs: { + trace_ids: { + terms: { + field: TRACE_ID, + size: traceIdBucketSize, + execution_hint: 'map' as const, + // remove bias towards large traces by sorting on trace.id + // which will be random-esque + order: { + _key: 'desc' as const, + }, }, }, }, @@ -123,33 +126,34 @@ export async function getTraceSampleIds({ }, }, }, - }, - }; + }; - try { - const tracesSampleResponse = await apmEventClient.search(params); - // make sure at least one trace per composite/connection bucket - // is queried - const traceIdsWithPriority = - tracesSampleResponse.aggregations?.connections.buckets.flatMap((bucket) => - bucket.sample.trace_ids.buckets.map((sampleDocBucket, index) => ({ - traceId: sampleDocBucket.key as string, - priority: index, - })) - ) || []; + try { + const tracesSampleResponse = await apmEventClient.search(params); + // make sure at least one trace per composite/connection bucket + // is queried + const traceIdsWithPriority = + tracesSampleResponse.aggregations?.connections.buckets.flatMap( + (bucket) => + bucket.sample.trace_ids.buckets.map((sampleDocBucket, index) => ({ + traceId: sampleDocBucket.key as string, + priority: index, + })) + ) || []; - const traceIds = take( - uniq( - sortBy(traceIdsWithPriority, 'priority').map(({ traceId }) => traceId) - ), - MAX_TRACES_TO_INSPECT - ); + const traceIds = take( + uniq( + sortBy(traceIdsWithPriority, 'priority').map(({ traceId }) => traceId) + ), + MAX_TRACES_TO_INSPECT + ); - return { traceIds }; - } catch (error) { - if ('displayName' in error && error.displayName === 'RequestTimeout') { - throw Boom.internal(SERVICE_MAP_TIMEOUT_ERROR); + return { traceIds }; + } catch (error) { + if ('displayName' in error && error.displayName === 'RequestTimeout') { + throw Boom.internal(SERVICE_MAP_TIMEOUT_ERROR); + } + throw error; } - throw error; - } + }); } diff --git a/x-pack/plugins/apm/server/lib/service_nodes/index.ts b/x-pack/plugins/apm/server/lib/service_nodes/index.ts index 01a9f3fdac3ec..a22c732a5e8ce 100644 --- a/x-pack/plugins/apm/server/lib/service_nodes/index.ts +++ b/x-pack/plugins/apm/server/lib/service_nodes/index.ts @@ -14,76 +14,79 @@ import { import { SERVICE_NODE_NAME_MISSING } from '../../../common/service_nodes'; import { getServiceNodesProjection } from '../../projections/service_nodes'; import { mergeProjection } from '../../projections/util/merge_projection'; +import { withApmSpan } from '../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; -const getServiceNodes = async ({ +const getServiceNodes = ({ setup, serviceName, }: { setup: Setup & SetupTimeRange; serviceName: string; }) => { - const { apmEventClient } = setup; + return withApmSpan('get_service_nodes', async () => { + const { apmEventClient } = setup; - const projection = getServiceNodesProjection({ setup, serviceName }); + const projection = getServiceNodesProjection({ setup, serviceName }); - const params = mergeProjection(projection, { - body: { - aggs: { - nodes: { - terms: { - ...projection.body.aggs.nodes.terms, - size: 10000, - missing: SERVICE_NODE_NAME_MISSING, - }, - aggs: { - cpu: { - avg: { - field: METRIC_PROCESS_CPU_PERCENT, - }, + const params = mergeProjection(projection, { + body: { + aggs: { + nodes: { + terms: { + ...projection.body.aggs.nodes.terms, + size: 10000, + missing: SERVICE_NODE_NAME_MISSING, }, - heapMemory: { - avg: { - field: METRIC_JAVA_HEAP_MEMORY_USED, + aggs: { + cpu: { + avg: { + field: METRIC_PROCESS_CPU_PERCENT, + }, }, - }, - nonHeapMemory: { - avg: { - field: METRIC_JAVA_NON_HEAP_MEMORY_USED, + heapMemory: { + avg: { + field: METRIC_JAVA_HEAP_MEMORY_USED, + }, }, - }, - threadCount: { - max: { - field: METRIC_JAVA_THREAD_COUNT, + nonHeapMemory: { + avg: { + field: METRIC_JAVA_NON_HEAP_MEMORY_USED, + }, + }, + threadCount: { + max: { + field: METRIC_JAVA_THREAD_COUNT, + }, }, }, }, }, }, - }, - }); + }); - const response = await apmEventClient.search(params); + const response = await apmEventClient.search(params); - if (!response.aggregations) { - return []; - } + if (!response.aggregations) { + return []; + } - return response.aggregations.nodes.buckets - .map((bucket) => ({ - name: bucket.key as string, - cpu: bucket.cpu.value, - heapMemory: bucket.heapMemory.value, - nonHeapMemory: bucket.nonHeapMemory.value, - threadCount: bucket.threadCount.value, - })) - .filter( - (item) => - item.cpu !== null || - item.heapMemory !== null || - item.nonHeapMemory !== null || - item.threadCount != null - ); + return response.aggregations.nodes.buckets + .map((bucket) => ({ + name: bucket.key as string, + cpu: bucket.cpu.value, + heapMemory: bucket.heapMemory.value, + nonHeapMemory: bucket.nonHeapMemory.value, + threadCount: bucket.threadCount.value, + })) + .filter( + (item) => + item.cpu !== null || + item.heapMemory !== null || + item.nonHeapMemory !== null || + item.threadCount != null + ); + }); }; export { getServiceNodes }; diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts index d701c5380b2e4..7c746aac29af9 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_derived_service_annotations.ts @@ -13,6 +13,7 @@ import { SERVICE_VERSION, } from '../../../../common/elasticsearch_fieldnames'; import { rangeFilter } from '../../../../common/utils/range_filter'; +import { withApmSpan } from '../../../utils/with_apm_span'; import { getDocumentTypeFilterForAggregatedTransactions, getProcessorEventForAggregatedTransactions, @@ -31,93 +32,97 @@ export async function getDerivedServiceAnnotations({ setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; }) { - const { start, end, apmEventClient } = setup; + return withApmSpan('get_derived_service_annotations', async () => { + const { start, end, apmEventClient } = setup; - const filter: ESFilter[] = [ - { term: { [SERVICE_NAME]: serviceName } }, - ...getDocumentTypeFilterForAggregatedTransactions( - searchAggregatedTransactions - ), - ...getEnvironmentUiFilterES(environment), - ]; + const filter: ESFilter[] = [ + { term: { [SERVICE_NAME]: serviceName } }, + ...getDocumentTypeFilterForAggregatedTransactions( + searchAggregatedTransactions + ), + ...getEnvironmentUiFilterES(environment), + ]; - const versions = - ( - await apmEventClient.search({ - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [...filter, { range: rangeFilter(start, end) }], - }, + const versions = + ( + await apmEventClient.search({ + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], }, - aggs: { - versions: { - terms: { - field: SERVICE_VERSION, + body: { + size: 0, + query: { + bool: { + filter: [...filter, { range: rangeFilter(start, end) }], + }, + }, + aggs: { + versions: { + terms: { + field: SERVICE_VERSION, + }, }, }, }, - }, - }) - ).aggregations?.versions.buckets.map((bucket) => bucket.key) ?? []; + }) + ).aggregations?.versions.buckets.map((bucket) => bucket.key) ?? []; - if (versions.length <= 1) { - return []; - } - const annotations = await Promise.all( - versions.map(async (version) => { - const response = await apmEventClient.search({ - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [...filter, { term: { [SERVICE_VERSION]: version } }], + if (versions.length <= 1) { + return []; + } + const annotations = await Promise.all( + versions.map(async (version) => { + return withApmSpan('get_first_seen_of_version', async () => { + const response = await apmEventClient.search({ + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], }, - }, - aggs: { - first_seen: { - min: { - field: '@timestamp', + body: { + size: 0, + query: { + bool: { + filter: [...filter, { term: { [SERVICE_VERSION]: version } }], + }, + }, + aggs: { + first_seen: { + min: { + field: '@timestamp', + }, + }, }, }, - }, - }, - }); + }); - const firstSeen = response.aggregations?.first_seen.value; + const firstSeen = response.aggregations?.first_seen.value; - if (!isNumber(firstSeen)) { - throw new Error( - 'First seen for version was unexpectedly undefined or null.' - ); - } + if (!isNumber(firstSeen)) { + throw new Error( + 'First seen for version was unexpectedly undefined or null.' + ); + } - if (firstSeen < start || firstSeen > end) { - return null; - } + if (firstSeen < start || firstSeen > end) { + return null; + } - return { - type: AnnotationType.VERSION, - id: version, - '@timestamp': firstSeen, - text: version, - }; - }) - ); - return annotations.filter(Boolean) as Annotation[]; + return { + type: AnnotationType.VERSION, + id: version, + '@timestamp': firstSeen, + text: version, + }; + }); + }) + ); + return annotations.filter(Boolean) as Annotation[]; + }); } diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts index fafd0ce3d3cdc..20629a4b4f553 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts @@ -15,8 +15,9 @@ import { Annotation, AnnotationType } from '../../../../common/annotations'; import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; +import { withApmSpan } from '../../../utils/with_apm_span'; -export async function getStoredAnnotations({ +export function getStoredAnnotations({ setup, serviceName, environment, @@ -31,56 +32,58 @@ export async function getStoredAnnotations({ annotationsClient: ScopedAnnotationsClient; logger: Logger; }): Promise { - const body = { - size: 50, - query: { - bool: { - filter: [ - { - range: rangeFilter(setup.start, setup.end), - }, - { term: { 'annotation.type': 'deployment' } }, - { term: { tags: 'apm' } }, - { term: { [SERVICE_NAME]: serviceName } }, - ...getEnvironmentUiFilterES(environment), - ], + return withApmSpan('get_stored_annotations', async () => { + const body = { + size: 50, + query: { + bool: { + filter: [ + { + range: rangeFilter(setup.start, setup.end), + }, + { term: { 'annotation.type': 'deployment' } }, + { term: { tags: 'apm' } }, + { term: { [SERVICE_NAME]: serviceName } }, + ...getEnvironmentUiFilterES(environment), + ], + }, }, - }, - }; + }; - try { - const response: ESSearchResponse< - ESAnnotation, - { body: typeof body } - > = await unwrapEsResponse( - client.search({ - index: annotationsClient.index, - body, - }) - ); + try { + const response: ESSearchResponse< + ESAnnotation, + { body: typeof body } + > = await unwrapEsResponse( + client.search({ + index: annotationsClient.index, + body, + }) + ); - return response.hits.hits.map((hit) => { - return { - type: AnnotationType.VERSION, - id: hit._id, - '@timestamp': new Date(hit._source['@timestamp']).getTime(), - text: hit._source.message, - }; - }); - } catch (error) { - // index is only created when an annotation has been indexed, - // so we should handle this error gracefully - if (error.body?.error?.type === 'index_not_found_exception') { - return []; - } + return response.hits.hits.map((hit) => { + return { + type: AnnotationType.VERSION, + id: hit._id, + '@timestamp': new Date(hit._source['@timestamp']).getTime(), + text: hit._source.message, + }; + }); + } catch (error) { + // index is only created when an annotation has been indexed, + // so we should handle this error gracefully + if (error.body?.error?.type === 'index_not_found_exception') { + return []; + } - if (error.body?.error?.type === 'security_exception') { - logger.warn( - `Unable to get stored annotations due to a security exception. Please make sure that the user has 'indices:data/read/search' permissions for ${annotationsClient.index}` - ); - return []; - } + if (error.body?.error?.type === 'security_exception') { + logger.warn( + `Unable to get stored annotations due to a security exception. Please make sure that the user has 'indices:data/read/search' permissions for ${annotationsClient.index}` + ); + return []; + } - throw error; - } + throw error; + } + }); } diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts index f7a781fb7c075..ef2b50cbdbedf 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_destination_map.ts @@ -24,8 +24,9 @@ import { ProcessorEvent } from '../../../../common/processor_event'; import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; import { joinByKey } from '../../../../common/utils/join_by_key'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; +import { withApmSpan } from '../../../utils/with_apm_span'; -export const getDestinationMap = async ({ +export const getDestinationMap = ({ setup, serviceName, environment, @@ -34,171 +35,183 @@ export const getDestinationMap = async ({ serviceName: string; environment: string; }) => { - const { start, end, apmEventClient } = setup; - - const response = await apmEventClient.search({ - apm: { - events: [ProcessorEvent.span], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { exists: { field: SPAN_DESTINATION_SERVICE_RESOURCE } }, - { range: rangeFilter(start, end) }, - ...getEnvironmentUiFilterES(environment), - ], + return withApmSpan('get_service_destination_map', async () => { + const { start, end, apmEventClient } = setup; + + const response = await withApmSpan('get_exit_span_samples', async () => + apmEventClient.search({ + apm: { + events: [ProcessorEvent.span], }, - }, - aggs: { - connections: { - composite: { - size: 1000, - sources: [ - { - [SPAN_DESTINATION_SERVICE_RESOURCE]: { - terms: { field: SPAN_DESTINATION_SERVICE_RESOURCE }, - }, - }, - // make sure we get samples for both successful - // and failed calls - { [EVENT_OUTCOME]: { terms: { field: EVENT_OUTCOME } } }, - ], + body: { + size: 0, + query: { + bool: { + filter: [ + { term: { [SERVICE_NAME]: serviceName } }, + { exists: { field: SPAN_DESTINATION_SERVICE_RESOURCE } }, + { range: rangeFilter(start, end) }, + ...getEnvironmentUiFilterES(environment), + ], + }, }, aggs: { - docs: { - top_hits: { - docvalue_fields: [SPAN_TYPE, SPAN_SUBTYPE, SPAN_ID] as const, - _source: false, - sort: { - '@timestamp': 'desc', + connections: { + composite: { + size: 1000, + sources: [ + { + [SPAN_DESTINATION_SERVICE_RESOURCE]: { + terms: { field: SPAN_DESTINATION_SERVICE_RESOURCE }, + }, + }, + // make sure we get samples for both successful + // and failed calls + { [EVENT_OUTCOME]: { terms: { field: EVENT_OUTCOME } } }, + ], + }, + aggs: { + docs: { + top_hits: { + docvalue_fields: [ + SPAN_TYPE, + SPAN_SUBTYPE, + SPAN_ID, + ] as const, + _source: false, + sort: { + '@timestamp': 'desc', + }, + }, }, }, }, }, }, + }) + ); + + const outgoingConnections = + response.aggregations?.connections.buckets.map((bucket) => { + const doc = bucket.docs.hits.hits[0]; + + return { + [SPAN_DESTINATION_SERVICE_RESOURCE]: String( + bucket.key[SPAN_DESTINATION_SERVICE_RESOURCE] + ), + [SPAN_ID]: String(doc.fields[SPAN_ID]?.[0]), + [SPAN_TYPE]: String(doc.fields[SPAN_TYPE]?.[0] ?? ''), + [SPAN_SUBTYPE]: String(doc.fields[SPAN_SUBTYPE]?.[0] ?? ''), + }; + }) ?? []; + + const transactionResponse = await withApmSpan( + 'get_transactions_for_exit_spans', + () => + apmEventClient.search({ + apm: { + events: [ProcessorEvent.transaction], + }, + body: { + query: { + bool: { + filter: [ + { + terms: { + [PARENT_ID]: outgoingConnections.map( + (connection) => connection[SPAN_ID] + ), + }, + }, + { range: rangeFilter(start, end) }, + ], + }, + }, + size: outgoingConnections.length, + docvalue_fields: [ + SERVICE_NAME, + SERVICE_ENVIRONMENT, + AGENT_NAME, + PARENT_ID, + ] as const, + _source: false, + }, + }) + ); + + const incomingConnections = transactionResponse.hits.hits.map((hit) => ({ + [SPAN_ID]: String(hit.fields[PARENT_ID]![0]), + service: { + name: String(hit.fields[SERVICE_NAME]![0]), + environment: String(hit.fields[SERVICE_ENVIRONMENT]?.[0] ?? ''), + agentName: hit.fields[AGENT_NAME]![0] as AgentName, }, - }, - }); - - const outgoingConnections = - response.aggregations?.connections.buckets.map((bucket) => { - const doc = bucket.docs.hits.hits[0]; + })); + + // merge outgoing spans with transactions by span.id/parent.id + const joinedBySpanId = joinByKey( + [...outgoingConnections, ...incomingConnections], + SPAN_ID + ); + + // we could have multiple connections per address because + // of multiple event outcomes + const dedupedConnectionsByAddress = joinByKey( + joinedBySpanId, + SPAN_DESTINATION_SERVICE_RESOURCE + ); + + // identify a connection by either service.name, service.environment, agent.name + // OR span.destination.service.resource + + const connectionsWithId = dedupedConnectionsByAddress.map((connection) => { + const id = + 'service' in connection + ? { service: connection.service } + : pickKeys(connection, SPAN_DESTINATION_SERVICE_RESOURCE); return { - [SPAN_DESTINATION_SERVICE_RESOURCE]: String( - bucket.key[SPAN_DESTINATION_SERVICE_RESOURCE] - ), - [SPAN_ID]: String(doc.fields[SPAN_ID]?.[0]), - [SPAN_TYPE]: String(doc.fields[SPAN_TYPE]?.[0] ?? ''), - [SPAN_SUBTYPE]: String(doc.fields[SPAN_SUBTYPE]?.[0] ?? ''), + ...connection, + id, }; - }) ?? []; - - const transactionResponse = await apmEventClient.search({ - apm: { - events: [ProcessorEvent.transaction], - }, - body: { - query: { - bool: { - filter: [ - { - terms: { - [PARENT_ID]: outgoingConnections.map( - (connection) => connection[SPAN_ID] - ), - }, - }, - { range: rangeFilter(start, end) }, - ], - }, - }, - size: outgoingConnections.length, - docvalue_fields: [ - SERVICE_NAME, - SERVICE_ENVIRONMENT, - AGENT_NAME, - PARENT_ID, - ] as const, - _source: false, - }, - }); + }); - const incomingConnections = transactionResponse.hits.hits.map((hit) => ({ - [SPAN_ID]: String(hit.fields[PARENT_ID]![0]), - service: { - name: String(hit.fields[SERVICE_NAME]![0]), - environment: String(hit.fields[SERVICE_ENVIRONMENT]?.[0] ?? ''), - agentName: hit.fields[AGENT_NAME]![0] as AgentName, - }, - })); - - // merge outgoing spans with transactions by span.id/parent.id - const joinedBySpanId = joinByKey( - [...outgoingConnections, ...incomingConnections], - SPAN_ID - ); - - // we could have multiple connections per address because - // of multiple event outcomes - const dedupedConnectionsByAddress = joinByKey( - joinedBySpanId, - SPAN_DESTINATION_SERVICE_RESOURCE - ); - - // identify a connection by either service.name, service.environment, agent.name - // OR span.destination.service.resource - - const connectionsWithId = dedupedConnectionsByAddress.map((connection) => { - const id = - 'service' in connection - ? { service: connection.service } - : pickKeys(connection, SPAN_DESTINATION_SERVICE_RESOURCE); - - return { - ...connection, - id, - }; - }); + const dedupedConnectionsById = joinByKey(connectionsWithId, 'id'); - const dedupedConnectionsById = joinByKey(connectionsWithId, 'id'); - - const connectionsByAddress = keyBy( - connectionsWithId, - SPAN_DESTINATION_SERVICE_RESOURCE - ); - - // per span.destination.service.resource, return merged/deduped item - return mapValues(connectionsByAddress, ({ id }) => { - const connection = dedupedConnectionsById.find((dedupedConnection) => - isEqual(id, dedupedConnection.id) - )!; - - return { - id, - span: { - type: connection[SPAN_TYPE], - subtype: connection[SPAN_SUBTYPE], - destination: { - service: { - resource: connection[SPAN_DESTINATION_SERVICE_RESOURCE], - }, - }, - }, - ...('service' in connection && connection.service - ? { + const connectionsByAddress = keyBy( + connectionsWithId, + SPAN_DESTINATION_SERVICE_RESOURCE + ); + + // per span.destination.service.resource, return merged/deduped item + return mapValues(connectionsByAddress, ({ id }) => { + const connection = dedupedConnectionsById.find((dedupedConnection) => + isEqual(id, dedupedConnection.id) + )!; + + return { + id, + span: { + type: connection[SPAN_TYPE], + subtype: connection[SPAN_SUBTYPE], + destination: { service: { - name: connection.service.name, - environment: connection.service.environment, - }, - agent: { - name: connection.service.agentName, + resource: connection[SPAN_DESTINATION_SERVICE_RESOURCE], }, - } - : {}), - }; + }, + }, + ...('service' in connection && connection.service + ? { + service: { + name: connection.service.name, + environment: connection.service.environment, + }, + agent: { + name: connection.service.agentName, + }, + } + : {}), + }; + }); }); }; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts index 5500b9c30b20e..9a020daa7e095 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/get_metrics.ts @@ -19,8 +19,9 @@ import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_e import { getBucketSize } from '../../helpers/get_bucket_size'; import { EventOutcome } from '../../../../common/event_outcome'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; +import { withApmSpan } from '../../../utils/with_apm_span'; -export const getMetrics = async ({ +export const getMetrics = ({ setup, serviceName, environment, @@ -31,61 +32,65 @@ export const getMetrics = async ({ environment: string; numBuckets: number; }) => { - const { start, end, apmEventClient } = setup; + return withApmSpan('get_service_destination_metrics', async () => { + const { start, end, apmEventClient } = setup; - const response = await apmEventClient.search({ - apm: { - events: [ProcessorEvent.metric], - }, - body: { - track_total_hits: true, - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { exists: { field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT } }, - { range: rangeFilter(start, end) }, - ...getEnvironmentUiFilterES(environment), - ], - }, + const response = await apmEventClient.search({ + apm: { + events: [ProcessorEvent.metric], }, - aggs: { - connections: { - terms: { - field: SPAN_DESTINATION_SERVICE_RESOURCE, - size: 100, - }, - aggs: { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: getBucketSize({ start, end, numBuckets }) - .intervalString, - extended_bounds: { - min: start, - max: end, - }, + body: { + track_total_hits: true, + size: 0, + query: { + bool: { + filter: [ + { term: { [SERVICE_NAME]: serviceName } }, + { + exists: { field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT }, }, - aggs: { - latency_sum: { - sum: { - field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM, + { range: rangeFilter(start, end) }, + ...getEnvironmentUiFilterES(environment), + ], + }, + }, + aggs: { + connections: { + terms: { + field: SPAN_DESTINATION_SERVICE_RESOURCE, + size: 100, + }, + aggs: { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: getBucketSize({ start, end, numBuckets }) + .intervalString, + extended_bounds: { + min: start, + max: end, }, }, - count: { - sum: { - field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT, + aggs: { + latency_sum: { + sum: { + field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM, + }, }, - }, - [EVENT_OUTCOME]: { - terms: { - field: EVENT_OUTCOME, + count: { + sum: { + field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT, + }, }, - aggs: { - count: { - sum: { - field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT, + [EVENT_OUTCOME]: { + terms: { + field: EVENT_OUTCOME, + }, + aggs: { + count: { + sum: { + field: SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT, + }, }, }, }, @@ -95,47 +100,47 @@ export const getMetrics = async ({ }, }, }, - }, - }); + }); - return ( - response.aggregations?.connections.buckets.map((bucket) => ({ - span: { - destination: { - service: { - resource: String(bucket.key), + return ( + response.aggregations?.connections.buckets.map((bucket) => ({ + span: { + destination: { + service: { + resource: String(bucket.key), + }, }, }, - }, - value: { - count: sum( - bucket.timeseries.buckets.map( - (dateBucket) => dateBucket.count.value ?? 0 - ) - ), - latency_sum: sum( - bucket.timeseries.buckets.map( - (dateBucket) => dateBucket.latency_sum.value ?? 0 - ) - ), - error_count: sum( - bucket.timeseries.buckets.flatMap( - (dateBucket) => - dateBucket[EVENT_OUTCOME].buckets.find( - (outcomeBucket) => outcomeBucket.key === EventOutcome.failure - )?.count.value ?? 0 - ) - ), - }, - timeseries: bucket.timeseries.buckets.map((dateBucket) => ({ - x: dateBucket.key, - count: dateBucket.count.value ?? 0, - latency_sum: dateBucket.latency_sum.value ?? 0, - error_count: - dateBucket[EVENT_OUTCOME].buckets.find( - (outcomeBucket) => outcomeBucket.key === EventOutcome.failure - )?.count.value ?? 0, - })), - })) ?? [] - ); + value: { + count: sum( + bucket.timeseries.buckets.map( + (dateBucket) => dateBucket.count.value ?? 0 + ) + ), + latency_sum: sum( + bucket.timeseries.buckets.map( + (dateBucket) => dateBucket.latency_sum.value ?? 0 + ) + ), + error_count: sum( + bucket.timeseries.buckets.flatMap( + (dateBucket) => + dateBucket[EVENT_OUTCOME].buckets.find( + (outcomeBucket) => outcomeBucket.key === EventOutcome.failure + )?.count.value ?? 0 + ) + ), + }, + timeseries: bucket.timeseries.buckets.map((dateBucket) => ({ + x: dateBucket.key, + count: dateBucket.count.value ?? 0, + latency_sum: dateBucket.latency_sum.value ?? 0, + error_count: + dateBucket[EVENT_OUTCOME].buckets.find( + (outcomeBucket) => outcomeBucket.key === EventOutcome.failure + )?.count.value ?? 0, + })), + })) ?? [] + ); + }); }; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts index 5ccb6fb19cbda..19f306f5cb803 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts @@ -16,6 +16,7 @@ import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { getMetrics } from './get_metrics'; import { getDestinationMap } from './get_destination_map'; import { calculateThroughput } from '../../helpers/calculate_throughput'; +import { withApmSpan } from '../../../utils/with_apm_span'; export type ServiceDependencyItem = { name: string; @@ -42,7 +43,7 @@ export type ServiceDependencyItem = { | { type: 'external'; spanType?: string; spanSubtype?: string } ); -export async function getServiceDependencies({ +export function getServiceDependencies({ setup, serviceName, environment, @@ -53,171 +54,177 @@ export async function getServiceDependencies({ environment: string; numBuckets: number; }): Promise { - const { start, end } = setup; - const [allMetrics, destinationMap] = await Promise.all([ - getMetrics({ - setup, - serviceName, - environment, - numBuckets, - }), - getDestinationMap({ - setup, - serviceName, - environment, - }), - ]); - - const metricsWithDestinationIds = allMetrics.map((metricItem) => { - const spanDestination = metricItem.span.destination.service.resource; - - const destination = maybe(destinationMap[spanDestination]); - const id = destination?.id || { - [SPAN_DESTINATION_SERVICE_RESOURCE]: spanDestination, - }; - - return merge( - { - id, - metrics: [metricItem], - span: { - destination: { - service: { - resource: spanDestination, + return withApmSpan('get_service_dependencies', async () => { + const { start, end } = setup; + const [allMetrics, destinationMap] = await Promise.all([ + getMetrics({ + setup, + serviceName, + environment, + numBuckets, + }), + getDestinationMap({ + setup, + serviceName, + environment, + }), + ]); + + const metricsWithDestinationIds = allMetrics.map((metricItem) => { + const spanDestination = metricItem.span.destination.service.resource; + + const destination = maybe(destinationMap[spanDestination]); + const id = destination?.id || { + [SPAN_DESTINATION_SERVICE_RESOURCE]: spanDestination, + }; + + return merge( + { + id, + metrics: [metricItem], + span: { + destination: { + service: { + resource: spanDestination, + }, }, }, }, - }, - destination + destination + ); + }, []); + + const metricsJoinedByDestinationId = joinByKey( + metricsWithDestinationIds, + 'id', + (a, b) => { + const { metrics: metricsA, ...itemA } = a; + const { metrics: metricsB, ...itemB } = b; + + return merge({}, itemA, itemB, { metrics: metricsA.concat(metricsB) }); + } ); - }, []); - const metricsJoinedByDestinationId = joinByKey( - metricsWithDestinationIds, - 'id', - (a, b) => { - const { metrics: metricsA, ...itemA } = a; - const { metrics: metricsB, ...itemB } = b; + const metricsByResolvedAddress = metricsJoinedByDestinationId.map( + (item) => { + const mergedMetrics = item.metrics.reduce< + Omit, 'span'> + >( + (prev, current) => { + return { + value: { + count: prev.value.count + current.value.count, + latency_sum: prev.value.latency_sum + current.value.latency_sum, + error_count: prev.value.error_count + current.value.error_count, + }, + timeseries: joinByKey( + [...prev.timeseries, ...current.timeseries], + 'x', + (a, b) => ({ + x: a.x, + count: a.count + b.count, + latency_sum: a.latency_sum + b.latency_sum, + error_count: a.error_count + b.error_count, + }) + ), + }; + }, + { + value: { + count: 0, + latency_sum: 0, + error_count: 0, + }, + timeseries: [], + } + ); + + const destMetrics = { + latency: { + value: + mergedMetrics.value.count > 0 + ? mergedMetrics.value.latency_sum / mergedMetrics.value.count + : null, + timeseries: mergedMetrics.timeseries.map((point) => ({ + x: point.x, + y: point.count > 0 ? point.latency_sum / point.count : null, + })), + }, + throughput: { + value: + mergedMetrics.value.count > 0 + ? calculateThroughput({ + start, + end, + value: mergedMetrics.value.count, + }) + : null, + timeseries: mergedMetrics.timeseries.map((point) => ({ + x: point.x, + y: + point.count > 0 + ? calculateThroughput({ start, end, value: point.count }) + : null, + })), + }, + errorRate: { + value: + mergedMetrics.value.count > 0 + ? (mergedMetrics.value.error_count ?? 0) / + mergedMetrics.value.count + : null, + timeseries: mergedMetrics.timeseries.map((point) => ({ + x: point.x, + y: + point.count > 0 ? (point.error_count ?? 0) / point.count : null, + })), + }, + }; - return merge({}, itemA, itemB, { metrics: metricsA.concat(metricsB) }); - } - ); + if (item.service) { + return { + name: item.service.name, + type: 'service' as const, + serviceName: item.service.name, + environment: item.service.environment, + // agent.name should always be there, type returned from joinByKey is too pessimistic + agentName: item.agent!.name, + ...destMetrics, + }; + } - const metricsByResolvedAddress = metricsJoinedByDestinationId.map((item) => { - const mergedMetrics = item.metrics.reduce< - Omit, 'span'> - >( - (prev, current) => { return { - value: { - count: prev.value.count + current.value.count, - latency_sum: prev.value.latency_sum + current.value.latency_sum, - error_count: prev.value.error_count + current.value.error_count, - }, - timeseries: joinByKey( - [...prev.timeseries, ...current.timeseries], - 'x', - (a, b) => ({ - x: a.x, - count: a.count + b.count, - latency_sum: a.latency_sum + b.latency_sum, - error_count: a.error_count + b.error_count, - }) - ), + name: item.span.destination.service.resource, + type: 'external' as const, + spanType: item.span.type, + spanSubtype: item.span.subtype, + ...destMetrics, }; - }, - { - value: { - count: 0, - latency_sum: 0, - error_count: 0, - }, - timeseries: [], } ); - const destMetrics = { - latency: { - value: - mergedMetrics.value.count > 0 - ? mergedMetrics.value.latency_sum / mergedMetrics.value.count - : null, - timeseries: mergedMetrics.timeseries.map((point) => ({ - x: point.x, - y: point.count > 0 ? point.latency_sum / point.count : null, - })), - }, - throughput: { - value: - mergedMetrics.value.count > 0 - ? calculateThroughput({ - start, - end, - value: mergedMetrics.value.count, - }) - : null, - timeseries: mergedMetrics.timeseries.map((point) => ({ - x: point.x, - y: - point.count > 0 - ? calculateThroughput({ start, end, value: point.count }) - : null, - })), - }, - errorRate: { - value: - mergedMetrics.value.count > 0 - ? (mergedMetrics.value.error_count ?? 0) / mergedMetrics.value.count - : null, - timeseries: mergedMetrics.timeseries.map((point) => ({ - x: point.x, - y: point.count > 0 ? (point.error_count ?? 0) / point.count : null, - })), - }, - }; - - if (item.service) { - return { - name: item.service.name, - type: 'service' as const, - serviceName: item.service.name, - environment: item.service.environment, - // agent.name should always be there, type returned from joinByKey is too pessimistic - agentName: item.agent!.name, - ...destMetrics, - }; - } + const latencySums = metricsByResolvedAddress + .map( + (metric) => (metric.latency.value ?? 0) * (metric.throughput.value ?? 0) + ) + .filter(isFiniteNumber); - return { - name: item.span.destination.service.resource, - type: 'external' as const, - spanType: item.span.type, - spanSubtype: item.span.subtype, - ...destMetrics, - }; - }); + const minLatencySum = Math.min(...latencySums); + const maxLatencySum = Math.max(...latencySums); + + return metricsByResolvedAddress.map((metric) => { + const impact = + isFiniteNumber(metric.latency.value) && + isFiniteNumber(metric.throughput.value) + ? ((metric.latency.value * metric.throughput.value - minLatencySum) / + (maxLatencySum - minLatencySum)) * + 100 + : 0; - const latencySums = metricsByResolvedAddress - .map( - (metric) => (metric.latency.value ?? 0) * (metric.throughput.value ?? 0) - ) - .filter(isFiniteNumber); - - const minLatencySum = Math.min(...latencySums); - const maxLatencySum = Math.max(...latencySums); - - return metricsByResolvedAddress.map((metric) => { - const impact = - isFiniteNumber(metric.latency.value) && - isFiniteNumber(metric.throughput.value) - ? ((metric.latency.value * metric.throughput.value - minLatencySum) / - (maxLatencySum - minLatencySum)) * - 100 - : 0; - - return { - ...metric, - impact, - }; + return { + ...metric, + impact, + }; + }); }); } diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts index f27125874f2b4..f5aa01e1dfa58 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/index.ts @@ -21,6 +21,7 @@ import { import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { getErrorName } from '../../helpers/get_error_name'; +import { withApmSpan } from '../../../utils/with_apm_span'; export type ServiceErrorGroupItem = ValuesType< PromiseReturnType @@ -45,139 +46,152 @@ export async function getServiceErrorGroups({ sortField: 'name' | 'last_seen' | 'occurrences'; transactionType: string; }) { - const { apmEventClient, start, end, esFilter } = setup; + return withApmSpan('get_service_error_groups', async () => { + const { apmEventClient, start, end, esFilter } = setup; - const { intervalString } = getBucketSize({ start, end, numBuckets }); + const { intervalString } = getBucketSize({ start, end, numBuckets }); - const response = await apmEventClient.search({ - apm: { - events: [ProcessorEvent.error], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, - ...esFilter, - ], + const response = await withApmSpan('get_top_service_error_groups', () => + apmEventClient.search({ + apm: { + events: [ProcessorEvent.error], }, - }, - aggs: { - error_groups: { - terms: { - field: ERROR_GROUP_ID, - size: 500, - order: { - _count: 'desc', + body: { + size: 0, + query: { + bool: { + filter: [ + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + { range: rangeFilter(start, end) }, + ...esFilter, + ], }, }, aggs: { - sample: { - top_hits: { - size: 1, - _source: [ERROR_LOG_MESSAGE, ERROR_EXC_MESSAGE, '@timestamp'], - sort: { - '@timestamp': 'desc', + error_groups: { + terms: { + field: ERROR_GROUP_ID, + size: 500, + order: { + _count: 'desc', + }, + }, + aggs: { + sample: { + top_hits: { + size: 1, + _source: [ + ERROR_LOG_MESSAGE, + ERROR_EXC_MESSAGE, + '@timestamp', + ], + sort: { + '@timestamp': 'desc', + }, + }, }, }, }, }, }, - }, - }, - }); + }) + ); - const errorGroups = - response.aggregations?.error_groups.buckets.map((bucket) => ({ - group_id: bucket.key as string, - name: - getErrorName(bucket.sample.hits.hits[0]._source) ?? NOT_AVAILABLE_LABEL, - last_seen: new Date( - bucket.sample.hits.hits[0]?._source['@timestamp'] - ).getTime(), - occurrences: { - value: bucket.doc_count, - }, - })) ?? []; + const errorGroups = + response.aggregations?.error_groups.buckets.map((bucket) => ({ + group_id: bucket.key as string, + name: + getErrorName(bucket.sample.hits.hits[0]._source) ?? + NOT_AVAILABLE_LABEL, + last_seen: new Date( + bucket.sample.hits.hits[0]?._source['@timestamp'] + ).getTime(), + occurrences: { + value: bucket.doc_count, + }, + })) ?? []; - // Sort error groups first, and only get timeseries for data in view. - // This is to limit the possibility of creating too many buckets. + // Sort error groups first, and only get timeseries for data in view. + // This is to limit the possibility of creating too many buckets. - const sortedAndSlicedErrorGroups = orderBy( - errorGroups, - (group) => { - if (sortField === 'occurrences') { - return group.occurrences.value; - } - return group[sortField]; - }, - [sortDirection] - ).slice(pageIndex * size, pageIndex * size + size); + const sortedAndSlicedErrorGroups = orderBy( + errorGroups, + (group) => { + if (sortField === 'occurrences') { + return group.occurrences.value; + } + return group[sortField]; + }, + [sortDirection] + ).slice(pageIndex * size, pageIndex * size + size); - const sortedErrorGroupIds = sortedAndSlicedErrorGroups.map( - (group) => group.group_id - ); + const sortedErrorGroupIds = sortedAndSlicedErrorGroups.map( + (group) => group.group_id + ); - const timeseriesResponse = await apmEventClient.search({ - apm: { - events: [ProcessorEvent.error], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { terms: { [ERROR_GROUP_ID]: sortedErrorGroupIds } }, - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, - ...esFilter, - ], - }, - }, - aggs: { - error_groups: { - terms: { - field: ERROR_GROUP_ID, - size, + const timeseriesResponse = await withApmSpan( + 'get_service_error_groups_timeseries', + async () => + apmEventClient.search({ + apm: { + events: [ProcessorEvent.error], }, - aggs: { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - min_doc_count: 0, - extended_bounds: { - min: start, - max: end, + body: { + size: 0, + query: { + bool: { + filter: [ + { terms: { [ERROR_GROUP_ID]: sortedErrorGroupIds } }, + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + { range: rangeFilter(start, end) }, + ...esFilter, + ], + }, + }, + aggs: { + error_groups: { + terms: { + field: ERROR_GROUP_ID, + size, + }, + aggs: { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: start, + max: end, + }, + }, + }, }, }, }, }, + }) + ); + + return { + total_error_groups: errorGroups.length, + is_aggregation_accurate: + (response.aggregations?.error_groups.sum_other_doc_count ?? 0) === 0, + error_groups: sortedAndSlicedErrorGroups.map((errorGroup) => ({ + ...errorGroup, + occurrences: { + ...errorGroup.occurrences, + timeseries: + timeseriesResponse.aggregations?.error_groups.buckets + .find((bucket) => bucket.key === errorGroup.group_id) + ?.timeseries.buckets.map((dateBucket) => ({ + x: dateBucket.key, + y: dateBucket.doc_count, + })) ?? null, }, - }, - }, + })), + }; }); - - return { - total_error_groups: errorGroups.length, - is_aggregation_accurate: - (response.aggregations?.error_groups.sum_other_doc_count ?? 0) === 0, - error_groups: sortedAndSlicedErrorGroups.map((errorGroup) => ({ - ...errorGroup, - occurrences: { - ...errorGroup.occurrences, - timeseries: - timeseriesResponse.aggregations?.error_groups.buckets - .find((bucket) => bucket.key === errorGroup.group_id) - ?.timeseries.buckets.map((dateBucket) => ({ - x: dateBucket.key, - y: dateBucket.doc_count, - })) ?? null, - }, - })), - }; } diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts index 999ed62ed4ba6..4f8088352d0ae 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts @@ -23,6 +23,7 @@ import { percentCgroupMemoryUsedScript, percentSystemMemoryUsedScript, } from '../../metrics/by_agent/shared/memory'; +import { withApmSpan } from '../../../utils/with_apm_span'; export async function getServiceInstanceSystemMetricStats({ setup, @@ -30,122 +31,124 @@ export async function getServiceInstanceSystemMetricStats({ size, numBuckets, }: ServiceInstanceParams) { - const { apmEventClient, start, end, esFilter } = setup; + return withApmSpan('get_service_instance_system_metric_stats', async () => { + const { apmEventClient, start, end, esFilter } = setup; - const { intervalString } = getBucketSize({ start, end, numBuckets }); + const { intervalString } = getBucketSize({ start, end, numBuckets }); - const systemMemoryFilter = { - bool: { - filter: [ - { exists: { field: METRIC_SYSTEM_FREE_MEMORY } }, - { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, - ], - }, - }; + const systemMemoryFilter = { + bool: { + filter: [ + { exists: { field: METRIC_SYSTEM_FREE_MEMORY } }, + { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, + ], + }, + }; - const cgroupMemoryFilter = { - exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES }, - }; + const cgroupMemoryFilter = { + exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES }, + }; - const cpuUsageFilter = { exists: { field: METRIC_PROCESS_CPU_PERCENT } }; + const cpuUsageFilter = { exists: { field: METRIC_PROCESS_CPU_PERCENT } }; - function withTimeseries(agg: T) { - return { - avg: { avg: agg }, - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - min_doc_count: 0, - extended_bounds: { - min: start, - max: end, + function withTimeseries(agg: T) { + return { + avg: { avg: agg }, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: start, + max: end, + }, + }, + aggs: { + avg: { avg: agg }, }, }, - aggs: { - avg: { avg: agg }, - }, + }; + } + + const subAggs = { + memory_usage_cgroup: { + filter: cgroupMemoryFilter, + aggs: withTimeseries({ script: percentCgroupMemoryUsedScript }), + }, + memory_usage_system: { + filter: systemMemoryFilter, + aggs: withTimeseries({ script: percentSystemMemoryUsedScript }), + }, + cpu_usage: { + filter: cpuUsageFilter, + aggs: withTimeseries({ field: METRIC_PROCESS_CPU_PERCENT }), }, }; - } - const subAggs = { - memory_usage_cgroup: { - filter: cgroupMemoryFilter, - aggs: withTimeseries({ script: percentCgroupMemoryUsedScript }), - }, - memory_usage_system: { - filter: systemMemoryFilter, - aggs: withTimeseries({ script: percentSystemMemoryUsedScript }), - }, - cpu_usage: { - filter: cpuUsageFilter, - aggs: withTimeseries({ field: METRIC_PROCESS_CPU_PERCENT }), - }, - }; - - const response = await apmEventClient.search({ - apm: { - events: [ProcessorEvent.metric], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { range: rangeFilter(start, end) }, - { term: { [SERVICE_NAME]: serviceName } }, - ...esFilter, - ], - should: [cgroupMemoryFilter, systemMemoryFilter, cpuUsageFilter], - minimum_should_match: 1, - }, + const response = await apmEventClient.search({ + apm: { + events: [ProcessorEvent.metric], }, - aggs: { - [SERVICE_NODE_NAME]: { - terms: { - field: SERVICE_NODE_NAME, - missing: SERVICE_NODE_NAME_MISSING, - size, + body: { + size: 0, + query: { + bool: { + filter: [ + { range: rangeFilter(start, end) }, + { term: { [SERVICE_NAME]: serviceName } }, + ...esFilter, + ], + should: [cgroupMemoryFilter, systemMemoryFilter, cpuUsageFilter], + minimum_should_match: 1, + }, + }, + aggs: { + [SERVICE_NODE_NAME]: { + terms: { + field: SERVICE_NODE_NAME, + missing: SERVICE_NODE_NAME_MISSING, + size, + }, + aggs: subAggs, }, - aggs: subAggs, }, }, - }, - }); + }); - return ( - response.aggregations?.[SERVICE_NODE_NAME].buckets.map( - (serviceNodeBucket) => { - const hasCGroupData = - serviceNodeBucket.memory_usage_cgroup.avg.value !== null; + return ( + response.aggregations?.[SERVICE_NODE_NAME].buckets.map( + (serviceNodeBucket) => { + const hasCGroupData = + serviceNodeBucket.memory_usage_cgroup.avg.value !== null; - const memoryMetricsKey = hasCGroupData - ? 'memory_usage_cgroup' - : 'memory_usage_system'; + const memoryMetricsKey = hasCGroupData + ? 'memory_usage_cgroup' + : 'memory_usage_system'; - return { - serviceNodeName: String(serviceNodeBucket.key), - cpuUsage: { - value: serviceNodeBucket.cpu_usage.avg.value, - timeseries: serviceNodeBucket.cpu_usage.timeseries.buckets.map( - (dateBucket) => ({ + return { + serviceNodeName: String(serviceNodeBucket.key), + cpuUsage: { + value: serviceNodeBucket.cpu_usage.avg.value, + timeseries: serviceNodeBucket.cpu_usage.timeseries.buckets.map( + (dateBucket) => ({ + x: dateBucket.key, + y: dateBucket.avg.value, + }) + ), + }, + memoryUsage: { + value: serviceNodeBucket[memoryMetricsKey].avg.value, + timeseries: serviceNodeBucket[ + memoryMetricsKey + ].timeseries.buckets.map((dateBucket) => ({ x: dateBucket.key, y: dateBucket.avg.value, - }) - ), - }, - memoryUsage: { - value: serviceNodeBucket[memoryMetricsKey].avg.value, - timeseries: serviceNodeBucket[ - memoryMetricsKey - ].timeseries.buckets.map((dateBucket) => ({ - x: dateBucket.key, - y: dateBucket.avg.value, - })), - }, - }; - } - ) ?? [] - ); + })), + }, + }; + } + ) ?? [] + ); + }); } diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts index 42c822fb4e133..423ecf7e26cdb 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts @@ -21,6 +21,7 @@ import { getTransactionDurationFieldForAggregatedTransactions, } from '../../helpers/aggregated_transactions'; import { calculateThroughput } from '../../helpers/calculate_throughput'; +import { withApmSpan } from '../../../utils/with_apm_span'; export async function getServiceInstanceTransactionStats({ setup, @@ -30,120 +31,122 @@ export async function getServiceInstanceTransactionStats({ searchAggregatedTransactions, numBuckets, }: ServiceInstanceParams) { - const { apmEventClient, start, end, esFilter } = setup; + return withApmSpan('get_service_instance_transacion_stats', async () => { + const { apmEventClient, start, end, esFilter } = setup; - const { intervalString, bucketSize } = getBucketSize({ - start, - end, - numBuckets, - }); + const { intervalString, bucketSize } = getBucketSize({ + start, + end, + numBuckets, + }); - const field = getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ); + const field = getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ); - const subAggs = { - avg_transaction_duration: { - avg: { - field, + const subAggs = { + avg_transaction_duration: { + avg: { + field, + }, }, - }, - failures: { - filter: { - term: { - [EVENT_OUTCOME]: EventOutcome.failure, + failures: { + filter: { + term: { + [EVENT_OUTCOME]: EventOutcome.failure, + }, }, }, - }, - }; + }; - const response = await apmEventClient.search({ - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { range: rangeFilter(start, end) }, - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - ...esFilter, - ], - }, + const response = await apmEventClient.search({ + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], }, - aggs: { - [SERVICE_NODE_NAME]: { - terms: { - field: SERVICE_NODE_NAME, - missing: SERVICE_NODE_NAME_MISSING, - size, + body: { + size: 0, + query: { + bool: { + filter: [ + { range: rangeFilter(start, end) }, + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + ...esFilter, + ], }, - aggs: { - ...subAggs, - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - min_doc_count: 0, - extended_bounds: { - min: start, - max: end, + }, + aggs: { + [SERVICE_NODE_NAME]: { + terms: { + field: SERVICE_NODE_NAME, + missing: SERVICE_NODE_NAME_MISSING, + size, + }, + aggs: { + ...subAggs, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: start, + max: end, + }, + }, + aggs: { + ...subAggs, }, - }, - aggs: { - ...subAggs, }, }, }, }, }, - }, - }); + }); - const bucketSizeInMinutes = bucketSize / 60; + const bucketSizeInMinutes = bucketSize / 60; - return ( - response.aggregations?.[SERVICE_NODE_NAME].buckets.map( - (serviceNodeBucket) => { - const { - doc_count: count, - avg_transaction_duration: avgTransactionDuration, - key, - failures, - timeseries, - } = serviceNodeBucket; + return ( + response.aggregations?.[SERVICE_NODE_NAME].buckets.map( + (serviceNodeBucket) => { + const { + doc_count: count, + avg_transaction_duration: avgTransactionDuration, + key, + failures, + timeseries, + } = serviceNodeBucket; - return { - serviceNodeName: String(key), - errorRate: { - value: failures.doc_count / count, - timeseries: timeseries.buckets.map((dateBucket) => ({ - x: dateBucket.key, - y: dateBucket.failures.doc_count / dateBucket.doc_count, - })), - }, - throughput: { - value: calculateThroughput({ start, end, value: count }), - timeseries: timeseries.buckets.map((dateBucket) => ({ - x: dateBucket.key, - y: dateBucket.doc_count / bucketSizeInMinutes, - })), - }, - latency: { - value: avgTransactionDuration.value, - timeseries: timeseries.buckets.map((dateBucket) => ({ - x: dateBucket.key, - y: dateBucket.avg_transaction_duration.value, - })), - }, - }; - } - ) ?? [] - ); + return { + serviceNodeName: String(key), + errorRate: { + value: failures.doc_count / count, + timeseries: timeseries.buckets.map((dateBucket) => ({ + x: dateBucket.key, + y: dateBucket.failures.doc_count / dateBucket.doc_count, + })), + }, + throughput: { + value: calculateThroughput({ start, end, value: count }), + timeseries: timeseries.buckets.map((dateBucket) => ({ + x: dateBucket.key, + y: dateBucket.doc_count / bucketSizeInMinutes, + })), + }, + latency: { + value: avgTransactionDuration.value, + timeseries: timeseries.buckets.map((dateBucket) => ({ + x: dateBucket.key, + y: dateBucket.avg_transaction_duration.value, + })), + }, + }; + } + ) ?? [] + ); + }); } diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts index 4dae5e0a33a90..021774f9522c1 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts @@ -6,6 +6,7 @@ */ import { joinByKey } from '../../../../common/utils/join_by_key'; +import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { getServiceInstanceSystemMetricStats } from './get_service_instance_system_metric_stats'; import { getServiceInstanceTransactionStats } from './get_service_instance_transaction_stats'; @@ -22,20 +23,22 @@ export interface ServiceInstanceParams { export async function getServiceInstances( params: Omit ) { - const paramsForSubQueries = { - ...params, - size: 50, - }; + return withApmSpan('get_service_instances', async () => { + const paramsForSubQueries = { + ...params, + size: 50, + }; - const [transactionStats, systemMetricStats] = await Promise.all([ - getServiceInstanceTransactionStats(paramsForSubQueries), - getServiceInstanceSystemMetricStats(paramsForSubQueries), - ]); + const [transactionStats, systemMetricStats] = await Promise.all([ + getServiceInstanceTransactionStats(paramsForSubQueries), + getServiceInstanceSystemMetricStats(paramsForSubQueries), + ]); - const stats = joinByKey( - [...transactionStats, ...systemMetricStats], - 'serviceNodeName' - ); + const stats = joinByKey( + [...transactionStats, ...systemMetricStats], + 'serviceNodeName' + ); - return stats; + return stats; + }); } diff --git a/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts b/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts index 246184f617b07..24ed72ea99510 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_metadata_details.ts @@ -26,6 +26,7 @@ import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw' import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { should } from './get_service_metadata_icons'; +import { withApmSpan } from '../../utils/with_apm_span'; type ServiceMetadataDetailsRaw = Pick< TransactionRaw, @@ -59,7 +60,7 @@ interface ServiceMetadataDetails { }; } -export async function getServiceMetadataDetails({ +export function getServiceMetadataDetails({ serviceName, setup, searchAggregatedTransactions, @@ -68,103 +69,105 @@ export async function getServiceMetadataDetails({ setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; }): Promise { - const { start, end, apmEventClient } = setup; + return withApmSpan('get_service_metadata_details', async () => { + const { start, end, apmEventClient } = setup; - const filter = [ - { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, - ]; + const filter = [ + { term: { [SERVICE_NAME]: serviceName } }, + { range: rangeFilter(start, end) }, + ]; - const params = { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ProcessorEvent.error, - ProcessorEvent.metric, - ], - }, - body: { - size: 1, - _source: [SERVICE, AGENT, HOST, CONTAINER_ID, KUBERNETES, CLOUD], - query: { bool: { filter, should } }, - aggs: { - serviceVersions: { - terms: { - field: SERVICE_VERSION, - size: 10, - order: { _key: 'desc' } as SortOptions, + const params = { + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ProcessorEvent.error, + ProcessorEvent.metric, + ], + }, + body: { + size: 1, + _source: [SERVICE, AGENT, HOST, CONTAINER_ID, KUBERNETES, CLOUD], + query: { bool: { filter, should } }, + aggs: { + serviceVersions: { + terms: { + field: SERVICE_VERSION, + size: 10, + order: { _key: 'desc' } as SortOptions, + }, }, - }, - availabilityZones: { - terms: { - field: CLOUD_AVAILABILITY_ZONE, - size: 10, + availabilityZones: { + terms: { + field: CLOUD_AVAILABILITY_ZONE, + size: 10, + }, }, - }, - machineTypes: { - terms: { - field: CLOUD_MACHINE_TYPE, - size: 10, + machineTypes: { + terms: { + field: CLOUD_MACHINE_TYPE, + size: 10, + }, }, + totalNumberInstances: { cardinality: { field: SERVICE_NODE_NAME } }, }, - totalNumberInstances: { cardinality: { field: SERVICE_NODE_NAME } }, }, - }, - }; + }; - const response = await apmEventClient.search(params); + const response = await apmEventClient.search(params); - if (response.hits.total.value === 0) { - return { - service: undefined, - container: undefined, - cloud: undefined, - }; - } + if (response.hits.total.value === 0) { + return { + service: undefined, + container: undefined, + cloud: undefined, + }; + } - const { service, agent, host, kubernetes, container, cloud } = response.hits - .hits[0]._source as ServiceMetadataDetailsRaw; + const { service, agent, host, kubernetes, container, cloud } = response.hits + .hits[0]._source as ServiceMetadataDetailsRaw; - const serviceMetadataDetails = { - versions: response.aggregations?.serviceVersions.buckets.map( - (bucket) => bucket.key as string - ), - runtime: service.runtime, - framework: service.framework?.name, - agent, - }; + const serviceMetadataDetails = { + versions: response.aggregations?.serviceVersions.buckets.map( + (bucket) => bucket.key as string + ), + runtime: service.runtime, + framework: service.framework?.name, + agent, + }; - const totalNumberInstances = - response.aggregations?.totalNumberInstances.value; + const totalNumberInstances = + response.aggregations?.totalNumberInstances.value; - const containerDetails = - host || container || totalNumberInstances || kubernetes + const containerDetails = + host || container || totalNumberInstances || kubernetes + ? { + os: host?.os?.platform, + type: (!!kubernetes ? 'Kubernetes' : 'Docker') as ContainerType, + isContainerized: !!container?.id, + totalNumberInstances, + } + : undefined; + + const cloudDetails = cloud ? { - os: host?.os?.platform, - type: (!!kubernetes ? 'Kubernetes' : 'Docker') as ContainerType, - isContainerized: !!container?.id, - totalNumberInstances, + provider: cloud.provider, + projectName: cloud.project?.name, + availabilityZones: response.aggregations?.availabilityZones.buckets.map( + (bucket) => bucket.key as string + ), + machineTypes: response.aggregations?.machineTypes.buckets.map( + (bucket) => bucket.key as string + ), } : undefined; - const cloudDetails = cloud - ? { - provider: cloud.provider, - projectName: cloud.project?.name, - availabilityZones: response.aggregations?.availabilityZones.buckets.map( - (bucket) => bucket.key as string - ), - machineTypes: response.aggregations?.machineTypes.buckets.map( - (bucket) => bucket.key as string - ), - } - : undefined; - - return { - service: serviceMetadataDetails, - container: containerDetails, - cloud: cloudDetails, - }; + return { + service: serviceMetadataDetails, + container: containerDetails, + cloud: cloudDetails, + }; + }); } diff --git a/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts b/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts index b09b2305629ab..6636820defdeb 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_metadata_icons.ts @@ -20,6 +20,7 @@ import { rangeFilter } from '../../../common/utils/range_filter'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { withApmSpan } from '../../utils/with_apm_span'; type ServiceMetadataIconsRaw = Pick< TransactionRaw, @@ -40,7 +41,7 @@ export const should = [ { exists: { field: AGENT_NAME } }, ]; -export async function getServiceMetadataIcons({ +export function getServiceMetadataIcons({ serviceName, setup, searchAggregatedTransactions, @@ -49,53 +50,55 @@ export async function getServiceMetadataIcons({ setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; }): Promise { - const { start, end, apmEventClient } = setup; + return withApmSpan('get_service_metadata_icons', async () => { + const { start, end, apmEventClient } = setup; - const filter = [ - { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, - ]; + const filter = [ + { term: { [SERVICE_NAME]: serviceName } }, + { range: rangeFilter(start, end) }, + ]; - const params = { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ProcessorEvent.error, - ProcessorEvent.metric, - ], - }, - body: { - size: 1, - _source: [KUBERNETES, CLOUD_PROVIDER, CONTAINER_ID, AGENT_NAME], - query: { bool: { filter, should } }, - }, - }; + const params = { + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ProcessorEvent.error, + ProcessorEvent.metric, + ], + }, + body: { + size: 1, + _source: [KUBERNETES, CLOUD_PROVIDER, CONTAINER_ID, AGENT_NAME], + query: { bool: { filter, should } }, + }, + }; - const response = await apmEventClient.search(params); + const response = await apmEventClient.search(params); - if (response.hits.total.value === 0) { - return { - agentName: undefined, - containerType: undefined, - cloudProvider: undefined, - }; - } + if (response.hits.total.value === 0) { + return { + agentName: undefined, + containerType: undefined, + cloudProvider: undefined, + }; + } - const { kubernetes, cloud, container, agent } = response.hits.hits[0] - ._source as ServiceMetadataIconsRaw; + const { kubernetes, cloud, container, agent } = response.hits.hits[0] + ._source as ServiceMetadataIconsRaw; - let containerType: ContainerType; - if (!!kubernetes) { - containerType = 'Kubernetes'; - } else if (!!container) { - containerType = 'Docker'; - } + let containerType: ContainerType; + if (!!kubernetes) { + containerType = 'Kubernetes'; + } else if (!!container) { + containerType = 'Docker'; + } - return { - agentName: agent?.name, - containerType, - cloudProvider: cloud?.provider, - }; + return { + agentName: agent?.name, + containerType, + cloudProvider: cloud?.provider, + }; + }); } diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_timeseries_data_for_transaction_groups.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_timeseries_data_for_transaction_groups.ts index 21db304c4dfe8..e73eca4dbc501 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_timeseries_data_for_transaction_groups.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_timeseries_data_for_transaction_groups.ts @@ -25,12 +25,13 @@ import { import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { getLatencyAggregation } from '../../helpers/latency_aggregation_type'; +import { withApmSpan } from '../../../utils/with_apm_span'; export type TransactionGroupTimeseriesData = PromiseReturnType< typeof getTimeseriesDataForTransactionGroups >; -export async function getTimeseriesDataForTransactionGroups({ +export function getTimeseriesDataForTransactionGroups({ apmEventClient, start, end, @@ -55,65 +56,72 @@ export async function getTimeseriesDataForTransactionGroups({ transactionType: string; latencyAggregationType: LatencyAggregationType; }) { - const { intervalString } = getBucketSize({ start, end, numBuckets }); + return withApmSpan( + 'get_service_overview_transaction_groups_timeseries_data', + async () => { + const { intervalString } = getBucketSize({ start, end, numBuckets }); - const field = getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ); + const field = getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ); - const timeseriesResponse = await apmEventClient.search({ - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { terms: { [TRANSACTION_NAME]: transactionNames } }, - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, - ...getDocumentTypeFilterForAggregatedTransactions( + const timeseriesResponse = await apmEventClient.search({ + apm: { + events: [ + getProcessorEventForAggregatedTransactions( searchAggregatedTransactions ), - ...esFilter, ], }, - }, - aggs: { - transaction_groups: { - terms: { - field: TRANSACTION_NAME, - size, + body: { + size: 0, + query: { + bool: { + filter: [ + { terms: { [TRANSACTION_NAME]: transactionNames } }, + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + { range: rangeFilter(start, end) }, + ...getDocumentTypeFilterForAggregatedTransactions( + searchAggregatedTransactions + ), + ...esFilter, + ], + }, }, aggs: { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - min_doc_count: 0, - extended_bounds: { - min: start, - max: end, - }, + transaction_groups: { + terms: { + field: TRANSACTION_NAME, + size, }, aggs: { - ...getLatencyAggregation(latencyAggregationType, field), - [EVENT_OUTCOME]: { - filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: start, + max: end, + }, + }, + aggs: { + ...getLatencyAggregation(latencyAggregationType, field), + [EVENT_OUTCOME]: { + filter: { + term: { [EVENT_OUTCOME]: EventOutcome.failure }, + }, + }, + }, }, }, }, }, }, - }, - }, - }); + }); - return timeseriesResponse.aggregations?.transaction_groups.buckets ?? []; + return timeseriesResponse.aggregations?.transaction_groups.buckets ?? []; + } + ); } diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups_for_page.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups_for_page.ts index 9038ddff04e3c..0915008a78b66 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups_for_page.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/get_transaction_groups_for_page.ts @@ -28,6 +28,7 @@ import { getLatencyValue, } from '../../helpers/latency_aggregation_type'; import { calculateThroughput } from '../../helpers/calculate_throughput'; +import { withApmSpan } from '../../../utils/with_apm_span'; export type ServiceOverviewTransactionGroupSortField = | 'name' @@ -40,7 +41,7 @@ export type TransactionGroupWithoutTimeseriesData = ValuesType< PromiseReturnType['transactionGroups'] >; -export async function getTransactionGroupsForPage({ +export function getTransactionGroupsForPage({ apmEventClient, searchAggregatedTransactions, serviceName, @@ -67,99 +68,104 @@ export async function getTransactionGroupsForPage({ transactionType: string; latencyAggregationType: LatencyAggregationType; }) { - const field = getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ); + return withApmSpan( + 'get_service_overview_transaction_groups_for_page', + async () => { + const field = getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ); - const response = await apmEventClient.search({ - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, - ...esFilter, + const response = await apmEventClient.search({ + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), ], }, - }, - aggs: { - transaction_groups: { - terms: { - field: TRANSACTION_NAME, - size: 500, - order: { _count: 'desc' }, + body: { + size: 0, + query: { + bool: { + filter: [ + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + { range: rangeFilter(start, end) }, + ...esFilter, + ], + }, }, aggs: { - ...getLatencyAggregation(latencyAggregationType, field), - [EVENT_OUTCOME]: { - filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, + transaction_groups: { + terms: { + field: TRANSACTION_NAME, + size: 500, + order: { _count: 'desc' }, + }, + aggs: { + ...getLatencyAggregation(latencyAggregationType, field), + [EVENT_OUTCOME]: { + filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } }, + }, + }, }, }, }, - }, - }, - }); + }); - const transactionGroups = - response.aggregations?.transaction_groups.buckets.map((bucket) => { - const errorRate = - bucket.doc_count > 0 - ? bucket[EVENT_OUTCOME].doc_count / bucket.doc_count - : null; + const transactionGroups = + response.aggregations?.transaction_groups.buckets.map((bucket) => { + const errorRate = + bucket.doc_count > 0 + ? bucket[EVENT_OUTCOME].doc_count / bucket.doc_count + : null; - return { - name: bucket.key as string, - latency: getLatencyValue({ - latencyAggregationType, - aggregation: bucket.latency, - }), - throughput: calculateThroughput({ - start, - end, - value: bucket.doc_count, - }), - errorRate, - }; - }) ?? []; + return { + name: bucket.key as string, + latency: getLatencyValue({ + latencyAggregationType, + aggregation: bucket.latency, + }), + throughput: calculateThroughput({ + start, + end, + value: bucket.doc_count, + }), + errorRate, + }; + }) ?? []; - const totalDurationValues = transactionGroups.map( - (group) => (group.latency ?? 0) * group.throughput - ); + const totalDurationValues = transactionGroups.map( + (group) => (group.latency ?? 0) * group.throughput + ); - const minTotalDuration = Math.min(...totalDurationValues); - const maxTotalDuration = Math.max(...totalDurationValues); + const minTotalDuration = Math.min(...totalDurationValues); + const maxTotalDuration = Math.max(...totalDurationValues); - const transactionGroupsWithImpact = transactionGroups.map((group) => ({ - ...group, - impact: - (((group.latency ?? 0) * group.throughput - minTotalDuration) / - (maxTotalDuration - minTotalDuration)) * - 100, - })); + const transactionGroupsWithImpact = transactionGroups.map((group) => ({ + ...group, + impact: + (((group.latency ?? 0) * group.throughput - minTotalDuration) / + (maxTotalDuration - minTotalDuration)) * + 100, + })); - // Sort transaction groups first, and only get timeseries for data in view. - // This is to limit the possibility of creating too many buckets. + // Sort transaction groups first, and only get timeseries for data in view. + // This is to limit the possibility of creating too many buckets. - const sortedAndSlicedTransactionGroups = orderBy( - transactionGroupsWithImpact, - sortField, - [sortDirection] - ).slice(pageIndex * size, pageIndex * size + size); + const sortedAndSlicedTransactionGroups = orderBy( + transactionGroupsWithImpact, + sortField, + [sortDirection] + ).slice(pageIndex * size, pageIndex * size + size); - return { - transactionGroups: sortedAndSlicedTransactionGroups, - totalTransactionGroups: transactionGroups.length, - isAggregationAccurate: - (response.aggregations?.transaction_groups.sum_other_doc_count ?? 0) === - 0, - }; + return { + transactionGroups: sortedAndSlicedTransactionGroups, + totalTransactionGroups: transactionGroups.length, + isAggregationAccurate: + (response.aggregations?.transaction_groups.sum_other_doc_count ?? + 0) === 0, + }; + } + ); } diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts index 3b5426a3c1764..87a92d39bb479 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/index.ts @@ -6,6 +6,7 @@ */ import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; +import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { getTimeseriesDataForTransactionGroups } from './get_timeseries_data_for_transaction_groups'; import { @@ -37,53 +38,55 @@ export async function getServiceTransactionGroups({ transactionType: string; latencyAggregationType: LatencyAggregationType; }) { - const { apmEventClient, start, end, esFilter } = setup; + return withApmSpan('get_service_overview_transaction_groups', async () => { + const { apmEventClient, start, end, esFilter } = setup; - const { - transactionGroups, - totalTransactionGroups, - isAggregationAccurate, - } = await getTransactionGroupsForPage({ - apmEventClient, - start, - end, - serviceName, - esFilter, - pageIndex, - sortField, - sortDirection, - size, - searchAggregatedTransactions, - transactionType, - latencyAggregationType, - }); - - const transactionNames = transactionGroups.map((group) => group.name); - - const timeseriesData = await getTimeseriesDataForTransactionGroups({ - apmEventClient, - start, - end, - esFilter, - numBuckets, - searchAggregatedTransactions, - serviceName, - size, - transactionNames, - transactionType, - latencyAggregationType, - }); - - return { - transactionGroups: mergeTransactionGroupData({ + const { transactionGroups, - timeseriesData, + totalTransactionGroups, + isAggregationAccurate, + } = await getTransactionGroupsForPage({ + apmEventClient, start, end, + serviceName, + esFilter, + pageIndex, + sortField, + sortDirection, + size, + searchAggregatedTransactions, + transactionType, latencyAggregationType, + }); + + const transactionNames = transactionGroups.map((group) => group.name); + + const timeseriesData = await getTimeseriesDataForTransactionGroups({ + apmEventClient, + start, + end, + esFilter, + numBuckets, + searchAggregatedTransactions, + serviceName, + size, + transactionNames, transactionType, - }), - totalTransactionGroups, - isAggregationAccurate, - }; + latencyAggregationType, + }); + + return { + transactionGroups: mergeTransactionGroupData({ + transactionGroups, + timeseriesData, + start, + end, + latencyAggregationType, + transactionType, + }), + totalTransactionGroups, + isAggregationAccurate, + }; + }); } diff --git a/x-pack/plugins/apm/server/lib/services/get_throughput.ts b/x-pack/plugins/apm/server/lib/services/get_throughput.ts index 492f41e153a89..c4e217f95bcd1 100644 --- a/x-pack/plugins/apm/server/lib/services/get_throughput.ts +++ b/x-pack/plugins/apm/server/lib/services/get_throughput.ts @@ -19,6 +19,7 @@ import { import { getBucketSize } from '../helpers/get_bucket_size'; import { calculateThroughput } from '../helpers/calculate_throughput'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { withApmSpan } from '../../utils/with_apm_span'; interface Options { searchAggregatedTransactions: boolean; @@ -86,8 +87,10 @@ async function fetcher({ return apmEventClient.search(params); } -export async function getThroughput(options: Options) { - return { - throughput: transform(options, await fetcher(options)), - }; +export function getThroughput(options: Options) { + return withApmSpan('get_throughput_for_service', async () => { + return { + throughput: transform(options, await fetcher(options)), + }; + }); } diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts index ff1315ed1e3f0..1885382435562 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts @@ -12,8 +12,9 @@ import { AgentConfigurationIntake, } from '../../../../common/agent_configuration/configuration_types'; import { APMIndexDocumentParams } from '../../helpers/create_es_client/create_internal_es_client'; +import { withApmSpan } from '../../../utils/with_apm_span'; -export async function createOrUpdateConfiguration({ +export function createOrUpdateConfiguration({ configurationId, configurationIntake, setup, @@ -22,28 +23,30 @@ export async function createOrUpdateConfiguration({ configurationIntake: AgentConfigurationIntake; setup: Setup; }) { - const { internalClient, indices } = setup; + return withApmSpan('create_or_update_configuration', async () => { + const { internalClient, indices } = setup; - const params: APMIndexDocumentParams = { - refresh: true, - index: indices.apmAgentConfigurationIndex, - body: { - agent_name: configurationIntake.agent_name, - service: { - name: configurationIntake.service.name, - environment: configurationIntake.service.environment, + const params: APMIndexDocumentParams = { + refresh: true, + index: indices.apmAgentConfigurationIndex, + body: { + agent_name: configurationIntake.agent_name, + service: { + name: configurationIntake.service.name, + environment: configurationIntake.service.environment, + }, + settings: configurationIntake.settings, + '@timestamp': Date.now(), + applied_by_agent: false, + etag: hash(configurationIntake), }, - settings: configurationIntake.settings, - '@timestamp': Date.now(), - applied_by_agent: false, - etag: hash(configurationIntake), - }, - }; + }; - // by specifying an id elasticsearch will delete the previous doc and insert the updated doc - if (configurationId) { - params.id = configurationId; - } + // by specifying an id elasticsearch will delete the previous doc and insert the updated doc + if (configurationId) { + params.id = configurationId; + } - return internalClient.index(params); + return internalClient.index(params); + }); } diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts index 5184411506769..6ed6f79979889 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup } from '../../helpers/setup_request'; export async function deleteConfiguration({ @@ -14,13 +15,15 @@ export async function deleteConfiguration({ configurationId: string; setup: Setup; }) { - const { internalClient, indices } = setup; + return withApmSpan('delete_agent_configuration', async () => { + const { internalClient, indices } = setup; - const params = { - refresh: 'wait_for' as const, - index: indices.apmAgentConfigurationIndex, - id: configurationId, - }; + const params = { + refresh: 'wait_for' as const, + index: indices.apmAgentConfigurationIndex, + id: configurationId, + }; - return internalClient.delete(params); + return internalClient.delete(params); + }); } diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts index 1657177f7672e..55d00b70b8c29 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/find_exact_configuration.ts @@ -11,44 +11,49 @@ import { SERVICE_ENVIRONMENT, SERVICE_NAME, } from '../../../../common/elasticsearch_fieldnames'; +import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup } from '../../helpers/setup_request'; import { convertConfigSettingsToString } from './convert_settings_to_string'; -export async function findExactConfiguration({ +export function findExactConfiguration({ service, setup, }: { service: AgentConfiguration['service']; setup: Setup; }) { - const { internalClient, indices } = setup; - - const serviceNameFilter = service.name - ? { term: { [SERVICE_NAME]: service.name } } - : { bool: { must_not: [{ exists: { field: SERVICE_NAME } }] } }; - - const environmentFilter = service.environment - ? { term: { [SERVICE_ENVIRONMENT]: service.environment } } - : { bool: { must_not: [{ exists: { field: SERVICE_ENVIRONMENT } }] } }; - - const params = { - index: indices.apmAgentConfigurationIndex, - body: { - query: { - bool: { filter: [serviceNameFilter, environmentFilter] }, + return withApmSpan('find_exact_agent_configuration', async () => { + const { internalClient, indices } = setup; + + const serviceNameFilter = service.name + ? { term: { [SERVICE_NAME]: service.name } } + : { bool: { must_not: [{ exists: { field: SERVICE_NAME } }] } }; + + const environmentFilter = service.environment + ? { term: { [SERVICE_ENVIRONMENT]: service.environment } } + : { bool: { must_not: [{ exists: { field: SERVICE_ENVIRONMENT } }] } }; + + const params = { + index: indices.apmAgentConfigurationIndex, + body: { + query: { + bool: { filter: [serviceNameFilter, environmentFilter] }, + }, }, - }, - }; + }; - const resp = await internalClient.search( - params - ); + const resp = await internalClient.search( + params + ); - const hit = resp.hits.hits[0] as ESSearchHit | undefined; + const hit = resp.hits.hits[0] as + | ESSearchHit + | undefined; - if (!hit) { - return; - } + if (!hit) { + return; + } - return convertConfigSettingsToString(hit); + return convertConfigSettingsToString(hit); + }); } diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts index 322bb5fc3b1fe..379ed12e37389 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_agent_name_by_service.ts @@ -9,6 +9,7 @@ import { ProcessorEvent } from '../../../../common/processor_event'; import { Setup } from '../../helpers/setup_request'; import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; import { AGENT_NAME } from '../../../../common/elasticsearch_fieldnames'; +import { withApmSpan } from '../../../utils/with_apm_span'; export async function getAgentNameByService({ serviceName, @@ -17,33 +18,35 @@ export async function getAgentNameByService({ serviceName: string; setup: Setup; }) { - const { apmEventClient } = setup; + return withApmSpan('get_agent_name_by_service', async () => { + const { apmEventClient } = setup; - const params = { - terminateAfter: 1, - apm: { - events: [ - ProcessorEvent.transaction, - ProcessorEvent.error, - ProcessorEvent.metric, - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [{ term: { [SERVICE_NAME]: serviceName } }], - }, + const params = { + terminateAfter: 1, + apm: { + events: [ + ProcessorEvent.transaction, + ProcessorEvent.error, + ProcessorEvent.metric, + ], }, - aggs: { - agent_names: { - terms: { field: AGENT_NAME, size: 1 }, + body: { + size: 0, + query: { + bool: { + filter: [{ term: { [SERVICE_NAME]: serviceName } }], + }, + }, + aggs: { + agent_names: { + terms: { field: AGENT_NAME, size: 1 }, + }, }, }, - }, - }; + }; - const { aggregations } = await apmEventClient.search(params); - const agentName = aggregations?.agent_names.buckets[0]?.key; - return agentName as string | undefined; + const { aggregations } = await apmEventClient.search(params); + const agentName = aggregations?.agent_names.buckets[0]?.key; + return agentName as string | undefined; + }); } diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts index 01350db8ae4a4..4a32b3c3a370b 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { withApmSpan } from '../../../../utils/with_apm_span'; import { Setup } from '../../../helpers/setup_request'; import { SERVICE_NAME, @@ -19,34 +20,36 @@ export async function getExistingEnvironmentsForService({ serviceName: string | undefined; setup: Setup; }) { - const { internalClient, indices, config } = setup; - const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; + return withApmSpan('get_existing_environments_for_service', async () => { + const { internalClient, indices, config } = setup; + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; - const bool = serviceName - ? { filter: [{ term: { [SERVICE_NAME]: serviceName } }] } - : { must_not: [{ exists: { field: SERVICE_NAME } }] }; + const bool = serviceName + ? { filter: [{ term: { [SERVICE_NAME]: serviceName } }] } + : { must_not: [{ exists: { field: SERVICE_NAME } }] }; - const params = { - index: indices.apmAgentConfigurationIndex, - body: { - size: 0, - query: { bool }, - aggs: { - environments: { - terms: { - field: SERVICE_ENVIRONMENT, - missing: ALL_OPTION_VALUE, - size: maxServiceEnvironments, + const params = { + index: indices.apmAgentConfigurationIndex, + body: { + size: 0, + query: { bool }, + aggs: { + environments: { + terms: { + field: SERVICE_ENVIRONMENT, + missing: ALL_OPTION_VALUE, + size: maxServiceEnvironments, + }, }, }, }, - }, - }; + }; - const resp = await internalClient.search(params); - const existingEnvironments = - resp.aggregations?.environments.buckets.map( - (bucket) => bucket.key as string - ) || []; - return existingEnvironments; + const resp = await internalClient.search(params); + const existingEnvironments = + resp.aggregations?.environments.buckets.map( + (bucket) => bucket.key as string + ) || []; + return existingEnvironments; + }); } diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/index.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/index.ts index 46fe4bbf9363f..0ab56ac372706 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/index.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_environments/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { withApmSpan } from '../../../../utils/with_apm_span'; import { getAllEnvironments } from '../../../environments/get_all_environments'; import { Setup } from '../../../helpers/setup_request'; import { PromiseReturnType } from '../../../../../../observability/typings/common'; @@ -24,15 +25,17 @@ export async function getEnvironments({ setup: Setup; searchAggregatedTransactions: boolean; }) { - const [allEnvironments, existingEnvironments] = await Promise.all([ - getAllEnvironments({ serviceName, setup, searchAggregatedTransactions }), - getExistingEnvironmentsForService({ serviceName, setup }), - ]); + return withApmSpan('get_environments_for_agent_configuration', async () => { + const [allEnvironments, existingEnvironments] = await Promise.all([ + getAllEnvironments({ serviceName, setup, searchAggregatedTransactions }), + getExistingEnvironmentsForService({ serviceName, setup }), + ]); - return [ALL_OPTION_VALUE, ...allEnvironments].map((environment) => { - return { - name: environment, - alreadyConfigured: existingEnvironments.includes(environment), - }; + return [ALL_OPTION_VALUE, ...allEnvironments].map((environment) => { + return { + name: environment, + alreadyConfigured: existingEnvironments.includes(environment), + }; + }); }); } diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts index 3f39268fc3531..9c56455f45902 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/get_service_names.ts @@ -11,49 +11,52 @@ import { PromiseReturnType } from '../../../../../observability/typings/common'; import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; import { ALL_OPTION_VALUE } from '../../../../common/agent_configuration/all_option'; import { getProcessorEventForAggregatedTransactions } from '../../helpers/aggregated_transactions'; +import { withApmSpan } from '../../../utils/with_apm_span'; export type AgentConfigurationServicesAPIResponse = PromiseReturnType< typeof getServiceNames >; -export async function getServiceNames({ +export function getServiceNames({ setup, searchAggregatedTransactions, }: { setup: Setup; searchAggregatedTransactions: boolean; }) { - const { apmEventClient, config } = setup; - const maxServiceSelection = config['xpack.apm.maxServiceSelection']; + return withApmSpan('get_service_names_for_agent_config', async () => { + const { apmEventClient, config } = setup; + const maxServiceSelection = config['xpack.apm.maxServiceSelection']; - const params = { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ProcessorEvent.error, - ProcessorEvent.metric, - ], - }, - body: { - timeout: '1ms', - size: 0, - aggs: { - services: { - terms: { - field: SERVICE_NAME, - size: maxServiceSelection, - min_doc_count: 0, + const params = { + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ProcessorEvent.error, + ProcessorEvent.metric, + ], + }, + body: { + timeout: '1ms', + size: 0, + aggs: { + services: { + terms: { + field: SERVICE_NAME, + size: maxServiceSelection, + min_doc_count: 0, + }, }, }, }, - }, - }; + }; - const resp = await apmEventClient.search(params); - const serviceNames = - resp.aggregations?.services.buckets - .map((bucket) => bucket.key as string) - .sort() || []; - return [ALL_OPTION_VALUE, ...serviceNames]; + const resp = await apmEventClient.search(params); + const serviceNames = + resp.aggregations?.services.buckets + .map((bucket) => bucket.key as string) + .sort() || []; + return [ALL_OPTION_VALUE, ...serviceNames]; + }); } diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts index 871a96f6f85f1..adcfe88392dc8 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts @@ -8,6 +8,7 @@ import { Setup } from '../../helpers/setup_request'; import { AgentConfiguration } from '../../../../common/agent_configuration/configuration_types'; import { convertConfigSettingsToString } from './convert_settings_to_string'; +import { withApmSpan } from '../../../utils/with_apm_span'; export async function listConfigurations({ setup }: { setup: Setup }) { const { internalClient, indices } = setup; @@ -17,7 +18,10 @@ export async function listConfigurations({ setup }: { setup: Setup }) { size: 200, }; - const resp = await internalClient.search(params); + const resp = await withApmSpan('list_agent_configurations', () => + internalClient.search(params) + ); + return resp.hits.hits .map(convertConfigSettingsToString) .map((hit) => hit._source); diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts index b5e35dde04a90..2026742a936a4 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts @@ -8,6 +8,7 @@ import { Setup } from '../../helpers/setup_request'; import { AgentConfiguration } from '../../../../common/agent_configuration/configuration_types'; +// We're not wrapping this function with a span as it is not blocking the request export async function markAppliedByAgent({ id, body, diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts index 546d1e562de1c..0e7205c309e9f 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/search_configurations.ts @@ -13,6 +13,7 @@ import { import { Setup } from '../../helpers/setup_request'; import { AgentConfiguration } from '../../../../common/agent_configuration/configuration_types'; import { convertConfigSettingsToString } from './convert_settings_to_string'; +import { withApmSpan } from '../../../utils/with_apm_span'; export async function searchConfigurations({ service, @@ -21,61 +22,67 @@ export async function searchConfigurations({ service: AgentConfiguration['service']; setup: Setup; }) { - const { internalClient, indices } = setup; + return withApmSpan('search_agent_configurations', async () => { + const { internalClient, indices } = setup; - // In the following `constant_score` is being used to disable IDF calculation (where frequency of a term influences scoring). - // Additionally a boost has been added to service.name to ensure it scores higher. - // If there is tie between a config with a matching service.name and a config with a matching environment, the config that matches service.name wins - const serviceNameFilter = service.name - ? [ - { - constant_score: { - filter: { term: { [SERVICE_NAME]: service.name } }, - boost: 2, + // In the following `constant_score` is being used to disable IDF calculation (where frequency of a term influences scoring). + // Additionally a boost has been added to service.name to ensure it scores higher. + // If there is tie between a config with a matching service.name and a config with a matching environment, the config that matches service.name wins + const serviceNameFilter = service.name + ? [ + { + constant_score: { + filter: { term: { [SERVICE_NAME]: service.name } }, + boost: 2, + }, }, - }, - ] - : []; + ] + : []; - const environmentFilter = service.environment - ? [ - { - constant_score: { - filter: { term: { [SERVICE_ENVIRONMENT]: service.environment } }, - boost: 1, + const environmentFilter = service.environment + ? [ + { + constant_score: { + filter: { term: { [SERVICE_ENVIRONMENT]: service.environment } }, + boost: 1, + }, }, - }, - ] - : []; + ] + : []; - const params = { - index: indices.apmAgentConfigurationIndex, - body: { - query: { - bool: { - minimum_should_match: 2, - should: [ - ...serviceNameFilter, - ...environmentFilter, - { bool: { must_not: [{ exists: { field: SERVICE_NAME } }] } }, - { - bool: { must_not: [{ exists: { field: SERVICE_ENVIRONMENT } }] }, - }, - ], + const params = { + index: indices.apmAgentConfigurationIndex, + body: { + query: { + bool: { + minimum_should_match: 2, + should: [ + ...serviceNameFilter, + ...environmentFilter, + { bool: { must_not: [{ exists: { field: SERVICE_NAME } }] } }, + { + bool: { + must_not: [{ exists: { field: SERVICE_ENVIRONMENT } }], + }, + }, + ], + }, }, }, - }, - }; + }; - const resp = await internalClient.search( - params - ); + const resp = await internalClient.search( + params + ); - const hit = resp.hits.hits[0] as ESSearchHit | undefined; + const hit = resp.hits.hits[0] as + | ESSearchHit + | undefined; - if (!hit) { - return; - } + if (!hit) { + return; + } - return convertConfigSettingsToString(hit); + return convertConfigSettingsToString(hit); + }); } diff --git a/x-pack/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts b/x-pack/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts index 3f346891138c4..14a5830d8246c 100644 --- a/x-pack/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts +++ b/x-pack/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts @@ -10,19 +10,22 @@ import { APM_INDICES_SAVED_OBJECT_TYPE, APM_INDICES_SAVED_OBJECT_ID, } from '../../../../common/apm_saved_object_constants'; +import { withApmSpan } from '../../../utils/with_apm_span'; import { ApmIndicesConfig } from './get_apm_indices'; -export async function saveApmIndices( +export function saveApmIndices( savedObjectsClient: SavedObjectsClientContract, apmIndices: Partial ) { - return await savedObjectsClient.create( - APM_INDICES_SAVED_OBJECT_TYPE, - removeEmpty(apmIndices), - { - id: APM_INDICES_SAVED_OBJECT_ID, - overwrite: true, - } + return withApmSpan('save_apm_indices', () => + savedObjectsClient.create( + APM_INDICES_SAVED_OBJECT_TYPE, + removeEmpty(apmIndices), + { + id: APM_INDICES_SAVED_OBJECT_ID, + overwrite: true, + } + ) ); } diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts index b24124565b181..7e546fb555036 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_or_update_custom_link.ts @@ -12,8 +12,9 @@ import { import { Setup } from '../../helpers/setup_request'; import { toESFormat } from './helper'; import { APMIndexDocumentParams } from '../../helpers/create_es_client/create_internal_es_client'; +import { withApmSpan } from '../../../utils/with_apm_span'; -export async function createOrUpdateCustomLink({ +export function createOrUpdateCustomLink({ customLinkId, customLink, setup, @@ -22,21 +23,23 @@ export async function createOrUpdateCustomLink({ customLink: Omit; setup: Setup; }) { - const { internalClient, indices } = setup; + return withApmSpan('create_or_update_custom_link', () => { + const { internalClient, indices } = setup; - const params: APMIndexDocumentParams = { - refresh: true, - index: indices.apmCustomLinkIndex, - body: { - '@timestamp': Date.now(), - ...toESFormat(customLink), - }, - }; + const params: APMIndexDocumentParams = { + refresh: true, + index: indices.apmCustomLinkIndex, + body: { + '@timestamp': Date.now(), + ...toESFormat(customLink), + }, + }; - // by specifying an id elasticsearch will delete the previous doc and insert the updated doc - if (customLinkId) { - params.id = customLinkId; - } + // by specifying an id elasticsearch will delete the previous doc and insert the updated doc + if (customLinkId) { + params.id = customLinkId; + } - return internalClient.index(params); + return internalClient.index(params); + }); } diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/delete_custom_link.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/delete_custom_link.ts index 64eba026770e5..7c88bcc43cc7f 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/delete_custom_link.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/delete_custom_link.ts @@ -5,22 +5,25 @@ * 2.0. */ +import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup } from '../../helpers/setup_request'; -export async function deleteCustomLink({ +export function deleteCustomLink({ customLinkId, setup, }: { customLinkId: string; setup: Setup; }) { - const { internalClient, indices } = setup; + return withApmSpan('delete_custom_link', () => { + const { internalClient, indices } = setup; - const params = { - refresh: 'wait_for' as const, - index: indices.apmCustomLinkIndex, - id: customLinkId, - }; + const params = { + refresh: 'wait_for' as const, + index: indices.apmCustomLinkIndex, + id: customLinkId, + }; - return internalClient.delete(params); + return internalClient.delete(params); + }); } diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts index 679b6b62776e0..8e343ecfe6a64 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/get_transaction.ts @@ -10,40 +10,43 @@ import { Setup } from '../../helpers/setup_request'; import { ProcessorEvent } from '../../../../common/processor_event'; import { filterOptionsRt } from './custom_link_types'; import { splitFilterValueByComma } from './helper'; +import { withApmSpan } from '../../../utils/with_apm_span'; -export async function getTransaction({ +export function getTransaction({ setup, filters = {}, }: { setup: Setup; filters?: t.TypeOf; }) { - const { apmEventClient } = setup; + return withApmSpan('get_transaction_for_custom_link', async () => { + const { apmEventClient } = setup; - const esFilters = Object.entries(filters) - // loops through the filters splitting the value by comma and removing white spaces - .map(([key, value]) => { - if (value) { - return { terms: { [key]: splitFilterValueByComma(value) } }; - } - }) - // removes filters without value - .filter((value) => value); + const esFilters = Object.entries(filters) + // loops through the filters splitting the value by comma and removing white spaces + .map(([key, value]) => { + if (value) { + return { terms: { [key]: splitFilterValueByComma(value) } }; + } + }) + // removes filters without value + .filter((value) => value); - const params = { - terminateAfter: 1, - apm: { - events: [ProcessorEvent.transaction as const], - }, - size: 1, - body: { - query: { - bool: { - filter: esFilters, + const params = { + terminateAfter: 1, + apm: { + events: [ProcessorEvent.transaction as const], + }, + size: 1, + body: { + query: { + bool: { + filter: esFilters, + }, }, }, - }, - }; - const resp = await apmEventClient.search(params); - return resp.hits.hits[0]?._source; + }; + const resp = await apmEventClient.search(params); + return resp.hits.hits[0]?._source; + }); } diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts index 3d8a003155455..7437b8328b876 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/list_custom_links.ts @@ -13,51 +13,54 @@ import { import { Setup } from '../../helpers/setup_request'; import { fromESFormat } from './helper'; import { filterOptionsRt } from './custom_link_types'; +import { withApmSpan } from '../../../utils/with_apm_span'; -export async function listCustomLinks({ +export function listCustomLinks({ setup, filters = {}, }: { setup: Setup; filters?: t.TypeOf; }): Promise { - const { internalClient, indices } = setup; - const esFilters = Object.entries(filters).map(([key, value]) => { - return { - bool: { - minimum_should_match: 1, - should: [ - { term: { [key]: value } }, - { bool: { must_not: [{ exists: { field: key } }] } }, - ], - }, - }; - }); - - const params = { - index: indices.apmCustomLinkIndex, - size: 500, - body: { - query: { + return withApmSpan('list_custom_links', async () => { + const { internalClient, indices } = setup; + const esFilters = Object.entries(filters).map(([key, value]) => { + return { bool: { - filter: esFilters, + minimum_should_match: 1, + should: [ + { term: { [key]: value } }, + { bool: { must_not: [{ exists: { field: key } }] } }, + ], }, - }, - sort: [ - { - 'label.keyword': { - order: 'asc', + }; + }); + + const params = { + index: indices.apmCustomLinkIndex, + size: 500, + body: { + query: { + bool: { + filter: esFilters, }, }, - ], - }, - }; - const resp = await internalClient.search(params); - const customLinks = resp.hits.hits.map((item) => - fromESFormat({ - id: item._id, - ...item._source, - }) - ); - return customLinks; + sort: [ + { + 'label.keyword': { + order: 'asc', + }, + }, + ], + }, + }; + const resp = await internalClient.search(params); + const customLinks = resp.hits.hits.map((item) => + fromESFormat({ + id: item._id, + ...item._source, + }) + ); + return customLinks; + }); } diff --git a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts index de2136b0c8b5a..c9769b6143c95 100644 --- a/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts +++ b/x-pack/plugins/apm/server/lib/traces/get_trace_items.ts @@ -18,6 +18,7 @@ import { APMError } from '../../../typings/es_schemas/ui/apm_error'; import { rangeFilter } from '../../../common/utils/range_filter'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { PromiseValueType } from '../../../typings/common'; +import { withApmSpan } from '../../utils/with_apm_span'; interface ErrorsPerTransaction { [transactionId: string]: number; @@ -27,94 +28,103 @@ export async function getTraceItems( traceId: string, setup: Setup & SetupTimeRange ) { - const { start, end, apmEventClient, config } = setup; - const maxTraceItems = config['xpack.apm.ui.maxTraceItems']; - const excludedLogLevels = ['debug', 'info', 'warning']; + return withApmSpan('get_trace_items', async () => { + const { start, end, apmEventClient, config } = setup; + const maxTraceItems = config['xpack.apm.ui.maxTraceItems']; + const excludedLogLevels = ['debug', 'info', 'warning']; - const errorResponsePromise = apmEventClient.search({ - apm: { - events: [ProcessorEvent.error], - }, - body: { - size: maxTraceItems, - query: { - bool: { - filter: [ - { term: { [TRACE_ID]: traceId } }, - { range: rangeFilter(start, end) }, - ], - must_not: { terms: { [ERROR_LOG_LEVEL]: excludedLogLevels } }, + const errorResponsePromise = withApmSpan('get_trace_error_items', () => + apmEventClient.search({ + apm: { + events: [ProcessorEvent.error], }, - }, - aggs: { - by_transaction_id: { - terms: { - field: TRANSACTION_ID, - size: maxTraceItems, - // high cardinality - execution_hint: 'map' as const, + body: { + size: maxTraceItems, + query: { + bool: { + filter: [ + { term: { [TRACE_ID]: traceId } }, + { range: rangeFilter(start, end) }, + ], + must_not: { terms: { [ERROR_LOG_LEVEL]: excludedLogLevels } }, + }, + }, + aggs: { + by_transaction_id: { + terms: { + field: TRANSACTION_ID, + size: maxTraceItems, + // high cardinality + execution_hint: 'map' as const, + }, + }, }, }, - }, - }, - }); + }) + ); - const traceResponsePromise = apmEventClient.search({ - apm: { - events: [ProcessorEvent.span, ProcessorEvent.transaction], - }, - body: { - size: maxTraceItems, - query: { - bool: { - filter: [ - { term: { [TRACE_ID]: traceId } }, - { range: rangeFilter(start, end) }, - ], - should: { - exists: { field: PARENT_ID }, + const traceResponsePromise = withApmSpan('get_trace_span_items', () => + apmEventClient.search({ + apm: { + events: [ProcessorEvent.span, ProcessorEvent.transaction], + }, + body: { + size: maxTraceItems, + query: { + bool: { + filter: [ + { term: { [TRACE_ID]: traceId } }, + { range: rangeFilter(start, end) }, + ], + should: { + exists: { field: PARENT_ID }, + }, + }, }, + sort: [ + { _score: { order: 'asc' as const } }, + { [TRANSACTION_DURATION]: { order: 'desc' as const } }, + { [SPAN_DURATION]: { order: 'desc' as const } }, + ], + track_total_hits: true, }, - }, - sort: [ - { _score: { order: 'asc' as const } }, - { [TRANSACTION_DURATION]: { order: 'desc' as const } }, - { [SPAN_DURATION]: { order: 'desc' as const } }, - ], - track_total_hits: true, - }, - }); + }) + ); - const [errorResponse, traceResponse]: [ - // explicit intermediary types to avoid TS "excessively deep" error - PromiseValueType, - PromiseValueType - ] = (await Promise.all([errorResponsePromise, traceResponsePromise])) as any; + const [errorResponse, traceResponse]: [ + // explicit intermediary types to avoid TS "excessively deep" error + PromiseValueType, + PromiseValueType + ] = (await Promise.all([ + errorResponsePromise, + traceResponsePromise, + ])) as any; - const exceedsMax = traceResponse.hits.total.value > maxTraceItems; + const exceedsMax = traceResponse.hits.total.value > maxTraceItems; - const items = traceResponse.hits.hits.map((hit) => hit._source); + const items = traceResponse.hits.hits.map((hit) => hit._source); - const errorFrequencies: { - errorsPerTransaction: ErrorsPerTransaction; - errorDocs: APMError[]; - } = { - errorDocs: errorResponse.hits.hits.map(({ _source }) => _source), - errorsPerTransaction: - errorResponse.aggregations?.by_transaction_id.buckets.reduce( - (acc, current) => { - return { - ...acc, - [current.key]: current.doc_count, - }; - }, - {} as ErrorsPerTransaction - ) ?? {}, - }; + const errorFrequencies: { + errorsPerTransaction: ErrorsPerTransaction; + errorDocs: APMError[]; + } = { + errorDocs: errorResponse.hits.hits.map(({ _source }) => _source), + errorsPerTransaction: + errorResponse.aggregations?.by_transaction_id.buckets.reduce( + (acc, current) => { + return { + ...acc, + [current.key]: current.doc_count, + }; + }, + {} as ErrorsPerTransaction + ) ?? {}, + }; - return { - items, - exceedsMax, - ...errorFrequencies, - }; + return { + items, + exceedsMax, + ...errorFrequencies, + }; + }); } 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 0d0f376f49353..46a55d9004aba 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -17,6 +17,7 @@ import { import { joinByKey } from '../../../common/utils/join_by_key'; import { getTransactionGroupsProjection } from '../../projections/transaction_groups'; import { mergeProjection } from '../../projections/util/merge_projection'; +import { withApmSpan } from '../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getAverages, @@ -95,115 +96,121 @@ function getItemsWithRelativeImpact( return itemsWithRelativeImpact; } -export async function transactionGroupsFetcher( +export function transactionGroupsFetcher( options: Options, setup: TransactionGroupSetup, bucketSize: number ) { - const projection = getTransactionGroupsProjection({ - setup, - options, - }); - - const isTopTraces = options.type === 'top_traces'; - - // @ts-expect-error - delete projection.body.aggs; - - // traces overview is hardcoded to 10000 - // transactions overview: 1 extra bucket is added to check whether the total number of buckets exceed the specified bucket size. - const expectedBucketSize = isTopTraces ? 10000 : bucketSize; - const size = isTopTraces ? 10000 : expectedBucketSize + 1; - - const request = mergeProjection(projection, { - body: { - size: 0, - aggs: { - transaction_groups: { - ...(isTopTraces - ? { - composite: { - sources: [ - { [SERVICE_NAME]: { terms: { field: SERVICE_NAME } } }, - { - [TRANSACTION_NAME]: { - terms: { field: TRANSACTION_NAME }, + const spanName = + options.type === 'top_traces' ? 'get_top_traces' : 'get_top_transactions'; + + return withApmSpan(spanName, async () => { + const projection = getTransactionGroupsProjection({ + setup, + options, + }); + + const isTopTraces = options.type === 'top_traces'; + + // @ts-expect-error + delete projection.body.aggs; + + // traces overview is hardcoded to 10000 + // transactions overview: 1 extra bucket is added to check whether the total number of buckets exceed the specified bucket size. + const expectedBucketSize = isTopTraces ? 10000 : bucketSize; + const size = isTopTraces ? 10000 : expectedBucketSize + 1; + + const request = mergeProjection(projection, { + body: { + size: 0, + aggs: { + transaction_groups: { + ...(isTopTraces + ? { + composite: { + sources: [ + { [SERVICE_NAME]: { terms: { field: SERVICE_NAME } } }, + { + [TRANSACTION_NAME]: { + terms: { field: TRANSACTION_NAME }, + }, }, - }, - ], - size, - }, - } - : { - terms: { - field: TRANSACTION_NAME, - size, - }, - }), + ], + size, + }, + } + : { + terms: { + field: TRANSACTION_NAME, + size, + }, + }), + }, }, }, - }, - }); + }); - const params = { - request, - setup, - searchAggregatedTransactions: options.searchAggregatedTransactions, - }; + const params = { + request, + setup, + searchAggregatedTransactions: options.searchAggregatedTransactions, + }; - const [counts, averages, sums, percentiles] = await Promise.all([ - getCounts(params), - getAverages(params), - getSums(params), - !isTopTraces ? getPercentiles(params) : Promise.resolve(undefined), - ]); - - const stats = [ - ...averages, - ...counts, - ...sums, - ...(percentiles ? percentiles : []), - ]; - - const items = joinByKey(stats, 'key'); - - const itemsWithRelativeImpact = getItemsWithRelativeImpact(setup, items); - - const defaultServiceName = - options.type === 'top_transactions' ? options.serviceName : undefined; - - const itemsWithKeys: TransactionGroup[] = itemsWithRelativeImpact.map( - (item) => { - let transactionName: string; - let serviceName: string; - - if (typeof item.key === 'string') { - transactionName = item.key; - serviceName = defaultServiceName!; - } else { - transactionName = item.key[TRANSACTION_NAME]; - serviceName = item.key[SERVICE_NAME]; + const [counts, averages, sums, percentiles] = await Promise.all([ + getCounts(params), + getAverages(params), + getSums(params), + !isTopTraces ? getPercentiles(params) : Promise.resolve(undefined), + ]); + + const stats = [ + ...averages, + ...counts, + ...sums, + ...(percentiles ? percentiles : []), + ]; + + const items = joinByKey(stats, 'key'); + + const itemsWithRelativeImpact = getItemsWithRelativeImpact(setup, items); + + const defaultServiceName = + options.type === 'top_transactions' ? options.serviceName : undefined; + + const itemsWithKeys: TransactionGroup[] = itemsWithRelativeImpact.map( + (item) => { + let transactionName: string; + let serviceName: string; + + if (typeof item.key === 'string') { + transactionName = item.key; + serviceName = defaultServiceName!; + } else { + transactionName = item.key[TRANSACTION_NAME]; + serviceName = item.key[SERVICE_NAME]; + } + + return { + ...item, + transactionName, + serviceName, + }; } + ); - return { - ...item, - transactionName, - serviceName, - }; - } - ); - - return { - items: take( - // sort by impact by default so most impactful services are not cut off - sortBy(itemsWithKeys, 'impact').reverse(), - bucketSize - ), - // The aggregation is considered accurate if the configured bucket size is larger or equal to the number of buckets returned - // the actual number of buckets retrieved are `bucketsize + 1` to detect whether it's above the limit - isAggregationAccurate: expectedBucketSize >= itemsWithRelativeImpact.length, - bucketSize, - }; + return { + items: take( + // sort by impact by default so most impactful services are not cut off + sortBy(itemsWithKeys, 'impact').reverse(), + bucketSize + ), + // The aggregation is considered accurate if the configured bucket size is larger or equal to the number of buckets returned + // the actual number of buckets retrieved are `bucketsize + 1` to detect whether it's above the limit + isAggregationAccurate: + expectedBucketSize >= itemsWithRelativeImpact.length, + bucketSize, + }; + }); } interface TransactionGroup { diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts index bd0edfcf9e9e5..839efc9009c38 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts @@ -26,6 +26,7 @@ import { getOutcomeAggregation, getTransactionErrorRateTimeSeries, } from '../helpers/transaction_error_rate'; +import { withApmSpan } from '../../utils/with_apm_span'; export async function getErrorRate({ serviceName, @@ -44,74 +45,78 @@ export async function getErrorRate({ transactionErrorRate: Coordinate[]; average: number | null; }> { - const { start, end, esFilter, apmEventClient } = setup; + return withApmSpan('get_transaction_group_error_rate', async () => { + const { start, end, esFilter, apmEventClient } = setup; - const transactionNamefilter = transactionName - ? [{ term: { [TRANSACTION_NAME]: transactionName } }] - : []; - const transactionTypefilter = transactionType - ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] - : []; + const transactionNamefilter = transactionName + ? [{ term: { [TRANSACTION_NAME]: transactionName } }] + : []; + const transactionTypefilter = transactionType + ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] + : []; - const filter = [ - { term: { [SERVICE_NAME]: serviceName } }, - { range: rangeFilter(start, end) }, - { - terms: { [EVENT_OUTCOME]: [EventOutcome.failure, EventOutcome.success] }, - }, - ...getDocumentTypeFilterForAggregatedTransactions( - searchAggregatedTransactions - ), - ...transactionNamefilter, - ...transactionTypefilter, - ...esFilter, - ]; + const filter = [ + { term: { [SERVICE_NAME]: serviceName } }, + { range: rangeFilter(start, end) }, + { + terms: { + [EVENT_OUTCOME]: [EventOutcome.failure, EventOutcome.success], + }, + }, + ...getDocumentTypeFilterForAggregatedTransactions( + searchAggregatedTransactions + ), + ...transactionNamefilter, + ...transactionTypefilter, + ...esFilter, + ]; - const outcomes = getOutcomeAggregation(); + const outcomes = getOutcomeAggregation(); - const params = { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { bool: { filter } }, - aggs: { - outcomes, - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: getBucketSize({ start, end }).intervalString, - min_doc_count: 0, - extended_bounds: { min: start, max: end }, - }, - aggs: { - outcomes, + const params = { + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + body: { + size: 0, + query: { bool: { filter } }, + aggs: { + outcomes, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: getBucketSize({ start, end }).intervalString, + min_doc_count: 0, + extended_bounds: { min: start, max: end }, + }, + aggs: { + outcomes, + }, }, }, }, - }, - }; + }; - const resp = await apmEventClient.search(params); + const resp = await apmEventClient.search(params); - const noHits = resp.hits.total.value === 0; + const noHits = resp.hits.total.value === 0; - if (!resp.aggregations) { - return { noHits, transactionErrorRate: [], average: null }; - } + if (!resp.aggregations) { + return { noHits, transactionErrorRate: [], average: null }; + } - const transactionErrorRate = getTransactionErrorRateTimeSeries( - resp.aggregations.timeseries.buckets - ); + const transactionErrorRate = getTransactionErrorRateTimeSeries( + resp.aggregations.timeseries.buckets + ); - const average = calculateTransactionErrorPercentage( - resp.aggregations.outcomes - ); + const average = calculateTransactionErrorPercentage( + resp.aggregations.outcomes + ); - return { noHits, transactionErrorRate, average }; + return { noHits, transactionErrorRate, average }; + }); } diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts index 5c740fc0db686..ee19a6a8d1591 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_transaction_group_stats.ts @@ -11,6 +11,7 @@ import { arrayUnionToCallable } from '../../../common/utils/array_union_to_calla import { AggregationInputMap } from '../../../../../typings/elasticsearch'; import { TransactionGroupRequestBase, TransactionGroupSetup } from './fetcher'; import { getTransactionDurationFieldForAggregatedTransactions } from '../helpers/aggregated_transactions'; +import { withApmSpan } from '../../utils/with_apm_span'; interface MetricParams { request: TransactionGroupRequestBase; @@ -35,116 +36,120 @@ function mergeRequestWithAggs< }); } -export async function getAverages({ +export function getAverages({ request, setup, searchAggregatedTransactions, }: MetricParams) { - const params = mergeRequestWithAggs(request, { - avg: { + return withApmSpan('get_avg_transaction_group_duration', async () => { + const params = mergeRequestWithAggs(request, { avg: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), + avg: { + field: getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ), + }, }, - }, - }); - - const response = await setup.apmEventClient.search(params); - - return arrayUnionToCallable( - response.aggregations?.transaction_groups.buckets ?? [] - ).map((bucket) => { - return { - key: bucket.key as BucketKey, - avg: bucket.avg.value, - }; + }); + + const response = await setup.apmEventClient.search(params); + + return arrayUnionToCallable( + response.aggregations?.transaction_groups.buckets ?? [] + ).map((bucket) => { + return { + key: bucket.key as BucketKey, + avg: bucket.avg.value, + }; + }); }); } -export async function getCounts({ - request, - setup, - searchAggregatedTransactions, -}: MetricParams) { - const params = mergeRequestWithAggs(request, { - transaction_type: { - top_hits: { - size: 1, - _source: [TRANSACTION_TYPE], +export function getCounts({ request, setup }: MetricParams) { + return withApmSpan('get_transaction_group_transaction_count', async () => { + const params = mergeRequestWithAggs(request, { + transaction_type: { + top_hits: { + size: 1, + _source: [TRANSACTION_TYPE], + }, }, - }, - }); - - const response = await setup.apmEventClient.search(params); - - return arrayUnionToCallable( - response.aggregations?.transaction_groups.buckets ?? [] - ).map((bucket) => { - // type is Transaction | APMBaseDoc because it could be a metric document - const source = (bucket.transaction_type.hits.hits[0] - ._source as unknown) as { transaction: { type: string } }; - - return { - key: bucket.key as BucketKey, - count: bucket.doc_count, - transactionType: source.transaction.type, - }; + }); + + const response = await setup.apmEventClient.search(params); + + return arrayUnionToCallable( + response.aggregations?.transaction_groups.buckets ?? [] + ).map((bucket) => { + // type is Transaction | APMBaseDoc because it could be a metric document + const source = (bucket.transaction_type.hits.hits[0] + ._source as unknown) as { transaction: { type: string } }; + + return { + key: bucket.key as BucketKey, + count: bucket.doc_count, + transactionType: source.transaction.type, + }; + }); }); } -export async function getSums({ +export function getSums({ request, setup, searchAggregatedTransactions, }: MetricParams) { - const params = mergeRequestWithAggs(request, { - sum: { + return withApmSpan('get_transaction_group_latency_sums', async () => { + const params = mergeRequestWithAggs(request, { sum: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), + sum: { + field: getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ), + }, }, - }, - }); - - const response = await setup.apmEventClient.search(params); - - return arrayUnionToCallable( - response.aggregations?.transaction_groups.buckets ?? [] - ).map((bucket) => { - return { - key: bucket.key as BucketKey, - sum: bucket.sum.value, - }; + }); + + const response = await setup.apmEventClient.search(params); + + return arrayUnionToCallable( + response.aggregations?.transaction_groups.buckets ?? [] + ).map((bucket) => { + return { + key: bucket.key as BucketKey, + sum: bucket.sum.value, + }; + }); }); } -export async function getPercentiles({ +export function getPercentiles({ request, setup, searchAggregatedTransactions, }: MetricParams) { - const params = mergeRequestWithAggs(request, { - p95: { - percentiles: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), - hdr: { number_of_significant_value_digits: 2 }, - percents: [95], + return withApmSpan('get_transaction_group_latency_percentiles', async () => { + const params = mergeRequestWithAggs(request, { + p95: { + percentiles: { + field: getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ), + hdr: { number_of_significant_value_digits: 2 }, + percents: [95], + }, }, - }, - }); - - const response = await setup.apmEventClient.search(params); - - return arrayUnionToCallable( - response.aggregations?.transaction_groups.buckets ?? [] - ).map((bucket) => { - return { - key: bucket.key as BucketKey, - p95: Object.values(bucket.p95.values)[0], - }; + }); + + const response = await setup.apmEventClient.search(params); + + return arrayUnionToCallable( + response.aggregations?.transaction_groups.buckets ?? [] + ).map((bucket) => { + return { + key: bucket.key as BucketKey, + p95: Object.values(bucket.p95.values)[0], + }; + }); }); } diff --git a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts index 53669d745c534..8a2579b4a2b87 100644 --- a/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/breakdown/index.ts @@ -22,8 +22,9 @@ import { rangeFilter } from '../../../../common/utils/range_filter'; import { getMetricsDateHistogramParams } from '../../helpers/metrics'; import { MAX_KPIS } from './constants'; import { getVizColorForIndex } from '../../../../common/viz_colors'; +import { withApmSpan } from '../../../utils/with_apm_span'; -export async function getTransactionBreakdown({ +export function getTransactionBreakdown({ setup, serviceName, transactionName, @@ -34,193 +35,195 @@ export async function getTransactionBreakdown({ transactionName?: string; transactionType: string; }) { - const { esFilter, apmEventClient, start, end, config } = setup; + return withApmSpan('get_transaction_breakdown', async () => { + const { esFilter, apmEventClient, start, end, config } = setup; - const subAggs = { - sum_all_self_times: { - sum: { - field: SPAN_SELF_TIME_SUM, - }, - }, - total_transaction_breakdown_count: { - sum: { - field: TRANSACTION_BREAKDOWN_COUNT, + const subAggs = { + sum_all_self_times: { + sum: { + field: SPAN_SELF_TIME_SUM, + }, }, - }, - types: { - terms: { - field: SPAN_TYPE, - size: 20, - order: { - _count: 'desc' as const, + total_transaction_breakdown_count: { + sum: { + field: TRANSACTION_BREAKDOWN_COUNT, }, }, - aggs: { - subtypes: { - terms: { - field: SPAN_SUBTYPE, - missing: '', - size: 20, - order: { - _count: 'desc' as const, - }, + types: { + terms: { + field: SPAN_TYPE, + size: 20, + order: { + _count: 'desc' as const, }, - aggs: { - total_self_time_per_subtype: { - sum: { - field: SPAN_SELF_TIME_SUM, + }, + aggs: { + subtypes: { + terms: { + field: SPAN_SUBTYPE, + missing: '', + size: 20, + order: { + _count: 'desc' as const, + }, + }, + aggs: { + total_self_time_per_subtype: { + sum: { + field: SPAN_SELF_TIME_SUM, + }, }, }, }, }, }, - }, - }; - - const filters = [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - { range: rangeFilter(start, end) }, - ...esFilter, - ]; - - if (transactionName) { - filters.push({ term: { [TRANSACTION_NAME]: transactionName } }); - } - - const params = { - apm: { - events: [ProcessorEvent.metric], - }, - body: { - size: 0, - query: { - bool: { - filter: filters, - }, + }; + + const filters = [ + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + { range: rangeFilter(start, end) }, + ...esFilter, + ]; + + if (transactionName) { + filters.push({ term: { [TRANSACTION_NAME]: transactionName } }); + } + + const params = { + apm: { + events: [ProcessorEvent.metric], }, - aggs: { - ...subAggs, - by_date: { - date_histogram: getMetricsDateHistogramParams( - start, - end, - config['xpack.apm.metricsInterval'] - ), - aggs: subAggs, + body: { + size: 0, + query: { + bool: { + filter: filters, + }, + }, + aggs: { + ...subAggs, + by_date: { + date_histogram: getMetricsDateHistogramParams( + start, + end, + config['xpack.apm.metricsInterval'] + ), + aggs: subAggs, + }, }, }, - }, - }; - - const resp = await apmEventClient.search(params); - - const formatBucket = ( - aggs: - | Required['aggregations'] - | Required['aggregations']['by_date']['buckets'][0] - ) => { - const sumAllSelfTimes = aggs.sum_all_self_times.value || 0; - - const breakdowns = flatten( - aggs.types.buckets.map((bucket) => { - const type = bucket.key as string; - - return bucket.subtypes.buckets.map((subBucket) => { - return { - name: (subBucket.key as string) || type, - percentage: - (subBucket.total_self_time_per_subtype.value || 0) / - sumAllSelfTimes, - }; - }); - }) - ); - - return breakdowns; - }; - - const visibleKpis = resp.aggregations - ? orderBy(formatBucket(resp.aggregations), 'percentage', 'desc').slice( - 0, - MAX_KPIS - ) - : []; - - const kpis = orderBy( - visibleKpis.map((kpi) => ({ - ...kpi, - lowerCaseName: kpi.name.toLowerCase(), - })), - 'lowerCaseName' - ).map((kpi, index) => { - const { lowerCaseName, ...rest } = kpi; - return { - ...rest, - color: getVizColorForIndex(index), }; - }); - const kpiNames = kpis.map((kpi) => kpi.name); + const resp = await apmEventClient.search(params); + + const formatBucket = ( + aggs: + | Required['aggregations'] + | Required['aggregations']['by_date']['buckets'][0] + ) => { + const sumAllSelfTimes = aggs.sum_all_self_times.value || 0; + + const breakdowns = flatten( + aggs.types.buckets.map((bucket) => { + const type = bucket.key as string; + + return bucket.subtypes.buckets.map((subBucket) => { + return { + name: (subBucket.key as string) || type, + percentage: + (subBucket.total_self_time_per_subtype.value || 0) / + sumAllSelfTimes, + }; + }); + }) + ); + + return breakdowns; + }; - const bucketsByDate = resp.aggregations?.by_date.buckets || []; + const visibleKpis = resp.aggregations + ? orderBy(formatBucket(resp.aggregations), 'percentage', 'desc').slice( + 0, + MAX_KPIS + ) + : []; + + const kpis = orderBy( + visibleKpis.map((kpi) => ({ + ...kpi, + lowerCaseName: kpi.name.toLowerCase(), + })), + 'lowerCaseName' + ).map((kpi, index) => { + const { lowerCaseName, ...rest } = kpi; + return { + ...rest, + color: getVizColorForIndex(index), + }; + }); - const timeseriesPerSubtype = bucketsByDate.reduce((prev, bucket) => { - const formattedValues = formatBucket(bucket); - const time = bucket.key; + const kpiNames = kpis.map((kpi) => kpi.name); - const updatedSeries = kpiNames.reduce((p, kpiName) => { - const { name, percentage } = formattedValues.find( - (val) => val.name === kpiName - ) || { - name: kpiName, - percentage: null, - }; + const bucketsByDate = resp.aggregations?.by_date.buckets || []; - if (!p[name]) { - p[name] = []; - } - return { - ...p, - [name]: p[name].concat({ - x: time, - y: percentage, - }), - }; - }, prev); - - const lastValues = Object.values(updatedSeries).map(last); - - // If for a given timestamp, some series have data, but others do not, - // we have to set any null values to 0 to make sure the stacked area chart - // is drawn correctly. - // If we set all values to 0, the chart always displays null values as 0, - // and the chart looks weird. - const hasAnyValues = lastValues.some((value) => value?.y !== null); - const hasNullValues = lastValues.some((value) => value?.y === null); - - if (hasAnyValues && hasNullValues) { - Object.values(updatedSeries).forEach((series) => { - const value = series[series.length - 1]; - const isEmpty = value.y === null; - if (isEmpty) { - // local mutation to prevent complicated map/reduce calls - value.y = 0; + const timeseriesPerSubtype = bucketsByDate.reduce((prev, bucket) => { + const formattedValues = formatBucket(bucket); + const time = bucket.key; + + const updatedSeries = kpiNames.reduce((p, kpiName) => { + const { name, percentage } = formattedValues.find( + (val) => val.name === kpiName + ) || { + name: kpiName, + percentage: null, + }; + + if (!p[name]) { + p[name] = []; } - }); - } + return { + ...p, + [name]: p[name].concat({ + x: time, + y: percentage, + }), + }; + }, prev); + + const lastValues = Object.values(updatedSeries).map(last); + + // If for a given timestamp, some series have data, but others do not, + // we have to set any null values to 0 to make sure the stacked area chart + // is drawn correctly. + // If we set all values to 0, the chart always displays null values as 0, + // and the chart looks weird. + const hasAnyValues = lastValues.some((value) => value?.y !== null); + const hasNullValues = lastValues.some((value) => value?.y === null); + + if (hasAnyValues && hasNullValues) { + Object.values(updatedSeries).forEach((series) => { + const value = series[series.length - 1]; + const isEmpty = value.y === null; + if (isEmpty) { + // local mutation to prevent complicated map/reduce calls + value.y = 0; + } + }); + } - return updatedSeries; - }, {} as Record>); + return updatedSeries; + }, {} as Record>); - const timeseries = kpis.map((kpi) => ({ - title: kpi.name, - color: kpi.color, - type: 'areaStacked', - data: timeseriesPerSubtype[kpi.name], - hideLegend: false, - legendValue: asPercent(kpi.percentage, 1), - })); + const timeseries = kpis.map((kpi) => ({ + title: kpi.name, + color: kpi.color, + type: 'areaStacked', + data: timeseriesPerSubtype[kpi.name], + hideLegend: false, + legendValue: asPercent(kpi.percentage, 1), + })); - return { timeseries }; + return { timeseries }; + }); } diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts index 86621c7aaa7e2..22a34ded4c20d 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { withApmSpan } from '../../../../utils/with_apm_span'; import { SERVICE_NAME, TRACE_ID, @@ -65,134 +66,147 @@ export async function getBuckets({ setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; }) { - const { start, end, esFilter, apmEventClient } = setup; + return withApmSpan( + 'get_latency_distribution_buckets_with_samples', + async () => { + const { start, end, esFilter, apmEventClient } = setup; - const commonFilters = [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - { term: { [TRANSACTION_NAME]: transactionName } }, - { range: rangeFilter(start, end) }, - ...esFilter, - ]; + const commonFilters = [ + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + { term: { [TRANSACTION_NAME]: transactionName } }, + { range: rangeFilter(start, end) }, + ...esFilter, + ]; - async function getSamplesForDistributionBuckets() { - const response = await apmEventClient.search({ - apm: { - events: [ProcessorEvent.transaction], - }, - body: { - query: { - bool: { - filter: [ - ...commonFilters, - { term: { [TRANSACTION_SAMPLED]: true } }, - ], - should: [ - { term: { [TRACE_ID]: traceId } }, - { term: { [TRANSACTION_ID]: transactionId } }, - ], - }, - }, - aggs: { - distribution: { - histogram: getHistogramAggOptions({ - bucketSize, - field: TRANSACTION_DURATION, - distributionMax, - }), - aggs: { - samples: { - top_hits: { - _source: [TRANSACTION_ID, TRACE_ID], - size: 10, - sort: { - _score: 'desc', + async function getSamplesForDistributionBuckets() { + const response = await withApmSpan( + 'get_samples_for_latency_distribution_buckets', + () => + apmEventClient.search({ + apm: { + events: [ProcessorEvent.transaction], + }, + body: { + query: { + bool: { + filter: [ + ...commonFilters, + { term: { [TRANSACTION_SAMPLED]: true } }, + ], + should: [ + { term: { [TRACE_ID]: traceId } }, + { term: { [TRANSACTION_ID]: transactionId } }, + ], + }, + }, + aggs: { + distribution: { + histogram: getHistogramAggOptions({ + bucketSize, + field: TRANSACTION_DURATION, + distributionMax, + }), + aggs: { + samples: { + top_hits: { + _source: [TRANSACTION_ID, TRACE_ID], + size: 10, + sort: { + _score: 'desc', + }, + }, + }, + }, }, }, }, - }, - }, - }, - }, - }); + }) + ); - return ( - response.aggregations?.distribution.buckets.map((bucket) => { - return { - key: bucket.key, - samples: bucket.samples.hits.hits.map((hit) => ({ - traceId: hit._source.trace.id, - transactionId: hit._source.transaction.id, - })), - }; - }) ?? [] - ); - } + return ( + response.aggregations?.distribution.buckets.map((bucket) => { + return { + key: bucket.key, + samples: bucket.samples.hits.hits.map((hit) => ({ + traceId: hit._source.trace.id, + transactionId: hit._source.transaction.id, + })), + }; + }) ?? [] + ); + } - async function getDistributionBuckets() { - const response = await apmEventClient.search({ - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - query: { - bool: { - filter: [ - ...commonFilters, - ...getDocumentTypeFilterForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - }, - aggs: { - distribution: { - histogram: getHistogramAggOptions({ - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), - bucketSize, - distributionMax, - }), - }, - }, - }, - }); + async function getDistributionBuckets() { + const response = await withApmSpan( + 'get_latency_distribution_buckets', + () => + apmEventClient.search({ + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + body: { + query: { + bool: { + filter: [ + ...commonFilters, + ...getDocumentTypeFilterForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + }, + aggs: { + distribution: { + histogram: getHistogramAggOptions({ + field: getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ), + bucketSize, + distributionMax, + }), + }, + }, + }, + }) + ); - return ( - response.aggregations?.distribution.buckets.map((bucket) => { - return { - key: bucket.key, - count: bucket.doc_count, - }; - }) ?? [] - ); - } + return ( + response.aggregations?.distribution.buckets.map((bucket) => { + return { + key: bucket.key, + count: bucket.doc_count, + }; + }) ?? [] + ); + } - const [ - samplesForDistributionBuckets, - distributionBuckets, - ] = await Promise.all([ - getSamplesForDistributionBuckets(), - getDistributionBuckets(), - ]); + const [ + samplesForDistributionBuckets, + distributionBuckets, + ] = await Promise.all([ + getSamplesForDistributionBuckets(), + getDistributionBuckets(), + ]); - const buckets = joinByKey( - [...samplesForDistributionBuckets, ...distributionBuckets], - 'key' - ).map((bucket) => ({ - ...bucket, - samples: bucket.samples ?? [], - count: bucket.count ?? 0, - })); + const buckets = joinByKey( + [...samplesForDistributionBuckets, ...distributionBuckets], + 'key' + ).map((bucket) => ({ + ...bucket, + samples: bucket.samples ?? [], + count: bucket.count ?? 0, + })); - return { - noHits: buckets.length === 0, - bucketSize, - buckets, - }; + return { + noHits: buckets.length === 0, + bucketSize, + buckets, + }; + } + ); } diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts index 5a7502aaf5932..ed54dae32704e 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts @@ -15,6 +15,7 @@ import { getProcessorEventForAggregatedTransactions, getTransactionDurationFieldForAggregatedTransactions, } from '../../helpers/aggregated_transactions'; +import { withApmSpan } from '../../../utils/with_apm_span'; export async function getDistributionMax({ serviceName, @@ -29,49 +30,51 @@ export async function getDistributionMax({ setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; }) { - const { start, end, esFilter, apmEventClient } = setup; + return withApmSpan('get_latency_distribution_max', async () => { + const { start, end, esFilter, apmEventClient } = setup; - const params = { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - { term: { [TRANSACTION_NAME]: transactionName } }, - { - range: { - '@timestamp': { - gte: start, - lte: end, - format: 'epoch_millis', + const params = { + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ], + }, + body: { + size: 0, + query: { + bool: { + filter: [ + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + { term: { [TRANSACTION_NAME]: transactionName } }, + { + range: { + '@timestamp': { + gte: start, + lte: end, + format: 'epoch_millis', + }, }, }, - }, - ...esFilter, - ], + ...esFilter, + ], + }, }, - }, - aggs: { - stats: { - max: { - field: getTransactionDurationFieldForAggregatedTransactions( - searchAggregatedTransactions - ), + aggs: { + stats: { + max: { + field: getTransactionDurationFieldForAggregatedTransactions( + searchAggregatedTransactions + ), + }, }, }, }, - }, - }; + }; - const resp = await apmEventClient.search(params); - return resp.aggregations?.stats.value ?? null; + const resp = await apmEventClient.search(params); + return resp.aggregations?.stats.value ?? null; + }); } diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts index d1d2881b6cb5b..22436cac40183 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts @@ -10,6 +10,7 @@ import { getBuckets } from './get_buckets'; import { getDistributionMax } from './get_distribution_max'; import { roundToNearestFiveOrTen } from '../../helpers/round_to_nearest_five_or_ten'; import { MINIMUM_BUCKET_SIZE, BUCKET_TARGET_COUNT } from '../constants'; +import { withApmSpan } from '../../../utils/with_apm_span'; function getBucketSize(max: number) { const bucketSize = max / BUCKET_TARGET_COUNT; @@ -35,35 +36,37 @@ export async function getTransactionDistribution({ setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; }) { - const distributionMax = await getDistributionMax({ - serviceName, - transactionName, - transactionType, - setup, - searchAggregatedTransactions, - }); + return withApmSpan('get_transaction_latency_distribution', async () => { + const distributionMax = await getDistributionMax({ + serviceName, + transactionName, + transactionType, + setup, + searchAggregatedTransactions, + }); - if (distributionMax == null) { - return { noHits: true, buckets: [], bucketSize: 0 }; - } + if (distributionMax == null) { + return { noHits: true, buckets: [], bucketSize: 0 }; + } - const bucketSize = getBucketSize(distributionMax); + const bucketSize = getBucketSize(distributionMax); - const { buckets, noHits } = await getBuckets({ - serviceName, - transactionName, - transactionType, - transactionId, - traceId, - distributionMax, - bucketSize, - setup, - searchAggregatedTransactions, - }); + const { buckets, noHits } = await getBuckets({ + serviceName, + transactionName, + transactionType, + transactionId, + traceId, + distributionMax, + bucketSize, + setup, + searchAggregatedTransactions, + }); - return { - noHits, - buckets, - bucketSize, - }; + return { + noHits, + buckets, + bucketSize, + }; + }); } diff --git a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts index 7dd9ba63bd6f5..002ddd1ec35f0 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/fetcher.ts @@ -7,6 +7,7 @@ import { ESSearchResponse } from '../../../../../../typings/elasticsearch'; import { PromiseReturnType } from '../../../../../observability/typings/common'; +import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup } from '../../helpers/setup_request'; export type ESResponse = Exclude< @@ -14,7 +15,7 @@ export type ESResponse = Exclude< undefined >; -export async function anomalySeriesFetcher({ +export function anomalySeriesFetcher({ serviceName, transactionType, intervalString, @@ -29,63 +30,65 @@ export async function anomalySeriesFetcher({ start: number; end: number; }) { - const params = { - body: { - size: 0, - query: { - bool: { - filter: [ - { terms: { result_type: ['model_plot', 'record'] } }, - { term: { partition_field_value: serviceName } }, - { term: { by_field_value: transactionType } }, - { - range: { - timestamp: { - gte: start, - lte: end, - format: 'epoch_millis', + return withApmSpan('get_latency_anomaly_data', async () => { + const params = { + body: { + size: 0, + query: { + bool: { + filter: [ + { terms: { result_type: ['model_plot', 'record'] } }, + { term: { partition_field_value: serviceName } }, + { term: { by_field_value: transactionType } }, + { + range: { + timestamp: { + gte: start, + lte: end, + format: 'epoch_millis', + }, }, }, - }, - ], - }, - }, - aggs: { - job_id: { - terms: { - field: 'job_id', + ], }, - aggs: { - ml_avg_response_times: { - date_histogram: { - field: 'timestamp', - fixed_interval: intervalString, - extended_bounds: { min: start, max: end }, - }, - aggs: { - anomaly_score: { - top_metrics: { - metrics: [ - { field: 'record_score' }, - { field: 'timestamp' }, - { field: 'bucket_span' }, - ] as const, - sort: { - record_score: 'desc' as const, + }, + aggs: { + job_id: { + terms: { + field: 'job_id', + }, + aggs: { + ml_avg_response_times: { + date_histogram: { + field: 'timestamp', + fixed_interval: intervalString, + extended_bounds: { min: start, max: end }, + }, + aggs: { + anomaly_score: { + top_metrics: { + metrics: [ + { field: 'record_score' }, + { field: 'timestamp' }, + { field: 'bucket_span' }, + ] as const, + sort: { + record_score: 'desc' as const, + }, }, }, + lower: { min: { field: 'model_lower' } }, + upper: { max: { field: 'model_upper' } }, }, - lower: { min: { field: 'model_lower' } }, - upper: { max: { field: 'model_upper' } }, }, }, }, }, }, - }, - }; + }; - return (ml.mlSystem.mlAnomalySearch(params, []) as unknown) as Promise< - ESSearchResponse - >; + return (ml.mlSystem.mlAnomalySearch(params, []) as unknown) as Promise< + ESSearchResponse + >; + }); } diff --git a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts index f3dae239d957d..29dd562330cc1 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_anomaly_data/index.ts @@ -15,6 +15,7 @@ import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { anomalySeriesFetcher } from './fetcher'; import { getMLJobIds } from '../../service_map/get_service_anomalies'; import { ANOMALY_THRESHOLD } from '../../../../../ml/common'; +import { withApmSpan } from '../../../utils/with_apm_span'; export async function getAnomalySeries({ serviceName, @@ -59,74 +60,77 @@ export async function getAnomalySeries({ return undefined; } - const { intervalString } = getBucketSize({ start, end }); - - // move the start back with one bucket size, to ensure to get anomaly data in the beginning - // this is required because ML has a minimum bucket size (default is 900s) so if our buckets - // are smaller, we might have several null buckets in the beginning - const mlStart = start - 900 * 1000; - - const [anomaliesResponse, jobIds] = await Promise.all([ - anomalySeriesFetcher({ - serviceName, - transactionType, - intervalString, - ml, - start: mlStart, - end, - }), - getMLJobIds(ml.anomalyDetectors, environment), - ]); - - const scoreSeriesCollection = anomaliesResponse?.aggregations?.job_id.buckets - .filter((bucket) => jobIds.includes(bucket.key as string)) - .map((bucket) => { - const dateBuckets = bucket.ml_avg_response_times.buckets; - - return { - jobId: bucket.key as string, - anomalyScore: compact( - dateBuckets.map((dateBucket) => { - const metrics = maybe(dateBucket.anomaly_score.top[0])?.metrics; - const score = metrics?.record_score; - - if ( - !metrics || - !isFiniteNumber(score) || - score < ANOMALY_THRESHOLD.CRITICAL - ) { - return null; - } - - const anomalyStart = Date.parse(metrics.timestamp as string); - const anomalyEnd = - anomalyStart + (metrics.bucket_span as number) * 1000; - - return { - x0: anomalyStart, - x: anomalyEnd, - y: score, - }; - }) - ), - anomalyBoundaries: dateBuckets - .filter( - (dateBucket) => - dateBucket.lower.value !== null && dateBucket.upper.value !== null - ) - .map((dateBucket) => ({ - x: dateBucket.key, - y0: dateBucket.lower.value as number, - y: dateBucket.upper.value as number, - })), - }; - }); - - if ((scoreSeriesCollection?.length ?? 0) > 1) { - logger.warn( - `More than one ML job was found for ${serviceName} for environment ${environment}. Only showing results from ${scoreSeriesCollection?.[0].jobId}` - ); - } - - return scoreSeriesCollection?.[0]; + return withApmSpan('get_latency_anomaly_series', async () => { + const { intervalString } = getBucketSize({ start, end }); + + // move the start back with one bucket size, to ensure to get anomaly data in the beginning + // this is required because ML has a minimum bucket size (default is 900s) so if our buckets + // are smaller, we might have several null buckets in the beginning + const mlStart = start - 900 * 1000; + + const [anomaliesResponse, jobIds] = await Promise.all([ + anomalySeriesFetcher({ + serviceName, + transactionType, + intervalString, + ml, + start: mlStart, + end, + }), + getMLJobIds(ml.anomalyDetectors, environment), + ]); + + const scoreSeriesCollection = anomaliesResponse?.aggregations?.job_id.buckets + .filter((bucket) => jobIds.includes(bucket.key as string)) + .map((bucket) => { + const dateBuckets = bucket.ml_avg_response_times.buckets; + + return { + jobId: bucket.key as string, + anomalyScore: compact( + dateBuckets.map((dateBucket) => { + const metrics = maybe(dateBucket.anomaly_score.top[0])?.metrics; + const score = metrics?.record_score; + + if ( + !metrics || + !isFiniteNumber(score) || + score < ANOMALY_THRESHOLD.CRITICAL + ) { + return null; + } + + const anomalyStart = Date.parse(metrics.timestamp as string); + const anomalyEnd = + anomalyStart + (metrics.bucket_span as number) * 1000; + + return { + x0: anomalyStart, + x: anomalyEnd, + y: score, + }; + }) + ), + anomalyBoundaries: dateBuckets + .filter( + (dateBucket) => + dateBucket.lower.value !== null && + dateBucket.upper.value !== null + ) + .map((dateBucket) => ({ + x: dateBucket.key, + y0: dateBucket.lower.value as number, + y: dateBucket.upper.value as number, + })), + }; + }); + + if ((scoreSeriesCollection?.length ?? 0) > 1) { + logger.warn( + `More than one ML job was found for ${serviceName} for environment ${environment}. Only showing results from ${scoreSeriesCollection?.[0].jobId}` + ); + } + + return scoreSeriesCollection?.[0]; + }); } diff --git a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts index f5dd645eacdbe..ee27d00fdc0d4 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts @@ -21,6 +21,7 @@ import { } from '../../../lib/helpers/aggregated_transactions'; import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../../lib/helpers/setup_request'; +import { withApmSpan } from '../../../utils/with_apm_span'; import { getLatencyAggregation, getLatencyValue, @@ -29,7 +30,7 @@ export type LatencyChartsSearchResponse = PromiseReturnType< typeof searchLatency >; -async function searchLatency({ +function searchLatency({ serviceName, transactionType, transactionName, @@ -100,7 +101,7 @@ async function searchLatency({ return apmEventClient.search(params); } -export async function getLatencyTimeseries({ +export function getLatencyTimeseries({ serviceName, transactionType, transactionName, @@ -115,32 +116,34 @@ export async function getLatencyTimeseries({ searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; }) { - const response = await searchLatency({ - serviceName, - transactionType, - transactionName, - setup, - searchAggregatedTransactions, - latencyAggregationType, - }); + return withApmSpan('get_latency_charts', async () => { + const response = await searchLatency({ + serviceName, + transactionType, + transactionName, + setup, + searchAggregatedTransactions, + latencyAggregationType, + }); - if (!response.aggregations) { - return { latencyTimeseries: [], overallAvgDuration: null }; - } + if (!response.aggregations) { + return { latencyTimeseries: [], overallAvgDuration: null }; + } - return { - overallAvgDuration: - response.aggregations.overall_avg_duration.value || null, - latencyTimeseries: response.aggregations.latencyTimeseries.buckets.map( - (bucket) => { - return { - x: bucket.key, - y: getLatencyValue({ - latencyAggregationType, - aggregation: bucket.latency, - }), - }; - } - ), - }; + return { + overallAvgDuration: + response.aggregations.overall_avg_duration.value || null, + latencyTimeseries: response.aggregations.latencyTimeseries.buckets.map( + (bucket) => { + return { + x: bucket.key, + y: getLatencyValue({ + latencyAggregationType, + aggregation: bucket.latency, + }), + }; + } + ), + }; + }); } diff --git a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts index 71d9c21c67782..7c1fa9c3a2368 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_throughput_charts/index.ts @@ -20,13 +20,14 @@ import { } from '../../../lib/helpers/aggregated_transactions'; import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../../lib/helpers/setup_request'; +import { withApmSpan } from '../../../utils/with_apm_span'; import { getThroughputBuckets } from './transform'; export type ThroughputChartsResponse = PromiseReturnType< typeof searchThroughput >; -async function searchThroughput({ +function searchThroughput({ serviceName, transactionType, transactionName, @@ -102,22 +103,24 @@ export async function getThroughputCharts({ setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; }) { - const { bucketSize, intervalString } = getBucketSize(setup); + return withApmSpan('get_transaction_throughput_series', async () => { + const { bucketSize, intervalString } = getBucketSize(setup); - const response = await searchThroughput({ - serviceName, - transactionType, - transactionName, - setup, - searchAggregatedTransactions, - intervalString, - }); + const response = await searchThroughput({ + serviceName, + transactionType, + transactionName, + setup, + searchAggregatedTransactions, + intervalString, + }); - return { - throughputTimeseries: getThroughputBuckets({ - throughputResultBuckets: response.aggregations?.throughput.buckets, - bucketSize, - setupTimeRange: setup, - }), - }; + return { + throughputTimeseries: getThroughputBuckets({ + throughputResultBuckets: response.aggregations?.throughput.buckets, + bucketSize, + setupTimeRange: setup, + }), + }; + }); } diff --git a/x-pack/plugins/apm/server/lib/transactions/get_transaction_by_trace/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_transaction_by_trace/index.ts index 4024c339b5a23..dfdad2f59a848 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_transaction_by_trace/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_transaction_by_trace/index.ts @@ -11,40 +11,40 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { Setup } from '../../helpers/setup_request'; import { ProcessorEvent } from '../../../../common/processor_event'; +import { withApmSpan } from '../../../utils/with_apm_span'; -export async function getRootTransactionByTraceId( - traceId: string, - setup: Setup -) { - const { apmEventClient } = setup; +export function getRootTransactionByTraceId(traceId: string, setup: Setup) { + return withApmSpan('get_root_transaction_by_trace_id', async () => { + const { apmEventClient } = setup; - const params = { - apm: { - events: [ProcessorEvent.transaction as const], - }, - body: { - size: 1, - query: { - bool: { - should: [ - { - constant_score: { - filter: { - bool: { - must_not: { exists: { field: PARENT_ID } }, + const params = { + apm: { + events: [ProcessorEvent.transaction as const], + }, + body: { + size: 1, + query: { + bool: { + should: [ + { + constant_score: { + filter: { + bool: { + must_not: { exists: { field: PARENT_ID } }, + }, }, }, }, - }, - ], - filter: [{ term: { [TRACE_ID]: traceId } }], + ], + filter: [{ term: { [TRACE_ID]: traceId } }], + }, }, }, - }, - }; + }; - const resp = await apmEventClient.search(params); - return { - transaction: resp.hits.hits[0]?._source, - }; + const resp = await apmEventClient.search(params); + return { + transaction: resp.hits.hits[0]?._source, + }; + }); } 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 1be16698a1a1f..dc7def0625933 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 @@ -13,6 +13,7 @@ import { import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; import { ProcessorEvent } from '../../../common/processor_event'; import { rangeFilter } from '../../../common/utils/range_filter'; +import { withApmSpan } from '../../utils/with_apm_span'; import { getProcessorEventForAggregatedTransactions } from '../helpers/aggregated_transactions'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; @@ -25,54 +26,60 @@ export async function getEnvironments({ serviceName?: string; searchAggregatedTransactions: boolean; }) { - const { start, end, apmEventClient, config } = setup; + const spanName = serviceName + ? 'get_environments_for_service' + : 'get_environments'; - const filter: ESFilter[] = [{ range: rangeFilter(start, end) }]; + return withApmSpan(spanName, async () => { + const { start, end, apmEventClient, config } = setup; - if (serviceName) { - filter.push({ - term: { [SERVICE_NAME]: serviceName }, - }); - } + const filter: ESFilter[] = [{ range: rangeFilter(start, end) }]; - const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; + if (serviceName) { + filter.push({ + term: { [SERVICE_NAME]: serviceName }, + }); + } - const params = { - apm: { - events: [ - getProcessorEventForAggregatedTransactions( - searchAggregatedTransactions - ), - ProcessorEvent.metric, - ProcessorEvent.error, - ], - }, - body: { - size: 0, - query: { - bool: { - filter, - }, + const maxServiceEnvironments = config['xpack.apm.maxServiceEnvironments']; + + const params = { + apm: { + events: [ + getProcessorEventForAggregatedTransactions( + searchAggregatedTransactions + ), + ProcessorEvent.metric, + ProcessorEvent.error, + ], }, - aggs: { - environments: { - terms: { - field: SERVICE_ENVIRONMENT, - missing: ENVIRONMENT_NOT_DEFINED.value, - size: maxServiceEnvironments, + body: { + size: 0, + query: { + bool: { + filter, + }, + }, + aggs: { + environments: { + terms: { + field: SERVICE_ENVIRONMENT, + missing: ENVIRONMENT_NOT_DEFINED.value, + size: maxServiceEnvironments, + }, }, }, }, - }, - }; + }; - const resp = await apmEventClient.search(params); - const aggs = resp.aggregations; - const environmentsBuckets = aggs?.environments.buckets || []; + const resp = await apmEventClient.search(params); + const aggs = resp.aggregations; + const environmentsBuckets = aggs?.environments.buckets || []; - const environments = environmentsBuckets.map( - (environmentBucket) => environmentBucket.key as string - ); + const environments = environmentsBuckets.map( + (environmentBucket) => environmentBucket.key as string + ); - return environments; + return environments; + }); } diff --git a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts index 12bfc7e23bf4c..966f44158a7bb 100644 --- a/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts +++ b/x-pack/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts @@ -13,12 +13,13 @@ import { getLocalFilterQuery } from './get_local_filter_query'; import { Setup } from '../../helpers/setup_request'; import { localUIFilters } from './config'; import { LocalUIFilterName } from '../../../../common/ui_filter'; +import { withApmSpan } from '../../../utils/with_apm_span'; export type LocalUIFiltersAPIResponse = PromiseReturnType< typeof getLocalUIFilters >; -export async function getLocalUIFilters({ +export function getLocalUIFilters({ setup, projection, uiFilters, @@ -29,41 +30,45 @@ export async function getLocalUIFilters({ uiFilters: UIFilters; localFilterNames: LocalUIFilterName[]; }) { - const { apmEventClient } = setup; + return withApmSpan('get_ui_filter_options', () => { + const { apmEventClient } = setup; - const projectionWithoutAggs = cloneDeep(projection); + const projectionWithoutAggs = cloneDeep(projection); - delete projectionWithoutAggs.body.aggs; + delete projectionWithoutAggs.body.aggs; - return Promise.all( - localFilterNames.map(async (name) => { - const query = getLocalFilterQuery({ - uiFilters, - projection, - localUIFilterName: name, - }); + return Promise.all( + localFilterNames.map(async (name) => + withApmSpan('get_ui_filter_options_for_field', async () => { + const query = getLocalFilterQuery({ + uiFilters, + projection, + localUIFilterName: name, + }); - const response = await apmEventClient.search(query); + const response = await apmEventClient.search(query); - const filter = localUIFilters[name]; + const filter = localUIFilters[name]; - const buckets = response?.aggregations?.by_terms?.buckets ?? []; + const buckets = response?.aggregations?.by_terms?.buckets ?? []; - return { - ...filter, - options: orderBy( - buckets.map((bucket) => { - return { - name: bucket.key as string, - count: bucket.bucket_count - ? bucket.bucket_count.value - : bucket.doc_count, - }; - }), - 'count', - 'desc' - ), - }; - }) - ); + return { + ...filter, + options: orderBy( + buckets.map((bucket) => { + return { + name: bucket.key as string, + count: bucket.bucket_count + ? bucket.bucket_count.value + : bucket.doc_count, + }; + }), + 'count', + 'desc' + ), + }; + }) + ) + ); + }); } diff --git a/x-pack/plugins/apm/server/routes/observability_overview.ts b/x-pack/plugins/apm/server/routes/observability_overview.ts index 149f899fdfb5f..1a1fa799639bc 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview.ts @@ -13,6 +13,7 @@ import { hasData } from '../lib/observability_overview/has_data'; import { createRoute } from './create_route'; import { rangeRt } from './default_api_types'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; +import { withApmSpan } from '../utils/with_apm_span'; export const observabilityOverviewHasDataRoute = createRoute({ endpoint: 'GET /api/apm/observability_overview/has_data', @@ -36,20 +37,19 @@ export const observabilityOverviewRoute = createRoute({ setup ); - const serviceCountPromise = getServiceCount({ - setup, - searchAggregatedTransactions, + return withApmSpan('observability_overview', async () => { + const [serviceCount, transactionCoordinates] = await Promise.all([ + getServiceCount({ + setup, + searchAggregatedTransactions, + }), + getTransactionCoordinates({ + setup, + bucketSize, + searchAggregatedTransactions, + }), + ]); + return { serviceCount, transactionCoordinates }; }); - const transactionCoordinatesPromise = getTransactionCoordinates({ - setup, - bucketSize, - searchAggregatedTransactions, - }); - - const [serviceCount, transactionCoordinates] = await Promise.all([ - serviceCountPromise, - transactionCoordinatesPromise, - ]); - return { serviceCount, transactionCoordinates }; }, }); diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index 02cae86f6992e..a5c4de7552d78 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -25,6 +25,7 @@ import { getThroughput } from '../lib/services/get_throughput'; import { getServiceInstances } from '../lib/services/get_service_instances'; import { getServiceMetadataDetails } from '../lib/services/get_service_metadata_details'; import { getServiceMetadataIcons } from '../lib/services/get_service_metadata_icons'; +import { withApmSpan } from '../utils/with_apm_span'; export const servicesRoute = createRoute({ endpoint: 'GET /api/apm/services', @@ -178,14 +179,17 @@ export const serviceAnnotationsRoute = createRoute({ const { serviceName } = context.params.path; const { environment } = context.params.query; + const { observability } = context.plugins; + const [ annotationsClient, searchAggregatedTransactions, ] = await Promise.all([ - context.plugins.observability?.getScopedAnnotationsClient( - context, - request - ), + observability + ? withApmSpan('get_scoped_annotations_client', () => + observability.getScopedAnnotationsClient(context, request) + ) + : undefined, getSearchAggregatedTransactions(setup), ]); @@ -229,10 +233,13 @@ export const serviceAnnotationsCreateRoute = createRoute({ ]), }), handler: async ({ request, context }) => { - const annotationsClient = await context.plugins.observability?.getScopedAnnotationsClient( - context, - request - ); + const { observability } = context.plugins; + + const annotationsClient = observability + ? await withApmSpan('get_scoped_annotations_client', () => + observability.getScopedAnnotationsClient(context, request) + ) + : undefined; if (!annotationsClient) { throw Boom.notFound(); @@ -240,18 +247,20 @@ export const serviceAnnotationsCreateRoute = createRoute({ const { body, path } = context.params; - return annotationsClient.create({ - message: body.service.version, - ...body, - annotation: { - type: 'deployment', - }, - service: { - ...body.service, - name: path.serviceName, - }, - tags: uniq(['apm'].concat(body.tags ?? [])), - }); + return withApmSpan('create_annotation', () => + annotationsClient.create({ + message: body.service.version, + ...body, + annotation: { + type: 'deployment', + }, + service: { + ...body.service, + name: path.serviceName, + }, + tags: uniq(['apm'].concat(body.tags ?? [])), + }) + ); }, }); diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts index 40235e8d8a74f..25afb11f26459 100644 --- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts +++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts @@ -17,6 +17,7 @@ import { getAllEnvironments } from '../../lib/environments/get_all_environments' import { hasLegacyJobs } from '../../lib/anomaly_detection/has_legacy_jobs'; import { getSearchAggregatedTransactions } from '../../lib/helpers/aggregated_transactions'; import { notifyFeatureUsage } from '../../feature'; +import { withApmSpan } from '../../utils/with_apm_span'; // get ML anomaly detection jobs for each environment export const anomalyDetectionJobsRoute = createRoute({ @@ -31,10 +32,13 @@ export const anomalyDetectionJobsRoute = createRoute({ throw Boom.forbidden(ML_ERRORS.INVALID_LICENSE); } - const [jobs, legacyJobs] = await Promise.all([ - getAnomalyDetectionJobs(setup, context.logger), - hasLegacyJobs(setup), - ]); + const [jobs, legacyJobs] = await withApmSpan('get_available_ml_jobs', () => + Promise.all([ + getAnomalyDetectionJobs(setup, context.logger), + hasLegacyJobs(setup), + ]) + ); + return { jobs, hasLegacyJobs: legacyJobs,