From 03fc284ac67c8f1ffcf9922b8ac12204f13b7fb7 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Thu, 11 Apr 2024 10:21:31 +0100 Subject: [PATCH 01/20] [ProfilingxAPM] Show APM transaction links on Profiling ui --- .../kbn-profiling-utils/common/functions.ts | 2 + .../common/topN_functions.ts | 40 ++++++ packages/kbn-profiling-utils/index.ts | 1 + .../profiling/common/index.ts | 1 + .../apm_transactions.tsx | 18 +++ .../frame_information_ai_assistant.tsx | 15 ++- .../frame_information_window/index.tsx | 32 ++++- .../profiling/server/routes/apm.ts | 104 +++++++++++++++ .../profiling/server/routes/functions.ts | 53 +++++--- .../profiling/server/routes/index.ts | 2 + .../common/profiling_es_client.ts | 15 +++ .../server/services/functions/es_functions.ts | 125 ++++++++++++++++++ .../server/services/register_services.ts | 3 + .../utils/create_profiling_es_client.ts | 43 ++++++ 14 files changed, 422 insertions(+), 32 deletions(-) create mode 100644 packages/kbn-profiling-utils/common/topN_functions.ts create mode 100644 x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx create mode 100644 x-pack/plugins/observability_solution/profiling/server/routes/apm.ts create mode 100644 x-pack/plugins/observability_solution/profiling_data_access/server/services/functions/es_functions.ts diff --git a/packages/kbn-profiling-utils/common/functions.ts b/packages/kbn-profiling-utils/common/functions.ts index 7fcf8df2025ee8..0aa7cde3ca0231 100644 --- a/packages/kbn-profiling-utils/common/functions.ts +++ b/packages/kbn-profiling-utils/common/functions.ts @@ -49,6 +49,7 @@ type TopNFunction = Pick< > & { Id: string; Rank: number; + subGroups: Record; }; export interface TopNFunctions { @@ -207,6 +208,7 @@ export function createTopNFunctions({ selfAnnualCostUSD: frameAndCount.selfAnnualCostUSD, totalAnnualCO2kgs: frameAndCount.totalAnnualCO2kgs, totalAnnualCostUSD: frameAndCount.totalAnnualCostUSD, + subGroups: {}, }; }); diff --git a/packages/kbn-profiling-utils/common/topN_functions.ts b/packages/kbn-profiling-utils/common/topN_functions.ts new file mode 100644 index 00000000000000..25b281142f7fd9 --- /dev/null +++ b/packages/kbn-profiling-utils/common/topN_functions.ts @@ -0,0 +1,40 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface Frame { + frame_type: number; + inline: boolean; + address_or_line: number; + function_name: string; + file_name: string; + line_number: number; + executable_file_name: string; +} + +interface TopNFunction { + id: string; + rank: number; + frame: Frame; + sub_groups: Record; + self_count: number; + total_count: number; + self_annual_co2_tons: number; + total_annual_co2_tons: number; + self_annual_costs_usd: number; + total_annual_costs_usd: number; +} + +export interface ESTopNFunctions { + self_count: number; + total_count: number; + self_annual_co2_tons: number; + self_annual_cost_usd: number; + topn: TopNFunction[]; +} + +export type AggregationField = 'service.name' | 'transaction.name'; diff --git a/packages/kbn-profiling-utils/index.ts b/packages/kbn-profiling-utils/index.ts index a2c582009c7855..cecc7a5a47d7ee 100644 --- a/packages/kbn-profiling-utils/index.ts +++ b/packages/kbn-profiling-utils/index.ts @@ -53,3 +53,4 @@ export type { } from './common/profiling'; export type { ProfilingStatus } from './common/profiling_status'; export type { TopNFunctions } from './common/functions'; +export type { AggregationField, ESTopNFunctions } from './common/topN_functions'; diff --git a/x-pack/plugins/observability_solution/profiling/common/index.ts b/x-pack/plugins/observability_solution/profiling/common/index.ts index f6266b606ee5a5..c3107f14b19d15 100644 --- a/x-pack/plugins/observability_solution/profiling/common/index.ts +++ b/x-pack/plugins/observability_solution/profiling/common/index.ts @@ -26,6 +26,7 @@ export function getRoutePaths() { TopNHosts: `${BASE_ROUTE_PATH}/topn/hosts`, TopNThreads: `${BASE_ROUTE_PATH}/topn/threads`, TopNTraces: `${BASE_ROUTE_PATH}/topn/traces`, + APMTransactions: `${BASE_ROUTE_PATH}/topn/functions/apm/transactions`, Flamechart: `${BASE_ROUTE_PATH}/flamechart`, HasSetupESResources: `${BASE_ROUTE_PATH}/setup/es_resources`, SetupDataCollectionInstructions: `${BASE_ROUTE_PATH}/setup/instructions`, diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx new file mode 100644 index 00000000000000..b87f031ab696d4 --- /dev/null +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx @@ -0,0 +1,18 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export function APMTransactions() { + console.log('#### render'); + return ( +
+ {i18n.translate('xpack.profiling.aPMTransactions.div.apmTransactionsLabel', { + defaultMessage: 'apm transactions', + })} +
+ ); +} diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/frame_information_ai_assistant.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/frame_information_ai_assistant.tsx index 1096951865eb93..fb68611a74b7d8 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/frame_information_ai_assistant.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/frame_information_ai_assistant.tsx @@ -8,6 +8,7 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import type { Message } from '@kbn/observability-ai-assistant-plugin/public'; +import { EuiFlexItem } from '@elastic/eui'; import { Frame } from '.'; import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; @@ -78,12 +79,14 @@ export function FrameInformationAIAssistant({ frame }: Props) { return ( <> {observabilityAIAssistant?.ObservabilityAIAssistantContextualInsight && promptMessages ? ( - + + + ) : null} ); diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx index a0dbc1c7737b4c..a992d08f454fd2 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx @@ -4,12 +4,20 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiTitle } from '@elastic/eui'; +import { + EuiAccordion, + EuiAccordionProps, + EuiFlexGroup, + EuiFlexItem, + EuiStat, + EuiTitle, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FrameSymbolStatus, getFrameSymbolStatus } from '@kbn/profiling-utils'; -import React from 'react'; +import React, { useState } from 'react'; import { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_estimates'; import { FramesSummary } from '../frames_summary'; +import { APMTransactions } from './apm_transactions'; import { EmptyFrame } from './empty_frame'; import { FrameInformationAIAssistant } from './frame_information_ai_assistant'; import { FrameInformationPanel } from './frame_information_panel'; @@ -45,6 +53,7 @@ export interface Props { rank?: number; showSymbolsStatus?: boolean; compressed?: boolean; + subGroups?: Record; } export function FrameInformationWindow({ @@ -58,7 +67,9 @@ export function FrameInformationWindow({ totalSeconds, rank, compressed = false, + subGroups = {}, }: Props) { + const [accordionState, setAccordionState] = useState('closed'); const calculateImpactEstimates = useCalculateImpactEstimate(); if (!frame) { @@ -143,14 +154,25 @@ export function FrameInformationWindow({ ))} - - - + {showSymbolsStatus && symbolStatus !== FrameSymbolStatus.SYMBOLIZED ? ( ) : null} + + setAccordionState(isOpen ? 'open' : 'closed')} + > + {accordionState === 'open' ? : null} + + ; + +export function registerTopNFunctionsAPMTransactionsRoute({ + router, + logger, + dependencies: { + start: { profilingDataAccess }, + }, +}: RouteRegisterParameters) { + const paths = getRoutePaths(); + router.get( + { + path: paths.APMTransactions, + options: { tags: ['access:profiling'], timeout: { idleSocket: IDLE_SOCKET_TIMEOUT } }, + validate: { query: querySchema }, + }, + async (context, request, response) => { + try { + const core = await context.core; + + const { + timeFrom, + timeTo, + startIndex, + endIndex, + functionName, + serviceNames, + }: QuerySchemaType = request.query; + const startSecs = timeFrom / 1000; + const endSecs = timeTo / 1000; + + const esClient = await getClient(context); + + const query = { + bool: { + filter: [ + ...termQuery('service.name', serviceNames[0]), + { + range: { + ['@timestamp']: { + gte: String(startSecs), + lt: String(endSecs), + format: 'epoch_second', + }, + }, + }, + ], + }, + }; + + const [topNFunctions, newtopNFunctions] = await Promise.all([ + profilingDataAccess.services.fetchFunctions({ + core, + esClient, + startIndex, + endIndex, + totalSeconds: endSecs - startSecs, + query, + }), + profilingDataAccess.services.fetchESFunctions({ + core, + esClient, + query, + aggregationField: 'service.name', + }), + ]); + + return response.ok({ + body: newtopNFunctions, + }); + } catch (error) { + return handleRouteHandlerError({ + error, + logger, + response, + message: 'Error while fetching TopN functions', + }); + } + } + ); +} 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 e6b71b17db8be3..d4f156ff883338 100644 --- a/x-pack/plugins/observability_solution/profiling/server/routes/functions.ts +++ b/x-pack/plugins/observability_solution/profiling/server/routes/functions.ts @@ -45,32 +45,43 @@ export function registerTopNFunctionsSearchRoute({ const endSecs = timeTo / 1000; const esClient = await getClient(context); - const topNFunctions = await profilingDataAccess.services.fetchFunctions({ - core, - esClient, - startIndex, - endIndex, - totalSeconds: endSecs - startSecs, - query: { - bool: { - filter: [ - ...kqlQuery(kuery), - { - range: { - ['@timestamp']: { - gte: String(startSecs), - lt: String(endSecs), - format: 'epoch_second', - }, + + const query = { + bool: { + filter: [ + ...kqlQuery(kuery), + { + range: { + ['@timestamp']: { + gte: String(startSecs), + lt: String(endSecs), + format: 'epoch_second', }, }, - ], - }, + }, + ], }, - }); + }; + + const [topNFunctions, newtopNFunctions] = await Promise.all([ + profilingDataAccess.services.fetchFunctions({ + core, + esClient, + startIndex, + endIndex, + totalSeconds: endSecs - startSecs, + query, + }), + profilingDataAccess.services.fetchESFunctions({ + core, + esClient, + query, + aggregationField: 'service.name', + }), + ]); return response.ok({ - body: topNFunctions, + body: newtopNFunctions, }); } catch (error) { return handleRouteHandlerError({ diff --git a/x-pack/plugins/observability_solution/profiling/server/routes/index.ts b/x-pack/plugins/observability_solution/profiling/server/routes/index.ts index 95c468048609e3..74e6e3b71be904 100644 --- a/x-pack/plugins/observability_solution/profiling/server/routes/index.ts +++ b/x-pack/plugins/observability_solution/profiling/server/routes/index.ts @@ -16,6 +16,7 @@ import { TelemetryUsageCounter, } from '../types'; import { ProfilingESClient } from '../utils/create_profiling_es_client'; +import { registerTopNFunctionsAPMTransactionsRoute } from './apm'; import { registerFlameChartSearchRoute } from './flamechart'; import { registerTopNFunctionsSearchRoute } from './functions'; import { registerSetupRoute } from './setup/route'; @@ -61,4 +62,5 @@ export function registerRoutes(params: RouteRegisterParameters) { // and will show instructions on how to add data registerSetupRoute(params); registerStorageExplorerRoute(params); + registerTopNFunctionsAPMTransactionsRoute(params); } diff --git a/x-pack/plugins/observability_solution/profiling_data_access/common/profiling_es_client.ts b/x-pack/plugins/observability_solution/profiling_data_access/common/profiling_es_client.ts index e70a5ea4c1737a..5a1daab5706a6c 100644 --- a/x-pack/plugins/observability_solution/profiling_data_access/common/profiling_es_client.ts +++ b/x-pack/plugins/observability_solution/profiling_data_access/common/profiling_es_client.ts @@ -9,7 +9,9 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWith import { ElasticsearchClient } from '@kbn/core/server'; import type { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; import type { + AggregationField, BaseFlameGraph, + ESTopNFunctions, ProfilingStatusResponse, StackTraceResponse, } from '@kbn/profiling-utils'; @@ -49,4 +51,17 @@ export interface ProfilingESClient { indices?: string[]; stacktraceIdsField?: string; }): Promise; + topNFunctions(params: { + query: QueryDslQueryContainer; + indices?: string[]; + stacktraceIdsField?: string; + aggregationField?: AggregationField; + co2PerKWH?: number; + datacenterPUE?: number; + pervCPUWattX86?: number; + pervCPUWattArm64?: number; + awsCostDiscountRate?: number; + azureCostDiscountRate?: number; + costPervCPUPerHour?: number; + }): Promise; } diff --git a/x-pack/plugins/observability_solution/profiling_data_access/server/services/functions/es_functions.ts b/x-pack/plugins/observability_solution/profiling_data_access/server/services/functions/es_functions.ts new file mode 100644 index 00000000000000..f4c5f7de6c0d8e --- /dev/null +++ b/x-pack/plugins/observability_solution/profiling_data_access/server/services/functions/es_functions.ts @@ -0,0 +1,125 @@ +/* + * 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 { + profilingAWSCostDiscountRate, + profilingCo2PerKWH, + profilingCostPervCPUPerHour, + profilingDatacenterPUE, + profilingPervCPUWattArm64, + profilingPervCPUWattX86, + profilingAzureCostDiscountRate, + profilingShowErrorFrames, +} from '@kbn/observability-plugin/common'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { CoreRequestHandlerContext, ElasticsearchClient } from '@kbn/core/server'; +import { + AggregationField, + convertTonsToKgs, + ESTopNFunctions, + TopNFunctions, +} from '@kbn/profiling-utils'; +import { RegisterServicesParams } from '../register_services'; +import { percentToFactor } from '../../utils/percent_to_factor'; + +export interface FetchFunctionsParams { + core: CoreRequestHandlerContext; + esClient: ElasticsearchClient; + indices?: string[]; + stacktraceIdsField?: string; + query: QueryDslQueryContainer; + aggregationField?: AggregationField; +} + +const targetSampleSize = 20000; // minimum number of samples to get statistically sound results + +export function createFetchESFunctions({ createProfilingEsClient }: RegisterServicesParams) { + return async ({ + core, + esClient, + indices, + stacktraceIdsField, + query, + aggregationField, + }: FetchFunctionsParams) => { + const [ + co2PerKWH, + datacenterPUE, + pervCPUWattX86, + pervCPUWattArm64, + awsCostDiscountRate, + costPervCPUPerHour, + azureCostDiscountRate, + ] = await Promise.all([ + core.uiSettings.client.get(profilingCo2PerKWH), + core.uiSettings.client.get(profilingDatacenterPUE), + core.uiSettings.client.get(profilingPervCPUWattX86), + core.uiSettings.client.get(profilingPervCPUWattArm64), + core.uiSettings.client.get(profilingAWSCostDiscountRate), + core.uiSettings.client.get(profilingCostPervCPUPerHour), + core.uiSettings.client.get(profilingAzureCostDiscountRate), + core.uiSettings.client.get(profilingShowErrorFrames), + ]); + + const profilingEsClient = createProfilingEsClient({ esClient }); + + const esTopNFunctions = await profilingEsClient.topNFunctions({ + query, + indices, + stacktraceIdsField, + aggregationField, + co2PerKWH, + datacenterPUE, + pervCPUWattX86, + pervCPUWattArm64, + awsCostDiscountRate: percentToFactor(awsCostDiscountRate), + costPervCPUPerHour, + azureCostDiscountRate: percentToFactor(azureCostDiscountRate), + }); + + return transformToKibanaTopNFunction(esTopNFunctions); + }; +} + +/** + * Transforms object returned by ES because we share a lot of components in the UI with the current data model + * We must first align the ES api response type then remove this + */ +function transformToKibanaTopNFunction(esTopNFunctions: ESTopNFunctions): TopNFunctions { + return { + TotalCount: esTopNFunctions.total_count, + totalCPU: esTopNFunctions.total_count, + selfCPU: esTopNFunctions.self_count, + totalAnnualCO2Kgs: convertTonsToKgs(esTopNFunctions.self_annual_co2_tons), + totalAnnualCostUSD: esTopNFunctions.self_annual_cost_usd, + SamplingRate: 1, + TopN: esTopNFunctions.topn.map((item) => { + return { + Id: item.id, + Rank: item.rank, + CountExclusive: item.self_count, + CountInclusive: item.total_count, + selfAnnualCO2kgs: convertTonsToKgs(item.self_annual_co2_tons), + selfAnnualCostUSD: item.self_annual_costs_usd, + totalAnnualCO2kgs: convertTonsToKgs(item.total_annual_co2_tons), + totalAnnualCostUSD: item.total_annual_costs_usd, + subGroups: item.sub_groups, + Frame: { + AddressOrLine: item.frame.address_or_line, + ExeFileName: item.frame.executable_file_name, + FrameType: item.frame.frame_type, + FunctionName: item.frame.function_name, + Inline: item.frame.inline, + SourceFilename: item.frame.file_name, + SourceLine: item.frame.line_number, + FileID: '', + FrameID: '', + FunctionOffset: 0, + }, + }; + }), + }; +} 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 dfd51e2125c460..57cb21ee57a74c 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 @@ -13,6 +13,7 @@ import { createGetStatusService } from './status'; import { ProfilingESClient } from '../../common/profiling_es_client'; import { createFetchFunctions } from './functions'; import { createSetupState } from './setup_state'; +import { createFetchESFunctions } from './functions/es_functions'; export interface RegisterServicesParams { createProfilingEsClient: (params: { @@ -31,6 +32,8 @@ export function registerServices(params: RegisterServicesParams) { fetchFlamechartData: createFetchFlamechart(params), getStatus: createGetStatusService(params), getSetupState: createSetupState(params), + // Legacy fetch functions api based on stacktraces fetchFunctions: createFetchFunctions(params), + fetchESFunctions: createFetchESFunctions(params), }; } diff --git a/x-pack/plugins/observability_solution/profiling_data_access/server/utils/create_profiling_es_client.ts b/x-pack/plugins/observability_solution/profiling_data_access/server/utils/create_profiling_es_client.ts index 4c209540f37a18..0bdbb8c4248f2d 100644 --- a/x-pack/plugins/observability_solution/profiling_data_access/server/utils/create_profiling_es_client.ts +++ b/x-pack/plugins/observability_solution/profiling_data_access/server/utils/create_profiling_es_client.ts @@ -9,6 +9,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; import type { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; import type { BaseFlameGraph, + ESTopNFunctions, ProfilingStatusResponse, StackTraceResponse, } from '@kbn/profiling-utils'; @@ -150,5 +151,47 @@ export function createProfilingEsClient({ }); return unwrapEsResponse(promise) as Promise; }, + topNFunctions({ + query, + aggregationField, + indices, + stacktraceIdsField, + co2PerKWH, + datacenterPUE, + awsCostDiscountRate, + costPervCPUPerHour, + pervCPUWattArm64, + pervCPUWattX86, + azureCostDiscountRate, + }) { + const controller = new AbortController(); + + const promise = withProfilingSpan('_profiling/topn/functions', () => { + return esClient.transport.request( + { + method: 'POST', + path: encodeURI('/_profiling/topn/functions'), + body: { + query, + indices, + stacktrace_ids_field: stacktraceIdsField, + aggregation_field: aggregationField, + co2_per_kwh: co2PerKWH, + per_core_watt_x86: pervCPUWattX86, + per_core_watt_arm64: pervCPUWattArm64, + datacenter_pue: datacenterPUE, + aws_cost_factor: awsCostDiscountRate, + cost_per_core_hour: costPervCPUPerHour, + azure_cost_factor: azureCostDiscountRate, + }, + }, + { + signal: controller.signal, + meta: true, + } + ); + }); + return unwrapEsResponse(promise) as Promise; + }, }; } From 62b7182fee23de9202aafc7ae149259705d7e595 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Thu, 11 Apr 2024 15:29:14 +0100 Subject: [PATCH 02/20] ui changes --- .../profiling/kibana.jsonc | 3 +- .../apm_transactions.tsx | 76 +++++++++++++-- .../frame_information_window/index.tsx | 40 +++++--- .../public/components/topn_functions/utils.ts | 3 + .../profiling/public/services.ts | 29 ++++++ .../profiling/server/routes/apm.ts | 97 +++++++++++-------- .../profiling/server/types.ts | 6 ++ 7 files changed, 194 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/observability_solution/profiling/kibana.jsonc b/x-pack/plugins/observability_solution/profiling/kibana.jsonc index c5d60c2fd378c2..e5df7002f4a353 100644 --- a/x-pack/plugins/observability_solution/profiling/kibana.jsonc +++ b/x-pack/plugins/observability_solution/profiling/kibana.jsonc @@ -13,7 +13,8 @@ "security", "cloud", "fleet", - "observabilityAIAssistant" + "observabilityAIAssistant", + "apmDataAccess", ], "requiredPlugins": [ "charts", diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx index b87f031ab696d4..39c4bec9c9cef6 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx @@ -4,15 +4,79 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiInMemoryTableProps, + EuiLoadingSpinner, +} from '@elastic/eui'; +import React from 'react'; import { i18n } from '@kbn/i18n'; +import { AsyncStatus } from '../../hooks/use_async'; +import { useTimeRangeAsync } from '../../hooks/use_time_range_async'; +import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; + +interface Props { + serviceNames: Record; + timeFrom: string; + timeTo: string; + functionName: string; +} + +export function APMTransactions({ functionName, serviceNames, timeTo, timeFrom }: Props) { + const { + services: { fetchTopNFunctionAPMTransactions }, + } = useProfilingDependencies(); + + const { status, data = [] } = useTimeRangeAsync( + ({ http }) => { + return fetchTopNFunctionAPMTransactions({ + http, + timeFrom: 1712444400000, + timeTo: 1713049199999, + functionName, + serviceNames: Object.keys(serviceNames), + }); + }, + [fetchTopNFunctionAPMTransactions, functionName, serviceNames] + ); + + const columns: EuiInMemoryTableProps['columns'] = [ + { + field: 'serviceName', + name: i18n.translate('xpack.profiling.apmTransactions.columns.serviceName', { + defaultMessage: 'Service Name', + }), + truncateText: true, + }, + { + field: 'transactionName', + name: i18n.translate('xpack.profiling.apmTransactions.columns.transactionName', { + defaultMessage: 'Transaction Name', + }), + truncateText: true, + }, + ]; + + if (status !== AsyncStatus.Settled) { + return ( + + + + + + ); + } -export function APMTransactions() { - console.log('#### render'); return ( -
- {i18n.translate('xpack.profiling.aPMTransactions.div.apmTransactionsLabel', { - defaultMessage: 'apm transactions', + + items={data} + columns={columns} + pagination={{ itemsPerPage: 5, showPerPageOptions: false }} + /> ); } diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx index a992d08f454fd2..6e27f2ae0fcc71 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx @@ -14,6 +14,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FrameSymbolStatus, getFrameSymbolStatus } from '@kbn/profiling-utils'; +import { isEmpty } from 'lodash'; import React, { useState } from 'react'; import { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_estimates'; import { FramesSummary } from '../frames_summary'; @@ -40,6 +41,7 @@ export interface Frame { totalAnnualCO2Kgs: number; selfAnnualCostUSD: number; totalAnnualCostUSD: number; + subGroups?: Record; } export interface Props { @@ -53,7 +55,6 @@ export interface Props { rank?: number; showSymbolsStatus?: boolean; compressed?: boolean; - subGroups?: Record; } export function FrameInformationWindow({ @@ -67,7 +68,6 @@ export function FrameInformationWindow({ totalSeconds, rank, compressed = false, - subGroups = {}, }: Props) { const [accordionState, setAccordionState] = useState('closed'); const calculateImpactEstimates = useCalculateImpactEstimate(); @@ -90,6 +90,7 @@ export function FrameInformationWindow({ functionName, sourceFileName, sourceLine, + subGroups = {}, } = frame; const informationRows = getInformationRows({ @@ -160,19 +161,28 @@ export function FrameInformationWindow({ ) : null} - - setAccordionState(isOpen ? 'open' : 'closed')} - > - {accordionState === 'open' ? : null} - - + {isEmpty(subGroups) ? null : ( + + setAccordionState(isOpen ? 'open' : 'closed')} + > + {accordionState === 'open' ? ( + + ) : null} + + + )} ; diff?: { rank: number; samples: number; @@ -149,6 +150,7 @@ export function getFunctionsRows({ selfAnnualCostUSD: topN.selfAnnualCostUSD, totalAnnualCO2kgs: topN.totalAnnualCO2kgs, totalAnnualCostUSD: topN.totalAnnualCostUSD, + subGroups: topN.subGroups, diff: calculateDiff(), }; }); @@ -214,5 +216,6 @@ export function convertRowToFrame(row: IFunctionRow) { totalAnnualCO2Kgs: row.totalAnnualCO2kgs, selfAnnualCostUSD: row.selfAnnualCostUSD, totalAnnualCostUSD: row.totalAnnualCostUSD, + subGroups: row.subGroups, }; } diff --git a/x-pack/plugins/observability_solution/profiling/public/services.ts b/x-pack/plugins/observability_solution/profiling/public/services.ts index 3a8aceed514c53..6ce88f33d38c95 100644 --- a/x-pack/plugins/observability_solution/profiling/public/services.ts +++ b/x-pack/plugins/observability_solution/profiling/public/services.ts @@ -23,6 +23,11 @@ import { TopNResponse } from '../common/topn'; import type { SetupDataCollectionInstructions } from '../server/routes/setup/get_cloud_setup_instructions'; import { AutoAbortedHttpService } from './hooks/use_auto_aborted_http_client'; +type APMTransactions = Array<{ + serviceName: string; + transactionName: string | null; +}>; + export interface ProfilingSetupStatus { has_setup: boolean; has_data: boolean; @@ -77,6 +82,13 @@ export interface Services { http: AutoAbortedHttpService; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; }) => Promise; + fetchTopNFunctionAPMTransactions: (params: { + http: AutoAbortedHttpService; + timeFrom: number; + timeTo: number; + functionName: string; + serviceNames: string[]; + }) => Promise; } export function getServices(): Services { @@ -167,5 +179,22 @@ export function getServices(): Services { )) as IndicesStorageDetailsAPIResponse; return eventsMetricsSizeTimeseries; }, + fetchTopNFunctionAPMTransactions: async ({ + functionName, + http, + serviceNames, + timeFrom, + timeTo, + }) => { + const query: HttpFetchQuery = { + timeFrom, + timeTo, + functionName, + serviceNames: JSON.stringify(serviceNames), + }; + return (await http.get(paths.APMTransactions, { + query, + })) as Promise; + }, }; } diff --git a/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts b/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts index 6e683d2649cf85..3ac836d2ba3e84 100644 --- a/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts +++ b/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts @@ -6,7 +6,8 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { termQuery, termsQuery } from '@kbn/observability-plugin/server'; +import { termQuery } from '@kbn/observability-plugin/server'; +import { keyBy } from 'lodash'; import { IDLE_SOCKET_TIMEOUT, RouteRegisterParameters } from '.'; import { getRoutePaths } from '../../common'; import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; @@ -15,8 +16,8 @@ import { getClient } from './compat'; const querySchema = schema.object({ timeFrom: schema.number(), timeTo: schema.number(), - startIndex: schema.number(), - endIndex: schema.number(), + // startIndex: schema.number(), + // endIndex: schema.number(), functionName: schema.string(), serviceNames: schema.arrayOf(schema.string()), }); @@ -28,6 +29,7 @@ export function registerTopNFunctionsAPMTransactionsRoute({ logger, dependencies: { start: { profilingDataAccess }, + setup: { apmDataAccess }, }, }: RouteRegisterParameters) { const paths = getRoutePaths(); @@ -39,57 +41,76 @@ export function registerTopNFunctionsAPMTransactionsRoute({ }, async (context, request, response) => { try { + if (!apmDataAccess) { + return response.ok({ + body: [], + }); + } const core = await context.core; + const { transaction: transactionIndices } = await apmDataAccess.getApmIndices( + core.savedObjects.client + ); + + const esClient = await getClient(context); const { timeFrom, timeTo, - startIndex, - endIndex, + // startIndex, + // endIndex, functionName, serviceNames, }: QuerySchemaType = request.query; const startSecs = timeFrom / 1000; const endSecs = timeTo / 1000; - const esClient = await getClient(context); - - const query = { - bool: { - filter: [ - ...termQuery('service.name', serviceNames[0]), - { - range: { - ['@timestamp']: { - gte: String(startSecs), - lt: String(endSecs), - format: 'epoch_second', - }, + const transactionsPerService = await Promise.all( + serviceNames.map(async (serviceName) => { + const apmFunctions = await profilingDataAccess.services.fetchESFunctions({ + core, + esClient, + query: { + bool: { + filter: [ + ...termQuery('service.name', serviceName), + { + range: { + ['@timestamp']: { + gte: String(startSecs), + lt: String(endSecs), + format: 'epoch_second', + }, + }, + }, + ], }, }, - ], - }, - }; + aggregationField: 'transaction.name', + indices: transactionIndices.split(','), + stacktraceIdsField: 'transaction.profiler_stack_trace_ids', + }); + const apmFunction = apmFunctions.TopN.find( + (topNFunction) => topNFunction.Frame.FunctionName === functionName + ); + + return { serviceName, transactionNames: Object.keys(apmFunction?.subGroups || {}) }; + }) + ); - const [topNFunctions, newtopNFunctions] = await Promise.all([ - profilingDataAccess.services.fetchFunctions({ - core, - esClient, - startIndex, - endIndex, - totalSeconds: endSecs - startSecs, - query, - }), - profilingDataAccess.services.fetchESFunctions({ - core, - esClient, - query, - aggregationField: 'service.name', - }), - ]); + const transactionsGroupedByService = keyBy(transactionsPerService, 'serviceName'); return response.ok({ - body: newtopNFunctions, + body: serviceNames.flatMap( + (serviceName): Array<{ serviceName: string; transactionName: string | null }> => { + const transactionsFromService = transactionsGroupedByService[serviceName]; + return !!transactionsFromService?.transactionNames.length + ? transactionsFromService.transactionNames.map((transactionName) => ({ + serviceName, + transactionName, + })) + : [{ serviceName, transactionName: null }]; + } + ), }); } catch (error) { return handleRouteHandlerError({ diff --git a/x-pack/plugins/observability_solution/profiling/server/types.ts b/x-pack/plugins/observability_solution/profiling/server/types.ts index adc672c932083d..ef5028b8d311bf 100644 --- a/x-pack/plugins/observability_solution/profiling/server/types.ts +++ b/x-pack/plugins/observability_solution/profiling/server/types.ts @@ -17,6 +17,10 @@ import { ProfilingDataAccessPluginStart, } from '@kbn/profiling-data-access-plugin/server'; import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; +import { + ApmDataAccessPluginSetup, + ApmDataAccessPluginStart, +} from '@kbn/apm-data-access-plugin/server'; export interface ProfilingPluginSetupDeps { observability: ObservabilityPluginSetup; @@ -27,6 +31,7 @@ export interface ProfilingPluginSetupDeps { usageCollection?: UsageCollectionSetup; profilingDataAccess: ProfilingDataAccessPluginSetup; security?: SecurityPluginSetup; + apmDataAccess?: ApmDataAccessPluginSetup; } export interface ProfilingPluginStartDeps { @@ -37,6 +42,7 @@ export interface ProfilingPluginStartDeps { spaces?: SpacesPluginStart; profilingDataAccess: ProfilingDataAccessPluginStart; security?: SecurityPluginStart; + apmDataAccess?: ApmDataAccessPluginStart; } // eslint-disable-next-line @typescript-eslint/no-empty-interface From daf661245237831e429f8a3292418cd5e73cc24a Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Thu, 11 Apr 2024 16:01:53 +0100 Subject: [PATCH 03/20] adding apm service locator --- .../locators/apm/service_overview_locator.ts | 34 +++++++++++++++++++ .../observability_shared/public/plugin.ts | 10 ++++++ .../apm_transactions.tsx | 19 +++++++++-- .../profiling/public/services.ts | 8 ++--- 4 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/observability_solution/observability_shared/public/locators/apm/service_overview_locator.ts diff --git a/x-pack/plugins/observability_solution/observability_shared/public/locators/apm/service_overview_locator.ts b/x-pack/plugins/observability_solution/observability_shared/public/locators/apm/service_overview_locator.ts new file mode 100644 index 00000000000000..a96d67840ce37e --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_shared/public/locators/apm/service_overview_locator.ts @@ -0,0 +1,34 @@ +/* + * 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 qs from 'query-string'; +import type { SerializableRecord } from '@kbn/utility-types'; +import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; + +export interface ServiceOverviewParams extends SerializableRecord { + serviceName: string; + rangeFrom?: string; + rangeTo?: string; +} + +export type ServiceOverviewLocator = LocatorPublic; + +export class ServiceOverviewLocatorDefinition implements LocatorDefinition { + public readonly id = 'serviceOverviewLocator'; + + public readonly getLocation = async ({ + rangeFrom, + rangeTo, + serviceName, + }: ServiceOverviewParams) => { + const params = { rangeFrom, rangeTo }; + return { + app: 'apm', + path: `/services/${serviceName}/overview?${qs.stringify(params)}`, + state: {}, + }; + }; +} diff --git a/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts b/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts index 204e9d3fbc99d4..fbf533fd0f0db6 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts @@ -39,6 +39,10 @@ import { type TopNFunctionsLocator, TopNFunctionsLocatorDefinition, } from './locators/profiling/topn_functions_locator'; +import { + type ServiceOverviewLocator, + ServiceOverviewLocatorDefinition, +} from './locators/apm/service_overview_locator'; import { updateGlobalNavigation } from './services/update_global_navigation'; export interface ObservabilitySharedSetup { share: SharePluginSetup; @@ -67,6 +71,9 @@ interface ObservabilitySharedLocators { topNFunctionsLocator: TopNFunctionsLocator; stacktracesLocator: StacktracesLocator; }; + apm: { + serviceOverview: ServiceOverviewLocator; + }; } export class ObservabilitySharedPlugin implements Plugin { @@ -131,6 +138,9 @@ export class ObservabilitySharedPlugin implements Plugin { topNFunctionsLocator: urlService.locators.create(new TopNFunctionsLocatorDefinition()), stacktracesLocator: urlService.locators.create(new StacktracesLocatorDefinition()), }, + apm: { + serviceOverview: urlService.locators.create(new ServiceOverviewLocatorDefinition()), + }, }; } } diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx index 39c4bec9c9cef6..7464fc4759e9e5 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx @@ -5,16 +5,18 @@ * 2.0. */ import { + EuiBasicTableColumn, EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, - EuiInMemoryTableProps, + EuiLink, EuiLoadingSpinner, } from '@elastic/eui'; -import React from 'react'; import { i18n } from '@kbn/i18n'; +import React from 'react'; import { AsyncStatus } from '../../hooks/use_async'; import { useTimeRangeAsync } from '../../hooks/use_time_range_async'; +import type { APMTransactionsPerService } from '../../services'; import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; interface Props { @@ -27,6 +29,7 @@ interface Props { export function APMTransactions({ functionName, serviceNames, timeTo, timeFrom }: Props) { const { services: { fetchTopNFunctionAPMTransactions }, + setup: { observabilityShared }, } = useProfilingDependencies(); const { status, data = [] } = useTimeRangeAsync( @@ -42,13 +45,23 @@ export function APMTransactions({ functionName, serviceNames, timeTo, timeFrom } [fetchTopNFunctionAPMTransactions, functionName, serviceNames] ); - const columns: EuiInMemoryTableProps['columns'] = [ + const columns: Array> = [ { field: 'serviceName', name: i18n.translate('xpack.profiling.apmTransactions.columns.serviceName', { defaultMessage: 'Service Name', }), truncateText: true, + render: (_, { serviceName }) => { + return ( + + {serviceName} + + ); + }, }, { field: 'transactionName', diff --git a/x-pack/plugins/observability_solution/profiling/public/services.ts b/x-pack/plugins/observability_solution/profiling/public/services.ts index 6ce88f33d38c95..a6e3d4a8a94c5d 100644 --- a/x-pack/plugins/observability_solution/profiling/public/services.ts +++ b/x-pack/plugins/observability_solution/profiling/public/services.ts @@ -23,10 +23,10 @@ import { TopNResponse } from '../common/topn'; import type { SetupDataCollectionInstructions } from '../server/routes/setup/get_cloud_setup_instructions'; import { AutoAbortedHttpService } from './hooks/use_auto_aborted_http_client'; -type APMTransactions = Array<{ +export interface APMTransactionsPerService { serviceName: string; transactionName: string | null; -}>; +} export interface ProfilingSetupStatus { has_setup: boolean; @@ -88,7 +88,7 @@ export interface Services { timeTo: number; functionName: string; serviceNames: string[]; - }) => Promise; + }) => Promise; } export function getServices(): Services { @@ -194,7 +194,7 @@ export function getServices(): Services { }; return (await http.get(paths.APMTransactions, { query, - })) as Promise; + })) as Promise; }, }; } From 1cfc73431ea42c5531dcaa08d467f95f8ad53465 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Thu, 11 Apr 2024 16:26:47 +0100 Subject: [PATCH 04/20] apm transaction by name locator --- .../app/transaction_details_link/index.tsx | 80 +++++++++++++++++++ .../components/routing/apm_route_config.tsx | 20 ++++- .../apm/server/routes/traces/route.ts | 38 +++++++++ .../get_transaction_by_name/index.ts | 57 +++++++++++++ .../transaction_details_by_name_locator.ts | 38 +++++++++ .../observability_shared/public/plugin.ts | 8 ++ .../apm_transactions.tsx | 16 ++++ 7 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/observability_solution/apm/public/components/app/transaction_details_link/index.tsx create mode 100644 x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction_by_name/index.ts create mode 100644 x-pack/plugins/observability_solution/observability_shared/public/locators/apm/transaction_details_by_name_locator.ts diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details_link/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details_link/index.tsx new file mode 100644 index 00000000000000..a197c5a1fafee7 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details_link/index.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiEmptyPrompt } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { Redirect } from 'react-router-dom'; +import { css } from '@emotion/react'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { getRedirectToTransactionDetailPageUrl } from '../trace_link/get_redirect_to_transaction_detail_page_url'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { useTimeRange } from '../../../hooks/use_time_range'; + +export function TransactionDetailsByNameLink() { + const { + query: { rangeFrom, rangeTo, transactionName, serviceName }, + } = useApmParams('/link-to/transaction'); + + const { start, end } = useTimeRange({ + rangeFrom: rangeFrom || new Date(0).toISOString(), + rangeTo: rangeTo || new Date().toISOString(), + }); + + const { data = { transaction: null }, status } = useFetcher( + (callApmApi) => { + return callApmApi('GET /internal/apm/transactions', { + params: { + query: { + start, + end, + transactionName, + serviceName, + }, + }, + }); + }, + [start, end, transactionName, serviceName] + ); + + if (status === FETCH_STATUS.SUCCESS) { + if (data.transaction) { + return ( + + ); + } + + return null; + } + + return ( +
+ + {i18n.translate( + 'xpack.apm.transactionDetailsLink.h2.fetchingTransactionLabel', + { defaultMessage: 'Fetching transaction...' } + )} + + } + /> +
+ ); +} diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/apm_route_config.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/apm_route_config.tsx index b3d03381039979..a7646f4484babd 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/apm_route_config.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/apm_route_config.tsx @@ -23,6 +23,7 @@ import { ApmMainTemplate } from './templates/apm_main_template'; import { ServiceGroupsList } from '../app/service_groups'; import { offsetRt } from '../../../common/comparison_rt'; import { diagnosticsRoute } from '../app/diagnostics'; +import { TransactionDetailsByNameLink } from '../app/transaction_details_link'; const ServiceGroupsTitle = i18n.translate( 'xpack.apm.views.serviceGroups.title', @@ -34,6 +35,18 @@ const ServiceGroupsTitle = i18n.translate( * creates the routes. */ const apmRoutes = { + '/link-to/transaction': { + element: , + params: t.type({ + query: t.intersection([ + t.type({ transactionName: t.string, serviceName: t.string }), + t.partial({ + rangeFrom: t.string, + rangeTo: t.string, + }), + ]), + }), + }, '/link-to/transaction/{transactionId}': { element: , params: t.intersection([ @@ -69,7 +82,12 @@ const apmRoutes = { }, '/': { element: ( - + ), diff --git a/x-pack/plugins/observability_solution/apm/server/routes/traces/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/traces/route.ts index f55a145cfb2551..24090c4303c40a 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/traces/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/traces/route.ts @@ -36,6 +36,7 @@ import { import { getSpan } from '../transactions/get_span'; import { Transaction } from '../../../typings/es_schemas/ui/transaction'; import { Span } from '../../../typings/es_schemas/ui/span'; +import { getTransactionByName } from '../transactions/get_transaction_by_name'; const tracesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/traces', @@ -186,6 +187,42 @@ const transactionByIdRoute = createApmServerRoute({ }, }); +const transactionByNameRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/transactions', + params: t.type({ + query: t.intersection([ + rangeRt, + t.type({ + transactionName: t.string, + serviceName: t.string, + }), + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise<{ + transaction: Transaction; + }> => { + const { + params: { + query: { start, end, transactionName, serviceName }, + }, + } = resources; + + const apmEventClient = await getApmEventClient(resources); + return { + transaction: await getTransactionByName({ + transactionName, + apmEventClient, + start, + end, + serviceName, + }), + }; + }, +}); + const findTracesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/traces/find', params: t.type({ @@ -338,4 +375,5 @@ export const traceRouteRepository = { ...aggregatedCriticalPathRoute, ...transactionFromTraceByIdRoute, ...spanFromTraceByIdRoute, + ...transactionByNameRoute, }; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction_by_name/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction_by_name/index.ts new file mode 100644 index 00000000000000..1c810dfe394ed8 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/transactions/get_transaction_by_name/index.ts @@ -0,0 +1,57 @@ +/* + * 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 { rangeQuery } from '@kbn/observability-plugin/server'; +import { ApmDocumentType } from '../../../../common/document_type'; +import { + SERVICE_NAME, + TRANSACTION_NAME, +} from '../../../../common/es_fields/apm'; +import { RollupInterval } from '../../../../common/rollup'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +export async function getTransactionByName({ + transactionName, + serviceName, + apmEventClient, + start, + end, +}: { + transactionName: string; + serviceName: string; + apmEventClient: APMEventClient; + start: number; + end: number; +}) { + const resp = await apmEventClient.search('get_transaction', { + apm: { + sources: [ + { + documentType: ApmDocumentType.TransactionEvent, + rollupInterval: RollupInterval.None, + }, + ], + }, + body: { + track_total_hits: false, + size: 1, + terminate_after: 1, + query: { + bool: { + filter: asMutableArray([ + { term: { [TRANSACTION_NAME]: transactionName } }, + { term: { [SERVICE_NAME]: serviceName } }, + ...rangeQuery(start, end), + ]), + }, + }, + }, + }); + + return resp.hits.hits[0]?._source; +} diff --git a/x-pack/plugins/observability_solution/observability_shared/public/locators/apm/transaction_details_by_name_locator.ts b/x-pack/plugins/observability_solution/observability_shared/public/locators/apm/transaction_details_by_name_locator.ts new file mode 100644 index 00000000000000..468faf0ba18b79 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_shared/public/locators/apm/transaction_details_by_name_locator.ts @@ -0,0 +1,38 @@ +/* + * 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 qs from 'query-string'; +import type { SerializableRecord } from '@kbn/utility-types'; +import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; + +export interface TransactionDetailsByNameParams extends SerializableRecord { + serviceName: string; + transactionName: string; + rangeFrom?: string; + rangeTo?: string; +} + +export type TransactionDetailsByNameLocator = LocatorPublic; + +export class TransactionDetailsByNameLocatorDefinition + implements LocatorDefinition +{ + public readonly id = 'TransactionDetailsByNameLocator'; + + public readonly getLocation = async ({ + rangeFrom, + rangeTo, + serviceName, + transactionName, + }: TransactionDetailsByNameParams) => { + const params = { rangeFrom, rangeTo, serviceName, transactionName }; + return { + app: 'apm', + path: `/link-to/transaction?${qs.stringify(params)}`, + state: {}, + }; + }; +} diff --git a/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts b/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts index fbf533fd0f0db6..625bdbaaeeb3a7 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts @@ -44,6 +44,10 @@ import { ServiceOverviewLocatorDefinition, } from './locators/apm/service_overview_locator'; import { updateGlobalNavigation } from './services/update_global_navigation'; +import { + type TransactionDetailsByNameLocator, + TransactionDetailsByNameLocatorDefinition, +} from './locators/apm/transaction_details_by_name_locator'; export interface ObservabilitySharedSetup { share: SharePluginSetup; } @@ -73,6 +77,7 @@ interface ObservabilitySharedLocators { }; apm: { serviceOverview: ServiceOverviewLocator; + transactionDetailsByName: TransactionDetailsByNameLocator; }; } @@ -140,6 +145,9 @@ export class ObservabilitySharedPlugin implements Plugin { }, apm: { serviceOverview: urlService.locators.create(new ServiceOverviewLocatorDefinition()), + transactionDetailsByName: urlService.locators.create( + new TransactionDetailsByNameLocatorDefinition() + ), }, }; } diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx index 7464fc4759e9e5..e244637b086c74 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx @@ -69,6 +69,22 @@ export function APMTransactions({ functionName, serviceNames, timeTo, timeFrom } defaultMessage: 'Transaction Name', }), truncateText: true, + render(_, { serviceName, transactionName }) { + if (transactionName) { + return ( + + {transactionName} + + ); + } + return 'N/A'; + }, }, ]; From 2f3a6351f0857bda43ecb99864bfd5fce544d3ad Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Fri, 12 Apr 2024 10:17:14 +0100 Subject: [PATCH 05/20] adding time range --- .../apm_transactions.tsx | 24 ++++++++++------- .../frame_information_window/index.tsx | 26 ++++++++++++------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx index e244637b086c74..b1d4ee409ee107 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx @@ -14,19 +14,25 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { NOT_AVAILABLE_LABEL } from '../../../common'; import { AsyncStatus } from '../../hooks/use_async'; +import { useAnyOfProfilingParams } from '../../hooks/use_profiling_params'; +import { useTimeRange } from '../../hooks/use_time_range'; import { useTimeRangeAsync } from '../../hooks/use_time_range_async'; import type { APMTransactionsPerService } from '../../services'; import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; interface Props { serviceNames: Record; - timeFrom: string; - timeTo: string; functionName: string; } -export function APMTransactions({ functionName, serviceNames, timeTo, timeFrom }: Props) { +export function APMTransactions({ functionName, serviceNames }: Props) { + const { + query: { rangeFrom, rangeTo }, + } = useAnyOfProfilingParams('/functions/*', '/flamegraphs/*'); + const timeRange = useTimeRange({ rangeFrom, rangeTo }); + const { services: { fetchTopNFunctionAPMTransactions }, setup: { observabilityShared }, @@ -36,13 +42,13 @@ export function APMTransactions({ functionName, serviceNames, timeTo, timeFrom } ({ http }) => { return fetchTopNFunctionAPMTransactions({ http, - timeFrom: 1712444400000, - timeTo: 1713049199999, + timeFrom: new Date(timeRange.start).getTime(), + timeTo: new Date(timeRange.end).getTime(), functionName, - serviceNames: Object.keys(serviceNames), + serviceNames: Object.keys(...serviceNames), }); }, - [fetchTopNFunctionAPMTransactions, functionName, serviceNames] + [fetchTopNFunctionAPMTransactions, functionName, serviceNames, timeRange.end, timeRange.start] ); const columns: Array> = [ @@ -83,7 +89,7 @@ export function APMTransactions({ functionName, serviceNames, timeTo, timeFrom } ); } - return 'N/A'; + return NOT_AVAILABLE_LABEL; }, }, ]; @@ -105,7 +111,7 @@ export function APMTransactions({ functionName, serviceNames, timeTo, timeFrom } })} items={data} columns={columns} - pagination={{ itemsPerPage: 5, showPerPageOptions: false }} + pagination={{ pageSize: 5, showPerPageOptions: false }} /> ); } diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx index 6e27f2ae0fcc71..f92198263f4cee 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx @@ -10,8 +10,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiStat, + EuiText, EuiTitle, + useEuiTheme, } from '@elastic/eui'; +import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { FrameSymbolStatus, getFrameSymbolStatus } from '@kbn/profiling-utils'; import { isEmpty } from 'lodash'; @@ -69,6 +72,7 @@ export function FrameInformationWindow({ rank, compressed = false, }: Props) { + const { euiTheme } = useEuiTheme(); const [accordionState, setAccordionState] = useState('closed'); const calculateImpactEstimates = useCalculateImpactEstimate(); @@ -165,20 +169,22 @@ export function FrameInformationWindow({ + {i18n.translate('xpack.profiling.frameInformationWindow.apmTransactions', { + defaultMessage: 'APM Transactions', + })} + + } forceState={accordionState} onToggle={(isOpen) => setAccordionState(isOpen ? 'open' : 'closed')} > {accordionState === 'open' ? ( - + ) : null} From b5ee771c8d37874d5750f512b2931d0e1735fa09 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Fri, 12 Apr 2024 10:18:18 +0100 Subject: [PATCH 06/20] fixing --- .../components/frame_information_window/apm_transactions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx index b1d4ee409ee107..4c49d33e9d6914 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx @@ -45,7 +45,7 @@ export function APMTransactions({ functionName, serviceNames }: Props) { timeFrom: new Date(timeRange.start).getTime(), timeTo: new Date(timeRange.end).getTime(), functionName, - serviceNames: Object.keys(...serviceNames), + serviceNames: Object.keys(serviceNames), }); }, [fetchTopNFunctionAPMTransactions, functionName, serviceNames, timeRange.end, timeRange.start] From e090081bceb22d309e0722763162ec9e2feadca7 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Fri, 12 Apr 2024 10:54:13 +0100 Subject: [PATCH 07/20] adv settings --- docs/management/advanced-options.asciidoc | 3 ++ .../server/collectors/management/schema.ts | 4 ++ .../server/collectors/management/types.ts | 1 + src/plugins/telemetry/schema/oss_plugins.json | 10 ++++- .../observability/common/index.ts | 1 + .../observability/common/ui_settings_keys.ts | 2 + .../observability/server/ui_settings.ts | 16 ++++++++ .../profiling/public/views/settings/index.tsx | 3 +- .../profiling/server/routes/apm.ts | 12 +----- .../profiling/server/routes/functions.ts | 38 ++++++++++--------- .../common/profiling_es_client.ts | 2 + .../server/services/functions/es_functions.ts | 4 ++ .../utils/create_profiling_es_client.ts | 4 ++ 13 files changed, 70 insertions(+), 30 deletions(-) diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 4ab341571668e6..9e400f01c88ddb 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -486,6 +486,9 @@ If you're enrolled in the AWS Enterprise Discount Program (EDP), enter your disc [[observability-profiling-azure-cost-discount-rate]]`observability:profilingAzureCostDiscountRate`:: If you have an Azure Enterprise Agreement with Microsoft, enter your discount rate to update the profiling cost calculation. +[[observability-profiling-use-topNFunctions-from-stacktraces]]`observability:profilingFetchTopNFunctionsFromStacktraces`:: +Switch to fetch the TopN Functions from the Stacktraces API. + [[observability-profiling-cost-per-vcpu-per-hour]]`observability:profilingCostPervCPUPerHour`:: Default Hourly Cost per CPU Core for machines not on AWS or Azure. diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index ad4ef03237e4de..470463200f6c3c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -659,4 +659,8 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'keyword', _meta: { description: 'Non-default value of setting.' }, }, + 'observability:profilingFetchTopNFunctionsFromStacktraces': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, }; diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 80384cc22e03c7..a6ca611a135148 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -173,4 +173,5 @@ export interface UsageStats { 'observability:apmEnableTransactionProfiling': boolean; 'devTools:enablePersistentConsole': boolean; 'aiAssistant:preferredAIAssistantType': string; + 'observability:profilingFetchTopNFunctionsFromStacktraces': boolean; } diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index c727356a8f5ea3..5e2e4353e35373 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -10433,7 +10433,13 @@ "_meta": { "description": "Non-default value of setting." } - } + }, + "observability:profilingFetchTopNFunctionsFromStacktraces": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, } }, "static_telemetry": { @@ -11869,4 +11875,4 @@ } } } -} +} \ No newline at end of file diff --git a/x-pack/plugins/observability_solution/observability/common/index.ts b/x-pack/plugins/observability_solution/observability/common/index.ts index c8b65fe127d2ac..e71a8d2bc5777a 100644 --- a/x-pack/plugins/observability_solution/observability/common/index.ts +++ b/x-pack/plugins/observability_solution/observability/common/index.ts @@ -54,6 +54,7 @@ export { profilingAzureCostDiscountRate, apmEnableTransactionProfiling, apmEnableServiceInventoryTableSearchBar, + profilingFetchTopNFunctionsFromStacktraces, } from './ui_settings_keys'; export { diff --git a/x-pack/plugins/observability_solution/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability_solution/observability/common/ui_settings_keys.ts index a95731ef8c67fb..87f60b977691c7 100644 --- a/x-pack/plugins/observability_solution/observability/common/ui_settings_keys.ts +++ b/x-pack/plugins/observability_solution/observability/common/ui_settings_keys.ts @@ -43,3 +43,5 @@ export const profilingAWSCostDiscountRate = 'observability:profilingAWSCostDisco export const profilingCostPervCPUPerHour = 'observability:profilingCostPervCPUPerHour'; export const profilingAzureCostDiscountRate = 'observability:profilingAzureCostDiscountRate'; export const apmEnableTransactionProfiling = 'observability:apmEnableTransactionProfiling'; +export const profilingFetchTopNFunctionsFromStacktraces = + 'observability:profilingFetchTopNFunctionsFromStacktraces'; 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 7c9ced9e308ec7..2b5468e0715f84 100644 --- a/x-pack/plugins/observability_solution/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability_solution/observability/server/ui_settings.ts @@ -43,6 +43,7 @@ import { apmEnableTransactionProfiling, enableInfrastructureAssetCustomDashboards, apmEnableServiceInventoryTableSearchBar, + profilingFetchTopNFunctionsFromStacktraces, } from '../common/ui_settings_keys'; const betaLabel = i18n.translate('xpack.observability.uiSettings.betaLabel', { @@ -600,6 +601,21 @@ export const uiSettings: Record = { schema: schema.boolean(), requiresPageReload: true, }, + [profilingFetchTopNFunctionsFromStacktraces]: { + category: [observabilityFeatureId], + name: i18n.translate('xpack.observability.profilingFetchTopNFunctionsFromStacktraces', { + defaultMessage: 'Switch to fetch the TopN Functions from the Stacktraces API.', + }), + description: i18n.translate( + 'xpack.observability.profilingFetchTopNFunctionsFromStacktracesDescription', + { + defaultMessage: `The topN functions pages use the topN/functions API, turn it on to switch to the stacktraces api`, + } + ), + value: false, + schema: schema.boolean(), + requiresPageReload: false, + }, }; function throttlingDocsLink({ href }: { href: string }) { diff --git a/x-pack/plugins/observability_solution/profiling/public/views/settings/index.tsx b/x-pack/plugins/observability_solution/profiling/public/views/settings/index.tsx index a5656c4451aed7..2ed5b2dc952058 100644 --- a/x-pack/plugins/observability_solution/profiling/public/views/settings/index.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/views/settings/index.tsx @@ -27,6 +27,7 @@ import { profilingAzureCostDiscountRate, profilingCostPervCPUPerHour, profilingShowErrorFrames, + profilingFetchTopNFunctionsFromStacktraces, } from '@kbn/observability-plugin/common'; import { useEditableSettings, useUiTracker } from '@kbn/observability-shared-plugin/public'; import { isEmpty } from 'lodash'; @@ -53,7 +54,7 @@ const costSettings = [ profilingAzureCostDiscountRate, profilingCostPervCPUPerHour, ]; -const miscSettings = [profilingShowErrorFrames]; +const miscSettings = [profilingShowErrorFrames, profilingFetchTopNFunctionsFromStacktraces]; export function Settings() { const trackProfilingEvent = useUiTracker({ app: 'profiling' }); diff --git a/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts b/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts index 3ac836d2ba3e84..a9f1f6c892d6b9 100644 --- a/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts +++ b/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts @@ -16,8 +16,6 @@ import { getClient } from './compat'; const querySchema = schema.object({ timeFrom: schema.number(), timeTo: schema.number(), - // startIndex: schema.number(), - // endIndex: schema.number(), functionName: schema.string(), serviceNames: schema.arrayOf(schema.string()), }); @@ -53,14 +51,7 @@ export function registerTopNFunctionsAPMTransactionsRoute({ const esClient = await getClient(context); - const { - timeFrom, - timeTo, - // startIndex, - // endIndex, - functionName, - serviceNames, - }: QuerySchemaType = request.query; + const { timeFrom, timeTo, functionName, serviceNames }: QuerySchemaType = request.query; const startSecs = timeFrom / 1000; const endSecs = timeTo / 1000; @@ -88,6 +79,7 @@ export function registerTopNFunctionsAPMTransactionsRoute({ aggregationField: 'transaction.name', indices: transactionIndices.split(','), stacktraceIdsField: 'transaction.profiler_stack_trace_ids', + limit: 1000, }); const apmFunction = apmFunctions.TopN.find( (topNFunction) => topNFunction.Frame.FunctionName === functionName 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 d4f156ff883338..e7dd4e7f9d9088 100644 --- a/x-pack/plugins/observability_solution/profiling/server/routes/functions.ts +++ b/x-pack/plugins/observability_solution/profiling/server/routes/functions.ts @@ -7,6 +7,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { kqlQuery } from '@kbn/observability-plugin/server'; +import { profilingFetchTopNFunctionsFromStacktraces } from '@kbn/observability-plugin/common'; import { IDLE_SOCKET_TIMEOUT, RouteRegisterParameters } from '.'; import { getRoutePaths } from '../../common'; import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; @@ -63,25 +64,28 @@ export function registerTopNFunctionsSearchRoute({ }, }; - const [topNFunctions, newtopNFunctions] = await Promise.all([ - profilingDataAccess.services.fetchFunctions({ - core, - esClient, - startIndex, - endIndex, - totalSeconds: endSecs - startSecs, - query, - }), - profilingDataAccess.services.fetchESFunctions({ - core, - esClient, - query, - aggregationField: 'service.name', - }), - ]); + const useStacktracesAPI = await core.uiSettings.client.get( + profilingFetchTopNFunctionsFromStacktraces + ); + + const topNFunctions = useStacktracesAPI + ? await profilingDataAccess.services.fetchFunctions({ + core, + esClient, + startIndex, + endIndex, + totalSeconds: endSecs - startSecs, + query, + }) + : await profilingDataAccess.services.fetchESFunctions({ + core, + esClient, + query, + aggregationField: 'service.name', + }); return response.ok({ - body: newtopNFunctions, + body: topNFunctions, }); } catch (error) { return handleRouteHandlerError({ diff --git a/x-pack/plugins/observability_solution/profiling_data_access/common/profiling_es_client.ts b/x-pack/plugins/observability_solution/profiling_data_access/common/profiling_es_client.ts index 5a1daab5706a6c..96cef8726cf66c 100644 --- a/x-pack/plugins/observability_solution/profiling_data_access/common/profiling_es_client.ts +++ b/x-pack/plugins/observability_solution/profiling_data_access/common/profiling_es_client.ts @@ -53,6 +53,8 @@ export interface ProfilingESClient { }): Promise; topNFunctions(params: { query: QueryDslQueryContainer; + limit?: number; + sampleSize?: number; indices?: string[]; stacktraceIdsField?: string; aggregationField?: AggregationField; diff --git a/x-pack/plugins/observability_solution/profiling_data_access/server/services/functions/es_functions.ts b/x-pack/plugins/observability_solution/profiling_data_access/server/services/functions/es_functions.ts index f4c5f7de6c0d8e..3296ab2141d1c1 100644 --- a/x-pack/plugins/observability_solution/profiling_data_access/server/services/functions/es_functions.ts +++ b/x-pack/plugins/observability_solution/profiling_data_access/server/services/functions/es_functions.ts @@ -32,6 +32,7 @@ export interface FetchFunctionsParams { stacktraceIdsField?: string; query: QueryDslQueryContainer; aggregationField?: AggregationField; + limit?: number; } const targetSampleSize = 20000; // minimum number of samples to get statistically sound results @@ -44,6 +45,7 @@ export function createFetchESFunctions({ createProfilingEsClient }: RegisterServ stacktraceIdsField, query, aggregationField, + limit, }: FetchFunctionsParams) => { const [ co2PerKWH, @@ -67,6 +69,8 @@ export function createFetchESFunctions({ createProfilingEsClient }: RegisterServ const profilingEsClient = createProfilingEsClient({ esClient }); const esTopNFunctions = await profilingEsClient.topNFunctions({ + sampleSize: targetSampleSize, + limit, query, indices, stacktraceIdsField, diff --git a/x-pack/plugins/observability_solution/profiling_data_access/server/utils/create_profiling_es_client.ts b/x-pack/plugins/observability_solution/profiling_data_access/server/utils/create_profiling_es_client.ts index 0bdbb8c4248f2d..ab7483a99916f1 100644 --- a/x-pack/plugins/observability_solution/profiling_data_access/server/utils/create_profiling_es_client.ts +++ b/x-pack/plugins/observability_solution/profiling_data_access/server/utils/create_profiling_es_client.ts @@ -163,6 +163,8 @@ export function createProfilingEsClient({ pervCPUWattArm64, pervCPUWattX86, azureCostDiscountRate, + sampleSize, + limit, }) { const controller = new AbortController(); @@ -173,6 +175,8 @@ export function createProfilingEsClient({ path: encodeURI('/_profiling/topn/functions'), body: { query, + sample_size: sampleSize, + limit, indices, stacktrace_ids_field: stacktraceIdsField, aggregation_field: aggregationField, From 3cb76c0f379651f6aa3e78bae8c936ed4079bd80 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:10:27 +0000 Subject: [PATCH 08/20] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/observability_solution/profiling/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/profiling/tsconfig.json b/x-pack/plugins/observability_solution/profiling/tsconfig.json index 381c6ab4c5d1f8..fc9080606e4543 100644 --- a/x-pack/plugins/observability_solution/profiling/tsconfig.json +++ b/x-pack/plugins/observability_solution/profiling/tsconfig.json @@ -53,7 +53,8 @@ "@kbn/security-plugin", "@kbn/shared-ux-utility", "@kbn/management-settings-components-field-row", - "@kbn/deeplinks-observability" + "@kbn/deeplinks-observability", + "@kbn/apm-data-access-plugin" // add references to other TypeScript projects the plugin depends on // requiredPlugins from ./kibana.json From b36ba03e1ff49000db2927f1780771ee1bdc14a3 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Fri, 12 Apr 2024 13:16:16 +0100 Subject: [PATCH 09/20] fixing ci --- src/plugins/telemetry/schema/oss_plugins.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 5e2e4353e35373..e67781742ee39d 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -10439,7 +10439,7 @@ "_meta": { "description": "Non-default value of setting." } - }, + } } }, "static_telemetry": { From d7935a719a34ad398e4854f815509f3b4f5b10c8 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Fri, 12 Apr 2024 14:49:04 +0100 Subject: [PATCH 10/20] renaming --- .../common/{topN_functions.ts => es_functions.ts} | 0 packages/kbn-profiling-utils/index.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/kbn-profiling-utils/common/{topN_functions.ts => es_functions.ts} (100%) diff --git a/packages/kbn-profiling-utils/common/topN_functions.ts b/packages/kbn-profiling-utils/common/es_functions.ts similarity index 100% rename from packages/kbn-profiling-utils/common/topN_functions.ts rename to packages/kbn-profiling-utils/common/es_functions.ts diff --git a/packages/kbn-profiling-utils/index.ts b/packages/kbn-profiling-utils/index.ts index cecc7a5a47d7ee..4978c659373881 100644 --- a/packages/kbn-profiling-utils/index.ts +++ b/packages/kbn-profiling-utils/index.ts @@ -53,4 +53,4 @@ export type { } from './common/profiling'; export type { ProfilingStatus } from './common/profiling_status'; export type { TopNFunctions } from './common/functions'; -export type { AggregationField, ESTopNFunctions } from './common/topN_functions'; +export type { AggregationField, ESTopNFunctions } from './common/es_functions'; From c442827b10faa59c3a0aff208244c50fadb23318 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Mon, 15 Apr 2024 12:26:14 +0100 Subject: [PATCH 11/20] search field --- .../apm_transactions.tsx | 128 +++++++++++------- .../frame_information_window/index.tsx | 46 +++++-- .../profiling/public/services.ts | 1 + .../profiling/server/routes/apm.ts | 27 +++- 4 files changed, 136 insertions(+), 66 deletions(-) diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx index 4c49d33e9d6914..c534337ff534fe 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx @@ -6,20 +6,20 @@ */ import { EuiBasicTableColumn, - EuiFlexGroup, - EuiFlexItem, EuiInMemoryTable, + EuiInMemoryTableProps, EuiLink, - EuiLoadingSpinner, + EuiSearchBarProps, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; +import React, { useMemo } from 'react'; import { NOT_AVAILABLE_LABEL } from '../../../common'; import { AsyncStatus } from '../../hooks/use_async'; import { useAnyOfProfilingParams } from '../../hooks/use_profiling_params'; import { useTimeRange } from '../../hooks/use_time_range'; import { useTimeRangeAsync } from '../../hooks/use_time_range_async'; import type { APMTransactionsPerService } from '../../services'; +import { asNumber } from '../../utils/formatters/as_number'; import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; interface Props { @@ -27,6 +27,23 @@ interface Props { functionName: string; } +const search: EuiSearchBarProps = { + box: { + incremental: true, + schema: true, + placeholder: i18n.translate('xpack.profiling.apmTransactions.searchPlaceholder', { + defaultMessage: 'Search services or transactions by name', + }), + }, +}; + +const sorting: EuiInMemoryTableProps['sorting'] = { + sort: { + field: 'transactionSamples', + direction: 'desc', + }, +}; + export function APMTransactions({ functionName, serviceNames }: Props) { const { query: { rangeFrom, rangeTo }, @@ -51,67 +68,84 @@ export function APMTransactions({ functionName, serviceNames }: Props) { [fetchTopNFunctionAPMTransactions, functionName, serviceNames, timeRange.end, timeRange.start] ); - const columns: Array> = [ - { - field: 'serviceName', - name: i18n.translate('xpack.profiling.apmTransactions.columns.serviceName', { - defaultMessage: 'Service Name', - }), - truncateText: true, - render: (_, { serviceName }) => { - return ( - - {serviceName} - - ); - }, - }, - { - field: 'transactionName', - name: i18n.translate('xpack.profiling.apmTransactions.columns.transactionName', { - defaultMessage: 'Transaction Name', - }), - truncateText: true, - render(_, { serviceName, transactionName }) { - if (transactionName) { + const columns: Array> = useMemo( + () => [ + { + field: 'serviceName', + name: i18n.translate('xpack.profiling.apmTransactions.columns.serviceName', { + defaultMessage: 'Service Name', + }), + truncateText: true, + sortable: true, + render: (_, { serviceName }) => { return ( - {transactionName} + {serviceName} ); - } - return NOT_AVAILABLE_LABEL; + }, }, - }, - ]; - - if (status !== AsyncStatus.Settled) { - return ( - - - - - - ); - } + { + field: 'transactionName', + name: i18n.translate('xpack.profiling.apmTransactions.columns.transactionName', { + defaultMessage: 'Transaction Name', + }), + truncateText: true, + sortable: true, + render(_, { serviceName, transactionName }) { + if (transactionName) { + return ( + + {transactionName} + + ); + } + return NOT_AVAILABLE_LABEL; + }, + }, + { + field: 'transactionSamples', + name: i18n.translate('xpack.profiling.apmTransactions.columns.transactionName', { + defaultMessage: 'Transaction Samples', + }), + sortable: true, + render(_, { transactionSamples }) { + if (transactionSamples === null) { + return NOT_AVAILABLE_LABEL; + } + return asNumber(transactionSamples); + }, + }, + ], + [ + observabilityShared.locators.apm.serviceOverview, + observabilityShared.locators.apm.transactionDetailsByName, + ] + ); return ( ); } diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx index f92198263f4cee..b392be8717ffc7 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/index.tsx @@ -9,12 +9,12 @@ import { EuiAccordionProps, EuiFlexGroup, EuiFlexItem, + EuiIcon, EuiStat, EuiText, + EuiTextColor, EuiTitle, - useEuiTheme, } from '@elastic/eui'; -import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { FrameSymbolStatus, getFrameSymbolStatus } from '@kbn/profiling-utils'; import { isEmpty } from 'lodash'; @@ -72,7 +72,6 @@ export function FrameInformationWindow({ rank, compressed = false, }: Props) { - const { euiTheme } = useEuiTheme(); const [accordionState, setAccordionState] = useState('closed'); const calculateImpactEstimates = useCalculateImpactEstimate(); @@ -169,16 +168,39 @@ export function FrameInformationWindow({ - {i18n.translate('xpack.profiling.frameInformationWindow.apmTransactions', { - defaultMessage: 'APM Transactions', - })} - +
+ + + + + + +

+ {i18n.translate( + 'xpack.profiling.frameInformationWindow.apmTransactions', + { defaultMessage: 'Distributed Tracing Correlation' } + )} +

+
+
+
+ +

+ + {i18n.translate( + 'xpack.profiling.frameInformationWindow.apmTransactions.description', + { + defaultMessage: + 'A curated view of APM services and transactions that call this function.', + } + )} + +

+
+
} forceState={accordionState} onToggle={(isOpen) => setAccordionState(isOpen ? 'open' : 'closed')} diff --git a/x-pack/plugins/observability_solution/profiling/public/services.ts b/x-pack/plugins/observability_solution/profiling/public/services.ts index a6e3d4a8a94c5d..6d4a5bc72ac1e9 100644 --- a/x-pack/plugins/observability_solution/profiling/public/services.ts +++ b/x-pack/plugins/observability_solution/profiling/public/services.ts @@ -26,6 +26,7 @@ import { AutoAbortedHttpService } from './hooks/use_auto_aborted_http_client'; export interface APMTransactionsPerService { serviceName: string; transactionName: string | null; + transactionSamples: number | null; } export interface ProfilingSetupStatus { diff --git a/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts b/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts index a9f1f6c892d6b9..a9f59fce0da42f 100644 --- a/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts +++ b/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts @@ -84,8 +84,14 @@ export function registerTopNFunctionsAPMTransactionsRoute({ const apmFunction = apmFunctions.TopN.find( (topNFunction) => topNFunction.Frame.FunctionName === functionName ); - - return { serviceName, transactionNames: Object.keys(apmFunction?.subGroups || {}) }; + const subGroups = apmFunction?.subGroups || {}; + return { + serviceName, + transactions: Object.keys(subGroups).map((key) => ({ + name: key, + samples: subGroups[key], + })), + }; }) ); @@ -93,14 +99,21 @@ export function registerTopNFunctionsAPMTransactionsRoute({ return response.ok({ body: serviceNames.flatMap( - (serviceName): Array<{ serviceName: string; transactionName: string | null }> => { + ( + serviceName + ): Array<{ + serviceName: string; + transactionName: string | null; + transactionSamples: number | null; + }> => { const transactionsFromService = transactionsGroupedByService[serviceName]; - return !!transactionsFromService?.transactionNames.length - ? transactionsFromService.transactionNames.map((transactionName) => ({ + return !!transactionsFromService?.transactions.length + ? transactionsFromService.transactions.map((transaction) => ({ serviceName, - transactionName, + transactionName: transaction.name, + transactionSamples: transaction.samples, })) - : [{ serviceName, transactionName: null }]; + : [{ serviceName, transactionName: null, transactionSamples: null }]; } ), }); From f27d7da7e176b08833b16a00be44f7118074ea05 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Mon, 15 Apr 2024 14:48:44 +0100 Subject: [PATCH 12/20] paginating --- .../app/transaction_details_link/index.tsx | 17 ++-- .../apm_transactions.tsx | 88 +++++++++++++++++-- .../profiling/public/services.ts | 21 ++--- .../profiling/server/routes/apm.ts | 40 +++------ 4 files changed, 112 insertions(+), 54 deletions(-) diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details_link/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details_link/index.tsx index a197c5a1fafee7..ee3876c9dab48f 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details_link/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details_link/index.tsx @@ -15,15 +15,22 @@ import { getRedirectToTransactionDetailPageUrl } from '../trace_link/get_redirec import { useApmParams } from '../../../hooks/use_apm_params'; import { useTimeRange } from '../../../hooks/use_time_range'; +const _15_MIN_IN_MS = 15 * 60 * 1000; + export function TransactionDetailsByNameLink() { + const currentTime = new Date(); + const fallbackRangeFrom = new Date(currentTime.getTime() - _15_MIN_IN_MS); + const { - query: { rangeFrom, rangeTo, transactionName, serviceName }, + query: { + rangeFrom = fallbackRangeFrom.toISOString(), + rangeTo = currentTime.toISOString(), + transactionName, + serviceName, + }, } = useApmParams('/link-to/transaction'); - const { start, end } = useTimeRange({ - rangeFrom: rangeFrom || new Date(0).toISOString(), - rangeTo: rangeTo || new Date().toISOString(), - }); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); const { data = { transaction: null }, status } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx index c534337ff534fe..6ca8b6aac763b4 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx @@ -5,6 +5,7 @@ * 2.0. */ import { + CriteriaWithPagination, EuiBasicTableColumn, EuiInMemoryTable, EuiInMemoryTableProps, @@ -12,7 +13,7 @@ import { EuiSearchBarProps, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { NOT_AVAILABLE_LABEL } from '../../../common'; import { AsyncStatus } from '../../hooks/use_async'; import { useAnyOfProfilingParams } from '../../hooks/use_profiling_params'; @@ -39,11 +40,20 @@ const search: EuiSearchBarProps = { const sorting: EuiInMemoryTableProps['sorting'] = { sort: { - field: 'transactionSamples', + field: 'serviceSamples', direction: 'desc', }, }; +const PAGE_SIZE = 5; + +interface ServicesAndTransactions { + serviceName: string; + serviceSamples: number; + transactionName: string | null; + transactionSamples: number | null; +} + export function APMTransactions({ functionName, serviceNames }: Props) { const { query: { rangeFrom, rangeTo }, @@ -55,20 +65,58 @@ export function APMTransactions({ functionName, serviceNames }: Props) { setup: { observabilityShared }, } = useProfilingDependencies(); - const { status, data = [] } = useTimeRangeAsync( + const [pagination, setPagination] = useState({ pageIndex: 0 }); + + function onTableChange({ page: { index } }: CriteriaWithPagination) { + setPagination({ pageIndex: index }); + } + + const { status, data: transactionsPerServiceMap = {} } = useTimeRangeAsync( ({ http }) => { + const pageStart = pagination.pageIndex * PAGE_SIZE; + return fetchTopNFunctionAPMTransactions({ http, timeFrom: new Date(timeRange.start).getTime(), timeTo: new Date(timeRange.end).getTime(), functionName, - serviceNames: Object.keys(serviceNames), + serviceNames: Object.keys(serviceNames).slice(pageStart, pageStart + PAGE_SIZE), }); }, - [fetchTopNFunctionAPMTransactions, functionName, serviceNames, timeRange.end, timeRange.start] + [ + fetchTopNFunctionAPMTransactions, + functionName, + pagination.pageIndex, + serviceNames, + timeRange.end, + timeRange.start, + ] ); - const columns: Array> = useMemo( + const servicesAndTransactions: ServicesAndTransactions[] = useMemo(() => { + return Object.keys(serviceNames).flatMap((key) => { + const serviceTransactions = transactionsPerServiceMap[key]; + return serviceTransactions?.transactions?.length + ? serviceTransactions.transactions.map((transaction) => ({ + serviceName: key, + serviceSamples: serviceNames[key], + transactionName: transaction.name, + transactionSamples: transaction.samples, + })) + : [ + { + serviceName: key, + serviceSamples: serviceNames[key], + transactionName: null, + transactionSamples: null, + }, + ]; + }); + }, [serviceNames, transactionsPerServiceMap]); + + const isLoadingTransactions = status !== AsyncStatus.Settled; + + const columns: Array> = useMemo( () => [ { field: 'serviceName', @@ -90,6 +138,17 @@ export function APMTransactions({ functionName, serviceNames }: Props) { ); }, }, + { + field: 'serviceSamples', + name: i18n.translate('xpack.profiling.apmTransactions.columns.serviceSamplesName', { + defaultMessage: 'Service Samples', + }), + width: '150px', + sortable: true, + render(_, { serviceSamples }) { + return asNumber(serviceSamples); + }, + }, { field: 'transactionName', name: i18n.translate('xpack.profiling.apmTransactions.columns.transactionName', { @@ -98,6 +157,10 @@ export function APMTransactions({ functionName, serviceNames }: Props) { truncateText: true, sortable: true, render(_, { serviceName, transactionName }) { + if (isLoadingTransactions) { + return '--'; + } + if (transactionName) { return ( ; + }; } export interface ProfilingSetupStatus { @@ -89,7 +90,7 @@ export interface Services { timeTo: number; functionName: string; serviceNames: string[]; - }) => Promise; + }) => Promise; } export function getServices(): Services { @@ -180,22 +181,16 @@ export function getServices(): Services { )) as IndicesStorageDetailsAPIResponse; return eventsMetricsSizeTimeseries; }, - fetchTopNFunctionAPMTransactions: async ({ - functionName, - http, - serviceNames, - timeFrom, - timeTo, - }) => { + fetchTopNFunctionAPMTransactions: ({ functionName, http, serviceNames, timeFrom, timeTo }) => { const query: HttpFetchQuery = { timeFrom, timeTo, functionName, serviceNames: JSON.stringify(serviceNames), }; - return (await http.get(paths.APMTransactions, { + return http.get(paths.APMTransactions, { query, - })) as Promise; + }) as Promise; }, }; } diff --git a/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts b/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts index a9f59fce0da42f..ba58e90c612521 100644 --- a/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts +++ b/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts @@ -56,7 +56,7 @@ export function registerTopNFunctionsAPMTransactionsRoute({ const endSecs = timeTo / 1000; const transactionsPerService = await Promise.all( - serviceNames.map(async (serviceName) => { + serviceNames.slice(0, 5).map(async (serviceName) => { const apmFunctions = await profilingDataAccess.services.fetchESFunctions({ core, esClient, @@ -84,38 +84,24 @@ export function registerTopNFunctionsAPMTransactionsRoute({ const apmFunction = apmFunctions.TopN.find( (topNFunction) => topNFunction.Frame.FunctionName === functionName ); - const subGroups = apmFunction?.subGroups || {}; - return { - serviceName, - transactions: Object.keys(subGroups).map((key) => ({ - name: key, - samples: subGroups[key], - })), - }; + + if (apmFunction?.subGroups) { + const subGroups = apmFunction.subGroups; + return { + serviceName, + transactions: Object.keys(subGroups).map((key) => ({ + name: key, + samples: subGroups[key], + })), + }; + } }) ); const transactionsGroupedByService = keyBy(transactionsPerService, 'serviceName'); return response.ok({ - body: serviceNames.flatMap( - ( - serviceName - ): Array<{ - serviceName: string; - transactionName: string | null; - transactionSamples: number | null; - }> => { - const transactionsFromService = transactionsGroupedByService[serviceName]; - return !!transactionsFromService?.transactions.length - ? transactionsFromService.transactions.map((transaction) => ({ - serviceName, - transactionName: transaction.name, - transactionSamples: transaction.samples, - })) - : [{ serviceName, transactionName: null, transactionSamples: null }]; - } - ), + body: transactionsGroupedByService, }); } catch (error) { return handleRouteHandlerError({ From 6999697d106df661e0dd5d7d9715cf5b74932bbe Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Tue, 16 Apr 2024 11:45:13 +0100 Subject: [PATCH 13/20] paginating --- .../apm_transactions.tsx | 243 ++++++++++++------ 1 file changed, 161 insertions(+), 82 deletions(-) diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx index 6ca8b6aac763b4..ad96139557ee30 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx @@ -5,21 +5,24 @@ * 2.0. */ import { - CriteriaWithPagination, + Comparators, + Criteria, + EuiBasicTable, EuiBasicTableColumn, - EuiInMemoryTable, - EuiInMemoryTableProps, + EuiFieldSearch, + EuiFlexGroup, + EuiFlexItem, EuiLink, - EuiSearchBarProps, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { isEmpty } from 'lodash'; import React, { useMemo, useState } from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; import { NOT_AVAILABLE_LABEL } from '../../../common'; import { AsyncStatus } from '../../hooks/use_async'; import { useAnyOfProfilingParams } from '../../hooks/use_profiling_params'; import { useTimeRange } from '../../hooks/use_time_range'; import { useTimeRangeAsync } from '../../hooks/use_time_range_async'; -import type { APMTransactionsPerService } from '../../services'; import { asNumber } from '../../utils/formatters/as_number'; import { useProfilingDependencies } from '../contexts/profiling_dependencies/use_profiling_dependencies'; @@ -28,25 +31,6 @@ interface Props { functionName: string; } -const search: EuiSearchBarProps = { - box: { - incremental: true, - schema: true, - placeholder: i18n.translate('xpack.profiling.apmTransactions.searchPlaceholder', { - defaultMessage: 'Search services or transactions by name', - }), - }, -}; - -const sorting: EuiInMemoryTableProps['sorting'] = { - sort: { - field: 'serviceSamples', - direction: 'desc', - }, -}; - -const PAGE_SIZE = 5; - interface ServicesAndTransactions { serviceName: string; serviceSamples: number; @@ -54,6 +38,46 @@ interface ServicesAndTransactions { transactionSamples: number | null; } +const findServicesAndTransactions = ( + servicesAndTransactions: ServicesAndTransactions[], + pageIndex: number, + pageSize: number, + sortField: keyof ServicesAndTransactions, + sortDirection: 'asc' | 'desc', + filter: string +) => { + let filteredItems: ServicesAndTransactions[] = servicesAndTransactions; + if (!isEmpty(filter)) { + filteredItems = servicesAndTransactions.filter((item) => item.serviceName.includes(filter)); + } + + let sortedItems: ServicesAndTransactions[]; + if (sortField) { + sortedItems = filteredItems + .slice(0) + .sort(Comparators.property(sortField, Comparators.default(sortDirection))); + } else { + sortedItems = filteredItems; + } + + let pageOfItems; + + if (!pageIndex && !pageSize) { + pageOfItems = sortedItems; + } else { + const startIndex = pageIndex * pageSize; + pageOfItems = sortedItems.slice( + startIndex, + Math.min(startIndex + pageSize, filteredItems.length) + ); + } + + return { + pageOfItems, + totalItemCount: filteredItems.length, + }; +}; + export function APMTransactions({ functionName, serviceNames }: Props) { const { query: { rangeFrom, rangeTo }, @@ -65,54 +89,87 @@ export function APMTransactions({ functionName, serviceNames }: Props) { setup: { observabilityShared }, } = useProfilingDependencies(); - const [pagination, setPagination] = useState({ pageIndex: 0 }); - - function onTableChange({ page: { index } }: CriteriaWithPagination) { - setPagination({ pageIndex: index }); - } + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(5); + const [sortField, setSortField] = useState('serviceSamples'); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); + const [filter, setFilter] = useState(''); + const [filterDebounced, setFilterDebounced] = useState(''); - const { status, data: transactionsPerServiceMap = {} } = useTimeRangeAsync( - ({ http }) => { - const pageStart = pagination.pageIndex * PAGE_SIZE; - - return fetchTopNFunctionAPMTransactions({ - http, - timeFrom: new Date(timeRange.start).getTime(), - timeTo: new Date(timeRange.end).getTime(), - functionName, - serviceNames: Object.keys(serviceNames).slice(pageStart, pageStart + PAGE_SIZE), - }); + useDebounce( + () => { + setFilterDebounced(filter); }, - [ - fetchTopNFunctionAPMTransactions, - functionName, - pagination.pageIndex, - serviceNames, - timeRange.end, - timeRange.start, - ] + 500, + [filter] ); - const servicesAndTransactions: ServicesAndTransactions[] = useMemo(() => { - return Object.keys(serviceNames).flatMap((key) => { - const serviceTransactions = transactionsPerServiceMap[key]; - return serviceTransactions?.transactions?.length - ? serviceTransactions.transactions.map((transaction) => ({ - serviceName: key, - serviceSamples: serviceNames[key], - transactionName: transaction.name, - transactionSamples: transaction.samples, - })) - : [ - { - serviceName: key, - serviceSamples: serviceNames[key], - transactionName: null, - transactionSamples: null, - }, - ]; + const onTableChange = ({ page, sort }: Criteria) => { + if (page) { + const { index, size } = page; + setPageIndex(index); + setPageSize(size); + } + if (sort) { + const { field, direction } = sort; + setSortField(field); + setSortDirection(direction); + } + }; + + const initialServices: ServicesAndTransactions[] = useMemo(() => { + return Object.keys(serviceNames).map((key) => { + const samples = serviceNames[key]; + return { + serviceName: key, + serviceSamples: samples, + transactionName: null, + transactionSamples: null, + }; }); - }, [serviceNames, transactionsPerServiceMap]); + }, [serviceNames]); + + const { pageOfItems, totalItemCount } = useMemo( + () => + findServicesAndTransactions( + initialServices, + pageIndex, + pageSize, + sortField, + sortDirection, + filterDebounced + ), + [initialServices, pageIndex, pageSize, sortField, sortDirection, filterDebounced] + ); + + const { status, data: transactionsPerServiceMap = pageOfItems } = useTimeRangeAsync( + ({ http }) => { + const serviceNamesToSearch = pageOfItems.map((item) => item.serviceName).sort(); + if (serviceNamesToSearch.length) { + return fetchTopNFunctionAPMTransactions({ + http, + timeFrom: new Date(timeRange.start).getTime(), + timeTo: new Date(timeRange.end).getTime(), + functionName, + serviceNames: serviceNamesToSearch, + }).then((resp) => { + return pageOfItems.flatMap((item) => { + const transactionDetails = resp[item.serviceName]; + if (transactionDetails?.transactions?.length) { + return transactionDetails.transactions.map((transaction) => ({ + ...item, + transactionName: transaction.name, + transactionSamples: transaction.samples, + })); + } + return [item]; + }); + }); + } + return Promise.resolve(pageOfItems); + }, + [fetchTopNFunctionAPMTransactions, functionName, pageOfItems, timeRange.end, timeRange.start] + ); const isLoadingTransactions = status !== AsyncStatus.Settled; @@ -155,7 +212,6 @@ export function APMTransactions({ functionName, serviceNames }: Props) { defaultMessage: 'Transaction Name', }), truncateText: true, - sortable: true, render(_, { serviceName, transactionName }) { if (isLoadingTransactions) { return '--'; @@ -182,7 +238,6 @@ export function APMTransactions({ functionName, serviceNames }: Props) { name: i18n.translate('xpack.profiling.apmTransactions.columns.transactionName', { defaultMessage: 'Transaction Samples', }), - sortable: true, width: '150px', render(_, { transactionSamples }) { if (isLoadingTransactions) { @@ -204,18 +259,42 @@ export function APMTransactions({ functionName, serviceNames }: Props) { ); return ( - + + + setFilter(e.target.value)} + isClearable + fullWidth + placeholder={i18n.translate('xpack.profiling.apmTransactions.searchPlaceholder', { + defaultMessage: 'Search services by name', + })} + /> + + + + + ); } From 3605d35df8c55d29746d57562bd8123359822053 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Tue, 16 Apr 2024 11:52:14 +0100 Subject: [PATCH 14/20] fixing links --- .../app/transaction_details_link/index.tsx | 30 ++++++++++++++----- .../apm_transactions.tsx | 6 ++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details_link/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details_link/index.tsx index ee3876c9dab48f..5305eeeeccc020 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details_link/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/transaction_details_link/index.tsx @@ -15,16 +15,11 @@ import { getRedirectToTransactionDetailPageUrl } from '../trace_link/get_redirec import { useApmParams } from '../../../hooks/use_apm_params'; import { useTimeRange } from '../../../hooks/use_time_range'; -const _15_MIN_IN_MS = 15 * 60 * 1000; - export function TransactionDetailsByNameLink() { - const currentTime = new Date(); - const fallbackRangeFrom = new Date(currentTime.getTime() - _15_MIN_IN_MS); - const { query: { - rangeFrom = fallbackRangeFrom.toISOString(), - rangeTo = currentTime.toISOString(), + rangeFrom = 'now-15m', + rangeTo = 'now', transactionName, serviceName, }, @@ -61,7 +56,26 @@ export function TransactionDetailsByNameLink() { ); } - return null; + return ( +
+ + {i18n.translate( + 'xpack.apm.transactionDetailsLink.h2.transactionNotFound', + { defaultMessage: 'No transaction found' } + )} + + } + /> +
+ ); } return ( diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx index ad96139557ee30..5b9fb48b20b6dd 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx @@ -188,6 +188,8 @@ export function APMTransactions({ functionName, serviceNames }: Props) { data-test-subj="profilingColumnsLink" href={observabilityShared.locators.apm.serviceOverview.getRedirectUrl({ serviceName, + rangeFrom, + rangeTo, })} > {serviceName} @@ -224,6 +226,8 @@ export function APMTransactions({ functionName, serviceNames }: Props) { href={observabilityShared.locators.apm.transactionDetailsByName.getRedirectUrl({ serviceName, transactionName, + rangeFrom, + rangeTo, })} > {transactionName} @@ -255,6 +259,8 @@ export function APMTransactions({ functionName, serviceNames }: Props) { isLoadingTransactions, observabilityShared.locators.apm.serviceOverview, observabilityShared.locators.apm.transactionDetailsByName, + rangeFrom, + rangeTo, ] ); From 18f5dcfddca0bf96cbf0bdf4a38af744d7e0094f Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Tue, 16 Apr 2024 11:53:03 +0100 Subject: [PATCH 15/20] fixing i18n --- .../components/frame_information_window/apm_transactions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx index 5b9fb48b20b6dd..9d5986deca9b21 100644 --- a/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/components/frame_information_window/apm_transactions.tsx @@ -239,7 +239,7 @@ export function APMTransactions({ functionName, serviceNames }: Props) { }, { field: 'transactionSamples', - name: i18n.translate('xpack.profiling.apmTransactions.columns.transactionName', { + name: i18n.translate('xpack.profiling.apmTransactions.columns.transactionSamples', { defaultMessage: 'Transaction Samples', }), width: '150px', From a78c5ba0a66bec20074c3d231485b323ea5f1f2d Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Tue, 16 Apr 2024 13:35:54 +0100 Subject: [PATCH 16/20] fixing profiling route --- .../utils/create_profiling_es_client.ts | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/x-pack/plugins/observability_solution/profiling/server/utils/create_profiling_es_client.ts b/x-pack/plugins/observability_solution/profiling/server/utils/create_profiling_es_client.ts index 1a32b0c4a676e6..402314ba891e40 100644 --- a/x-pack/plugins/observability_solution/profiling/server/utils/create_profiling_es_client.ts +++ b/x-pack/plugins/observability_solution/profiling/server/utils/create_profiling_es_client.ts @@ -11,7 +11,9 @@ import type { KibanaRequest } from '@kbn/core/server'; import { unwrapEsResponse } from '@kbn/observability-plugin/server'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { + AggregationField, BaseFlameGraph, + ESTopNFunctions, ProfilingStatusResponse, StackTraceResponse, } from '@kbn/profiling-utils'; @@ -45,6 +47,21 @@ export interface ProfilingESClient { query: QueryDslQueryContainer; sampleSize: number; }): Promise; + topNFunctions(params: { + query: QueryDslQueryContainer; + limit?: number; + sampleSize?: number; + indices?: string[]; + stacktraceIdsField?: string; + aggregationField?: AggregationField; + co2PerKWH?: number; + datacenterPUE?: number; + pervCPUWattX86?: number; + pervCPUWattArm64?: number; + awsCostDiscountRate?: number; + azureCostDiscountRate?: number; + costPervCPUPerHour?: number; + }): Promise; } export function createProfilingEsClient({ @@ -151,5 +168,51 @@ export function createProfilingEsClient({ }); return unwrapEsResponse(promise) as Promise; }, + topNFunctions({ + query, + aggregationField, + indices, + stacktraceIdsField, + co2PerKWH, + datacenterPUE, + awsCostDiscountRate, + costPervCPUPerHour, + pervCPUWattArm64, + pervCPUWattX86, + azureCostDiscountRate, + sampleSize, + limit, + }) { + const controller = new AbortController(); + + const promise = withProfilingSpan('_profiling/topn/functions', () => { + return esClient.transport.request( + { + method: 'POST', + path: encodeURI('/_profiling/topn/functions'), + body: { + query, + sample_size: sampleSize, + limit, + indices, + stacktrace_ids_field: stacktraceIdsField, + aggregation_field: aggregationField, + co2_per_kwh: co2PerKWH, + per_core_watt_x86: pervCPUWattX86, + per_core_watt_arm64: pervCPUWattArm64, + datacenter_pue: datacenterPUE, + aws_cost_factor: awsCostDiscountRate, + cost_per_core_hour: costPervCPUPerHour, + azure_cost_factor: azureCostDiscountRate, + }, + }, + { + signal: controller.signal, + meta: true, + } + ); + }); + return unwrapEsResponse(promise) as Promise; + }, }; } From 8794dc10dbdb85f40f8bd94029e44197f5d81e7a Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Tue, 16 Apr 2024 14:27:41 +0100 Subject: [PATCH 17/20] adding apm access --- .../observability_solution/profiling/server/routes/apm.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts b/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts index ba58e90c612521..f77d121a699d24 100644 --- a/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts +++ b/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts @@ -34,7 +34,10 @@ export function registerTopNFunctionsAPMTransactionsRoute({ router.get( { path: paths.APMTransactions, - options: { tags: ['access:profiling'], timeout: { idleSocket: IDLE_SOCKET_TIMEOUT } }, + options: { + tags: ['access:profiling', 'access:apm'], + timeout: { idleSocket: IDLE_SOCKET_TIMEOUT }, + }, validate: { query: querySchema }, }, async (context, request, response) => { From 7db23f6956d1c95179725415e41906a2f311b6f3 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Tue, 16 Apr 2024 15:14:08 +0100 Subject: [PATCH 18/20] fixing ci --- .../observability_solution/profiling/server/routes/topn.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/observability_solution/profiling/server/routes/topn.test.ts b/x-pack/plugins/observability_solution/profiling/server/routes/topn.test.ts index 2985d8d9d78f70..b576c3b54d1445 100644 --- a/x-pack/plugins/observability_solution/profiling/server/routes/topn.test.ts +++ b/x-pack/plugins/observability_solution/profiling/server/routes/topn.test.ts @@ -69,6 +69,7 @@ describe('TopN data from Elasticsearch', () => { }, }) as Promise ), + topNFunctions: jest.fn(), }; const logger = loggerMock.create(); From dba2d6b7d55085bb0f5848a8a89aff0bf455f6ea Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Tue, 16 Apr 2024 16:27:48 +0100 Subject: [PATCH 19/20] e2e --- .../differential_functions.cy.ts | 40 +++++++-------- .../e2e/profiling_views/functions.cy.ts | 50 +++++++++---------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/differential_functions.cy.ts b/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/differential_functions.cy.ts index 187a9a09d49c7d..52b539286471bf 100644 --- a/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/differential_functions.cy.ts +++ b/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/differential_functions.cy.ts @@ -31,9 +31,9 @@ describe('Differential Functions page', () => { cy.wait('@getTopNFunctions'); [ { id: 'overallPerformance', value: '0%' }, - { id: 'annualizedCo2', value: '74.49 lbs / 33.79 kg' }, - { id: 'annualizedCost', value: '$318.32' }, - { id: 'totalNumberOfSamples', value: '513' }, + { id: 'annualizedCo2', value: '79.81 lbs / 36.2 kg' }, + { id: 'annualizedCost', value: '$341.05' }, + { id: 'totalNumberOfSamples', value: '17,186' }, ].forEach((item) => { cy.get(`[data-test-subj="${item.id}_value"]`).contains(item.value); cy.get(`[data-test-subj="${item.id}_comparison_value"]`).should('not.exist'); @@ -50,9 +50,9 @@ describe('Differential Functions page', () => { cy.wait('@getTopNFunctions'); [ { id: 'overallPerformance', value: '0%' }, - { id: 'annualizedCo2', value: '0 lbs / 0 kg', comparisonValue: '74.49 lbs / 33.79 kg' }, - { id: 'annualizedCost', value: '$0', comparisonValue: '$318.32' }, - { id: 'totalNumberOfSamples', value: '0', comparisonValue: '15,390' }, + { id: 'annualizedCo2', value: '0 lbs / 0 kg', comparisonValue: '79.81 lbs / 36.2 kg' }, + { id: 'annualizedCost', value: '$0', comparisonValue: '$341.05' }, + { id: 'totalNumberOfSamples', value: '0', comparisonValue: '515,580' }, ].forEach((item) => { cy.get(`[data-test-subj="${item.id}_value"]`).contains(item.value); if (item.comparisonValue) { @@ -73,23 +73,23 @@ describe('Differential Functions page', () => { cy.wait('@getTopNFunctions'); cy.wait('@getTopNFunctions'); [ - { id: 'overallPerformance', value: '65.89%', icon: 'sortUp_success' }, + { id: 'overallPerformance', value: '78.01%', icon: 'sortUp_success' }, { id: 'annualizedCo2', - value: '74.49 lbs / 33.79 kg', - comparisonValue: '25.41 lbs / 11.53 kg (65.89%)', + value: '9.81 lbs / 36.2 kg', + comparisonValue: '28.23 lbs / 12.81 kg (64.62%)', icon: 'comparison_sortUp_success', }, { id: 'annualizedCost', - value: '$318.32', - comparisonValue: '$108.59 (65.89%)', + value: '$341.05', + comparisonValue: '$120.65 (64.62%)', icon: 'comparison_sortUp_success', }, { id: 'totalNumberOfSamples', - value: '513', - comparisonValue: '175 (65.89%)', + value: '17,186', + comparisonValue: '3,780 (78.01%)', icon: 'comparison_sortUp_success', }, ].forEach((item) => { @@ -113,23 +113,23 @@ describe('Differential Functions page', () => { cy.wait('@getTopNFunctions'); cy.wait('@getTopNFunctions'); [ - { id: 'overallPerformance', value: '193.14%', icon: 'sortDown_danger' }, + { id: 'overallPerformance', value: '354.66%', icon: 'sortDown_danger' }, { id: 'annualizedCo2', - value: '25.41 lbs / 11.53 kg', - comparisonValue: '74.49 lbs / 33.79 kg (193.14%)', + value: '28.23 lbs / 12.81 kg', + comparisonValue: '79.81 lbs / 36.2 kg (182.67%)', icon: 'comparison_sortDown_danger', }, { id: 'annualizedCost', - value: '$108.59', - comparisonValue: '$318.32 (193.14%)', + value: '$120.65', + comparisonValue: '$341.05 (182.67%)', icon: 'comparison_sortDown_danger', }, { id: 'totalNumberOfSamples', - value: '175', - comparisonValue: '513 (193.14%)', + value: '3,780', + comparisonValue: '17,186 (354.66%)', icon: 'comparison_sortDown_danger', }, ].forEach((item) => { diff --git a/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts b/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts index 6daa414df36e70..bd2822d300240a 100644 --- a/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts +++ b/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts @@ -36,10 +36,10 @@ describe('Functions page', () => { const firstRowSelector = '[data-grid-row-index="0"] [data-test-subj="dataGridRowCell"]'; cy.get(firstRowSelector).eq(1).contains('1'); cy.get(firstRowSelector).eq(2).contains('vmlinux'); - cy.get(firstRowSelector).eq(3).contains('5.46%'); - cy.get(firstRowSelector).eq(4).contains('5.46%'); - cy.get(firstRowSelector).eq(5).contains('4.07 lbs / 1.84 kg'); - cy.get(firstRowSelector).eq(6).contains('$17.37'); + cy.get(firstRowSelector).eq(3).contains('0.16%'); + cy.get(firstRowSelector).eq(4).contains('0.16%'); + cy.get(firstRowSelector).eq(5).contains('4.41 lbs / 2 kg'); + cy.get(firstRowSelector).eq(6).contains('$18.61'); cy.get(firstRowSelector).eq(7).contains('28'); }); @@ -56,8 +56,8 @@ describe('Functions page', () => { { parentKey: 'informationRows', key: 'executable', value: 'vmlinux' }, { parentKey: 'informationRows', key: 'function', value: 'N/A' }, { parentKey: 'informationRows', key: 'sourceFile', value: 'N/A' }, - { parentKey: 'impactEstimates', key: 'totalCPU', value: '5.46%' }, - { parentKey: 'impactEstimates', key: 'selfCPU', value: '5.46%' }, + { parentKey: 'impactEstimates', key: 'totalCPU', value: '0.16%' }, + { parentKey: 'impactEstimates', key: 'selfCPU', value: '0.16%' }, { parentKey: 'impactEstimates', key: 'samples', value: '28' }, { parentKey: 'impactEstimates', key: 'selfSamples', value: '28' }, { parentKey: 'impactEstimates', key: 'coreSeconds', value: '1.4 seconds' }, @@ -66,16 +66,16 @@ describe('Functions page', () => { { parentKey: 'impactEstimates', key: 'annualizedSelfCoreSeconds', value: '17.03 days' }, { parentKey: 'impactEstimates', key: 'co2Emission', value: '~0.00 lbs / ~0.00 kg' }, { parentKey: 'impactEstimates', key: 'selfCo2Emission', value: '~0.00 lbs / ~0.00 kg' }, - { parentKey: 'impactEstimates', key: 'annualizedCo2Emission', value: '4.07 lbs / 1.84 kg' }, + { parentKey: 'impactEstimates', key: 'annualizedCo2Emission', value: '4.41 lbs / 2 kg' }, { parentKey: 'impactEstimates', key: 'annualizedSelfCo2Emission', - value: '4.07 lbs / 1.84 kg', + value: '4.41 lbs / 2 kg', }, { parentKey: 'impactEstimates', key: 'dollarCost', value: '$~0.00' }, { parentKey: 'impactEstimates', key: 'selfDollarCost', value: '$~0.00' }, - { parentKey: 'impactEstimates', key: 'annualizedDollarCost', value: '$17.37' }, - { parentKey: 'impactEstimates', key: 'annualizedSelfDollarCost', value: '$17.37' }, + { parentKey: 'impactEstimates', key: 'annualizedDollarCost', value: '$18.61' }, + { parentKey: 'impactEstimates', key: 'annualizedSelfDollarCost', value: '$18.61' }, ].forEach(({ parentKey, key, value }) => { cy.get(`[data-test-subj="${parentKey}_${key}"]`).contains(value); }); @@ -118,32 +118,32 @@ describe('Functions page', () => { columnIndex: 3, highRank: 1, lowRank: 389, - highValue: '5.46%', + highValue: '0.16%', lowValue: '0.00%', }, { columnKey: 'totalCPU', columnIndex: 4, - highRank: 3623, + highRank: 693, lowRank: 44, - highValue: '60.43%', - lowValue: '0.19%', + highValue: '1.80%', + lowValue: '0.01%', }, { columnKey: 'annualizedCo2', columnIndex: 5, - highRank: 1, + highRank: 693, lowRank: 44, - highValue: '45.01 lbs / 20.42 kg', - lowValue: '0.15 lbs / 0.07 kg', + highValue: '48.28 lbs / 21.9 kg', + lowValue: '0 lbs / 0 kg', }, { columnKey: 'annualizedDollarCost', columnIndex: 6, - highRank: 1, + highRank: 693, lowRank: 44, - highValue: '$192.36', - lowValue: '$0.62', + highValue: '$206.09', + lowValue: '$0.66', }, ].forEach(({ columnKey, columnIndex, highRank, highValue, lowRank, lowValue }) => { cy.get(`[data-test-subj="dataGridHeaderCell-${columnKey}"]`).click(); @@ -170,7 +170,7 @@ describe('Functions page', () => { cy.get('[data-test-subj="dataGridHeaderCell-frame"]').click(); cy.contains('Sort A-Z').click(); - cy.get(firstRowSelector).eq(1).contains('371'); + cy.get(firstRowSelector).eq(1).contains('88'); cy.get(firstRowSelector).eq(2).contains('/'); }); @@ -189,7 +189,7 @@ describe('Functions page', () => { const firstRowSelector = '[data-grid-row-index="0"] [data-test-subj="dataGridRowCell"]'; cy.get(firstRowSelector).eq(1).contains('1'); cy.get(firstRowSelector).eq(2).contains('vmlinux'); - cy.get(firstRowSelector).eq(5).contains('4.07 lbs / 1.84 kg'); + cy.get(firstRowSelector).eq(5).contains('4.41 lbs / 2 kg'); cy.contains('Settings').click(); cy.contains('Advanced Settings'); cy.get(`[data-test-subj="management-settings-editField-${profilingCo2PerKWH}"]`) @@ -208,7 +208,7 @@ describe('Functions page', () => { }); cy.go('back'); cy.wait('@getTopNFunctions'); - cy.get(firstRowSelector).eq(5).contains('1.87k lbs / 847.83 kg'); + cy.get(firstRowSelector).eq(5).contains('2k lbs / 908.4 kg'); const firstRowSelectorActionButton = '[data-grid-row-index="0"] [data-test-subj="dataGridRowCell"] .euiButtonIcon'; cy.get(firstRowSelectorActionButton).click(); @@ -218,12 +218,12 @@ describe('Functions page', () => { { parentKey: 'impactEstimates', key: 'annualizedCo2Emission', - value: '1.87k lbs / 847.83 kg', + value: '2k lbs / 908.4 kg', }, { parentKey: 'impactEstimates', key: 'annualizedSelfCo2Emission', - value: '1.87k lbs / 847.83 kg', + value: '2k lbs / 908.4 kg', }, ].forEach(({ parentKey, key, value }) => { cy.get(`[data-test-subj="${parentKey}_${key}"]`).contains(value); From 91ad3211c24d903c3d14ed005ba45bbbc0b2543d Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Tue, 16 Apr 2024 20:08:57 +0100 Subject: [PATCH 20/20] skipping --- .../e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts b/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts index b7d5680cc34775..5f9f69180e4499 100644 --- a/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts +++ b/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts @@ -89,8 +89,8 @@ describe('Storage explorer page', () => { cy.wait('@indicesDetails'); cy.get('table > tbody tr.euiTableRow').should('have.length', 10); }); - - it('displays a chart with percentage of each index', () => { + // Skipping it we should not rely on dom elements from third-level libraries to write our tests + it.skip('displays a chart with percentage of each index', () => { cy.intercept('GET', '/internal/profiling/storage_explorer/indices_storage_details?*').as( 'indicesDetails' ); @@ -108,6 +108,7 @@ describe('Storage explorer page', () => { ]; cy.get('.echChartPointerContainer table tbody tr').each(($row, idx) => { + // These are no longer valid elements on charts cy.wrap($row).find('th').contains(indices[idx].index); cy.wrap($row).find('td').contains(indices[idx].perc); });