Skip to content

Commit

Permalink
Statistics of secondary network interfaces
Browse files Browse the repository at this point in the history
Signed-off-by: Dana Orr <dorr@redhat.com>
  • Loading branch information
DanaOrr committed Feb 8, 2023
1 parent dd9e4b3 commit 8121b3a
Show file tree
Hide file tree
Showing 12 changed files with 354 additions and 96 deletions.
7 changes: 5 additions & 2 deletions locales/en/plugin__kubevirt-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@
"Boot source reference cannot be edited": "Boot source reference cannot be edited",
"Boot source type": "Boot source type",
"bootable": "bootable",
"Bootable volumes": "Bootable volumes",
"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,
yTickFormat,
} from '../utils/utils';

type NetworkThresholdSingleSourceChartProps = {
data: PrometheusValue[];
data: PrometheusResult[];
link: string;
};

Expand All @@ -28,14 +35,33 @@ 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 tickValues = Array.from({ length: Ymax + 1 }, (_, index) => {
if (index === 0) return '1 Bps';
if (index === Math.round(length)) return `${Math.round(length + 1)} Bps`;
return index.toString() + ' Bps';
});

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

return (
<ComponentReady isReady={isReady}>
Expand All @@ -44,20 +70,44 @@ const NetworkThresholdSingleSourceChart: React.FC<NetworkThresholdSingleSourceCh
<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, tickValues?.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() + ':' + datum?.x?.getMinutes()}
/>
}
mouseFollowTooltips
voronoiDimension="x"
/>
}
>
<ChartAxis
dependentAxis
tickFormat={yTickFormat}
tickValues={tickValues}
style={{
ticks: {
stroke: 'transparent',
},
}}
axisComponent={<></>}
/>
<ChartAxis
tickFormat={tickFormat(duration, currentTime)}
tickCount={TICKS_COUNT}
Expand All @@ -67,14 +117,15 @@ 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>
Expand Down
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
54 changes: 51 additions & 3 deletions src/utils/components/Charts/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
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';

export const SINGLE_VM_DURATION = 'SINGLE_VM_DURATION';
export const TICKS_COUNT = 100;
Expand Down Expand Up @@ -32,5 +37,48 @@ 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 allNetworks = 'All networks';
export const getPrometheusDataAllNics = (response: PrometheusResponse): PrometheusResult[] => {
if (!response?.data?.result) {
return [];
}
return [
{
...response?.data?.result?.[0],
metric: { ...response?.data?.result?.[0]?.metric, interface: allNetworks },
},
];
};
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 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,65 @@
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 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

0 comments on commit 8121b3a

Please sign in to comment.