Skip to content

Commit bf53093

Browse files
authored
Use statsd client instead of Promotheus (#160)
* Use statsd client instead of Prometheus * Remove mentions of Prometheus (unused in production) * Add statsd to mypy ignores * Set container entrypoint back to /bin/sh (default)
1 parent 6591895 commit bf53093

File tree

10 files changed

+51
-114
lines changed

10 files changed

+51
-114
lines changed

Dockerfile

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ RUN poetry install --no-dev --no-root
2222

2323
# `production` stage uses the dependencies downloaded in the `base` stage
2424
FROM python:3.10.5-slim as production
25-
ENV PROMETHEUS_MULTIPROC=1 \
26-
PORT=8000 \
25+
ENV PORT=8000 \
2726
PYTHONUNBUFFERED=1 \
2827
PYTHONDONTWRITEBYTECODE=1 \
2928
VIRTUAL_ENV=/opt/pysetup/.venv
@@ -42,5 +41,4 @@ WORKDIR /app
4241
COPY . .
4342

4443
EXPOSE $PORT
45-
ENTRYPOINT ["bin/docker-entrypoint.sh"]
4644
CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "-c", "config/gunicorn_conf.py", "src.app.api:app"]

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,12 @@ GET /whiteboard_tags/
221221

222222
## Metrics
223223

224-
The following metrics are sent via Prometheus:
225-
226-
- `jbi_bugzilla_ignored_total`
227-
- `jbi_bugzilla_processed_total`
228-
- `jbi_action_execution_milliseconds`
229-
- `jbi_jira_methods_milliseconds`
230-
- `jbi_jira_methods_total`
231-
- `jbi_bugzilla_methods_total`
232-
- `jbi_bugzilla_methods_milliseconds`
224+
The following metrics are sent via StatsD:
225+
226+
- `jbi.bugzilla.ignored.count`
227+
- `jbi.bugzilla.processed.count`
228+
- `jbi.action.execution.timer`
229+
- `jbi.jira.methods.*.count`
230+
- `jbi.jira.methods.*.timer`
231+
- `jbi.bugzilla.methods.*.count`
232+
- `jbi.bugzilla.methods.*.timer`

bin/docker-entrypoint.sh

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

config/gunicorn_conf.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import multiprocessing
77
from typing import Optional
88

9-
from prometheus_client import multiprocess
109
from pydantic import BaseSettings, conint, root_validator, validator
1110

1211

@@ -77,7 +76,3 @@ def set_workers(cls, values):
7776
"host": gunicorn_settings.host,
7877
"port": gunicorn_settings.port,
7978
}
80-
81-
82-
def child_exit(server, worker): # pylint: disable=missing-function-docstring
83-
multiprocess.mark_process_dead(worker.pid)

poetry.lock

Lines changed: 13 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ fastapi = "^0.73.0"
1111
pydantic = {version = "^1.9.1", extras = ["dotenv", "email"]}
1212
uvicorn = {extras = ["standard"], version = "^0.18.2"}
1313
gunicorn = "^20.1.0"
14-
prometheus-client = "^0.14.1"
1514
python-bugzilla = "^3.2.0"
1615
atlassian-python-api = "^3.25.0"
1716
dockerflow = "2022.7.0"
1817
Jinja2 = "^3.1.2"
1918
pydantic-yaml = {extras = ["pyyaml","ruamel"], version = "^0.6.1"}
2019
sentry-sdk = "^1.8.0"
2120
backoff = "^2.1.2"
21+
statsd = "^3.3.0"
2222

2323

2424
[tool.poetry.dev-dependencies]
@@ -74,7 +74,7 @@ python_version = "3.10"
7474
warn_return_any = true
7575

7676
[[tool.mypy.overrides]]
77-
module = ["ruamel", "bugzilla", "atlassian"]
77+
module = ["ruamel", "bugzilla", "atlassian", "statsd.defaults.env"]
7878
ignore_missing_imports = true
7979

8080
[tool.coverage]

src/jbi/runner.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44
import logging
55

6-
from prometheus_client import Counter, Summary
6+
from statsd.defaults.env import statsd
77

88
from src.app.environment import Settings
99
from src.jbi.bugzilla import BugzillaBug, BugzillaWebhookRequest
@@ -12,14 +12,6 @@
1212

1313
logger = logging.getLogger(__name__)
1414

15-
counter_ignored = Counter("jbi_bugzilla_ignored_total", "Bugzilla WebHooks ignored")
16-
counter_processed = Counter(
17-
"jbi_bugzilla_processed_total", "Bugzilla WebHooks processed"
18-
)
19-
action_execution_timer = Summary(
20-
"jbi_action_execution_milliseconds", "Action execution duration"
21-
)
22-
2315

2416
class Operations:
2517
"""Track status of incoming requests in log entries."""
@@ -30,7 +22,7 @@ class Operations:
3022
SUCCESS = "success"
3123

3224

33-
@action_execution_timer.time()
25+
@statsd.timer("jbi.action.execution.timer")
3426
def execute_action(
3527
request: BugzillaWebhookRequest,
3628
actions: Actions,
@@ -96,13 +88,13 @@ def execute_action(
9688
bug_obj.id,
9789
extra={"operation": Operations.SUCCESS, **log_context},
9890
)
99-
counter_processed.inc()
91+
statsd.incr("jbi.bugzilla.processed.count")
10092
return content
10193
except IgnoreInvalidRequestError as exception:
10294
logger.debug(
10395
"Ignore incoming request: %s",
10496
exception,
10597
extra={"operation": Operations.IGNORE, **log_context},
10698
)
107-
counter_ignored.inc()
99+
statsd.incr("jbi.bugzilla.ignored.count")
108100
raise

src/jbi/services.py

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import backoff
66
import bugzilla as rh_bugzilla
77
from atlassian import Jira, errors
8-
from prometheus_client import Counter, Summary
8+
from statsd.defaults.env import statsd
99

1010
from src.app import environment
1111
from src.jbi.models import Actions
@@ -24,29 +24,12 @@ class InstrumentedClient:
2424
It retries the methods if the specified exceptions are raised.
2525
"""
2626

27-
counters: Dict[str, Counter] = {}
28-
timers: Dict[str, Counter] = {}
29-
3027
def __init__(self, wrapped, prefix, methods, exceptions):
3128
self.wrapped = wrapped
29+
self.prefix = prefix
3230
self.methods = methods
3331
self.exceptions = exceptions
3432

35-
# We have a single instrument per prefix. Methods are reported as labels.
36-
counter_name = prefix + "_methods_total"
37-
if counter_name not in self.counters:
38-
self.counters[counter_name] = Counter(
39-
counter_name, f"{prefix} method calls", labelnames=["method"]
40-
)
41-
self.counter = self.counters[counter_name]
42-
43-
timer_name = prefix + "_methods_milliseconds"
44-
if timer_name not in self.timers:
45-
self.timers[timer_name] = Summary(
46-
timer_name, f"{prefix} method timing", labelnames=["method"]
47-
)
48-
self.timer = self.timers[timer_name]
49-
5033
def __getattr__(self, attr):
5134
if attr not in self.methods:
5235
return getattr(self.wrapped, attr)
@@ -58,9 +41,9 @@ def __getattr__(self, attr):
5841
)
5942
def wrapped_func(*args, **kwargs):
6043
# Increment the call counter.
61-
self.counter.labels(method=attr).inc()
44+
statsd.incr(f"jbi.{self.prefix}.methods.{attr}.count")
6245
# Time its execution.
63-
with self.timer.labels(method=attr).time():
46+
with statsd.timer(f"jbi.{self.prefix}.methods.{attr}.timer"):
6447
return getattr(self.wrapped, attr)(*args, **kwargs)
6548

6649
# The method was not called yet.
@@ -83,7 +66,7 @@ def get_jira():
8366
)
8467
return InstrumentedClient(
8568
wrapped=jira_client,
86-
prefix="jbi_jira",
69+
prefix="jira",
8770
methods=instrumented_methods,
8871
exceptions=(errors.ApiError,),
8972
)
@@ -108,7 +91,7 @@ def get_bugzilla():
10891
)
10992
return InstrumentedClient(
11093
wrapped=bugzilla_client,
111-
prefix="jbi_bugzilla",
94+
prefix="bugzilla",
11295
methods=instrumented_methods,
11396
exceptions=(rh_bugzilla.BugzillaError,),
11497
)

tests/unit/jbi/test_runner.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,25 +140,25 @@ def test_counter_is_incremented_on_ignored_requests(
140140
assert webhook_create_example.bug
141141
webhook_create_example.bug.whiteboard = "foo"
142142

143-
with mock.patch("src.jbi.runner.counter_ignored") as mocked:
143+
with mock.patch("src.jbi.runner.statsd") as mocked:
144144
with pytest.raises(IgnoreInvalidRequestError):
145145
execute_action(
146146
request=webhook_create_example,
147147
actions=actions_example,
148148
settings=settings,
149149
)
150-
assert mocked.inc.called, "ignored counter was called"
150+
mocked.incr.assert_called_with("jbi.bugzilla.ignored.count")
151151

152152

153153
def test_counter_is_incremented_on_processed_requests(
154154
webhook_create_example: BugzillaWebhookRequest,
155155
actions_example: Actions,
156156
settings: Settings,
157157
):
158-
with mock.patch("src.jbi.runner.counter_processed") as mocked:
158+
with mock.patch("src.jbi.runner.statsd") as mocked:
159159
execute_action(
160160
request=webhook_create_example,
161161
actions=actions_example,
162162
settings=settings,
163163
)
164-
assert mocked.inc.called, "processed counter was called"
164+
mocked.incr.assert_called_with("jbi.bugzilla.processed.count")

tests/unit/jbi/test_services.py

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,42 +10,30 @@
1010

1111

1212
def test_counter_is_incremented_on_jira_create_issue():
13-
with mock.patch("src.jbi.services.Jira.create_issue"):
14-
jira_client = get_jira()
13+
jira_client = get_jira()
1514

16-
labelled_counter = jira_client.counters["jbi_jira_methods_total"].labels(
17-
method="create_issue"
18-
)
19-
with mock.patch.object(labelled_counter, "inc") as mocked:
20-
jira_client.create_issue({})
15+
with mock.patch("src.jbi.services.statsd") as mocked:
16+
jira_client.create_issue({})
2117

22-
assert mocked.called, "Counter was incremented on create_issue()"
18+
mocked.incr.assert_called_with("jbi.jira.methods.create_issue.count")
2319

2420

2521
def test_timer_is_used_on_jira_create_issue():
26-
with mock.patch("src.jbi.services.Jira.create_issue"):
27-
jira_client = get_jira()
22+
jira_client = get_jira()
2823

29-
labelled_timer = jira_client.timers["jbi_jira_methods_milliseconds"].labels(
30-
method="create_issue"
31-
)
32-
with mock.patch.object(labelled_timer, "time") as mocked:
33-
jira_client.create_issue({})
24+
with mock.patch("src.jbi.services.statsd") as mocked:
25+
jira_client.create_issue({})
3426

35-
assert mocked.called, "Timer was used on create_issue()"
27+
mocked.timer.assert_called_with("jbi.jira.methods.create_issue.timer")
3628

3729

3830
def test_timer_is_used_on_bugzilla_getcomments():
39-
with mock.patch("src.jbi.services.rh_bugzilla.Bugzilla"):
40-
bugzilla_client = get_bugzilla()
31+
bugzilla_client = get_bugzilla()
4132

42-
labelled_timer = bugzilla_client.timers[
43-
"jbi_bugzilla_methods_milliseconds"
44-
].labels(method="get_comments")
45-
with mock.patch.object(labelled_timer, "time") as mocked:
46-
bugzilla_client.get_comments([])
33+
with mock.patch("src.jbi.services.statsd") as mocked:
34+
bugzilla_client.get_comments([])
4735

48-
assert mocked.called, "Timer was used on get_comments()"
36+
mocked.timer.assert_called_with("jbi.bugzilla.methods.get_comments.timer")
4937

5038

5139
def test_bugzilla_methods_are_retried_if_raising():

0 commit comments

Comments
 (0)