Skip to content

feat(usage-collector): add OpenTelemetry metrics/observability layer#4181

Merged
Artifizer merged 1 commit into
constructorfabric:mainfrom
capybutler:usage-collector/metrics
Jul 10, 2026
Merged

feat(usage-collector): add OpenTelemetry metrics/observability layer#4181
Artifizer merged 1 commit into
constructorfabric:mainfrom
capybutler:usage-collector/metrics

Conversation

@capybutler

@capybutler capybutler commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Adds an OpenTelemetry metrics/observability layer to the usage-collector gear,
implementing the operational metric inventory defined in DESIGN §3.11.5. Metrics are
exported via OTLP through ToolKit's global SdkMeterProvider.

Domain

  • New UsageCollectorMetrics port (domain/ports/metrics.rs) with a no-op default
    implementation, plus typed, bounded label vocabularies (PdpOp, PluginOp,
    PdpFailureCause, AuthzDecision, PluginErrorCategory, RequestOutcome,
    RecordKind, QueryKind, …) so every uc_* instrument only carries closed-set
    label values.
  • The service is instrumented across all flows: PDP decisions/failures and readiness,
    plugin call duration / accept errors and readiness, ingestion (batch size, duration,
    per-record and per-request outcomes, metadata bytes), query (inflight gauge, result
    rows, request outcomes), deactivation requests, and usage-type requests.

Infra

  • Port implementation over opentelemetry::global::meter_with_scope, building the full
    uc_* instrument set at gear bootstrap.

uc_usage_types catalog gauge

  • Refreshed from a 60s serve-lifecycle loop (rather than per-mutation), paginating the
    whole catalog via CursorV1 and summing item counts across pages. Bounded by a
    refresh timeout, page-size limit, and page-count safety cap; any SPI error,
    undecodable cursor, or cap breach leaves the gauge at its prior value (best-effort,
    never a partial-count publish). Supports multi-instance aggregation.

Supporting changes

  • Gear config for the metrics layer.
  • tokio-util promoted to a runtime dependency for the serve lifecycle.
  • New domain / infra / module / config tests (including a multi-page list_usage_types
    queue in HappyPathPlugin); DESIGN and feature docs updated.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update

Testing

  • Unit tests pass
  • Integration tests pass
  • Manual testing completed
  • New tests added for new functionality

Documentation

  • Code is documented with rustdoc comments
  • README updated (if applicable)
  • API documentation updated (if applicable)

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • No linting errors (cargo clippy)
  • Code is properly formatted (cargo fmt)
  • Tests pass (cargo test)

Summary by CodeRabbit

  • New Features
    • Added configurable usage-collector metrics prefix (default uc) and full OTLP-based operational telemetry.
    • Added a periodic usage-type availability gauge refresh, plus improved lifecycle shutdown behavior.
  • Bug Fixes
    • Standardized PDP authorization enforcement and error translations with clearer fail-closed behavior.
    • Improved readiness and metric reporting for unready/unavailable plugins and PDP issues.
  • Documentation
    • Updated authorization and observability/design docs to reflect the latest PDP-helper model and uc_* metric inventory.
  • Tests
    • Added/expanded metrics and module lifecycle tests to verify emitted instrumentation.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 94835e69-45ea-496c-be8a-e50d8898bc9f

📥 Commits

Reviewing files that changed from the base of the PR and between 038aabd and 9b03ef1.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • gears/system/usage-collector/docs/ADR/0001-pdp-centric-authorization.md
  • gears/system/usage-collector/docs/DECOMPOSITION.md
  • gears/system/usage-collector/docs/DESIGN.md
  • gears/system/usage-collector/docs/PRD.md
  • gears/system/usage-collector/docs/features/event-deactivation.md
  • gears/system/usage-collector/docs/features/foundation.md
  • gears/system/usage-collector/docs/features/usage-emission.md
  • gears/system/usage-collector/docs/features/usage-query.md
  • gears/system/usage-collector/docs/features/usage-type-lifecycle.md
  • gears/system/usage-collector/docs/plugin-spi.md
  • gears/system/usage-collector/docs/sdk-trait.md
  • gears/system/usage-collector/usage-collector/Cargo.toml
  • gears/system/usage-collector/usage-collector/src/config.rs
  • gears/system/usage-collector/usage-collector/src/config_tests.rs
  • gears/system/usage-collector/usage-collector/src/domain/authz.rs
  • gears/system/usage-collector/usage-collector/src/domain/authz_tests.rs
  • gears/system/usage-collector/usage-collector/src/domain/local_client.rs
  • gears/system/usage-collector/usage-collector/src/domain/mod.rs
  • gears/system/usage-collector/usage-collector/src/domain/ports/metrics.rs
  • gears/system/usage-collector/usage-collector/src/domain/ports/mod.rs
  • gears/system/usage-collector/usage-collector/src/domain/service.rs
  • gears/system/usage-collector/usage-collector/src/domain/service_metrics_tests.rs
  • gears/system/usage-collector/usage-collector/src/domain/test_support.rs
  • gears/system/usage-collector/usage-collector/src/infra/metrics.rs
  • gears/system/usage-collector/usage-collector/src/infra/metrics_tests.rs
  • gears/system/usage-collector/usage-collector/src/infra/mod.rs
  • gears/system/usage-collector/usage-collector/src/module.rs
  • gears/system/usage-collector/usage-collector/src/module_tests.rs
✅ Files skipped from review due to trivial changes (4)
  • gears/system/usage-collector/docs/PRD.md
  • gears/system/usage-collector/usage-collector/src/domain/local_client.rs
  • gears/system/usage-collector/docs/sdk-trait.md
  • gears/system/usage-collector/docs/plugin-spi.md
🚧 Files skipped from review as they are similar to previous changes (19)
  • gears/system/usage-collector/docs/ADR/0001-pdp-centric-authorization.md
  • gears/system/usage-collector/usage-collector/src/infra/mod.rs
  • gears/system/usage-collector/usage-collector/src/domain/ports/mod.rs
  • gears/system/usage-collector/usage-collector/src/domain/mod.rs
  • gears/system/usage-collector/usage-collector/Cargo.toml
  • gears/system/usage-collector/usage-collector/src/infra/metrics.rs
  • gears/system/usage-collector/usage-collector/src/module.rs
  • gears/system/usage-collector/usage-collector/src/config.rs
  • gears/system/usage-collector/usage-collector/src/module_tests.rs
  • gears/system/usage-collector/usage-collector/src/domain/authz_tests.rs
  • gears/system/usage-collector/usage-collector/src/domain/ports/metrics.rs
  • gears/system/usage-collector/usage-collector/src/infra/metrics_tests.rs
  • gears/system/usage-collector/usage-collector/src/domain/authz.rs
  • gears/system/usage-collector/usage-collector/src/domain/service_metrics_tests.rs
  • gears/system/usage-collector/docs/features/event-deactivation.md
  • gears/system/usage-collector/usage-collector/src/domain/test_support.rs
  • gears/system/usage-collector/docs/features/usage-type-lifecycle.md
  • gears/system/usage-collector/usage-collector/src/domain/service.rs
  • gears/system/usage-collector/docs/features/foundation.md

📝 Walkthrough

Walkthrough

This PR adds usage-collector metrics plumbing end to end: config and domain metric contracts, PDP and SPI instrumentation, an OpenTelemetry adapter, module bootstrap and refresh handling, and supporting tests. Documentation is updated to use access_scope_with and to describe the new uc_* metrics and operational-visibility requirements.

Changes

Usage Collector metrics implementation

Layer / File(s) Summary
Config, metric ports, and PDP wrapper
gears/system/usage-collector/usage-collector/Cargo.toml, gears/system/usage-collector/usage-collector/src/config.rs, gears/system/usage-collector/usage-collector/src/config_tests.rs, gears/system/usage-collector/usage-collector/src/domain/mod.rs, gears/system/usage-collector/usage-collector/src/domain/ports/*, gears/system/usage-collector/usage-collector/src/domain/authz.rs, gears/system/usage-collector/usage-collector/src/domain/authz_tests.rs
Adds metrics config, bounded label vocabularies, the UsageCollectorMetrics port, a shared PDP wrapper, and updated authorization call signatures and tests.
Service, infra, and module wiring
gears/system/usage-collector/usage-collector/src/domain/service.rs, gears/system/usage-collector/usage-collector/src/domain/local_client.rs, gears/system/usage-collector/usage-collector/src/infra/*, gears/system/usage-collector/usage-collector/src/module.rs, gears/system/usage-collector/usage-collector/src/module_tests.rs
Adds SPI dispatch instrumentation, OpenTelemetry metric implementation, module bootstrap and refresh loop wiring, and lifecycle tests.
Metrics tests and test support
gears/system/usage-collector/usage-collector/src/domain/service_metrics_tests.rs, gears/system/usage-collector/usage-collector/src/domain/test_support.rs
Adds metrics-backed test helpers, plugin paging controls, and assertions for PDP, SPI, ingestion, query, deactivation, and usage-type metrics.

Documentation Updates

Layer / File(s) Summary
Helper naming and metric term updates
gears/system/usage-collector/docs/ADR/0001-pdp-centric-authorization.md, gears/system/usage-collector/docs/DECOMPOSITION.md, gears/system/usage-collector/docs/DESIGN.md, gears/system/usage-collector/docs/PRD.md, gears/system/usage-collector/docs/plugin-spi.md, gears/system/usage-collector/docs/sdk-trait.md
Renames the authorization helper references to access_scope_with and updates readiness/accept-error metric names.
Feature spec telemetry and operational visibility
gears/system/usage-collector/docs/features/*.md
Adds telemetry algorithms, operational-visibility sections, error-mapping refinements, and acceptance criteria updates for deactivation, foundation, and usage-type lifecycle specs.

Estimated code review effort: 4 (Complex) | ~75 minutes

Possibly related PRs

  • constructorfabric/gears-rust#4099: Introduced the usage-collector PDP authorization entry points that this PR extends with metrics-aware wrapping and updated helper signatures.
  • constructorfabric/gears-rust#4138: Also refactors usage-collector PDP handling toward stricter constraint-aware authorization and fail-closed list/record flows.

Suggested reviewers: Artifizer

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an OpenTelemetry metrics and observability layer to usage-collector.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@capybutler capybutler force-pushed the usage-collector/metrics branch 3 times, most recently from 1096d16 to 038aabd Compare July 6, 2026 10:54
@capybutler capybutler marked this pull request as ready for review July 6, 2026 11:48

@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.

🧹 Nitpick comments (1)
gears/system/usage-collector/usage-collector/src/domain/test_support.rs (1)

648-814: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate the six near-identical metric-readback traversal loops.

counter_sum_with_label, histogram_count, histogram_count_with_label, histogram_sum, histogram_sum_with_label, and gauge_last each repeat the same for rm in &metrics { for sm in rm.scope_metrics() { for metric in sm.metrics() { if metric.name() == name ... } } } traversal, differing only in the AggregatedMetrics/MetricData match arm and the data-point projection. Extracting a shared "find metric by name" helper would cut ~150 lines of duplication and centralize a change if the exporter API shifts.

♻️ Suggested consolidation
+fn find_metric<'a>(
+    metrics: &'a [opentelemetry_sdk::metrics::data::ResourceMetrics],
+    name: &str,
+) -> Option<&'a AggregatedMetrics> {
+    metrics
+        .iter()
+        .flat_map(|rm| rm.scope_metrics())
+        .flat_map(|sm| sm.metrics())
+        .find(|m| m.name() == name)
+        .map(|m| m.data())
+}
+
 pub fn counter_sum_with_label(
     exporter: &InMemoryMetricExporter,
     name: &str,
     label_key: &str,
     label_value: &str,
 ) -> u64 {
     let metrics = exporter.get_finished_metrics().expect("finished metrics");
-    for rm in &metrics {
-        for sm in rm.scope_metrics() {
-            for metric in sm.metrics() {
-                if metric.name() == name
-                    && let AggregatedMetrics::U64(MetricData::Sum(sum)) = metric.data()
-                {
-                    return sum
-                        .data_points()
-                        .filter(|dp| {
-                            dp.attributes().any(|kv| {
-                                kv.key.as_str() == label_key && kv.value.as_str() == label_value
-                            })
-                        })
-                        .map(opentelemetry_sdk::metrics::data::SumDataPoint::value)
-                        .sum();
-                }
-            }
-        }
-    }
-    0
+    let Some(AggregatedMetrics::U64(MetricData::Sum(sum))) = find_metric(&metrics, name) else {
+        return 0;
+    };
+    sum.data_points()
+        .filter(|dp| {
+            dp.attributes()
+                .any(|kv| kv.key.as_str() == label_key && kv.value.as_str() == label_value)
+        })
+        .map(opentelemetry_sdk::metrics::data::SumDataPoint::value)
+        .sum()
 }

The remaining five functions follow the same pattern.

🤖 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 `@gears/system/usage-collector/usage-collector/src/domain/test_support.rs`
around lines 648 - 814, The six readback helpers duplicate the same exporter
traversal and metric-name lookup logic, so consolidate that shared search into a
helper in test_support.rs and have counter_sum_with_label, histogram_count,
histogram_count_with_label, histogram_sum, histogram_sum_with_label, and
gauge_last reuse it. Keep the helper focused on finding the matching metric by
name across finished metrics/scope metrics, then let each public function handle
only its specific AggregatedMetrics/MetricData projection and aggregation.
🤖 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.

Nitpick comments:
In `@gears/system/usage-collector/usage-collector/src/domain/test_support.rs`:
- Around line 648-814: The six readback helpers duplicate the same exporter
traversal and metric-name lookup logic, so consolidate that shared search into a
helper in test_support.rs and have counter_sum_with_label, histogram_count,
histogram_count_with_label, histogram_sum, histogram_sum_with_label, and
gauge_last reuse it. Keep the helper focused on finding the matching metric by
name across finished metrics/scope metrics, then let each public function handle
only its specific AggregatedMetrics/MetricData projection and aggregation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e74d0ba2-63d8-4194-affe-13118586a444

📥 Commits

Reviewing files that changed from the base of the PR and between e6d859f and 038aabd.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • gears/system/usage-collector/docs/ADR/0001-pdp-centric-authorization.md
  • gears/system/usage-collector/docs/DECOMPOSITION.md
  • gears/system/usage-collector/docs/DESIGN.md
  • gears/system/usage-collector/docs/PRD.md
  • gears/system/usage-collector/docs/features/event-deactivation.md
  • gears/system/usage-collector/docs/features/foundation.md
  • gears/system/usage-collector/docs/features/usage-emission.md
  • gears/system/usage-collector/docs/features/usage-query.md
  • gears/system/usage-collector/docs/features/usage-type-lifecycle.md
  • gears/system/usage-collector/docs/plugin-spi.md
  • gears/system/usage-collector/docs/sdk-trait.md
  • gears/system/usage-collector/usage-collector/Cargo.toml
  • gears/system/usage-collector/usage-collector/src/config.rs
  • gears/system/usage-collector/usage-collector/src/config_tests.rs
  • gears/system/usage-collector/usage-collector/src/domain/authz.rs
  • gears/system/usage-collector/usage-collector/src/domain/authz_tests.rs
  • gears/system/usage-collector/usage-collector/src/domain/local_client.rs
  • gears/system/usage-collector/usage-collector/src/domain/mod.rs
  • gears/system/usage-collector/usage-collector/src/domain/ports/metrics.rs
  • gears/system/usage-collector/usage-collector/src/domain/ports/mod.rs
  • gears/system/usage-collector/usage-collector/src/domain/service.rs
  • gears/system/usage-collector/usage-collector/src/domain/service_metrics_tests.rs
  • gears/system/usage-collector/usage-collector/src/domain/test_support.rs
  • gears/system/usage-collector/usage-collector/src/infra/metrics.rs
  • gears/system/usage-collector/usage-collector/src/infra/metrics_tests.rs
  • gears/system/usage-collector/usage-collector/src/infra/mod.rs
  • gears/system/usage-collector/usage-collector/src/module.rs
  • gears/system/usage-collector/usage-collector/src/module_tests.rs

Comment thread gears/system/usage-collector/usage-collector/src/config.rs
Instruments the usage-collector gear with the operational metric inventory
from DESIGN §3.11.5, pushed via OTLP through ToolKit's global
SdkMeterProvider.

Domain
- Add the `UsageCollectorMetrics` port (domain/ports/metrics.rs) with a
  no-op default impl, plus typed, bounded label vocabularies (PdpOp,
  PluginOp, PdpFailureCause, AuthzDecision, PluginErrorCategory,
  RequestOutcome, RecordKind, QueryKind, …) so the `uc_*` instruments only
  ever carry closed-set label values.
- Instrument the service across every flow: PDP decisions/failures and
  readiness, plugin call duration / accept errors and readiness, ingestion
  (batch size, duration, per-record and per-request outcomes, metadata
  bytes), query (inflight gauge, result rows, request outcomes),
  deactivation requests, and usage-type requests.

Infra
- Implement the port over `opentelemetry::global::meter_with_scope`,
  constructing the full `uc_*` instrument set at gear bootstrap.

uc_usage_types catalog gauge
- Refresh the `uc_usage_types` gauge from a 60s serve-lifecycle loop rather
  than per-mutation, paginating the whole catalog via `CursorV1` and summing
  item counts across pages. Bounded by the refresh timeout, a page-size
  limit, and a page-count safety cap; any SPI error, undecodable cursor, or
  cap breach leaves the gauge at its prior value (best-effort, never a
  partial-count publish). Supports multi-instance aggregation.

Supporting changes
- Add gear config for the metrics layer.
- Promote tokio-util to a runtime dependency for the serve lifecycle.
- Add domain, infra, and module tests (incl. a multi-page list_usage_types
  queue in HappyPathPlugin) and update DESIGN / feature docs.

Signed-off-by: capybutler <capybutler@gmail.com>
@capybutler capybutler force-pushed the usage-collector/metrics branch from 038aabd to 9b03ef1 Compare July 9, 2026 11:56
@Artifizer Artifizer merged commit 757b59c into constructorfabric:main Jul 10, 2026
28 checks passed
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.

2 participants