Skip to content

Commit

Permalink
feat: manual scale recommendations
Browse files Browse the repository at this point in the history
  • Loading branch information
neurosnap committed Oct 30, 2024
1 parent 9aeea7a commit d0f2f4b
Show file tree
Hide file tree
Showing 23 changed files with 669 additions and 123 deletions.
2 changes: 1 addition & 1 deletion src/app/test/database-detail-scale.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ describe("DatabaseScalePage", () => {
/Changed from General Purpose \(M\) to Compute Optimized \(C\)/,
),
).toBeInTheDocument();
expect(screen.getByText(/Container Size/)).toBeInTheDocument();
expect(screen.getByText("Container Size")).toBeInTheDocument();
expect(
screen.getByText(/Changed from 0.5 GB to 2 GB/),
).toBeInTheDocument();
Expand Down
7 changes: 7 additions & 0 deletions src/bootup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import {
fetchEndpoints,
fetchEnvironments,
fetchLogDrains,
fetchManualScaleRecommendations,
fetchMetricDrains,
fetchOperationsByOrgId,
fetchServiceSizingPolicies,
fetchServices,
fetchStacks,
} from "@app/deploy";
Expand Down Expand Up @@ -88,7 +90,12 @@ function* onFetchResourceData() {
fetchSources.run(),
fetchDeployments.run(),
fetchMembershipsByOrgId.run({ orgId: org.id }),
fetchServiceSizingPolicies.run(),
]);
// feature flag temporarily
if (org.id === "df0ee681-9e02-4c28-8916-3b215d539b08") {
yield* fetchManualScaleRecommendations.run();
}
yield* group;
}

Expand Down
2 changes: 1 addition & 1 deletion src/date/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// https://moment.github.io/luxon/#/formatting?id=table-of-tokens
import { DateTime } from "luxon";

const isoToDate = (dateStr = "") => {
export const isoToDate = (dateStr = "") => {
return DateTime.fromISO(dateStr, { zone: "utc" });
};

Expand Down
1 change: 1 addition & 0 deletions src/deploy/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export interface DeployDatabaseRow extends DeployDatabase {
diskSize: number;
containerSize: number;
imageDesc: string;
savings: number;
}

export const hasDeployDatabase = (a: DeployDatabase) => a.id !== "";
Expand Down
2 changes: 2 additions & 0 deletions src/deploy/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { endpointEntities } from "./endpoint";
import { environmentEntities } from "./environment";
import { imageEntities } from "./image";
import { logDrainEntities } from "./log-drain";
import { manualScaleRecommendationEntities } from "./manual_scale_recommendation";
import { metricDrainEntities } from "./metric-drain";
import { opEntities } from "./operation";
import { permissionEntities } from "./permission";
Expand Down Expand Up @@ -53,4 +54,5 @@ export const entities = {
...imageEntities,
...diskEntities,
...serviceSizingPolicyEntities,
...manualScaleRecommendationEntities,
};
1 change: 1 addition & 0 deletions src/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ export * from "./support";
export * from "./entities";
export * from "./search";
export * from "./cost";
export * from "./manual_scale_recommendation";
91 changes: 91 additions & 0 deletions src/deploy/manual_scale_recommendation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { api, cacheTimer } from "@app/api";
import { defaultEntity, defaultHalHref } from "@app/hal";
import { type WebState, schema } from "@app/schema";
import type { LinkResponse, ManualScaleRecommendation } from "@app/types";
import { createSelector } from "starfx";

export interface DeployManualScaleRecommendationResponse {
id: number;
service_id: number;
cpu_usage: number;
ram_usage: number;
ram_target: number;
recommended_instance_class: string;
recommended_container_memory_limit_mb: number;
cost_savings: number;
metric_percentile: number;
created_at: string;
updated_at: string;
_links: {
service: LinkResponse;
};
_type: "manual_service_sizing_recommendation";
}

export const defaultManualScaleRecommendationResponse = (
s: Partial<DeployManualScaleRecommendationResponse> = {},
): DeployManualScaleRecommendationResponse => {
const now = new Date().toISOString();
return {
id: 0,
service_id: 0,
cpu_usage: 0,
ram_usage: 0,
ram_target: 0,
recommended_instance_class: "",
recommended_container_memory_limit_mb: 0,
cost_savings: 0,
metric_percentile: 0,
created_at: now,
updated_at: now,
_links: {
service: defaultHalHref(),
},
...s,
_type: "manual_service_sizing_recommendation",
};
};

export const deserializeManualScaleRecommendation = (
payload: DeployManualScaleRecommendationResponse,
): ManualScaleRecommendation => {
return {
id: `${payload.id}`,
serviceId: `${payload.service_id}`,
cpuUsage: payload.cpu_usage,
ramUsage: payload.ram_usage,
ramTarget: payload.ram_target,
recommendedInstanceClass: payload.recommended_instance_class,
recommendedContainerMemoryLimitMb:
payload.recommended_container_memory_limit_mb,
costSavings: payload.cost_savings,
metricPercentile: payload.metric_percentile,
createdAt: payload.created_at,
};
};

export const manualScaleRecommendationEntities = {
manual_service_sizing_recommendation: defaultEntity({
id: "manual_service_sizing_recommendation",
deserialize: deserializeManualScaleRecommendation,
save: schema.manualScaleRecommendations.add,
}),
};

export const selectManualScaleRecommendationByServiceId = createSelector(
schema.manualScaleRecommendations.selectTableAsList,
(_: WebState, p: { serviceId: string }) => p.serviceId,
(recs, serviceId) =>
recs.find((r) => {
return r.serviceId === serviceId;
}) || schema.manualScaleRecommendations.empty,
);

export const fetchManualScaleRecommendations = api.get(
"/manual_service_sizing_recommendations?per_page=5000",
{ supervisor: cacheTimer() },
);

export const fetchManualScaleRecommendationById = api.get<{
id: string;
}>("/manual_service_sizing_recommendations/:id");
24 changes: 22 additions & 2 deletions src/deploy/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export const selectServicesForTable = createSelector(
selectApps,
selectServicesByOrgId,
selectEndpointsAsList,
(envs, apps, services, endpoints) =>
schema.manualScaleRecommendations.selectTableAsList,
(envs, apps, services, endpoints, recs) =>
services
// making sure we have a valid environment associated with it
.filter((service) => {
Expand All @@ -68,11 +69,13 @@ export const selectServicesForTable = createSelector(
} else {
resourceHandle = "Unknown";
}
const rec = recs.find((r) => r.serviceId === service.id);

return {
...service,
envHandle: env.handle,
resourceHandle,
savings: rec?.costSavings || 0,
cost: estimateMonthlyCost({
services: [service],
endpoints: findEndpointsByServiceId(endpoints, service.id),
Expand All @@ -93,6 +96,13 @@ const createServiceSortFn = (
return b.cost - a.cost;
}

if (sortBy === "savings") {
if (sortDir === "asc") {
return a.savings - b.savings;
}
return b.savings - a.savings;
}

if (sortBy === "resourceHandle") {
if (sortDir === "asc") {
return a.resourceHandle.localeCompare(b.resourceHandle);
Expand Down Expand Up @@ -530,6 +540,13 @@ const createDatabaseSortFn = (
return b.cost - a.cost;
}

if (sortBy === "savings") {
if (sortDir === "asc") {
return a.savings - b.savings;
}
return b.savings - a.savings;
}

if (sortBy === "handle") {
if (sortDir === "asc") {
return a.handle.localeCompare(b.handle);
Expand Down Expand Up @@ -581,7 +598,8 @@ export const selectDatabasesForTable = createSelector(
selectEndpointsAsList,
selectBackupsAsList,
selectDatabaseImages,
(dbs, envs, ops, disks, services, endpoints, backups, images) =>
schema.manualScaleRecommendations.selectTableAsList,
(dbs, envs, ops, disks, services, endpoints, backups, images, recs) =>
dbs
.map((dbb): DeployDatabaseRow => {
const env = findEnvById(envs, { id: dbb.environmentId });
Expand All @@ -600,6 +618,7 @@ export const selectDatabasesForTable = createSelector(
});
const metrics = calcMetrics([service]);
const img = findDatabaseImageById(images, { id: dbb.databaseImageId });
const rec = recs.find((s) => s.serviceId === dbb.serviceId);
return {
...dbb,
imageDesc: img.description,
Expand All @@ -608,6 +627,7 @@ export const selectDatabasesForTable = createSelector(
diskSize: disk.size,
cost,
containerSize: metrics.totalMemoryLimit / 1024,
savings: rec?.costSavings || 0,
};
})
.sort((a, b) => a.handle.localeCompare(b.handle)),
Expand Down
9 changes: 8 additions & 1 deletion src/deploy/service-sizing-policy/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { api, cacheShortTimer, thunks } from "@app/api";
import { api, cacheShortTimer, cacheTimer, thunks } from "@app/api";
import { createSelector } from "@app/fx";
import { defaultEntity, defaultHalHref, extractIdFromLink } from "@app/hal";
import { schema } from "@app/schema";
Expand Down Expand Up @@ -134,6 +134,13 @@ export const selectAutoscalingEnabledByServiceId = createSelector(
(policy) => policy.scalingEnabled,
);

export const fetchServiceSizingPolicies = api.get(
"/service_sizing_policies?per_page=5000",
{
supervisor: cacheTimer(),
},
);

export const fetchServiceSizingPoliciesByEnvironmentId = api.get<{
id: string;
}>("/accounts/:id/service_sizing_policies", { supervisor: cacheShortTimer() });
Expand Down
20 changes: 20 additions & 0 deletions src/schema/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
type GithubIntegration,
type InstanceClass,
type Invitation,
type ManualScaleRecommendation,
type Membership,
ModalType,
type Organization,
Expand Down Expand Up @@ -896,3 +897,22 @@ export const defaultGithubIntegration = (
...s,
};
};

export const defaultManualScaleRecommendation = (
s: Partial<ManualScaleRecommendation> = {},
): ManualScaleRecommendation => {
const now = new Date().toISOString();
return {
id: "",
serviceId: "",
cpuUsage: 0,
ramUsage: 0,
ramTarget: 0,
costSavings: 0,
recommendedInstanceClass: "",
recommendedContainerMemoryLimitMb: 0,
metricPercentile: 0,
createdAt: now,
...s,
};
};
3 changes: 3 additions & 0 deletions src/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,8 @@ export const [schema, initialState] = createSchema({
githubIntegrations: slice.table({
empty: factory.defaultGithubIntegration(),
}),
manualScaleRecommendations: slice.table({
empty: factory.defaultManualScaleRecommendation(),
}),
});
export type WebState = typeof initialState;
14 changes: 14 additions & 0 deletions src/types/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export interface DeployServiceRow extends DeployService {
envHandle: string;
resourceHandle: string;
cost: number;
savings: number;
url?: string;
}

Expand Down Expand Up @@ -100,3 +101,16 @@ export interface GithubIntegration {
createdAt: string;
updatedAt: string;
}

export interface ManualScaleRecommendation {
id: string;
serviceId: string;
cpuUsage: number;
ramUsage: number;
ramTarget: number;
recommendedInstanceClass: string;
recommendedContainerMemoryLimitMb: number;
costSavings: number;
createdAt: string;
metricPercentile: number;
}
4 changes: 4 additions & 0 deletions src/ui/layouts/database-detail-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
DetailInfoItem,
DetailPageHeaderView,
DetailTitleBar,
ScaleRecsView,
type TabItem,
} from "../shared";
import { ActiveOperationNotice } from "../shared/active-operation-notice";
Expand Down Expand Up @@ -115,6 +116,9 @@ export function DatabaseHeader({
</Link>
</DetailInfoItem>
)}
<DetailInfoItem title="Scaling Recommendations">
<ScaleRecsView service={service} />
</DetailInfoItem>
</DetailInfoGrid>
</DetailHeader>
);
Expand Down
4 changes: 4 additions & 0 deletions src/ui/layouts/service-detail-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
IconChevronDown,
IconChevronRight,
PreCode,
ScaleRecsView,
type TabItem,
listToInvertedTextColor,
} from "../shared";
Expand Down Expand Up @@ -135,6 +136,9 @@ export function ServiceHeader({
{deploymentStrategy}
</Link>
</DetailInfoItem>
<DetailInfoItem title="Scaling Recommendations">
<ScaleRecsView service={service} />
</DetailInfoItem>
</DetailInfoGrid>
{service.command ? (
<div>
Expand Down
Loading

0 comments on commit d0f2f4b

Please sign in to comment.