Commit 63ddbd9b by Calen Pennington

Cache CustomCourseForEdX object lookup per-request

parent 1e84ad99
......@@ -7,6 +7,8 @@ import logging
from django.db import transaction, IntegrityError
import request_cache
from courseware.field_overrides import FieldOverrideProvider # pylint: disable=import-error
from opaque_keys.edx.keys import CourseKey, UsageKey
from ccx_keys.locator import CCXLocator, CCXBlockUsageLocator
......@@ -69,7 +71,11 @@ def get_current_ccx(course_key):
if not isinstance(course_key, CCXLocator):
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):
......
......@@ -5,6 +5,7 @@ Performance tests for field overrides.
import ddt
import itertools
import mock
from nose.plugins.skip import SkipTest
from courseware.views import progress # pylint: disable=import-error
from courseware.field_overrides import OverrideFieldData
......@@ -25,6 +26,8 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, \
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.utils import ProceduralCourseTestMixin
from ccx_keys.locator import CCXLocator
from ccx.tests.factories import CcxFactory, CcxMembershipFactory
@attr('shard_1')
......@@ -58,6 +61,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
self.request = self.request_factory.get("foo")
self.request.user = self.student
self.course = None
self.ccx = None
MakoMiddleware().process_request(self.request)
......@@ -113,17 +117,42 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
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.
"""
course_key = course.id
if view_as_ccx:
course_key = CCXLocator.from_course_locator(course_key, self.ccx.id)
return progress(
self.request,
course_id=course.id.to_deprecated_string(),
course_id=unicode(course_key),
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.
"""
......@@ -146,16 +175,16 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
OverrideFieldData.provider_classes = None
with self.assertNumQueries(queries):
with check_mongo_calls(reads):
with check_sum_of_calls(XBlock, ['__init__'], xblocks, xblocks, include_arguments=False):
self.grade_course(self.course)
with self.assertMongoCallCount(reads):
with self.assertXBlockInstantiations(xblocks):
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
@override_settings(
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.
"""
......@@ -163,9 +192,18 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
'no_overrides': (),
'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]):
queries, reads, xblocks = self.TEST_DATA[(overrides, course_width, enable_ccx)]
self.instrument_course_progress_render(course_width, enable_ccx, queries, reads, xblocks)
queries, reads, xblocks = self.TEST_DATA[(overrides, course_width, enable_ccx, view_as_ccx)]
self.instrument_course_progress_render(course_width, enable_ccx, view_as_ccx, queries, reads, xblocks)
class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
......@@ -176,19 +214,25 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
__test__ = True
TEST_DATA = {
# (providers, course_width, enable_ccx): # of sql queries, # of mongo queries, # of xblocks
('no_overrides', 1, True): (23, 7, 14),
('no_overrides', 2, True): (68, 7, 85),
('no_overrides', 3, True): (263, 7, 336),
('ccx', 1, True): (23, 7, 14),
('ccx', 2, True): (68, 7, 85),
('ccx', 3, True): (263, 7, 336),
('no_overrides', 1, False): (23, 7, 14),
('no_overrides', 2, False): (68, 7, 85),
('no_overrides', 3, False): (263, 7, 336),
('ccx', 1, False): (23, 7, 14),
('ccx', 2, False): (68, 7, 85),
('ccx', 3, False): (263, 7, 336),
# (providers, course_width, enable_ccx, view_as_ccx): # of sql queries, # of mongo queries, # of xblocks
('no_overrides', 1, True, False): (23, 7, 14),
('no_overrides', 2, True, False): (68, 7, 85),
('no_overrides', 3, True, False): (263, 7, 336),
('ccx', 1, True, False): (23, 7, 14),
('ccx', 2, True, False): (68, 7, 85),
('ccx', 3, True, False): (263, 7, 336),
('ccx', 1, True, True): (23, 7, 14),
('ccx', 2, True, True): (68, 7, 85),
('ccx', 3, True, True): (263, 7, 336),
('no_overrides', 1, False, False): (23, 7, 14),
('no_overrides', 2, False, False): (68, 7, 85),
('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):
__test__ = True
TEST_DATA = {
('no_overrides', 1, True): (23, 4, 9),
('no_overrides', 2, True): (68, 19, 54),
('no_overrides', 3, True): (263, 84, 215),
('ccx', 1, True): (23, 4, 9),
('ccx', 2, True): (68, 19, 54),
('ccx', 3, True): (263, 84, 215),
('no_overrides', 1, False): (23, 4, 9),
('no_overrides', 2, False): (68, 19, 54),
('no_overrides', 3, False): (263, 84, 215),
('ccx', 1, False): (23, 4, 9),
('ccx', 2, False): (68, 19, 54),
('ccx', 3, False): (263, 84, 215),
('no_overrides', 1, True, False): (23, 4, 9),
('no_overrides', 2, True, False): (68, 19, 54),
('no_overrides', 3, True, False): (263, 84, 215),
('ccx', 1, True, False): (23, 4, 9),
('ccx', 2, True, False): (68, 19, 54),
('ccx', 3, True, False): (263, 84, 215),
('ccx', 1, True, True): (33, 4, 13),
('ccx', 2, True, True): (68, 19, 84),
('ccx', 3, True, True): (263, 84, 335),
('no_overrides', 1, False, False): (23, 4, 9),
('no_overrides', 2, False, False): (68, 19, 54),
('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
from courseware.field_overrides import OverrideFieldData # pylint: disable=import-error
from django.test.utils import override_settings
from request_cache.middleware import RequestCache
from student.tests.factories import AdminFactory # pylint: disable=import-error
from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase,
......@@ -66,6 +67,8 @@ class TestFieldOverrides(ModuleStoreTestCase):
get_ccx.return_value = ccx
self.addCleanup(patch.stop)
self.addCleanup(RequestCache.clear_request_cache)
# 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
# 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