How to run the tests (moodle 45)
Versión 3 (Emilio Penna, Jueves, 19 de Marzo de 2026 10:08:03 -0300) → Versión 4/9 (Emilio Penna, Jueves, 19 de Marzo de 2026 10:08:43 -0300)
h2. JMeter script configuration
Tested with JMeter 5.6.3 and Moodle 4.5.
h3. User-defined variables (Test Plan level)
All parameters are configured as user-defined variables at the top of the Test Plan:
|_.Variable|_.Description|_.Example|
|@curso@|Course ID (numeric)|@2@|
|@prueba@|Quiz module ID (numeric, the @cmid@)|@2@|
|@host@|Server hostname (no protocol)|@moodle-perf.example.edu.uy@| protocol)|@eva-perf.seciu.edu.uy@|
|@servidor@|Server URL with protocol|@https://moodle-perf.example.edu.uy@| protocol|@https://eva-perf.seciu.edu.uy@|
|@port@|HTTPS port|@443@|
|@csvfile@|Absolute path to the users CSV file|@/home/user/perftestusers5000.csv@|
|@twaitmin@|Minimum think time in milliseconds|@30000@|
|@twaitmax@|Maximum think time in milliseconds|@90000@|
|@assertiontext1@|Text asserted on quiz pages (English)|@page@|
|@assertiontext2@|Text asserted on quiz pages (Spanish)|@página@|
The @assertiontext1@/@assertiontext2@ pair handles Moodle installations in either language.
They verify that quiz attempt pages loaded correctly after each @processattempt.php@ call.
h3. Thread Group
Set the number of threads (VUs) and ramp-up time in the Thread Group. The script is configured
for a single iteration per thread (@LoopController.loops=1@): each virtual user logs in,
completes the quiz once, and exits. On error, the thread stops (@stopthread@).
We tested up to 3000 threads with a 180 s ramp-up.
Start with 1 thread to confirm the script runs without errors before scaling up.
h3. CSV Data Set Config
The script reads user credentials from the CSV file. Columns are: @user,n1,n2,mail,pwd@.
Set the @csvfile@ variable to the absolute path of your users file.
h3. Synchronizing Timer and Gaussian Random Timer
The script simulates students starting the exam at the same moment using two timers placed
inside the @startattempt@ transaction controller, just before the POST to @startattempt.php@:
* *Synchronizing Timer* (@groupSize=0@): holds all threads at this point until the entire
thread group has arrived, then releases them simultaneously. This models the worst-case
scenario of all students clicking "Start attempt" at exactly the same time.
* *Gaussian Random Timer* (deviation @30000@ ms, offset @5000@ ms): after release, each
thread waits a random delay before actually sending the startattempt request. With these
settings, approximately *66% of threads fire within the first 30 seconds*, and ~95% within
the first minute. This models the real-world observation that in large exams, 55–75% of
students tend to start in the first minute, making this a conservative worst-case scenario.
h3. Think time
Each quiz page uses a @TestAction@ pause of @${twait}@ milliseconds, where @twait@ is
initialized per thread at the start of the test by a JSR223 sampler (Groovy) that picks a
random value uniformly between @twaitmin@ and @twaitmax@. This means each virtual user has
a *fixed* think time for the entire test, but different users have different values — a
reasonable model for students answering at different speeds.
h3. Script coverage and limitations
The script covers the main HTTP interactions of the quiz flow: login, course page, quiz view,
exam start, answering all 16 questions across 8 pages (via @processattempt.php@), and
finishing the attempt. Quiz attempts and answers are saved correctly in Moodle — this has
been verified after each test run by reviewing the quiz results in the admin interface.
The script does not replicate every browser request. In particular, it omits most AJAX calls
(e.g. autosave, flag updates, analytics beacons) and does not fetch all embedded static
resources on each page. A real browser session generates significantly more requests per page.
However, the requests that drive server-side PHP processing — which is the actual bottleneck —
are all included. The "get static resources" controller in the script is disabled; static
asset delivery is better tested via @mod_cache@ or a dedicated CDN rather than through JMeter.
The @sesskey@ token (Moodle's CSRF protection) is extracted dynamically via regex after login
and after the quiz view page, and included in all subsequent POST requests. The @attempt@ ID
is similarly extracted after @startattempt.php@ and used throughout.