6f70dad3ed92152992ba7334e65ca9971dac7fda
[moodle.git] / admin / tool / generator / classes / testplan_backend.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Test plan generator.
19  *
20  * @package tool_generator
21  * @copyright 2013 David MonllaĆ³
22  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 defined('MOODLE_INTERNAL') || die();
27 /**
28  * Generates the files required by JMeter.
29  *
30  * @package tool_generator
31  * @copyright 2013 David MonllaĆ³
32  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33  */
34 class tool_generator_testplan_backend extends tool_generator_backend {
36     /**
37      * @var The URL to the repository of the external project.
38      */
39     protected static $repourl = 'https://github.com/moodlehq/moodle-performance-comparison';
41     /**
42      * @var Number of users depending on the selected size.
43      */
44     protected static $users = array(1, 30, 200, 1000, 5000, 10000);
46     /**
47      * @var Number of loops depending on the selected size.
48      */
49     protected static $loops = array(1, 1, 2, 3, 3, 5);
51     /**
52      * @var Rampup period depending on the selected size.
53      */
54     protected static $rampups = array(1, 6, 40, 100, 500, 800);
56     /**
57      * Gets a list of size choices supported by this backend.
58      *
59      * @return array List of size (int) => text description for display
60      */
61     public static function get_size_choices() {
63         $options = array();
64         for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
65             $a = new stdClass();
66             $a->users = self::$users[$size];
67             $a->loops = self::$loops[$size];
68             $a->rampup = self::$rampups[$size];
69             $options[$size] = get_string('testplansize_' . $size, 'tool_generator', $a);
70         }
71         return $options;
72     }
74     /**
75      * Gets the list of courses that can be used used to generate a test.
76      *
77      * @return array The list of options as courseid => name
78      */
79     public static function get_course_options() {
80         $courses = get_courses('all', 'c.sortorder ASC', 'c.id, c.shortname, c.fullname');
81         if (!$courses) {
82             print_error('error_nocourses', 'tool_generator');
83         }
85         $options = array();
86         unset($courses[1]);
87         foreach ($courses as $course) {
88             $options[$course->id] = $course->fullname . '(' . $course->shortname . ')';
89         }
90         return $options;
91     }
93     /**
94      * Getter for moodle-performance-comparison project URL.
95      *
96      * @return string
97      */
98     public static function get_repourl() {
99         return self::$repourl;
100     }
102     /**
103      * Creates the test plan file.
104      *
105      * @param int $courseid The target course id
106      * @param int $size The test plan size
107      * @return stored_file
108      */
109     public static function create_testplan_file($courseid, $size) {
110         $jmxcontents = self::generate_test_plan($courseid, $size);
112         $fs = get_file_storage();
113         $filerecord = self::get_file_record('testplan', 'jmx');
114         return $fs->create_file_from_string($filerecord, $jmxcontents);
115     }
117     /**
118      * Creates the users data file.
119      *
120      * @param int $courseid The target course id
121      * @param bool $updateuserspassword Updates the course users password to $CFG->tool_generator_users_password
122      * @return stored_file
123      */
124     public static function create_users_file($courseid, $updateuserspassword) {
125         $csvcontents = self::generate_users_file($courseid, $updateuserspassword);
127         $fs = get_file_storage();
128         $filerecord = self::get_file_record('users', 'csv');
129         return $fs->create_file_from_string($filerecord, $csvcontents);
130     }
132     /**
133      * Generates the test plan according to the target course contents.
134      *
135      * @param int $targetcourseid The target course id
136      * @param int $size The test plan size
137      * @return string The test plan as a string
138      */
139     protected static function generate_test_plan($targetcourseid, $size) {
140         global $CFG;
142         // Getting the template.
143         $template = file_get_contents(__DIR__ . '/../testplan.template.jmx');
145         // Getting the course modules data.
146         $coursedata = self::get_course_test_data($targetcourseid);
148         // Host and path to the site.
149         $urlcomponents = parse_url($CFG->wwwroot);
150         if (empty($urlcomponents['path'])) {
151             $urlcomponents['path'] = '';
152         }
154         $replacements = array(
155             self::$users[$size],
156             self::$loops[$size],
157             self::$rampups[$size],
158             $urlcomponents['host'],
159             $urlcomponents['path'],
160             get_string('shortsize_' . $size, 'tool_generator'),
161             $targetcourseid,
162             $coursedata->pageid,
163             $coursedata->forumid,
164             $coursedata->forumdiscussionid,
165             $coursedata->forumreplyid
166         );
168         $placeholders = array(
169             '{{USERS_PLACEHOLDER}}',
170             '{{LOOPS_PLACEHOLDER}}',
171             '{{RAMPUP_PLACEHOLDER}}',
172             '{{HOST_PLACEHOLDER}}',
173             '{{SITEPATH_PLACEHOLDER}}',
174             '{{SIZE_PLACEHOLDER}}',
175             '{{COURSEID_PLACEHOLDER}}',
176             '{{PAGEACTIVITYID_PLACEHOLDER}}',
177             '{{FORUMACTIVITYID_PLACEHOLDER}}',
178             '{{FORUMDISCUSSIONID_PLACEHOLDER}}',
179             '{{FORUMREPLYID_PLACEHOLDER}}'
180         );
182         // Fill the template with the target course values.
183         return str_replace($placeholders, $replacements, $template);
184     }
186     /**
187      * Generates the user's credentials file with all the course's users
188      *
189      * @param int $targetcourseid
190      * @param bool $updateuserspassword Updates the course users password to $CFG->tool_generator_users_password
191      * @return string The users csv file contents.
192      */
193     protected static function generate_users_file($targetcourseid, $updateuserspassword) {
194         global $CFG;
196         $coursecontext = context_course::instance($targetcourseid);
198         $users = get_enrolled_users($coursecontext, '', 0, 'u.id, u.username, u.auth', 'u.username ASC');
199         if (!$users) {
200             print_error('coursewithoutusers', 'tool_generator');
201         }
203         $lines = array();
204         foreach ($users as $user) {
206             // Updating password to the one set in config.php.
207             if ($updateuserspassword) {
208                 $userauth = get_auth_plugin($user->auth);
209                 if (!$userauth->user_update_password($user, $CFG->tool_generator_users_password)) {
210                     print_error('errorpasswordupdate', 'auth');
211                 }
212             }
214             // Here we already checked that $CFG->tool_generator_users_password is not null.
215             $lines[] = $user->username . ',' . $CFG->tool_generator_users_password;
216         }
218         return implode(PHP_EOL, $lines);
219     }
221     /**
222      * Returns a tool_generator file record
223      *
224      * @param string $filearea testplan or users
225      * @param string $filetype The file extension jmx or csv
226      * @return stdClass The file record to use when creating tool_generator files
227      */
228     protected static function get_file_record($filearea, $filetype) {
230         $systemcontext = context_system::instance();
232         $filerecord = new stdClass();
233         $filerecord->contextid = $systemcontext->id;
234         $filerecord->component = 'tool_generator';
235         $filerecord->filearea = $filearea;
236         $filerecord->itemid = 0;
237         $filerecord->filepath = '/';
239         // Random generated number to avoid concurrent execution problems.
240         $filerecord->filename = $filearea . '_' . date('YmdHi', time()) . '_' . rand(1000, 9999) . '.' . $filetype;
242         return $filerecord;
243     }
245     /**
246      * Gets the data required to fill the test plan template with the database contents.
247      *
248      * @param int $targetcourseid The target course id
249      * @return stdClass The ids required by the test plan
250      */
251     protected static function get_course_test_data($targetcourseid) {
252         global $DB, $USER;
254         $data = new stdClass();
256         // Getting course contents info as the current user (will be an admin).
257         $course = new stdClass();
258         $course->id = $targetcourseid;
259         $courseinfo = new course_modinfo($course, $USER->id);
261         // Getting the first page module instance.
262         if (!$pages = $courseinfo->get_instances_of('page')) {
263             print_error('error_nopageinstances', 'tool_generator');
264         }
265         $data->pageid = reset($pages)->id;
267         // Getting the first forum module instance and it's first discussion and reply as well.
268         if (!$forums = $courseinfo->get_instances_of('forum')) {
269             print_error('error_noforuminstances', 'tool_generator');
270         }
271         $forum = reset($forums);
273         // Getting the first discussion (and reply).
274         if (!$discussions = forum_get_discussions($forum, 'd.timemodified ASC', false, -1, 1)) {
275             print_error('error_noforumdiscussions', 'tool_generator');
276         }
277         $discussion = reset($discussions);
279         $data->forumid = $forum->id;
280         $data->forumdiscussionid = $discussion->discussion;
281         $data->forumreplyid = $discussion->id;
283         // According to the current test plan.
284         return $data;
285     }
287     /**
288      * Checks if the selected target course is ok.
289      *
290      * @param int|string $course
291      * @param int $size
292      * @return array Errors array or false if everything is ok
293      */
294     public static function has_selected_course_any_problem($course, $size) {
295         global $DB;
297         $errors = array();
299         if (!is_numeric($course)) {
300             if (!$course = $DB->get_field('course', 'id', array('shortname' => $course))) {
301                 $errors['courseid'] = get_string('error_nonexistingcourse', 'tool_generator');
302                 return $errors;
303             }
304         }
306         $coursecontext = context_course::instance($course, IGNORE_MISSING);
307         if (!$coursecontext) {
308             $errors['courseid'] = get_string('error_nonexistingcourse', 'tool_generator');
309             return $errors;
310         }
312         if (!$users = get_enrolled_users($coursecontext, '', 0, 'u.id')) {
313             $errors['courseid'] = get_string('coursewithoutusers', 'tool_generator');
314         }
316         // Checks that the selected course has enough users.
317         $coursesizes = tool_generator_course_backend::get_users_per_size();
318         if (count($users) < $coursesizes[$size]) {
319             $errors['size'] = get_string('notenoughusers', 'tool_generator');
320         }
322         if (empty($errors)) {
323             return false;
324         }
326         return $errors;
327     }