diff --git a/apps/studio/components/interfaces/Observability/DatabaseInfrastructureSection.tsx b/apps/studio/components/interfaces/Observability/DatabaseInfrastructureSection.tsx index 61f6dbfca12..9c92aac9781 100644 --- a/apps/studio/components/interfaces/Observability/DatabaseInfrastructureSection.tsx +++ b/apps/studio/components/interfaces/Observability/DatabaseInfrastructureSection.tsx @@ -169,8 +169,8 @@ export const DatabaseInfrastructureSection = ({ - - Connections + + Peak Connections @@ -178,7 +178,7 @@ export const DatabaseInfrastructureSection = ({
{errorMessage}
) : connections.max > 0 ? ( - {connections.current}/{connections.max} + {connections.peak}/{connections.max} ) : ( -- diff --git a/apps/studio/components/interfaces/Observability/DatabaseInfrastructureSection.utils.test.ts b/apps/studio/components/interfaces/Observability/DatabaseInfrastructureSection.utils.test.ts index de1bdbaf819..75e97896df7 100644 --- a/apps/studio/components/interfaces/Observability/DatabaseInfrastructureSection.utils.test.ts +++ b/apps/studio/components/interfaces/Observability/DatabaseInfrastructureSection.utils.test.ts @@ -192,103 +192,114 @@ describe('parseInfrastructureMetrics', () => { }) describe('parseConnectionsData', () => { + const buildResponse = ( + values: Array + ): 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 }) }) }) diff --git a/apps/studio/components/interfaces/Observability/DatabaseInfrastructureSection.utils.ts b/apps/studio/components/interfaces/Observability/DatabaseInfrastructureSection.utils.ts index 03cf8e4ebbc..85ae7b5f058 100644 --- a/apps/studio/components/interfaces/Observability/DatabaseInfrastructureSection.utils.ts +++ b/apps/studio/components/interfaces/Observability/DatabaseInfrastructureSection.utils.ts @@ -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 } } diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx index 203ecbb0b12..f6a3427ba7c 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx @@ -201,7 +201,7 @@ export const PrimaryNode = ({ data }: NodeProps>) => { <> ยท - {connections.current}/{connections.max} conns + {connections.peak}/{connections.max} conns )} diff --git a/apps/studio/hooks/analytics/useComputeMetrics.ts b/apps/studio/hooks/analytics/useComputeMetrics.ts index 37b84320451..6ca5e0e42e6 100644 --- a/apps/studio/hooks/analytics/useComputeMetrics.ts +++ b/apps/studio/hooks/analytics/useComputeMetrics.ts @@ -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 }