import { InformationCircleIcon } from '@heroicons/react/16/solid' import { X } from 'lucide-react' import { parseAsString, useQueryStates } from 'nuqs' import { useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import { LOCAL_STORAGE_KEYS, useParams } from 'common' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' import { formatDatabaseID } from 'data/read-replicas/replicas.utils' import { executeSql } from 'data/sql/execute-sql-query' import { DbQueryHook } from 'hooks/analytics/useDbQuery' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { IS_PLATFORM } from 'lib/constants' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { Button, LoadingLine, TabsList_Shadcn_, TabsTrigger_Shadcn_, Tabs_Shadcn_, Tooltip, TooltipContent, TooltipTrigger, cn, } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' import { Markdown } from '../Markdown' import { useQueryPerformanceQuery } from '../Reports/Reports.queries' import { PresetHookResult } from '../Reports/Reports.utils' import { QUERY_PERFORMANCE_REPORT_TYPES } from './QueryPerformance.constants' import { QueryPerformanceFilterBar } from './QueryPerformanceFilterBar' import { QueryPerformanceGrid } from './QueryPerformanceGrid' interface QueryPerformanceProps { queryHitRate: PresetHookResult queryPerformanceQuery: DbQueryHook } export const QueryPerformance = ({ queryHitRate, queryPerformanceQuery, }: QueryPerformanceProps) => { const { ref } = useParams() const { data: project } = useSelectedProjectQuery() const state = useDatabaseSelectorStateSnapshot() const [{ preset }, setSearchParams] = useQueryStates({ sort: parseAsString, search: parseAsString, order: parseAsString, preset: parseAsString.withDefault(QUERY_PERFORMANCE_REPORT_TYPES.MOST_TIME_CONSUMING), }) const { isLoading, isRefetching } = queryPerformanceQuery const isPrimaryDatabase = state.selectedDatabaseId === ref const formattedDatabaseId = formatDatabaseID(state.selectedDatabaseId ?? '') const [showResetgPgStatStatements, setShowResetgPgStatStatements] = useState(false) const [showBottomSection, setShowBottomSection] = useLocalStorageQuery( LOCAL_STORAGE_KEYS.QUERY_PERF_SHOW_BOTTOM_SECTION, true ) const handleRefresh = () => { queryPerformanceQuery.runQuery() queryHitRate.runQuery() } const { data: databases } = useReadReplicasQuery({ projectRef: ref }) const { data: mostTimeConsumingQueries, isLoading: isLoadingMTC } = useQueryPerformanceQuery({ preset: 'mostTimeConsuming', }) const { data: mostFrequentlyInvoked, isLoading: isLoadingMFI } = useQueryPerformanceQuery({ preset: 'mostFrequentlyInvoked', }) const { data: slowestExecutionTime, isLoading: isLoadingMMF } = useQueryPerformanceQuery({ preset: 'slowestExecutionTime', }) const QUERY_PERFORMANCE_TABS = useMemo(() => { return [ { id: QUERY_PERFORMANCE_REPORT_TYPES.MOST_TIME_CONSUMING, label: 'Most time consuming', description: 'Lists queries ordered by their cumulative total execution time.', isLoading: isLoadingMTC, max: (mostTimeConsumingQueries ?? []).length > 0 ? Math.max(...(mostTimeConsumingQueries ?? []).map((x: any) => x.total_time)).toFixed(2) : undefined, }, { id: QUERY_PERFORMANCE_REPORT_TYPES.MOST_FREQUENT, label: 'Most frequent', description: 'Lists queries in order of their execution count', isLoading: isLoadingMFI, max: (mostFrequentlyInvoked ?? []).length > 0 ? Math.max(...(mostFrequentlyInvoked ?? []).map((x: any) => x.calls)).toFixed(2) : undefined, }, { id: QUERY_PERFORMANCE_REPORT_TYPES.SLOWEST_EXECUTION, label: 'Slowest execution', description: 'Lists queries ordered by their maximum execution time', isLoading: isLoadingMMF, max: (slowestExecutionTime ?? []).length > 0 ? Math.max(...(slowestExecutionTime ?? []).map((x: any) => x.max_time)).toFixed(2) : undefined, }, ] }, [ isLoadingMFI, isLoadingMMF, isLoadingMTC, mostFrequentlyInvoked, mostTimeConsumingQueries, slowestExecutionTime, ]) useEffect(() => { state.setSelectedDatabaseId(ref) }, [ref]) return ( <> setSearchParams({ preset: value })} > {QUERY_PERFORMANCE_TABS.map((tab) => { const tabMax = Number(tab.max) const maxValue = tab.id !== QUERY_PERFORMANCE_REPORT_TYPES.MOST_FREQUENT ? tabMax > 1000 ? (tabMax / 1000).toFixed(2) : tabMax.toFixed(0) : tabMax.toLocaleString() return ( {tab.id === preset && (
)}
{tab.label} {tab.description}
{tab.isLoading ? ( ) : tab.max === undefined ? ( No data yet ) : ( {maxValue} {tab.id !== QUERY_PERFORMANCE_REPORT_TYPES.MOST_FREQUENT ? tabMax > 1000 ? 's' : 'ms' : ' calls'} )} {tab.id === preset && (
)} ) })} setShowResetgPgStatStatements(true)} />

Reset report

Consider resetting the analysis after optimizing any queries

How is this report generated?

Inspect your database for potential issues

setShowResetgPgStatStatements(false)} onConfirm={async () => { const connectionString = databases?.find( (db) => db.identifier === state.selectedDatabaseId )?.connectionString if (IS_PLATFORM && !connectionString) { return toast.error('Unable to run query: Connection string is missing') } try { await executeSql({ projectRef: project?.ref, connectionString, sql: `SELECT pg_stat_statements_reset();`, }) handleRefresh() setShowResetgPgStatStatements(false) } catch (error: any) { toast.error(`Failed to reset analysis: ${error.message}`) } }} >

This will reset the pg_stat_statements table in the extensions schema on your{' '} {isPrimaryDatabase ? 'primary database' : `read replica (ID: ${formattedDatabaseId})`} , which is used to calculate query performance. This data will repopulate immediately after.

) }