From 75e4dc5caf8a59537a7b3062f64da876c28366cf Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Thu, 17 Aug 2023 12:01:03 +0200 Subject: [PATCH] [Lens] add performance journey to track rendering time for XY visualization and suggestions panel (#163412) ## Summary Related to #163089 Adding the first performance journey for the Lens Editor. It simulated loading existing Lens visualisation with data view having 10k fields. We collect the following metrics: - `fetchFieldsExistenceInfo` reports time it takes to fetch fields in Data Panel - `lensVisualizationRenderTime` reports both time it takes to fetch the data (`time_to_data`) and render the main visualization (`time_to_render`) - `lensSuggestionsRenderTime` reports time it takes to render suggestions panel Metrics consistency image Run locally with ``` node scripts/functional_tests --config x-pack/performance/journeys/many_fields_lens_editor.ts ``` Metrics will be available here https://telemetry-v2-staging.elastic.dev/s/kibana-performance/app/dashboards#/view/dd0473ac-826f-5621-9a10-25319700326e?_g=h@61c5ac8 --------- Co-authored-by: Drew Tate (cherry picked from commit 15b118c724d174d1482ae9a31f9e87dccfe2a66c) --- .buildkite/ftr_configs.yml | 1 + .../src/hooks/use_existing_fields.ts | 16 +- packages/kbn-unified-field-list/tsconfig.json | 1 + .../journeys/many_fields_lens_editor.ts | 28 +++ .../kbn_archives/lens_many_fields.json | 209 ++++++++++++++++++ .../editor_frame/editor_frame.tsx | 1 + .../editor_frame/suggestion_panel.test.tsx | 3 + .../editor_frame/suggestion_panel.tsx | 27 ++- .../workspace_panel/workspace_panel.tsx | 27 +-- x-pack/plugins/lens/tsconfig.json | 1 + 10 files changed, 287 insertions(+), 27 deletions(-) create mode 100644 x-pack/performance/journeys/many_fields_lens_editor.ts create mode 100644 x-pack/performance/kbn_archives/lens_many_fields.json diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index ca6914bcf2ac..b388545ce0e3 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -405,6 +405,7 @@ enabled: - x-pack/performance/journeys/flight_dashboard.ts - x-pack/performance/journeys/login.ts - x-pack/performance/journeys/many_fields_discover.ts + - x-pack/performance/journeys/many_fields_lens_editor.ts - x-pack/performance/journeys/many_fields_transform.ts - x-pack/performance/journeys/promotion_tracking_dashboard.ts - x-pack/performance/journeys/web_logs_dashboard.ts diff --git a/packages/kbn-unified-field-list/src/hooks/use_existing_fields.ts b/packages/kbn-unified-field-list/src/hooks/use_existing_fields.ts index c0cc6e524f03..d099a6f2b85e 100644 --- a/packages/kbn-unified-field-list/src/hooks/use_existing_fields.ts +++ b/packages/kbn-unified-field-list/src/hooks/use_existing_fields.ts @@ -14,6 +14,7 @@ import type { AggregateQuery, EsQueryConfig, Filter, Query } from '@kbn/es-query import { type DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/common'; import { getEsQueryConfig } from '@kbn/data-service/src/es_query'; +import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { loadFieldExisting } from '../services/field_existing'; import { ExistenceFetchStatus } from '../types'; @@ -35,7 +36,7 @@ export interface ExistingFieldsFetcherParams { query: Query | AggregateQuery | undefined; filters: Filter[] | undefined; services: { - core: Pick; + core: Pick; data: DataPublicPluginStart; dataViews: DataViewsContract; }; @@ -178,6 +179,8 @@ export const useExistingFieldsFetcher = ( const dataViewsHash = getDataViewsHash(params.dataViews); const refetchFieldsExistenceInfo = useCallback( async (dataViewId?: string) => { + const startTime = window.performance.now(); + const metricEventName = 'fetchFieldsExistenceInfo'; const fetchId = generateId(); lastFetchId = fetchId; @@ -192,6 +195,11 @@ export const useExistingFieldsFetcher = ( ...options, dataViewId, }); + reportPerformanceMetricEvent(params.services.core.analytics, { + eventName: metricEventName, + duration: window.performance.now() - startTime, + meta: { dataViewsCount: 1 }, + }); return; } // refetch for all mentioned data views @@ -203,6 +211,11 @@ export const useExistingFieldsFetcher = ( }) ) ); + reportPerformanceMetricEvent(params.services.core.analytics, { + eventName: metricEventName, + duration: window.performance.now() - startTime, + meta: { dataViewsCount: params.dataViews.length }, + }); }, // eslint-disable-next-line react-hooks/exhaustive-deps [ @@ -212,6 +225,7 @@ export const useExistingFieldsFetcher = ( params.filters, params.fromDate, params.toDate, + params.services.core, ] ); diff --git a/packages/kbn-unified-field-list/tsconfig.json b/packages/kbn-unified-field-list/tsconfig.json index f944471d56a1..78ea71ca4434 100644 --- a/packages/kbn-unified-field-list/tsconfig.json +++ b/packages/kbn-unified-field-list/tsconfig.json @@ -28,6 +28,7 @@ "@kbn/dom-drag-drop", "@kbn/shared-ux-utility", "@kbn/discover-utils", + "@kbn/ebt-tools", ], "exclude": ["target/**/*"] } diff --git a/x-pack/performance/journeys/many_fields_lens_editor.ts b/x-pack/performance/journeys/many_fields_lens_editor.ts new file mode 100644 index 000000000000..18af2111115e --- /dev/null +++ b/x-pack/performance/journeys/many_fields_lens_editor.ts @@ -0,0 +1,28 @@ +/* + * 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 { Journey } from '@kbn/journeys'; +import { subj } from '@kbn/test-subj-selector'; + +export const journey = new Journey({ + kbnArchives: ['x-pack/performance/kbn_archives/lens_many_fields'], + esArchives: ['test/functional/fixtures/es_archiver/stress_test'], +}) + .step('Go to Visualize Library landing page', async ({ page, kbnUrl }) => { + await page.goto( + kbnUrl.get( + `/app/visualize#/?_g=(filters:!(),time:(from:'2022-09-07T10:53:30.262Z',to:'2022-09-07T10:55:09.280Z'))` + ) + ); + await page.waitForSelector(subj('table-is-ready')); + // wait extra 5 seconds: we're not sure why, but the extra sleep before loading the editor makes the metrics more consistent + await page.waitForTimeout(5000); + }) + .step('Open existing Lens visualization', async ({ page, kibanaPage }) => { + await page.click(subj('visListingTitleLink-Lens-Stress-Test')); + await kibanaPage.waitForCharts(6); + }); diff --git a/x-pack/performance/kbn_archives/lens_many_fields.json b/x-pack/performance/kbn_archives/lens_many_fields.json new file mode 100644 index 000000000000..cb95d25787d3 --- /dev/null +++ b/x-pack/performance/kbn_archives/lens_many_fields.json @@ -0,0 +1,209 @@ +{ + "attributes":{ + "fieldAttrs":"{}", + "fields":"[]", + "runtimeFieldMap":"{}", + "title":"stresstest", + "typeMeta":"{}" + }, + "coreMigrationVersion":"8.8.0", + "created_at":"2023-07-27T16:21:31.312Z", + "id":"6244bd06-f711-44db-b756-bc25e4a32a60", + "managed":false, + "references":[], + "type":"index-pattern", + "typeMigrationVersion":"8.0.0", + "updated_at":"2023-07-27T16:21:31.312Z", + "version":"WzIxLDFd" +} + +{ + "attributes":{ + "description":"", + "state":{ + "adHocDataViews":{}, + "datasourceStates":{ + "formBased":{ + "layers":{ + "591b8a11-31f9-41e5-87ba-f0b6f5face08":{ + "columnOrder":[ + "fed7a521-30ac-4696-bf2d-85a26ba6db58", + "5bcdbfd2-c4c0-4b27-891e-d848ea468cbe", + "532742da-8bea-4357-a5e1-9319111a61e7", + "4707670e-1c99-4e86-a09c-7801490131a1", + "e822b0fa-1c95-44fa-8a5a-06c339f841ca", + "881198db-8a3d-497b-b662-d7f5835775af", + "fc484025-c6c3-47e5-8a2d-428d6d6e3dbb" + ], + "columns":{ + "4707670e-1c99-4e86-a09c-7801490131a1":{ + "dataType":"number", + "isBucketed":false, + "label":"Moving average of Unique count of field1", + "operationType":"moving_average", + "params":{ + "window":5 + }, + "references":[ + "e822b0fa-1c95-44fa-8a5a-06c339f841ca" + ], + "scale":"ratio" + }, + "532742da-8bea-4357-a5e1-9319111a61e7":{ + "dataType":"number", + "isBucketed":false, + "label":"Count of records", + "operationType":"count", + "params":{ + "emptyAsNull":true + }, + "scale":"ratio", + "sourceField":"___records___" + }, + "5bcdbfd2-c4c0-4b27-891e-d848ea468cbe":{ + "dataType":"date", + "isBucketed":true, + "label":"@timestamp", + "operationType":"date_histogram", + "params":{ + "dropPartials":false, + "includeEmptyRows":true, + "interval":"ms" + }, + "scale":"interval", + "sourceField":"@timestamp" + }, + "881198db-8a3d-497b-b662-d7f5835775af":{ + "dataType":"number", + "isBucketed":false, + "label":"Cumulative sum of Records", + "operationType":"cumulative_sum", + "references":[ + "fc484025-c6c3-47e5-8a2d-428d6d6e3dbb" + ], + "scale":"ratio" + }, + "e822b0fa-1c95-44fa-8a5a-06c339f841ca":{ + "dataType":"number", + "isBucketed":false, + "label":"Unique count of field1", + "operationType":"unique_count", + "params":{ + "emptyAsNull":true + }, + "scale":"ratio", + "sourceField":"field1" + }, + "fc484025-c6c3-47e5-8a2d-428d6d6e3dbb":{ + "dataType":"number", + "isBucketed":false, + "label":"Count of records", + "operationType":"count", + "params":{ + "emptyAsNull":true + }, + "scale":"ratio", + "sourceField":"___records___" + }, + "fed7a521-30ac-4696-bf2d-85a26ba6db58":{ + "dataType":"string", + "isBucketed":true, + "label":"Top 1000 values of field0", + "operationType":"terms", + "params":{ + "missingBucket":false, + "orderBy":{ + "columnId":"532742da-8bea-4357-a5e1-9319111a61e7", + "type":"column" + }, + "orderDirection":"desc", + "otherBucket":false, + "parentFormat":{ + "id":"terms" + }, + "size":1000 + }, + "scale":"ordinal", + "sourceField":"field0" + } + }, + "incompleteColumns":{} + } + } + } + }, + "filters":[], + "internalReferences":[], + "query":{ + "language":"kuery", + "query":"" + }, + "visualization":{ + "axisTitlesVisibilitySettings":{ + "x":true, + "yLeft":true, + "yRight":true + }, + "fittingFunction":"None", + "gridlinesVisibilitySettings":{ + "x":true, + "yLeft":true, + "yRight":true + }, + "labelsOrientation":{ + "x":0, + "yLeft":0, + "yRight":0 + }, + "layers":[ + { + "accessors":[ + "532742da-8bea-4357-a5e1-9319111a61e7", + "4707670e-1c99-4e86-a09c-7801490131a1", + "881198db-8a3d-497b-b662-d7f5835775af" + ], + "layerId":"591b8a11-31f9-41e5-87ba-f0b6f5face08", + "layerType":"data", + "position":"top", + "seriesType":"bar_stacked", + "showGridlines":false, + "splitAccessor":"fed7a521-30ac-4696-bf2d-85a26ba6db58", + "xAccessor":"5bcdbfd2-c4c0-4b27-891e-d848ea468cbe" + } + ], + "legend":{ + "isVisible":true, + "legendSize":"auto", + "position":"right" + }, + "preferredSeriesType":"bar_stacked", + "tickLabelsVisibilitySettings":{ + "x":true, + "yLeft":true, + "yRight":true + }, + "valueLabels":"hide" + } + }, + "title":"Lens Stress Test", + "visualizationType":"lnsXY" + }, + "coreMigrationVersion":"8.8.0", + "created_at":"2023-07-27T16:58:15.808Z", + "id":"c7787800-2c9e-11ee-b1db-6df582ca24e0", + "managed":false, + "references":[ + { + "id":"6244bd06-f711-44db-b756-bc25e4a32a60", + "name":"indexpattern-datasource-layer-591b8a11-31f9-41e5-87ba-f0b6f5face08", + "type":"index-pattern" + } + ], + "timeFrom": "2022-09-07T12:53:30.262Z", + "timeTo": "2022-09-07T12:55:09.280Z", + "timeRestore": true, + "type":"lens", + "typeMigrationVersion":"8.9.0", + "updated_at":"2023-07-27T16:58:15.808Z", + "version":"WzYyLDFd" +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 24d0d3bba780..d68dbbf943b7 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -185,6 +185,7 @@ export function EditorFrame(props: EditorFrameProps) { frame={framePublicAPI} getUserMessages={props.getUserMessages} nowProvider={props.plugins.data.nowProvider} + core={props.core} /> ) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx index 7f21e2b149ba..09e5c6406e79 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.test.tsx @@ -21,6 +21,8 @@ import { getSuggestions } from './suggestion_helpers'; import { EuiIcon, EuiPanel, EuiToolTip, EuiAccordion } from '@elastic/eui'; import { IconChartDatatable } from '@kbn/chart-icons'; import { mountWithProvider } from '../../mocks'; +import { coreMock } from '@kbn/core/public/mocks'; + import { applyChanges, LensAppState, @@ -106,6 +108,7 @@ describe('suggestion_panel', () => { frame: createMockFramePublicAPI(), getUserMessages: () => [], nowProvider: { get: jest.fn(() => new Date()) }, + core: coreMock.createStart(), }; }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 11f50252b993..6194d5ff33bc 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -30,6 +30,8 @@ import { ReactExpressionRendererProps, ReactExpressionRendererType, } from '@kbn/expressions-plugin/public'; +import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; +import { CoreStart } from '@kbn/core/public'; import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../../utils'; import { Datasource, @@ -100,6 +102,7 @@ export interface SuggestionPanelProps { frame: FramePublicAPI; getUserMessages: UserMessagesGetter; nowProvider: DataPublicPluginStart['nowProvider']; + core: CoreStart; } const PreviewRenderer = ({ @@ -226,6 +229,7 @@ export function SuggestionPanel({ ExpressionRenderer: ExpressionRendererComponent, getUserMessages, nowProvider, + core, }: SuggestionPanelProps) { const dispatchLens = useLensDispatch(); const activeDatasourceId = useLensSelector(selectActiveDatasourceId); @@ -368,16 +372,19 @@ export function SuggestionPanel({ const suggestionsRendered = useRef([]); const totalSuggestions = suggestions.length + 1; - const onSuggestionRender = useCallback((suggestionIndex: number) => { - suggestionsRendered.current[suggestionIndex] = true; - if (initialRenderComplete.current === false && suggestionsRendered.current.every(Boolean)) { - initialRenderComplete.current = true; - // console.log( - // 'time to fetch data and perform initial render for all suggestions', - // performance.now() - startTime.current - // ); - } - }, []); + const onSuggestionRender = useCallback( + (suggestionIndex: number) => { + suggestionsRendered.current[suggestionIndex] = true; + if (initialRenderComplete.current === false && suggestionsRendered.current.every(Boolean)) { + initialRenderComplete.current = true; + reportPerformanceMetricEvent(core.analytics, { + eventName: 'lensSuggestionsRenderTime', + duration: performance.now() - startTime.current, + }); + } + }, + [core.analytics] + ); const rollbackToCurrentVisualization = useCallback(() => { if (lastSelectedSuggestion !== -1) { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 02db9e18919f..4e66763b54a9 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -35,6 +35,7 @@ import type { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common'; import type { Datatable } from '@kbn/expressions-plugin/public'; import { DropIllustration } from '@kbn/chart-icons'; import { DragDrop, useDragDropContext, DragDropIdentifier } from '@kbn/dom-drag-drop'; +import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { trackUiCounterEvents } from '../../../lens_ui_telemetry'; import { getSearchWarningMessages } from '../../../utils'; import { @@ -138,10 +139,6 @@ export const WorkspacePanel = React.memo(function WorkspacePanel(props: Workspac ); }); -const log = (...messages: Array) => { - // console.log(...messages); -}; - // Exported for testing purposes only. export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ framePublicAPI, @@ -208,10 +205,15 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ if (!initialVisualizationRenderComplete.current) { initialVisualizationRenderComplete.current = true; // NOTE: this metric is only reported for an initial editor load of a pre-existing visualization - log( - 'initial visualization took to render after data received', - performance.now() - dataReceivedTime.current - ); + const currentTime = performance.now(); + reportPerformanceMetricEvent(core.analytics, { + eventName: 'lensVisualizationRenderTime', + duration: currentTime - visualizationRenderStartTime.current, + key1: 'time_to_data', + value1: dataReceivedTime.current - visualizationRenderStartTime.current, + key2: 'time_to_render', + value2: currentTime - dataReceivedTime.current, + }); } const datasourceEvents = Object.values(renderDeps.current.datasourceMap).reduce( (acc, datasource) => { @@ -243,7 +245,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ trackUiCounterEvents(events); } - }, []); + }, [core.analytics]); const removeSearchWarningMessagesRef = useRef<() => void>(); const removeExpressionBuildErrorsRef = useRef<() => void>(); @@ -252,13 +254,6 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ (_data: unknown, adapters?: Partial) => { if (renderDeps.current) { dataReceivedTime.current = performance.now(); - if (!initialVisualizationRenderComplete.current) { - // NOTE: this metric is only reported for an initial editor load of a pre-existing visualization - log( - 'initial data took to arrive', - dataReceivedTime.current - visualizationRenderStartTime.current - ); - } const [defaultLayerId] = Object.keys(renderDeps.current.datasourceLayers); const datasource = Object.values(renderDeps.current.datasourceMap)[0]; diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index 1a197f781a6a..484734f1ae38 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -85,6 +85,7 @@ "@kbn/event-annotation-components", "@kbn/content-management-utils", "@kbn/serverless", + "@kbn/ebt-tools", ], "exclude": [ "target/**/*",