Passed
Push — master ( f9598b...4c4a55 )
by Vlad
01:28
created

kibana_collector._requests()   A

Complexity

Conditions 2

Size

Total Lines 15
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 15
rs 9.75
c 0
b 0
f 0
cc 2
nop 1
1
from datetime import datetime
2
import logging
3
from typing import Iterator
4
5
from prometheus_client.core import InfoMetricFamily, StateSetMetricFamily, GaugeMetricFamily, Metric
6
7
from requests import get
8
from requests.compat import urljoin
9
from requests.exceptions import ConnectionError, Timeout, HTTPError, RequestException
10
11
from helpers import TimestampGaugeMetricFamily, TimestampCounterMetricFamily
12
13
14
logger = logging.getLogger(__name__)
15
16
17
def datestring_to_timestamp(date_str: str) -> float:
18
    return datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%S.%f%z').timestamp()
19
20
21
def _status(status: dict) -> (StateSetMetricFamily, GaugeMetricFamily):
22
    status_dict = {state: state == status['overall']['state'] for state in ['red', 'yellow', 'green']}
23
    since = datestring_to_timestamp(status['overall']['since'])
24
    status = StateSetMetricFamily('kibana_status', 'Kibana Status', value=status_dict)
25
    since = GaugeMetricFamily('kibana_status_since', 'Last change of status, in seconds since epoch', value=since)
26
    return status, since
27
28
29
class Metrics(object):
30
    def __init__(self, metrics_dict: dict):
31
        self._timestamp = datestring_to_timestamp(metrics_dict['last_updated'])
32
        self._metrics_dict = metrics_dict
33
34
    def __iter__(self):
35
        yield from self._response_times()
36
        yield from self._requests()
37
        yield from self._process()
38
        yield from self._os()
39
40
    def _os(self) -> Iterator[Metric]:
41
        os_dict = self._metrics_dict['os']
42
43
        yield from (TimestampGaugeMetricFamily('kibana_os_load_%s' % key,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable key does not seem to be defined.
Loading history...
44
                                               'Kibana OS load %s' % key,
45
                                               value=value,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable value does not seem to be defined.
Loading history...
46
                                               timestamp=self._timestamp) for key, value in os_dict['load'].items())
47
48
        yield from (TimestampGaugeMetricFamily('kibana_os_memory_%s_bytes' % key.split('_')[0],
49
                                               'Kibana %s OS memory' % key.split('_')[0],
50
                                               value=value,
51
                                               timestamp=self._timestamp) for key, value in os_dict['memory'].items())
52
53
        yield TimestampCounterMetricFamily('kibana_os_uptime_seconds',
54
                                           'Kibana OS uptime in seconds',
55
                                           value=os_dict['uptime_in_millis'] / 1000,
56
                                           timestamp=self._timestamp)
57
58
    def _response_times(self) -> Iterator[Metric]:
59
        rt_dict = self._metrics_dict['response_times']
60
61
        yield TimestampGaugeMetricFamily('kibana_response_time_max_seconds',
62
                                         'Kibana maximum response time in seconds',
63
                                         value=rt_dict['max_in_millis'] / 1000,
64
                                         timestamp=self._timestamp)
65
66
        # Kibana statistics lib can sometimes return NaN for this value.
67
        # If that is the case, this is set to 0 in order to avoid gaps in the time series.
68
        # Reference: https://github.com/elastic/kibana/blob/6.7/src/server/status/lib/metrics.js#L73
69
        # NaN is converted to `undefined` which then has the whole field removed from the response JSON
70
        yield TimestampGaugeMetricFamily('kibana_response_time_avg_seconds',
71
                                         'Kibana average response time in seconds',
72
                                         value=rt_dict.setdefault('avg_in_millis', 0) / 1000,
73
                                         timestamp=self._timestamp)
74
75
    def _requests(self) -> Iterator[Metric]:
76
        req_dict = self._metrics_dict['requests']
77
        yield TimestampGaugeMetricFamily('kibana_requests_total',
78
                                         'Total requests serviced',
79
                                         value=req_dict['total'],
80
                                         timestamp=self._timestamp)
81
82
        yield TimestampGaugeMetricFamily('kibana_requests_disconnects',
83
                                         'Total requests disconnected',
84
                                         value=req_dict['disconnects'],
85
                                         timestamp=self._timestamp)
86
87
        per_status = TimestampGaugeMetricFamily('kibana_requests',
88
                                                'Total requests by status code',
89
                                                labels=['status_code'],
90
                                                timestamp=self._timestamp)
91
92
        for code, count in req_dict['status_codes'].items():
93
            per_status.add_metric(labels=[code], value=count)
94
95
        yield per_status
96
97
    def _process(self) -> Iterator[Metric]:
98
        process_dict = self._metrics_dict['process']
99
100
        yield TimestampGaugeMetricFamily('kibana_process_memory_heap_total_bytes',
101
                                         'Total heap size in bytes',
102
                                         value=process_dict['memory']['heap']['total_in_bytes'],
103
                                         timestamp=self._timestamp)
104
        yield TimestampGaugeMetricFamily('kibana_process_memory_heap_used_bytes',
105
                                         'Used heap size in bytes',
106
                                         value=process_dict['memory']['heap']['used_in_bytes'],
107
                                         timestamp=self._timestamp)
108
109
        yield TimestampGaugeMetricFamily('kibana_process_memory_heap_size_limit_bytes',
110
                                         'Heap size limit in bytes',
111
                                         value=process_dict['memory']['heap']['size_limit'],
112
                                         timestamp=self._timestamp)
113
114
        yield TimestampGaugeMetricFamily('kibana_process_memory_resident_set_size_bytes',
115
                                         'Memory resident set size',
116
                                         value=process_dict['memory']['resident_set_size_in_bytes'],
117
                                         timestamp=self._timestamp)
118
119
        yield TimestampCounterMetricFamily('kibana_process_uptime_seconds',
120
                                           'Kibana process uptime in seconds',
121
                                           value=process_dict['uptime_in_millis'] / 1000,
122
                                           timestamp=self._timestamp)
123
124
125
class KibanaCollector(object):
126
    def __init__(self, host: str, path: str = '/api/status', kibana_login: str = None, kibana_password: str = None):
127
        self._url = urljoin(host, path)
128
        self._kibana_login = kibana_login
129
        self._kibana_password = kibana_password
130
131
    def _fetch_stats(self) -> dict:
132
        if self._kibana_login:
133
            auth = (self._kibana_login, self._kibana_password)
134
        else:
135
            auth = None
136
        r = get(self._url, auth=auth)
137
        r.raise_for_status()
138
        return r.json()
139
140
    def collect(self):
141
        kibana_up = GaugeMetricFamily('kibana_node_reachable', 'Kibana node was reached', value=0)
142
        try:
143
            stats = self._fetch_stats()
144
        except ConnectionError as e:
145
            logger.warning('Got a connection error while trying to contact Kibana:\n%s' % e)
146
        except Timeout as e:
147
            logger.warning('Got a timeout while trying to contact Kibana:\n%s' % e)
148
        except HTTPError as e:
149
            logger.warning('Got a HTTP error %s while trying to contact Kibana:\n%s' % (e.response.status_code, e))
150
        except RequestException as e:
151
            logger.warning('Got a RequestException while trying to contact Kibana:\n%s' % e)
152
        else:
153
            kibana_up = GaugeMetricFamily('kibana_node_reachable', 'Kibana node was reached', value=1)
154
            yield InfoMetricFamily('kibana_version', 'Kibana Version', value=stats['version'])
155
            yield from _status(stats['status'])
156
            yield from Metrics(stats['metrics'])
157
        finally:
158
            yield kibana_up
159