MDL-72619 core_cache: Provide admin page to view cache size estimates

This commit is contained in:
sam marshall 2021-09-20 18:04:02 +01:00
parent b8291d04f4
commit 1b94bb8c20
15 changed files with 749 additions and 3 deletions

View File

@ -708,6 +708,8 @@ if ($hassiteconfig) {
$ADMIN->add('modules', new admin_category('cache', new lang_string('caching', 'cache')));
$ADMIN->add('cache', new admin_externalpage('cacheconfig', new lang_string('cacheconfig', 'cache'), $CFG->wwwroot .'/cache/admin.php'));
$ADMIN->add('cache', new admin_externalpage('cachetestperformance', new lang_string('testperformance', 'cache'), $CFG->wwwroot . '/cache/testperformance.php'));
$ADMIN->add('cache', new admin_externalpage('cacheusage',
new lang_string('cacheusage', 'cache'), $CFG->wwwroot . '/cache/usage.php'));
$ADMIN->add('cache', new admin_category('cachestores', new lang_string('cachestores', 'cache')));
$ADMIN->locate('cachestores')->set_sorting(true);
foreach (core_component::get_plugin_list('cachestore') as $plugin => $path) {

View File

@ -404,4 +404,28 @@ abstract class administration_helper extends cache_helper {
* @return string the HTML for the page.
*/
abstract public function generate_admin_page(\core_cache\output\renderer $renderer): string;
/**
* Gets usage information about the whole cache system.
*
* This is a slow function and should only be used on an admin information page.
*
* The returned array lists all cache definitions with fields 'cacheid' and 'stores'. For
* each store, the following fields are available:
*
* - name (store name)
* - class (e.g. cachestore_redis)
* - supported (true if we have any information)
* - items (number of items stored)
* - mean (mean size of item)
* - sd (standard deviation for item sizes)
* - margin (margin of error for mean at 95% confidence)
* - storetotal (total usage for store if known, otherwise null)
*
* The storetotal field will be the same for every cache that uses the same store.
*
* @param int $samplekeys Number of keys to sample when checking size of large caches
* @return array Details of cache usage
*/
abstract public function get_usage(int $samplekeys): array;
}

View File

@ -792,4 +792,87 @@ class administration_display_helper extends \core_cache\administration_helper {
return $html;
}
/**
* Gets usage information about the whole cache system.
*
* This is a slow function and should only be used on an admin information page.
*
* The returned array lists all cache definitions with fields 'cacheid' and 'stores'. For
* each store, the following fields are available:
*
* - name (store name)
* - class (e.g. cachestore_redis)
* - supported (true if we have any information)
* - items (number of items stored)
* - mean (mean size of item)
* - sd (standard deviation for item sizes)
* - margin (margin of error for mean at 95% confidence)
* - storetotal (total usage for store if known, otherwise null)
*
* The storetotal field will be the same for every cache that uses the same store.
*
* @param int $samplekeys Number of keys to sample when checking size of large caches
* @return array Details of cache usage
*/
public function get_usage(int $samplekeys): array {
$results = [];
$factory = cache_factory::instance();
// Check the caches we already have an instance of, so we don't make another one...
$got = $factory->get_caches_in_use();
$gotid = [];
foreach ($got as $longid => $unused) {
// The IDs here can be of the form cacheid/morestuff if there are parameters in the
// cache. Any entry for a cacheid is good enough to consider that we don't need to make
// another entry ourselves, so we remove the extra bits and track the basic cache id.
$gotid[preg_replace('~^([^/]+/[^/]+)/.*$~', '$1', $longid)] = true;
}
$storetotals = [];
$config = $factory->create_config_instance();
foreach ($config->get_definitions() as $configdetails) {
if (!array_key_exists($configdetails['component'] . '/' . $configdetails['area'], $gotid)) {
// Where possible (if it doesn't need identifiers), make an instance of the cache, otherwise
// we can't get the store instances for it (and it won't show up in the list).
if (empty($configdetails['requireidentifiers'])) {
\cache::make($configdetails['component'], $configdetails['area']);
}
}
$definition = $factory->create_definition($configdetails['component'], $configdetails['area']);
$stores = $factory->get_store_instances_in_use($definition);
// Create object for results about this cache definition.
$currentresult = (object)['cacheid' => $definition->get_id(), 'stores' => []];
$results[$currentresult->cacheid] = $currentresult;
/** @var cache_store $store */
foreach ($stores as $store) {
// Skip static cache.
if ($store instanceof \cachestore_static) {
continue;
}
// Get cache size details from store.
$currentstore = $store->cache_size_details($samplekeys);
// Add in basic information about store.
$currentstore->name = $store->my_name();
$currentstore->class = get_class($store);
// Add in store total.
if (!array_key_exists($currentstore->name, $storetotals)) {
$storetotals[$currentstore->name] = $store->store_total_size();
}
$currentstore->storetotal = $storetotals[$currentstore->name];
$currentresult->stores[] = $currentstore;
}
}
ksort($results);
return $results;
}
}

View File

@ -413,4 +413,138 @@ class renderer extends \plugin_renderer_base {
$html .= html_writer::end_div();
return $html;
}
/**
* Creates the two tables which display on the usage page.
*
* @param array $usage Usage information (from cache_helper::usage)
* @return array Array of 2 tables (main and summary table)
* @throws \coding_exception
*/
public function usage_tables(array $usage): array {
$table = new \html_table();
$table->id = 'usage_main';
$table->head = [
get_string('definition', 'cache'),
get_string('storename', 'cache'),
get_string('plugin', 'cache'),
get_string('usage_items', 'cache'),
get_string('usage_mean', 'cache'),
get_string('usage_sd', 'cache'),
get_string('usage_total', 'cache'),
get_string('usage_totalmargin', 'cache')];
$table->align = [
'left', 'left', 'left',
'right', 'right', 'right', 'right', 'right'
];
$table->data = [];
$summarytable = new \html_table();
$summarytable->id = 'usage_summary';
$summarytable->head = [
get_string('storename', 'cache'),
get_string('plugin', 'cache'),
get_string('usage_total', 'cache'),
get_string('usage_realtotal', 'cache')
];
$summarytable->align = [
'left', 'left',
'right', 'right',
];
$summarytable->data = [];
$summarytable->attributes['class'] = 'generaltable w-auto';
$storetotals = [];
// We will highlight all cells that are more than 2% of total size, so work that out first.
$total = 0;
foreach ($usage as $definition) {
foreach ($definition->stores as $storedata) {
$total += $storedata->items * $storedata->mean;
}
}
$highlightover = round($total / 50);
foreach ($usage as $definition) {
foreach ($definition->stores as $storedata) {
$row = [];
$row[] = s($definition->cacheid);
$row[] = s($storedata->name);
$row[] = s($storedata->class);
if (!$storedata->supported) {
// We don't have data for this store because it isn't searchable.
$row[] = '-';
} else {
$row[] = $storedata->items;
}
if ($storedata->items) {
$row[] = display_size(round($storedata->mean));
if ($storedata->items > 1) {
$row[] = display_size(round($storedata->sd));
} else {
$row[] = '';
}
$cellsize = round($storedata->items * $storedata->mean);
$row[] = display_size($cellsize, 1, 'MB');
if (!array_key_exists($storedata->name, $storetotals)) {
$storetotals[$storedata->name] = (object)[
'plugin' => $storedata->class,
'total' => 0,
'storetotal' => $storedata->storetotal,
];
}
$storetotals[$storedata->name]->total += $cellsize;
} else {
$row[] = '';
$row[] = '';
$cellsize = 0;
$row[] = '';
}
if ($storedata->margin) {
// Plus or minus.
$row[] = '±' . display_size($storedata->margin * $storedata->items, 1, 'MB');
} else {
$row[] = '';
}
$htmlrow = new \html_table_row($row);
if ($cellsize > $highlightover) {
$htmlrow->attributes = ['class' => 'table-warning'];
}
$table->data[] = $htmlrow;
}
}
ksort($storetotals);
foreach ($storetotals as $storename => $storedetails) {
$row = [s($storename), s($storedetails->plugin)];
$row[] = display_size($storedetails->total, 1, 'MB');
if ($storedetails->storetotal !== null) {
$row[] = display_size($storedetails->storetotal, 1, 'MB');
} else {
$row[] = '-';
}
$summarytable->data[] = $row;
}
return [$table, $summarytable];
}
/**
* Renders the usage page.
*
* @param \html_table $maintable Main table
* @param \html_table $summarytable Summary table
* @param \moodleform $samplesform Form to select number of samples
* @return string HTML for page
*/
public function usage_page(\html_table $maintable, \html_table $summarytable, \moodleform $samplesform): string {
$data = [
'maintable' => \html_writer::table($maintable),
'summarytable' => \html_writer::table($summarytable),
'samplesform' => $samplesform->render()
];
return $this->render_from_template('core_cache/usage', $data);
}
}

View File

@ -0,0 +1,58 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Form for usage page to select number of samples.
*
* @package core_cache
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_cache\output;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
/**
* Form for usage page to select number of samples.
*
* @package core_cache
*/
class usage_samples_form extends \moodleform {
/**
* Constructor sets form up to use GET request to current page.
*/
public function __construct() {
parent::__construct(null, null, 'get');
}
/**
* Adds controls to form.
*/
protected function definition() {
$mform = $this->_form;
$radioarray = [];
foreach ([50, 100, 200, 500, 1000] as $samples) {
$radioarray[] = $mform->createElement('radio', 'samples', '', $samples, $samples);
}
$mform->setDefault('samples', 50);
$mform->addGroup($radioarray, 'samplesradios', get_string('usage_samples', 'cache'), [' '], false);
$mform->addElement('submit', 'submit', get_string('update'));
}
}

View File

@ -381,6 +381,123 @@ abstract class cache_store implements cache_store_interface {
return array();
}
/**
* Estimates the storage size used within this cache if the given value is stored with the
* given key.
*
* This function is not exactly accurate; it does not necessarily take into account all the
* overheads involved. It is only intended to give a good idea of the relative size of
* different caches.
*
* The default implementation serializes both key and value and sums the lengths (as a rough
* estimate which is probably good enough for everything unless the cache offers compression).
*
* @param mixed $key Key
* @param mixed $value Value
* @return int Size in bytes
*/
public function estimate_stored_size($key, $value): int {
return strlen(serialize($key)) + strlen(serialize($value));
}
/**
* Gets the amount of memory/storage currently used by this cache store if known.
*
* This value should be obtained quickly from the store itself, if available.
*
* This is the total memory usage of the entire store, not for ther specific cache in question.
*
* Where not supported (default), will always return null.
*
* @return int|null Amount of memory used in bytes or null
*/
public function store_total_size(): ?int {
return null;
}
/**
* Gets the amount of memory used by this specific cache within the store, if known.
*
* This function may be slow and should not be called in normal usage, only for administration
* pages. The value is usually an estimate, and may not be available at all.
*
* When estimating, a number of sample items will be used for the estimate. If set to 50
* (default), then this function will retrieve 50 random items and use that to estimate the
* total size.
*
* The return value has the following fields:
* - supported (true if any other values are completed)
* - items (number of items)
* - mean (mean size of one item in bytes)
* - sd (standard deviation of item size in bytes, based on sample)
* - margin (95% confidence margin for mean - will be 0 if exactly computed)
*
* @param int $samplekeys Number of samples to use
* @return stdClass Object with information about the store size
*/
public function cache_size_details(int $samplekeys = 50): stdClass {
$result = (object)[
'supported' => false,
'items' => 0,
'mean' => 0,
'sd' => 0,
'margin' => 0
];
// If this cache isn't searchable, we don't know the answer.
if (!$this->is_searchable()) {
return $result;
}
$result->supported = true;
// Get all the keys for the cache.
$keys = $this->find_all();
$result->items = count($keys);
// Don't do anything else if there are no items.
if ($result->items === 0) {
return $result;
}
// Select N random keys.
$exact = false;
if ($result->items <= $samplekeys) {
$samples = $keys;
$exact = true;
} else {
$indexes = array_rand($keys, $samplekeys);
$samples = [];
foreach ($indexes as $index) {
$samples[] = $keys[$index];
}
}
// Get the random items from cache and estimate the size of each.
$sizes = [];
foreach ($samples as $samplekey) {
$value = $this->get($samplekey);
$sizes[] = $this->estimate_stored_size($samplekey, $value);
}
$number = count($sizes);
// Calculate the mean and standard deviation.
$result->mean = array_sum($sizes) / $number;
$squarediff = 0;
foreach ($sizes as $size) {
$squarediff += ($size - $result->mean) ** 2;
}
$squarediff /= $number;
$result->sd = sqrt($squarediff);
// If it's not exact, also calculate the confidence interval.
if (!$exact) {
// 95% confidence has a Z value of 1.96.
$result->margin = (1.96 * $result->sd) / sqrt($number);
}
return $result;
}
/**
* Returns true if this cache store instance is both suitable for testing, and ready for testing.
*

View File

@ -784,4 +784,57 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
}
return $return;
}
/**
* Gets total size for the directory used by the cache store.
*
* @return int Total size in bytes
*/
public function store_total_size(): ?int {
return get_directory_size($this->filestorepath);
}
/**
* Gets total size for a specific cache.
*
* With the file cache we can just look at the directory listing without having to
* actually load any files, so the $samplekeys parameter is ignored.
*
* @param int $samplekeys Unused
* @return stdClass Cache details
*/
public function cache_size_details(int $samplekeys = 50): stdClass {
$result = (object)[
'supported' => true,
'items' => 0,
'mean' => 0,
'sd' => 0,
'margin' => 0
];
// Find all the files in this cache.
$this->ensure_path_exists();
$files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
if ($files === false || count($files) === 0) {
return $result;
}
// Get the sizes and count of files.
$sizes = [];
foreach ($files as $file) {
$result->items++;
$sizes[] = filesize($file);
}
// Work out mean and standard deviation.
$total = array_sum($sizes);
$result->mean = $total / $result->items;
$squarediff = 0;
foreach ($sizes as $size) {
$squarediff += ($size - $result->mean) ** 2;
}
$squarediff /= $result->items;
$result->sd = sqrt($squarediff);
return $result;
}
}

View File

@ -572,7 +572,7 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
$count = 0;
$batches = 0;
$timebefore = microtime(true);
$memorybefore = $this->get_used_memory();
$memorybefore = $this->store_total_size();
do {
$keys = $this->redis->zRangeByScore($this->hash . self::TTL_SUFFIX, 0, $limit,
['limit' => [0, self::TTL_EXPIRE_BATCH]]);
@ -580,7 +580,7 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
$count += count($keys);
$batches++;
} while (count($keys) === self::TTL_EXPIRE_BATCH);
$memoryafter = $this->get_used_memory();
$memoryafter = $this->store_total_size();
$timeafter = microtime(true);
$result = ['keys' => $count, 'batches' => $batches, 'time' => $timeafter - $timebefore];
@ -623,12 +623,29 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
}
}
/**
* Estimates the stored size, taking into account whether compression is turned on.
*
* @param mixed $key Key name
* @param mixed $value Value
* @return int Approximate stored size
*/
public function estimate_stored_size($key, $value): int {
if ($this->compressor == self::COMPRESSOR_NONE) {
// If uncompressed, use default estimate.
return parent::estimate_stored_size($key, $value);
} else {
// If compressed, compress value.
return strlen($this->serialize($key)) + strlen($this->compress($value));
}
}
/**
* Gets Redis reported memory usage.
*
* @return int|null Memory used by Redis or null if we don't know
*/
protected function get_used_memory(): ?int {
public function store_total_size(): ?int {
$details = $this->redis->info('MEMORY');
if (empty($details['used_memory'])) {
return null;

46
cache/templates/usage.mustache vendored Normal file
View File

@ -0,0 +1,46 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_cache/usage
The cache usage page
Context variables required for this template:
* maintable - Main table HTML
* summarytable - Summary table HTML
* samplesform - Form to choose number of samples
Example context (json):
{
"maintable": "",
"summarytable": "",
"samplesform": ""
}
}}
<h2>{{# str }} cacheusage, cache {{/ str }}</h2>
<div class="mt-6 mb-6">
<h3>{{# str }} cachestores, cache {{/ str }}</h3>
{{{summarytable}}}
</div>
<div class="mt-6 mb-6">
<h3>{{# str }} details {{/ str }}</h3>
{{{maintable}}}
</div>
{{{samplesform}}}

View File

@ -235,4 +235,39 @@ class core_cache_administration_helper_testcase extends advanced_testcase {
$result = cache_helper::hash_key('test/test', $definition);
$this->assertEquals(sha1($definition->generate_single_key_prefix().'-test/test'), $result);
}
/**
* Tests the get_usage function.
*/
public function test_get_usage(): void {
// Create a test cache definition and put items in it.
$instance = cache_config_testing::instance(true);
$instance->phpunit_add_definition('phpunit/test', [
'mode' => cache_store::MODE_APPLICATION,
'component' => 'phpunit',
'area' => 'test',
'simplekeys' => true
]);
$cache = cache::make('phpunit', 'test');
for ($i = 0; $i < 100; $i++) {
$cache->set('key' . $i, str_repeat('x', $i));
}
$factory = cache_factory::instance();
$adminhelper = $factory->get_administration_display_helper();
$usage = $adminhelper->get_usage(10)['phpunit/test'];
$this->assertEquals('phpunit/test', $usage->cacheid);
$this->assertCount(1, $usage->stores);
$store = $usage->stores[0];
$this->assertEquals('default_application', $store->name);
$this->assertEquals('cachestore_file', $store->class);
$this->assertEquals(true, $store->supported);
$this->assertEquals(100, $store->items);
// As file store checks all items, the values should be exact.
$this->assertEqualsWithDelta(57.4, $store->mean, 0.1);
$this->assertEqualsWithDelta(29.0, $store->sd, 0.1);
$this->assertEquals(0, $store->margin);
}
}

35
cache/tests/behat/usage.feature vendored Normal file
View File

@ -0,0 +1,35 @@
@core @core_cache
Feature: Display usage information for cache
In order to investigate performance problems with caching
As an administrator
I need to be able to monitor the size of items in the cache
Background:
Given 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"
And I navigate to "Plugins > Caching > Cache usage" in site administration
# Check one row of the summary table. The actual total is currently 3.6MB so it's likely to
# continue to be in the MB range.
Then "default_application" row "Plugin" column of "usage_summary" table should contain "cachestore_file"
And "default_application" row "Estimated total" column of "usage_summary" table should contain "MB"
And "default_application" row "Actual usage (if known)" column of "usage_summary" table should contain "MB"
# And one row of the main table. The totals are fixed to use the MB unit.
And "core/config" row "Store name" column of "usage_main" table should contain "default_application"
And "core/config" row "Plugin" column of "usage_main" table should contain "cachestore_file"
And "core/config" row "Estimated total" column of "usage_main" table should contain "MB"
Scenario: Sample option works
When I am on the "C1" "Course" page logged in as "admin"
And I navigate to "Plugins > Caching > Cache usage" in site administration
And I set the field "samples" to "1000"
And I press "Update"
Then the field "samples" matches value "1000"
And "usage_summary" "table" should exist
And "usage_main" "table" should exist

79
cache/tests/store_test.php vendored Normal file
View File

@ -0,0 +1,79 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_cache;
/**
* Unit tests for cache_store functionality.
*
* @package core_cache
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class store_test extends \advanced_testcase {
/**
* Tests the default implementation of cache_size_details, which does some
* complicated statistics.
*/
public function test_cache_size_details(): void {
// Fill a store with 100 entries of varying size.
$store = self::create_static_store();
for ($i = 0; $i < 100; $i++) {
$store->set('key' . $i, str_repeat('x', $i));
}
// Do the statistics for 10 random picks.
$details = $store->cache_size_details(10);
$this->assertTrue($details->supported);
$this->assertEquals(100, $details->items);
// Min/max possible means if it picks the smallest/largest 10.
$this->assertGreaterThan(22, $details->mean);
$this->assertLessThan(115, $details->mean);
// Min/max possible SD.
$this->assertLessThan(49, $details->sd);
$this->assertGreaterThan(2.8, $details->sd);
// Lowest possible confidence margin is about 1.74.
$this->assertGreaterThan(1.7, $details->margin);
// Repeat the statistics for a pick of all 100 entries (exact).
$details = $store->cache_size_details(100);
$this->assertTrue($details->supported);
$this->assertEquals(100, $details->items);
$this->assertEqualsWithDelta(69.3, $details->mean, 0.1);
$this->assertEqualsWithDelta(29.2, $details->sd, 0.1);
$this->assertEquals(0, $details->margin);
}
/**
* Creates a static store for testing.
*
* @return \cachestore_static Store
*/
protected static function create_static_store(): \cachestore_static {
require_once(__DIR__ . '/../stores/static/lib.php');
$store = new \cachestore_static('frog');
$definition = \cache_definition::load('zombie', [
'mode' => \cache_store::MODE_REQUEST,
'component' => 'phpunit',
'area' => 'store_test',
'simplekeys' => true
]);
$store->initialise($definition);
return $store;
}
}

5
cache/upgrade.txt vendored
View File

@ -1,6 +1,11 @@
This files describes API changes in /cache/stores/* - cache store plugins.
Information provided here is intended especially for developers.
=== 4.0 ===
* The cache_store class now has functions cache_size_details(), store_total_size(), and
estimate_stored_size(), related to size used by the cache. These can be overridden by a cache
store to provide better information for the new cache usage admin page.
=== 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

50
cache/usage.php vendored Normal file
View File

@ -0,0 +1,50 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Show current cache usage (number of items, size of caches).
*
* @package core_cache
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../config.php');
require_once($CFG->dirroot . '/lib/adminlib.php');
admin_externalpage_setup('cacheusage');
$adminhelper = cache_factory::instance()->get_administration_display_helper();
raise_memory_limit(MEMORY_EXTRA);
$samples = optional_param('samples', 50, PARAM_INT);
// Just for safety reasons, stop people choosing a stupid number.
if ($samples > 1000) {
$samples = 1000;
}
// Get the actual data.
$usage = $adminhelper->get_usage($samples);
// Set up the renderer and organise data to render.
$renderer = $PAGE->get_renderer('core_cache');
[$table, $summarytable] = $renderer->usage_tables($usage);
$form = new \core_cache\output\usage_samples_form();
echo $OUTPUT->header();
echo $renderer->usage_page($table, $summarytable, $form);
echo $OUTPUT->footer();

View File

@ -93,6 +93,7 @@ $string['cachedef_yuimodules'] = 'YUI Module definitions';
$string['cachedef_gradesetting'] = 'Course grade setting';
$string['cachelock_file_default'] = 'Default file locking';
$string['cachestores'] = 'Cache stores';
$string['cacheusage'] = 'Cache usage';
$string['canuselocalstore'] = 'Can use local store';
$string['component'] = 'Component';
$string['confirmlockdeletion'] = 'Confirm lock deletion';
@ -203,6 +204,13 @@ $string['tested'] = 'Tested';
$string['testperformance'] = 'Test performance';
$string['unsupportedmode'] = 'Unsupported mode';
$string['untestable'] = 'Untestable';
$string['usage_items'] = 'Items';
$string['usage_mean'] = 'Mean item size';
$string['usage_samples'] = 'Items sampled per cache';
$string['usage_sd'] = 'Std. dev.';
$string['usage_total'] = 'Estimated total';
$string['usage_totalmargin'] = 'Error margin (95%)';
$string['usage_realtotal'] = 'Actual usage (if known)';
$string['userinputsharingkey'] = 'Custom key for sharing';
$string['userinputsharingkey_help'] = 'Enter your own private key here. When you set up other stores on other sites you wish to share data with make sure you set the exact same key there.';