| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- import { type SafeSqlFragment } from '@supabase/pg-meta'
- import type { QueryPerformanceRow } from '../../QueryPerformance/QueryPerformance.types'
- import type { Logs } from '../../Settings/Logs/Logs.types'
- import {
- SCHEMA_INTROSPECTION_REGEX,
- SUPAMONITOR_EXCLUDED_APP_NAMES,
- SUPAMONITOR_EXCLUDED_ROLES,
- TRANSACTION_CONTROL_REGEX,
- } from '../QueryInsights.constants'
- import type { ChartDataPoint, ParsedLogEntry } from '../QueryInsights.types'
- export function filterSystemLogs(
- logs: ParsedLogEntry[],
- { includeIntrospection = false }: { includeIntrospection?: boolean } = {}
- ): ParsedLogEntry[] {
- return logs.filter((log) => {
- if (log.user_name && (SUPAMONITOR_EXCLUDED_ROLES as readonly string[]).includes(log.user_name))
- return false
- if (
- log.application_name &&
- (SUPAMONITOR_EXCLUDED_APP_NAMES as readonly string[]).includes(log.application_name)
- )
- return false
- if (log.query && TRANSACTION_CONTROL_REGEX.test(log.query)) return false
- if (!includeIntrospection && log.query && SCHEMA_INTROSPECTION_REGEX.test(log.query))
- return false
- return true
- })
- }
- function asString(unknown: unknown): string | undefined {
- if (typeof unknown === 'string') return unknown
- if (unknown === null || unknown === undefined) return undefined
- return String(unknown)
- }
- function asNumber(unknown: unknown): number | undefined {
- if (typeof unknown === 'number') return unknown
- if (unknown === null || unknown === undefined) return undefined
- const parsed = Number(unknown)
- return Number.isNaN(parsed) ? undefined : parsed
- }
- export function parseSupamonitorLogs(logData: Logs['result']): ParsedLogEntry[] {
- if (!logData || logData.length === 0) return []
- return logData.map((log) => ({
- timestamp: asString(log.timestamp),
- application_name: asString(log.application_name),
- calls: asNumber(log.calls),
- database_name: asString(log.database_name),
- query: log.query,
- query_id: asNumber(log.query_id),
- total_exec_time: asNumber(log.total_exec_time),
- total_plan_time: asNumber(log.total_plan_time),
- user_name: asString(log.user_name),
- mean_exec_time: asNumber(log.mean_exec_time),
- mean_plan_time: asNumber(log.mean_plan_time),
- min_exec_time: asNumber(log.min_exec_time),
- max_exec_time: asNumber(log.max_exec_time),
- min_plan_time: asNumber(log.min_plan_time),
- max_plan_time: asNumber(log.max_plan_time),
- p50_exec_time: asNumber(log.p50_exec_time),
- p95_exec_time: asNumber(log.p95_exec_time),
- p50_plan_time: asNumber(log.p50_plan_time),
- p95_plan_time: asNumber(log.p95_plan_time),
- }))
- }
- export function transformLogsToChartData(parsedLogs: ParsedLogEntry[]): ChartDataPoint[] {
- if (!parsedLogs || parsedLogs.length === 0) return []
- return parsedLogs
- .map((log: ParsedLogEntry) => {
- if (!log.timestamp) return null
- const periodStart = new Date(log.timestamp).getTime()
- if (isNaN(periodStart)) return null
- const meanExecTime = parseFloat(String(log.mean_exec_time ?? 0))
- const meanPlanTime = parseFloat(String(log.mean_plan_time ?? 0))
- const calls = parseInt(String(log.calls ?? 0), 10)
- return {
- period_start: periodStart,
- timestamp: log.timestamp,
- query_latency: meanExecTime + meanPlanTime,
- mean_time: meanExecTime,
- min_time: (log.min_exec_time ?? 0) + (log.min_plan_time ?? 0),
- max_time: (log.max_exec_time ?? 0) + (log.max_plan_time ?? 0),
- stddev_time: 0,
- p50_time: (log.p50_exec_time ?? 0) + (log.p50_plan_time ?? 0),
- p95_time: (log.p95_exec_time ?? 0) + (log.p95_plan_time ?? 0),
- rows_read: 0,
- calls,
- cache_hits: 0,
- cache_misses: 0,
- }
- })
- .filter((item): item is NonNullable<typeof item> => item !== null)
- .sort((a, b) => a.period_start - b.period_start)
- }
- function normalizeQuery(query: string): string {
- return query.replace(/\s+/g, ' ').trim()
- }
- export function aggregateLogsByQuery(parsedLogs: ParsedLogEntry[]): QueryPerformanceRow[] {
- if (!parsedLogs || parsedLogs.length === 0) return []
- const queryGroups = new Map<string, ParsedLogEntry[]>()
- parsedLogs.forEach((log) => {
- const query = normalizeQuery(log.query || '')
- if (!query) return
- if (!queryGroups.has(query)) {
- queryGroups.set(query, [])
- }
- queryGroups.get(query)!.push(log)
- })
- const aggregatedData: QueryPerformanceRow[] = []
- let totalExecutionTime = 0
- const queryStats = Array.from(queryGroups.entries()).map(([query, logs]) => {
- const count = logs.length
- let totalCalls = 0
- let totalExecTime = 0
- let totalPlanTime = 0
- let p95Sum = 0
- let p95Count = 0
- let minTime = Infinity
- let maxTime = -Infinity
- const rolname = logs[0]?.user_name || ''
- const applicationName = logs[0]?.application_name || ''
- let firstSeen = logs[0]?.timestamp ?? ''
- logs.forEach((log) => {
- if (log.timestamp && (!firstSeen || log.timestamp < firstSeen)) firstSeen = log.timestamp
- const logCalls = parseInt(String(log.calls ?? 0), 10)
- totalCalls += logCalls
- totalExecTime += parseFloat(String(log.total_exec_time ?? 0))
- totalPlanTime += parseFloat(String(log.total_plan_time ?? 0))
- const logP95 =
- parseFloat(String(log.p95_exec_time ?? 0)) + parseFloat(String(log.p95_plan_time ?? 0))
- if (logP95 > 0) {
- p95Sum += logP95
- p95Count++
- }
- minTime = Math.min(
- minTime,
- parseFloat(String(log.min_exec_time ?? 0)) + parseFloat(String(log.min_plan_time ?? 0))
- )
- maxTime = Math.max(
- maxTime,
- parseFloat(String(log.max_exec_time ?? 0)) + parseFloat(String(log.max_plan_time ?? 0))
- )
- })
- const totalTime = totalExecTime + totalPlanTime
- const avgMeanTime = totalCalls > 0 ? totalTime / totalCalls : 0
- const avgP95Time = p95Count > 0 ? p95Sum / p95Count : 0
- const finalMinTime = minTime === Infinity ? 0 : minTime
- const finalMaxTime = maxTime === -Infinity ? 0 : maxTime
- totalExecutionTime += totalTime
- return {
- query,
- rolname,
- applicationName,
- firstSeen,
- count,
- avgMeanTime,
- avgP95Time,
- minTime: finalMinTime,
- maxTime: finalMaxTime,
- totalCalls,
- totalTime,
- }
- })
- queryStats.forEach((stats) => {
- const propTotalTime = totalExecutionTime > 0 ? (stats.totalTime / totalExecutionTime) * 100 : 0
- aggregatedData.push({
- query: stats.query as SafeSqlFragment,
- rolname: stats.rolname,
- application_name: stats.applicationName,
- calls: stats.totalCalls,
- mean_time: stats.avgMeanTime,
- p95_time: stats.avgP95Time,
- min_time: stats.minTime,
- max_time: stats.maxTime,
- total_time: stats.totalTime,
- rows_read: 0,
- cache_hit_rate: 0,
- prop_total_time: propTotalTime,
- index_advisor_result: null,
- _total_cache_hits: 0,
- _total_cache_misses: 0,
- _count: stats.count,
- first_seen: stats.firstSeen,
- })
- })
- return aggregatedData.sort((a, b) => b.total_time - a.total_time)
- }
|