From 35f3c08279da6b824d63ccaa387819c293c275eb Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 9 Aug 2022 14:08:21 -0400 Subject: [PATCH] Convert all metric requests to be injectable Signed-off-by: Sebastian Malton --- src/common/k8s-api/endpoints/cluster.api.ts | 41 --------- .../k8s-api/endpoints/daemon-set.api.ts | 19 ---- .../k8s-api/endpoints/deployment.api.ts | 20 +--- src/common/k8s-api/endpoints/ingress.api.ts | 22 ----- src/common/k8s-api/endpoints/job.api.ts | 20 +--- src/common/k8s-api/endpoints/metrics.api.ts | 91 +++---------------- ...luster-metrics-by-node-names.injectable.ts | 63 +++++++++++++ .../get-ingress-metrics.injectable.ts | 38 ++++++++ .../get-metrics-for-all-nodes.injectable.ts | 44 +++++++++ ...sistent-volume-claim-metrics.injectable.ts | 35 +++++++ ...-pod-metrics-for-daemon-sets.injectable.ts | 46 ++++++++++ ...-pod-metrics-for-deployments.injectable.ts | 46 ++++++++++ .../get-pod-metrics-for-jobs.injectable.ts | 46 ++++++++++ ...pod-metrics-for-replica-sets.injectable.ts | 46 ++++++++++ ...od-metrics-for-stateful-sets.injectable.ts | 47 ++++++++++ ...get-pod-metrics-in-namespace.injectable.ts | 44 +++++++++ .../metrics.api/get-pod-metrics.injectable.ts | 54 +++++++++++ .../metrics.api/get-providers.injectable.ts | 26 ++++++ .../endpoints/metrics.api/get.injectable.ts | 74 +++++++++++++++ src/common/k8s-api/endpoints/namespace.api.ts | 18 ---- src/common/k8s-api/endpoints/node.api.ts | 28 ------ .../endpoints/persistent-volume-claim.api.ts | 18 ---- src/common/k8s-api/endpoints/pod.api.ts | 37 -------- .../k8s-api/endpoints/replica-set.api.ts | 19 ---- .../k8s-api/endpoints/stateful-set.api.ts | 19 ---- src/main/get-metrics.injectable.ts | 4 +- .../metrics/add-metrics-route.injectable.ts | 4 +- .../cluster-overview-store.ts | 24 ++--- .../+cluster/cluster-pie-charts.tsx | 39 ++++++-- .../+namespaces/namespace-details.tsx | 10 +- .../+network-ingresses/ingress-details.tsx | 24 ++++- src/renderer/components/+nodes/details.tsx | 13 ++- src/renderer/components/+nodes/route.tsx | 50 +++++++--- .../volume-claim-details.tsx | 41 +++++++-- .../daemonset-details.tsx | 12 ++- .../deployment-details.tsx | 13 ++- .../+workloads-jobs/job-details.tsx | 12 ++- .../+workloads-pods/pod-details.tsx | 43 +++++++-- .../replicaset-details.tsx | 13 ++- .../statefulset-details.tsx | 13 ++- .../components/cluster-prometheus-setting.tsx | 9 +- .../resource-metrics-text.tsx | 18 +++- .../resource-metrics/resource-metrics.tsx | 7 +- 43 files changed, 869 insertions(+), 441 deletions(-) create mode 100644 src/common/k8s-api/endpoints/metrics.api/get-cluster-metrics-by-node-names.injectable.ts create mode 100644 src/common/k8s-api/endpoints/metrics.api/get-ingress-metrics.injectable.ts create mode 100644 src/common/k8s-api/endpoints/metrics.api/get-metrics-for-all-nodes.injectable.ts create mode 100644 src/common/k8s-api/endpoints/metrics.api/get-persistent-volume-claim-metrics.injectable.ts create mode 100644 src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-daemon-sets.injectable.ts create mode 100644 src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-deployments.injectable.ts create mode 100644 src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-jobs.injectable.ts create mode 100644 src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-replica-sets.injectable.ts create mode 100644 src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-stateful-sets.injectable.ts create mode 100644 src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-in-namespace.injectable.ts create mode 100644 src/common/k8s-api/endpoints/metrics.api/get-pod-metrics.injectable.ts create mode 100644 src/common/k8s-api/endpoints/metrics.api/get-providers.injectable.ts create mode 100644 src/common/k8s-api/endpoints/metrics.api/get.injectable.ts diff --git a/src/common/k8s-api/endpoints/cluster.api.ts b/src/common/k8s-api/endpoints/cluster.api.ts index 082f0adb09..aae4c239ad 100644 --- a/src/common/k8s-api/endpoints/cluster.api.ts +++ b/src/common/k8s-api/endpoints/cluster.api.ts @@ -3,8 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { MetricData, IMetricsReqParams } from "./metrics.api"; -import { metricsApi } from "./metrics.api"; import { KubeObject } from "../kube-object"; import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api"; import { KubeApi } from "../kube-api"; @@ -28,30 +26,6 @@ export class ClusterApi extends KubeApi { } } -export function getMetricsByNodeNames(nodeNames: string[], params?: IMetricsReqParams): Promise { - const nodes = nodeNames.join("|"); - const opts = { category: "cluster", nodes }; - - return metricsApi.getMetrics({ - memoryUsage: opts, - workloadMemoryUsage: opts, - memoryRequests: opts, - memoryLimits: opts, - memoryCapacity: opts, - memoryAllocatableCapacity: opts, - cpuUsage: opts, - cpuRequests: opts, - cpuLimits: opts, - cpuCapacity: opts, - cpuAllocatableCapacity: opts, - podUsage: opts, - podCapacity: opts, - podAllocatableCapacity: opts, - fsSize: opts, - fsUsage: opts, - }, params); -} - export enum ClusterStatus { ACTIVE = "Active", CREATING = "Creating", @@ -59,21 +33,6 @@ export enum ClusterStatus { ERROR = "Error", } -export interface ClusterMetricData extends Partial> { - memoryUsage: MetricData; - memoryRequests: MetricData; - memoryLimits: MetricData; - memoryCapacity: MetricData; - cpuUsage: MetricData; - cpuRequests: MetricData; - cpuLimits: MetricData; - cpuCapacity: MetricData; - podUsage: MetricData; - podCapacity: MetricData; - fsSize: MetricData; - fsUsage: MetricData; -} - export interface Cluster { spec: { clusterNetwork?: { diff --git a/src/common/k8s-api/endpoints/daemon-set.api.ts b/src/common/k8s-api/endpoints/daemon-set.api.ts index f023652306..49719b04f9 100644 --- a/src/common/k8s-api/endpoints/daemon-set.api.ts +++ b/src/common/k8s-api/endpoints/daemon-set.api.ts @@ -5,8 +5,6 @@ import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api"; import { KubeApi } from "../kube-api"; -import { metricsApi } from "./metrics.api"; -import type { PodMetricData } from "./pod.api"; import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; import { KubeObject } from "../kube-object"; import type { PodTemplateSpec } from "./types/pod-template-spec"; @@ -90,20 +88,3 @@ export class DaemonSetApi extends KubeApi { }); } } - -export function getMetricsForDaemonSets(daemonsets: DaemonSet[], namespace: string, selector = ""): Promise { - const podSelector = daemonsets.map(daemonset => `${daemonset.getName()}-[[:alnum:]]{5}`).join("|"); - const opts = { category: "pods", pods: podSelector, namespace, selector }; - - return metricsApi.getMetrics({ - cpuUsage: opts, - memoryUsage: opts, - fsUsage: opts, - fsWrites: opts, - fsReads: opts, - networkReceive: opts, - networkTransmit: opts, - }, { - namespace, - }); -} diff --git a/src/common/k8s-api/endpoints/deployment.api.ts b/src/common/k8s-api/endpoints/deployment.api.ts index f67f26f70f..318ea8563f 100644 --- a/src/common/k8s-api/endpoints/deployment.api.ts +++ b/src/common/k8s-api/endpoints/deployment.api.ts @@ -7,8 +7,7 @@ import moment from "moment"; import type { DerivedKubeApiOptions } from "../kube-api"; import { KubeApi } from "../kube-api"; -import { metricsApi } from "./metrics.api"; -import type { PodMetricData, PodSpec } from "./pod.api"; +import type { PodSpec } from "./pod.api"; import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; import { KubeObject } from "../kube-object"; import { hasTypedProperty, isNumber, isObject } from "../../utils"; @@ -70,23 +69,6 @@ export class DeploymentApi extends KubeApi { } } -export function getMetricsForDeployments(deployments: Deployment[], namespace: string, selector = ""): Promise { - const podSelector = deployments.map(deployment => `${deployment.getName()}-[[:alnum:]]{9,}-[[:alnum:]]{5}`).join("|"); - const opts = { category: "pods", pods: podSelector, namespace, selector }; - - return metricsApi.getMetrics({ - cpuUsage: opts, - memoryUsage: opts, - fsUsage: opts, - fsWrites: opts, - fsReads: opts, - networkReceive: opts, - networkTransmit: opts, - }, { - namespace, - }); -} - export interface DeploymentSpec { replicas: number; selector: LabelSelector; diff --git a/src/common/k8s-api/endpoints/ingress.api.ts b/src/common/k8s-api/endpoints/ingress.api.ts index 84910d2783..d223530013 100644 --- a/src/common/k8s-api/endpoints/ingress.api.ts +++ b/src/common/k8s-api/endpoints/ingress.api.ts @@ -6,8 +6,6 @@ import type { NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object"; import { KubeObject } from "../kube-object"; import { hasTypedProperty, isString, iter } from "../../utils"; -import type { MetricData } from "./metrics.api"; -import { metricsApi } from "./metrics.api"; import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api"; import { KubeApi } from "../kube-api"; import type { RequireExactlyOne } from "type-fest"; @@ -24,26 +22,6 @@ export class IngressApi extends KubeApi { } } -export function getMetricsForIngress(ingress: string, namespace: string): Promise { - const opts = { category: "ingress", ingress, namespace }; - - return metricsApi.getMetrics({ - bytesSentSuccess: opts, - bytesSentFailure: opts, - requestDurationSeconds: opts, - responseDurationSeconds: opts, - }, { - namespace, - }); -} - -export interface IngressMetricData extends Partial> { - bytesSentSuccess: MetricData; - bytesSentFailure: MetricData; - requestDurationSeconds: MetricData; - responseDurationSeconds: MetricData; -} - export interface ILoadBalancerIngress { hostname?: string; ip?: string; diff --git a/src/common/k8s-api/endpoints/job.api.ts b/src/common/k8s-api/endpoints/job.api.ts index 84e166ecc4..66f4ef3768 100644 --- a/src/common/k8s-api/endpoints/job.api.ts +++ b/src/common/k8s-api/endpoints/job.api.ts @@ -5,8 +5,7 @@ import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api"; import { KubeApi } from "../kube-api"; -import { metricsApi } from "./metrics.api"; -import type { PodMetricData, PodSpec } from "./pod.api"; +import type { PodSpec } from "./pod.api"; import type { Container } from "./types/container"; import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; import { KubeObject } from "../kube-object"; @@ -103,20 +102,3 @@ export class JobApi extends KubeApi { }); } } - -export function getMetricsForJobs(jobs: Job[], namespace: string, selector = ""): Promise { - const podSelector = jobs.map(job => `${job.getName()}-[[:alnum:]]{5}`).join("|"); - const opts = { category: "pods", pods: podSelector, namespace, selector }; - - return metricsApi.getMetrics({ - cpuUsage: opts, - memoryUsage: opts, - fsUsage: opts, - fsWrites: opts, - fsReads: opts, - networkReceive: opts, - networkTransmit: opts, - }, { - namespace, - }); -} diff --git a/src/common/k8s-api/endpoints/metrics.api.ts b/src/common/k8s-api/endpoints/metrics.api.ts index b5469e8a24..406ab1d0b2 100644 --- a/src/common/k8s-api/endpoints/metrics.api.ts +++ b/src/common/k8s-api/endpoints/metrics.api.ts @@ -6,7 +6,7 @@ // Metrics api import moment from "moment"; -import { apiBase } from "../index"; +import { isDefined, object } from "../../utils"; export interface MetricData { status: string; @@ -29,63 +29,6 @@ export interface MetricResult { values: [number, string][]; } -export interface MetricProviderInfo { - name: string; - id: string; - isConfigurable: boolean; -} - -export interface IMetricsReqParams { - start?: number | string; // timestamp in seconds or valid date-string - end?: number | string; - step?: number; // step in seconds (default: 60s = each point 1m) - range?: number; // time-range in seconds for data aggregation (default: 3600s = last 1h) - namespace?: string; // rbac-proxy validation param -} - -export interface IResourceMetrics { - [metric: string]: T; - cpuUsage: T; - memoryUsage: T; - fsUsage: T; - fsWrites: T; - fsReads: T; - networkReceive: T; - networkTransmit: T; -} - -async function getMetrics(query: string, reqParams?: IMetricsReqParams): Promise; -async function getMetrics(query: string[], reqParams?: IMetricsReqParams): Promise; -async function getMetrics(query: Record>>, reqParams?: IMetricsReqParams): Promise>; - -async function getMetrics(query: string | string[] | Partial>>>, reqParams: IMetricsReqParams = {}): Promise>> { - const { range = 3600, step = 60, namespace } = reqParams; - let { start, end } = reqParams; - - if (!start && !end) { - const timeNow = Date.now() / 1000; - const now = moment.unix(timeNow).startOf("minute").unix(); // round date to minutes - - start = now - range; - end = now; - } - - return apiBase.post("/metrics", { - data: query, - query: { - start, end, step, - "kubernetes_namespace": namespace, - }, - }); -} - -export const metricsApi = { - getMetrics, - async getMetricProviders(): Promise { - return apiBase.get("/metrics/providers"); - }, -}; - export function normalizeMetrics(metrics: MetricData | undefined | null, frames = 60): MetricData { if (!metrics?.data?.result) { return { @@ -145,7 +88,7 @@ export function isMetricsEmpty(metrics: Partial>) { 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; } @@ -166,22 +109,16 @@ export function getItemMetrics(metrics: Partial> | nu return itemMetrics; } -export function getMetricLastPoints>>(metrics: T): Record { - const result: Partial<{ [metric: string]: number }> = {}; - - Object.keys(metrics).forEach(metricName => { - try { - const metric = metrics[metricName]; - - if (metric?.data.result.length) { - result[metricName] = +metric.data.result[0].values.slice(-1)[0][1]; - } - } catch { - // ignore error - } - - return result; - }, {}); - - return result as Record; +export function getMetricLastPoints(metrics: Partial>): Partial> { + return object.fromEntries( + object.entries(metrics) + .map(([metricName, metric]) => { + try { + return [metricName, +metric.data.result[0].values.slice(-1)[0][1]] as const; + } catch { + return undefined; + } + }) + .filter(isDefined), + ); } diff --git a/src/common/k8s-api/endpoints/metrics.api/get-cluster-metrics-by-node-names.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get-cluster-metrics-by-node-names.injectable.ts new file mode 100644 index 0000000000..7ac6ebfde5 --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get-cluster-metrics-by-node-names.injectable.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MetricData } from "../metrics.api"; +import type { RequestMetricsParams } from "./get.injectable"; +import requestMetricsInjectable from "./get.injectable"; + +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 type RequestClusterMetricsByNodeNames = (nodeNames: string[], params?: RequestMetricsParams) => Promise; + +const requestClusterMetricsByNodeNamesInjectable = getInjectable({ + id: "get-cluster-metrics-by-node-names", + instantiate: (di): RequestClusterMetricsByNodeNames => { + const requestMetrics = di.inject(requestMetricsInjectable); + + return (nodeNames, params) => { + const opts = { + category: "cluster", + nodes: nodeNames.join("|"), + }; + + return requestMetrics({ + memoryUsage: opts, + workloadMemoryUsage: opts, + memoryRequests: opts, + memoryLimits: opts, + memoryCapacity: opts, + memoryAllocatableCapacity: opts, + cpuUsage: opts, + cpuRequests: opts, + cpuLimits: opts, + cpuCapacity: opts, + cpuAllocatableCapacity: opts, + podUsage: opts, + podCapacity: opts, + podAllocatableCapacity: opts, + fsSize: opts, + fsUsage: opts, + }, params); + }; + }, +}); + +export default requestClusterMetricsByNodeNamesInjectable; diff --git a/src/common/k8s-api/endpoints/metrics.api/get-ingress-metrics.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get-ingress-metrics.injectable.ts new file mode 100644 index 0000000000..f7dabe6031 --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get-ingress-metrics.injectable.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MetricData } from "../metrics.api"; +import requestMetricsInjectable from "./get.injectable"; + +export interface IngressMetricData { + bytesSentSuccess: MetricData; + bytesSentFailure: MetricData; + requestDurationSeconds: MetricData; + responseDurationSeconds: MetricData; +} + +export type RequestIngressMetrics = (ingress: string, namespace: string) => Promise; + +const requestIngressMetricsInjectable = getInjectable({ + id: "request-ingress-metrics", + instantiate: (di): RequestIngressMetrics => { + const requestMetrics = di.inject(requestMetricsInjectable); + + return (ingress, namespace) => { + const opts = { category: "ingress", ingress, namespace }; + + return requestMetrics({ + bytesSentSuccess: opts, + bytesSentFailure: opts, + requestDurationSeconds: opts, + responseDurationSeconds: opts, + }, { + namespace, + }); + }; + }, +}); + +export default requestIngressMetricsInjectable; diff --git a/src/common/k8s-api/endpoints/metrics.api/get-metrics-for-all-nodes.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get-metrics-for-all-nodes.injectable.ts new file mode 100644 index 0000000000..25f56ba2e7 --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get-metrics-for-all-nodes.injectable.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MetricData } from "../metrics.api"; +import requestMetricsInjectable from "./get.injectable"; + +export interface NodeMetricData { + memoryUsage: MetricData; + workloadMemoryUsage: MetricData; + memoryCapacity: MetricData; + memoryAllocatableCapacity: MetricData; + cpuUsage: MetricData; + cpuCapacity: MetricData; + fsUsage: MetricData; + fsSize: MetricData; +} + +export type RequestAllNodeMetrics = () => Promise; + +const requestAllNodeMetricsInjectable = getInjectable({ + id: "request-all-node-metrics", + instantiate: (di): RequestAllNodeMetrics => { + const requestMetrics = di.inject(requestMetricsInjectable); + + return () => { + const opts = { category: "nodes" }; + + return requestMetrics({ + memoryUsage: opts, + workloadMemoryUsage: opts, + memoryCapacity: opts, + memoryAllocatableCapacity: opts, + cpuUsage: opts, + cpuCapacity: opts, + fsSize: opts, + fsUsage: opts, + }); + }; + }, +}); + +export default requestAllNodeMetricsInjectable; diff --git a/src/common/k8s-api/endpoints/metrics.api/get-persistent-volume-claim-metrics.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get-persistent-volume-claim-metrics.injectable.ts new file mode 100644 index 0000000000..857de0699d --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get-persistent-volume-claim-metrics.injectable.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MetricData } from "../metrics.api"; +import type { PersistentVolumeClaim } from "../persistent-volume-claim.api"; +import requestMetricsInjectable from "./get.injectable"; + +export interface PersistentVolumeClaimMetricData { + diskUsage: MetricData; + diskCapacity: MetricData; +} + +export type RequestPersistentVolumeClaimMetrics = (claim: PersistentVolumeClaim) => Promise; + +const requestPersistentVolumeClaimMetricsInjectable = getInjectable({ + id: "request-persistent-volume-claim-metrics", + instantiate: (di): RequestPersistentVolumeClaimMetrics => { + const requestMetrics = di.inject(requestMetricsInjectable); + + return (claim) => { + const opts = { category: "pvc", pvc: claim.getName(), namespace: claim.getNs() }; + + return requestMetrics({ + diskUsage: opts, + diskCapacity: opts, + }, { + namespace: opts.namespace, + }); + }; + }, +}); + +export default requestPersistentVolumeClaimMetricsInjectable; diff --git a/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-daemon-sets.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-daemon-sets.injectable.ts new file mode 100644 index 0000000000..12c7ebd275 --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-daemon-sets.injectable.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { DaemonSet } from "../daemon-set.api"; +import type { MetricData } from "../metrics.api"; +import requestMetricsInjectable from "./get.injectable"; + +export interface DaemonSetPodMetricData { + cpuUsage: MetricData; + memoryUsage: MetricData; + fsUsage: MetricData; + fsWrites: MetricData; + fsReads: MetricData; + networkReceive: MetricData; + networkTransmit: MetricData; +} + +export type RequestPodMetricsForDaemonSets = (daemonsets: DaemonSet[], namespace: string, selector?: string) => Promise; + +const requestPodMetricsForDaemonSetsInjectable = getInjectable({ + id: "request-pod-metrics-for-daemon-sets", + instantiate: (di): RequestPodMetricsForDaemonSets => { + const requestMetrics = di.inject(requestMetricsInjectable); + + return (daemonSets, namespace, selector = "") => { + const podSelector = daemonSets.map(daemonSet => `${daemonSet.getName()}-[[:alnum:]]{5}`).join("|"); + const opts = { category: "pods", pods: podSelector, namespace, selector }; + + return requestMetrics({ + cpuUsage: opts, + memoryUsage: opts, + fsUsage: opts, + fsWrites: opts, + fsReads: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); + }; + }, +}); + +export default requestPodMetricsForDaemonSetsInjectable; diff --git a/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-deployments.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-deployments.injectable.ts new file mode 100644 index 0000000000..c35345747c --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-deployments.injectable.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { Deployment } from "../deployment.api"; +import type { MetricData } from "../metrics.api"; +import requestMetricsInjectable from "./get.injectable"; + +export interface DeploymentPodMetricData { + cpuUsage: MetricData; + memoryUsage: MetricData; + fsUsage: MetricData; + fsWrites: MetricData; + fsReads: MetricData; + networkReceive: MetricData; + networkTransmit: MetricData; +} + +export type RequestPodMetricsForDeployments = (deployments: Deployment[], namespace: string, selector?: string) => Promise; + +const requestPodMetricsForDeploymentsInjectable = getInjectable({ + id: "request-pod-metrics-for-deployments", + instantiate: (di): RequestPodMetricsForDeployments => { + const requestMetrics = di.inject(requestMetricsInjectable); + + return (deployments, namespace, selector = "") => { + const podSelector = deployments.map(deployment => `${deployment.getName()}-[[:alnum:]]{9,}-[[:alnum:]]{5}`).join("|"); + const opts = { category: "pods", pods: podSelector, namespace, selector }; + + return requestMetrics({ + cpuUsage: opts, + memoryUsage: opts, + fsUsage: opts, + fsWrites: opts, + fsReads: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); + }; + }, +}); + +export default requestPodMetricsForDeploymentsInjectable; diff --git a/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-jobs.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-jobs.injectable.ts new file mode 100644 index 0000000000..173aac6b1a --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-jobs.injectable.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { Job } from "../job.api"; +import type { MetricData } from "../metrics.api"; +import requestMetricsInjectable from "./get.injectable"; + +export interface JobPodMetricData { + cpuUsage: MetricData; + memoryUsage: MetricData; + fsUsage: MetricData; + fsWrites: MetricData; + fsReads: MetricData; + networkReceive: MetricData; + networkTransmit: MetricData; +} + +export type RequestPodMetricsForJobs = (jobs: Job[], namespace: string, selector?: string) => Promise; + +const requestPodMetricsForJobsInjectable = getInjectable({ + id: "request-pod-metrics-for-jobs", + instantiate: (di): RequestPodMetricsForJobs => { + const requestMetrics = di.inject(requestMetricsInjectable); + + return (jobs, namespace, selector) => { + const podSelector = jobs.map(job => `${job.getName()}-[[:alnum:]]{5}`).join("|"); + const opts = { category: "pods", pods: podSelector, namespace, selector }; + + return requestMetrics({ + cpuUsage: opts, + memoryUsage: opts, + fsUsage: opts, + fsWrites: opts, + fsReads: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); + }; + }, +}); + +export default requestPodMetricsForJobsInjectable; diff --git a/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-replica-sets.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-replica-sets.injectable.ts new file mode 100644 index 0000000000..bf0eb6a9ed --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-replica-sets.injectable.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MetricData } from "../metrics.api"; +import type { ReplicaSet } from "../replica-set.api"; +import requestMetricsInjectable from "./get.injectable"; + +export interface ReplicaSetPodMetricData { + cpuUsage: MetricData; + memoryUsage: MetricData; + fsUsage: MetricData; + fsWrites: MetricData; + fsReads: MetricData; + networkReceive: MetricData; + networkTransmit: MetricData; +} + +export type RequestPodMetricsForReplicaSets = (replicaSets: ReplicaSet[], namespace: string, selector?: string) => Promise; + +const requestPodMetricsForReplicaSetsInjectable = getInjectable({ + id: "request-pod-metrics-for-replica-sets", + instantiate: (di): RequestPodMetricsForReplicaSets => { + const requestMetrics = di.inject(requestMetricsInjectable); + + return (replicaSets, namespace, selector = "") => { + const podSelector = replicaSets.map(replicaSet => `${replicaSet.getName()}-[[:alnum:]]{5}`).join("|"); + const opts = { category: "pods", pods: podSelector, namespace, selector }; + + return requestMetrics({ + cpuUsage: opts, + memoryUsage: opts, + fsUsage: opts, + fsWrites: opts, + fsReads: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); + }; + }, +}); + +export default requestPodMetricsForReplicaSetsInjectable; diff --git a/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-stateful-sets.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-stateful-sets.injectable.ts new file mode 100644 index 0000000000..0cda8e9b09 --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-stateful-sets.injectable.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MetricData } from "../metrics.api"; +import type { StatefulSet } from "../stateful-set.api"; +import requestMetricsInjectable from "./get.injectable"; + +export interface StatefulSetPodMetricData { + cpuUsage: MetricData; + memoryUsage: MetricData; + fsUsage: MetricData; + fsWrites: MetricData; + fsReads: MetricData; + networkReceive: MetricData; + networkTransmit: MetricData; +} + +export type RequestPodMetricsForStatefulSets = (statefulSets: StatefulSet[], namespace: string, selector?: string) => Promise; + +const requestPodMetricsForStatefulSetsInjectable = getInjectable({ + id: "request-pod-metrics-for-stateful-sets", + instantiate: (di): RequestPodMetricsForStatefulSets => { + const requestMetrics = di.inject(requestMetricsInjectable); + + return (statefulSets, namespace, selector = "") => { + const podSelector = statefulSets.map(statefulset => `${statefulset.getName()}-[[:digit:]]+`).join("|"); + const opts = { category: "pods", pods: podSelector, namespace, selector }; + + return requestMetrics({ + cpuUsage: opts, + memoryUsage: opts, + fsUsage: opts, + fsWrites: opts, + fsReads: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); + }; + }, +}); + +export default requestPodMetricsForStatefulSetsInjectable; + diff --git a/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-in-namespace.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-in-namespace.injectable.ts new file mode 100644 index 0000000000..23fbeac1a9 --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics-in-namespace.injectable.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MetricData } from "../metrics.api"; +import requestMetricsInjectable from "./get.injectable"; + +export interface PodMetricInNamespaceData { + cpuUsage: MetricData; + memoryUsage: MetricData; + fsUsage: MetricData; + fsWrites: MetricData; + fsReads: MetricData; + networkReceive: MetricData; + networkTransmit: MetricData; +} + +export type RequestPodMetricsInNamespace = (namespace: string, selector?: string) => Promise; + +const requestPodMetricsInNamespaceInjectable = getInjectable({ + id: "request-pod-metrics-in-namespace", + instantiate: (di): RequestPodMetricsInNamespace => { + const requestMetrics = di.inject(requestMetricsInjectable); + + return (namespace, selector) => { + const opts = { category: "pods", pods: ".*", namespace, selector }; + + return requestMetrics({ + cpuUsage: opts, + memoryUsage: opts, + fsUsage: opts, + fsWrites: opts, + fsReads: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); + }; + }, +}); + +export default requestPodMetricsInNamespaceInjectable; diff --git a/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics.injectable.ts new file mode 100644 index 0000000000..6d844aba80 --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get-pod-metrics.injectable.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { MetricData } from "../metrics.api"; +import type { Pod } from "../pod.api"; +import requestMetricsInjectable from "./get.injectable"; + +export interface PodMetricData { + cpuUsage: MetricData; + memoryUsage: MetricData; + fsUsage: MetricData; + fsWrites: MetricData; + fsReads: MetricData; + networkReceive: MetricData; + networkTransmit: MetricData; + cpuRequests: MetricData; + cpuLimits: MetricData; + memoryRequests: MetricData; + memoryLimits: MetricData; +} + +export type RequestPodMetrics = (pods: Pod[], namespace: string, selector?: string) => Promise; + +const requestPodMetricsInjectable = getInjectable({ + id: "request-pod-metrics", + instantiate: (di): RequestPodMetrics => { + const requestMetrics = di.inject(requestMetricsInjectable); + + return (pods, namespace, selector = "pod, namespace") => { + const podSelector = pods.map(pod => pod.getName()).join("|"); + const opts = { category: "pods", pods: podSelector, namespace, selector }; + + return requestMetrics({ + cpuUsage: opts, + cpuRequests: opts, + cpuLimits: opts, + memoryUsage: opts, + memoryRequests: opts, + memoryLimits: opts, + fsUsage: opts, + fsWrites: opts, + fsReads: opts, + networkReceive: opts, + networkTransmit: opts, + }, { + namespace, + }); + }; + }, +}); + +export default requestPodMetricsInjectable; diff --git a/src/common/k8s-api/endpoints/metrics.api/get-providers.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get-providers.injectable.ts new file mode 100644 index 0000000000..4711333ca6 --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get-providers.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectable } from "@ogre-tools/injectable"; +import { apiBaseInjectionToken } from "../../api-base"; + +export interface MetricProviderInfo { + name: string; + id: string; + isConfigurable: boolean; +} + +export type RequestMetricsProviders = () => Promise; + +const requestMetricsProvidersInjectable = getInjectable({ + id: "request-metrics-providers", + instantiate: (di): RequestMetricsProviders => { + const apiBase = di.inject(apiBaseInjectionToken); + + return () => apiBase.get("/metrics/providers"); + }, +}); + +export default requestMetricsProvidersInjectable; diff --git a/src/common/k8s-api/endpoints/metrics.api/get.injectable.ts b/src/common/k8s-api/endpoints/metrics.api/get.injectable.ts new file mode 100644 index 0000000000..08141890a9 --- /dev/null +++ b/src/common/k8s-api/endpoints/metrics.api/get.injectable.ts @@ -0,0 +1,74 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import moment from "moment"; +import { apiBaseInjectionToken } from "../../api-base"; +import type { MetricData } from "../metrics.api"; + + +export interface RequestMetricsParams { + /** + * timestamp in seconds or valid date-string + */ + start?: number | string; + + /** + * timestamp in seconds or valid date-string + */ + end?: number | string; + + /** + * step in seconds + * @default 60 (1 minute) + */ + step?: number; + + /** + * time-range in seconds for data aggregation + * @default 3600 (1 hour) + */ + range?: number; + + /** + * rbac-proxy validation param + */ + namespace?: string; +} + +export interface RequestMetrics { + (query: string, params?: RequestMetricsParams): Promise; + (query: string[], params?: RequestMetricsParams): Promise; + (query: Record>>, params?: RequestMetricsParams): Promise>; +} + +const requestMetricsInjectable = getInjectable({ + id: "request-metrics", + instantiate: (di) => { + const apiBase = di.inject(apiBaseInjectionToken); + + return (async (query: object, params: RequestMetricsParams = {}) => { + const { range = 3600, step = 60, namespace } = params; + let { start, end } = params; + + if (!start && !end) { + const timeNow = Date.now() / 1000; + const now = moment.unix(timeNow).startOf("minute").unix(); // round date to minutes + + start = now - range; + end = now; + } + + return apiBase.post("/metrics", { + data: query, + query: { + start, end, step, + "kubernetes_namespace": namespace, + }, + }); + }) as RequestMetrics; + }, +}); + +export default requestMetricsInjectable; diff --git a/src/common/k8s-api/endpoints/namespace.api.ts b/src/common/k8s-api/endpoints/namespace.api.ts index 6a2c7e2a95..774d91cf61 100644 --- a/src/common/k8s-api/endpoints/namespace.api.ts +++ b/src/common/k8s-api/endpoints/namespace.api.ts @@ -7,8 +7,6 @@ import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api"; import { KubeApi } from "../kube-api"; import type { ClusterScopedMetadata, KubeObjectStatus } from "../kube-object"; import { KubeObject } from "../kube-object"; -import { metricsApi } from "./metrics.api"; -import type { PodMetricData } from "./pod.api"; export enum NamespaceStatusKind { ACTIVE = "Active", @@ -45,19 +43,3 @@ export class NamespaceApi extends KubeApi { }); } } - -export function getMetricsForNamespace(namespace: string, selector = ""): Promise { - const opts = { category: "pods", pods: ".*", namespace, selector }; - - return metricsApi.getMetrics({ - cpuUsage: opts, - memoryUsage: opts, - fsUsage: opts, - fsWrites: opts, - fsReads: opts, - networkReceive: opts, - networkTransmit: opts, - }, { - namespace, - }); -} diff --git a/src/common/k8s-api/endpoints/node.api.ts b/src/common/k8s-api/endpoints/node.api.ts index 15da73311b..e1d726bed1 100644 --- a/src/common/k8s-api/endpoints/node.api.ts +++ b/src/common/k8s-api/endpoints/node.api.ts @@ -6,8 +6,6 @@ import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object"; import { KubeObject } from "../kube-object"; import { cpuUnitsToNumber, unitsToBytes, isObject } from "../../../renderer/utils"; -import type { MetricData } from "./metrics.api"; -import { metricsApi } from "./metrics.api"; import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api"; import { KubeApi } from "../kube-api"; import { TypedRegEx } from "typed-regex"; @@ -21,32 +19,6 @@ export class NodeApi extends KubeApi { } } -export function getMetricsForAllNodes(): Promise { - const opts = { category: "nodes" }; - - return metricsApi.getMetrics({ - memoryUsage: opts, - workloadMemoryUsage: opts, - memoryCapacity: opts, - memoryAllocatableCapacity: opts, - cpuUsage: opts, - cpuCapacity: opts, - fsSize: opts, - fsUsage: opts, - }); -} - -export interface NodeMetricData extends Partial> { - memoryUsage: MetricData; - workloadMemoryUsage: MetricData; - memoryCapacity: MetricData; - memoryAllocatableCapacity: MetricData; - cpuUsage: MetricData; - cpuCapacity: MetricData; - fsUsage: MetricData; - fsSize: MetricData; -} - export interface NodeTaint { key: string; value?: string; diff --git a/src/common/k8s-api/endpoints/persistent-volume-claim.api.ts b/src/common/k8s-api/endpoints/persistent-volume-claim.api.ts index 733a9768c5..4c3c575699 100644 --- a/src/common/k8s-api/endpoints/persistent-volume-claim.api.ts +++ b/src/common/k8s-api/endpoints/persistent-volume-claim.api.ts @@ -5,8 +5,6 @@ import type { LabelSelector, NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object"; import { KubeObject } from "../kube-object"; -import type { MetricData } from "./metrics.api"; -import { metricsApi } from "./metrics.api"; import type { Pod } from "./pod.api"; import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api"; import { KubeApi } from "../kube-api"; @@ -22,22 +20,6 @@ export class PersistentVolumeClaimApi extends KubeApi { } } -export function getMetricsForPvc(pvc: PersistentVolumeClaim): Promise { - const opts = { category: "pvc", pvc: pvc.getName(), namespace: pvc.getNs() }; - - return metricsApi.getMetrics({ - diskUsage: opts, - diskCapacity: opts, - }, { - namespace: opts.namespace, - }); -} - -export interface PersistentVolumeClaimMetricData extends Partial> { - diskUsage: MetricData; - diskCapacity: MetricData; -} - export interface PersistentVolumeClaimSpec { accessModes?: string[]; dataSource?: TypedLocalObjectReference; diff --git a/src/common/k8s-api/endpoints/pod.api.ts b/src/common/k8s-api/endpoints/pod.api.ts index 5822c0c5ef..95dd694c1e 100644 --- a/src/common/k8s-api/endpoints/pod.api.ts +++ b/src/common/k8s-api/endpoints/pod.api.ts @@ -3,8 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { MetricData } from "./metrics.api"; -import { metricsApi } from "./metrics.api"; import type { DerivedKubeApiOptions, IgnoredKubeApiOptions, ResourceDescriptor } from "../kube-api"; import { KubeApi } from "../kube-api"; import type { RequireExactlyOne } from "type-fest"; @@ -33,41 +31,6 @@ export class PodApi extends KubeApi { } } -export function getMetricsForPods(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise { - const podSelector = pods.map(pod => pod.getName()).join("|"); - const opts = { category: "pods", pods: podSelector, namespace, selector }; - - return metricsApi.getMetrics({ - cpuUsage: opts, - cpuRequests: opts, - cpuLimits: opts, - memoryUsage: opts, - memoryRequests: opts, - memoryLimits: opts, - fsUsage: opts, - fsWrites: opts, - fsReads: opts, - networkReceive: opts, - networkTransmit: opts, - }, { - namespace, - }); -} - -export interface PodMetricData extends Partial> { - cpuUsage: MetricData; - memoryUsage: MetricData; - fsUsage: MetricData; - fsWrites: MetricData; - fsReads: MetricData; - networkReceive: MetricData; - networkTransmit: MetricData; - cpuRequests?: MetricData; - cpuLimits?: MetricData; - memoryRequests?: MetricData; - memoryLimits?: MetricData; -} - // Reference: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-log-pod-v1-core export interface PodLogsQuery { container?: string; diff --git a/src/common/k8s-api/endpoints/replica-set.api.ts b/src/common/k8s-api/endpoints/replica-set.api.ts index ea3cdc25b9..401ba6cf05 100644 --- a/src/common/k8s-api/endpoints/replica-set.api.ts +++ b/src/common/k8s-api/endpoints/replica-set.api.ts @@ -5,8 +5,6 @@ import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api"; import { KubeApi } from "../kube-api"; -import { metricsApi } from "./metrics.api"; -import type { PodMetricData } from "./pod.api"; import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; import { KubeObject } from "../kube-object"; import type { PodTemplateSpec } from "./types/pod-template-spec"; @@ -41,23 +39,6 @@ export class ReplicaSetApi extends KubeApi { } } -export function getMetricsForReplicaSets(replicasets: ReplicaSet[], namespace: string, selector = ""): Promise { - const podSelector = replicasets.map(replicaset => `${replicaset.getName()}-[[:alnum:]]{5}`).join("|"); - const opts = { category: "pods", pods: podSelector, namespace, selector }; - - return metricsApi.getMetrics({ - cpuUsage: opts, - memoryUsage: opts, - fsUsage: opts, - fsWrites: opts, - fsReads: opts, - networkReceive: opts, - networkTransmit: opts, - }, { - namespace, - }); -} - export interface ReplicaSetSpec { replicas?: number; selector: LabelSelector; diff --git a/src/common/k8s-api/endpoints/stateful-set.api.ts b/src/common/k8s-api/endpoints/stateful-set.api.ts index f6e36c36fb..1d6895f934 100644 --- a/src/common/k8s-api/endpoints/stateful-set.api.ts +++ b/src/common/k8s-api/endpoints/stateful-set.api.ts @@ -5,8 +5,6 @@ import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api"; import { KubeApi } from "../kube-api"; -import { metricsApi } from "./metrics.api"; -import type { PodMetricData } from "./pod.api"; import type { LabelSelector, NamespaceScopedMetadata } from "../kube-object"; import { KubeObject } from "../kube-object"; import type { PodTemplateSpec } from "./types/pod-template-spec"; @@ -46,23 +44,6 @@ export class StatefulSetApi extends KubeApi { } } -export function getMetricsForStatefulSets(statefulSets: StatefulSet[], namespace: string, selector = ""): Promise { - const podSelector = statefulSets.map(statefulset => `${statefulset.getName()}-[[:digit:]]+`).join("|"); - const opts = { category: "pods", pods: podSelector, namespace, selector }; - - return metricsApi.getMetrics({ - cpuUsage: opts, - memoryUsage: opts, - fsUsage: opts, - fsWrites: opts, - fsReads: opts, - networkReceive: opts, - networkTransmit: opts, - }, { - namespace, - }); -} - export interface StatefulSetSpec { serviceName: string; replicas: number; diff --git a/src/main/get-metrics.injectable.ts b/src/main/get-metrics.injectable.ts index 4d6634909c..9e446ef9c5 100644 --- a/src/main/get-metrics.injectable.ts +++ b/src/main/get-metrics.injectable.ts @@ -4,10 +4,10 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import type { Cluster } from "../common/cluster/cluster"; -import type { IMetricsReqParams } from "../common/k8s-api/endpoints/metrics.api"; +import type { RequestMetricsParams } from "../common/k8s-api/endpoints/metrics.api/get.injectable"; import k8sRequestInjectable from "./k8s-request.injectable"; -export type GetMetrics = (cluster: Cluster, prometheusPath: string, queryParams: IMetricsReqParams & { query: string }) => Promise; +export type GetMetrics = (cluster: Cluster, prometheusPath: string, queryParams: RequestMetricsParams & { query: string }) => Promise; const getMetricsInjectable = getInjectable({ id: "get-metrics", diff --git a/src/main/routes/metrics/add-metrics-route.injectable.ts b/src/main/routes/metrics/add-metrics-route.injectable.ts index 5f97ddf4c3..e55fc8bec8 100644 --- a/src/main/routes/metrics/add-metrics-route.injectable.ts +++ b/src/main/routes/metrics/add-metrics-route.injectable.ts @@ -18,7 +18,7 @@ import getMetricsInjectable from "../../get-metrics.injectable"; // This is used for backoff retry tracking. const ATTEMPTS = [false, false, false, false, true]; -const loadMetricsFor = (getMetrics: GetMetrics) => async (promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Record): Promise => { +const loadMetricsFor = (getMetrics: GetMetrics) => async (promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Partial>): Promise => { const queries = promQueries.map(p => p.trim()); const loaders = new Map>(); @@ -59,7 +59,7 @@ const addMetricsRouteInjectable = getRouteInjectable({ const getMetrics = di.inject(getMetricsInjectable); const loadMetrics = loadMetricsFor(getMetrics); - const queryParams = Object.fromEntries(query.entries()); + const queryParams: Partial> = Object.fromEntries(query.entries()); const prometheusMetadata: ClusterPrometheusMetadata = {}; try { diff --git a/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.ts b/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.ts index d3f47cb430..31fcd8af39 100644 --- a/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.ts +++ b/src/renderer/components/+cluster/cluster-overview-store/cluster-overview-store.ts @@ -6,11 +6,12 @@ import { action, observable, reaction, when, makeObservable } from "mobx"; import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store"; import type { Cluster, ClusterApi } from "../../../../common/k8s-api/endpoints"; -import { getMetricsByNodeNames, type ClusterMetricData } from "../../../../common/k8s-api/endpoints"; import type { StorageLayer } from "../../../utils"; import { autoBind } from "../../../utils"; -import { type IMetricsReqParams, normalizeMetrics } from "../../../../common/k8s-api/endpoints/metrics.api"; import type { NodeStore } from "../../+nodes/store"; +import type { ClusterMetricData, RequestClusterMetricsByNodeNames } from "../../../../common/k8s-api/endpoints/metrics.api/get-cluster-metrics-by-node-names.injectable"; +import type { RequestMetricsParams } from "../../../../common/k8s-api/endpoints/metrics.api/get.injectable"; +import { normalizeMetrics } from "../../../../common/k8s-api/endpoints/metrics.api"; export enum MetricType { MEMORY = "memory", @@ -30,11 +31,11 @@ export interface ClusterOverviewStorageState { interface ClusterOverviewStoreDependencies { readonly storage: StorageLayer; readonly nodeStore: NodeStore; + requestClusterMetricsByNodeNames: RequestClusterMetricsByNodeNames; } export class ClusterOverviewStore extends KubeObjectStore implements ClusterOverviewStorageState { - @observable metrics: Partial = {}; - @observable metricsLoaded = false; + @observable metrics: ClusterMetricData | undefined = undefined; get metricType(): MetricType { return this.dependencies.storage.get().metricType; @@ -64,9 +65,10 @@ export class ClusterOverviewStore extends KubeObjectStore i // TODO: refactor, seems not a correct place to be // auto-refresh metrics on user-action reaction(() => this.metricNodeRole, () => { - if (!this.metricsLoaded) return; - this.resetMetrics(); - this.loadMetrics(); + if (this.metrics) { + this.resetMetrics(); + this.loadMetrics(); + } }); // check which node type to select @@ -79,13 +81,12 @@ export class ClusterOverviewStore extends KubeObjectStore i } @action - async loadMetrics(params?: IMetricsReqParams) { + async loadMetrics(params?: RequestMetricsParams) { await when(() => this.dependencies.nodeStore.isLoaded); const { masterNodes, workerNodes } = this.dependencies.nodeStore; const nodes = this.metricNodeRole === MetricNodeRole.MASTER && masterNodes.length ? masterNodes : workerNodes; - this.metrics = await getMetricsByNodeNames(nodes.map(node => node.getName()), params); - this.metricsLoaded = true; + this.metrics = await this.dependencies.requestClusterMetricsByNodeNames(nodes.map(node => node.getName()), params); } getMetricsValues(source: Partial): [number, string][] { @@ -101,8 +102,7 @@ export class ClusterOverviewStore extends KubeObjectStore i @action resetMetrics() { - this.metrics = {}; - this.metricsLoaded = false; + this.metrics = undefined; } reset() { diff --git a/src/renderer/components/+cluster/cluster-pie-charts.tsx b/src/renderer/components/+cluster/cluster-pie-charts.tsx index 6d633c9eb9..108fd6a11f 100644 --- a/src/renderer/components/+cluster/cluster-pie-charts.tsx +++ b/src/renderer/components/+cluster/cluster-pie-charts.tsx @@ -23,6 +23,7 @@ import clusterOverviewStoreInjectable from "./cluster-overview-store/cluster-ove 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/get-cluster-metrics-by-node-names.injectable"; function createLabels(rawLabelData: [string, number | undefined][]): string[] { return rawLabelData.map(([key, value]) => `${key}: ${value?.toFixed(2) || "N/A"}`); @@ -48,16 +49,32 @@ const NonInjectedClusterPieCharts = observer(({ ); }; - const renderCharts = () => { - const data = getMetricLastPoints(clusterOverviewStore.metrics); - const { memoryUsage, memoryRequests, memoryAllocatableCapacity, memoryCapacity, memoryLimits } = data; - const { cpuUsage, cpuRequests, cpuAllocatableCapacity, cpuCapacity, cpuLimits } = data; - const { podUsage, podAllocatableCapacity, podCapacity } = data; + 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 cpuLimits !== "number" || + typeof podCapacity !== "number" || + typeof podAllocatableCapacity !== "number" || + typeof memoryAllocatableCapacity !== "number" || + typeof memoryCapacity !== "number" || + typeof memoryLimits !== "number" || + typeof memoryUsage !== "number" || + typeof memoryRequests !== "number" + ) { + return null; + } + const cpuLimitsOverload = cpuLimits > cpuAllocatableCapacity; const memoryLimitsOverload = memoryLimits > memoryAllocatableCapacity; const defaultColor = activeTheme.get().colors.pieChartDefaultColor; - if (!memoryCapacity || !cpuCapacity || !podCapacity || !memoryAllocatableCapacity || !cpuAllocatableCapacity || !podAllocatableCapacity) return null; const cpuData: PieChartData = { datasets: [ { @@ -218,7 +235,7 @@ const NonInjectedClusterPieCharts = observer(({ ); }; - const renderContent = ({ metricNodeRole, metricsLoaded }: ClusterOverviewStore) => { + const renderContent = ({ metricNodeRole, metrics }: ClusterOverviewStore) => { const { masterNodes, workerNodes } = nodeStore; const nodes = metricNodeRole === MetricNodeRole.MASTER ? masterNodes : workerNodes; @@ -231,14 +248,16 @@ const NonInjectedClusterPieCharts = observer(({ ); } - if (!metricsLoaded) { + if (!metrics) { return (
); } - const { memoryCapacity, cpuCapacity, podCapacity } = getMetricLastPoints(clusterOverviewStore.metrics); + + const lastPoints = getMetricLastPoints(metrics); + const { memoryCapacity, cpuCapacity, podCapacity } = lastPoints; if (!memoryCapacity || !cpuCapacity || !podCapacity) { return ( @@ -248,7 +267,7 @@ const NonInjectedClusterPieCharts = observer(({ ); } - return renderCharts(); + return renderCharts(lastPoints); }; return ( diff --git a/src/renderer/components/+namespaces/namespace-details.tsx b/src/renderer/components/+namespaces/namespace-details.tsx index 12bb7a4208..7043f98ea5 100644 --- a/src/renderer/components/+namespaces/namespace-details.tsx +++ b/src/renderer/components/+namespaces/namespace-details.tsx @@ -10,7 +10,7 @@ import { computed, makeObservable, observable, reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { DrawerItem } from "../drawer"; import { cssNames } from "../../utils"; -import { getMetricsForNamespace, type PodMetricData, Namespace } from "../../../common/k8s-api/endpoints"; +import { Namespace } from "../../../common/k8s-api/endpoints"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { Link } from "react-router-dom"; import { Spinner } from "../spinner"; @@ -31,6 +31,8 @@ import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-activ import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable"; import limitRangeStoreInjectable from "../+config-limit-ranges/store.injectable"; import resourceQuotaStoreInjectable from "../+config-resource-quotas/store.injectable"; +import type { PodMetricInNamespaceData, RequestPodMetricsInNamespace } from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics-in-namespace.injectable"; +import requestPodMetricsInNamespaceInjectable from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics-in-namespace.injectable"; export interface NamespaceDetailsProps extends KubeObjectDetailsProps { } @@ -41,11 +43,12 @@ interface Dependencies { getDetailsUrl: GetDetailsUrl; resourceQuotaStore: ResourceQuotaStore; limitRangeStore: LimitRangeStore; + requestPodMetricsInNamespace: RequestPodMetricsInNamespace; } @observer class NonInjectedNamespaceDetails extends React.Component { - @observable metrics: PodMetricData | null = null; + @observable metrics: PodMetricInNamespaceData | null = null; constructor(props: NamespaceDetailsProps & Dependencies) { super(props); @@ -78,7 +81,7 @@ class NonInjectedNamespaceDetails extends React.Component { - this.metrics = await getMetricsForNamespace(this.props.object.getName(), ""); + this.metrics = await this.props.requestPodMetricsInNamespace(this.props.object.getName()); }; render() { @@ -144,6 +147,7 @@ export const NamespaceDetails = withInjectables { } +interface Dependencies { + requestIngressMetrics: RequestIngressMetrics; +} + @observer -export class IngressDetails extends React.Component { +class NonInjectedIngressDetails extends React.Component { @observable metrics: IngressMetricData | null = null; - constructor(props: IngressDetailsProps) { + constructor(props: IngressDetailsProps & Dependencies) { super(props); makeObservable(this); } @@ -42,9 +49,9 @@ export class IngressDetails extends React.Component { } loadMetrics = async () => { - const { object: ingress } = this.props; + const { object: ingress, requestIngressMetrics } = this.props; - this.metrics = await getMetricsForIngress(ingress.getName(), ingress.getNs()); + this.metrics = await requestIngressMetrics(ingress.getName(), ingress.getNs()); }; renderPaths(ingress: Ingress) { @@ -170,3 +177,10 @@ export class IngressDetails extends React.Component { ); } } + +export const IngressDetails = withInjectables(NonInjectedIngressDetails, { + getProps: (di, props) => ({ + ...props, + requestIngressMetrics: di.inject(requestIngressMetricsInjectable), + }), +}); diff --git a/src/renderer/components/+nodes/details.tsx b/src/renderer/components/+nodes/details.tsx index 3377bcb4a5..b9666e2ceb 100644 --- a/src/renderer/components/+nodes/details.tsx +++ b/src/renderer/components/+nodes/details.tsx @@ -13,8 +13,7 @@ import { DrawerItem, DrawerItemLabels } from "../drawer"; import { Badge } from "../badge"; import { ResourceMetrics } from "../resource-metrics"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import type { ClusterMetricData } from "../../../common/k8s-api/endpoints"; -import { formatNodeTaint, getMetricsByNodeNames, Node } from "../../../common/k8s-api/endpoints"; +import { formatNodeTaint, Node } from "../../../common/k8s-api/endpoints"; import { NodeCharts } from "./node-charts"; import { makeObservable, observable, reaction } from "mobx"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; @@ -30,6 +29,8 @@ import type { PodStore } from "../+workloads-pods/store"; import podStoreInjectable from "../+workloads-pods/store.injectable"; import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable"; import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import type { ClusterMetricData, RequestClusterMetricsByNodeNames } from "../../../common/k8s-api/endpoints/metrics.api/get-cluster-metrics-by-node-names.injectable"; +import requestClusterMetricsByNodeNamesInjectable from "../../../common/k8s-api/endpoints/metrics.api/get-cluster-metrics-by-node-names.injectable"; export interface NodeDetailsProps extends KubeObjectDetailsProps { } @@ -38,11 +39,12 @@ interface Dependencies { subscribeStores: SubscribeStores; podStore: PodStore; getActiveClusterEntity: GetActiveClusterEntity; + requestClusterMetricsByNodeNames: RequestClusterMetricsByNodeNames; } @observer class NonInjectedNodeDetails extends React.Component { - @observable metrics: Partial | null = null; + @observable metrics: ClusterMetricData | null = null; constructor(props: NodeDetailsProps & Dependencies) { super(props); @@ -62,9 +64,9 @@ class NonInjectedNodeDetails extends React.Component { - const { object: node } = this.props; + const { object: node, requestClusterMetricsByNodeNames } = this.props; - this.metrics = await getMetricsByNodeNames([node.getName()]); + this.metrics = await requestClusterMetricsByNodeNames([node.getName()]); }; render() { @@ -196,6 +198,7 @@ export const NodeDetails = withInjectables(NonIn subscribeStores: di.inject(subscribeStoresInjectable), podStore: di.inject(podStoreInjectable), getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable), + requestClusterMetricsByNodeNames: di.inject(requestClusterMetricsByNodeNamesInjectable), }), }); diff --git a/src/renderer/components/+nodes/route.tsx b/src/renderer/components/+nodes/route.tsx index 782d4808e6..4a2fc903e3 100644 --- a/src/renderer/components/+nodes/route.tsx +++ b/src/renderer/components/+nodes/route.tsx @@ -8,10 +8,9 @@ import React from "react"; import { observer } from "mobx-react"; import { cssNames, interval } from "../../utils"; import { TabLayout } from "../layout/tab-layout-2"; -import { nodeStore } from "./legacy-store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; -import type { NodeMetricData, Node } from "../../../common/k8s-api/endpoints/node.api"; -import { formatNodeTaint, getMetricsForAllNodes } from "../../../common/k8s-api/endpoints/node.api"; +import type { Node } from "../../../common/k8s-api/endpoints/node.api"; +import { formatNodeTaint } from "../../../common/k8s-api/endpoints/node.api"; import { LineProgress } from "../line-progress"; import { bytesToUnits } from "../../../common/utils/convertMemory"; import { Tooltip, TooltipPosition } from "../tooltip"; @@ -19,10 +18,15 @@ import kebabCase from "lodash/kebabCase"; import upperFirst from "lodash/upperFirst"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { Badge } from "../badge/badge"; -import { eventStore } from "../+events/legacy-store"; import { makeObservable, observable } from "mobx"; -import isEmpty from "lodash/isEmpty"; import { KubeObjectAge } from "../kube-object/age"; +import type { NodeMetricData, RequestAllNodeMetrics } from "../../../common/k8s-api/endpoints/metrics.api/get-metrics-for-all-nodes.injectable"; +import type { NodeStore } from "./store"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import nodeStoreInjectable from "./store.injectable"; +import requestAllNodeMetricsInjectable from "../../../common/k8s-api/endpoints/metrics.api/get-metrics-for-all-nodes.injectable"; +import eventStoreInjectable from "../+events/store.injectable"; +import type { EventStore } from "../+events/store"; enum columnId { name = "name", @@ -42,16 +46,23 @@ type MetricsTooltipFormatter = (metrics: [number, number]) => string; interface UsageArgs { node: Node; title: string; - metricNames: [string, string]; + metricNames: [keyof NodeMetricData, keyof NodeMetricData]; formatters: MetricsTooltipFormatter[]; } -@observer -export class NodesRoute extends React.Component { - @observable.ref metrics: Partial = {}; - private metricsWatcher = interval(30, async () => this.metrics = await getMetricsForAllNodes()); +interface Dependencies { + requestAllNodeMetrics: RequestAllNodeMetrics; + nodeStore: NodeStore; + eventStore: EventStore; +} - constructor(props: any) { +@observer +class NonInjectedNodesRoute extends React.Component { + @observable metrics: NodeMetricData | null = null; + + private metricsWatcher = interval(30, async () => this.metrics = await this.props.requestAllNodeMetrics()); + + constructor(props: Dependencies) { super(props); makeObservable(this); } @@ -64,8 +75,8 @@ export class NodesRoute extends React.Component { this.metricsWatcher.stop(); } - getLastMetricValues(node: Node, metricNames: string[]): number[] { - if (isEmpty(this.metrics)) { + getLastMetricValues(node: Node, metricNames: (keyof NodeMetricData)[]): number[] { + if (!this.metrics) { return []; } @@ -73,7 +84,7 @@ export class NodesRoute extends React.Component { return metricNames.map(metricName => { try { - const metric = this.metrics[metricName]; + const metric = this.metrics?.[metricName]; const result = metric?.data.result.find(({ metric: { node, instance, kubernetes_node }}) => ( nodeName === node || nodeName === instance @@ -175,6 +186,8 @@ export class NodesRoute extends React.Component { } render() { + const { nodeStore, eventStore } = this.props; + return ( (NonInjectedNodesRoute, { + getProps: (di, props) => ({ + ...props, + nodeStore: di.inject(nodeStoreInjectable), + eventStore: di.inject(eventStoreInjectable), + requestAllNodeMetrics: di.inject(requestAllNodeMetricsInjectable), + }), +}); diff --git a/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx b/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx index cd4bd5e0a6..36dbf76299 100644 --- a/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx +++ b/src/renderer/components/+storage-volume-claims/volume-claim-details.tsx @@ -10,26 +10,39 @@ import { makeObservable, observable, reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge"; -import { podStore } from "../+workloads-pods/legacy-store"; import { Link } from "react-router-dom"; import { ResourceMetrics } from "../resource-metrics"; import { VolumeClaimDiskChart } from "./volume-claim-disk-chart"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import { getMetricsForPvc, type PersistentVolumeClaimMetricData, PersistentVolumeClaim } from "../../../common/k8s-api/endpoints"; -import { getActiveClusterEntity } from "../../api/catalog/entity/legacy-globals"; +import { PersistentVolumeClaim } from "../../../common/k8s-api/endpoints"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; import { KubeObjectMeta } from "../kube-object-meta"; -import { getDetailsUrl } from "../kube-detail-params"; import logger from "../../../common/logger"; +import type { PersistentVolumeClaimMetricData, RequestPersistentVolumeClaimMetrics } from "../../../common/k8s-api/endpoints/metrics.api/get-persistent-volume-claim-metrics.injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import requestPersistentVolumeClaimMetricsInjectable from "../../../common/k8s-api/endpoints/metrics.api/get-persistent-volume-claim-metrics.injectable"; +import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import type { GetDetailsUrl } from "../kube-detail-params/get-details-url.injectable"; +import type { PodStore } from "../+workloads-pods/store"; +import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable"; +import podStoreInjectable from "../+workloads-pods/store.injectable"; export interface PersistentVolumeClaimDetailsProps extends KubeObjectDetailsProps { } +interface Dependencies { + requestPersistentVolumeClaimMetrics: RequestPersistentVolumeClaimMetrics; + getActiveClusterEntity: GetActiveClusterEntity; + getDetailsUrl: GetDetailsUrl; + podStore: PodStore; +} + @observer -export class PersistentVolumeClaimDetails extends React.Component { +class NonInjectedPersistentVolumeClaimDetails extends React.Component { @observable metrics: PersistentVolumeClaimMetricData | null = null; - constructor(props: PersistentVolumeClaimDetailsProps) { + constructor(props: PersistentVolumeClaimDetailsProps & Dependencies) { super(props); makeObservable(this); } @@ -43,13 +56,13 @@ export class PersistentVolumeClaimDetails extends React.Component { - const { object: volumeClaim } = this.props; + const { object: volumeClaim, requestPersistentVolumeClaimMetrics } = this.props; - this.metrics = await getMetricsForPvc(volumeClaim); + this.metrics = await requestPersistentVolumeClaimMetrics(volumeClaim); }; render() { - const { object: volumeClaim } = this.props; + const { object: volumeClaim, getActiveClusterEntity, podStore, getDetailsUrl } = this.props; if (!volumeClaim) { return null; @@ -119,3 +132,13 @@ export class PersistentVolumeClaimDetails extends React.Component(NonInjectedPersistentVolumeClaimDetails, { + getProps: (di, props) => ({ + ...props, + requestPersistentVolumeClaimMetrics: di.inject(requestPersistentVolumeClaimMetricsInjectable), + getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable), + getDetailsUrl: di.inject(getDetailsUrlInjectable), + podStore: di.inject(podStoreInjectable), + }), +}); diff --git a/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx b/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx index 0cc16ff6f6..39c19ff3b9 100644 --- a/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx +++ b/src/renderer/components/+workloads-daemonsets/daemonset-details.tsx @@ -15,7 +15,7 @@ import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities" import type { DaemonSetStore } from "./store"; import type { PodStore } from "../+workloads-pods/store"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import { DaemonSet, getMetricsForDaemonSets, type PodMetricData } from "../../../common/k8s-api/endpoints"; +import { DaemonSet } from "../../../common/k8s-api/endpoints"; import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { makeObservable, observable, reaction } from "mobx"; @@ -30,6 +30,8 @@ import daemonSetStoreInjectable from "./store.injectable"; import podStoreInjectable from "../+workloads-pods/store.injectable"; import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable"; import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import type { DaemonSetPodMetricData, RequestPodMetricsForDaemonSets } from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-daemon-sets.injectable"; +import requestPodMetricsForDaemonSetsInjectable from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-daemon-sets.injectable"; export interface DaemonSetDetailsProps extends KubeObjectDetailsProps { } @@ -39,11 +41,12 @@ interface Dependencies { daemonSetStore: DaemonSetStore; podStore: PodStore; getActiveClusterEntity: GetActiveClusterEntity; + requestPodMetricsForDaemonSets: RequestPodMetricsForDaemonSets; } @observer class NonInjectedDaemonSetDetails extends React.Component { - @observable metrics: PodMetricData | null = null; + @observable metrics: DaemonSetPodMetricData | null = null; constructor(props: DaemonSetDetailsProps & Dependencies) { super(props); @@ -62,9 +65,9 @@ class NonInjectedDaemonSetDetails extends React.Component { - const { object: daemonSet } = this.props; + const { object: daemonSet, requestPodMetricsForDaemonSets } = this.props; - this.metrics = await getMetricsForDaemonSets([daemonSet], daemonSet.getNs(), ""); + this.metrics = await requestPodMetricsForDaemonSets([daemonSet], daemonSet.getNs()); }; render() { @@ -143,5 +146,6 @@ export const DaemonSetDetails = withInjectables { } @@ -44,11 +45,12 @@ interface Dependencies { replicaSetStore: ReplicaSetStore; deploymentStore: DeploymentStore; getActiveClusterEntity: GetActiveClusterEntity; + requestPodMetricsForDeployments: RequestPodMetricsForDeployments; } @observer class NonInjectedDeploymentDetails extends React.Component { - @observable metrics: PodMetricData | null = null; + @observable metrics: DeploymentPodMetricData | null = null; constructor(props: DeploymentDetailsProps & Dependencies) { super(props); @@ -69,9 +71,9 @@ class NonInjectedDeploymentDetails extends React.Component { - const { object: deployment } = this.props; + const { object: deployment, requestPodMetricsForDeployments } = this.props; - this.metrics = await getMetricsForDeployments([deployment], deployment.getNs(), ""); + this.metrics = await requestPodMetricsForDeployments([deployment], deployment.getNs()); }; render() { @@ -173,6 +175,7 @@ export const DeploymentDetails = withInjectables { } @@ -40,11 +42,12 @@ interface Dependencies { podStore: PodStore; jobStore: JobStore; getActiveClusterEntity: GetActiveClusterEntity; + requestPodMetricsForJobs: RequestPodMetricsForJobs; } @observer class NonInjectedJobDetails extends React.Component { - @observable metrics: PodMetricData | null = null; + @observable metrics: JobPodMetricData | null = null; constructor(props: JobDetailsProps & Dependencies) { super(props); @@ -63,9 +66,9 @@ class NonInjectedJobDetails extends React.Component { - const { object: job } = this.props; + const { object: job, requestPodMetricsForJobs } = this.props; - this.metrics = await getMetricsForJobs([job], job.getNs(), ""); + this.metrics = await requestPodMetricsForJobs([job], job.getNs(), ""); }; render() { @@ -159,6 +162,7 @@ export const JobDetails = withInjectables(NonInje podStore: di.inject(podStoreInjectable), jobStore: di.inject(jobStoreInjectable), getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable), + requestPodMetricsForJobs: di.inject(requestPodMetricsForJobsInjectable), }), }); diff --git a/src/renderer/components/+workloads-pods/pod-details.tsx b/src/renderer/components/+workloads-pods/pod-details.tsx index d87ce249e0..0e21f09c4a 100644 --- a/src/renderer/components/+workloads-pods/pod-details.tsx +++ b/src/renderer/components/+workloads-pods/pod-details.tsx @@ -10,8 +10,8 @@ import kebabCase from "lodash/kebabCase"; import { disposeOnUnmount, observer } from "mobx-react"; import { Link } from "react-router-dom"; import { observable, reaction, makeObservable } from "mobx"; -import type { PodMetricData } from "../../../common/k8s-api/endpoints"; -import { nodeApi, Pod, getMetricsForPods } from "../../../common/k8s-api/endpoints"; +import type { NodeApi } from "../../../common/k8s-api/endpoints"; +import { Pod } from "../../../common/k8s-api/endpoints"; import { DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge"; import { cssNames, toJS } from "../../utils"; @@ -24,21 +24,34 @@ import type { KubeObjectDetailsProps } from "../kube-object-details"; import { getItemMetrics } from "../../../common/k8s-api/endpoints/metrics.api"; import { PodCharts, podMetricTabs } from "./pod-charts"; import { KubeObjectMeta } from "../kube-object-meta"; -import { getActiveClusterEntity } from "../../api/catalog/entity/legacy-globals"; import { ClusterMetricsResourceType } from "../../../common/cluster-types"; -import { getDetailsUrl } from "../kube-detail-params"; import logger from "../../../common/logger"; import { PodVolumes } from "./details/volumes/view"; +import type { PodMetricData, RequestPodMetrics } from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics.injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import requestPodMetricsInjectable from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics.injectable"; +import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import type { GetDetailsUrl } from "../kube-detail-params/get-details-url.injectable"; +import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable"; +import nodeApiInjectable from "../../../common/k8s-api/endpoints/node.api.injectable"; export interface PodDetailsProps extends KubeObjectDetailsProps { } +interface Dependencies { + requestPodMetrics: RequestPodMetrics; + getActiveClusterEntity: GetActiveClusterEntity; + getDetailsUrl: GetDetailsUrl; + nodeApi: NodeApi; +} + @observer -export class PodDetails extends React.Component { +class NonInjectedPodDetails extends React.Component { @observable metrics: PodMetricData | null = null; @observable containerMetrics: PodMetricData | null = null; - constructor(props: PodDetailsProps) { + constructor(props: PodDetailsProps & Dependencies) { super(props); makeObservable(this); } @@ -53,14 +66,14 @@ export class PodDetails extends React.Component { } loadMetrics = async () => { - const { object: pod } = this.props; + const { object: pod, requestPodMetrics } = this.props; - this.metrics = await getMetricsForPods([pod], pod.getNs()); - this.containerMetrics = await getMetricsForPods([pod], pod.getNs(), "container, namespace"); + this.metrics = await requestPodMetrics([pod], pod.getNs()); + this.containerMetrics = await requestPodMetrics([pod], pod.getNs(), "container, namespace"); }; render() { - const { object: pod } = this.props; + const { object: pod, getActiveClusterEntity, getDetailsUrl, nodeApi } = this.props; if (!pod) { return null; @@ -183,3 +196,13 @@ export class PodDetails extends React.Component { ); } } + +export const PodDetails = withInjectables(NonInjectedPodDetails, { + getProps: (di, props) => ({ + ...props, + requestPodMetrics: di.inject(requestPodMetricsInjectable), + getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable), + getDetailsUrl: di.inject(getDetailsUrlInjectable), + nodeApi: di.inject(nodeApiInjectable), + }), +}); diff --git a/src/renderer/components/+workloads-replicasets/replicaset-details.tsx b/src/renderer/components/+workloads-replicasets/replicaset-details.tsx index 6f239f3dbb..08395330f3 100644 --- a/src/renderer/components/+workloads-replicasets/replicaset-details.tsx +++ b/src/renderer/components/+workloads-replicasets/replicaset-details.tsx @@ -13,8 +13,7 @@ import { PodDetailsTolerations } from "../+workloads-pods/pod-details-toleration import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities"; import { disposeOnUnmount, observer } from "mobx-react"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import type { PodMetricData } from "../../../common/k8s-api/endpoints"; -import { getMetricsForReplicaSets, ReplicaSet } from "../../../common/k8s-api/endpoints"; +import { ReplicaSet } from "../../../common/k8s-api/endpoints"; import { ResourceMetrics, ResourceMetricsText } from "../resource-metrics"; import { PodCharts, podMetricTabs } from "../+workloads-pods/pod-charts"; import { PodDetailsList } from "../+workloads-pods/pod-details-list"; @@ -30,6 +29,8 @@ import type { ReplicaSetStore } from "./store"; import replicaSetStoreInjectable from "./store.injectable"; import type { GetActiveClusterEntity } from "../../api/catalog/entity/get-active-cluster-entity.injectable"; import getActiveClusterEntityInjectable from "../../api/catalog/entity/get-active-cluster-entity.injectable"; +import type { ReplicaSetPodMetricData, RequestPodMetricsForReplicaSets } from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-replica-sets.injectable"; +import requestPodMetricsForReplicaSetsInjectable from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-replica-sets.injectable"; export interface ReplicaSetDetailsProps extends KubeObjectDetailsProps { } @@ -39,11 +40,12 @@ interface Dependencies { podStore: PodStore; replicaSetStore: ReplicaSetStore; getActiveClusterEntity: GetActiveClusterEntity; + requestPodMetricsForReplicaSets: RequestPodMetricsForReplicaSets; } @observer class NonInjectedReplicaSetDetails extends React.Component { - @observable metrics: PodMetricData | null = null; + @observable metrics: ReplicaSetPodMetricData | null = null; constructor(props: ReplicaSetDetailsProps & Dependencies) { super(props); @@ -63,9 +65,9 @@ class NonInjectedReplicaSetDetails extends React.Component { - const { object: replicaSet } = this.props; + const { object: replicaSet, requestPodMetricsForReplicaSets } = this.props; - this.metrics = await getMetricsForReplicaSets([replicaSet], replicaSet.getNs(), ""); + this.metrics = await requestPodMetricsForReplicaSets([replicaSet], replicaSet.getNs()); }; render() { @@ -144,5 +146,6 @@ export const ReplicaSetDetails = withInjectables { } @@ -40,11 +41,12 @@ interface Dependencies { podStore: PodStore; statefulSetStore: StatefulSetStore; getActiveClusterEntity: GetActiveClusterEntity; + requestPodMetricsForStatefulSets: RequestPodMetricsForStatefulSets; } @observer class NonInjectedStatefulSetDetails extends React.Component { - @observable metrics: PodMetricData | null = null; + @observable metrics: StatefulSetPodMetricData | null = null; constructor(props: StatefulSetDetailsProps & Dependencies) { super(props); @@ -64,9 +66,9 @@ class NonInjectedStatefulSetDetails extends React.Component { - const { object: statefulSet } = this.props; + const { object: statefulSet, requestPodMetricsForStatefulSets } = this.props; - this.metrics = await getMetricsForStatefulSets([statefulSet], statefulSet.getNs(), ""); + this.metrics = await requestPodMetricsForStatefulSets([statefulSet], statefulSet.getNs()); }; render() { @@ -143,6 +145,7 @@ export const StatefulSetDetails = withInjectables { this.loading = false; this.loadedOptions.replace(values.map(provider => [provider.id, provider])); @@ -174,5 +174,6 @@ export const ClusterPrometheusSetting = withInjectables ({ ...props, productName: di.inject(productNameInjectable), + requestMetricsProviders: di.inject(requestMetricsProvidersInjectable), }), }); diff --git a/src/renderer/components/resource-metrics/resource-metrics-text.tsx b/src/renderer/components/resource-metrics/resource-metrics-text.tsx index cedf0d136f..2e10596dd3 100644 --- a/src/renderer/components/resource-metrics/resource-metrics-text.tsx +++ b/src/renderer/components/resource-metrics/resource-metrics-text.tsx @@ -4,14 +4,23 @@ */ import React from "react"; -import type { PodMetricData } from "../../../common/k8s-api/endpoints"; +import type { MetricData } from "../../../common/k8s-api/endpoints/metrics.api"; import { getMetricLastPoints } from "../../../common/k8s-api/endpoints/metrics.api"; import { bytesToUnits } from "../../utils"; import { Badge } from "../badge"; import { DrawerItem } from "../drawer"; +export interface ResourceMetricsTextMetrics { + cpuUsage?: MetricData; + cpuRequests?: MetricData; + cpuLimits?: MetricData; + memoryUsage?: MetricData; + memoryRequests?: MetricData; + memoryLimits?: MetricData; +} + export interface ResourceMetricsTextProps { - metrics: PodMetricData | null | undefined; + metrics: ResourceMetricsTextMetrics | null | undefined; } export function ResourceMetricsText({ metrics }: ResourceMetricsTextProps) { @@ -19,7 +28,10 @@ export function ResourceMetricsText({ metrics }: ResourceMetricsTextProps) { return null; } - const { cpuUsage, memoryUsage } = getMetricLastPoints(metrics); + const { + cpuUsage = 0, + memoryUsage = 0, + } = getMetricLastPoints(metrics); return ( <> diff --git a/src/renderer/components/resource-metrics/resource-metrics.tsx b/src/renderer/components/resource-metrics/resource-metrics.tsx index 0080491727..5865dc5d76 100644 --- a/src/renderer/components/resource-metrics/resource-metrics.tsx +++ b/src/renderer/components/resource-metrics/resource-metrics.tsx @@ -16,13 +16,14 @@ import type { MetricData } from "../../../common/k8s-api/endpoints/metrics.api"; export type AtLeastOneMetricTab = [MetricsTab, ...MetricsTab[]]; -export interface ResourceMetricsProps extends React.HTMLProps { +export interface ResourceMetricsProps { tabs: AtLeastOneMetricTab; object: KubeObject; loader?: () => void; interval?: number; className?: string; - metrics: Partial> | null | undefined; + metrics: Record | null | undefined; + children: React.ReactChild | React.ReactChild[]; } export interface ResourceMetricsValue { @@ -33,7 +34,7 @@ export interface ResourceMetricsValue { export const ResourceMetricsContext = createContext(null); -export function ResourceMetrics({ object, loader = noop, interval = 60, tabs, children, className, metrics }: ResourceMetricsProps) { +export function ResourceMetrics({ object, loader = noop, interval = 60, tabs, children, className, metrics }: ResourceMetricsProps) { const [tab, setTab] = useState(tabs[0]); // This is done just incase `loader` is actually something like `() => Promise`