diff --git a/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.test.ts b/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.test.ts index 54346abe3fe3d..d53e67640c3d4 100644 --- a/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.test.ts +++ b/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.test.ts @@ -157,7 +157,81 @@ describe('trackPerformanceMeasureEntries', () => { expect(analyticsClientMock.reportEvent).toHaveBeenCalledWith('performance_metric', { duration: 1000, eventName: 'kibana:plugin_render_time', - meta: { query_range_secs: 86400, query_offset_secs: 0 }, + meta: { + query_range_secs: 86400, + query_offset_secs: 0, + }, + }); + }); + + test('reports an analytics event with description metadata', () => { + setupMockPerformanceObserver([ + { + name: '/', + entryType: 'measure', + startTime: 100, + duration: 1000, + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + meta: { + isInitialLoad: false, + description: + '[ttfmp_dependencies] onPageReady is called when the most important content is rendered', + }, + }, + }, + ]); + trackPerformanceMeasureEntries(analyticsClientMock, true); + + expect(analyticsClientMock.reportEvent).toHaveBeenCalledTimes(1); + expect(analyticsClientMock.reportEvent).toHaveBeenCalledWith('performance_metric', { + duration: 1000, + eventName: 'kibana:plugin_render_time', + meta: { + is_initial_load: false, + query_range_secs: undefined, + query_offset_secs: undefined, + description: + '[ttfmp_dependencies] onPageReady is called when the most important content is rendered', + }, }); }); + + test('reports an analytics event with truncated description metadata', () => { + setupMockPerformanceObserver([ + { + name: '/', + entryType: 'measure', + startTime: 100, + duration: 1000, + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + meta: { + isInitialLoad: false, + description: + '[ttfmp_dependencies] This is a very long long long long long long long long description. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque non risus in nunc tincidunt tincidunt. Proin vehicula, nunc at feugiat cursus, justo nulla fermentum lorem, non ultricies metus libero nec purus. Sed ut perspiciatis unde omnis iste natus.', + }, + }, + }, + ]); + trackPerformanceMeasureEntries(analyticsClientMock, true); + const truncatedDescription = + '[ttfmp_dependencies] This is a very long long long long long long long long description. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque non risus in nunc tincidunt tincidunt. Proin vehicula, nunc at feugiat cursus, justo nulla fermentum l'; + + expect(analyticsClientMock.reportEvent).toHaveBeenCalledTimes(1); + expect(analyticsClientMock.reportEvent).toHaveBeenCalledWith('performance_metric', { + duration: 1000, + eventName: 'kibana:plugin_render_time', + meta: { + is_initial_load: false, + query_range_secs: undefined, + query_offset_secs: undefined, + description: truncatedDescription, + }, + }); + + expect(truncatedDescription.length).toBe(256); + }); }); diff --git a/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.ts b/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.ts index 42b0e93aa8789..7b92655cde055 100644 --- a/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.ts +++ b/src/core/packages/analytics/browser-internal/src/track_performance_measure_entries.ts @@ -11,6 +11,7 @@ import type { AnalyticsClient } from '@elastic/ebt/client'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; const MAX_CUSTOM_METRICS = 9; +const MAX_DESCRIPTION_LENGTH = 256; // The keys and values for the custom metrics are limited to 9 pairs const ALLOWED_CUSTOM_METRICS_KEYS_VALUES = Array.from({ length: MAX_CUSTOM_METRICS }, (_, i) => [ `key${i + 1}`, @@ -28,6 +29,8 @@ export function trackPerformanceMeasureEntries(analytics: AnalyticsClient, isDev const target = entry?.name; const duration = entry.duration; const meta = entry.detail?.meta; + const description = meta?.description; + const customMetrics = Object.keys(entry.detail?.customMetrics ?? {}).reduce( (acc, metric) => { if (ALLOWED_CUSTOM_METRICS_KEYS_VALUES.includes(metric)) { @@ -55,6 +58,13 @@ export function trackPerformanceMeasureEntries(analytics: AnalyticsClient, isDev ); } + if (description?.length > MAX_DESCRIPTION_LENGTH) { + // eslint-disable-next-line no-console + console.warn( + `The description for the measure: ${target} is too long. The maximum length is ${MAX_DESCRIPTION_LENGTH}. Strings longer than ${MAX_DESCRIPTION_LENGTH} will not be indexed or stored` + ); + } + // eslint-disable-next-line no-console console.log(`The measure ${target} completed in ${duration / 1000}s`); } @@ -74,6 +84,7 @@ export function trackPerformanceMeasureEntries(analytics: AnalyticsClient, isDev meta: { query_range_secs: meta?.queryRangeSecs, query_offset_secs: meta?.queryOffsetSecs, + description: description?.slice(0, MAX_DESCRIPTION_LENGTH), is_initial_load: meta?.isInitialLoad, }, }); diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts index ccef11f828c9d..d8b55e8a79c8e 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/measure_interaction/index.ts @@ -14,11 +14,13 @@ import { } from '@kbn/timerange'; import { EventData } from '../performance_context'; import { perfomanceMarkers } from '../../performance_markers'; +import { DescriptionWithPrefix } from '../types'; interface PerformanceMeta { queryRangeSecs: number; queryOffsetSecs: number; isInitialLoad?: boolean; + description?: DescriptionWithPrefix; } export function measureInteraction(pathname: string) { @@ -35,7 +37,7 @@ export function measureInteraction(pathname: string) { performance.mark(perfomanceMarkers.endPageReady); if (eventData?.meta) { - const { rangeFrom, rangeTo } = eventData.meta; + const { rangeFrom, rangeTo, description } = eventData.meta; // Convert the date range to epoch timestamps (in milliseconds) const dateRangesInEpoch = getDateRange({ @@ -47,6 +49,7 @@ export function measureInteraction(pathname: string) { queryRangeSecs: getTimeDifferenceInSeconds(dateRangesInEpoch), queryOffsetSecs: rangeTo === 'now' ? 0 : getOffsetFromNowInSeconds(dateRangesInEpoch.endDate), + description, }; } diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx index 70c2c69c4af98..3a652268eb60a 100644 --- a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx @@ -13,13 +13,15 @@ import { useLocation } from 'react-router-dom'; import { PerformanceApi, PerformanceContext } from './use_performance_context'; import { PerformanceMetricEvent } from '../../performance_metric_events'; import { measureInteraction } from './measure_interaction'; - +import { DescriptionWithPrefix } from './types'; export type CustomMetrics = Omit; export interface Meta { rangeFrom: string; rangeTo: string; + description?: DescriptionWithPrefix; } + export interface EventData { customMetrics?: CustomMetrics; meta?: Meta; diff --git a/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/types.ts b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/types.ts new file mode 100644 index 0000000000000..f427ede9bc5cf --- /dev/null +++ b/src/platform/packages/shared/kbn-ebt-tools/src/performance_metrics/context/types.ts @@ -0,0 +1,15 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +type ApmPageId = 'services' | 'traces' | 'dependencies'; +type InfraPageId = 'hosts'; + +export type Key = `${ApmPageId}` | `${InfraPageId}`; + +export type DescriptionWithPrefix = `[ttfmp_${Key}] ${string}`; diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/dependencies_inventory/dependencies_inventory_table/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/dependencies_inventory/dependencies_inventory_table/index.tsx index 1d007f67921e4..b25c7fbf8bd1a 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/dependencies_inventory/dependencies_inventory_table/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/dependencies_inventory/dependencies_inventory_table/index.tsx @@ -59,6 +59,8 @@ export function DependenciesInventoryTable() { meta: { rangeFrom, rangeTo, + description: + '[ttfmp_dependencies] Dependencies table is ready after fetching top_dependencies.', }, }); }