mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Profiling] Add Co2 and dollar cost columns and show more information action to functions table (#154097)
This PR adds: - Annualized Co2 column - Annualized dollar cost column - Show more information action that opens the details of the selected function in a flyout. <img width="1569" alt="Screenshot 2023-03-30 at 3 33 12 PM" src="https://user-images.githubusercontent.com/55978943/228944955-c0f3b7a4-6fa2-43fc-b388-35721f0ce256.png"> <img width="1585" alt="Screenshot 2023-03-30 at 3 33 22 PM" src="https://user-images.githubusercontent.com/55978943/228944959-51cca82e-81f0-413d-a69e-0289bb62523e.png">
This commit is contained in:
parent
7ea4722fb2
commit
c00df762c2
19 changed files with 358 additions and 202 deletions
|
@ -1,142 +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, EuiHorizontalRule, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { getImpactRows } from './get_impact_rows';
|
||||
import { getInformationRows } from './get_information_rows';
|
||||
|
||||
interface Props {
|
||||
frame?: {
|
||||
fileID: string;
|
||||
frameType: number;
|
||||
exeFileName: string;
|
||||
addressOrLine: number;
|
||||
functionName: string;
|
||||
sourceFileName: string;
|
||||
sourceLine: number;
|
||||
countInclusive: number;
|
||||
countExclusive: number;
|
||||
};
|
||||
totalSamples: number;
|
||||
totalSeconds: number;
|
||||
}
|
||||
|
||||
function KeyValueList({ rows }: { rows: Array<{ label: string; value: React.ReactNode }> }) {
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{rows.map((row, index) => (
|
||||
<>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem grow>{row.label}:</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} style={{ alignSelf: 'flex-end', overflowWrap: 'anywhere' }}>
|
||||
{row.value}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{index < rows.length - 1 ? (
|
||||
<EuiFlexItem>
|
||||
<EuiHorizontalRule size="full" margin="none" />
|
||||
</EuiFlexItem>
|
||||
) : undefined}
|
||||
</>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function FlamegraphFrameInformationPanel({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
{i18n.translate('xpack.profiling.flameGraphInformationWindowTitle', {
|
||||
defaultMessage: 'Frame information',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>{children}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
export function FlamegraphInformationWindow({ frame, totalSamples, totalSeconds }: Props) {
|
||||
if (!frame) {
|
||||
return (
|
||||
<FlamegraphFrameInformationPanel>
|
||||
<EuiText>
|
||||
{i18n.translate('xpack.profiling.flamegraphInformationWindow.selectFrame', {
|
||||
defaultMessage: 'Click on a frame to display more information',
|
||||
})}
|
||||
</EuiText>
|
||||
</FlamegraphFrameInformationPanel>
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
fileID,
|
||||
frameType,
|
||||
exeFileName,
|
||||
addressOrLine,
|
||||
functionName,
|
||||
sourceFileName,
|
||||
sourceLine,
|
||||
countInclusive,
|
||||
countExclusive,
|
||||
} = frame;
|
||||
|
||||
const informationRows = getInformationRows({
|
||||
fileID,
|
||||
frameType,
|
||||
exeFileName,
|
||||
addressOrLine,
|
||||
functionName,
|
||||
sourceFileName,
|
||||
sourceLine,
|
||||
});
|
||||
|
||||
const impactRows = getImpactRows({
|
||||
countInclusive,
|
||||
countExclusive,
|
||||
totalSamples,
|
||||
totalSeconds,
|
||||
});
|
||||
|
||||
return (
|
||||
<FlamegraphFrameInformationPanel>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<KeyValueList rows={informationRows} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xxs">
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
'xpack.profiling.flameGraphInformationWindow.impactEstimatesTitle',
|
||||
{ defaultMessage: 'Impact estimates' }
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<KeyValueList rows={impactRows} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</FlamegraphFrameInformationPanel>
|
||||
);
|
||||
}
|
|
@ -49,8 +49,7 @@ export function FlameGraphsView({ children }: { children: React.ReactElement })
|
|||
const baselineScale: number = get(query, 'baseline', 1);
|
||||
const comparisonScale: number = get(query, 'comparison', 1);
|
||||
|
||||
const totalSeconds =
|
||||
(new Date(timeRange.end).getTime() - new Date(timeRange.start).getTime()) / 1000;
|
||||
const totalSeconds = timeRange.inSeconds.end - timeRange.inSeconds.start;
|
||||
const totalComparisonSeconds =
|
||||
(new Date(comparisonTimeRange.end!).getTime() -
|
||||
new Date(comparisonTimeRange.start!).getTime()) /
|
||||
|
@ -75,15 +74,15 @@ export function FlameGraphsView({ children }: { children: React.ReactElement })
|
|||
return Promise.all([
|
||||
fetchElasticFlamechart({
|
||||
http,
|
||||
timeFrom: new Date(timeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(timeRange.end).getTime() / 1000,
|
||||
timeFrom: timeRange.inSeconds.start,
|
||||
timeTo: timeRange.inSeconds.end,
|
||||
kuery,
|
||||
}),
|
||||
comparisonTimeRange.start && comparisonTimeRange.end
|
||||
comparisonTimeRange.inSeconds.start && comparisonTimeRange.inSeconds.end
|
||||
? fetchElasticFlamechart({
|
||||
http,
|
||||
timeFrom: new Date(comparisonTimeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(comparisonTimeRange.end).getTime() / 1000,
|
||||
timeFrom: comparisonTimeRange.inSeconds.start,
|
||||
timeTo: comparisonTimeRange.inSeconds.end,
|
||||
kuery: comparisonKuery,
|
||||
})
|
||||
: Promise.resolve(undefined),
|
||||
|
@ -95,11 +94,11 @@ export function FlameGraphsView({ children }: { children: React.ReactElement })
|
|||
});
|
||||
},
|
||||
[
|
||||
timeRange.start,
|
||||
timeRange.end,
|
||||
timeRange.inSeconds.start,
|
||||
timeRange.inSeconds.end,
|
||||
kuery,
|
||||
comparisonTimeRange.start,
|
||||
comparisonTimeRange.end,
|
||||
comparisonTimeRange.inSeconds.start,
|
||||
comparisonTimeRange.inSeconds.end,
|
||||
comparisonKuery,
|
||||
fetchElasticFlamechart,
|
||||
]
|
||||
|
|
|
@ -18,10 +18,10 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { isNumber } from 'lodash';
|
||||
import React from 'react';
|
||||
import { calculateImpactEstimates } from '../../utils/calculate_impact_estimates';
|
||||
import { asCost } from '../../utils/formatters/as_cost';
|
||||
import { asPercentage } from '../../utils/formatters/as_percentage';
|
||||
import { asWeight } from '../../utils/formatters/as_weight';
|
||||
import { calculateImpactEstimates } from '../flame_graphs_view/calculate_impact_estimates';
|
||||
import { TooltipRow } from './tooltip_row';
|
||||
|
||||
interface Props {
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
*/
|
||||
|
||||
import { Chart, Datum, Flame, FlameLayerValue, PartialTheme, Settings } from '@elastic/charts';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiFlyout, EuiFlyoutBody, useEuiTheme } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui';
|
||||
import { Maybe } from '@kbn/observability-plugin/common/typings';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { ElasticFlameGraph, FlameGraphComparisonMode } from '../../../common/flamegraph';
|
||||
import { getFlamegraphModel } from '../../utils/get_flamegraph_model';
|
||||
import { FlamegraphInformationWindow } from '../flame_graphs_view/flamegraph_information_window';
|
||||
import { FlameGraphLegend } from '../flame_graphs_view/flame_graph_legend';
|
||||
import { FrameInformationWindow } from '../frame_information_window';
|
||||
import { FrameInformationTooltip } from '../frame_information_window/frame_information_tooltip';
|
||||
import { FlameGraphTooltip } from './flamegraph_tooltip';
|
||||
|
||||
interface Props {
|
||||
|
@ -70,7 +71,7 @@ export function FlameGraph({
|
|||
|
||||
const [highlightedVmIndex, setHighlightedVmIndex] = useState<number | undefined>(undefined);
|
||||
|
||||
const selected: undefined | React.ComponentProps<typeof FlamegraphInformationWindow>['frame'] =
|
||||
const selected: undefined | React.ComponentProps<typeof FrameInformationWindow>['frame'] =
|
||||
primaryFlamegraph && highlightedVmIndex !== undefined
|
||||
? {
|
||||
fileID: primaryFlamegraph.FileID[highlightedVmIndex],
|
||||
|
@ -169,15 +170,12 @@ export function FlameGraph({
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{showInformationWindow && (
|
||||
<EuiFlyout onClose={toggleShowInformationWindow} size="s">
|
||||
<EuiFlyoutBody>
|
||||
<FlamegraphInformationWindow
|
||||
frame={selected}
|
||||
totalSeconds={primaryFlamegraph?.TotalSeconds ?? 0}
|
||||
totalSamples={totalSamples}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
</EuiFlyout>
|
||||
<FrameInformationTooltip
|
||||
onClose={toggleShowInformationWindow}
|
||||
frame={selected}
|
||||
totalSeconds={primaryFlamegraph?.TotalSeconds ?? 0}
|
||||
totalSamples={totalSamples}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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 { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function FrameInformationPanel({ children }: Props) {
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
{i18n.translate('xpack.profiling.flameGraphInformationWindowTitle', {
|
||||
defaultMessage: 'Frame information',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>{children}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 { EuiFlyout, EuiFlyoutBody } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { FrameInformationWindow, Props as FrameInformationWindowProps } from '.';
|
||||
|
||||
interface Props extends FrameInformationWindowProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function FrameInformationTooltip({ onClose, ...props }: Props) {
|
||||
return (
|
||||
<EuiFlyout onClose={onClose} size="s">
|
||||
<EuiFlyoutBody>
|
||||
<FrameInformationWindow {...props} />
|
||||
</EuiFlyoutBody>
|
||||
</EuiFlyout>
|
||||
);
|
||||
}
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { calculateImpactEstimates } from '../../utils/calculate_impact_estimates';
|
||||
import { asCost } from '../../utils/formatters/as_cost';
|
||||
import { asDuration } from '../../utils/formatters/as_duration';
|
||||
import { asNumber } from '../../utils/formatters/as_number';
|
||||
import { asPercentage } from '../../utils/formatters/as_percentage';
|
||||
import { asWeight } from '../../utils/formatters/as_weight';
|
||||
import { calculateImpactEstimates } from './calculate_impact_estimates';
|
||||
|
||||
export function getImpactRows({
|
||||
countInclusive,
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { FrameInformationPanel } from './frame_information_panel';
|
||||
import { getImpactRows } from './get_impact_rows';
|
||||
import { getInformationRows } from './get_information_rows';
|
||||
import { KeyValueList } from './key_value_list';
|
||||
|
||||
export interface Props {
|
||||
frame?: {
|
||||
fileID: string;
|
||||
frameType: number;
|
||||
exeFileName: string;
|
||||
addressOrLine: number;
|
||||
functionName: string;
|
||||
sourceFileName: string;
|
||||
sourceLine: number;
|
||||
countInclusive: number;
|
||||
countExclusive: number;
|
||||
};
|
||||
totalSamples: number;
|
||||
totalSeconds: number;
|
||||
}
|
||||
|
||||
export function FrameInformationWindow({ frame, totalSamples, totalSeconds }: Props) {
|
||||
if (!frame) {
|
||||
return (
|
||||
<FrameInformationPanel>
|
||||
<EuiText>
|
||||
{i18n.translate('xpack.profiling.frameInformationWindow.selectFrame', {
|
||||
defaultMessage: 'Click on a frame to display more information',
|
||||
})}
|
||||
</EuiText>
|
||||
</FrameInformationPanel>
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
fileID,
|
||||
frameType,
|
||||
exeFileName,
|
||||
addressOrLine,
|
||||
functionName,
|
||||
sourceFileName,
|
||||
sourceLine,
|
||||
countInclusive,
|
||||
countExclusive,
|
||||
} = frame;
|
||||
|
||||
const informationRows = getInformationRows({
|
||||
fileID,
|
||||
frameType,
|
||||
exeFileName,
|
||||
addressOrLine,
|
||||
functionName,
|
||||
sourceFileName,
|
||||
sourceLine,
|
||||
});
|
||||
|
||||
const impactRows = getImpactRows({
|
||||
countInclusive,
|
||||
countExclusive,
|
||||
totalSamples,
|
||||
totalSeconds,
|
||||
});
|
||||
|
||||
return (
|
||||
<FrameInformationPanel>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<KeyValueList rows={informationRows} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xxs">
|
||||
<h2>
|
||||
{i18n.translate('xpack.profiling.frameInformationWindow.impactEstimatesTitle', {
|
||||
defaultMessage: 'Impact estimates',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<KeyValueList rows={impactRows} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</FrameInformationPanel>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
rows: Array<{ label: string; value: React.ReactNode }>;
|
||||
}
|
||||
|
||||
export function KeyValueList({ rows }: Props) {
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{rows.map((row, index) => (
|
||||
<>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem grow>{row.label}:</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} style={{ alignSelf: 'flex-end', overflowWrap: 'anywhere' }}>
|
||||
{row.value}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{index < rows.length - 1 ? (
|
||||
<EuiFlexItem>
|
||||
<EuiHorizontalRule size="full" margin="none" />
|
||||
</EuiFlexItem>
|
||||
) : undefined}
|
||||
</>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -46,31 +46,36 @@ export function FunctionsView({ children }: { children: React.ReactElement }) {
|
|||
({ http }) => {
|
||||
return fetchTopNFunctions({
|
||||
http,
|
||||
timeFrom: new Date(timeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(timeRange.end).getTime() / 1000,
|
||||
timeFrom: timeRange.inSeconds.start,
|
||||
timeTo: timeRange.inSeconds.end,
|
||||
startIndex: 0,
|
||||
endIndex: 100000,
|
||||
kuery,
|
||||
});
|
||||
},
|
||||
[timeRange.start, timeRange.end, kuery, fetchTopNFunctions]
|
||||
[timeRange.inSeconds.start, timeRange.inSeconds.end, kuery, fetchTopNFunctions]
|
||||
);
|
||||
|
||||
const comparisonState = useTimeRangeAsync(
|
||||
({ http }) => {
|
||||
if (!comparisonTimeRange.start || !comparisonTimeRange.end) {
|
||||
if (!comparisonTimeRange.inSeconds.start || !comparisonTimeRange.inSeconds.end) {
|
||||
return undefined;
|
||||
}
|
||||
return fetchTopNFunctions({
|
||||
http,
|
||||
timeFrom: new Date(comparisonTimeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(comparisonTimeRange.end).getTime() / 1000,
|
||||
timeFrom: comparisonTimeRange.inSeconds.start,
|
||||
timeTo: comparisonTimeRange.inSeconds.end,
|
||||
startIndex: 0,
|
||||
endIndex: 100000,
|
||||
kuery: comparisonKuery,
|
||||
});
|
||||
},
|
||||
[comparisonTimeRange.start, comparisonTimeRange.end, comparisonKuery, fetchTopNFunctions]
|
||||
[
|
||||
comparisonTimeRange.inSeconds.start,
|
||||
comparisonTimeRange.inSeconds.end,
|
||||
comparisonKuery,
|
||||
fetchTopNFunctions,
|
||||
]
|
||||
);
|
||||
|
||||
const routePath = useProfilingRoutePath() as
|
||||
|
@ -137,10 +142,14 @@ export function FunctionsView({ children }: { children: React.ReactElement }) {
|
|||
},
|
||||
});
|
||||
}}
|
||||
totalSeconds={timeRange.inSeconds.end - timeRange.inSeconds.start}
|
||||
isDifferentialView={isDifferentialView}
|
||||
/>
|
||||
</AsyncComponent>
|
||||
</EuiFlexItem>
|
||||
{isDifferentialView && comparisonTimeRange.start && comparisonTimeRange.end ? (
|
||||
{isDifferentialView &&
|
||||
comparisonTimeRange.inSeconds.start &&
|
||||
comparisonTimeRange.inSeconds.end ? (
|
||||
<EuiFlexItem>
|
||||
<AsyncComponent {...comparisonState} size="xl" alignTop>
|
||||
<TopNFunctionsTable
|
||||
|
@ -161,6 +170,10 @@ export function FunctionsView({ children }: { children: React.ReactElement }) {
|
|||
}}
|
||||
topNFunctions={comparisonState.data}
|
||||
comparisonTopNFunctions={state.data}
|
||||
totalSeconds={
|
||||
comparisonTimeRange.inSeconds.end - comparisonTimeRange.inSeconds.start
|
||||
}
|
||||
isDifferentialView={isDifferentialView}
|
||||
/>
|
||||
</AsyncComponent>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -59,8 +59,8 @@ export function StackTracesView() {
|
|||
return fetchTopN({
|
||||
http,
|
||||
type: topNType,
|
||||
timeFrom: new Date(timeRange.start).getTime() / 1000,
|
||||
timeTo: new Date(timeRange.end).getTime() / 1000,
|
||||
timeFrom: timeRange.inSeconds.start,
|
||||
timeTo: timeRange.inSeconds.end,
|
||||
kuery,
|
||||
}).then((response: TopNResponse) => {
|
||||
const totalCount = response.TotalCount;
|
||||
|
@ -76,7 +76,7 @@ export function StackTracesView() {
|
|||
};
|
||||
});
|
||||
},
|
||||
[topNType, timeRange.start, timeRange.end, fetchTopN, kuery]
|
||||
[topNType, timeRange.inSeconds.start, timeRange.inSeconds.end, fetchTopN, kuery]
|
||||
);
|
||||
|
||||
const { data } = state;
|
||||
|
|
|
@ -19,9 +19,13 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { keyBy, orderBy } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { TopNFunctions, TopNFunctionSortField } from '../../../common/functions';
|
||||
import { getCalleeFunction, StackFrameMetadata } from '../../../common/profiling';
|
||||
import { calculateImpactEstimates } from '../../utils/calculate_impact_estimates';
|
||||
import { asCost } from '../../utils/formatters/as_cost';
|
||||
import { asWeight } from '../../utils/formatters/as_weight';
|
||||
import { FrameInformationTooltip } from '../frame_information_window/frame_information_tooltip';
|
||||
import { StackFrameSummary } from '../stack_frame_summary';
|
||||
import { GetLabel } from './get_label';
|
||||
|
||||
|
@ -31,6 +35,7 @@ interface Row {
|
|||
samples: number;
|
||||
exclusiveCPU: number;
|
||||
inclusiveCPU: number;
|
||||
impactEstimates?: ReturnType<typeof calculateImpactEstimates>;
|
||||
diff?: {
|
||||
rank: number;
|
||||
samples: number;
|
||||
|
@ -125,13 +130,7 @@ function CPUStat({ cpu, diffCPU }: { cpu: number; diffCPU?: number }) {
|
|||
);
|
||||
}
|
||||
|
||||
export const TopNFunctionsTable = ({
|
||||
sortDirection,
|
||||
sortField,
|
||||
onSortChange,
|
||||
topNFunctions,
|
||||
comparisonTopNFunctions,
|
||||
}: {
|
||||
interface Props {
|
||||
sortDirection: 'asc' | 'desc';
|
||||
sortField: TopNFunctionSortField;
|
||||
onSortChange: (options: {
|
||||
|
@ -140,7 +139,21 @@ export const TopNFunctionsTable = ({
|
|||
}) => void;
|
||||
topNFunctions?: TopNFunctions;
|
||||
comparisonTopNFunctions?: TopNFunctions;
|
||||
}) => {
|
||||
totalSeconds: number;
|
||||
isDifferentialView: boolean;
|
||||
}
|
||||
|
||||
export function TopNFunctionsTable({
|
||||
sortDirection,
|
||||
sortField,
|
||||
onSortChange,
|
||||
topNFunctions,
|
||||
comparisonTopNFunctions,
|
||||
totalSeconds,
|
||||
isDifferentialView,
|
||||
}: Props) {
|
||||
const [selectedRow, setSelectedRow] = useState<Row | undefined>();
|
||||
|
||||
const totalCount: number = useMemo(() => {
|
||||
if (!topNFunctions || !topNFunctions.TotalCount) {
|
||||
return 0;
|
||||
|
@ -163,6 +176,17 @@ export const TopNFunctionsTable = ({
|
|||
|
||||
const inclusiveCPU = (topN.CountInclusive / topNFunctions.TotalCount) * 100;
|
||||
const exclusiveCPU = (topN.CountExclusive / topNFunctions.TotalCount) * 100;
|
||||
const totalSamples = topN.CountExclusive;
|
||||
|
||||
const impactEstimates =
|
||||
totalSeconds > 0
|
||||
? calculateImpactEstimates({
|
||||
countExclusive: exclusiveCPU,
|
||||
countInclusive: inclusiveCPU,
|
||||
totalSamples,
|
||||
totalSeconds,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const diff =
|
||||
comparisonTopNFunctions && comparisonRow
|
||||
|
@ -184,10 +208,11 @@ export const TopNFunctionsTable = ({
|
|||
samples: topN.CountExclusive,
|
||||
exclusiveCPU,
|
||||
inclusiveCPU,
|
||||
impactEstimates,
|
||||
diff,
|
||||
};
|
||||
});
|
||||
}, [topNFunctions, comparisonTopNFunctions]);
|
||||
}, [topNFunctions, comparisonTopNFunctions, totalSeconds]);
|
||||
|
||||
const theme = useEuiTheme();
|
||||
|
||||
|
@ -197,30 +222,30 @@ export const TopNFunctionsTable = ({
|
|||
name: i18n.translate('xpack.profiling.functionsView.rankColumnLabel', {
|
||||
defaultMessage: 'Rank',
|
||||
}),
|
||||
align: 'right',
|
||||
render: (_, { rank }) => {
|
||||
return <EuiText style={{ whiteSpace: 'nowrap', fontSize: 'inherit' }}>{rank}</EuiText>;
|
||||
},
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
field: TopNFunctionSortField.Frame,
|
||||
name: i18n.translate('xpack.profiling.functionsView.functionColumnLabel', {
|
||||
defaultMessage: 'Function',
|
||||
}),
|
||||
width: '100%',
|
||||
render: (_, { frame }) => <StackFrameSummary frame={frame} />,
|
||||
width: '50%',
|
||||
},
|
||||
{
|
||||
field: TopNFunctionSortField.Samples,
|
||||
name: i18n.translate('xpack.profiling.functionsView.samplesColumnLabel', {
|
||||
defaultMessage: 'Samples (estd.)',
|
||||
}),
|
||||
align: 'right',
|
||||
render: (_, { samples, diff }) => {
|
||||
return (
|
||||
<SampleStat samples={samples} diffSamples={diff?.samples} totalSamples={totalCount} />
|
||||
);
|
||||
},
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
field: TopNFunctionSortField.ExclusiveCPU,
|
||||
|
@ -302,6 +327,49 @@ export const TopNFunctionsTable = ({
|
|||
},
|
||||
});
|
||||
}
|
||||
if (!isDifferentialView) {
|
||||
columns.push(
|
||||
{
|
||||
field: 'annualized_co2',
|
||||
name: i18n.translate('xpack.profiling.functionsView.annualizedCo2', {
|
||||
defaultMessage: 'Annualized CO2',
|
||||
}),
|
||||
render: (_, { impactEstimates }) => {
|
||||
if (impactEstimates?.annualizedCo2) {
|
||||
return <div>{asWeight(impactEstimates.annualizedCo2)}</div>;
|
||||
}
|
||||
},
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
field: 'annualized_dollar_cost',
|
||||
name: i18n.translate('xpack.profiling.functionsView.annualizedDollarCost', {
|
||||
defaultMessage: `Annualized dollar cost`,
|
||||
}),
|
||||
render: (_, { impactEstimates }) => {
|
||||
if (impactEstimates?.annualizedDollarCost) {
|
||||
return <div>{asCost(impactEstimates.annualizedDollarCost)}</div>;
|
||||
}
|
||||
},
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
name: 'Actions',
|
||||
actions: [
|
||||
{
|
||||
name: 'show_more_information',
|
||||
description: i18n.translate('xpack.profiling.functionsView.showMoreButton', {
|
||||
defaultMessage: `Show more information`,
|
||||
}),
|
||||
icon: 'inspect',
|
||||
color: 'primary',
|
||||
type: 'icon',
|
||||
onClick: setSelectedRow,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const sortedRows = orderBy(
|
||||
rows,
|
||||
|
@ -339,6 +407,26 @@ export const TopNFunctionsTable = ({
|
|||
},
|
||||
}}
|
||||
/>
|
||||
{selectedRow && (
|
||||
<FrameInformationTooltip
|
||||
onClose={() => {
|
||||
setSelectedRow(undefined);
|
||||
}}
|
||||
frame={{
|
||||
addressOrLine: selectedRow.frame.AddressOrLine,
|
||||
countExclusive: selectedRow.exclusiveCPU,
|
||||
countInclusive: selectedRow.inclusiveCPU,
|
||||
exeFileName: selectedRow.frame.ExeFileName,
|
||||
fileID: selectedRow.frame.FileID,
|
||||
frameType: selectedRow.frame.FrameType,
|
||||
functionName: selectedRow.frame.FunctionName,
|
||||
sourceFileName: selectedRow.frame.SourceFilename,
|
||||
sourceLine: selectedRow.frame.SourceLine,
|
||||
}}
|
||||
totalSeconds={totalSeconds ?? 0}
|
||||
totalSamples={selectedRow.samples}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,18 +14,25 @@ interface TimeRangeAPI {
|
|||
timeRangeId: string;
|
||||
}
|
||||
|
||||
interface TimeRangeInSeconds {
|
||||
inSeconds: { start: number; end: number };
|
||||
}
|
||||
interface PartialTimeRangeInSeconds {
|
||||
inSeconds: Pick<Partial<TimeRangeInSeconds['inSeconds']>, 'start' | 'end'>;
|
||||
}
|
||||
|
||||
type PartialTimeRange = Pick<Partial<TimeRange>, 'start' | 'end'>;
|
||||
|
||||
export function useTimeRange(range: {
|
||||
rangeFrom?: string;
|
||||
rangeTo?: string;
|
||||
optional: true;
|
||||
}): TimeRangeAPI & PartialTimeRange;
|
||||
}): TimeRangeAPI & PartialTimeRange & PartialTimeRangeInSeconds;
|
||||
|
||||
export function useTimeRange(range: {
|
||||
rangeFrom: string;
|
||||
rangeTo: string;
|
||||
}): TimeRangeAPI & TimeRange;
|
||||
}): TimeRangeAPI & TimeRange & TimeRangeInSeconds;
|
||||
|
||||
export function useTimeRange({
|
||||
rangeFrom,
|
||||
|
@ -35,7 +42,9 @@ export function useTimeRange({
|
|||
rangeFrom?: string;
|
||||
rangeTo?: string;
|
||||
optional?: boolean;
|
||||
}): TimeRangeAPI & (TimeRange | PartialTimeRange) {
|
||||
}): TimeRangeAPI &
|
||||
(TimeRange | PartialTimeRange) &
|
||||
(TimeRangeInSeconds | PartialTimeRangeInSeconds) {
|
||||
const timeRangeApi = useTimeRangeContext();
|
||||
|
||||
const { start, end } = useMemo(() => {
|
||||
|
@ -54,6 +63,10 @@ export function useTimeRange({
|
|||
return {
|
||||
start,
|
||||
end,
|
||||
inSeconds: {
|
||||
start: start ? new Date(start).getTime() / 1000 : undefined,
|
||||
end: end ? new Date(end).getTime() / 1000 : undefined,
|
||||
},
|
||||
timeRangeId: timeRangeApi.timeRangeId,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { calculateImpactEstimates } from './calculate_impact_estimates';
|
||||
import { calculateImpactEstimates } from '.';
|
||||
|
||||
describe('calculateImpactEstimates', () => {
|
||||
it('calculates impact when countExclusive is lower than countInclusive', () => {
|
|
@ -26419,12 +26419,10 @@
|
|||
"xpack.profiling.flameGraphInformationWindow.executableLabel": "Exécutable",
|
||||
"xpack.profiling.flameGraphInformationWindow.frameTypeLabel": "Type de cadre",
|
||||
"xpack.profiling.flameGraphInformationWindow.functionLabel": "Fonction",
|
||||
"xpack.profiling.flameGraphInformationWindow.impactEstimatesTitle": "Estimations de l'impact",
|
||||
"xpack.profiling.flameGraphInformationWindow.percentageCpuTimeExclusiveLabel": "% de temps processeur (enfants excl.)",
|
||||
"xpack.profiling.flameGraphInformationWindow.percentageCpuTimeInclusiveLabel": "% de temps processeur",
|
||||
"xpack.profiling.flameGraphInformationWindow.samplesExclusiveLabel": "Échantillons (enfants excl.)",
|
||||
"xpack.profiling.flameGraphInformationWindow.samplesInclusiveLabel": "Échantillons",
|
||||
"xpack.profiling.flamegraphInformationWindow.selectFrame": "Cliquer sur un cadre pour afficher plus d'informations",
|
||||
"xpack.profiling.flameGraphInformationWindow.sourceFileLabel": "Fichier source",
|
||||
"xpack.profiling.flameGraphInformationWindowTitle": "Informations sur le cadre",
|
||||
"xpack.profiling.flameGraphLegend.improvement": "Amélioration",
|
||||
|
|
|
@ -26400,12 +26400,10 @@
|
|||
"xpack.profiling.flameGraphInformationWindow.executableLabel": "実行ファイル",
|
||||
"xpack.profiling.flameGraphInformationWindow.frameTypeLabel": "フレームタイプ",
|
||||
"xpack.profiling.flameGraphInformationWindow.functionLabel": "関数",
|
||||
"xpack.profiling.flameGraphInformationWindow.impactEstimatesTitle": "影響度の推定",
|
||||
"xpack.profiling.flameGraphInformationWindow.percentageCpuTimeExclusiveLabel": "CPU時間の割合(子を除く)",
|
||||
"xpack.profiling.flameGraphInformationWindow.percentageCpuTimeInclusiveLabel": "CPU時間の割合",
|
||||
"xpack.profiling.flameGraphInformationWindow.samplesExclusiveLabel": "サンプル(子を除く)",
|
||||
"xpack.profiling.flameGraphInformationWindow.samplesInclusiveLabel": "サンプル",
|
||||
"xpack.profiling.flamegraphInformationWindow.selectFrame": "フレームをクリックすると、詳細が表示されます",
|
||||
"xpack.profiling.flameGraphInformationWindow.sourceFileLabel": "ソースファイル",
|
||||
"xpack.profiling.flameGraphInformationWindowTitle": "フレーム情報",
|
||||
"xpack.profiling.flameGraphLegend.improvement": "改善",
|
||||
|
|
|
@ -26416,12 +26416,10 @@
|
|||
"xpack.profiling.flameGraphInformationWindow.executableLabel": "可执行",
|
||||
"xpack.profiling.flameGraphInformationWindow.frameTypeLabel": "帧类型",
|
||||
"xpack.profiling.flameGraphInformationWindow.functionLabel": "函数",
|
||||
"xpack.profiling.flameGraphInformationWindow.impactEstimatesTitle": "影响评估",
|
||||
"xpack.profiling.flameGraphInformationWindow.percentageCpuTimeExclusiveLabel": "CPU 时间百分比(不包括子项)",
|
||||
"xpack.profiling.flameGraphInformationWindow.percentageCpuTimeInclusiveLabel": "CPU 时间百分比",
|
||||
"xpack.profiling.flameGraphInformationWindow.samplesExclusiveLabel": "样例(不包括子项)",
|
||||
"xpack.profiling.flameGraphInformationWindow.samplesInclusiveLabel": "样例",
|
||||
"xpack.profiling.flamegraphInformationWindow.selectFrame": "单击帧可显示更多信息",
|
||||
"xpack.profiling.flameGraphInformationWindow.sourceFileLabel": "源文件",
|
||||
"xpack.profiling.flameGraphInformationWindowTitle": "帧信息",
|
||||
"xpack.profiling.flameGraphLegend.improvement": "提升",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue