7.9 KiB
overleaf-cep monitoring (Prometheus + Grafana + exporters)
Stack in /data_1/docker/compose (modeled on the OlliTeX toolkit compose from
/data_1/image_mining/the_diff/overleaf/toolkit/compose.yaml), all of it served
through the existing nginx facade. Container/service names are unprefixed
(monitoring-only, added 2026-10-27; renamed from ollitex-* on 2026-10-27).
Services (all on the shared external network overleaf-network)
| service | role |
|---|---|
prometheus |
TSDB, 15d retention, 127.0.0.1:9090 (SSH-tunnel/admin only) |
grafana |
UI, no host port — served under https://<site>/grafana/ |
mongodb-exporter |
percona exporter for the stack Mongo (sharelatex db) |
mongodb-exporter-probe |
busybox spider: /metrics must answer (exporter image has no shell) |
redis-exporter |
oliver006 exporter for the stack Redis |
redis-exporter-probe |
busybox spider for the redis exporter /metrics |
mongo-probe |
mongosh ping — protocol-level Mongo liveness (stack's own image) |
node-exporter |
host CPU/mem/fs signals (reads host /, /proc, /sys ro) |
instance-sensor |
PR yu-i-i/overleaf-cep#178 sensors as live gauges (see below) |
The repo's own helpers down_grafana.sh / exec_grafana.sh now address this
grafana service directly.
What is measured
- Overleaf app sensors — the sharelatex image ships
@overleaf/metricson the web process (overleaf:3000/metrics,app="web-api"):num_active_users(live editor users),timer_http_request(summaries with quantiles),mongo_command_started,nodejs_eventloop_lag_*, GC/heap,security_http_auth_check_credentials,editor_join_project,tpds_add_doc,lock_mongoTransaction_*. (In-image side processes like compile:4000bind 127.0.0.1 inside their container — not scrapable.) - Instance stats — PR #178, integrated into Prometheus/Grafana instead of
the admin page (
instance-sensor, port 9464):- users: total + active(24h) — optional standard/guest split via
INSTANCE_STATS_SEGMENTATION=true(PR#176 rule:'guest-user'inusers.adminRoles) - projects: total + active(24h,
lastUpdated) - files: total docs
- storage: mongodb
dbStats.totalSize, filestoreduofseaweedfs-volume/data(ro), redis RAM (used_memory) + redis disk (AOF size) Computed fresh on every scrape (no hourly Mongo snapshot state).
- users: total + active(24h) — optional standard/guest split via
- MongoDB — connections, per-db data/storage size, oplog free space.
- Redis — memory, clients, command rate, keyspace hit/miss, AOF.
- Host — CPU, memory, root fs, load, uptime, network.
- Filestore (SeaweedFS master
:9327, native prometheus) — leader, under-replicated volumes, in-flight uploads/downloads. - Scrape self-health —
upper job + the monitoring stack itself.
URLs / access
- Grafana:
https://psintern.neuro.uni-bremen.de/grafana/login:admin/ (see top section of.env,GRAFANA_ADMIN_USER/PASSWORD; unset → Grafana fallbackadmin/admin) - Prometheus:
http://127.0.0.1:9090(local only — SSH tunnel from a laptop) - Dashboard: folder Overleaf → Overleaf — instance overview
(file-provisioned; pinned datasource uid
prometheus,grafana/provisioning/datasources/prometheus.yml)
Git hygiene
Data directories are git-ignored per folder: grafana/.gitignore →
grafana-data, prometheus/.gitignore → prometheus-data (verified with
git check-ignore). .env itself is in the root .gitignore. Keep new
runtime dirs out of git by adding one line to the folder .gitignore as you
add them.
SMTP
Grafana reuses the stack's Overleaf relay — no separate Grafana mail vars;
grafana/compose.yaml reads the existing Overleaf settings as a single
source of truth:
| Grafana setting | taken from |
|---|---|
GF_SMTP_ENABLED=true |
fixed |
GF_SMTP_HOST |
OVERLEAF_EMAIL_SMTP_HOST:OVERLEAF_EMAIL_SMTP_PORT |
GF_SMTP_USER |
OVERLEAF_EMAIL_SMTP_USER |
GF_SMTP_PASSWORD |
OVERLEAF_EMAIL_PASSWORD |
GF_SMTP_FROM_ADDRESS |
OVERLEAF_EMAIL_FROM_ADDRESS |
GF_SMTP_FROM_NAME |
"Overleaf Monitoring" (display label only) |
GF_SMTP_SKIP_VERIFY=false |
fixed |
Used for Grafana's reset-password mail (verified live: sent via the relay,
server responded 200 {"message":"Email sent"}). Grafana 11's alerting has no
built-in SMTP contact point — SMTP covers account e-mail; alert routing would
need integrations (webhook/Slack/…).
Gotcha (learned 2026-10-27): the admin account's email must be set.
Grafana creates the admin user with email admin@localhost; the reset-mail
endpoint POST /api/user/password/send-reset-email {"userOrEmail": …} matches
users by that email and answers 200 "Email sent" even when nobody matches
(anti user-enumeration) — so it silently sends nothing. Fixed on two layers:
- self-healing on fresh data:
GF_SECURITY_ADMIN_EMAILingrafana/compose.yaml=${OVERLEAF_ADMIN_EMAIL:-overleaf@uni-bremen.de}— honored only when Grafana creates the admin (i.e. after a grafana-data wipe); it never rewrites an existing user row. - existing install:
update user set email='overleaf@uni-bremen.de' where login='admin'ingrafana-data/grafana.db(grafana stopped). Verify withselect email from user where login='admin'. The working relay itself was proven with a rawsmtplib.SMTP_SSLtest (TLS-465 + auth + RCPT all OK).
After a grafana-data wipe (fresh admin creation): login =
GRAFANA_ADMIN_USER/GRAFANA_ADMIN_PASSWORD from .env; e-mail = GF_SECURITY_ADMIN_EMAIL
(default overleaf@uni-bremen.de); dashboards re-provision automatically
(file provider); admin UI settings/annotations are lost.
To disable SMTP: set GF_SMTP_ENABLED: "false" in grafana/compose.yaml +
docker compose up -d grafana.
Ops notes / gotchas (learned while bringing this up)
- percona/mongodb_exporter 0.43 ships most collectors OFF by default
(a default run answers only
mongodb_up+ go/process metrics). Working flags for this stack are inmongodb-exporter/compose.yaml:--collector.diagnosticdata --collector.dbstats --collector.dbstatsfreestorage --collector.replicasetstatus(verified against this mongo 9.0). - Bind-mount ownership:
prometheus/prometheus-datamust be owned by uid 65534,grafana/grafana-databy uid 472 (both images run unprivileged; docker creates fresh bind-mount dirs as root). Pre-create +chownif the dirs are ever deleted. - Grafana subpath:
GF_SERVER_SERVE_FROM_SUB_PATH=true+GF_SERVER_ROOT_URL=https://<site>/grafana/— nginx serves/grafana/without stripping the prefix (seenginx/nginx.conf). After editingnginx.conf:docker exec nginx nginx -s reload(conf is bind-mounted, not baked in). - Grafana 11 login via API:
POST /grafana/loginwith JSON{user, password}(the oldPOST /api/loginis gone; form-encoded POSTs returnform-auth.invalid). - Healthchecks: compose interpolates
${VAR}inside healthcheck strings at load time —MONGO_HOSTNAME/MONGO_PORTtherefore live in.env. - The
instance-sensorimage is a local build:docker build -t overleaf-instance-sensor:local ./instance-sensor.
Adding/retiring
Everything opt-in per service; remove a block from the root compose.yaml
and docker compose <svc> down to retire one. Image pins: .env
(commented block in the monitoring section).