How to run the tests (moodle 45)

Versión 6 (Emilio Penna, Jueves, 19 de Marzo de 2026 10:17:39 -0300)

1 5 Emilio Penna
h1. Moodle Quiz Load Testing — Setup and Procedure (Moodle 4.5, 2026)
2 5 Emilio Penna
3 5 Emilio Penna
This page describes how to set up and run the quiz load tests.
4 5 Emilio Penna
For actual results and infrastructure sizing data, see: [[Load_test_results_moodle45_2026]]
5 5 Emilio Penna
6 5 Emilio Penna
Attached to this page:
7 5 Emilio Penna
* Moodle quiz backup (16-question quiz, 8 pages)
8 5 Emilio Penna
* Test users file (CSV, for bulk upload and enrollment)
9 5 Emilio Penna
* JMeter script (jmx)
10 5 Emilio Penna
11 5 Emilio Penna
---
12 5 Emilio Penna
13 5 Emilio Penna
h2. Setting up test data in Moodle
14 5 Emilio Penna
15 5 Emilio Penna
# Create a course for the test (or use an existing one).
16 5 Emilio Penna
# Restore the quiz backup (@.mbz@ file) into that course.
17 5 Emilio Penna
# Upload test users via _Site administration > Users > Accounts > Upload users_.
18 5 Emilio Penna
** The CSV file contains 5000 users. Edit the @course@ column to match your course shortname.
19 5 Emilio Penna
** Set "Force password change" to _None_.
20 5 Emilio Penna
# Enroll all users in the course (handled by the upload if the CSV includes the course column).
21 5 Emilio Penna
22 6 Emilio Penna
Also, if upload of csv does not work, you can use cli script admin/tool/uploaduser/cli/uploaduser.php
23 6 Emilio Penna
24 5 Emilio Penna
After the test, verify that attempts and answers were recorded correctly in the quiz results.
25 5 Emilio Penna
26 5 Emilio Penna
*Resetting between runs:* if a test run is interrupted before finishing, attempts remain "in progress".
27 5 Emilio Penna
Users cannot start a new attempt in that state. Reset with:
28 5 Emilio Penna
29 5 Emilio Penna
<pre>
30 5 Emilio Penna
UPDATE mdl_quiz_attempts SET state='finished' WHERE quiz=<quiz_id>;
31 5 Emilio Penna
</pre>
32 5 Emilio Penna
33 5 Emilio Penna
---
34 5 Emilio Penna
35 5 Emilio Penna
36 5 Emilio Penna
h2. JMeter script configuration
37 5 Emilio Penna
38 1
39 2 Emilio Penna
Tested with JMeter 5.6.3 and Moodle 4.5.
40 1
41 2 Emilio Penna
h3. User-defined variables (Test Plan level)
42 1
43 2 Emilio Penna
All parameters are configured as user-defined variables at the top of the Test Plan:
44 2 Emilio Penna
45 2 Emilio Penna
|_.Variable|_.Description|_.Example|
46 1
|@curso@|Course ID (numeric)|@2@|
47 1
|@prueba@|Quiz module ID (numeric, the @cmid@)|@2@|
48 6 Emilio Penna
|@host@|Server hostname (no protocol)|@moodle-perf.example.edu.uy@|
49 6 Emilio Penna
|@servidor@|Server URL with protocol|@https://moodle-perf.example.edu.uy@|
50 2 Emilio Penna
|@port@|HTTPS port|@443@|
51 2 Emilio Penna
|@csvfile@|Absolute path to the users CSV file|@/home/user/perftestusers5000.csv@|
52 2 Emilio Penna
|@twaitmin@|Minimum think time in milliseconds|@30000@|
53 2 Emilio Penna
|@twaitmax@|Maximum think time in milliseconds|@90000@|
54 2 Emilio Penna
|@assertiontext1@|Text asserted on quiz pages (English)|@page@|
55 2 Emilio Penna
|@assertiontext2@|Text asserted on quiz pages (Spanish)|@página@|
56 1
57 2 Emilio Penna
The @assertiontext1@/@assertiontext2@ pair handles Moodle installations in either language.
58 2 Emilio Penna
They verify that quiz attempt pages loaded correctly after each @processattempt.php@ call.
59 1
60 2 Emilio Penna
h3. Thread Group
61 1
62 2 Emilio Penna
Set the number of threads (VUs) and ramp-up time in the Thread Group. The script is configured
63 2 Emilio Penna
for a single iteration per thread (@LoopController.loops=1@): each virtual user logs in,
64 2 Emilio Penna
completes the quiz once, and exits. On error, the thread stops (@stopthread@).
65 1
66 2 Emilio Penna
We tested up to 3000 threads with a 180 s ramp-up.
67 1
68 2 Emilio Penna
Start with 1 thread to confirm the script runs without errors before scaling up.
69 1
70 2 Emilio Penna
h3. CSV Data Set Config
71 1
72 2 Emilio Penna
The script reads user credentials from the CSV file. Columns are: @user,n1,n2,mail,pwd@.
73 2 Emilio Penna
Set the @csvfile@ variable to the absolute path of your users file.
74 1
75 2 Emilio Penna
h3. Synchronizing Timer and Gaussian Random Timer
76 1
77 2 Emilio Penna
The script simulates students starting the exam at the same moment using two timers placed
78 2 Emilio Penna
inside the @startattempt@ transaction controller, just before the POST to @startattempt.php@:
79 1
80 2 Emilio Penna
* *Synchronizing Timer* (@groupSize=0@): holds all threads at this point until the entire
81 2 Emilio Penna
  thread group has arrived, then releases them simultaneously. This models the worst-case
82 2 Emilio Penna
  scenario of all students clicking "Start attempt" at exactly the same time.
83 2 Emilio Penna
* *Gaussian Random Timer* (deviation @30000@ ms, offset @5000@ ms): after release, each
84 2 Emilio Penna
  thread waits a random delay before actually sending the startattempt request. With these
85 2 Emilio Penna
  settings, approximately *66% of threads fire within the first 30 seconds*, and ~95% within
86 2 Emilio Penna
  the first minute. This models the real-world observation that in large exams, 55–75% of
87 2 Emilio Penna
  students tend to start in the first minute, making this a conservative worst-case scenario.
88 1
89 2 Emilio Penna
h3. Think time
90 1
91 2 Emilio Penna
Each quiz page uses a @TestAction@ pause of @${twait}@ milliseconds, where @twait@ is
92 2 Emilio Penna
initialized per thread at the start of the test by a JSR223 sampler (Groovy) that picks a
93 2 Emilio Penna
random value uniformly between @twaitmin@ and @twaitmax@. This means each virtual user has
94 2 Emilio Penna
a *fixed* think time for the entire test, but different users have different values — a
95 2 Emilio Penna
reasonable model for students answering at different speeds.
96 1
97 2 Emilio Penna
h3. Script coverage and limitations
98 2 Emilio Penna
99 2 Emilio Penna
The script covers the main HTTP interactions of the quiz flow: login, course page, quiz view,
100 2 Emilio Penna
exam start, answering all 16 questions across 8 pages (via @processattempt.php@), and
101 2 Emilio Penna
finishing the attempt. Quiz attempts and answers are saved correctly in Moodle — this has
102 2 Emilio Penna
been verified after each test run by reviewing the quiz results in the admin interface.
103 2 Emilio Penna
104 2 Emilio Penna
The script does not replicate every browser request. In particular, it omits most AJAX calls
105 2 Emilio Penna
(e.g. autosave, flag updates, analytics beacons) and does not fetch all embedded static
106 2 Emilio Penna
resources on each page. A real browser session generates significantly more requests per page.
107 2 Emilio Penna
However, the requests that drive server-side PHP processing — which is the actual bottleneck —
108 2 Emilio Penna
are all included. The "get static resources" controller in the script is disabled; static
109 2 Emilio Penna
asset delivery is better tested via @mod_cache@ or a dedicated CDN rather than through JMeter.
110 2 Emilio Penna
111 2 Emilio Penna
The @sesskey@ token (Moodle's CSRF protection) is extracted dynamically via regex after login
112 1
and after the quiz view page, and included in all subsequent POST requests. The @attempt@ ID
113 1
is similarly extracted after @startattempt.php@ and used throughout.
114 5 Emilio Penna
115 5 Emilio Penna
---
116 5 Emilio Penna
117 5 Emilio Penna
h2. Running the tests
118 5 Emilio Penna
119 5 Emilio Penna
From the command line (recommended for load tests — GUI mode has overhead):
120 5 Emilio Penna
121 5 Emilio Penna
<pre>
122 5 Emilio Penna
./jmeter -n -t /path/to/moo4quiz.jmx -l /path/to/output.jtl \
123 5 Emilio Penna
  -Xms8g -Xmx8g -XX:+UseG1GC
124 5 Emilio Penna
</pre>
125 5 Emilio Penna
126 5 Emilio Penna
*JMeter machine sizing:* use a dedicated machine. We used 32 GB RAM / 8 CPU.
127 5 Emilio Penna
With a single client we ran up to 2000 VUs reliably. For 3000 VUs we did not observe
128 5 Emilio Penna
client-side errors, but if you do, consider splitting the load across two JMeter instances
129 5 Emilio Penna
or using JMeter's distributed testing mode.
130 5 Emilio Penna
131 5 Emilio Penna
---
132 5 Emilio Penna
133 5 Emilio Penna
h2. Server monitoring during tests
134 5 Emilio Penna
135 5 Emilio Penna
Capture server-side metrics during each run to correlate with JMeter results.
136 5 Emilio Penna
A simple shell script running at 10-second intervals collecting the following is sufficient:
137 5 Emilio Penna
138 5 Emilio Penna
* @uptime@ — load average (1, 5, 15 min)
139 5 Emilio Penna
* @free -m@ — RAM and swap used
140 5 Emilio Penna
* @ps aux | grep apache@ — Apache process count
141 5 Emilio Penna
* @curl -s 127.0.0.1/fpm-status@ — PHP-FPM pool status (total, busy, idle workers)
142 5 Emilio Penna
* @ss -s@ or @netstat@ — HTTPS connections (port 443), MySQL TCP connections
143 5 Emilio Penna
144 5 Emilio Penna
Write output as CSV (one line per interval) for easy post-processing.
145 5 Emilio Penna
146 5 Emilio Penna
For the database server, @nmon -f -s 5@ provides CPU, memory, disk I/O, and network at
147 5 Emilio Penna
5-second granularity.
148 5 Emilio Penna
149 5 Emilio Penna
---
150 5 Emilio Penna
151 5 Emilio Penna
h2. Tips and lessons learned
152 5 Emilio Penna
153 5 Emilio Penna
* *Repeat each test at least twice.* On shared VMware infrastructure, hypervisor contention
154 5 Emilio Penna
  from other VMs introduces variability. Two identical runs let you distinguish real behavior
155 5 Emilio Penna
  from noise.
156 5 Emilio Penna
* *The startattempt spike is the critical moment.* Everything else in the quiz is easy for
157 5 Emilio Penna
  the server. Focus your analysis on the synchronized exam start — that is where the system
158 5 Emilio Penna
  will fail first.
159 5 Emilio Penna
* *mod_cache on the reverse proxy* improves static asset delivery (10–35% latency reduction)
160 5 Emilio Penna
  but can worsen the startattempt spike by delivering pre-exam pages faster, clustering VUs
161 5 Emilio Penna
  more tightly at the synchronization point.
162 5 Emilio Penna
* *CPU, not RAM, is the bottleneck* with PHP-FPM + OPcache. Don't over-provision RAM at the
163 5 Emilio Penna
  expense of CPU cores. See [[Load_test_results_moodle45_2026]] for detailed data.
164 5 Emilio Penna
* *InnoDB buffer pool sizing matters.* If the working dataset fits in the buffer pool,
165 5 Emilio Penna
  the database server will show near-zero read I/O during the test. Ensure
166 5 Emilio Penna
  @innodb_buffer_pool_size@ is set appropriately (we used 16 GB RAM on the DB server).
167 5 Emilio Penna
* *Check OPcache hit rate* during tests (@opcache_get_status()@). With a warm cache,
168 5 Emilio Penna
  hit rate should be above 99%. A cold OPcache at test start will produce artificially
169 5 Emilio Penna
  poor results for the first few requests.