Skip to content

Commit

Permalink
Merge pull request #7 from YounesOMK/feat/prometheus_up_time_deducer
Browse files Browse the repository at this point in the history
✨ feat: add prometheus service availability
  • Loading branch information
YounesOMK authored Dec 27, 2023
2 parents edf6059 + 4b44d46 commit 9b321c4
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 0 deletions.
65 changes: 65 additions & 0 deletions metricheq/deducers/prometheus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel
from metricheq.connectors.base import Connector
from metricheq.connectors.prometheus import PrometheusConnector
from urllib.parse import quote

from metricheq.deducers.base import Deducer

# TODO: provide default value for step based on start, end date and points


class PrometheusServiceAvailabilityParams(BaseModel):
labels: dict[str, str]
start_time: datetime
end_time: datetime
step: Optional[int] = None


class PrometheusServiceAvailabilityDeducer(Deducer):
def __init__(self, connector: Connector, params: dict):
if not isinstance(connector, PrometheusConnector):
raise TypeError(
"The provided connector is not a valid prometheus connector"
)
self.params_model = PrometheusServiceAvailabilityParams(**params)
super().__init__(connector, params)

def retrieve_data(self):
label_filters = ",".join(
[f'{key}="{value}"' for key, value in self.params_model.labels.items()]
)
start_date_rfc3339 = self.params_model.start_time.isoformat() + "Z"
end_date_rfc3339 = self.params_model.end_time.isoformat() + "Z"
step = self.params.get("step", 60)

query = f"up{{{label_filters}}}"
encoded_query = quote(query)
endpoint = f"query_range?query={encoded_query}&start={start_date_rfc3339}&end={end_date_rfc3339}&step={step}"

response = self.client.make_request(endpoint)
if response.status_code == 200:
return response.json()
else:
response.raise_for_status()

def process_data(self, data):
results = data.get("data", {}).get("result", [])

up_times = 0
total_times = 0

for result in results:
for _, status in result.get("values", []):
total_times += 1
if status == "1": # service was up
up_times += 1

availability_percentage = (
(up_times / total_times) * 100 if total_times > 0 else 0
)
return availability_percentage

def finalize(self, processed_data):
return processed_data
78 changes: 78 additions & 0 deletions tests/prometheus/test_availability_deducer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from datetime import datetime, timedelta
import unittest
from unittest.mock import Mock

from requests import HTTPError

from metricheq.connectors.prometheus import PrometheusConnector
from metricheq.deducers.prometheus import PrometheusServiceAvailabilityDeducer


class TestPrometheusServiceAvailabilityDeducer(unittest.TestCase):
def setUp(self):
self.mock_client = Mock()
self.mock_connector = Mock(spec=PrometheusConnector, client=self.mock_client)

start_time = datetime.now() - timedelta(days=1)
end_time = datetime.now()

self.params = {
"labels": {"job": "job_label"},
"start_time": start_time,
"end_time": end_time,
"step": 60,
}

self.deducer = PrometheusServiceAvailabilityDeducer(
self.mock_connector, self.params
)

def test_init_with_invalid_connector(self):
with self.assertRaises(TypeError):
invalid_connector = Mock()
PrometheusServiceAvailabilityDeducer(invalid_connector, self.params)

def test_retrieve_data_successful(self):
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"data": {"result": []}}
self.mock_client.make_request.return_value = mock_response

result = self.deducer.retrieve_data()
self.assertIsNotNone(result)

def test_retrieve_data_failure(self):
mock_response = Mock()
mock_response.status_code = 500
mock_response.raise_for_status.side_effect = HTTPError("500 Server Error")
self.mock_client.make_request.return_value = mock_response

with self.assertRaises(HTTPError):
self.deducer.retrieve_data()

def test_process_data(self):
mock_data = {
"data": {"result": [{"values": [["timestamp", "1"], ["timestamp", "0"]]}]}
}
result = self.deducer.process_data(mock_data)
expected_availability = (
50.0 # Assuming one 'up' and one 'down' in the mock data
)
self.assertEqual(result, expected_availability)

def test_finalize(self):
processed_data = 75.0
result = self.deducer.finalize(processed_data)
self.assertEqual(result, processed_data)

def test_deduce_integration(self):
mock_retrieve_data = Mock(return_value={"data": {"result": []}})
mock_process_data = Mock(return_value=100.0)
mock_finalize = Mock(return_value=100.0)

self.deducer.retrieve_data = mock_retrieve_data
self.deducer.process_data = mock_process_data
self.deducer.finalize = mock_finalize

result = self.deducer.deduce()
self.assertEqual(result, 100.0)

0 comments on commit 9b321c4

Please sign in to comment.