diff --git a/frontend/src/components/common/Resource/Resource.tsx b/frontend/src/components/common/Resource/Resource.tsx
index 26777726d0..dac01c830b 100644
--- a/frontend/src/components/common/Resource/Resource.tsx
+++ b/frontend/src/components/common/Resource/Resource.tsx
@@ -25,6 +25,7 @@ import { KubeObject } from '../../../lib/k8s/KubeObject';
import { KubeObjectInterface } from '../../../lib/k8s/KubeObject';
import { KubeObjectClass } from '../../../lib/k8s/KubeObject';
import Pod, { KubePod, KubeVolume } from '../../../lib/k8s/pod';
+import { METRIC_REFETCH_INTERVAL_MS, PodMetrics } from '../../../lib/k8s/PodMetrics';
import { createRouteURL, RouteURLProps } from '../../../lib/router';
import { getThemeName } from '../../../lib/themes';
import { localeDate, useId } from '../../../lib/util';
@@ -976,6 +977,10 @@ export function OwnedPodsSection(props: OwnedPodsSectionProps) {
};
const [pods, error] = Pod.useList(queryData);
+ const { items: podMetrics } = PodMetrics.useList({
+ ...queryData,
+ refetchInterval: METRIC_REFETCH_INTERVAL_MS,
+ });
const onlyOneNamespace = !!resource.metadata.namespace || resource.kind === 'Namespace';
const hideNamespaceFilter = onlyOneNamespace || noSearch;
@@ -984,6 +989,7 @@ export function OwnedPodsSection(props: OwnedPodsSectionProps) {
hideColumns={hideColumns || onlyOneNamespace ? ['namespace'] : undefined}
pods={pods}
error={error}
+ metrics={podMetrics}
noNamespaceFilter={hideNamespaceFilter}
/>
);
diff --git a/frontend/src/components/namespace/NamespaceDetails.stories.tsx b/frontend/src/components/namespace/NamespaceDetails.stories.tsx
index 0a3a4c3496..cc952d4c56 100644
--- a/frontend/src/components/namespace/NamespaceDetails.stories.tsx
+++ b/frontend/src/components/namespace/NamespaceDetails.stories.tsx
@@ -69,6 +69,31 @@ Active.parameters = {
http.get('http://localhost:4466/api/v1/resourcequotas', () => HttpResponse.error()),
http.get('http://localhost:4466/api/v1/limitranges', () => HttpResponse.error()),
http.get('http://localhost:4466/api/v1/pods', () => HttpResponse.error()),
+ http.get(
+ 'http://localhost:4466/apis/metrics.k8s.io/v1beta1/namespaces/my-namespaces/pods',
+ () =>
+ HttpResponse.json({
+ kind: 'PodMetricsList',
+ apiVersion: 'metrics.k8s.io/v1beta1',
+ metadata: {},
+ items: [
+ {
+ metadata: {
+ name: 'successful',
+ },
+ containers: [
+ {
+ name: 'etcd',
+ usage: {
+ cpu: '16317640n',
+ memory: '47544Ki',
+ },
+ },
+ ],
+ },
+ ],
+ })
+ ),
],
},
},
diff --git a/frontend/src/components/pod/List.tsx b/frontend/src/components/pod/List.tsx
index f4887f3b8a..11aba09604 100644
--- a/frontend/src/components/pod/List.tsx
+++ b/frontend/src/components/pod/List.tsx
@@ -4,6 +4,8 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { ApiError } from '../../lib/k8s/apiProxy';
import Pod from '../../lib/k8s/pod';
+import { METRIC_REFETCH_INTERVAL_MS, PodMetrics } from '../../lib/k8s/PodMetrics';
+import { parseCpu, parseRam, unparseCpu, unparseRam } from '../../lib/units';
import { timeAgo } from '../../lib/util';
import { useNamespaces } from '../../redux/filterSlice';
import { HeadlampEventType, useEventCallback } from '../../redux/headlampEventSlice';
@@ -62,6 +64,7 @@ function getReadinessGatesStatus(pods: Pod) {
export interface PodListProps {
pods: Pod[] | null;
error: ApiError | null;
+ metrics: PodMetrics[] | null;
hideColumns?: ('namespace' | 'restarts')[];
reflectTableInURL?: SimpleTableProps['reflectInURL'];
noNamespaceFilter?: boolean;
@@ -72,6 +75,7 @@ export function PodListRenderer(props: PodListProps) {
const {
pods,
error,
+ metrics,
hideColumns = [],
reflectTableInURL = 'pods',
noNamespaceFilter,
@@ -117,6 +121,41 @@ export function PodListRenderer(props: PodListProps) {
getValue: pod => pod.getDetailedStatus().reason,
render: makePodStatusLabel,
},
+ {
+ id: 'cpu',
+ label: t('CPU'),
+ gridTemplate: 'min-content',
+ getValue: pod => {
+ const metric = metrics?.find(it => it.getName() === pod.getName());
+ if (!metric) return;
+
+ const cpuUsage =
+ metric?.jsonData.containers
+ .map(it => parseCpu(it.usage.cpu))
+ .reduce((a, b) => a + b, 0) ?? 0;
+
+ const { value, unit } = unparseCpu(String(cpuUsage));
+
+ return `${value} ${unit}`;
+ },
+ },
+ {
+ id: 'memory',
+ label: t('Memory'),
+ getValue: pod => {
+ const metric = metrics?.find(it => it.getName() === pod.getName());
+ if (!metric) return;
+
+ const memoryUsage =
+ metric?.jsonData.containers
+ .map(it => parseRam(it.usage.memory))
+ .reduce((a, b) => a + b, 0) ?? 0;
+
+ const { value, unit } = unparseRam(memoryUsage);
+
+ return `${value} ${unit}`;
+ },
+ },
{
id: 'ip',
label: t('glossary|IP'),
@@ -218,6 +257,10 @@ export function PodListRenderer(props: PodListProps) {
export default function PodList() {
const { items, error, clusterErrors } = Pod.useList({ namespace: useNamespaces() });
+ const { items: podMetrics, clusterErrors: metricsClusterErrors } = PodMetrics.useList({
+ namespace: useNamespaces(),
+ refetchInterval: METRIC_REFETCH_INTERVAL_MS,
+ });
const dispatchHeadlampEvent = useEventCallback(HeadlampEventType.LIST_VIEW);
@@ -230,6 +273,12 @@ export default function PodList() {
}, [items, error]);
return (
-
+
+
+
+
+
+
+ CPU
+
+
+
+
+
+
+ 0
+
+
+ |
+
+
+
+
+
+
+
+ Memory
+
+
+
+
+
+
+ 0
+
+
+ |
+ | + | @@ -876,6 +992,16 @@ | ++ 16.32 m + | ++ 46.4 Mi + | @@ -983,6 +1109,12 @@ + | + | @@ -1092,6 +1224,12 @@ | ++ | @@ -1201,6 +1339,12 @@ | ++ | @@ -1310,6 +1454,12 @@ | ++ | @@ -1419,6 +1569,12 @@ | ++ | @@ -1528,6 +1684,12 @@ | ++ |
diff --git a/frontend/src/lib/k8s/KubeObject.ts b/frontend/src/lib/k8s/KubeObject.ts
index 4d457e2e7d..b415d09e22 100644
--- a/frontend/src/lib/k8s/KubeObject.ts
+++ b/frontend/src/lib/k8s/KubeObject.ts
@@ -300,11 +300,14 @@ export class KubeObject |
---|