Files
supabase/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.constants.ts
kemal.earth 70a64f8c00 feat(studio): query performance metrics chart (#39431)
* feat: setup chart area and tabs

This sets up the area where we can expect the insights chart as well as the tabs mechanism.

* feat: parse pg_stat_monitor logs as json

* feat: create query perf chart utils and move transfrom function

Created a utils file for our QueryPerformanceChart component. This moves the logs to JSON transform function there.

* feat: add timerange to chart

* feat: add date selector to query perf overview

This adds the selector to the top right of the page allowing the user to switch between last hour, 3 hours and 24 hours

* feat: modify chart component to accomodate hiding bits

* feat: add metrics to each tab

* chore: update to 60 min by default and some css

* feat: centralise data parsing for logs

* feat: clean up filters bar

This rewires the export to give you the aggregate pg_stat_monitor data. Also removes unused buttons and filters.

* feat: percentiles for query latency chart

* feat: filter out non evenets from pg_stat_monitor logs

* feat: utils for cache misses and hits

* feat: add selected query to chart on click

* feat: add click through to query panel

* chore: tidy up files

* chore: distinction between selected and open panel

* feat: move query performance fully into reports area

* fix: preserve query params on reports link

* fix: remove right icon syntax in report menu

* chore: remove cache misses from cache chart

* refactor: backwards compatibility for statements if right db version isnt available

* chore: delete randomly generated empty file

* chore: tidy up unused imports and vars

* chore: remove console logs

* chore: remove isMounted from query perf

* fix: cmd k query perf path

* feat: simplify query latency only p50 and p95

This seems to give us a more accurate reading as we can calculate these two

* fix: cache hit rate not showing inside query details

* chore: chart bg colour adjust

So it contrasts a little better on light mode.

* feat: show selected query on other verticals

* feat: bring back symlink in advisors
2025-10-15 13:39:29 +01:00

134 lines
4.4 KiB
TypeScript

export enum QUERY_PERFORMANCE_REPORT_TYPES {
MOST_TIME_CONSUMING = 'most_time_consuming',
MOST_FREQUENT = 'most_frequent',
SLOWEST_EXECUTION = 'slowest_execution',
UNIFIED = 'unified',
}
export const QUERY_PERFORMANCE_PRESET_MAP = {
[QUERY_PERFORMANCE_REPORT_TYPES.MOST_TIME_CONSUMING]: 'mostTimeConsuming',
[QUERY_PERFORMANCE_REPORT_TYPES.MOST_FREQUENT]: 'mostFrequentlyInvoked',
[QUERY_PERFORMANCE_REPORT_TYPES.SLOWEST_EXECUTION]: 'slowestExecutionTime',
[QUERY_PERFORMANCE_REPORT_TYPES.UNIFIED]: 'unified',
} as const
export const QUERY_PERFORMANCE_COLUMNS = [
{ id: 'query', name: 'Query', description: undefined, minWidth: 500 },
{ id: 'prop_total_time', name: 'Time consumed', description: undefined, minWidth: 150 },
{ id: 'calls', name: 'Count', description: undefined, minWidth: 100 },
{ id: 'max_time', name: 'Max time', description: undefined, minWidth: 100 },
{ id: 'mean_time', name: 'Mean time', description: undefined, minWidth: 100 },
{ id: 'min_time', name: 'Min time', description: undefined, minWidth: 100 },
{ id: 'rows_read', name: 'Rows processed', description: undefined, minWidth: 130 },
{ id: 'cache_hit_rate', name: 'Cache hit rate', description: undefined, minWidth: 130 },
{ id: 'rolname', name: 'Role', description: undefined, minWidth: 200 },
] as const
export const QUERY_PERFORMANCE_ROLE_DESCRIPTION = [
{ name: 'postgres', description: 'The default Postgres role. This has admin privileges.' },
{
name: 'anon',
description:
'For unauthenticated, public access. This is the role which the API (PostgREST) will use when a user is not logged in.',
},
{
name: 'authenticator',
description:
'A special role for the API (PostgREST). It has very limited access, and is used to validate a JWT and then "change into" another role determined by the JWT verification.',
},
{
name: 'authenticated',
description:
'For "authenticated access." This is the role which the API (PostgREST) will use when a user is logged in.',
},
{
name: 'service_role',
description:
'For elevated access. This role is used by the API (PostgREST) to bypass Row Level Security.',
},
{
name: 'supabase_auth_admin',
description:
'Used by the Auth middleware to connect to the database and run migration. Access is scoped to the auth schema.',
},
{
name: 'supabase_storage_admin',
description:
'Used by the Auth middleware to connect to the database and run migration. Access is scoped to the storage schema.',
},
{ name: 'dashboard_user', description: 'For running commands via the Supabase UI.' },
{
name: 'supabase_admin',
description:
'An internal role Supabase uses for administrative tasks, such as running upgrades and automations.',
},
{
name: 'pgbouncer',
description:
'PgBouncer is a lightweight connection pooler for PostgreSQL. Available on paid plans only.',
},
] as const
export const QUERY_PERFORMANCE_CHART_TABS = [
{
id: 'query_latency',
label: 'Query latency',
},
{
id: 'rows_read',
label: 'Rows read',
},
{
id: 'calls',
label: 'Calls',
},
{
id: 'cache_hits',
label: 'Cache hits',
},
]
export const QUERY_PERFORMANCE_TIME_RANGES = [
{
id: 'last_60_minutes',
label: 'Last 60 minutes',
},
{
id: 'last_3_hours',
label: 'Last 3 hours',
},
{
id: 'last_24_hours',
label: 'Last 24 hours',
},
]
export const getPgStatMonitorLogsQuery = (startTime: string, endTime: string) =>
`
select
id,
pgl.timestamp as timestamp,
'postgres' as log_type,
CAST(pgl_parsed.sql_state_code AS STRING) as status,
CASE
WHEN pgl_parsed.error_severity = 'LOG' THEN 'success'
WHEN pgl_parsed.error_severity = 'WARNING' THEN 'warning'
WHEN pgl_parsed.error_severity = 'FATAL' THEN 'error'
WHEN pgl_parsed.error_severity = 'ERROR' THEN 'error'
ELSE null
END as level,
event_message as event_message
from postgres_logs as pgl
cross join unnest(pgl.metadata) as pgl_metadata
cross join unnest(pgl_metadata.parsed) as pgl_parsed
WHERE pgl.event_message LIKE '%[pg_stat_monitor]%'
AND pgl.timestamp >= CAST('${startTime}' AS TIMESTAMP)
AND pgl.timestamp <= CAST('${endTime}' AS TIMESTAMP)
ORDER BY timestamp DESC
`.trim()
export const PG_STAT_MONITOR_LOGS_QUERY = getPgStatMonitorLogsQuery(
new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
new Date().toISOString()
)