diff --git a/packages/technical-features/metrics/src/cluster-overview/cluster-overview-ui-blocks/cluster-pie-charts.injectable.tsx b/packages/technical-features/metrics/src/cluster-overview/cluster-overview-ui-blocks/cluster-pie-charts.injectable.tsx index e4fcbbdcd9..c6990d1ef2 100644 --- a/packages/technical-features/metrics/src/cluster-overview/cluster-overview-ui-blocks/cluster-pie-charts.injectable.tsx +++ b/packages/technical-features/metrics/src/cluster-overview/cluster-overview-ui-blocks/cluster-pie-charts.injectable.tsx @@ -3,312 +3,302 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -// import styles from "./cluster-pie-charts.module.scss"; +import styles from "./cluster-pie-charts.module.scss"; import React from "react"; -// import { observer } from "mobx-react"; -// import type { ClusterOverviewStore } from "../cluster-overview-store/cluster-overview-store"; -// import { MetricNodeRole } from "../cluster-overview-store/cluster-overview-store"; -// import { Spinner } from "../../spinner"; -// import { Icon } from "../../icon"; -// import type { NodeStore } from "../../+nodes/store"; -// import type { PieChartData } from "../../chart"; -// import { PieChart } from "../../chart"; -// import { ClusterNoMetrics } from "../cluster-no-metrics"; -// import { bytesToUnits, cssNames } from "@k8slens/utilities"; -// import type { LensTheme } from "../../../themes/lens-theme"; -// import { getMetricLastPoints } from "../../../../common/k8s-api/endpoints/metrics.api"; -// import { withInjectables } from "@ogre-tools/injectable-react"; -// import clusterOverviewStoreInjectable from "../cluster-overview-store/cluster-overview-store.injectable"; -// import nodeStoreInjectable from "../../+nodes/store.injectable"; -// import type { IComputedValue } from "mobx"; -// import activeThemeInjectable from "../../../themes/active.injectable"; -// import type { ClusterMetricData } from "../../../../common/k8s-api/endpoints/metrics.api/request-cluster-metrics-by-node-names.injectable"; +import { observer } from "mobx-react"; +import { Spinner } from "../components/spinner"; +import { Icon } from "../components/icon"; +import { PieChart, PieChartData } from "../components/chart"; +import { ClusterNoMetrics } from "../components/cluster-no-metrics"; +import { bytesToUnits, cssNames } from "@k8slens/utilities"; +import type { LensTheme } from "../components/chart/lens-theme"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import type { IComputedValue } from "mobx"; +import { ClusterMetricData, getMetricLastPoints } from "../metrics.api"; import { getInjectable } from "@ogre-tools/injectable"; -import { clusterOverviewUIBlockInjectionToken } from "../injection-tokens"; +import { activeThemeInjectionToken, ClusterOverviewStore, clusterOverviewStoreInjectionToken, clusterOverviewUIBlockInjectionToken, MetricNodeRole, NodeStore, nodeStoreInjectionToken } from "../injection-tokens"; -// function createLabels(rawLabelData: [string, number | undefined][]): string[] { -// return rawLabelData.map( -// ([key, value]) => `${key}: ${value?.toFixed(2) || "N/A"}` -// ); -// } -// -// const checkedBytesToUnits = (value: number | undefined) => -// typeof value === "number" ? bytesToUnits(value) : "N/A"; -// -// interface Dependencies { -// clusterOverviewStore: ClusterOverviewStore; -// nodeStore: NodeStore; -// activeTheme: IComputedValue; -// } +function createLabels(rawLabelData: [string, number | undefined][]): string[] { + return rawLabelData.map( + ([key, value]) => `${key}: ${value?.toFixed(2) || "N/A"}` + ); +} -// const NonInjectedClusterPieCharts = observer( -// ({ clusterOverviewStore, nodeStore, activeTheme }: Dependencies) => { -// const renderLimitWarning = () => { -// return ( -//
-// -//

Specified limits are higher than node capacity!

-//
-// ); -// }; -// -// const renderCharts = ( -// lastPoints: Partial> -// ) => { -// const { -// memoryUsage, -// memoryRequests, -// memoryAllocatableCapacity, -// memoryCapacity, -// memoryLimits, -// cpuUsage, -// cpuRequests, -// cpuAllocatableCapacity, -// cpuCapacity, -// cpuLimits, -// podUsage, -// podAllocatableCapacity, -// podCapacity, -// } = lastPoints; -// -// if ( -// typeof cpuCapacity !== "number" || -// typeof cpuAllocatableCapacity !== "number" || -// typeof podCapacity !== "number" || -// typeof podAllocatableCapacity !== "number" || -// typeof memoryAllocatableCapacity !== "number" || -// typeof memoryCapacity !== "number" || -// typeof memoryUsage !== "number" || -// typeof memoryRequests !== "number" -// ) { -// return null; -// } -// -// const defaultColor = activeTheme.get().colors.pieChartDefaultColor; -// -// const cpuData: PieChartData = { -// datasets: [ -// { -// data: [cpuUsage, cpuUsage ? cpuAllocatableCapacity - cpuUsage : 1], -// backgroundColor: ["#c93dce", defaultColor], -// id: "cpuUsage", -// label: "Usage", -// }, -// { -// data: [ -// cpuRequests, -// cpuRequests ? cpuAllocatableCapacity - cpuRequests : 1, -// ], -// backgroundColor: ["#4caf50", defaultColor], -// id: "cpuRequests", -// label: "Requests", -// }, -// { -// data: [ -// cpuLimits, -// Math.max( -// 0, -// cpuAllocatableCapacity - (cpuLimits ?? cpuAllocatableCapacity) -// ), -// ], -// backgroundColor: ["#3d90ce", defaultColor], -// id: "cpuLimits", -// label: "Limits", -// }, -// ], -// labels: createLabels([ -// ["Usage", cpuUsage], -// ["Requests", cpuRequests], -// ["Limits", cpuLimits], -// ["Allocatable Capacity", cpuAllocatableCapacity], -// ["Capacity", cpuCapacity], -// ]), -// }; -// const memoryData: PieChartData = { -// datasets: [ -// { -// data: [ -// memoryUsage, -// memoryUsage ? memoryAllocatableCapacity - memoryUsage : 1, -// ], -// backgroundColor: ["#c93dce", defaultColor], -// id: "memoryUsage", -// label: "Usage", -// }, -// { -// data: [ -// memoryRequests, -// memoryRequests ? memoryAllocatableCapacity - memoryRequests : 1, -// ], -// backgroundColor: ["#4caf50", defaultColor], -// id: "memoryRequests", -// label: "Requests", -// }, -// { -// data: [ -// memoryLimits, -// Math.max( -// 0, -// memoryAllocatableCapacity - -// (memoryLimits ?? memoryAllocatableCapacity) -// ), -// ], -// backgroundColor: ["#3d90ce", defaultColor], -// id: "memoryLimits", -// label: "Limits", -// }, -// ], -// labels: [ -// `Usage: ${bytesToUnits(memoryUsage)}`, -// `Requests: ${bytesToUnits(memoryRequests)}`, -// `Limits: ${checkedBytesToUnits(memoryLimits)}`, -// `Allocatable Capacity: ${bytesToUnits(memoryAllocatableCapacity)}`, -// `Capacity: ${bytesToUnits(memoryCapacity)}`, -// ], -// }; -// const podsData: PieChartData = { -// datasets: [ -// { -// data: [podUsage, podUsage ? podAllocatableCapacity - podUsage : 1], -// backgroundColor: ["#4caf50", defaultColor], -// id: "podUsage", -// label: "Usage", -// tooltipLabels: [ -// (percent) => `Usage: ${percent}`, -// (percent) => `Available: ${percent}`, -// ], -// }, -// ], -// labels: [ -// `Usage: ${podUsage || 0}`, -// `Capacity: ${podAllocatableCapacity}`, -// ], -// }; -// -// return ( -//
-//
-// -// {(cpuLimits ?? cpuAllocatableCapacity) > cpuAllocatableCapacity && -// renderLimitWarning()} -//
-//
-// -// {(memoryLimits ?? memoryAllocatableCapacity) > -// memoryAllocatableCapacity && renderLimitWarning()} -//
-//
-// -//
-//
-// ); -// }; -// -// const renderContent = ({ -// metricNodeRole, -// metrics, -// }: ClusterOverviewStore) => { -// const { masterNodes, workerNodes } = nodeStore; -// const nodes = -// metricNodeRole === MetricNodeRole.MASTER ? masterNodes : workerNodes; -// -// if (!nodes.length) { -// return ( -//
-// -// No Nodes Available. -//
-// ); -// } -// -// if (!metrics) { -// return ( -//
-// -//
-// ); -// } -// -// const lastPoints = getMetricLastPoints(metrics); -// const { memoryCapacity, cpuCapacity, podCapacity } = lastPoints; -// -// if (!memoryCapacity || !cpuCapacity || !podCapacity) { -// return ( -//
-// -//
-// ); -// } -// -// return renderCharts(lastPoints); -// }; -// -// return
{renderContent(clusterOverviewStore)}
; -// } -// ); +const checkedBytesToUnits = (value: number | undefined) => + typeof value === "number" ? bytesToUnits(value) : "N/A"; -// const ClusterPieCharts = withInjectables( -// NonInjectedClusterPieCharts, -// { -// getProps: (di) => ({ -// clusterOverviewStore: di.inject(clusterOverviewStoreInjectable), -// nodeStore: di.inject(nodeStoreInjectable), -// activeTheme: di.inject(activeThemeInjectable), -// }), -// } -// ); +interface Dependencies { + clusterOverviewStore: ClusterOverviewStore; + nodeStore: NodeStore; + activeTheme: IComputedValue; +} -const ClusterPieCharts = () =>
cluster-pie-charts.injectable
; +const NonInjectedClusterPieCharts = observer( + ({ clusterOverviewStore, nodeStore, activeTheme }: Dependencies) => { + const renderLimitWarning = () => { + return ( +
+ +

Specified limits are higher than node capacity!

+
+ ); + }; + + const renderCharts = ( + lastPoints: Partial> + ) => { + const { + memoryUsage, + memoryRequests, + memoryAllocatableCapacity, + memoryCapacity, + memoryLimits, + cpuUsage, + cpuRequests, + cpuAllocatableCapacity, + cpuCapacity, + cpuLimits, + podUsage, + podAllocatableCapacity, + podCapacity, + } = lastPoints; + + if ( + typeof cpuCapacity !== "number" || + typeof cpuAllocatableCapacity !== "number" || + typeof podCapacity !== "number" || + typeof podAllocatableCapacity !== "number" || + typeof memoryAllocatableCapacity !== "number" || + typeof memoryCapacity !== "number" || + typeof memoryUsage !== "number" || + typeof memoryRequests !== "number" + ) { + return null; + } + + const defaultColor = activeTheme.get().colors.pieChartDefaultColor; + + const cpuData: PieChartData = { + datasets: [ + { + data: [cpuUsage, cpuUsage ? cpuAllocatableCapacity - cpuUsage : 1], + backgroundColor: ["#c93dce", defaultColor], + id: "cpuUsage", + label: "Usage", + }, + { + data: [ + cpuRequests, + cpuRequests ? cpuAllocatableCapacity - cpuRequests : 1, + ], + backgroundColor: ["#4caf50", defaultColor], + id: "cpuRequests", + label: "Requests", + }, + { + data: [ + cpuLimits, + Math.max( + 0, + cpuAllocatableCapacity - (cpuLimits ?? cpuAllocatableCapacity) + ), + ], + backgroundColor: ["#3d90ce", defaultColor], + id: "cpuLimits", + label: "Limits", + }, + ], + labels: createLabels([ + ["Usage", cpuUsage], + ["Requests", cpuRequests], + ["Limits", cpuLimits], + ["Allocatable Capacity", cpuAllocatableCapacity], + ["Capacity", cpuCapacity], + ]), + }; + const memoryData: PieChartData = { + datasets: [ + { + data: [ + memoryUsage, + memoryUsage ? memoryAllocatableCapacity - memoryUsage : 1, + ], + backgroundColor: ["#c93dce", defaultColor], + id: "memoryUsage", + label: "Usage", + }, + { + data: [ + memoryRequests, + memoryRequests ? memoryAllocatableCapacity - memoryRequests : 1, + ], + backgroundColor: ["#4caf50", defaultColor], + id: "memoryRequests", + label: "Requests", + }, + { + data: [ + memoryLimits, + Math.max( + 0, + memoryAllocatableCapacity - + (memoryLimits ?? memoryAllocatableCapacity) + ), + ], + backgroundColor: ["#3d90ce", defaultColor], + id: "memoryLimits", + label: "Limits", + }, + ], + labels: [ + `Usage: ${bytesToUnits(memoryUsage)}`, + `Requests: ${bytesToUnits(memoryRequests)}`, + `Limits: ${checkedBytesToUnits(memoryLimits)}`, + `Allocatable Capacity: ${bytesToUnits(memoryAllocatableCapacity)}`, + `Capacity: ${bytesToUnits(memoryCapacity)}`, + ], + }; + const podsData: PieChartData = { + datasets: [ + { + data: [podUsage, podUsage ? podAllocatableCapacity - podUsage : 1], + backgroundColor: ["#4caf50", defaultColor], + id: "podUsage", + label: "Usage", + tooltipLabels: [ + (percent) => `Usage: ${percent}`, + (percent) => `Available: ${percent}`, + ], + }, + ], + labels: [ + `Usage: ${podUsage || 0}`, + `Capacity: ${podAllocatableCapacity}`, + ], + }; + + return ( +
+
+ + {(cpuLimits ?? cpuAllocatableCapacity) > cpuAllocatableCapacity && + renderLimitWarning()} +
+
+ + {(memoryLimits ?? memoryAllocatableCapacity) > + memoryAllocatableCapacity && renderLimitWarning()} +
+
+ +
+
+ ); + }; + + const renderContent = ({ + metricNodeRole, + metrics, + }: ClusterOverviewStore) => { + const { masterNodes, workerNodes } = nodeStore; + const nodes = + metricNodeRole === MetricNodeRole.MASTER ? masterNodes : workerNodes; + + if (!nodes.length) { + return ( +
+ + No Nodes Available. +
+ ); + } + + if (!metrics) { + return ( +
+ +
+ ); + } + + const lastPoints = getMetricLastPoints(metrics); + const { memoryCapacity, cpuCapacity, podCapacity } = lastPoints; + + if (!memoryCapacity || !cpuCapacity || !podCapacity) { + return ( +
+ +
+ ); + } + + return renderCharts(lastPoints); + }; + + return
{renderContent(clusterOverviewStore)}
; + } +); + +const ClusterPieCharts = withInjectables( + NonInjectedClusterPieCharts, + { + getProps: (di) => ({ + clusterOverviewStore: di.inject(clusterOverviewStoreInjectionToken), + nodeStore: di.inject(nodeStoreInjectionToken), + activeTheme: di.inject(activeThemeInjectionToken), + }), + } +); const clusterPieChartsClusterOverviewInjectable = getInjectable({ id: "cluster-pie-charts-cluster-overview", diff --git a/packages/technical-features/metrics/src/cluster-overview/injection-tokens.ts b/packages/technical-features/metrics/src/cluster-overview/injection-tokens.ts index d3bdf9e367..aab83e28a2 100644 --- a/packages/technical-features/metrics/src/cluster-overview/injection-tokens.ts +++ b/packages/technical-features/metrics/src/cluster-overview/injection-tokens.ts @@ -68,4 +68,3 @@ export enum MetricNodeRole { MASTER = "master", WORKER = "worker", } - diff --git a/packages/technical-features/metrics/src/cluster-overview/metrics.api.ts b/packages/technical-features/metrics/src/cluster-overview/metrics.api.ts index 69465bbf6e..eddf022605 100644 --- a/packages/technical-features/metrics/src/cluster-overview/metrics.api.ts +++ b/packages/technical-features/metrics/src/cluster-overview/metrics.api.ts @@ -8,6 +8,27 @@ import moment from "moment"; import { isDefined, object } from "@k8slens/utilities"; +// hack, copied from: +// packages/core/src/common/k8s-api/endpoints/metrics.api/request-cluster-metrics-by-node-names.injectable.ts + +export interface ClusterMetricData { + memoryUsage: MetricData; + memoryRequests: MetricData; + memoryLimits: MetricData; + memoryCapacity: MetricData; + memoryAllocatableCapacity: MetricData; + cpuUsage: MetricData; + cpuRequests: MetricData; + cpuLimits: MetricData; + cpuCapacity: MetricData; + cpuAllocatableCapacity: MetricData; + podUsage: MetricData; + podCapacity: MetricData; + podAllocatableCapacity: MetricData; + fsSize: MetricData; + fsUsage: MetricData; +} + export interface MetricData { status: string; data: { @@ -34,10 +55,12 @@ export function normalizeMetrics(metrics: MetricData | undefined | null, frames return { data: { resultType: "", - result: [{ - metric: {}, - values: [], - }], + result: [ + { + metric: {}, + values: [], + }, + ], }, status: "", }; @@ -48,8 +71,10 @@ export function normalizeMetrics(metrics: MetricData | undefined | null, frames if (result.length) { if (frames > 0) { // fill the gaps - result.forEach(res => { - if (!res.values || !res.values.length) return; + result.forEach((res) => { + if (!res.values || !res.values.length) { + return; + } let now = moment().startOf("minute").subtract(1, "minute").unix(); let timestamp = res.values[0][0]; @@ -72,8 +97,7 @@ export function normalizeMetrics(metrics: MetricData | undefined | null, frames } }); } - } - else { + } else { // always return at least empty values array result.push({ metric: {}, @@ -85,10 +109,13 @@ export function normalizeMetrics(metrics: MetricData | undefined | null, frames } export function isMetricsEmpty(metrics: Partial>) { - return Object.values(metrics).every(metric => !metric?.data?.result?.length); + return Object.values(metrics).every((metric) => !metric?.data?.result?.length); } -export function getItemMetrics(metrics: Partial> | null | undefined, itemName: string): Partial> | undefined { +export function getItemMetrics( + metrics: Partial> | null | undefined, + itemName: string, +): Partial> | undefined { if (!metrics) { return undefined; } @@ -100,7 +127,7 @@ export function getItemMetrics(metrics: Partial Object.values(res.metric)[0] == itemName); + const result = results?.find((res) => Object.values(res.metric)[0] == itemName); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion itemMetrics[metric]!.data.result = result ? [result] : []; @@ -109,9 +136,12 @@ export function getItemMetrics(metrics: Partial(metrics: Partial>): Partial> { +export function getMetricLastPoints( + metrics: Partial>, +): Partial> { return object.fromEntries( - object.entries(metrics) + object + .entries(metrics) .map(([metricName, metric]) => { try { return [metricName, +metric.data.result[0].values.slice(-1)[0][1]] as const;