etherpad-lite/doc/stats.md
John McLear 2d1a09dbe0
docs: refresh docs for 3.2.0 — correct stale content, document recent features (#7888)
* docs: refresh docs for 3.2.0 — correct stale content, document recent features

The hand-maintained VitePress docs under doc/ had drifted behind a lot of
recent work. They are authored prose (not generated from the OpenAPI spec),
so they need manual upkeep. This pass corrects content that was actively
wrong and documents features shipped since they were last touched.

Corrections (was wrong / misleading):
- cli.md: every command used `node bin/foo.js`, but the scripts are
  TypeScript run via pnpm — copy-paste failed. Rewrote to
  `pnpm run --filter bin <script>`, documented ~13 previously-undocumented
  operator tools, and split running-vs-stopped requirements. Registered the
  missing `compactStalePads` script in bin/package.json so the documented
  invocation actually works.
- stats.md: described a pre-Prometheus world. Rewrote for the gated
  `/stats` (JSON) and `/stats/prometheus` endpoints, the live metric set,
  the opt-in `scalingDiveMetrics` instruments (#7756), and `measured-core`.
- admin/updates.md: removed three false "SMTP not yet wired" claims (it is,
  via nodemailer + the `mail.*` block), documented the `node-engine-mismatch`
  preflight check and the rollback/preflight failure emails, and stripped
  obsolete "PR 1 / PR 2" staging language now that all tiers ship.
- api/http_api.md: added the undocumented `anonymizeAuthor` (GDPR Art. 17)
  call, fixed copyPad/movePad version annotations (1.2.8 → 1.2.9), corrected
  getPadID's param name (readOnlyID → roID), and dropped a reference to a
  non-existent `getEtherpad` API call.
- skins.md: colibris is the current default, not an "experimental" skin for
  a future 2.0.
- localization.md: bare `window._('key')` is unbound and returns undefined;
  recommend `window.html10n.get(...)` / data-l10n-id instead.
- README.md: bumped the v2.2.5 upgrade example to v3.2.0; fixed a
  docker.adoc link to docker.md.
- docker.md: added MAIL_*, ENABLE_METRICS, GDPR_AUTHOR_ERASURE_ENABLED,
  PRIVACY_BANNER_*, PUBLIC_URL, AUTHENTICATION_METHOD, ENABLE_DARK_MODE,
  ENABLE_PAD_WIDE_SETTINGS; fixed the SOCKETIO_MAX_HTTP_BUFFER_SIZE default
  (50000 → 1000000).

New documentation:
- configuration.md (new): how settings + `${VAR:default}` substitution work,
  trustProxy, and — the previously-undocumented feature — running under a
  subpath/ingress via x-proxy-path / X-Forwarded-Prefix / X-Ingress-Path,
  with the sanitizer rules and Traefik/NGINX examples. Wired into the
  VitePress sidebar and the index hero.
- hooks_server-side.md: ccRegisterBlockElements (the server-side companion
  plugin authors miss), exportConvert, exportHTMLSend, createServer,
  restartServer, and clientReady (marked deprecated).
- hooks_client-side.md: aceDrop, acePaste, handleClientTimesliderMessage_<name>.

VitePress build passes. The legacy .adoc set was intentionally left in place
— it still feeds the per-version doc archives published to ether.github.com
at release time (bin/release.ts), so it is not dead and is out of scope here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: address Qodo review — drop .js invocations from configuration.md and CLI help

- configuration.md: the settings-override example referenced a nonexistent
  `node src/node/server.js`. Use the supported launcher instead
  (`bin/run.sh -s <file>`), and note the runtime is server.ts via tsx.
- compactStalePads.ts / compactPad.ts / compactAllPads.ts: their header
  comments and runtime usage output still printed `node bin/*.js`, which
  points at files that don't exist. Switched to the documented
  `pnpm run --filter bin <script>` form so the --help text matches the docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:44:25 +01:00

6.3 KiB

Statistics and metrics

Etherpad tracks runtime statistics about the edit machinery, the database layer, and the Node.js process, and can expose them over HTTP for monitoring.

There are two endpoints:

  • GET /stats — a JSON dump of the internal measured-core collection.
  • GET /stats/prometheus — the same kind of data (plus process/runtime metrics) in the Prometheus text exposition format.

Enabling the endpoints

Both endpoints are gated behind the enableMetrics setting, which defaults to true:

{
  "enableMetrics": true
}

When enableMetrics is false the routes are not registered at all — a request to /stats or /stats/prometheus returns the normal 404 handling, not an empty response. The admin-panel statistics view is unaffected by this setting.

GET /stats (JSON)

Returns the current snapshot of the measured-core collection as JSON. The following metrics are collected:

Metric Type Meaning
totalUsers gauge Number of users currently connected across all pads.
activePads gauge Number of pads with at least one connected user.
connects meter Rate of new client connections.
disconnects meter Rate of client disconnections.
rateLimited meter Rate of messages dropped by the per-connection rate limiter.
pendingEdits counter Edits received but not yet fully processed.
edits timer Time taken to process an incoming USER_CHANGES edit (full handler span).
failedChangesets meter Rate of changesets that failed to apply.
httpRequests timer Duration of HTTP requests served by Express.
http500 meter Rate of HTTP 500 responses.
memoryUsage gauge Process resident set size (process.memoryUsage().rss).
memoryUsageHeap gauge Process heap usage (process.memoryUsage().heapUsed).
lastDisconnect gauge Timestamp (ms) of the most recent socket disconnect.
ueberdb_* gauge One gauge per ueberDB database statistic, e.g. read/write counts and timings (ueberdb_reads, ueberdb_writes, …). The exact set depends on the configured database driver.

Under the hood these are provided by measured-core. To read or extend them from a plugin, require the shared collection:

const stats = require('ep_etherpad-lite/node/stats');
// stats is a measured-core Collection
stats.counter('my_plugin_events').inc();
console.log(stats.toJSON());

GET /stats/prometheus (Prometheus exposition format)

Served from a dedicated prom-client registry. This is the endpoint you point a Prometheus scraper at.

Metrics exposed by default

Metric Type Labels Meaning
etherpad_total_users gauge Total number of connected users.
etherpad_active_pads gauge Total number of active pads.
ueberdb_stats gauge type ueberDB statistics, one series per numeric ueberDB metric (the metric name is carried in the type label).

In addition, the registry calls prom-client's collectDefaultMetrics(), so the standard Node.js / process metrics are also exposed, including (names as emitted by prom-client):

  • process_cpu_user_seconds_total, process_cpu_system_seconds_total, process_cpu_seconds_total
  • process_resident_memory_bytes, process_heap_bytes, process_virtual_memory_bytes
  • process_open_fds, process_max_fds
  • process_start_time_seconds
  • nodejs_eventloop_lag_seconds and the nodejs_eventloop_lag_* family
  • nodejs_active_handles, nodejs_active_requests, nodejs_active_resources (and their _total variants)
  • nodejs_heap_size_total_bytes, nodejs_heap_size_used_bytes, nodejs_external_memory_bytes, nodejs_heap_space_size_*_bytes
  • nodejs_gc_duration_seconds
  • nodejs_version_info

The exact default-metric set is determined by prom-client and the Node.js version, not by Etherpad.

Opt-in scaling-dive metrics (scalingDiveMetrics)

A second, more detailed instrument set was added for the scaling investigation (PR #7756). It is gated behind the scalingDiveMetrics setting, which defaults to false:

{
  "scalingDiveMetrics": false
}

When the flag is off, these metrics are never registered and their recording helpers short-circuit to no-ops, so production deployments pay nothing for the instrumentation. When enabled, the following are added to the /stats/prometheus output:

Metric Type Labels Meaning
etherpad_changeset_apply_duration_seconds histogram Time spent applying an incoming USER_CHANGES message on the server (apply path only; excludes fan-out to other clients). Buckets: 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 5 seconds.
etherpad_socket_emits_total counter type Number of socket.io broadcast emits, bucketed by message type.
etherpad_pad_users gauge padId Active users connected to each pad, keyed by pad id.

Cardinality caution. The scaling-dive metrics carry high-cardinality labels:

  • etherpad_pad_users adds one time series per active pad (padId label). On instances with many pads this can produce a large number of series; stale labels are reset on each scrape so drained pads drop out.
  • etherpad_socket_emits_total uses the type label. To keep cardinality bounded, only a fixed allowlist of known message types is reported; any other (or missing) type is rolled into a single other bucket, so a misbehaving plugin or API caller cannot explode the label space.

Enable scalingDiveMetrics for targeted load-testing or capacity investigations, not as a permanent production default.

Scraping with Prometheus

Add a scrape job pointing at the /stats/prometheus endpoint, for example:

scrape_configs:
  - job_name: etherpad
    metrics_path: /stats/prometheus
    static_configs:
      - targets: ['localhost:9001']

Make sure enableMetrics is true (the default) so the endpoint exists. If your instance is reachable from untrusted networks, restrict access to /stats and /stats/prometheus at your reverse proxy, since they expose operational details about the deployment.