Commit 1325d493 authored by David Monllaó's avatar David Monllaó
Browse files

MDL-41421 tool_generator: Adding a test plan generator to feed JMeter

Also changing course_backend.php coding style
according to codechecker.
parent 8520de1e
......@@ -328,7 +328,7 @@ class tool_generator_course_backend extends tool_generator_backend {
// Create pages.
$number = self::$parampages[$this->size];
$this->log('createpages', $number, true);
for ($i=0; $i<$number; $i++) {
for ($i = 0; $i < $number; $i++) {
$record = array('course' => $this->course->id);
$options = array('section' => $this->get_target_section());
$pagegenerator->create_instance($record, $options);
......@@ -385,7 +385,7 @@ class tool_generator_course_backend extends tool_generator_backend {
return substr($data, -$length);
}
$length -= strlen($data);
for ($j=0; $j < $length; $j++) {
for ($j = 0; $j < $length; $j++) {
$data .= chr(rand(1, 255));
}
return $data;
......@@ -463,13 +463,13 @@ class tool_generator_course_backend extends tool_generator_backend {
// Add discussions and posts.
$sofar = 0;
for ($i=0; $i < $discussions; $i++) {
for ($i = 0; $i < $discussions; $i++) {
$record = array('forum' => $forum->id, 'course' => $this->course->id,
'userid' => $this->get_target_user());
$discussion = $forumgenerator->create_discussion($record);
$parentid = $DB->get_field('forum_posts', 'id', array('discussion' => $discussion->id), MUST_EXIST);
$sofar++;
for ($j=0; $j < $posts - 1; $j++, $sofar++) {
for ($j = 0; $j < $posts - 1; $j++, $sofar++) {
$record = array('discussion' => $discussion->id,
'userid' => $this->get_target_user(), 'parent' => $parentid);
$forumgenerator->create_post($record);
......
......@@ -14,6 +14,14 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course form.
*
* @package tool_generator
* @copyright 2013 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
......@@ -60,7 +68,7 @@ class tool_generator_make_course_form extends moodleform {
// Check course doesn't already exist.
if (!empty($data['shortname'])) {
// Check shortname.
$error = tool_generator_course_backend::check_shortname_available($data['shortname']);
$error = tool_generator_course_backend::check_shortname_available($data['shortname']);
if ($error) {
$errors['shortname'] = $error;
}
......
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Test plan form.
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
/**
* Form with options for creating large course.
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_generator_make_testplan_form extends moodleform {
/**
* Test plan form definition.
*
* @return void
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('select', 'size', get_string('size', 'tool_generator'),
tool_generator_testplan_backend::get_size_choices());
$mform->setDefault('size', tool_generator_testplan_backend::DEFAULT_SIZE);
$mform->addElement('select', 'courseid', get_string('targetcourse', 'tool_generator'),
tool_generator_testplan_backend::get_course_options());
$mform->addElement('advcheckbox', 'updateuserspassword', get_string('updateuserspassword', 'tool_generator'));
$mform->addHelpButton('updateuserspassword', 'updateuserspassword', 'tool_generator');
$mform->addElement('submit', 'submit', get_string('createtestplan', 'tool_generator'));
}
/**
* Checks that the submitted data allows us to create a test plan.
*
* @param array $data
* @param array $files
* @return array An array of errors
*/
public function validation($data, $files) {
global $CFG, $DB;
$errors = array();
if (empty($CFG->tool_generator_users_password) || is_bool($CFG->tool_generator_users_password)) {
$errors['updateuserspassword'] = get_string('error_nouserspassword', 'tool_generator');
}
// Check the course has users.
// Better to repeat here the query than to do it afterwards and end up with an exception.
$coursecontext = context_course::instance($data['courseid']);
if (!$users = get_enrolled_users($coursecontext, '', 0, 'u.id')) {
$errors['courseid'] = get_string('coursewithoutusers', 'tool_generator');
}
// Checks that the selected course has enough users.
$coursesizes = tool_generator_course_backend::get_users_per_size();
if (count($users) < $coursesizes[$data['size']]) {
$errors['size'] = get_string('notenoughusers', 'tool_generator');
}
return $errors;
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Test plan generator.
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Generates the files required by JMeter.
*
* @package tool_generator
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_generator_testplan_backend extends tool_generator_backend {
/**
* @var Number of users depending on the selected size.
*/
protected static $users = array(1, 30, 200, 1000, 5000, 10000);
/**
* @var Number of loops depending on the selected size.
*/
protected static $loops = array(1, 1, 2, 3, 3, 5);
/**
* @var Rampup period depending on the selected size.
*/
protected static $rampups = array(1, 6, 40, 100, 500, 800);
/**
* Gets a list of size choices supported by this backend.
*
* @return array List of size (int) => text description for display
*/
public static function get_size_choices() {
$options = array();
for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
$a = new stdClass();
$a->users = self::$users[$size];
$a->loops = self::$loops[$size];
$a->rampup = self::$rampups[$size];
$options[$size] = get_string('testplansize_' . $size, 'tool_generator', $a);
}
return $options;
}
/**
* Gets the list of courses that can be used used to generate a test.
*
* @return array The list of options as courseid => name
*/
public static function get_course_options() {
$courses = get_courses('all', 'c.sortorder ASC', 'c.id, c.shortname, c.fullname');
if (!$courses) {
print_error('error_nocourses', 'tool_generator');
}
$options = array();
unset($courses[1]);
foreach ($courses as $course) {
$options[$course->id] = $course->fullname . '(' . $course->shortname . ')';
}
return $options;
}
/**
* Creates the test plan file.
*
* @param int $courseid The target course id
* @param int $size The test plan size
* @return stored_file
*/
public static function create_testplan_file($courseid, $size) {
$jmxcontents = self::generate_test_plan($courseid, $size);
$fs = get_file_storage();
$filerecord = self::get_file_record('testplan', 'jmx');
return $fs->create_file_from_string($filerecord, $jmxcontents);
}
/**
* Creates the users data file.
*
* @param int $courseid The target course id
* @param bool $updateuserspassword Updates the course users password to $CFG->tool_generator_users_password
* @return stored_file
*/
public static function create_users_file($courseid, $updateuserspassword) {
$csvcontents = self::generate_users_file($courseid, $updateuserspassword);
$fs = get_file_storage();
$filerecord = self::get_file_record('users', 'csv');
return $fs->create_file_from_string($filerecord, $csvcontents);
}
/**
* Generates the test plan according to the target course contents.
*
* @param int $targetcourseid The target course id
* @param int $size The test plan size
* @return string The test plan as a string
*/
protected static function generate_test_plan($targetcourseid, $size) {
global $CFG;
// Getting the template.
$template = file_get_contents(__DIR__ . '/../testplan.template.jmx');
// Getting the course modules data.
$coursedata = self::get_course_test_data($targetcourseid);
// Host and path to the site.
$urlcomponents = parse_url($CFG->wwwroot);
if (empty($urlcomponents['path'])) {
$urlcomponents['path'] = '';
}
$replacements = array(
self::$users[$size],
self::$loops[$size],
self::$rampups[$size],
$urlcomponents['host'],
$urlcomponents['path'],
get_string('shortsize_' . $size, 'tool_generator'),
$targetcourseid,
$coursedata->pageid,
$coursedata->forumid,
$coursedata->forumdiscussionid,
$coursedata->forumreplyid
);
$placeholders = array(
'{{USERS_PLACEHOLDER}}',
'{{LOOPS_PLACEHOLDER}}',
'{{RAMPUP_PLACEHOLDER}}',
'{{HOST_PLACEHOLDER}}',
'{{SITEPATH_PLACEHOLDER}}',
'{{SIZE_PLACEHOLDER}}',
'{{COURSEID_PLACEHOLDER}}',
'{{PAGEACTIVITYID_PLACEHOLDER}}',
'{{FORUMACTIVITYID_PLACEHOLDER}}',
'{{FORUMDISCUSSIONID_PLACEHOLDER}}',
'{{FORUMREPLYID_PLACEHOLDER}}'
);
// Fill the template with the target course values.
return str_replace($placeholders, $replacements, $template);
}
/**
* Generates the user's credentials file with all the course's users
*
* @param int $targetcourseid
* @param bool $updateuserspassword Updates the course users password to $CFG->tool_generator_users_password
* @return string The users csv file contents.
*/
protected static function generate_users_file($targetcourseid, $updateuserspassword) {
global $CFG;
$coursecontext = context_course::instance($targetcourseid);
$users = get_enrolled_users($coursecontext, '', 0, 'u.id, u.username, u.auth', 'u.username ASC');
if (!$users) {
print_error('coursewithoutusers', 'tool_generator');
}
$lines = array();
foreach ($users as $user) {
// Updating password to the one set in config.php.
if ($updateuserspassword) {
$userauth = get_auth_plugin($user->auth);
if (!$userauth->user_update_password($user, $CFG->tool_generator_users_password)) {
print_error('errorpasswordupdate', 'auth');
}
}
// Here we already checked that $CFG->tool_generator_users_password is not null.
$lines[] = $user->username . ',' . $CFG->tool_generator_users_password;
}
return implode(PHP_EOL, $lines);
}
/**
* Returns a tool_generator file record
*
* @param string $filearea testplan or users
* @param string $filetype The file extension jmx or csv
* @return stdClass The file record to use when creating tool_generator files
*/
protected static function get_file_record($filearea, $filetype) {
$systemcontext = context_system::instance();
$filerecord = new stdClass();
$filerecord->contextid = $systemcontext->id;
$filerecord->component = 'tool_generator';
$filerecord->filearea = $filearea;
$filerecord->itemid = 0;
$filerecord->filepath = '/';
// Random generated number to avoid concurrent execution problems.
$filerecord->filename = $filearea . '_' . date('YmdHi', time()) . '_' . rand(1000, 9999) . '.' . $filetype;
return $filerecord;
}
/**
* Gets the data required to fill the test plan template with the database contents.
*
* @param int $targetcourseid The target course id
* @return stdClass The ids required by the test plan
*/
protected static function get_course_test_data($targetcourseid) {
global $DB, $USER;
$data = new stdClass();
// Getting course contents info as the current user (will be an admin).
$course = new stdClass();
$course->id = $targetcourseid;
$courseinfo = new course_modinfo($course, $USER->id);
// Getting the first page module instance.
if (!$pages = $courseinfo->get_instances_of('page')) {
print_error('error_nopageinstances', 'tool_generator');
}
$data->pageid = reset($pages)->id;
// Getting the first forum module instance and it's first discussion and reply as well.
if (!$forums = $courseinfo->get_instances_of('forum')) {
print_error('error_noforuminstances', 'tool_generator');
}
$forum = reset($forums);
// Getting the first discussion (and reply).
if (!$discussions = forum_get_discussions($forum, 'd.timemodified ASC', false, -1, 1)) {
print_error('error_noforumdiscussions', 'tool_generator');
}
$discussion = reset($discussions);
$data->forumid = $forum->id;
$data->forumdiscussionid = $discussion->discussion;
$data->forumreplyid = $discussion->id;
// According to the current test plan.
return $data;
}
}
......@@ -23,16 +23,7 @@
*/
$string['bigfile'] = 'Big file {$a}';
$string['coursesize_0'] = 'XS (~10KB; create in ~1 second)';
$string['coursesize_1'] = 'S (~10MB; create in ~30 seconds)';
$string['coursesize_2'] = 'M (~100MB; create in ~5 minutes)';
$string['coursesize_3'] = 'L (~1GB; create in ~1 hour)';
$string['coursesize_4'] = 'XL (~10GB; create in ~4 hours)';
$string['coursesize_5'] = 'XXL (~20GB; create in ~8 hours)';
$string['createcourse'] = 'Create course';
$string['creating'] = 'Creating course';
$string['done'] = 'done ({$a}s)';
$string['explanation'] = 'This tool creates standard test courses that include many
$string['courseexplanation'] = 'This tool creates standard test courses that include many
sections, activities, and files.
This is intended to provide a standardised measure for checking the reliability
......@@ -50,16 +41,38 @@ filesystem space (tens of gigabytes). You will need to delete the courses
(To avoid accidental use, this feature is disabled unless you have also selected
DEVELOPER debugging level.)';
$string['coursesize_0'] = 'XS (~10KB; create in ~1 second)';
$string['coursesize_1'] = 'S (~10MB; create in ~30 seconds)';
$string['coursesize_2'] = 'M (~100MB; create in ~5 minutes)';
$string['coursesize_3'] = 'L (~1GB; create in ~1 hour)';
$string['coursesize_4'] = 'XL (~10GB; create in ~4 hours)';
$string['coursesize_5'] = 'XXL (~20GB; create in ~8 hours)';
$string['coursewithoutusers'] = 'The selected course has no users';
$string['createcourse'] = 'Create course';
$string['createtestplan'] = 'Create test plan';
$string['creating'] = 'Creating course';
$string['done'] = 'done ({$a}s)';
$string['downloadtestplan'] = 'Download test plan';
$string['downloadusersfile'] = 'Download users file';
$string['error_nocourses'] = 'There are no courses to generate the test plan';
$string['error_noforumdiscussions'] = 'The selected course does not contain forum discussions';
$string['error_noforuminstances'] = 'The selected course does not contain forum module instances';
$string['error_noforumreplies'] = 'The selected course does not contain forum replies';
$string['error_nonexistingcourse'] = 'The specified course does not exist';
$string['error_nopageinstances'] = 'The selected course does not contain page module instances';
$string['error_notdebugging'] = 'Not available on this server because debugging is not set to DEVELOPER';
$string['error_nouserspassword'] = 'You need to set $CFG->tool_generator_users_password in config.php to generate the test plan';
$string['firstname'] = 'Test course user';
$string['fullname'] = 'Test course: {$a->size}';
$string['maketestcourse'] = 'Make test course';
$string['maketestplan'] = 'Make JMeter test plan';
$string['notenoughusers'] = 'The selected course does not have enough users';
$string['pluginname'] = 'Development data generator';
$string['progress_createcourse'] = 'Creating course {$a}';
$string['progress_checkaccounts'] = 'Checking user accounts ({$a})';
$string['progress_coursecompleted'] = 'Course completed ({$a}s)';
$string['progress_createaccounts'] = 'Creating user accounts ({$a->from} - {$a->to})';
$string['progress_createbigfiles'] = 'Creating big files ({$a})';
$string['progress_createcourse'] = 'Creating course {$a}';
$string['progress_createforum'] = 'Creating forum ({$a} posts)';
$string['progress_createpages'] = 'Creating pages ({$a})';
$string['progress_createsmallfiles'] = 'Creating small files ({$a})';
......@@ -79,3 +92,38 @@ $string['sitesize_4'] = 'XL (~10GB; 1065 courses, created in ~5 hours)';
$string['sitesize_5'] = 'XXL (~20GB; 4177 courses, created in ~10 hours)';
$string['size'] = 'Size of course';
$string['smallfiles'] = 'Small files';
$string['targetcourse'] = 'Test target course';
$string['testplanexplanation'] = 'This tool creates a JMeter test plan file along with the user credentials file.
This test plan is designed to work along with {$a}, which makes easier to run the test plan in a specific Moodle environment, gathers information about the runs and compares the results, so you will need to download it and use it\'s test_runner.sh script or follow the installation and usage instructions.
You need to set a password for the course users in config.php (p.e. $CFG->tool_generator_users_password = \'moodle\';) there
is no default value for this password to prevent unintended usages of the tool.
It is part of tool_generator so it works well with the courses generated by the courses and the site generators, it can
also be used with any course that contains, at least:
* Enough enrolled users (depends on the test plan size you select) with the password reset to \'moodle\'
* A page module instance
* A forum module instance with at least one discussion and one reply
You might want to consider your servers capacity when running large test plans as the amount to load generated by JMeter
can be specially big. The ramp up period has been adjusted according to the number of threads (users) to reduce this kind
of issues but the load is still huge.
**Do not run the test plan on a live system**. This feature only creates the files to feed JMeter so is not dangerous by
itself, but you should **NEVER** run this test plan in a production site as:
* It changes the database contents
* Logins lots of users at the same time so there is risk that your server can not handle the load and dies
* It can update the course users passwords
';
$string['testplansize_0'] = 'XS ({$a->users} users, {$a->loops} loops and {$a->rampup} rampup period)';
$string['testplansize_1'] = 'S ({$a->users} users, {$a->loops} loops and {$a->rampup} rampup period)';
$string['testplansize_2'] = 'M ({$a->users} users, {$a->loops} loops and {$a->rampup} rampup period)';
$string['testplansize_3'] = 'L ({$a->users} users, {$a->loops} loops and {$a->rampup} rampup period)';
$string['testplansize_4'] = 'XL ({$a->users} users, {$a->loops} loops and {$a->rampup} rampup period)';
$string['testplansize_5'] = 'XXL ({$a->users} users, {$a->loops} loops and {$a->rampup} rampup period)';
$string['updateuserspassword'] = 'Update course users password';
$string['updateuserspassword_help'] = 'JMeter needs to login as the course users, you can set the users password using $CFG->tool_generator_users_password in config.php; this setting updates the course user\'s password according to $CFG->tool_generator_users_password. It can be useful in case you are using a course not generated by tool_generator or $CFG->tool_generator_users_password was not set when you created the test courses.';
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Generator tool functions.
*
* @package tool_generator
* @copyright David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Files support.
*
* Exits if the required permissions are not satisfied.
*
* @param stdClass $course course object
* @param stdClass $cm
* @param stdClass $context context object
* @param string $filearea file area
* @param array $args extra arguments
* @param bool $forcedownload whether or not force download
* @param array $options additional options affecting the file serving
* @return void The file is sent along with it's headers
*/
function tool_generator_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) {
// Only for admins.
if (!is_siteadmin()) {
die;
}
if ($context->contextlevel != CONTEXT_SYSTEM) {
send_file_not_found();
}
$fs = get_file_storage();
$file = $fs->get_file($context->id, 'tool_generator', $filearea, $args[0], '/', $args[1]);
// Send the file, always forcing download, we don't want options.
session_get_instance()->write_close();
send_stored_file($file, 0, 0, true);
}
......@@ -15,8 +15,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Script creates a standardised large course for testing reliability and
* performance.
* Script creates a standardised large course for testing reliability and performance.
*
* @package tool_generator
* @copyright 2013 The Open University
......
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//