diff --git a/application/controllers/Debug.php b/application/controllers/Debug.php index 8f3db042d..d52a15ede 100644 --- a/application/controllers/Debug.php +++ b/application/controllers/Debug.php @@ -105,6 +105,9 @@ class Debug extends CI_Controller $data['userdata_status'] = $userdata_status; } + // Cache Info + $data['cache_info'] = $this->debug_model->get_cache_info(); + $data['dxcc_update'] = $this->cron_model->cron('update_dxcc')->row(); $data['dok_update'] = $this->cron_model->cron('update_update_dok')->row(); $data['lotw_user_update'] = $this->cron_model->cron('update_lotw_users')->row(); @@ -288,6 +291,22 @@ class Debug extends CI_Controller echo json_encode($commit_hash); } + public function clear_cache() { + $this->load->model('user_model'); + if ($this->user_model->authorize(2) == false) { + header('Content-Type: application/json'); + echo json_encode(['status' => false, 'message' => __("You're not allowed to do that!")]); + return; + } + + $this->load->model('Debug_model'); + $status = $this->Debug_model->clear_cache(); + + header('Content-Type: application/json'); + echo json_encode(['status' => (bool) $status]); + return; + } + public function migrate_userdata() { // Check if users logged in $this->load->model('user_model'); diff --git a/application/models/Debug_model.php b/application/models/Debug_model.php index 0082cb420..40acc2a2e 100644 --- a/application/models/Debug_model.php +++ b/application/models/Debug_model.php @@ -185,4 +185,261 @@ class Debug_model extends CI_Model $result = $query->result_array(); return $result; } + + function get_cache_info() { + + $response = []; + + $cache_path = $this->config->item('cache_path') ?? NULL; + if (!$cache_path && $cache_path !== '') { + $cache_path = ''; // default path is application/cache + $response['config']['cache_path'] = sprintf(__(" %s not set in config, using default path: %s"), "'cache_path'", "application/cache"); + } else { + $response['config']['cache_path'] = $cache_path; + } + + $cache_adapter = $this->config->item('cache_adapter') ?? NULL; + if (!$cache_adapter) { + $cache_adapter = 'file'; // default adapter is file + $response['config']['cache_adapter'] = sprintf(__(" %s not set in config, using default adapter: %s"), "'cache_adapter'", "file"); + } else { + $response['config']['cache_adapter'] = $cache_adapter; + } + + $cache_backup = $this->config->item('cache_backup') ?? NULL; + if (!$cache_backup) { + $cache_backup = 'file'; // default backup is file + $response['config']['cache_backup'] = sprintf(__(" %s not set in config, using default backup: %s"), "'cache_backup'", "file"); + } else { + $response['config']['cache_backup'] = $cache_backup; + } + + $cache_key_prefix = $this->config->item('cache_key_prefix') ?? NULL; + if (!$cache_key_prefix) { + $cache_key_prefix = ''; // default key prefix is empty + $response['config']['cache_key_prefix'] = sprintf(__(" %s not set in config, using default: empty string %s"), "'cache_key_prefix'", "('')"); + } else { + $response['config']['cache_key_prefix'] = $cache_key_prefix; + } + + // Load cache driver + $this->load->driver('cache', [ + 'adapter' => $cache_adapter, + 'backup' => $cache_backup, + 'key_prefix' => $cache_key_prefix + ]); + + // Get cache details + $cache_size = $this->get_cache_size(); + $cache_keys_count = $this->get_cache_keys_count(); + + $response['details']['size'] = $this->format_bytes($cache_size); + $response['details']['size_bytes'] = $cache_size; + $response['details']['keys_count'] = $cache_keys_count; + + $available_adapters = ['file', 'redis', 'memcached', 'apcu']; + foreach ($available_adapters as $adapter) { + $response['adapters'][$adapter] = $this->cache->is_supported($adapter); + } + + return $response; + } + + public function clear_cache() { + $cache_adapter = $this->config->item('cache_adapter') ?? 'file'; + $cache_backup = $this->config->item('cache_backup') ?? 'file'; + $cache_key_prefix = $this->config->item('cache_key_prefix') ?? ''; + + $this->load->driver('cache', [ + 'adapter' => $cache_adapter, + 'backup' => $cache_backup, + 'key_prefix' => $cache_key_prefix + ]); + + if ($cache_adapter === 'file') { + $cache_path = $this->config->item('cache_path') ?: 'application/cache'; + $cache_path = realpath(APPPATH . '../') . '/' . $cache_path; + + if (!is_dir($cache_path)) { + return false; + } + + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($cache_path, RecursiveDirectoryIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($files as $file) { + if ($file->isFile() && !in_array($file->getFilename(), ['index.html', '.htaccess'])) { + @unlink($file->getRealPath()); + } + } + + return true; + } + + if (method_exists($this->cache, 'clean')) { + return $this->cache->clean(); + } + + return false; + } + + function get_cache_size($adapter = NULL) { + $cache_adapter = $adapter ?? ($this->config->item('cache_adapter') ?? 'file'); + + switch ($cache_adapter) { + case 'file': + $cache_path = $this->config->item('cache_path') ?: 'application/cache'; + $cache_path = realpath(APPPATH . '../') . '/' . $cache_path; + + if (!is_dir($cache_path)) { + return 0; + } + + $size = 0; + $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($cache_path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($files as $file) { + if ($file->isFile() && !in_array($file->getFilename(), ['index.html', '.htaccess'])) { + $size += $file->getSize(); + } + } + + return $size; + + case 'redis': + if ($this->cache->is_supported('redis')) { + $redis_info = $this->cache->cache_info('redis'); + // Note: This returns total Redis server memory usage, not just cache keys with prefix + // used_memory_dataset excludes overhead and is more accurate for data size + if (isset($redis_info['used_memory_dataset'])) { + return (int)$redis_info['used_memory_dataset']; + } + return isset($redis_info['used_memory']) ? (int)$redis_info['used_memory'] : 0; + } + return 0; + + case 'memcached': + if ($this->cache->is_supported('memcached')) { + $memcached_info = $this->cache->cache_info('memcached'); + log_message('debug', 'Memcached Info: ' . print_r($memcached_info, true)); + + // Memcached returns array of servers, each with stats + if (is_array($memcached_info)) { + $total_bytes = 0; + foreach ($memcached_info as $server_stats) { + if (is_array($server_stats)) { + // bytes is the current bytes used in the cache + if (isset($server_stats['bytes'])) { + $total_bytes += (int) $server_stats['bytes']; + } + } + } + return $total_bytes; + } + + // Fallback for single server format + if (isset($memcached_info['bytes'])) { + return (int) $memcached_info['bytes']; + } + + return 0; + } + return 0; + + case 'apcu': + if ($this->cache->is_supported('apcu')) { + $apcu_info = apcu_cache_info(); + return isset($apcu_info['mem_size']) ? (int)$apcu_info['mem_size'] : 0; + } + return 0; + + default: + return 0; + } + } + + function get_cache_keys_count($adapter = NULL) { + $cache_adapter = $adapter ?? ($this->config->item('cache_adapter') ?? 'file'); + + switch ($cache_adapter) { + case 'file': + $cache_path = $this->config->item('cache_path') ?: 'application/cache'; + $cache_path = realpath(APPPATH . '../') . '/' . $cache_path; + + if (!is_dir($cache_path)) { + return 0; + } + + $count = 0; + $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($cache_path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($files as $file) { + if ($file->isFile() && !in_array($file->getFilename(), ['index.html', '.htaccess'])) { + $count++; + } + } + + return $count; + + case 'redis': + if ($this->cache->is_supported('redis')) { + $redis_info = $this->cache->cache_info('redis'); + $total_keys = 0; + // Parse keyspace info (db0, db1, etc.) + foreach ($redis_info as $key => $value) { + if (preg_match('/^db(\d+)$/', $key) && is_string($value)) { + // Parse "keys=4,expires=4,avg_ttl=43131246" format + if (preg_match('/keys=(\d+)/', $value, $matches)) { + $total_keys += (int)$matches[1]; + } + } + } + return $total_keys; + } + return 0; + + case 'memcached': + if ($this->cache->is_supported('memcached')) { + $memcached_info = $this->cache->cache_info('memcached'); + if (isset($memcached_info['curr_items'])) { + return (int) $memcached_info['curr_items']; + } + + if (is_array($memcached_info)) { + $total_items = 0; + foreach ($memcached_info as $server_stats) { + if (is_array($server_stats) && isset($server_stats['curr_items'])) { + $total_items += (int) $server_stats['curr_items']; + } + } + return $total_items; + } + return 0; + } + return 0; + + case 'apcu': + if ($this->cache->is_supported('apcu')) { + $apcu_info = apcu_cache_info(); + return isset($apcu_info['num_entries']) ? (int)$apcu_info['num_entries'] : 0; + } + return 0; + + default: + return 0; + } + + } + + function format_bytes($bytes, $precision = 2) { + $units = ['B', 'KB', 'MB', 'GB']; + $bytes = max($bytes, 0); + $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); + $pow = min($pow, count($units) - 1); + $bytes /= (1 << (10 * $pow)); + + return round($bytes, $precision) . ' ' . $units[$pow]; + } } diff --git a/application/views/debug/index.php b/application/views/debug/index.php index 41dd0b25a..14030eaf2 100644 --- a/application/views/debug/index.php +++ b/application/views/debug/index.php @@ -399,6 +399,74 @@ + +
= __("Current Configuration"); ?>
+| = __("Adapter"); ?> | ++ + | +
| = __("Backup"); ?> | ++ + | +
| = __("Path"); ?> | + |
+
| = __("Key Prefix"); ?> | + |
+
= __("Cache Details"); ?>
+| = __("Total Size"); ?> | ++ + | +
| = __("Number of Keys"); ?> | ++ + | +
= __("Available Adapters"); ?>
+