Skip to content

atecontroller: the :8080 metrics listener follows OTEL_METRICS_EXPORTER - #1976

Open
Eric Curtin (ericcurtin) wants to merge 7 commits into
agent-substrate:mainfrom
ericcurtin:atecontroller-no-metrics-listener
Open

Eric Curtin (ericcurtin) wants to merge 7 commits into
agent-substrate:mainfrom
ericcurtin:atecontroller-no-metrics-listener

Conversation

@ericcurtin

@ericcurtin Eric Curtin (ericcurtin) commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

The :8080 listener follows OTEL_METRICS_EXPORTER, per the policy in #2067. Unset keeps push and :8080, otlp closes :8080, prometheus serves only, none does neither.

Fixes #933

  • Tests pass
  • Appropriate changes to documentation are included in the PR

Its registry leaves over OTLP, so nothing scrapes :8080. Drop the
Service and port too.
@ericcurtin

Copy link
Copy Markdown
Contributor Author

Jeff Luo (@JeffLuoo) Lior Lieberman (@LiorLieberman) PTAL when you get a chance. Thank you!

@JeffLuoo

Copy link
Copy Markdown
Collaborator

Thank you for this PR, and for the cleanup in managerOptions.

I suggest that we hold this PR until #2067 decides the metrics policy.

@ericcurtin

Copy link
Copy Markdown
Contributor Author

Sounds good, holding until #2067 is decided. Will rebase after.

@ericcurtin

Copy link
Copy Markdown
Contributor Author

Merged main and resolved the conflict. Still holding for #2067.

@JeffLuoo

Jeff Luo (JeffLuoo) commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Thank you for holding this. #2067 has decided the policy, and task 3 there replaces this PR. :8080 must follow OTEL_METRICS_EXPORTER, not always turn off. There are multiple PRs at the moment trying to add a function to parse OTel standard environment variables. I will suggest still hold until the env var parsing part is done, and then rework this PR.

@ericcurtin

Copy link
Copy Markdown
Contributor Author

Jeff Luo (@JeffLuoo) Understood, thanks. I will hold until the OTel env var parsing lands and then rework this.

@JeffLuoo

Jeff Luo (JeffLuoo) commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Eric Curtin (@ericcurtin)

Thank you for waiting. Here is the rework, based on the policy in #2067. Please rebase after #2190 merges. That PR moves OTEL_METRICS_EXPORTER to the shared parser and makes prometheus a known name.

@JeffLuoo

Copy link
Copy Markdown
Collaborator

The change I mentioned is merged.

@ericcurtin Eric Curtin (ericcurtin) changed the title atecontroller: disable the unused metrics listener atecontroller: the :8080 metrics listener follows OTEL_METRICS_EXPORTER Oct 8, 2026
@ericcurtin

Copy link
Copy Markdown
Contributor Author

Jeff Luo (@JeffLuoo) Reworked on top of #2190, PTAL.

@JeffLuoo Jeff Luo (JeffLuoo) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The implementation and tests look good. Two comments on docs/observability.md inline.

Comment thread docs/observability.md Outdated
### 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`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Two stale details in this paragraph:

  1. "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 when none was the scrape-only setting. Two sentences later, this paragraph notes that ateom skips prometheus as unknown and keeps the push on unless the variable is none (and atecontroller does not propagate OTEL_METRICS_EXPORTER to worker pods). Please remove that sentence or scope it to none.
  2. "otlp (the default) keeps the push" — with metricsExporters setting def = Exporters{ExporterOTLP: true, exporterPrometheus: true} for components that serve /metrics, unset defaults to otlp,prometheus (otlp only on ateom), which matters because the next paragraph contrasts Unset with otlp.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a9ea0d7, thanks.

Comment thread docs/observability.md Outdated
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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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:

Suggested change
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`.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a9ea0d7, thanks.

}

// servePull follows the policy table: unset and the list keep both paths.
func TestInitMetricsBridgedServePull(t *testing.T) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please add a name field. The empty value row currently shows up as subtest #00.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a46d59d, thanks.

exporters := metricsExporters(ctx, true)
servePull = exporters.Has(exporterPrometheus)
switch {
case exporters.Has(ExporterOTLP):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Tracked in #2402, thanks. A fix needs a separate registry to avoid pushing them twice.

Comment thread cmd/atecontroller/main.go
// 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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's log the resolved exporters and whether :8080 is on.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a46d59d, thanks.

Comment thread cmd/atecontroller/main.go Outdated
func managerOptions(egressMITMCAPool types.NamespacedName, servePull bool) ctrl.Options {
metricsAddr := "0" // "0" disables the server.
if servePull {
metricsAddr = metricsserver.DefaultBindAddress

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: we could use 8080 as a const that we can reference in tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a46d59d, thanks.

Comment thread cmd/atecontroller/main_test.go Outdated
Comment on lines +91 to +92
for _, servePull := range []bool{true, false} {
opts := managerOptions(types.NamespacedName{Namespace: "ate-system", Name: "pool"}, servePull)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's rewrite as a tests := []struct{...} table with t.Run, following the repo's convention.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a46d59d, thanks.

Comment thread docs/observability.md Outdated
### 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`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This conflicts with what we discussed over at #2067 where none means no /metrics. Please it as temporary until we move the probes off :9090, and link #2067, so readers don't take it as the policy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a46d59d, thanks.

Name the table rows, make :8080 a const, log the resolved exporters, and
mark the /metrics note temporary (agent-substrate#2067).
@ericcurtin

Copy link
Copy Markdown
Contributor Author

Jeff Luo (@JeffLuoo) Krisztian F (@krisztianfekete) Addressed the review, PTAL when you get a chance. Thank you!

@JeffLuoo Jeff Luo (JeffLuoo) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just a few more nits

Comment thread docs/observability.md Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5529baa, thanks.

Comment thread cmd/atecontroller/main.go Outdated
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))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5529baa, logs the resolved address.

Comment thread internal/serverboot/serverboot.go Outdated
}
if metricsPushEnabled(ctx, true) {
exporters := metricsExporters(ctx, true)
slog.InfoContext(ctx, "Metrics exporters resolved", slog.String("exporters", exporters.String()))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5529baa, moved into metricsExporters.

@ericcurtin

Copy link
Copy Markdown
Contributor Author

Jeff Luo (@JeffLuoo) Krisztian F (@krisztianfekete) Addressed, PTAL. The e2e failure was httpbin.org returning EOF, unrelated; the new push reruns it.

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

Labels

area/observability kind/cleanup Small fixes that are not bugs, for example a typo in a code comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

atecontroller metrics are never scraped

4 participants