From 9984bbc29ab6b7092b787ba5cf35580dd4a58cec Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Tue, 9 Feb 2016 17:40:54 -0500 Subject: [PATCH 1/2] Make Capa problems do initial load without AJAX. Before this commit, calling the student_view on a capa problem would cause it to render an empty placeholder
, wait for the DOMContentLoaded event to be fired, and then make AJAX requests to the the problem_get handlers to retrieve the HTML it needed to render the actual problems. This can significantly increase the end user load times for pages, particularly when there are many problems in a vertical. This commit takes a very conservative approach and has the server side add the rendered HTML into a new data-content attribute on the
enclosing the problem. When Capa's JS initialization runs, it grabs from that data-content attribute rather than reaching over the network for an AJAX request. I had attempted to make it somewhat smarter and push the rendered problem straight into the document instead of relying on the data-content attribute. This was faster, and should be our long term goal. However, it caused odd bugs, particularly around MathJAX rendering, and I never quite tracked the issue down. I'm still going forward with these changes because it's significantly better than the current situation that students have to deal with, and we can make the JS more performant in a future iteration. [PERF-261] --- common/lib/xmodule/xmodule/capa_base.py | 1 + common/lib/xmodule/xmodule/js/src/capa/display.coffee | 3 ++- lms/templates/problem_ajax.html | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/capa_base.py b/common/lib/xmodule/xmodule/capa_base.py index 64e9729c2a..0eb1241de9 100644 --- a/common/lib/xmodule/xmodule/capa_base.py +++ b/common/lib/xmodule/xmodule/capa_base.py @@ -399,6 +399,7 @@ class CapaMixin(CapaFields): 'ajax_url': self.runtime.ajax_url, 'progress_status': Progress.to_js_status_str(progress), 'progress_detail': Progress.to_js_detail_str(progress), + 'content': self.get_problem_html(encapsulate=False) }) def check_button_name(self): diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.coffee b/common/lib/xmodule/xmodule/js/src/capa/display.coffee index ecd85d9226..524b33778e 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/capa/display.coffee @@ -5,6 +5,7 @@ class @Problem @id = @el.data('problem-id') @element_id = @el.attr('id') @url = @el.data('url') + @content = @el.data('content') # has_timed_out and has_response are used to ensure that are used to # ensure that we wait a minimum of ~ 1s before transitioning the check @@ -12,7 +13,7 @@ class @Problem @has_timed_out = false @has_response = false - @render() + @render(@content) $: (selector) -> $(selector, @el) diff --git a/lms/templates/problem_ajax.html b/lms/templates/problem_ajax.html index 05c3c2ada8..664b7567c0 100644 --- a/lms/templates/problem_ajax.html +++ b/lms/templates/problem_ajax.html @@ -1 +1 @@ -
+
From 13682e4694412a38fe7b551194041185709c5611 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Mon, 15 Feb 2016 01:28:15 -0500 Subject: [PATCH 2/2] Cache edxmako request context computation. When capa problem rendering was moved to happen inline on courseware page loads, we started executing many more Mako templates on sequences with large numbers of thse problems. To help offset this, we're caching the context generation (it showed up as the easiest piece of low hanging fruit on profiles of the courseware index page). [PERF-261] --- common/djangoapps/edxmako/middleware.py | 10 ++++++++++ common/djangoapps/edxmako/shortcuts.py | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/common/djangoapps/edxmako/middleware.py b/common/djangoapps/edxmako/middleware.py index cb67e2f142..e5268b4f55 100644 --- a/common/djangoapps/edxmako/middleware.py +++ b/common/djangoapps/edxmako/middleware.py @@ -19,6 +19,8 @@ from django.template.context import _builtin_context_processors from django.utils.module_loading import import_string from util.request import safe_get_host +from request_cache.middleware import RequestCache + REQUEST_CONTEXT = threading.local() @@ -51,6 +53,12 @@ def get_template_request_context(): request = getattr(REQUEST_CONTEXT, "request", None) if not request: return None + + request_cache_dict = RequestCache.get_request_cache().data + cache_key = "edxmako_request_context" + if cache_key in request_cache_dict: + return request_cache_dict[cache_key] + context = RequestContext(request) context['is_secure'] = request.is_secure() context['site'] = safe_get_host(request) @@ -62,4 +70,6 @@ def get_template_request_context(): for processor in get_template_context_processors(): context.update(processor(request)) + request_cache_dict[cache_key] = context + return context diff --git a/common/djangoapps/edxmako/shortcuts.py b/common/djangoapps/edxmako/shortcuts.py index aaa60f465d..da5ddfe3cd 100644 --- a/common/djangoapps/edxmako/shortcuts.py +++ b/common/djangoapps/edxmako/shortcuts.py @@ -33,7 +33,6 @@ def marketing_link(name): possible URLs for certain links. This function is to decides which URL should be provided. """ - # link_map maps URLs from the marketing site to the old equivalent on # the Django site link_map = settings.MKTG_URL_LINK_MAP