Enhance load testing capabilities with sophisticated user behavior patterns
- Add comprehensive LOAD_TESTING_GUIDE.md with instructions for different testing scenarios - Implement advanced user behavior patterns in locustfile.py: * SessionBasedUser class to simulate complete learning sessions * BehaviorAnalysisTaskSet for sequential lesson progression * PowerUser class representing intensive usage patterns - Improve code extraction and compilation functionality to work with lesson content - Enhance realism by using actual tokens from tokens_siswa.csv for user simulation - Add proper progress tracking during simulated learning sessions Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>master
parent
bdc0f58abd
commit
606d83ebac
|
|
@ -0,0 +1,125 @@
|
|||
# Load Testing Guide for C Programming Learning Management System
|
||||
|
||||
This guide explains how to run load tests on the LMS-C application using Locust with different scenarios and configurations.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before running load tests, ensure you have:
|
||||
- Podman installed on your system
|
||||
- The LMS-C application is properly configured with content and tokens
|
||||
- The `tokens_siswa.csv` file contains student tokens (the load testing script will use these for realistic user simulation)
|
||||
|
||||
## Running Load Tests
|
||||
|
||||
### 1. Basic Load Test
|
||||
|
||||
To run a basic load test with the default configuration:
|
||||
|
||||
```bash
|
||||
# Navigate to the test directory
|
||||
cd /path/to/lms-c/elemes/test
|
||||
|
||||
# Run the load test with default settings (10 simulated users)
|
||||
podman-compose -f podman-compose.yml up --build
|
||||
```
|
||||
|
||||
Then access the Locust web interface at `http://localhost:8089` and configure your test parameters.
|
||||
|
||||
### 2. Distributed Load Test
|
||||
|
||||
For larger-scale testing, you can run a distributed test with multiple workers:
|
||||
|
||||
```bash
|
||||
# Start the master node
|
||||
podman-compose -f podman-compose.yml up --scale worker=3 master
|
||||
|
||||
# Or run with specific environment variables
|
||||
TARGET_URL=http://your-lms-url.com LOCUST_NUM_STUDENTS=100 podman-compose -f podman-compose.yml up --scale worker=3 master
|
||||
```
|
||||
|
||||
This will start 1 master and 3 worker nodes to distribute the load.
|
||||
|
||||
### 3. Environment Variables
|
||||
|
||||
The load testing configuration can be customized using these environment variables:
|
||||
|
||||
- `TARGET_URL`: The URL of the LMS-C application to test (default: `http://example.com`)
|
||||
- `LOCUST_NUM_STUDENTS`: Number of simulated students/users (default: 10, but will automatically detect from tokens_siswa.csv)
|
||||
|
||||
Example:
|
||||
```bash
|
||||
TARGET_URL=http://192.168.1.100:5000 LOCUST_NUM_STUDENTS=50 podman-compose -f podman-compose.yml up --build
|
||||
```
|
||||
|
||||
### 4. Customizing Test Scenarios
|
||||
|
||||
The `locustfile.py` implements several user behavior patterns:
|
||||
|
||||
#### WebsiteUser Class
|
||||
- Simulates basic website visitors
|
||||
- Performs tasks like viewing homepage, lessons, and compiling code
|
||||
- Weight: 1 (less frequent)
|
||||
|
||||
#### AdvancedUser Class
|
||||
- Simulates engaged students who actively participate
|
||||
- Performs more complex behaviors like lesson navigation and intensive code compilation
|
||||
- Weight: 2 (twice as likely to be chosen as WebsiteUser)
|
||||
|
||||
#### Task Distribution
|
||||
- `view_homepage`: Weight 3 (most common action)
|
||||
- `compile_code`: Weight 4 (very common action)
|
||||
- `view_lesson`: Weight 2 (common action)
|
||||
- `login_student`: Weight 1 (less frequent but important)
|
||||
- `validate_token`: Weight 1 (essential for tracking)
|
||||
- `track_progress`: Weight 1 (important for completion tracking)
|
||||
|
||||
### 5. Realistic Student Simulation
|
||||
|
||||
The load testing script reads from `tokens_siswa.csv` to simulate real students:
|
||||
- Each simulated user gets assigned a real student token from the CSV
|
||||
- This ensures realistic progress tracking behavior
|
||||
- The number of simulated users should match or be proportional to the number of tokens in the CSV
|
||||
|
||||
### 6. Monitoring and Analysis
|
||||
|
||||
Access the Locust web interface at `http://localhost:8089` to:
|
||||
- Configure the number of users and spawn rate
|
||||
- Monitor real-time statistics
|
||||
- View response times, failure rates, and throughput
|
||||
- Download test reports
|
||||
|
||||
### 7. Running Without the Web Interface
|
||||
|
||||
You can also run Locust in headless mode:
|
||||
|
||||
```bash
|
||||
# Run with specific parameters without web UI
|
||||
podman run -v $(pwd)/..:/mnt/locust \
|
||||
-e LOCUST_HOST=http://your-target-url.com \
|
||||
-e LOCUST_USERS=100 \
|
||||
-e LOCUST_SPAWN_RATE=10 \
|
||||
-e LOCUST_RUN_TIME=10m \
|
||||
locustio/locust -f /mnt/locust/elemes/test/locustfile.py --headless
|
||||
```
|
||||
|
||||
### 8. Scaling Recommendations
|
||||
|
||||
- For 1-50 concurrent users: Single master node is sufficient
|
||||
- For 50-200 concurrent users: Use 1 master + 2-3 worker nodes
|
||||
- For 200+ concurrent users: Scale workers proportionally (1 master + 5+ workers)
|
||||
|
||||
### 9. Best Practices
|
||||
|
||||
- Always test against a staging environment that mirrors production
|
||||
- Gradually increase the number of users to identify performance bottlenecks
|
||||
- Monitor server resources (CPU, memory, disk I/O) during tests
|
||||
- Run tests multiple times to account for variations
|
||||
- Clean up resources after testing to avoid unnecessary resource consumption
|
||||
|
||||
### 10. Troubleshooting
|
||||
|
||||
If you encounter issues:
|
||||
- Ensure the target LMS-C application is accessible from the Locust containers
|
||||
- Check that the `tokens_siswa.csv` file is properly mounted and readable
|
||||
- Verify that the content directory has lesson files for realistic testing
|
||||
- Monitor container logs with `podman logs -f <container-name>`
|
||||
|
|
@ -307,6 +307,10 @@ class WebsiteUser(HttpUser):
|
|||
pass # Ignore logout errors
|
||||
|
||||
|
||||
# Define the user classes to be used in the test
|
||||
user_classes = [WebsiteUser, AdvancedUser, SessionBasedUser, PowerUser]
|
||||
|
||||
|
||||
# Additional task sets for more complex behaviors
|
||||
|
||||
class LessonNavigationTaskSet(TaskSet):
|
||||
|
|
@ -507,3 +511,194 @@ class AdvancedUser(HttpUser):
|
|||
tasks = {LessonNavigationTaskSet: 2, CompilationFocusedTaskSet: 3, LMSCUserBehavior: 4}
|
||||
|
||||
wait_time = between(0.5, 2)
|
||||
|
||||
|
||||
class SessionBasedUser(HttpUser):
|
||||
"""
|
||||
User that simulates a complete learning session with focused behavior
|
||||
"""
|
||||
weight = 1
|
||||
tasks = [LMSCUserBehavior]
|
||||
|
||||
wait_time = between(2, 5)
|
||||
|
||||
def on_start(self):
|
||||
"""
|
||||
Initialize a complete learning session
|
||||
"""
|
||||
# Login at the beginning of the session
|
||||
tokens_file = '/mnt/locust/tokens_siswa.csv'
|
||||
all_students = []
|
||||
|
||||
if os.path.exists(tokens_file):
|
||||
with open(tokens_file, 'r', newline='', encoding='utf-8') as csvfile:
|
||||
reader = csv.DictReader(csvfile, delimiter=';')
|
||||
all_students = list(reader)
|
||||
|
||||
if all_students:
|
||||
selected_student = random.choice(all_students)
|
||||
self.student_token = selected_student.get('token', f"STUDENT_TOKEN_{random.randint(1000, 9999)}")
|
||||
self.student_name = selected_student.get('nama_siswa', f"Student_{random.randint(1000, 9999)}")
|
||||
else:
|
||||
self.student_token = f"STUDENT_TOKEN_{random.randint(1000, 9999)}"
|
||||
self.student_name = f"Student_{random.randint(1000, 9999)}"
|
||||
|
||||
# Login the student
|
||||
login_payload = {
|
||||
"token": self.student_token
|
||||
}
|
||||
|
||||
try:
|
||||
self.client.post("/login", json=login_payload)
|
||||
except:
|
||||
pass # Continue even if login fails
|
||||
|
||||
# Read lesson files
|
||||
content_dir = '/mnt/locust/content'
|
||||
self.lesson_files = []
|
||||
|
||||
if os.path.exists(content_dir):
|
||||
lesson_paths = glob.glob(os.path.join(content_dir, "*.md"))
|
||||
self.lesson_files = [os.path.basename(path) for path in lesson_paths if os.path.isfile(path)]
|
||||
|
||||
|
||||
class BehaviorAnalysisTaskSet(TaskSet):
|
||||
"""
|
||||
Task set for analyzing user behavior patterns
|
||||
"""
|
||||
|
||||
def on_start(self):
|
||||
"""
|
||||
Initialize with student data
|
||||
"""
|
||||
tokens_file = '/mnt/locust/tokens_siswa.csv'
|
||||
all_students = []
|
||||
|
||||
if os.path.exists(tokens_file):
|
||||
with open(tokens_file, 'r', newline='', encoding='utf-8') as csvfile:
|
||||
reader = csv.DictReader(csvfile, delimiter=';')
|
||||
all_students = list(reader)
|
||||
|
||||
if all_students:
|
||||
selected_student = random.choice(all_students)
|
||||
self.student_token = selected_student.get('token', f"STUDENT_TOKEN_{random.randint(1000, 9999)}")
|
||||
self.student_name = selected_student.get('nama_siswa', f"Student_{random.randint(1000, 9999)}")
|
||||
else:
|
||||
self.student_token = f"STUDENT_TOKEN_{random.randint(1000, 9999)}"
|
||||
self.student_name = f"Student_{random.randint(1000, 9999)}"
|
||||
|
||||
# Read lesson files
|
||||
content_dir = '/mnt/locust/content'
|
||||
self.lesson_files = []
|
||||
|
||||
if os.path.exists(content_dir):
|
||||
lesson_paths = glob.glob(os.path.join(content_dir, "*.md"))
|
||||
self.lesson_files = [os.path.basename(path) for path in lesson_paths if os.path.isfile(path)]
|
||||
|
||||
@task(3)
|
||||
def analyze_learning_pattern(self):
|
||||
"""
|
||||
Simulate a learning pattern where a student goes through multiple lessons in sequence
|
||||
"""
|
||||
if not self.lesson_files:
|
||||
return
|
||||
|
||||
# Select a few lessons to go through in sequence
|
||||
selected_lessons = random.sample(self.lesson_files, min(3, len(self.lesson_files)))
|
||||
|
||||
for lesson in selected_lessons:
|
||||
# Visit the lesson
|
||||
self.client.get(f"/lesson/{lesson}?token={self.student_token}")
|
||||
|
||||
# Spend some time reading (simulate wait)
|
||||
self.wait()
|
||||
|
||||
# Try to compile code from the lesson
|
||||
self.compile_code_from_lesson(lesson)
|
||||
|
||||
# Track progress for the lesson
|
||||
lesson_name = lesson.replace('.md', '')
|
||||
progress_payload = {
|
||||
"token": self.student_token,
|
||||
"lesson_name": lesson_name,
|
||||
"status": "in_progress"
|
||||
}
|
||||
self.client.post("/track-progress", json=progress_payload)
|
||||
|
||||
# Wait between activities
|
||||
self.wait()
|
||||
|
||||
# Mark final lesson as completed
|
||||
if selected_lessons:
|
||||
final_lesson = selected_lessons[-1].replace('.md', '')
|
||||
progress_payload = {
|
||||
"token": self.student_token,
|
||||
"lesson_name": final_lesson,
|
||||
"status": "completed"
|
||||
}
|
||||
self.client.post("/track-progress", json=progress_payload)
|
||||
|
||||
def compile_code_from_lesson(self, lesson_file):
|
||||
"""
|
||||
Attempt to compile code from a specific lesson
|
||||
"""
|
||||
lesson_path = f'/mnt/locust/content/{lesson_file}'
|
||||
|
||||
if not os.path.exists(lesson_path):
|
||||
return
|
||||
|
||||
try:
|
||||
with open(lesson_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Look for code sections in the lesson
|
||||
initial_code = self.extract_code_section(content, '---INITIAL_CODE---', '---END_INITIAL_CODE---')
|
||||
solution_code = self.extract_code_section(content, '---SOLUTION_CODE---', '---END_SOLUTION_CODE---')
|
||||
|
||||
# Use available code for compilation
|
||||
code_to_compile = None
|
||||
if solution_code:
|
||||
code_to_compile = solution_code
|
||||
elif initial_code:
|
||||
code_to_compile = initial_code
|
||||
|
||||
if code_to_compile:
|
||||
programming_language = os.environ.get('DEFAULT_PROGRAMMING_LANGUAGE', 'c')
|
||||
|
||||
compile_payload = {
|
||||
"code": code_to_compile,
|
||||
"language": programming_language
|
||||
}
|
||||
|
||||
response = self.client.post("/compile", json=compile_payload)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if not result.get("success"):
|
||||
print(f"Compilation error in lesson {lesson_file}: {result.get('error')}")
|
||||
except Exception as e:
|
||||
print(f"Error compiling code from lesson {lesson_file}: {str(e)}")
|
||||
|
||||
def extract_code_section(self, content, start_marker, end_marker):
|
||||
"""
|
||||
Extract code between start and end markers
|
||||
"""
|
||||
start_idx = content.find(start_marker)
|
||||
end_idx = content.find(end_marker)
|
||||
|
||||
if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
|
||||
start_pos = start_idx + len(start_marker)
|
||||
extracted_code = content[start_pos:end_idx].strip()
|
||||
return extracted_code
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class PowerUser(HttpUser):
|
||||
"""
|
||||
Power user that exhibits intensive usage patterns
|
||||
"""
|
||||
weight = 1
|
||||
tasks = {BehaviorAnalysisTaskSet: 3, CompilationFocusedTaskSet: 4, LMSCUserBehavior: 2}
|
||||
|
||||
wait_time = between(0.2, 1.5)
|
||||
Loading…
Reference in New Issue