Skip to content

Commit

Permalink
feat: Add command to load test tracking events
Browse files Browse the repository at this point in the history
  • Loading branch information
bmtcril committed Mar 18, 2024
1 parent 3430bff commit f754df9
Show file tree
Hide file tree
Showing 8 changed files with 514 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
"""
Generates tracking events by creating test users and fake activity.
This should never be run on a production server as it will generate a lot of
bad data. It is entirely for benchmarking purposes in load test environments.
It is also fragile due to reaching into the edx-platform testing internals.
"""

import logging
import uuid
from datetime import datetime, timedelta
from random import choice
from textwrap import dedent
from time import sleep
from typing import Any

Check warning on line 15 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L9-L15

Added lines #L9 - L15 were not covered by tests

from django.contrib.auth.models import User
from django.core.management.base import BaseCommand, CommandError

Check warning on line 18 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L17-L18

Added lines #L17 - L18 were not covered by tests

try:
from cms.djangoapps.contentstore.views.course import create_new_course_in_store
from common.djangoapps.student.helpers import do_create_account
from common.djangoapps.student.models.course_enrollment import CourseEnrollment
from openedx.core.djangoapps.user_authn.views.registration_form import (

Check warning on line 24 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L20-L24

Added lines #L20 - L24 were not covered by tests
AccountCreationForm,
)
from xmodule.modulestore import ModuleStoreEnum

Check warning on line 27 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L27

Added line #L27 was not covered by tests

RUNNING_IN_PLATFORM = True
except ImportError:
RUNNING_IN_PLATFORM = False

Check warning on line 31 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L29-L31

Added lines #L29 - L31 were not covered by tests

log = logging.getLogger(__name__)

Check warning on line 33 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L33

Added line #L33 was not covered by tests


class LoadTest:

Check warning on line 36 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L36

Added line #L36 was not covered by tests
"""
Base class for setting up and sending events.
"""

course = None
instructor = None
users = []
sent_event_count = 0

Check warning on line 44 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L41-L44

Added lines #L41 - L44 were not covered by tests

def __init__(self, num_users: int, username_prefix: str):
course_shortname = str(uuid.uuid4())[:6]
self.instructor = self.create_user(

Check warning on line 48 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L46-L48

Added lines #L46 - L48 were not covered by tests
username=f"instructor_{course_shortname}",
name="Instructor",
password="aspects",
email=f"instructor_{course_shortname}@openedx.invalid",
)

start_date = datetime.now() - timedelta(days=7)

Check warning on line 55 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L55

Added line #L55 was not covered by tests

fields = {"start": start_date, "display_name": f"Course {course_shortname}"}

Check warning on line 57 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L57

Added line #L57 was not covered by tests

log.info(

Check warning on line 59 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L59

Added line #L59 was not covered by tests
f"""Creating course:
Instructor: {self.instructor.id}
Org: "OEX"
Number: "{course_shortname}"
Run: "2024-1"
Fields: {fields}
"""
)

self.course = create_new_course_in_store(

Check warning on line 69 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L69

Added line #L69 was not covered by tests
ModuleStoreEnum.Type.split,
self.instructor,
"OEX",
course_shortname,
"2024-1",
fields,
)

log.info(f"Created course {self.course.id}")
self.create_and_enroll_learners(num_users, username_prefix)

Check warning on line 79 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L78-L79

Added lines #L78 - L79 were not covered by tests

def create_and_enroll_learners(self, num_users, username_prefix):
log.info(f"Creating {num_users} users prefixed with {username_prefix}.")

Check warning on line 82 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L81-L82

Added lines #L81 - L82 were not covered by tests

for _ in range(num_users):
user_short_name = str(uuid.uuid4())[:6]
u = self.create_user(

Check warning on line 86 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L85-L86

Added lines #L85 - L86 were not covered by tests
username=f"{username_prefix}_{user_short_name}",
name=f"Learner {user_short_name}",
password="aspects",
email=f"{user_short_name}@openedx.invalid",
)
self.users.append(u)
e = CourseEnrollment.get_or_create_enrollment(

Check warning on line 93 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L92-L93

Added lines #L92 - L93 were not covered by tests
user=u, course_key=self.course.id
)
e.is_active = True
e.save()

Check warning on line 97 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L96-L97

Added lines #L96 - L97 were not covered by tests

def create_user(self, **user_data):
account_creation_form = AccountCreationForm(data=user_data, tos_required=False)

Check warning on line 100 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L99-L100

Added lines #L99 - L100 were not covered by tests

user, _, _ = do_create_account(account_creation_form)
user.is_active = True
user.save()
return user

Check warning on line 105 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L102-L105

Added lines #L102 - L105 were not covered by tests

def trigger_events(

Check warning on line 107 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L107

Added line #L107 was not covered by tests
self, num_events: int, sleep_time: float, run_until_killed: bool
) -> None:
if run_until_killed:
log.info(f"Creating events until killed with {sleep_time} between!")

Check warning on line 111 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L111

Added line #L111 was not covered by tests
while True:
self.trigger_event_and_sleep(sleep_time)

Check warning on line 113 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L113

Added line #L113 was not covered by tests
else:
log.info(f"Creating events {num_events} with {sleep_time} between!")

Check warning on line 115 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L115

Added line #L115 was not covered by tests
for _ in range(num_events):
self.trigger_event_and_sleep(sleep_time)

Check warning on line 117 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L117

Added line #L117 was not covered by tests

def trigger_event_and_sleep(self, sleep_time: float) -> None:
user = choice(self.users)
log.info(f"Triggering event for user {user.username}.")
e = CourseEnrollment.get_or_create_enrollment(

Check warning on line 122 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L119-L122

Added lines #L119 - L122 were not covered by tests
user=user, course_key=self.course.id
)

if e.is_active:
e.unenroll(user, self.course.id)

Check warning on line 127 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L127

Added line #L127 was not covered by tests
else:
e.enroll(user, self.course.id)

Check warning on line 129 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L129

Added line #L129 was not covered by tests

self.sent_event_count += 1
sleep(sleep_time)

Check warning on line 132 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L131-L132

Added lines #L131 - L132 were not covered by tests


class Command(BaseCommand):

Check warning on line 135 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L135

Added line #L135 was not covered by tests
"""
Create tracking log events for load testing purposes.
Example:
tutor local run lms ./manage.py lms load_test_tracking_events --sleep_time 0
"""

help = dedent(__doc__).strip()

Check warning on line 143 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L143

Added line #L143 was not covered by tests

def add_arguments(self, parser: Any) -> None:
parser.add_argument(

Check warning on line 146 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L145-L146

Added lines #L145 - L146 were not covered by tests
"--num_users",
type=int,
default=10,
help="The number of users to create. All events will be generated for these learners.",
)
parser.add_argument(

Check warning on line 152 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L152

Added line #L152 was not covered by tests
"--username_prefix",
type=str,
default="lt_",
help="Prefix for the generated user names.",
)
parser.add_argument(

Check warning on line 158 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L158

Added line #L158 was not covered by tests
"--num_events",
type=int,
default=10,
help="The number of events to generate. This is ignored if --run_until_killed is set.",
)
parser.add_argument(

Check warning on line 164 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L164

Added line #L164 was not covered by tests
"--run_until_killed",
action="store_true",
default=False,
help="If this is set, the process will run endlessly until killed.",
)
parser.add_argument(

Check warning on line 170 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L170

Added line #L170 was not covered by tests
"--sleep_time",
type=float,
default=0.75,
help="Fractional number of seconds to sleep between sending events.",
)

def handle(self, *args, **options):

Check warning on line 177 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L177

Added line #L177 was not covered by tests
"""
Creates users and triggers events for them as configured above.
"""
if not RUNNING_IN_PLATFORM:
raise CommandError("This command must be run in the Open edX LMS or CMS.")

Check warning on line 182 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L182

Added line #L182 was not covered by tests

start = datetime.now()
lt = LoadTest(options["num_users"], options["username_prefix"])

Check warning on line 185 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L184-L185

Added lines #L184 - L185 were not covered by tests

try:
lt.trigger_events(

Check warning on line 188 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L187-L188

Added lines #L187 - L188 were not covered by tests
options["num_events"],
options["sleep_time"],
options["run_until_killed"],
)
except KeyboardInterrupt:
log.warning("Killed by keyboard, finishing.")

Check warning on line 194 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L193-L194

Added lines #L193 - L194 were not covered by tests

end = datetime.now()
log.info(f"Sent {lt.sent_event_count} events in {end - start}.")

Check warning on line 197 in platform_plugin_aspects/management/commands/load_test_tracking_events.py

View check run for this annotation

Codecov / codecov/patch

platform_plugin_aspects/management/commands/load_test_tracking_events.py#L196-L197

Added lines #L196 - L197 were not covered by tests
Loading

0 comments on commit f754df9

Please sign in to comment.