Repository navigation
atecontroller: the :8080 metrics listener follows OTEL_METRICS_EXPORTER - #1976
Conversation
Its registry leaves over OTLP, so nothing scrapes :8080. Drop the Service and port too.
|
Jeff Luo (@JeffLuoo) Lior Lieberman (@LiorLieberman) PTAL when you get a chance. Thank you! |
|
Thank you for this PR, and for the cleanup in managerOptions. I suggest that we hold this PR until #2067 decides the metrics policy. |
|
Sounds good, holding until #2067 is decided. Will rebase after. |
|
Merged main and resolved the conflict. Still holding for #2067. |
|
Thank you for holding this. #2067 has decided the policy, and task 3 there replaces this PR. |
|
Jeff Luo (@JeffLuoo) Understood, thanks. I will hold until the OTel env var parsing lands and then rework this. |
|
The change I mentioned is merged. |
|
Jeff Luo (@JeffLuoo) Reworked on top of #2190, PTAL. |
Jeff Luo (JeffLuoo)
left a comment
There was a problem hiding this comment.
The implementation and tests look good. Two comments on docs/observability.md inline.
| ### Scraping instead of pushing | ||
|
|
||
| ateapi, atelet, atenet-router and the credential provider also serve every instrument on their Prometheus `/metrics` endpoint. A cluster that scrapes those endpoints sets `OTEL_METRICS_EXPORTER=none` on the components, so each series reaches the backend once. With `none` the components install no OTLP metric reader and keep the Prometheus one; traces and logs are unaffected. atecontroller then registers its instruments (`ate.workerpool.*`) on controller-runtime's registry, so the manager's `:8080` serves them next to the controller-runtime families. ateom serves no endpoint of its own, so leave the variable unset on the worker pods, or it exports no metrics at all. The variable takes a comma-separated list, with the same rules as `OTEL_LOGS_EXPORTER`. `otlp` (the default) keeps the push, and `prometheus` or `none` stops it. `/metrics` stays on for each value. ateom serves no `/metrics`, so there `prometheus` is skipped as unknown with a warning, and the push stays on unless the variable is `none`. | ||
| ateapi, atelet, atenet-router and the credential provider also serve every instrument on their Prometheus `/metrics` endpoint. A cluster that scrapes those endpoints sets `OTEL_METRICS_EXPORTER=prometheus` on the components, so each series reaches the backend once. With `prometheus` the components install no OTLP metric reader and keep the Prometheus one; traces and logs are unaffected. `none` stops the push too, and leaves their `/metrics` on. ateom serves no endpoint of its own, so leave the variable unset on the worker pods, or it exports no metrics at all. The variable takes a comma-separated list, with the same rules as `OTEL_LOGS_EXPORTER`. `otlp` (the default) keeps the push, and `prometheus` or `none` stops it. ateom serves no `/metrics`, so there `prometheus` is skipped as unknown with a warning, and the push stays on unless the variable is `none`. |
There was a problem hiding this comment.
Two stale details in this paragraph:
"ateom serves no endpoint of its own, so leave the variable unset on the worker pods, or it exports no metrics at all"was written whennonewas the scrape-only setting. Two sentences later, this paragraph notes thatateomskipsprometheusas unknown and keeps the push on unless the variable isnone(andatecontrollerdoes not propagateOTEL_METRICS_EXPORTERto worker pods). Please remove that sentence or scope it tonone."otlp (the default) keeps the push"— withmetricsExporterssettingdef = Exporters{ExporterOTLP: true, exporterPrometheus: true}for components that serve/metrics, unset defaults tootlp,prometheus(otlponly onateom), which matters because the next paragraph contrastsUnsetwithotlp.
| ateapi, atelet, atenet-router and the credential provider also serve every instrument on their Prometheus `/metrics` endpoint. A cluster that scrapes those endpoints sets `OTEL_METRICS_EXPORTER=none` on the components, so each series reaches the backend once. With `none` the components install no OTLP metric reader and keep the Prometheus one; traces and logs are unaffected. atecontroller then registers its instruments (`ate.workerpool.*`) on controller-runtime's registry, so the manager's `:8080` serves them next to the controller-runtime families. ateom serves no endpoint of its own, so leave the variable unset on the worker pods, or it exports no metrics at all. The variable takes a comma-separated list, with the same rules as `OTEL_LOGS_EXPORTER`. `otlp` (the default) keeps the push, and `prometheus` or `none` stops it. `/metrics` stays on for each value. ateom serves no `/metrics`, so there `prometheus` is skipped as unknown with a warning, and the push stays on unless the variable is `none`. | ||
| ateapi, atelet, atenet-router and the credential provider also serve every instrument on their Prometheus `/metrics` endpoint. A cluster that scrapes those endpoints sets `OTEL_METRICS_EXPORTER=prometheus` on the components, so each series reaches the backend once. With `prometheus` the components install no OTLP metric reader and keep the Prometheus one; traces and logs are unaffected. `none` stops the push too, and leaves their `/metrics` on. ateom serves no endpoint of its own, so leave the variable unset on the worker pods, or it exports no metrics at all. The variable takes a comma-separated list, with the same rules as `OTEL_LOGS_EXPORTER`. `otlp` (the default) keeps the push, and `prometheus` or `none` stops it. ateom serves no `/metrics`, so there `prometheus` is skipped as unknown with a warning, and the push stays on unless the variable is `none`. | ||
|
|
||
| atecontroller's `:8080` listener follows the variable. Unset, or a list with both, it pushes and serves; `otlp` pushes and closes the listener; `prometheus` serves only, and registers its instruments (`ate.workerpool.*`) on controller-runtime's registry, so `:8080` serves them next to the controller-runtime families; `none` does neither. |
There was a problem hiding this comment.
When the variable is unset or otlp,prometheus, :8080 is open but does not serve the ate.workerpool.* instruments: because OTLP push is enabled, InitMetricsBridged keeps the OTel instruments off ctrlmetrics.Registry to avoid pushing a second copy through the bridge, and only registers them on :8080 under prometheus alone. Saying "it pushes and serves" makes a reader expect the full set on :8080 in the default mode. Please state which metrics :8080 serves in each mode:
| atecontroller's `:8080` listener follows the variable. Unset, or a list with both, it pushes and serves; `otlp` pushes and closes the listener; `prometheus` serves only, and registers its instruments (`ate.workerpool.*`) on controller-runtime's registry, so `:8080` serves them next to the controller-runtime families; `none` does neither. | |
| atecontroller's `:8080` listener follows the variable. When the variable is unset or `otlp,prometheus`, atecontroller pushes every metric over OTLP, and `:8080` serves only the controller-runtime families. With `otlp`, atecontroller pushes every metric and closes `:8080`. With `prometheus`, atecontroller does not push, and `:8080` serves the controller-runtime families and the `ate.workerpool.*` instruments. With `none`, atecontroller does not push and closes `:8080`. |
| } | ||
|
|
||
| // servePull follows the policy table: unset and the list keep both paths. | ||
| func TestInitMetricsBridgedServePull(t *testing.T) { |
There was a problem hiding this comment.
Please add a name field. The empty value row currently shows up as subtest #00.
| exporters := metricsExporters(ctx, true) | ||
| servePull = exporters.Has(exporterPrometheus) | ||
| switch { | ||
| case exporters.Has(ExporterOTLP): |
There was a problem hiding this comment.
In this branch :8080 misses ate.workerpool.*, so unset and otlp,prometheus are not following the policy. This can be a follow-up, but please open an issue to track this in that case.
There was a problem hiding this comment.
Tracked in #2402, thanks. A fix needs a separate registry to avoid pushing them twice.
| // own Prometheus registry. OTEL_METRICS_EXPORTER picks where they go: the | ||
| // OTLP push, which pads the bridged queue histograms so the Telemetry API | ||
| // accepts idle ones, and the manager's scrape listener. | ||
| mp, servePull, err := serverboot.InitMetricsBridged(ctx, serviceName, ctrlmetrics.Registry, padEmptyExponentialHistograms) |
There was a problem hiding this comment.
Let's log the resolved exporters and whether :8080 is on.
| func managerOptions(egressMITMCAPool types.NamespacedName, servePull bool) ctrl.Options { | ||
| metricsAddr := "0" // "0" disables the server. | ||
| if servePull { | ||
| metricsAddr = metricsserver.DefaultBindAddress |
There was a problem hiding this comment.
Nit: we could use 8080 as a const that we can reference in tests.
| for _, servePull := range []bool{true, false} { | ||
| opts := managerOptions(types.NamespacedName{Namespace: "ate-system", Name: "pool"}, servePull) |
There was a problem hiding this comment.
Let's rewrite as a tests := []struct{...} table with t.Run, following the repo's convention.
| ### Scraping instead of pushing | ||
|
|
||
| ateapi, atelet, atenet-router and the credential provider also serve every instrument on their Prometheus `/metrics` endpoint. A cluster that scrapes those endpoints sets `OTEL_METRICS_EXPORTER=none` on the components, so each series reaches the backend once. With `none` the components install no OTLP metric reader and keep the Prometheus one; traces and logs are unaffected. atecontroller then registers its instruments (`ate.workerpool.*`) on controller-runtime's registry, so the manager's `:8080` serves them next to the controller-runtime families. ateom serves no endpoint of its own, so leave the variable unset on the worker pods, or it exports no metrics at all. The variable takes a comma-separated list, with the same rules as `OTEL_LOGS_EXPORTER`. `otlp` (the default) keeps the push, and `prometheus` or `none` stops it. `/metrics` stays on for each value. ateom serves no `/metrics`, so there `prometheus` is skipped as unknown with a warning, and the push stays on unless the variable is `none`. | ||
| ateapi, atelet, atenet-router and the credential provider also serve every instrument on their Prometheus `/metrics` endpoint. A cluster that scrapes those endpoints sets `OTEL_METRICS_EXPORTER=prometheus` on the components, so each series reaches the backend once. With `prometheus` the components install no OTLP metric reader and keep the Prometheus one; traces and logs are unaffected. `none` stops the push too, and leaves their `/metrics` on. The variable takes a comma-separated list, with the same rules as `OTEL_LOGS_EXPORTER`. Unset is `otlp,prometheus`, or `otlp` alone on ateom. `otlp` keeps the push, and `prometheus` or `none` stops it. ateom serves no `/metrics`, so there `prometheus` is skipped as unknown with a warning, and the push stays on unless the variable is `none`. |
Name the table rows, make :8080 a const, log the resolved exporters, and mark the /metrics note temporary (agent-substrate#2067).
|
Jeff Luo (@JeffLuoo) Krisztian F (@krisztianfekete) Addressed the review, PTAL when you get a chance. Thank you! |
Jeff Luo (JeffLuoo)
left a comment
There was a problem hiding this comment.
Just a few more nits
There was a problem hiding this comment.
Here it still says "which the manager serves on an unscraped :8080". Now that :8080 is disabled under otlp/none and scraped under prometheus, could we update the text here to match line 338?
| serverboot.Fatal(ctx, "Failed to initialize metrics", err) | ||
| } | ||
| defer serverboot.ShutdownProvider("MeterProvider", mp.Shutdown) | ||
| slog.InfoContext(ctx, "Metrics listener", slog.Bool("enabled", servePull), slog.String("addr", metricsPullAddr)) |
There was a problem hiding this comment.
When servePull is false, managerOptions binds to metricsOffAddr ("0"), so logging addr=":8080" alongside enabled=false is misleading. Consider logging the resolved opts.Metrics.BindAddress (or omitting addr when disabled).
There was a problem hiding this comment.
Fixed in 5529baa, logs the resolved address.
| } | ||
| if metricsPushEnabled(ctx, true) { | ||
| exporters := metricsExporters(ctx, true) | ||
| slog.InfoContext(ctx, "Metrics exporters resolved", slog.String("exporters", exporters.String())) |
There was a problem hiding this comment.
nit - if we move this log into metricsExporters(ctx) (or newMeterProvider), InitMetrics and InitMetricsPushOnlyVia will also log the resolved exporters at startup, not just atecontroller.
There was a problem hiding this comment.
Fixed in 5529baa, moved into metricsExporters.
|
Jeff Luo (@JeffLuoo) Krisztian F (@krisztianfekete) Addressed, PTAL. The e2e failure was httpbin.org returning EOF, unrelated; the new push reruns it. |
| @@ -445,10 +445,13 @@ func bridgedRegistry(t *testing.T) *prometheus.Registry { | |||
| func TestInitMetricsBridgedPushesEachMetricOnce(t *testing.T) { | |||
There was a problem hiding this comment.
Let's make this table-driven over "", otlp and otlp,prometheus. If the code registered the OTel reader on reg in the default mode, every instrument would be pushed twice, and no test would fail.
There was a problem hiding this comment.
Done in bec44ef, now covers unset, otlp and otlp,prometheus.
|
Merged main. The e2e failure was a restore wakeup probe timeout in TestMultipleDurableDirLifecycle, unrelated to the listener; the push reruns it. |
The
:8080listener followsOTEL_METRICS_EXPORTER, per the policy in #2067. Unset keeps push and:8080,otlpcloses:8080,prometheusserves only,nonedoes neither.Fixes #933