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.

The recommended deployment shape is to bind /metrics behind a network barrier - a private port, an internal load balancer, or a sidecar scrape target - so the operating-system perimeter does the access control. When that is not available (same-listener mounts, scrape-from-public-network setups), use metrics.authedHandler(predicate) for application-level auth:

import { timingSafeEqual } from 'node:crypto';

const expected = Buffer.from(process.env.SCRAPE_TOKEN);

app.get('/metrics', metrics.authedHandler((res, req) => {
  const provided = req.getHeader('x-scrape-token');
  if (!provided) return false;
  const got = Buffer.from(provided);
  if (got.length !== expected.length) return false;
  return timingSafeEqual(got, expected);
}));

The predicate runs against (res, req) and serves metrics only when it returns truthy. Async predicates are awaited. Predicate exceptions are caught and treated as denial (no error info leaked back). Denials return 401 Unauthorized with a minimal text body and no metrics payload. The wrapper uses res.cork() + res.onAborted() per uWS best practice so a client hang-up during an async predicate does not write to a dead socket.

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
maxBuckets32Reject histogram registration with buckets.length > maxBuckets. Per-histogram override via the 7th positional arg to histogram(). Pass Infinity to disable.
maxSeries10000Per-metric labelset cap. New labelsets past the cap are silently dropped, a one-shot console.warn fires per metric, and the built-in prometheus_series_dropped_total{metric=<name>} counter tracks ongoing drops. Per-metric override via the 4th positional arg to counter / gauge (6th to histogram). Pass Infinity to disable.

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 caps

maxBuckets and maxSeries are defense against unbounded prom-scrape growth. The defaults match mature client libraries (Go’s client_golang warns past ~100 buckets; Java’s Micrometer soft-caps at 64; per-metric labelset state past ~10k usually means the metric is no longer query-able through Grafana).

Histogram bucket cap (maxBuckets, default 32): fires once at registration. The error message names the actual length, the configured cap, and both override knobs so the caller can either narrow the bucket array or opt up explicitly.

Per-metric labelset cap (maxSeries, default 10000): the data-plane action is a no-op rather than throwing, so a high-cardinality bug cannot crash the app on metric increment. Existing labelsets continue to increment past the cap; only new labelsets are dropped. The one-shot warn points at mapTopic as the typical fix when a user-controlled dimension (raw client IP, message id, unbounded topic) is leaking into a label.

The dropped-series counter has maxSeries: Infinity so a high-cardinality drop pattern cannot make the drop counter start dropping. The data-plane cost is one Map.size >= maxSeries comparison per inc / set / dec / observe; unmeasurable on hot paths.

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 (always 0 since extensions 0.5.0 per-field HPEXPIRE replaced application-side cleanup; field kept for back-compat)
presence_diff_frames_totalcountertopicdiff frames published to topic subscribers. Compared against presence_joins_total + presence_leaves_total it tells you how much per-tick coalescing the buffer is doing.
presence_diff_coalesced_totalcountertopicBuffered diff entries overwritten by a later op in the same tick. Non-zero rate confirms same-key collapse is working.
presence_total_onlinegaugetopicLive online count snapshot.
presence_heartbeat_latency_msgaugeMost recent heartbeat tick duration.
presence_keyspace_cleanups_totalcounterEmpty-state events fired by keyspaceNotifications: true on hash expiry.
presence_joins_aborted_totalcountertopic, reasonpresence.join() aborts. Currently reason="ws_closed" only; the label is future-proofed. Added in extensions 0.5.5.

Replay buffer (Redis and Postgres)

MetricTypeLabelsDescription
replay_publishes_totalcountertopicMessages published
replay_messages_replayed_totalcountertopicMessages replayed to clients
replay_truncations_totalcountertopicTruncation events detected
replay_idmp_hits_totalcountertopicIdempotency cache hits (duplicate publishId deduped at replay time).
replay_idmp_writes_totalcountertopicIdempotency cache writes on fresh publishes.
replay_replications_totalcounterCross-instance replication acks.
replay_replication_timeouts_totalcounterReplication acks that timed out.

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
cursor_attaches_aborted_totalcountertopic, reasoncursor.attach() aborts. Currently reason="ws_closed" only. Added in extensions 0.5.5.

LISTEN/NOTIFY bridge

MetricTypeLabelsDescription
notify_received_totalcounterchannelNotifications received
notify_parse_errors_totalcounterchannelParse failures
notify_reconnects_totalcounterReconnect attempts

Connection rates

import { wirePublishRateMetrics, connectionMetricsHook } from 'svelte-adapter-uws-extensions/prometheus';

export function init({ platform }) {
  wirePublishRateMetrics(platform, metrics);
}

export const close = connectionMetricsHook(metrics);
MetricTypeLabelsDescription
ws_topic_publish_rategaugetopicPer-topic publishes per second (scrape-time).
ws_topic_publish_bytesgaugetopicPer-topic bytes per second (scrape-time).
ws_connection_*histogrammessagesIn, messagesOut, bytesIn, bytesOut, duration per closed connection.
ws_connection_close_totalcountercodePer-close-code count.

connectionMetricsHook(metrics, userClose?) is a close hook factory that records per-connection traffic stats. Pre-existing user close hooks compose by passing them as the second arg.

Cluster-wide publish rate

import { createPublishRateAggregator } from 'svelte-adapter-uws-extensions/redis/publish-rate';
import { wireClusterPublishRateMetrics } from 'svelte-adapter-uws-extensions/prometheus';

const rates = await createPublishRateAggregator(redis);
wireClusterPublishRateMetrics(rates, metrics);
MetricTypeLabelsDescription
cluster_publish_rate_broadcasts_totalcounterLocal rate snapshots broadcast.
cluster_publish_rate_received_totalcounterSnapshots received from peers.
cluster_publish_rate_parse_errors_totalcounterMalformed snapshots received.
cluster_publish_rate_instance_countgaugeLive peer count.

Sharded pub/sub bus

MetricTypeLabelsDescription
sharded_pubsub_messages_relayed_totalcountertopicMessages relayed via SPUBLISH.
sharded_pubsub_messages_received_totalcountertopicMessages received via SSUBSCRIBE.
sharded_pubsub_echo_suppressed_totalcounterMessages dropped by echo suppression.
sharded_pubsub_ssubscribes_totalcounterSSUBSCRIBE calls.
sharded_pubsub_sunsubscribes_totalcounterSUNSUBSCRIBE calls.
sharded_pubsub_parse_errors_totalcounterMalformed sharded envelopes received.

Pub/sub bus state

MetricTypeDescription
pubsub_degraded_totalcounterAuto-emitted when the shared circuit breaker trips.
pubsub_recovered_totalcounterAuto-emitted when the breaker re-closes.
pubsub_parse_errors_totalcounterMalformed envelopes received.

Leader election

MetricTypeLabelsDescription
leader_acquired_totalcounterkey_classSuccessful lease acquisitions.
leader_lost_totalcounterkey_classLease losses.
leader_renewals_totalcounterkey_classSuccessful lease renewals.
leader_renewal_failures_totalcounterkey_classRenewal failures.

Distributed lock

MetricTypeLabelsDescription
lock_acquired_totalcounterkey_classSuccessful lock acquisitions.
lock_acquire_wait_mshistogramkey_classTime spent waiting for the lock.
lock_acquire_timeouts_totalcounterkey_classLockAcquireTimeoutError rate.
lock_lost_totalcounterkey_classLease lost mid-handler.

Distributed session

MetricTypeLabelsDescription
session_get_totalcounterresultHit / miss.
session_set_totalcounterSets.
session_delete_totalcounterresultHit / miss on deletes.
session_touch_totalcounterresultHit / miss on touches.

Connection registry / live.push

MetricTypeLabelsDescription
push_requests_totalcounterresultRequest outcomes.
push_reply_latency_mshistogramEnd-to-end reply latency.
push_registry_sizegaugeLive tracked-user count.
push_late_replies_totalcounterReplies arriving after timeout.
push_sends_totalcounterresultTargeted sends.
push_coalesced_totalcounterresultCoalesced targeted sends.
push_sendto_totalcounterresultsendTo matches.

Admission control

MetricTypeLabelsDescription
admission_accepted_totalcounterclassAccepted dispatches.
admission_rejected_totalcounterclass, reasonRejected dispatches.

Replay storage

MetricTypeLabelsDescription
replay_storage_fallbacks_totalcountertopicBest-effort platform.publish fallbacks when localFanoutOnStorageFailure: true.

Job queue (Postgres)

MetricTypeLabelsDescription
jobs_enqueued_totalcounterqueueJobs enqueued.
jobs_claimed_totalcounterqueueJobs claimed for processing.
jobs_completed_totalcounterqueueJobs completed successfully.
jobs_failed_totalcounterqueueJobs that failed terminally.

Task runner (Postgres)

MetricTypeLabelsDescription
tasks_enqueued_totalcounternameTasks enqueued.
tasks_completed_totalcounternameTasks completed successfully.
tasks_failed_totalcounternameTasks that failed terminally.
tasks_retried_totalcounternameRetry attempts.

Redis function library

MetricTypeLabelsDescription
redis_function_loads_totalcounterFUNCTION LOAD calls (library hot-reload).
redis_function_calls_totalcounternameFunction invocations.
redis_function_errors_totalcounternameFunction call errors.

Production assertions

import { wireAssertionMetrics } from 'svelte-adapter-uws-extensions/prometheus';
wireAssertionMetrics(metrics);
MetricTypeLabelsDescription
extensions_assertion_violations_totalcountercategoryCritical invariant violations. ~10 categories today. A non-zero rate is a framework bug.

Was this page helpful?