mirror of
https://github.com/supabase/supabase.git
synced 2026-06-02 10:55:11 +08:00
feat: initial supamonitor changes (#42313)
Add a Query Performance page implementation powered by [supamonitor](https://github.com/supabase/supamonitor). [Context](https://linear.app/supabase/project/build-extension-for-supabase-query-insights-df4fb145352c/overview) This looks largely the same as the pg_stat_monitor implementation: <img width="2556" height="960" alt="Screenshot 2026-02-12 at 7 35 47 PM" src="https://github.com/user-attachments/assets/bf37466e-f7af-41f2-b4f2-cf8eb6a8c76f" /> Only available on projects on custom AMI - existing users are unaffected <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Supamonitor-based query performance view: charts, aggregated metrics, date-range controls, and export/download. * Added "Application" column for per-application tracking. * Interactive Supamonitor grid: sorting, filtering, keyboard navigation, selection, retry/error handling. * Automatic per-project Supamonitor detection with toggleable UI integration. * **Bug Fixes** * Chart latency calculation prefers histogram data for more accurate p95. * **Documentation** * Minor blog formatting fix. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: kemal <hello@kemal.earth> Co-authored-by: Ali Waseem <waseema393@gmail.com>
This commit is contained in:
@@ -4,8 +4,7 @@ import { QUERY_PERFORMANCE_CHART_TABS } from './QueryPerformance.constants'
|
||||
import { Loader2 } from 'lucide-react'
|
||||
import { ComposedChart } from 'components/ui/Charts/ComposedChart'
|
||||
import type { MultiAttribute } from 'components/ui/Charts/ComposedChart.utils'
|
||||
import type { ChartDataPoint } from './WithMonitor/WithMonitor.utils'
|
||||
import { calculatePercentilesFromHistogram } from './WithMonitor/WithMonitor.utils'
|
||||
import type { ChartDataPoint } from './QueryPerformance.types'
|
||||
|
||||
interface QueryPerformanceChartProps {
|
||||
dateRange?: {
|
||||
@@ -62,33 +61,11 @@ export const QueryPerformanceChart = ({
|
||||
|
||||
switch (selectedMetric) {
|
||||
case 'query_latency': {
|
||||
let trueP95: number = 0
|
||||
|
||||
if (parsedLogs && parsedLogs.length > 0) {
|
||||
const bucketCount = parsedLogs[0]?.resp_calls?.length || 50
|
||||
const combinedHistogram = new Array(bucketCount).fill(0)
|
||||
|
||||
parsedLogs.forEach((log) => {
|
||||
if (log.resp_calls && Array.isArray(log.resp_calls)) {
|
||||
log.resp_calls.forEach((count: number, index: number) => {
|
||||
if (index < combinedHistogram.length) {
|
||||
combinedHistogram[index] += count
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// [kemal]: this might need a revisit
|
||||
const percentiles = calculatePercentilesFromHistogram(combinedHistogram)
|
||||
trueP95 = percentiles.p95
|
||||
} else {
|
||||
// [kemal]: fallback to weighted average
|
||||
const totalCalls = chartData.reduce((sum, d) => sum + d.calls, 0)
|
||||
trueP95 =
|
||||
totalCalls > 0
|
||||
? chartData.reduce((sum, d) => sum + d.p95_time * d.calls, 0) / totalCalls
|
||||
: 0
|
||||
}
|
||||
const totalCalls = chartData.reduce((sum, d) => sum + d.calls, 0)
|
||||
const trueP95 =
|
||||
totalCalls > 0
|
||||
? chartData.reduce((sum, d) => sum + d.p95_time * d.calls, 0) / totalCalls
|
||||
: 0
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -167,12 +144,7 @@ export const QueryPerformanceChart = ({
|
||||
>()
|
||||
|
||||
queryLogs.forEach((log) => {
|
||||
const timestamps = [log.bucket_start_time, log.bucket, log.timestamp, log.ts]
|
||||
const validTimestamp = timestamps.find((t) => t && !isNaN(new Date(t).getTime()))
|
||||
|
||||
if (!validTimestamp) return
|
||||
|
||||
const time = new Date(validTimestamp).getTime()
|
||||
const time = new Date(log.timestamp).getTime()
|
||||
const meanTime = log.mean_time ?? log.mean_exec_time ?? log.mean_query_time ?? 0
|
||||
const rowsRead = log.rows_read ?? log.rows ?? 0
|
||||
const calls = log.calls ?? 0
|
||||
@@ -258,8 +230,14 @@ export const QueryPerformanceChart = ({
|
||||
|
||||
const baseAttributes = attributeMap[selectedMetric] || []
|
||||
|
||||
// Add selected query line based on current metric
|
||||
if (currentSelectedQuery && querySpecificData) {
|
||||
const dimmedBaseAttributes = baseAttributes.map((attr) => ({
|
||||
...attr,
|
||||
color: attr.color
|
||||
? { light: attr.color.light + '4D', dark: attr.color.dark + '4D' }
|
||||
: attr.color,
|
||||
}))
|
||||
|
||||
const selectedQueryAttributes: Record<string, MultiAttribute> = {
|
||||
query_latency: {
|
||||
attribute: 'selected_query_time',
|
||||
@@ -297,7 +275,7 @@ export const QueryPerformanceChart = ({
|
||||
|
||||
const selectedQueryAttr = selectedQueryAttributes[selectedMetric]
|
||||
if (selectedQueryAttr) {
|
||||
return [...baseAttributes, selectedQueryAttr]
|
||||
return [...dimmedBaseAttributes, selectedQueryAttr]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,12 +338,7 @@ export const QueryPerformanceChart = ({
|
||||
hideHighlightArea={true}
|
||||
showTooltip={true}
|
||||
showGrid={true}
|
||||
showLegend={
|
||||
selectedMetric === 'query_latency' ||
|
||||
selectedMetric === 'cache_hits' ||
|
||||
selectedMetric === 'rows_read' ||
|
||||
selectedMetric === 'calls'
|
||||
}
|
||||
showLegend={true}
|
||||
showTotal={false}
|
||||
showMaxValue={false}
|
||||
updateDateRange={updateDateRange}
|
||||
|
||||
Reference in New Issue
Block a user