Skip to content

Commit e9b0be6

Browse files
committed
Finished tests for Telemetry and cleanup for redis telemetry
1 parent 95660cd commit e9b0be6

File tree

8 files changed

+154
-12
lines changed

8 files changed

+154
-12
lines changed

splitio/client/factory.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def _update_redis_init(self):
161161
"""Push Config Telemetry into redis storage"""
162162
redundant_factory_count, active_factory_count = _get_active_and_redundant_count()
163163
self._telemetry_init_producer.record_active_and_redundant_factories(active_factory_count, redundant_factory_count)
164+
self._telemetry_init_producer.get_telemetry_storage().push_config_stats()
164165

165166
def _update_status_when_ready(self):
166167
"""Wait until the sdk is ready and update the status."""

splitio/engine/telemetry.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ def record_not_ready_usage(self):
5151
def record_active_and_redundant_factories(self, active_factory_count, redundant_factory_count):
5252
self._telemetry_storage.record_active_and_redundant_factories(active_factory_count, redundant_factory_count)
5353

54+
def get_telemetry_storage(self):
55+
return self._telemetry_storage
56+
5457
class TelemetryEvaluationProducer(object):
5558
"""Telemetry evaluation producer class."""
5659

splitio/storage/adapters/redis.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
"""Redis client wrapper with prefix support."""
22
from builtins import str
33

4+
import logging
5+
_LOGGER = logging.getLogger(__name__)
6+
47
from splitio.version import __version__
58
from splitio.util.host_info import get_ip, get_hostname
69

@@ -309,6 +312,7 @@ def pipeline(self):
309312
raise RedisAdapterException('Error executing ttl operation') from exc
310313

311314
def record_init(self, *values):
315+
"""Write config init values to redis."""
312316
try:
313317
host_ip = get_ip()
314318
host_name = get_hostname()

splitio/storage/redis.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -614,12 +614,12 @@ def record_config(self, config, extra_config):
614614
"""
615615
self._tel_config.record_config(config, extra_config)
616616

617-
def record_active_and_redundant_factories(self, active_factory_count, redundant_factory_count):
618-
"""Record active and redundant factories."""
619-
self._tel_config.record_active_and_redundant_factories(active_factory_count, redundant_factory_count)
617+
def push_config_stats(self):
618+
"""push config stats to redis."""
620619
self._redis_client.record_init(self._format_config_stats())
621620

622621
def _format_config_stats(self):
622+
"""format only selected config stats to json"""
623623
config_stats = self._tel_config.get_stats()
624624
return json.dumps({
625625
'aF': config_stats['aF'],
@@ -628,6 +628,10 @@ def _format_config_stats(self):
628628
'oM': config_stats['oM']
629629
})
630630

631+
def record_active_and_redundant_factories(self, active_factory_count, redundant_factory_count):
632+
"""Record active and redundant factories."""
633+
self._tel_config.record_active_and_redundant_factories(active_factory_count, redundant_factory_count)
634+
631635
def add_latency_to_pipe(self, method, latency, pipe):
632636
"""
633637
record latency data
@@ -668,7 +672,7 @@ def record_exception(self, method):
668672
pipe.hincrby(self._TELEMETRY_EXCEPTIONS_KEY, 'python-' + __version__ + '/' + self.host_name+ '/' + self.host_ip + '/' +
669673
method.value, 1)
670674
result = pipe.execute()
671-
self._expire_keys(self._TELEMETRY_EXCEPTIONS_KEY, self._TELEMETRY_KEY_DEFAULT_TTL, 1, result[0])
675+
self.expire_keys(self._TELEMETRY_EXCEPTIONS_KEY, self._TELEMETRY_KEY_DEFAULT_TTL, 1, result[0])
672676

673677
def record_not_ready_usage(self):
674678
"""

tests/integration/test_client_e2e.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import os
55
import threading
6+
import time
67
import pytest
78

89
from redis import StrictRedis
@@ -63,14 +64,18 @@ def setup_method(self):
6364
}
6465
impmanager = ImpressionsManager(StrategyDebugMode(), telemetry_runtime_producer) # no listener
6566
recorder = StandardRecorder(impmanager, storages['events'], storages['impressions'], telemetry_evaluation_producer)
66-
self.factory = SplitFactory('some_api_key',
67+
# Since we are passing None as SDK_Ready event, the factory will use the Redis telemetry call, using try catch to ignore the exception.
68+
try:
69+
self.factory = SplitFactory('some_api_key',
6770
storages,
6871
True,
6972
recorder,
7073
None,
7174
telemetry_producer=telemetry_producer,
7275
telemetry_init_consumer=telemetry_consumer.get_telemetry_init_consumer(),
7376
) # pylint:disable=attribute-defined-outside-init
77+
except:
78+
pass
7479

7580
def teardown_method(self):
7681
"""Shut down the factory."""
@@ -256,7 +261,10 @@ def test_get_treatments_with_config(self):
256261

257262
def test_manager_methods(self):
258263
"""Test manager.split/splits."""
259-
manager = self.factory.manager()
264+
try:
265+
manager = self.factory.manager()
266+
except:
267+
pass
260268
result = manager.split('all_feature')
261269
assert result.name == 'all_feature'
262270
assert result.traffic_type is None

tests/recorder/test_recorder.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
import pytest
44

5+
from splitio.client.util import get_method_constant
56
from splitio.recorder.recorder import StandardRecorder, PipelinedRecorder
67
from splitio.engine.impressions.impressions import Manager as ImpressionsManager
7-
from splitio.storage.inmemmory import EventStorage, ImpressionStorage
8-
from splitio.storage.redis import ImpressionPipelinedStorage, EventStorage, RedisEventsStorage, RedisImpressionsStorage
8+
from splitio.engine.telemetry import TelemetryStorageProducer
9+
from splitio.storage.inmemmory import EventStorage, ImpressionStorage, InMemoryTelemetryStorage
10+
from splitio.storage.redis import ImpressionPipelinedStorage, EventStorage, RedisEventsStorage, RedisImpressionsStorage, RedisTelemetryStorage
911
from splitio.storage.adapters.redis import RedisAdapter
1012
from splitio.models.impressions import Impression
1113

1214

15+
1316
class StandardRecorderTests(object):
1417
"""StandardRecorderTests test cases."""
1518

@@ -22,10 +25,20 @@ def test_standard_recorder(self, mocker):
2225
impmanager.process_impressions.return_value = impressions
2326
event = mocker.Mock(spec=EventStorage)
2427
impression = mocker.Mock(spec=ImpressionStorage)
25-
recorder = StandardRecorder(impmanager, event, impression, mocker.Mock())
28+
telemetry_storage = mocker.Mock(spec=InMemoryTelemetryStorage)
29+
telemetry_producer = TelemetryStorageProducer(telemetry_storage)
30+
31+
def record_latency(*args, **kwargs):
32+
self.passed_args = args
33+
34+
telemetry_storage.record_latency.side_effect = record_latency
35+
36+
recorder = StandardRecorder(impmanager, event, impression, telemetry_producer.get_telemetry_evaluation_producer())
2637
recorder.record_treatment_stats(impressions, 1, 'some', 'get_treatment')
2738

2839
assert recorder._impression_storage.put.mock_calls[0][1][0] == impressions
40+
assert(self.passed_args[0] == get_method_constant('treatment'))
41+
assert(self.passed_args[1] == 1)
2942

3043
def test_pipelined_recorder(self, mocker):
3144
impressions = [

tests/storage/adapters/test_redis_adapter.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ def test_forwarding(self, mocker):
5555
adapter.incr('key1')
5656
assert redis_mock.incr.mock_calls[0] == mocker.call('some_prefix.key1', 1)
5757

58+
adapter.hincrby('key1', 'name1')
59+
assert redis_mock.hincrby.mock_calls[0] == mocker.call('some_prefix.key1', 'name1', 1)
60+
61+
adapter.hincrby('key1', 'name1', 5)
62+
assert redis_mock.hincrby.mock_calls[1] == mocker.call('some_prefix.key1', 'name1', 5)
63+
5864
adapter.getset('key1', 'new_value')
5965
assert redis_mock.getset.mock_calls[0] == mocker.call('some_prefix.key1', 'new_value')
6066

@@ -194,3 +200,9 @@ def test_forwarding(self, mocker):
194200

195201
adapter.incr('key1')
196202
assert redis_mock_2.incr.mock_calls[0] == mocker.call('some_prefix.key1', 1)
203+
204+
adapter.hincrby('key1', 'name1')
205+
assert redis_mock_2.hincrby.mock_calls[0] == mocker.call('some_prefix.key1', 'name1', 1)
206+
207+
adapter.hincrby('key1', 'name1', 5)
208+
assert redis_mock_2.hincrby.mock_calls[1] == mocker.call('some_prefix.key1', 'name1', 5)

tests/storage/test_redis.py

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33

44
import json
55
import time
6+
import unittest.mock as mock
7+
import pytest
68

7-
from splitio.client.util import get_metadata
9+
from splitio.client.util import get_metadata, SdkMetadata, get_method_constant
810
from splitio.storage.redis import RedisEventsStorage, RedisImpressionsStorage, \
9-
RedisSegmentStorage, RedisSplitStorage
11+
RedisSegmentStorage, RedisSplitStorage, RedisTelemetryStorage
12+
from splitio.storage.adapters.redis import RedisAdapter, RedisAdapterException, build
1013
from splitio.models.segments import Segment
1114
from splitio.models.impressions import Impression
1215
from splitio.models.events import Event, EventWrapper
13-
from splitio.storage.adapters.redis import RedisAdapter, RedisAdapterException
16+
from splitio.models.telemetry import MethodExceptions, MethodLatencies, TelemetryConfig
1417

1518

1619
class RedisSplitStorageTests(object):
@@ -380,3 +383,97 @@ def _raise_exc(*_):
380383
raise RedisAdapterException('something')
381384
adapter.rpush.side_effect = _raise_exc
382385
assert storage.put(events) is False
386+
387+
class RedisTelemetryStorageTests(object):
388+
"""Redis Telemetry storage test cases."""
389+
390+
def test_init(self, mocker):
391+
redis_telemetry = RedisTelemetryStorage(mocker.Mock(), mocker.Mock())
392+
assert(redis_telemetry._redis_client is not None)
393+
assert(redis_telemetry._sdk_metadata is not None)
394+
assert(isinstance(redis_telemetry._method_latencies, MethodLatencies))
395+
assert(isinstance(redis_telemetry._method_exceptions, MethodExceptions))
396+
assert(isinstance(redis_telemetry._tel_config, TelemetryConfig))
397+
assert(redis_telemetry.host_ip is not None)
398+
assert(redis_telemetry.host_name is not None)
399+
assert(redis_telemetry._make_pipe is not None)
400+
401+
@mock.patch('splitio.models.telemetry.TelemetryConfig.record_config')
402+
def test_record_config(self, mocker):
403+
redis_telemetry = RedisTelemetryStorage(mocker.Mock(), mocker.Mock())
404+
redis_telemetry.record_config(mocker.Mock(), mocker.Mock())
405+
assert(mocker.called)
406+
407+
@mock.patch('splitio.storage.adapters.redis.RedisAdapter.record_init')
408+
def test_push_config_stats(self, mocker):
409+
adapter = build({})
410+
redis_telemetry = RedisTelemetryStorage(adapter, mocker.Mock())
411+
redis_telemetry.push_config_stats()
412+
assert(mocker.called)
413+
414+
def test_format_config_stats(self, mocker):
415+
redis_telemetry = RedisTelemetryStorage(mocker.Mock(), mocker.Mock())
416+
json_value = redis_telemetry._format_config_stats()
417+
stats = redis_telemetry._tel_config.get_stats()
418+
assert(json_value == json.dumps({
419+
'aF': stats['aF'],
420+
'rF': stats['rF'],
421+
'sT': stats['sT'],
422+
'oM': stats['oM']
423+
}))
424+
425+
def test_record_active_and_redundant_factories(self, mocker):
426+
redis_telemetry = RedisTelemetryStorage(mocker.Mock(), mocker.Mock())
427+
active_factory_count = 1
428+
redundant_factory_count = 2
429+
redis_telemetry.record_active_and_redundant_factories(1, 2)
430+
assert (redis_telemetry._tel_config._active_factory_count == active_factory_count)
431+
assert (redis_telemetry._tel_config._redundant_factory_count == redundant_factory_count)
432+
433+
def test_add_latency_to_pipe(self, mocker):
434+
def _mocked_hincrby(*args, **kwargs):
435+
assert(args[1] == RedisTelemetryStorage._TELEMETRY_LATENCIES_KEY)
436+
assert(args[2][-11:] == 'treatment/0')
437+
assert(args[3] == 1)
438+
439+
adapter = build({})
440+
metadata = SdkMetadata('python-1.1.1', 'hostname', 'ip')
441+
redis_telemetry = RedisTelemetryStorage(adapter, metadata)
442+
pipe = adapter._decorated.pipeline()
443+
with mock.patch('redis.client.Pipeline.hincrby', _mocked_hincrby):
444+
redis_telemetry.add_latency_to_pipe('treatment', 20, pipe)
445+
446+
def test_record_exception(self, mocker):
447+
def _mocked_hincrby(*args, **kwargs):
448+
assert(args[1] == RedisTelemetryStorage._TELEMETRY_EXCEPTIONS_KEY)
449+
assert(args[2][-9:] == 'treatment')
450+
assert(args[3] == 1)
451+
452+
adapter = build({})
453+
metadata = SdkMetadata('python-1.1.1', 'hostname', 'ip')
454+
redis_telemetry = RedisTelemetryStorage(adapter, metadata)
455+
with mock.patch('redis.client.Pipeline.hincrby', _mocked_hincrby):
456+
with mock.patch('redis.client.Pipeline.execute') as mock_method:
457+
mock_method.return_value = [1]
458+
redis_telemetry.record_exception(get_method_constant('treatment'))
459+
460+
def test_expire_latency_keys(self, mocker):
461+
redis_telemetry = RedisTelemetryStorage(mocker.Mock(), mocker.Mock())
462+
def _mocked_method(*args, **kwargs):
463+
assert(args[1] == RedisTelemetryStorage._TELEMETRY_LATENCIES_KEY)
464+
assert(args[2] == RedisTelemetryStorage._TELEMETRY_KEY_DEFAULT_TTL)
465+
assert(args[3] == 1)
466+
assert(args[4] == 2)
467+
468+
with mock.patch('splitio.storage.redis.RedisTelemetryStorage.expire_keys', _mocked_method):
469+
redis_telemetry.expire_latency_keys(1, 2)
470+
471+
@mock.patch('redis.client.Redis.expire')
472+
def test_expire_keys(self, mocker):
473+
adapter = build({})
474+
metadata = SdkMetadata('python-1.1.1', 'hostname', 'ip')
475+
redis_telemetry = RedisTelemetryStorage(adapter, metadata)
476+
redis_telemetry.expire_keys('key', 12, 1, 2)
477+
assert(not mocker.called)
478+
redis_telemetry.expire_keys('key', 12, 2, 2)
479+
assert(mocker.called)

0 commit comments

Comments
 (0)