Skip to content

Commit a57e762

Browse files
committed
extract and add tests for statsd blocks
1 parent 936f841 commit a57e762

5 files changed

Lines changed: 139 additions & 26 deletions

File tree

blitzortung/cache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def clean_expired(self):
117117
def get_time_to_live(self):
118118
return self.__ttl_seconds
119119

120-
def get_ratio(self):
120+
def get_ratio(self) -> float:
121121
if self.total_hit_count == 0:
122122
return 0.0
123123
return self.total_hit_count / self.total_count

blitzortung/cli/webservice.py

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from blitzortung.gis.constants import grid, global_grid
2424
from blitzortung.gis.local_grid import LocalGrid
2525
from blitzortung.service.cache import ServiceCache
26+
from blitzortung.service.metrics import StatsDMetrics
2627
from blitzortung.util import TimeConstraint
2728

2829
try:
@@ -47,8 +48,6 @@
4748

4849
is_pypy = platform.python_implementation() == 'PyPy'
4950

50-
statsd_client = statsd.StatsClient('localhost', 8125, prefix='org.blitzortung.service')
51-
5251
FORBIDDEN_IPS = {}
5352

5453
USER_AGENT_PREFIX = 'bo-android-'
@@ -93,6 +92,8 @@ def __init__(self, db_connection_pool, log_directory):
9392
self.next_memory_info = 0.0
9493
self.minute_constraints = TimeConstraint(self.DEFAULT_MINUTE_LENGTH, self.MAX_MINUTES_PER_DAY)
9594

95+
self.metrics = StatsDMetrics(statsd_client)
96+
9697
addSlash = True
9798

9899
def __get_epoch(self, timestamp):
@@ -143,7 +144,7 @@ def get_strikes_grid(self, minute_length, grid_baselength, minute_offset, region
143144
time_interval = create_time_interval(minute_length, minute_offset)
144145

145146
grid_result, state = self.strike_grid_query.create(grid_parameters, time_interval, self.connection_pool,
146-
statsd_client)
147+
self.metrics.statsd)
147148

148149
histogram_result = self.get_histogram(time_interval, envelope=grid_parameters.grid) \
149150
if minute_length > self.HISTOGRAM_MINUTE_THRESHOLD else succeed([])
@@ -160,7 +161,7 @@ def get_global_strikes_grid(self, minute_length, grid_baselength, minute_offset,
160161
time_interval = create_time_interval(minute_length, minute_offset)
161162

162163
grid_result, state = self.global_strike_grid_query.create(grid_parameters, time_interval, self.connection_pool,
163-
statsd_client)
164+
self.metrics.statsd)
164165

165166
histogram_result = self.get_histogram(
166167
time_interval) if minute_length > self.HISTOGRAM_MINUTE_THRESHOLD else succeed([])
@@ -179,7 +180,7 @@ def get_local_strikes_grid(self, x, y, grid_baselength, minute_length, minute_of
179180
time_interval = create_time_interval(minute_length, minute_offset)
180181

181182
grid_result, state = self.strike_grid_query.create(grid_parameters, time_interval, self.connection_pool,
182-
statsd_client)
183+
self.metrics.statsd)
183184

184185
histogram_result = self.get_histogram(time_interval, envelope=grid_parameters.grid) \
185186
if minute_length > self.HISTOGRAM_MINUTE_THRESHOLD else succeed([])
@@ -237,12 +238,7 @@ def jsonrpc_get_global_strikes_grid(self, request, minute_length, grid_base_leng
237238
minute_offset,
238239
0, count_threshold, client, user_agent))
239240

240-
statsd_client.incr('strikes_grid.total_count')
241-
statsd_client.incr('global_strikes_grid.total_count')
242-
statsd_client.gauge('global_strikes_grid.cache_hits', cache.get_ratio())
243-
if minute_length == 10:
244-
statsd_client.incr('strikes_grid.bg_count')
245-
statsd_client.incr('global_strikes_grid.bg_count')
241+
self.metrics.for_global_strikes(minute_length, cache.get_ratio())
246242

247243
return response
248244

@@ -289,13 +285,7 @@ def jsonrpc_get_local_strikes_grid(self, request, x, y, grid_base_length=10000,
289285
minute_offset,
290286
-1, count_threshold, client, user_agent, x, y, data_area))
291287

292-
statsd_client.incr('strikes_grid.total_count')
293-
statsd_client.incr('local_strikes_grid.total_count')
294-
statsd_client.incr(f'local_strikes_grid.data_area.{data_area}')
295-
statsd_client.gauge('local_strikes_grid.cache_hits', cache.get_ratio())
296-
if minute_length == 10:
297-
statsd_client.incr('strikes_grid.bg_count')
298-
statsd_client.incr('local_strikes_grid.bg_count')
288+
self.metrics.for_local_strikes(minute_length, data_area, cache.get_ratio())
299289

300290
return response
301291

@@ -340,12 +330,7 @@ def jsonrpc_get_strikes_grid(self, request, minute_length, grid_base_length=1000
340330
region,
341331
count_threshold, client, user_agent))
342332

343-
statsd_client.incr('strikes_grid.total_count')
344-
statsd_client.incr('strikes_grid.total_count.{}'.format(region))
345-
statsd_client.gauge('strikes_grid.cache_hits', cache.get_ratio())
346-
if minute_length == 10:
347-
statsd_client.incr('strikes_grid.bg_count')
348-
statsd_client.incr('strikes_grid.bg_count.{}'.format(region))
333+
self.metrics.for_strikes(minute_length, region, cache.get_ratio())
349334

350335
return response
351336

blitzortung/service/metrics.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from typing import Optional
2+
3+
from statsd import StatsClient
4+
5+
6+
class StatsDMetrics:
7+
8+
def __init__(self, statsd: Optional[StatsClient]= None):
9+
self.statsd = statsd if statsd else StatsClient('localhost', 8125, prefix='org.blitzortung.service')
10+
11+
def for_global_strikes(self, minute_length: int, cache_ratio: float):
12+
self.statsd.incr('strikes_grid.total_count')
13+
self.statsd.incr('global_strikes_grid.total_count')
14+
self.statsd.gauge('global_strikes_grid.cache_hits', cache_ratio)
15+
if minute_length == 10:
16+
self.statsd.incr('strikes_grid.bg_count')
17+
self.statsd.incr('global_strikes_grid.bg_count')
18+
19+
def for_local_strikes(self, minute_length: int, data_area, cache_ratio: float):
20+
self.statsd.incr('strikes_grid.total_count')
21+
self.statsd.incr('local_strikes_grid.total_count')
22+
self.statsd.incr(f'local_strikes_grid.data_area.{data_area}')
23+
self.statsd.gauge('local_strikes_grid.cache_hits', cache_ratio)
24+
if minute_length == 10:
25+
self.statsd.incr('strikes_grid.bg_count')
26+
self.statsd.incr('local_strikes_grid.bg_count')
27+
28+
def for_strikes(self, minute_length:int, region: int, cache_ratio: float):
29+
self.statsd.incr('strikes_grid.total_count')
30+
self.statsd.incr('strikes_grid.total_count.{}'.format(region))
31+
self.statsd.gauge('strikes_grid.cache_hits', cache_ratio)
32+
if minute_length == 10:
33+
self.statsd.incr('strikes_grid.bg_count')
34+
self.statsd.incr('strikes_grid.bg_count.{}'.format(region))

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = 'blitzortung'
3-
version = '1.11.29'
3+
version = '1.11.30'
44
description = 'blitzortung.org python modules'
55
authors = [
66
{name = "Andreas Würl",email = "andi@tryb.de"}

tests/service/test_metrics.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import pytest
2+
from unittest.mock import Mock
3+
4+
from blitzortung.service.metrics import StatsDMetrics
5+
6+
7+
@pytest.fixture
8+
def mock_statsd():
9+
"""Create a mock StatsClient for testing."""
10+
statsd = Mock()
11+
statsd.incr = Mock()
12+
statsd.gauge = Mock()
13+
return statsd
14+
15+
16+
class TestStatsDMetrics:
17+
"""Tests for StatsDMetrics class."""
18+
19+
@pytest.fixture
20+
def metrics(self, mock_statsd):
21+
return StatsDMetrics(mock_statsd)
22+
23+
24+
def test_init_with_custom_statsd(self, metrics, mock_statsd):
25+
"""Test initialization with custom StatsClient."""
26+
assert metrics.statsd is mock_statsd
27+
28+
def test_init_with_default_statsd(self):
29+
"""Test initialization with default StatsClient."""
30+
metrics = StatsDMetrics()
31+
assert metrics.statsd is not None
32+
# Default should create StatsClient with specific config
33+
assert hasattr(metrics.statsd, 'incr')
34+
assert hasattr(metrics.statsd, 'gauge')
35+
36+
@pytest.mark.parametrize("minute_length,is_bg,cache_ratio", [
37+
(10, True, 0.75),
38+
(60, False, 0.85),
39+
])
40+
def test_for_global_strikes(self, metrics, mock_statsd, minute_length, is_bg, cache_ratio):
41+
"""Test for_global_strikes with 10 minute length."""
42+
43+
metrics.for_global_strikes(minute_length=minute_length, cache_ratio=cache_ratio)
44+
45+
# Verify all expected calls
46+
assert mock_statsd.incr.call_count == 2 + 2 * is_bg
47+
48+
mock_statsd.incr.assert_any_call('strikes_grid.total_count')
49+
mock_statsd.incr.assert_any_call('global_strikes_grid.total_count')
50+
if is_bg:
51+
mock_statsd.incr.assert_any_call('strikes_grid.bg_count')
52+
mock_statsd.incr.assert_any_call('global_strikes_grid.bg_count')
53+
54+
mock_statsd.gauge.assert_called_once_with('global_strikes_grid.cache_hits', cache_ratio)
55+
56+
@pytest.mark.parametrize("minute_length,is_bg,cache_ratio,data_area", [
57+
(10, True, 0.75, 5),
58+
(60, False, 0.85, 10),
59+
])
60+
def test_for_local_strikes_with_10_minute_length(self, metrics, mock_statsd, minute_length, is_bg, cache_ratio, data_area):
61+
"""Test for_local_strikes."""
62+
63+
metrics.for_local_strikes(minute_length=minute_length, data_area=data_area, cache_ratio=cache_ratio)
64+
65+
# Verify all expected calls
66+
assert mock_statsd.incr.call_count == 3 + 2 * is_bg
67+
mock_statsd.incr.assert_any_call('strikes_grid.total_count')
68+
mock_statsd.incr.assert_any_call('local_strikes_grid.total_count')
69+
mock_statsd.incr.assert_any_call(f'local_strikes_grid.data_area.{data_area}')
70+
if is_bg:
71+
mock_statsd.incr.assert_any_call('strikes_grid.bg_count')
72+
mock_statsd.incr.assert_any_call('local_strikes_grid.bg_count')
73+
74+
mock_statsd.gauge.assert_called_once_with('local_strikes_grid.cache_hits', cache_ratio)
75+
76+
77+
@pytest.mark.parametrize("minute_length,is_bg,cache_ratio,region", [
78+
(10, True, 0.75, 1),
79+
(60, False, 0.85, 2),
80+
])
81+
def test_for_strikes_with_10_minute_length(self, metrics, mock_statsd, minute_length, is_bg, cache_ratio, region):
82+
"""Test for_strikes on regions."""
83+
84+
metrics.for_strikes(minute_length=minute_length, region=region, cache_ratio=cache_ratio)
85+
86+
# Verify all expected calls
87+
assert mock_statsd.incr.call_count == 2 + 2 * is_bg
88+
mock_statsd.incr.assert_any_call('strikes_grid.total_count')
89+
mock_statsd.incr.assert_any_call(f'strikes_grid.total_count.{region}')
90+
if is_bg:
91+
mock_statsd.incr.assert_any_call('strikes_grid.bg_count')
92+
mock_statsd.incr.assert_any_call(f'strikes_grid.bg_count.{region}')
93+
94+
mock_statsd.gauge.assert_called_once_with('strikes_grid.cache_hits', cache_ratio)

0 commit comments

Comments
 (0)