From faab91c1067a52617f727cd92264db13b447ee14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 28 Jun 2023 09:40:55 +0100 Subject: [PATCH] [Profiling] link functions to flamegraph (#160548) https://github.com/elastic/kibana/assets/55978943/e213e261-0d26-44e0-8786-407f29d6fa55 --- .../public/components/flamegraph/index.tsx | 9 +- .../public/components/stack_frame_summary.tsx | 26 --- .../components/stack_frame_summary/index.tsx | 51 +++++ .../components/stacked_bar_chart/index.tsx | 5 +- .../components/topn_functions/index.tsx | 6 +- .../profiling/public/routing/index.tsx | 21 +- .../utils/get_flamegraph_model/index.ts | 4 +- .../public/views/flame_graphs_view/index.tsx | 182 ------------------ ...differential_flame_graph_search_panel.tsx} | 51 +++-- .../differential_flamegraphs/index.tsx | 144 ++++++++++++++ .../views/flamegraphs/flamegraph/index.tsx | 81 ++++++++ .../public/views/flamegraphs/index.tsx | 57 ++++++ .../functions/differential_topn/index.tsx | 9 + .../public/views/functions/topn/index.tsx | 8 + 14 files changed, 405 insertions(+), 249 deletions(-) delete mode 100644 x-pack/plugins/profiling/public/components/stack_frame_summary.tsx create mode 100644 x-pack/plugins/profiling/public/components/stack_frame_summary/index.tsx delete mode 100644 x-pack/plugins/profiling/public/views/flame_graphs_view/index.tsx rename x-pack/plugins/profiling/public/views/{flame_graphs_view/flame_graph_search_panel.tsx => flamegraphs/differential_flamegraphs/differential_flame_graph_search_panel.tsx} (59%) create mode 100644 x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/index.tsx create mode 100644 x-pack/plugins/profiling/public/views/flamegraphs/flamegraph/index.tsx create mode 100644 x-pack/plugins/profiling/public/views/flamegraphs/index.tsx diff --git a/x-pack/plugins/profiling/public/components/flamegraph/index.tsx b/x-pack/plugins/profiling/public/components/flamegraph/index.tsx index 5fc9fd4f997a1..4d143f77b35d0 100644 --- a/x-pack/plugins/profiling/public/components/flamegraph/index.tsx +++ b/x-pack/plugins/profiling/public/components/flamegraph/index.tsx @@ -13,6 +13,7 @@ import { PartialTheme, Settings, Tooltip, + FlameSpec, } from '@elastic/charts'; import { EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; import { Maybe } from '@kbn/observability-plugin/common/typings'; @@ -27,13 +28,15 @@ import { ComparisonMode } from '../normalization_menu'; interface Props { id: string; - comparisonMode: ComparisonMode; + comparisonMode?: ComparisonMode; primaryFlamegraph?: ElasticFlameGraph; comparisonFlamegraph?: ElasticFlameGraph; baseline?: number; comparison?: number; showInformationWindow: boolean; toggleShowInformationWindow: () => void; + searchText?: string; + onChangeSearchText?: FlameSpec['onSearchTextChange']; } export function FlameGraph({ @@ -45,6 +48,8 @@ export function FlameGraph({ comparison, showInformationWindow, toggleShowInformationWindow, + searchText, + onChangeSearchText, }: Props) { const theme = useEuiTheme(); @@ -165,6 +170,8 @@ export function FlameGraph({ valueFormatter={(value) => `${value}`} animation={{ duration: 100 }} controlProviderCallback={{}} + search={searchText ? { text: searchText } : undefined} + onSearchTextChange={onChangeSearchText} /> diff --git a/x-pack/plugins/profiling/public/components/stack_frame_summary.tsx b/x-pack/plugins/profiling/public/components/stack_frame_summary.tsx deleted file mode 100644 index 9f25b4d9d8900..0000000000000 --- a/x-pack/plugins/profiling/public/components/stack_frame_summary.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import React from 'react'; -import { getCalleeFunction, getCalleeSource, StackFrameMetadata } from '../../common/profiling'; - -export function StackFrameSummary({ frame }: { frame: StackFrameMetadata }) { - return ( - - -
- - {getCalleeFunction(frame)} - -
-
- - {getCalleeSource(frame) || '‎'} - -
- ); -} diff --git a/x-pack/plugins/profiling/public/components/stack_frame_summary/index.tsx b/x-pack/plugins/profiling/public/components/stack_frame_summary/index.tsx new file mode 100644 index 0000000000000..a92934df79009 --- /dev/null +++ b/x-pack/plugins/profiling/public/components/stack_frame_summary/index.tsx @@ -0,0 +1,51 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText } from '@elastic/eui'; +import React from 'react'; +import { getCalleeFunction, getCalleeSource, StackFrameMetadata } from '../../../common/profiling'; + +interface Props { + frame: StackFrameMetadata; + onFrameClick?: (functionName: string) => void; +} + +function CalleeFunctionText({ calleeFunctionName }: { calleeFunctionName: string }) { + return ( + + {calleeFunctionName} + + ); +} + +export function StackFrameSummary({ frame, onFrameClick }: Props) { + const calleeFunctionName = getCalleeFunction(frame); + + function handleOnClick() { + if (onFrameClick) { + onFrameClick(calleeFunctionName); + } + } + + return ( + + +
+ {onFrameClick ? ( + + + + ) : ( + + )} +
+
+ + {getCalleeSource(frame) || '‎'} + +
+ ); +} diff --git a/x-pack/plugins/profiling/public/components/stacked_bar_chart/index.tsx b/x-pack/plugins/profiling/public/components/stacked_bar_chart/index.tsx index 22cb6e19e701c..a8031583535f0 100644 --- a/x-pack/plugins/profiling/public/components/stacked_bar_chart/index.tsx +++ b/x-pack/plugins/profiling/public/components/stacked_bar_chart/index.tsx @@ -17,7 +17,6 @@ import { Tooltip, XYChartElementEvent, TooltipContainer, - CustomTooltip, } from '@elastic/charts'; import { EuiPanel } from '@elastic/eui'; import { keyBy } from 'lodash'; @@ -57,7 +56,7 @@ export function StackedBarChart({ const { chartsBaseTheme, chartsTheme } = useProfilingChartsTheme(); - const CustomTooltipWithSubChart: CustomTooltip = () => { + function CustomTooltipWithSubChart() { if (!highlightedSample) { return null; } @@ -90,7 +89,7 @@ export function StackedBarChart({ ); - }; + } return ( diff --git a/x-pack/plugins/profiling/public/components/topn_functions/index.tsx b/x-pack/plugins/profiling/public/components/topn_functions/index.tsx index daf9d213e8e0c..c43ecd2ac0cec 100644 --- a/x-pack/plugins/profiling/public/components/topn_functions/index.tsx +++ b/x-pack/plugins/profiling/public/components/topn_functions/index.tsx @@ -146,6 +146,7 @@ interface Props { isDifferentialView: boolean; baselineScaleFactor?: number; comparisonScaleFactor?: number; + onFrameClick?: (functionName: string) => void; } function scaleValue({ value, scaleFactor = 1 }: { value: number; scaleFactor?: number }) { @@ -162,6 +163,7 @@ export function TopNFunctionsTable({ isDifferentialView, baselineScaleFactor, comparisonScaleFactor, + onFrameClick, }: Props) { const [selectedRow, setSelectedRow] = useState(); const isEstimatedA = (topNFunctions?.SamplingRate ?? 1.0) !== 1.0; @@ -260,7 +262,9 @@ export function TopNFunctionsTable({ name: i18n.translate('xpack.profiling.functionsView.functionColumnLabel', { defaultMessage: 'Function', }), - render: (_, { frame }) => , + render: (_, { frame }) => { + return ; + }, width: '50%', }, { diff --git a/x-pack/plugins/profiling/public/routing/index.tsx b/x-pack/plugins/profiling/public/routing/index.tsx index d674a99aa530c..4c2861cf5d220 100644 --- a/x-pack/plugins/profiling/public/routing/index.tsx +++ b/x-pack/plugins/profiling/public/routing/index.tsx @@ -13,7 +13,9 @@ import { TopNFunctionSortField, topNFunctionSortFieldRt } from '../../common/fun import { StackTracesDisplayOption, TopNType } from '../../common/stack_traces'; import { ComparisonMode, NormalizationMode } from '../components/normalization_menu'; import { RedirectTo } from '../components/redirect_to'; -import { FlameGraphsView } from '../views/flame_graphs_view'; +import { FlameGraphsView } from '../views/flamegraphs'; +import { DifferentialFlameGraphsView } from '../views/flamegraphs/differential_flamegraphs'; +import { FlameGraphView } from '../views/flamegraphs/flamegraph'; import { FunctionsView } from '../views/functions'; import { DifferentialTopNFunctionsView } from '../views/functions/differential_topn'; import { TopNFunctionsView } from '../views/functions/topn'; @@ -109,9 +111,14 @@ const routes = { })} href="/flamegraphs/flamegraph" > - + ), + params: t.type({ + query: t.partial({ + searchText: t.string, + }), + }), }, '/flamegraphs/differential': { element: ( @@ -121,7 +128,7 @@ const routes = { })} href="/flamegraphs/differential" > - + ), params: t.type({ @@ -134,19 +141,23 @@ const routes = { t.literal(ComparisonMode.Absolute), t.literal(ComparisonMode.Relative), ]), - }), - t.partial({ normalizationMode: t.union([ t.literal(NormalizationMode.Scale), t.literal(NormalizationMode.Time), ]), + }), + t.partial({ baseline: toNumberRt, comparison: toNumberRt, + searchText: t.string, }), ]), }), defaults: { query: { + comparisonRangeFrom: 'now-15m', + comparisonRangeTo: 'now', + comparisonKuery: '', comparisonMode: ComparisonMode.Absolute, normalizationMode: NormalizationMode.Time, }, diff --git a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts index 08c08e2eb6014..9e0ec087523ad 100644 --- a/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts +++ b/x-pack/plugins/profiling/public/utils/get_flamegraph_model/index.ts @@ -31,7 +31,7 @@ export function getFlamegraphModel({ colorSuccess, colorDanger, colorNeutral, - comparisonMode, + comparisonMode = ComparisonMode.Absolute, comparison, baseline, }: { @@ -40,7 +40,7 @@ export function getFlamegraphModel({ colorSuccess: string; colorDanger: string; colorNeutral: string; - comparisonMode: ComparisonMode; + comparisonMode?: ComparisonMode; baseline?: number; comparison?: number; }): { diff --git a/x-pack/plugins/profiling/public/views/flame_graphs_view/index.tsx b/x-pack/plugins/profiling/public/views/flame_graphs_view/index.tsx deleted file mode 100644 index b15936141f4ef..0000000000000 --- a/x-pack/plugins/profiling/public/views/flame_graphs_view/index.tsx +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { EuiFlexGroup, EuiFlexItem, EuiPageHeaderContentProps } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { get } from 'lodash'; -import React, { useState } from 'react'; -import { useProfilingParams } from '../../hooks/use_profiling_params'; -import { useProfilingRouter } from '../../hooks/use_profiling_router'; -import { useProfilingRoutePath } from '../../hooks/use_profiling_route_path'; -import { useTimeRange } from '../../hooks/use_time_range'; -import { useTimeRangeAsync } from '../../hooks/use_time_range_async'; -import { AsyncComponent } from '../../components/async_component'; -import { useProfilingDependencies } from '../../components/contexts/profiling_dependencies/use_profiling_dependencies'; -import { FlameGraph } from '../../components/flamegraph'; -import { ProfilingAppPageTemplate } from '../../components/profiling_app_page_template'; -import { RedirectTo } from '../../components/redirect_to'; -import { FlameGraphSearchPanel } from './flame_graph_search_panel'; -import { - ComparisonMode, - NormalizationMode, - NormalizationOptions, -} from '../../components/normalization_menu'; - -export function FlameGraphsView({ children }: { children: React.ReactElement }) { - const { - query, - query: { rangeFrom, rangeTo, kuery }, - } = useProfilingParams('/flamegraphs/*'); - - const timeRange = useTimeRange({ rangeFrom, rangeTo }); - - const comparisonTimeRange = useTimeRange( - 'comparisonRangeFrom' in query - ? { rangeFrom: query.comparisonRangeFrom, rangeTo: query.comparisonRangeTo, optional: true } - : { rangeFrom: undefined, rangeTo: undefined, optional: true } - ); - - const comparisonKuery = 'comparisonKuery' in query ? query.comparisonKuery : ''; - const comparisonMode = 'comparisonMode' in query ? query.comparisonMode : ComparisonMode.Absolute; - - const normalizationMode: NormalizationMode = get( - query, - 'normalizationMode', - NormalizationMode.Time - ); - - const baselineScale: number = get(query, 'baseline', 1); - const comparisonScale: number = get(query, 'comparison', 1); - - const totalSeconds = timeRange.inSeconds.end - timeRange.inSeconds.start; - const totalComparisonSeconds = - (new Date(comparisonTimeRange.end!).getTime() - - new Date(comparisonTimeRange.start!).getTime()) / - 1000; - - const baselineTime = 1; - const comparisonTime = totalSeconds / totalComparisonSeconds; - - const normalizationOptions: NormalizationOptions = { - baselineScale, - baselineTime, - comparisonScale, - comparisonTime, - }; - - const { - services: { fetchElasticFlamechart }, - } = useProfilingDependencies(); - - const state = useTimeRangeAsync( - ({ http }) => { - return Promise.all([ - fetchElasticFlamechart({ - http, - timeFrom: timeRange.inSeconds.start, - timeTo: timeRange.inSeconds.end, - kuery, - }), - comparisonTimeRange.inSeconds.start && comparisonTimeRange.inSeconds.end - ? fetchElasticFlamechart({ - http, - timeFrom: comparisonTimeRange.inSeconds.start, - timeTo: comparisonTimeRange.inSeconds.end, - kuery: comparisonKuery, - }) - : Promise.resolve(undefined), - ]).then(([primaryFlamegraph, comparisonFlamegraph]) => { - return { - primaryFlamegraph, - comparisonFlamegraph, - }; - }); - }, - [ - timeRange.inSeconds.start, - timeRange.inSeconds.end, - kuery, - comparisonTimeRange.inSeconds.start, - comparisonTimeRange.inSeconds.end, - comparisonKuery, - fetchElasticFlamechart, - ] - ); - - const { data } = state; - - const routePath = useProfilingRoutePath(); - - const profilingRouter = useProfilingRouter(); - - const isDifferentialView = routePath === '/flamegraphs/differential'; - - const tabs: Required['tabs'] = [ - { - label: i18n.translate('xpack.profiling.flameGraphsView.flameGraphTabLabel', { - defaultMessage: 'Flamegraph', - }), - isSelected: !isDifferentialView, - href: profilingRouter.link('/flamegraphs/flamegraph', { query }), - }, - { - label: i18n.translate('xpack.profiling.flameGraphsView.differentialFlameGraphTabLabel', { - defaultMessage: 'Differential flamegraph', - }), - isSelected: isDifferentialView, - href: profilingRouter.link('/flamegraphs/differential', { - // @ts-expect-error Code gets too complicated to satisfy TS constraints - query: { - ...query, - comparisonRangeFrom: query.rangeFrom, - comparisonRangeTo: query.rangeTo, - comparisonKuery: query.kuery, - }, - }), - }, - ]; - - const [showInformationWindow, setShowInformationWindow] = useState(false); - function toggleShowInformationWindow() { - setShowInformationWindow((prev) => !prev); - } - - if (routePath === '/flamegraphs') { - return ; - } - - const isNormalizedByTime = normalizationMode === NormalizationMode.Time; - - return ( - - - - - - - - - - {children} - - - - ); -} diff --git a/x-pack/plugins/profiling/public/views/flame_graphs_view/flame_graph_search_panel.tsx b/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/differential_flame_graph_search_panel.tsx similarity index 59% rename from x-pack/plugins/profiling/public/views/flame_graphs_view/flame_graph_search_panel.tsx rename to x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/differential_flame_graph_search_panel.tsx index 6d1416f91079b..3b737eb4498af 100644 --- a/x-pack/plugins/profiling/public/views/flame_graphs_view/flame_graph_search_panel.tsx +++ b/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/differential_flame_graph_search_panel.tsx @@ -6,30 +6,27 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel } from '@elastic/eui'; import React from 'react'; -import { useProfilingParams } from '../../hooks/use_profiling_params'; -import { useProfilingRouter } from '../../hooks/use_profiling_router'; -import { useProfilingRoutePath } from '../../hooks/use_profiling_route_path'; -import { PrimaryAndComparisonSearchBar } from '../../components/primary_and_comparison_search_bar'; -import { PrimaryProfilingSearchBar } from '../../components/profiling_app_page_template/primary_profiling_search_bar'; +import { useProfilingParams } from '../../../hooks/use_profiling_params'; +import { useProfilingRouter } from '../../../hooks/use_profiling_router'; +import { useProfilingRoutePath } from '../../../hooks/use_profiling_route_path'; +import { PrimaryAndComparisonSearchBar } from '../../../components/primary_and_comparison_search_bar'; import { ComparisonMode, NormalizationMode, NormalizationOptions, NormalizationMenu, -} from '../../components/normalization_menu'; -import { DifferentialComparisonMode } from '../../components/differential_comparison_mode'; +} from '../../../components/normalization_menu'; +import { DifferentialComparisonMode } from '../../../components/differential_comparison_mode'; interface Props { - isDifferentialView: boolean; comparisonMode: ComparisonMode; normalizationMode: NormalizationMode; normalizationOptions: NormalizationOptions; } -export function FlameGraphSearchPanel({ +export function DifferentialFlameGraphSearchPanel({ comparisonMode, normalizationMode, - isDifferentialView, normalizationOptions, }: Props) { const { path, query } = useProfilingParams('/flamegraphs/*'); @@ -77,29 +74,25 @@ export function FlameGraphSearchPanel({ } return ( - {isDifferentialView ? : } + - {isDifferentialView && ( - <> - - {comparisonMode === ComparisonMode.Absolute && ( + + {comparisonMode === ComparisonMode.Absolute && ( + + - - - - - + - )} - + + )} diff --git a/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/index.tsx b/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/index.tsx new file mode 100644 index 0000000000000..474524fe15437 --- /dev/null +++ b/x-pack/plugins/profiling/public/views/flamegraphs/differential_flamegraphs/index.tsx @@ -0,0 +1,144 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React, { useState } from 'react'; +import { AsyncComponent } from '../../../components/async_component'; +import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies'; +import { FlameGraph } from '../../../components/flamegraph'; +import { NormalizationMode, NormalizationOptions } from '../../../components/normalization_menu'; +import { useProfilingParams } from '../../../hooks/use_profiling_params'; +import { useProfilingRouter } from '../../../hooks/use_profiling_router'; +import { useProfilingRoutePath } from '../../../hooks/use_profiling_route_path'; +import { useTimeRange } from '../../../hooks/use_time_range'; +import { useTimeRangeAsync } from '../../../hooks/use_time_range_async'; +import { DifferentialFlameGraphSearchPanel } from './differential_flame_graph_search_panel'; + +export function DifferentialFlameGraphsView() { + const { + query, + query: { + rangeFrom, + rangeTo, + kuery, + comparisonRangeFrom, + comparisonRangeTo, + comparisonKuery, + comparisonMode, + baseline = 1, + comparison = 1, + normalizationMode, + searchText, + }, + } = useProfilingParams('/flamegraphs/differential'); + const routePath = useProfilingRoutePath(); + const profilingRouter = useProfilingRouter(); + const [showInformationWindow, setShowInformationWindow] = useState(false); + + const timeRange = useTimeRange({ rangeFrom, rangeTo }); + + const comparisonTimeRange = useTimeRange({ + rangeFrom: comparisonRangeFrom, + rangeTo: comparisonRangeTo, + optional: true, + }); + + const { + services: { fetchElasticFlamechart }, + } = useProfilingDependencies(); + + const state = useTimeRangeAsync( + ({ http }) => { + return Promise.all([ + fetchElasticFlamechart({ + http, + timeFrom: timeRange.inSeconds.start, + timeTo: timeRange.inSeconds.end, + kuery, + }), + comparisonTimeRange.inSeconds.start && comparisonTimeRange.inSeconds.end + ? fetchElasticFlamechart({ + http, + timeFrom: comparisonTimeRange.inSeconds.start, + timeTo: comparisonTimeRange.inSeconds.end, + kuery: comparisonKuery, + }) + : Promise.resolve(undefined), + ]).then(([primaryFlamegraph, comparisonFlamegraph]) => { + return { + primaryFlamegraph, + comparisonFlamegraph, + }; + }); + }, + [ + timeRange.inSeconds.start, + timeRange.inSeconds.end, + kuery, + comparisonTimeRange.inSeconds.start, + comparisonTimeRange.inSeconds.end, + comparisonKuery, + fetchElasticFlamechart, + ] + ); + + const totalSeconds = timeRange.inSeconds.end - timeRange.inSeconds.start; + const totalComparisonSeconds = + (new Date(comparisonTimeRange.end!).getTime() - + new Date(comparisonTimeRange.start!).getTime()) / + 1000; + + const baselineTime = 1; + const comparisonTime = totalSeconds / totalComparisonSeconds; + + const normalizationOptions: NormalizationOptions = { + baselineScale: baseline, + baselineTime, + comparisonScale: comparison, + comparisonTime, + }; + + const { data } = state; + + const isNormalizedByTime = normalizationMode === NormalizationMode.Time; + + function toggleShowInformationWindow() { + setShowInformationWindow((prev) => !prev); + } + + function handleSearchTextChange(newSearchText: string) { + // @ts-expect-error Code gets too complicated to satisfy TS constraints + profilingRouter.push(routePath, { query: { ...query, searchText: newSearchText } }); + } + + return ( + + + + + + + + + + + ); +} diff --git a/x-pack/plugins/profiling/public/views/flamegraphs/flamegraph/index.tsx b/x-pack/plugins/profiling/public/views/flamegraphs/flamegraph/index.tsx new file mode 100644 index 0000000000000..50d536e90fbe9 --- /dev/null +++ b/x-pack/plugins/profiling/public/views/flamegraphs/flamegraph/index.tsx @@ -0,0 +1,81 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel } from '@elastic/eui'; +import React, { useState } from 'react'; +import { AsyncComponent } from '../../../components/async_component'; +import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies'; +import { FlameGraph } from '../../../components/flamegraph'; +import { PrimaryProfilingSearchBar } from '../../../components/profiling_app_page_template/primary_profiling_search_bar'; +import { useProfilingParams } from '../../../hooks/use_profiling_params'; +import { useProfilingRouter } from '../../../hooks/use_profiling_router'; +import { useProfilingRoutePath } from '../../../hooks/use_profiling_route_path'; +import { useTimeRange } from '../../../hooks/use_time_range'; +import { useTimeRangeAsync } from '../../../hooks/use_time_range_async'; + +export function FlameGraphView() { + const { + query, + query: { rangeFrom, rangeTo, kuery, searchText }, + } = useProfilingParams('/flamegraphs/flamegraph'); + + const timeRange = useTimeRange({ rangeFrom, rangeTo }); + + const { + services: { fetchElasticFlamechart }, + } = useProfilingDependencies(); + + const state = useTimeRangeAsync( + ({ http }) => { + return fetchElasticFlamechart({ + http, + timeFrom: timeRange.inSeconds.start, + timeTo: timeRange.inSeconds.end, + kuery, + }); + }, + [timeRange.inSeconds.start, timeRange.inSeconds.end, kuery, fetchElasticFlamechart] + ); + + const { data } = state; + + const routePath = useProfilingRoutePath(); + + const profilingRouter = useProfilingRouter(); + + const [showInformationWindow, setShowInformationWindow] = useState(false); + function toggleShowInformationWindow() { + setShowInformationWindow((prev) => !prev); + } + + function handleSearchTextChange(newSearchText: string) { + // @ts-expect-error Code gets too complicated to satisfy TS constraints + profilingRouter.push(routePath, { query: { ...query, searchText: newSearchText } }); + } + + return ( + + + + + + + + + + + + + + ); +} diff --git a/x-pack/plugins/profiling/public/views/flamegraphs/index.tsx b/x-pack/plugins/profiling/public/views/flamegraphs/index.tsx new file mode 100644 index 0000000000000..0d773a327984d --- /dev/null +++ b/x-pack/plugins/profiling/public/views/flamegraphs/index.tsx @@ -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 { EuiPageHeaderContentProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { ProfilingAppPageTemplate } from '../../components/profiling_app_page_template'; +import { RedirectTo } from '../../components/redirect_to'; +import { useProfilingParams } from '../../hooks/use_profiling_params'; +import { useProfilingRouter } from '../../hooks/use_profiling_router'; +import { useProfilingRoutePath } from '../../hooks/use_profiling_route_path'; + +export function FlameGraphsView({ children }: { children: React.ReactElement }) { + const { query } = useProfilingParams('/flamegraphs/*'); + const routePath = useProfilingRoutePath(); + const profilingRouter = useProfilingRouter(); + + if (routePath === '/flamegraphs') { + return ; + } + + const isDifferentialView = routePath === '/flamegraphs/differential'; + + const tabs: Required['tabs'] = [ + { + label: i18n.translate('xpack.profiling.flameGraphsView.flameGraphTabLabel', { + defaultMessage: 'Flamegraph', + }), + isSelected: !isDifferentialView, + href: profilingRouter.link('/flamegraphs/flamegraph', { query }), + }, + { + label: i18n.translate('xpack.profiling.flameGraphsView.differentialFlameGraphTabLabel', { + defaultMessage: 'Differential flamegraph', + }), + isSelected: isDifferentialView, + href: profilingRouter.link('/flamegraphs/differential', { + // @ts-expect-error Code gets too complicated to satisfy TS constraints + query: { + ...query, + comparisonRangeFrom: query.rangeFrom, + comparisonRangeTo: query.rangeTo, + comparisonKuery: query.kuery, + }, + }), + }, + ]; + + return ( + + {children} + + ); +} diff --git a/x-pack/plugins/profiling/public/views/functions/differential_topn/index.tsx b/x-pack/plugins/profiling/public/views/functions/differential_topn/index.tsx index 01e2a21eb4524..1c87247d227bf 100644 --- a/x-pack/plugins/profiling/public/views/functions/differential_topn/index.tsx +++ b/x-pack/plugins/profiling/public/views/functions/differential_topn/index.tsx @@ -135,6 +135,13 @@ export function DifferentialTopNFunctionsView() { const isNormalizedByTime = normalizationMode === NormalizationMode.Time; + function handleOnFrameClick(functionName: string) { + profilingRouter.push('/flamegraphs/flamegraph', { + path: {}, + query: { ...query, searchText: functionName }, + }); + } + return ( <> @@ -169,6 +176,7 @@ export function DifferentialTopNFunctionsView() { totalSeconds={timeRange.inSeconds.end - timeRange.inSeconds.start} isDifferentialView={true} baselineScaleFactor={isNormalizedByTime ? baselineTime : baseline} + onFrameClick={handleOnFrameClick} /> @@ -196,6 +204,7 @@ export function DifferentialTopNFunctionsView() { isDifferentialView={true} baselineScaleFactor={isNormalizedByTime ? comparisonTime : comparison} comparisonScaleFactor={isNormalizedByTime ? baselineTime : baseline} + onFrameClick={handleOnFrameClick} /> diff --git a/x-pack/plugins/profiling/public/views/functions/topn/index.tsx b/x-pack/plugins/profiling/public/views/functions/topn/index.tsx index 08129d6320a11..768f5256fe3c3 100644 --- a/x-pack/plugins/profiling/public/views/functions/topn/index.tsx +++ b/x-pack/plugins/profiling/public/views/functions/topn/index.tsx @@ -46,6 +46,13 @@ export function TopNFunctionsView() { const profilingRouter = useProfilingRouter(); + function handleOnFrameClick(functionName: string) { + profilingRouter.push('/flamegraphs/flamegraph', { + path: {}, + query: { ...query, searchText: functionName }, + }); + } + return ( <> @@ -69,6 +76,7 @@ export function TopNFunctionsView() { }} totalSeconds={timeRange.inSeconds.end - timeRange.inSeconds.start} isDifferentialView={false} + onFrameClick={handleOnFrameClick} />