Skip to content

Commit b15858a

Browse files
committed
Add prometheus to services methods calls (fixes #62)
1 parent dd2e68c commit b15858a

File tree

2 files changed

+106
-2
lines changed

2 files changed

+106
-2
lines changed

src/jbi/services.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Services and functions that can be used to create custom actions"""
2+
import contextlib
23
import logging
34
from typing import Dict, List
45

56
import bugzilla as rh_bugzilla
67
from atlassian import Jira
8+
from prometheus_client import Counter, Summary
79

810
from src.app import environment
911
from src.jbi.models import Actions
@@ -16,14 +18,61 @@
1618
ServiceHealth = Dict[str, bool]
1719

1820

21+
class InstrumentedClient:
22+
"""This class wraps an object and increments a counter everytime
23+
the specieds methods are called, and times their execution.
24+
"""
25+
26+
counters: Dict[str, Counter] = {}
27+
timers: Dict[str, Counter] = {}
28+
29+
def __init__(self, wrapped, prefix, methods):
30+
self.wrapped = wrapped
31+
self.methods = methods
32+
33+
# We have a single instrument per prefix. Methods are reported as labels.
34+
counter_name = prefix + "_methods_total"
35+
if counter_name not in self.counters:
36+
self.counters[counter_name] = Counter(
37+
counter_name, f"{prefix} method calls", labelnames=["method"]
38+
)
39+
self.counter = self.counters[counter_name]
40+
41+
timer_name = prefix + "_methods_milliseconds"
42+
if timer_name not in self.timers:
43+
self.timers[timer_name] = Summary(
44+
timer_name, f"{prefix} method timing", labelnames=["method"]
45+
)
46+
self.timer = self.timers[timer_name]
47+
48+
def __getattr__(self, attr):
49+
if attr in self.methods:
50+
self.counter.labels(method=attr).inc()
51+
timer_cm = self.timer.labels(method=attr).time()
52+
else:
53+
timer_cm = contextlib.nullcontext()
54+
55+
with timer_cm:
56+
return getattr(self.wrapped, attr)
57+
58+
1959
def get_jira():
2060
"""Get atlassian Jira Service"""
21-
return Jira(
61+
jira_client = Jira(
2262
url=settings.jira_base_url,
2363
username=settings.jira_username,
2464
password=settings.jira_api_key, # package calls this param 'password' but actually expects an api key
2565
cloud=True, # we run against an instance of Jira cloud
2666
)
67+
instrumented_methods = (
68+
"update_issue_field",
69+
"set_issue_status",
70+
"issue_add_comment",
71+
"create_issue",
72+
)
73+
return InstrumentedClient(
74+
wrapped=jira_client, prefix="jbi_jira", methods=instrumented_methods
75+
)
2776

2877

2978
def jira_visible_projects(jira=None) -> List[Dict]:
@@ -35,9 +84,17 @@ def jira_visible_projects(jira=None) -> List[Dict]:
3584

3685
def get_bugzilla():
3786
"""Get bugzilla service"""
38-
return rh_bugzilla.Bugzilla(
87+
bugzilla_client = rh_bugzilla.Bugzilla(
3988
settings.bugzilla_base_url, api_key=str(settings.bugzilla_api_key)
4089
)
90+
instrumented_methods = (
91+
"get_bug",
92+
"get_comments",
93+
"update_bugs",
94+
)
95+
return InstrumentedClient(
96+
wrapped=bugzilla_client, prefix="jbi_bugzilla", methods=instrumented_methods
97+
)
4198

4299

43100
def _bugzilla_check_health() -> ServiceHealth:

tests/unit/jbi/test_services.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""
2+
Module for testing src/jbi/services.py
3+
"""
4+
from unittest import mock
5+
6+
import pytest
7+
8+
from src.jbi.services import get_bugzilla, get_jira
9+
10+
11+
def test_counter_is_incremented_on_jira_create_issue():
12+
with mock.patch("src.jbi.services.Jira.create_issue"):
13+
jira_client = get_jira()
14+
15+
labelled_counter = jira_client.counters["jbi_jira_methods_total"].labels(
16+
method="create_issue"
17+
)
18+
with mock.patch.object(labelled_counter, "inc") as mocked:
19+
jira_client.create_issue({})
20+
21+
assert mocked.called, "Counter was incremented on create_issue()"
22+
23+
24+
def test_timer_is_used_on_jira_create_issue():
25+
with mock.patch("src.jbi.services.Jira.create_issue"):
26+
jira_client = get_jira()
27+
28+
labelled_timer = jira_client.timers["jbi_jira_methods_milliseconds"].labels(
29+
method="create_issue"
30+
)
31+
with mock.patch.object(labelled_timer, "time") as mocked:
32+
jira_client.create_issue({})
33+
34+
assert mocked.called, "Timer was used on create_issue()"
35+
36+
37+
def test_timer_is_used_on_bugzilla_getcomments():
38+
with mock.patch("src.jbi.services.rh_bugzilla.Bugzilla"):
39+
bugzilla_client = get_bugzilla()
40+
41+
labelled_timer = bugzilla_client.timers[
42+
"jbi_bugzilla_methods_milliseconds"
43+
].labels(method="get_comments")
44+
with mock.patch.object(labelled_timer, "time") as mocked:
45+
bugzilla_client.get_comments([])
46+
47+
assert mocked.called, "Timer was used on get_comments()"

0 commit comments

Comments
 (0)