-
Notifications
You must be signed in to change notification settings - Fork 370
telemetry: native multi-process Prometheus aggregation (prometheus_mp) #1920
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
e-eygin
wants to merge
18
commits into
ai-dynamo:main
Choose a base branch
from
e-eygin:eeygin/nix-1614-native-mp-prometheus-aggregation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
044999a
telemetry: add multiprocess metric-state store (mp_store)
e-eygin f17b312
telemetry: add multiprocess scrape-time collector (mp_collector)
e-eygin 5f71e4e
telemetry: label mp series by pid to guarantee uniqueness
e-eygin 000eaa3
telemetry: add prometheus_mp exporter, plugin, and bind-race election
e-eygin 30f12dd
telemetry: add prometheus_mp multiprocess e2e forking test
e-eygin 38b5093
telemetry: harden mp collector against mid-scrape churn and orphans
e-eygin 2a624a5
docs: document the prometheus_mp multi-process telemetry exporter
e-eygin 0fa45a1
docs: add prometheus_mp plugin README
e-eygin 93f95ae
docs: align prometheus_mp shared-dir guidance with Dynamo
e-eygin b4093af
telemetry: fix codespell (reuse nixlTime::getNs, unparsable spelling)
e-eygin 3bf2139
telemetry: address CodeRabbit review (mp bounds guard, dir iteration,…
e-eygin cd22887
telemetry: rename mp rank label dp_rank -> local_rank
e-eygin 4e69307
telemetry: remove mp store file on clean writer destruction
e-eygin f5de93f
telemetry: add agent_instance label so same-name mp agents stay distinct
e-eygin 4c74834
docs: clarify mp stale TTL is from last update and document agent_ins…
e-eygin ad437a8
docs: note the status label on agent_errors_total in telemetry.md
e-eygin aeed305
Merge branch 'main' into eeygin/nix-1614-native-mp-prometheus-aggrega…
e-eygin aa3f6bd
Merge branch 'main' into eeygin/nix-1614-native-mp-prometheus-aggrega…
e-eygin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| <!-- | ||
| SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| --> | ||
|
|
||
| # NIXL multi-process Prometheus Telemetry exporter plug-in (`prometheus_mp`) | ||
|
|
||
| `prometheus_mp` exposes the telemetry of **all** processes of a multi-process | ||
| NIXL run (e.g. tensor/data parallelism) behind a **single** Prometheus scrape | ||
| endpoint, without any DOCA/DTS dependency. | ||
|
|
||
| It complements the single-process [`prometheus`](../prometheus/README.md) exporter | ||
| (which binds one port per process, so only one rank's metrics are scraped) and the | ||
| DOCA/CollectX exporter (which aggregates via an external DTS service). Use | ||
| `prometheus_mp` when you want all ranks aggregated natively with no extra | ||
| infrastructure. General NIXL telemetry background: [docs/telemetry.md](../../../../docs/telemetry.md). | ||
|
|
||
| ## Dependencies | ||
|
|
||
| Same as the `prometheus` plug-in: the bundled prometheus-cpp subproject and | ||
| `libcurl` (`libcurl4-openssl-dev` / `libcurl-devel`). | ||
|
|
||
| ## How it works | ||
|
|
||
| - **Every process writes its own metric state** to a per-process memory-mapped | ||
| file in a shared directory (`NIXL_TELEMETRY_MULTIPROC_DIR`). Updates are | ||
| lock-free; there is no serialization. | ||
| - **Bind-race owner election.** On startup each process tries to bind the scrape | ||
| port. The one that wins ("owner") runs the HTTP endpoint plus a collector that, | ||
| on each scrape, reads every live process's file and republishes them as one | ||
| exposition. The processes that lose the race run in **writer-only** mode (no | ||
| HTTP server). A bind collision is therefore benign -- every process gets a valid | ||
| telemetry sink; no rank is dropped and no scary error is logged. | ||
| - **Per-process series.** Each process is exported as its own series (cumulative | ||
| counters and last-operation gauges), never summed across processes, so | ||
| per-process values stay correct and monotonic. | ||
| - **Stale handling.** On clean shutdown a process removes its own store file. If | ||
| it instead crashes or is killed -- and so cannot clean up after itself -- the | ||
| owner drops its series once the process is gone (verified by pid + `/proc` start | ||
| time) or its data ages past the TTL, and reaps the file. | ||
|
|
||
| ## Configuration | ||
|
|
||
| ```bash | ||
| export NIXL_TELEMETRY_ENABLE="y" | ||
| export NIXL_TELEMETRY_EXPORTER="prometheus_mp" # selects libtelemetry_exporter_prometheus_mp.so | ||
| export NIXL_TELEMETRY_MULTIPROC_DIR="/tmp/nixl_metrics" # REQUIRED: shared by all ranks in the pod | ||
| ``` | ||
|
|
||
| This mirrors Dynamo's `PROMETHEUS_MULTIPROC_DIR` convention (a shared folder that | ||
| every related process writes into, one leader exports): all ranks that should be | ||
| aggregated together must point `NIXL_TELEMETRY_MULTIPROC_DIR` at the **same** | ||
| directory. Unlike Dynamo -- which auto-creates a temp dir in the parent and lets | ||
| child engine processes inherit it -- NIXL is a library loaded independently in each | ||
| rank, so there is no parent to propagate the path; the launcher/operator must set | ||
| the same directory for every rank (hence it is required, not auto-defaulted, so a | ||
| per-process temp dir can never silently break aggregation). | ||
|
|
||
| Recommended, following Dynamo's model: a shared **local** folder, one per pod / | ||
| process-family, treated as ephemeral (e.g. a per-pod Kubernetes `emptyDir`, or a | ||
| temp dir cleaned between runs). It must be a local filesystem -- **not** a network | ||
| filesystem (NFS/CIFS), where mmap `MAP_SHARED` cross-process visibility is not | ||
| guaranteed (the same restriction Dynamo's multiprocess dir has). tmpfs (e.g. a | ||
| Memory-medium `emptyDir` or `/dev/shm`) works and avoids any disk writeback, but is | ||
| optional -- a plain local dir is fine, since updates hit the page cache and the | ||
| per-process store files are ~one page each. | ||
|
|
||
| ### Optional configuration | ||
|
|
||
| ```bash | ||
| # Scrape port (default 9090) and bind scope -- shared with the prometheus plug-in. | ||
| export NIXL_TELEMETRY_PROMETHEUS_PORT="<port_num>" | ||
| export NIXL_TELEMETRY_PROMETHEUS_LOCAL="y" # bind 127.0.0.1 instead of 0.0.0.0 | ||
|
|
||
| # Optional local_rank label: names the env var that holds the rank (default LOCAL_RANK). | ||
| # If that env var is unset, no local_rank label is emitted (series stay unique via pid). | ||
| export NIXL_TELEMETRY_RANK_ENV="LOCAL_RANK" | ||
|
|
||
| # Seconds after a dead process's last update before its store is considered stale | ||
| # and reaped (default 30). A live process is always published regardless of age. | ||
| export NIXL_TELEMETRY_MP_STALE_TTL="30" | ||
| ``` | ||
|
|
||
| ## Metric labels | ||
|
|
||
| Every series is labeled by: | ||
|
|
||
| - `hostname` -- host where the agent runs. | ||
| - `agent_name` -- the agent name given at initialization. | ||
| - `pid` -- the producing process id. This guarantees each process is a distinct | ||
| series even if agent names collide; it is deliberately **not** named `instance` | ||
| (a reserved Prometheus target label). | ||
| - `agent_instance` -- a per-process counter distinguishing multiple agents created | ||
| in the same process (which share `pid`, `hostname`, and `agent_name`), so their | ||
| series never collide. `0` for the common single-agent-per-process case. | ||
| - `local_rank` -- **optional**, present only when a rank env var (see | ||
| `NIXL_TELEMETRY_RANK_ENV`) is set. This is the local/per-GPU (TP) rank, distinct | ||
| from Dynamo's data-parallel `dp_rank`. | ||
| - `status` -- only on `agent_errors_total`, bounded by the fixed `AGENT_ERR_*` set. | ||
|
|
||
| The metric names, types, counter/gauge semantics, and events are identical to the | ||
| single-process [`prometheus`](../prometheus/README.md) exporter (same shared | ||
| descriptor). | ||
|
|
||
| ## Design scope & limitations | ||
|
|
||
| This exporter is **purpose-built for NIXL's telemetry model, not a generic | ||
| Prometheus multiprocess store** (in particular it is not compatible with, and does | ||
| not reuse, Python `prometheus_client`'s multiprocess format): | ||
|
|
||
| - The metric set is fixed at compile time; slots are positional, so metric names | ||
| are never stored in the files. | ||
| - Per-process label values (`hostname`, `agent_name`, `pid`, `local_rank`) are | ||
| captured once at startup and never change. Events carry only a numeric value -- | ||
| there are **no per-observation labels**. | ||
| - Consequently the store **cannot represent a metric with a dynamic / | ||
| high-cardinality label** whose value varies per observation. No NIXL metric has | ||
| such a label today; if one is ever added, this exporter would need a different | ||
| (keyed) store. | ||
|
|
||
| This is the native, dependency-free path. For aggregation through an external | ||
| telemetry service, use the DOCA/CollectX exporter (IPC to DTS) instead. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| # Multiprocess metric-state store: a small, prometheus-cpp-free component (mmap | ||
| # per-process store) shared by the prometheus_mp plugin and its unit tests. Built | ||
| # unconditionally so the CTest coverage runs even where prometheus-cpp is absent; | ||
| # the plugin shared library (which needs prometheus-cpp) is added in a later step. | ||
| prometheus_mp_inc_dirs = include_directories('.') | ||
|
|
||
| telemetry_mp_store_lib = static_library( | ||
| 'nixl_telemetry_mp_store', | ||
| 'mp_store.cpp', | ||
| include_directories: [nixl_inc_dirs, utils_inc_dirs, prometheus_mp_inc_dirs], | ||
| dependencies: [nixl_common_dep], | ||
| install: false, | ||
| pic: true, | ||
| ) | ||
|
|
||
| nixl_telemetry_mp_store_dep = declare_dependency( | ||
| link_with: telemetry_mp_store_lib, | ||
| include_directories: prometheus_mp_inc_dirs, | ||
| dependencies: [nixl_common_dep], | ||
| ) | ||
|
|
||
| # Scrape-time collector: converts peer store snapshots into prometheus-cpp metric | ||
| # families. Needs prometheus-cpp (core), so it is gated on it being available | ||
| # (same condition as the prometheus plugin, set in the sibling subdir above). | ||
| if prometheus_found | ||
| telemetry_mp_collector_lib = static_library( | ||
| 'nixl_telemetry_mp_collector', | ||
| 'mp_collector.cpp', | ||
| include_directories: [nixl_inc_dirs, utils_inc_dirs, prometheus_mp_inc_dirs], | ||
| dependencies: [nixl_common_dep, prometheus_core_dep, nixl_telemetry_mp_store_dep], | ||
| install: false, | ||
| pic: true, | ||
| ) | ||
|
|
||
| nixl_telemetry_mp_collector_dep = declare_dependency( | ||
| link_with: telemetry_mp_collector_lib, | ||
| include_directories: prometheus_mp_inc_dirs, | ||
| dependencies: [nixl_common_dep, prometheus_core_dep, nixl_telemetry_mp_store_dep], | ||
| ) | ||
|
|
||
| # Exporter with bind-race election. Built as a static library too so it can be | ||
| # unit-tested directly (in addition to being packaged into the plugin .so). | ||
| telemetry_mp_exporter_lib = static_library( | ||
| 'nixl_telemetry_mp_exporter', | ||
| 'prometheus_mp_exporter.cpp', | ||
| include_directories: [nixl_inc_dirs, utils_inc_dirs, prometheus_mp_inc_dirs], | ||
| dependencies: [nixl_common_dep, prometheus_core_dep, prometheus_pull_dep, | ||
| nixl_telemetry_mp_store_dep, nixl_telemetry_mp_collector_dep], | ||
| install: false, | ||
| pic: true, | ||
| ) | ||
|
|
||
| nixl_telemetry_mp_exporter_dep = declare_dependency( | ||
| link_with: telemetry_mp_exporter_lib, | ||
| include_directories: prometheus_mp_inc_dirs, | ||
| dependencies: [nixl_common_dep, prometheus_core_dep, prometheus_pull_dep, | ||
| nixl_telemetry_mp_store_dep, nixl_telemetry_mp_collector_dep], | ||
| ) | ||
|
|
||
| # Installable plugin: selected via NIXL_TELEMETRY_EXPORTER=prometheus_mp. | ||
| prometheus_mp_exporter_plugin = shared_library( | ||
| 'libtelemetry_exporter_prometheus_mp', | ||
| 'prometheus_mp_plugin.cpp', | ||
| include_directories: [nixl_inc_dirs, utils_inc_dirs, prometheus_mp_inc_dirs], | ||
| dependencies: [nixl_infra, nixl_common_dep, absl_log_dep, prometheus_core_dep, | ||
| prometheus_pull_dep, nixl_telemetry_mp_exporter_dep], | ||
| install: true, | ||
| install_dir: plugin_install_dir, | ||
| name_prefix: '', | ||
| ) | ||
| message('Building Prometheus multiprocess telemetry exporter plugin') | ||
| endif |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.