diff --git a/schema.graphql b/schema.graphql index dd7b59d84..9c9892603 100644 --- a/schema.graphql +++ b/schema.graphql @@ -5858,6 +5858,9 @@ enum UtilizationResourceType { """Resource utilization type.""" type UtilizationSample { + """The instance for the utilization data.""" + instance: String! + """Timestamp of the value.""" timestamp: Time! diff --git a/src/routes/team/[team]/[env]/app/[app]/utilization2/+page.gql b/src/routes/team/[team]/[env]/app/[app]/utilization2/+page.gql new file mode 100644 index 000000000..27e6c240e --- /dev/null +++ b/src/routes/team/[team]/[env]/app/[app]/utilization2/+page.gql @@ -0,0 +1,32 @@ +query ResourceUtilizationForApp2( + $team: Slug! + $app: String! + $env: String! + $start: Time! + $end: Time! +) { + team(slug: $team) { + environment(name: $env) { + application(name: $app) { + utilization { + current_cpu: current(resourceType: CPU) + current_memory: current(resourceType: MEMORY) + requested_cpu: requested(resourceType: CPU) + requested_memory: requested(resourceType: MEMORY) + limit_cpu: limit(resourceType: CPU) + limit_memory: limit(resourceType: MEMORY) + cpu_series: series(input: { start: $start, end: $end, resourceType: CPU }) { + timestamp + value + instance + } + memory_series: series(input: { start: $start, end: $end, resourceType: MEMORY }) { + timestamp + value + instance + } + } + } + } + } +} diff --git a/src/routes/team/[team]/[env]/app/[app]/utilization2/+page.svelte b/src/routes/team/[team]/[env]/app/[app]/utilization2/+page.svelte new file mode 100644 index 000000000..9103d862e --- /dev/null +++ b/src/routes/team/[team]/[env]/app/[app]/utilization2/+page.svelte @@ -0,0 +1,310 @@ + + + + +
+ + These graphs help you analyze your app's CPU and memory usage over time. +
    +
  • Blue Line (Requests): The guaranteed CPU or memory allocation for your app.
  • +
  • Red Line (Limits, if present): The maximum allowed usage before restrictions apply.
  • +
  • Shaded Area: The actual resource consumption over time.
  • +
+ Your app can exceed the request line, which is expected if additional resources are available: +
    +
  • + For CPU: Exceeding requests may cause throttling, leading to reduced performance but no + crashes. +
  • +
  • + For Memory: Exceeding the limit causes termination (OOMKilled) because memory cannot be + throttled. +
  • +
+
To optimize costs while maintaining performance:
+
✅ If usage is consistently below requests, consider lowering requests to save money.
+
✅ If CPU usage is frequently throttled, increasing requests may improve performance.
+
+ ✅ If memory usage hits the limit, increasing requests or optimizing memory use may prevent + crashes. +
+
+
+ Memory usage + {#if $ResourceUtilizationForApp2.data} + {@const utilization = + $ResourceUtilizationForApp2.data.team.environment.application.utilization} + + At the latest data point, usage is {( + memoryUtilization(utilization.requested_memory, utilization.current_memory) * 100 + ).toFixed(0)}% of {prettyBytes(utilization.requested_memory, { + locale: 'en', + minimumFractionDigits: 2, + maximumFractionDigits: 2 + })} requested memory. Based on this data point, the estimated annual cost of unused memory of + {euroValueFormatter( + yearlyOverageCost( + UtilizationResourceType.MEMORY, + utilization.requested_memory, + utilization.current_memory + ) + )}. + +
+ changeParams({ interval }, { noScroll: true })} + > + {#each ['1h', '6h', '1d', '7d', '30d'] as interval (interval)} + {interval} + {/each} + +
+ { + return { + timestamp: d.timestamp, + value: d.value / 1024 / 1024 / 1024, + instance: d.instance + }; + }), + utilization.requested_memory / 1024 / 1024 / 1024, + utilization.limit_memory ? utilization.limit_memory / 1024 / 1024 / 1024 : undefined, + (value) => + value == null + ? '-' + : prettyBytes(value * 1024 ** 3, { + locale: 'en', + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }) + )} + style="height: 400px" + /> + {:else} +
+ +
+ {/if} +
+
+ CPU usage + {#if $ResourceUtilizationForApp2.data} + {@const utilization = + $ResourceUtilizationForApp2.data.team.environment.application.utilization} + + At the latest data point, usage is {cpuUtilization( + utilization.requested_cpu, + utilization.current_cpu + )}% of {utilization.requested_cpu.toLocaleString('en-GB', { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + })} requested CPUs. Based on this data point, the estimated annual cost of unused CPU of {euroValueFormatter( + yearlyOverageCost( + UtilizationResourceType.CPU, + utilization.requested_cpu, + utilization.current_cpu + ) + )}. + +
+ changeParams({ interval }, { noScroll: true })} + > + {#each ['1h', '6h', '1d', '7d', '30d'] as interval (interval)} + {interval} + {/each} + +
+ + value == null + ? '-' + : `${value.toLocaleString('en-GB', { maximumFractionDigits: 4 })} CPUs` + )} + style="height: 400px" + /> + {:else} +
+ +
+ {/if} +
+
+ + diff --git a/src/routes/team/[team]/[env]/app/[app]/utilization2/+page.ts b/src/routes/team/[team]/[env]/app/[app]/utilization2/+page.ts new file mode 100644 index 000000000..ca8221bfe --- /dev/null +++ b/src/routes/team/[team]/[env]/app/[app]/utilization2/+page.ts @@ -0,0 +1,38 @@ +import { load_ResourceUtilizationForApp2 } from '$houdini'; +import type { PageLoad } from './$houdini'; + +function getStart(interval: string | null) { + switch (interval) { + case '1h': + return new Date(Date.now() - 1000 * 60 * 60); + case '6h': + return new Date(Date.now() - 6 * 1000 * 60 * 60); + case '1d': + return new Date(Date.now() - 24 * 1000 * 60 * 60); + case '30d': + return new Date(Date.now() - 30 * 24 * 1000 * 60 * 60); + default: + return new Date(Date.now() - 7 * 24 * 1000 * 60 * 60); + } +} + +export const ssr = false; +export const load: PageLoad = async (event) => { + const interval = event.url.searchParams.get('interval'); + const end = new Date(Date.now()); + const start = getStart(interval); + + return { + interval, + ...(await load_ResourceUtilizationForApp2({ + event, + variables: { + app: event.params.app, + env: event.params.env, + team: event.params.team, + start, + end + } + })) + }; +};