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

147 lines
6.3 KiB
Markdown

# 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](https://prometheus.io/) text exposition format.
## Enabling the endpoints
Both endpoints are gated behind the `enableMetrics` setting, which defaults to
`true`:
```json
{
"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](https://github.com/ether/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`](https://github.com/yaorg/node-measured/tree/master/packages/measured-core).
To read or extend them from a plugin, require the shared collection:
```js
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`](https://github.com/siimon/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`:
```json
{
"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:
```yaml
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.