MDL-72596 core_cache: Track cache I/O size in perfdebug

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)
This commit is contained in:
sam marshall 2021-09-16 16:26:47 +01:00
parent 1a9bee69e6
commit 9c29979b8b
10 changed files with 286 additions and 26 deletions

View File

@ -381,6 +381,7 @@ class cache_helper {
'hits' => 0,
'misses' => 0,
'sets' => 0,
'iobytes' => cache_store::IO_BYTES_NOT_SUPPORTED,
)
)
);
@ -390,6 +391,7 @@ class cache_helper {
'hits' => 0,
'misses' => 0,
'sets' => 0,
'iobytes' => cache_store::IO_BYTES_NOT_SUPPORTED,
);
}
}
@ -429,8 +431,9 @@ class cache_helper {
* @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);
@ -439,6 +442,13 @@ class cache_helper {
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;
}
}
}
/**
@ -479,8 +489,10 @@ class cache_helper {
* @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);
@ -489,6 +501,13 @@ class cache_helper {
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;
}
}
}
/**

View File

@ -445,7 +445,8 @@ class cache implements cache_loader {
}
$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) {
@ -492,6 +493,7 @@ class cache implements cache_loader {
$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();
@ -515,6 +517,9 @@ class cache implements cache_loader {
// 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) {
@ -599,7 +604,7 @@ class cache implements cache_loader {
$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);
}
@ -625,9 +630,6 @@ class cache implements cache_loader {
* @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.
@ -654,7 +656,12 @@ class cache implements cache_loader {
}
$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;
}
/**
@ -781,7 +788,8 @@ class cache implements cache_loader {
}
$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;
}
@ -1845,6 +1853,9 @@ class cache_session extends cache {
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) {
@ -1864,7 +1875,7 @@ class cache_session extends cache {
$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) {
@ -1907,9 +1918,6 @@ class cache_session extends cache {
// 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)) {
@ -1923,7 +1931,12 @@ class cache_session extends cache {
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;
}
/**
@ -1971,6 +1984,9 @@ class cache_session extends cache {
$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;
@ -2038,7 +2054,7 @@ class cache_session extends cache {
$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;
@ -2116,7 +2132,8 @@ class cache_session extends cache {
}
$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;
}

View File

@ -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.
*
@ -392,4 +397,23 @@ abstract class cache_store implements cache_store_interface {
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;
}
}

View File

@ -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.
*
@ -334,6 +341,7 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
* @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();
@ -369,6 +377,7 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
} while (!feof($handle));
// Unlock it.
flock($handle, LOCK_UN);
$this->lastiobytes = strlen($data);
// Return it unserialised.
return $this->prep_data_after_read($data);
}
@ -384,12 +393,25 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
*/
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.
*
@ -434,7 +456,9 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
$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;
@ -482,11 +506,14 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
*/
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;
}

View File

@ -74,4 +74,31 @@ class cachestore_file_test extends cachestore_tests {
$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());
}
}

View File

@ -112,6 +112,13 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
*/
protected $compressor = self::COMPRESSOR_NONE;
/**
* Bytes read or written by last call to set()/get() or set_many()/get_many().
*
* @var int
*/
protected $lastiobytes = 0;
/**
* Determines if the requirements for this type of store are met.
*
@ -290,6 +297,9 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
return $value;
}
// When using compression, values are always strings, so strlen will work.
$this->lastiobytes = strlen($value);
return $this->uncompress($value);
}
@ -306,13 +316,34 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
return $values;
}
$this->lastiobytes = 0;
foreach ($values as &$value) {
$this->lastiobytes += strlen($value);
$value = $this->uncompress($value);
}
return $values;
}
/**
* Gets the number of bytes read from or written to cache as a result of the last action.
*
* If compression is not enabled, this function always returns IO_BYTES_NOT_SUPPORTED. The reason is that
* when compression is not enabled, data sent to the cache is not serialized, and we would
* need to serialize it to compute the size, which would have a significant performance cost.
*
* @return int Bytes read or written
* @since Moodle 4.0
*/
public function get_last_io_bytes(): int {
if ($this->compressor != self::COMPRESSOR_NONE) {
return $this->lastiobytes;
} else {
// Not supported unless compression is on.
return parent::get_last_io_bytes();
}
}
/**
* Set the value of a key.
*
@ -323,6 +354,7 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
public function set($key, $value) {
if ($this->compressor != self::COMPRESSOR_NONE) {
$value = $this->compress($value);
$this->lastiobytes = strlen($value);
}
if ($this->redis->hSet($this->hash, $key, $value) === false) {
@ -354,10 +386,12 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
$now = self::get_time();
}
$this->lastiobytes = 0;
foreach ($keyvaluearray as $pair) {
$key = $pair['key'];
if ($this->compressor != self::COMPRESSOR_NONE) {
$pairs[$key] = $this->compress($pair['value']);
$this->lastiobytes += strlen($pairs[$key]);
} else {
$pairs[$key] = $pair['value'];
}

View File

@ -71,12 +71,15 @@ class cachestore_redis_test extends cachestore_tests {
/**
* Creates the required cachestore for the tests to run against Redis.
*
* @param array $extraconfig Extra configuration options for Redis instance, if any
* @return cachestore_redis
*/
protected function create_cachestore_redis() {
protected function create_cachestore_redis(array $extraconfig = []): cachestore_redis {
/** @var cache_definition $definition */
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_redis', 'phpunit_test');
$store = new cachestore_redis('Test', cachestore_redis::unit_test_configuration());
$configuration = array_merge(cachestore_redis::unit_test_configuration(), $extraconfig);
$store = new cachestore_redis('Test', $configuration);
$store->initialise($definition);
$this->store = $store;
@ -123,4 +126,45 @@ class cachestore_redis_test extends cachestore_tests {
$this->assertFalse($store->release_lock('lock', '321'));
$this->assertTrue($store->release_lock('lock', '123'));
}
/**
* Tests the get_last_io_bytes function when not using compression (just returns unknown).
*/
public function test_get_last_io_bytes(): void {
$store = $this->create_cachestore_redis();
$store->set('foo', [1, 2, 3, 4]);
$this->assertEquals(\cache_store::IO_BYTES_NOT_SUPPORTED, $store->get_last_io_bytes());
$store->get('foo');
$this->assertEquals(\cache_store::IO_BYTES_NOT_SUPPORTED, $store->get_last_io_bytes());
}
/**
* Tests the get_last_io_bytes byte count when using compression.
*/
public function test_get_last_io_bytes_compressed(): void {
$store = $this->create_cachestore_redis(['compressor' => cachestore_redis::COMPRESSOR_PHP_GZIP]);
$alphabet = 'abcdefghijklmnopqrstuvwxyz';
$store->set('small', $alphabet);
$store->set('large', str_repeat($alphabet, 10));
$store->get('small');
// Interesting 'compression'.
$this->assertEquals(54, $store->get_last_io_bytes());
$store->get('large');
// This one is actually smaller than uncompressed value!
$this->assertEquals(57, $store->get_last_io_bytes());
$store->get_many(['small', 'large']);
$this->assertEquals(111, $store->get_last_io_bytes());
$store->set('small', str_repeat($alphabet, 2));
$this->assertEquals(56, $store->get_last_io_bytes());
$store->set_many([
['key' => 'small', 'value' => $alphabet],
['key' => 'large', 'value' => str_repeat($alphabet, 10)]
]);
$this->assertEquals(111, $store->get_last_io_bytes());
}
}

21
cache/tests/behat/perf_info.feature vendored Normal file
View File

@ -0,0 +1,21 @@
@core @core_cache
Feature: Display cache information in performance info
In order to investigate performance problems with caching
As an administrator
I need to be able to see cache information in perfinfo
Background:
Given the following config values are set as admin:
| perfdebug | 15 |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
Scenario: Cache performance info displays
When I am on the "C1" "Course" page logged in as "admin"
# Confirm that the first cache info table is visible by checking an arbitrary row.
Then I should see "default_application" in the "core/databasemeta" "table_row"
# Don't specify the exact size as it may vary.
And I should see "KB" in the "core/databasemeta" "table_row"
# Confirm that the second cache info table is visible.
And I should see "default_application" in the "cachestore_file" "table_row"

4
cache/upgrade.txt vendored
View File

@ -1,6 +1,10 @@
This files describes API changes in /cache/stores/* - cache store plugins.
Information provided here is intended especially for developers.
=== 4.0 ===
* Cache stores may implement new optional function cache_store::get_last_io_bytes() to provide
information about the size of data transferred (shown in footer if performance info enabled).
=== 3.10 ===
* The function supports_recursion() from the lock_factory interface has been deprecated including the related implementations.
* The function extend_lock() from the lock_factory interface has been deprecated without replacement including the related

View File

@ -9562,9 +9562,9 @@ function get_performance_info() {
$table = new html_table();
$table->attributes['class'] = 'cachesused table table-dark table-sm w-auto table-bordered';
$table->head = ['Mode', 'Cache item', 'Static', 'H', 'M', get_string('mappingprimary', 'cache'), 'H', 'M', 'S'];
$table->head = ['Mode', 'Cache item', 'Static', 'H', 'M', get_string('mappingprimary', 'cache'), 'H', 'M', 'S', 'I/O'];
$table->data = [];
$table->align = ['left', 'left', 'left', 'right', 'right', 'left', 'right', 'right', 'right'];
$table->align = ['left', 'left', 'left', 'right', 'right', 'left', 'right', 'right', 'right', 'right'];
$text = 'Caches used (hits/misses/sets): ';
$hits = 0;
@ -9595,9 +9595,11 @@ function get_performance_info() {
$table->align[] = 'right';
$table->align[] = 'right';
$table->align[] = 'right';
$table->align[] = 'right';
$table->head[] = 'H';
$table->head[] = 'M';
$table->head[] = 'S';
$table->head[] = 'I/O';
}
ksort($stats);
@ -9657,6 +9659,22 @@ function get_performance_info() {
$cell = new html_table_cell($data['sets']);
$cell->attributes = ['class' => $cachestoreclass];
$row[] = $cell;
if ($data['hits'] || $data['sets']) {
if ($data['iobytes'] === cache_store::IO_BYTES_NOT_SUPPORTED) {
$size = '-';
} else {
$size = display_size($data['iobytes'], 1, 'KB');
if ($data['iobytes'] >= 10 * 1024) {
$cachestoreclass = ' bg-warning text-dark';
}
}
} else {
$size = '';
}
$cell = new html_table_cell($size);
$cell->attributes = ['class' => $cachestoreclass];
$row[] = $cell;
}
$storec++;
}
@ -9665,6 +9683,7 @@ function get_performance_info() {
$row[] = '';
$row[] = '';
$row[] = '';
$row[] = '';
}
$text .= '} ';
@ -9675,11 +9694,11 @@ function get_performance_info() {
// Now lets also show sub totals for each cache store.
$storetotals = [];
$storetotal = ['hits' => 0, 'misses' => 0, 'sets' => 0];
$storetotal = ['hits' => 0, 'misses' => 0, 'sets' => 0, 'iobytes' => 0];
foreach ($stats as $definition => $details) {
foreach ($details['stores'] as $store => $data) {
if (!array_key_exists($store, $storetotals)) {
$storetotals[$store] = ['hits' => 0, 'misses' => 0, 'sets' => 0];
$storetotals[$store] = ['hits' => 0, 'misses' => 0, 'sets' => 0, 'iobytes' => 0];
}
$storetotals[$store]['class'] = $data['class'];
$storetotals[$store]['hits'] += $data['hits'];
@ -9688,14 +9707,18 @@ function get_performance_info() {
$storetotal['hits'] += $data['hits'];
$storetotal['misses'] += $data['misses'];
$storetotal['sets'] += $data['sets'];
if ($data['iobytes'] !== cache_store::IO_BYTES_NOT_SUPPORTED) {
$storetotals[$store]['iobytes'] += $data['iobytes'];
$storetotal['iobytes'] += $data['iobytes'];
}
}
}
$table = new html_table();
$table->attributes['class'] = 'cachesused table table-dark table-sm w-auto table-bordered';
$table->head = [get_string('storename', 'cache'), get_string('type_cachestore', 'plugin'), 'H', 'M', 'S'];
$table->head = [get_string('storename', 'cache'), get_string('type_cachestore', 'plugin'), 'H', 'M', 'S', 'I/O'];
$table->data = [];
$table->align = ['left', 'left', 'right', 'right', 'right'];
$table->align = ['left', 'left', 'right', 'right', 'right', 'right'];
ksort($storetotals);
@ -9723,14 +9746,34 @@ function get_performance_info() {
$cell = new html_table_cell($data['sets']);
$cell->attributes = ['class' => $cachestoreclass];
$row[] = $cell;
if ($data['hits'] || $data['sets']) {
if ($data['iobytes']) {
$size = display_size($data['iobytes'], 1, 'KB');
} else {
$size = '-';
}
} else {
$size = '';
}
$cell = new html_table_cell($size);
$cell->attributes = ['class' => $cachestoreclass];
$row[] = $cell;
$table->data[] = $row;
}
if (!empty($storetotal['iobytes'])) {
$size = display_size($storetotal['iobytes'], 1, 'KB');
} else if (!empty($storetotal['hits']) || !empty($storetotal['sets'])) {
$size = '-';
} else {
$size = '';
}
$row = [
get_string('total'),
'',
$storetotal['hits'],
$storetotal['misses'],
$storetotal['sets'],
$size,
];
$table->data[] = $row;