Skip to content

Commit 32ccb65

Browse files
author
Anand Sanmukhani
authored
Merge pull request #53 from durandom/master
merge v.0.0.2 into master
2 parents 5a41dd6 + 5145cc9 commit 32ccb65

File tree

26 files changed

+1923
-155
lines changed

26 files changed

+1923
-155
lines changed

.coafile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ bears = PycodestyleBear, PyDocStyleBear
1010
files = **.py
1111
language = Python
1212
editor = vim
13-
ignore = setup.py
13+
ignore = setup.py, docs/*, tests/*
1414

1515
[all.yaml]
1616
bears = YAMLLintBear

.stickler.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
linters:
3+
black:
4+
config: ./pyproject.toml
5+
fixer: true
6+
fixers:
7+
enable: true

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/4f6e655e936c4d54b838317bc4414017)](https://www.codacy.com/app/4n4nd/prometheus-connect?utm_source=github.com&utm_medium=referral&utm_content=4n4nd/prometheus-connect&utm_campaign=Badge_Grade)
12
# prometheus-api-client
23

34
A python wrapper for the prometheus http api
45

5-
Docs: https://prometheus-api-client-python.readthedocs.io/en/latest/source/prometheus_api_client.html#module-prometheus_api_client.prometheus_connect
6+
## Installation
7+
To install the latest release:
8+
9+
`pip install prometheus-api-client==0.0.2`
10+
11+
To install directly from this branch:
12+
13+
`pip install https://github.com/AICoE/prometheus-api-client-python/zipball/v0.0.2`
14+
15+
## Documentation
16+
17+
Docs: [https://prometheus-api-client-python.readthedocs.io/en/v0.0.2/source/prometheus_api_client.html]

app.py

Lines changed: 0 additions & 19 deletions
This file was deleted.

docs/source/prometheus_api_client.rst

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,36 @@ prometheus\_api\_client package
44
Submodules
55
----------
66

7+
prometheus\_api\_client.metric module
8+
-------------------------------------
9+
10+
.. automodule:: prometheus_api_client.metric
11+
:members: Metric
12+
:special-members: __add__, __eq__, __str__
13+
:undoc-members:
14+
:show-inheritance:
15+
16+
prometheus\_api\_client.metrics\_list module
17+
--------------------------------------------
18+
19+
.. automodule:: prometheus_api_client.metrics_list
20+
:members:
21+
:undoc-members:
22+
:show-inheritance:
23+
724
prometheus\_api\_client.prometheus\_connect module
825
--------------------------------------------------
926

1027
.. automodule:: prometheus_api_client.prometheus_connect
11-
:members:
12-
:undoc-members:
13-
:show-inheritance:
28+
:members:
29+
:undoc-members:
30+
:show-inheritance:
1431

1532

1633
Module contents
1734
---------------
1835

1936
.. automodule:: prometheus_api_client
20-
:members:
21-
:undoc-members:
22-
:show-inheritance:
37+
:members:
38+
:undoc-members:
39+
:show-inheritance:

examples/MetricsList_example.ipynb

Lines changed: 1178 additions & 0 deletions
Large diffs are not rendered by default.

prometheus_api_client/__init__.py

Lines changed: 4 additions & 2 deletions
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"
4-
__version__ = "0.0.1"
4+
__version__ = "0.0.2b4"
55

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

prometheus_api_client/metric.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
"""A Class for metric object."""
2+
from copy import deepcopy
3+
import datetime
4+
import pandas
5+
6+
try:
7+
import matplotlib.pyplot as plt
8+
from pandas.plotting import register_matplotlib_converters
9+
10+
register_matplotlib_converters()
11+
_MPL_FOUND = True
12+
except ImportError as exce:
13+
_MPL_FOUND = False
14+
15+
16+
class Metric:
17+
r"""
18+
A Class for `Metric` object.
19+
20+
:param metric: (dict) A metric item from the list of metrics received from prometheus
21+
:param oldest_data_datetime: (datetime|timedelta) Any metric values in the dataframe that are \
22+
older than this value will be deleted when new data is added to the dataframe \
23+
using the __add__("+") operator.
24+
25+
* `oldest_data_datetime=datetime.timedelta(days=2)`, will delete the \
26+
metric data that is 2 days older than the latest metric. \
27+
The dataframe is pruned only when new data is added to it. \n
28+
* `oldest_data_datetime=datetime.datetime(2019,5,23,12,0)`, will delete \
29+
any data that is older than "23 May 2019 12:00:00" \n
30+
* `oldest_data_datetime=datetime.datetime.fromtimestamp(1561475156)` \
31+
can also be set using the unix timestamp
32+
33+
Example Usage:
34+
``prom = PrometheusConnect()``
35+
36+
``my_label_config = {'cluster': 'my_cluster_id', 'label_2': 'label_2_value'}``
37+
38+
``metric_data = prom.get_metric_range_data(metric_name='up', label_config=my_label_config)``
39+
``Here metric_data is a list of metrics received from prometheus``
40+
41+
``# only for the first item in the list``
42+
``my_metric_object = Metric(metric_data[0], datetime.timedelta(days=10)) ``
43+
44+
"""
45+
46+
def __init__(self, metric, oldest_data_datetime=None):
47+
"""Constructor for the Metric object."""
48+
if not isinstance(
49+
oldest_data_datetime, (datetime.datetime, datetime.timedelta, type(None))
50+
):
51+
# if it is neither a datetime object nor a timedelta object raise exception
52+
raise TypeError(
53+
"oldest_data_datetime can only be datetime.datetime/ datetime.timedelta or None"
54+
)
55+
56+
if isinstance(metric, Metric):
57+
# if metric is a Metric object, just copy the object and update its parameters
58+
self.metric_name = metric.metric_name
59+
self.label_config = metric.label_config
60+
self.metric_values = metric.metric_values
61+
self.oldest_data_datetime = oldest_data_datetime
62+
else:
63+
self.metric_name = metric["metric"]["__name__"]
64+
self.label_config = deepcopy(metric["metric"])
65+
self.oldest_data_datetime = oldest_data_datetime
66+
del self.label_config["__name__"]
67+
68+
# if it is a single value metric change key name
69+
if "value" in metric:
70+
metric["values"] = [metric["value"]]
71+
72+
self.metric_values = pandas.DataFrame(metric["values"], columns=["ds", "y"]).apply(
73+
pandas.to_numeric, errors="raise"
74+
)
75+
self.metric_values["ds"] = pandas.to_datetime(self.metric_values["ds"], unit="s")
76+
77+
# Set the metric start time and the metric end time
78+
self.start_time = self.metric_values.iloc[0, 0]
79+
self.end_time = self.metric_values.iloc[-1, 0]
80+
81+
def __eq__(self, other):
82+
"""
83+
Overloading operator ``=``.
84+
85+
Check whether two metrics are the same (are the same time-series regardless of their data)
86+
87+
Example Usage:
88+
``metric_1 = Metric(metric_data_1)``
89+
90+
``metric_2 = Metric(metric_data_2)``
91+
92+
``print(metric_1 == metric_2) # will print True if they belong to the same time-series``
93+
94+
:return: (bool) If two Metric objects belong to the same time-series,
95+
i.e. same name and label config, it will return True, else False
96+
"""
97+
return bool(
98+
(self.metric_name == other.metric_name) and (self.label_config == other.label_config)
99+
)
100+
101+
def __str__(self):
102+
"""
103+
Make it print in a cleaner way when print function is used on a Metric object.
104+
105+
Example Usage:
106+
``metric_1 = Metric(metric_data_1)``
107+
108+
``print(metric_1) # will print the name, labels and the head of the dataframe``
109+
110+
"""
111+
name = "metric_name: " + repr(self.metric_name) + "\n"
112+
labels = "label_config: " + repr(self.label_config) + "\n"
113+
values = "metric_values: " + repr(self.metric_values)
114+
115+
return "{" + "\n" + name + labels + values + "\n" + "}"
116+
117+
def __add__(self, other):
118+
r"""
119+
Overloading operator ``+``.
120+
121+
Add two metric objects for the same time-series
122+
123+
Example Usage:
124+
.. code-block:: python
125+
126+
metric_1 = Metric(metric_data_1)
127+
metric_2 = Metric(metric_data_2)
128+
metric_12 = metric_1 + metric_2 # will add the data in ``metric_2`` to ``metric_1``
129+
# so if any other parameters are set in ``metric_1``
130+
# will also be set in ``metric_12``
131+
# (like ``oldest_data_datetime``)
132+
133+
:return: (`Metric`) Returns a `Metric` object with the combined metric data \
134+
of the two added metrics
135+
136+
:raises: (TypeError) Raises an exception when two metrics being added are \
137+
from different metric time-series
138+
"""
139+
if self == other:
140+
new_metric = deepcopy(self)
141+
new_metric.metric_values = new_metric.metric_values.append(
142+
other.metric_values, ignore_index=True
143+
)
144+
new_metric.metric_values = new_metric.metric_values.dropna()
145+
new_metric.metric_values = (
146+
new_metric.metric_values.drop_duplicates("ds")
147+
.sort_values(by=["ds"])
148+
.reset_index(drop=True)
149+
)
150+
# if oldest_data_datetime is set, trim the dataframe and only keep the newer data
151+
if new_metric.oldest_data_datetime:
152+
if isinstance(new_metric.oldest_data_datetime, datetime.timedelta):
153+
# create a time range mask
154+
mask = new_metric.metric_values["ds"] >= (
155+
new_metric.metric_values.iloc[-1, 0] - abs(new_metric.oldest_data_datetime)
156+
)
157+
else:
158+
# create a time range mask
159+
mask = new_metric.metric_values["ds"] >= new_metric.oldest_data_datetime
160+
# truncate the df within the mask
161+
new_metric.metric_values = new_metric.metric_values.loc[mask]
162+
163+
# Update the metric start time and the metric end time for the new Metric
164+
new_metric.start_time = new_metric.metric_values.iloc[0, 0]
165+
new_metric.end_time = new_metric.metric_values.iloc[-1, 0]
166+
167+
return new_metric
168+
169+
if self.metric_name != other.metric_name:
170+
error_string = "Different metric names"
171+
else:
172+
error_string = "Different metric labels"
173+
raise TypeError("Cannot Add different metric types. " + error_string)
174+
175+
def plot(self):
176+
"""Plot a very simple line graph for the metric time-series."""
177+
if _MPL_FOUND:
178+
fig, axis = plt.subplots()
179+
axis.plot_date(self.metric_values.ds, self.metric_values.y, linestyle=":")
180+
fig.autofmt_xdate()
181+
# if matplotlib was not imported
182+
else:
183+
raise ImportError("matplotlib was not found")
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""docstring for MetricsList."""
2+
3+
from .metric import Metric
4+
5+
6+
class MetricsList(list):
7+
"""A Class to initialize a list of Metric objects at once.
8+
9+
:param metric_data_list: (list|json) This is an individual metric or list of metrics received
10+
from prometheus as a result of a promql query.
11+
12+
Example Usage:
13+
.. code-block:: python
14+
15+
prom = PrometheusConnect()
16+
my_label_config = {'cluster': 'my_cluster_id', 'label_2': 'label_2_value'}
17+
metric_data = prom.get_metric_range_data(metric_name='up', label_config=my_label_config)
18+
19+
metric_object_list = MetricsList(metric_data) # metric_object_list will be initialized as
20+
# a list of Metric objects for all the
21+
# metrics downloaded using get_metric query
22+
23+
"""
24+
25+
def __init__(self, metric_data_list):
26+
"""Class MetricsList constructor."""
27+
# if the input is not a list
28+
if not isinstance(metric_data_list, list):
29+
# make it into a list
30+
metric_data_list = [metric_data_list]
31+
32+
metric_object_list = []
33+
for i in metric_data_list:
34+
# If it is a list of lists (for example: while reading from multiple json files)
35+
if isinstance(i, list):
36+
for metric in i:
37+
metric_object = Metric(metric)
38+
if metric_object in metric_object_list:
39+
metric_object_list[metric_object_list.index(metric_object)] += metric_object
40+
else:
41+
metric_object_list.append(metric_object)
42+
else:
43+
metric_object = Metric(i)
44+
if metric_object in metric_object_list:
45+
metric_object_list[metric_object_list.index(metric_object)] += metric_object
46+
else:
47+
metric_object_list.append(metric_object)
48+
super(MetricsList, self).__init__(metric_object_list)

0 commit comments

Comments
 (0)