241 lines
7.6 KiB
Python
241 lines
7.6 KiB
Python
"""
|
|
Support for Prometheus metrics export.
|
|
|
|
For more details about this component, please refer to the documentation at
|
|
https://home-assistant.io/components/prometheus/
|
|
"""
|
|
import asyncio
|
|
import logging
|
|
|
|
import voluptuous as vol
|
|
from aiohttp import web
|
|
|
|
from homeassistant.components.http import HomeAssistantView
|
|
from homeassistant.components import recorder
|
|
from homeassistant.const import (CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE,
|
|
CONF_INCLUDE, EVENT_STATE_CHANGED,
|
|
TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
|
from homeassistant import core as hacore
|
|
from homeassistant.helpers import state as state_helper
|
|
from homeassistant.util.temperature import fahrenheit_to_celsius
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
REQUIREMENTS = ['prometheus_client==0.0.19']
|
|
|
|
DOMAIN = 'prometheus'
|
|
DEPENDENCIES = ['http']
|
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
|
DOMAIN: recorder.FILTER_SCHEMA,
|
|
}, extra=vol.ALLOW_EXTRA)
|
|
|
|
API_ENDPOINT = '/api/prometheus'
|
|
|
|
|
|
def setup(hass, config):
|
|
"""Activate Prometheus component."""
|
|
import prometheus_client
|
|
|
|
hass.http.register_view(PrometheusView(prometheus_client))
|
|
|
|
conf = config.get(DOMAIN, {})
|
|
exclude = conf.get(CONF_EXCLUDE, {})
|
|
include = conf.get(CONF_INCLUDE, {})
|
|
metrics = Metrics(prometheus_client, exclude, include)
|
|
|
|
hass.bus.listen(EVENT_STATE_CHANGED, metrics.handle_event)
|
|
|
|
return True
|
|
|
|
|
|
class Metrics:
|
|
"""Model all of the metrics which should be exposed to Prometheus."""
|
|
|
|
def __init__(self, prometheus_client, exclude, include):
|
|
"""Initialize Prometheus Metrics."""
|
|
self.prometheus_client = prometheus_client
|
|
self.exclude = exclude.get(CONF_ENTITIES, []) + \
|
|
exclude.get(CONF_DOMAINS, [])
|
|
self.include_domains = include.get(CONF_DOMAINS, [])
|
|
self.include_entities = include.get(CONF_ENTITIES, [])
|
|
self._metrics = {}
|
|
|
|
def handle_event(self, event):
|
|
"""Listen for new messages on the bus, and add them to Prometheus."""
|
|
state = event.data.get('new_state')
|
|
if state is None:
|
|
return
|
|
|
|
entity_id = state.entity_id
|
|
_LOGGER.debug("Handling state update for %s", entity_id)
|
|
domain, _ = hacore.split_entity_id(entity_id)
|
|
|
|
if entity_id in self.exclude:
|
|
return
|
|
if domain in self.exclude and entity_id not in self.include_entities:
|
|
return
|
|
if self.include_domains and domain not in self.include_domains:
|
|
return
|
|
if not self.exclude and (self.include_entities and
|
|
entity_id not in self.include_entities):
|
|
return
|
|
|
|
handler = '_handle_' + domain
|
|
|
|
if hasattr(self, handler):
|
|
getattr(self, handler)(state)
|
|
|
|
def _metric(self, metric, factory, documentation, labels=None):
|
|
if labels is None:
|
|
labels = ['entity', 'friendly_name']
|
|
|
|
try:
|
|
return self._metrics[metric]
|
|
except KeyError:
|
|
self._metrics[metric] = factory(metric, documentation, labels)
|
|
return self._metrics[metric]
|
|
|
|
@staticmethod
|
|
def _labels(state):
|
|
return {
|
|
'entity': state.entity_id,
|
|
'friendly_name': state.attributes.get('friendly_name'),
|
|
}
|
|
|
|
def _battery(self, state):
|
|
if 'battery_level' in state.attributes:
|
|
metric = self._metric(
|
|
'battery_level_percent',
|
|
self.prometheus_client.Gauge,
|
|
'Battery level as a percentage of its capacity',
|
|
)
|
|
try:
|
|
value = float(state.attributes['battery_level'])
|
|
metric.labels(**self._labels(state)).set(value)
|
|
except ValueError:
|
|
pass
|
|
|
|
def _handle_binary_sensor(self, state):
|
|
metric = self._metric(
|
|
'binary_sensor_state',
|
|
self.prometheus_client.Gauge,
|
|
'State of the binary sensor (0/1)',
|
|
)
|
|
value = state_helper.state_as_number(state)
|
|
metric.labels(**self._labels(state)).set(value)
|
|
|
|
def _handle_device_tracker(self, state):
|
|
metric = self._metric(
|
|
'device_tracker_state',
|
|
self.prometheus_client.Gauge,
|
|
'State of the device tracker (0/1)',
|
|
)
|
|
value = state_helper.state_as_number(state)
|
|
metric.labels(**self._labels(state)).set(value)
|
|
|
|
def _handle_light(self, state):
|
|
metric = self._metric(
|
|
'light_state',
|
|
self.prometheus_client.Gauge,
|
|
'Load level of a light (0..1)',
|
|
)
|
|
|
|
try:
|
|
if 'brightness' in state.attributes:
|
|
value = state.attributes['brightness'] / 255.0
|
|
else:
|
|
value = state_helper.state_as_number(state)
|
|
value = value * 100
|
|
metric.labels(**self._labels(state)).set(value)
|
|
except ValueError:
|
|
pass
|
|
|
|
def _handle_lock(self, state):
|
|
metric = self._metric(
|
|
'lock_state',
|
|
self.prometheus_client.Gauge,
|
|
'State of the lock (0/1)',
|
|
)
|
|
value = state_helper.state_as_number(state)
|
|
metric.labels(**self._labels(state)).set(value)
|
|
|
|
def _handle_sensor(self, state):
|
|
_sensor_types = {
|
|
TEMP_CELSIUS: (
|
|
'temperature_c', self.prometheus_client.Gauge,
|
|
'Temperature in degrees Celsius',
|
|
),
|
|
TEMP_FAHRENHEIT: (
|
|
'temperature_c', self.prometheus_client.Gauge,
|
|
'Temperature in degrees Celsius',
|
|
),
|
|
'%': (
|
|
'relative_humidity', self.prometheus_client.Gauge,
|
|
'Relative humidity (0..100)',
|
|
),
|
|
'lux': (
|
|
'light_lux', self.prometheus_client.Gauge,
|
|
'Light level in lux',
|
|
),
|
|
'kWh': (
|
|
'electricity_used_kwh', self.prometheus_client.Gauge,
|
|
'Electricity used by this device in KWh',
|
|
),
|
|
'V': (
|
|
'voltage', self.prometheus_client.Gauge,
|
|
'Currently reported voltage in Volts',
|
|
),
|
|
'W': (
|
|
'electricity_usage_w', self.prometheus_client.Gauge,
|
|
'Currently reported electricity draw in Watts',
|
|
),
|
|
}
|
|
|
|
unit = state.attributes.get('unit_of_measurement')
|
|
metric = _sensor_types.get(unit)
|
|
|
|
if metric is not None:
|
|
metric = self._metric(*metric)
|
|
try:
|
|
value = state_helper.state_as_number(state)
|
|
if unit == TEMP_FAHRENHEIT:
|
|
value = fahrenheit_to_celsius(value)
|
|
metric.labels(**self._labels(state)).set(value)
|
|
except ValueError:
|
|
pass
|
|
|
|
self._battery(state)
|
|
|
|
def _handle_switch(self, state):
|
|
metric = self._metric(
|
|
'switch_state',
|
|
self.prometheus_client.Gauge,
|
|
'State of the switch (0/1)',
|
|
)
|
|
value = state_helper.state_as_number(state)
|
|
metric.labels(**self._labels(state)).set(value)
|
|
|
|
def _handle_zwave(self, state):
|
|
self._battery(state)
|
|
|
|
|
|
class PrometheusView(HomeAssistantView):
|
|
"""Handle Prometheus requests."""
|
|
|
|
url = API_ENDPOINT
|
|
name = 'api:prometheus'
|
|
|
|
def __init__(self, prometheus_client):
|
|
"""Initialize Prometheus view."""
|
|
self.prometheus_client = prometheus_client
|
|
|
|
@asyncio.coroutine
|
|
def get(self, request):
|
|
"""Handle request for Prometheus metrics."""
|
|
_LOGGER.debug('Received Prometheus metrics request')
|
|
|
|
return web.Response(
|
|
body=self.prometheus_client.generate_latest(),
|
|
content_type="text/plain")
|