Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
59e0d22f
Commit
59e0d22f
authored
May 29, 2015
by
utkjad
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Adding call stack manager
parent
5fb2a1fd
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
377 additions
and
10 deletions
+377
-10
cms/envs/test.py
+1
-1
lms/djangoapps/courseware/models.py
+3
-8
lms/envs/test.py
+1
-1
openedx/core/djangoapps/call_stack_manager/__init__.py
+5
-0
openedx/core/djangoapps/call_stack_manager/core.py
+144
-0
openedx/core/djangoapps/call_stack_manager/models.py
+8
-0
openedx/core/djangoapps/call_stack_manager/tests.py
+215
-0
No files found.
cms/envs/test.py
View file @
59e0d22f
...
...
@@ -173,7 +173,7 @@ CACHES = {
INSTALLED_APPS
+=
(
'external_auth'
,
)
# Add milestones to Installed apps for testing
INSTALLED_APPS
+=
(
'milestones'
,
)
INSTALLED_APPS
+=
(
'milestones'
,
'openedx.core.djangoapps.call_stack_manager'
)
# hide ratelimit warnings while running tests
filterwarnings
(
'ignore'
,
message
=
'No request passed to the backend, unable to rate-limit'
)
...
...
lms/djangoapps/courseware/models.py
View file @
59e0d22f
...
...
@@ -26,6 +26,7 @@ from student.models import user_by_anonymous_id
from
submissions.models
import
score_set
,
score_reset
from
xmodule_django.models
import
CourseKeyField
,
LocationKeyField
,
BlockTypeKeyField
# pylint: disable=import-error
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
"edx.courseware"
)
...
...
@@ -72,7 +73,6 @@ class StudentModule(models.Model):
Keeps student state for a particular module in a particular course.
"""
objects
=
ChunkingManager
()
MODEL_TAGS
=
[
'course_id'
,
'module_type'
]
# For a homework problem, contains a JSON
...
...
@@ -96,10 +96,10 @@ class StudentModule(models.Model):
class
Meta
(
object
):
# pylint: disable=missing-docstring
unique_together
=
((
'student'
,
'module_state_key'
,
'course_id'
),)
#
#
Internal state of the object
# Internal state of the object
state
=
models
.
TextField
(
null
=
True
,
blank
=
True
)
#
#
Grade, and are we done?
# Grade, and are we done?
grade
=
models
.
FloatField
(
null
=
True
,
blank
=
True
,
db_index
=
True
)
max_grade
=
models
.
FloatField
(
null
=
True
,
blank
=
True
)
DONE_TYPES
=
(
...
...
@@ -146,7 +146,6 @@ class StudentModuleHistory(models.Model):
"""Keeps a complete history of state changes for a given XModule for a given
Student. Right now, we restrict this to problems so that the table doesn't
explode in size."""
HISTORY_SAVING_TYPES
=
{
'problem'
}
class
Meta
(
object
):
# pylint: disable=missing-docstring
...
...
@@ -211,7 +210,6 @@ class XModuleUserStateSummaryField(XBlockFieldBase):
"""
Stores data set in the Scope.user_state_summary scope by an xmodule field
"""
class
Meta
(
object
):
# pylint: disable=missing-docstring
unique_together
=
((
'usage_id'
,
'field_name'
),)
...
...
@@ -223,7 +221,6 @@ class XModuleStudentPrefsField(XBlockFieldBase):
"""
Stores data set in the Scope.preferences scope by an xmodule field
"""
class
Meta
(
object
):
# pylint: disable=missing-docstring
unique_together
=
((
'student'
,
'module_type'
,
'field_name'
),)
...
...
@@ -237,10 +234,8 @@ class XModuleStudentInfoField(XBlockFieldBase):
"""
Stores data set in the Scope.preferences scope by an xmodule field
"""
class
Meta
(
object
):
# pylint: disable=missing-docstring
unique_together
=
((
'student'
,
'field_name'
),)
student
=
models
.
ForeignKey
(
User
,
db_index
=
True
)
...
...
lms/envs/test.py
View file @
59e0d22f
...
...
@@ -457,7 +457,7 @@ FEATURES['ENABLE_EDXNOTES'] = True
FEATURES
[
'ENABLE_TEAMS'
]
=
True
# Add milestones to Installed apps for testing
INSTALLED_APPS
+=
(
'milestones'
,
)
INSTALLED_APPS
+=
(
'milestones'
,
'openedx.core.djangoapps.call_stack_manager'
)
# Enable courseware search for tests
FEATURES
[
'ENABLE_COURSEWARE_SEARCH'
]
=
True
...
...
openedx/core/djangoapps/call_stack_manager/__init__.py
0 → 100644
View file @
59e0d22f
"""
Root Package for getting call stacks of various Model classes being used
"""
from
__future__
import
absolute_import
from
.core
import
CallStackManager
,
CallStackMixin
,
donottrack
openedx/core/djangoapps/call_stack_manager/core.py
0 → 100644
View file @
59e0d22f
"""
Get call stacks of Model Class
in three cases-
1. QuerySet API
2. save()
3. delete()
classes:
CallStackManager - stores all stacks in global dictionary and logs
CallStackMixin - used for Model save(), and delete() method
Functions:
capture_call_stack - global function used to store call stack
Decorators:
donottrack - mainly for the places where we know the calls. This decorator will let us not to track in specified cases
How to use-
1. Import following in the file where class to be tracked resides
from openedx.core.djangoapps.call_stack_manager import CallStackManager, CallStackMixin
2. Override objects of default manager by writing following in any model class which you want to track-
objects = CallStackManager()
3. For tracking Save and Delete events-
Use mixin called "CallStackMixin"
For ex.
class StudentModule(CallStackMixin, models.Model):
4. Decorator is a parameterized decorator with class name/s as argument
How to use -
1. Import following
import from openedx.core.djangoapps.call_stack_manager import donottrack
"""
import
logging
import
traceback
import
re
import
collections
from
django.db.models
import
Manager
log
=
logging
.
getLogger
(
__name__
)
# list of regular expressions acting as filters
REGULAR_EXPS
=
[
re
.
compile
(
x
)
for
x
in
[
'^.*python2.7.*$'
,
'^.*<exec_function>.*$'
,
'^.*exec_code_object.*$'
,
'^.*edxapp/src.*$'
,
'^.*call_stack_manager.*$'
]]
# Variable which decides whether to track calls in the function or not. Do it by default.
TRACK_FLAG
=
True
# List keeping track of Model classes not be tracked for special cases
# usually cases where we know that the function is calling Model classes.
HALT_TRACKING
=
[]
# Module Level variables
# dictionary which stores call stacks.
# { "ModelClasses" : [ListOfFrames]}
# Frames - ('FilePath','LineNumber','Context')
# ex. {"<class 'courseware.models.StudentModule'>" : [[(file,line number,context),(---,---,---)],
# [(file,line number,context),(---,---,---)]]}
STACK_BOOK
=
collections
.
defaultdict
(
list
)
def
capture_call_stack
(
current_model
):
""" logs customised call stacks in global dictionary `STACK_BOOK`, and logs it.
Args:
current_model - Name of the model class
"""
# holds temporary callstack
# frame[0][6:-1] -> File name along with path
# frame[1][6:] -> Line Number
# frame[2][3:] -> Context
temp_call_stack
=
[(
frame
[
0
][
6
:
-
1
],
frame
[
1
][
6
:],
frame
[
2
][
3
:])
for
frame
in
[
stack
.
replace
(
"
\n
"
,
""
)
.
strip
()
.
split
(
','
)
for
stack
in
traceback
.
format_stack
()]
if
not
any
(
reg
.
match
(
frame
[
0
])
for
reg
in
REGULAR_EXPS
)]
# avoid duplication.
if
temp_call_stack
not
in
STACK_BOOK
[
current_model
]
and
TRACK_FLAG
\
and
not
issubclass
(
current_model
,
tuple
(
HALT_TRACKING
)):
STACK_BOOK
[
current_model
]
.
append
(
temp_call_stack
)
log
.
info
(
"logging new call stack for
%
s:
\n
%
s"
,
current_model
,
temp_call_stack
)
class
CallStackMixin
(
object
):
""" A mixin class for getting call stacks when Save() and Delete() methods are called
"""
def
save
(
self
,
*
args
,
**
kwargs
):
"""
Logs before save and overrides respective model API save()
"""
capture_call_stack
(
type
(
self
))
return
super
(
CallStackMixin
,
self
)
.
save
(
*
args
,
**
kwargs
)
def
delete
(
self
,
*
args
,
**
kwargs
):
"""
Logs before delete and overrides respective model API delete()
"""
capture_call_stack
(
type
(
self
))
return
super
(
CallStackMixin
,
self
)
.
delete
(
*
args
,
**
kwargs
)
class
CallStackManager
(
Manager
):
""" A Manager class which overrides the default Manager class for getting call stacks
"""
def
get_query_set
(
self
):
"""overriding the default queryset API method
"""
capture_call_stack
(
self
.
model
)
return
super
(
CallStackManager
,
self
)
.
get_query_set
()
def
donottrack
(
*
classes_not_to_be_tracked
):
"""function decorator which deals with toggling call stack
Args:
classes_not_to_be_tracked: model classes where tracking is undesirable
Returns:
wrapped function
"""
def
real_donottrack
(
function
):
"""takes function to be decorated and returns wrapped function
Args:
function - wrapped function i.e. real_donottrack
"""
def
wrapper
(
*
args
,
**
kwargs
):
""" wrapper function for decorated function
Returns:
wrapper function i.e. wrapper
"""
if
len
(
classes_not_to_be_tracked
)
==
0
:
global
TRACK_FLAG
# pylint: disable=W0603
current_flag
=
TRACK_FLAG
TRACK_FLAG
=
False
function
(
*
args
,
**
kwargs
)
TRACK_FLAG
=
current_flag
else
:
global
HALT_TRACKING
# pylint: disable=W0603
current_halt_track
=
HALT_TRACKING
HALT_TRACKING
=
classes_not_to_be_tracked
function
(
*
args
,
**
kwargs
)
HALT_TRACKING
=
current_halt_track
return
wrapper
return
real_donottrack
openedx/core/djangoapps/call_stack_manager/models.py
0 → 100644
View file @
59e0d22f
"""
Dummy models.py file
Note -
django-nose loads models for tests, but only if the django app that the test is contained in has models itself.
This file is empty so that the unit tests can have models.
For call_stack_manager - models specific to tests are defined in tests.py
"""
openedx/core/djangoapps/call_stack_manager/tests.py
0 → 100644
View file @
59e0d22f
"""
Test cases for Call Stack Manager
"""
from
mock
import
patch
from
django.db
import
models
from
django.test
import
TestCase
from
openedx.core.djangoapps.call_stack_manager
import
donottrack
,
CallStackManager
,
CallStackMixin
class
ModelMixinCallStckMngr
(
CallStackMixin
,
models
.
Model
):
"""
Test Model class which uses both CallStackManager, and CallStackMixin
"""
# override Manager objects
objects
=
CallStackManager
()
id_field
=
models
.
IntegerField
()
class
ModelMixin
(
CallStackMixin
,
models
.
Model
):
"""
Test Model that uses CallStackMixin but does not use CallStackManager
"""
id_field
=
models
.
IntegerField
()
class
ModelNothingCallStckMngr
(
models
.
Model
):
"""
Test Model class that neither uses CallStackMixin nor CallStackManager
"""
id_field
=
models
.
IntegerField
()
class
ModelAnotherCallStckMngr
(
models
.
Model
):
"""
Test Model class that only uses overridden Manager CallStackManager
"""
objects
=
CallStackManager
()
id_field
=
models
.
IntegerField
()
class
ModelWithCallStackMngr
(
models
.
Model
):
"""
Test Model Class with overridden CallStackManager
"""
objects
=
CallStackManager
()
id_field
=
models
.
IntegerField
()
class
ModelWithCallStckMngrChild
(
ModelWithCallStackMngr
):
"""child class of ModelWithCallStackMngr
"""
objects
=
CallStackManager
()
child_id_field
=
models
.
IntegerField
()
@donottrack
(
ModelWithCallStackMngr
)
def
donottrack_subclass
():
""" function in which subclass and superclass calls QuerySetAPI
"""
ModelWithCallStackMngr
.
objects
.
filter
(
id_field
=
1
)
ModelWithCallStckMngrChild
.
objects
.
filter
(
child_id_field
=
1
)
def
track_without_donottrack
():
""" function calling QuerySetAPI, another function, again QuerySetAPI
"""
ModelAnotherCallStckMngr
.
objects
.
filter
(
id_field
=
1
)
donottrack_child_func
()
ModelAnotherCallStckMngr
.
objects
.
filter
(
id_field
=
1
)
@donottrack
(
ModelAnotherCallStckMngr
)
def
donottrack_child_func
():
""" decorated child function
"""
# should not be tracked
ModelAnotherCallStckMngr
.
objects
.
filter
(
id_field
=
1
)
# should be tracked
ModelMixinCallStckMngr
.
objects
.
filter
(
id_field
=
1
)
@donottrack
(
ModelMixinCallStckMngr
)
def
donottrack_parent_func
():
""" decorated parent function
"""
# should not be tracked
ModelMixinCallStckMngr
.
objects
.
filter
(
id_field
=
1
)
# should be tracked
ModelAnotherCallStckMngr
.
objects
.
filter
(
id_field
=
1
)
donottrack_child_func
()
@donottrack
()
def
donottrack_func_parent
():
""" non-parameterized @donottrack decorated function calling child function
"""
ModelMixin
.
objects
.
all
()
donottrack_func_child
()
ModelMixin
.
objects
.
filter
(
id_field
=
1
)
@donottrack
()
def
donottrack_func_child
():
""" child decorated non-parameterized function
"""
# Should not be tracked
ModelMixin
.
objects
.
all
()
@patch
(
'openedx.core.djangoapps.call_stack_manager.core.log.info'
)
@patch
(
'openedx.core.djangoapps.call_stack_manager.core.REGULAR_EXPS'
,
[])
class
TestingCallStackManager
(
TestCase
):
"""Tests for call_stack_manager
1. Tests CallStackManager QuerySetAPI functionality
2. Tests @donottrack decorator
"""
def
test_save
(
self
,
log_capt
):
""" tests save() of CallStackMixin/ applies same for delete()
classes with CallStackMixin should participate in logging.
"""
ModelMixin
(
id_field
=
1
)
.
save
()
self
.
assertEqual
(
ModelMixin
,
log_capt
.
call_args
[
0
][
1
])
def
test_withoutmixin_save
(
self
,
log_capt
):
"""tests save() of CallStackMixin/ applies same for delete()
classes without CallStackMixin should not participate in logging
"""
ModelAnotherCallStckMngr
(
id_field
=
1
)
.
save
()
self
.
assertEqual
(
len
(
log_capt
.
call_args_list
),
0
)
def
test_queryset
(
self
,
log_capt
):
""" Tests for Overriding QuerySet API
classes with CallStackManager should get logged.
"""
ModelAnotherCallStckMngr
(
id_field
=
1
)
.
save
()
ModelAnotherCallStckMngr
.
objects
.
all
()
self
.
assertEqual
(
ModelAnotherCallStckMngr
,
log_capt
.
call_args
[
0
][
1
])
def
test_withoutqueryset
(
self
,
log_capt
):
""" Tests for Overriding QuerySet API
classes without CallStackManager should not get logged
"""
# create and save objects of class not overriding queryset API
ModelNothingCallStckMngr
(
id_field
=
1
)
.
save
()
# class not using Manager, should not get logged
ModelNothingCallStckMngr
.
objects
.
all
()
self
.
assertEqual
(
len
(
log_capt
.
call_args_list
),
0
)
def
test_donottrack
(
self
,
log_capt
):
""" Test for @donottrack
calls in decorated function should not get logged
"""
donottrack_func_parent
()
self
.
assertEqual
(
len
(
log_capt
.
call_args_list
),
0
)
def
test_parameterized_donottrack
(
self
,
log_capt
):
""" Test for parameterized @donottrack
classes specified in the decorator @donottrack should not get logged
"""
ModelAnotherCallStckMngr
(
id_field
=
1
)
.
save
()
ModelMixinCallStckMngr
(
id_field
=
1
)
.
save
()
donottrack_child_func
()
self
.
assertEqual
(
ModelMixinCallStckMngr
,
log_capt
.
call_args
[
0
][
1
])
def
test_nested_parameterized_donottrack
(
self
,
log_capt
):
""" Tests parameterized nested @donottrack
should not track call of classes specified in decorated with scope bounded to the respective class
"""
ModelAnotherCallStckMngr
(
id_field
=
1
)
.
save
()
donottrack_parent_func
()
self
.
assertEqual
(
ModelAnotherCallStckMngr
,
log_capt
.
call_args_list
[
0
][
0
][
1
])
self
.
assertEqual
(
ModelMixinCallStckMngr
,
log_capt
.
call_args_list
[
1
][
0
][
1
])
def
test_nested_parameterized_donottrack_after
(
self
,
log_capt
):
""" Tests parameterized nested @donottrack
QuerySetAPI calls after calling function with @donottrack should get logged
"""
donottrack_child_func
()
# class with CallStackManager as Manager
ModelAnotherCallStckMngr
(
id_field
=
1
)
.
save
()
# test is this- that this should get called.
ModelAnotherCallStckMngr
.
objects
.
filter
(
id_field
=
1
)
self
.
assertEqual
(
ModelMixinCallStckMngr
,
log_capt
.
call_args_list
[
0
][
0
][
1
])
self
.
assertEqual
(
ModelAnotherCallStckMngr
,
log_capt
.
call_args_list
[
1
][
0
][
1
])
def
test_donottrack_called_in_func
(
self
,
log_capt
):
""" test for function which calls decorated function
functions without @donottrack decorator should log
"""
ModelAnotherCallStckMngr
(
id_field
=
1
)
.
save
()
ModelMixinCallStckMngr
(
id_field
=
1
)
.
save
()
track_without_donottrack
()
self
.
assertEqual
(
ModelMixinCallStckMngr
,
log_capt
.
call_args_list
[
0
][
0
][
1
])
self
.
assertEqual
(
ModelAnotherCallStckMngr
,
log_capt
.
call_args_list
[
1
][
0
][
1
])
self
.
assertEqual
(
ModelMixinCallStckMngr
,
log_capt
.
call_args_list
[
2
][
0
][
1
])
self
.
assertEqual
(
ModelAnotherCallStckMngr
,
log_capt
.
call_args_list
[
3
][
0
][
1
])
def
test_donottrack_child_too
(
self
,
log_capt
):
""" Test for inheritance
subclass should not be tracked when superclass is called in a @donottrack decorated function
"""
ModelWithCallStackMngr
(
id_field
=
1
)
.
save
()
ModelWithCallStckMngrChild
(
id_field
=
1
,
child_id_field
=
1
)
.
save
()
donottrack_subclass
()
self
.
assertEqual
(
len
(
log_capt
.
call_args_list
),
0
)
def
test_duplication
(
self
,
log_capt
):
""" Test for duplication of call stacks
should not log duplicated call stacks
"""
for
__
in
range
(
1
,
5
):
ModelMixinCallStckMngr
(
id_field
=
1
)
.
save
()
self
.
assertEqual
(
len
(
log_capt
.
call_args_list
),
1
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment