Skip to content

feat(orchestration): route Gym's Lens telemetry through the job collector - #3619

Merged
iguehring-nvidia merged 4 commits into
mainfrom
iguehring/orchestrator-lens-telemetry
Sep 30, 2026
Merged

iguehring-nvidia merged 4 commits into
mainfrom
iguehring/orchestrator-lens-telemetry

Conversation

@iguehring-nvidia

@iguehring-nvidia iguehring-nvidia commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Standalone against main since #3610 (the collector) merged as f2f446a. Revision of 2026-09-30: rebased onto main (28c6035), the feature squashed into one commit, plus the two review fixes as their own commits (display-name doc, submit-time check for the telemetry extra) and a comment trim.

What

Gym's own servers become producers for the collector that #3610 runs beside every job:

  • The driver is installed with the telemetry extra and started with the environment that switches nemo-lens on and points it at the collector on localhost: NEMO_GYM_OTEL_ENABLED, NEMO_GYM_OTEL_RUN_ID (the gym job id), NEMO_GYM_OTEL_SPAN_GROUPS, NEMO_GYM_OTEL_LOGS_ENABLED, OTEL_EXPORTER_OTLP_ENDPOINT/_PROTOCOL. Every server the driver starts inherits it; an explicit driver.env value wins.
  • otel.gym_telemetry (default on) switches Gym's own nemo-lens telemetry; it needs driver.gym_install, where the telemetry extra is installed, and the submit fails with a message naming both ways out when that is missing. Off keeps the collector for engine and node metrics only.
  • otel.gym_span_groups (default default,verify) selects Gym's span groups for the job; otel.gym_logs (default on) ships Gym's Python logging as OTel logs. Because nemo-lens builds its log exporter over gRPC regardless of the protocol setting, logs get the collector's gRPC port through OTEL_EXPORTER_OTLP_LOGS_ENDPOINT.
  • The collector keeps each producer's own service.name as service.name.override before stamping the routing identity (transform/identity), accepts OTLP metrics, and derives latency histograms and call counters from spans with the span_metrics connector, keyed by span name, producer and sandbox provider, so sandbox start/exec latency and error rates need no metric of their own.
  • The collector now runs for every enabled job, not only jobs with a model service: a type: api policy or a CPU-only judge run produces rollout and verify telemetry all the same. The Prometheus receiver is rendered only when there are scrape jobs.

Why

#3610 gives engine and node metrics with a per-run identity. This PR adds the application layer: how long rollouts and verifications take, one trace per rollout across agent, model and resources servers, request latency per route, sandbox operations, and trace-correlated logs, all under the same run_id/user labels on the same boards.

No issue filed; follow-up to #3610 as planned there.

Validation

  • pytest tests/unit_tests/test_orchestration_otel.py tests/unit_tests/test_slurm_script.py tests/unit_tests/test_slurm_executor.py tests/unit_tests/test_orchestration_api.py tests/unit_tests/test_fern_docs_links.py: 363 passed (including the docs-link tests). The executor bookkeeping tests, which build model-less configs, now set otel.enabled: false, since the collector no longer skips such jobs.
  • pre-commit run --files ... on the changed files: pass.

Rollout evidence

Slurm cluster, vLLM policy, gym eval submit, nemo-lens in the driver venv:

  • scicode (jobs 7329053, 7329528): gym.rollout.duration_ms, gym.verify.* (with the verify group) and FastAPI request histograms per Gym server in the metrics backend; one trace per job visible in the traces backend with run_id on every span.
  • Terminal-Bench 4 on OpenSandbox (7331831; full runs 7332698, 7335617, 7336762, 7337676): traces_span_metrics_* series for gym.sandbox.start/gym.sandbox.exec with the provider label, including error status on failed creates.
  • Logs (7335821, 7337676): Gym log records reach the collector trace-correlated once routed to the gRPC port; an earlier run with the HTTP port lost them all and hung the driver at exit in the exporter's retry loop, which is why the per-signal endpoint is set explicitly.

Compatibility

Adds fields with defaults; no change for jobs with a model service beyond the extra telemetry. Jobs without a model service now get a collector too (they got none in #3610). otel.gym_span_groups/gym_logs tune or turn off Gym-side telemetry; otel.enabled: false turns the whole thing off.

@copy-pr-bot

copy-pr-bot Bot commented Sep 22, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions

Copy link
Copy Markdown
Contributor

@iguehring-nvidia
iguehring-nvidia force-pushed the iguehring/orchestrator-lens-telemetry branch from 2ce46de to 7afa366 Compare September 22, 2026 15:39
@iguehring-nvidia iguehring-nvidia self-assigned this Sep 22, 2026
@iguehring-nvidia iguehring-nvidia added the area:orchestration Job submission, deployment, services, and lifecycle management label Sep 22, 2026
@iguehring-nvidia
iguehring-nvidia force-pushed the iguehring/orchestrator-otel-collector branch from e53d170 to de933b7 Compare September 23, 2026 08:13
@iguehring-nvidia
iguehring-nvidia force-pushed the iguehring/orchestrator-lens-telemetry branch from e845e61 to da5a708 Compare September 23, 2026 08:13
@copy-pr-bot

copy-pr-bot Bot commented Sep 23, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@iguehring-nvidia
iguehring-nvidia force-pushed the iguehring/orchestrator-otel-collector branch from de933b7 to 1747dc4 Compare September 23, 2026 10:15
@iguehring-nvidia
iguehring-nvidia force-pushed the iguehring/orchestrator-lens-telemetry branch 3 times, most recently from 7724f74 to 4720610 Compare September 23, 2026 12:29
@iguehring-nvidia
iguehring-nvidia marked this pull request as ready for review September 23, 2026 13:47
@nemo-automation-bot
nemo-automation-bot Bot requested review from a team September 23, 2026 13:48
@yaoyu-33 yaoyu-33 added complexity:medium Single-domain change with interacting parts or a moderate review surface feature New capabilities, enhancements, or enablement work needs-review PR is ready for code review and waiting on a reviewer labels Sep 23, 2026
Base automatically changed from iguehring/orchestrator-otel-collector to main September 23, 2026 19:17
@iguehring-nvidia
iguehring-nvidia force-pushed the iguehring/orchestrator-lens-telemetry branch from 4720610 to dc5426e Compare September 23, 2026 21:04

@oyilmaz-nvidia oyilmaz-nvidia left a comment

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.

So, this PR does not start the OTel collector itself, it only wires Gym's servers to export telemetry to a collector already started.

In the clusters, most likely we won't be able to send data outside of the cluster. So, it's not convenient to start Grapaha and related system, and then submit the task.

I guess we should either start grapaha, etc with the script or save the data to a local disk that can be imported by the grapaha.

`slurm_job_id`, `benchmark`, `cluster` and `model`, plus `service.name` set to the routing identity
and `service.name.override` to the producer's display name.
and `service.name.override` to the producer's display name: each Gym server keeps its own name
(`nemo-gym/<server>`) and the scraped engine carries `component`.

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.

docs claim service.name.override equals just component, but the collector config actually sets it to component/name (e.g. gym-vllm/policy). Users filtering dashboards per the docs will get empty results.

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.

Thx! Raising a new revision.

repo=gi.repo if gi else None,
ref=gi.ref if gi else None,
prepare_cmd=prepare_cmd,
extras=(GYM_TELEMETRY_EXTRA,) if observed else (),

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.

when otel.enabled: true (the default) but driver.gym_install isn't set, the telemetry extra is requested but render_driver_entrypoint only emits the install line inside its if repo and ref: branch, so nemo-lens never gets installed. Telemetry silently no-ops with no visible error.

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.

Thx! Raising a new revision.

@github-actions github-actions Bot added sla:author-overdue Author response is over the one-business-day SLA and removed sla:review-overdue Review response is over the one-business-day SLA labels Sep 28, 2026
@iguehring-nvidia

Copy link
Copy Markdown
Contributor Author

So, this PR does not start the OTel collector itself, it only wires Gym's servers to export telemetry to a collector already started.

In the clusters, most likely we won't be able to send data outside of the cluster. So, it's not convenient to start Grapaha and related system, and then submit the task.

I guess we should either start grapaha, etc with the script or save the data to a local disk that can be imported by the grapaha.

▎ So, this PR does not start the OTel collector itself, it only wires Gym's servers to export telemetry to a collector already started.

Correct. For runs through the Gym orchestrator the collector is started per job by #3610, merged last week, as the first srun step of the sbatch script. We need that collector regardless of Gym's own telemetry, because vLLM and the GPU exporters only expose Prometheus endpoints and something on the node has to scrape them and stamp the run identity. This PR wires Gym's servers into that same collector. They could talk to a node-local collector directly, but going through ours means one place stamps run_id, benchmark, cluster and user on Gym rows and engine rows alike, one place fans out to the destinations.

From the collector there are two destinations, both configuration:

  1. The managed Grafana stack (Mimir, Tempo, Loki) in our team's org. Needs HTTPS egress from the compute node plus a token, and that works on our clusters. I verified it via a POC.
  2. The cluster's node-local collector, operated by the infrastructure team, which forwards into their ClickHouse, read through their Grafana. No egress from the job at all, no token in the job. This is the ClickHouse reuse @ananthsub suggested. It is a separate small follow-up on top of this PR.

I have verified both options in a POC.

▎ I guess we should either start Grafana etc with the script or save the data to a local disk that can be imported by the Grafana.

In both options the Grafana already exists and is operated by another team, so it does not need to be started per job. That has the benefit that all runs will appear in the same dashboard. Local disk is already covered since #3610: the collector writes everything as OTLP JSON to /otel/, and this PR adds Gym's spans, metrics and logs to those files. A collector with the file receiver replays them into any backend, including the docker stack from #2935, so submit-now-look-later on a laptop works without any live endpoint.

iguehring-nvidia and others added 4 commits September 29, 2026 16:23
…ctor

The driver installs Gym with the telemetry extra and receives the environment that
switches nemo-lens on and points it at the job collector on localhost; every Gym
server process then pushes spans, metrics and logs into the pipeline the collector
PR (#3610) set up. The collector keeps each producer's own service.name as the
display identity, derives latency and call-count series from spans with the
span_metrics connector, and runs for every job whether or not it has a model
service to scrape. Logs use the collector's gRPC port because nemo-lens builds its
log exporter over gRPC regardless of the protocol setting.

Config: otel.gym_span_groups (default "default,verify") and otel.gym_logs (default on).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
…ervice

The collector names the scrape job <component>/<service>, so the engine's
service.name.override is gym-vllm/policy, not the bare component the page said.
A renderer test pins the shape.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
…alled

The telemetry extra is installed with the driver's gym_install checkout. Without
one, Lens was switched on in a driver that could not import it and the run
exported engine and node metrics only, with no error anywhere. The submit now
fails with a message naming both ways out, and the new otel.gym_telemetry switch
keeps the collector for engine and node metrics while skipping Lens entirely.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
@iguehring-nvidia
iguehring-nvidia force-pushed the iguehring/orchestrator-lens-telemetry branch from dc5426e to 89af61a Compare September 29, 2026 23:25
@nemo-automation-bot
nemo-automation-bot Bot requested a review from a team September 29, 2026 23:26
@github-actions github-actions Bot removed the sla:author-overdue Author response is over the one-business-day SLA label Sep 29, 2026
@iguehring-nvidia
iguehring-nvidia added this pull request to the merge queue Sep 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/36666409661

Merged via the queue into main with commit b3b5457 Sep 30, 2026
43 checks passed
@iguehring-nvidia
iguehring-nvidia deleted the iguehring/orchestrator-lens-telemetry branch September 30, 2026 04:09
iguehring-nvidia added a commit that referenced this pull request Oct 6, 2026
…RPC port

The driver environment pointed OTEL_EXPORTER_OTLP_LOGS_ENDPOINT at the
collector's gRPC port because the nemo-lens of #3619 built its log exporter
over gRPC whatever the protocol said. Lens picks that exporter by import
order, not by protocol, so which one runs depends on the environment; in the
Slurm jobs it is now the OTLP/HTTP exporter, which the gRPC port answers with a
connection reset. Every Gym log batch fails with "Transient error
('Connection aborted.', ConnectionResetError(104, 'Connection reset by
peer'))" while traces and metrics flow (seen on oci-hsg job 7755363).

Logs now follow the shared endpoint and protocol like the other two signals.
NVIDIA-NeMo/Lens#80 makes Lens honour the protocol for logs, so the choice is
deterministic once that pin lands; until then this matches what the jobs
actually run. The two prefill/decode fixtures lose the override line.

Validation: pytest tests/unit_tests/test_orchestration_otel.py
tests/unit_tests/test_slurm_script.py tests/unit_tests/test_slurm_executor.py
tests/unit_tests/test_orchestration_api.py -- 367 passed, 1 failed
(test_build_vllm_ray_serve_command_single_node_model_with_space_survives_quoting
shells out and needs pip on the login-shell PATH; fails identically on main
here). pre-commit on the changed files passes. Cluster check in progress:
oci-hsg job 7755613 runs the same scicode config with driver.env routing logs
to the HTTP port.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
iguehring-nvidia added a commit that referenced this pull request Oct 6, 2026
…RPC port

The driver environment pointed OTEL_EXPORTER_OTLP_LOGS_ENDPOINT at the
collector's gRPC port because the nemo-lens of #3619 built its log exporter
over gRPC whatever the protocol said. Lens picks that exporter by import
order, not by protocol, and in one Slurm job the processes disagree: the
driver gets the gRPC log exporter, the Gym servers get the OTLP/HTTP one. With
the gRPC port configured, the servers' log batches fail with "Transient error
('Connection aborted.', ConnectionResetError(104, 'Connection reset by
peer'))", no server log reaches the collector, and the driver spends its
shutdown in the HTTP exporter's retry loop until the job hits its wall time
(oci-hsg job 7755363: rollouts done at 10:11, killed at 11:00 after 477
retries).

Logs now follow the shared endpoint and protocol like the other two signals,
which is correct once nemo-lens selects the log exporter by protocol
(NVIDIA-NeMo/Lens#80). This change must land together with that pin bump: on
the current pin the driver's gRPC log exporter would aim at the HTTP port
instead (oci-hsg job 7755613 showed exactly that when the port was overridden
by hand). The two prefill/decode fixtures lose the override line.

Validation: pytest tests/unit_tests/test_orchestration_otel.py
tests/unit_tests/test_slurm_script.py tests/unit_tests/test_slurm_executor.py
tests/unit_tests/test_orchestration_api.py -- 367 passed, 1 failed
(test_build_vllm_ray_serve_command_single_node_model_with_space_survives_quoting
shells out and needs pip on the login-shell PATH; fails identically on main
here). pre-commit on the changed files passes. Cluster validation of the
combined fix pending the Lens pin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
iguehring-nvidia added a commit to NVIDIA-NeMo/Lens that referenced this pull request Oct 8, 2026
`_build_log_exporter` picked the gRPC log exporter whenever that package
imported and fell back to HTTP otherwise, ignoring OTEL_EXPORTER_OTLP_PROTOCOL
and OTEL_EXPORTER_OTLP_LOGS_PROTOCOL. Spans and metrics already resolve the
protocol first and fall back on ImportError only. A process configured for
http/protobuf could therefore end up with a gRPC log exporter aimed at an HTTP
endpoint, or, once a caller worked around that with a per-signal gRPC endpoint,
with an HTTP exporter aimed at the gRPC port. Either way every log record is
dropped after a connection reset, while traces and metrics flow.

Seen in NeMo Gym's Slurm jobs: the driver sets http/protobuf with the
collector's HTTP endpoint plus OTEL_EXPORTER_OTLP_LOGS_ENDPOINT on the gRPC
port (the workaround from NVIDIA-NeMo/Gym#3619); the HTTP log exporter then
logs "Transient error ... Connection reset by peer" for each batch.

Logs now follow the same rule as the other two signals: the signal-specific
protocol wins, then the general one, default grpc, with the existing import
fallback. Three tests mirror the span-exporter protocol tests.

Signed-off-by: Ingo Guehring <iguehring@nvidia.com>
ahmadki pushed a commit to NVIDIA-NeMo/Lens that referenced this pull request Oct 11, 2026
…80)

* fix: select the OTLP log exporter by protocol like spans and metrics

`_build_log_exporter` picked the gRPC log exporter whenever that package
imported and fell back to HTTP otherwise, ignoring OTEL_EXPORTER_OTLP_PROTOCOL
and OTEL_EXPORTER_OTLP_LOGS_PROTOCOL. Spans and metrics already resolve the
protocol first and fall back on ImportError only. A process configured for
http/protobuf could therefore end up with a gRPC log exporter aimed at an HTTP
endpoint, or, once a caller worked around that with a per-signal gRPC endpoint,
with an HTTP exporter aimed at the gRPC port. Either way every log record is
dropped after a connection reset, while traces and metrics flow.

Seen in NeMo Gym's Slurm jobs: the driver sets http/protobuf with the
collector's HTTP endpoint plus OTEL_EXPORTER_OTLP_LOGS_ENDPOINT on the gRPC
port (the workaround from NVIDIA-NeMo/Gym#3619); the HTTP log exporter then
logs "Transient error ... Connection reset by peer" for each batch.

Logs now follow the same rule as the other two signals: the signal-specific
protocol wins, then the general one, default grpc, with the existing import
fallback. Three tests mirror the span-exporter protocol tests.

Signed-off-by: Ingo Guehring <iguehring@nvidia.com>

* test: cover the OTLP log exporter import fallbacks

Exercise the HTTP-to-gRPC and gRPC-to-HTTP fallbacks and the error when neither exporter package is installed.

Signed-off-by: Ingo Guehring <iguehring@nvidia.com>

---------

Signed-off-by: Ingo Guehring <iguehring@nvidia.com>

This branch was previously deployed

1 inactive deployment
public — 89af61a1 Deployed Sep 29, 2026 by copy-pr-bot[bot] via release / finalize / notify #3992
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:orchestration Job submission, deployment, services, and lifecycle management complexity:medium Single-domain change with interacting parts or a moderate review surface feature New capabilities, enhancements, or enablement work needs-review PR is ready for code review and waiting on a reviewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants