Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POR-1794] Smart opt v2 #3679

Merged
merged 35 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
55604a2
Registry hotfix
sdess09 Sep 21, 2023
df1f9a6
Registry hotfix
sdess09 Sep 21, 2023
08e396b
~Merge branch 'master' of github.com:porter-dev/porter
sdess09 Sep 22, 2023
48acb50
Merge branch 'master' of github.com:porter-dev/porter
sdess09 Sep 22, 2023
86b0778
Merge branch 'master' of github.com:porter-dev/porter
sdess09 Sep 25, 2023
8c83aee
Merge branch 'master' of github.com:porter-dev/porter
sdess09 Sep 25, 2023
676d588
Merge branch 'master' of github.com:porter-dev/porter
sdess09 Sep 26, 2023
e4d724c
Merge branch 'master' of github.com:porter-dev/porter
sdess09 Sep 27, 2023
56dfc06
Merge branch 'master' of github.com:porter-dev/porter
sdess09 Sep 27, 2023
b51fa8f
Merge branch 'master' of github.com:porter-dev/porter
sdess09 Sep 27, 2023
50269b8
Merge branch 'master' of github.com:porter-dev/porter
sdess09 Sep 27, 2023
edd8412
Fix Routing
sdess09 Sep 28, 2023
bbe70e2
Merge branch 'master' of github.com:porter-dev/porter
sdess09 Sep 28, 2023
ca34235
Merge branch 'master' of github.com:porter-dev/porter
sdess09 Sep 28, 2023
2bb7ec5
merge
sdess09 Sep 28, 2023
11aa085
smart-opt
sdess09 Sep 28, 2023
9f39d2d
Merge branch 'master' into smart-opt-v2
sdess09 Sep 28, 2023
05df4dd
Merge branch 'master' of github.com:porter-dev/porter into smart-opt-v2
Sep 29, 2023
bba62d3
separate logic
Oct 2, 2023
8689e60
add back set value
Oct 2, 2023
966481a
fixes
Oct 2, 2023
ed53628
Merge branch 'master' of github.com:porter-dev/porter into smart-opt-v2
Oct 2, 2023
8f3ef93
final touches
Oct 2, 2023
6aadc30
merge conflicts
Oct 2, 2023
1dc1fb6
change modal copy
Oct 3, 2023
1ba8ef9
Merge branch 'master' of github.com:porter-dev/porter into smart-opt-v2
Oct 3, 2023
6f46a8f
wip
Oct 3, 2023
1e6eddc
Merge branch 'master' of github.com:porter-dev/porter into smart-opt-v2
Oct 3, 2023
195c9bc
Merge branch 'master' of github.com:porter-dev/porter into smart-opt-v2
Oct 3, 2023
26aaace
cleanups
Oct 3, 2023
40ded00
remove package-lock.json diff
Oct 3, 2023
3ad5a4b
checkout master package.json
Oct 3, 2023
9227d30
revert old changes
Oct 3, 2023
b435aef
revert old changes
Oct 3, 2023
1464528
Merge branch 'master' into smart-opt-v2
Oct 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions dashboard/src/lib/hooks/useClusterResourceLimits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { AWS_INSTANCE_LIMITS } from "main/home/app-dashboard/validate-apply/services-settings/tabs/utils";
import { useEffect, useState } from "react";
import convert from "convert";
import { useQuery } from "@tanstack/react-query";
import { z } from "zod";
import api from "shared/api";

const clusterDataValidator = z.object({
labels: z.object({
"beta.kubernetes.io/instance-type": z.string().nullish(),
}).optional(),
}).transform((data) => {
const defaultResources = {
maxCPU: AWS_INSTANCE_LIMITS["t3"]["medium"]["vCPU"],
maxRAM: AWS_INSTANCE_LIMITS["t3"]["medium"]["RAM"],
};
if (!data.labels) {
return defaultResources;
}
const instanceType = data.labels["beta.kubernetes.io/instance-type"];
const res = z.tuple([z.string(), z.string()]).safeParse(instanceType)
if (!res.success) {
return defaultResources;
}
const [instanceClass, instanceSize] = res.data;
if (AWS_INSTANCE_LIMITS[instanceClass] && AWS_INSTANCE_LIMITS[instanceClass][instanceSize]) {
const { vCPU, RAM } = AWS_INSTANCE_LIMITS[instanceClass][instanceSize];
return {
maxCPU: vCPU,
maxRAM: RAM,
};
}
return defaultResources;
});
export const useClusterResourceLimits = (
{
projectId,
clusterId,
}: {
projectId: number | undefined,
clusterId: number | undefined,
}
) => {
const UPPER_BOUND = 0.75;

const [maxCPU, setMaxCPU] = useState(
AWS_INSTANCE_LIMITS["t3"]["medium"]["vCPU"] * UPPER_BOUND
); //default is set to a t3 medium
const [maxRAM, setMaxRAM] = useState(
// round to nearest 100
Math.round(
convert(AWS_INSTANCE_LIMITS["t3"]["medium"]["RAM"], "GiB").to("MB") *
UPPER_BOUND / 100
) * 100
); //default is set to a t3 medium

const { data } = useQuery(
["getClusterNodes", projectId, clusterId],
async () => {
if (!projectId || !clusterId) {
return Promise.resolve([]);
}

const res = await api.getClusterNodes(
"<token>",
{},
{
project_id: projectId,
cluster_id: clusterId,
}
)

return await z.array(clusterDataValidator).parseAsync(res.data);
},
{
enabled: !!projectId && !!clusterId,
refetchOnWindowFocus: false,
}
);

useEffect(() => {
if (data) {
// this logic handles CPU and RAM independently - we might want to change this later
const maxCPU = data.reduce((acc, curr) => {
return Math.max(acc, curr.maxCPU);
}, 0);
const maxRAM = data.reduce((acc, curr) => {
return Math.max(acc, curr.maxRAM);
}, 0);
// if the instance type has more than 4 GB ram, we use 90% of the ram/cpu
// otherwise, we use 75%
if (maxRAM > 4) {
// round down to nearest 0.5 cores
setMaxCPU(Math.floor(maxCPU * 0.9 * 2) / 2);
setMaxRAM(
Math.round(
convert(maxRAM, "GiB").to("MB") * 0.9 / 100
) * 100
);
} else {
setMaxCPU(Math.floor(maxCPU * UPPER_BOUND * 2) / 2);
setMaxRAM(
Math.round(
convert(maxRAM, "GiB").to("MB") * UPPER_BOUND / 100
) * 100
);
}
}
}, [data])


return {
maxCPU,
maxRAM
}
}

// this function returns the fraction which the resource sliders 'snap' to when the user turns on smart optimization
export const lowestClosestResourceMultipler = (min: number, max: number, value: number): number => {
const fractions = [0.5, 0.25, 0.125];

for (const fraction of fractions) {
const newValue = fraction * (max - min) + min;
if (newValue <= value) {
return fraction;
}
}

return 0.125; // Return 0 if no fraction rounds down
}

// this function is used to snap both resource sliders in unison when one is changed
export const closestMultiplier = (min: number, max: number, value: number): number => {
const fractions = [0.5, 0.25, 0.125];
let closestFraction = 0.125;
for (const fraction of fractions) {
const newValue = fraction * (max - min) + min;
if (Math.abs(newValue - value) < Math.abs(closestFraction * (max - min) + min - value)) {
closestFraction = fraction;
}
}

return closestFraction;
}
20 changes: 11 additions & 9 deletions dashboard/src/lib/porter-apps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,9 @@ export function clientAppToProto(data: PorterAppFormData): PorterApp {
const { app, source } = data;

const services = app.services.reduce((acc: Record<string, Service>, svc) => {
acc[svc.name.value] = serviceProto(serializeService(svc));
const serialized = serializeService(svc)
const proto = serviceProto(serialized)
acc[svc.name.value] = proto
return acc;
}, {});

Expand Down Expand Up @@ -363,15 +365,15 @@ export function clientAppFromProto({
const predeployOverrides = serializeService(overrides.predeploy);
const predeploy = proto.predeploy
? [
deserializeService({
service: serializedServiceFromProto({
name: "pre-deploy",
service: proto.predeploy,
isPredeploy: true,
}),
override: predeployOverrides,
deserializeService({
service: serializedServiceFromProto({
name: "pre-deploy",
service: proto.predeploy,
isPredeploy: true,
}),
]
override: predeployOverrides,
}),
]
: undefined;

return {
Expand Down
108 changes: 58 additions & 50 deletions dashboard/src/lib/porter-apps/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const serviceValidator = z.object({
port: serviceNumberValidator,
cpuCores: serviceNumberValidator,
ramMegabytes: serviceNumberValidator,
smartOptimization: serviceBooleanValidator.optional(),
config: z.discriminatedUnion("type", [
webConfigValidator,
workerConfigValidator,
Expand All @@ -95,30 +96,31 @@ export type SerializedService = {
port: number;
cpuCores: number;
ramMegabytes: number;
smartOptimization?: boolean;
config:
| {
type: "web";
domains: {
name: string;
}[];
autoscaling?: SerializedAutoscaling;
healthCheck?: SerializedHealthcheck;
private?: boolean;
}
| {
type: "worker";
autoscaling?: SerializedAutoscaling;
}
| {
type: "job";
allowConcurrent?: boolean;
cron: string;
suspendCron?: boolean;
timeoutSeconds: number;
}
| {
type: "predeploy";
};
| {
type: "web";
domains: {
name: string;
}[];
autoscaling?: SerializedAutoscaling;
healthCheck?: SerializedHealthcheck;
private?: boolean;
}
| {
type: "worker";
autoscaling?: SerializedAutoscaling;
}
| {
type: "job";
allowConcurrent?: boolean;
cron: string;
suspendCron?: boolean;
timeoutSeconds: number;
}
| {
type: "predeploy";
};
};

export function isPredeployService(service: SerializedService | ClientService) {
Expand Down Expand Up @@ -146,6 +148,7 @@ export function defaultSerialized({
port: 3000,
cpuCores: 0.1,
ramMegabytes: 256,
smartOptimization: true,
};

const defaultAutoscaling: SerializedAutoscaling = {
Expand Down Expand Up @@ -211,6 +214,7 @@ export function serializeService(service: ClientService): SerializedService {
port: service.port.value,
cpuCores: service.cpuCores.value,
ramMegabytes: service.ramMegabytes.value,
smartOptimization: service.smartOptimization?.value,
config: {
type: "web" as const,
autoscaling: serializeAutoscaling({
Expand All @@ -232,6 +236,7 @@ export function serializeService(service: ClientService): SerializedService {
port: service.port.value,
cpuCores: service.cpuCores.value,
ramMegabytes: service.ramMegabytes.value,
smartOptimization: service.smartOptimization?.value,
config: {
type: "worker" as const,
autoscaling: serializeAutoscaling({
Expand All @@ -248,6 +253,7 @@ export function serializeService(service: ClientService): SerializedService {
port: service.port.value,
cpuCores: service.cpuCores.value,
ramMegabytes: service.ramMegabytes.value,
smartOptimization: service.smartOptimization?.value,
config: {
type: "job" as const,
allowConcurrent: config.allowConcurrent?.value,
Expand All @@ -264,6 +270,7 @@ export function serializeService(service: ClientService): SerializedService {
instances: service.instances.value,
port: service.port.value,
cpuCores: service.cpuCores.value,
smartOptimization: service.smartOptimization?.value,
ramMegabytes: service.ramMegabytes.value,
config: {
type: "predeploy" as const,
Expand Down Expand Up @@ -296,6 +303,7 @@ export function deserializeService({
service.ramMegabytes,
override?.ramMegabytes
),
smartOptimization: ServiceField.boolean(service.smartOptimization, override?.smartOptimization),
domainDeletions: [],
};

Expand Down Expand Up @@ -334,7 +342,7 @@ export function deserializeService({
})),
private:
typeof config.private === "boolean" ||
typeof overrideWebConfig?.private === "boolean"
typeof overrideWebConfig?.private === "boolean"
? ServiceField.boolean(config.private, overrideWebConfig?.private)
: undefined,
},
Expand Down Expand Up @@ -365,28 +373,28 @@ export function deserializeService({
type: "job" as const,
allowConcurrent:
typeof config.allowConcurrent === "boolean" ||
typeof overrideJobConfig?.allowConcurrent === "boolean"
typeof overrideJobConfig?.allowConcurrent === "boolean"
? ServiceField.boolean(
config.allowConcurrent,
overrideJobConfig?.allowConcurrent
)
config.allowConcurrent,
overrideJobConfig?.allowConcurrent
)
: ServiceField.boolean(false, undefined),
cron: ServiceField.string(config.cron, overrideJobConfig?.cron),
suspendCron:
typeof config.suspendCron === "boolean" ||
typeof overrideJobConfig?.suspendCron === "boolean"
typeof overrideJobConfig?.suspendCron === "boolean"
? ServiceField.boolean(
config.suspendCron,
overrideJobConfig?.suspendCron
)
config.suspendCron,
overrideJobConfig?.suspendCron
)
: ServiceField.boolean(false, undefined),
timeoutSeconds:
config.timeoutSeconds == 0
? ServiceField.number(3600, overrideJobConfig?.timeoutSeconds)
: ServiceField.number(
config.timeoutSeconds,
overrideJobConfig?.timeoutSeconds
),
config.timeoutSeconds,
overrideJobConfig?.timeoutSeconds
),
},
};
})
Expand Down Expand Up @@ -511,22 +519,22 @@ export function serializedServiceFromProto({
.with({ case: "jobConfig" }, ({ value }) =>
isPredeploy
? {
...service,
name,
config: {
type: "predeploy" as const,
},
}
...service,
name,
config: {
type: "predeploy" as const,
},
}
: {
...service,
name,
config: {
type: "job" as const,
...value,
allowConcurrent: value.allowConcurrentOptional,
timeoutSeconds: Number(value.timeoutSeconds),
},
}
...service,
name,
config: {
type: "job" as const,
...value,
allowConcurrent: value.allowConcurrentOptional,
timeoutSeconds: Number(value.timeoutSeconds),
},
}
)
.exhaustive();
}
Loading