From f296815d515c81eeeb8e890976a280281fe6c483 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 22 Oct 2012 19:50:34 -0400 Subject: [PATCH 01/12] added instrumentation to count: user logins, registrations, correct answers, wrong answers, enrollment --- common/djangoapps/student/views.py | 9 ++++++--- common/lib/capa/capa/correctmap.py | 7 ++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index b2fcc73ca3..db9367ba23 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -39,6 +39,8 @@ from collections import namedtuple from courseware.courses import get_courses_by_university from courseware.access import has_access +from statsd import statsd + log = logging.getLogger("mitx.student") Article = namedtuple('Article', 'title url author image deck publication publish_date') @@ -204,7 +206,7 @@ def change_enrollment(request): return {'success': False, 'error': 'enrollment in {} not allowed at this time' .format(course.display_name)} - + statsd.increment("user.enrollment_in_" + str(course_id)) enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) return {'success': True} @@ -212,6 +214,7 @@ def change_enrollment(request): try: enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id) enrollment.delete() + statsd.increment("user.unenrollment_in_" + str(course_id)) return {'success': True} except CourseEnrollment.DoesNotExist: return {'success': False, 'error': 'You are not enrolled for this course.'} @@ -260,7 +263,7 @@ def login_user(request, error=""): log.info("Login success - {0} ({1})".format(username, email)) try_change_enrollment(request) - + statsd.increment("user.successful_login") return HttpResponse(json.dumps({'success': True})) log.warning("Login failed - Account not active for user {0}, resending activation".format(username)) @@ -466,7 +469,7 @@ def create_account(request, post_override=None): log.debug('bypassing activation email') login_user.is_active = True login_user.save() - + statsd.increment("user.account_created") js = {'success': True} return HttpResponse(json.dumps(js), mimetype="application/json") diff --git a/common/lib/capa/capa/correctmap.py b/common/lib/capa/capa/correctmap.py index 227f85bc8e..e61f6f6a9c 100644 --- a/common/lib/capa/capa/correctmap.py +++ b/common/lib/capa/capa/correctmap.py @@ -2,7 +2,7 @@ # class used to store graded responses to CAPA questions # # Used by responsetypes and capa_problem - +from statsd import statsd class CorrectMap(object): """ @@ -53,6 +53,11 @@ class CorrectMap(object): 'hintmode': hintmode, 'queuestate': queuestate, } + + if correctness=="correct": + statsd.increment("user.correct_answer") + else: + statsd.increment("user.incorrect_answer) def __repr__(self): return repr(self.cmap) From 3ba45fec73b0e975c6f761782e3908bbfe79f82b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 22 Oct 2012 20:01:39 -0400 Subject: [PATCH 02/12] added instrumentation, including user logins,registrations,enrollment,correct answers --- common/lib/capa/capa/correctmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/capa/capa/correctmap.py b/common/lib/capa/capa/correctmap.py index e61f6f6a9c..bbe09a3c57 100644 --- a/common/lib/capa/capa/correctmap.py +++ b/common/lib/capa/capa/correctmap.py @@ -57,7 +57,7 @@ class CorrectMap(object): if correctness=="correct": statsd.increment("user.correct_answer") else: - statsd.increment("user.incorrect_answer) + statsd.increment("user.incorrect_answer") def __repr__(self): return repr(self.cmap) From e02d8403e70ed3ca0af8dccdeae3439d563493d5 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 23 Oct 2012 09:25:08 -0400 Subject: [PATCH 03/12] updated requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 2ebca50bc5..dc13ae873e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,3 +50,4 @@ pygraphviz -r repo-requirements.txt pil nltk +dogstatsd-python \ No newline at end of file From d76241e6ae4fde67b3de17f49a74a922629a97b1 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 23 Oct 2012 10:56:00 -0400 Subject: [PATCH 04/12] prefix metrics with lms, use tags for enrollment --- common/djangoapps/student/views.py | 8 ++++---- common/lib/capa/capa/correctmap.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index db9367ba23..00dee79cdc 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -206,7 +206,7 @@ def change_enrollment(request): return {'success': False, 'error': 'enrollment in {} not allowed at this time' .format(course.display_name)} - statsd.increment("user.enrollment_in_" + str(course_id)) + statsd.increment("lms.user.enrollment",tags=["course:" + str(course_id)]) enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) return {'success': True} @@ -214,7 +214,7 @@ def change_enrollment(request): try: enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id) enrollment.delete() - statsd.increment("user.unenrollment_in_" + str(course_id)) + statsd.increment("lms.user.unenrollment", tags=["course:"+ str(course_id)]) return {'success': True} except CourseEnrollment.DoesNotExist: return {'success': False, 'error': 'You are not enrolled for this course.'} @@ -263,7 +263,7 @@ def login_user(request, error=""): log.info("Login success - {0} ({1})".format(username, email)) try_change_enrollment(request) - statsd.increment("user.successful_login") + statsd.increment("lms.user.successful_login") return HttpResponse(json.dumps({'success': True})) log.warning("Login failed - Account not active for user {0}, resending activation".format(username)) @@ -469,7 +469,7 @@ def create_account(request, post_override=None): log.debug('bypassing activation email') login_user.is_active = True login_user.save() - statsd.increment("user.account_created") + statsd.increment("lms.user.account_created") js = {'success': True} return HttpResponse(json.dumps(js), mimetype="application/json") diff --git a/common/lib/capa/capa/correctmap.py b/common/lib/capa/capa/correctmap.py index bbe09a3c57..fdc4b44253 100644 --- a/common/lib/capa/capa/correctmap.py +++ b/common/lib/capa/capa/correctmap.py @@ -55,9 +55,9 @@ class CorrectMap(object): } if correctness=="correct": - statsd.increment("user.correct_answer") + statsd.increment("lms.user.correct_answer") else: - statsd.increment("user.incorrect_answer") + statsd.increment("lms.user.incorrect_answer") def __repr__(self): return repr(self.cmap) From 51f3b7195e67b9b55af937dd93cad3b498a683d0 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 23 Oct 2012 13:20:37 -0400 Subject: [PATCH 05/12] roll back changes to common --- common/lib/capa/capa/correctmap.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/common/lib/capa/capa/correctmap.py b/common/lib/capa/capa/correctmap.py index fdc4b44253..3c4f43a1d6 100644 --- a/common/lib/capa/capa/correctmap.py +++ b/common/lib/capa/capa/correctmap.py @@ -2,7 +2,6 @@ # class used to store graded responses to CAPA questions # # Used by responsetypes and capa_problem -from statsd import statsd class CorrectMap(object): """ @@ -53,11 +52,6 @@ class CorrectMap(object): 'hintmode': hintmode, 'queuestate': queuestate, } - - if correctness=="correct": - statsd.increment("lms.user.correct_answer") - else: - statsd.increment("lms.user.incorrect_answer") def __repr__(self): return repr(self.cmap) From 64b117a7c2a7cb333ef4f04ce644c64731f3067b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 23 Oct 2012 18:14:16 -0400 Subject: [PATCH 06/12] changed metric names, added into render page --- common/djangoapps/student/views.py | 14 ++++++++++++-- lms/djangoapps/courseware/module_render.py | 11 +++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 00dee79cdc..8d93d06a7c 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -206,7 +206,10 @@ def change_enrollment(request): return {'success': False, 'error': 'enrollment in {} not allowed at this time' .format(course.display_name)} - statsd.increment("lms.user.enrollment",tags=["course:" + str(course_id)]) + + split_course=course_id.split("/") + statsd.increment("lms.user.enrollment",tags=["org:" + str(split_course[0]),"course:" + str(split_course[1]),"run:" + str(split_course[2])]) + enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) return {'success': True} @@ -214,7 +217,10 @@ def change_enrollment(request): try: enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id) enrollment.delete() - statsd.increment("lms.user.unenrollment", tags=["course:"+ str(course_id)]) + + split_course=course_id.split("/") + statsd.increment("lms.user.unenrollment",tags=["org:" + str(split_course[0]),"course:" + str(split_course[1]),"run:" + str(split_course[2])]) + return {'success': True} except CourseEnrollment.DoesNotExist: return {'success': False, 'error': 'You are not enrolled for this course.'} @@ -263,7 +269,9 @@ def login_user(request, error=""): log.info("Login success - {0} ({1})".format(username, email)) try_change_enrollment(request) + statsd.increment("lms.user.successful_login") + return HttpResponse(json.dumps({'success': True})) log.warning("Login failed - Account not active for user {0}, resending activation".format(username)) @@ -469,7 +477,9 @@ def create_account(request, post_override=None): log.debug('bypassing activation email') login_user.is_active = True login_user.save() + statsd.increment("lms.user.account_created") + js = {'success': True} return HttpResponse(json.dumps(js), mimetype="application/json") diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 1e45822ebf..6e6f3c9189 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -28,6 +28,8 @@ from xmodule.x_module import ModuleSystem from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor from xmodule_modifiers import replace_course_urls, replace_static_urls, add_histogram, wrap_xmodule +from statsd import statsd + log = logging.getLogger("mitx.courseware") @@ -381,6 +383,10 @@ def xqueue_callback(request, course_id, userid, id, dispatch): instance_module.grade = instance.get_score()['score'] if instance_module.grade != oldgrade or instance_module.state != old_instance_state: instance_module.save() + + course_split=course_id.split("/") + if(instance_module.state=="correct" or instance_module.state=="incorrect"): + statsd.increment("lms.user.question_answered",tags=["org:" + str(course_split[0]),"course:" + str(course_split[1]), "run:" + str(course_split[2]), "answer:" + str(instance_module.state), "score:" + str(instance_module.grade), "type:xqueue"]) return HttpResponse("") @@ -465,6 +471,11 @@ def modx_dispatch(request, dispatch, location, course_id): instance_module.state != old_instance_state or instance_module.max_grade != old_instance_max_grade): instance_module.save() + + course_split=course_id.split("/") + if(instance_module.state=="correct" or instance_module.state=="incorrect"): + statsd.increment("lms.user.question_answered",tags=["org:" + str(course_split[0]),"course:" + str(course_split[1]), "run:" + str(course_split[2]), "answer:" + str(instance_module.state), "score:" + str(instance_module.grade), "type:ajax"]) + if shared_module is not None: shared_module.state = instance.get_shared_state() From 57a78637f21170b45223bc81b1e06b1209ae2b92 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 23 Oct 2012 18:58:11 -0400 Subject: [PATCH 07/12] pep8 fixes and score bucketing --- lms/djangoapps/courseware/module_render.py | 31 ++++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 6e6f3c9189..9b9c621d84 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -384,10 +384,20 @@ def xqueue_callback(request, course_id, userid, id, dispatch): if instance_module.grade != oldgrade or instance_module.state != old_instance_state: instance_module.save() - course_split=course_id.split("/") + score_bucket=0 + if(instance_module.grade>0 and instance_module.grade0 and instance_module.grade Date: Tue, 23 Oct 2012 19:00:26 -0400 Subject: [PATCH 08/12] bugfix --- lms/djangoapps/courseware/module_render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 9b9c621d84..0fdbb91ac3 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -396,7 +396,7 @@ def xqueue_callback(request, course_id, userid, id, dispatch): tags=["org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run), - "score_bucket:{0}".format(score_bucket)), + "score_bucket:{0}".format(score_bucket), "type:xqueue"]) return HttpResponse("") @@ -494,7 +494,7 @@ def modx_dispatch(request, dispatch, location, course_id): tags=["org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run), - "score_bucket:{0}".format(score_bucket)), + "score_bucket:{0}".format(score_bucket), "type:ajax"]) From 952b3468cf17e65d92ea0b9ff2ed210227ebe140 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 23 Oct 2012 19:07:06 -0400 Subject: [PATCH 09/12] pep8 in views, remove instance state check --- common/djangoapps/student/views.py | 16 ++++++++----- lms/djangoapps/courseware/module_render.py | 26 ++++++++++------------ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 8d93d06a7c..2cface304c 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -207,8 +207,11 @@ def change_enrollment(request): 'error': 'enrollment in {} not allowed at this time' .format(course.display_name)} - split_course=course_id.split("/") - statsd.increment("lms.user.enrollment",tags=["org:" + str(split_course[0]),"course:" + str(split_course[1]),"run:" + str(split_course[2])]) + org, course_num, run=course_id.split("/") + statsd.increment("lms.user.enrollment", + tags=["org:{0}".format(org), + "course:{0}".format(course_num), + "run:{0}".format(run)]) enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) return {'success': True} @@ -218,9 +221,12 @@ def change_enrollment(request): enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id) enrollment.delete() - split_course=course_id.split("/") - statsd.increment("lms.user.unenrollment",tags=["org:" + str(split_course[0]),"course:" + str(split_course[1]),"run:" + str(split_course[2])]) - + org, course_num, run=course_id.split("/") + statsd.increment("lms.user.unenrollment", + tags=["org:{0}".format(org), + "course:{0}".format(course_num), + "run:{0}".format(run)]) + return {'success': True} except CourseEnrollment.DoesNotExist: return {'success': False, 'error': 'You are not enrolled for this course.'} diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 0fdbb91ac3..8c91405b96 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -391,13 +391,12 @@ def xqueue_callback(request, course_id, userid, id, dispatch): score_bucket=2 org, course_num, run=course_id.split("/") - if(instance_module.state=="correct" or instance_module.state=="incorrect"): - statsd.increment("lms.user.question_answered", - tags=["org:{0}".format(org), - "course:{0}".format(course_num), - "run:{0}".format(run), - "score_bucket:{0}".format(score_bucket), - "type:xqueue"]) + statsd.increment("lms.user.question_answered", + tags=["org:{0}".format(org), + "course:{0}".format(course_num), + "run:{0}".format(run), + "score_bucket:{0}".format(score_bucket), + "type:xqueue"]) return HttpResponse("") @@ -489,13 +488,12 @@ def modx_dispatch(request, dispatch, location, course_id): score_bucket=2 org, course_num, run=course_id.split("/") - if(instance_module.state=="correct" or instance_module.state=="incorrect"): - statsd.increment("lms.user.question_answered", - tags=["org:{0}".format(org), - "course:{0}".format(course_num), - "run:{0}".format(run), - "score_bucket:{0}".format(score_bucket), - "type:ajax"]) + statsd.increment("lms.user.question_answered", + tags=["org:{0}".format(org), + "course:{0}".format(course_num), + "run:{0}".format(run), + "score_bucket:{0}".format(score_bucket), + "type:ajax"]) if shared_module is not None: From 4360d10aece77fb238001e2e0539ead8c2e1cf0d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 23 Oct 2012 19:12:08 -0400 Subject: [PATCH 10/12] altered spacing --- common/djangoapps/student/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 2cface304c..dc541ff40b 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -223,9 +223,9 @@ def change_enrollment(request): org, course_num, run=course_id.split("/") statsd.increment("lms.user.unenrollment", - tags=["org:{0}".format(org), - "course:{0}".format(course_num), - "run:{0}".format(run)]) + tags=["org:{0}".format(org), + "course:{0}".format(course_num), + "run:{0}".format(run)]) return {'success': True} except CourseEnrollment.DoesNotExist: From a75acffe01a79fe80c7fafc456bd755954f9027f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 24 Oct 2012 15:11:41 -0400 Subject: [PATCH 11/12] made requested changes to statsd names, score bucket --- common/djangoapps/student/views.py | 8 +++--- lms/djangoapps/courseware/module_render.py | 32 +++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index dc541ff40b..2b940fe982 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -208,7 +208,7 @@ def change_enrollment(request): .format(course.display_name)} org, course_num, run=course_id.split("/") - statsd.increment("lms.user.enrollment", + statsd.increment("common.student.enrollment", tags=["org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run)]) @@ -222,7 +222,7 @@ def change_enrollment(request): enrollment.delete() org, course_num, run=course_id.split("/") - statsd.increment("lms.user.unenrollment", + statsd.increment("common.student.unenrollment", tags=["org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run)]) @@ -276,7 +276,7 @@ def login_user(request, error=""): try_change_enrollment(request) - statsd.increment("lms.user.successful_login") + statsd.increment("common.student.successful_login") return HttpResponse(json.dumps({'success': True})) @@ -484,7 +484,7 @@ def create_account(request, post_override=None): login_user.is_active = True login_user.save() - statsd.increment("lms.user.account_created") + statsd.increment("common.student.account_created") js = {'success': True} return HttpResponse(json.dumps(js), mimetype="application/json") diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 8c91405b96..94bbc918bf 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -383,15 +383,10 @@ def xqueue_callback(request, course_id, userid, id, dispatch): instance_module.grade = instance.get_score()['score'] if instance_module.grade != oldgrade or instance_module.state != old_instance_state: instance_module.save() - - score_bucket=0 - if(instance_module.grade>0 and instance_module.grade0 and instance_module.grade0 and grade Date: Wed, 24 Oct 2012 15:14:06 -0400 Subject: [PATCH 12/12] added docstrings --- lms/djangoapps/courseware/module_render.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 94bbc918bf..e0b0c3aaec 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -384,6 +384,7 @@ def xqueue_callback(request, course_id, userid, id, dispatch): if instance_module.grade != oldgrade or instance_module.state != old_instance_state: instance_module.save() + #Bin score into range and increment stats score_bucket=get_score_bucket(instance_module.grade, instance_module.max_grade) org, course_num, run=course_id.split("/") statsd.increment("lms.courseware.question_answered", @@ -476,6 +477,7 @@ def modx_dispatch(request, dispatch, location, course_id): instance_module.max_grade != old_instance_max_grade): instance_module.save() + #Bin score into range and increment stats score_bucket=get_score_bucket(instance_module.grade, instance_module.max_grade) org, course_num, run=course_id.split("/") statsd.increment("lms.courseware.question_answered", @@ -530,9 +532,12 @@ def preview_chemcalc(request): return HttpResponse(json.dumps(result)) -#Function to split arbitrary score ranges into 3 buckets. -#Used with statsd tracking. + def get_score_bucket(grade,max_grade): + """ + Function to split arbitrary score ranges into 3 buckets. + Used with statsd tracking. + """ score_bucket="incorrect" if(grade>0 and grade