Skip to content

Commit 3b980f7

Browse files
committed
Add per-backend query count sensors
1 parent 86fb262 commit 3b980f7

1 file changed

Lines changed: 97 additions & 0 deletions

File tree

custom_components/dnsdist/sensor.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from homeassistant.helpers import entity_registry as er
4646

4747
from .const import (
48+
ATTR_BACKENDS,
4849
ATTR_CACHE_HITS,
4950
ATTR_CACHE_HITRATE,
5051
ATTR_CACHE_MISSES,
@@ -223,6 +224,49 @@ def _async_sync_dynamic_rules() -> None:
223224
_async_sync_dynamic_rules()
224225
entry.async_on_unload(coordinator.async_add_listener(_async_sync_dynamic_rules))
225226

227+
# Backend query sensors (host entries only)
228+
if not is_group:
229+
backend_sensor_entities: dict[str, DnsdistBackendSensor] = {}
230+
231+
@callback
232+
def _async_sync_backend_sensors() -> None:
233+
if not coordinator.data:
234+
return
235+
backends = coordinator.data.get(ATTR_BACKENDS)
236+
if not isinstance(backends, dict):
237+
backends = {}
238+
239+
current_slugs = set(backends.keys())
240+
known_slugs = set(backend_sensor_entities.keys())
241+
242+
removed_slugs = known_slugs - current_slugs
243+
ent_reg = er.async_get(hass)
244+
for slug in removed_slugs:
245+
entity = backend_sensor_entities.pop(slug, None)
246+
if entity:
247+
if entity.entity_id and ent_reg.async_get(entity.entity_id):
248+
ent_reg.async_remove(entity.entity_id)
249+
else:
250+
hass.async_create_task(entity.async_remove())
251+
252+
new_entities: list[DnsdistBackendSensor] = []
253+
for slug in current_slugs:
254+
if slug in backend_sensor_entities:
255+
continue
256+
entity = DnsdistBackendSensor(
257+
coordinator=coordinator,
258+
entry_id=entry.entry_id,
259+
backend_slug=slug,
260+
)
261+
backend_sensor_entities[slug] = entity
262+
new_entities.append(entity)
263+
264+
if new_entities:
265+
async_add_entities(new_entities)
266+
267+
_async_sync_backend_sensors()
268+
entry.async_on_unload(coordinator.async_add_listener(_async_sync_backend_sensors))
269+
226270

227271
class DnsdistSensor(CoordinatorEntity, SensorEntity):
228272
"""Representation of a dnsdist metric sensor (host or group)."""
@@ -477,3 +521,56 @@ def extra_state_attributes(self) -> dict[str, Any]:
477521
@property
478522
def device_info(self) -> DeviceInfo:
479523
return build_device_info(self.coordinator, self._is_group)
524+
525+
526+
class DnsdistBackendSensor(CoordinatorEntity, SensorEntity):
527+
"""Sensor tracking query count for a dnsdist backend server."""
528+
529+
_attr_has_entity_name = False
530+
_attr_should_poll = False
531+
_attr_state_class = SensorStateClass.TOTAL_INCREASING
532+
_attr_icon = "mdi:dns"
533+
534+
def __init__(self, *, coordinator, entry_id: str, backend_slug: str) -> None:
535+
super().__init__(coordinator)
536+
self._slug = backend_slug
537+
self._attr_unique_id = f"{entry_id}:backend_queries:{backend_slug}"
538+
self._attr_native_unit_of_measurement = COUNT
539+
540+
def _backend_data(self) -> dict[str, Any]:
541+
data = self.coordinator.data or {}
542+
backends = data.get(ATTR_BACKENDS, {}) if isinstance(data, dict) else {}
543+
if isinstance(backends, dict):
544+
return backends.get(self._slug, {})
545+
return {}
546+
547+
@property
548+
def name(self) -> str:
549+
host = getattr(self.coordinator, "_name", "dnsdist")
550+
backend = self._backend_data()
551+
address = backend.get("address") or self._slug
552+
name = backend.get("name")
553+
if name:
554+
return f"{host} Backend {name} Queries"
555+
return f"{host} Backend {address} Queries"
556+
557+
@property
558+
def native_value(self):
559+
backend = self._backend_data()
560+
queries = backend.get("queries")
561+
if isinstance(queries, (int, float)):
562+
return int(queries)
563+
return None
564+
565+
@property
566+
def extra_state_attributes(self) -> dict[str, Any]:
567+
backend = self._backend_data()
568+
attrs: dict[str, Any] = {}
569+
for key in ("address", "name", "responses", "drops", "latency", "qps", "outstanding"):
570+
if key in backend and backend[key] is not None:
571+
attrs[key] = backend[key]
572+
return attrs
573+
574+
@property
575+
def device_info(self) -> DeviceInfo:
576+
return build_device_info(self.coordinator, False)

0 commit comments

Comments
 (0)