Repository navigation
Add a component label so multi-component devices don't break /metrics - #8
Open
RuiSantos76 wants to merge 1 commit into
Open
RuiSantos76 wants to merge 1 commit into
RuiSantos76 wants to merge 1 commit into
Conversation
registerComponentMetrics() labels every attribute with deviceId and componentId, but the map it iterates over is keyed by capability, not by component, so componentId holds the capability name and the real component id is never exported. A device with several components sharing a capability - a four button remote exposes button1..button4, each with the "button" capability - therefore produces identical series for every component. client_golang rejects the duplicates and the whole /metrics request fails with HTTP 500, so a single such device stops metrics for every device on the account. Pass the component id down and export it as a new "component" label. componentId keeps its current value so existing queries and dashboards continue to work. Note that the extra label changes the label set of every smartthings_attribute_* metric, so Prometheus starts a new series for each of them on upgrade. History is split across the old and new series: graphs spanning the upgrade show both, and range functions such as increase() over a window crossing it only see part of the data. Queries can use "without (component)" to aggregate the label away.
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.
Problem
Adding a four button remote (a Zigbee "zigbee-thing" with components
main,button1..button4) to a SmartThings account made the exporter return HTTP 500 on every scrape, so no metrics were exported for any device on the account:GetDeviceComponentStatus()returns a map keyed by capability, butregisterComponentMetrics()exports that key ascomponentId, and the actual component id is never exported. Every component that shares a capability with another component of the same device therefore produces identical series. client_golang rejects the duplicates and fails the whole request, so one such device is enough to stop metrics for every device.Change
registerComponentMetrics()and export it as a newcomponentlabel.componentIdkeeps its current value (the capability), so existing queries and dashboards that filter on it, e.g.componentId="tamperAlert", keep working. Single-component devices simply gaincomponent="main".collector_test.gobuilds a device with two components sharing thebuttoncapability and gathers through a registry. It fails onmainwith the error above and passes with the change.Example output after the change:
Important
Every
smartthings_attribute_*metric becomes a new series in Prometheus. A series is identified by its full label set, so addingcomponentgives every existing attribute a new identity, even on devices with only amaincomponent. After upgrading:component) is marked stale at the first scrape after the upgrade and the new one starts there. History is not lost, but it is split across two series.component.rate(),increase(),delta()and*_over_time()over a window that crosses the upgrade are computed separately for each series, so each result covers only part of the window. For example,increase(smartthings_attribute_energy[1d])under-reports for the first day.on(...)/ignoring(...)now also has to matchcomponent. Rules built on range vectors (absent_over_time(),changes(),count_over_time()) see two partial series until the window has moved past the upgrade.Queries that should ignore the change can aggregate the label away, e.g.
max without (component) (smartthings_attribute_temperature). Series count stays the same for single-component devices (one old series is replaced by one new series). The only new series come from multi-component devices, which could not be scraped at all before.If you would rather avoid the churn, an alternative is to add
componentonly when it isn'tmain. Existing single-component series would then keep their identity, at the cost of an inconsistent label set within a metric. I'm happy to switch to that if you prefer.Tested with
go test ./...andgo vet ./.... The change has also been running against a real account with 31 devices since 2026-09-24: Samsung Room A/C units, Aeotec Smart Switch 7 and MultiSensor 7, Zigbee temperature/humidity sensors, the four button remote, SmartTags, a TV, a dishwasher and a Sonos speaker. Scrapes return HTTP 200 with 865 series and no errors, and existing Grafana dashboards work unchanged.Diagnosis and patch were developed with the help of an AI assistant (Claude Code). The results above are from the real deployment.