Skip to content

feat(metrics)!: add server label to per-worker series#2396

Open
nicolas-grekas wants to merge 1 commit intophp:mainfrom
nicolas-grekas:metrics-server-label
Open

feat(metrics)!: add server label to per-worker series#2396
nicolas-grekas wants to merge 1 commit intophp:mainfrom
nicolas-grekas:metrics-server-label

Conversation

@nicolas-grekas
Copy link
Copy Markdown
Contributor

Summary

Today's per-worker Prometheus metrics carry only a worker label. Two php_server blocks declaring workers with the same name therefore collide on the same metric series. This PR adds a sibling server label so each (server, worker) pair stays on its own series.

What changes

  • Scope plumbing (new scope.go): Scope opaque type, NextScope() per-block allocator, and a ScopeLabel / SetScopeLabel registry that maps a Scope to a human-readable label.
  • Caddy wiring: each FrankenPHPModule provisions its own scope and, on the first ServeHTTP, resolves a label via cascade — first host of the route's host matcher → user-set Caddy server name → first listener address → numeric id fallback. Registered once via sync.Once.
  • Metrics interface: every method that took (name string) now takes (server, name string). Mirrors the shape of introduces worker name option, use label on worker metrics instead #1376 which introduced the worker label the same way. PrometheusMetrics and nullMetrics are updated in-tree; the basicLabels becomes {\"server\", \"worker\"}.
  • Docs: docs/metrics.md reflects the new label set + cascade.

Breaking change

Embedders implementing frankenphp.Metrics need to widen their method signatures. No runtime behaviour change for users who don't implement that interface.

Test plan

  • Tests (Linux 8.2-8.5 + macOS) — Go unit + caddy integration with updated fixture
  • Lint
  • asan / msan
  • Manual: confirm `frankenphp_total_workers{server="api.example.com",worker="..."}` shows up correctly under a Caddyfile with named hosts

Workers declared in distinct php_server blocks today share a single
"worker" Prometheus label dimension, so metric series collide. This
adds a sibling "server" label so each (server, worker) pair stays on
its own series.

Plumbing:
- New Scope opaque type + NextScope() in scope.go: a per-php_server
  identifier the embedder allocates once per declarative block.
- ScopeLabel(s) / SetScopeLabel(s, label): registry mapping Scope to a
  human-readable label, falling back to the numeric id when none is
  set. Empty labels are ignored.
- WithWorkerScope(Scope) tags a declared worker; worker.scope copies
  it through newWorker.
- Caddy module: each FrankenPHPModule provisions its own scope and on
  the first ServeHTTP resolves a label via the cascade
  (route host matcher -> user-set caddy server name -> first listen
  addr) and registers it via SetScopeLabel.

API change:
- BREAKING: every Metrics interface method that took (name string)
  now takes (server, name string). Embedders implementing
  frankenphp.Metrics need to widen their signatures; PrometheusMetrics
  and the null impl are updated in-tree. Mirrors the shape of php#1376
  (which introduced the "worker" label the same way).
- basicLabels becomes {"server", "worker"}.

The scope mechanism is independently reusable for other per-server
features (future per-server isolation, dispatching, etc.).
@nicolas-grekas
Copy link
Copy Markdown
Contributor Author

I could also move caddy/scopelabel.go et al from #2393 here. For now, I organized these PRs so that they can be merged independently.
Ideally we would agree quickly on this one (including caddy/scopelabel.go et al), merge it, and I'd rebase the other on top.
So: WDYT of the approach here? Worth landing?

Copy link
Copy Markdown
Contributor

@henderkes henderkes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this? Worker names are already unique so where's the win here? If a user sets multiple workers per domain? But this is per-php_server so even for that I'm not sure.

If it's just to better distinguish between different workers without a specific name set, why not change the automatically generated prefix instead?

Comment thread caddy/scopelabel.go
// isAutoServerName reports whether name is one of Caddy's auto-assigned
// server names (srv0, srv1, ...). Anything else is treated as user-set.
func isAutoServerName(name string) bool {
if !strings.HasPrefix(name, "srv") || len(name) <= 3 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a very common thing to prefix servers with, likely not the best check

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants