Skip to content

Commit

Permalink
Refactor service resource limits (#4363)
Browse files Browse the repository at this point in the history
  • Loading branch information
Feroze Mohideen authored Mar 4, 2024
1 parent d8ed126 commit 5f0f86a
Show file tree
Hide file tree
Showing 25 changed files with 808 additions and 701 deletions.
240 changes: 240 additions & 0 deletions dashboard/src/lib/clusters/constants.ts

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion dashboard/src/lib/clusters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ export type ClientMachineType = {
displayName: string;
supportedRegions: Array<AWSRegion | GCPRegion | AzureRegion>;
isGPU: boolean;
cpuCores: number;
ramMegabytes: number;
};
type PreflightCheckResolutionStep = {
text: string;
Expand All @@ -261,7 +263,7 @@ export const nodeValidator = z.object({
});
export type ClientNode = {
nodeGroupType: NodeGroupType;
instanceType: string;
instanceType: ClientMachineType;
};

// Cluster
Expand All @@ -272,6 +274,7 @@ export const clusterValidator = z.object({
cloud_provider: cloudProviderValidator,
cloud_provider_credential_identifier: z.string(),
status: z.string(),
ingress_ip: z.string().optional().default(""),
});
export type SerializedCluster = z.infer<typeof clusterValidator>;
export type ClientCluster = Omit<SerializedCluster, "cloud_provider"> & {
Expand Down
19 changes: 17 additions & 2 deletions dashboard/src/lib/hooks/useCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ export const useClusterNodeList = ({
);

const parsed = await z.array(nodeValidator).parseAsync(res.data);
return parsed
const nodes = parsed
.map((n) => {
const nodeGroupType = match(n.labels["porter.run/workload-kind"])
.with("application", () => "APPLICATION" as const)
Expand All @@ -508,7 +508,21 @@ export const useClusterNodeList = ({
if (nodeGroupType === "UNKNOWN") {
return undefined;
}
const instanceType = n.labels["node.kubernetes.io/instance-type"];
const instanceTypeName = n.labels["node.kubernetes.io/instance-type"];
if (!instanceTypeName) {
return undefined;
}
// TODO: use more node information to narrow down which cloud provider instance type list to check against
const instanceType =
CloudProviderAWS.machineTypes.find(
(i) => i.name === instanceTypeName
) ??
CloudProviderAzure.machineTypes.find(
(i) => i.name === instanceTypeName
) ??
CloudProviderGCP.machineTypes.find(
(i) => i.name === instanceTypeName
);
if (!instanceType) {
return undefined;
}
Expand All @@ -518,6 +532,7 @@ export const useClusterNodeList = ({
};
})
.filter(valueExists);
return nodes;
},
{
refetchInterval,
Expand Down
22 changes: 16 additions & 6 deletions dashboard/src/lib/hooks/usePorterYaml.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { useCallback, useContext, useEffect, useState } from "react";
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
import { PorterApp } from "@porter-dev/api-contracts";
import { useQuery } from "@tanstack/react-query";
import { z } from "zod";

import { useClusterContext } from "main/home/infrastructure-dashboard/ClusterContextProvider";
import { serviceOverrides, type SourceOptions } from "lib/porter-apps";
import { type DetectedServices } from "lib/porter-apps/services";
import {
getServiceResourceAllowances,
type DetectedServices,
} from "lib/porter-apps/services";

import api from "shared/api";
import { useClusterResources } from "shared/ClusterResourcesContext";
import { Context } from "shared/Context";

type PorterYamlStatus =
Expand Down Expand Up @@ -41,11 +44,18 @@ export const usePorterYaml = ({
useDefaults?: boolean;
}): PorterYamlStatus => {
const { currentProject, currentCluster } = useContext(Context);
const { currentClusterResources } = useClusterResources();
const [detectedServices, setDetectedServices] =
useState<DetectedServices | null>(null);
const [detectedName, setDetectedName] = useState<string | null>(null);
const [porterYamlFound, setPorterYamlFound] = useState(false);
const { nodes } = useClusterContext();
const { newServiceDefaultCpuCores, newServiceDefaultRamMegabytes } =
useMemo(() => {
return getServiceResourceAllowances(
nodes,
currentProject?.sandbox_enabled
);
}, [nodes]);

const { data, status } = useQuery(
[
Expand Down Expand Up @@ -135,8 +145,8 @@ export const usePorterYaml = ({
const { services, predeploy, build } = serviceOverrides({
overrides: proto,
useDefaults,
defaultCPU: currentClusterResources.defaultCPU,
defaultRAM: currentClusterResources.defaultRAM,
defaultCPU: newServiceDefaultCpuCores,
defaultRAM: newServiceDefaultRamMegabytes,
});

if (services.length || predeploy || build) {
Expand Down
69 changes: 69 additions & 0 deletions dashboard/src/lib/porter-apps/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import _ from "lodash";
import { match } from "ts-pattern";
import { z } from "zod";

import { type ClientNode } from "lib/clusters/types";

import { type BuildOptions } from "./build";
import {
autoscalingValidator,
Expand Down Expand Up @@ -719,3 +721,70 @@ export function serializedServiceFromProto({
)
.exhaustive();
}

const SMALL_INSTANCE_UPPER_BOUND = 0.75;
const LARGE_INSTANCE_UPPER_BOUND = 0.9;
const NEW_SERVICE_RESOURCE_DEFAULT_MULTIPLIER = 0.125;

const DEFAULT_RESOURCE_ALLOWANCES = {
maxCpuCores: 1.5,
newServiceDefaultCpuCores: 0.19,
maxRamMegabytes: 3100,
newServiceDefaultRamMegabytes: 400,
};

const DEFAULT_SANDBOX_RESOURCE_ALLOWANCES = {
maxCpuCores: 0.2,
newServiceDefaultCpuCores: 0.1,
maxRamMegabytes: 250,
newServiceDefaultRamMegabytes: 120,
};

export function getServiceResourceAllowances(
nodes: ClientNode[],
isSandboxEnabled?: boolean
): {
maxCpuCores: number;
maxRamMegabytes: number;
newServiceDefaultCpuCores: number;
newServiceDefaultRamMegabytes: number;
} {
if (isSandboxEnabled) {
return DEFAULT_SANDBOX_RESOURCE_ALLOWANCES;
}

if (nodes.length === 0) {
return DEFAULT_RESOURCE_ALLOWANCES;
}
const maxRamApplicationInstance = nodes
.filter((n) => n.nodeGroupType === "APPLICATION")
.reduce((max, node) =>
node.instanceType.ramMegabytes > max.instanceType.ramMegabytes
? node
: max
);
const multiplier =
maxRamApplicationInstance.instanceType.ramMegabytes > 16000
? LARGE_INSTANCE_UPPER_BOUND
: SMALL_INSTANCE_UPPER_BOUND;

const maxCpuCores =
Math.floor(
maxRamApplicationInstance.instanceType.cpuCores * multiplier * 2
) / 2; // round to nearest half
const maxRamMegabytes =
Math.round(
(maxRamApplicationInstance.instanceType.ramMegabytes * multiplier) / 100
) * 100; // round to nearest 100 MB
return {
maxCpuCores,
newServiceDefaultCpuCores: Number(
(maxCpuCores * NEW_SERVICE_RESOURCE_DEFAULT_MULTIPLIER).toFixed(2)
), // round to hundredths place
maxRamMegabytes,
newServiceDefaultRamMegabytes:
Math.round(
(maxRamMegabytes * NEW_SERVICE_RESOURCE_DEFAULT_MULTIPLIER) / 100
) * 100, // round to nearest 100 MB
};
}
Loading

0 comments on commit 5f0f86a

Please sign in to comment.