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
+ }
+ }))
+ };
+};