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.name}

+
+
+ {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. +

+ + + + + +
+ ) : 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. +

+ + + + + +
+ ) : 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