forked from liquidctl/liquidctl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
prometheus-liquidctl-exporter
executable file
·138 lines (113 loc) · 4.84 KB
/
prometheus-liquidctl-exporter
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
#!/usr/bin/env python3
"""prometheus-liquidctl-exporter – host a metrics HTTP endpoint with Prometheus formatted data from liquidctl
This is an experimental script that collects stats from liquidctl and exposes them as a http://localhost:8098/metrics
endpoint in the Prometheus text format.
See: https://prometheus.io/docs/instrumenting/exposition_formats/#text-format-example
Example metric with labels:
# HELP liquidctl liquidctl exported metrics
# TYPE liquidctl gauge
liquidctl{device="NZXT Kraken X (X42, X52, X62 or X72)",sensor="liquid_temperature",unit="°C"} 33.6
Usage:
prometheus-liquidctl-exporter [options]
Options:
--legacy-690lc Use Asetek 690LC in legacy mode (old Krakens)
--server-port <number> Port for the HTTP /metrics endpoint
-v, --verbose Output additional information
-g, --debug Show debug information on stderr
--version Display the version number
--help Show this message
Copyright (C) 2020–2022 Alex Berryman, Jonas Malaco and contributors
SPDX-License-Identifier: GPL-3.0-or-later
"""
import logging
import sys
import time
import usb
from datetime import timedelta
from docopt import docopt
from liquidctl.driver import *
from prometheus_client import start_http_server
from prometheus_client.core import GaugeMetricFamily, REGISTRY, InfoMetricFamily
LOGGER = logging.getLogger(__name__)
def gauge_name_sanitize(name):
return name.replace(" ", "_").lower()
class LiquidCollector(object):
def __init__(self):
self.description = 'liquidctl exported metrics'
def collect(self):
labels = ['device', 'sensor', 'unit']
g = GaugeMetricFamily('liquidctl', self.description, labels=labels)
i = InfoMetricFamily('liquidctl', self.description, labels=['device'])
for d in devs:
try:
get_status = d.get_status()
for metric in get_status:
sanitized_name = gauge_name_sanitize(metric[0])
sample_value = metric[1]
unit = metric[2]
if isinstance(sample_value, timedelta):
# cast timedelta into seconds and override the supplied unit
sample_value = sample_value.seconds
unit = 'seconds'
if unit != '':
# FIXME doesn't handle multiple equal devices well
label_values = [d.description.replace(' (experimental)', ''), sanitized_name, unit]
g.add_metric(label_values, value=sample_value)
LOGGER.debug(
'%s: %s as GaugeMetric %s labels %s',
d.description, metric, sanitized_name, '/'.join(label_values))
else:
i.add_metric([d.description], value={sanitized_name: sample_value})
LOGGER.debug(
'%s: %s InfoMetric labeled with %s => %s',
d.description, metric, sanitized_name, sample_value)
except usb.core.USBError as err:
LOGGER.warning('failed to read from the device, possibly serving stale data')
LOGGER.debug(err, exc_info=True)
yield g
yield i
def _make_opts(arguments):
options = {}
for arg, val in arguments.items():
if val is not None and arg in _PARSE_ARG:
opt = arg.replace('--', '').replace('-', '_')
options[opt] = _PARSE_ARG[arg](val)
return options
_PARSE_ARG = {
'--legacy-690lc': bool
}
if __name__ == '__main__':
args = docopt(__doc__, version='0.1.1')
opts = _make_opts(args)
devs = list(find_liquidctl_devices(**opts))
for d in devs:
LOGGER.info('initializing %s', d.description)
d.connect()
if args['--debug']:
args['--verbose'] = True
logging.basicConfig(level=logging.DEBUG, format='[%(levelname)s] %(name)s: %(message)s')
elif args['--verbose']:
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
else:
logging.basicConfig(level=logging.WARNING, format='%(message)s')
sys.tracebacklimit = 0
REGISTRY.register(LiquidCollector())
if args['--server-port']:
server_port = int(args['--server-port'])
else:
server_port = 8098
start_http_server(server_port)
LOGGER.debug('server started on port %s', server_port)
try:
while True:
# Keep HTTP server alive in a loop
time.sleep(2)
except KeyboardInterrupt:
LOGGER.info('canceled by user')
finally:
for d in devs:
try:
LOGGER.info('disconnecting from %s', d.description)
d.disconnect()
except:
LOGGER.exception('unexpected error when disconnecting')