Prometheus Metrics

Exposes extension metrics in Prometheus text exposition format. No external dependencies. Zero overhead when not enabled - every metric call uses optional chaining on a nullish reference, so V8 short-circuits on a single pointer check.

Setup

// src/lib/server/metrics.js
import { createMetrics } from 'svelte-adapter-uws-extensions/prometheus';

export const metrics = createMetrics({
  prefix: 'myapp_',
  mapTopic: (topic) => topic.startsWith('room:') ? 'room:*' : topic
});

Pass the metrics object to any extension via its options:

import { metrics } from './metrics.js';
import { redis } from './redis.js';
import { createPresence } from 'svelte-adapter-uws-extensions/redis/presence';
import { createPubSubBus } from 'svelte-adapter-uws-extensions/redis/pubsub';
import { createReplay } from 'svelte-adapter-uws-extensions/redis/replay';
import { createRateLimit } from 'svelte-adapter-uws-extensions/redis/ratelimit';
import { createGroup } from 'svelte-adapter-uws-extensions/redis/groups';
import { createCursor } from 'svelte-adapter-uws-extensions/redis/cursor';

export const bus = createPubSubBus(redis, { metrics });
export const presence = createPresence(redis, { metrics, key: 'id' });
export const replay = createReplay(redis, { metrics });
export const limiter = createRateLimit(redis, { points: 10, interval: 1000, metrics });
export const lobby = createGroup(redis, 'lobby', { metrics });
export const cursors = createCursor(redis, { metrics });

Mounting the endpoint

With uWebSockets.js:

app.get('/metrics', metrics.handler);

Or use metrics.serialize() to get the raw text and serve it however you like.

Options

OptionDefaultDescription
prefix''Prefix for all metric names
mapTopicidentityMap topic names to bounded label values for cardinality control
defaultBuckets[1, 5, 10, 25, 50, 100, 250, 500, 1000]Default histogram buckets

Metric names must match [a-zA-Z_:][a-zA-Z0-9_:]* and label names must match [a-zA-Z_][a-zA-Z0-9_]* (no __ prefix). Invalid names throw at registration time. HELP text containing backslashes or newlines is escaped automatically.

Cardinality control

If your topics are user-generated (e.g. room:abc123), per-topic labels will grow unbounded. Use mapTopic to collapse them:

const metrics = createMetrics({
  mapTopic: (topic) => {
    if (topic.startsWith('room:')) return 'room:*';
    if (topic.startsWith('user:')) return 'user:*';
    return topic;
  }
});

Metrics reference

Pub/sub bus

MetricTypeDescription
pubsub_messages_relayed_totalcounterMessages relayed to Redis
pubsub_messages_received_totalcounterMessages received from Redis
pubsub_echo_suppressed_totalcounterMessages dropped by echo suppression
pubsub_relay_batch_sizehistogramRelay batch size per flush

Presence

MetricTypeLabelsDescription
presence_joins_totalcountertopicJoin events
presence_leaves_totalcountertopicLeave events
presence_heartbeats_totalcounterHeartbeat refresh cycles
presence_stale_cleaned_totalcounterStale entries removed by cleanup

Replay buffer (Redis and Postgres)

MetricTypeLabelsDescription
replay_publishes_totalcountertopicMessages published
replay_messages_replayed_totalcountertopicMessages replayed to clients
replay_truncations_totalcountertopicTruncation events detected

Rate limiting

MetricTypeDescription
ratelimit_allowed_totalcounterRequests allowed
ratelimit_denied_totalcounterRequests denied
ratelimit_bans_totalcounterBans applied

Broadcast groups

MetricTypeLabelsDescription
group_joins_totalcountergroupJoin events
group_joins_rejected_totalcountergroupJoins rejected (full)
group_leaves_totalcountergroupLeave events
group_publishes_totalcountergroupPublish events

Cursor

MetricTypeLabelsDescription
cursor_updates_totalcountertopicCursor update calls
cursor_broadcasts_totalcountertopicBroadcasts actually sent
cursor_throttled_totalcountertopicUpdates deferred by throttle

LISTEN/NOTIFY bridge

MetricTypeLabelsDescription
notify_received_totalcounterchannelNotifications received
notify_parse_errors_totalcounterchannelParse failures
notify_reconnects_totalcounterReconnect attempts

Was this page helpful?