mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
Add charts for db and storage egress
This commit is contained in:
1 parent
b792c5afcb
commit
940dfc8a44
4 files changed
+221
-46
No files matched your search
@@ -1,24 +1,39 @@
|
||||
import { useParams } from 'common'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { DATE_FORMAT } from 'lib/constants'
|
||||
import { useDailyStatsQuery } from 'data/analytics/daily-stats-query'
|
||||
import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query'
|
||||
import { useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
import BarChart from './BarChart'
|
||||
import { generateUsageData } from './Usage.utils'
|
||||
import { Button } from 'ui'
|
||||
import SparkBar from 'components/ui/SparkBar'
|
||||
import { formatBytes } from 'lib/helpers'
|
||||
|
||||
const Bandwidth = () => {
|
||||
const { ref } = useParams()
|
||||
const { data: usage } = useProjectUsageQuery({ projectRef: ref })
|
||||
const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref })
|
||||
const { current_period_start, current_period_end } = subscription?.billing ?? {}
|
||||
const startDate = new Date((current_period_start ?? 0) * 1000).toISOString()
|
||||
const endDate = new Date((current_period_end ?? 0) * 1000).toISOString()
|
||||
|
||||
const currentDate = 1682669326710 / 1000
|
||||
const startDate = dayjs.unix(currentDate).subtract(24, 'hour').format(DATE_FORMAT)
|
||||
const endDate = dayjs.unix(currentDate).format(DATE_FORMAT)
|
||||
|
||||
const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({
|
||||
const { db_egress } = usage ?? {}
|
||||
const dbEgressExcess = (db_egress?.usage ?? 0) - (db_egress?.limit ?? 0)
|
||||
const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({
|
||||
projectRef: ref,
|
||||
attribute: 'disk_io_budget',
|
||||
attribute: 'total_rest_egress',
|
||||
interval: '1d',
|
||||
startDate,
|
||||
endDate,
|
||||
})
|
||||
|
||||
const { storage_egress } = usage ?? {}
|
||||
const storageEgressExcess = (storage_egress?.usage ?? 0) - (storage_egress?.limit ?? 0)
|
||||
const { data: storageEgressData, isLoading: isLoadingStorageEgressData } = useDailyStatsQuery({
|
||||
projectRef: ref,
|
||||
attribute: 'total_storage_egress',
|
||||
interval: '1d',
|
||||
startDate,
|
||||
endDate,
|
||||
interval: '1h',
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -31,24 +46,57 @@ const Bandwidth = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* DATABASE EGRESS - need to fix if no value yet (API will return period_start as 0 in first data point) */}
|
||||
<div className="border-b">
|
||||
<div className="1xl:px-28 mx-auto flex flex-col gap-10 px-5 lg:px-16 2xl:px-32 py-16">
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-5">
|
||||
<div className="sticky top-16">
|
||||
<p className="text-base">IO Budget</p>
|
||||
<p className="text-base">Database Egress</p>
|
||||
<p className="text-sm text-scale-1000">Some description here</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-7 space-y-6">
|
||||
<div className="space-y-1">
|
||||
<p>IO budget per day</p>
|
||||
<p className="text-sm text-scale-1000">
|
||||
Just FYI for now its a percentage for IO budget, which is different from what the
|
||||
figma designs are (in minutes). We will need o11y or infra to help update this
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm">
|
||||
Egress {subscription?.tier.key.toLowerCase()} quota usage
|
||||
</p>
|
||||
<Button type="default" size="tiny" onClick={() => {}}>
|
||||
Upgrade project
|
||||
</Button>
|
||||
</div>
|
||||
<SparkBar
|
||||
type="horizontal"
|
||||
barClass="bg-scale-1200"
|
||||
value={db_egress?.usage ?? 0}
|
||||
max={db_egress?.limit ?? 0}
|
||||
/>
|
||||
<div>
|
||||
<div className="flex items-center justify-between border-b py-1">
|
||||
<p className="text-xs text-scale-1000">
|
||||
Included in {subscription?.tier.name.toLowerCase()}
|
||||
</p>
|
||||
<p className="text-xs">{formatBytes(db_egress?.limit ?? 0)}</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-b py-1">
|
||||
<p className="text-xs text-scale-1000">Used</p>
|
||||
<p className="text-xs">{formatBytes(db_egress?.usage ?? 0)}</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<p className="text-xs text-scale-1000">Extra volume used this month</p>
|
||||
<p className="text-xs">
|
||||
{dbEgressExcess < 0 ? formatBytes(0) : formatBytes(dbEgressExcess)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isLoadingIoBudgetData ? (
|
||||
<div className="space-y-1">
|
||||
<p>Database egress over time</p>
|
||||
<p className="text-sm text-scale-1000">Some description here</p>
|
||||
</div>
|
||||
{isLoadingDbEgressData ? (
|
||||
<div className="space-y-2">
|
||||
<ShimmeringLoader />
|
||||
<ShimmeringLoader className="w-3/4" />
|
||||
@@ -56,16 +104,78 @@ const Bandwidth = () => {
|
||||
</div>
|
||||
) : (
|
||||
<BarChart
|
||||
attribute="disk_io_budget"
|
||||
data={generateUsageData('disk_io_budget', 30)}
|
||||
reference={{
|
||||
value: 65,
|
||||
label: 'FREE QUOTA',
|
||||
x: 60,
|
||||
y: 49,
|
||||
width: 200,
|
||||
height: 24,
|
||||
}}
|
||||
attribute="total_rest_egress"
|
||||
data={dbEgressData?.data ?? []}
|
||||
unit={undefined}
|
||||
yDomain={[0, 100]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* STORAGE EGRESS - need to fix if no value yet (API will return period_start as 0 in first data point) */}
|
||||
<div className="border-b">
|
||||
<div className="1xl:px-28 mx-auto flex flex-col gap-10 px-5 lg:px-16 2xl:px-32 py-16">
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-5">
|
||||
<div className="sticky top-16">
|
||||
<p className="text-base">Storage Egress</p>
|
||||
<p className="text-sm text-scale-1000">Some description here</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-7 space-y-6">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm">
|
||||
Storage egress {subscription?.tier.key.toLowerCase()} quota usage
|
||||
</p>
|
||||
<Button type="default" size="tiny" onClick={() => {}}>
|
||||
Upgrade project
|
||||
</Button>
|
||||
</div>
|
||||
<SparkBar
|
||||
type="horizontal"
|
||||
barClass="bg-scale-1200"
|
||||
value={storage_egress?.usage ?? 0}
|
||||
max={storage_egress?.limit ?? 0}
|
||||
/>
|
||||
<div>
|
||||
<div className="flex items-center justify-between border-b py-1">
|
||||
<p className="text-xs text-scale-1000">
|
||||
Included in {subscription?.tier.name.toLowerCase()}
|
||||
</p>
|
||||
<p className="text-xs">{formatBytes(storage_egress?.limit ?? 0)}</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-b py-1">
|
||||
<p className="text-xs text-scale-1000">Used</p>
|
||||
<p className="text-xs">{formatBytes(storage_egress?.usage ?? 0)}</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<p className="text-xs text-scale-1000">Extra volume used this month</p>
|
||||
<p className="text-xs">
|
||||
{storageEgressExcess < 0 ? formatBytes(0) : formatBytes(storageEgressExcess)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p>Storage egress over time</p>
|
||||
<p className="text-sm text-scale-1000">Some description here</p>
|
||||
</div>
|
||||
{isLoadingStorageEgressData ? (
|
||||
<div className="space-y-2">
|
||||
<ShimmeringLoader />
|
||||
<ShimmeringLoader className="w-3/4" />
|
||||
<ShimmeringLoader className="w-1/2" />
|
||||
</div>
|
||||
) : (
|
||||
<BarChart
|
||||
attribute="total_rest_egress"
|
||||
data={storageEgressData?.data ?? []}
|
||||
unit={undefined}
|
||||
yDomain={[0, 100]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,6 @@ import { useParams } from 'common'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query'
|
||||
import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { DATE_FORMAT } from 'lib/constants'
|
||||
import BarChart from './BarChart'
|
||||
|
||||
const Infrastructure = () => {
|
||||
@@ -16,18 +14,26 @@ const Infrastructure = () => {
|
||||
const { data: cpuUsageData, isLoading: isLoadingCpuUsageData } = useInfraMonitoringQuery({
|
||||
projectRef: ref,
|
||||
attribute: 'cpu_usage',
|
||||
interval: '1d',
|
||||
startDate,
|
||||
endDate,
|
||||
interval: '1d',
|
||||
modifier: (x: number) => x * 100,
|
||||
})
|
||||
|
||||
const { data: memoryUsageData, isLoading: isLoadingMemoryUsageData } = useInfraMonitoringQuery({
|
||||
projectRef: ref,
|
||||
attribute: 'ram_usage',
|
||||
interval: '1d',
|
||||
startDate,
|
||||
endDate,
|
||||
})
|
||||
|
||||
const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({
|
||||
projectRef: ref,
|
||||
attribute: 'disk_io_budget',
|
||||
interval: '1d',
|
||||
startDate,
|
||||
endDate,
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -108,6 +114,40 @@ const Infrastructure = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* DISK IO BUDGET */}
|
||||
<div className="border-b">
|
||||
<div className="1xl:px-28 mx-auto flex flex-col gap-10 px-5 lg:px-16 2xl:px-32 py-16">
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-5">
|
||||
<div className="sticky top-16">
|
||||
<p className="text-base">IO Budget</p>
|
||||
<p className="text-sm text-scale-1000">Some description here</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-7 space-y-6">
|
||||
<div className="space-y-1">
|
||||
<p>Disk IO budget per day</p>
|
||||
<p className="text-sm text-scale-1000">Some description here</p>
|
||||
</div>
|
||||
{isLoadingIoBudgetData ? (
|
||||
<div className="space-y-2">
|
||||
<ShimmeringLoader />
|
||||
<ShimmeringLoader className="w-3/4" />
|
||||
<ShimmeringLoader className="w-1/2" />
|
||||
</div>
|
||||
) : (
|
||||
<BarChart
|
||||
attribute="disk_io_budget"
|
||||
data={ioBudgetData?.data ?? []}
|
||||
unit={ioBudgetData?.format}
|
||||
yDomain={[0, 100]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -75,9 +75,9 @@ const Usage = () => {
|
||||
</div>
|
||||
|
||||
<Infrastructure />
|
||||
{/* <Bandwidth />
|
||||
<SizeAndCounts />
|
||||
<Activity /> */}
|
||||
<Bandwidth />
|
||||
{/* <SizeAndCounts /> */}
|
||||
{/* <Activity /> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ import { API_URL } from 'lib/constants'
|
||||
import { useCallback } from 'react'
|
||||
import { analyticsKeys } from './keys'
|
||||
import { AnalyticsData } from './constants'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export type DailyStatsVariables = {
|
||||
projectRef?: string
|
||||
attribute?:
|
||||
attribute:
|
||||
| 'total_realtime_egress'
|
||||
| 'total_realtime_requests'
|
||||
| 'total_realtime_ingress'
|
||||
@@ -50,17 +51,11 @@ export type DailyStatsVariables = {
|
||||
endDate?: string
|
||||
interval?: string
|
||||
dateFormat?: string
|
||||
modifier?: (x: number) => number
|
||||
}
|
||||
|
||||
export async function getDailyStats(
|
||||
{
|
||||
projectRef,
|
||||
attribute,
|
||||
startDate,
|
||||
endDate,
|
||||
interval = '1d',
|
||||
dateFormat = 'DD MMM',
|
||||
}: DailyStatsVariables,
|
||||
{ projectRef, attribute, startDate, endDate, interval = '1d' }: DailyStatsVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!projectRef) throw new Error('Project ref is required')
|
||||
@@ -82,14 +77,44 @@ export type DailyStatsData = Awaited<ReturnType<typeof getDailyStats>>
|
||||
export type DailyStatsError = unknown
|
||||
|
||||
export const useDailyStatsQuery = <TData = DailyStatsData>(
|
||||
{ projectRef, attribute, startDate, endDate, interval = '1d' }: DailyStatsVariables,
|
||||
{
|
||||
projectRef,
|
||||
attribute,
|
||||
startDate,
|
||||
endDate,
|
||||
interval = '1d',
|
||||
dateFormat,
|
||||
modifier,
|
||||
}: DailyStatsVariables,
|
||||
{ enabled = true, ...options }: UseQueryOptions<DailyStatsData, DailyStatsError, TData> = {}
|
||||
) =>
|
||||
useQuery<DailyStatsData, DailyStatsError, TData>(
|
||||
analyticsKeys.dailyStats(projectRef, { attribute, startDate, endDate, interval }),
|
||||
({ signal }) => getDailyStats({ projectRef, attribute, startDate, endDate, interval }, signal),
|
||||
{
|
||||
enabled: enabled && typeof projectRef !== 'undefined',
|
||||
enabled:
|
||||
enabled &&
|
||||
typeof projectRef !== 'undefined' &&
|
||||
typeof attribute !== 'undefined' &&
|
||||
typeof startDate !== 'undefined' &&
|
||||
typeof endDate !== 'undefined',
|
||||
// @ts-ignore
|
||||
select(data) {
|
||||
return {
|
||||
...data,
|
||||
data: data.data.map((x) => {
|
||||
return {
|
||||
...x,
|
||||
[attribute]:
|
||||
modifier !== undefined ? modifier(Number(x[attribute])) : Number(x[attribute]),
|
||||
period_start:
|
||||
Number(x.period_start) > 0
|
||||
? dayjs(x.period_start).format(dateFormat)
|
||||
: x.period_start,
|
||||
}
|
||||
}),
|
||||
}
|
||||
},
|
||||
...options,
|
||||
}
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user