Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CNV-16653: Statistics of secondary network interfaces #1033

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion locales/en/plugin__kubevirt-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
"Boot source type": "Boot source type",
"bootable": "bootable",
"Bootable volumes": "Bootable volumes",
"Breakdown by network": "Breakdown by network",
"Bridge": "Bridge",
"Build with guided documentation": "Build with guided documentation",
"By CPU": "By CPU",
Expand Down Expand Up @@ -618,6 +619,7 @@
"Network interfaces ({{count}})_plural": "Network interfaces ({{count}})",
"Network out": "Network out",
"Network Transfer": "Network Transfer",
"Network transfer breakdown": "Network transfer breakdown",
"Networking": "Networking",
"Networks": "Networks",
"Networks misconfigured": "Networks misconfigured",
Expand Down Expand Up @@ -735,7 +737,6 @@
"Preference": "Preference",
"Preffered": "Preffered",
"Primary live migration network": "Primary live migration network",
"Primary Network": "Primary Network",
"project": "project",
"Project": "Project",
"Project labels": "Project labels",
Expand Down Expand Up @@ -974,6 +975,7 @@
"Today": "Today",
"Tolerations": "Tolerations",
"Tolerations are applied to VirtualMachines, and allow (but do not require) the VirtualMachines to schedule onto Nodes with matching taints.": "Tolerations are applied to VirtualMachines, and allow (but do not require) the VirtualMachines to schedule onto Nodes with matching taints.",
"Top consumer": "Top consumer",
"Top consumers": "Top consumers",
"Topology key": "Topology key",
"Topology key must not be empty": "Topology key must not be empty",
Expand Down Expand Up @@ -1050,6 +1052,7 @@
"View documentation": "View documentation",
"View events": "View events",
"View matching {{matchingNodeText}}": "View matching {{matchingNodeText}}",
"View more": "View more",
"View More": "View More",
"View Persistent Volume Claim details": "View Persistent Volume Claim details",
"View PersistentVolumeClaim details": "View PersistentVolumeClaim details",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@ import { Link } from 'react-router-dom';
import xbytes from 'xbytes';

import { isEmpty } from '@kubevirt-utils/utils/utils';
import { PrometheusValue } from '@openshift-console/dynamic-plugin-sdk';
import { PrometheusResult } from '@openshift-console/dynamic-plugin-sdk';
import {
Chart,
ChartArea,
ChartAxis,
ChartGroup,
ChartVoronoiContainer,
ChartLegendTooltip,
ChartLine,
ChartThemeColor,
createContainer,
} from '@patternfly/react-charts';
import chart_color_blue_400 from '@patternfly/react-tokens/dist/esm/chart_color_blue_300';
import useDuration from '@virtualmachines/details/tabs/metrics/hooks/useDuration';

import ComponentReady from '../ComponentReady/ComponentReady';
import useResponsiveCharts from '../hooks/useResponsiveCharts';
import { MILLISECONDS_MULTIPLIER, tickFormat, TICKS_COUNT } from '../utils/utils';

import {
findChartMaxYAxis,
MILLISECONDS_MULTIPLIER,
tickFormat,
TICKS_COUNT,
tickValue,
yTickFormat,
} from '../utils/utils';
type NetworkThresholdSingleSourceChartProps = {
data: PrometheusValue[];
data: PrometheusResult[];
link: string;
};

Expand All @@ -28,36 +35,75 @@ const NetworkThresholdSingleSourceChart: React.FC<NetworkThresholdSingleSourceCh
link,
}) => {
const { currentTime, duration, timespan } = useDuration();

const { ref, width, height } = useResponsiveCharts();

const chartData = data?.map(([x, y]) => {
return { x: new Date(x * MILLISECONDS_MULTIPLIER), y: Number(y), name: 'Network In' };
});

const chartData =
!isEmpty(data) &&
data?.map((obj) => {
return (obj?.values || [])?.map(([x, y]) => {
return {
x: new Date(x * MILLISECONDS_MULTIPLIER),
y: Number(y),
name: obj?.metric?.interface,
};
});
});
const isReady = !isEmpty(chartData);
const Ymax = findChartMaxYAxis(chartData);

const CursorVoronoiContainer = createContainer('voronoi', 'cursor');
const legendData =
!isEmpty(chartData) &&
chartData?.map((newChartdata, index) => {
return { childName: newChartdata?.[index]?.name, name: newChartdata?.[index]?.name };

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the values for childName and name seem to be identical. is this intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The childName is used to synchronize the data series associated with the given chart name so it is better to have the same name I think...

});
return (
<ComponentReady isReady={isReady}>
<div className="util-threshold-chart" ref={ref}>
<Link to={link}>
<Chart
height={height}
width={width}
padding={35}
padding={{ top: 30, bottom: 60, left: 70, right: 60 }}
scale={{ x: 'time', y: 'linear' }}
themeColor={ChartThemeColor.multiUnordered}
domain={{
x: [currentTime - timespan, currentTime],
y: [0, tickValue(Ymax + 1)?.length],
}}
containerComponent={
<ChartVoronoiContainer
<CursorVoronoiContainer
cursorDimension="x"
labels={({ datum }) => {
return `${datum?.name}: ${xbytes(datum?.y, { iec: true, fixed: 2 })}`;
return `${xbytes(datum?.y, {
iec: true,
fixed: 2,
})}ps`;
}}
constrainToVisibleArea
labelComponent={
<ChartLegendTooltip
legendData={legendData}
title={(datum) =>
datum?.x?.getHours() + ':' + String(datum?.x?.getMinutes())?.padStart(2, '0')
}
/>
}
mouseFollowTooltips
voronoiDimension="x"
/>
}
>
<ChartAxis
dependentAxis
tickFormat={yTickFormat}
tickValues={tickValue(Ymax)}
style={{
ticks: {
stroke: 'transparent',
},
}}
axisComponent={<></>}
/>
<ChartAxis
tickFormat={tickFormat(duration, currentTime)}
tickCount={TICKS_COUNT}
Expand All @@ -67,20 +113,20 @@ const NetworkThresholdSingleSourceChart: React.FC<NetworkThresholdSingleSourceCh
axisComponent={<></>}
/>
<ChartGroup>
<ChartArea
data={chartData}
style={{
data: {
stroke: chart_color_blue_400.value,
},
}}
/>
{isReady &&
chartData?.map((newChartdata) => (
<ChartLine
key={newChartdata?.[0]?.name}
name={newChartdata?.[0]?.name}
data={newChartdata}
themeColor={ChartThemeColor.multiUnordered}
/>
))}
</ChartGroup>
</Chart>
</Link>
</div>
</ComponentReady>
);
};

export default NetworkThresholdSingleSourceChart;
9 changes: 5 additions & 4 deletions src/utils/components/Charts/utils/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ enum VMQueries {
NETWORK_OUT_USAGE = 'NETWORK_OUT_USAGE',
NETWORK_IN_BY_INTERFACE_USAGE = 'NETWORK_IN_BY_INTERFACE_USAGE',
NETWORK_OUT_BY_INTERFACE_USAGE = 'NETWORK_OUT_BY_INTERFACE_USAGE',
NETWORK_TOTAL_BY_INTERFACE_USAGE = 'NETWORK_TOTAL_BY_INTERFACE_USAGE',
NETWORK_TOTAL_USAGE = 'NETWORK_TOTAL_USAGE',
STORAGE_IOPS_TOTAL = 'STORAGE_IOPS_TOTAL',
MIGRATION_DATA_PROCESSED = 'MIGRATION_DATA_PROCESSED',
Expand All @@ -34,7 +35,6 @@ export const getUtilizationQueries: GetUtilizationQueries = ({
obj,
duration,
launcherPodName,
nic,
}) => {
const { name, namespace } = obj?.metadata || {};
return {
Expand All @@ -43,9 +43,10 @@ export const getUtilizationQueries: GetUtilizationQueries = ({
[VMQueries.MEMORY_USAGE]: `sum(kubevirt_vmi_memory_used_bytes{name='${name}',namespace='${namespace}'}) BY (name)`,
[VMQueries.NETWORK_IN_USAGE]: `sum(rate(kubevirt_vmi_network_receive_bytes_total{name='${name}',namespace='${namespace}'}[${duration}])) BY (name, namespace)`,
[VMQueries.NETWORK_OUT_USAGE]: `sum(rate(kubevirt_vmi_network_transmit_bytes_total{name='${name}',namespace='${namespace}'}[${duration}])) BY (name, namespace)`,
[VMQueries.NETWORK_IN_BY_INTERFACE_USAGE]: `sum(rate(kubevirt_vmi_network_receive_bytes_total{name='${name}',namespace='${namespace}',interface='${nic}'}[${duration}])) BY (name, namespace, interface)`,
[VMQueries.NETWORK_OUT_BY_INTERFACE_USAGE]: `sum(rate(kubevirt_vmi_network_transmit_bytes_total{name='${name}',namespace='${namespace}',interface='${nic}'}[${duration}])) BY (name, namespace, interface)`,
[VMQueries.NETWORK_TOTAL_USAGE]: `sum(rate(kubevirt_vmi_network_receive_bytes_total{name='${name}',namespace='${namespace}',interface='${nic}'}[${duration}]) + rate(kubevirt_vmi_network_transmit_bytes_total{name='${name}',namespace='${namespace}',interface='${nic}'}[${duration}])) BY (name, namespace, interface)`,
[VMQueries.NETWORK_IN_BY_INTERFACE_USAGE]: `sum(rate(kubevirt_vmi_network_receive_bytes_total{name='${name}',namespace='${namespace}'}[${duration}])) BY (name, namespace, interface)`,
[VMQueries.NETWORK_OUT_BY_INTERFACE_USAGE]: `sum(rate(kubevirt_vmi_network_transmit_bytes_total{name='${name}',namespace='${namespace}'}[${duration}])) BY (name, namespace, interface)`,
[VMQueries.NETWORK_TOTAL_BY_INTERFACE_USAGE]: `sum(rate(kubevirt_vmi_network_receive_bytes_total{name='${name}',namespace='${namespace}'}[${duration}])) BY (name, namespace, interface)`,
[VMQueries.NETWORK_TOTAL_USAGE]: `sum(rate(kubevirt_vmi_network_receive_bytes_total{name='${name}',namespace='${namespace}'}[${duration}])) BY (name, namespace)`,
[VMQueries.FILESYSTEM_READ_USAGE]: `sum(rate(kubevirt_vmi_storage_read_traffic_bytes_total{name='${name}',namespace='${namespace}'}[${duration}])) BY (name, namespace)`,
[VMQueries.FILESYSTEM_WRITE_USAGE]: `sum(rate(kubevirt_vmi_storage_write_traffic_bytes_total{name='${name}',namespace='${namespace}'}[${duration}])) BY (name, namespace)`,
[VMQueries.FILESYSTEM_USAGE_TOTAL]: `sum(rate(kubevirt_vmi_storage_read_traffic_bytes_total{name='${name}',namespace='${namespace}'}[${duration}]) + rate(kubevirt_vmi_storage_write_traffic_bytes_total{name='${name}',namespace='${namespace}'}[${duration}])) BY (name, namespace)`,
Expand Down
62 changes: 59 additions & 3 deletions src/utils/components/Charts/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import DurationOption from '@kubevirt-utils/components/DurationOption/DurationOption';
import { PrometheusResponse, PrometheusValue } from '@openshift-console/dynamic-plugin-sdk';
import { isEmpty } from '@kubevirt-utils/utils/utils';
import {
PrometheusResponse,
PrometheusResult,
PrometheusValue,
} from '@openshift-console/dynamic-plugin-sdk';
import { ALL_NETWORKS } from '@virtualmachines/details/tabs/metrics/utils/constants';

export const SINGLE_VM_DURATION = 'SINGLE_VM_DURATION';
export const TICKS_COUNT = 100;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move those constants into a separate constants.ts file ;) WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good idea but it requires me to change the import to many more files and it creates a load on this PR..
so maybe in another PR? :)

Expand Down Expand Up @@ -32,5 +38,55 @@ export const tickFormat =
return '';
};

export const getPrometheusData = (response: PrometheusResponse): PrometheusValue[] =>
response?.data?.result?.[0]?.values;
export const getPrometheusData = (response: PrometheusResponse): PrometheusValue[] => {
return response?.data?.result?.[0]?.values;
};

export const getPrometheusDataByNic = (
response: PrometheusResponse,
nic: string,
): PrometheusResult[] => {
if (!response?.data?.result) {
return [];
}
const singleNic = response?.data?.result?.find((res) => res.metric?.interface === nic);
return singleNic ? [singleNic] : response?.data?.result;
};

export const getPrometheusDataAllNics = (response: PrometheusResponse): PrometheusResult[] => {
if (!response?.data?.result) {
return [];
}
return [
{
...response?.data?.result?.[0],
metric: { ...response?.data?.result?.[0]?.metric, interface: ALL_NETWORKS },
},
];
};
export const findChartMaxYAxis = (chartData: { x: Date; y: number; name: string }[][]) => {
const yValues =
!isEmpty(chartData) &&
chartData?.map((dataArray) => {
return Math.max(...dataArray?.map((data) => data?.y));
});
const maxY = Math.max(...(yValues || []));
return maxY;
};

export const tickValue = (Ymax: number) => {
const tickValues = Array.from({ length: Ymax + 1 }, (_, index) => {
if (index === 0) return '1 Bps';
if (index === Math.round(Ymax)) return `${Math.round(Ymax + 1)} Bps`;
return index.toString() + ' Bps';
});
return tickValues;
};
export const yTickFormat = (tick: any, index: number, ticks: any[]) => {
const isFirst = index === 0;
const isLast = index === ticks.length - 1;
if (isLast || isFirst) {
return tick;
}
return;
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,67 @@
import React, { useMemo } from 'react';
import { useHistory } from 'react-router-dom';

import { V1VirtualMachineInstance } from '@kubevirt-ui/kubevirt-api/kubevirt';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { Dropdown, DropdownItem, DropdownToggle, Title } from '@patternfly/react-core';

import { ALL_NETWORKS } from '../utils/constants';

import useQuery from './hook/useQuery';
import NetworkChartsByNIC from './NetworkChartsByNIC';

import '../virtual-machine-metrics-tab.scss';
type NetworkChartsProps = {
vmi: V1VirtualMachineInstance;
};

const NetworkCharts: React.FC<NetworkChartsProps> = ({ vmi }) => {
const interfacesNames = useMemo(
() => vmi?.spec?.domain?.devices?.interfaces.map((nic) => nic?.name),
[vmi],
const { t } = useKubevirtTranslation();
const history = useHistory();

const interfacesNames = useMemo(() => {
const interfaces = vmi?.spec?.domain?.devices?.interfaces?.map((nic) => nic?.name);
interfaces?.unshift(ALL_NETWORKS);
return interfaces;
}, [vmi]);

const query = useQuery();
const [isDropdownOpen, setIsDropdownOpen] = React.useState<boolean>(false);
const [selectedNetwork, setSelectedNetwork] = React.useState<string>(
query?.get('network') || ALL_NETWORKS,
);

return (
<div>
{interfacesNames?.map((nic) => (
<NetworkChartsByNIC key={nic} vmi={vmi} nic={nic} />
))}
<Title headingLevel="h4" className="networkcharts-by-nic--title">
{t('Network interface:')}
</Title>{' '}
<Dropdown
className="network ul.pf-c-dropdown__menu"
isPlain
isText
dropdownItems={interfacesNames?.map((nic) => (
<DropdownItem
key={nic}
onClick={(e) => {
setSelectedNetwork(e?.currentTarget?.innerText);
setIsDropdownOpen(false);
history?.push(
`/k8s/ns/${vmi?.metadata?.namespace}/kubevirt.io~v1~VirtualMachine/${vmi?.metadata?.name}/metrics?network=${nic}`,
);
}}
>
{nic}
</DropdownItem>
))}
isOpen={isDropdownOpen}
toggle={
<DropdownToggle onToggle={(toogle) => setIsDropdownOpen(toogle)}>
{selectedNetwork}
</DropdownToggle>
}
></Dropdown>
<NetworkChartsByNIC vmi={vmi} nic={selectedNetwork} />
</div>
);
};
Expand Down
Loading