Skip to content

RUM Phase 1: stats collection + viewer consent/upload - #8

Open
AviaAv wants to merge 7 commits into
developmentfrom
rum-phase1
Open

RUM Phase 1: stats collection + viewer consent/upload#8
AviaAv wants to merge 7 commits into
developmentfrom
rum-phase1

Conversation

@AviaAv

@AviaAv AviaAv commented Jun 15, 2026

Copy link
Copy Markdown
Owner

RUM Phase 1 — Real User Monitoring (RSDEV-9259)

Anonymous, opt-in usage statistics. The SDK collects locally (gated by ENABLED_STATS, on by default); the viewer handles the consent prompt and background upload to a local dev-server stub.

What's here

  • SDK: stats collector + config, anonymous source_id (stored in rum.json), instrumentation hooks (device / stream / option-change / filter / notification), public rs2_rum_* API + python bindings.
  • Viewer: first-run consent popup, Settings → Privacy (consent + upload cadence + export), background uploader (cadence-gated, joined at teardown).
  • Local dev-server stub, docs, and python tests.

Notable deviations from the plan

  • Public API trimmed to 3 calls (get-report + get/set consent); dropped flush/export — the SDK auto-persists at session end, and get-report returns the live current session.
  • Upload timing is entirely viewer-side — the background uploader ships the previous saved session and enforces the cadence window (default 24h); the SDK has no role in upload timing.
  • ENABLED_STATS=OFF keeps rs2_rum_* exported but a complete no-op (inert, ABI-stable), not symbol-free.
  • Aggregation: Option A only (A-vs-B benchmark deferred).
  • Storage cap dropped; driver version captured for MIPI only (UVC/WinUSB → Phase 2); cross-process upload handoff → Phase 2.

Tests

  • unit-tests/rum/pytest-rum-config.py (non-live): 5/5
  • unit-tests/rum/pytest-rum-device.py (live, D435I): 4/4
  • ON and OFF builds both verified.

Jira: RSDEV-9259

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added Real User Monitoring (RUM) with anonymous, aggregated usage statistics and local report generation.
    • Added a Privacy tab in the viewer to manage cloud-upload consent, upload cadence, local JSON export, and “Upload now”.
    • Introduced new C/C++ and Python APIs to read reports and control cloud-upload consent; uploads are opt-in and build-gated.
  • Documentation
    • Added RUM documentation covering collected data, privacy controls, and consent behavior.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a complete Real User Monitoring (RUM) subsystem: a new ENABLED_STATS CMake option (default ON), a core rum_collector/rum_config engine persisting anonymous usage telemetry to local JSON, rum_hooks wired into device creation, stream open/stop, option setting, filter invocation, and context shutdown, a public C/C++/Python API, a viewer Privacy settings tab with first-run consent popup and background libcurl upload, and unit tests plus a Python dev-server stub.

Changes

RUM Telemetry Feature

Layer / File(s) Summary
Build option and CMake wiring
CMake/lrs_options.cmake, CMake/global_config.cmake, src/rum/CMakeLists.txt, src/CMakeLists.txt, common/CMakeLists.txt, include/CMakeLists.txt, wrappers/python/CMakeLists.txt, tools/realsense-viewer/CMakeLists.txt, .github/workflows/buildsCI.yaml
Introduces ENABLED_STATS CMake option (default ON), injects -DENABLED_STATS compile definition in global_set_flags, registers src/rum with core library, adds rum-uploader files to common module, includes RUM headers in the include target, conditionally extends viewer and Python wrapper builds, and enables stats in CI jobs.
rum_collector and rum_config: data model and persistence
src/rum/rum-collector.h, src/rum/rum-collector.cpp, src/rum/rum-config.h, src/rum/rum-config.cpp
Thread-safe singleton rum_collector aggregates devices, streams, option changes, filters, and notifications into a versioned JSON report with stable source_id, and persists via flush(); singleton rum_config persists cloud-upload consent to JSON file with RS2_RUM_CLOUD_ENABLED environment-variable override; both guarded by mutexes.
rum_hooks: instrumentation bridge
src/rum/rum-hooks.h, src/rum/rum-hooks.cpp
Declares and implements librealsense::rum::hooks free-function interface translating SDK events into rum_collector record calls; includes RETURN_IF_NO_RUM guard for compile-time disable, stream tally key extraction, sensor-vs-processing-block distinction, and whitelisted filter name matching.
SDK call-path instrumentation
src/context.cpp, src/rs.cpp, src/sensor.{h,cpp}, src/proc/synthetic-stream.{h,cpp}
Wires hooks into existing paths: context destructor calls on_context_closed to flush; rs2_create_device calls on_device; rs2_open/rs2_open_multiple call on_open; rs2_set_option introduces applied value and calls on_set_option; raise_notification calls on_notification; synthetic_sensor::start/stop measure and report stream duration; processing_block::invoke fires on_filter once per instance via atomic guard.
Public C/C++ API surface and DLL exports
include/librealsense2/h/rs_rum.h, include/librealsense2/hpp/rs_rum.hpp, include/librealsense2/rs.h, include/librealsense2/rs.hpp, src/rs.cpp, common/device-model.h, src/realsense.def
Declares rs2_rum_get_report, rs2_rum_set_cloud_enabled, rs2_rum_is_cloud_enabled in C header and implements in src/rs.cpp; wraps as inline C++ in rs_rum.hpp; adds both headers to umbrella rs.h/rs.hpp includes; defines privacy config keys (rum_cloud_enabled, rum_upload_cadence_hours, rum_last_upload) in device-model.h; exports three symbols from Windows DLL def file.
Python bindings for rs.rum
wrappers/python/pyrealsense2.h, wrappers/python/pyrs_rum.cpp, wrappers/python/pyrealsense2.cpp
Adds init_rum function registering a rum submodule under pyrealsense2 with get_report, set_cloud_enabled, and is_cloud_enabled bindings; declares init_rum in header; calls it in PYBIND11_MODULE initialization sequence.
rum-uploader: HTTP upload with consent gating
common/rum-uploader/rum-uploader.h, common/rum-uploader/rum-uploader.cpp
endpoint() resolves cloud URL via RS2_RUM_ENDPOINT override; saved_report() loads persisted rum.json; upload(json, endpoint) POSTs via libcurl with mutex-guarded curl_easy_init, cloud-consent check, and error logging; start_saved_upload(cadence_hours, last_upload_unix, callback) spawns background worker enforcing cadence window and invoking callback on success; join_saved_upload() synchronizes at teardown.
Viewer Privacy UI and consent flow
common/viewer.h, common/viewer.cpp, tools/realsense-viewer/realsense-viewer.cpp
Adds Privacy tab (index 4) to settings modal with cloud toggle, cadence slider, local JSON export, and "Upload now" spawning guarded background thread (_rum_upload_thread/_rum_uploading atomic); destructor joins upload thread; first-run consent ImGui popup in realsense-viewer.cpp when rum_cloud_enabled key missing, persisting choice and launching cadence-throttled background upload with callback updating rum_last_upload on success; teardown joins saved-upload worker.
Dev server, tests, and documentation
tools/rum-uploader/dev-server/rum_dev_server.py, unit-tests/rum/pytest-rum-config.py, unit-tests/rum/pytest-rum-device.py, doc/rum.md
Python threaded HTTP stub accepting POST /v1/rum, persisting timestamped JSON files, and printing parsed summaries; pytest config tests for submodule exposure, consent round-trip, report JSON schema with 36-char source_id and required fields, source_id stability, and processing-block option exclusion; pytest device tests (D400*) validating device/stream/filter/option recording; rum.md documentation describing collected data, exclusions, opt-in controls, and storage locations.

Sequence Diagram(s)

sequenceDiagram
  participant viewer as realsense-viewer.cpp
  participant privacyUI as viewer.cpp Privacy Tab
  participant hooks as rum_hooks/collector
  participant config as rum_config
  participant uploader as rum_uploader
  participant cloud as RUM Cloud Endpoint

  Note over viewer: Startup
  viewer->>config: rum_cloud_enabled key present?
  alt First run
    viewer->>viewer: Show ImGui consent modal
    viewer->>config: set_cloud_enabled(true/false)
    viewer->>uploader: start_saved_upload(cadence, last_ts, callback)
  else Already configured
    viewer->>uploader: start_saved_upload(cadence, last_ts, callback)
  end
  uploader->>config: is_cloud_enabled()
  uploader->>uploader: saved_report() from rum.json
  uploader->>cloud: HTTP POST /v1/rum (libcurl)
  uploader->>config: on_uploaded callback(now_unix)

  Note over hooks: During session
  hooks->>hooks: on_device / on_open / on_set_option / on_filter / on_notification
  hooks->>hooks: on_stream_duration → rum_collector record_*

  Note over privacyUI: User "Upload now"
  privacyUI->>uploader: upload(get_report(), endpoint())
  uploader->>cloud: HTTP POST /v1/rum

  Note over viewer: Teardown
  hooks->>hooks: on_context_closed() → collector.flush()
  viewer->>uploader: join_saved_upload()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐇 A rabbit hopped in with a clipboard and cheer,
"Let's count all the streams and the filters so dear!
No serials, no secrets—just metrics we share,
With consent at the start and a heart full of care."
The SDK now whispers its stats through the air,
A JSON report floating on libcurl with flair! 📊

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: RUM Phase 1 implementation with stats collection and viewer consent/upload functionality.
Description check ✅ Passed The PR description is well-structured, explaining objectives, implementation details, deviations from plan, testing, and design decisions. However, it minimally follows the repository's template, which appears to be minimal.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rum-phase1

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/rs.cpp (1)

928-974: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Record the applied option value, not the raw input value.

rs2_set_option may coerce the input before setting (e.g., integer truncation), but the telemetry hook currently receives the original value. This can produce inaccurate options_changed.last_value in reports.

Suggested fix
 void rs2_set_option(const rs2_options* options, rs2_option option, float value, rs2_error** error) BEGIN_API_CALL
 {
@@
-    auto range = option_ref.get_range();
+    auto range = option_ref.get_range();
+    float applied_value = value;
@@
     case RS2_OPTION_TYPE_INTEGER:
@@
-        option_ref.set(std::trunc(value));
+        applied_value = std::trunc( value );
+        option_ref.set( applied_value );
         break;
@@
     case RS2_OPTION_TYPE_BOOLEAN:
         if (value == 0.f)
-            option_ref.set_value(false);
+        {
+            option_ref.set_value(false);
+            applied_value = 0.f;
+        }
         else if (value == 1.f)
-            option_ref.set_value(true);
+        {
+            option_ref.set_value(true);
+            applied_value = 1.f;
+        }
         else
             throw invalid_value_exception(rsutils::string::from() << "not a boolean: " << value);
         break;
@@
-    librealsense::rum::hooks::on_set_option( *options->options, option, value, range.def );
+    librealsense::rum::hooks::on_set_option( *options->options, option, applied_value, range.def );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rs.cpp` around lines 928 - 974, The telemetry hook call to
librealsense::rum::hooks::on_set_option at the end of the rs2_set_option
function receives the original input value parameter, but the function may
coerce this value before setting it (e.g., truncating floats to integers for
RS2_OPTION_TYPE_INTEGER, converting to boolean for RS2_OPTION_TYPE_BOOLEAN, or
converting to string enum descriptions for RS2_OPTION_TYPE_STRING). Capture the
actual coerced value that gets applied in each case within the switch statement
and pass that captured value to the on_set_option hook instead of the original
value parameter to ensure telemetry accurately records what was actually set.
🧹 Nitpick comments (2)
unit-tests/rum/pytest-rum-config.py (1)

12-16: 💤 Low value

Consider saving and restoring the original consent state.

The test modifies the cloud consent setting without restoring the original value. While pytest test isolation typically prevents cross-test interference, saving and restoring the original state is a best practice for tests that modify persistent settings.

♻️ Proposed enhancement
 def test_cloud_consent_round_trips():
+    original = rs.rum.is_cloud_enabled()
+    try:
-    rs.rum.set_cloud_enabled( True )
-    assert rs.rum.is_cloud_enabled()
-    rs.rum.set_cloud_enabled( False )
-    assert not rs.rum.is_cloud_enabled()
+        rs.rum.set_cloud_enabled( True )
+        assert rs.rum.is_cloud_enabled()
+        rs.rum.set_cloud_enabled( False )
+        assert not rs.rum.is_cloud_enabled()
+    finally:
+        rs.rum.set_cloud_enabled( original )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@unit-tests/rum/pytest-rum-config.py` around lines 12 - 16, The test function
test_cloud_consent_round_trips modifies the cloud consent setting but does not
restore the original state. Capture the original cloud consent state at the
beginning of the test by calling rs.rum.is_cloud_enabled() and saving it to a
variable, then ensure it is restored to that original value after all assertions
complete. Use a try/finally block or restore at the end of the test to guarantee
the original state is restored even if an assertion fails.
tools/rum-uploader/dev-server/rum_dev_server.py (1)

50-50: ⚡ Quick win

Refine exception handling to avoid catching all exceptions.

Catching bare Exception can mask unexpected errors. Narrow the scope to the specific exceptions expected during JSON decode and UTF-8 decode.

♻️ Proposed refinement
-        except Exception:
+        except (ValueError, UnicodeDecodeError):
             with open(path, "wb") as f:
                 f.write(body)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/rum-uploader/dev-server/rum_dev_server.py` at line 50, The bare `except
Exception:` at line 50 is too broad and can mask unexpected errors. Replace it
with specific exception types that are expected during the JSON decode and UTF-8
decode operations. Catch the specific exceptions that can be raised:
json.JSONDecodeError for JSON parsing failures and UnicodeDecodeError for UTF-8
decoding issues. This narrows the error handling scope and allows unexpected
exceptions to propagate, making debugging easier.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@common/viewer.cpp`:
- Around line 3166-3169: The _rum_upload_thread.join() call in the render loop
blocks the UI thread if the previous upload is still running. Remove the join()
call from this location. Instead, track an in-progress state for the upload
operation (e.g., a boolean flag or atomic variable), disable or ignore button
interactions while the upload is active, and move the join() call to only
execute during teardown (destructor or cleanup) or after confirming the worker
thread has completed.
- Around line 3176-3179: The config_file::instance() singleton is accessed from
background RUM upload worker threads, creating a data race with the viewer UI
thread. In common/viewer.cpp#L3176-L3179, refactor the manual upload worker to
return the success status and timestamp value instead of directly calling
config_file::instance().set(configurations::privacy::rum_last_upload, ...) from
the worker thread; the caller should then persist rum_last_upload on the viewer
thread. In tools/realsense-viewer/realsense-viewer.cpp#L60-L70, snapshot all
cadence configuration inputs before spawning the boot worker to avoid background
thread config access, and commit the successful upload timestamp result on the
viewer thread or protected by a shared config mutex. This ensures all
config_file writes occur on a single thread or under synchronized access.

In `@src/rs.cpp`:
- Around line 294-295: Hook invocations on core execution paths can throw
exceptions and cause API operations to fail even after primary work succeeds.
Wrap each hook call in a try/catch block that catches exceptions and logs them
at debug level only, ensuring telemetry failures do not affect SDK behavior.
Apply this fail-safe pattern at all hook call sites in src/rs.cpp: the
on_notification hook call at lines 294-295, and the other hook invocations at
lines 415-418, 834-835, 850-851, and 973-974. For each site, place the hook call
inside a try block and catch all exceptions, logging them with processLogger or
appropriate debug logging at a level that does not propagate the error to the
caller.

In `@tools/rum-uploader/dev-server/rum_dev_server.py`:
- Around line 40-42: The _Handler._counter is being incremented and accessed
without thread synchronization, causing a race condition when
ThreadingHTTPServer handles concurrent requests. To fix this, introduce a
threading.Lock at the class level in _Handler, then wrap the counter increment
and path construction (where _Handler._counter is accessed) in a lock context
manager to ensure atomic access to the counter. This prevents simultaneous
reads/writes and ensures unique filenames are generated for each request even
under concurrent load.

In `@tools/rum-uploader/rum-uploader.cpp`:
- Around line 71-75: The `rum_uploader::upload()` function runs on background
threads but libcurl requires process-wide global initialization via
`curl_global_init()` before any thread-safe handle creation. While the mutex
protects `curl_easy_init()` at lines 71-75, it does not satisfy this
requirement. Add a `std::call_once` static guard that calls `curl_global_init()`
once at process startup, before the existing mutex-protected `curl_easy_init()`
call in the curl initialization block. This ensures libcurl is properly
initialized at the process level regardless of which background thread calls
`upload()` first.

In `@unit-tests/rum/pytest-rum-device.py`:
- Around line 11-13: The depth_z16_profile function uses next() without a
default value, which raises a cryptic StopIteration exception if no matching Z16
profile is found. Add a try-except block around the next() call in the
depth_z16_profile function to catch StopIteration and raise a more informative
exception with a clear error message indicating that a Z16 depth format profile
was expected but not found on the device.

---

Outside diff comments:
In `@src/rs.cpp`:
- Around line 928-974: The telemetry hook call to
librealsense::rum::hooks::on_set_option at the end of the rs2_set_option
function receives the original input value parameter, but the function may
coerce this value before setting it (e.g., truncating floats to integers for
RS2_OPTION_TYPE_INTEGER, converting to boolean for RS2_OPTION_TYPE_BOOLEAN, or
converting to string enum descriptions for RS2_OPTION_TYPE_STRING). Capture the
actual coerced value that gets applied in each case within the switch statement
and pass that captured value to the on_set_option hook instead of the original
value parameter to ensure telemetry accurately records what was actually set.

---

Nitpick comments:
In `@tools/rum-uploader/dev-server/rum_dev_server.py`:
- Line 50: The bare `except Exception:` at line 50 is too broad and can mask
unexpected errors. Replace it with specific exception types that are expected
during the JSON decode and UTF-8 decode operations. Catch the specific
exceptions that can be raised: json.JSONDecodeError for JSON parsing failures
and UnicodeDecodeError for UTF-8 decoding issues. This narrows the error
handling scope and allows unexpected exceptions to propagate, making debugging
easier.

In `@unit-tests/rum/pytest-rum-config.py`:
- Around line 12-16: The test function test_cloud_consent_round_trips modifies
the cloud consent setting but does not restore the original state. Capture the
original cloud consent state at the beginning of the test by calling
rs.rum.is_cloud_enabled() and saving it to a variable, then ensure it is
restored to that original value after all assertions complete. Use a try/finally
block or restore at the end of the test to guarantee the original state is
restored even if an assertion fails.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b673d982-4609-45f7-a192-6275a3e0135e

📥 Commits

Reviewing files that changed from the base of the PR and between 7bd3145 and 80c430a.

📒 Files selected for processing (37)
  • CMake/global_config.cmake
  • CMake/lrs_options.cmake
  • common/device-model.h
  • common/viewer.cpp
  • common/viewer.h
  • doc/rum.md
  • include/CMakeLists.txt
  • include/librealsense2/h/rs_rum.h
  • include/librealsense2/hpp/rs_rum.hpp
  • include/librealsense2/rs.h
  • include/librealsense2/rs.hpp
  • src/CMakeLists.txt
  • src/context.cpp
  • src/proc/synthetic-stream.cpp
  • src/proc/synthetic-stream.h
  • src/realsense.def
  • src/rs.cpp
  • src/rum/CMakeLists.txt
  • src/rum/rum-collector.cpp
  • src/rum/rum-collector.h
  • src/rum/rum-config.cpp
  • src/rum/rum-config.h
  • src/rum/rum-hooks.cpp
  • src/rum/rum-hooks.h
  • src/sensor.cpp
  • src/sensor.h
  • tools/realsense-viewer/CMakeLists.txt
  • tools/realsense-viewer/realsense-viewer.cpp
  • tools/rum-uploader/dev-server/rum_dev_server.py
  • tools/rum-uploader/rum-uploader.cpp
  • tools/rum-uploader/rum-uploader.h
  • unit-tests/rum/pytest-rum-config.py
  • unit-tests/rum/pytest-rum-device.py
  • wrappers/python/CMakeLists.txt
  • wrappers/python/pyrealsense2.cpp
  • wrappers/python/pyrealsense2.h
  • wrappers/python/pyrs_rum.cpp

Comment thread common/viewer.cpp Outdated
Comment thread common/viewer.cpp Outdated
Comment thread src/rs.cpp
Comment thread tools/rum-uploader/dev-server/rum_dev_server.py
Comment thread common/rum-uploader/rum-uploader.cpp Outdated
Comment thread unit-tests/rum/pytest-rum-device.py Outdated
@AviaAv

AviaAv commented Jun 16, 2026

Copy link
Copy Markdown
Owner Author

Thanks for the review. Addressed 2, declining 4 with reasoning:

Fixed

  • Upload now blocking the UI thread — moved the manual upload to a worker thread, guarded by an in-flight atomic so the render loop never blocks (and never join()s a running upload). The thread is joined in ~viewer_model.
  • depth_z16_profile StopIteration — now uses next(..., None) + an explicit assert with a clear message.

Declining (with reasons)

  • config_file data raceconfig_file already serializes every set/get/save behind an internal std::recursive_mutex (common/rs-config.cpp), so concurrent worker + UI access is safe. The only residual is a benign stale-cadence TOCTOU, not a data race.
  • RUM hooks fail-safe — the hook call sites are already inside BEGIN_API_CALL/HANDLE_EXCEPTIONS_AND_RETURN (and processing_block::invoke's own try/catch), so a throwing hook cannot crash or leak. The lone site without a macro (on_notification) only does a map insert, so the only thing that could escape is bad_alloc (process already failing). Opted to rely on the existing handling rather than add a wrapper that re-indents every hook.
  • dev-server _counter race — this is a local dev/inspection stub; a single viewer uploads one report at a time (cadence-gated), so concurrent POSTs don't occur in practice, and filenames are timestamped. Left as-is.
  • curl_global_init — RUM's curl_easy_init calls are already serialized by curl_init_mutex, so the implicit global-init can't race between RUM threads. The cross-feature race (vs sw-update's curl use) needs a single SDK-wide curl_global_init at startup; a RUM-local call_once wouldn't coordinate with it, so that belongs in a separate SDK-level change rather than this PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@doc/rum.md`:
- Around line 35-37: The documentation for the `-DENABLED_STATS=OFF` build flag
in the "Disable collection entirely at build time" section (lines 35-37 of
doc/rum.md) incorrectly states "no RUM code runs," which implies the functions
are completely removed. Reword this section to accurately reflect the ABI
contract: clarify that the RUM APIs remain available as no-op functions that
collect and upload nothing, rather than implying symbol removal or zero
execution paths. Keep the existing language about no statistics being collected
but adjust the phrasing to be precise about function availability.

In `@tools/rum-uploader/dev-server/rum_dev_server.py`:
- Around line 45-60: The broad `except Exception` block at line 50 is catching
both JSON parsing errors and file I/O errors, but the handler returns 200 OK in
all cases, masking persistence failures to clients. Replace the single broad
exception handler with two separate exception handlers: catch
`(UnicodeDecodeError, json.JSONDecodeError)` first to handle the non-JSON
fallback path with the existing logic, then catch `OSError` separately to detect
persistence failures and return a 500 status code with an appropriate error
response instead of the 200 OK response. Ensure that the successful 200 OK
response at line 55 onwards is only sent when both JSON parsing (if applicable)
and file I/O operations complete successfully.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 853741be-7950-4835-8d27-e476cfa3e74a

📥 Commits

Reviewing files that changed from the base of the PR and between 80c430a and d9333f1.

📒 Files selected for processing (27)
  • common/device-model.h
  • common/viewer.cpp
  • common/viewer.h
  • doc/rum.md
  • include/CMakeLists.txt
  • include/librealsense2/h/rs_rum.h
  • include/librealsense2/hpp/rs_rum.hpp
  • include/librealsense2/rs.h
  • include/librealsense2/rs.hpp
  • src/context.cpp
  • src/proc/synthetic-stream.cpp
  • src/proc/synthetic-stream.h
  • src/realsense.def
  • src/rs.cpp
  • src/sensor.cpp
  • src/sensor.h
  • tools/realsense-viewer/CMakeLists.txt
  • tools/realsense-viewer/realsense-viewer.cpp
  • tools/rum-uploader/dev-server/rum_dev_server.py
  • tools/rum-uploader/rum-uploader.cpp
  • tools/rum-uploader/rum-uploader.h
  • unit-tests/rum/pytest-rum-config.py
  • unit-tests/rum/pytest-rum-device.py
  • wrappers/python/CMakeLists.txt
  • wrappers/python/pyrealsense2.cpp
  • wrappers/python/pyrealsense2.h
  • wrappers/python/pyrs_rum.cpp
✅ Files skipped from review due to trivial changes (3)
  • tools/realsense-viewer/CMakeLists.txt
  • include/librealsense2/rs.h
  • src/sensor.h
🚧 Files skipped from review as they are similar to previous changes (21)
  • common/device-model.h
  • wrappers/python/pyrs_rum.cpp
  • wrappers/python/pyrealsense2.cpp
  • src/realsense.def
  • include/CMakeLists.txt
  • common/viewer.h
  • wrappers/python/pyrealsense2.h
  • src/proc/synthetic-stream.cpp
  • wrappers/python/CMakeLists.txt
  • src/context.cpp
  • tools/rum-uploader/rum-uploader.h
  • src/proc/synthetic-stream.h
  • include/librealsense2/rs.hpp
  • src/sensor.cpp
  • tools/realsense-viewer/realsense-viewer.cpp
  • tools/rum-uploader/rum-uploader.cpp
  • src/rs.cpp
  • include/librealsense2/h/rs_rum.h
  • unit-tests/rum/pytest-rum-device.py
  • unit-tests/rum/pytest-rum-config.py
  • common/viewer.cpp

Comment thread doc/rum.md Outdated
Comment thread tools/rum-uploader/dev-server/rum_dev_server.py
@AviaAv

AviaAv commented Jun 16, 2026

Copy link
Copy Markdown
Owner Author

Re-review follow-up:

  • doc/rum.md OFF-mode wording — fixed. Reworded to state the rs2_rum_* API stays available (ABI-stable) but becomes a no-op when built with -DENABLED_STATS=OFF (nothing collected/persisted/uploaded), instead of implying the code is removed.
  • rum_dev_server.py broad except — declining. This is a local dev/inspection stub, not the production ingest server. A failed write there only means a missing local inspection file; returning 200 vs 500 has no consumer (the viewer doesn't act on the stub's status). Keeping it minimal.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/rs.cpp`:
- Line 973: The call to librealsense::rum::hooks::on_set_option at line 973
reports the raw input value to telemetry, but integer options are coerced
earlier in the function at line 948. To fix this, capture the actual
coerced/applied value that results from the option processing and pass that to
on_set_option instead of the original raw value parameter. This ensures
telemetry accurately reports what value was actually applied rather than what
was requested, allowing proper classification of default-vs-changed state.

In `@tools/rum-uploader/dev-server/rum_dev_server.py`:
- Around line 7-8: The docstring at the beginning of the file incorrectly claims
the server accepts "HTTPS/HTTP POST" when in reality the implementation uses
plain ThreadingHTTPServer without any TLS context, making it HTTP-only. Update
the docstring to remove the "HTTPS/" reference and accurately state that the
server accepts only "HTTP POST /v1/rum" to prevent confusion during endpoint
setup and debugging.

In `@unit-tests/rum/pytest-rum-config.py`:
- Around line 12-16: The test_cloud_consent_round_trips function modifies global
SDK state by calling set_cloud_enabled without restoring the original value,
which can cause other tests to behave unexpectedly depending on execution order.
Save the original cloud enabled state at the start of the test using
is_cloud_enabled(), then wrap the test logic in a try/finally block to ensure
the original state is restored by calling set_cloud_enabled with the saved value
in the finally clause.
- Around line 41-46: The assertion in
test_processing_block_option_excluded_from_options_changed currently checks for
the global absence of "Min Distance" in the options_changed list, which can fail
if "Min Distance" was recorded by earlier tests. Instead, capture the count of
"Min Distance" entries before calling th.set_option(), then capture the count
again after rs.rum.get_report(), and assert that the count did not increase (the
delta should be zero). This validates that this specific processing-block
operation does not add to the recorded count, rather than checking for absolute
absence.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 60bd263e-91b6-4eda-8e96-c017eb0e5619

📥 Commits

Reviewing files that changed from the base of the PR and between d9333f1 and 2dd0f2f.

📒 Files selected for processing (27)
  • common/device-model.h
  • common/viewer.cpp
  • common/viewer.h
  • doc/rum.md
  • include/CMakeLists.txt
  • include/librealsense2/h/rs_rum.h
  • include/librealsense2/hpp/rs_rum.hpp
  • include/librealsense2/rs.h
  • include/librealsense2/rs.hpp
  • src/context.cpp
  • src/proc/synthetic-stream.cpp
  • src/proc/synthetic-stream.h
  • src/realsense.def
  • src/rs.cpp
  • src/sensor.cpp
  • src/sensor.h
  • tools/realsense-viewer/CMakeLists.txt
  • tools/realsense-viewer/realsense-viewer.cpp
  • tools/rum-uploader/dev-server/rum_dev_server.py
  • tools/rum-uploader/rum-uploader.cpp
  • tools/rum-uploader/rum-uploader.h
  • unit-tests/rum/pytest-rum-config.py
  • unit-tests/rum/pytest-rum-device.py
  • wrappers/python/CMakeLists.txt
  • wrappers/python/pyrealsense2.cpp
  • wrappers/python/pyrealsense2.h
  • wrappers/python/pyrs_rum.cpp
✅ Files skipped from review due to trivial changes (2)
  • include/librealsense2/rs.h
  • include/CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (20)
  • include/librealsense2/rs.hpp
  • wrappers/python/pyrs_rum.cpp
  • wrappers/python/CMakeLists.txt
  • src/realsense.def
  • src/proc/synthetic-stream.cpp
  • common/device-model.h
  • tools/rum-uploader/rum-uploader.h
  • src/sensor.h
  • src/proc/synthetic-stream.h
  • include/librealsense2/h/rs_rum.h
  • wrappers/python/pyrealsense2.h
  • common/viewer.h
  • common/viewer.cpp
  • wrappers/python/pyrealsense2.cpp
  • src/sensor.cpp
  • tools/realsense-viewer/realsense-viewer.cpp
  • tools/realsense-viewer/CMakeLists.txt
  • include/librealsense2/hpp/rs_rum.hpp
  • tools/rum-uploader/rum-uploader.cpp
  • unit-tests/rum/pytest-rum-device.py

Comment thread src/rs.cpp Outdated
Comment thread tools/rum-uploader/dev-server/rum_dev_server.py
Comment thread unit-tests/rum/pytest-rum-config.py Outdated
Comment thread unit-tests/rum/pytest-rum-config.py
@AviaAv

AviaAv commented Jun 16, 2026

Copy link
Copy Markdown
Owner Author

Latest round:

  • rs.cpp report coerced option value — fixed. on_set_option now receives the value actually applied (integer options truncated via std::trunc), so telemetry matches what the device was set to.
  • rum_dev_server.py HTTPS/HTTP docstring — declining. Minor wording on the local dev stub; not worth a churn on a throwaway inspection tool.
  • pytest-rum-config.py save/restore consent — declining. Deliberately keeping the tests out of the business of snapshotting/restoring shared config state; no test in the suite depends on the consent value, so leaving it toggled is harmless.
  • pytest-rum-config.py Min Distance delta vs absence — declining (not applicable). "Min Distance" is a processing-block option, which RUM excludes from options_changed by design — it is never recorded regardless of test order, so the global-absence assertion can't be tripped by a prior test.

@AviaAv
AviaAv force-pushed the rum-phase1 branch 2 times, most recently from 385b16c to 4f68d29 Compare June 21, 2026 11:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
common/rum-uploader/rum-uploader.cpp (1)

131-154: 💤 Low value

Assigning to a joinable std::thread will call std::terminate.

If start_saved_upload() is ever called twice without an intervening join_saved_upload(), the assignment at line 134 will invoke std::terminate() because saved_upload_thread is still joinable.

The current viewer correctly guards this with a static rum_startup_done flag, so there's no immediate bug. However, the API itself is fragile—consider adding a defensive check or documenting the single-call constraint.

🛡️ Optional defensive fix
 void start_saved_upload( int cadence_hours, long long last_upload_unix,
                          std::function< void( long long ) > on_uploaded )
 {
+    if( saved_upload_thread.joinable() )
+    {
+        LOG_WARNING( "RUM: start_saved_upload called while upload already in progress; ignoring" );
+        return;
+    }
     saved_upload_thread = std::thread( [cadence_hours, last_upload_unix, on_uploaded = std::move( on_uploaded )]()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@common/rum-uploader/rum-uploader.cpp` around lines 131 - 154, The
start_saved_upload function directly assigns to saved_upload_thread without
checking if it's already joinable, which will call std::terminate if the
function is called twice without an intervening join. Add a defensive check in
start_saved_upload before the thread assignment to verify if saved_upload_thread
is joinable and join it if needed, or alternatively add clear documentation
explaining that this function must not be called multiple times without properly
joining the previous thread first.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CMake/lrs_options.cmake`:
- Line 65: The ENABLED_STATS option in CMake/lrs_options.cmake is currently set
to OFF as the default, which contradicts the PR objective of enabling RUM
usage-statistics collection by default. Change the default value from OFF to ON
in the option definition for ENABLED_STATS to ensure RUM telemetry is enabled in
standard builds unless users explicitly disable it.

In `@doc/rum.md`:
- Around line 35-36: The documentation in the RUM statistics section contradicts
the actual default behavior. Line 35 currently states that collection is "off by
default," but according to this PR's contract, the collection should be ON by
default. Update the wording on line 35 to accurately reflect that the
ENABLED_STATS collection is ON by default at build time, and clarify how the
build flag `-DENABLED_STATS=ON` relates to this default state (whether it
enforces the default or changes behavior). This ensures packagers and operators
have correct information about the default telemetry behavior.

---

Nitpick comments:
In `@common/rum-uploader/rum-uploader.cpp`:
- Around line 131-154: The start_saved_upload function directly assigns to
saved_upload_thread without checking if it's already joinable, which will call
std::terminate if the function is called twice without an intervening join. Add
a defensive check in start_saved_upload before the thread assignment to verify
if saved_upload_thread is joinable and join it if needed, or alternatively add
clear documentation explaining that this function must not be called multiple
times without properly joining the previous thread first.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 032a040b-9544-4c78-870c-20d85c77b7ed

📥 Commits

Reviewing files that changed from the base of the PR and between 385b16c and 4f68d29.

📒 Files selected for processing (39)
  • .github/workflows/buildsCI.yaml
  • CMake/global_config.cmake
  • CMake/lrs_options.cmake
  • common/CMakeLists.txt
  • common/device-model.h
  • common/rum-uploader/rum-uploader.cpp
  • common/rum-uploader/rum-uploader.h
  • common/viewer.cpp
  • common/viewer.h
  • doc/rum.md
  • include/CMakeLists.txt
  • include/librealsense2/h/rs_rum.h
  • include/librealsense2/hpp/rs_rum.hpp
  • include/librealsense2/rs.h
  • include/librealsense2/rs.hpp
  • src/CMakeLists.txt
  • src/context.cpp
  • src/proc/synthetic-stream.cpp
  • src/proc/synthetic-stream.h
  • src/realsense.def
  • src/rs.cpp
  • src/rum/CMakeLists.txt
  • src/rum/rum-collector.cpp
  • src/rum/rum-collector.h
  • src/rum/rum-config.cpp
  • src/rum/rum-config.h
  • src/rum/rum-hooks.cpp
  • src/rum/rum-hooks.h
  • src/sensor.cpp
  • src/sensor.h
  • tools/realsense-viewer/CMakeLists.txt
  • tools/realsense-viewer/realsense-viewer.cpp
  • tools/rum-uploader/dev-server/rum_dev_server.py
  • unit-tests/rum/pytest-rum-config.py
  • unit-tests/rum/pytest-rum-device.py
  • wrappers/python/CMakeLists.txt
  • wrappers/python/pyrealsense2.cpp
  • wrappers/python/pyrealsense2.h
  • wrappers/python/pyrs_rum.cpp
✅ Files skipped from review due to trivial changes (6)
  • common/device-model.h
  • src/rum/CMakeLists.txt
  • include/librealsense2/rs.h
  • include/librealsense2/hpp/rs_rum.hpp
  • include/librealsense2/rs.hpp
  • include/CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (25)
  • CMake/global_config.cmake
  • wrappers/python/pyrealsense2.h
  • tools/realsense-viewer/CMakeLists.txt
  • wrappers/python/pyrealsense2.cpp
  • wrappers/python/pyrs_rum.cpp
  • src/CMakeLists.txt
  • src/sensor.cpp
  • src/realsense.def
  • src/sensor.h
  • unit-tests/rum/pytest-rum-config.py
  • wrappers/python/CMakeLists.txt
  • src/rum/rum-collector.h
  • include/librealsense2/h/rs_rum.h
  • src/rum/rum-config.h
  • common/CMakeLists.txt
  • src/proc/synthetic-stream.cpp
  • src/proc/synthetic-stream.h
  • unit-tests/rum/pytest-rum-device.py
  • common/viewer.h
  • common/viewer.cpp
  • src/rum/rum-hooks.h
  • src/context.cpp
  • src/rum/rum-hooks.cpp
  • src/rum/rum-collector.cpp
  • src/rs.cpp

Comment thread CMake/lrs_options.cmake Outdated
option(USE_EXTERNAL_LZ4 "Use externally build LZ4 library instead of building and using the in this repo provided version" OFF)
option(BUILD_ASAN "Enable AddressSanitizer" OFF)
option(BUILD_ROSBAG2 "Build and use rosbag2 recording system" ON) # temporary flag, should be removed when deprecated ROSBAG1 recording system is removed
option(ENABLED_STATS "Enable RUM (Real User Monitoring) usage-statistics collection" OFF)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Default contradicts PR objectives.

The PR summary states the feature is "enabled by default," but this option sets OFF as the default. This means RUM telemetry will be disabled in standard builds unless users explicitly override it.

🔧 Proposed fix to enable by default
-option(ENABLED_STATS "Enable RUM (Real User Monitoring) usage-statistics collection" OFF)
+option(ENABLED_STATS "Enable RUM (Real User Monitoring) usage-statistics collection" ON)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
option(ENABLED_STATS "Enable RUM (Real User Monitoring) usage-statistics collection" OFF)
option(ENABLED_STATS "Enable RUM (Real User Monitoring) usage-statistics collection" ON)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CMake/lrs_options.cmake` at line 65, The ENABLED_STATS option in
CMake/lrs_options.cmake is currently set to OFF as the default, which
contradicts the PR objective of enabling RUM usage-statistics collection by
default. Change the default value from OFF to ON in the option definition for
ENABLED_STATS to ensure RUM telemetry is enabled in standard builds unless users
explicitly disable it.

Comment thread doc/rum.md Outdated
Comment on lines +35 to +36
- **Collection is off by default at build time**: build the SDK with `-DENABLED_STATS=ON` to enable
it. When off, the `rs2_rum_*` API stays available (ABI-stable) but is a no-op — nothing is

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix contradictory default-state wording for ENABLED_STATS.

Line 35 says collection is “off by default,” but this PR’s contract is default ON. This can mislead packagers/operators about telemetry behavior.

Suggested minimal doc fix
-- **Collection is off by default at build time**: build the SDK with `-DENABLED_STATS=ON` to enable
+- **Collection can be disabled at build time**: build the SDK with `-DENABLED_STATS=OFF` to disable
   it. When off, the `rs2_rum_*` API stays available (ABI-stable) but is a no-op — nothing is
   collected, persisted, or uploaded.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- **Collection is off by default at build time**: build the SDK with `-DENABLED_STATS=ON` to enable
it. When off, the `rs2_rum_*` API stays available (ABI-stable) but is a no-op — nothing is
- **Collection can be disabled at build time**: build the SDK with `-DENABLED_STATS=OFF` to disable
it. When off, the `rs2_rum_*` API stays available (ABI-stable) but is a no-op — nothing is
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doc/rum.md` around lines 35 - 36, The documentation in the RUM statistics
section contradicts the actual default behavior. Line 35 currently states that
collection is "off by default," but according to this PR's contract, the
collection should be ON by default. Update the wording on line 35 to accurately
reflect that the ENABLED_STATS collection is ON by default at build time, and
clarify how the build flag `-DENABLED_STATS=ON` relates to this default state
(whether it enforces the default or changes behavior). This ensures packagers
and operators have correct information about the default telemetry behavior.

@AviaAv
AviaAv force-pushed the rum-phase1 branch 3 times, most recently from 69114cd to 8098ffc Compare June 22, 2026 08:25
@AviaAv
AviaAv force-pushed the rum-phase1 branch 2 times, most recently from a1b7b54 to fbac3c7 Compare June 22, 2026 09:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant