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

Parse HPA metrics from different versions (#6971)

* First sketch of hpav2 metrics

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* HPA metrics initial tests

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Receive target Object metrics for v2

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* External target metrics

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Add more types for ObjectMetricStatus

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Move metrics parsing from HPA object to separate injectable

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Add metric parser for HPA v2

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Using metrics parser in hpa list and details

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Add more test cases for HPA v2

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Add HorizontalPodAutoscalerV1MetricParser

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding injectable for hpa v1 metric parser

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding test cases for autoscaling/v1 metrics

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Add test cases for hpa beta versions

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Check for legacy targetCPUUtilizationPercentage

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fix external metirc parser output

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Small clean up

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Linter fixes

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Removing fallbackApiBases

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Remove left comments

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Making metric parser classes as not injectable

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fix metrics in hpa details for newer versions

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Spreading types to V2 and V2Beta1

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Move getMetricName() to its own file

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
Alex Andreev 2023-01-23 16:22:44 +03:00 committed by GitHub
parent 5eefa8f03a
commit 5f416921e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1564 additions and 167 deletions

View File

@ -3,11 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { BaseKubeObjectCondition, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; import type { OptionVarient } from "../../utils";
import { KubeObject } from "../kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
import type { OptionVarient } from "../../utils"; import type { BaseKubeObjectCondition, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
export enum HpaMetricType { export enum HpaMetricType {
Resource = "Resource", Resource = "Resource",
@ -17,46 +17,131 @@ export enum HpaMetricType {
ContainerResource = "ContainerResource", ContainerResource = "ContainerResource",
} }
export interface MetricCurrentTarget {
current?: string;
target?: string;
}
export interface HorizontalPodAutoscalerMetricTarget { export interface HorizontalPodAutoscalerMetricTarget {
kind: string; kind: string;
name: string; name: string;
apiVersion: string; apiVersion: string;
} }
export interface ContainerResourceMetricSource { export interface V2ContainerResourceMetricSource {
container: string;
name: string;
target?: {
averageUtilization?: number;
averageValue?: string;
type?: string;
};
}
export interface V2Beta1ContainerResourceMetricSource {
container: string; container: string;
name: string; name: string;
targetAverageUtilization?: number; targetAverageUtilization?: number;
targetAverageValue?: string; targetAverageValue?: string;
} }
export interface ExternalMetricSource { export type ContainerResourceMetricSource =
metricName: string; | V2ContainerResourceMetricSource
| V2Beta1ContainerResourceMetricSource;
export interface V2ExternalMetricSource {
metricName?: string;
metricSelector?: LabelSelector;
metric?: {
name?: string;
selector?: LabelSelector;
};
target?: {
type: string;
value?: string;
averageValue?: string;
};
}
export interface V2Beta1ExternalMetricSource {
metricName?: string;
metricSelector?: LabelSelector; metricSelector?: LabelSelector;
targetAverageValue?: string; targetAverageValue?: string;
targetValue?: string; targetValue?: string;
metric?: {
selector?: LabelSelector;
};
} }
export interface ObjectMetricSource { export type ExternalMetricSource =
| V2Beta1ExternalMetricSource
| V2ExternalMetricSource;
export interface V2ObjectMetricSource {
metric?: {
name?: string;
selector?: LabelSelector;
};
target?: {
type?: string;
value?: string;
averageValue?: string;
};
describedObject?: CrossVersionObjectReference;
}
export interface V2Beta1ObjectMetricSource {
averageValue?: string; averageValue?: string;
metricName: string; metricName?: string;
selector?: LabelSelector; selector?: LabelSelector;
target: CrossVersionObjectReference; targetValue?: string;
targetValue: string; describedObject?: CrossVersionObjectReference;
} }
export interface PodsMetricSource { export type ObjectMetricSource =
metricName: string; | V2ObjectMetricSource
selector?: LabelSelector; | V2Beta1ObjectMetricSource;
targetAverageValue: string;
export interface V2PodsMetricSource {
metric?: {
name?: string;
selector?: LabelSelector;
};
target?: {
averageValue?: string;
type?: string;
};
} }
export interface ResourceMetricSource { export interface V2Beta1PodsMetricSource {
metricName?: string;
selector?: LabelSelector;
targetAverageValue?: string;
}
export type PodsMetricSource =
| V2PodsMetricSource
| V2Beta1PodsMetricSource;
export interface V2ResourceMetricSource {
name: string;
target?: {
averageUtilization?: number;
averageValue?: string;
type?: string;
};
}
export interface V2Beta1ResourceMetricSource {
name: string; name: string;
targetAverageUtilization?: number; targetAverageUtilization?: number;
targetAverageValue?: string; targetAverageValue?: string;
} }
export type ResourceMetricSource =
| V2ResourceMetricSource
| V2Beta1ResourceMetricSource;
export interface BaseHorizontalPodAutoscalerMetricSpec { export interface BaseHorizontalPodAutoscalerMetricSpec {
containerResource: ContainerResourceMetricSource; containerResource: ContainerResourceMetricSource;
external: ExternalMetricSource; external: ExternalMetricSource;
@ -93,40 +178,112 @@ interface HPAScalingPolicy {
type HPAScalingPolicyType = string; type HPAScalingPolicyType = string;
export interface ContainerResourceMetricStatus { export interface V2ContainerResourceMetricStatus {
container: string; container?: string;
name: string;
current?: {
averageUtilization?: number;
averageValue?: string;
};
}
export interface V2Beta1ContainerResourceMetricStatus {
container?: string;
currentAverageUtilization?: number; currentAverageUtilization?: number;
currentAverageValue: string; currentAverageValue?: string;
name: string; name: string;
} }
export interface ExternalMetricStatus { export type ContainerResourceMetricStatus =
| V2ContainerResourceMetricStatus
| V2Beta1ContainerResourceMetricStatus;
export interface V2ExternalMetricStatus {
metric?: {
name?: string;
selector?: LabelSelector;
};
current?: {
averageValue?: string;
value?: string;
};
}
export interface V2Beta1ExternalMetricStatus {
currentAverageValue?: string; currentAverageValue?: string;
currentValue: string; currentValue?: string;
metricName: string; metricName?: string;
metricSelector?: LabelSelector; metricSelector?: LabelSelector;
} }
export interface ObjectMetricStatus { export type ExternalMetricStatus =
| V2Beta1ExternalMetricStatus
| V2ExternalMetricStatus;
export interface V2ObjectMetricStatus {
metric?: {
name?: string;
selector?: LabelSelector;
};
current?: {
type?: string;
value?: string;
averageValue?: string;
};
describedObject?: CrossVersionObjectReference;
}
export interface V2Beta1ObjectMetricStatus {
averageValue?: string; averageValue?: string;
currentValue?: string; currentValue?: string;
metricName: string; metricName?: string;
selector?: LabelSelector; selector?: LabelSelector;
target: CrossVersionObjectReference; describedObject?: CrossVersionObjectReference;
} }
export interface PodsMetricStatus { export type ObjectMetricStatus =
currentAverageValue: string; | V2Beta1ObjectMetricStatus
metricName: string; | V2ObjectMetricStatus;
export interface V2PodsMetricStatus {
selector?: LabelSelector;
metric?: {
name?: string;
selector?: LabelSelector;
};
current?: {
averageValue?: string;
};
}
export interface V2Beta1PodsMetricStatus {
currentAverageValue?: string;
metricName?: string;
selector?: LabelSelector; selector?: LabelSelector;
} }
export interface ResourceMetricStatus { export type PodsMetricStatus =
| V2Beta1PodsMetricStatus
| V2PodsMetricStatus;
export interface V2ResourceMetricStatus {
name: string;
current?: {
averageUtilization?: number;
averageValue?: string;
};
}
export interface V2Beta1ResourceMetricStatus {
currentAverageUtilization?: number; currentAverageUtilization?: number;
currentAverageValue: string; currentAverageValue?: string;
name: string; name: string;
} }
export type ResourceMetricStatus =
| V2Beta1ResourceMetricStatus
| V2ResourceMetricStatus;
export interface BaseHorizontalPodAutoscalerMetricStatus { export interface BaseHorizontalPodAutoscalerMetricStatus {
containerResource: ContainerResourceMetricStatus; containerResource: ContainerResourceMetricStatus;
external: ExternalMetricStatus; external: ExternalMetricStatus;
@ -154,6 +311,7 @@ export interface HorizontalPodAutoscalerSpec {
maxReplicas: number; maxReplicas: number;
metrics?: HorizontalPodAutoscalerMetricSpec[]; metrics?: HorizontalPodAutoscalerMetricSpec[];
behavior?: HorizontalPodAutoscalerBehavior; behavior?: HorizontalPodAutoscalerBehavior;
targetCPUUtilizationPercentage?: number; // used only in autoscaling/v1
} }
export interface HorizontalPodAutoscalerStatus { export interface HorizontalPodAutoscalerStatus {
@ -161,11 +319,7 @@ export interface HorizontalPodAutoscalerStatus {
currentReplicas: number; currentReplicas: number;
desiredReplicas: number; desiredReplicas: number;
currentMetrics?: HorizontalPodAutoscalerMetricStatus[]; currentMetrics?: HorizontalPodAutoscalerMetricStatus[];
} currentCPUUtilizationPercentage?: number; // used only in autoscaling/v1
interface MetricCurrentTarget {
current?: string;
target?: string;
} }
export class HorizontalPodAutoscaler extends KubeObject< export class HorizontalPodAutoscaler extends KubeObject<
@ -212,15 +366,6 @@ export class HorizontalPodAutoscaler extends KubeObject<
getCurrentMetrics() { getCurrentMetrics() {
return this.status?.currentMetrics ?? []; return this.status?.currentMetrics ?? [];
} }
getMetricValues(metric: HorizontalPodAutoscalerMetricSpec): string {
const {
current = "unknown",
target = "unknown",
} = getMetricCurrentTarget(metric, this.getCurrentMetrics());
return `${current} / ${target}`;
}
} }
export class HorizontalPodAutoscalerApi extends KubeApi<HorizontalPodAutoscaler> { export class HorizontalPodAutoscalerApi extends KubeApi<HorizontalPodAutoscaler> {
@ -229,114 +374,6 @@ export class HorizontalPodAutoscalerApi extends KubeApi<HorizontalPodAutoscaler>
...opts ?? {}, ...opts ?? {},
objectConstructor: HorizontalPodAutoscaler, objectConstructor: HorizontalPodAutoscaler,
checkPreferredVersion: true, checkPreferredVersion: true,
// Kubernetes < 1.26
fallbackApiBases: [
"/apis/autoscaling/v2beta2/horizontalpodautoscalers",
"/apis/autoscaling/v2beta1/horizontalpodautoscalers",
"/apis/autoscaling/v1/horizontalpodautoscalers",
],
}); });
} }
} }
function getMetricName(metric: HorizontalPodAutoscalerMetricSpec | HorizontalPodAutoscalerMetricStatus): string | undefined {
switch (metric.type) {
case HpaMetricType.Resource:
return metric.resource.name;
case HpaMetricType.Pods:
return metric.pods.metricName;
case HpaMetricType.Object:
return metric.object.metricName;
case HpaMetricType.External:
return metric.external.metricName;
case HpaMetricType.ContainerResource:
return metric.containerResource.name;
default:
return undefined;
}
}
function getResourceMetricValue(currentMetric: ResourceMetricStatus | undefined, targetMetric: ResourceMetricSource): MetricCurrentTarget {
return {
current: (
typeof currentMetric?.currentAverageUtilization === "number"
? `${currentMetric.currentAverageUtilization}%`
: currentMetric?.currentAverageValue
),
target: (
typeof targetMetric?.targetAverageUtilization === "number"
? `${targetMetric.targetAverageUtilization}%`
: targetMetric?.targetAverageValue
),
};
}
function getPodsMetricValue(currentMetric: PodsMetricStatus | undefined, targetMetric: PodsMetricSource): MetricCurrentTarget {
return {
current: currentMetric?.currentAverageValue,
target: targetMetric?.targetAverageValue,
};
}
function getObjectMetricValue(currentMetric: ObjectMetricStatus | undefined, targetMetric: ObjectMetricSource): MetricCurrentTarget {
return {
current: (
currentMetric?.currentValue
?? currentMetric?.averageValue
),
target: (
targetMetric?.targetValue
?? targetMetric?.averageValue
),
};
}
function getExternalMetricValue(currentMetric: ExternalMetricStatus | undefined, targetMetric: ExternalMetricSource): MetricCurrentTarget {
return {
current: (
currentMetric?.currentValue
?? currentMetric?.currentAverageValue
),
target: (
targetMetric?.targetValue
?? targetMetric?.targetAverageValue
),
};
}
function getContainerResourceMetricValue(currentMetric: ContainerResourceMetricStatus | undefined, targetMetric: ContainerResourceMetricSource): MetricCurrentTarget {
return {
current: (
typeof currentMetric?.currentAverageUtilization === "number"
? `${currentMetric.currentAverageUtilization}%`
: currentMetric?.currentAverageValue
),
target: (
typeof targetMetric?.targetAverageUtilization === "number"
? `${targetMetric.targetAverageUtilization}%`
: targetMetric?.targetAverageValue
),
};
}
function getMetricCurrentTarget(spec: HorizontalPodAutoscalerMetricSpec, status: HorizontalPodAutoscalerMetricStatus[]): MetricCurrentTarget {
const currentMetric = status.find(m => (
m.type === spec.type
&& getMetricName(m) === getMetricName(spec)
));
switch (spec.type) {
case HpaMetricType.Resource:
return getResourceMetricValue(currentMetric?.resource, spec.resource);
case HpaMetricType.Pods:
return getPodsMetricValue(currentMetric?.pods, spec.pods);
case HpaMetricType.Object:
return getObjectMetricValue(currentMetric?.object, spec.object);
case HpaMetricType.External:
return getExternalMetricValue(currentMetric?.external, spec.external);
case HpaMetricType.ContainerResource:
return getContainerResourceMetricValue(currentMetric?.containerResource, spec.containerResource);
default:
return {};
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { HpaMetricType } from "../../../common/k8s-api/endpoints";
import type { LabelSelector } from "../../../common/k8s-api/kube-object";
type MetricNames = Partial<Record<"resource" | "pods" | "object" | "external" | "containerResource", {
name?: string;
metricName?: string;
metric?: {
name?: string;
selector?: LabelSelector;
};
}>>;
interface Metric extends MetricNames {
type: HpaMetricType;
}
export function getMetricName(metric: Metric): string | undefined {
switch (metric.type) {
case HpaMetricType.Resource:
return metric.resource?.name;
case HpaMetricType.Pods:
return metric.pods?.metricName || metric.pods?.metric?.name;
case HpaMetricType.Object:
return metric.object?.metricName || metric.object?.metric?.name;
case HpaMetricType.External:
return metric.external?.metricName || metric.external?.metric?.name;
case HpaMetricType.ContainerResource:
return metric.containerResource?.name;
default:
return undefined;
}
}

View File

@ -0,0 +1,66 @@
/**
* 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 { HorizontalPodAutoscaler, HorizontalPodAutoscalerMetricSpec, HorizontalPodAutoscalerMetricStatus } from "../../../common/k8s-api/endpoints";
import { HpaMetricType } from "../../../common/k8s-api/endpoints";
import { getMetricName } from "./get-hpa-metric-name";
import { HorizontalPodAutoscalerV1MetricParser } from "./hpa-v1-metric-parser";
import { HorizontalPodAutoscalerV2MetricParser } from "./hpa-v2-metric-parser";
type Parser = HorizontalPodAutoscalerV1MetricParser | HorizontalPodAutoscalerV2MetricParser;
const getHorizontalPodAutoscalerMetrics = getInjectable({
id: "get-horizontal-pod-autoscaler-metrics",
instantiate: () => (hpa: HorizontalPodAutoscaler) => {
const hpaV1Parser = new HorizontalPodAutoscalerV1MetricParser();
const hpaV2Parser = new HorizontalPodAutoscalerV2MetricParser();
const metrics = hpa.spec?.metrics ?? [];
const currentMetrics = hpa.status?.currentMetrics ?? [];
const cpuUtilization = hpa.spec?.targetCPUUtilizationPercentage;
if (cpuUtilization) {
const utilizationCurrent = hpa.status?.currentCPUUtilizationPercentage ? `${hpa.status.currentCPUUtilizationPercentage}%` : "unknown";
const utilizationTarget = cpuUtilization ? `${cpuUtilization}%` : "unknown";
return [`${utilizationCurrent} / ${utilizationTarget}`];
}
return metrics.map((metric) => {
const currentMetric = currentMetrics.find(current =>
current.type === metric.type
&& getMetricName(current) === getMetricName(metric),
);
const h2Values = getMetricValues<HorizontalPodAutoscalerV2MetricParser>(hpaV2Parser, currentMetric, metric);
const h1Values = getMetricValues<HorizontalPodAutoscalerV1MetricParser>(hpaV1Parser, currentMetric, metric);
let values = h1Values;
if (h2Values.current || h2Values.target) {
values = h2Values;
}
return `${values.current ?? "unknown"} / ${values.target ?? "unknown"}`;
});
},
});
function getMetricValues<Type extends Parser>(parser: Type, current: HorizontalPodAutoscalerMetricStatus | undefined, target: HorizontalPodAutoscalerMetricSpec) {
switch (target.type) {
case HpaMetricType.Resource:
return parser.getResource({ current: current?.resource, target: target.resource });
case HpaMetricType.Pods:
return parser.getPods({ current: current?.pods, target: target.pods });
case HpaMetricType.Object:
return parser.getObject({ current: current?.object, target: target.object });
case HpaMetricType.External:
return parser.getExternal({ current: current?.external, target: target.external });
case HpaMetricType.ContainerResource:
return parser.getContainerResource({ current: current?.containerResource, target: target.containerResource });
default:
return {};
}
}
export default getHorizontalPodAutoscalerMetrics;

View File

@ -11,9 +11,19 @@
} }
.metrics .Table { .metrics .Table {
margin: 0 (-$margin * 3);
.TableCell { .TableCell {
word-break: break-word; word-break: break-word;
&:first-child {
margin-left: $margin * 2;
}
&:last-child {
margin-right: $margin * 2;
}
&.name { &.name {
flex-grow: 2; flex-grow: 2;
} }

View File

@ -22,6 +22,8 @@ import { withInjectables } from "@ogre-tools/injectable-react";
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable"; import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable"; import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable";
import loggerInjectable from "../../../common/logger.injectable"; import loggerInjectable from "../../../common/logger.injectable";
import getHorizontalPodAutoscalerMetrics from "./get-hpa-metrics.injectable";
import { getMetricName } from "./get-hpa-metric-name";
export interface HpaDetailsProps extends KubeObjectDetailsProps<HorizontalPodAutoscaler> { export interface HpaDetailsProps extends KubeObjectDetailsProps<HorizontalPodAutoscaler> {
} }
@ -30,6 +32,7 @@ interface Dependencies {
apiManager: ApiManager; apiManager: ApiManager;
logger: Logger; logger: Logger;
getDetailsUrl: GetDetailsUrl; getDetailsUrl: GetDetailsUrl;
getMetrics: (hpa: HorizontalPodAutoscaler) => string[];
} }
@observer @observer
@ -57,47 +60,46 @@ class NonInjectedHpaDetails extends React.Component<HpaDetailsProps & Dependenci
const { object: hpa } = this.props; const { object: hpa } = this.props;
const renderName = (metric: HorizontalPodAutoscalerMetricSpec) => { const renderName = (metric: HorizontalPodAutoscalerMetricSpec) => {
const metricName = getMetricName(metric);
switch (metric.type) { switch (metric.type) {
case HpaMetricType.ContainerResource: case HpaMetricType.ContainerResource:
// fallthrough // fallthrough
case HpaMetricType.Resource: { case HpaMetricType.Resource: {
const metricSpec = metric.resource ?? metric.containerResource; const metricSpec = metric.resource ?? metric.containerResource;
const addition = metricSpec.targetAverageUtilization
? " (as a percentage of request)"
: "";
return `Resource ${metricSpec.name} on Pods${addition}`; return `Resource ${metricSpec.name} on Pods`;
} }
case HpaMetricType.Pods: case HpaMetricType.Pods:
return `${metric.pods.metricName} on Pods`; return `${metricName} on Pods`;
case HpaMetricType.Object: { case HpaMetricType.Object: {
return ( return (
<> <>
{metric.object.metricName} {metricName}
{" "} {" "}
{this.renderTargetLink(metric.object.target)} {this.renderTargetLink(metric.object?.describedObject)}
</> </>
); );
} }
case HpaMetricType.External: case HpaMetricType.External:
return `${metric.external.metricName} on ${JSON.stringify(metric.external.metricSelector)}`; return `${metricName} on ${JSON.stringify(metric.external.metricSelector ?? metric.external.metric?.selector)}`;
} }
}; };
return ( return (
<Table> <Table>
<TableHead> <TableHead flat>
<TableCell className="name">Name</TableCell> <TableCell className="name">Name</TableCell>
<TableCell className="metrics">Current / Target</TableCell> <TableCell className="metrics">Current / Target</TableCell>
</TableHead> </TableHead>
{ {
hpa.getMetrics() this.props.getMetrics(hpa)
.map((metric, index) => ( .map((metrics, index) => (
<TableRow key={index}> <TableRow key={index}>
<TableCell className="name">{renderName(metric)}</TableCell> <TableCell className="name">{renderName(hpa.getMetrics()[index])}</TableCell>
<TableCell className="metrics">{hpa.getMetricValues(metric)}</TableCell> <TableCell className="metrics">{metrics}</TableCell>
</TableRow> </TableRow>
)) ))
} }
@ -175,5 +177,6 @@ export const HpaDetails = withInjectables<Dependencies, HpaDetailsProps>(NonInje
apiManager: di.inject(apiManagerInjectable), apiManager: di.inject(apiManagerInjectable),
getDetailsUrl: di.inject(getDetailsUrlInjectable), getDetailsUrl: di.inject(getDetailsUrlInjectable),
logger: di.inject(loggerInjectable), logger: di.inject(loggerInjectable),
getMetrics: di.inject(getHorizontalPodAutoscalerMetrics),
}), }),
}); });

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { MetricCurrentTarget, V2Beta1ContainerResourceMetricSource, V2Beta1ContainerResourceMetricStatus, V2Beta1ExternalMetricSource, V2Beta1ExternalMetricStatus, V2Beta1ObjectMetricSource, V2Beta1ObjectMetricStatus, V2Beta1PodsMetricSource, V2Beta1PodsMetricStatus, V2Beta1ResourceMetricSource, V2Beta1ResourceMetricStatus } from "../../../common/k8s-api/endpoints";
export class HorizontalPodAutoscalerV1MetricParser {
public getResource({ current, target }: { current: V2Beta1ResourceMetricStatus | undefined; target: V2Beta1ResourceMetricSource }): MetricCurrentTarget {
return {
current: (
typeof current?.currentAverageUtilization === "number"
? `${current.currentAverageUtilization}%`
: current?.currentAverageValue
),
target: (
typeof target?.targetAverageUtilization === "number"
? `${target.targetAverageUtilization}%`
: target?.targetAverageValue
),
};
}
public getPods({ current, target }: { current: V2Beta1PodsMetricStatus | undefined; target: V2Beta1PodsMetricSource }): MetricCurrentTarget {
return {
current: current?.currentAverageValue,
target: target?.targetAverageValue,
};
}
public getObject({ current, target }: { current: V2Beta1ObjectMetricStatus | undefined; target: V2Beta1ObjectMetricSource }): MetricCurrentTarget {
return {
current: (
current?.currentValue
?? current?.averageValue
),
target: (
target?.targetValue
?? target?.averageValue
),
};
}
public getExternal({ current, target }: { current: V2Beta1ExternalMetricStatus | undefined; target: V2Beta1ExternalMetricSource }): MetricCurrentTarget {
return {
current: (
current?.currentValue
?? current?.currentAverageValue
),
target: (
target?.targetValue
?? target?.targetAverageValue
),
};
}
public getContainerResource({ current, target }: { current: V2Beta1ContainerResourceMetricStatus | undefined; target: V2Beta1ContainerResourceMetricSource }): MetricCurrentTarget {
return {
current: (
typeof current?.currentAverageUtilization === "number"
? `${current.currentAverageUtilization}%`
: current?.currentAverageValue
),
target: (
typeof target?.targetAverageUtilization === "number"
? `${target.targetAverageUtilization}%`
: target?.targetAverageValue
),
};
}
}

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { MetricCurrentTarget, V2ContainerResourceMetricSource, V2ContainerResourceMetricStatus, V2ExternalMetricSource, V2ExternalMetricStatus, V2ObjectMetricSource, V2ObjectMetricStatus, V2PodsMetricSource, V2PodsMetricStatus, V2ResourceMetricSource, V2ResourceMetricStatus } from "../../../common/k8s-api/endpoints";
export class HorizontalPodAutoscalerV2MetricParser {
public getResource({ current, target }: { current: V2ResourceMetricStatus | undefined; target: V2ResourceMetricSource }): MetricCurrentTarget {
return {
current: (
typeof current?.current?.averageUtilization === "number"
? `${current.current?.averageUtilization}%`
: current?.current?.averageValue
),
target: typeof target?.target?.averageUtilization === "number"
? `${target.target.averageUtilization}%`
: target?.target?.averageValue,
};
}
public getPods({ current, target }: { current: V2PodsMetricStatus | undefined; target: V2PodsMetricSource }): MetricCurrentTarget {
return {
current: current?.current?.averageValue,
target: target?.target?.averageValue,
};
}
public getObject({ current, target }: { current: V2ObjectMetricStatus | undefined; target: V2ObjectMetricSource }): MetricCurrentTarget {
return {
current: (
current?.current?.value
?? current?.current?.averageValue
),
target: (
target?.target?.value
?? target?.target?.averageValue
),
};
}
public getExternal({ current, target }: { current: V2ExternalMetricStatus | undefined; target: V2ExternalMetricSource }): MetricCurrentTarget {
const currentAverage = current?.current?.averageValue ? `${current?.current?.averageValue} (avg)` : undefined;
const targetAverage = target?.target?.averageValue ? `${target?.target?.averageValue} (avg)` : undefined;
return {
current: (
current?.current?.value
?? currentAverage
),
target: (
target?.target?.value
?? targetAverage
),
};
}
public getContainerResource({ current, target }: { current: V2ContainerResourceMetricStatus | undefined; target: V2ContainerResourceMetricSource }): MetricCurrentTarget {
return {
current: (
current?.current?.averageValue
?? current?.current?.averageUtilization ? `${current?.current?.averageUtilization}%` : undefined
),
target: (
target?.target?.averageValue
?? target?.target?.averageUtilization ? `${target?.target?.averageUtilization}%` : undefined
),
};
}
}

View File

@ -17,6 +17,7 @@ import { KubeObjectAge } from "../kube-object/age";
import type { HorizontalPodAutoscalerStore } from "./store"; import type { HorizontalPodAutoscalerStore } from "./store";
import { withInjectables } from "@ogre-tools/injectable-react"; import { withInjectables } from "@ogre-tools/injectable-react";
import horizontalPodAutoscalerStoreInjectable from "./store.injectable"; import horizontalPodAutoscalerStoreInjectable from "./store.injectable";
import getHorizontalPodAutoscalerMetrics from "./get-hpa-metrics.injectable";
import { NamespaceSelectBadge } from "../+namespaces/namespace-select-badge"; import { NamespaceSelectBadge } from "../+namespaces/namespace-select-badge";
enum columnId { enum columnId {
@ -32,6 +33,7 @@ enum columnId {
interface Dependencies { interface Dependencies {
horizontalPodAutoscalerStore: HorizontalPodAutoscalerStore; horizontalPodAutoscalerStore: HorizontalPodAutoscalerStore;
getMetrics: (hpa: HorizontalPodAutoscaler) => string[];
} }
@observer @observer
@ -39,7 +41,7 @@ class NonInjectedHorizontalPodAutoscalers extends React.Component<Dependencies>
getTargets(hpa: HorizontalPodAutoscaler) { getTargets(hpa: HorizontalPodAutoscaler) {
const metrics = hpa.getMetrics(); const metrics = hpa.getMetrics();
if (metrics.length === 0) { if (metrics.length === 0 && !hpa.spec?.targetCPUUtilizationPercentage) {
return <p>--</p>; return <p>--</p>;
} }
@ -47,7 +49,7 @@ class NonInjectedHorizontalPodAutoscalers extends React.Component<Dependencies>
return ( return (
<p> <p>
{hpa.getMetricValues(metrics[0])} {this.props.getMetrics(hpa)[0]}
{" "} {" "}
{metricsRemain} {metricsRemain}
</p> </p>
@ -120,5 +122,6 @@ export const HorizontalPodAutoscalers = withInjectables<Dependencies>(NonInjecte
getProps: (di, props) => ({ getProps: (di, props) => ({
...props, ...props,
horizontalPodAutoscalerStore: di.inject(horizontalPodAutoscalerStoreInjectable), horizontalPodAutoscalerStore: di.inject(horizontalPodAutoscalerStoreInjectable),
getMetrics: di.inject(getHorizontalPodAutoscalerMetrics),
}), }),
}); });

View File

@ -27,7 +27,7 @@ const horizontalPodAutoscalerDetailItemInjectable = getInjectable({
export const isHorizontalPodAutoscaler = kubeObjectMatchesToKindAndApiVersion( export const isHorizontalPodAutoscaler = kubeObjectMatchesToKindAndApiVersion(
"HorizontalPodAutoscaler", "HorizontalPodAutoscaler",
["autoscaling/v2beta1"], ["autoscaling/v2", "autoscaling/v2beta2", "autoscaling/v2beta1", "autoscaling/v1"],
); );
export default horizontalPodAutoscalerDetailItemInjectable; export default horizontalPodAutoscalerDetailItemInjectable;