From fce5e3cceae5e8ce23548742d4b7ea3cd6d43854 Mon Sep 17 00:00:00 2001 From: shum Date: Fri, 21 Aug 2026 09:12:33 +0000 Subject: [PATCH] scripts: add relay metrics exporter --- docs/CHAT-RELAY.md | 2 + scripts/relay/docker-compose.yaml | 16 +++++++ scripts/relay/sql_exporter.yml | 71 +++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 scripts/relay/sql_exporter.yml diff --git a/docs/CHAT-RELAY.md b/docs/CHAT-RELAY.md index 021538c1d1..9d95884f20 100644 --- a/docs/CHAT-RELAY.md +++ b/docs/CHAT-RELAY.md @@ -190,6 +190,8 @@ docker compose run --rm --entrypoint sh relay -c \ 'simplex-chat-relay -d "$DB_CONN" -e "/set profile image file /avatar.png"' ``` +The stack also runs [sql_exporter](https://github.com/burningalchemist/sql_exporter), which publishes relay metrics read from the database (channels by relay status, members, queued deliveries and the age of the oldest one, messages in the last 24 hours) on `127.0.0.1:9399/metrics`. Edit `sql_exporter.yml` to change the queries, and remove the `metrics` service if they are not needed. + The compose file already runs the relay with the web-preview options, writing previews and the CORS file to `/var/www/relay-web-channels`. To serve them, follow [Serve the previews with Caddy](#serve-the-previews-with-caddy) and [Reload CORS automatically](#reload-cors-automatically) below, but skip the `usermod -aG relay caddy` step: with Docker the files are world-readable and there is no `relay` user. ## Channel web previews diff --git a/scripts/relay/docker-compose.yaml b/scripts/relay/docker-compose.yaml index 9ccde75ee8..22648b7726 100644 --- a/scripts/relay/docker-compose.yaml +++ b/scripts/relay/docker-compose.yaml @@ -26,6 +26,22 @@ services: - ./out:/out restart: unless-stopped + metrics: + image: burningalchemist/sql_exporter:latest + container_name: chat-relay-metrics + depends_on: + db: + condition: service_healthy + environment: + # Percent-encode the password if it contains URL-reserved characters. + SQLEXPORTER_TARGET_DSN: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?sslmode=disable + volumes: + - ./sql_exporter.yml:/etc/sql_exporter/sql_exporter.yml:ro + command: ["-config.file=/etc/sql_exporter/sql_exporter.yml"] + ports: + - "127.0.0.1:9399:9399" + restart: unless-stopped + db: image: postgres:18 container_name: chat-relay-db diff --git a/scripts/relay/sql_exporter.yml b/scripts/relay/sql_exporter.yml new file mode 100644 index 0000000000..ee8f62218b --- /dev/null +++ b/scripts/relay/sql_exporter.yml @@ -0,0 +1,71 @@ +global: + min_interval: 0s + max_connections: 3 + max_idle_connections: 3 + +target: + name: chat_relay + # Replaced by SQLEXPORTER_TARGET_DSN. A non-empty value is required here + # because the DSN is validated before environment variables are applied. + data_source_name: "postgresql://replaced-by-env" + collectors: [chat_relay] + +collectors: + - collector_name: chat_relay + metrics: + - metric_name: chat_relay_channels + type: gauge + help: "Channels by relay status." + key_labels: [status] + values: [channels] + query: | + SELECT relay_own_status AS status, count(*) AS channels + FROM simplex_v1_chat_schema.groups + WHERE relay_own_status IS NOT NULL + GROUP BY relay_own_status + + - metric_name: chat_relay_published_channels + type: gauge + help: "Served channels that have a public address." + values: [channels] + query: | + SELECT count(*) AS channels + FROM simplex_v1_chat_schema.groups g + JOIN simplex_v1_chat_schema.group_profiles gp + ON gp.group_profile_id = g.group_profile_id + WHERE gp.public_group_id IS NOT NULL + AND g.relay_own_status IN ('active', 'accepted') + + - metric_name: chat_relay_members + type: gauge + help: "Members across all channels." + values: [members] + query: | + SELECT count(*) AS members FROM simplex_v1_chat_schema.group_members + + - metric_name: chat_relay_pending_deliveries + type: gauge + help: "Messages queued for delivery." + values: [deliveries] + query: | + SELECT count(*) AS deliveries + FROM simplex_v1_chat_schema.msg_deliveries + WHERE delivery_status = 'snd_pending' + + - metric_name: chat_relay_oldest_pending_delivery_seconds + type: gauge + help: "Age of the oldest message queued for delivery." + values: [age] + query: | + SELECT COALESCE(EXTRACT(EPOCH FROM (now() - min(created_at))), 0) AS age + FROM simplex_v1_chat_schema.msg_deliveries + WHERE delivery_status = 'snd_pending' + + - metric_name: chat_relay_messages_24h + type: gauge + help: "Chat items created in the last 24 hours." + values: [messages] + query: | + SELECT count(*) AS messages + FROM simplex_v1_chat_schema.chat_items + WHERE created_at > now() - interval '24 hours'