From a19b3723646ae2e606b86652679de370317e6f35 Mon Sep 17 00:00:00 2001 From: Dana Orr Date: Wed, 1 Feb 2023 11:44:10 +0200 Subject: [PATCH] Statistics of secondary network interfaces Signed-off-by: Dana Orr --- locales/en/plugin__kubevirt-plugin.json | 5 +- .../NetworkThresholdChartSingleSource.tsx | 106 ++++++++++++++---- src/utils/components/Charts/utils/queries.ts | 9 +- src/utils/components/Charts/utils/utils.ts | 34 +++++- .../metrics/NetworkCharts/NetworkCharts.tsx | 47 +++++++- .../NetworkCharts/NetworkChartsByNIC.tsx | 14 +-- .../NetworkCharts/hook/useNetworkData.tsx | 86 ++++++++++---- .../metrics/NetworkCharts/hook/useQuery.ts | 10 ++ .../UtilizationCharts/UtilizationCharts.tsx | 9 -- .../tabs/metrics/VirtualMachineMetricsTab.tsx | 34 +++--- .../metrics/virtual-machine-metrics-tab.scss | 9 ++ .../components/NetworkUtil/NetworkUtil.tsx | 69 +++++++++++- 12 files changed, 339 insertions(+), 93 deletions(-) create mode 100644 src/views/virtualmachines/details/tabs/metrics/NetworkCharts/hook/useQuery.ts diff --git a/locales/en/plugin__kubevirt-plugin.json b/locales/en/plugin__kubevirt-plugin.json index 0e71189cbe..41ce4ec74a 100644 --- a/locales/en/plugin__kubevirt-plugin.json +++ b/locales/en/plugin__kubevirt-plugin.json @@ -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", @@ -617,6 +618,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", @@ -731,7 +733,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", @@ -968,6 +969,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", @@ -1043,6 +1045,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", diff --git a/src/utils/components/Charts/NetworkUtil/NetworkThresholdChartSingleSource.tsx b/src/utils/components/Charts/NetworkUtil/NetworkThresholdChartSingleSource.tsx index cc5f0565d5..fa87474b44 100644 --- a/src/utils/components/Charts/NetworkUtil/NetworkThresholdChartSingleSource.tsx +++ b/src/utils/components/Charts/NetworkUtil/NetworkThresholdChartSingleSource.tsx @@ -3,15 +3,16 @@ 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'; @@ -19,7 +20,7 @@ import useResponsiveCharts from '../hooks/useResponsiveCharts'; import { MILLISECONDS_MULTIPLIER, tickFormat, TICKS_COUNT } from '../utils/utils'; type NetworkThresholdSingleSourceChartProps = { - data: PrometheusValue[]; + data: PrometheusResult[]; link: string; }; @@ -28,14 +29,51 @@ const NetworkThresholdSingleSourceChart: React.FC { 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 findChartMaxYAxis = (chartData1: { x: Date; y: number; name: string }[][]) => { + const yValues = + !isEmpty(chartData1) && + chartData1?.map((dataArray) => { + return Math.max(...dataArray?.map((data1) => data1?.y)); + }); + const maxY = Math.max(...(yValues || [])); + return maxY; + }; + const Ymax = findChartMaxYAxis(chartData); + 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'; }); - const isReady = !isEmpty(chartData); + const newTickFormat = (tick: any, index: number, ticks: any[]) => { + const isFirst = index === 0; + const isLast = index === ticks.length - 1; + if (isLast || isFirst) { + return tick; + } + return; + }; + const CursorVoronoiContainer = createContainer('voronoi', 'cursor'); + const legendData = + !isEmpty(chartData) && + chartData?.map((newChartdata, index) => { + return { childName: newChartdata?.[index]?.name, name: newChartdata?.[index]?.name }; + }); return ( @@ -44,20 +82,44 @@ const NetworkThresholdSingleSourceChart: React.FC { - return `${datum?.name}: ${xbytes(datum?.y, { iec: true, fixed: 2 })}`; + return `${xbytes(datum?.y, { + iec: true, + fixed: 2, + })}ps`; }} - constrainToVisibleArea + labelComponent={ + datum.x.getHours() + ':' + datum.x.getMinutes()} + /> + } + mouseFollowTooltips + voronoiDimension="x" /> } > + } + /> } /> + - + {isReady && + chartData?.map((newChartdata) => ( + // eslint-disable-next-line react/jsx-key + + ))} diff --git a/src/utils/components/Charts/utils/queries.ts b/src/utils/components/Charts/utils/queries.ts index 727aece4fc..85ee0e1798 100644 --- a/src/utils/components/Charts/utils/queries.ts +++ b/src/utils/components/Charts/utils/queries.ts @@ -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', @@ -34,7 +35,6 @@ export const getUtilizationQueries: GetUtilizationQueries = ({ obj, duration, launcherPodName, - nic, }) => { const { name, namespace } = obj?.metadata || {}; return { @@ -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)`, diff --git a/src/utils/components/Charts/utils/utils.ts b/src/utils/components/Charts/utils/utils.ts index d351549400..5e186fbd07 100644 --- a/src/utils/components/Charts/utils/utils.ts +++ b/src/utils/components/Charts/utils/utils.ts @@ -1,5 +1,9 @@ import DurationOption from '@kubevirt-utils/components/DurationOption/DurationOption'; -import { PrometheusResponse, PrometheusValue } from '@openshift-console/dynamic-plugin-sdk'; +import { + PrometheusResponse, + PrometheusResult, + PrometheusValue, +} from '@openshift-console/dynamic-plugin-sdk'; export const SINGLE_VM_DURATION = 'SINGLE_VM_DURATION'; export const TICKS_COUNT = 100; @@ -32,5 +36,29 @@ 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' }, + }, + ]; +}; diff --git a/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/NetworkCharts.tsx b/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/NetworkCharts.tsx index 7c381e0abb..506c261dc4 100644 --- a/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/NetworkCharts.tsx +++ b/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/NetworkCharts.tsx @@ -1,24 +1,59 @@ import React, { useMemo } from 'react'; 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 = ({ vmi }) => { - const interfacesNames = useMemo( - () => vmi?.spec?.domain?.devices?.interfaces.map((nic) => nic?.name), - [vmi], + const { t } = useKubevirtTranslation(); + 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(false); + const [selectedNetwork, setSelectedNetwork] = React.useState( + query?.get('network') || 'All networks', ); return (
- {interfacesNames?.map((nic) => ( - - ))} + + {t('Network interface:')} + {' '} + ( + { + setSelectedNetwork(e?.currentTarget?.innerText); + setIsDropdownOpen(false); + }} + > + {nic} + + ))} + isOpen={isDropdownOpen} + toggle={ + setIsDropdownOpen(toogle)}> + {selectedNetwork} + + } + > +
); }; diff --git a/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/NetworkChartsByNIC.tsx b/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/NetworkChartsByNIC.tsx index 0315ac2a7d..c1f69c86f1 100644 --- a/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/NetworkChartsByNIC.tsx +++ b/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/NetworkChartsByNIC.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { V1VirtualMachineInstance } from '@kubevirt-ui/kubevirt-api/kubevirt'; import NetworkThresholdSingleSourceChart from '@kubevirt-utils/components/Charts/NetworkUtil/NetworkThresholdChartSingleSource'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; -import { Card, CardBody, CardTitle, Grid, GridItem, Title } from '@patternfly/react-core'; +import { Card, CardBody, CardTitle, Grid, GridItem } from '@patternfly/react-core'; import useNetworkData from './hook/useNetworkData'; @@ -14,20 +14,16 @@ type NetworkChartsByNICProps = { const NetworkChartsByNIC: React.FC = ({ vmi, nic }) => { const { t } = useKubevirtTranslation(); - const [networkTotal, networkIn, networkOut, links] = useNetworkData(vmi, nic); + const { data, links } = useNetworkData(vmi, nic); return (
- - {t('Network interface:')} - {' '} - {nic} {t('Network in')} - + @@ -35,7 +31,7 @@ const NetworkChartsByNIC: React.FC = ({ vmi, nic }) => {t('Network out')} - + @@ -43,7 +39,7 @@ const NetworkChartsByNIC: React.FC = ({ vmi, nic }) => {t('Network bandwidth')} - + diff --git a/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/hook/useNetworkData.tsx b/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/hook/useNetworkData.tsx index 681d559343..6f190aa470 100644 --- a/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/hook/useNetworkData.tsx +++ b/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/hook/useNetworkData.tsx @@ -2,10 +2,14 @@ import { useMemo } from 'react'; import { V1VirtualMachineInstance } from '@kubevirt-ui/kubevirt-api/kubevirt'; import { getUtilizationQueries } from '@kubevirt-utils/components/Charts/utils/queries'; -import { getPrometheusData, queriesToLink } from '@kubevirt-utils/components/Charts/utils/utils'; +import { + getPrometheusDataAllNics, + getPrometheusDataByNic, + queriesToLink, +} from '@kubevirt-utils/components/Charts/utils/utils'; import { PrometheusEndpoint, - PrometheusValue, + PrometheusResult, usePrometheusPoll, } from '@openshift-console/dynamic-plugin-sdk'; @@ -14,12 +18,14 @@ import useDuration from '../../hooks/useDuration'; type UseNetworkData = ( vmi: V1VirtualMachineInstance, nic: string, -) => [ - networkTotal: PrometheusValue[], - NetworkIn: PrometheusValue[], - NetworkOut: PrometheusValue[], - links: { [key: string]: string }, -]; +) => { + data: { + total: PrometheusResult[]; + in: PrometheusResult[]; + out: PrometheusResult[]; + }; + links: { [key: string]: string }; +}; const useNetworkData: UseNetworkData = (vmi, nic) => { const { currentTime, duration, timespan } = useDuration(); @@ -28,8 +34,31 @@ const useNetworkData: UseNetworkData = (vmi, nic) => { [vmi, duration, nic], ); + const isAllNetwork = nic === 'All networks'; + + const [networkByNICTotal] = usePrometheusPoll({ + query: queries?.NETWORK_TOTAL_BY_INTERFACE_USAGE, + endpoint: PrometheusEndpoint?.QUERY_RANGE, + namespace: vmi?.metadata?.namespace, + endTime: currentTime, + timespan, + }); + const [networkByNICIn] = usePrometheusPoll({ + query: queries?.NETWORK_IN_BY_INTERFACE_USAGE, + endpoint: PrometheusEndpoint?.QUERY_RANGE, + namespace: vmi?.metadata?.namespace, + endTime: currentTime, + timespan, + }); + const [networkByNICOut] = usePrometheusPoll({ + query: queries?.NETWORK_OUT_BY_INTERFACE_USAGE, + endpoint: PrometheusEndpoint?.QUERY_RANGE, + namespace: vmi?.metadata?.namespace, + endTime: currentTime, + timespan, + }); const [networkTotal] = usePrometheusPoll({ - query: queries?.NETWORK_TOTAL_USAGE, + query: isAllNetwork && queries?.NETWORK_TOTAL_USAGE, endpoint: PrometheusEndpoint?.QUERY_RANGE, namespace: vmi?.metadata?.namespace, endTime: currentTime, @@ -37,7 +66,7 @@ const useNetworkData: UseNetworkData = (vmi, nic) => { }); const [networkIn] = usePrometheusPoll({ - query: queries?.NETWORK_IN_BY_INTERFACE_USAGE, + query: isAllNetwork && queries?.NETWORK_IN_USAGE, endpoint: PrometheusEndpoint?.QUERY_RANGE, namespace: vmi?.metadata?.namespace, endTime: currentTime, @@ -45,23 +74,40 @@ const useNetworkData: UseNetworkData = (vmi, nic) => { }); const [networkOut] = usePrometheusPoll({ - query: queries?.NETWORK_OUT_BY_INTERFACE_USAGE, + query: isAllNetwork && queries?.NETWORK_OUT_USAGE, endpoint: PrometheusEndpoint?.QUERY_RANGE, namespace: vmi?.metadata?.namespace, endTime: currentTime, timespan, }); - return [ - getPrometheusData(networkTotal), - getPrometheusData(networkIn), - getPrometheusData(networkOut), - { - in: queriesToLink(queries?.NETWORK_IN_BY_INTERFACE_USAGE), - out: queriesToLink(queries?.NETWORK_OUT_BY_INTERFACE_USAGE), - total: queriesToLink(queries?.NETWORK_TOTAL_USAGE), + return { + data: { + total: [ + ...(isAllNetwork ? getPrometheusDataAllNics(networkTotal) : []), + ...getPrometheusDataByNic(networkByNICTotal, nic), + ], + in: [ + ...(isAllNetwork ? getPrometheusDataAllNics(networkIn) : []), + ...getPrometheusDataByNic(networkByNICIn, nic), + ], + out: [ + ...(isAllNetwork ? getPrometheusDataAllNics(networkOut) : []), + ...getPrometheusDataByNic(networkByNICOut, nic), + ], + }, + links: { + in: isAllNetwork + ? queriesToLink(queries?.NETWORK_IN_BY_INTERFACE_USAGE) + : queriesToLink(queries?.NETWORK_IN_USAGE), + out: isAllNetwork + ? queriesToLink(queries?.NETWORK_OUT_BY_INTERFACE_USAGE) + : queriesToLink(queries?.NETWORK_OUT_USAGE), + total: isAllNetwork + ? queriesToLink(queries?.NETWORK_TOTAL_BY_INTERFACE_USAGE) + : queriesToLink(queries?.NETWORK_TOTAL_USAGE), }, - ]; + }; }; export default useNetworkData; diff --git a/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/hook/useQuery.ts b/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/hook/useQuery.ts new file mode 100644 index 0000000000..63dc61322d --- /dev/null +++ b/src/views/virtualmachines/details/tabs/metrics/NetworkCharts/hook/useQuery.ts @@ -0,0 +1,10 @@ +import { useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; + +const useQuery = () => { + const { search } = useLocation(); + + return useMemo(() => new URLSearchParams(search), [search]); +}; + +export default useQuery; diff --git a/src/views/virtualmachines/details/tabs/metrics/UtilizationCharts/UtilizationCharts.tsx b/src/views/virtualmachines/details/tabs/metrics/UtilizationCharts/UtilizationCharts.tsx index c8d42d2e33..00fbdb7e6e 100644 --- a/src/views/virtualmachines/details/tabs/metrics/UtilizationCharts/UtilizationCharts.tsx +++ b/src/views/virtualmachines/details/tabs/metrics/UtilizationCharts/UtilizationCharts.tsx @@ -3,7 +3,6 @@ import React from 'react'; import { V1VirtualMachineInstance } from '@kubevirt-ui/kubevirt-api/kubevirt'; import CPUThresholdChart from '@kubevirt-utils/components/Charts/CPUUtil/CPUThresholdChart'; import MemoryThresholdChart from '@kubevirt-utils/components/Charts/MemoryUtil/MemoryThresholdChart'; -import NetworkThresholdChart from '@kubevirt-utils/components/Charts/NetworkUtil/NetworkThresholdChart'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk'; import { Card, CardBody, CardTitle, Grid, GridItem } from '@patternfly/react-core'; @@ -34,14 +33,6 @@ const UtilizationCharts: React.FC = ({ vmi, pods }) => { - - - {t('Network interfaces')} - - - - - ); }; diff --git a/src/views/virtualmachines/details/tabs/metrics/VirtualMachineMetricsTab.tsx b/src/views/virtualmachines/details/tabs/metrics/VirtualMachineMetricsTab.tsx index 42af112a2e..8a8c51158c 100644 --- a/src/views/virtualmachines/details/tabs/metrics/VirtualMachineMetricsTab.tsx +++ b/src/views/virtualmachines/details/tabs/metrics/VirtualMachineMetricsTab.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt'; @@ -26,26 +26,28 @@ const VirtualMachineMetricsTab: React.FC = ({ location, }) => { const { t } = useKubevirtTranslation(); - const { vmi, pods } = useVMIAndPodsForVM(vm?.metadata?.name, vm?.metadata?.namespace); + const { vmi, pods, loaded } = useVMIAndPodsForVM(vm?.metadata?.name, vm?.metadata?.namespace); - const getKeyExpandedValue = (key: string): boolean => { - if (isEmpty(location?.search)) return true; - return location?.search?.includes(key); - }; const [expended, setExpended] = useState<{ [key in MetricsTabExpendedSections]: boolean }>({ - [MetricsTabExpendedSections.utilization]: getKeyExpandedValue( - MetricsTabExpendedSections.utilization, - ), - [MetricsTabExpendedSections.storage]: getKeyExpandedValue(MetricsTabExpendedSections.storage), - [MetricsTabExpendedSections.network]: getKeyExpandedValue(MetricsTabExpendedSections.network), - [MetricsTabExpendedSections.migration]: getKeyExpandedValue( - MetricsTabExpendedSections.migration, - ), + [MetricsTabExpendedSections.utilization]: true, + [MetricsTabExpendedSections.storage]: true, + [MetricsTabExpendedSections.network]: true, + [MetricsTabExpendedSections.migration]: true, }); const onToggle = (value) => () => setExpended((currentOpen) => ({ ...currentOpen, [value]: !currentOpen?.[value] })); + useEffect(() => { + if (!isEmpty(location?.search) && loaded) { + const focusedSectionId = Object.values(MetricsTabExpendedSections).find((focusedSection) => + location?.search?.includes(focusedSection), + ); + const focusedExpandableSection = document.getElementById(focusedSectionId); + focusedExpandableSection.scrollIntoView(); + } + }, [location?.search, loaded]); + return (
@@ -54,6 +56,7 @@ const VirtualMachineMetricsTab: React.FC = ({ toggleText={t('Utilization')} onToggle={onToggle(MetricsTabExpendedSections.utilization)} isExpanded={expended?.[MetricsTabExpendedSections.utilization]} + id={MetricsTabExpendedSections.utilization} > @@ -62,6 +65,7 @@ const VirtualMachineMetricsTab: React.FC = ({ toggleText={t('Storage')} onToggle={onToggle(MetricsTabExpendedSections.storage)} isExpanded={expended?.[MetricsTabExpendedSections.storage]} + id={MetricsTabExpendedSections.storage} > @@ -69,6 +73,7 @@ const VirtualMachineMetricsTab: React.FC = ({ toggleText={t('Network')} onToggle={onToggle(MetricsTabExpendedSections.network)} isExpanded={expended?.[MetricsTabExpendedSections.network]} + id={MetricsTabExpendedSections.network} > @@ -76,6 +81,7 @@ const VirtualMachineMetricsTab: React.FC = ({ toggleText={t('Migration')} onToggle={onToggle(MetricsTabExpendedSections.migration)} isExpanded={expended?.[MetricsTabExpendedSections.migration]} + id={MetricsTabExpendedSections.migration} > diff --git a/src/views/virtualmachines/details/tabs/metrics/virtual-machine-metrics-tab.scss b/src/views/virtualmachines/details/tabs/metrics/virtual-machine-metrics-tab.scss index f7c11c67db..62a263f254 100644 --- a/src/views/virtualmachines/details/tabs/metrics/virtual-machine-metrics-tab.scss +++ b/src/views/virtualmachines/details/tabs/metrics/virtual-machine-metrics-tab.scss @@ -49,3 +49,12 @@ width: 100%; height: 150px; } +.network ul.pf-c-dropdown__menu { + max-height: 200px; + overflow-y: scroll; +} +.network-poppver { +display: flex; +justify-content: space-between; +} + diff --git a/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabUtilization/components/NetworkUtil/NetworkUtil.tsx b/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabUtilization/components/NetworkUtil/NetworkUtil.tsx index ac016d557b..b2e3884c27 100644 --- a/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabUtilization/components/NetworkUtil/NetworkUtil.tsx +++ b/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabUtilization/components/NetworkUtil/NetworkUtil.tsx @@ -1,12 +1,16 @@ -import React from 'react'; +import React, { useMemo } from 'react'; +import { Link } from 'react-router-dom'; import xbytes from 'xbytes'; +import VirtualMachineModel from '@kubevirt-ui/kubevirt-api/console/models/VirtualMachineModel'; import { V1VirtualMachineInstance } from '@kubevirt-ui/kubevirt-api/kubevirt'; import ComponentReady from '@kubevirt-utils/components/Charts/ComponentReady/ComponentReady'; import { getUtilizationQueries } from '@kubevirt-utils/components/Charts/utils/queries'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { getResourceUrl } from '@kubevirt-utils/resources/shared'; import { isEmpty } from '@kubevirt-utils/utils/utils'; import { PrometheusEndpoint, usePrometheusPoll } from '@openshift-console/dynamic-plugin-sdk'; +import { Button, Popover, Text, TextVariants } from '@patternfly/react-core'; import useDuration from '@virtualmachines/details/tabs/metrics/hooks/useDuration'; type NetworkUtilProps = { @@ -20,7 +24,7 @@ const NetworkUtil: React.FC = ({ vmi }) => { () => getUtilizationQueries({ obj: vmi, duration }), [vmi, duration], ); - + const interfacesNames = useMemo(() => vmi?.spec?.domain?.devices?.interfaces, [vmi]); const [networkIn] = usePrometheusPoll({ query: queries?.NETWORK_IN_USAGE, endpoint: PrometheusEndpoint?.QUERY, @@ -28,21 +32,26 @@ const NetworkUtil: React.FC = ({ vmi }) => { endTime: currentTime, }); + const [networkTotal] = usePrometheusPoll({ + query: queries?.NETWORK_TOTAL_BY_INTERFACE_USAGE, + endpoint: PrometheusEndpoint?.QUERY, + namespace: vmi?.metadata?.namespace, + endTime: currentTime, + }); + const [networkOut] = usePrometheusPoll({ query: queries?.NETWORK_OUT_USAGE, endpoint: PrometheusEndpoint?.QUERY, namespace: vmi?.metadata?.namespace, endTime: currentTime, }); - + const networkInterfaceTotal = Number(networkTotal?.data?.result?.[0]?.value?.[1]).toFixed(2); const networkInData = +networkIn?.data?.result?.[0]?.value?.[1]; const networkOutData = +networkOut?.data?.result?.[0]?.value?.[1]; - const totalTransferred = xbytes(networkInData + networkOutData || 0, { iec: true, fixed: 0, }); - const isReady = !isEmpty(networkInData) || !isEmpty(networkOutData); return ( @@ -50,8 +59,56 @@ const NetworkUtil: React.FC = ({ vmi }) => {
{t('Network Transfer')} -
{t('Primary Network')}
+ + {t('Network transfer breakdown')} + {t('Top consumer')} + {interfacesNames?.map((networkInterface) => ( +
+ + {networkInterface.name} + + {networkInterface.name === networkInterfaceTotal ? ( +
{`${networkInterfaceTotal} MBps`}
+ ) : ( +
+ {networkTotal?.data?.result?.map( + (name) => + name?.metric?.interface === networkInterface.name && + `${Number(name?.value?.[1]).toFixed(2)} MBps`, + )} +
+ )} +
+ ))} + {interfacesNames?.length > 1 && ( + + {t('View more')} + + )} +
+ } + position="bottom" + > +
+ +
+
+
{`${totalTransferred}s`}