Skip to content

Commit

Permalink
CNV-49533: Add Memory, CPU and Network usage on vm list
Browse files Browse the repository at this point in the history
  • Loading branch information
upalatucci committed Dec 4, 2024
1 parent c0b910f commit 12e41c2
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 4 deletions.
2 changes: 2 additions & 0 deletions src/views/virtualmachines/list/VirtualMachinesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import VirtualMachineRow from './components/VirtualMachineRow/VirtualMachineRow'
import VirtualMachinesCreateButton from './components/VirtualMachinesCreateButton/VirtualMachinesCreateButton';
import useSelectedFilters from './hooks/useSelectedFilters';
import useVirtualMachineColumns from './hooks/useVirtualMachineColumns';
import useVMMetrics from './hooks/useVMMetrics';
import { deselectAll, selectAll, selectedVMs } from './selectedVMs';

import '@kubevirt-utils/styles/list-managment-group.scss';
Expand All @@ -63,6 +64,7 @@ const VirtualMachinesList: FC<VirtualMachinesListProps> = ({ kind, namespace })
const isProxyPodAlive = useKubevirtDataPodHealth();

useSignals();
useVMMetrics();

const query = useQuery();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import { setSelectedTreeItem, treeDataMap } from '@virtualmachines/tree/utils/ut
import VirtualMachineStatus from '../VirtualMachineStatus/VirtualMachineStatus';
import { VMStatusConditionLabelList } from '../VMStatusConditionLabel';

import CPUPercentage from './components/CPUPercentage';
import MemoryPercentage from './components/MemoryPercentage';
import NetworkUsage from './components/NetworkUsage';

import './virtual-machine-row-layout.scss';

const VirtualMachineRowLayout: React.FC<
Expand All @@ -32,6 +36,9 @@ const VirtualMachineRowLayout: React.FC<
> = ({ activeColumnIDs, obj, rowData: { ips, isSingleNodeCluster, node, vmim } }) => {
const selected = isVMSelected(obj);

const vmName = getName(obj);
const vmNamespace = getNamespace(obj);

const [actions] = useVirtualMachineActionsProvider(obj, vmim, isSingleNodeCluster);
return (
<>
Expand All @@ -45,19 +52,19 @@ const VirtualMachineRowLayout: React.FC<
<TableData activeColumnIDs={activeColumnIDs} className="vm-column" id="name">
<ResourceLink
onClick={() => {
setSelectedTreeItem(treeDataMap.value[`${getNamespace(obj)}/${getName(obj)}`]);
setSelectedTreeItem(treeDataMap.value[`${vmNamespace}/${vmName}`]);
}}
groupVersionKind={VirtualMachineModelGroupVersionKind}
name={getName(obj)}
namespace={getNamespace(obj)}
name={vmName}
namespace={vmNamespace}
/>
</TableData>
<TableData
activeColumnIDs={activeColumnIDs}
className="pf-m-width-10 vm-column"
id="namespace"
>
<ResourceLink kind="Namespace" name={getNamespace(obj)} />
<ResourceLink kind="Namespace" name={vmNamespace} />
</TableData>
<TableData activeColumnIDs={activeColumnIDs} className="pf-m-width-15 vm-column" id="status">
<VirtualMachineStatus vm={obj} />
Expand All @@ -82,6 +89,15 @@ const VirtualMachineRowLayout: React.FC<
>
{ips}
</TableData>
<TableData activeColumnIDs={activeColumnIDs} className="vm-column" id="memory-usage">
<MemoryPercentage vmName={vmName} vmNamespace={vmNamespace} />
</TableData>
<TableData activeColumnIDs={activeColumnIDs} className="vm-column" id="cpu-usage">
<CPUPercentage vmName={vmName} vmNamespace={vmNamespace} />
</TableData>
<TableData activeColumnIDs={activeColumnIDs} className="vm-column" id="network-usage">
<NetworkUsage vmName={vmName} vmNamespace={vmNamespace} />
</TableData>
<TableData
activeColumnIDs={activeColumnIDs}
className="dropdown-kebab-pf pf-v5-c-table__action"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { FC, memo } from 'react';

import { NO_DATA_DASH } from '@kubevirt-utils/resources/vm/utils/constants';
import { isEmpty } from '@kubevirt-utils/utils/utils';
import { getVMMetrics } from '@virtualmachines/list/metrics';

type CPUPercentageProps = {
vmName: string;
vmNamespace: string;
};

const CPUPercentage: FC<CPUPercentageProps> = ({ vmName, vmNamespace }) => {
const { cpuRequested, cpuUsage } = getVMMetrics(vmName, vmNamespace);

if (isEmpty(cpuRequested) || isEmpty(cpuUsage)) return <span>{NO_DATA_DASH}</span>;

const percentage = Math.round((cpuUsage * 10000) / cpuRequested) / 100;
return <span>{percentage}%</span>;
};

export default memo(CPUPercentage);
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { FC, memo } from 'react';

import { NO_DATA_DASH } from '@kubevirt-utils/resources/vm/utils/constants';
import { isEmpty } from '@kubevirt-utils/utils/utils';
import { getVMMetrics } from '@virtualmachines/list/metrics';

type MemoryPercentageProps = {
vmName: string;
vmNamespace: string;
};

const MemoryPercentage: FC<MemoryPercentageProps> = ({ vmName, vmNamespace }) => {
const { memoryRequested, memoryUsage } = getVMMetrics(vmName, vmNamespace);

if (isEmpty(memoryRequested) || isEmpty(memoryUsage)) return <span>{NO_DATA_DASH}</span>;

const percentage = Math.round((memoryUsage / memoryRequested) * 10000) / 100;
return <span>{percentage}%</span>;
};

export default memo(MemoryPercentage);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { FC } from 'react';
import xbytes from 'xbytes';

import { NO_DATA_DASH } from '@kubevirt-utils/resources/vm/utils/constants';
import { isEmpty } from '@kubevirt-utils/utils/utils';
import { getVMMetrics } from '@virtualmachines/list/metrics';

type NetworkUsageProps = {
vmName: string;
vmNamespace: string;
};

const NetworkUsage: FC<NetworkUsageProps> = ({ vmName, vmNamespace }) => {
const { networkUsage } = getVMMetrics(vmName, vmNamespace);
if (isEmpty(networkUsage)) return <>{NO_DATA_DASH}</>;

const totalTransferred = xbytes(networkUsage || 0, {
fixed: 0,
iec: true,
});

return <div>{totalTransferred}ps</div>;
};

export default NetworkUsage;
23 changes: 23 additions & 0 deletions src/views/virtualmachines/list/hooks/constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,25 @@
import { ALL_NAMESPACES_SESSION_KEY } from '@kubevirt-utils/hooks/constants';

export const TEXT_FILTER_NAME_ID = 'name';
export const TEXT_FILTER_LABELS_ID = 'labels';

export const VMListQueries = {
CPU_REQUESTED: 'CPU_REQUESTED',
CPU_USAGE: 'CPU_USAGE',
MEMORY_REQUESTED: 'MEMORY_REQUESTED',
MEMORY_USAGE: 'MEMORY_USAGE',
NETWORK_TOTAL_USAGE: 'NETWORK_TOTAL_USAGE',
};

export const getVMListQueries = (namespace: string) => {
const namespaceFilter =
namespace === ALL_NAMESPACES_SESSION_KEY ? '' : `namespace='${namespace}'`;

return {
[VMListQueries.CPU_REQUESTED]: `kube_pod_resource_request{resource='cpu',${namespaceFilter}}`,
[VMListQueries.CPU_USAGE]: `rate(kubevirt_vmi_cpu_usage_seconds_total{${namespaceFilter}}[5m])`,
[VMListQueries.MEMORY_REQUESTED]: `kube_pod_resource_request{resource='memory',${namespaceFilter}}`,
[VMListQueries.MEMORY_USAGE]: `kubevirt_vmi_memory_used_bytes{${namespaceFilter}}`,
[VMListQueries.NETWORK_TOTAL_USAGE]: `rate(kubevirt_vmi_network_transmit_bytes_total{${namespaceFilter}}[5m]) + rate(kubevirt_vmi_network_receive_bytes_total{${namespaceFilter}}[5m])`,
};
};
132 changes: 132 additions & 0 deletions src/views/virtualmachines/list/hooks/useVMMetrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { useEffect, useMemo } from 'react';

import { IoK8sApiCoreV1Pod } from '@kubevirt-ui/kubevirt-api/kubernetes';
import { ALL_NAMESPACES_SESSION_KEY } from '@kubevirt-utils/hooks/constants';
import { modelToGroupVersionKind, PodModel } from '@kubevirt-utils/models';
import { isEmpty } from '@kubevirt-utils/utils/utils';
import {
PrometheusEndpoint,
useActiveNamespace,
useK8sWatchResource,
usePrometheusPoll,
} from '@openshift-console/dynamic-plugin-sdk';

import {
setVMCPURequested,
setVMCPUUsage,
setVMMemoryRequested,
setVMMemoryUsage,
setVMNetworkUsage,
} from '../metrics';

import { getVMListQueries, VMListQueries } from './constants';
import { getVMNamesFromPodsNames } from './utils';

const useVMMetrics = () => {
const [activeNamespace] = useActiveNamespace();
const allNamespace = activeNamespace === ALL_NAMESPACES_SESSION_KEY;
const currentTime = useMemo<number>(() => Date.now(), []);

const [pods] = useK8sWatchResource<IoK8sApiCoreV1Pod[]>({
groupVersionKind: modelToGroupVersionKind(PodModel),
isList: true,
namespace: allNamespace ? undefined : activeNamespace,
});

const launcherNameToVMName = useMemo(() => getVMNamesFromPodsNames(pods), [pods]);

const queries = useMemo(() => getVMListQueries(activeNamespace), [activeNamespace]);

const [memoryUsageResponse] = usePrometheusPoll({
endpoint: PrometheusEndpoint?.QUERY,
endTime: currentTime,
namespace: allNamespace ? undefined : activeNamespace,
query: queries?.[VMListQueries.MEMORY_USAGE],
});

const [networkTotalResponse] = usePrometheusPoll({
endpoint: PrometheusEndpoint?.QUERY,
endTime: currentTime,
namespace: allNamespace ? undefined : activeNamespace,
query: queries?.NETWORK_TOTAL_USAGE,
});

const [memoryRequestedResponse] = usePrometheusPoll({
endpoint: PrometheusEndpoint?.QUERY,
endTime: currentTime,
namespace: allNamespace ? undefined : activeNamespace,
query: queries?.[VMListQueries.MEMORY_REQUESTED],
});

const [cpuUsageResponse] = usePrometheusPoll({
endpoint: PrometheusEndpoint?.QUERY,
endTime: currentTime,
namespace: allNamespace ? undefined : activeNamespace,
query: queries?.[VMListQueries.CPU_USAGE],
});

const [cpuRequestedResponse] = usePrometheusPoll({
endpoint: PrometheusEndpoint?.QUERY,
endTime: currentTime,
namespace: allNamespace ? undefined : activeNamespace,
query: queries?.[VMListQueries.CPU_REQUESTED],
});

useEffect(() => {
networkTotalResponse?.data?.result?.forEach((result) => {
const vmName = result?.metric?.name;
const vmNamespace = result?.metric?.namespace;
const memoryUsage = parseFloat(result?.value?.[1]);

setVMNetworkUsage(vmName, vmNamespace, memoryUsage);
});
}, [networkTotalResponse]);

useEffect(() => {
memoryUsageResponse?.data?.result?.forEach((result) => {
const vmName = result?.metric?.name;
const vmNamespace = result?.metric?.namespace;
const memoryUsage = parseFloat(result?.value?.[1]);

setVMMemoryUsage(vmName, vmNamespace, memoryUsage);
});
}, [memoryUsageResponse]);

useEffect(() => {
memoryRequestedResponse?.data?.result?.forEach((result) => {
const vmName = launcherNameToVMName?.[`${result?.metric?.namespace}-${result?.metric?.pod}`];

if (isEmpty(vmName)) return;
const vmNamespace = result?.metric?.namespace;

const memoryRequested = parseFloat(result?.value?.[1]);

setVMMemoryRequested(vmName, vmNamespace, memoryRequested);
});
}, [memoryRequestedResponse, launcherNameToVMName]);

useEffect(() => {
cpuUsageResponse?.data?.result?.forEach((result) => {
const vmName = result?.metric?.name;
const vmNamespace = result?.metric?.namespace;
const cpuUsage = parseFloat(result?.value?.[1]);

setVMCPUUsage(vmName, vmNamespace, cpuUsage);
});
}, [cpuUsageResponse]);

useEffect(() => {
cpuRequestedResponse?.data?.result?.forEach((result) => {
const vmName = launcherNameToVMName?.[`${result?.metric?.namespace}-${result?.metric?.pod}`];

if (isEmpty(vmName)) return;

const vmNamespace = result?.metric?.namespace;
const cpuRequested = parseFloat(result?.value?.[1]);

setVMCPURequested(vmName, vmNamespace, cpuRequested);
});
}, [cpuRequestedResponse, launcherNameToVMName]);
};

export default useVMMetrics;
15 changes: 15 additions & 0 deletions src/views/virtualmachines/list/hooks/useVirtualMachineColumns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ const useVirtualMachineColumns = (
props: { className: 'pf-m-width-10' },
title: t('IP address'),
},
{
additional: true,
id: 'memory-usage',
title: t('Memory'),
},
{
additional: true,
id: 'cpu-usage',
title: t('CPU'),
},
{
additional: true,
id: 'network-usage',
title: t('Network'),
},
{
id: '',
props: { className: 'dropdown-kebab-pf pf-v5-c-table__action' },
Expand Down
21 changes: 21 additions & 0 deletions src/views/virtualmachines/list/hooks/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import VirtualMachineInstanceModel from '@kubevirt-ui/kubevirt-api/console/models/VirtualMachineInstanceModel';
import { IoK8sApiCoreV1Pod } from '@kubevirt-ui/kubevirt-api/kubernetes';
import { getName, getNamespace } from '@kubevirt-utils/resources/shared';
import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk';

const getVMIOwner = (resource: K8sResourceCommon) =>
resource?.metadata?.ownerReferences?.find(
(owner) => owner.kind === VirtualMachineInstanceModel.kind,
);

export const getVMNamesFromPodsNames = (pods: IoK8sApiCoreV1Pod[]) => {
return pods?.reduce((acc, pod) => {
const vmiOwner = getVMIOwner(pod);

if (!vmiOwner) return acc;

acc[`${getNamespace(pod)}-${getName(pod)}`] = vmiOwner.name;

return acc;
}, {});
};
Loading

0 comments on commit 12e41c2

Please sign in to comment.