diff --git a/studio/components/interfaces/BillingV2/Subscription/Subscription.tsx b/studio/components/interfaces/BillingV2/Subscription/Subscription.tsx
index b7a57df0b61..bde2e625517 100644
--- a/studio/components/interfaces/BillingV2/Subscription/Subscription.tsx
+++ b/studio/components/interfaces/BillingV2/Subscription/Subscription.tsx
@@ -50,7 +50,7 @@ const Subscription = ({}: SubscriptionProps) => {
)}
-
+
diff --git a/studio/components/interfaces/Organization/Usage/UsageSection/AttributeUsage.tsx b/studio/components/interfaces/Organization/Usage/UsageSection/AttributeUsage.tsx
index a3ccc2ac8b4..3439d983aa7 100644
--- a/studio/components/interfaces/Organization/Usage/UsageSection/AttributeUsage.tsx
+++ b/studio/components/interfaces/Organization/Usage/UsageSection/AttributeUsage.tsx
@@ -181,8 +181,8 @@ const AttributeUsage = ({
/>
)}
-
- {projectRef === undefined && (
+ {projectRef === undefined && (
+
Included in {subscription?.plan?.name.toLowerCase()} plan
@@ -197,22 +197,19 @@ const AttributeUsage = ({
)}
- )}
- {currentBillingCycleSelected && (
-
-
- {attribute.chartPrefix || 'Used '} in period
-
-
- {attribute.unit === 'bytes'
- ? `${usageMeta?.usage ?? 0} GB`
- : (usageMeta?.usage ?? 0).toLocaleString()}
-
-
- )}
- {projectRef === undefined &&
- currentBillingCycleSelected &&
- (usageMeta?.pricing_free_units ?? 0) > 0 && (
+ {currentBillingCycleSelected && (
+
+
+ {attribute.chartPrefix || 'Used '} in period
+
+
+ {attribute.unit === 'bytes'
+ ? `${usageMeta?.usage ?? 0} GB`
+ : (usageMeta?.usage ?? 0).toLocaleString()}
+
+
+ )}
+ {currentBillingCycleSelected && (usageMeta?.pricing_free_units ?? 0) > 0 && (
Overage in period
@@ -224,7 +221,8 @@ const AttributeUsage = ({
)}
-
+
+ )}
{attribute.additionalInfo?.(usage)}
diff --git a/studio/components/interfaces/Settings/Infrastructure/Infrastructure.constants.ts b/studio/components/interfaces/Settings/Infrastructure/Infrastructure.constants.ts
new file mode 100644
index 00000000000..c351a9e25de
--- /dev/null
+++ b/studio/components/interfaces/Settings/Infrastructure/Infrastructure.constants.ts
@@ -0,0 +1,102 @@
+// [Joshen TODO] Please clean up - a lot of repeated code from other files here
+// all of these interfaces, see what can be reused
+
+// [Joshen TODO] Remove any dependency on BillingV2 stuff so that we can deprecate easily
+
+export interface Attribute {
+ key: string
+ name?: string
+ color: 'white' | 'blue' | 'green' | 'yellow' | 'orange'
+}
+export interface CategoryAttribute {
+ anchor: string
+ key: string // Property from organization usage
+ attributes: Attribute[] // For querying against stats-daily / infra-monitoring
+ name: string
+ unit: 'bytes' | 'absolute' | 'percentage'
+ links: {
+ name: string
+ url: string
+ }[]
+ description: string
+ chartPrefix?: 'Max' | 'Average'
+ chartDescription: string
+}
+
+export interface CategoryMeta {
+ key: string
+ name: string
+ description: string
+ attributes: CategoryAttribute[]
+}
+
+export const INFRA_ACTIVITY_METRICS: CategoryMeta[] = [
+ {
+ key: 'infra',
+ name: 'Infrastructure',
+ description: 'Usage statistics related to your server instance',
+ attributes: [
+ {
+ anchor: 'cpu',
+ key: 'max_cpu_usage',
+ attributes: [{ key: 'max_cpu_usage', color: 'white' }],
+ name: 'CPU',
+ unit: 'percentage',
+ description: 'Max CPU usage of your server',
+ chartDescription: '',
+ links: [
+ {
+ name: 'Compute Add-Ons',
+ url: 'https://supabase.com/docs/guides/platform/compute-add-ons',
+ },
+ { name: 'High CPU Usage', url: 'https://supabase.com/docs/guides/platform/exhaust-cpu' },
+ {
+ name: 'Metrics',
+ url: 'https://supabase.com/docs/guides/platform/metrics',
+ },
+ ],
+ },
+ {
+ anchor: 'ram',
+ key: 'ram_usage',
+ attributes: [{ key: 'ram_usage', color: 'white' }],
+ name: 'Memory',
+ unit: 'percentage',
+ description:
+ 'Memory usage of your server.\nYou might observe elevated memory usage, even with little to no load. Besides Postgres, a wide range of services is running under the hood resulting in an elevated base memory usage.',
+ chartDescription: '',
+ links: [
+ {
+ name: 'Compute Add-Ons',
+ url: 'https://supabase.com/docs/guides/platform/compute-add-ons',
+ },
+ { name: 'High RAM Usage', url: 'https://supabase.com/docs/guides/platform/exhaust-ram' },
+ {
+ name: 'Metrics',
+ url: 'https://supabase.com/docs/guides/platform/metrics',
+ },
+ ],
+ },
+ {
+ anchor: 'disk_io',
+ key: 'disk_io_consumption',
+ attributes: [{ key: 'disk_io_consumption', color: 'white' }],
+ name: 'Disk IO Bandwidth',
+ unit: 'percentage',
+ links: [
+ {
+ name: 'Disk Throughput and IOPS',
+ url: 'https://supabase.com/docs/guides/platform/compute-add-ons#disk-throughput-and-iops',
+ },
+ {
+ name: 'Metrics',
+ url: 'https://supabase.com/docs/guides/platform/metrics',
+ },
+ ],
+ description:
+ 'The disk performance of your workload is determined by the Disk IO bandwidth.\nSmaller compute instances (below 4XL) can burst up to their largest throughput and IOPS for 30 minutes in a day. Beyond that, the performance reverts to the baseline. Your disk budget gets replenished throughout the day.',
+ chartDescription: '',
+ },
+ ],
+ },
+]
diff --git a/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx b/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx
new file mode 100644
index 00000000000..bdbc4b6b173
--- /dev/null
+++ b/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx
@@ -0,0 +1,379 @@
+import dayjs from 'dayjs'
+import Link from 'next/link'
+import { Fragment, useMemo, useState } from 'react'
+
+import { useParams } from 'common'
+import {
+ ScaffoldContainer,
+ ScaffoldDivider,
+ ScaffoldSection,
+ ScaffoldSectionContent,
+ ScaffoldSectionDetail,
+} from 'components/layouts/Scaffold'
+import DateRangePicker from 'components/to-be-cleaned/DateRangePicker'
+import Panel from 'components/ui/Panel'
+import ShimmeringLoader from 'components/ui/ShimmeringLoader'
+import { DataPoint } from 'data/analytics/constants'
+import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query'
+import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
+import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
+import { useSelectedOrganization } from 'hooks'
+import { TIME_PERIODS_BILLING, TIME_PERIODS_REPORTS } from 'lib/constants'
+import { Alert, Button, IconBarChart2, IconExternalLink } from 'ui'
+import { getAddons } from '../../BillingV2/Subscription/Subscription.utils'
+import SectionHeader from '../../Organization/Usage/SectionHeader'
+import UsageBarChart from '../../Organization/Usage/UsageBarChart'
+import { INFRA_ACTIVITY_METRICS } from './Infrastructure.constants'
+
+const InfrastructureActivity = () => {
+ const { ref: projectRef } = useParams()
+ const organization = useSelectedOrganization()
+ const [dateRange, setDateRange] = useState
()
+
+ const currentBillingCycleSelected = true
+ const upgradeUrl = '/'
+ const categoryMeta = INFRA_ACTIVITY_METRICS.find((category) => category.key === 'infra')
+
+ const { data: subscription, isLoading: isLoadingSubscription } = useOrgSubscriptionQuery({
+ orgSlug: organization?.slug,
+ })
+ const isFreePlan = subscription?.plan?.id === 'free'
+
+ const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef })
+ const selectedAddons = addons?.selected_addons ?? []
+ const { computeInstance } = getAddons(selectedAddons)
+ const currentComputeInstanceSpecs = computeInstance?.variant?.meta ?? {
+ baseline_disk_io_mbs: 87,
+ max_disk_io_mbs: 2085,
+ cpu_cores: 2,
+ cpu_dedicated: true,
+ memory_gb: 1,
+ }
+
+ const startDate = useMemo(() => {
+ // If end date is in future, set end date to now
+ if (!dateRange?.period_start?.date) {
+ return undefined
+ } else {
+ // LF seems to have an issue with the milliseconds, causes infinite loading sometimes
+ return new Date(dateRange?.period_start?.date).toISOString().slice(0, -5) + 'Z'
+ }
+ }, [dateRange, subscription])
+
+ const endDate = useMemo(() => {
+ // If end date is in future, set end date to end of current day
+ if (dateRange?.period_end?.date && dayjs(dateRange.period_end.date).isAfter(dayjs())) {
+ // LF seems to have an issue with the milliseconds, causes infinite loading sometimes
+ // In order to have full days from Prometheus metrics when using 1d interval,
+ // the time needs to be greater or equal than the time of the start date
+ return dayjs().endOf('day').toISOString().slice(0, -5) + 'Z'
+ } else if (dateRange?.period_end?.date) {
+ // LF seems to have an issue with the milliseconds, causes infinite loading sometimes
+ return new Date(dateRange.period_end.date).toISOString().slice(0, -5) + 'Z'
+ }
+ }, [dateRange, subscription])
+
+ // Switch to hourly interval, if the timeframe is <48 hours
+ let interval: '1d' | '1h' = '1d'
+ let dateFormat = 'DD MMM'
+ if (startDate && endDate) {
+ const diffInHours = dayjs(endDate).diff(startDate, 'hours')
+
+ if (diffInHours <= 48) {
+ interval = '1h'
+ dateFormat = 'h a'
+ }
+ }
+
+ const { data: cpuUsageData, isLoading: isLoadingCpuUsageData } = useInfraMonitoringQuery({
+ projectRef,
+ attribute: 'max_cpu_usage',
+ interval,
+ startDate,
+ endDate,
+ dateFormat,
+ })
+
+ const { data: memoryUsageData, isLoading: isLoadingMemoryUsageData } = useInfraMonitoringQuery({
+ projectRef,
+ attribute: 'ram_usage',
+ interval,
+ startDate,
+ endDate,
+ dateFormat,
+ })
+
+ const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({
+ projectRef,
+ attribute: 'disk_io_consumption',
+ interval,
+ startDate,
+ endDate,
+ dateFormat,
+ })
+
+ const highestIoBudgetConsumption = Math.max(
+ ...(ioBudgetData?.data || []).map((x) => Number(x.disk_io_consumption) ?? 0),
+ 0
+ )
+
+ const chartMeta: { [key: string]: { data: DataPoint[]; isLoading: boolean } } = {
+ max_cpu_usage: {
+ isLoading: isLoadingCpuUsageData,
+ data: cpuUsageData?.data ?? [],
+ },
+ ram_usage: {
+ isLoading: isLoadingMemoryUsageData,
+ data: memoryUsageData?.data ?? [],
+ },
+ disk_io_consumption: {
+ isLoading: isLoadingIoBudgetData,
+ data: ioBudgetData?.data ?? [],
+ },
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+ {!isLoadingSubscription && (
+ <>
+
+
+ {dayjs(startDate).format('DD MMM YYYY')} - {dayjs(endDate).format('DD MMM YYYY')}
+
+ >
+ )}
+
+
+
+ {categoryMeta?.attributes.map((attribute) => {
+ const chartData = chartMeta[attribute.key]?.data ?? []
+
+ return (
+
+
+
+
+
+
+
+
+ {attribute.description.split('\n').map((value, idx) => (
+
+ {value}
+
+ ))}
+
+
+
+
+ More information
+
+ {attribute.links.map((link) => (
+
+ ))}
+
+
+
+
+ {attribute.key === 'disk_io_consumption' && (
+ <>
+ {currentBillingCycleSelected && highestIoBudgetConsumption >= 100 ? (
+
+
+ Your workload has used up all the burst IO throughput minutes and ran at
+ the baseline performance. If you need consistent disk performance,
+ consider upgrading to a larger compute add-on.
+
+
+
+
+ {isFreePlan ? 'Upgrade project' : 'Change compute add-on'}
+
+
+
+
+ ) : currentBillingCycleSelected && highestIoBudgetConsumption >= 80 ? (
+
+
+ Your workload is about to use up all the burst IO throughput minutes
+ during the day. Once this is completely used up, your workload will run
+ at the baseline performance. If you need consistent disk performance,
+ consider upgrading to a larger compute add-on.
+
+
+
+
+ {isFreePlan ? 'Upgrade project' : 'Change compute add-on'}
+
+
+
+
+ ) : null}
+
+
Disk IO Bandwidth
+
+ {currentComputeInstanceSpecs.baseline_disk_io_mbs ===
+ currentComputeInstanceSpecs.max_disk_io_mbs ? (
+
+ Your current compute can has a baseline and maximum disk throughput of{' '}
+ {currentComputeInstanceSpecs.max_disk_io_mbs?.toLocaleString()} Mbps.
+
+ ) : (
+
+ Your current compute can burst up to{' '}
+ {currentComputeInstanceSpecs.max_disk_io_mbs?.toLocaleString()} Mbps for
+ 30 minutes a day and reverts to the baseline performance of{' '}
+ {currentComputeInstanceSpecs.baseline_disk_io_mbs?.toLocaleString()}{' '}
+ Mbps.
+
+ )}
+
+
+
Overview
+
+
Current compute instance
+
{computeInstance?.variant?.name ?? 'Micro'}
+
+
+
+ Maximum IO Bandwidth (burst limit)
+
+
+ {currentComputeInstanceSpecs.max_disk_io_mbs?.toLocaleString()} Mbps
+
+
+
+
Baseline IO Bandwidth
+
+ {currentComputeInstanceSpecs.baseline_disk_io_mbs?.toLocaleString()}{' '}
+ Mbps
+
+
+ {currentComputeInstanceSpecs.max_disk_io_mbs !==
+ currentComputeInstanceSpecs?.baseline_disk_io_mbs && (
+
+
Daily burst time limit
+
30 mins
+
+ )}
+
+ >
+ )}
+
+
+ {attribute.key === 'disk_io_consumption' ? (
+
Disk IO consumed per {interval === '1d' ? 'day' : 'hour'}
+ ) : (
+
+ Max{' '}
+
+ {attribute.name}
+ {' '}
+ utilization per {interval === '1d' ? 'day' : 'hour'}
+
+ )}
+
+
+ {attribute.key === 'ram_usage' && (
+
+
+ Your compute instance has {currentComputeInstanceSpecs.memory_gb} GB of
+ memory.
+
+ {currentComputeInstanceSpecs.memory_gb === 1 && (
+
+ As your project is running on the smallest compute instance, it is not
+ unusual for your project to have a base memory usage of ~50%.
+
+ )}
+
+ )}
+
+ {attribute.key === 'max_cpu_usage' && (
+
+ Your compute instance has {currentComputeInstanceSpecs.cpu_cores} CPU cores.
+
+ )}
+
+ {attribute.chartDescription.split('\n').map((paragraph, idx) => (
+
+ {paragraph}
+
+ ))}
+
+ {chartMeta[attribute.key].isLoading ? (
+
+
+
+
+
+ ) : chartData.length ? (
+ `${Math.round(Number(value))}%`}
+ tooltipFormatter={(value) => `${value}%`}
+ yLimit={100}
+ />
+ ) : (
+
+
+
+
+
No data in period
+
May take a few minutes to show
+
+
+
+ )}
+
+
+
+
+
+ )
+ })}
+ >
+ )
+}
+
+export default InfrastructureActivity
diff --git a/studio/components/layouts/SettingsLayout/SettingsMenu.utils.ts b/studio/components/layouts/SettingsLayout/SettingsMenu.utils.ts
index eb310431d1a..22768003e49 100644
--- a/studio/components/layouts/SettingsLayout/SettingsMenu.utils.ts
+++ b/studio/components/layouts/SettingsLayout/SettingsMenu.utils.ts
@@ -107,9 +107,9 @@ export const generateSettingsMenu = (
items: [],
},
{
- name: 'Usage',
+ name: 'Infrastructure',
key: 'usage',
- url: isProjectBuilding ? buildingUrl : `/project/${ref}/settings/billing/usage`,
+ url: isProjectBuilding ? buildingUrl : `/project/${ref}/settings/infrastructure`,
items: [],
},
],
diff --git a/studio/pages/project/[ref]/settings/infrastructure.tsx b/studio/pages/project/[ref]/settings/infrastructure.tsx
new file mode 100644
index 00000000000..062608f728d
--- /dev/null
+++ b/studio/pages/project/[ref]/settings/infrastructure.tsx
@@ -0,0 +1,13 @@
+import InfrastructureActivity from 'components/interfaces/Settings/Infrastructure/InfrastructureActivity'
+import { SettingsLayout } from 'components/layouts'
+import { NextPageWithLayout } from 'types'
+
+const ProjectInfrastructure: NextPageWithLayout = () => {
+ return
+}
+
+ProjectInfrastructure.getLayout = (page) => (
+ {page}
+)
+
+export default ProjectInfrastructure