diff --git a/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.constants.ts b/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.constants.ts index ab07cd26b47..f54c683b4af 100644 --- a/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.constants.ts +++ b/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.constants.ts @@ -5,7 +5,7 @@ // - Shorter threshold as it indicates a lock export const WARN_DURATION_ACTIVE_QUERY = 30 // seconds export const WARN_DURATION_IDLE_TXN = 10 // seconds - +export const WARN_DURATION_BLOCKED = 10 // seconds export const WARN_TOP_BLOCKER = 3 // If the query is blocking more than 3 queries export const QUERY_STATE_TOOLTIP = { diff --git a/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.utils.ts b/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.utils.ts index 39316c92365..81457f80a89 100644 --- a/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.utils.ts +++ b/apps/studio/components/interfaces/Observability/DatabaseConnections/DatabaseConnections.utils.ts @@ -25,3 +25,16 @@ export const getBadgeVariant = (activity: DatabaseActivity) => { if (state === 'idle in transaction' || state === 'idle in transaction (aborted)') return 'warning' return 'default' } + +// Resolves the timestamp that duration should be measured from for a given state - +// query_start while actively running, transaction_start while idle in transaction. +export const getActivityStart = (activity: DatabaseActivity) => { + if (activity.state === 'active') return activity.query_start + if ( + activity.state === 'idle in transaction' || + activity.state === 'idle in transaction (aborted)' + ) { + return activity.transaction_start + } + return null +} diff --git a/apps/studio/components/interfaces/Observability/DatabaseConnections/Overview.tsx b/apps/studio/components/interfaces/Observability/DatabaseConnections/Overview.tsx index c7c64e60576..d8224a1ffa7 100644 --- a/apps/studio/components/interfaces/Observability/DatabaseConnections/Overview.tsx +++ b/apps/studio/components/interfaces/Observability/DatabaseConnections/Overview.tsx @@ -11,9 +11,11 @@ import { import { WARN_DURATION_ACTIVE_QUERY, + WARN_DURATION_BLOCKED, WARN_DURATION_IDLE_TXN, WARN_TOP_BLOCKER, } from './DatabaseConnections.constants' +import { getActivityStart } from './DatabaseConnections.utils' import { formatDuration } from '@/components/interfaces/QueryPerformance/QueryPerformance.utils' import { useDatabaseRolesQuery } from '@/data/database-roles/database-roles-query' import { useDatabaseActivityQuery, type DatabaseActivity } from '@/data/database/activity-query' @@ -42,7 +44,23 @@ export const Overview = ({ live }: OverviewProps) => { { refetchOnWindowFocus: live, refetchInterval: live ? 3000 : false } ) const activeQueries = (data ?? []).filter((x) => x.state === 'active') + const blockedQueries = (data ?? []).filter((x) => x.blocked_by.length > 0) + const warnBlockedQueries = blockedQueries.some((activity) => { + const start = getActivityStart(activity) + if (!start) return false + return dayjs().utc().diff(dayjs(start).utc(), 'second') >= WARN_DURATION_BLOCKED + }) + const longestBlockedQuery = blockedQueries.reduce<{ + activity: DatabaseActivity + duration: number + } | null>((longest, activity) => { + const start = getActivityStart(activity) + if (!start) return longest + const duration = Math.max(dayjs().utc().diff(dayjs(start).utc(), 'second'), 0) + return longest === null || duration > longest.duration ? { activity, duration } : longest + }, null) + const idleInTransactionQueries = (data ?? []).filter((x) => { const isIdleInTransaction = x.state === 'idle in transaction' || x.state === 'idle in transaction (aborted)' @@ -53,7 +71,7 @@ export const Overview = ({ live }: OverviewProps) => { const longestRunningQuery = (data ?? []) .filter((x) => LONG_RUNNING_STATES.includes(x.state)) .reduce<{ activity: DatabaseActivity; duration: number } | null>((longest, activity) => { - const start = activity.state === 'active' ? activity.query_start : activity.transaction_start + const start = getActivityStart(activity) if (!start) return longest const duration = Math.max(dayjs().utc().diff(dayjs(start).utc(), 'second'), 0) return longest === null || duration > longest.duration ? { activity, duration } : longest @@ -182,11 +200,11 @@ export const Overview = ({ live }: OverviewProps) => {

@@ -204,8 +222,32 @@ export const Overview = ({ live }: OverviewProps) => { - - {blockedQueries.length} + + {blockedQueries.length} + {warnBlockedQueries && longestBlockedQuery && ( + <> + ยท + setSelectedPid(longestBlockedQuery.activity.pid)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + setSelectedPid(longestBlockedQuery.activity.pid) + } + }} + > + PID {longestBlockedQuery.activity.pid} blocked for{' '} + + {formatDuration(longestBlockedQuery.duration * 1000, 0)} + + + + )}