Test Failed
Push — develop ( 393145...ce27d7 )
by Nicolas
57s queued 11s
created

Export.__init__()   A

Complexity

Conditions 4

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nop 3
dl 0
loc 24
rs 9.8
c 0
b 0
f 0
1
#
2
# This file is part of Glances.
3
#
4
# SPDX-FileCopyrightText: 2022 Nicolas Hennion <[email protected]>
5
#
6
# SPDX-License-Identifier: LGPL-3.0-only
7
#
8
9
"""Prometheus interface class."""
10
11
import sys
12
from numbers import Number
13
14
from prometheus_client import Gauge, start_http_server
15
16
from glances.api import GlancesStats
17
from glances.exports.export import GlancesExport
18
from glances.globals import listkeys
19
from glances.logger import logger
20
21
class Export(GlancesExport):
22
    """This class manages the Prometheus export module."""
23
24
    METRIC_SEPARATOR = '_'
25
26
    def __init__(self, config=None, args=None):
27
        """Init the Prometheus export IF."""
28
        super().__init__(config=config, args=args)
29
30
        # Load the Prometheus configuration file section
31
        self.export_enable = self.load_conf('prometheus', mandatories=['host', 'port', 'labels'], options=['prefix'])
32
        if not self.export_enable:
33
            exit('Missing PROMETHEUS config')
34
35
        # Optionals configuration keys
36
        if self.prefix is None:
37
            self.prefix = 'glances'
38
39
        if self.labels is None:
40
            self.labels = 'src:glances'
41
42
        # Init the metric dict
43
        # Perhaps a better method is possible...
44
        self._metric_dict = {}
45
46
        self._stats = GlancesStats()
47
48
        # Init the Prometheus Exporter
49
        self.init()
50
51
    def init(self):
52
        """Init the Prometheus Exporter"""
53
        try:
54
            start_http_server(port=int(self.port), addr=self.host)
55
        except Exception as e:
56
            logger.critical(f"Can not start Prometheus exporter on {self.host}:{self.port} ({e})")
57
            sys.exit(2)
58
        else:
59
            logger.info(f"Start Prometheus exporter on {self.host}:{self.port}")
60
61
    def export(self, name, columns, points):
62
        """Write the points to the Prometheus exporter using Gauge."""
63
        logger.debug(f"Export {name} stats to Prometheus exporter")
64
65
        # Remove non number stats and convert all to float (for Boolean)
66
        data = {str(k): float(v) for k, v in zip(columns, points) if isinstance(v, Number)}
67
68
        key_name = self._stats.get_plugin(name).get_key()
69
70
        # Write metrics to the Prometheus exporter
71
        for metric, value in data.items():
72
            labels = self.labels
73
            metric_name = self.prefix + self.METRIC_SEPARATOR + name + self.METRIC_SEPARATOR
74
            try:
75
                obj, stat = metric.split('.')
76
                metric_name += stat
77
                labels += f",{key_name}:{obj}"
78
            except ValueError:
79
                metric_name += metric
80
81
            # Prometheus is very sensible to the metric name
82
            # See: https://prometheus.io/docs/practices/naming/
83
            for c in ' .-/:[]':
84
                metric_name = metric_name.replace(c, self.METRIC_SEPARATOR)
85
86
            # Get the labels
87
            labels = self.parse_tags(labels)
88
            # Manage an internal dict between metric name and Gauge
89
            if metric_name not in self._metric_dict:
90
                self._metric_dict[metric_name] = Gauge(metric_name, "", labelnames=listkeys(labels))
91
            # Write the value
92
            if hasattr(self._metric_dict[metric_name], 'labels'):
93
                # Add the labels (see issue #1255)
94
                self._metric_dict[metric_name].labels(**labels).set(value)
95
            else:
96
                self._metric_dict[metric_name].set(value)
97