From 92927fba7278a40ad4ebab2be803ba5081275267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0koda?= Date: Wed, 12 Feb 2014 11:25:18 +0800 Subject: [PATCH] MDL-41284 hack stats to use new log readers if available --- lib/statslib.php | 95 ++++++++++++++++++++--- lib/tests/fixtures/stats_events.php | 57 ++++++++++++++ lib/tests/statslib_test.php | 114 +++++++++++++++++++++++++++- 3 files changed, 253 insertions(+), 13 deletions(-) create mode 100644 lib/tests/fixtures/stats_events.php diff --git a/lib/statslib.php b/lib/statslib.php index 71ec4c6b9de..d54874754a9 100644 --- a/lib/statslib.php +++ b/lib/statslib.php @@ -942,9 +942,31 @@ function stats_get_start_from($str) { // decide what to do based on our config setting (either all or none or a timestamp) switch ($CFG->statsfirstrun) { case 'all': - if ($firstlog = $DB->get_field_sql('SELECT MIN(time) FROM {log}')) { + $manager = get_log_manager(); + $stores = $manager->get_readers(); + $firstlog = false; + foreach ($stores as $store) { + if ($store instanceof \core\log\sql_internal_reader) { + $logtable = $store->get_internal_log_table_name(); + if (!$logtable) { + continue; + } + $first = $DB->get_field_sql("SELECT MIN(timecreated) FROM {{$logtable}}"); + if ($first and (!$firstlog or $firstlog > $first)) { + $firstlog = $first; + } + } + } + + $first = $DB->get_field_sql('SELECT MIN(time) FROM {log}'); + if ($first and (!$firstlog or $firstlog > $first)) { + $firstlog = $first; + } + + if ($firstlog) { return $firstlog; } + default: if (is_numeric($CFG->statsfirstrun)) { return time() - $CFG->statsfirstrun; @@ -1698,9 +1720,9 @@ function stats_temp_table_drop() { * * This function is meant to be called once at the start of stats generation * - * @param timestart timestamp of the start time of logs view - * @param timeend timestamp of the end time of logs view - * @return boolen success (true) or failure(false) + * @param int timestart timestamp of the start time of logs view + * @param int timeend timestamp of the end time of logs view + * @return bool success (true) or failure(false) */ function stats_temp_table_setup() { global $DB; @@ -1721,19 +1743,68 @@ function stats_temp_table_setup() { * * This function is meant to be called to get a new day of data * - * @param timestart timestamp of the start time of logs view - * @param timeend timestamp of the end time of logs view - * @return boolen success (true) or failure(false) + * @param int timestamp of the start time of logs view + * @param int timestamp of the end time of logs view + * @return bool success (true) or failure(false) */ function stats_temp_table_fill($timestart, $timeend) { global $DB; - $sql = 'INSERT INTO {temp_log1} (userid, course, action) + // First decide from where we want the data. - SELECT userid, course, action FROM {log} - WHERE time >= ? AND time < ?'; + $params = array('timestart' => $timestart, 'timeend' => $timeend, 'loginevent' => '\core\event\user_loggedin'); - $DB->execute($sql, array($timestart, $timeend)); + $filled = false; + $manager = get_log_manager(); + $stores = $manager->get_readers(); + foreach ($stores as $store) { + if ($store instanceof \core\log\sql_internal_reader) { + $logtable = $store->get_internal_log_table_name(); + if (!$logtable) { + continue; + } + + $sql = "SELECT COUNT('x') + FROM {{$logtable}} + WHERE timecreated >= :timestart AND timecreated < :timeend"; + + if (!$DB->get_field_sql($sql, $params)) { + continue; + } + + // Let's fake the old records using new log data. + + $sql = "INSERT INTO {temp_log1} (userid, course, action) + + SELECT userid, + CASE + WHEN courseid IS NULL THEN ".SITEID." + WHEN courseid = 0 THEN ".SITEID." + ELSE courseid + END, + CASE + WHEN eventname = :loginevent THEN 'login' + WHEN crud = 'r' THEN 'view' + ELSE 'update' + END + FROM {{$logtable}} + WHERE timecreated >= :timestart AND timecreated < :timeend"; + + $DB->execute($sql, $params); + $filled = true; + } + } + + if (!$filled) { + // Fallback to legacy data. + $sql = "INSERT INTO {temp_log1} (userid, course, action) + + SELECT userid, course, action + FROM {log} + WHERE time >= :timestart AND time < :timeend"; + + $DB->execute($sql, $params); + } $sql = 'INSERT INTO {temp_log2} (userid, course, action) @@ -1751,7 +1822,7 @@ function stats_temp_table_fill($timestart, $timeend) { /** * Deletes summary logs table for stats calculation * - * @return boolen success (true) or failure(false) + * @return bool success (true) or failure(false) */ function stats_temp_table_clean() { global $DB; diff --git a/lib/tests/fixtures/stats_events.php b/lib/tests/fixtures/stats_events.php new file mode 100644 index 00000000000..c9a86599a3f --- /dev/null +++ b/lib/tests/fixtures/stats_events.php @@ -0,0 +1,57 @@ +. + +namespace core_tests\event; + +/** + * Fixtures for new stats testing. + * + * @package core + * @category phpunit + * @copyright 2014 Petr Skoda + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + + +class create_executed extends \core\event\base { + protected function init() { + $this->data['crud'] = 'c'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } +} + +class read_executed extends \core\event\base { + protected function init() { + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } +} + +class update_executed extends \core\event\base { + protected function init() { + $this->data['crud'] = 'u'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } +} + +class delete_executed extends \core\event\base { + protected function init() { + $this->data['crud'] = 'd'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } +} diff --git a/lib/tests/statslib_test.php b/lib/tests/statslib_test.php index 8e45f2237dc..0317aed6039 100644 --- a/lib/tests/statslib_test.php +++ b/lib/tests/statslib_test.php @@ -29,6 +29,7 @@ global $CFG; require_once($CFG->libdir . '/adminlib.php'); require_once($CFG->libdir . '/statslib.php'); require_once($CFG->libdir . '/cronlib.php'); +require_once(__DIR__ . '/fixtures/stats_events.php'); /** * Test functions that affect daily stats. @@ -343,6 +344,43 @@ class core_statslib_testcase extends advanced_testcase { $this->prepare_db($dataset[1], array('stats_daily')); $this->assertEquals($day + (24 * 3600), stats_get_start_from('daily'), 'Daily stats start time'); + + // New log stores. + $this->preventResetByRollback(); + + $this->assertFileExists("$CFG->dirroot/$CFG->admin/tool/log/store/standard/version.php"); + set_config('enabled_stores', 'logstore_standard', 'tool_log'); + set_config('buffersize', 0, 'logstore_standard'); + set_config('logguests', 1, 'logstore_standard'); + get_log_manager(true); + + $this->assertEquals(0, $DB->count_records('logstore_standard_log')); + + $DB->delete_records('stats_daily'); + $CFG->statsfirstrun = 'all'; + $firstoldtime = $DB->get_field_sql('SELECT MIN(time) FROM {log}'); + + $this->assertEquals($firstoldtime, stats_get_start_from('daily')); + + \core_tests\event\create_executed::create(array('context' => context_system::instance()))->trigger(); + \core_tests\event\read_executed::create(array('context' => context_system::instance()))->trigger(); + \core_tests\event\update_executed::create(array('context' => context_system::instance()))->trigger(); + \core_tests\event\delete_executed::create(array('context' => context_system::instance()))->trigger(); + + $logs = $DB->get_records('logstore_standard_log'); + $this->assertCount(4, $logs); + + $firstnew = reset($logs); + $this->assertGreaterThan($firstoldtime, $firstnew->timecreated); + $DB->set_field('logstore_standard_log', 'timecreated', 10, array('id' => $firstnew->id)); + $this->assertEquals(10, stats_get_start_from('daily')); + + $DB->set_field('logstore_standard_log', 'timecreated', $firstnew->timecreated, array('id' => $firstnew->id)); + $DB->delete_records('log'); + $this->assertEquals($firstnew->timecreated, stats_get_start_from('daily')); + + set_config('enabled_stores', '', 'tool_log'); + get_log_manager(true); } /** @@ -491,7 +529,7 @@ class core_statslib_testcase extends advanced_testcase { * @depends test_statslib_temp_table_create_and_drop */ public function test_statslib_temp_table_fill() { - global $CFG, $DB; + global $CFG, $DB, $USER; $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test09.xml"); @@ -507,6 +545,80 @@ class core_statslib_testcase extends advanced_testcase { $this->assertEquals(1, $DB->count_records('temp_log2')); stats_temp_table_drop(); + + // New log stores. + $this->preventResetByRollback(); + stats_temp_table_create(); + + $course = $this->getDataGenerator()->create_course(); + $context = context_course::instance($course->id); + $fcontext = context_course::instance(SITEID); + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + $this->assertFileExists("$CFG->dirroot/$CFG->admin/tool/log/store/standard/version.php"); + set_config('enabled_stores', 'logstore_standard', 'tool_log'); + set_config('buffersize', 0, 'logstore_standard'); + set_config('logguests', 1, 'logstore_standard'); + get_log_manager(true); + + $DB->delete_records('logstore_standard_log'); + + \core_tests\event\create_executed::create(array('context' => $fcontext, 'courseid' => SITEID))->trigger(); + \core_tests\event\read_executed::create(array('context' => $context, 'courseid' => $course->id))->trigger(); + \core_tests\event\update_executed::create(array('context' => context_system::instance()))->trigger(); + \core_tests\event\delete_executed::create(array('context' => context_system::instance()))->trigger(); + + \core\event\user_loggedin::create( + array( + 'userid' => $USER->id, + 'objectid' => $USER->id, + 'other' => array('username' => $USER->username), + ) + )->trigger(); + + $DB->set_field('logstore_standard_log', 'timecreated', 10); + + $this->assertEquals(5, $DB->count_records('logstore_standard_log')); + + \core_tests\event\delete_executed::create(array('context' => context_system::instance()))->trigger(); + \core_tests\event\delete_executed::create(array('context' => context_system::instance()))->trigger(); + + $this->assertEquals(7, $DB->count_records('logstore_standard_log')); + + stats_temp_table_fill(9, 11); + + $logs1 = $DB->get_records('temp_log1'); + $logs2 = $DB->get_records('temp_log2'); + $this->assertCount(5, $logs1); + $this->assertCount(5, $logs2); + // The order of records in the temp tables is not guaranteed... + + $viewcount = 0; + $updatecount = 0; + $logincount = 0; + foreach ($logs1 as $log) { + if ($log->course == $course->id) { + $this->assertEquals('view', $log->action); + $viewcount++; + } else { + $this->assertTrue(in_array($log->action, array('update', 'login'))); + if ($log->action === 'update') { + $updatecount++; + } else { + $logincount++; + } + $this->assertEquals(SITEID, $log->course); + } + $this->assertEquals($user->id, $log->userid); + } + $this->assertEquals(1, $viewcount); + $this->assertEquals(3, $updatecount); + $this->assertEquals(1, $logincount); + + set_config('enabled_stores', '', 'tool_log'); + get_log_manager(true); + stats_temp_table_drop(); } /**