-
-
Notifications
You must be signed in to change notification settings - Fork 90
Expand file tree
/
Copy pathmetric.py
More file actions
207 lines (165 loc) · 8.5 KB
/
metric.py
File metadata and controls
207 lines (165 loc) · 8.5 KB
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
"""A Class for metric object."""
from copy import deepcopy
import datetime
try:
import pandas
except ImportError as e:
raise ImportError(
"Pandas is required for Metric class. "
"Please install it with: pip install prometheus-api-client[dataframe] "
"or pip install prometheus-api-client[all]"
) from e
from prometheus_api_client.exceptions import MetricValueConversionError
class Metric:
r"""
A Class for `Metric` object.
:param metric: (dict) A metric item from the list of metrics received from prometheus
:param oldest_data_datetime: (datetime|timedelta) Any metric values in the dataframe that are
older than this value will be deleted when new data is added to the dataframe
using the __add__("+") operator.
* `oldest_data_datetime=datetime.timedelta(days=2)`, will delete the
metric data that is 2 days older than the latest metric.
The dataframe is pruned only when new data is added to it.
* `oldest_data_datetime=datetime.datetime(2019,5,23,12,0)`, will delete
any data that is older than "23 May 2019 12:00:00"
* `oldest_data_datetime=datetime.datetime.fromtimestamp(1561475156)`
can also be set using the unix timestamp
Example Usage:
.. code-block:: python
prom = PrometheusConnect()
my_label_config = {'cluster': 'my_cluster_id', 'label_2': 'label_2_value'}
metric_data = prom.get_metric_range_data(metric_name='up', label_config=my_label_config)
# Here metric_data is a list of metrics received from prometheus
# only for the first item in the list
my_metric_object = Metric(metric_data[0], datetime.timedelta(days=10))
"""
def __init__(self, metric, oldest_data_datetime=None):
"""Functions as a Constructor for the Metric object."""
if not isinstance(
oldest_data_datetime, (datetime.datetime, datetime.timedelta, type(None))
):
# if it is neither a datetime object nor a timedelta object raise exception
raise TypeError(
"oldest_data_datetime can only be datetime.datetime/ datetime.timedelta or None"
)
if isinstance(metric, Metric):
# if metric is a Metric object, just copy the object and update its parameters
self.metric_name = metric.metric_name
self.label_config = metric.label_config
self.metric_values = metric.metric_values
self.oldest_data_datetime = oldest_data_datetime
else:
self.metric_name = metric["metric"].get("__name__", None)
self.label_config = deepcopy(metric["metric"])
if "__name__" in self.label_config:
del self.label_config["__name__"]
self.oldest_data_datetime = oldest_data_datetime
# if it is a single value metric change key name
if "value" in metric:
datestamp = metric["value"][0]
metric_value = metric["value"][1]
if isinstance(metric_value, str):
try:
metric_value = float(metric_value)
except (TypeError, ValueError):
raise MetricValueConversionError(
"Converting string metric value to float failed."
)
metric["values"] = [[datestamp, metric_value]]
self.metric_values = pandas.DataFrame(metric["values"], columns=["ds", "y"]).apply(
pandas.to_numeric, errors="raise"
)
self.metric_values["ds"] = pandas.to_datetime(self.metric_values["ds"], unit="s")
# Set the metric start time and the metric end time
self.start_time = self.metric_values.iloc[0, 0]
self.end_time = self.metric_values.iloc[-1, 0]
# We store the plot information as Class variable
Metric._plot = None
def __eq__(self, other):
"""
Overloading operator ``=``.
Check whether two metrics are the same (are the same time-series regardless of their data)
Example Usage:
.. code-block:: python
metric_1 = Metric(metric_data_1)
metric_2 = Metric(metric_data_2)
print(metric_1 == metric_2) # will print True if they belong to the same time-series
:return: (bool) If two Metric objects belong to the same time-series,
i.e. same name and label config, it will return True, else False
"""
return bool(
(self.metric_name == other.metric_name) and (self.label_config == other.label_config)
)
def __str__(self):
"""
Make it print in a cleaner way when print function is used on a Metric object.
Example Usage:
.. code-block:: python
metric_1 = Metric(metric_data_1)
print(metric_1) # will print the name, labels and the head of the dataframe
"""
name = "metric_name: " + repr(self.metric_name) + "\n"
labels = "label_config: " + repr(self.label_config) + "\n"
values = "metric_values: " + repr(self.metric_values)
return "{" + "\n" + name + labels + values + "\n" + "}"
def __add__(self, other):
r"""
Overloading operator ``+``.
Add two metric objects for the same time-series
Example Usage:
.. code-block:: python
metric_1 = Metric(metric_data_1)
metric_2 = Metric(metric_data_2)
metric_12 = metric_1 + metric_2 # will add the data in ``metric_2`` to ``metric_1``
# so if any other parameters are set in ``metric_1``
# will also be set in ``metric_12``
# (like ``oldest_data_datetime``)
:return: (`Metric`) Returns a `Metric` object with the combined metric data
of the two added metrics
:raises: (TypeError) Raises an exception when two metrics being added are
from different metric time-series
"""
if self == other:
new_metric = deepcopy(self)
new_metric.metric_values = pandas.concat([new_metric.metric_values,other.metric_values],ignore_index=True, axis=0)
new_metric.metric_values = new_metric.metric_values.dropna()
new_metric.metric_values = (
new_metric.metric_values.drop_duplicates("ds")
.sort_values(by=["ds"])
.reset_index(drop=True)
)
# if oldest_data_datetime is set, trim the dataframe and only keep the newer data
if new_metric.oldest_data_datetime:
if isinstance(new_metric.oldest_data_datetime, datetime.timedelta):
# create a time range mask
mask = new_metric.metric_values["ds"] >= (
new_metric.metric_values.iloc[-1, 0] - abs(new_metric.oldest_data_datetime)
)
else:
# create a time range mask
mask = new_metric.metric_values["ds"] >= new_metric.oldest_data_datetime
# truncate the df within the mask
new_metric.metric_values = new_metric.metric_values.loc[mask]
# Update the metric start time and the metric end time for the new Metric
new_metric.start_time = new_metric.metric_values.iloc[0, 0]
new_metric.end_time = new_metric.metric_values.iloc[-1, 0]
return new_metric
if self.metric_name != other.metric_name:
error_string = "Different metric names"
else:
error_string = "Different metric labels"
raise TypeError("Cannot Add different metric types. " + error_string)
_metric_plot = None
def plot(self, *args, **kwargs):
"""Plot a very simple line graph for the metric time-series."""
if not Metric._metric_plot:
from prometheus_api_client.metric_plot import MetricPlot
Metric._metric_plot = MetricPlot(*args, **kwargs)
metric = self
Metric._metric_plot.plot_date(metric)
def show(self, block=None):
"""Plot a very simple line graph for the metric time-series."""
if not Metric._metric_plot:
# can't show before plot
TypeError("Invalid operation: Can't show() before plot()")
Metric._metric_plot.show(block)