mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 09:59:03 +08:00
## Summary This is the second step towards merging compute and disk with infrastructure. There are some usage charts on the current infrastructure page that are useful to have in the context of compute and disk settings. This branch adds two charts which give a general sense of usage and whether an upgrade needs to happen. Other data points in infrastructure can be found within observability and organisation usage. - Adds rolling seven-day Compute and Disk charts to the existing Compute and Disk page. - Shows CPU, memory, optional burstable disk IO, and disk usage split into database, WAL, and system data. - Covers loading, error, empty, warning, and critical states, retaining the 75% warning and 90% critical thresholds. - Uses a dedicated PageSection and keeps the charts in two columns from 680px. - Uses the concise primary labels Compute and Disk, removes the database report link, and removes tooltip icons from secondary metrics. - Adds transformation, summary, and component tests covering dedicated IO behavior, legacy anchors, responsive layout, rolling refetch, and tooltip behavior. ## Stack 1. #48368 2. #48369 (this PR) 3. #48370 ## How to test 1. Check out `chore/infra-compute-2-charts` and start Studio with `pnpm dev:studio`. 2. Open `/project/<ref>/settings/compute-and-disk` on a project with recent metrics. 3. Confirm the charts are in their own page section with standard spacing below the page header. 4. Confirm the Compute chart shows CPU and memory, plus disk IO when applicable, and the Disk chart splits usage into database, WAL, and system data. 5. Confirm the primary labels are Compute and Disk, secondary metrics do not show tooltip icons, and there is no Database Observability/report link. 6. Resize across 680px. The charts should remain in two columns at and above the breakpoint and stack into two rows below it. 7. Exercise loading, error, empty, warning, and critical responses with the metrics mocks or response overrides. Confirm warning styling begins at 75%, critical styling begins at 90%, and an error or empty response does not break the configuration form. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added compute and disk usage charts to the disk management interface, including metric cards for CPU, memory, disk I/O, database, WAL, and system. * Added usage status indicators, peak calculations, tooltips, and a detailed disk breakdown with placeholders when data is missing. * Added special handling for dedicated-I/O instances to hide burst-only disk I/O. * **Style** * Simplified the disk space display by removing supplemental explanatory text. * **Tests** * Added comprehensive test coverage for chart rendering, loading/error/empty states, status/peak calculations, and rolling 7-day data window behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
328 lines
10 KiB
TypeScript
328 lines
10 KiB
TypeScript
import { useParams } from 'common'
|
|
import dayjs from 'dayjs'
|
|
import { Activity, BarChart2, Database } from 'lucide-react'
|
|
import { useMemo } from 'react'
|
|
import type { ChartConfig } from 'ui'
|
|
import { cn } from 'ui'
|
|
import {
|
|
Chart,
|
|
ChartCard,
|
|
ChartContent,
|
|
ChartEmptyState,
|
|
ChartHeader,
|
|
ChartLine,
|
|
ChartLoadingState,
|
|
ChartMetric,
|
|
} from 'ui-patterns/Chart'
|
|
|
|
import {
|
|
buildUsageChartData,
|
|
formatUsagePercent,
|
|
getComputeUsageSummary,
|
|
getDiskUsageSummary,
|
|
getUsageMetricStatus,
|
|
type UsageMetricStatus,
|
|
} from './ComputeAndDiskUsageCharts.utils'
|
|
import { hasBurstableIO } from './DiskManagement.utils'
|
|
import type { InfraMonitoringAttribute } from '@/data/analytics/infra-monitoring-query'
|
|
import {
|
|
getInfraMonitoringAttributes,
|
|
useInfraMonitoringAttributesQuery,
|
|
} from '@/data/analytics/infra-monitoring-query'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
import { formatBytes } from '@/lib/helpers'
|
|
|
|
const USAGE_ATTRIBUTES = [
|
|
'max_cpu_usage',
|
|
'ram_usage',
|
|
'disk_io_consumption',
|
|
'disk_fs_used_system',
|
|
'disk_fs_used_wal',
|
|
'pg_database_size',
|
|
'disk_fs_size',
|
|
] satisfies InfraMonitoringAttribute[]
|
|
|
|
const ROLLING_WINDOW_DAYS = 7
|
|
|
|
const getRollingWindow = () => {
|
|
const now = dayjs()
|
|
|
|
return {
|
|
startDate: now.subtract(ROLLING_WINDOW_DAYS, 'day').toISOString(),
|
|
endDate: now.toISOString(),
|
|
}
|
|
}
|
|
|
|
const COMPUTE_CHART_CONFIG = {
|
|
maxCpuUsage: {
|
|
label: 'CPU',
|
|
color: 'hsl(var(--brand-default))',
|
|
},
|
|
ramUsage: {
|
|
label: 'Memory',
|
|
color: 'hsl(var(--warning-default))',
|
|
},
|
|
diskIoConsumption: {
|
|
label: 'Disk IO',
|
|
color: 'hsl(var(--destructive-500))',
|
|
},
|
|
} satisfies ChartConfig
|
|
|
|
const DISK_CHART_CONFIG = {
|
|
databaseUsagePercent: {
|
|
label: 'Database',
|
|
color: 'hsl(var(--brand-default))',
|
|
},
|
|
walUsagePercent: {
|
|
label: 'WAL',
|
|
color: 'hsl(var(--warning-default))',
|
|
},
|
|
systemUsagePercent: {
|
|
label: 'System',
|
|
color: 'hsl(var(--destructive-500))',
|
|
},
|
|
} satisfies ChartConfig
|
|
|
|
const PERCENTAGE_Y_AXIS_PROPS = {
|
|
domain: [0, 100] as [number, number],
|
|
allowDataOverflow: true,
|
|
ticks: [0, 25, 50, 75, 100],
|
|
tickFormatter: (value: number) => `${Math.round(Number(value))}%`,
|
|
width: 64,
|
|
}
|
|
|
|
const getUsageCardClassName = (status: UsageMetricStatus) =>
|
|
cn(
|
|
'h-full flex flex-col transition-colors',
|
|
status === 'warning' && 'border-warning-400 bg-warning-200/30',
|
|
status === 'negative' && 'border-destructive-400 bg-destructive-200/30'
|
|
)
|
|
|
|
export const ComputeAndDiskUsageCharts = ({ className }: { className?: string }) => {
|
|
const { ref: projectRef } = useParams()
|
|
const { data: project } = useSelectedProjectQuery()
|
|
const supportsBurstableIO = hasBurstableIO(project?.infra_compute_size)
|
|
|
|
// Anchor query key dates to mount time; fetch uses a rolling 7-day window on each refetch.
|
|
const { startDate, endDate } = useMemo(getRollingWindow, [])
|
|
|
|
const {
|
|
data: usageData,
|
|
isLoading,
|
|
isError,
|
|
} = useInfraMonitoringAttributesQuery(
|
|
{
|
|
projectRef,
|
|
attributes: USAGE_ATTRIBUTES,
|
|
startDate,
|
|
endDate,
|
|
interval: '1d',
|
|
},
|
|
{
|
|
queryFn: ({ signal }) =>
|
|
getInfraMonitoringAttributes(
|
|
{
|
|
projectRef,
|
|
attributes: USAGE_ATTRIBUTES,
|
|
...getRollingWindow(),
|
|
interval: '1d',
|
|
},
|
|
signal
|
|
),
|
|
}
|
|
)
|
|
|
|
const { computeChartData, diskChartData } = useMemo(
|
|
() => buildUsageChartData(usageData),
|
|
[usageData]
|
|
)
|
|
|
|
const {
|
|
peakCpuUsage,
|
|
peakMemoryUsage,
|
|
peakDiskIoUsage,
|
|
peakComputeUsage,
|
|
status: computeUsageStatus,
|
|
} = useMemo(
|
|
() => getComputeUsageSummary(computeChartData, supportsBurstableIO),
|
|
[computeChartData, supportsBurstableIO]
|
|
)
|
|
|
|
const {
|
|
latestDataPoint: latestDiskDataPoint,
|
|
usagePercent: latestDiskUsage,
|
|
status: diskUsageStatus,
|
|
} = useMemo(() => getDiskUsageSummary(diskChartData), [diskChartData])
|
|
|
|
const isComputeEmpty = computeChartData.length === 0
|
|
const isDiskEmpty = diskChartData.length === 0
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'grid grid-cols-1 gap-4 @[680px]:grid-cols-2 @[680px]:items-stretch',
|
|
className
|
|
)}
|
|
>
|
|
<div id="cpu" className="scroll-mt-24 h-full min-h-0">
|
|
<span id="ram" className="block h-0 scroll-mt-24" aria-hidden />
|
|
<span id="disk_io" className="block h-0 scroll-mt-24" aria-hidden />
|
|
<Chart isLoading={isLoading} isErrored={isError} className="h-full">
|
|
<ChartCard className={getUsageCardClassName(computeUsageStatus)}>
|
|
<ChartHeader align="start" className="min-h-[5.5rem]">
|
|
<ChartMetric
|
|
label="Compute"
|
|
tooltip={
|
|
supportsBurstableIO
|
|
? 'Peak CPU, memory, and disk IO usage over the last 7 days. Sustained high usage may require a larger compute size.'
|
|
: 'Peak CPU and memory usage over the last 7 days. Sustained high usage may require a larger compute size.'
|
|
}
|
|
value={formatUsagePercent(peakComputeUsage)}
|
|
status={computeUsageStatus}
|
|
/>
|
|
<div className="flex flex-wrap items-center gap-6">
|
|
<ChartMetric
|
|
label="CPU"
|
|
value={formatUsagePercent(peakCpuUsage)}
|
|
status={getUsageMetricStatus(peakCpuUsage)}
|
|
align="end"
|
|
/>
|
|
<ChartMetric
|
|
label="Memory"
|
|
value={formatUsagePercent(peakMemoryUsage)}
|
|
status={getUsageMetricStatus(peakMemoryUsage)}
|
|
align="end"
|
|
/>
|
|
{supportsBurstableIO && (
|
|
<ChartMetric
|
|
label="Disk IO"
|
|
value={formatUsagePercent(peakDiskIoUsage)}
|
|
status={getUsageMetricStatus(peakDiskIoUsage)}
|
|
align="end"
|
|
/>
|
|
)}
|
|
</div>
|
|
</ChartHeader>
|
|
<ChartContent
|
|
className="p-0"
|
|
isEmpty={isComputeEmpty}
|
|
loadingState={<ChartLoadingState className="h-48 border-x-0 border-b-0" />}
|
|
errorState={
|
|
<ChartEmptyState
|
|
className="h-48 border-x-0 border-b-0"
|
|
icon={<Activity size={16} />}
|
|
title="Failed to load usage data"
|
|
/>
|
|
}
|
|
emptyState={
|
|
<ChartEmptyState
|
|
className="h-48 border-x-0 border-b-0"
|
|
icon={<Activity size={16} />}
|
|
title="No compute data"
|
|
description="Usage data may take a few minutes to appear."
|
|
/>
|
|
}
|
|
>
|
|
<div className="h-48 px-card pb-4 pt-2">
|
|
<ChartLine
|
|
data={computeChartData}
|
|
dataKey="maxCpuUsage"
|
|
dataKeys={
|
|
supportsBurstableIO
|
|
? ['maxCpuUsage', 'ramUsage', 'diskIoConsumption']
|
|
: ['maxCpuUsage', 'ramUsage']
|
|
}
|
|
DateTimeFormat="MMM D"
|
|
isFullHeight
|
|
showGrid
|
|
showYAxis
|
|
config={COMPUTE_CHART_CONFIG}
|
|
YAxisProps={PERCENTAGE_Y_AXIS_PROPS}
|
|
/>
|
|
</div>
|
|
</ChartContent>
|
|
</ChartCard>
|
|
</Chart>
|
|
</div>
|
|
|
|
<div id="disk" className="scroll-mt-24 h-full min-h-0">
|
|
<Chart isLoading={isLoading} isErrored={isError} className="h-full">
|
|
<ChartCard className={getUsageCardClassName(diskUsageStatus)}>
|
|
<ChartHeader align="start" className="min-h-[5.5rem]">
|
|
<ChartMetric
|
|
label="Disk"
|
|
tooltip="Provisioned disk and used disk split by database, WAL, and system usage over the last 7 days."
|
|
value={formatUsagePercent(latestDiskUsage)}
|
|
status={diskUsageStatus}
|
|
/>
|
|
<div className="flex flex-wrap items-center gap-6">
|
|
<ChartMetric
|
|
label="Database"
|
|
value={
|
|
latestDiskDataPoint === undefined
|
|
? '—'
|
|
: formatBytes(latestDiskDataPoint.databaseBytes, 1)
|
|
}
|
|
align="end"
|
|
/>
|
|
<ChartMetric
|
|
label="WAL"
|
|
value={
|
|
latestDiskDataPoint === undefined
|
|
? '—'
|
|
: formatBytes(latestDiskDataPoint.walBytes, 1)
|
|
}
|
|
align="end"
|
|
/>
|
|
<ChartMetric
|
|
label="System"
|
|
value={
|
|
latestDiskDataPoint === undefined
|
|
? '—'
|
|
: formatBytes(latestDiskDataPoint.systemBytes, 1)
|
|
}
|
|
align="end"
|
|
/>
|
|
</div>
|
|
</ChartHeader>
|
|
<ChartContent
|
|
className="p-0"
|
|
isEmpty={isDiskEmpty}
|
|
loadingState={<ChartLoadingState className="h-48 border-x-0 border-b-0" />}
|
|
errorState={
|
|
<ChartEmptyState
|
|
className="h-48 border-x-0 border-b-0"
|
|
icon={<BarChart2 size={16} />}
|
|
title="Failed to load usage data"
|
|
/>
|
|
}
|
|
emptyState={
|
|
<ChartEmptyState
|
|
className="h-48 border-x-0 border-b-0"
|
|
icon={<Database size={16} />}
|
|
title="No disk data"
|
|
description="Disk metrics may take a few minutes to appear."
|
|
/>
|
|
}
|
|
>
|
|
<div className="h-48 px-card pb-4 pt-2">
|
|
<ChartLine
|
|
data={diskChartData}
|
|
dataKey="databaseUsagePercent"
|
|
dataKeys={['databaseUsagePercent', 'walUsagePercent', 'systemUsagePercent']}
|
|
DateTimeFormat="MMM D"
|
|
isFullHeight
|
|
showGrid
|
|
showYAxis
|
|
config={DISK_CHART_CONFIG}
|
|
YAxisProps={PERCENTAGE_Y_AXIS_PROPS}
|
|
/>
|
|
</div>
|
|
</ChartContent>
|
|
</ChartCard>
|
|
</Chart>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|