Skip to content

Commit 9801554

Browse files
authored
Merge pull request #12 from 4n4nd/v0.0.2
Add `Metric` and `MetricsList` classes to make metric manipulation easier
2 parents 9423e7e + 3ada987 commit 9801554

File tree

4 files changed

+179
-1
lines changed

4 files changed

+179
-1
lines changed

prometheus_api_client/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
""" A Class for collection of metrics from a Prometheus Host """
1+
""" A collection of tools to collect and manipulate prometheus metrics"""
22

33
__title__ = "prometheus-connect"
44
__version__ = "0.0.1"
55

66
from .prometheus_connect import *
7+
from .metric import Metric
8+
from .metrics_list import MetricsList

prometheus_api_client/metric.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
"""
2+
A Class for metric object
3+
"""
4+
from copy import deepcopy
5+
import dateparser
6+
import pandas
7+
8+
try:
9+
import matplotlib.pyplot as plt
10+
from pandas.plotting import register_matplotlib_converters
11+
12+
register_matplotlib_converters()
13+
_MPL_FOUND = True
14+
except ImportError as exce:
15+
_MPL_FOUND = False
16+
print("WARNING: Plotting will not work as matplotlib was not found")
17+
18+
19+
class Metric:
20+
"""
21+
A Class for `Metric` object
22+
23+
:param metric: (dict) A metric item from the list of metrics received from prometheus
24+
:param oldest_data_datetime: (str) So any metric values in the dataframe that are older than
25+
this value will be deleted when new data is added to the dataframe using
26+
the __add__("+") operator.
27+
Example: oldest_data_datetime="10d", will delete the metric data that is older
28+
than 10 days. The dataframe is pruned only when new data is added to it.
29+
30+
oldest_data_datetime="23 May 2019 12:00:00"
31+
32+
oldest_data_datetime="1561475156" can be set using the unix timestamp
33+
34+
Example Usage:
35+
``prom = PrometheusConnect()``
36+
37+
``my_label_config = {'cluster': 'my_cluster_id', 'label_2': 'label_2_value'}``
38+
39+
``metric_data = prom.get_metric_range_data(metric_name='up', label_config=my_label_config)``
40+
``Here metric_data is a list of metrics received from prometheus``
41+
42+
``my_metric_object = Metric(metric_data[0], "10d") # only for the first item in the list``
43+
44+
"""
45+
46+
def __init__(self, metric, oldest_data_datetime=None):
47+
"""
48+
Constructor for the Metric object
49+
50+
"""
51+
self.metric_name = metric["metric"]["__name__"]
52+
self.label_config = deepcopy(metric["metric"])
53+
self.oldest_data_datetime = oldest_data_datetime
54+
del self.label_config["__name__"]
55+
56+
# if it is a single value metric change key name
57+
if "value" in metric:
58+
metric["values"] = [metric["value"]]
59+
60+
self.metric_values = pandas.DataFrame(metric["values"], columns=["ds", "y"]).apply(
61+
pandas.to_numeric, args=({"errors": "coerce"})
62+
)
63+
self.metric_values["ds"] = pandas.to_datetime(self.metric_values["ds"], unit="s")
64+
65+
def __eq__(self, other):
66+
"""
67+
overloading operator `=`
68+
69+
Check whether two metrics are the same (are the same time-series regardless of their data)
70+
71+
:return: (bool) If two Metric objects belong to the same time-series,
72+
i.e. same name and label config, it will return True, else False
73+
74+
Example Usage:
75+
``metric_1 = Metric(metric_data_1)``
76+
77+
``metric_2 = Metric(metric_data_2)``
78+
79+
``print(metric_1 == metric_2) # will print True if they belong to the same time-series``
80+
81+
"""
82+
return bool(
83+
(self.metric_name == other.metric_name) and (self.label_config == other.label_config)
84+
)
85+
86+
def __str__(self):
87+
"""
88+
This will make it print in a cleaner way when print function is used on a Metric object
89+
90+
Example Usage:
91+
``metric_1 = Metric(metric_data_1)``
92+
93+
``print(metric_1) # will print the name, labels and the head of the dataframe``
94+
95+
"""
96+
name = "metric_name: " + repr(self.metric_name) + "\n"
97+
labels = "label_config: " + repr(self.label_config) + "\n"
98+
values = "metric_values: " + repr(self.metric_values)
99+
100+
return "{" + "\n" + name + labels + values + "\n" + "}"
101+
102+
def __add__(self, other):
103+
"""
104+
overloading operator `+`
105+
Add two metric objects for the same time-series
106+
107+
Example Usage:
108+
``metric_1 = Metric(metric_data_1)``
109+
110+
``metric_2 = Metric(metric_data_2)``
111+
112+
``metric_12 = metric_1 + metric_2`` # will add the data in metric_2 to metric_1
113+
# so if any other parameters are set in metric_1
114+
# will also be set in metric_12
115+
# (like `oldest_data_datetime`)
116+
"""
117+
if self == other:
118+
new_metric = deepcopy(self)
119+
new_metric.metric_values = new_metric.metric_values.append(
120+
other.metric_values, ignore_index=True
121+
)
122+
new_metric.metric_values = new_metric.metric_values.dropna()
123+
new_metric.metric_values = (
124+
new_metric.metric_values.drop_duplicates("ds")
125+
.sort_values(by=["ds"])
126+
.reset_index(drop=True)
127+
)
128+
# if oldest_data_datetime is set, trim the dataframe and only keep the newer data
129+
if new_metric.oldest_data_datetime:
130+
# create a time range mask
131+
mask = new_metric.metric_values["ds"] >= dateparser.parse(
132+
str(new_metric.oldest_data_datetime)
133+
)
134+
# truncate the df within the mask
135+
new_metric.metric_values = new_metric.metric_values.loc[mask]
136+
137+
return new_metric
138+
139+
if self.metric_name != other.metric_name:
140+
error_string = "Different metric names"
141+
else:
142+
error_string = "Different metric labels"
143+
raise TypeError("Cannot Add different metric types. " + error_string)
144+
145+
def plot(self):
146+
147+
if _MPL_FOUND:
148+
fig, axis = plt.subplots()
149+
axis.plot_date(self.metric_values.ds, self.metric_values.y, linestyle=":")
150+
fig.autofmt_xdate()
151+
# if matplotlib was not imported
152+
else:
153+
raise ImportError("matplotlib was not found")

prometheus_api_client/metrics_list.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""docstring for MetricsList."""
2+
3+
from .metric import Metric
4+
5+
6+
class MetricsList(list):
7+
"""docstring for MetricsList."""
8+
9+
def __init__(self, metric_data_list):
10+
# if the input is not a list
11+
if not isinstance(metric_data_list, list):
12+
# make it into a list
13+
metric_data_list = [metric_data_list]
14+
15+
metric_object_list = []
16+
for i in metric_data_list:
17+
metric_object = Metric(i)
18+
if metric_object in metric_object_list:
19+
metric_object_list[metric_object_list.index(metric_object)] += metric_object
20+
else:
21+
metric_object_list.append(metric_object)
22+
super(MetricsList, self).__init__(metric_object_list)

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
retrying
22
requests
33
dateparser
4+
pandas

0 commit comments

Comments
 (0)