feat(otel): export node metrics via the OpenTelemetry SDK - #5178
feat(otel): export node metrics via the OpenTelemetry SDK#5178skandragon wants to merge 17 commits into
Conversation
|
Rule review against |
998d316 to
5e4e06f
Compare
ReviewThanks for this. The craft here is high, and a few things in it are genuinely careful in ways that are easy to get wrong: the The problems below are mostly about packaging, not correctness. The biggest one has a small fix. Review ran five independent lenses (supply chain / security, cryptography, code-first correctness, test quality, and architecture), plus direct verification of the load-bearing claims against crate sources. Blocking1. The OTel stack is now unconditional, and it pulls a C build dependency into every build.
Eleven new lockfile packages, ten of them downstream of that chain, plus a second reqwest major (0.13 alongside the workspace's 0.12) and a second TLS root store. Suggested fix: put the pipeline behind a default-off cargo feature, or switch 2. The release build path was never exercised, and it is where this is most likely to break.
So 3. Dual rustls crypto provider. On To be fair about severity: this does not panic today. Every reachable caller passes a provider explicitly ( 4. Functional bugs worth fixing before merge5.
Effect: two nodes on one host with distinct 6. The bearer token clobbers an operator's own
This one bites the use case in #5046 directly, since the ask was to export to the operator's own collector. Fix: Related: 7. The bearer token has no audience binding. Commit Two thoughts. First, if the auth scheme stays, sign the target endpoint into the payload. Second, and worth considering more seriously: To be clear, the cryptography itself checks out. I read 8. 9. The startup log hides the destination in the one case it matters. When the endpoint comes from TestsSeveral of the new tests cannot fail for the reason they name:
Smaller
Checked and cleanRecording these so nobody re-runs them: no CI, workflow, build script, or Suggested pathItems 1 through 4 are what I would want resolved before merge. Feature-gating (1) default-off takes care of (3) as a side effect and shrinks (2) to a formality. Items 5, 6, and 8 are small and worth doing in the same pass. Item 7 is the one real design question, and splitting the auth scheme into its own change would let the metrics exporter land sooner. Happy to re-review once those are in. [AI-assisted - Claude] |
|
Thanks — most of this landed. Pushed as 1–3 (deps, release build, dual crypto provider): fixed, but not the suggested wayThe It adds webpki roots on top of the same stack. What actually removes it:
I preferred this to a default-off cargo feature: feature-gating would mean an operator can't enable metrics without building their own binary, which defeats the point of the issue. 4 (rule-review warning): fixed —
|
118d99d to
6f72232
Compare
New OtelArgs/OtelConfig sit beside TelemetryArgs/TelemetryConfig rather than inside them: the SDK metrics pipeline and the dashboard reporter are independent features that are not expected to share a backend, so neither enable-flag nor endpoint may fall back to the other.
Both decisions are pure functions so the production direction is testable from inside a test process. Endpoint resolution deliberately returns None when a standard OTEL_* var is set: opentelemetry-otlp gives a programmatic endpoint priority over the env vars, which is the opposite of the precedence operators expect.
Installs a global meter provider backed by an OTLP/HTTP exporter, plus one RSS gauge so the pipeline carries a real datapoint end to end. Future instrumentation is a global::meter call at the site, with no registry to keep in sync. The OTel crates become non-optional because the pipeline ships in every build, not just trace-ot ones.
The isolation between telemetry-enabled and otel-telemetry-enabled is a design constraint, not an accident, so it belongs where the next person reads before touching either.
Config::otel used serde(default) instead of serde(flatten), so the documented flat config.toml keys (otel-telemetry-enabled, otel-endpoint) were silently ignored. opentelemetry-otlp also had no TLS backend, so an https:// collector would never export, and its default features pulled in the trace/logs exporters despite metrics being the only goal.
The trim drops the logs exporter only. http-proto mandates trace, prost and opentelemetry-proto, so the comment and the design doc were both wrong about what stays out of the default build. Also record the aws-lc-rs/CMake cost that reqwest-rustls adds to the release cross-compile.
Adds the instruments behind the dashboard's connection-status tiles plus
transport wire counters and RTT/cwnd distributions. Observable callbacks
read state that already existed for the local dashboard, so nothing new
lands on the hot path except two packet atomics.
Identity moves from a PeerId to the transport public key fingerprint:
PeerId renders as {pub_key}@{addr}, so the previous peer.id resource
attribute exported this node's socket address and re-identified the node
on every address change.
No instrument carries an attribute identifying the remote end of a
connection.
Claude-Session: https://claude.ai/code/session_015Entifnvj528KjPErWsRyJ
New otel-auth-mode config (default "freenet", or "disabled"). Each export request carries Authorization: Bearer freenet/<pubkey>/<ts>/<nonce>/<sig>, signed with the x25519 transport key itself (XEdDSA) so the collector verifies node identity against the same pubkey peers and UIs see. Resource attrs freenet.node.pubkey / freenet.node.fingerprint replace service.instance.id; the fingerprint is derivable from the pubkey, so the collector can validate both. Claude-Session: https://claude.ai/code/session_01QNvMzjXWyYsMeiJF5DrB2s
The collector no longer accepts a nonce field; freshness is the timestamp alone.
reqwest's blocking client owns a private tokio runtime; creating or dropping it inside an async context panics. build_provider now hops to a plain thread, and the test shuts down via spawn_blocking.
- correct stale nonce segment in OtelAuthMode's token-format doc - surface build-thread panics as ExporterBuildError instead of propagating into node startup - add wire-level test for FreenetAuthClient::send_bytes - consolidate module imports per code-style layout
- always install our own HttpClient, so opentelemetry-otlp needs no reqwest/TLS feature: 11 new lockfile packages down to 2, no reqwest 0.13, no aws-lc-sys/cmake, no dual rustls provider - never overwrite an operator's Authorization header, honor the export timeout, keep the response body for OTLP error detail - otel-auth-mode defaults to disabled, and tokens bind the target collector's authority so they cannot be replayed elsewhere - --otel-telemetry-enabled=false now overrides config.toml - resource attributes no longer clobber OTEL_SERVICE_NAME - pin init's suppression check and the hot-path record_* mirrors - trim AGENTS.md, drop the stale plan, add operator docs
The audience was the URI authority, which carries userinfo: an endpoint of https://user:secret@collector/ would have signed the operator's password into a token sent over the wire and logged by the collector. Now base58(SHA-256(canonical URL)[..16]) — credentials stripped, and hashed because a URL contains the token's '/' separator. Binding the path too distinguishes two collectors behind one hostname.
Hash host:port/path, not scheme://host:port/path. The scheme names a transport, not a party, so binding it never narrows which collector may use a token — it only forces a collector reachable over both http and https to be configured twice. Userinfo was already stripped.
Findings from a full review pass on this branch. Export outcomes were invisible: opentelemetry-otlp logs network errors and non-2xx at DEBUG, justified by a comment claiming PeriodicReader re-logs them via otel_error!. That holds for the batch log/span processors and NOT for metrics, where the reader logs its export result with otel_debug! and the only otel_error! is thread creation. A dead collector produced no output at all while startup still said "started". send_bytes now warns once per failing streak and logs the recovery edge. OTEL_RESOURCE_ATTRIBUTES could shadow freenet.node.pubkey, exporting an identity that does not match the key the bearer token was signed with, silently breaking the collector's self-validation. The two identity attributes are now always emitted; descriptive attributes still defer. Also: reject endpoints reqwest cannot send (http::Uri accepts host:port as a schemeless authority, so the exporter built and every export then failed); propagate the HTTP client build error instead of falling back to a client with no timeout, which could stall the reader thread forever; warn when the exporter is enabled but suppressed, and on unparseable or millisecond-confused export timeouts; return None rather than defaults when the ring provider is unregistered, so a gauge is absent rather than reporting a real zero; publish the fair-queue counters with fetch_max, since they are now exported as observable counters where a decrease reads as a reset. Docs corrected against the pinned crate sources: init's return value, the two endpoint env vars not being interchangeable, timeout ownership, OTEL_EXPORTER_OTLP_COMPRESSION being unsupported rather than free, slowdowns_triggered not being dead code, the auth-mode default, and the instrument counts. Fixed a guard test that rebuilt the identity strings locally instead of calling identity_attributes, so it would have passed had production switched back to PeerId.
6f72232 to
f891539
Compare
The hosting cache already records why each contract is there (access type, local-client flag, abandonment, subscriber maps) but nothing outside hosting.rs could see it, so a node's hosted count was a single opaque number. Adds HostingReason as a partition over the hosted set and exports count plus state bytes per reason. Partition, not flags: the signals overlap, and an overlapping breakdown makes sum-by-reason lie. Its own provider rather than RingStatsSnapshot — that one runs on every dashboard request and this is an O(hosted) walk under the cache lock.
Problem
Node operators have no way to monitor a node over time — memory, ring health, transport throughput, queue depths — beyond point-in-time snapshots. The existing telemetry pipeline (
telemetry-enabled/telemetry-endpoint) is a hand-rolled OTLP-JSON log POST that feeds the project's central dashboard; it isn't a general-purpose metrics exporter and operators shouldn't have to piggyback on it.Approach
Adds a second, fully independent pipeline built on the OpenTelemetry SDK (
tracing/otel.rs), gated byotel-telemetry-enabled/otel-endpoint. The two pipelines share no config, no endpoint, and no fallback — enabling one has no effect on the other, andotel-endpointnever defaults to the dashboard collector.Key decisions:
OTEL_EXPORTER_OTLP_ENDPOINT,OTEL_SERVICE_NAME, etc.); without any, exports go tohttp://localhost:4318.Histogram/Counter) held in aOnceLockso they bind to the real provider, recorded viarecord_*helpers that cost one atomic load when the exporter is off.with_view— no per-instrument bucket boundaries.otel-auth-mode, defaultdisabled): infreenetmode, a per-requestAuthorization: Bearer freenet/<pubkey>/<audience>/<timestamp>/<signature>— an XEdDSA signature over the token prefix, signed with the node's x25519 transport secret. The collector verifies with stock Ed25519 after Montgomery→Edwards conversion; no shared secret, no second key.<audience>is the target collector'shost:port, so a token cannot be replayed at a different collector. Default isdisabled(no header) so that pointing the exporter at your own collector never ships a signed identity assertion unasked; anAuthorizationsupplied viaOTEL_EXPORTER_OTLP_HEADERSis never overwritten in either mode.HttpClient(workspace reqwest 0.12), so none ofopentelemetry-otlp'sreqwest-*/TLS features are enabled. Net new packages inCargo.lock:xeddsa+convert_case.freenet.node.pubkey(verifiable against the bearer signature) andfreenet.node.fingerprint(dashboard cross-reference). Never aPeerId, which would leak the socket address.Design doc:
docs/design/otel-metrics-exporter.md. Operator docs:docs/otel-metrics.md.Testing
cargo test --release -p freenet --lib otel(18 passed),config::(139 passed),transport::(694 passed) all green locally.Note:
freenet.process.memory.rssis Linux-only by design; on macOS/Windows the gauge registers but emits no datapoints.Fixes
Refs #5046 — that issue also asks for log export, which is an explicit non-goal here, so it stays open.
[AI-assisted - Claude]