Repository navigation
Remove timestamp field from telemetry events - #1522
Conversation
|
👋 Hi BatshevaBlack! Thank you for contributing to ai-dynamo/nixl. Your PR reviewers will review your contribution then trigger the CI to test your changes. 🚀 |
|
/build |
|
/ok to test f72f03a |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughPer-event microsecond timestamps were removed from the telemetry schema and code; TELEMETRY_VERSION bumped to 2. Constructors, event storage, exporters, examples, tests, and docs were updated to omit timestamp fields, computations, and outputs, keeping category, event name, and value only. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
examples/python/telemetry_reader.py (1)
25-26:⚠️ Potential issue | 🟡 MinorFix isort pre-commit hook failure.
The pipeline indicates that
isortrequires a blank line before the# Set up loggingcomment to separate imports from module-level code.🔧 Proposed fix
import time + # Set up logging logging.basicConfig(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/python/telemetry_reader.py` around lines 25 - 26, isort is failing because there is no blank line separating the import block from module-level code; add a single blank line after the import statement (the line containing "import time") so there is an empty line before the "# Set up logging" comment to satisfy isort's import separation rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@examples/python/telemetry_reader.py`:
- Around line 25-26: isort is failing because there is no blank line separating
the import block from module-level code; add a single blank line after the
import statement (the line containing "import time") so there is an empty line
before the "# Set up logging" comment to satisfy isort's import separation rule.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 59ca529a-eec7-4ee8-964e-b21732f6f5af
📒 Files selected for processing (7)
examples/cpp/telemetry_reader.cppexamples/python/telemetry_reader.pysrc/api/cpp/backend/backend_engine.hsrc/core/telemetry/telemetry.cppsrc/core/telemetry/telemetry_event.hsrc/plugins/telemetry/README.mdtest/gtest/telemetry_test.cpp
💤 Files with no reviewable changes (1)
- examples/cpp/telemetry_reader.cpp
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
examples/python/telemetry_reader.py (2)
91-91:⚠️ Potential issue | 🟡 MinorUpdate the constructor default to
TELEMETRY_VERSION.
SharedRingBuffer()still defaults toversion=1, so any direct use of this helper now rejects the v2 buffers this PR writes. The CLI path is fine because Line 261 passesTELEMETRY_VERSIONexplicitly, but the class API is now stale.Suggested fix
- def __init__(self, file_path, version=1): + def __init__(self, file_path, version=TELEMETRY_VERSION):🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/python/telemetry_reader.py` at line 91, The constructor default for SharedRingBuffer (__init__) still uses version=1 which rejects v2 buffers; change the default to use TELEMETRY_VERSION instead (i.e., def __init__(self, file_path, version=TELEMETRY_VERSION):), and ensure TELEMETRY_VERSION is available in the module (import or reference the constant where defined) so callers that rely on the default will accept v2 buffers.
123-128:⚠️ Potential issue | 🟡 MinorDon't delete
temp_headerbefore formatting the mismatch error.On a version mismatch, this branch deletes
temp_headerand then immediately readstemp_header.versionin the f-string. That raisesUnboundLocalErrorinstead of the intended schema-mismatch error, which is now a likely user path after bumping the buffer version to 2.Suggested fix
if temp_header.version != self.version: + actual_version = temp_header.version del temp_header header_mmap.close() raise RuntimeError( - f"Version mismatch: expected {self.version}, got {temp_header.version}" + f"Version mismatch: expected {self.version}, got {actual_version}" )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/python/telemetry_reader.py` around lines 123 - 128, The branch that handles a version mismatch deletes temp_header before constructing the RuntimeError message, causing an UnboundLocalError when the f-string reads temp_header.version; update the mismatch branch in telemetry_reader.py to not delete temp_header (or move the del temp_header after raising) and then close header_mmap and raise RuntimeError(f"Version mismatch: expected {self.version}, got {temp_header.version}") so the error message can access temp_header.version (symbols: temp_header, header_mmap, self.version).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@examples/python/telemetry_reader.py`:
- Line 91: The constructor default for SharedRingBuffer (__init__) still uses
version=1 which rejects v2 buffers; change the default to use TELEMETRY_VERSION
instead (i.e., def __init__(self, file_path, version=TELEMETRY_VERSION):), and
ensure TELEMETRY_VERSION is available in the module (import or reference the
constant where defined) so callers that rely on the default will accept v2
buffers.
- Around line 123-128: The branch that handles a version mismatch deletes
temp_header before constructing the RuntimeError message, causing an
UnboundLocalError when the f-string reads temp_header.version; update the
mismatch branch in telemetry_reader.py to not delete temp_header (or move the
del temp_header after raising) and then close header_mmap and raise
RuntimeError(f"Version mismatch: expected {self.version}, got
{temp_header.version}") so the error message can access temp_header.version
(symbols: temp_header, header_mmap, self.version).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 67768f85-51b8-42ca-a2e0-f03fba018314
📒 Files selected for processing (1)
examples/python/telemetry_reader.py
alexanderbilk
left a comment
There was a problem hiding this comment.
Fix https://github.com/ai-dynamo/nixl/blob/main/docs/telemetry.md accordingly
The rest seems fine
|
/build |
|
/ok to test 84c9043 |
38b1794 to
5c7862b
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/api/cpp/backend/backend_engine.h (1)
65-70:⚠️ Potential issue | 🔴 CriticalLock before touching
telemetryEvents_.Line 67 reads
telemetryEvents_beforetelemetryEventsMutex_is acquired. That races with Line 69'semplace_back(...)and withgetTelemetryEvents()at Lines 89-91, which is undefined behavior and still allows the queue to overshootMAX_TELEMETRY_QUEUE_SIZEunder contention.🔒 Suggested fix
void addTelemetryEvent(const std::string &event_name, uint64_t value) { - if (!enableTelemetry_) return; - if (telemetryEvents_.size() >= MAX_TELEMETRY_QUEUE_SIZE) return; - std::lock_guard<std::mutex> lock(telemetryEventsMutex_); + if (!enableTelemetry_) { + return; + } + const std::lock_guard<std::mutex> lock(telemetryEventsMutex_); + if (telemetryEvents_.size() >= MAX_TELEMETRY_QUEUE_SIZE) { + return; + } telemetryEvents_.emplace_back( nixl_telemetry_category_t::NIXL_TELEMETRY_BACKEND, event_name, value); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/api/cpp/backend/backend_engine.h` around lines 65 - 70, In addTelemetryEvent, acquire telemetryEventsMutex_ before inspecting or modifying telemetryEvents_ to avoid races: keep the enableTelemetry_ check early if desired, then lock telemetryEventsMutex_ and perform the size check against MAX_TELEMETRY_QUEUE_SIZE and the emplace_back under that same lock; similarly ensure getTelemetryEvents() uses the same mutex for consistent access. This prevents the queue from being read/modified concurrently and avoids overshooting MAX_TELEMETRY_QUEUE_SIZE.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/telemetry.md`:
- Line 63: The sentence "Events are recorded after the corresponding operation
completes and appear in order." implies a global completion-order guarantee;
change it to clarify that only enqueue order is preserved across producers and
consumers observe buffer insertion order, not global operation completion
order—for example, replace that sentence with wording that states events are
recorded when enqueued, concurrent producers serialize on enqueue, and consumers
see insertion order but there is no cross-thread global completion ordering
guarantee.
In `@examples/python/telemetry_reader.py`:
- Line 36: The version mismatch handler crashes because TELEMETRY_VERSION was
bumped to 2 while SharedRingBuffer.__init__ still defaults to version=1 and the
handler deletes temp_header before reading temp_header.version; update the
initialization and/or handler so they use the module-level TELEMETRY_VERSION
consistently and avoid accessing a deleted variable: change
SharedRingBuffer.__init__ to default version=TELEMETRY_VERSION (or read
temp_header.version into a local var before deleting temp_header) and adjust the
version-mismatch error message to reference that local var instead of
temp_header.version.
In `@src/core/telemetry/telemetry_event.h`:
- Line 30: The API version is stale: add V2 = 2 to the
nixl_telemetry_plugin_api_version enum in
src/api/cpp/telemetry/telemetry_plugin.h so it matches the new struct layout
(TELEMETRY_VERSION = 2), and update all exporter plugin registrations
(prometheus, doca, csv, etc.) to register with the new V2 value; ensure
exporters that implement exportEvent(const nixlTelemetryEvent &event) are
registered with V2 to avoid ABI/struct-layout mismatches with
nixlTelemetryEvent.
- Around line 64-76: Change the enum declaration to give
nixl_telemetry_category_t an explicit underlying type that matches the Python
ctypes usage (e.g., "enum class nixl_telemetry_category_t : std::int32_t") and
include <cstdint>, then add a compile-time assertion that the C++ struct layout
used by nixlTelemetryEvent is stable for the mmap/ctypes consumer (for example a
static_assert that sizeof(nixlTelemetryEvent) == sizeof(std::int32_t) +
MAX_EVENT_NAME_LEN + sizeof(uint64_t) and/or that offsetof(nixlTelemetryEvent,
category_) == 0), so the category field and overall struct size match the Python
side; this touches the enum class nixl_telemetry_category_t and the struct/type
nixlTelemetryEvent (and include headers if needed).
---
Outside diff comments:
In `@src/api/cpp/backend/backend_engine.h`:
- Around line 65-70: In addTelemetryEvent, acquire telemetryEventsMutex_ before
inspecting or modifying telemetryEvents_ to avoid races: keep the
enableTelemetry_ check early if desired, then lock telemetryEventsMutex_ and
perform the size check against MAX_TELEMETRY_QUEUE_SIZE and the emplace_back
under that same lock; similarly ensure getTelemetryEvents() uses the same mutex
for consistent access. This prevents the queue from being read/modified
concurrently and avoids overshooting MAX_TELEMETRY_QUEUE_SIZE.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: e7208217-0d95-4b32-a9bc-b8204ed13b20
📒 Files selected for processing (8)
docs/telemetry.mdexamples/cpp/telemetry_reader.cppexamples/python/telemetry_reader.pysrc/api/cpp/backend/backend_engine.hsrc/core/telemetry/telemetry.cppsrc/core/telemetry/telemetry_event.hsrc/plugins/telemetry/README.mdtest/gtest/telemetry_test.cpp
💤 Files with no reviewable changes (1)
- examples/cpp/telemetry_reader.cpp
0480ea2 to
cdb6958
Compare
ColinNV
left a comment
There was a problem hiding this comment.
Btw, getting the current time on Linux is often a userspace operation without system call overhead. Of course if the timestamp is never needed we should still get rid of it.
9f071d7 to
4a6998f
Compare
The per-event timestamp (timestampUs_) added overhead on every event without being consumed by any exporter. Remove it from the struct, constructors, producers, readers, and bump TELEMETRY_VERSION to 2. Signed-off-by: Batsheva Black <bblack@nvidia.com>
Co-authored-by: Guy Ealey Morag <guymorag142@gmail.com> Signed-off-by: alexanderbilk <93654061+alexanderbilk@users.noreply.github.com>
- docs: describe ring insertion order without over-promising global ordering - examples: default SharedRingBuffer to TELEMETRY_VERSION; fix version mismatch path - plugins: add API V2; require V2 in dynamic loader; register buffer and prometheus - README: example registers with nixlTelemetryPluginApiVersionV2
Drop redundant nixlTelemetryPluginApiVersion* constexprs; use nixl_telemetry_plugin_api_version::V2 at registration and load check. Cast version numbers to unsigned int in mismatch logs (matches enum base).
Co-authored-by: Guy Ealey Morag <guymorag142@gmail.com> Signed-off-by: BatshevaBlack <132911331+BatshevaBlack@users.noreply.github.com>
fc86b36 to
ae39e41
Compare
|
/ok to test ae39e41 |
|
/build |
|
/build |
|
I'm not involved in the details of telemetry implementation, but rather surprised by title of this PR. The whole point of telemetry was to measure post and completion time, are we doing it in another way? Or in some update this capability was dropped? This sounds off to me. |
|
@mkhazraee post and completion time are getting exported as event values. What we are getting rid of is redundant timestamps of 'When event was captured". We have no real use of it, while turned out it has quite an impact on performance in some cases |
|
/ok to test 0f171d0 |
|
/build |
…#2295) ## What? Data plugins cannot emit tracing today: the only thing core hands a backend at construction is `nixlBackendInitParams`, which carries no tracer and no sink. This adds the path from core into a plugin. - `nixlBackendTraceSink` — a core-owned interface, reached through a `traceSink` pointer appended to `nixlBackendInitParams`, null when tracing is off. - Closed stage vocabulary `nixl_trace_stage_t`: `SUBMIT`, `WIRE_SUBMITTED`, `WIRE_COMPLETED`, `NOTIF_SENT`, `NOTIF_RECEIVED`, `REMOTE_OBSERVED`, plus a generic `STAGE` carrying a plugin label. Core turns each recorded phase into a span. - `NIXL_PLUGIN_API_VERSION` 1 → 2, plus the first test that check has ever had. Tracing stays internal: the sink header is not installed and `backend_aux.h` only forward-declares the type, so the public delta is one forward declaration, one pointer, and the version macro. No plugin emits stages yet — UCX and libfabric map onto the vocabulary in their own PRs. ## Why? [NIX-1876](https://linear.app/nvidia/issue/NIX-1876), a child of [NIX-1537](https://linear.app/nvidia/issue/NIX-1537) (distributed tracing). Trace backends are separate `.so`s from data plugins, so a plugin cannot call the `nixl::trace` facade directly and backend work shows up only as elapsed time inside a core span. This is the prerequisite for in-backend visibility. ## How? Appending to `nixlBackendInitParams` moves no existing field offset and the sink is a pure interface, so a plugin rebuilt from older source keeps working. The version bump is what makes it safe in the other direction: a plugin built against the new header and loaded by an **older** core would read a field that core never allocated. <details> <summary>Why strict equality rather than a version floor</summary> A floor that keeps accepting version 1 preserves the existing hazard instead of removing it. The macro has read `1` since March 2025 (#25) while the ABI broke underneath it at least three times: | PR | Change | Effect on an existing plugin binary | | --- | --- | --- | | #562 | Added three data members to `nixlBackendEngine` | `sizeof` and derived member offsets shift | | #701 | Removed the `supportsProgTh()` and `progress()` virtuals | vtable slots shift | | #1342 | Removed four more virtuals | vtable slots shift | Such a binary already miscomputes offsets or calls the wrong vtable slot today, silently; after the bump it is refused with a clear error. All three version checks in `nixl_plugin_manager.cpp` are exact-match, and telemetry's V1→V2 (#1522) kept strict equality. Cost is a rebuild: every in-tree plugin passes the macro symbolically. </details> Tested in normal, ASan, UBSan and TSan builds. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Backend tracing supports phase spans with backend names, timestamps, optional labels, and plugin-provided attributes. Tracing-disabled setups continue without a trace sink; no plugins currently emit backend phases. * The plugin API version is now 2. Plugins reporting a different version are refused at load and must be rebuilt to match. * **Documentation** * Clarified plugin version compatibility requirements and documented backend phase spans, their attributes, and available stages. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Efraim Eygin <eeygin@nvidia.com>
What?
Removes the per-event microsecond timestamp (timestampUs_) from nixlTelemetryEvent, updates all constructors and call sites (core telemetry, backend engine, buffer readers), and bumps TELEMETRY_VERSION to 2 so shared-memory ring buffers reject mismatched layouts. Example readers (C++ and Python) no longer print or decode a per-event timestamp field.
Why?
Recording a timestamp on every event added cost (syscall / clock read and 8 bytes per event) without any exporter or downstream consumer using it. Dropping the field reduces hot-path overhead and shrinks each event.
Summary by CodeRabbit
Breaking Changes
Documentation
Examples
Tests