forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
prometheus.py
240 lines (197 loc) · 7.63 KB
/
prometheus.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
"""
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")