Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   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/>.
  16  
  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   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  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 {
  35  
  36      /**
  37       * @var The URL to the repository of the external project.
  38       */
  39      protected static $repourl = 'https://github.com/moodlehq/moodle-performance-comparison';
  40  
  41      /**
  42       * @var Number of users depending on the selected size.
  43       */
  44      protected static $users = array(1, 30, 100, 1000, 5000, 10000);
  45  
  46      /**
  47       * @var Number of loops depending on the selected size.
  48       */
  49      protected static $loops = array(5, 5, 5, 6, 6, 7);
  50  
  51      /**
  52       * @var Rampup period depending on the selected size.
  53       */
  54      protected static $rampups = array(1, 6, 40, 100, 500, 800);
  55  
  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() {
  62  
  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      }
  73  
  74      /**
  75       * Getter for moodle-performance-comparison project URL.
  76       *
  77       * @return string
  78       */
  79      public static function get_repourl() {
  80          return self::$repourl;
  81      }
  82  
  83      /**
  84       * Creates the test plan file.
  85       *
  86       * @param int $courseid The target course id
  87       * @param int $size The test plan size
  88       * @return stored_file
  89       */
  90      public static function create_testplan_file($courseid, $size) {
  91          $jmxcontents = self::generate_test_plan($courseid, $size);
  92  
  93          $fs = get_file_storage();
  94          $filerecord = self::get_file_record('testplan', 'jmx');
  95          return $fs->create_file_from_string($filerecord, $jmxcontents);
  96      }
  97  
  98      /**
  99       * Creates the users data file.
 100       *
 101       * @param int $courseid The target course id
 102       * @param bool $updateuserspassword Updates the course users password to $CFG->tool_generator_users_password
 103       * @return stored_file
 104       */
 105      public static function create_users_file($courseid, $updateuserspassword) {
 106          $csvcontents = self::generate_users_file($courseid, $updateuserspassword);
 107  
 108          $fs = get_file_storage();
 109          $filerecord = self::get_file_record('users', 'csv');
 110          return $fs->create_file_from_string($filerecord, $csvcontents);
 111      }
 112  
 113      /**
 114       * Generates the test plan according to the target course contents.
 115       *
 116       * @param int $targetcourseid The target course id
 117       * @param int $size The test plan size
 118       * @return string The test plan as a string
 119       */
 120      protected static function generate_test_plan($targetcourseid, $size) {
 121          global $CFG;
 122  
 123          // Getting the template.
 124          $template = file_get_contents(__DIR__ . '/../testplan.template.jmx');
 125  
 126          // Getting the course modules data.
 127          $coursedata = self::get_course_test_data($targetcourseid);
 128  
 129          // Host and path to the site.
 130          $urlcomponents = parse_url($CFG->wwwroot);
 131          if (empty($urlcomponents['path'])) {
 132              $urlcomponents['path'] = '';
 133          }
 134  
 135          $replacements = array(
 136              $CFG->version,
 137              self::$users[$size],
 138              self::$loops[$size],
 139              self::$rampups[$size],
 140              $urlcomponents['host'],
 141              $urlcomponents['path'],
 142              get_string('shortsize_' . $size, 'tool_generator'),
 143              $targetcourseid,
 144              $coursedata->pageid,
 145              $coursedata->forumid,
 146              $coursedata->forumdiscussionid,
 147              $coursedata->forumreplyid
 148          );
 149  
 150          $placeholders = array(
 151              '{{MOODLEVERSION_PLACEHOLDER}}',
 152              '{{USERS_PLACEHOLDER}}',
 153              '{{LOOPS_PLACEHOLDER}}',
 154              '{{RAMPUP_PLACEHOLDER}}',
 155              '{{HOST_PLACEHOLDER}}',
 156              '{{SITEPATH_PLACEHOLDER}}',
 157              '{{SIZE_PLACEHOLDER}}',
 158              '{{COURSEID_PLACEHOLDER}}',
 159              '{{PAGEACTIVITYID_PLACEHOLDER}}',
 160              '{{FORUMACTIVITYID_PLACEHOLDER}}',
 161              '{{FORUMDISCUSSIONID_PLACEHOLDER}}',
 162              '{{FORUMREPLYID_PLACEHOLDER}}'
 163          );
 164  
 165          // Fill the template with the target course values.
 166          return str_replace($placeholders, $replacements, $template);
 167      }
 168  
 169      /**
 170       * Generates the user's credentials file with all the course's users
 171       *
 172       * @param int $targetcourseid
 173       * @param bool $updateuserspassword Updates the course users password to $CFG->tool_generator_users_password
 174       * @return string The users csv file contents.
 175       */
 176      protected static function generate_users_file($targetcourseid, $updateuserspassword) {
 177          global $CFG;
 178  
 179          $coursecontext = context_course::instance($targetcourseid);
 180  
 181          $users = get_enrolled_users($coursecontext, '', 0, 'u.id, u.username, u.auth', 'u.username ASC');
 182          if (!$users) {
 183              print_error('coursewithoutusers', 'tool_generator');
 184          }
 185  
 186          $lines = array();
 187          foreach ($users as $user) {
 188  
 189              // Updating password to the one set in config.php.
 190              if ($updateuserspassword) {
 191                  $userauth = get_auth_plugin($user->auth);
 192                  if (!$userauth->user_update_password($user, $CFG->tool_generator_users_password)) {
 193                      print_error('errorpasswordupdate', 'auth');
 194                  }
 195              }
 196  
 197              // Here we already checked that $CFG->tool_generator_users_password is not null.
 198              $lines[] = $user->username . ',' . $CFG->tool_generator_users_password;
 199          }
 200  
 201          return implode(PHP_EOL, $lines);
 202      }
 203  
 204      /**
 205       * Returns a tool_generator file record
 206       *
 207       * @param string $filearea testplan or users
 208       * @param string $filetype The file extension jmx or csv
 209       * @return stdClass The file record to use when creating tool_generator files
 210       */
 211      protected static function get_file_record($filearea, $filetype) {
 212  
 213          $systemcontext = context_system::instance();
 214  
 215          $filerecord = new stdClass();
 216          $filerecord->contextid = $systemcontext->id;
 217          $filerecord->component = 'tool_generator';
 218          $filerecord->filearea = $filearea;
 219          $filerecord->itemid = 0;
 220          $filerecord->filepath = '/';
 221  
 222          // Random generated number to avoid concurrent execution problems.
 223          $filerecord->filename = $filearea . '_' . date('YmdHi', time()) . '_' . rand(1000, 9999) . '.' . $filetype;
 224  
 225          return $filerecord;
 226      }
 227  
 228      /**
 229       * Gets the data required to fill the test plan template with the database contents.
 230       *
 231       * @param int $targetcourseid The target course id
 232       * @return stdClass The ids required by the test plan
 233       */
 234      protected static function get_course_test_data($targetcourseid) {
 235          global $DB, $USER;
 236  
 237          $data = new stdClass();
 238  
 239          // Getting course contents info as the current user (will be an admin).
 240          $course = new stdClass();
 241          $course->id = $targetcourseid;
 242          $courseinfo = new course_modinfo($course, $USER->id);
 243  
 244          // Getting the first page module instance.
 245          if (!$pages = $courseinfo->get_instances_of('page')) {
 246              print_error('error_nopageinstances', 'tool_generator');
 247          }
 248          $data->pageid = reset($pages)->id;
 249  
 250          // Getting the first forum module instance and it's first discussion and reply as well.
 251          if (!$forums = $courseinfo->get_instances_of('forum')) {
 252              print_error('error_noforuminstances', 'tool_generator');
 253          }
 254          $forum = reset($forums);
 255  
 256          // Getting the first discussion (and reply).
 257          if (!$discussions = forum_get_discussions($forum, 'd.timemodified ASC', false, -1, 1)) {
 258              print_error('error_noforumdiscussions', 'tool_generator');
 259          }
 260          $discussion = reset($discussions);
 261  
 262          $data->forumid = $forum->id;
 263          $data->forumdiscussionid = $discussion->discussion;
 264          $data->forumreplyid = $discussion->id;
 265  
 266          // According to the current test plan.
 267          return $data;
 268      }
 269  
 270      /**
 271       * Checks if the selected target course is ok.
 272       *
 273       * @param int|string $course
 274       * @param int $size
 275       * @return array Errors array or false if everything is ok
 276       */
 277      public static function has_selected_course_any_problem($course, $size) {
 278          global $DB;
 279  
 280          $errors = array();
 281  
 282          if (!is_numeric($course)) {
 283              if (!$course = $DB->get_field('course', 'id', array('shortname' => $course))) {
 284                  $errors['courseid'] = get_string('error_nonexistingcourse', 'tool_generator');
 285                  return $errors;
 286              }
 287          }
 288  
 289          $coursecontext = context_course::instance($course, IGNORE_MISSING);
 290          if (!$coursecontext) {
 291              $errors['courseid'] = get_string('error_nonexistingcourse', 'tool_generator');
 292              return $errors;
 293          }
 294  
 295          if (!$users = get_enrolled_users($coursecontext, '', 0, 'u.id')) {
 296              $errors['courseid'] = get_string('coursewithoutusers', 'tool_generator');
 297          }
 298  
 299          // Checks that the selected course has enough users.
 300          $coursesizes = tool_generator_course_backend::get_users_per_size();
 301          if (count($users) < self::$users[$size]) {
 302              $errors['size'] = get_string('notenoughusers', 'tool_generator');
 303          }
 304  
 305          if (empty($errors)) {
 306              return false;
 307          }
 308  
 309          return $errors;
 310      }
 311  }