Skip to content

Commit 91180f5

Browse files
authored
Merge pull request #161 from splitio/fix/stackInspectionRemoval
Stack inspection performance issue
2 parents bbfbb7d + fa304f5 commit 91180f5

File tree

9 files changed

+152
-164
lines changed

9 files changed

+152
-164
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
8.1.6 (Oct 31, 2019)
2+
- Fixed input validation performance issue.
3+
14
8.1.4 (Oct 14, 2019)
25
- Added logic to fetch multiple splits at once on get_treatments/get_treatments_with_config.
36
- Added flag `ipAddressesEnabled` into config to enable/disable sending machineName and machineIp when data is posted in headers.

splitio/client/client.py

Lines changed: 100 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class Client(object): # pylint: disable=too-many-instance-attributes
1919

2020
_METRIC_GET_TREATMENT = 'sdk.getTreatment'
2121
_METRIC_GET_TREATMENTS = 'sdk.getTreatments'
22+
_METRIC_GET_TREATMENT_WITH_CONFIG = 'sdk.getTreatmentWithConfig'
23+
_METRIC_GET_TREATMENTS_WITH_CONFIG = 'sdk.getTreatmentsWithConfig'
2224

2325
def __init__(self, factory, labels_enabled=True, impression_listener=None):
2426
"""
@@ -103,39 +105,25 @@ def _evaluate_if_ready(self, matching_key, bucketing_key, feature, attributes=No
103105
attributes
104106
)
105107

106-
def get_treatment_with_config(self, key, feature, attributes=None):
107-
"""
108-
Get the treatment and config for a feature and key, with optional dictionary of attributes.
109-
110-
This method never raises an exception. If there's a problem, the appropriate log message
111-
will be generated and the method will return the CONTROL treatment.
112-
113-
:param key: The key for which to get the treatment
114-
:type key: str
115-
:param feature: The name of the feature for which to get the treatment
116-
:type feature: str
117-
:param attributes: An optional dictionary of attributes
118-
:type attributes: dict
119-
:return: The treatment for the key and feature
120-
:rtype: tuple(str, str)
121-
"""
108+
def _make_evaluation(self, key, feature, attributes, method_name, metric_name):
122109
try:
123110
if self.destroyed:
124111
self._logger.error("Client has already been destroyed - no calls possible")
125112
return CONTROL, None
126113

127114
start = int(round(time.time() * 1000))
128115

129-
matching_key, bucketing_key = input_validator.validate_key(key)
116+
matching_key, bucketing_key = input_validator.validate_key(key, method_name)
130117
feature = input_validator.validate_feature_name(
131118
feature,
132119
self.ready,
133-
self._factory._get_storage('splits') # pylint: disable=protected-access
120+
self._factory._get_storage('splits'), # pylint: disable=protected-access
121+
method_name
134122
)
135123

136124
if (matching_key is None and bucketing_key is None) \
137125
or feature is None \
138-
or not input_validator.validate_attributes(attributes):
126+
or not input_validator.validate_attributes(attributes, method_name):
139127
return CONTROL, None
140128

141129
result = self._evaluate_if_ready(matching_key, bucketing_key, feature, attributes)
@@ -150,7 +138,7 @@ def get_treatment_with_config(self, key, feature, attributes=None):
150138
start
151139
)
152140

153-
self._record_stats(impression, start, self._METRIC_GET_TREATMENT)
141+
self._record_stats([impression], start, metric_name)
154142
self._send_impression_to_listener(impression, attributes)
155143
return result['treatment'], result['configurations']
156144
except Exception: # pylint: disable=broad-except
@@ -166,80 +154,29 @@ def get_treatment_with_config(self, key, feature, attributes=None):
166154
bucketing_key,
167155
start
168156
)
169-
self._record_stats(impression, start, self._METRIC_GET_TREATMENT)
157+
self._record_stats([impression], start, metric_name)
170158
self._send_impression_to_listener(impression, attributes)
171159
except Exception: # pylint: disable=broad-except
172160
self._logger.error('Error reporting impression into get_treatment exception block')
173161
self._logger.debug('Error: ', exc_info=True)
174162
return CONTROL, None
175163

176-
def get_treatment(self, key, feature, attributes=None):
177-
"""
178-
Get the treatment for a feature and key, with an optional dictionary of attributes.
179-
180-
This method never raises an exception. If there's a problem, the appropriate log message
181-
will be generated and the method will return the CONTROL treatment.
182-
183-
:param key: The key for which to get the treatment
184-
:type key: str
185-
:param feature: The name of the feature for which to get the treatment
186-
:type feature: str
187-
:param attributes: An optional dictionary of attributes
188-
:type attributes: dict
189-
:return: The treatment for the key and feature
190-
:rtype: str
191-
"""
192-
treatment, _ = self.get_treatment_with_config(key, feature, attributes)
193-
return treatment
194-
195-
def _evaluate_features_if_ready(self, matching_key, bucketing_key, features, attributes=None):
196-
if not self.ready:
197-
return {
198-
feature: {
199-
'treatment': CONTROL,
200-
'configurations': None,
201-
'impression': {'label': Label.NOT_READY, 'change_number': None}
202-
}
203-
for feature in features
204-
}
205-
206-
return self._evaluator.evaluate_features(
207-
features,
208-
matching_key,
209-
bucketing_key,
210-
attributes
211-
)
212-
213-
def get_treatments_with_config(self, key, features, attributes=None):
214-
"""
215-
Evaluate multiple features and return a dict with feature -> (treatment, config).
216-
217-
Get the treatments for a list of features considering a key, with an optional dictionary of
218-
attributes. This method never raises an exception. If there's a problem, the appropriate
219-
log message will be generated and the method will return the CONTROL treatment.
220-
:param key: The key for which to get the treatment
221-
:type key: str
222-
:param features: Array of the names of the features for which to get the treatment
223-
:type feature: list
224-
:param attributes: An optional dictionary of attributes
225-
:type attributes: dict
226-
:return: Dictionary with the result of all the features provided
227-
:rtype: dict
228-
"""
164+
def _make_evaluations(self, key, features, attributes, method_name, metric_name):
229165
if self.destroyed:
230166
self._logger.error("Client has already been destroyed - no calls possible")
231-
return input_validator.generate_control_treatments(features)
167+
return input_validator.generate_control_treatments(features, method_name)
232168

233169
start = int(round(time.time() * 1000))
234170

235-
matching_key, bucketing_key = input_validator.validate_key(key)
171+
matching_key, bucketing_key = input_validator.validate_key(key, method_name)
236172
if matching_key is None and bucketing_key is None:
237-
return input_validator.generate_control_treatments(features)
173+
return input_validator.generate_control_treatments(features, method_name)
238174

239-
if input_validator.validate_attributes(attributes) is False:
240-
return input_validator.generate_control_treatments(features)
175+
if input_validator.validate_attributes(attributes, method_name) is False:
176+
return input_validator.generate_control_treatments(features, method_name)
241177

242178
features, missing = input_validator.validate_features_get_treatments(
179+
method_name,
243180
features,
244181
self.ready,
245182
self._factory._get_storage('splits') # pylint: disable=protected-access
@@ -269,8 +206,8 @@ def get_treatments_with_config(self, key, features, attributes=None):
269206
treatments[feature] = (result['treatment'], result['configurations'])
270207

271208
except Exception: # pylint: disable=broad-except
272-
self._logger.error('get_treatments: An exception occured when evaluating '
273-
'feature ' + feature + ' returning CONTROL.')
209+
self._logger.error('%s: An exception occured when evaluating '
210+
'feature %s returning CONTROL.' % (method_name, feature))
274211
treatments[feature] = CONTROL, None
275212
self._logger.debug('Error: ', exc_info=True)
276213
continue
@@ -282,15 +219,91 @@ def get_treatments_with_config(self, key, features, attributes=None):
282219
for impression in bulk_impressions:
283220
self._send_impression_to_listener(impression, attributes)
284221
except Exception: # pylint: disable=broad-except
285-
self._logger.error('get_treatments: An exception when trying to store '
286-
'impressions.')
222+
self._logger.error('%s: An exception when trying to store '
223+
'impressions.' % method_name)
287224
self._logger.debug('Error: ', exc_info=True)
288225

289226
return treatments
290227
except Exception: # pylint: disable=broad-except
291228
self._logger.error('Error getting treatment for features')
292229
self._logger.debug('Error: ', exc_info=True)
293-
return input_validator.generate_control_treatments(list(features))
230+
return input_validator.generate_control_treatments(list(features), method_name)
231+
232+
def _evaluate_features_if_ready(self, matching_key, bucketing_key, features, attributes=None):
233+
if not self.ready:
234+
return {
235+
feature: {
236+
'treatment': CONTROL,
237+
'configurations': None,
238+
'impression': {'label': Label.NOT_READY, 'change_number': None}
239+
}
240+
for feature in features
241+
}
242+
243+
return self._evaluator.evaluate_features(
244+
features,
245+
matching_key,
246+
bucketing_key,
247+
attributes
248+
)
249+
250+
def get_treatment_with_config(self, key, feature, attributes=None):
251+
"""
252+
Get the treatment and config for a feature and key, with optional dictionary of attributes.
253+
254+
This method never raises an exception. If there's a problem, the appropriate log message
255+
will be generated and the method will return the CONTROL treatment.
256+
257+
:param key: The key for which to get the treatment
258+
:type key: str
259+
:param feature: The name of the feature for which to get the treatment
260+
:type feature: str
261+
:param attributes: An optional dictionary of attributes
262+
:type attributes: dict
263+
:return: The treatment for the key and feature
264+
:rtype: tuple(str, str)
265+
"""
266+
return self._make_evaluation(key, feature, attributes, 'get_treatment_with_config',
267+
self._METRIC_GET_TREATMENT_WITH_CONFIG)
268+
269+
def get_treatment(self, key, feature, attributes=None):
270+
"""
271+
Get the treatment for a feature and key, with an optional dictionary of attributes.
272+
273+
This method never raises an exception. If there's a problem, the appropriate log message
274+
will be generated and the method will return the CONTROL treatment.
275+
276+
:param key: The key for which to get the treatment
277+
:type key: str
278+
:param feature: The name of the feature for which to get the treatment
279+
:type feature: str
280+
:param attributes: An optional dictionary of attributes
281+
:type attributes: dict
282+
:return: The treatment for the key and feature
283+
:rtype: str
284+
"""
285+
treatment, _ = self._make_evaluation(key, feature, attributes, 'get_treatment',
286+
self._METRIC_GET_TREATMENT)
287+
return treatment
288+
289+
def get_treatments_with_config(self, key, features, attributes=None):
290+
"""
291+
Evaluate multiple features and return a dict with feature -> (treatment, config).
292+
293+
Get the treatments for a list of features considering a key, with an optional dictionary of
294+
attributes. This method never raises an exception. If there's a problem, the appropriate
295+
log message will be generated and the method will return the CONTROL treatment.
296+
:param key: The key for which to get the treatment
297+
:type key: str
298+
:param features: Array of the names of the features for which to get the treatment
299+
:type feature: list
300+
:param attributes: An optional dictionary of attributes
301+
:type attributes: dict
302+
:return: Dictionary with the result of all the features provided
303+
:rtype: dict
304+
"""
305+
return self._make_evaluations(key, features, attributes, 'get_treatments_with_config',
306+
self._METRIC_GET_TREATMENTS_WITH_CONFIG)
294307

295308
def get_treatments(self, key, features, attributes=None):
296309
"""
@@ -308,7 +321,8 @@ def get_treatments(self, key, features, attributes=None):
308321
:return: Dictionary with the result of all the features provided
309322
:rtype: dict
310323
"""
311-
with_config = self.get_treatments_with_config(key, features, attributes)
324+
with_config = self._make_evaluations(key, features, attributes, 'get_treatments',
325+
self._METRIC_GET_TREATMENTS)
312326
return {feature: result[0] for (feature, result) in six.iteritems(with_config)}
313327

314328
def _build_impression( # pylint: disable=too-many-arguments
@@ -346,10 +360,7 @@ def _record_stats(self, impressions, start, operation):
346360
"""
347361
try:
348362
end = int(round(time.time() * 1000))
349-
if operation == self._METRIC_GET_TREATMENT:
350-
self._impressions_storage.put([impressions])
351-
else:
352-
self._impressions_storage.put(impressions)
363+
self._impressions_storage.put(impressions)
353364
self._telemetry_storage.inc_latency(operation, get_latency_bucket_index(end - start))
354365
except Exception: # pylint: disable=broad-except
355366
self._logger.error('Error recording impressions and metrics')

0 commit comments

Comments
 (0)