mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Profiling][OnWeek project] Improve Diff topN functions grid view. (#170008)
- ✅ Same height on both sides
- ✅ Maximize feature
- ❌ ~~Highlight the corresponding item on the other side~~
- ✅ Details of the corresponding item on the other side
dd81664e
-b5ae-403d-847c-b56ca8881277
This commit is contained in:
parent
e7eba99835
commit
74e46f6939
12 changed files with 597 additions and 106 deletions
|
@ -207,3 +207,21 @@ export const topNFunctionSortFieldRt = t.union([
|
|||
t.literal(TopNFunctionSortField.AnnualizedCo2),
|
||||
t.literal(TopNFunctionSortField.AnnualizedDollarCost),
|
||||
]);
|
||||
|
||||
export enum TopNComparisonFunctionSortField {
|
||||
ComparisonRank = 'comparison_rank',
|
||||
ComparisonFrame = 'comparison_frame',
|
||||
ComparisonSamples = 'comparison_samples',
|
||||
ComparisonSelfCPU = 'comparison_selfCPU',
|
||||
ComparisonTotalCPU = 'comparison_totalCPU',
|
||||
ComparisonDiff = 'comparison_diff',
|
||||
}
|
||||
|
||||
export const topNComparisonFunctionSortFieldRt = t.union([
|
||||
t.literal(TopNComparisonFunctionSortField.ComparisonRank),
|
||||
t.literal(TopNComparisonFunctionSortField.ComparisonFrame),
|
||||
t.literal(TopNComparisonFunctionSortField.ComparisonSamples),
|
||||
t.literal(TopNComparisonFunctionSortField.ComparisonSelfCPU),
|
||||
t.literal(TopNComparisonFunctionSortField.ComparisonTotalCPU),
|
||||
t.literal(TopNComparisonFunctionSortField.ComparisonDiff),
|
||||
]);
|
||||
|
|
|
@ -30,6 +30,8 @@ export {
|
|||
createTopNFunctions,
|
||||
TopNFunctionSortField,
|
||||
topNFunctionSortFieldRt,
|
||||
TopNComparisonFunctionSortField,
|
||||
topNComparisonFunctionSortFieldRt,
|
||||
} from './common/functions';
|
||||
|
||||
export type { CalleeTree } from './common/callee';
|
||||
|
|
|
@ -167,17 +167,8 @@ describe('Differential Functions page', () => {
|
|||
});
|
||||
cy.wait('@getTopNFunctions');
|
||||
cy.wait('@getTopNFunctions');
|
||||
cy.get('[data-test-subj="topNFunctionsGrid"] .euiDataGridRow').should('have.length.gt', 1);
|
||||
cy.get('[data-test-subj="TopNFunctionsComparisonGrid"] .euiDataGridRow').should(
|
||||
'have.length.gt',
|
||||
1
|
||||
);
|
||||
cy.get(
|
||||
'[data-test-subj="topNFunctionsGrid"] [data-test-subj="profilingStackFrameSummaryLink"]'
|
||||
).contains('vmlinux');
|
||||
cy.get(
|
||||
'[data-test-subj="TopNFunctionsComparisonGrid"] [data-test-subj="profilingStackFrameSummaryLink"]'
|
||||
).contains('vmlinux');
|
||||
cy.get('[data-test-subj="frame"]').contains('vmlinux');
|
||||
cy.get('[data-test-subj="comparison_frame"]').contains('vmlinux');
|
||||
|
||||
cy.addKqlFilter({
|
||||
key: 'process.thread.name',
|
||||
|
@ -190,12 +181,8 @@ describe('Differential Functions page', () => {
|
|||
});
|
||||
cy.wait('@getTopNFunctions');
|
||||
cy.wait('@getTopNFunctions');
|
||||
cy.get(
|
||||
'[data-test-subj="topNFunctionsGrid"] [data-test-subj="profilingStackFrameSummaryLink"]'
|
||||
).contains('libsystemd-shared-237.so');
|
||||
cy.get(
|
||||
'[data-test-subj="TopNFunctionsComparisonGrid"] [data-test-subj="profilingStackFrameSummaryLink"]'
|
||||
).contains('libjvm.so');
|
||||
cy.get('[data-test-subj="frame"]').contains('libsystemd-shared-237.so');
|
||||
cy.get('[data-test-subj="comparison_frame"]').contains('libjvm.so');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -43,6 +43,7 @@ const mockPlugin = {
|
|||
};
|
||||
|
||||
const mockCore = {
|
||||
uiSettings: { get: () => {} },
|
||||
application: {
|
||||
currentAppId$: new Observable(),
|
||||
getUrlForApp: (appId: string) => '',
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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 { EuiDataGridColumn, EuiDataGridColumnCellAction } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TopNComparisonFunctionSortField, TopNFunctionSortField } from '@kbn/profiling-utils';
|
||||
import React from 'react';
|
||||
import { CPULabelWithHint } from '../cpu_label_with_hint';
|
||||
import { LabelWithHint } from '../label_with_hint';
|
||||
|
||||
export const getColumns = (
|
||||
compareFrameAction: EuiDataGridColumnCellAction
|
||||
): EuiDataGridColumn[] => [
|
||||
{
|
||||
id: TopNFunctionSortField.Rank,
|
||||
actions: { showHide: false },
|
||||
displayAsText: 'Rank',
|
||||
initialWidth: 65,
|
||||
schema: 'numeric',
|
||||
},
|
||||
{
|
||||
id: TopNFunctionSortField.Frame,
|
||||
actions: { showHide: false },
|
||||
displayAsText: i18n.translate('xpack.profiling.functionsView.functionColumnLabel', {
|
||||
defaultMessage: 'Function',
|
||||
}),
|
||||
cellActions: [compareFrameAction],
|
||||
},
|
||||
{
|
||||
id: TopNFunctionSortField.Samples,
|
||||
initialWidth: 120,
|
||||
schema: 'numeric',
|
||||
actions: { showHide: false },
|
||||
display: (
|
||||
<LabelWithHint
|
||||
label={i18n.translate('xpack.profiling.functionsView.samplesColumnLabel', {
|
||||
defaultMessage: 'Samples',
|
||||
})}
|
||||
hint={i18n.translate('xpack.profiling.functionsView.samplesColumnLabel.hint', {
|
||||
defaultMessage: 'Estimated values',
|
||||
})}
|
||||
labelSize="s"
|
||||
labelStyle={{ fontWeight: 700 }}
|
||||
iconSize="s"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: TopNFunctionSortField.SelfCPU,
|
||||
actions: { showHide: false },
|
||||
schema: 'numeric',
|
||||
initialWidth: 120,
|
||||
display: (
|
||||
<CPULabelWithHint type="self" labelSize="s" labelStyle={{ fontWeight: 700 }} iconSize="s" />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: TopNFunctionSortField.TotalCPU,
|
||||
actions: { showHide: false },
|
||||
schema: 'numeric',
|
||||
initialWidth: 120,
|
||||
display: (
|
||||
<CPULabelWithHint type="total" labelSize="s" labelStyle={{ fontWeight: 700 }} iconSize="s" />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: TopNComparisonFunctionSortField.ComparisonRank,
|
||||
actions: { showHide: false },
|
||||
schema: 'numeric',
|
||||
displayAsText: 'Rank',
|
||||
initialWidth: 69,
|
||||
displayHeaderCellProps: { className: 'thickBorderLeft' },
|
||||
},
|
||||
{
|
||||
id: TopNComparisonFunctionSortField.ComparisonFrame,
|
||||
actions: { showHide: false },
|
||||
displayAsText: i18n.translate('xpack.profiling.functionsView.functionColumnLabel', {
|
||||
defaultMessage: 'Function',
|
||||
}),
|
||||
cellActions: [compareFrameAction],
|
||||
},
|
||||
{
|
||||
id: TopNComparisonFunctionSortField.ComparisonSamples,
|
||||
actions: { showHide: false },
|
||||
schema: 'numeric',
|
||||
initialWidth: 120,
|
||||
display: (
|
||||
<LabelWithHint
|
||||
label={i18n.translate('xpack.profiling.functionsView.samplesColumnLabel', {
|
||||
defaultMessage: 'Samples',
|
||||
})}
|
||||
hint={i18n.translate('xpack.profiling.functionsView.samplesColumnLabel.hint', {
|
||||
defaultMessage: 'Estimated values',
|
||||
})}
|
||||
labelSize="s"
|
||||
labelStyle={{ fontWeight: 700 }}
|
||||
iconSize="s"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: TopNComparisonFunctionSortField.ComparisonSelfCPU,
|
||||
actions: { showHide: false },
|
||||
schema: 'numeric',
|
||||
initialWidth: 120,
|
||||
display: (
|
||||
<CPULabelWithHint type="self" labelSize="s" labelStyle={{ fontWeight: 700 }} iconSize="s" />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: TopNComparisonFunctionSortField.ComparisonTotalCPU,
|
||||
actions: { showHide: false },
|
||||
schema: 'numeric',
|
||||
initialWidth: 120,
|
||||
display: (
|
||||
<CPULabelWithHint type="total" labelSize="s" labelStyle={{ fontWeight: 700 }} iconSize="s" />
|
||||
),
|
||||
},
|
||||
{
|
||||
displayAsText: 'Diff',
|
||||
actions: { showHide: false },
|
||||
id: TopNComparisonFunctionSortField.ComparisonDiff,
|
||||
initialWidth: 70,
|
||||
isSortable: false,
|
||||
},
|
||||
];
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiBasicTable,
|
||||
EuiDataGridColumnCellAction,
|
||||
EuiDataGridColumnCellActionProps,
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/react';
|
||||
import { getCalleeFunction } from '@kbn/profiling-utils';
|
||||
import { getFrameIdentification, isComparisonColumn, SelectedFrame } from '.';
|
||||
import { IFunctionRow } from '../topn_functions/utils';
|
||||
|
||||
interface Props {
|
||||
baseRows: IFunctionRow[];
|
||||
comparisonRows: IFunctionRow[];
|
||||
selectedFrame?: SelectedFrame;
|
||||
onClick: (selectedFrame?: SelectedFrame) => void;
|
||||
}
|
||||
|
||||
export const getCompareFrameAction =
|
||||
({ baseRows, comparisonRows, selectedFrame, onClick }: Props): EuiDataGridColumnCellAction =>
|
||||
({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => {
|
||||
const isComparison = isComparisonColumn(columnId);
|
||||
const currentRow = isComparison ? comparisonRows[rowIndex] : baseRows[rowIndex];
|
||||
if (currentRow === undefined) {
|
||||
return null;
|
||||
}
|
||||
const currentFrameId = getFrameIdentification(currentRow.frame);
|
||||
|
||||
const isOpen = selectedFrame
|
||||
? selectedFrame.currentFrameId === currentFrameId &&
|
||||
selectedFrame.isComparison === isComparison
|
||||
: false;
|
||||
|
||||
const compareRow = isComparison
|
||||
? baseRows.find((item) => getFrameIdentification(item.frame) === currentFrameId)
|
||||
: comparisonRows.find((item) => getFrameIdentification(item.frame) === currentFrameId);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
button={
|
||||
<Component
|
||||
onClick={() => {
|
||||
onClick({ currentFrameId, isComparison });
|
||||
}}
|
||||
iconType="inspect"
|
||||
>
|
||||
{i18n.translate('xpack.profiling.compareFrame.component.findLabel', {
|
||||
defaultMessage: 'Find corresponding frame',
|
||||
})}
|
||||
</Component>
|
||||
}
|
||||
isOpen={isOpen}
|
||||
closePopover={() => {
|
||||
onClick(undefined);
|
||||
}}
|
||||
anchorPosition="upRight"
|
||||
css={css`
|
||||
.euiPopover__anchor {
|
||||
align-items: start;
|
||||
display: flex;
|
||||
}
|
||||
`}
|
||||
>
|
||||
{compareRow ? (
|
||||
<div style={{ maxWidth: 400 }}>
|
||||
<EuiPopoverTitle paddingSize="s">
|
||||
{isComparison
|
||||
? i18n.translate('xpack.profiling.diffTopNFunctions.baseLineFunction', {
|
||||
defaultMessage: 'Baseline function',
|
||||
})
|
||||
: i18n.translate('xpack.profiling.diffTopNFunctions.comparisonLineFunction', {
|
||||
defaultMessage: 'Comparison function',
|
||||
})}
|
||||
</EuiPopoverTitle>
|
||||
<EuiTitle size="xs">
|
||||
<EuiText>{getCalleeFunction(compareRow.frame)}</EuiText>
|
||||
</EuiTitle>
|
||||
<EuiBasicTable
|
||||
items={[compareRow]}
|
||||
columns={[
|
||||
{ field: 'rank', name: 'Rank' },
|
||||
{
|
||||
field: 'samples',
|
||||
name: 'Samples',
|
||||
render: (_, value) => value.samples.toLocaleString(),
|
||||
},
|
||||
{
|
||||
field: 'selfCPUPerc',
|
||||
name: 'Self CPU',
|
||||
render: (_, value) => `${value.selfCPUPerc.toFixed(2)}%`,
|
||||
},
|
||||
{
|
||||
field: 'totalCPUPerc',
|
||||
name: 'Total CPU',
|
||||
render: (_, value) => `${value.totalCPUPerc.toFixed(2)}%`,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<EuiText color="subdued" size="s">
|
||||
{i18n.translate('xpack.profiling.diffTopNFunctions.noCorrespondingValueFound', {
|
||||
defaultMessage: 'No corresponding value found',
|
||||
})}
|
||||
</EuiText>
|
||||
)}
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiDataGrid,
|
||||
EuiDataGridCellValueElementProps,
|
||||
EuiDataGridColumn,
|
||||
EuiDataGridSorting,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
getCalleeFunction,
|
||||
StackFrameMetadata,
|
||||
TopNComparisonFunctionSortField,
|
||||
TopNFunctions,
|
||||
TopNFunctionSortField,
|
||||
} from '@kbn/profiling-utils';
|
||||
import { orderBy } from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useCalculateImpactEstimate } from '../../hooks/use_calculate_impact_estimates';
|
||||
import { FunctionRow } from '../topn_functions/function_row';
|
||||
import { getFunctionsRows, IFunctionRow } from '../topn_functions/utils';
|
||||
import { getColumns } from './get_columns';
|
||||
import { getCompareFrameAction } from './get_compare_frame_action';
|
||||
|
||||
const removeComparisonFromId = (id: string) => id.replace('comparison_', '');
|
||||
export const isComparisonColumn = (id: string) => id.startsWith('comparison_');
|
||||
|
||||
type SortDirection = 'asc' | 'desc';
|
||||
|
||||
function sortRows(data: IFunctionRow[], sortField: string, sortDirection: SortDirection) {
|
||||
switch (sortField) {
|
||||
case TopNFunctionSortField.Frame:
|
||||
return orderBy(data, (row) => getCalleeFunction(row.frame), sortDirection);
|
||||
case TopNFunctionSortField.SelfCPU:
|
||||
return orderBy(data, (row) => row.selfCPUPerc, sortDirection);
|
||||
case TopNFunctionSortField.TotalCPU:
|
||||
return orderBy(data, (row) => row.totalCPUPerc, sortDirection);
|
||||
default:
|
||||
return orderBy(data, sortField, sortDirection);
|
||||
}
|
||||
}
|
||||
|
||||
export type OnChangeSortParams =
|
||||
| { sortField: TopNFunctionSortField; sortDirection: SortDirection }
|
||||
| {
|
||||
comparisonSortField: TopNComparisonFunctionSortField;
|
||||
comparisonSortDirection: SortDirection;
|
||||
};
|
||||
|
||||
export function getFrameIdentification(frame: StackFrameMetadata) {
|
||||
return [
|
||||
frame.SourceFilename,
|
||||
frame.FunctionName,
|
||||
frame.ExeFileName,
|
||||
frame.FileID,
|
||||
frame.AddressOrLine,
|
||||
].join('|');
|
||||
}
|
||||
|
||||
export interface SelectedFrame {
|
||||
currentFrameId?: string;
|
||||
isComparison: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
base?: TopNFunctions;
|
||||
baselineScaleFactor?: number;
|
||||
comparison?: TopNFunctions;
|
||||
comparisonScaleFactor?: number;
|
||||
onChangePage: (nextPage: number) => void;
|
||||
onChangeSort: (sorting: OnChangeSortParams) => void;
|
||||
onFrameClick?: (functionName: string) => void;
|
||||
pageIndex: number;
|
||||
sortDirection: 'asc' | 'desc';
|
||||
sortField: TopNFunctionSortField;
|
||||
comparisonSortDirection: 'asc' | 'desc';
|
||||
comparisonSortField: TopNComparisonFunctionSortField;
|
||||
totalSeconds: number;
|
||||
}
|
||||
|
||||
export function DifferentialTopNFunctionsGrid({
|
||||
base,
|
||||
baselineScaleFactor,
|
||||
comparison,
|
||||
comparisonScaleFactor,
|
||||
onChangePage,
|
||||
onChangeSort,
|
||||
pageIndex,
|
||||
sortDirection,
|
||||
sortField,
|
||||
totalSeconds,
|
||||
onFrameClick,
|
||||
comparisonSortDirection,
|
||||
comparisonSortField,
|
||||
}: Props) {
|
||||
const calculateImpactEstimates = useCalculateImpactEstimate();
|
||||
const [selectedFrame, setSelectedFrame] = useState<SelectedFrame | undefined>();
|
||||
const theme = useEuiTheme();
|
||||
|
||||
const totalCount = useMemo(() => {
|
||||
if (!base || !base.TotalCount) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return base.TotalCount;
|
||||
}, [base]);
|
||||
|
||||
function onSort(newSortingColumns: EuiDataGridSorting['columns']) {
|
||||
// As newSortingColumns is an array and we only sort by a single field for both base and comparison
|
||||
// I need to look for the item that is not the same as in the URL to identify what's the side being sorted.
|
||||
const sortingItem = newSortingColumns.reverse().find((item) => {
|
||||
const isComparison = isComparisonColumn(item.id);
|
||||
if (isComparison) {
|
||||
return !(comparisonSortField === item.id && comparisonSortDirection === item.direction);
|
||||
}
|
||||
return !(sortField === item.id && sortDirection === item.direction);
|
||||
});
|
||||
if (sortingItem) {
|
||||
const isComparison = isComparisonColumn(sortingItem.id);
|
||||
onChangeSort(
|
||||
isComparison
|
||||
? {
|
||||
comparisonSortDirection: sortingItem.direction,
|
||||
comparisonSortField: sortingItem.id as TopNComparisonFunctionSortField,
|
||||
}
|
||||
: {
|
||||
sortDirection: sortingItem.direction,
|
||||
sortField: sortingItem.id as TopNFunctionSortField,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { baseRows, comparisonRows } = useMemo(() => {
|
||||
return {
|
||||
baseRows: getFunctionsRows({
|
||||
calculateImpactEstimates,
|
||||
topNFunctions: base,
|
||||
totalSeconds: 900,
|
||||
}),
|
||||
comparisonRows: getFunctionsRows({
|
||||
baselineScaleFactor,
|
||||
calculateImpactEstimates,
|
||||
comparisonScaleFactor,
|
||||
comparisonTopNFunctions: base,
|
||||
topNFunctions: comparison,
|
||||
totalSeconds,
|
||||
}),
|
||||
};
|
||||
}, [
|
||||
base,
|
||||
baselineScaleFactor,
|
||||
calculateImpactEstimates,
|
||||
comparison,
|
||||
comparisonScaleFactor,
|
||||
totalSeconds,
|
||||
]);
|
||||
|
||||
const columns: EuiDataGridColumn[] = useMemo(() => {
|
||||
const compareFrameAction = getCompareFrameAction({
|
||||
baseRows,
|
||||
comparisonRows,
|
||||
onClick: setSelectedFrame,
|
||||
selectedFrame,
|
||||
});
|
||||
return getColumns(compareFrameAction);
|
||||
}, [baseRows, comparisonRows, selectedFrame]);
|
||||
|
||||
const sortedBaseRows = useMemo(() => {
|
||||
return sortRows(baseRows, sortField, sortDirection);
|
||||
}, [baseRows, sortDirection, sortField]);
|
||||
|
||||
const sortedComparisonRows = useMemo(() => {
|
||||
return sortRows(
|
||||
comparisonRows,
|
||||
removeComparisonFromId(comparisonSortField),
|
||||
comparisonSortDirection
|
||||
);
|
||||
}, [comparisonRows, comparisonSortDirection, comparisonSortField]);
|
||||
|
||||
const [visibleColumns, setVisibleColumns] = useState(columns.map(({ id }) => id));
|
||||
|
||||
function CellValue({ rowIndex, columnId, setCellProps }: EuiDataGridCellValueElementProps) {
|
||||
const isComparison = isComparisonColumn(columnId);
|
||||
const data = isComparison ? sortedComparisonRows[rowIndex] : sortedBaseRows[rowIndex];
|
||||
|
||||
useEffect(() => {
|
||||
// Add thick border to divide the baseline and comparison columns
|
||||
if (isComparison && columnId === TopNComparisonFunctionSortField.ComparisonRank) {
|
||||
setCellProps({
|
||||
style: { borderLeft: theme.euiTheme.border.thick },
|
||||
});
|
||||
} else if (columnId === TopNFunctionSortField.TotalCPU) {
|
||||
setCellProps({
|
||||
style: { borderRight: theme.euiTheme.border.thin },
|
||||
});
|
||||
}
|
||||
}, [columnId, isComparison, setCellProps]);
|
||||
|
||||
if (data === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj={columnId}>
|
||||
<FunctionRow
|
||||
functionRow={data}
|
||||
columnId={removeComparisonFromId(columnId)}
|
||||
setCellProps={setCellProps}
|
||||
totalCount={totalCount}
|
||||
onFrameClick={onFrameClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const rowCount = Math.min(Math.max(sortedBaseRows.length, sortedComparisonRows.length), 100);
|
||||
|
||||
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}
|
||||
columnVisibility={{ visibleColumns, setVisibleColumns }}
|
||||
rowCount={rowCount}
|
||||
renderCellValue={CellValue}
|
||||
sorting={{
|
||||
columns: [
|
||||
{ id: sortField, direction: sortDirection },
|
||||
{ id: comparisonSortField, direction: comparisonSortDirection },
|
||||
],
|
||||
onSort,
|
||||
}}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize: 50,
|
||||
// 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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -112,7 +112,7 @@ function DiffColumn({ diff, setCellProps }: DiffColumnProps) {
|
|||
const dangerColor = useEuiBackgroundColor('danger');
|
||||
|
||||
useEffect(() => {
|
||||
if (diff && diff.rank > 0) {
|
||||
if (diff && diff.rank !== 0) {
|
||||
const color = diff.rank > 0 ? 'success' : 'danger';
|
||||
setCellProps({
|
||||
style: { backgroundColor: color === 'success' ? successColor : dangerColor },
|
||||
|
|
|
@ -33,6 +33,7 @@ export function scaleValue({ value, scaleFactor = 1 }: { value: number; scaleFac
|
|||
}
|
||||
|
||||
export interface IFunctionRow {
|
||||
id: string;
|
||||
rank: number;
|
||||
frame: StackFrameMetadata;
|
||||
samples: number;
|
||||
|
@ -106,6 +107,7 @@ export function getFunctionsRows({
|
|||
const scaledDiffSamples = scaledSelfCPU - comparisonScaledSelfCPU;
|
||||
|
||||
return {
|
||||
id: comparisonRow.Id,
|
||||
rank: topN.Rank - comparisonRow.Rank,
|
||||
samples: scaledDiffSamples,
|
||||
selfCPU: comparisonRow.CountExclusive,
|
||||
|
@ -120,6 +122,7 @@ export function getFunctionsRows({
|
|||
}
|
||||
|
||||
return {
|
||||
id: topN.Id,
|
||||
rank: topN.Rank,
|
||||
frame: topN.Frame,
|
||||
samples: scaledSelfCPU,
|
||||
|
|
|
@ -6,33 +6,35 @@
|
|||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { toNumberRt } from '@kbn/io-ts-utils';
|
||||
import {
|
||||
StackTracesDisplayOption,
|
||||
TopNComparisonFunctionSortField,
|
||||
topNComparisonFunctionSortFieldRt,
|
||||
TopNFunctionSortField,
|
||||
topNFunctionSortFieldRt,
|
||||
TopNType,
|
||||
} from '@kbn/profiling-utils';
|
||||
import { createRouter, Outlet } from '@kbn/typed-react-router-config';
|
||||
import * as t from 'io-ts';
|
||||
import React from 'react';
|
||||
import {
|
||||
StackTracesDisplayOption,
|
||||
TopNType,
|
||||
TopNFunctionSortField,
|
||||
topNFunctionSortFieldRt,
|
||||
} from '@kbn/profiling-utils';
|
||||
import {
|
||||
indexLifecyclePhaseRt,
|
||||
IndexLifecyclePhaseSelectOption,
|
||||
} from '../../common/storage_explorer';
|
||||
import { ComparisonMode, NormalizationMode } from '../components/normalization_menu';
|
||||
import { RedirectTo } from '../components/redirect_to';
|
||||
import { AddDataTabs, AddDataView } from '../views/add_data_view';
|
||||
import { DeleteDataView } from '../views/delete_data_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';
|
||||
import { AddDataTabs, AddDataView } from '../views/add_data_view';
|
||||
import { Settings } from '../views/settings';
|
||||
import { StackTracesView } from '../views/stack_traces_view';
|
||||
import { StorageExplorerView } from '../views/storage_explorer';
|
||||
import { RouteBreadcrumb } from './route_breadcrumb';
|
||||
import { DeleteDataView } from '../views/delete_data_view';
|
||||
import { Settings } from '../views/settings';
|
||||
|
||||
const routes = {
|
||||
'/settings': {
|
||||
|
@ -253,6 +255,8 @@ const routes = {
|
|||
t.literal(NormalizationMode.Scale),
|
||||
t.literal(NormalizationMode.Time),
|
||||
]),
|
||||
comparisonSortField: topNComparisonFunctionSortFieldRt,
|
||||
comparisonSortDirection: t.union([t.literal('asc'), t.literal('desc')]),
|
||||
}),
|
||||
t.partial({
|
||||
baseline: toNumberRt,
|
||||
|
@ -267,6 +271,8 @@ const routes = {
|
|||
comparisonRangeTo: 'now',
|
||||
comparisonKuery: '',
|
||||
normalizationMode: NormalizationMode.Time,
|
||||
comparisonSortField: TopNComparisonFunctionSortField.ComparisonRank,
|
||||
comparisonSortDirection: 'asc',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -4,28 +4,21 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
EuiDataGridRefProps,
|
||||
EuiDataGridSorting,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { TopNFunctionSortField } from '@kbn/profiling-utils';
|
||||
import React, { useRef } from 'react';
|
||||
import { GridOnScrollProps } from 'react-window';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { AsyncComponent } from '../../../components/async_component';
|
||||
import { useProfilingDependencies } from '../../../components/contexts/profiling_dependencies/use_profiling_dependencies';
|
||||
import { FramesSummary } from '../../../components/frames_summary';
|
||||
import {
|
||||
DifferentialTopNFunctionsGrid,
|
||||
OnChangeSortParams,
|
||||
} from '../../../components/differential_topn_functions_grid';
|
||||
import {
|
||||
NormalizationMenu,
|
||||
NormalizationMode,
|
||||
NormalizationOptions,
|
||||
} from '../../../components/normalization_menu';
|
||||
import { PrimaryAndComparisonSearchBar } from '../../../components/primary_and_comparison_search_bar';
|
||||
import { TopNFunctionsGrid } from '../../../components/topn_functions';
|
||||
import { AsyncStatus } from '../../../hooks/use_async';
|
||||
import { useProfilingParams } from '../../../hooks/use_profiling_params';
|
||||
import { useProfilingRouter } from '../../../hooks/use_profiling_router';
|
||||
|
@ -34,8 +27,6 @@ import { useTimeRange } from '../../../hooks/use_time_range';
|
|||
import { useTimeRangeAsync } from '../../../hooks/use_time_range_async';
|
||||
|
||||
export function DifferentialTopNFunctionsView() {
|
||||
const baseGridRef = useRef<EuiDataGridRefProps>(null);
|
||||
const comparisonGridRef = useRef<EuiDataGridRefProps>(null);
|
||||
const { query } = useProfilingParams('/functions/differential');
|
||||
const {
|
||||
rangeFrom,
|
||||
|
@ -50,6 +41,8 @@ export function DifferentialTopNFunctionsView() {
|
|||
baseline = 1,
|
||||
comparison = 1,
|
||||
pageIndex = 0,
|
||||
comparisonSortDirection,
|
||||
comparisonSortField,
|
||||
} = query;
|
||||
|
||||
const timeRange = useTimeRange({ rangeFrom, rangeTo });
|
||||
|
@ -147,20 +140,6 @@ export function DifferentialTopNFunctionsView() {
|
|||
});
|
||||
}
|
||||
|
||||
function handleBaseGridScroll(scroll: GridOnScrollProps) {
|
||||
if (comparisonGridRef?.current?.scrollTo) {
|
||||
comparisonGridRef.current.scrollTo({
|
||||
scrollTop: scroll.scrollTop,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleComparisonGridScroll(scroll: GridOnScrollProps) {
|
||||
if (baseGridRef?.current?.scrollTo) {
|
||||
baseGridRef.current.scrollTo({ scrollTop: scroll.scrollTop });
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(nextPage: number) {
|
||||
profilingRouter.push('/functions/differential', {
|
||||
path: {},
|
||||
|
@ -168,14 +147,10 @@ export function DifferentialTopNFunctionsView() {
|
|||
});
|
||||
}
|
||||
|
||||
function handleSortChange(sorting: EuiDataGridSorting['columns'][0]) {
|
||||
function handleOnSort(sorting: OnChangeSortParams) {
|
||||
profilingRouter.push('/functions/differential', {
|
||||
path: {},
|
||||
query: {
|
||||
...query,
|
||||
sortField: sorting.id as TopNFunctionSortField,
|
||||
sortDirection: sorting.direction,
|
||||
},
|
||||
query: { ...query, ...sorting },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -223,50 +198,27 @@ export function DifferentialTopNFunctionsView() {
|
|||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="row" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<AsyncComponent {...state} size="xl" alignTop>
|
||||
<TopNFunctionsGrid
|
||||
ref={baseGridRef}
|
||||
topNFunctions={state.data}
|
||||
totalSeconds={timeRange.inSeconds.end - timeRange.inSeconds.start}
|
||||
isDifferentialView={true}
|
||||
onFrameClick={handleOnFrameClick}
|
||||
baselineScaleFactor={isNormalizedByTime ? baselineTime : baseline}
|
||||
onScroll={handleBaseGridScroll}
|
||||
pageIndex={pageIndex}
|
||||
onChangePage={handlePageChange}
|
||||
sortField={sortField}
|
||||
sortDirection={sortDirection}
|
||||
onChangeSort={handleSortChange}
|
||||
/>
|
||||
</AsyncComponent>
|
||||
</EuiFlexItem>
|
||||
{comparisonTimeRange.inSeconds.start && comparisonTimeRange.inSeconds.end ? (
|
||||
<EuiFlexItem>
|
||||
<AsyncComponent {...comparisonState} size="xl" alignTop>
|
||||
<TopNFunctionsGrid
|
||||
ref={comparisonGridRef}
|
||||
topNFunctions={comparisonState.data}
|
||||
baselineScaleFactor={isNormalizedByTime ? comparisonTime : comparison}
|
||||
comparisonTopNFunctions={state.data}
|
||||
comparisonScaleFactor={isNormalizedByTime ? baselineTime : baseline}
|
||||
totalSeconds={totalSeconds}
|
||||
isDifferentialView={true}
|
||||
onFrameClick={handleOnFrameClick}
|
||||
onScroll={handleComparisonGridScroll}
|
||||
showDiffColumn
|
||||
pageIndex={pageIndex}
|
||||
onChangePage={handlePageChange}
|
||||
sortField={sortField}
|
||||
sortDirection={sortDirection}
|
||||
onChangeSort={handleSortChange}
|
||||
dataTestSubj="TopNFunctionsComparisonGrid"
|
||||
/>
|
||||
</AsyncComponent>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
<AsyncComponent
|
||||
{...(comparisonState.status === AsyncStatus.Loading ? comparisonState : state)}
|
||||
size="xl"
|
||||
alignTop
|
||||
>
|
||||
<DifferentialTopNFunctionsGrid
|
||||
base={state.data}
|
||||
comparison={comparisonState.data}
|
||||
baselineScaleFactor={isNormalizedByTime ? comparisonTime : comparison}
|
||||
comparisonScaleFactor={isNormalizedByTime ? baselineTime : baseline}
|
||||
totalSeconds={totalSeconds}
|
||||
pageIndex={pageIndex}
|
||||
onChangePage={handlePageChange}
|
||||
onChangeSort={handleOnSort}
|
||||
sortField={sortField}
|
||||
sortDirection={sortDirection}
|
||||
onFrameClick={handleOnFrameClick}
|
||||
comparisonSortDirection={comparisonSortDirection}
|
||||
comparisonSortField={comparisonSortField}
|
||||
/>
|
||||
</AsyncComponent>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { EuiPageHeaderContentProps } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { TopNComparisonFunctionSortField } from '@kbn/profiling-utils';
|
||||
import { NormalizationMode } from '../../components/normalization_menu';
|
||||
import { ProfilingAppPageTemplate } from '../../components/profiling_app_page_template';
|
||||
import { RedirectTo } from '../../components/redirect_to';
|
||||
|
@ -51,6 +52,12 @@ export function FunctionsView({ children }: { children: React.ReactElement }) {
|
|||
comparisonKuery: query.kuery,
|
||||
normalizationMode:
|
||||
'normalizationMode' in query ? query.normalizationMode : NormalizationMode.Time,
|
||||
comparisonSortField:
|
||||
'comparisonSortField' in query
|
||||
? query.comparisonSortField
|
||||
: TopNComparisonFunctionSortField.ComparisonRank,
|
||||
comparisonSortDirection:
|
||||
'comparisonSortDirection' in query ? query.comparisonSortDirection : 'asc',
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue