1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Add back commented functionality to cluster pie charts clusteroverview UI block

Signed-off-by: Juho Heikka <juho.heikka@gmail.com>
This commit is contained in:
Juho Heikka 2023-03-22 15:58:49 +02:00
parent 6a870fa68a
commit a59fd52948
3 changed files with 332 additions and 313 deletions

View File

@ -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<LensTheme>;
// }
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 (
// <div className="node-warning flex gaps align-center">
// <Icon material="info" />
// <p>Specified limits are higher than node capacity!</p>
// </div>
// );
// };
//
// const renderCharts = (
// lastPoints: Partial<Record<keyof ClusterMetricData, number>>
// ) => {
// 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 (
// <div className="flex justify-center box grow gaps">
// <div
// className={cssNames(
// styles.chart,
// "flex column align-center box grow"
// )}
// >
// <PieChart
// data={cpuData}
// title="CPU"
// legendColors={[
// "#c93dce",
// "#4caf50",
// "#3d90ce",
// "#032b4d",
// defaultColor,
// ]}
// />
// {(cpuLimits ?? cpuAllocatableCapacity) > cpuAllocatableCapacity &&
// renderLimitWarning()}
// </div>
// <div
// className={cssNames(
// styles.chart,
// "flex column align-center box grow"
// )}
// >
// <PieChart
// data={memoryData}
// title="Memory"
// legendColors={[
// "#c93dce",
// "#4caf50",
// "#3d90ce",
// "#032b4d",
// defaultColor,
// ]}
// />
// {(memoryLimits ?? memoryAllocatableCapacity) >
// memoryAllocatableCapacity && renderLimitWarning()}
// </div>
// <div
// className={cssNames(
// styles.chart,
// "flex column align-center box grow"
// )}
// >
// <PieChart
// data={podsData}
// title="Pods"
// legendColors={["#4caf50", defaultColor]}
// />
// </div>
// </div>
// );
// };
//
// const renderContent = ({
// metricNodeRole,
// metrics,
// }: ClusterOverviewStore) => {
// const { masterNodes, workerNodes } = nodeStore;
// const nodes =
// metricNodeRole === MetricNodeRole.MASTER ? masterNodes : workerNodes;
//
// if (!nodes.length) {
// return (
// <div
// className={cssNames(
// styles.empty,
// "flex column box grow align-center justify-center"
// )}
// >
// <Icon material="info" />
// No Nodes Available.
// </div>
// );
// }
//
// if (!metrics) {
// return (
// <div
// className={cssNames(
// styles.empty,
// "flex justify-center align-center box grow"
// )}
// >
// <Spinner />
// </div>
// );
// }
//
// const lastPoints = getMetricLastPoints(metrics);
// const { memoryCapacity, cpuCapacity, podCapacity } = lastPoints;
//
// if (!memoryCapacity || !cpuCapacity || !podCapacity) {
// return (
// <div className={styles.noMetrics}>
// <ClusterNoMetrics className={styles.empty} />
// </div>
// );
// }
//
// return renderCharts(lastPoints);
// };
//
// return <div className="flex">{renderContent(clusterOverviewStore)}</div>;
// }
// );
const checkedBytesToUnits = (value: number | undefined) =>
typeof value === "number" ? bytesToUnits(value) : "N/A";
// const ClusterPieCharts = withInjectables<Dependencies>(
// NonInjectedClusterPieCharts,
// {
// getProps: (di) => ({
// clusterOverviewStore: di.inject(clusterOverviewStoreInjectable),
// nodeStore: di.inject(nodeStoreInjectable),
// activeTheme: di.inject(activeThemeInjectable),
// }),
// }
// );
interface Dependencies {
clusterOverviewStore: ClusterOverviewStore;
nodeStore: NodeStore;
activeTheme: IComputedValue<LensTheme>;
}
const ClusterPieCharts = () => <div>cluster-pie-charts.injectable</div>;
const NonInjectedClusterPieCharts = observer(
({ clusterOverviewStore, nodeStore, activeTheme }: Dependencies) => {
const renderLimitWarning = () => {
return (
<div className="node-warning flex gaps align-center">
<Icon material="info" />
<p>Specified limits are higher than node capacity!</p>
</div>
);
};
const renderCharts = (
lastPoints: Partial<Record<keyof ClusterMetricData, number>>
) => {
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 (
<div className="flex justify-center box grow gaps">
<div
className={cssNames(
styles.chart,
"flex column align-center box grow"
)}
>
<PieChart
data={cpuData}
title="CPU"
legendColors={[
"#c93dce",
"#4caf50",
"#3d90ce",
"#032b4d",
defaultColor,
]}
/>
{(cpuLimits ?? cpuAllocatableCapacity) > cpuAllocatableCapacity &&
renderLimitWarning()}
</div>
<div
className={cssNames(
styles.chart,
"flex column align-center box grow"
)}
>
<PieChart
data={memoryData}
title="Memory"
legendColors={[
"#c93dce",
"#4caf50",
"#3d90ce",
"#032b4d",
defaultColor,
]}
/>
{(memoryLimits ?? memoryAllocatableCapacity) >
memoryAllocatableCapacity && renderLimitWarning()}
</div>
<div
className={cssNames(
styles.chart,
"flex column align-center box grow"
)}
>
<PieChart
data={podsData}
title="Pods"
legendColors={["#4caf50", defaultColor]}
/>
</div>
</div>
);
};
const renderContent = ({
metricNodeRole,
metrics,
}: ClusterOverviewStore) => {
const { masterNodes, workerNodes } = nodeStore;
const nodes =
metricNodeRole === MetricNodeRole.MASTER ? masterNodes : workerNodes;
if (!nodes.length) {
return (
<div
className={cssNames(
styles.empty,
"flex column box grow align-center justify-center"
)}
>
<Icon material="info" />
No Nodes Available.
</div>
);
}
if (!metrics) {
return (
<div
className={cssNames(
styles.empty,
"flex justify-center align-center box grow"
)}
>
<Spinner />
</div>
);
}
const lastPoints = getMetricLastPoints(metrics);
const { memoryCapacity, cpuCapacity, podCapacity } = lastPoints;
if (!memoryCapacity || !cpuCapacity || !podCapacity) {
return (
<div className={styles.noMetrics}>
<ClusterNoMetrics className={styles.empty} />
</div>
);
}
return renderCharts(lastPoints);
};
return <div className="flex">{renderContent(clusterOverviewStore)}</div>;
}
);
const ClusterPieCharts = withInjectables<Dependencies>(
NonInjectedClusterPieCharts,
{
getProps: (di) => ({
clusterOverviewStore: di.inject(clusterOverviewStoreInjectionToken),
nodeStore: di.inject(nodeStoreInjectionToken),
activeTheme: di.inject(activeThemeInjectionToken),
}),
}
);
const clusterPieChartsClusterOverviewInjectable = getInjectable({
id: "cluster-pie-charts-cluster-overview",

View File

@ -68,4 +68,3 @@ export enum MetricNodeRole {
MASTER = "master",
WORKER = "worker",
}

View File

@ -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<Record<string, MetricData>>) {
return Object.values(metrics).every(metric => !metric?.data?.result?.length);
return Object.values(metrics).every((metric) => !metric?.data?.result?.length);
}
export function getItemMetrics<Keys extends string>(metrics: Partial<Record<Keys, MetricData>> | null | undefined, itemName: string): Partial<Record<Keys, MetricData>> | undefined {
export function getItemMetrics<Keys extends string>(
metrics: Partial<Record<Keys, MetricData>> | null | undefined,
itemName: string,
): Partial<Record<Keys, MetricData>> | undefined {
if (!metrics) {
return undefined;
}
@ -100,7 +127,7 @@ export function getItemMetrics<Keys extends string>(metrics: Partial<Record<Keys
continue;
}
const results = metrics[metric]?.data.result;
const result = results?.find(res => 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<Keys extends string>(metrics: Partial<Record<Keys
return itemMetrics;
}
export function getMetricLastPoints<Keys extends string>(metrics: Partial<Record<Keys, MetricData>>): Partial<Record<Keys, number>> {
export function getMetricLastPoints<Keys extends string>(
metrics: Partial<Record<Keys, MetricData>>,
): Partial<Record<Keys, number>> {
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;