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:
Bobbie Soedirgo
2026-02-17 15:11:46 +08:00
committed by GitHub
parent 46053398de
commit 105df5291d
19 changed files with 800 additions and 505 deletions

View File

@@ -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}