Repository navigation
feat(controller): serve Prometheus fleet metrics - #180
Open
devmittal02 wants to merge 1 commit into
Open
devmittal02 wants to merge 1 commit into
devmittal02 wants to merge 1 commit into
Conversation
Add an opt-in `metricsListen` address on which the controller serves `GET /metrics` in the Prometheus text format. The management UI listener must stay on loopback, so metrics get their own listener that a scraper can reach. Fleet state (devices, online devices, versions, configuration state and revision drift, reported MCP servers and skills) is read from the database at scrape time. Connected streams and stored telemetry events are counted in memory. Labels come from closed sets or from the OS and agent version of enrolled devices. Hostnames, users, MCP server names, and tool inputs are never exported, and a test checks that. Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: devmittal02 <94045165+devmittal02@users.noreply.github.com>
Contributor
Author
devmittal02
marked this pull request as ready for review
October 21, 2026 04:13
Collaborator
|
Ideally I'd like Krisztian Fekete to chime in here too -- I did ping him and he'll take a look once he's back. |
There was a problem hiding this comment.
🔵 Needs a closer look
Listener security and replica semantics still await maintainer review, and the connection gauge has an unresolved lifecycle defect.
1 open issue
What changed in this PR
Adds opt-in Prometheus fleet-health metrics to the controller, complementing the existing token and cost reporting.
Changes:
- Adds a separate, unauthenticated
/metricslistener configured throughmetricsListen. - Exposes database-backed fleet gauges and in-memory connection, telemetry, and error metrics.
- Documents endpoint security and replica behavior, with configuration and metrics tests.
| File | Description |
|---|---|
| schema/controller-config.md | Documents the optional metrics listener. |
| schema/controller-config.json | Adds the metrics listener to the configuration schema. |
| README.md | Documents metrics, access restrictions, and replica behavior. |
| crates/core/src/config.rs | Adds metricsListen and configuration tests. |
| crates/controller/src/service.rs | Instruments connections, telemetry storage, and message errors. |
| crates/controller/src/metrics.rs | Implements the exporter, counters, gauges, and tests. |
| crates/controller/src/main.rs | Wires shared metrics and starts the optional listener. |
| crates/controller/src/lib.rs | Exports the metrics module. |
| crates/controller/src/database.rs | Adds inventory totals and returns stored telemetry types. |
Review effort: Balanced
| let stream_guard = metrics.stream_connected(); | ||
| let auth_sender = sender.clone(); | ||
| tokio::spawn(async move { | ||
| let _stream_guard = stream_guard; |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Closes #183.
What
Adds an opt-in
metricsListencontroller setting. When set, the controller servesGET /metrics(Prometheus text format) on that address. Unset (the default) changes nothing.Why
Fleet health is only visible through the management UI today.
adminListenmust stay on loopback, so a scraper cannot reach it. Operators running a metrics stack (Prometheus, Grafana Alloy, and similar) have no way to alert on stale devices, failed configuration applies, or devices lagging behind the active configuration revision. The usage reporting from #160 covers tokens and cost, not fleet health.Metrics
agentdesktop_build_info{version}agentdesktop_devicesagentdesktop_devices_onlineagentdesktop_devices_connectedagentdesktop_device_oldest_seen_age_secondsagentdesktop_devices_by_version{os,agent_version}agentdesktop_devices_by_config_state{state}applied,failed,unreported)agentdesktop_devices_config_behindagentdesktop_config_active_revisionagentdesktop_inventory_mcp_serversagentdesktop_inventory_skillsagentdesktop_telemetry_events_total{type}agentdesktop_device_message_errors_totalFleet state is read from the database at scrape time, so it is correct after a restart. The connected gauge and the two counters are in memory and start from zero on restart, as Prometheus counters do. With several replicas those are per replica and should be summed.
Privacy
Label values come from closed sets, or from the OS and agent version of enrolled devices. Hostnames, users, device IDs, MCP server names, and tool inputs are never exported.
scrape_reports_fleet_state_without_identifying_labelsasserts that.Design notes
insert_telemetrynow returns the stored event type so the counter uses the same value that is persisted.Testing
cargo test -p agentdesktop-core -p agentdesktop-controllerpasses, including a scrape test against a SQLite database and a config test for the new field.cargo clippy -p agentdesktop-controller -p agentdesktop-core --all-targets -- -D warningsandcargo fmt --all --checkare clean.Draft while I get feedback on the metric names and on whether you would rather expose this through an OpenTelemetry exporter.
🤖 Generated with Claude Code