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

Convert all metric requests to be injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-08-09 14:08:21 -04:00
parent 12beacec81
commit 35f3c08279
43 changed files with 869 additions and 441 deletions

View File

@ -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<Cluster> {
}
}
export function getMetricsByNodeNames(nodeNames: string[], params?: IMetricsReqParams): Promise<ClusterMetricData> {
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<Record<string, MetricData>> {
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?: {

View File

@ -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<DaemonSet> {
});
}
}
export function getMetricsForDaemonSets(daemonsets: DaemonSet[], namespace: string, selector = ""): Promise<PodMetricData> {
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,
});
}

View File

@ -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<Deployment> {
}
}
export function getMetricsForDeployments(deployments: Deployment[], namespace: string, selector = ""): Promise<PodMetricData> {
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;

View File

@ -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<Ingress> {
}
}
export function getMetricsForIngress(ingress: string, namespace: string): Promise<IngressMetricData> {
const opts = { category: "ingress", ingress, namespace };
return metricsApi.getMetrics({
bytesSentSuccess: opts,
bytesSentFailure: opts,
requestDurationSeconds: opts,
responseDurationSeconds: opts,
}, {
namespace,
});
}
export interface IngressMetricData extends Partial<Record<string, MetricData>> {
bytesSentSuccess: MetricData;
bytesSentFailure: MetricData;
requestDurationSeconds: MetricData;
responseDurationSeconds: MetricData;
}
export interface ILoadBalancerIngress {
hostname?: string;
ip?: string;

View File

@ -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<Job> {
});
}
}
export function getMetricsForJobs(jobs: Job[], namespace: string, selector = ""): Promise<PodMetricData> {
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,
});
}

View File

@ -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<T extends MetricData> {
[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<MetricData>;
async function getMetrics(query: string[], reqParams?: IMetricsReqParams): Promise<MetricData[]>;
async function getMetrics<MetricNames extends string>(query: Record<MetricNames, Partial<Record<string, string>>>, reqParams?: IMetricsReqParams): Promise<Record<MetricNames, MetricData>>;
async function getMetrics(query: string | string[] | Partial<Record<string, Partial<Record<string, string>>>>, reqParams: IMetricsReqParams = {}): Promise<MetricData | MetricData[] | Partial<Record<string, MetricData>>> {
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<MetricProviderInfo[]> {
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<Record<string, MetricData>>) {
return Object.values(metrics).every(metric => !metric?.data?.result?.length);
}
export function getItemMetrics(metrics: Partial<Record<string, MetricData>> | null | undefined, itemName: string): Partial<Record<string, 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;
}
@ -166,22 +109,16 @@ export function getItemMetrics(metrics: Partial<Record<string, MetricData>> | nu
return itemMetrics;
}
export function getMetricLastPoints<T extends Partial<Record<string, MetricData>>>(metrics: T): Record<keyof T, number> {
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<keyof T, number>;
export function getMetricLastPoints<Keys extends string>(metrics: Partial<Record<Keys, MetricData>>): Partial<Record<Keys, number>> {
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),
);
}

View File

@ -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<ClusterMetricData>;
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;

View File

@ -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<IngressMetricData>;
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;

View File

@ -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<NodeMetricData>;
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;

View File

@ -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<PersistentVolumeClaimMetricData>;
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;

View File

@ -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<DaemonSetPodMetricData>;
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;

View File

@ -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<DeploymentPodMetricData>;
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;

View File

@ -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<JobPodMetricData>;
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;

View File

@ -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<ReplicaSetPodMetricData>;
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;

View File

@ -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<StatefulSetPodMetricData>;
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;

View File

@ -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<PodMetricInNamespaceData>;
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;

View File

@ -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<PodMetricData>;
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;

View File

@ -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<MetricProviderInfo[]>;
const requestMetricsProvidersInjectable = getInjectable({
id: "request-metrics-providers",
instantiate: (di): RequestMetricsProviders => {
const apiBase = di.inject(apiBaseInjectionToken);
return () => apiBase.get("/metrics/providers");
},
});
export default requestMetricsProvidersInjectable;

View File

@ -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<MetricData>;
(query: string[], params?: RequestMetricsParams): Promise<MetricData[]>;
<Keys extends string>(query: Record<Keys, Partial<Record<string, string>>>, params?: RequestMetricsParams): Promise<Record<Keys, MetricData>>;
}
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;

View File

@ -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<Namespace> {
});
}
}
export function getMetricsForNamespace(namespace: string, selector = ""): Promise<PodMetricData> {
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,
});
}

View File

@ -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<Node> {
}
}
export function getMetricsForAllNodes(): Promise<NodeMetricData> {
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<Record<string, MetricData>> {
memoryUsage: MetricData;
workloadMemoryUsage: MetricData;
memoryCapacity: MetricData;
memoryAllocatableCapacity: MetricData;
cpuUsage: MetricData;
cpuCapacity: MetricData;
fsUsage: MetricData;
fsSize: MetricData;
}
export interface NodeTaint {
key: string;
value?: string;

View File

@ -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<PersistentVolumeClaim> {
}
}
export function getMetricsForPvc(pvc: PersistentVolumeClaim): Promise<PersistentVolumeClaimMetricData> {
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<Record<string, MetricData>> {
diskUsage: MetricData;
diskCapacity: MetricData;
}
export interface PersistentVolumeClaimSpec {
accessModes?: string[];
dataSource?: TypedLocalObjectReference;

View File

@ -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<Pod> {
}
}
export function getMetricsForPods(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise<PodMetricData> {
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<Record<string, MetricData>> {
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;

View File

@ -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<ReplicaSet> {
}
}
export function getMetricsForReplicaSets(replicasets: ReplicaSet[], namespace: string, selector = ""): Promise<PodMetricData> {
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;

View File

@ -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<StatefulSet> {
}
}
export function getMetricsForStatefulSets(statefulSets: StatefulSet[], namespace: string, selector = ""): Promise<PodMetricData> {
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;

View File

@ -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<any>;
export type GetMetrics = (cluster: Cluster, prometheusPath: string, queryParams: RequestMetricsParams & { query: string }) => Promise<any>;
const getMetricsInjectable = getInjectable({
id: "get-metrics",

View File

@ -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<string, string>): Promise<any[]> => {
const loadMetricsFor = (getMetrics: GetMetrics) => async (promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Partial<Record<string, string>>): Promise<any[]> => {
const queries = promQueries.map(p => p.trim());
const loaders = new Map<string, Promise<any>>();
@ -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<Record<string, string>> = Object.fromEntries(query.entries());
const prometheusMetadata: ClusterPrometheusMetadata = {};
try {

View File

@ -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<ClusterOverviewStorageState>;
readonly nodeStore: NodeStore;
requestClusterMetricsByNodeNames: RequestClusterMetricsByNodeNames;
}
export class ClusterOverviewStore extends KubeObjectStore<Cluster, ClusterApi> implements ClusterOverviewStorageState {
@observable metrics: Partial<ClusterMetricData> = {};
@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<Cluster, ClusterApi> 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<Cluster, ClusterApi> 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<ClusterMetricData>): [number, string][] {
@ -101,8 +102,7 @@ export class ClusterOverviewStore extends KubeObjectStore<Cluster, ClusterApi> i
@action
resetMetrics() {
this.metrics = {};
this.metricsLoaded = false;
this.metrics = undefined;
}
reset() {

View File

@ -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<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 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 (
<div className={cssNames(styles.empty, "flex justify-center align-center box grow")}>
<Spinner/>
</div>
);
}
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 (

View File

@ -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<Namespace> {
}
@ -41,11 +43,12 @@ interface Dependencies {
getDetailsUrl: GetDetailsUrl;
resourceQuotaStore: ResourceQuotaStore;
limitRangeStore: LimitRangeStore;
requestPodMetricsInNamespace: RequestPodMetricsInNamespace;
}
@observer
class NonInjectedNamespaceDetails extends React.Component<NamespaceDetailsProps & Dependencies> {
@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<NamespaceDetailsProps
}
loadMetrics = async () => {
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<Dependencies, NamespaceDetailsPr
getDetailsUrl: di.inject(getDetailsUrlInjectable),
limitRangeStore: di.inject(limitRangeStoreInjectable),
resourceQuotaStore: di.inject(resourceQuotaStoreInjectable),
requestPodMetricsInNamespace: di.inject(requestPodMetricsInNamespaceInjectable),
}),
});

View File

@ -16,19 +16,26 @@ import { ResourceMetrics } from "../resource-metrics";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import { IngressCharts } from "./ingress-charts";
import { KubeObjectMeta } from "../kube-object-meta";
import { computeRuleDeclarations, getMetricsForIngress, type IngressMetricData } from "../../../common/k8s-api/endpoints/ingress.api";
import { computeRuleDeclarations } from "../../../common/k8s-api/endpoints/ingress.api";
import { getActiveClusterEntity } from "../../api/catalog/entity/legacy-globals";
import { ClusterMetricsResourceType } from "../../../common/cluster-types";
import logger from "../../../common/logger";
import type { IngressMetricData, RequestIngressMetrics } from "../../../common/k8s-api/endpoints/metrics.api/get-ingress-metrics.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import requestIngressMetricsInjectable from "../../../common/k8s-api/endpoints/metrics.api/get-ingress-metrics.injectable";
export interface IngressDetailsProps extends KubeObjectDetailsProps<Ingress> {
}
interface Dependencies {
requestIngressMetrics: RequestIngressMetrics;
}
@observer
export class IngressDetails extends React.Component<IngressDetailsProps> {
class NonInjectedIngressDetails extends React.Component<IngressDetailsProps & Dependencies> {
@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<IngressDetailsProps> {
}
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<IngressDetailsProps> {
);
}
}
export const IngressDetails = withInjectables<Dependencies, IngressDetailsProps>(NonInjectedIngressDetails, {
getProps: (di, props) => ({
...props,
requestIngressMetrics: di.inject(requestIngressMetricsInjectable),
}),
});

View File

@ -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<Node> {
}
@ -38,11 +39,12 @@ interface Dependencies {
subscribeStores: SubscribeStores;
podStore: PodStore;
getActiveClusterEntity: GetActiveClusterEntity;
requestClusterMetricsByNodeNames: RequestClusterMetricsByNodeNames;
}
@observer
class NonInjectedNodeDetails extends React.Component<NodeDetailsProps & Dependencies> {
@observable metrics: Partial<ClusterMetricData> | null = null;
@observable metrics: ClusterMetricData | null = null;
constructor(props: NodeDetailsProps & Dependencies) {
super(props);
@ -62,9 +64,9 @@ class NonInjectedNodeDetails extends React.Component<NodeDetailsProps & Dependen
}
loadMetrics = async () => {
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<Dependencies, NodeDetailsProps>(NonIn
subscribeStores: di.inject(subscribeStoresInjectable),
podStore: di.inject(podStoreInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
requestClusterMetricsByNodeNames: di.inject(requestClusterMetricsByNodeNamesInjectable),
}),
});

View File

@ -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<NodeMetricData> = {};
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<Dependencies> {
@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 (
<TabLayout>
<KubeObjectListLayout
@ -251,3 +264,12 @@ export class NodesRoute extends React.Component {
);
}
}
export const NodesRoute = withInjectables<Dependencies>(NonInjectedNodesRoute, {
getProps: (di, props) => ({
...props,
nodeStore: di.inject(nodeStoreInjectable),
eventStore: di.inject(eventStoreInjectable),
requestAllNodeMetrics: di.inject(requestAllNodeMetricsInjectable),
}),
});

View File

@ -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<PersistentVolumeClaim> {
}
interface Dependencies {
requestPersistentVolumeClaimMetrics: RequestPersistentVolumeClaimMetrics;
getActiveClusterEntity: GetActiveClusterEntity;
getDetailsUrl: GetDetailsUrl;
podStore: PodStore;
}
@observer
export class PersistentVolumeClaimDetails extends React.Component<PersistentVolumeClaimDetailsProps> {
class NonInjectedPersistentVolumeClaimDetails extends React.Component<PersistentVolumeClaimDetailsProps & Dependencies> {
@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<PersistentVolu
}
loadMetrics = async () => {
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<PersistentVolu
);
}
}
export const PersistentVolumeClaimDetails = withInjectables<Dependencies, PersistentVolumeClaimDetailsProps>(NonInjectedPersistentVolumeClaimDetails, {
getProps: (di, props) => ({
...props,
requestPersistentVolumeClaimMetrics: di.inject(requestPersistentVolumeClaimMetricsInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
getDetailsUrl: di.inject(getDetailsUrlInjectable),
podStore: di.inject(podStoreInjectable),
}),
});

View File

@ -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<DaemonSet> {
}
@ -39,11 +41,12 @@ interface Dependencies {
daemonSetStore: DaemonSetStore;
podStore: PodStore;
getActiveClusterEntity: GetActiveClusterEntity;
requestPodMetricsForDaemonSets: RequestPodMetricsForDaemonSets;
}
@observer
class NonInjectedDaemonSetDetails extends React.Component<DaemonSetDetailsProps & Dependencies> {
@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<DaemonSetDetailsProps
}
loadMetrics = async () => {
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<Dependencies, DaemonSetDetailsPr
daemonSetStore: di.inject(daemonSetStoreInjectable),
podStore: di.inject(podStoreInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
requestPodMetricsForDaemonSets: di.inject(requestPodMetricsForDaemonSetsInjectable),
}),
});

View File

@ -10,8 +10,7 @@ import kebabCase from "lodash/kebabCase";
import { disposeOnUnmount, observer } from "mobx-react";
import { DrawerItem } from "../drawer";
import { Badge } from "../badge";
import type { PodMetricData } from "../../../common/k8s-api/endpoints";
import { Deployment, getMetricsForDeployments } from "../../../common/k8s-api/endpoints";
import { Deployment } from "../../../common/k8s-api/endpoints";
import { PodDetailsTolerations } from "../+workloads-pods/pod-details-tolerations";
import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities";
import type { KubeObjectDetailsProps } from "../kube-object-details";
@ -34,6 +33,8 @@ import replicaSetStoreInjectable from "../+workloads-replicasets/store.injectabl
import deploymentStoreInjectable 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 { DeploymentPodMetricData, RequestPodMetricsForDeployments } from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-deployments.injectable";
import requestPodMetricsForDeploymentsInjectable from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-deployments.injectable";
export interface DeploymentDetailsProps extends KubeObjectDetailsProps<Deployment> {
}
@ -44,11 +45,12 @@ interface Dependencies {
replicaSetStore: ReplicaSetStore;
deploymentStore: DeploymentStore;
getActiveClusterEntity: GetActiveClusterEntity;
requestPodMetricsForDeployments: RequestPodMetricsForDeployments;
}
@observer
class NonInjectedDeploymentDetails extends React.Component<DeploymentDetailsProps & Dependencies> {
@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<DeploymentDetailsProp
}
loadMetrics = async () => {
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<Dependencies, DeploymentDetails
replicaSetStore: di.inject(replicaSetStoreInjectable),
deploymentStore: di.inject(deploymentStoreInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
requestPodMetricsForDeployments: di.inject(requestPodMetricsForDeploymentsInjectable),
}),
});

View File

@ -15,7 +15,7 @@ import { PodDetailsTolerations } from "../+workloads-pods/pod-details-toleration
import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities";
import type { JobStore } from "./store";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import { getMetricsForJobs, type PodMetricData, Job } from "../../../common/k8s-api/endpoints";
import { Job } from "../../../common/k8s-api/endpoints";
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { KubeObjectMeta } from "../kube-object-meta";
import { makeObservable, observable, reaction } from "mobx";
@ -31,6 +31,8 @@ import podStoreInjectable from "../+workloads-pods/store.injectable";
import jobStoreInjectable 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 { JobPodMetricData, RequestPodMetricsForJobs } from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-jobs.injectable";
import requestPodMetricsForJobsInjectable from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-jobs.injectable";
export interface JobDetailsProps extends KubeObjectDetailsProps<Job> {
}
@ -40,11 +42,12 @@ interface Dependencies {
podStore: PodStore;
jobStore: JobStore;
getActiveClusterEntity: GetActiveClusterEntity;
requestPodMetricsForJobs: RequestPodMetricsForJobs;
}
@observer
class NonInjectedJobDetails extends React.Component<JobDetailsProps & Dependencies> {
@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<JobDetailsProps & Dependenci
}
loadMetrics = async () => {
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<Dependencies, JobDetailsProps>(NonInje
podStore: di.inject(podStoreInjectable),
jobStore: di.inject(jobStoreInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
requestPodMetricsForJobs: di.inject(requestPodMetricsForJobsInjectable),
}),
});

View File

@ -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<Pod> {
}
interface Dependencies {
requestPodMetrics: RequestPodMetrics;
getActiveClusterEntity: GetActiveClusterEntity;
getDetailsUrl: GetDetailsUrl;
nodeApi: NodeApi;
}
@observer
export class PodDetails extends React.Component<PodDetailsProps> {
class NonInjectedPodDetails extends React.Component<PodDetailsProps & Dependencies> {
@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<PodDetailsProps> {
}
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<PodDetailsProps> {
);
}
}
export const PodDetails = withInjectables<Dependencies, PodDetailsProps>(NonInjectedPodDetails, {
getProps: (di, props) => ({
...props,
requestPodMetrics: di.inject(requestPodMetricsInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
getDetailsUrl: di.inject(getDetailsUrlInjectable),
nodeApi: di.inject(nodeApiInjectable),
}),
});

View File

@ -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<ReplicaSet> {
}
@ -39,11 +40,12 @@ interface Dependencies {
podStore: PodStore;
replicaSetStore: ReplicaSetStore;
getActiveClusterEntity: GetActiveClusterEntity;
requestPodMetricsForReplicaSets: RequestPodMetricsForReplicaSets;
}
@observer
class NonInjectedReplicaSetDetails extends React.Component<ReplicaSetDetailsProps & Dependencies> {
@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<ReplicaSetDetailsProp
}
loadMetrics = async () => {
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<Dependencies, ReplicaSetDetails
podStore: di.inject(podStoreInjectable),
replicaSetStore: di.inject(replicaSetStoreInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
requestPodMetricsForReplicaSets: di.inject(requestPodMetricsForReplicaSetsInjectable),
}),
});

View File

@ -15,8 +15,7 @@ import { PodDetailsTolerations } from "../+workloads-pods/pod-details-toleration
import { PodDetailsAffinities } from "../+workloads-pods/pod-details-affinities";
import type { StatefulSetStore } from "./store";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import type { PodMetricData } from "../../../common/k8s-api/endpoints";
import { getMetricsForStatefulSets, StatefulSet } from "../../../common/k8s-api/endpoints";
import { StatefulSet } 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";
@ -31,6 +30,8 @@ import podStoreInjectable from "../+workloads-pods/store.injectable";
import statefulSetStoreInjectable 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 { RequestPodMetricsForStatefulSets, StatefulSetPodMetricData } from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-stateful-sets.injectable";
import requestPodMetricsForStatefulSetsInjectable from "../../../common/k8s-api/endpoints/metrics.api/get-pod-metrics-for-stateful-sets.injectable";
export interface StatefulSetDetailsProps extends KubeObjectDetailsProps<StatefulSet> {
}
@ -40,11 +41,12 @@ interface Dependencies {
podStore: PodStore;
statefulSetStore: StatefulSetStore;
getActiveClusterEntity: GetActiveClusterEntity;
requestPodMetricsForStatefulSets: RequestPodMetricsForStatefulSets;
}
@observer
class NonInjectedStatefulSetDetails extends React.Component<StatefulSetDetailsProps & Dependencies> {
@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<StatefulSetDetailsPr
}
loadMetrics = async () => {
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<Dependencies, StatefulSetDetai
podStore: di.inject(podStoreInjectable),
statefulSetStore: di.inject(statefulSetStoreInjectable),
getActiveClusterEntity: di.inject(getActiveClusterEntityInjectable),
requestPodMetricsForStatefulSets: di.inject(requestPodMetricsForStatefulSetsInjectable),
}),
});

View File

@ -11,10 +11,10 @@ import type { SelectOption } from "../../select";
import { Select } from "../../select";
import { Input } from "../../input";
import { observable, computed, autorun, makeObservable } from "mobx";
import type { MetricProviderInfo } from "../../../../common/k8s-api/endpoints/metrics.api";
import { metricsApi } from "../../../../common/k8s-api/endpoints/metrics.api";
import { Spinner } from "../../spinner";
import type { MetricProviderInfo, RequestMetricsProviders } from "../../../../common/k8s-api/endpoints/metrics.api/get-providers.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import requestMetricsProvidersInjectable from "../../../../common/k8s-api/endpoints/metrics.api/get-providers.injectable";
import productNameInjectable from "../../../../common/vars/product-name.injectable";
export interface ClusterPrometheusSettingProps {
@ -27,6 +27,7 @@ type ProviderValue = typeof autoDetectPrometheus | string;
interface Dependencies {
productName: string;
requestMetricsProviders: RequestMetricsProviders;
}
@observer
@ -85,8 +86,7 @@ class NonInjectedClusterPrometheusSetting extends React.Component<ClusterPrometh
}),
);
metricsApi
.getMetricProviders()
this.props.requestMetricsProviders()
.then(values => {
this.loading = false;
this.loadedOptions.replace(values.map(provider => [provider.id, provider]));
@ -174,5 +174,6 @@ export const ClusterPrometheusSetting = withInjectables<Dependencies, ClusterPro
getProps: (di, props) => ({
...props,
productName: di.inject(productNameInjectable),
requestMetricsProviders: di.inject(requestMetricsProvidersInjectable),
}),
});

View File

@ -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 (
<>

View File

@ -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<any> {
export interface ResourceMetricsProps<Keys extends string> {
tabs: AtLeastOneMetricTab;
object: KubeObject;
loader?: () => void;
interval?: number;
className?: string;
metrics: Partial<Record<string, MetricData>> | null | undefined;
metrics: Record<Keys, MetricData> | null | undefined;
children: React.ReactChild | React.ReactChild[];
}
export interface ResourceMetricsValue {
@ -33,7 +34,7 @@ export interface ResourceMetricsValue {
export const ResourceMetricsContext = createContext<ResourceMetricsValue | null>(null);
export function ResourceMetrics({ object, loader = noop, interval = 60, tabs, children, className, metrics }: ResourceMetricsProps) {
export function ResourceMetrics<Keys extends string>({ object, loader = noop, interval = 60, tabs, children, className, metrics }: ResourceMetricsProps<Keys>) {
const [tab, setTab] = useState<MetricsTab>(tabs[0]);
// This is done just incase `loader` is actually something like `() => Promise<void>`