Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,9 @@ This will be indexed as:
"duration": 736, // Event duration as specified when reporting it
"meta": {
"target": '/home',
"query_range_secs": 900
"query_offset_secs": 0 // now
"query_range_secs": 900, // 15 minutes
"query_from_offset_secs": -900 // From 15 minutes ago
"query_to_offset_secs": 0 // To now
},
"context": { // Context holds information identifying the deployment, version, application and page that generated the event
"version": "8.16.0-SNAPSHOT",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ describe('trackPerformanceMeasureEntries', () => {
anyKey: 'anyKey',
anyValue: 'anyValue',
},
meta: {
isInitialLoad: true,
queryRangeSecs: 86400,
queryFromOffsetSecs: -86400,
queryToOffsetSecs: 0,
},
},
},
]);
Expand All @@ -124,7 +130,12 @@ describe('trackPerformanceMeasureEntries', () => {
duration: 1000,
eventName: 'kibana:plugin_render_time',
key1: 'key1',
meta: { target: '/' },
meta: {
is_initial_load: true,
query_range_secs: 86400,
query_from_offset_secs: -86400,
query_to_offset_secs: 0,
},
value1: 'value1',
});
});
Expand All @@ -141,7 +152,40 @@ describe('trackPerformanceMeasureEntries', () => {
type: 'kibana:performance',
meta: {
queryRangeSecs: 86400,
queryOffsetSecs: 0,
queryFromOffsetSecs: -86400,
queryToOffsetSecs: 0,
},
},
},
]);
trackPerformanceMeasureEntries(analyticsClientMock, true);

expect(analyticsClientMock.reportEvent).toHaveBeenCalledTimes(1);
expect(analyticsClientMock.reportEvent).toHaveBeenCalledWith('performance_metric', {
duration: 1000,
eventName: 'kibana:plugin_render_time',
meta: {
query_range_secs: 86400,
query_from_offset_secs: -86400,
query_to_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',
},
},
},
Expand All @@ -152,7 +196,52 @@ describe('trackPerformanceMeasureEntries', () => {
expect(analyticsClientMock.reportEvent).toHaveBeenCalledWith('performance_metric', {
duration: 1000,
eventName: 'kibana:plugin_render_time',
meta: { target: '/', query_range_secs: 86400, query_offset_secs: 0 },
meta: {
is_initial_load: false,
query_range_secs: undefined,
query_from_offset_secs: undefined,
query_to_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_from_offset_secs: undefined,
query_to_offset_secs: undefined,
description: truncatedDescription,
},
});

expect(truncatedDescription.length).toBe(256);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
Expand All @@ -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)) {
Expand Down Expand Up @@ -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`);
}
Expand All @@ -72,9 +82,11 @@ export function trackPerformanceMeasureEntries(analytics: AnalyticsClient, isDev
duration,
...customMetrics,
meta: {
target,
query_range_secs: meta?.queryRangeSecs,
query_offset_secs: meta?.queryOffsetSecs,
query_from_offset_secs: meta?.queryFromOffsetSecs,
query_to_offset_secs: meta?.queryToOffsetSecs,
description: description?.slice(0, MAX_DESCRIPTION_LENGTH),
is_initial_load: meta?.isInitialLoad,
},
});
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,32 @@ import {
getOffsetFromNowInSeconds,
getTimeDifferenceInSeconds,
} from '@kbn/timerange';
import { perfomanceMarkers } from '../../performance_markers';
import { EventData } from '../performance_context';
import { perfomanceMarkers } from '../../performance_markers';
import { DescriptionWithPrefix } from '../types';

interface PerformanceMeta {
queryRangeSecs: number;
queryOffsetSecs: number;
queryRangeSecs?: number;
queryFromOffsetSecs?: number;
queryToOffsetSecs?: number;
isInitialLoad?: boolean;
description?: DescriptionWithPrefix;
}

export function measureInteraction() {
export function measureInteraction(pathname: string) {
performance.mark(perfomanceMarkers.startPageChange);
const trackedRoutes: string[] = [];

return {
/**
* Marks the end of the page ready state and measures the performance between the start of the page change and the end of the page ready state.
* @param pathname - The pathname of the page.
* @param customMetrics - Custom metrics to be included in the performance measure.
*/
pageReady(pathname: string, eventData?: EventData) {
let performanceMeta: PerformanceMeta | undefined;
pageReady(eventData?: EventData) {
const performanceMeta: PerformanceMeta = {};
performance.mark(perfomanceMarkers.endPageReady);

if (eventData?.meta) {
if (eventData?.meta?.rangeFrom && eventData?.meta?.rangeTo) {
const { rangeFrom, rangeTo } = eventData.meta;

// Convert the date range to epoch timestamps (in milliseconds)
Expand All @@ -42,26 +46,59 @@ export function measureInteraction() {
to: rangeTo,
});

performanceMeta = {
queryRangeSecs: getTimeDifferenceInSeconds(dateRangesInEpoch),
queryOffsetSecs:
rangeTo === 'now' ? 0 : getOffsetFromNowInSeconds(dateRangesInEpoch.endDate),
};
performanceMeta.queryRangeSecs = getTimeDifferenceInSeconds(dateRangesInEpoch);
performanceMeta.queryFromOffsetSecs =
rangeFrom === 'now' ? 0 : getOffsetFromNowInSeconds(dateRangesInEpoch.startDate);
performanceMeta.queryToOffsetSecs =
rangeTo === 'now' ? 0 : getOffsetFromNowInSeconds(dateRangesInEpoch.endDate);
}

if (eventData?.meta?.description) {
performanceMeta.description = eventData.meta.description;
}

if (!trackedRoutes.includes(pathname)) {
performance.measure(pathname, {
if (
performance.getEntriesByName(perfomanceMarkers.startPageChange).length > 0 &&
performance.getEntriesByName(perfomanceMarkers.endPageReady).length > 0
) {
performance.measure(`[ttfmp:initial] - ${pathname}`, {
detail: {
eventName: 'kibana:plugin_render_time',
type: 'kibana:performance',
customMetrics: eventData?.customMetrics,
meta: performanceMeta,
meta: { ...performanceMeta, isInitialLoad: true },
},
start: perfomanceMarkers.startPageChange,
end: perfomanceMarkers.endPageReady,
});
trackedRoutes.push(pathname);

// Clean up the marks once the measure is done
performance.clearMarks(perfomanceMarkers.startPageChange);
performance.clearMarks(perfomanceMarkers.endPageReady);
}

if (
performance.getEntriesByName(perfomanceMarkers.startPageRefresh).length > 0 &&
performance.getEntriesByName(perfomanceMarkers.endPageReady).length > 0
) {
performance.measure(`[ttfmp:refresh] - ${pathname}`, {
detail: {
eventName: 'kibana:plugin_render_time',
type: 'kibana:performance',
customMetrics: eventData?.customMetrics,
meta: { ...performanceMeta, isInitialLoad: false },
},
start: perfomanceMarkers.startPageRefresh,
end: perfomanceMarkers.endPageReady,
});

// // Clean up the marks once the measure is done
performance.clearMarks(perfomanceMarkers.startPageRefresh);
performance.clearMarks(perfomanceMarkers.endPageReady);
}
},
pageRefreshStart() {
performance.mark(perfomanceMarkers.startPageRefresh);
},
};
}
Loading