Commit 63ddbd9b by Calen Pennington

Cache CustomCourseForEdX object lookup per-request

parent 1e84ad99
...@@ -7,6 +7,8 @@ import logging ...@@ -7,6 +7,8 @@ import logging
from django.db import transaction, IntegrityError from django.db import transaction, IntegrityError
import request_cache
from courseware.field_overrides import FieldOverrideProvider # pylint: disable=import-error from courseware.field_overrides import FieldOverrideProvider # pylint: disable=import-error
from opaque_keys.edx.keys import CourseKey, UsageKey from opaque_keys.edx.keys import CourseKey, UsageKey
from ccx_keys.locator import CCXLocator, CCXBlockUsageLocator from ccx_keys.locator import CCXLocator, CCXBlockUsageLocator
...@@ -69,7 +71,11 @@ def get_current_ccx(course_key): ...@@ -69,7 +71,11 @@ def get_current_ccx(course_key):
if not isinstance(course_key, CCXLocator): if not isinstance(course_key, CCXLocator):
return None return None
return CustomCourseForEdX.objects.get(pk=course_key.ccx) ccx_cache = request_cache.get_cache('ccx')
if course_key not in ccx_cache:
ccx_cache[course_key] = CustomCourseForEdX.objects.get(pk=course_key.ccx)
return ccx_cache[course_key]
def get_override_for_ccx(ccx, block, name, default=None): def get_override_for_ccx(ccx, block, name, default=None):
......
...@@ -5,6 +5,7 @@ Performance tests for field overrides. ...@@ -5,6 +5,7 @@ Performance tests for field overrides.
import ddt import ddt
import itertools import itertools
import mock import mock
from nose.plugins.skip import SkipTest
from courseware.views import progress # pylint: disable=import-error from courseware.views import progress # pylint: disable=import-error
from courseware.field_overrides import OverrideFieldData from courseware.field_overrides import OverrideFieldData
...@@ -25,6 +26,8 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, \ ...@@ -25,6 +26,8 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, \
TEST_DATA_SPLIT_MODULESTORE, TEST_DATA_MONGO_MODULESTORE TEST_DATA_SPLIT_MODULESTORE, TEST_DATA_MONGO_MODULESTORE
from xmodule.modulestore.tests.factories import check_mongo_calls, CourseFactory, check_sum_of_calls from xmodule.modulestore.tests.factories import check_mongo_calls, CourseFactory, check_sum_of_calls
from xmodule.modulestore.tests.utils import ProceduralCourseTestMixin from xmodule.modulestore.tests.utils import ProceduralCourseTestMixin
from ccx_keys.locator import CCXLocator
from ccx.tests.factories import CcxFactory, CcxMembershipFactory
@attr('shard_1') @attr('shard_1')
...@@ -58,6 +61,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin, ...@@ -58,6 +61,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
self.request = self.request_factory.get("foo") self.request = self.request_factory.get("foo")
self.request.user = self.student self.request.user = self.student
self.course = None self.course = None
self.ccx = None
MakoMiddleware().process_request(self.request) MakoMiddleware().process_request(self.request)
...@@ -113,17 +117,42 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin, ...@@ -113,17 +117,42 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
self.course.id self.course.id
) )
def grade_course(self, course): if enable_ccx:
self.ccx = CcxFactory.create()
CcxMembershipFactory.create(
student=self.student,
ccx=self.ccx
)
def grade_course(self, course, view_as_ccx):
""" """
Renders the progress page for the given course. Renders the progress page for the given course.
""" """
course_key = course.id
if view_as_ccx:
course_key = CCXLocator.from_course_locator(course_key, self.ccx.id)
return progress( return progress(
self.request, self.request,
course_id=course.id.to_deprecated_string(), course_id=unicode(course_key),
student_id=self.student.id student_id=self.student.id
) )
def instrument_course_progress_render(self, course_width, enable_ccx, queries, reads, xblocks): def assertMongoCallCount(self, calls):
"""
Assert that mongodb is queried ``calls`` times in the surrounded
context.
"""
return check_mongo_calls(calls)
def assertXBlockInstantiations(self, instantiations):
"""
Assert that exactly ``instantiations`` XBlocks are instantiated in
the surrounded context.
"""
return check_sum_of_calls(XBlock, ['__init__'], instantiations, instantiations, include_arguments=False)
def instrument_course_progress_render(self, course_width, enable_ccx, view_as_ccx, queries, reads, xblocks):
""" """
Renders the progress page, instrumenting Mongo reads and SQL queries. Renders the progress page, instrumenting Mongo reads and SQL queries.
""" """
...@@ -146,16 +175,16 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin, ...@@ -146,16 +175,16 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
OverrideFieldData.provider_classes = None OverrideFieldData.provider_classes = None
with self.assertNumQueries(queries): with self.assertNumQueries(queries):
with check_mongo_calls(reads): with self.assertMongoCallCount(reads):
with check_sum_of_calls(XBlock, ['__init__'], xblocks, xblocks, include_arguments=False): with self.assertXBlockInstantiations(xblocks):
self.grade_course(self.course) self.grade_course(self.course, view_as_ccx)
@ddt.data(*itertools.product(('no_overrides', 'ccx'), range(1, 4), (True, False))) @ddt.data(*itertools.product(('no_overrides', 'ccx'), range(1, 4), (True, False), (True, False)))
@ddt.unpack @ddt.unpack
@override_settings( @override_settings(
FIELD_OVERRIDE_PROVIDERS=(), FIELD_OVERRIDE_PROVIDERS=(),
) )
def test_field_overrides(self, overrides, course_width, enable_ccx): def test_field_overrides(self, overrides, course_width, enable_ccx, view_as_ccx):
""" """
Test without any field overrides. Test without any field overrides.
""" """
...@@ -163,9 +192,18 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin, ...@@ -163,9 +192,18 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
'no_overrides': (), 'no_overrides': (),
'ccx': ('ccx.overrides.CustomCoursesForEdxOverrideProvider',) 'ccx': ('ccx.overrides.CustomCoursesForEdxOverrideProvider',)
} }
if overrides == 'no_overrides' and view_as_ccx:
raise SkipTest("Can't view a ccx course if field overrides are disabled.")
if not enable_ccx and view_as_ccx:
raise SkipTest("Can't view a ccx course if ccx is disabled on the course")
if self.MODULESTORE == TEST_DATA_MONGO_MODULESTORE and view_as_ccx:
raise SkipTest("Can't use a MongoModulestore test as a CCX course")
with self.settings(FIELD_OVERRIDE_PROVIDERS=providers[overrides]): with self.settings(FIELD_OVERRIDE_PROVIDERS=providers[overrides]):
queries, reads, xblocks = self.TEST_DATA[(overrides, course_width, enable_ccx)] queries, reads, xblocks = self.TEST_DATA[(overrides, course_width, enable_ccx, view_as_ccx)]
self.instrument_course_progress_render(course_width, enable_ccx, queries, reads, xblocks) self.instrument_course_progress_render(course_width, enable_ccx, view_as_ccx, queries, reads, xblocks)
class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase): class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
...@@ -176,19 +214,25 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase): ...@@ -176,19 +214,25 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
__test__ = True __test__ = True
TEST_DATA = { TEST_DATA = {
# (providers, course_width, enable_ccx): # of sql queries, # of mongo queries, # of xblocks # (providers, course_width, enable_ccx, view_as_ccx): # of sql queries, # of mongo queries, # of xblocks
('no_overrides', 1, True): (23, 7, 14), ('no_overrides', 1, True, False): (23, 7, 14),
('no_overrides', 2, True): (68, 7, 85), ('no_overrides', 2, True, False): (68, 7, 85),
('no_overrides', 3, True): (263, 7, 336), ('no_overrides', 3, True, False): (263, 7, 336),
('ccx', 1, True): (23, 7, 14), ('ccx', 1, True, False): (23, 7, 14),
('ccx', 2, True): (68, 7, 85), ('ccx', 2, True, False): (68, 7, 85),
('ccx', 3, True): (263, 7, 336), ('ccx', 3, True, False): (263, 7, 336),
('no_overrides', 1, False): (23, 7, 14), ('ccx', 1, True, True): (23, 7, 14),
('no_overrides', 2, False): (68, 7, 85), ('ccx', 2, True, True): (68, 7, 85),
('no_overrides', 3, False): (263, 7, 336), ('ccx', 3, True, True): (263, 7, 336),
('ccx', 1, False): (23, 7, 14), ('no_overrides', 1, False, False): (23, 7, 14),
('ccx', 2, False): (68, 7, 85), ('no_overrides', 2, False, False): (68, 7, 85),
('ccx', 3, False): (263, 7, 336), ('no_overrides', 3, False, False): (263, 7, 336),
('ccx', 1, False, False): (23, 7, 14),
('ccx', 2, False, False): (68, 7, 85),
('ccx', 3, False, False): (263, 7, 336),
('ccx', 1, False, True): (23, 7, 14),
('ccx', 2, False, True): (68, 7, 85),
('ccx', 3, False, True): (263, 7, 336),
} }
...@@ -200,16 +244,22 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase): ...@@ -200,16 +244,22 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
__test__ = True __test__ = True
TEST_DATA = { TEST_DATA = {
('no_overrides', 1, True): (23, 4, 9), ('no_overrides', 1, True, False): (23, 4, 9),
('no_overrides', 2, True): (68, 19, 54), ('no_overrides', 2, True, False): (68, 19, 54),
('no_overrides', 3, True): (263, 84, 215), ('no_overrides', 3, True, False): (263, 84, 215),
('ccx', 1, True): (23, 4, 9), ('ccx', 1, True, False): (23, 4, 9),
('ccx', 2, True): (68, 19, 54), ('ccx', 2, True, False): (68, 19, 54),
('ccx', 3, True): (263, 84, 215), ('ccx', 3, True, False): (263, 84, 215),
('no_overrides', 1, False): (23, 4, 9), ('ccx', 1, True, True): (33, 4, 13),
('no_overrides', 2, False): (68, 19, 54), ('ccx', 2, True, True): (68, 19, 84),
('no_overrides', 3, False): (263, 84, 215), ('ccx', 3, True, True): (263, 84, 335),
('ccx', 1, False): (23, 4, 9), ('no_overrides', 1, False, False): (23, 4, 9),
('ccx', 2, False): (68, 19, 54), ('no_overrides', 2, False, False): (68, 19, 54),
('ccx', 3, False): (263, 84, 215), ('no_overrides', 3, False, False): (263, 84, 215),
('ccx', 1, False, False): (23, 4, 9),
('ccx', 2, False, False): (68, 19, 54),
('ccx', 3, False, False): (263, 84, 215),
('ccx', 1, False, True): (23, 4, 9),
('ccx', 2, False, True): (68, 19, 54),
('ccx', 3, False, True): (263, 84, 215),
} }
...@@ -9,6 +9,7 @@ from nose.plugins.attrib import attr ...@@ -9,6 +9,7 @@ from nose.plugins.attrib import attr
from courseware.field_overrides import OverrideFieldData # pylint: disable=import-error from courseware.field_overrides import OverrideFieldData # pylint: disable=import-error
from django.test.utils import override_settings from django.test.utils import override_settings
from request_cache.middleware import RequestCache
from student.tests.factories import AdminFactory # pylint: disable=import-error from student.tests.factories import AdminFactory # pylint: disable=import-error
from xmodule.modulestore.tests.django_utils import ( from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase, ModuleStoreTestCase,
...@@ -66,6 +67,8 @@ class TestFieldOverrides(ModuleStoreTestCase): ...@@ -66,6 +67,8 @@ class TestFieldOverrides(ModuleStoreTestCase):
get_ccx.return_value = ccx get_ccx.return_value = ccx
self.addCleanup(patch.stop) self.addCleanup(patch.stop)
self.addCleanup(RequestCache.clear_request_cache)
# Apparently the test harness doesn't use LmsFieldStorage, and I'm not # Apparently the test harness doesn't use LmsFieldStorage, and I'm not
# sure if there's a way to poke the test harness to do so. So, we'll # sure if there's a way to poke the test harness to do so. So, we'll
# just inject the override field storage in this brute force manner. # just inject the override field storage in this brute force manner.
......
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