diff --git a/classes/check/cachecheck.php b/classes/check/cachecheck.php
index 01c07a2..527240d 100644
--- a/classes/check/cachecheck.php
+++ b/classes/check/cachecheck.php
@@ -17,6 +17,7 @@
namespace tool_heartbeat\check;
use core\check\check;
use core\check\result;
+use tool_heartbeat\lib;
/**
* Cache check class
@@ -101,20 +102,65 @@ private function build_result(array $results): array {
* @param string $type type of check (e.g. web, cron)
*/
public function check($type) {
- global $DB;
-
$return = [];
- $key = "checkcache{$type}ping";
// Read from cache (e.g. get_config uses cache).
- $return[$type . 'api'] = get_config('tool_heartbeat', $key);
+ $cachevalue = self::get_cache_ping_value($type);
// Read directly from database.
- $return[$type . 'db'] = $DB->get_field('config_plugins', 'value', [
+ $dbvalue = self::get_db_ping_value($type);
+
+ // Log that it was checked, so we can see historical values for debugging.
+ if (get_config('tool_heartbeat', 'shouldlogcachecheck')) {
+ lib::record_cache_checked($cachevalue, $dbvalue, $type);
+ }
+
+ $return = [
+ $type . 'api' => $cachevalue,
+ $type . 'db' => $dbvalue,
+ ];
+
+ return $return;
+ }
+
+ /**
+ * Returns cache key
+ * @param string $type web or cron
+ * @return string
+ */
+ private static function get_key(string $type) {
+ return "checkcache{$type}ping";
+ }
+
+ /**
+ * Returns the cached value from get_config
+ * @param string $type web or cron
+ * @return int value
+ */
+ private static function get_cache_ping_value(string $type) {
+ return get_config('tool_heartbeat', self::get_key($type));
+ }
+
+ /**
+ * Returns the database stored ping value
+ * @param string $type web or cron
+ * @param int value
+ */
+ private static function get_db_ping_value(string $type) {
+ global $DB;
+ return $DB->get_field('config_plugins', 'value', [
'plugin' => 'tool_heartbeat',
- 'name' => $key,
+ 'name' => self::get_key($type),
]);
- return $return;
+ }
+
+ /**
+ * Set the cache ping value
+ * @param string $type web or cron
+ * @param int $value new value to set
+ */
+ private static function set_cache_ping_value(string $type, int $value) {
+ set_config(self::get_key($type), $value, 'tool_heartbeat');
}
/**
@@ -122,13 +168,22 @@ public function check($type) {
* @param string $type type of check (e.g. web, cron)
*/
public static function ping($type) {
- $key = "checkcache{$type}ping";
- $current = get_config('tool_heartbeat', $key);
+ $time = time();
+ $currentcache = self::get_cache_ping_value($type);
+ $currentdb = self::get_db_ping_value($type);
// Only update if the currently cached time is very old.
- if ($current < (time() - DAYSECS)) {
- debugging("\nHEARTBEAT doing {$type} ping {$current}\n", DEBUG_DEVELOPER);
- set_config($key, time(), 'tool_heartbeat');
+ if ($currentcache < ($time - DAYSECS)) {
+ debugging("\nHEARTBEAT doing {$type} ping\n", DEBUG_DEVELOPER);
+ self::set_cache_ping_value($type, $time);
+
+ // Read back the cached value immediately after setting it.
+ // This should help detect any cache replication delays.
+ $readbackvalue = self::get_cache_ping_value($type);
+
+ if (get_config('tool_heartbeat', 'shouldlogcacheping')) {
+ lib::record_cache_pinged($currentcache, $currentdb, $time, $readbackvalue, $type);
+ }
}
}
}
diff --git a/classes/lib.php b/classes/lib.php
index dae7fe3..c004d9a 100644
--- a/classes/lib.php
+++ b/classes/lib.php
@@ -16,6 +16,11 @@
namespace tool_heartbeat;
+use context_system;
+use Throwable;
+use tool_heartbeat\event\cache_check;
+use tool_heartbeat\event\cache_ping;
+
/**
* General functions for use with heartbeat.
*
@@ -59,4 +64,48 @@ public static function validate_ip_against_config() {
send_unknown($msg);
}
}
+
+ /**
+ * Records that the cache was pinged. This is useful for cache debugging.
+ * @param int $previousincache
+ * @param int $previousindb
+ * @param int $newvalueset the value that the cache was set to
+ * @param int $readbackvalue the value read back from the cache immediately after it was set.
+ * @param string $where cron or web
+ */
+ public static function record_cache_pinged(int $previousincache, int $previousindb, int $newvalueset, int $readbackvalue,
+ string $where) {
+ try {
+ $details = [
+ 'previousvalueindb' => $previousindb,
+ 'previousvalueincache' => $previousincache,
+ 'newvalueincache' => $newvalueset,
+ 'cachedvalueimmediatelyafterwrite' => $readbackvalue,
+ 'where' => $where,
+ ];
+ error_log("Heartbeat cache was updated/pinged: " . json_encode($details));
+ } catch (Throwable $e) {
+ debugging($e, DEBUG_DEVELOPER);
+ }
+ }
+
+ /**
+ * Records that the cache was checked. This is used for debugging cache mismatches
+ * @param int $valueincache
+ * @param int $valueindb
+ * @param string $where web or cron
+ */
+ public static function record_cache_checked(int $valueincache, int $valueindb, string $where) {
+ try {
+ $details = [
+ 'valueindb' => $valueindb,
+ 'valueincache' => $valueincache,
+ 'where' => $where,
+ ];
+ error_log("Heartbeat cache was checked: " . json_encode($details));
+ } catch (Throwable $e) {
+ debugging($e, DEBUG_DEVELOPER);
+ }
+ }
}
+
diff --git a/lang/en/tool_heartbeat.php b/lang/en/tool_heartbeat.php
index 637a027..99958e1 100644
--- a/lang/en/tool_heartbeat.php
+++ b/lang/en/tool_heartbeat.php
@@ -85,6 +85,13 @@
$string['checktasklatencycheck'] = 'Task latency check';
$string['taskconfigbad'] = 'Bad configurations {$a}';
$string['tasklatencyok'] = 'Task latency OK.';
+
+$string['settings:cachecheckheading'] = 'Cache consistency check';
+$string['settings:shouldlogcacheping:heading'] = 'Log cache ping';
+$string['settings:shouldlogcacheping:desc'] = 'If enabled, whenever the cache ping is updated (usually once every 24 hrs), a cache_ping
event will be triggered';
+$string['settings:shouldlogcachecheck:heading'] = 'Log cache check';
+$string['settings:shouldlogcachecheck:desc'] = 'If enabled, whenever the cache ping is checked (whenever the cachecheck
check is executed) a cache_check
event will be triggered';
+
/*
* Privacy provider (GDPR)
*/
diff --git a/settings.php b/settings.php
index 6439d16..7892ae1 100644
--- a/settings.php
+++ b/settings.php
@@ -95,5 +95,27 @@
$settings->add(new admin_setting_configtextarea('tool_heartbeat/tasklatencymonitoring',
get_string('tasklatencymonitoring', 'tool_heartbeat'),
get_string('tasklatencymonitoring_desc', 'tool_heartbeat', $example), '', PARAM_TEXT));
+
+ // Cache consistency check settings.
+ $settings->add(new admin_setting_heading('tool_heartbeat/cachechecksettings',
+ get_string('settings:cachecheckheading', 'tool_heartbeat'),
+ ''
+ ));
+
+ $settings->add(new admin_setting_configcheckbox('tool_heartbeat/shouldlogcacheping',
+ get_string('settings:shouldlogcacheping:heading', 'tool_heartbeat'),
+ get_string('settings:shouldlogcacheping:desc', 'tool_heartbeat'),
+ // Since pinging only happens usually once every 24 hrs, we default this on as it is quite lightweight.
+ 1
+ ));
+
+ $settings->add(new admin_setting_configcheckbox('tool_heartbeat/shouldlogcachecheck',
+ get_string('settings:shouldlogcachecheck:heading', 'tool_heartbeat'),
+ get_string('settings:shouldlogcachecheck:desc', 'tool_heartbeat'),
+ // This happens every time the check api cachecheck is called, which is a lot more often than pinging.
+ // For e.g. with external monitoring, it could be once or more per minute.
+ // So its defaulted to off unless turned on for specific debugging.
+ 0
+ ));
}
}
diff --git a/version.php b/version.php
index 41c4544..85209f4 100644
--- a/version.php
+++ b/version.php
@@ -24,8 +24,8 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2023102400;
-$plugin->release = 2023102400; // Match release exactly to version.
+$plugin->version = 2023102401;
+$plugin->release = 2023102401; // Match release exactly to version.
$plugin->requires = 2012120311; // Deep support going back to 2.4.
$plugin->supported = [24, 401];
$plugin->component = 'tool_heartbeat';