[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:
Cauê Marcondes 2023-03-31 09:09:54 -04:00 committed by GitHub
parent 7ea4722fb2
commit c00df762c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 358 additions and 202 deletions

View file

@ -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>
);
}

View file

@ -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,
]

View file

@ -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 {

View file

@ -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}
/>
)}
</>
);

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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,

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>

View file

@ -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;

View file

@ -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}
/>
)}
</>
);
};
}

View file

@ -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,
};
}

View file

@ -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', () => {

View file

@ -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",

View file

@ -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": "改善",

View file

@ -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": "提升",