From a84b4203f3aacc002c6005fee164fbc35388e3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:37:05 +0000 Subject: [PATCH] [APM] Show Universal Profiling data on transaction details page (#176922) - For Java agent services we show the flamegraph/functions based on APM data. - Use APM kql bar for java/otel-java agents - For non-java agent services we show the flamegraph/functions based on Profling data. - Use Profiling kql bar for non-java/otel-java agents - On the transaction details page we show the flamegraph/functions based on APM data filtering by transaction.name. - Hide Transaction Universal Profiling tab for non-java/otel-java agents. - `apmEnableTransactionProfiling` Feature flag, enabled by default. https://github.com/elastic/kibana/assets/55978943/0df83f11-6adf-4942-bf03-93c2779a1d97 --- .../app/profiling_overview/index.tsx | 173 +++++------ .../profiling_hosts_callout.tsx | 81 +++++ ...aph.tsx => profiling_hosts_flamegraph.tsx} | 8 +- ....tsx => profiling_hosts_top_functions.tsx} | 4 +- .../profiling_flamegraph.tsx | 80 ----- .../app/transaction_details/profiling_tab.tsx | 4 +- .../transaction_details_tabs.tsx | 7 +- .../shared/charts/flamegraph/index.tsx | 44 +++ .../shared/profiling/flamegraph/index.tsx | 92 ++++-- .../profiling/top_functions/index.tsx} | 47 ++- .../get_global_apm_server_route_repository.ts | 10 +- .../routes/profiling/fetch_flamegraph.ts | 77 +++++ .../routes/profiling/fetch_functions.ts | 83 ++++++ .../server/routes/profiling/hosts/route.ts | 173 +++++++++++ .../apm/server/routes/profiling/route.ts | 276 +++--------------- .../lib/fetch_profiling_functions.ts | 2 +- .../observability/server/ui_settings.ts | 2 +- .../profiling/server/routes/functions.ts | 2 +- .../server/services/register_services.ts | 2 +- 19 files changed, 676 insertions(+), 491 deletions(-) create mode 100644 x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_callout.tsx rename x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/{profiling_flamegraph.tsx => profiling_hosts_flamegraph.tsx} (89%) rename x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/{profiling_top_functions.tsx => profiling_hosts_top_functions.tsx} (95%) delete mode 100644 x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/profiling_flamegraph.tsx create mode 100644 x-pack/plugins/observability_solution/apm/public/components/shared/charts/flamegraph/index.tsx rename x-pack/plugins/observability_solution/apm/public/components/{app/transaction_details/profiling_top_functions.tsx => shared/profiling/top_functions/index.tsx} (65%) create mode 100644 x-pack/plugins/observability_solution/apm/server/routes/profiling/fetch_flamegraph.ts create mode 100644 x-pack/plugins/observability_solution/apm/server/routes/profiling/fetch_functions.ts create mode 100644 x-pack/plugins/observability_solution/apm/server/routes/profiling/hosts/route.ts diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/index.tsx index 9e8a0e2baf46e..3c2bb140235d7 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/index.tsx @@ -6,11 +6,6 @@ */ import { - EuiButton, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiLink, EuiLoadingSpinner, EuiSpacer, EuiTabbedContent, @@ -18,27 +13,29 @@ import { } from '@elastic/eui'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; import { EmbeddableProfilingSearchBar, ProfilingEmptyState, } from '@kbn/observability-shared-plugin/public'; import React, { useMemo } from 'react'; import { useHistory } from 'react-router-dom'; +import { isJavaAgentName as getIsJavaAgentName } from '../../../../common/agent_name'; import { ApmDocumentType } from '../../../../common/document_type'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useApmParams } from '../../../hooks/use_apm_params'; -import { useLocalStorage } from '../../../hooks/use_local_storage'; import { usePreferredDataSourceAndBucketSize } from '../../../hooks/use_preferred_data_source_and_bucket_size'; import { useProfilingPlugin } from '../../../hooks/use_profiling_plugin'; import { useTimeRange } from '../../../hooks/use_time_range'; -import { ApmPluginStartDeps } from '../../../plugin'; import { push } from '../../shared/links/url_helpers'; -import { ProfilingFlamegraph } from './profiling_flamegraph'; -import { ProfilingTopNFunctions } from './profiling_top_functions'; +import { ProfilingFlamegraph } from '../../shared/profiling/flamegraph'; +import { ProfilingTopNFunctions } from '../../shared/profiling/top_functions'; +import { SearchBar } from '../../shared/search_bar/search_bar'; +import { ProfilingHostsCallout } from './profiling_hosts_callout'; +import { ProfilingHostsFlamegraph } from './profiling_hosts_flamegraph'; +import { ProfilingHostsTopNFunctions } from './profiling_hosts_top_functions'; export function ProfilingOverview() { const history = useHistory(); - const { services } = useKibana(); const { path: { serviceName }, query: { rangeFrom, rangeTo, environment, kuery }, @@ -53,13 +50,8 @@ export function ProfilingOverview() { numBuckets: 20, }); - const [ - apmUniversalProfilingShowCallout, - setAPMUniversalProfilingShowCallout, - ] = useLocalStorage('apmUniversalProfilingShowCallout', true); - - const baseUrl = - services.docLinks?.ELASTIC_WEBSITE_URL || 'https://www.elastic.co/'; + const { agentName, transactionType } = useApmServiceContext(); + const isJavaAgent = getIsJavaAgentName(agentName); const tabs = useMemo((): EuiTabbedContentProps['tabs'] => { return [ @@ -71,16 +63,27 @@ export function ProfilingOverview() { content: ( <> - + {isJavaAgent ? ( + + ) : ( + + )} ), }, @@ -92,18 +95,29 @@ export function ProfilingOverview() { content: ( <> - + {isJavaAgent ? ( + + ) : ( + + )} ), }, @@ -111,12 +125,14 @@ export function ProfilingOverview() { }, [ end, environment, + isJavaAgent, kuery, preferred?.source, rangeFrom, rangeTo, serviceName, start, + transactionType, ]); if (isLoading) { @@ -138,68 +154,29 @@ export function ProfilingOverview() { return ( <> - {apmUniversalProfilingShowCallout && ( + {isJavaAgent ? ( + + ) : ( <> - -

- {i18n.translate('xpack.apm.profiling.callout.description', { - defaultMessage: - 'Universal Profiling provides unprecedented code visibility into the runtime behaviour of all applications. It profiles every line of code on the host(s) running your services, including not only your application code but also the kernel and third-party libraries.', - })} -

- - - - {i18n.translate('xpack.apm.profiling.callout.learnMore', { - defaultMessage: 'Learn more', - })} - - - - { - setAPMUniversalProfilingShowCallout(false); - }} - > - {i18n.translate('xpack.apm.profiling.callout.dismiss', { - defaultMessage: 'Dismiss', - })} - - - -
+ + { + push(history, { + query: { + kuery: next.query, + rangeFrom: next.dateRange.from, + rangeTo: next.dateRange.to, + }, + }); + }} + onRefresh={refreshTimeRange} + /> )} - { - push(history, { - query: { - kuery: next.query, - rangeFrom: next.dateRange.from, - rangeTo: next.dateRange.to, - }, - }); - }} - onRefresh={refreshTimeRange} - /> (); + + const baseUrl = + services.docLinks?.ELASTIC_WEBSITE_URL || 'https://www.elastic.co/'; + + const [ + apmUniversalProfilingShowCallout, + setAPMUniversalProfilingShowCallout, + ] = useLocalStorage('apmUniversalProfilingShowCallout', true); + + if (apmUniversalProfilingShowCallout === false) { + return null; + } + + return ( + +

+ {i18n.translate('xpack.apm.profiling.callout.description', { + defaultMessage: + 'Universal Profiling provides unprecedented code visibility into the runtime behaviour of all applications. It profiles every line of code on the host(s) running your services, including not only your application code but also the kernel and third-party libraries.', + })} +

+ + + + {i18n.translate('xpack.apm.profiling.callout.learnMore', { + defaultMessage: 'Learn more', + })} + + + + setAPMUniversalProfilingShowCallout(false)} + > + {i18n.translate('xpack.apm.profiling.callout.dismiss', { + defaultMessage: 'Dismiss', + })} + + + +
+ ); +} diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_flamegraph.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_flamegraph.tsx similarity index 89% rename from x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_flamegraph.tsx rename to x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_flamegraph.tsx index 2504ce687307b..9f8bcb2095f0e 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_flamegraph.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_flamegraph.tsx @@ -15,7 +15,7 @@ import { toKueryFilterFormat, } from '../../../../common/utils/kuery_utils'; import { useFetcher } from '../../../hooks/use_fetcher'; -import { ProfilingFlamegraphChart } from '../../shared/profiling/flamegraph'; +import { FlamegraphChart } from '../../shared/charts/flamegraph'; import { ProfilingFlamegraphLink } from '../../shared/profiling/flamegraph/flamegraph_link'; import { HostnamesFilterWarning } from './host_names_filter_warning'; @@ -32,7 +32,7 @@ interface Props { rangeTo: string; } -export function ProfilingFlamegraph({ +export function ProfilingHostsFlamegraph({ start, end, serviceName, @@ -46,7 +46,7 @@ export function ProfilingFlamegraph({ (callApmApi) => { if (dataSource) { return callApmApi( - 'GET /internal/apm/services/{serviceName}/profiling/flamegraph', + 'GET /internal/apm/services/{serviceName}/profiling/hosts/flamegraph', { params: { path: { serviceName }, @@ -87,7 +87,7 @@ export function ProfilingFlamegraph({ - + ); } diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_top_functions.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_top_functions.tsx similarity index 95% rename from x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_top_functions.tsx rename to x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_top_functions.tsx index 8785a4974a2d3..56d8b0cb1a604 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_top_functions.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_top_functions.tsx @@ -34,7 +34,7 @@ interface Props { rangeTo: string; } -export function ProfilingTopNFunctions({ +export function ProfilingHostsTopNFunctions({ serviceName, start, end, @@ -50,7 +50,7 @@ export function ProfilingTopNFunctions({ (callApmApi) => { if (dataSource) { return callApmApi( - 'GET /internal/apm/services/{serviceName}/profiling/functions', + 'GET /internal/apm/services/{serviceName}/profiling/hosts/functions', { params: { path: { serviceName }, diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/profiling_flamegraph.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/profiling_flamegraph.tsx deleted file mode 100644 index 2f87d2be50214..0000000000000 --- a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/profiling_flamegraph.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { EuiSpacer } from '@elastic/eui'; -import React from 'react'; -import { useFetcher } from '../../../hooks/use_fetcher'; -import { useTimeRange } from '../../../hooks/use_time_range'; -import { ProfilingFlamegraphChart } from '../../shared/profiling/flamegraph'; -import { ProfilingFlamegraphLink } from '../../shared/profiling/flamegraph/flamegraph_link'; - -interface Props { - serviceName: string; - rangeFrom: string; - rangeTo: string; - kuery: string; - transactionName: string; - transactionType?: string; - environment: string; -} - -export function ProfilingFlamegraph({ - serviceName, - rangeFrom, - rangeTo, - kuery, - transactionName, - transactionType, - environment, -}: Props) { - const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - - const { data, status } = useFetcher( - (callApmApi) => { - if (!transactionType) { - return; - } - return callApmApi( - 'GET /internal/apm/services/{serviceName}/transactions/flamegraph', - { - params: { - path: { serviceName }, - query: { - start, - end, - kuery, - transactionName, - transactionType, - environment, - }, - }, - } - ); - }, - [ - serviceName, - start, - end, - kuery, - transactionName, - transactionType, - environment, - ] - ); - - return ( - <> - - - - - ); -} diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/profiling_tab.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/profiling_tab.tsx index 1728e214ff8f3..4d56c62fa0a7a 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/profiling_tab.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/profiling_tab.tsx @@ -17,8 +17,8 @@ import React, { useMemo } from 'react'; import { ProfilingEmptyState } from '@kbn/observability-shared-plugin/public'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useProfilingPlugin } from '../../../hooks/use_profiling_plugin'; -import { ProfilingFlamegraph } from './profiling_flamegraph'; -import { ProfilingTopNFunctions } from './profiling_top_functions'; +import { ProfilingFlamegraph } from '../../shared/profiling/flamegraph'; +import { ProfilingTopNFunctions } from '../../shared/profiling/top_functions'; function ProfilingTab() { const { diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/transaction_details_tabs.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/transaction_details_tabs.tsx index 56511f94fb38d..5ccd49b539edb 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/transaction_details_tabs.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/transaction_details_tabs.tsx @@ -28,6 +28,8 @@ import { latencyCorrelationsTab } from './latency_correlations_tab'; import { profilingTab } from './profiling_tab'; import { traceSamplesTab } from './trace_samples_tab'; import { useTransactionProfilingSetting } from '../../../hooks/use_profiling_integration_setting'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { isJavaAgentName } from '../../../../common/agent_name'; export interface TabContentProps { clearChartSelection: () => void; @@ -43,6 +45,7 @@ export function TransactionDetailsTabs() { '/services/{serviceName}/transactions/view', '/mobile-services/{serviceName}/transactions/view' ); + const { agentName } = useApmServiceContext(); const isCriticalPathFeatureEnabled = useCriticalPathFeatureEnabledSetting(); const isTransactionProfilingEnabled = useTransactionProfilingSetting(); @@ -57,12 +60,12 @@ export function TransactionDetailsTabs() { tabs.push(aggregatedCriticalPathTab); } - if (isTransactionProfilingEnabled) { + if (isTransactionProfilingEnabled && isJavaAgentName(agentName)) { tabs.push(profilingTab); } return tabs; - }, [isCriticalPathFeatureEnabled, isTransactionProfilingEnabled]); + }, [agentName, isCriticalPathFeatureEnabled, isTransactionProfilingEnabled]); const { urlParams } = useLegacyUrlParams(); const history = useHistory(); diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/charts/flamegraph/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/charts/flamegraph/index.tsx new file mode 100644 index 0000000000000..b091e91f2a750 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/charts/flamegraph/index.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EmbeddableFlamegraph } from '@kbn/observability-shared-plugin/public'; +import { BaseFlameGraph } from '@kbn/profiling-utils'; +import { isEmpty } from 'lodash'; +import React from 'react'; +import { FETCH_STATUS, isPending } from '../../../../hooks/use_fetcher'; + +interface Props { + data?: BaseFlameGraph; + status: FETCH_STATUS; +} + +export function FlamegraphChart({ data, status }: Props) { + return ( + <> + {status === FETCH_STATUS.SUCCESS && + (isEmpty(data) || data?.TotalSamples === 0) ? ( + + {i18n.translate('xpack.apm.profiling.flamegraph.noDataFound', { + defaultMessage: 'No data found', + })} + + } + /> + ) : ( + + )} + + ); +} diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/profiling/flamegraph/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/profiling/flamegraph/index.tsx index cfe92331486a2..a492370578d90 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/profiling/flamegraph/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/profiling/flamegraph/index.tsx @@ -4,41 +4,77 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiEmptyPrompt } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { EmbeddableFlamegraph } from '@kbn/observability-shared-plugin/public'; -import { BaseFlameGraph } from '@kbn/profiling-utils'; -import { isEmpty } from 'lodash'; +import { EuiSpacer } from '@elastic/eui'; import React from 'react'; -import { FETCH_STATUS, isPending } from '../../../../hooks/use_fetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../../hooks/use_time_range'; +import { FlamegraphChart } from '../../charts/flamegraph'; +import { ProfilingFlamegraphLink } from './flamegraph_link'; interface Props { - data?: BaseFlameGraph; - status: FETCH_STATUS; + serviceName: string; + rangeFrom: string; + rangeTo: string; + kuery: string; + transactionName?: string; + transactionType?: string; + environment: string; } -export function ProfilingFlamegraphChart({ data, status }: Props) { +export function ProfilingFlamegraph({ + serviceName, + rangeFrom, + rangeTo, + kuery, + transactionName, + transactionType, + environment, +}: Props) { + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + + const { data, status } = useFetcher( + (callApmApi) => { + if (!transactionType) { + return; + } + return callApmApi( + 'GET /internal/apm/services/{serviceName}/profiling/flamegraph', + { + params: { + path: { serviceName }, + query: { + start, + end, + kuery, + transactionName, + transactionType, + environment, + }, + }, + } + ); + }, + [ + serviceName, + start, + end, + kuery, + transactionName, + transactionType, + environment, + ] + ); + return ( <> - {status === FETCH_STATUS.SUCCESS && - (isEmpty(data) || data?.TotalSamples === 0) ? ( - - {i18n.translate('xpack.apm.profiling.flamegraph.noDataFound', { - defaultMessage: 'No data found', - })} - - } - /> - ) : ( - - )} + + + ); } diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/profiling_top_functions.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/profiling/top_functions/index.tsx similarity index 65% rename from x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/profiling_top_functions.tsx rename to x-pack/plugins/observability_solution/apm/public/components/shared/profiling/top_functions/index.tsx index 96ef5f3497346..4d0cc1be4dad6 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details/profiling_top_functions.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/profiling/top_functions/index.tsx @@ -7,16 +7,16 @@ import { EuiSpacer } from '@elastic/eui'; import { EmbeddableFunctions } from '@kbn/observability-shared-plugin/public'; import React from 'react'; -import { isPending, useFetcher } from '../../../hooks/use_fetcher'; -import { useTimeRange } from '../../../hooks/use_time_range'; -import { ProfilingTopNFunctionsLink } from '../../shared/profiling/top_functions/top_functions_link'; +import { isPending, useFetcher } from '../../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../../hooks/use_time_range'; +import { ProfilingTopNFunctionsLink } from './top_functions_link'; interface Props { serviceName: string; rangeFrom: string; rangeTo: string; kuery: string; - transactionName: string; + transactionName?: string; transactionType?: string; environment: string; } @@ -34,27 +34,26 @@ export function ProfilingTopNFunctions({ const { data, status } = useFetcher( (callApmApi) => { - if (!transactionType) { - return; - } - return callApmApi( - 'GET /internal/apm/services/{serviceName}/transactions/functions', - { - params: { - path: { serviceName }, - query: { - start, - end, - kuery, - transactionName, - startIndex: 0, - endIndex: 10, - transactionType, - environment, + if (transactionType) { + return callApmApi( + 'GET /internal/apm/services/{serviceName}/profiling/functions', + { + params: { + path: { serviceName }, + query: { + start, + end, + kuery, + transactionName, + startIndex: 0, + endIndex: 10, + transactionType, + environment, + }, }, - }, - } - ); + } + ); + } }, [ serviceName, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts b/x-pack/plugins/observability_solution/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts index 7c555366c9e68..302a8d004760f 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts @@ -13,10 +13,13 @@ import { PickByValue } from 'utility-types'; import { agentExplorerRouteRepository } from '../agent_explorer/route'; import { agentKeysRouteRepository } from '../agent_keys/route'; import { alertsChartPreviewRouteRepository } from '../alerts/route'; +import { assistantRouteRepository } from '../assistant_functions/route'; import { correlationsRouteRepository } from '../correlations/route'; +import { serviceDashboardsRouteRepository } from '../custom_dashboards/route'; import { dataViewRouteRepository } from '../data_view/route'; import { debugTelemetryRoute } from '../debug_telemetry/route'; import { dependencisRouteRepository } from '../dependencies/route'; +import { diagnosticsRepository } from '../diagnostics/route'; import { environmentsRouteRepository } from '../environments/route'; import { errorsRouteRepository } from '../errors/route'; import { eventMetadataRouteRepository } from '../event_metadata/route'; @@ -28,6 +31,8 @@ import { latencyDistributionRouteRepository } from '../latency_distribution/rout import { metricsRouteRepository } from '../metrics/route'; import { mobileRouteRepository } from '../mobile/route'; import { observabilityOverviewRouteRepository } from '../observability_overview/route'; +import { profilingHostsRouteRepository } from '../profiling/hosts/route'; +import { profilingRouteRepository } from '../profiling/route'; import { serviceRouteRepository } from '../services/route'; import { serviceGroupRouteRepository } from '../service_groups/route'; import { serviceMapRouteRepository } from '../service_map/route'; @@ -35,7 +40,6 @@ import { agentConfigurationRouteRepository } from '../settings/agent_configurati import { anomalyDetectionRouteRepository } from '../settings/anomaly_detection/route'; import { apmIndicesRouteRepository } from '../settings/apm_indices/route'; import { customLinkRouteRepository } from '../settings/custom_link/route'; -import { diagnosticsRepository } from '../diagnostics/route'; import { labsRouteRepository } from '../settings/labs/route'; import { sourceMapsRouteRepository } from '../source_maps/route'; import { spanLinksRouteRepository } from '../span_links/route'; @@ -44,9 +48,6 @@ import { suggestionsRouteRepository } from '../suggestions/route'; import { timeRangeMetadataRoute } from '../time_range_metadata/route'; import { traceRouteRepository } from '../traces/route'; import { transactionRouteRepository } from '../transactions/route'; -import { assistantRouteRepository } from '../assistant_functions/route'; -import { profilingRouteRepository } from '../profiling/route'; -import { serviceDashboardsRouteRepository } from '../custom_dashboards/route'; function getTypedGlobalApmServerRouteRepository() { const repository = { @@ -86,6 +87,7 @@ function getTypedGlobalApmServerRouteRepository() { ...diagnosticsRepository, ...assistantRouteRepository, ...profilingRouteRepository, + ...profilingHostsRouteRepository, ...serviceDashboardsRouteRepository, }; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/profiling/fetch_flamegraph.ts b/x-pack/plugins/observability_solution/apm/server/routes/profiling/fetch_flamegraph.ts new file mode 100644 index 0000000000000..a1ac336938456 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/profiling/fetch_flamegraph.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ProfilingDataAccessPluginStart } from '@kbn/profiling-data-access-plugin/server'; +import { + CoreRequestHandlerContext, + ElasticsearchClient, +} from '@kbn/core/server'; +import { kqlQuery, termQuery } from '@kbn/observability-plugin/server'; +import { + SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_TYPE, +} from '../../../common/es_fields/apm'; +import { environmentQuery } from '../../../common/utils/environment_query'; + +interface Params { + profilingDataAccessStart: ProfilingDataAccessPluginStart; + core: CoreRequestHandlerContext; + esClient: ElasticsearchClient; + start: number; + end: number; + kuery: string; + serviceName?: string; + transactionName?: string; + environment?: string; + transactionType?: string; + indices?: string[]; + stacktraceIdsField?: string; +} + +export function fetchFlamegraph({ + profilingDataAccessStart, + core, + esClient, + start, + end, + kuery, + serviceName, + transactionName, + environment, + transactionType, + indices, + stacktraceIdsField, +}: Params) { + return profilingDataAccessStart.services.fetchFlamechartData({ + core, + esClient, + totalSeconds: end - start, + indices, + stacktraceIdsField, + query: { + bool: { + filter: [ + ...kqlQuery(kuery), + ...termQuery(SERVICE_NAME, serviceName), + ...termQuery(TRANSACTION_NAME, transactionName), + ...environmentQuery(environment), + ...termQuery(TRANSACTION_TYPE, transactionType), + { + range: { + ['@timestamp']: { + gte: String(start), + lt: String(end), + format: 'epoch_second', + }, + }, + }, + ], + }, + }, + }); +} diff --git a/x-pack/plugins/observability_solution/apm/server/routes/profiling/fetch_functions.ts b/x-pack/plugins/observability_solution/apm/server/routes/profiling/fetch_functions.ts new file mode 100644 index 0000000000000..c6b0bb66c9558 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/profiling/fetch_functions.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ProfilingDataAccessPluginStart } from '@kbn/profiling-data-access-plugin/server'; +import { + CoreRequestHandlerContext, + ElasticsearchClient, +} from '@kbn/core/server'; +import { kqlQuery, termQuery } from '@kbn/observability-plugin/server'; +import { + SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_TYPE, +} from '../../../common/es_fields/apm'; +import { environmentQuery } from '../../../common/utils/environment_query'; + +interface Params { + profilingDataAccessStart: ProfilingDataAccessPluginStart; + core: CoreRequestHandlerContext; + esClient: ElasticsearchClient; + startIndex: number; + endIndex: number; + start: number; + end: number; + kuery: string; + serviceName?: string; + transactionName?: string; + environment?: string; + transactionType?: string; + indices?: string[]; + stacktraceIdsField?: string; +} + +export function fetchFunctions({ + profilingDataAccessStart, + core, + esClient, + startIndex, + endIndex, + start, + end, + kuery, + serviceName, + transactionName, + environment, + transactionType, + indices, + stacktraceIdsField, +}: Params) { + return profilingDataAccessStart.services.fetchFunctions({ + core, + esClient, + startIndex, + endIndex, + totalSeconds: end - start, + indices, + stacktraceIdsField, + query: { + bool: { + filter: [ + ...kqlQuery(kuery), + ...termQuery(SERVICE_NAME, serviceName), + ...termQuery(TRANSACTION_NAME, transactionName), + ...environmentQuery(environment), + ...termQuery(TRANSACTION_TYPE, transactionType), + { + range: { + ['@timestamp']: { + gte: String(start), + lt: String(end), + format: 'epoch_second', + }, + }, + }, + ], + }, + }, + }); +} diff --git a/x-pack/plugins/observability_solution/apm/server/routes/profiling/hosts/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/profiling/hosts/route.ts new file mode 100644 index 0000000000000..ca57069942677 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/profiling/hosts/route.ts @@ -0,0 +1,173 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { toNumberRt } from '@kbn/io-ts-utils'; +import type { BaseFlameGraph, TopNFunctions } from '@kbn/profiling-utils'; +import * as t from 'io-ts'; +import { HOST_NAME } from '../../../../common/es_fields/apm'; +import { + mergeKueries, + toKueryFilterFormat, +} from '../../../../common/utils/kuery_utils'; +import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; +import { createApmServerRoute } from '../../apm_routes/create_apm_server_route'; +import { + environmentRt, + kueryRt, + rangeRt, + serviceTransactionDataSourceRt, +} from '../../default_api_types'; +import { fetchFlamegraph } from '../fetch_flamegraph'; +import { fetchFunctions } from '../fetch_functions'; +import { getServiceHostNames } from '../get_service_host_names'; + +const profilingHostsFlamegraphRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/services/{serviceName}/profiling/hosts/flamegraph', + params: t.type({ + path: t.type({ serviceName: t.string }), + query: t.intersection([ + rangeRt, + environmentRt, + serviceTransactionDataSourceRt, + kueryRt, + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise< + { flamegraph: BaseFlameGraph; hostNames: string[] } | undefined + > => { + const { context, plugins, params } = resources; + const core = await context.core; + const [esClient, apmEventClient, profilingDataAccessStart] = + await Promise.all([ + core.elasticsearch.client, + await getApmEventClient(resources), + await plugins.profilingDataAccess?.start(), + ]); + if (profilingDataAccessStart) { + const { start, end, environment, documentType, rollupInterval, kuery } = + params.query; + const { serviceName } = params.path; + + const serviceHostNames = await getServiceHostNames({ + apmEventClient, + start, + end, + environment, + serviceName, + documentType, + rollupInterval, + }); + + if (!serviceHostNames.length) { + return undefined; + } + const startSecs = start / 1000; + const endSecs = end / 1000; + + const flamegraph = await fetchFlamegraph({ + profilingDataAccessStart, + core, + esClient: esClient.asCurrentUser, + start: startSecs, + end: endSecs, + kuery: mergeKueries([ + `(${toKueryFilterFormat(HOST_NAME, serviceHostNames)})`, + kuery, + ]), + }); + + return { flamegraph, hostNames: serviceHostNames }; + } + + return undefined; + }, +}); + +const profilingHostsFunctionsRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/services/{serviceName}/profiling/hosts/functions', + params: t.type({ + path: t.type({ serviceName: t.string }), + query: t.intersection([ + rangeRt, + environmentRt, + serviceTransactionDataSourceRt, + t.type({ startIndex: toNumberRt, endIndex: toNumberRt }), + kueryRt, + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise<{ functions: TopNFunctions; hostNames: string[] } | undefined> => { + const { context, plugins, params } = resources; + const core = await context.core; + const [esClient, apmEventClient, profilingDataAccessStart] = + await Promise.all([ + core.elasticsearch.client, + await getApmEventClient(resources), + await plugins.profilingDataAccess?.start(), + ]); + if (profilingDataAccessStart) { + const { + start, + end, + environment, + startIndex, + endIndex, + documentType, + rollupInterval, + kuery, + } = params.query; + const { serviceName } = params.path; + + const serviceHostNames = await getServiceHostNames({ + apmEventClient, + start, + end, + environment, + serviceName, + documentType, + rollupInterval, + }); + + if (!serviceHostNames.length) { + return undefined; + } + + const startSecs = start / 1000; + const endSecs = end / 1000; + + const functions = await fetchFunctions({ + profilingDataAccessStart, + core, + esClient: esClient.asCurrentUser, + startIndex, + endIndex, + start: startSecs, + end: endSecs, + kuery: mergeKueries([ + `(${toKueryFilterFormat(HOST_NAME, serviceHostNames)})`, + kuery, + ]), + }); + + return { functions, hostNames: serviceHostNames }; + } + + return undefined; + }, +}); + +export const profilingHostsRouteRepository = { + ...profilingHostsFlamegraphRoute, + ...profilingHostsFunctionsRoute, +}; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/profiling/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/profiling/route.ts index 9009f60da03a1..a255cfd3d8c46 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/profiling/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/profiling/route.ts @@ -8,209 +8,25 @@ import { isoToEpochSecsRt, toNumberRt } from '@kbn/io-ts-utils'; import type { BaseFlameGraph, TopNFunctions } from '@kbn/profiling-utils'; import * as t from 'io-ts'; -import { kqlQuery, termQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { - HOST_NAME, - SERVICE_NAME, - TRANSACTION_NAME, - TRANSACTION_PROFILER_STACK_TRACE_IDS, - TRANSACTION_TYPE, -} from '../../../common/es_fields/apm'; -import { - mergeKueries, - toKueryFilterFormat, -} from '../../../common/utils/kuery_utils'; import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; -import { - environmentRt, - kueryRt, - rangeRt, - serviceTransactionDataSourceRt, -} from '../default_api_types'; -import { getServiceHostNames } from './get_service_host_names'; -import { environmentQuery } from '../../../common/utils/environment_query'; +import { environmentRt, kueryRt } from '../default_api_types'; +import { fetchFlamegraph } from './fetch_flamegraph'; +import { fetchFunctions } from './fetch_functions'; +import { TRANSACTION_PROFILER_STACK_TRACE_IDS } from '../../../common/es_fields/apm'; -const profilingFlamegraphRoute = createApmServerRoute({ +const servicesFlamegraphRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services/{serviceName}/profiling/flamegraph', - params: t.type({ - path: t.type({ serviceName: t.string }), - query: t.intersection([ - rangeRt, - environmentRt, - serviceTransactionDataSourceRt, - kueryRt, - ]), - }), - options: { tags: ['access:apm'] }, - handler: async ( - resources - ): Promise< - { flamegraph: BaseFlameGraph; hostNames: string[] } | undefined - > => { - const { context, plugins, params } = resources; - const core = await context.core; - const [esClient, apmEventClient, profilingDataAccessStart] = - await Promise.all([ - core.elasticsearch.client, - await getApmEventClient(resources), - await plugins.profilingDataAccess?.start(), - ]); - if (profilingDataAccessStart) { - const { start, end, environment, documentType, rollupInterval, kuery } = - params.query; - const { serviceName } = params.path; - - const serviceHostNames = await getServiceHostNames({ - apmEventClient, - start, - end, - environment, - serviceName, - documentType, - rollupInterval, - }); - - if (!serviceHostNames.length) { - return undefined; - } - const startSecs = start / 1000; - const endSecs = end / 1000; - - const flamegraph = - await profilingDataAccessStart?.services.fetchFlamechartData({ - core, - esClient: esClient.asCurrentUser, - totalSeconds: endSecs - startSecs, - query: { - bool: { - filter: [ - ...kqlQuery( - mergeKueries([ - `(${toKueryFilterFormat(HOST_NAME, serviceHostNames)})`, - kuery, - ]) - ), - { - range: { - ['@timestamp']: { - gte: String(startSecs), - lt: String(endSecs), - format: 'epoch_second', - }, - }, - }, - ], - }, - }, - }); - - return { flamegraph, hostNames: serviceHostNames }; - } - - return undefined; - }, -}); - -const profilingFunctionsRoute = createApmServerRoute({ - endpoint: 'GET /internal/apm/services/{serviceName}/profiling/functions', - params: t.type({ - path: t.type({ serviceName: t.string }), - query: t.intersection([ - rangeRt, - environmentRt, - serviceTransactionDataSourceRt, - t.type({ startIndex: toNumberRt, endIndex: toNumberRt }), - kueryRt, - ]), - }), - options: { tags: ['access:apm'] }, - handler: async ( - resources - ): Promise<{ functions: TopNFunctions; hostNames: string[] } | undefined> => { - const { context, plugins, params } = resources; - const core = await context.core; - const [esClient, apmEventClient, profilingDataAccessStart] = - await Promise.all([ - core.elasticsearch.client, - await getApmEventClient(resources), - await plugins.profilingDataAccess?.start(), - ]); - if (profilingDataAccessStart) { - const { - start, - end, - environment, - startIndex, - endIndex, - documentType, - rollupInterval, - kuery, - } = params.query; - const { serviceName } = params.path; - - const serviceHostNames = await getServiceHostNames({ - apmEventClient, - start, - end, - environment, - serviceName, - documentType, - rollupInterval, - }); - - if (!serviceHostNames.length) { - return undefined; - } - - const startSecs = start / 1000; - const endSecs = end / 1000; - - const functions = await profilingDataAccessStart?.services.fetchFunction({ - core, - esClient: esClient.asCurrentUser, - startIndex, - endIndex, - totalSeconds: endSecs - startSecs, - query: { - bool: { - filter: [ - ...kqlQuery( - mergeKueries([ - `(${toKueryFilterFormat(HOST_NAME, serviceHostNames)})`, - kuery, - ]) - ), - { - range: { - ['@timestamp']: { - gte: String(startSecs), - lt: String(endSecs), - format: 'epoch_second', - }, - }, - }, - ], - }, - }, - }); - return { functions, hostNames: serviceHostNames }; - } - - return undefined; - }, -}); - -const transactionsFlamegraphRoute = createApmServerRoute({ - endpoint: 'GET /internal/apm/services/{serviceName}/transactions/flamegraph', params: t.type({ path: t.type({ serviceName: t.string }), query: t.intersection([ kueryRt, environmentRt, - t.type({ + t.partial({ transactionName: t.string, + }), + t.type({ start: isoToEpochSecsRt, end: isoToEpochSecsRt, transactionType: t.string, @@ -242,32 +58,19 @@ const transactionsFlamegraphRoute = createApmServerRoute({ ProcessorEvent.transaction ); - return await profilingDataAccessStart?.services.fetchFlamechartData({ + return fetchFlamegraph({ + profilingDataAccessStart, core, esClient: esClient.asCurrentUser, + start, + end, + kuery, + serviceName, + transactionName, + environment, + transactionType, indices, stacktraceIdsField: TRANSACTION_PROFILER_STACK_TRACE_IDS, - totalSeconds: end - start, - query: { - bool: { - filter: [ - ...kqlQuery(kuery), - ...termQuery(SERVICE_NAME, serviceName), - ...termQuery(TRANSACTION_NAME, transactionName), - ...environmentQuery(environment), - ...termQuery(TRANSACTION_TYPE, transactionType), - { - range: { - ['@timestamp']: { - gte: String(start), - lt: String(end), - format: 'epoch_second', - }, - }, - }, - ], - }, - }, }); } @@ -275,18 +78,20 @@ const transactionsFlamegraphRoute = createApmServerRoute({ }, }); -const transactionsFunctionsRoute = createApmServerRoute({ - endpoint: 'GET /internal/apm/services/{serviceName}/transactions/functions', +const servicesFunctionsRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/services/{serviceName}/profiling/functions', params: t.type({ path: t.type({ serviceName: t.string }), query: t.intersection([ environmentRt, + t.partial({ + transactionName: t.string, + }), t.type({ start: isoToEpochSecsRt, end: isoToEpochSecsRt, startIndex: toNumberRt, endIndex: toNumberRt, - transactionName: t.string, transactionType: t.string, }), kueryRt, @@ -320,34 +125,21 @@ const transactionsFunctionsRoute = createApmServerRoute({ ProcessorEvent.transaction ); - return profilingDataAccessStart?.services.fetchFunction({ + return fetchFunctions({ + profilingDataAccessStart, core, esClient: esClient.asCurrentUser, startIndex, endIndex, indices, stacktraceIdsField: TRANSACTION_PROFILER_STACK_TRACE_IDS, - totalSeconds: end - start, - query: { - bool: { - filter: [ - ...kqlQuery(kuery), - ...termQuery(SERVICE_NAME, serviceName), - ...termQuery(TRANSACTION_NAME, transactionName), - ...environmentQuery(environment), - ...termQuery(TRANSACTION_TYPE, transactionType), - { - range: { - ['@timestamp']: { - gte: String(start), - lt: String(end), - format: 'epoch_second', - }, - }, - }, - ], - }, - }, + start, + end, + kuery, + serviceName, + transactionName, + environment, + transactionType, }); } @@ -386,9 +178,7 @@ const profilingStatusRoute = createApmServerRoute({ }); export const profilingRouteRepository = { - ...profilingFlamegraphRoute, + ...servicesFlamegraphRoute, ...profilingStatusRoute, - ...profilingFunctionsRoute, - ...transactionsFlamegraphRoute, - ...transactionsFunctionsRoute, + ...servicesFunctionsRoute, }; diff --git a/x-pack/plugins/observability_solution/infra/server/routes/profiling/lib/fetch_profiling_functions.ts b/x-pack/plugins/observability_solution/infra/server/routes/profiling/lib/fetch_profiling_functions.ts index fdf2399be4faa..8e60772ede3c7 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/profiling/lib/fetch_profiling_functions.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/profiling/lib/fetch_profiling_functions.ts @@ -20,7 +20,7 @@ export async function fetchProfilingFunctions( const startSecs = from / 1000; const endSecs = to / 1000; - return await profilingDataAccess.services.fetchFunction({ + return await profilingDataAccess.services.fetchFunctions({ core: coreRequestContext, esClient: coreRequestContext.elasticsearch.client.asCurrentUser, startIndex, diff --git a/x-pack/plugins/observability_solution/observability/server/ui_settings.ts b/x-pack/plugins/observability_solution/observability/server/ui_settings.ts index 532e093d3d3f7..61ae08864f308 100644 --- a/x-pack/plugins/observability_solution/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability_solution/observability/server/ui_settings.ts @@ -575,7 +575,7 @@ export const uiSettings: Record = { name: i18n.translate('xpack.observability.apmEnableTransactionProfiling', { defaultMessage: 'Enable Universal Profiling on Transaction view', }), - value: false, + value: true, schema: schema.boolean(), requiresPageReload: true, }, diff --git a/x-pack/plugins/observability_solution/profiling/server/routes/functions.ts b/x-pack/plugins/observability_solution/profiling/server/routes/functions.ts index a261708c5b457..e6b71b17db8be 100644 --- a/x-pack/plugins/observability_solution/profiling/server/routes/functions.ts +++ b/x-pack/plugins/observability_solution/profiling/server/routes/functions.ts @@ -45,7 +45,7 @@ export function registerTopNFunctionsSearchRoute({ const endSecs = timeTo / 1000; const esClient = await getClient(context); - const topNFunctions = await profilingDataAccess.services.fetchFunction({ + const topNFunctions = await profilingDataAccess.services.fetchFunctions({ core, esClient, startIndex, diff --git a/x-pack/plugins/observability_solution/profiling_data_access/server/services/register_services.ts b/x-pack/plugins/observability_solution/profiling_data_access/server/services/register_services.ts index 60f582ac16b7e..dfd51e2125c46 100644 --- a/x-pack/plugins/observability_solution/profiling_data_access/server/services/register_services.ts +++ b/x-pack/plugins/observability_solution/profiling_data_access/server/services/register_services.ts @@ -31,6 +31,6 @@ export function registerServices(params: RegisterServicesParams) { fetchFlamechartData: createFetchFlamechart(params), getStatus: createGetStatusService(params), getSetupState: createSetupState(params), - fetchFunction: createFetchFunctions(params), + fetchFunctions: createFetchFunctions(params), }; }