Commit 56da2852 by John Eskew

Merge pull request #9155 from edx/jeskew/csm_api_events_to_datadog

Add DataDog histogram events to DjangoXBlockUserStateClient class.
parents 39cccac7 f3e59acb
......@@ -5,20 +5,18 @@ data in a Django ORM model.
import itertools
from operator import attrgetter
from time import time
try:
import simplejson as json
except ImportError:
import json
import dogstats_wrapper as dog_stats_api
from django.contrib.auth.models import User
from xblock.fields import Scope, ScopeBase
from edx_user_state_client.interface import XBlockUserStateClient, XBlockUserState
from courseware.models import StudentModule, StudentModuleHistory
from contracts import contract, new_contract
from opaque_keys.edx.keys import UsageKey
new_contract('UsageKey', UsageKey)
from edx_user_state_client.interface import XBlockUserStateClient, XBlockUserState
class DjangoXBlockUserStateClient(XBlockUserStateClient):
......@@ -41,6 +39,9 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
even though the actual stored state in the database will be ``"{}"``).
"""
# Use this sample rate for DataDog events.
API_DATADOG_SAMPLE_RATE = 0.01
class ServiceUnavailable(XBlockUserStateClient.ServiceUnavailable):
"""
This error is raised if the service backing this client is currently unavailable.
......@@ -94,6 +95,27 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
usage_key = student_module.module_state_key.map_into_course(student_module.course_id)
yield (student_module, usage_key)
def _ddog_increment(self, evt_time, evt_name):
"""
DataDog increment method.
"""
dog_stats_api.increment(
'DjangoXBlockUserStateClient.{}'.format(evt_name),
timestamp=evt_time,
sample_rate=self.API_DATADOG_SAMPLE_RATE,
)
def _ddog_histogram(self, evt_time, evt_name, value):
"""
DataDog histogram method.
"""
dog_stats_api.histogram(
'DjangoXBlockUserStateClient.{}'.format(evt_name),
value,
timestamp=evt_time,
sample_rate=self.API_DATADOG_SAMPLE_RATE,
)
def get_many(self, username, block_keys, scope=Scope.user_state, fields=None):
"""
Retrieve the stored XBlock state for the specified XBlock usages.
......@@ -111,12 +133,16 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
if scope != Scope.user_state:
raise ValueError("Only Scope.user_state is supported, not {}".format(scope))
block_count = state_length = 0
evt_time = time()
modules = self._get_student_modules(username, block_keys)
for module, usage_key in modules:
if module.state is None:
continue
state = json.loads(module.state)
state_length += len(module.state)
# If the state is the empty dict, then it has been deleted, and so
# conformant UserStateClients should treat it as if it doesn't exist.
......@@ -129,8 +155,14 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
for field in fields
if field in state
}
block_count += 1
yield XBlockUserState(username, usage_key, state, module.modified, scope)
# The rest of this method exists only to submit DataDog events.
# Remove it once we're no longer interested in the data.
self._ddog_histogram(evt_time, 'get_many.blks_out', block_count)
self._ddog_histogram(evt_time, 'get_many.blks_size', state_length)
def set_many(self, username, block_keys_to_state, scope=Scope.user_state):
"""
Set fields for a particular XBlock.
......@@ -155,6 +187,8 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
else:
user = User.objects.get(username=username)
evt_time = time()
for usage_key, state in block_keys_to_state.items():
student_module, created = StudentModule.objects.get_or_create(
student=user,
......@@ -166,16 +200,43 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
},
)
num_fields_before = num_fields_after = num_new_fields_set = len(state)
num_fields_updated = 0
if not created:
if student_module.state is None:
current_state = {}
else:
current_state = json.loads(student_module.state)
num_fields_before = len(current_state)
current_state.update(state)
num_fields_after = len(current_state)
student_module.state = json.dumps(current_state)
# We just read this object, so we know that we can do an update
student_module.save(force_update=True)
# The rest of this method exists only to submit DataDog events.
# Remove it once we're no longer interested in the data.
#
# Record whether a state row has been created or updated.
if created:
self._ddog_increment(evt_time, 'set_many.state_created')
else:
self._ddog_increment(evt_time, 'set_many.state_updated')
# Event to record number of fields sent in to set/set_many.
self._ddog_histogram(evt_time, 'set_many.fields_in', len(state))
# Event to record number of new fields set in set/set_many.
num_new_fields_set = num_fields_after - num_fields_before
self._ddog_histogram(evt_time, 'set_many.fields_set', num_new_fields_set)
# Event to record number of existing fields updated in set/set_many.
num_fields_updated = max(0, len(state) - num_new_fields_set)
self._ddog_histogram(evt_time, 'set_many.fields_updated', num_fields_updated)
# Event for the entire set_many call.
self._ddog_histogram(evt_time, 'set_many.blks_updated', len(block_keys_to_state))
def delete_many(self, username, block_keys, scope=Scope.user_state, fields=None):
"""
Delete the stored XBlock state for a many xblock usages.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment