Skip to content

Commit b0687c6

Browse files
committed
Telemetry implementation for in-memory
1 parent 85a5b6d commit b0687c6

File tree

22 files changed

+1626
-469
lines changed

22 files changed

+1626
-469
lines changed

splitio/api/auth.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ def authenticate(self):
4242
'auth',
4343
'/v2/auth',
4444
self._apikey,
45-
extra_headers=self._metadata
45+
extra_headers=self._metadata,
46+
metric_name='token'
4647
)
4748
if 200 <= response.status_code < 300:
4849
payload = json.loads(response.body)

splitio/api/client.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class HttpClient(object):
2727
AUTH_URL = 'https://auth.split.io/api'
2828
TELEMETRY_URL = 'https://telemetry.split.io/api'
2929

30-
def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None):
30+
def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, telemetry_url=None, telemetry_runtime_producer=None):
3131
"""
3232
Class constructor.
3333
@@ -49,6 +49,7 @@ def __init__(self, timeout=None, sdk_url=None, events_url=None, auth_url=None, t
4949
'auth': auth_url if auth_url is not None else self.AUTH_URL,
5050
'telemetry': telemetry_url if telemetry_url is not None else self.TELEMETRY_URL,
5151
}
52+
self._telemetry_runtime_producer = telemetry_runtime_producer
5253

5354
def _build_url(self, server, path):
5455
"""
@@ -77,7 +78,7 @@ def _build_basic_headers(apikey):
7778
'Authorization': "Bearer %s" % apikey
7879
}
7980

80-
def get(self, server, path, apikey, query=None, extra_headers=None): # pylint: disable=too-many-arguments
81+
def get(self, server, path, apikey, query=None, extra_headers=None, metric_name=None): # pylint: disable=too-many-arguments
8182
"""
8283
Issue a get request.
8384
@@ -106,11 +107,22 @@ def get(self, server, path, apikey, query=None, extra_headers=None): # pylint:
106107
headers=headers,
107108
timeout=self._timeout
108109
)
109-
return HttpResponse(response.status_code, response.text)
110+
elapsed = response.elapsed.total_seconds()
111+
response = HttpResponse(response.status_code, response.text)
112+
self._telemetry_runtime_producer.record_sync_latency(metric_name, elapsed)
113+
if not 200 <= response.status_code < 300:
114+
self._telemetry_runtime_producer.record_sync_error(metric_name, response.status_code)
115+
if metric_name == 'token':
116+
self._telemetry_runtime_producer.record_auth_rejections()
117+
else:
118+
self._telemetry_runtime_producer.record_suceessful_sync(metric_name, round(1000 * elapsed))
119+
if metric_name == 'token':
120+
self._telemetry_runtime_producer.record_token_refreshes()
121+
return response
110122
except Exception as exc: # pylint: disable=broad-except
111123
raise HttpClientException('requests library is throwing exceptions') from exc
112124

113-
def post(self, server, path, apikey, body, query=None, extra_headers=None): # pylint: disable=too-many-arguments
125+
def post(self, server, path, apikey, body, query=None, extra_headers=None, metric_name=None): # pylint: disable=too-many-arguments
114126
"""
115127
Issue a POST request.
116128
@@ -143,6 +155,13 @@ def post(self, server, path, apikey, body, query=None, extra_headers=None): # p
143155
headers=headers,
144156
timeout=self._timeout
145157
)
146-
return HttpResponse(response.status_code, response.text)
158+
elapsed = response.elapsed.total_seconds()
159+
response = HttpResponse(response.status_code, response.text)
160+
self._telemetry_runtime_producer.record_sync_latency(metric_name, elapsed)
161+
if not 200 <= response.status_code < 300:
162+
self._telemetry_runtime_producer.record_sync_error(metric_name, response.status_code)
163+
else:
164+
self._telemetry_runtime_producer.record_suceessful_sync(metric_name, round(1000 * elapsed))
165+
return response
147166
except Exception as exc: # pylint: disable=broad-except
148167
raise HttpClientException('requests library is throwing exceptions') from exc

splitio/api/events.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ def flush_events(self, events):
6767
'/events/bulk',
6868
self._apikey,
6969
body=bulk,
70-
extra_headers=self._metadata
70+
extra_headers=self._metadata,
71+
metric_name='event'
7172
)
7273
if not 200 <= response.status_code < 300:
7374
raise APIException(response.body, response.status_code)

splitio/api/impressions.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ def flush_impressions(self, impressions):
9797
'/testImpressions/bulk',
9898
self._apikey,
9999
body=bulk,
100-
extra_headers=self._metadata
100+
extra_headers=self._metadata,
101+
metric_name='impression'
101102
)
102103
if not 200 <= response.status_code < 300:
103104
raise APIException(response.body, response.status_code)
@@ -122,7 +123,8 @@ def flush_counters(self, counters):
122123
'/testImpressions/count',
123124
self._apikey,
124125
body=bulk,
125-
extra_headers=self._metadata
126+
extra_headers=self._metadata,
127+
metric_name='im'
126128
)
127129
if not 200 <= response.status_code < 300:
128130
raise APIException(response.body, response.status_code)

splitio/api/segments.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ def fetch_segment(self, segment_name, change_number, fetch_options):
5454
self._apikey,
5555
extra_headers=extra_headers,
5656
query=query,
57+
metric_name='segment'
5758
)
58-
5959
if 200 <= response.status_code < 300:
6060
return json.loads(response.body)
6161
else:

splitio/api/splits.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def fetch_splits(self, change_number, fetch_options):
5050
self._apikey,
5151
extra_headers=extra_headers,
5252
query=query,
53+
metric_name='split'
5354
)
5455
if 200 <= response.status_code < 300:
5556
return json.loads(response.body)

splitio/api/telemetry.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"""Impressions API module."""
2-
32
import logging
43

54
from splitio.api import APIException
@@ -34,18 +33,19 @@ def record_unique_keys(self, uniques):
3433
try:
3534
response = self._client.post(
3635
'telemetry',
37-
'/keys/ss',
36+
'/v1/keys/ss',
3837
self._apikey,
3938
body=uniques,
40-
extra_headers=self._metadata
39+
extra_headers=self._metadata,
40+
metric_name='telemetry'
4141
)
4242
if not 200 <= response.status_code < 300:
4343
raise APIException(response.body, response.status_code)
4444
except HttpClientException as exc:
45-
_LOGGER.error(
45+
_LOGGER.info(
4646
'Error posting unique keys because an exception was raised by the HTTPClient'
4747
)
48-
_LOGGER.debug('Error: ', exc_info=True)
48+
_LOGGER.info('Error: ', exc_info=True)
4949
raise APIException('Unique keys not flushed properly.') from exc
5050

5151
def record_init(self, configs):
@@ -57,41 +57,43 @@ def record_init(self, configs):
5757
"""
5858
try:
5959
response = self._client.post(
60-
'metrics',
61-
'/config',
60+
'telemetry',
61+
'/v1/metrics/config',
6262
self._apikey,
6363
body=configs,
64-
extra_headers=self._metadata
64+
extra_headers=self._metadata,
65+
metric_name='telemetry'
6566
)
6667
if not 200 <= response.status_code < 300:
6768
raise APIException(response.body, response.status_code)
6869
except HttpClientException as exc:
69-
_LOGGER.error(
70+
_LOGGER.info(
7071
'Error posting init config because an exception was raised by the HTTPClient'
7172
)
72-
_LOGGER.debug('Error: ', exc_info=True)
73+
_LOGGER.info('Error: ', exc_info=True)
7374
raise APIException('Init config data not flushed properly.') from exc
7475

7576
def record_stats(self, stats):
7677
"""
7778
Send runtime stats to the backend.
7879
79-
:param configs: configs
80+
:param stats: stats
8081
:type json
8182
"""
8283
try:
8384
response = self._client.post(
84-
'metrics',
85-
'/usage',
85+
'telemetry',
86+
'/v1/metrics/usage',
8687
self._apikey,
8788
body=stats,
88-
extra_headers=self._metadata
89+
extra_headers=self._metadata,
90+
metric_name='telemetry'
8991
)
9092
if not 200 <= response.status_code < 300:
9193
raise APIException(response.body, response.status_code)
9294
except HttpClientException as exc:
93-
_LOGGER.error(
95+
_LOGGER.info(
9496
'Error posting runtime stats because an exception was raised by the HTTPClient'
9597
)
96-
_LOGGER.debug('Error: ', exc_info=True)
98+
_LOGGER.info('Error: ', exc_info=True)
9799
raise APIException('Runtime stats not flushed properly.') from exc

splitio/client/client.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class Client(object): # pylint: disable=too-many-instance-attributes
2121
_METRIC_GET_TREATMENT_WITH_CONFIG = 'sdk.getTreatmentWithConfig'
2222
_METRIC_GET_TREATMENTS_WITH_CONFIG = 'sdk.getTreatmentsWithConfig'
2323

24-
def __init__(self, factory, recorder, labels_enabled=True):
24+
def __init__(self, factory, recorder, labels_enabled=True, telemetry_evaluation_producer=None):
2525
"""
2626
Construct a Client instance.
2727
@@ -44,6 +44,7 @@ def __init__(self, factory, recorder, labels_enabled=True):
4444
self._segment_storage = factory._get_storage('segments') # pylint: disable=protected-access
4545
self._events_storage = factory._get_storage('events') # pylint: disable=protected-access
4646
self._evaluator = Evaluator(self._split_storage, self._segment_storage, self._splitter)
47+
self._telemetry_evaluation_producer = telemetry_evaluation_producer
4748

4849
def destroy(self):
4950
"""
@@ -116,12 +117,13 @@ def _make_evaluation(self, key, feature, attributes, method_name, metric_name):
116117
bucketing_key,
117118
utctime_ms(),
118119
)
119-
120120
self._record_stats([(impression, attributes)], start, metric_name)
121+
self._telemetry_evaluation_producer.record_latency(method_name[4:], 1000 * (int(round(time.time() * 1000)) - start))
121122
return result['treatment'], result['configurations']
122123
except Exception: # pylint: disable=broad-except
123124
_LOGGER.error('Error getting treatment for feature')
124125
_LOGGER.debug('Error: ', exc_info=True)
126+
self._telemetry_evaluation_producer.record_exception(method_name[4:])
125127
try:
126128
impression = self._build_impression(
127129
matching_key,
@@ -204,9 +206,12 @@ def _make_evaluations(self, key, features, attributes, method_name, metric_name)
204206
_LOGGER.error('%s: An exception when trying to store '
205207
'impressions.' % method_name)
206208
_LOGGER.debug('Error: ', exc_info=True)
209+
self._telemetry_evaluation_producer.record_exception(method_name[4:])
207210

211+
self._telemetry_evaluation_producer.record_latency(method_name[4:], 1000 * (int(round(time.time() * 1000)) - start))
208212
return treatments
209213
except Exception: # pylint: disable=broad-except
214+
self._telemetry_evaluation_producer.record_exception(method_name)
210215
_LOGGER.error('Error getting treatment for features')
211216
_LOGGER.debug('Error: ', exc_info=True)
212217
return input_validator.generate_control_treatments(list(features), method_name)
@@ -369,6 +374,7 @@ def track(self, key, traffic_type, event_type, value=None, properties=None):
369374
_LOGGER.error("Client is not ready - no calls possible")
370375
return False
371376

377+
start = int(round(time.time() * 1000))
372378
key = input_validator.validate_track_key(key)
373379
event_type = input_validator.validate_event_type(event_type)
374380
should_validate_existance = self.ready and self._factory._apikey != 'localhost' # pylint: disable=protected-access
@@ -393,7 +399,13 @@ def track(self, key, traffic_type, event_type, value=None, properties=None):
393399
timestamp=utctime_ms(),
394400
properties=properties,
395401
)
396-
return self._recorder.record_track_stats([EventWrapper(
402+
403+
return_flag = self._recorder.record_track_stats([EventWrapper(
397404
event=event,
398405
size=size,
399406
)])
407+
self._telemetry_evaluation_producer.record_latency('track', 1000 * (int(round(time.time() * 1000)) - start))
408+
if not return_flag:
409+
self._telemetry_evaluation_producer.record_exception('track')
410+
411+
return return_flag

0 commit comments

Comments
 (0)