Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@
/packages/sql_input @elastic/obs-infraobs-integrations
/packages/sql_server_input_otel @elastic/ecosystem
/packages/squid @elastic/integration-experience
/packages/supabase_integration_otel @elastic/obs-infraobs-integrations
/packages/stan @elastic/obs-infraobs-integrations
/packages/statsd_input @elastic/obs-infraobs-integrations
/packages/statsd_input_otel @elastic/ecosystem
Expand Down
68 changes: 68 additions & 0 deletions packages/supabase_integration_otel/_dev/build/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Supabase Integration (OTel)

Collect metrics from [Supabase](https://supabase.com) using the OpenTelemetry Prometheus receiver.

## Overview

This integration scrapes metrics from the Supabase Metrics API (Prometheus-compatible) using the OTel Collector's Prometheus receiver. Metrics are organized into three data streams:

- **Node** (`supabase_integration_otel.node`) — Infrastructure metrics: CPU, memory, load, disk I/O, filesystem, network, vmstat, system
- **PostgreSQL** (`supabase_integration_otel.postgres`) — Database metrics: size, connections, transactions, cache, WAL, replication, bgwriter, statements, activity
- **Services** (`supabase_integration_otel.services`) — Application metrics: Supavisor connection pooler, PostgREST API, GoTrue auth, Realtime subscriptions, Storage

## Prerequisites

| Requirement | Details |
|---|---|
| **Supabase plan** | Pro, Team, or Enterprise (Metrics API is not available on Free tier) |
| **Elastic Stack** | 9.4.0+ |
| **Input package** | `prometheus_input_otel` (installed automatically as a dependency) |

## Setup

1. **Get your Supabase credentials**:
- Go to your Supabase dashboard → **Settings** → **API**
- Copy the **Project URL** (e.g., `abcdefghijkl.supabase.co`)
- Copy the **service_role** key (under "Project API keys")

2. **Add the integration in Kibana**:
- Go to **Management** → **Integrations** → search for "Supabase"
- Click **Add Supabase**
- Fill in the same credentials for each data stream:
- **Targets**: `<your-project-ref>.supabase.co:443`
- **Username**: `service_role`
- **Service Role Key**: paste your service_role JWT

3. **Verify data**:
- Go to **Discover** and filter on any of:
- `data_stream.dataset: "supabase_integration_otel.node.otel"`
- `data_stream.dataset: "supabase_integration_otel.postgres.otel"`
- `data_stream.dataset: "supabase_integration_otel.services.otel"`

## Dashboards

Kibana dashboards are provided by the **Supabase Dashboards** (`supabase_otel`) content package, which is declared as a dependency and installed automatically.

## Metrics Reference

Metrics are scraped from the Supabase Metrics API endpoint:

```
GET https://<project-ref>.supabase.co/customer/v1/privileged/metrics
```

Authentication uses HTTP Basic Auth with `service_role` as the username and the service_role JWT as the password.

See the [Supabase Metrics documentation](https://supabase.com/docs/guides/telemetry/metrics) for full details on available metrics.

### Node Metrics

{{fields "node"}}

### PostgreSQL Metrics

{{fields "postgres"}}

### Service Metrics

{{fields "services"}}
6 changes: 6 additions & 0 deletions packages/supabase_integration_otel/changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# newer versions go on top

Check notice on line 1 in packages/supabase_integration_otel/changelog.yml

View workflow job for this annotation

GitHub Actions / Lint user-facing content

Elastic.Versions: Use 'later versions' instead of 'newer versions' when referring to versions.
- version: "0.1.0"
changes:
- description: Initial release of the Supabase OTel integration for metrics collection via Prometheus receiver.
type: enhancement
link: https://github.com/elastic/integrations/pull/99999
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
---
description: >
Rename node_exporter Prometheus metrics into the supabase.node.* namespace.
Uses a script processor with a lookup map instead of individual rename processors.
processors:
- script:
tag: rename_metrics_to_supabase_namespace
description: >
Renames raw Prometheus metric names from the metrics.* object into
the supabase.* namespace using a static lookup map (106 mappings).
lang: painless
source: >
def RENAME_MAP = [
'node_boot_time_seconds': 'supabase.node.boot_time_seconds',
'node_context_switches_total': 'supabase.node.context_switches.count',
'node_cooling_device_cur_state': 'supabase.node.cooling_device_cur_state',
'node_cpu_guest_seconds_total': 'supabase.node.cpu.guest_seconds.count',
'node_cpu_seconds_total': 'supabase.node.cpu.seconds.count',
'node_disk_discards_completed_total': 'supabase.node.disk.discards_completed.count',
'node_disk_discards_merged_total': 'supabase.node.disk.discards_merged.count',
'node_disk_io_now': 'supabase.node.disk.io_now',
'node_disk_io_time_seconds_total': 'supabase.node.disk.io_time_seconds.count',
'node_disk_io_time_weighted_seconds_total': 'supabase.node.disk.io_time_weighted_seconds.count',
'node_disk_read_bytes_total': 'supabase.node.disk.read_bytes.count',
'node_disk_read_time_seconds_total': 'supabase.node.disk.read_time_seconds.count',
'node_disk_reads_completed_total': 'supabase.node.disk.reads_completed.count',
'node_disk_reads_merged_total': 'supabase.node.disk.reads_merged.count',
'node_disk_write_time_seconds_total': 'supabase.node.disk.write_time_seconds.count',
'node_disk_writes_completed_total': 'supabase.node.disk.writes_completed.count',
'node_disk_writes_merged_total': 'supabase.node.disk.writes_merged.count',
'node_disk_written_bytes_total': 'supabase.node.disk.written_bytes.count',
'node_entropy_available_bits': 'supabase.node.entropy_available_bits',
'node_filefd_allocated': 'supabase.node.filefd.allocated',
'node_filefd_maximum': 'supabase.node.filefd.maximum',
'node_filesystem_avail_bytes': 'supabase.node.filesystem.available_bytes',
'node_filesystem_device_error': 'supabase.node.filesystem.device_error',
'node_filesystem_files': 'supabase.node.filesystem.files',
'node_filesystem_files_free': 'supabase.node.filesystem.files_free',
'node_filesystem_free_bytes': 'supabase.node.filesystem.free_bytes',
'node_filesystem_readonly': 'supabase.node.filesystem.readonly',
'node_filesystem_size_bytes': 'supabase.node.filesystem.size_bytes',
'node_forks_total': 'supabase.node.forks.count',
'node_hwmon_temp_celsius': 'supabase.node.hwmon_temp_celsius',
'node_interrupts_total': 'supabase.node.interrupts.count',
'node_load1': 'supabase.node.load1',
'node_load15': 'supabase.node.load15',
'node_load5': 'supabase.node.load5',
'node_memory_Active_anon_bytes': 'supabase.node.memory.active_anon_bytes',
'node_memory_Active_bytes': 'supabase.node.memory.active_bytes',
'node_memory_Active_file_bytes': 'supabase.node.memory.active_file_bytes',
'node_memory_AnonHugePages_bytes': 'supabase.node.memory.anon_huge_pages_bytes',
'node_memory_AnonPages_bytes': 'supabase.node.memory.anon_pages_bytes',
'node_memory_Bounce_bytes': 'supabase.node.memory.bounce_bytes',
'node_memory_Buffers_bytes': 'supabase.node.memory.buffers_bytes',
'node_memory_Cached_bytes': 'supabase.node.memory.cached_bytes',
'node_memory_CommitLimit_bytes': 'supabase.node.memory.commit_limit_bytes',
'node_memory_Committed_AS_bytes': 'supabase.node.memory.committed_as_bytes',
'node_memory_Dirty_bytes': 'supabase.node.memory.dirty_bytes',
'node_memory_HardwareCorrupted_bytes': 'supabase.node.memory.hardware_corrupted_bytes',
'node_memory_HugePages_Free': 'supabase.node.memory.huge_pages_free',
'node_memory_HugePages_Rsvd': 'supabase.node.memory.huge_pages_rsvd',
'node_memory_HugePages_Surp': 'supabase.node.memory.huge_pages_surp',
'node_memory_HugePages_Total': 'supabase.node.memory.huge_pages_total',
'node_memory_Hugepagesize_bytes': 'supabase.node.memory.hugepagesize_bytes',
'node_memory_Inactive_anon_bytes': 'supabase.node.memory.inactive_anon_bytes',
'node_memory_Inactive_bytes': 'supabase.node.memory.inactive_bytes',
'node_memory_Inactive_file_bytes': 'supabase.node.memory.inactive_file_bytes',
'node_memory_KernelStack_bytes': 'supabase.node.memory.kernel_stack_bytes',
'node_memory_Mapped_bytes': 'supabase.node.memory.mapped_bytes',
'node_memory_MemAvailable_bytes': 'supabase.node.memory.mem_available_bytes',
'node_memory_MemFree_bytes': 'supabase.node.memory.mem_free_bytes',
'node_memory_MemTotal_bytes': 'supabase.node.memory.mem_total_bytes',
'node_memory_Mlocked_bytes': 'supabase.node.memory.mlocked_bytes',
'node_memory_NFS_Unstable_bytes': 'supabase.node.memory.nfs_unstable_bytes',
'node_memory_PageTables_bytes': 'supabase.node.memory.page_tables_bytes',
'node_memory_Percpu_bytes': 'supabase.node.memory.percpu_bytes',
'node_memory_SReclaimable_bytes': 'supabase.node.memory.s_reclaimable_bytes',
'node_memory_SUnreclaim_bytes': 'supabase.node.memory.s_unreclaim_bytes',
'node_memory_Shmem_bytes': 'supabase.node.memory.shmem_bytes',
'node_memory_Slab_bytes': 'supabase.node.memory.slab_bytes',
'node_memory_SwapCached_bytes': 'supabase.node.memory.swap_cached_bytes',
'node_memory_SwapFree_bytes': 'supabase.node.memory.swap_free_bytes',
'node_memory_SwapTotal_bytes': 'supabase.node.memory.swap_total_bytes',
'node_memory_Unevictable_bytes': 'supabase.node.memory.unevictable_bytes',
'node_memory_VmallocChunk_bytes': 'supabase.node.memory.vmalloc_chunk_bytes',
'node_memory_VmallocTotal_bytes': 'supabase.node.memory.vmalloc_total_bytes',
'node_memory_VmallocUsed_bytes': 'supabase.node.memory.vmalloc_used_bytes',
'node_memory_WritebackTmp_bytes': 'supabase.node.memory.writeback_tmp_bytes',
'node_memory_Writeback_bytes': 'supabase.node.memory.writeback_bytes',
'node_network_receive_bytes_total': 'supabase.node.network.receive_bytes.count',
'node_network_receive_compressed_total': 'supabase.node.network.receive_compressed.count',
'node_network_receive_drop_total': 'supabase.node.network.receive_drop.count',
'node_network_receive_errs_total': 'supabase.node.network.receive_errors.count',
'node_network_receive_fifo_total': 'supabase.node.network.receive_fifo.count',
'node_network_receive_frame_total': 'supabase.node.network.receive_frame.count',
'node_network_receive_multicast_total': 'supabase.node.network.receive_multicast.count',
'node_network_receive_packets_total': 'supabase.node.network.receive_packets.count',
'node_network_transmit_bytes_total': 'supabase.node.network.transmit_bytes.count',
'node_network_transmit_carrier_total': 'supabase.node.network.transmit_carrier.count',
'node_network_transmit_colls_total': 'supabase.node.network.transmit_colls.count',
'node_network_transmit_compressed_total': 'supabase.node.network.transmit_compressed.count',
'node_network_transmit_drop_total': 'supabase.node.network.transmit_drop.count',
'node_network_transmit_errs_total': 'supabase.node.network.transmit_errors.count',
'node_network_transmit_fifo_total': 'supabase.node.network.transmit_fifo.count',
'node_network_transmit_packets_total': 'supabase.node.network.transmit_packets.count',
'node_nf_conntrack_entries': 'supabase.node.nf_conntrack.entries',
'node_nf_conntrack_entries_limit': 'supabase.node.nf_conntrack.entries_limit',
'node_power_supply_online': 'supabase.node.power_supply_online',
'node_procs_blocked': 'supabase.node.procs_blocked',
'node_procs_running': 'supabase.node.procs_running',
'node_scrape_collector_duration_seconds': 'supabase.node.scrape_collector_duration_seconds',
'node_scrape_collector_success': 'supabase.node.scrape_collector_success',
'node_vmstat_oom_kill': 'supabase.node.vmstat.oom_kill',
'node_vmstat_pgfault': 'supabase.node.vmstat.pgfault',
'node_vmstat_pgmajfault': 'supabase.node.vmstat.pgmajfault',
'node_vmstat_pgpgin': 'supabase.node.vmstat.pgpgin',
'node_vmstat_pgpgout': 'supabase.node.vmstat.pgpgout',
'node_vmstat_pswpin': 'supabase.node.vmstat.pswpin',
'node_vmstat_pswpout': 'supabase.node.vmstat.pswpout'
];
def DOT = /[.]/;
if (ctx.containsKey('metrics')) {
def metricsMap = ctx.metrics;
def keysToRemove = [];
for (def entry : metricsMap.entrySet()) {
if (RENAME_MAP.containsKey(entry.getKey())) {
def target = RENAME_MAP.get(entry.getKey());
def parts = DOT.split(target);
def current = ctx;
for (int i = 0; i < parts.length - 1; i++) {
if (!current.containsKey(parts[i])) {
current[parts[i]] = new HashMap();
}
current = current[parts[i]];
}
current[parts[parts.length - 1]] = entry.getValue();
keysToRemove.add(entry.getKey());
}
}
for (def key : keysToRemove) {
metricsMap.remove(key);
}
}
- remove:
tag: drop_remaining_metrics
field: metrics
ignore_missing: true

on_failure:
- append:
field: error.message
value: >-
Processor '{{{ _ingest.on_failure_processor_type }}}'
{{{#_ingest.on_failure_processor_tag}}}with tag '{{{ _ingest.on_failure_processor_tag }}}'
{{{/_ingest.on_failure_processor_tag}}}failed with message '{{{ _ingest.on_failure_message }}}'
in pipeline '{{{ _ingest.pipeline }}}'
- set:
field: event.kind
tag: set_pipeline_error_to_event_kind
value: pipeline_error
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
- name: data_stream.type
type: constant_keyword
description: Data stream type.
- name: data_stream.dataset
type: constant_keyword
description: Data stream dataset.
- name: data_stream.namespace
type: constant_keyword
description: Data stream namespace.
- name: '@timestamp'
type: date
description: Event timestamp.
Loading
Loading