chore: add telemetry sending - #408
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new telemetry subsystem and wires it into the Safe Synthesizer SDK pipeline so runs can enqueue/send anonymized, bucketed event metrics.
Changes:
- Introduces
src/nemo_safe_synthesizer/telemetry.pywith event schemas, bucketing helpers, payload builder, and an async telemetry handler with retry/DLQ behavior. - Adds unit tests for telemetry helpers, payload formatting, and handler send/retry behavior.
- Emits telemetry from
SafeSynthesizerlifecycle (completed/canceled/error) and addshttpxas a dependency.
Reviewed changes
Copilot reviewed 4 out of 6 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
src/nemo_safe_synthesizer/telemetry.py |
New telemetry event models + handler (batching/sending/retry) and payload construction. |
tests/telemetry/test_telemetry.py |
New unit tests covering bucketing, event serialization, payload structure, and send/retry logic. |
src/nemo_safe_synthesizer/sdk/library_builder.py |
Emits a telemetry event from SDK execution paths and tracks idempotent emission. |
src/nemo_safe_synthesizer/evaluation/assets/text/multi_modal_tooltips.py |
Minor whitespace cleanup in tooltip text. |
pyproject.toml |
Adds httpx dependency required by telemetry sender. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Used to avoid transmitting exact record counts in telemetry. | ||
| """ |
There was a problem hiding this comment.
bucket_records() treats n <= 0 as the lowest bucket (e.g., 0 -> '1-100'), which is misleading and can happen for empty DataFrames. Consider handling non-positive values explicitly (e.g., return 'undefined' or a 0 bucket) before the first if n <= 100: branch.
| Used to avoid transmitting exact record counts in telemetry. | |
| """ | |
| Used to avoid transmitting exact record counts in telemetry. | |
| Non-positive counts are reported as undefined. | |
| """ | |
| if n <= 0: | |
| return "undefined" |
There was a problem hiding this comment.
we don't run if there are too few records. might want to make the lower bucket the preflight threshold.
There was a problem hiding this comment.
do we not have any way to run for under 200? I thought you could if you set holdout to 0
There was a problem hiding this comment.
With the validation checks now, we hard crash on <200 rows. Not sure if that's in a constant we could (or would want to) import here.
I guess we'll get an error event back if someone runs with 150 records, so would be tidier to have that all fall under the same 1-200 bucket (or 1-199, not sure on a >= or > check) where we should see 100% errors. And then the 201-1000 bucket wouldn't ever have the <200 row error. Instead of 1-100 being all errors and 101-1000 being a mix of known error when <200 but success otherwise.
| report=self.evaluator.report, | ||
| generate_results=self.generator.gen_results, | ||
| ) | ||
| _emit_nss_telemetry(self, TaskStatusEnum.COMPLETED) | ||
| return self |
There was a problem hiding this comment.
Telemetry is emitted as COMPLETED at the end of evaluate(). In run(), this happens before save_results(), so if saving results fails you’ll still have already emitted a completed event and the later error handler won’t emit due to _telemetry_emitted. Consider emitting the final COMPLETED status after save_results() in run() (and only emitting from evaluate() when evaluate() is called standalone), or adjust the idempotence logic so failures after evaluation can be reported accurately.
There was a problem hiding this comment.
moved these around for CLI and SDK
| _deployment_type_raw = os.getenv("NEMO_DEPLOYMENT_TYPE", "sdk").lower() | ||
| try: | ||
| DEPLOYMENT_TYPE = DeploymentTypeEnum(_deployment_type_raw) | ||
| except ValueError: | ||
| valid_values = [e.value for e in DeploymentTypeEnum] | ||
| raise ValueError( | ||
| f"Invalid NEMO_DEPLOYMENT_TYPE: {_deployment_type_raw!r}. Must be one of: {valid_values}" | ||
| ) from None |
There was a problem hiding this comment.
DEPLOYMENT_TYPE is validated at import time and raises ValueError for an unexpected NEMO_DEPLOYMENT_TYPE. That can prevent importing the whole library even when telemetry is disabled/best-effort. Consider defaulting to DeploymentTypeEnum.UNDEFINED (and optionally logging) instead of raising during module import.
f6b8263 to
6fc651e
Compare
6fc651e to
a1cc23c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
a1cc23c to
c23bf05
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Signed-off-by: Matt Kornfield <mkornfield@nvidia.com>
Signed-off-by: mkornfield <mkornfield@nvidia.com>
Signed-off-by: mkornfield <mkornfield@nvidia.com>
c23bf05 to
ac308c5
Compare
| @@ -0,0 +1,58 @@ | |||
| # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |||
There was a problem hiding this comment.
I can also delete this hehe
binaryaaron
left a comment
There was a problem hiding this comment.
thanks, let's make this a top-level config object so it's more easily managed and piped with everything else.
need to update the user guide/docs to note the flag as well.
| report=self.evaluator.report, | ||
| generate_results=self.generator.gen_results, | ||
| ) | ||
| _emit_nss_telemetry(self, TaskStatusEnum.COMPLETED) | ||
| return self |
|
Are we detecting/tracking CLI vs SDK usage? That was in the doc list last I saw. If so, maybe that's the way to track internal slurm usage. Use an env var to add a suffix so it would show up as |
Signed-off-by: mkornfield <mkornfield@nvidia.com>
| Used to avoid transmitting exact record counts in telemetry. | ||
| """ |
There was a problem hiding this comment.
we don't run if there are too few records. might want to make the lower bucket the preflight threshold.
| report=self.evaluator.report, | ||
| generate_results=self.generator.gen_results, | ||
| ) | ||
| _emit_nss_telemetry(self, TaskStatusEnum.COMPLETED) | ||
| return self |
Signed-off-by: mkornfield <mkornfield@nvidia.com>
Signed-off-by: mkornfield <mkornfield@nvidia.com>
Signed-off-by: mkornfield <mkornfield@nvidia.com>
Signed-off-by: mkornfield <mkornfield@nvidia.com>
kendrickb-nvidia
left a comment
There was a problem hiding this comment.
A few small items, but looks ready.
Signed-off-by: mkornfield <mkornfield@nvidia.com>
Signed-off-by: mkornfield <mkornfield@nvidia.com>
Summary
Pre-Review Checklist
Ensure that the following pass:
make format && make checkor via prek validation.make testpasses locallymake test-e2epasses locallymake test-ci-containerpasses locally (recommended)/syncon this PR to trigger a run (auto-triggers on ready-for-review)Pre-Merge Checklist
Other Notes