Skip to content
4 changes: 2 additions & 2 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@
'edx_django_utils.monitoring.middleware.MonitoringMemoryMiddleware',

# Cookie monitoring
'openedx.core.lib.request_utils.CookieMetricsMiddleware',
'openedx.core.lib.request_utils.CookieMonitoringMiddleware',

'openedx.core.djangoapps.header_control.middleware.HeaderControlMiddleware',
'django.middleware.cache.UpdateCacheMiddleware',
Expand Down Expand Up @@ -724,7 +724,7 @@
# Enables force_django_cache_miss functionality for TieredCache.
'edx_django_utils.cache.middleware.TieredCacheMiddleware',

# Outputs monitoring metrics for a request.
# Adds monitoring attributes to requests.
'edx_rest_framework_extensions.middleware.RequestCustomAttributesMiddleware',

'edx_rest_framework_extensions.auth.jwt.middleware.EnsureJWTAuthSettingsMiddleware',
Expand Down
22 changes: 11 additions & 11 deletions lms/djangoapps/courseware/user_state_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,23 +106,23 @@ def _get_student_modules(self, username, block_keys):
usage_key = student_module.module_state_key.map_into_course(student_module.course_id)
yield (student_module, usage_key)

def _nr_metric_name(self, function_name, stat_name, block_type=None):
def _nr_attribute_name(self, function_name, stat_name, block_type=None):
"""
Return a metric name (string) representing the provided descriptors.
The return value is directly usable for custom NR metrics.
Return an attribute name (string) representing the provided descriptors.
The return value is directly usable for New Relic custom attributes.
"""
if block_type is None:
metric_name_parts = ['xb_user_state', function_name, stat_name]
attribute_name_parts = ['xb_user_state', function_name, stat_name]
else:
metric_name_parts = ['xb_user_state', function_name, block_type, stat_name]
return '.'.join(metric_name_parts)
attribute_name_parts = ['xb_user_state', function_name, block_type, stat_name]
return '.'.join(attribute_name_parts)

def _nr_stat_accumulate(self, function_name, stat_name, value):
"""
Accumulate arbitrary NR stats (not specific to block types).
"""
monitoring_utils.accumulate(
self._nr_metric_name(function_name, stat_name),
self._nr_attribute_name(function_name, stat_name),
value
)

Expand All @@ -137,11 +137,11 @@ def _nr_block_stat_accumulate(self, function_name, block_type, stat_name, value)
Accumulate NR stats related to block types.
"""
monitoring_utils.accumulate(
self._nr_metric_name(function_name, stat_name),
self._nr_attribute_name(function_name, stat_name),
value,
)
monitoring_utils.accumulate(
self._nr_metric_name(function_name, stat_name, block_type=block_type),
self._nr_attribute_name(function_name, stat_name, block_type=block_type),
value,
)

Expand Down Expand Up @@ -190,7 +190,7 @@ def get_many(self, username, block_keys, scope=Scope.user_state, fields=None):
if state == {}:
continue

# collect statistics for metric reporting
# collect statistics for custom attribute reporting
self._nr_block_stat_increment('get_many', usage_key.block_type, 'blocks_out')
self._nr_block_stat_accumulate('get_many', usage_key.block_type, 'size', state_length)
total_block_count += 1
Expand All @@ -204,7 +204,7 @@ def get_many(self, username, block_keys, scope=Scope.user_state, fields=None):
}
yield XBlockUserState(username, usage_key, state, module.modified, scope)

# The rest of this method exists only to report metrics.
# The rest of this method exists only to report custom attributes.
finish_time = time()
duration = (finish_time - evt_time) * 1000 # milliseconds
self._nr_stat_accumulate('get_many', 'duration', duration)
Expand Down
4 changes: 2 additions & 2 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1684,7 +1684,7 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring
'edx_django_utils.monitoring.code_owner.middleware.CodeOwnerMonitoringMiddleware',

# Cookie monitoring
'openedx.core.lib.request_utils.CookieMetricsMiddleware',
'openedx.core.lib.request_utils.CookieMonitoringMiddleware',

'mobile_api.middleware.AppVersionUpgrade',
'openedx.core.djangoapps.header_control.middleware.HeaderControlMiddleware',
Expand Down Expand Up @@ -1763,7 +1763,7 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring
# Enables force_django_cache_miss functionality for TieredCache.
'edx_django_utils.cache.middleware.TieredCacheMiddleware',

# Outputs monitoring metrics for a request.
# Adds monitoring attributes to requests.
'edx_rest_framework_extensions.middleware.RequestCustomAttributesMiddleware',

'edx_rest_framework_extensions.auth.jwt.middleware.EnsureJWTAuthSettingsMiddleware',
Expand Down
4 changes: 2 additions & 2 deletions openedx/core/djangoapps/oauth_dispatch/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def test_jwt_access_token_from_parameter_not_header(self, client_attr):
)
@ddt.unpack
@patch('edx_django_utils.monitoring.set_custom_attribute')
def test_access_token_metrics(self, token_type, expected_token_type, mock_set_custom_attribute):
def test_access_token_attributes(self, token_type, expected_token_type, mock_set_custom_attribute):
response = self._post_request(self.user, self.dot_app, token_type=token_type)
self.assertEqual(response.status_code, 200)
expected_calls = [
Expand All @@ -244,7 +244,7 @@ def test_access_token_metrics(self, token_type, expected_token_type, mock_set_cu
mock_set_custom_attribute.assert_has_calls(expected_calls, any_order=True)

@patch('edx_django_utils.monitoring.set_custom_attribute')
def test_access_token_metrics_for_bad_request(self, mock_set_custom_attribute):
def test_access_token_attributes_for_bad_request(self, mock_set_custom_attribute):
grant_type = dot_models.Application.GRANT_PASSWORD
invalid_body = {
'grant_type': grant_type.replace('-', '_'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def test_resolver_send(self, mock_ace):
@ddt.data(1, 10, 100)
@patch.object(tasks, 'ace')
@patch.object(resolvers, 'set_custom_attribute')
def test_schedule_bin(self, schedule_count, mock_metric, mock_ace):
def test_schedule_bin(self, schedule_count, mock_attribute, mock_ace):
with patch.object(self.task, 'async_send_task') as mock_schedule_send:
current_day, offset, target_day, upgrade_deadline = self._get_dates()
schedules = [
Expand Down Expand Up @@ -226,7 +226,7 @@ def test_schedule_bin(self, schedule_count, mock_metric, mock_ace):
site_id=self.site_config.site.id, target_day_str=target_day_str, day_offset=offset, bin_num=b,
))

num_schedules = mock_metric.call_args[0][1]
num_schedules = mock_attribute.call_args[0][1]
if b in bins_in_use:
self.assertGreater(num_schedules, 0)
else:
Expand Down
58 changes: 29 additions & 29 deletions openedx/core/djangoapps/waffle_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
or new Open edX releases. For help with this, see:
openedx/core/djangoapps/waffle_utils/docs/decisions/0001-refactor-waffle-flag-default.rst

Also see ``WAFFLE_FLAG_CUSTOM_METRICS`` and docstring for _set_waffle_flag_metric
Also see ``WAFFLE_FLAG_CUSTOM_ATTRIBUTES`` and docstring for _set_waffle_flag_attribute
for temporarily instrumenting/monitoring waffle flag usage.

"""
Expand Down Expand Up @@ -285,12 +285,12 @@ def is_flag_active(self, flag_name, check_before_waffle_callback=None):
if value is not None:
# Do not cache value for the callback, because the key might be different.
# The callback needs to handle its own caching if it wants it.
self._set_waffle_flag_metric(namespaced_flag_name, value)
self._set_waffle_flag_attribute(namespaced_flag_name, value)
return value

value = self._cached_flags.get(namespaced_flag_name)
if value is not None:
self._set_waffle_flag_metric(namespaced_flag_name, value)
self._set_waffle_flag_attribute(namespaced_flag_name, value)
return value

request = crum.get_current_request()
Expand All @@ -301,14 +301,14 @@ def is_flag_active(self, flag_name, check_before_waffle_callback=None):
# in a normal request context. This case seems to occur when
# a page redirects to a 404, or for celery workers.
value = self._is_flag_active_for_everyone(namespaced_flag_name)
self._set_waffle_flag_metric(namespaced_flag_name, value)
self._set_waffle_flag_attribute(namespaced_flag_name, value)
set_custom_attribute('warn_flag_no_request_return_value', value)
return value

value = flag_is_active(request, namespaced_flag_name)
self._cached_flags[namespaced_flag_name] = value

self._set_waffle_flag_metric(namespaced_flag_name, value)
self._set_waffle_flag_attribute(namespaced_flag_name, value)
return value

def _is_flag_active_for_everyone(self, namespaced_flag_name):
Expand All @@ -325,14 +325,14 @@ def _is_flag_active_for_everyone(self, namespaced_flag_name):
except Flag.DoesNotExist:
return False

def _set_waffle_flag_metric(self, name, value):
def _set_waffle_flag_attribute(self, name, value):
"""
For any flag name in _WAFFLE_FLAG_CUSTOM_METRIC_SET, add name/value
to cached values and set custom metric if the value changed.
For any flag name in _WAFFLE_FLAG_CUSTOM_ATTRIBUTE_SET, add name/value
to cached values and set custom attribute if the value changed.

The name of the custom metric will have the prefix ``flag_`` and the
The name of the custom attribute will have the prefix ``flag_`` and the
suffix will match the name of the flag.
The value of the custom metric could be False, True, or Both.
The value of the custom attribute could be False, True, or Both.

The value Both would mean that the flag had both a True and False
value at different times during the transaction. This is most
Expand All @@ -347,49 +347,49 @@ def _set_waffle_flag_metric(self, name, value):
WHERE flag_my.waffle.flag IS NOT NULL
FACET appName, flag_my.waffle.flag

Important: Remember to configure ``WAFFLE_FLAG_CUSTOM_METRICS`` for
Important: Remember to configure ``WAFFLE_FLAG_CUSTOM_ATTRIBUTES`` for
LMS, Studio and Workers in order to see waffle flag usage in all
edx-platform environments.

"""
if name not in _WAFFLE_FLAG_CUSTOM_METRIC_SET:
if name not in _WAFFLE_FLAG_CUSTOM_ATTRIBUTE_SET:
return

flag_metric_data = self._get_request_cache().setdefault('flag_metric', {})
flag_attribute_data = self._get_request_cache().setdefault('flag_attribute', {})
is_value_change = False
if name not in flag_metric_data:
flag_metric_data[name] = str(value)
if name not in flag_attribute_data:
flag_attribute_data[name] = str(value)
is_value_change = True
else:
if flag_metric_data[name] != str(value):
flag_metric_data[name] = 'Both'
if flag_attribute_data[name] != str(value):
flag_attribute_data[name] = 'Both'
is_value_change = True

if is_value_change:
metric_name = 'flag_{}'.format(name)
set_custom_attribute(metric_name, flag_metric_data[name])
attribute_name = 'flag_{}'.format(name)
set_custom_attribute(attribute_name, flag_attribute_data[name])


def _get_waffle_flag_custom_metrics_set():
def _get_waffle_flag_custom_attributes_set():
"""
Returns a set based on the Django setting WAFFLE_FLAG_CUSTOM_METRICS (list).
Returns a set based on the Django setting WAFFLE_FLAG_CUSTOM_ATTRIBUTES (list).
"""
waffle_flag_custom_metrics = getattr(settings, _WAFFLE_FLAG_CUSTOM_METRICS, None)
waffle_flag_custom_metrics = waffle_flag_custom_metrics if waffle_flag_custom_metrics else []
return set(waffle_flag_custom_metrics)
waffle_flag_custom_attributes = getattr(settings, _WAFFLE_FLAG_CUSTOM_ATTRIBUTES, None)
waffle_flag_custom_attributes = waffle_flag_custom_attributes if waffle_flag_custom_attributes else []
return set(waffle_flag_custom_attributes)

# .. toggle_name: WAFFLE_FLAG_CUSTOM_METRICS
# .. toggle_name: WAFFLE_FLAG_CUSTOM_ATTRIBUTES
# .. toggle_implementation: DjangoSetting
# .. toggle_default: False
# .. toggle_description: A list of waffle flag to track with custom metrics having
# .. toggle_description: A list of waffle flags to track with custom attributes having
# values of (True, False, or Both).
# .. toggle_use_cases: opt_in
# .. toggle_creation_date: 2020-06-17
# .. toggle_warnings: Intent is for temporary research (1 day - several weeks) of a flag's usage.
_WAFFLE_FLAG_CUSTOM_METRICS = 'WAFFLE_FLAG_CUSTOM_METRICS'
_WAFFLE_FLAG_CUSTOM_ATTRIBUTES = 'WAFFLE_FLAG_CUSTOM_ATTRIBUTES'

# set of waffle flags that should be instrumented with custom metrics
_WAFFLE_FLAG_CUSTOM_METRIC_SET = _get_waffle_flag_custom_metrics_set()
# set of waffle flags that should be instrumented with custom attributes
_WAFFLE_FLAG_CUSTOM_ATTRIBUTE_SET = _get_waffle_flag_custom_attributes_set()


class WaffleFlag(object):
Expand Down
36 changes: 18 additions & 18 deletions openedx/core/djangoapps/waffle_utils/tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from waffle.testutils import override_flag

from .. import (
_get_waffle_flag_custom_metrics_set,
_get_waffle_flag_custom_attributes_set,
CourseWaffleFlag,
WaffleFlagNamespace,
WaffleSwitchNamespace,
Expand Down Expand Up @@ -46,7 +46,7 @@ def setUp(self):
crum.set_current_request(request)
RequestCache.clear_all_namespaces()

@override_settings(WAFFLE_FLAG_CUSTOM_METRICS=[NAMESPACED_FLAG_NAME])
@override_settings(WAFFLE_FLAG_CUSTOM_ATTRIBUTES=[NAMESPACED_FLAG_NAME])
@patch('openedx.core.djangoapps.waffle_utils.set_custom_attribute')
@ddt.data(
{'course_override': WaffleFlagCourseOverrideModel.ALL_CHOICES.on, 'waffle_enabled': False, 'result': True},
Expand All @@ -60,8 +60,8 @@ def test_course_waffle_flag(self, data, mock_set_custom_attribute):
for a course.
"""
with patch(
'openedx.core.djangoapps.waffle_utils._WAFFLE_FLAG_CUSTOM_METRIC_SET',
_get_waffle_flag_custom_metrics_set(),
'openedx.core.djangoapps.waffle_utils._WAFFLE_FLAG_CUSTOM_ATTRIBUTE_SET',
_get_waffle_flag_custom_attributes_set(),
):
with patch.object(WaffleFlagCourseOverrideModel, 'override_value', return_value=data['course_override']):
with override_flag(self.NAMESPACED_FLAG_NAME, active=data['waffle_enabled']):
Expand All @@ -74,7 +74,7 @@ def test_course_waffle_flag(self, data, mock_set_custom_attribute):
self.TEST_COURSE_KEY
)

self._assert_waffle_flag_metric(mock_set_custom_attribute, expected_flag_value=str(data['result']))
self._assert_waffle_flag_attribute(mock_set_custom_attribute, expected_flag_value=str(data['result']))
mock_set_custom_attribute.reset_mock()

# check flag for a second course
Expand All @@ -90,9 +90,9 @@ def test_course_waffle_flag(self, data, mock_set_custom_attribute):
self.assertEqual(self.TEST_COURSE_FLAG.is_enabled(self.TEST_COURSE_2_KEY), second_value)

expected_flag_value = None if second_value == data['result'] else 'Both'
self._assert_waffle_flag_metric(mock_set_custom_attribute, expected_flag_value=expected_flag_value)
self._assert_waffle_flag_attribute(mock_set_custom_attribute, expected_flag_value=expected_flag_value)

@override_settings(WAFFLE_FLAG_CUSTOM_METRICS=[NAMESPACED_FLAG_NAME])
@override_settings(WAFFLE_FLAG_CUSTOM_ATTRIBUTES=[NAMESPACED_FLAG_NAME])
@patch('openedx.core.djangoapps.waffle_utils.set_custom_attribute')
def test_undefined_waffle_flag(self, mock_set_custom_attribute):
"""
Expand All @@ -105,8 +105,8 @@ def test_undefined_waffle_flag(self, mock_set_custom_attribute):
)

with patch(
'openedx.core.djangoapps.waffle_utils._WAFFLE_FLAG_CUSTOM_METRIC_SET',
_get_waffle_flag_custom_metrics_set(),
'openedx.core.djangoapps.waffle_utils._WAFFLE_FLAG_CUSTOM_ATTRIBUTE_SET',
_get_waffle_flag_custom_attributes_set(),
):
with patch.object(
WaffleFlagCourseOverrideModel,
Expand All @@ -122,7 +122,7 @@ def test_undefined_waffle_flag(self, mock_set_custom_attribute):
self.TEST_COURSE_KEY
)

self._assert_waffle_flag_metric(
self._assert_waffle_flag_attribute(
mock_set_custom_attribute,
expected_flag_value=str(False),
)
Expand Down Expand Up @@ -153,19 +153,19 @@ def test_without_request_and_everyone_active_waffle(self):
self.assertEqual(test_course_flag.is_enabled(self.TEST_COURSE_KEY), True)

@ddt.data(
{'expected_count': 0, 'waffle_flag_metric_setting': None},
{'expected_count': 1, 'waffle_flag_metric_setting': [NAMESPACED_FLAG_NAME]},
{'expected_count': 2, 'waffle_flag_metric_setting': [NAMESPACED_FLAG_NAME, NAMESPACED_FLAG_2_NAME]},
{'expected_count': 0, 'waffle_flag_attribute_setting': None},
{'expected_count': 1, 'waffle_flag_attribute_setting': [NAMESPACED_FLAG_NAME]},
{'expected_count': 2, 'waffle_flag_attribute_setting': [NAMESPACED_FLAG_NAME, NAMESPACED_FLAG_2_NAME]},
)
@patch('openedx.core.djangoapps.waffle_utils.set_custom_attribute')
def test_waffle_flag_metric_for_various_settings(self, data, mock_set_custom_attribute):
def test_waffle_flag_attribute_for_various_settings(self, data, mock_set_custom_attribute):
"""
Test that custom attributes are recorded when waffle flag accessed.
"""
with override_settings(WAFFLE_FLAG_CUSTOM_METRICS=data['waffle_flag_metric_setting']):
with override_settings(WAFFLE_FLAG_CUSTOM_ATTRIBUTES=data['waffle_flag_attribute_setting']):
with patch(
'openedx.core.djangoapps.waffle_utils._WAFFLE_FLAG_CUSTOM_METRIC_SET',
_get_waffle_flag_custom_metrics_set(),
'openedx.core.djangoapps.waffle_utils._WAFFLE_FLAG_CUSTOM_ATTRIBUTE_SET',
_get_waffle_flag_custom_attributes_set(),
):
test_course_flag = CourseWaffleFlag(self.TEST_NAMESPACE, self.FLAG_NAME, __name__)
test_course_flag.is_enabled(self.TEST_COURSE_KEY)
Expand All @@ -174,7 +174,7 @@ def test_waffle_flag_metric_for_various_settings(self, data, mock_set_custom_att

self.assertEqual(mock_set_custom_attribute.call_count, data['expected_count'])

def _assert_waffle_flag_metric(self, mock_set_custom_attribute, expected_flag_value=None):
def _assert_waffle_flag_attribute(self, mock_set_custom_attribute, expected_flag_value=None):
"""
Assert that a custom attribute was set as expected on the mock.
"""
Expand Down
Loading