mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
fix(observability): align overview Connections count with details view DEBUG-75 (#46271)
This commit is contained in:
1 parent
00afaeac73
commit
2c915ca9fb
5 files changed
+100
-85
No files matched your search
@@ -169,8 +169,8 @@ export const DatabaseInfrastructureSection = ({
|
||||
<Link href={databaseReportUrl} className="block group">
|
||||
<MetricCard isLoading={infraLoading}>
|
||||
<MetricCardHeader href={databaseReportUrl} linkTooltip="Go to database report">
|
||||
<MetricCardLabel tooltip="Active database connections (current/max). Monitor to avoid connection exhaustion">
|
||||
Connections
|
||||
<MetricCardLabel tooltip="Highest concurrent database connections observed in the selected window, against the connection limit. Monitor to avoid connection exhaustion.">
|
||||
Peak Connections
|
||||
</MetricCardLabel>
|
||||
</MetricCardHeader>
|
||||
<MetricCardContent>
|
||||
@@ -178,7 +178,7 @@ export const DatabaseInfrastructureSection = ({
|
||||
<div className="text-xs text-destructive wrap-break-word">{errorMessage}</div>
|
||||
) : connections.max > 0 ? (
|
||||
<MetricCardValue>
|
||||
{connections.current}/{connections.max}
|
||||
{connections.peak}/{connections.max}
|
||||
</MetricCardValue>
|
||||
) : (
|
||||
<MetricCardValue>--</MetricCardValue>
|
||||
|
||||
+80
-71
@@ -192,103 +192,114 @@ describe('parseInfrastructureMetrics', () => {
|
||||
})
|
||||
|
||||
describe('parseConnectionsData', () => {
|
||||
const buildResponse = (
|
||||
values: Array<string | number | undefined>
|
||||
): InfraMonitoringMultiResponse => ({
|
||||
data: values.map((v, i) => ({
|
||||
period_start: `2026-05-22T00:0${i}:00.000Z`,
|
||||
values: { pg_stat_database_num_backends: v as string | undefined },
|
||||
})),
|
||||
series: {
|
||||
pg_stat_database_num_backends: {
|
||||
format: 'number',
|
||||
total: 0,
|
||||
totalAverage: 0,
|
||||
yAxisLimit: 100,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
it('returns zeros when data is undefined', () => {
|
||||
expect(parseConnectionsData(undefined, undefined)).toEqual({ current: 0, max: 0 })
|
||||
expect(parseConnectionsData(undefined, undefined)).toEqual({ peak: 0, max: 0 })
|
||||
expect(parseConnectionsData(undefined, { maxConnections: 100 })).toEqual({
|
||||
current: 0,
|
||||
max: 0,
|
||||
peak: 0,
|
||||
max: 100,
|
||||
})
|
||||
})
|
||||
|
||||
it('parses connections data correctly', () => {
|
||||
const mockInfraData: InfraMonitoringMultiResponse = {
|
||||
data: [],
|
||||
series: {
|
||||
pg_stat_database_num_backends: {
|
||||
format: 'number',
|
||||
total: 150,
|
||||
totalAverage: 25.5,
|
||||
yAxisLimit: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
it('returns the peak value across the window, not totalAverage', () => {
|
||||
const mockInfraData = buildResponse([4, 4, 6, 4, 5, 4])
|
||||
// totalAverage in series is intentionally stale to ensure we ignore it
|
||||
mockInfraData.series.pg_stat_database_num_backends.totalAverage = 25
|
||||
|
||||
const mockMaxData = { maxConnections: 100 }
|
||||
const result = parseConnectionsData(mockInfraData, { maxConnections: 100 })
|
||||
|
||||
const result = parseConnectionsData(mockInfraData, mockMaxData)
|
||||
|
||||
expect(result).toEqual({ current: 26, max: 100 }) // 25.5 rounded to 26
|
||||
expect(result).toEqual({ peak: 6, max: 100 })
|
||||
})
|
||||
|
||||
it('handles string values for connections', () => {
|
||||
const mockInfraData: InfraMonitoringMultiResponse = {
|
||||
data: [],
|
||||
series: {
|
||||
pg_stat_database_num_backends: {
|
||||
format: 'number',
|
||||
total: 150,
|
||||
totalAverage: '30.7',
|
||||
yAxisLimit: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
it('rounds the peak value', () => {
|
||||
const mockInfraData = buildResponse([3, 5.6, 4])
|
||||
|
||||
const mockMaxData = { maxConnections: 100 }
|
||||
const result = parseConnectionsData(mockInfraData, { maxConnections: 100 })
|
||||
|
||||
const result = parseConnectionsData(mockInfraData, mockMaxData)
|
||||
|
||||
expect(result).toEqual({ current: 31, max: 100 }) // 30.7 rounded to 31
|
||||
expect(result).toEqual({ peak: 6, max: 100 })
|
||||
})
|
||||
|
||||
it('returns current connections even when maxConnectionsData is undefined', () => {
|
||||
const mockInfraData: InfraMonitoringMultiResponse = {
|
||||
data: [],
|
||||
series: {
|
||||
pg_stat_database_num_backends: {
|
||||
format: 'number',
|
||||
total: 150,
|
||||
totalAverage: 25,
|
||||
yAxisLimit: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
it('handles string values when computing peak', () => {
|
||||
const mockInfraData = buildResponse(['4', '7', '5.4'])
|
||||
|
||||
const result = parseConnectionsData(mockInfraData, { maxConnections: 100 })
|
||||
|
||||
expect(result).toEqual({ peak: 7, max: 100 })
|
||||
})
|
||||
|
||||
it('skips missing values when computing peak', () => {
|
||||
const mockInfraData = buildResponse([5, undefined, 7, undefined])
|
||||
|
||||
const result = parseConnectionsData(mockInfraData, { maxConnections: 100 })
|
||||
|
||||
expect(result).toEqual({ peak: 7, max: 100 })
|
||||
})
|
||||
|
||||
it('skips empty-string values when computing peak', () => {
|
||||
const mockInfraData = buildResponse(['5', '', '7', ''])
|
||||
|
||||
const result = parseConnectionsData(mockInfraData, { maxConnections: 100 })
|
||||
|
||||
expect(result).toEqual({ peak: 7, max: 100 })
|
||||
})
|
||||
|
||||
it('returns peak connections even when maxConnectionsData is undefined', () => {
|
||||
const mockInfraData = buildResponse([4, 9, 6])
|
||||
|
||||
const result = parseConnectionsData(mockInfraData, undefined)
|
||||
|
||||
expect(result).toEqual({ current: 25, max: 0 })
|
||||
expect(result).toEqual({ peak: 9, max: 0 })
|
||||
})
|
||||
|
||||
it('returns 0 max when maxConnections is missing from data object', () => {
|
||||
const mockInfraData: InfraMonitoringMultiResponse = {
|
||||
data: [],
|
||||
series: {
|
||||
pg_stat_database_num_backends: {
|
||||
format: 'number',
|
||||
total: 150,
|
||||
totalAverage: 25,
|
||||
yAxisLimit: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
const mockInfraData = buildResponse([4])
|
||||
|
||||
const mockMaxData = {}
|
||||
const result = parseConnectionsData(mockInfraData, {})
|
||||
|
||||
const result = parseConnectionsData(mockInfraData, mockMaxData)
|
||||
|
||||
expect(result).toEqual({ current: 25, max: 0 })
|
||||
expect(result).toEqual({ peak: 4, max: 0 })
|
||||
})
|
||||
|
||||
it('returns 0 current when connections metric is missing', () => {
|
||||
it('returns 0 peak when data array is empty', () => {
|
||||
const mockInfraData: InfraMonitoringMultiResponse = {
|
||||
data: [],
|
||||
series: {},
|
||||
}
|
||||
|
||||
const mockMaxData = { maxConnections: 100 }
|
||||
const result = parseConnectionsData(mockInfraData, { maxConnections: 100 })
|
||||
|
||||
const result = parseConnectionsData(mockInfraData, mockMaxData)
|
||||
expect(result).toEqual({ peak: 0, max: 100 })
|
||||
})
|
||||
|
||||
expect(result).toEqual({ current: 0, max: 100 })
|
||||
it('returns 0 peak when connections metric is missing from data points', () => {
|
||||
const mockInfraData: InfraMonitoringMultiResponse = {
|
||||
data: [
|
||||
{
|
||||
period_start: '2026-05-22T00:00:00.000Z',
|
||||
values: { avg_cpu_usage: '50' },
|
||||
},
|
||||
],
|
||||
series: {},
|
||||
}
|
||||
|
||||
const result = parseConnectionsData(mockInfraData, { maxConnections: 100 })
|
||||
|
||||
expect(result).toEqual({ peak: 0, max: 100 })
|
||||
})
|
||||
|
||||
it('handles single-response format (legacy)', () => {
|
||||
@@ -300,10 +311,8 @@ describe('parseConnectionsData', () => {
|
||||
yAxisLimit: 100,
|
||||
}
|
||||
|
||||
const mockMaxData = { maxConnections: 100 }
|
||||
const result = parseConnectionsData(mockInfraData, { maxConnections: 100 })
|
||||
|
||||
const result = parseConnectionsData(mockInfraData, mockMaxData)
|
||||
|
||||
expect(result).toEqual({ current: 0, max: 100 })
|
||||
expect(result).toEqual({ peak: 0, max: 100 })
|
||||
})
|
||||
})
|
||||
+15
-9
@@ -64,7 +64,7 @@ export function parseInfrastructureMetrics(
|
||||
}
|
||||
|
||||
type ConnectionsData = {
|
||||
current: number
|
||||
peak: number
|
||||
max: number
|
||||
}
|
||||
|
||||
@@ -76,15 +76,21 @@ export function parseConnectionsData(
|
||||
infraData: InfraMonitoringResponse | undefined,
|
||||
maxConnectionsData: MaxConnectionsData | undefined
|
||||
): ConnectionsData {
|
||||
if (!infraData) {
|
||||
return { current: 0, max: 0 }
|
||||
const max = maxConnectionsData?.maxConnections ?? 0
|
||||
|
||||
if (!infraData || !('series' in infraData)) {
|
||||
return { peak: 0, max }
|
||||
}
|
||||
|
||||
const series = 'series' in infraData ? infraData.series : {}
|
||||
// Show the highest connection count observed in the selected window rather
|
||||
// than the window's totalAverage. The average varies with bucket granularity
|
||||
// (e.g. 1h vs 1m) and is hard to reason about; peak is interval-stable and
|
||||
// the more actionable signal for headroom against the connection limit.
|
||||
let peak = 0
|
||||
for (const point of infraData.data ?? []) {
|
||||
const n = parseNumericValue(point?.values?.pg_stat_database_num_backends)
|
||||
if (n > peak) peak = n
|
||||
}
|
||||
|
||||
const currentVal = series.pg_stat_database_num_backends?.totalAverage
|
||||
const current = Math.round(parseNumericValue(currentVal))
|
||||
const max = maxConnectionsData?.maxConnections || 0
|
||||
|
||||
return { current, max }
|
||||
return { peak: Math.round(peak), max }
|
||||
}
|
||||
+1
-1
@@ -201,7 +201,7 @@ export const PrimaryNode = ({ data }: NodeProps<Node<PrimaryNodeData>>) => {
|
||||
<>
|
||||
<span className="text-foreground-lighter">·</span>
|
||||
<span className="text-foreground-light">
|
||||
{connections.current}/{connections.max} conns
|
||||
{connections.peak}/{connections.max} conns
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -13,7 +13,7 @@ export type ComputeMetrics = {
|
||||
cpu: number
|
||||
disk: number
|
||||
memory: number
|
||||
connections: { current: number; max: number }
|
||||
connections: { peak: number; max: number }
|
||||
isLoading: boolean
|
||||
isError: boolean
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user