mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Profiling] Improve finding a function (#210437)
closes https://github.com/elastic/prodfiler/issues/4534 TopN functions: https://github.com/user-attachments/assets/1b62a50f-3c6f-4cd5-8971-019ca403893c Diff TopN functions: https://github.com/user-attachments/assets/0c598317-9423-44b4-9d3f-079d22a0194c
This commit is contained in:
parent
c5857f929d
commit
929cd7de4f
8 changed files with 338 additions and 219 deletions
|
@ -10,19 +10,27 @@ import type {
|
|||
EuiDataGridColumn,
|
||||
EuiDataGridSorting,
|
||||
} from '@elastic/eui';
|
||||
import { EuiButtonIcon, EuiDataGrid, EuiScreenReaderOnly, useEuiTheme } from '@elastic/eui';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiDataGrid,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiScreenReaderOnly,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { StackFrameMetadata, TopNFunctions } from '@kbn/profiling-utils';
|
||||
import {
|
||||
getCalleeFunction,
|
||||
TopNComparisonFunctionSortField,
|
||||
TopNFunctionSortField,
|
||||
getCalleeFunction,
|
||||
} from '@kbn/profiling-utils';
|
||||
import { orderBy } from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_estimates';
|
||||
import { FrameInformationTooltip } from '../frame_information_window/frame_information_tooltip';
|
||||
import { SearchFunctionsInput } from '../search_functions_input';
|
||||
import { FunctionRow } from '../topn_functions/function_row';
|
||||
import type { IFunctionRow } from '../topn_functions/utils';
|
||||
import { convertRowToFrame, getFunctionsRows, getTotalCount } from '../topn_functions/utils';
|
||||
|
@ -84,6 +92,8 @@ interface Props {
|
|||
comparisonSortField: TopNComparisonFunctionSortField;
|
||||
totalSeconds: number;
|
||||
comparisonTotalSeconds: number;
|
||||
searchFunctionName: string;
|
||||
onSearchFunctionNameChange: (functionName: string) => void;
|
||||
}
|
||||
|
||||
export function DifferentialTopNFunctionsGrid({
|
||||
|
@ -101,6 +111,8 @@ export function DifferentialTopNFunctionsGrid({
|
|||
sortDirection,
|
||||
sortField,
|
||||
totalSeconds,
|
||||
searchFunctionName,
|
||||
onSearchFunctionNameChange,
|
||||
}: Props) {
|
||||
const theme = useEuiTheme();
|
||||
const calculateImpactEstimates = useCalculateImpactEstimate();
|
||||
|
@ -145,6 +157,7 @@ export function DifferentialTopNFunctionsGrid({
|
|||
calculateImpactEstimates,
|
||||
topNFunctions: base,
|
||||
totalSeconds,
|
||||
functionNameSearchQuery: searchFunctionName,
|
||||
}),
|
||||
comparisonRows: getFunctionsRows({
|
||||
baselineScaleFactor,
|
||||
|
@ -153,6 +166,7 @@ export function DifferentialTopNFunctionsGrid({
|
|||
comparisonTopNFunctions: base,
|
||||
topNFunctions: comparison,
|
||||
totalSeconds: comparisonTotalSeconds,
|
||||
functionNameSearchQuery: searchFunctionName,
|
||||
}),
|
||||
};
|
||||
}, [
|
||||
|
@ -162,6 +176,7 @@ export function DifferentialTopNFunctionsGrid({
|
|||
comparison,
|
||||
comparisonScaleFactor,
|
||||
comparisonTotalSeconds,
|
||||
searchFunctionName,
|
||||
totalSeconds,
|
||||
]);
|
||||
|
||||
|
@ -226,109 +241,114 @@ export function DifferentialTopNFunctionsGrid({
|
|||
const rowCount = Math.max(sortedBaseRows.length, sortedComparisonRows.length);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiDataGrid
|
||||
data-test-subj="profilingDiffTopNFunctionsGrid"
|
||||
css={css`
|
||||
.thickBorderLeft {
|
||||
border-left: ${theme.euiTheme.border.thick} !important;
|
||||
}
|
||||
`}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.profiling.onWeelkDiffTopN.euiDataGrid.topNFunctionsLabel',
|
||||
{
|
||||
defaultMessage: 'TopN functions',
|
||||
}
|
||||
)}
|
||||
columns={columns}
|
||||
leadingControlColumns={[
|
||||
{
|
||||
id: 'actions',
|
||||
width: 40,
|
||||
headerCellRender() {
|
||||
return (
|
||||
<EuiScreenReaderOnly>
|
||||
<span>
|
||||
{i18n.translate('xpack.profiling.topNFunctionsGrid.span.controlsLabel', {
|
||||
defaultMessage: 'Controls',
|
||||
})}
|
||||
</span>
|
||||
</EuiScreenReaderOnly>
|
||||
);
|
||||
},
|
||||
rowCellRender: function RowCellRender({ rowIndex }) {
|
||||
function handleOnClick() {
|
||||
const row = sortedBaseRows[rowIndex];
|
||||
const currentFrameId = getFrameIdentification(row.frame);
|
||||
const compareRow = sortedComparisonRows.find(
|
||||
(item) => getFrameIdentification(item.frame) === currentFrameId
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SearchFunctionsInput onChange={onSearchFunctionNameChange} value={searchFunctionName} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiDataGrid
|
||||
data-test-subj="profilingDiffTopNFunctionsGrid"
|
||||
css={css`
|
||||
.thickBorderLeft {
|
||||
border-left: ${theme.euiTheme.border.thick};
|
||||
}
|
||||
`}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.profiling.onWeelkDiffTopN.euiDataGrid.topNFunctionsLabel',
|
||||
{
|
||||
defaultMessage: 'TopN functions',
|
||||
}
|
||||
)}
|
||||
columns={columns}
|
||||
leadingControlColumns={[
|
||||
{
|
||||
id: 'actions',
|
||||
width: 40,
|
||||
headerCellRender() {
|
||||
return (
|
||||
<EuiScreenReaderOnly>
|
||||
<span>
|
||||
{i18n.translate('xpack.profiling.topNFunctionsGrid.span.controlsLabel', {
|
||||
defaultMessage: 'Controls',
|
||||
})}
|
||||
</span>
|
||||
</EuiScreenReaderOnly>
|
||||
);
|
||||
setRowsInformation({
|
||||
baseRow: row,
|
||||
comparisonRow: compareRow,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
data-test-subj="profilingTopNFunctionsGridButton"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.profiling.topNFunctionsGrid.euiButtonIcon.showActionsLabel',
|
||||
{ defaultMessage: 'Show actions' }
|
||||
)}
|
||||
iconType="expand"
|
||||
color="text"
|
||||
onClick={handleOnClick}
|
||||
/>
|
||||
);
|
||||
},
|
||||
rowCellRender: function RowCellRender({ rowIndex }) {
|
||||
function handleOnClick() {
|
||||
const row = sortedBaseRows[rowIndex];
|
||||
const currentFrameId = getFrameIdentification(row.frame);
|
||||
const compareRow = sortedComparisonRows.find(
|
||||
(item) => getFrameIdentification(item.frame) === currentFrameId
|
||||
);
|
||||
setRowsInformation({
|
||||
baseRow: row,
|
||||
comparisonRow: compareRow,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
data-test-subj="profilingTopNFunctionsGridButton"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.profiling.topNFunctionsGrid.euiButtonIcon.showActionsLabel',
|
||||
{ defaultMessage: 'Show actions' }
|
||||
)}
|
||||
iconType="expand"
|
||||
color="text"
|
||||
onClick={handleOnClick}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
]}
|
||||
columnVisibility={{ visibleColumns, setVisibleColumns }}
|
||||
rowCount={rowCount}
|
||||
renderCellValue={CellValue}
|
||||
sorting={{
|
||||
columns: [
|
||||
{ id: sortField, direction: sortDirection },
|
||||
{ id: comparisonSortField, direction: comparisonSortDirection },
|
||||
],
|
||||
onSort,
|
||||
}}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize: 100,
|
||||
// Left it empty on purpose as it is a required property on the pagination
|
||||
onChangeItemsPerPage: () => {},
|
||||
onChangePage,
|
||||
pageSizeOptions: [],
|
||||
}}
|
||||
rowHeightsOptions={{ defaultHeight: 'auto' }}
|
||||
toolbarVisibility={{
|
||||
showColumnSelector: false,
|
||||
showKeyboardShortcuts: false,
|
||||
showDisplaySelector: false,
|
||||
showSortSelector: false,
|
||||
}}
|
||||
/>
|
||||
{rowsInformation && (
|
||||
<FrameInformationTooltip
|
||||
compressed
|
||||
comparisonRank={rowsInformation.comparisonRow?.rank}
|
||||
comparisonFrame={
|
||||
rowsInformation.comparisonRow
|
||||
? convertRowToFrame(rowsInformation.comparisonRow)
|
||||
: undefined
|
||||
}
|
||||
comparisonTotalSamples={comparisonTotalCount}
|
||||
comparisonTotalSeconds={comparisonTotalSeconds}
|
||||
rank={rowsInformation.baseRow.rank}
|
||||
frame={convertRowToFrame(rowsInformation.baseRow)}
|
||||
totalSamples={totalCount}
|
||||
totalSeconds={totalSeconds}
|
||||
onClose={() => {
|
||||
setRowsInformation(undefined);
|
||||
]}
|
||||
columnVisibility={{ visibleColumns, setVisibleColumns }}
|
||||
rowCount={rowCount}
|
||||
renderCellValue={CellValue}
|
||||
sorting={{
|
||||
columns: [
|
||||
{ id: sortField, direction: sortDirection },
|
||||
{ id: comparisonSortField, direction: comparisonSortDirection },
|
||||
],
|
||||
onSort,
|
||||
}}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize: 100,
|
||||
// Left it empty on purpose as it is a required property on the pagination
|
||||
onChangeItemsPerPage: () => {},
|
||||
onChangePage,
|
||||
pageSizeOptions: [],
|
||||
}}
|
||||
rowHeightsOptions={{ defaultHeight: 'auto' }}
|
||||
toolbarVisibility={{
|
||||
showColumnSelector: false,
|
||||
showKeyboardShortcuts: false,
|
||||
showDisplaySelector: false,
|
||||
showSortSelector: false,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
{rowsInformation && (
|
||||
<FrameInformationTooltip
|
||||
compressed
|
||||
comparisonRank={rowsInformation.comparisonRow?.rank}
|
||||
comparisonFrame={
|
||||
rowsInformation.comparisonRow
|
||||
? convertRowToFrame(rowsInformation.comparisonRow)
|
||||
: undefined
|
||||
}
|
||||
comparisonTotalSamples={comparisonTotalCount}
|
||||
comparisonTotalSeconds={comparisonTotalSeconds}
|
||||
rank={rowsInformation.baseRow.rank}
|
||||
frame={convertRowToFrame(rowsInformation.baseRow)}
|
||||
totalSamples={totalCount}
|
||||
totalSeconds={totalSeconds}
|
||||
onClose={() => {
|
||||
setRowsInformation(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 { EuiFieldSearch } from '@elastic/eui';
|
||||
import { debounce } from 'lodash';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
onChange: (functionName: string) => void;
|
||||
}
|
||||
|
||||
export function SearchFunctionsInput({ value, onChange }: Props) {
|
||||
const [searchQuery, setSearchQuery] = useState(value);
|
||||
const debouncedOnChange = useMemo(() => debounce(onChange, 300), [onChange]);
|
||||
const handleSearchChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchQuery(e.target.value);
|
||||
debouncedOnChange(e.target.value);
|
||||
},
|
||||
[debouncedOnChange]
|
||||
);
|
||||
return (
|
||||
<EuiFieldSearch
|
||||
data-test-subj="tableSearchInput"
|
||||
placeholder="Search functions by name"
|
||||
fullWidth={true}
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -11,11 +11,17 @@ import type {
|
|||
EuiDataGridControlColumn,
|
||||
EuiDataGridSorting,
|
||||
} from '@elastic/eui';
|
||||
import { EuiButtonIcon, EuiDataGrid, EuiScreenReaderOnly } from '@elastic/eui';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiDataGrid,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiScreenReaderOnly,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useUiTracker } from '@kbn/observability-shared-plugin/public';
|
||||
import type { TopNFunctions } from '@kbn/profiling-utils';
|
||||
import { getCalleeFunction, TopNFunctionSortField } from '@kbn/profiling-utils';
|
||||
import { TopNFunctionSortField, getCalleeFunction } from '@kbn/profiling-utils';
|
||||
import { last, orderBy } from 'lodash';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import type { GridOnScrollProps } from 'react-window';
|
||||
|
@ -23,6 +29,7 @@ import { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_est
|
|||
import { CPULabelWithHint } from '../cpu_label_with_hint';
|
||||
import { FrameInformationTooltip } from '../frame_information_window/frame_information_tooltip';
|
||||
import { LabelWithHint } from '../label_with_hint';
|
||||
import { SearchFunctionsInput } from '../search_functions_input';
|
||||
import { FunctionRow } from './function_row';
|
||||
import type { IFunctionRow } from './utils';
|
||||
import { convertRowToFrame, getFunctionsRows, getTotalCount } from './utils';
|
||||
|
@ -45,6 +52,8 @@ interface Props {
|
|||
onChangeSort: (sorting: EuiDataGridSorting['columns'][0]) => void;
|
||||
dataTestSubj?: string;
|
||||
isEmbedded?: boolean;
|
||||
searchFunctionName: string;
|
||||
onSearchFunctionNameChange: (functionName: string) => void;
|
||||
}
|
||||
|
||||
export const TopNFunctionsGrid = ({
|
||||
|
@ -65,6 +74,8 @@ export const TopNFunctionsGrid = ({
|
|||
onChangeSort,
|
||||
dataTestSubj = 'topNFunctionsGrid',
|
||||
isEmbedded = false,
|
||||
searchFunctionName,
|
||||
onSearchFunctionNameChange,
|
||||
}: Props) => {
|
||||
const [selectedRow, setSelectedRow] = useState<IFunctionRow | undefined>();
|
||||
const trackProfilingEvent = useUiTracker({ app: 'profiling' });
|
||||
|
@ -87,12 +98,14 @@ export const TopNFunctionsGrid = ({
|
|||
topNFunctions,
|
||||
totalSeconds,
|
||||
calculateImpactEstimates,
|
||||
functionNameSearchQuery: searchFunctionName,
|
||||
});
|
||||
}, [
|
||||
baselineScaleFactor,
|
||||
calculateImpactEstimates,
|
||||
comparisonScaleFactor,
|
||||
comparisonTopNFunctions,
|
||||
searchFunctionName,
|
||||
topNFunctions,
|
||||
totalSeconds,
|
||||
]);
|
||||
|
@ -288,55 +301,59 @@ export const TopNFunctionsGrid = ({
|
|||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiDataGrid
|
||||
data-test-subj={dataTestSubj}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.profiling.topNFunctionsGrid.euiDataGrid.topNFunctionsLabel',
|
||||
{ defaultMessage: 'TopN functions' }
|
||||
)}
|
||||
columns={columns}
|
||||
columnVisibility={{ visibleColumns, setVisibleColumns }}
|
||||
rowCount={sortedRows.length}
|
||||
renderCellValue={RenderCellValue}
|
||||
sorting={{ columns: [{ id: sortField, direction: sortDirection }], onSort }}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize: 100,
|
||||
// Left it empty on purpose as it is a required property on the pagination
|
||||
onChangeItemsPerPage: () => {},
|
||||
onChangePage,
|
||||
pageSizeOptions: [],
|
||||
}}
|
||||
rowHeightsOptions={{ defaultHeight: 'auto' }}
|
||||
toolbarVisibility={{
|
||||
showColumnSelector: false,
|
||||
showKeyboardShortcuts: !isDifferentialView,
|
||||
showDisplaySelector: !isDifferentialView,
|
||||
showFullScreenSelector: showFullScreenSelector && !isDifferentialView,
|
||||
showSortSelector: false,
|
||||
}}
|
||||
virtualizationOptions={{
|
||||
onScroll,
|
||||
}}
|
||||
/>
|
||||
{selectedRow && (
|
||||
<FrameInformationTooltip
|
||||
compressed
|
||||
onClose={() => {
|
||||
setSelectedRow(undefined);
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SearchFunctionsInput onChange={onSearchFunctionNameChange} value={searchFunctionName} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiDataGrid
|
||||
data-test-subj={dataTestSubj}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.profiling.topNFunctionsGrid.euiDataGrid.topNFunctionsLabel',
|
||||
{ defaultMessage: 'TopN functions' }
|
||||
)}
|
||||
columns={columns}
|
||||
columnVisibility={{ visibleColumns, setVisibleColumns }}
|
||||
rowCount={sortedRows.length}
|
||||
renderCellValue={RenderCellValue}
|
||||
sorting={{ columns: [{ id: sortField, direction: sortDirection }], onSort }}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize: 100,
|
||||
// Left it empty on purpose as it is a required property on the pagination
|
||||
onChangeItemsPerPage: () => {},
|
||||
onChangePage,
|
||||
pageSizeOptions: [],
|
||||
}}
|
||||
frame={convertRowToFrame(selectedRow)}
|
||||
totalSeconds={totalSeconds}
|
||||
totalSamples={totalCount}
|
||||
showSymbolsStatus={!isEmbedded}
|
||||
rowHeightsOptions={{ defaultHeight: 'auto' }}
|
||||
toolbarVisibility={{
|
||||
showColumnSelector: false,
|
||||
showKeyboardShortcuts: !isDifferentialView,
|
||||
showDisplaySelector: !isDifferentialView,
|
||||
showFullScreenSelector: showFullScreenSelector && !isDifferentialView,
|
||||
showSortSelector: false,
|
||||
}}
|
||||
virtualizationOptions={{ onScroll }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
{selectedRow && (
|
||||
<FrameInformationTooltip
|
||||
compressed
|
||||
onClose={() => {
|
||||
setSelectedRow(undefined);
|
||||
}}
|
||||
frame={convertRowToFrame(selectedRow)}
|
||||
totalSeconds={totalSeconds}
|
||||
totalSamples={totalCount}
|
||||
showSymbolsStatus={!isEmbedded}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { keyBy } from 'lodash';
|
||||
import type { StackFrameMetadata, TopNFunctions } from '@kbn/profiling-utils';
|
||||
import {
|
||||
getCalleeFunction,
|
||||
type StackFrameMetadata,
|
||||
type TopNFunctions,
|
||||
} from '@kbn/profiling-utils';
|
||||
import type {
|
||||
CalculateImpactEstimates,
|
||||
ImpactEstimates,
|
||||
|
@ -71,6 +75,7 @@ export function getFunctionsRows({
|
|||
topNFunctions,
|
||||
totalSeconds,
|
||||
calculateImpactEstimates,
|
||||
functionNameSearchQuery,
|
||||
}: {
|
||||
baselineScaleFactor?: number;
|
||||
comparisonScaleFactor?: number;
|
||||
|
@ -78,6 +83,7 @@ export function getFunctionsRows({
|
|||
topNFunctions?: TopNFunctions;
|
||||
totalSeconds: number;
|
||||
calculateImpactEstimates: CalculateImpactEstimates;
|
||||
functionNameSearchQuery: string;
|
||||
}): IFunctionRow[] {
|
||||
if (!topNFunctions || !topNFunctions.TotalCount || topNFunctions.TotalCount === 0) {
|
||||
return [];
|
||||
|
@ -87,72 +93,80 @@ export function getFunctionsRows({
|
|||
? keyBy(comparisonTopNFunctions.TopN, 'Id')
|
||||
: {};
|
||||
|
||||
return topNFunctions.TopN.filter((topN) => topN.CountExclusive >= 0).map((topN, i) => {
|
||||
const comparisonRow = comparisonDataById?.[topN.Id];
|
||||
|
||||
const scaledSelfCPU = scaleValue({
|
||||
value: topN.CountExclusive,
|
||||
scaleFactor: baselineScaleFactor,
|
||||
});
|
||||
|
||||
const totalCPUPerc = (topN.CountInclusive / topNFunctions.selfCPU) * 100;
|
||||
const selfCPUPerc = (topN.CountExclusive / topNFunctions.selfCPU) * 100;
|
||||
|
||||
const impactEstimates =
|
||||
totalSeconds > 0
|
||||
? calculateImpactEstimates({
|
||||
countExclusive: topN.CountExclusive,
|
||||
countInclusive: topN.CountInclusive,
|
||||
totalSamples: topNFunctions.selfCPU,
|
||||
totalSeconds,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
function calculateDiff() {
|
||||
if (comparisonTopNFunctions && comparisonRow) {
|
||||
const comparisonScaledSelfCPU = scaleValue({
|
||||
value: comparisonRow.CountExclusive,
|
||||
scaleFactor: comparisonScaleFactor,
|
||||
});
|
||||
|
||||
const scaledDiffSamples = scaledSelfCPU - comparisonScaledSelfCPU;
|
||||
|
||||
return {
|
||||
id: comparisonRow.Id,
|
||||
rank: topN.Rank - comparisonRow.Rank,
|
||||
samples: scaledDiffSamples,
|
||||
selfCPU: comparisonRow.CountExclusive,
|
||||
totalCPU: comparisonRow.CountInclusive,
|
||||
selfCPUPerc:
|
||||
selfCPUPerc - (comparisonRow.CountExclusive / comparisonTopNFunctions.selfCPU) * 100,
|
||||
totalCPUPerc:
|
||||
totalCPUPerc - (comparisonRow.CountInclusive / comparisonTopNFunctions.selfCPU) * 100,
|
||||
selfAnnualCO2kgs: comparisonRow.selfAnnualCO2kgs,
|
||||
selfAnnualCostUSD: comparisonRow.selfAnnualCostUSD,
|
||||
totalAnnualCO2kgs: comparisonRow.totalAnnualCO2kgs,
|
||||
totalAnnualCostUSD: comparisonRow.totalAnnualCostUSD,
|
||||
};
|
||||
return topNFunctions.TopN.filter((topN) => topN.CountExclusive >= 0)
|
||||
.filter((topN) => {
|
||||
if (functionNameSearchQuery) {
|
||||
const functionName = getCalleeFunction(topN.Frame);
|
||||
return functionName.toLowerCase().includes(functionNameSearchQuery.toLowerCase());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((topN, i) => {
|
||||
const comparisonRow = comparisonDataById?.[topN.Id];
|
||||
|
||||
return {
|
||||
id: topN.Id,
|
||||
rank: topN.Rank,
|
||||
frame: topN.Frame,
|
||||
samples: scaledSelfCPU,
|
||||
selfCPUPerc,
|
||||
totalCPUPerc,
|
||||
selfCPU: topN.CountExclusive,
|
||||
totalCPU: topN.CountInclusive,
|
||||
impactEstimates,
|
||||
selfAnnualCO2kgs: topN.selfAnnualCO2kgs,
|
||||
selfAnnualCostUSD: topN.selfAnnualCostUSD,
|
||||
totalAnnualCO2kgs: topN.totalAnnualCO2kgs,
|
||||
totalAnnualCostUSD: topN.totalAnnualCostUSD,
|
||||
subGroups: topN.subGroups,
|
||||
diff: calculateDiff(),
|
||||
};
|
||||
});
|
||||
const scaledSelfCPU = scaleValue({
|
||||
value: topN.CountExclusive,
|
||||
scaleFactor: baselineScaleFactor,
|
||||
});
|
||||
|
||||
const totalCPUPerc = (topN.CountInclusive / topNFunctions.selfCPU) * 100;
|
||||
const selfCPUPerc = (topN.CountExclusive / topNFunctions.selfCPU) * 100;
|
||||
|
||||
const impactEstimates =
|
||||
totalSeconds > 0
|
||||
? calculateImpactEstimates({
|
||||
countExclusive: topN.CountExclusive,
|
||||
countInclusive: topN.CountInclusive,
|
||||
totalSamples: topNFunctions.selfCPU,
|
||||
totalSeconds,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
function calculateDiff() {
|
||||
if (comparisonTopNFunctions && comparisonRow) {
|
||||
const comparisonScaledSelfCPU = scaleValue({
|
||||
value: comparisonRow.CountExclusive,
|
||||
scaleFactor: comparisonScaleFactor,
|
||||
});
|
||||
|
||||
const scaledDiffSamples = scaledSelfCPU - comparisonScaledSelfCPU;
|
||||
|
||||
return {
|
||||
id: comparisonRow.Id,
|
||||
rank: topN.Rank - comparisonRow.Rank,
|
||||
samples: scaledDiffSamples,
|
||||
selfCPU: comparisonRow.CountExclusive,
|
||||
totalCPU: comparisonRow.CountInclusive,
|
||||
selfCPUPerc:
|
||||
selfCPUPerc - (comparisonRow.CountExclusive / comparisonTopNFunctions.selfCPU) * 100,
|
||||
totalCPUPerc:
|
||||
totalCPUPerc - (comparisonRow.CountInclusive / comparisonTopNFunctions.selfCPU) * 100,
|
||||
selfAnnualCO2kgs: comparisonRow.selfAnnualCO2kgs,
|
||||
selfAnnualCostUSD: comparisonRow.selfAnnualCostUSD,
|
||||
totalAnnualCO2kgs: comparisonRow.totalAnnualCO2kgs,
|
||||
totalAnnualCostUSD: comparisonRow.totalAnnualCostUSD,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: topN.Id,
|
||||
rank: topN.Rank,
|
||||
frame: topN.Frame,
|
||||
samples: scaledSelfCPU,
|
||||
selfCPUPerc,
|
||||
totalCPUPerc,
|
||||
selfCPU: topN.CountExclusive,
|
||||
totalCPU: topN.CountInclusive,
|
||||
impactEstimates,
|
||||
selfAnnualCO2kgs: topN.selfAnnualCO2kgs,
|
||||
selfAnnualCostUSD: topN.selfAnnualCostUSD,
|
||||
totalAnnualCO2kgs: topN.totalAnnualCO2kgs,
|
||||
totalAnnualCostUSD: topN.totalAnnualCostUSD,
|
||||
subGroups: topN.subGroups,
|
||||
diff: calculateDiff(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function calculateBaseComparisonDiff({
|
||||
|
|
|
@ -21,6 +21,7 @@ export function EmbeddableFunctionsGrid({ data, totalSeconds, showFullScreenSele
|
|||
const [sortField, setSortField] = useState(TopNFunctionSortField.Rank);
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [searchFunctionName, setSearchFunctionName] = useState('');
|
||||
|
||||
return (
|
||||
<TopNFunctionsGrid
|
||||
|
@ -37,6 +38,8 @@ export function EmbeddableFunctionsGrid({ data, totalSeconds, showFullScreenSele
|
|||
}}
|
||||
isEmbedded
|
||||
showFullScreenSelector={showFullScreenSelector}
|
||||
onSearchFunctionNameChange={setSearchFunctionName}
|
||||
searchFunctionName={searchFunctionName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -240,7 +240,7 @@ const routes = {
|
|||
</RouteBreadcrumb>
|
||||
),
|
||||
params: t.type({
|
||||
query: t.partial({ pageIndex: toNumberRt }),
|
||||
query: t.partial({ pageIndex: toNumberRt, searchFunctionName: t.string }),
|
||||
}),
|
||||
},
|
||||
'/functions/differential': {
|
||||
|
@ -271,6 +271,7 @@ const routes = {
|
|||
baseline: toNumberRt,
|
||||
comparison: toNumberRt,
|
||||
pageIndex: toNumberRt,
|
||||
searchFunctionName: t.string,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
|
|
|
@ -40,6 +40,7 @@ export function DifferentialTopNFunctionsView() {
|
|||
pageIndex = 0,
|
||||
comparisonSortDirection,
|
||||
comparisonSortField,
|
||||
searchFunctionName = '',
|
||||
} = query;
|
||||
|
||||
const timeRange = useTimeRange({ rangeFrom, rangeTo });
|
||||
|
@ -128,10 +129,10 @@ export function DifferentialTopNFunctionsView() {
|
|||
|
||||
const isNormalizedByTime = normalizationMode === NormalizationMode.Time;
|
||||
|
||||
function handleOnFrameClick(functionName: string) {
|
||||
function handleOnFrameClick(value: string) {
|
||||
profilingRouter.push('/flamegraphs/flamegraph', {
|
||||
path: {},
|
||||
query: { ...query, searchText: functionName },
|
||||
query: { ...query, searchText: value },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -149,6 +150,13 @@ export function DifferentialTopNFunctionsView() {
|
|||
});
|
||||
}
|
||||
|
||||
function handleSearchFunctionNameChange(value: string) {
|
||||
profilingRouter.push('/functions/differential', {
|
||||
path: {},
|
||||
query: { ...query, searchFunctionName: value },
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (state.status === AsyncStatus.Settled || comparisonState.status === AsyncStatus.Settled) {
|
||||
onPageReady({
|
||||
|
@ -233,6 +241,8 @@ export function DifferentialTopNFunctionsView() {
|
|||
sortDirection={sortDirection}
|
||||
sortField={sortField}
|
||||
totalSeconds={totalSeconds}
|
||||
searchFunctionName={searchFunctionName}
|
||||
onSearchFunctionNameChange={handleSearchFunctionNameChange}
|
||||
/>
|
||||
</AsyncComponent>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -21,7 +21,15 @@ import { AsyncStatus } from '../../../hooks/use_async';
|
|||
export function TopNFunctionsView() {
|
||||
const { onPageReady } = usePerformanceContext();
|
||||
const { query } = useProfilingParams('/functions/topn');
|
||||
const { rangeFrom, rangeTo, kuery, sortDirection, sortField, pageIndex = 0 } = query;
|
||||
const {
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
kuery,
|
||||
sortDirection,
|
||||
sortField,
|
||||
pageIndex = 0,
|
||||
searchFunctionName = '',
|
||||
} = query;
|
||||
|
||||
const timeRange = useTimeRange({ rangeFrom, rangeTo });
|
||||
|
||||
|
@ -45,10 +53,10 @@ export function TopNFunctionsView() {
|
|||
|
||||
const profilingRouter = useProfilingRouter();
|
||||
|
||||
function handleOnFrameClick(functionName: string) {
|
||||
function handleOnFrameClick(value: string) {
|
||||
profilingRouter.push('/flamegraphs/flamegraph', {
|
||||
path: {},
|
||||
query: { ...query, searchText: functionName },
|
||||
query: { ...query, searchText: value },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -69,6 +77,14 @@ export function TopNFunctionsView() {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleSearchFunctionNameChange(value: string) {
|
||||
profilingRouter.push('/functions/topn', {
|
||||
path: {},
|
||||
query: { ...query, searchFunctionName: value },
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (state.status === AsyncStatus.Settled) {
|
||||
onPageReady({
|
||||
|
@ -83,6 +99,7 @@ export function TopNFunctionsView() {
|
|||
});
|
||||
}
|
||||
}, [state.status, state.data, onPageReady, rangeFrom, rangeTo]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup direction="column">
|
||||
|
@ -100,6 +117,8 @@ export function TopNFunctionsView() {
|
|||
sortField={sortField}
|
||||
sortDirection={sortDirection}
|
||||
onChangeSort={handleSortChange}
|
||||
searchFunctionName={searchFunctionName}
|
||||
onSearchFunctionNameChange={handleSearchFunctionNameChange}
|
||||
/>
|
||||
</AsyncComponent>
|
||||
</EuiFlexItem>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue