How to Test Performance
Load Testing
Server Side
Legacy Tests - https://github.com/edx/load-tests (internal-only repo)
Current Tests - https://github.com/edx/edx-load-tests
Framework - https://github.com/locustio/locust(used currently in edx-load-tests. We also have a fork, and some legacy JMeter scripts)
Client Side (Browser)
Framework - http://www.sitespeed.io/ (Install via npm)
Useful add-in - https://github.com/edx/edx-sitespeed (for logged-in sessions)
Network throttling tools: Network Link Conditioner, tc (ubuntu native), wondersharper (which does not work on virtual machines), etc
Monitoring in Environments
New Relic
Profiling
PyInstrument
PyInstrument can be helpful because of the way it writes output data as a call tree. More info here:
https://github.com/joerick/pyinstrument
Example of a profile using pyinstrument, writing output to a file:
from pyinstrument import Profiler profiler = Profiler(use_signal=False) profiler.start() # Do stuff here profiler.stop() # write profile data to file with open('my.log', 'w') as f: f.write(profiler.output_text())
SnakeViz
Here are the steps to profile your python code with cProfile and then view the results using SnakeViz.
- export VAGRANT_X11=1
- vagrant ssh
- First time only: Install SnakeViz. Please update this document with the steps you used if you do it.
- sudo su edxapp
Add the following to the code that you want to profile
@contextmanager def collect_profile(file_prefix): """ Context manager to collect profile information. """ import cProfile import uuid profiler = cProfile.Profile() profiler.enable() try: yield finally: profiler.disable() profiler.dump_stats("{0}_{1}.profile".format(file_prefix, uuid.uuid4())) with collect_profile: <python_code_to_profile>
OR, if you want to profile an entire function, you can use this decorator:
def collect_profile_func(file_prefix, enabled=False): """ Method decorator for collecting profile. """ import functools def _outer(func): """ Outer function decorator. """ @functools.wraps(func) def _inner(self, *args, **kwargs): """ Inner wrapper function. """ if enabled: with collect_profile(file_prefix): return func(self, *args, **kwargs) else: return func(self, *args, **kwargs) return _inner return _outer @contextmanager def collect_profile(file_prefix): """ Context manager to collect profile information. """ import cProfile import uuid profiler = cProfile.Profile() profiler.enable() try: yield finally: profiler.disable() profiler.dump_stats("{0}_{1}.profile".format(file_prefix, uuid.uuid4()))
Then use SnakeViz by issuing:
snakeviz DIRECTORY/FILE_NAME.profile
Tips
When profiling locally, disable features that would affect performance numbers. For instance, the following can be set in the a local [lms/cms]/envs/private.py file:
from .common import INSTALLED_APPS, MIDDLEWARE_CLASSES, FEATURES def tuple_without(source_tuple, exclusion_list): """Return new tuple excluding any entries in the exclusion list. Needed because tuples are immutable. Order preserved.""" return tuple([i for i in source_tuple if i not in exclusion_list]) INSTALLED_APPS = tuple_without(INSTALLED_APPS, ['debug_toolbar', 'debug_toolbar_mongo']) MIDDLEWARE_CLASSES = tuple_without(MIDDLEWARE_CLASSES, [ 'django_comment_client.utils.QueryCountDebugMiddleware', 'debug_toolbar.middleware.DebugToolbarMiddleware', ]) DEBUG_TOOLBAR_MONGO_STACKTRACES = False import contracts contracts.disable_all()