Skip to content

Commit

Permalink
MDL-72596 core_cache: Track cache I/O size in perfdebug
Browse files Browse the repository at this point in the history
For cache types which mean this information can be obtained without a
significant performance cost (i.e. just by calling strlen and not
having to serialize something that wasn't serialized already),
this change calculates the size of data read from or written to cache
in each request and includes it in the perfdebug table at bottom of
output (when that is turned on).

This supports the following cache types:

* File store
* Redis (only if caching is enabled)
  • Loading branch information
sammarshallou committed Oct 19, 2021
1 parent 1a9bee6 commit 9c29979
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 26 deletions.
23 changes: 21 additions & 2 deletions cache/classes/helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ protected static function ensure_ready_for_stats($store, $storeclass, $definitio
'hits' => 0,
'misses' => 0,
'sets' => 0,
'iobytes' => cache_store::IO_BYTES_NOT_SUPPORTED,
)
)
);
Expand All @@ -390,6 +391,7 @@ protected static function ensure_ready_for_stats($store, $storeclass, $definitio
'hits' => 0,
'misses' => 0,
'sets' => 0,
'iobytes' => cache_store::IO_BYTES_NOT_SUPPORTED,
);
}
}
Expand Down Expand Up @@ -429,8 +431,9 @@ protected static function get_definition_stat_id_and_mode($definition) {
* @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
* actual cache_definition object now.
* @param int $hits The number of hits to record (by default 1)
* @param int $readbytes Number of bytes read from the cache or cache_store::IO_BYTES_NOT_SUPPORTED
*/
public static function record_cache_hit($store, $definition, $hits = 1) {
public static function record_cache_hit($store, $definition, int $hits = 1, int $readbytes = cache_store::IO_BYTES_NOT_SUPPORTED): void {
$storeclass = '';
if ($store instanceof cache_store) {
$storeclass = get_class($store);
Expand All @@ -439,6 +442,13 @@ public static function record_cache_hit($store, $definition, $hits = 1) {
list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
self::$stats[$definitionstr]['stores'][$store]['hits'] += $hits;
if ($readbytes !== cache_store::IO_BYTES_NOT_SUPPORTED) {
if (self::$stats[$definitionstr]['stores'][$store]['iobytes'] === cache_store::IO_BYTES_NOT_SUPPORTED) {
self::$stats[$definitionstr]['stores'][$store]['iobytes'] = $readbytes;
} else {
self::$stats[$definitionstr]['stores'][$store]['iobytes'] += $readbytes;
}
}
}

/**
Expand Down Expand Up @@ -479,8 +489,10 @@ public static function record_cache_miss($store, $definition, $misses = 1) {
* @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
* actual cache_definition object now.
* @param int $sets The number of sets to record (by default 1)
* @param int $writebytes Number of bytes written to the cache or cache_store::IO_BYTES_NOT_SUPPORTED
*/
public static function record_cache_set($store, $definition, $sets = 1) {
public static function record_cache_set($store, $definition, int $sets = 1,
int $writebytes = cache_store::IO_BYTES_NOT_SUPPORTED) {
$storeclass = '';
if ($store instanceof cache_store) {
$storeclass = get_class($store);
Expand All @@ -489,6 +501,13 @@ public static function record_cache_set($store, $definition, $sets = 1) {
list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
self::$stats[$definitionstr]['stores'][$store]['sets'] += $sets;
if ($writebytes !== cache_store::IO_BYTES_NOT_SUPPORTED) {
if (self::$stats[$definitionstr]['stores'][$store]['iobytes'] === cache_store::IO_BYTES_NOT_SUPPORTED) {
self::$stats[$definitionstr]['stores'][$store]['iobytes'] = $writebytes;
} else {
self::$stats[$definitionstr]['stores'][$store]['iobytes'] += $writebytes;
}
}
}

/**
Expand Down
45 changes: 31 additions & 14 deletions cache/classes/loaders.php
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,8 @@ public function get($key, $strictness = IGNORE_MISSING) {
}
$setaftervalidation = ($result !== false);
} else if ($this->perfdebug) {
cache_helper::record_cache_hit($this->store, $this->definition);
$readbytes = $this->store->get_last_io_bytes();
cache_helper::record_cache_hit($this->store, $this->definition, 1, $readbytes);
}
// 5. Validate strictness.
if ($strictness === MUST_EXIST && $result === false) {
Expand Down Expand Up @@ -492,6 +493,7 @@ public function get_many(array $keys, $strictness = IGNORE_MISSING) {
$resultpersist = array();
$resultstore = array();
$keystofind = array();
$readbytes = cache_store::IO_BYTES_NOT_SUPPORTED;

// First up check the persist cache for each key.
$isusingpersist = $this->use_static_acceleration();
Expand All @@ -515,6 +517,9 @@ public function get_many(array $keys, $strictness = IGNORE_MISSING) {
// Next assuming we didn't find all of the keys in the persist cache try loading them from the store.
if (count($keystofind)) {
$resultstore = $this->store->get_many(array_keys($keystofind));
if ($this->perfdebug) {
$readbytes = $this->store->get_last_io_bytes();
}
// Process each item in the result to "unwrap" it.
foreach ($resultstore as $key => $value) {
if ($value instanceof cache_ttl_wrapper) {
Expand Down Expand Up @@ -599,7 +604,7 @@ public function get_many(array $keys, $strictness = IGNORE_MISSING) {
$hits++;
}
}
cache_helper::record_cache_hit($this->store, $this->definition, $hits);
cache_helper::record_cache_hit($this->store, $this->definition, $hits, $readbytes);
cache_helper::record_cache_miss($this->store, $this->definition, $misses);
}

Expand All @@ -625,9 +630,6 @@ public function get_many(array $keys, $strictness = IGNORE_MISSING) {
* @return bool True on success, false otherwise.
*/
public function set($key, $data) {
if ($this->perfdebug) {
cache_helper::record_cache_set($this->store, $this->definition);
}
if ($this->loader !== false) {
// We have a loader available set it there as well.
// We have to let the loader do its own parsing of data as it may be unique.
Expand All @@ -654,7 +656,12 @@ public function set($key, $data) {
}
$parsedkey = $this->parse_key($key);

return $this->store->set($parsedkey, $data);
$success = $this->store->set($parsedkey, $data);
if ($this->perfdebug) {
cache_helper::record_cache_set($this->store, $this->definition, 1,
$this->store->get_last_io_bytes());
}
return $success;
}

/**
Expand Down Expand Up @@ -781,7 +788,8 @@ public function set_many(array $keyvaluearray) {
}
$successfullyset = $this->store->set_many($data);
if ($this->perfdebug && $successfullyset) {
cache_helper::record_cache_set($this->store, $this->definition, $successfullyset);
cache_helper::record_cache_set($this->store, $this->definition, $successfullyset,
$this->store->get_last_io_bytes());
}
return $successfullyset;
}
Expand Down Expand Up @@ -1845,6 +1853,9 @@ public function get($key, $strictness = IGNORE_MISSING) {
if ($result instanceof cache_cached_object) {
$result = $result->restore_object();
}
if ($this->perfdebug) {
$readbytes = $this->get_store()->get_last_io_bytes();
}
}
// 4. Load if from the loader/datasource if we don't already have it.
if ($result === false) {
Expand All @@ -1864,7 +1875,7 @@ public function get($key, $strictness = IGNORE_MISSING) {
$this->set($key, $result);
}
} else if ($this->perfdebug) {
cache_helper::record_cache_hit($this->get_store(), $this->get_definition());
cache_helper::record_cache_hit($this->get_store(), $this->get_definition(), 1, $readbytes);
}
// 5. Validate strictness.
if ($strictness === MUST_EXIST && $result === false) {
Expand Down Expand Up @@ -1907,9 +1918,6 @@ public function set($key, $data) {
// We have to let the loader do its own parsing of data as it may be unique.
$loader->set($key, $data);
}
if ($this->perfdebug) {
cache_helper::record_cache_set($this->get_store(), $this->get_definition());
}
if (is_object($data) && $data instanceof cacheable_object) {
$data = new cache_cached_object($data);
} else if (!$this->get_store()->supports_dereferencing_objects() && !is_scalar($data)) {
Expand All @@ -1923,7 +1931,12 @@ public function set($key, $data) {
if ($this->has_a_ttl() && !$this->store_supports_native_ttl()) {
$data = new cache_ttl_wrapper($data, $this->get_definition()->get_ttl());
}
return $this->get_store()->set($this->parse_key($key), $data);
$success = $this->get_store()->set($this->parse_key($key), $data);
if ($this->perfdebug) {
cache_helper::record_cache_set($this->get_store(), $this->get_definition(), 1,
$this->get_store()->get_last_io_bytes());
}
return $success;
}

/**
Expand Down Expand Up @@ -1971,6 +1984,9 @@ public function get_many(array $keys, $strictness = IGNORE_MISSING) {
$keymap[$parsedkey] = $key;
}
$result = $this->get_store()->get_many($parsedkeys);
if ($this->perfdebug) {
$readbytes = $this->get_store()->get_last_io_bytes();
}
$return = array();
$missingkeys = array();
$hasmissingkeys = false;
Expand Down Expand Up @@ -2038,7 +2054,7 @@ public function get_many(array $keys, $strictness = IGNORE_MISSING) {
$hits++;
}
}
cache_helper::record_cache_hit($this->get_store(), $this->get_definition(), $hits);
cache_helper::record_cache_hit($this->get_store(), $this->get_definition(), $hits, $readbytes);
cache_helper::record_cache_miss($this->get_store(), $this->get_definition(), $misses);
}
return $return;
Expand Down Expand Up @@ -2116,7 +2132,8 @@ public function set_many(array $keyvaluearray) {
}
$successfullyset = $this->get_store()->set_many($data);
if ($this->perfdebug && $successfullyset) {
cache_helper::record_cache_set($this->get_store(), $this->get_definition(), $successfullyset);
cache_helper::record_cache_set($this->get_store(), $this->get_definition(), $successfullyset,
$this->get_store()->get_last_io_bytes());
}
return $successfullyset;
}
Expand Down
24 changes: 24 additions & 0 deletions cache/classes/store.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ abstract class cache_store implements cache_store_interface {
*/
const STATIC_ACCEL = '** static accel. **';

/**
* Returned from get_last_io_bytes if this cache store doesn't support counting bytes read/sent.
*/
const IO_BYTES_NOT_SUPPORTED = -1;

/**
* Constructs an instance of the cache store.
*
Expand Down Expand Up @@ -392,4 +397,23 @@ public function get_warnings() {
public static function ready_to_be_used_for_testing() {
return false;
}

/**
* Gets the number of bytes read from or written to cache as a result of the last action.
*
* This includes calls to the functions get(), get_many(), set(), and set_many(). The number
* is reset by calling any of these functions.
*
* This should be the actual number of bytes of the value read from or written to cache,
* giving an impression of the network or other load. It will not be exactly the same amount
* as netowrk traffic because of protocol overhead, key text, etc.
*
* If not supported, returns IO_BYTES_NOT_SUPPORTED.
*
* @return int Bytes read (or 0 if none/not supported)
* @since Moodle 4.0
*/
public function get_last_io_bytes(): int {
return self::IO_BYTES_NOT_SUPPORTED;
}
}
29 changes: 28 additions & 1 deletion cache/stores/file/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
*/
protected $definition;

/**
* Bytes read or written by last call to set()/get() or set_many()/get_many().
*
* @var int
*/
protected $lastiobytes = 0;

/**
* A reference to the global $CFG object.
*
Expand Down Expand Up @@ -334,6 +341,7 @@ protected function file_path_for_key($key, $create = false) {
* @return mixed The data that was associated with the key, or false if the key did not exist.
*/
public function get($key) {
$this->lastiobytes = 0;
$filename = $key.'.cache';
$file = $this->file_path_for_key($key);
$ttl = $this->definition->get_ttl();
Expand Down Expand Up @@ -369,6 +377,7 @@ public function get($key) {
} while (!feof($handle));
// Unlock it.
flock($handle, LOCK_UN);
$this->lastiobytes = strlen($data);
// Return it unserialised.
return $this->prep_data_after_read($data);
}
Expand All @@ -384,12 +393,25 @@ public function get($key) {
*/
public function get_many($keys) {
$result = array();
$total = 0;
foreach ($keys as $key) {
$result[$key] = $this->get($key);
$total += $this->lastiobytes;
}
$this->lastiobytes = $total;
return $result;
}

/**
* Gets bytes read by last get() or get_many(), or written by set() or set_many().
*
* @return int Bytes read or written
* @since Moodle 4.0
*/
public function get_last_io_bytes(): int {
return $this->lastiobytes;
}

/**
* Deletes an item from the cache store.
*
Expand Down Expand Up @@ -434,7 +456,9 @@ public function set($key, $data) {
$this->ensure_path_exists();
$filename = $key.'.cache';
$file = $this->file_path_for_key($key, true);
$result = $this->write_file($file, $this->prep_data_before_save($data));
$serialized = $this->prep_data_before_save($data);
$this->lastiobytes = strlen($serialized);
$result = $this->write_file($file, $serialized);
if (!$result) {
// Couldn't write the file.
return false;
Expand Down Expand Up @@ -482,11 +506,14 @@ protected function prep_data_after_read($data) {
*/
public function set_many(array $keyvaluearray) {
$count = 0;
$totaliobytes = 0;
foreach ($keyvaluearray as $pair) {
if ($this->set($pair['key'], $pair['value'])) {
$totaliobytes += $this->lastiobytes;
$count++;
}
}
$this->lastiobytes = $totaliobytes;
return $count;
}

Expand Down
29 changes: 28 additions & 1 deletion cache/stores/file/tests/file_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,31 @@ public function test_cache_get_with_prescan_and_purge() {

$cache->get('testing');
}
}

/**
* Tests the get_last_read byte count.
*/
public function test_get_last_io_bytes(): void {
$definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, 'cachestore_file', 'phpunit_test');
$store = new \cachestore_file('Test');
$store->initialise($definition);

$store->set('foo', 'bar');
$store->set('frog', 'ribbit');
$store->get('foo');
// It's not 3 bytes, because the data is stored serialized.
$this->assertEquals(10, $store->get_last_io_bytes());
$store->get('frog');
$this->assertEquals(13, $store->get_last_io_bytes());
$store->get_many(['foo', 'frog']);
$this->assertEquals(23, $store->get_last_io_bytes());

$store->set('foo', 'goo');
$this->assertEquals(10, $store->get_last_io_bytes());
$store->set_many([
['key' => 'foo', 'value' => 'bar'],
['key' => 'frog', 'value' => 'jump']
]);
$this->assertEquals(21, $store->get_last_io_bytes());
}
}
Loading

0 comments on commit 9c29979

Please sign in to comment.