Changeset 3320647
- Timestamp:
- 07/01/2025 01:02:25 PM (9 months ago)
- Location:
- dynamic-front-end-heartbeat-control
- Files:
-
- 35 added
- 12 edited
-
tags/1.2.99 (added)
-
tags/1.2.99/LICENSE (added)
-
tags/1.2.99/admin (added)
-
tags/1.2.99/admin/affix.php (added)
-
tags/1.2.99/admin/ajax-handler.php (added)
-
tags/1.2.99/admin/asset-manager.php (added)
-
tags/1.2.99/admin/heartbeat-config.php (added)
-
tags/1.2.99/admin/unclogger-menu.php (added)
-
tags/1.2.99/css (added)
-
tags/1.2.99/css/dfhcsl-admin.css (added)
-
tags/1.2.99/defibrillator (added)
-
tags/1.2.99/defibrillator/cli-helper.php (added)
-
tags/1.2.99/defibrillator/db-health.php (added)
-
tags/1.2.99/defibrillator/load-estimator.php (added)
-
tags/1.2.99/defibrillator/rest-api.php (added)
-
tags/1.2.99/defibrillator/unclogger-db.php (added)
-
tags/1.2.99/defibrillator/unclogger.php (added)
-
tags/1.2.99/engine (added)
-
tags/1.2.99/engine/interval-helper.php (added)
-
tags/1.2.99/engine/server-load.php (added)
-
tags/1.2.99/engine/server-response.php (added)
-
tags/1.2.99/engine/system-load-fallback.php (added)
-
tags/1.2.99/heartbeat-async.php (added)
-
tags/1.2.99/heartbeat-controller.php (added)
-
tags/1.2.99/js (added)
-
tags/1.2.99/js/chart.js (added)
-
tags/1.2.99/js/dfhcsl-admin.js (added)
-
tags/1.2.99/js/heartbeat.js (added)
-
tags/1.2.99/js/heartbeat.min.js (added)
-
tags/1.2.99/readme.txt (added)
-
tags/1.2.99/settings.php (added)
-
tags/1.2.99/visitor (added)
-
tags/1.2.99/visitor/cookie-helper.php (added)
-
tags/1.2.99/visitor/manager.php (added)
-
tags/1.2.99/widget.php (added)
-
trunk/defibrillator/load-estimator.php (modified) (1 diff)
-
trunk/engine/interval-helper.php (modified) (1 diff)
-
trunk/engine/server-load.php (modified) (13 diffs)
-
trunk/engine/server-response.php (modified) (10 diffs)
-
trunk/engine/system-load-fallback.php (modified) (2 diffs)
-
trunk/heartbeat-async.php (modified) (7 diffs)
-
trunk/heartbeat-controller.php (modified) (8 diffs)
-
trunk/js/heartbeat.js (modified) (2 diffs)
-
trunk/js/heartbeat.min.js (modified) (1 diff)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/visitor/cookie-helper.php (modified) (1 diff)
-
trunk/visitor/manager.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
dynamic-front-end-heartbeat-control/trunk/defibrillator/load-estimator.php
r3310561 r3320647 6 6 class Dfehc_ServerLoadEstimator { 7 7 const BASELINE_TRANSIENT_PREFIX = 'dfehc_baseline_'; 8 const LOAD_CACHE_TRANSIENT = 'dfehc_last_known_load';9 const LOAD_SPIKE_TRANSIENT = 'dfehc_load_spike_score';10 11 public static function get_server_load( $duration = 0.025) {8 const LOAD_CACHE_TRANSIENT = 'dfehc_last_known_load'; 9 const LOAD_SPIKE_TRANSIENT = 'dfehc_load_spike_score'; 10 11 public static function get_server_load(float $duration = 0.025) { 12 12 if (!function_exists('microtime') || (defined('DFEHC_DISABLE_LOAD_ESTIMATION') && DFEHC_DISABLE_LOAD_ESTIMATION)) { 13 13 return false; 14 14 } 15 15 16 $hostname = self::get_hostname_key(); 17 $baseline_transient = self::get_baseline_transient_name($hostname); 18 $cached_load = get_transient(self::LOAD_CACHE_TRANSIENT); 19 20 if ($cached_load !== false) return $cached_load; 21 22 $baseline_expiration = apply_filters('dfehc_baseline_expiration', 7 * DAY_IN_SECONDS); 23 $cache_ttl = apply_filters('dfehc_load_cache_ttl', 90); 24 25 $baseline = self::get_baseline_value($baseline_transient); 16 $hostname = self::get_hostname_key(); 17 $baseline_t = self::get_baseline_transient_name($hostname); 18 $cache_ttl = (int) apply_filters('dfehc_load_cache_ttl', 90); 19 $cached = get_transient(self::LOAD_CACHE_TRANSIENT); 20 21 if ($cached !== false) { 22 return $cached; 23 } 24 25 $sys_avg = self::try_sys_getloadavg(); 26 if ($sys_avg !== null) { 27 set_transient(self::LOAD_CACHE_TRANSIENT, $sys_avg, $cache_ttl); 28 return $sys_avg; 29 } 30 31 $baseline = self::get_baseline_value($baseline_t); 26 32 if (!$baseline) { 27 $baseline = self::calibrate_baseline($duration); 28 self::set_baseline_value($baseline_transient, $baseline, $baseline_expiration); 29 } 30 33 $baseline = self::maybe_calibrate($baseline_t, $duration); 34 } 35 36 $loops_per_sec = self::run_loop($duration); 37 if ($loops_per_sec <= 0) { 38 return false; 39 } 40 41 $load_ratio = ($baseline * 0.125) / max($loops_per_sec, 1); 42 $load_percent = round(min(100, max(0, $load_ratio * 100)), 2); 43 44 self::update_spike_score($load_percent); 45 set_transient(self::LOAD_CACHE_TRANSIENT, $load_percent, $cache_ttl); 46 47 return $load_percent; 48 } 49 50 public static function calibrate_baseline(float $duration = 0.025): float { 51 return self::run_loop($duration); 52 } 53 54 public static function maybe_calibrate_during_cron(): void { 55 if (!defined('DOING_CRON') || !DOING_CRON) { 56 return; 57 } 58 self::ensure_baseline(); 59 } 60 61 public static function maybe_calibrate_if_idle(): void { 62 if (is_admin() || is_user_logged_in()) { 63 return; 64 } 65 self::ensure_baseline(); 66 } 67 68 private static function try_sys_getloadavg(): ?float { 69 if (!function_exists('sys_getloadavg')) { 70 return null; 71 } 72 $avg = sys_getloadavg(); 73 if (!is_array($avg) || !isset($avg[0])) { 74 return null; 75 } 76 $cores = function_exists('dfehc_get_cpu_cores') ? dfehc_get_cpu_cores() : 1; 77 if ($cores <= 0) { 78 $cores = 1; 79 } 80 return min(100, round(($avg[0] / $cores) * 100, 2)); 81 } 82 83 private static function run_loop(float $duration): float { 31 84 $start = microtime(true); 32 $end = $start + $duration; 33 $count = 0; 34 $now = $start; 35 85 $end = $start + $duration; 86 $cnt = 0; 87 $now = $start; 36 88 while ($now < $end) { 37 $count++;89 ++$cnt; 38 90 $now = microtime(true); 39 91 } 40 41 $time_taken = microtime(true) - $start; 42 $loops_per_second = $count / max($time_taken, 0.0001); 43 $load_ratio = ($baseline * 0.125) / max($loops_per_second, 1); 44 $load_percent = round(min(100, max(0, $load_ratio * 100)), 2); 45 46 do_action('dfehc_debug_load_metrics', compact('baseline', 'count', 'loops_per_second', 'load_percent')); 47 48 $spike_score = (float) get_transient(self::LOAD_SPIKE_TRANSIENT); 49 $spike_decay = apply_filters('dfehc_spike_decay', 0.5); 50 $spike_increment = apply_filters('dfehc_spike_increment', 1.0); 51 $spike_threshold = apply_filters('dfehc_spike_threshold', 3.0); 92 $elapsed = $now - $start; 93 return $elapsed > 0 ? $cnt / $elapsed : 0.0; 94 } 95 96 private static function maybe_calibrate(string $baseline_t, float $duration): float { 97 $hostname = self::get_hostname_key(); 98 $lock_key = 'dfehc_calibrating_' . $hostname; 99 $lock = self::acquire_lock($lock_key, 30); 100 101 $baseline = self::run_loop($duration); 102 if ($lock) { 103 $exp = (int) apply_filters('dfehc_baseline_expiration', 7 * DAY_IN_SECONDS); 104 self::set_baseline_value($baseline_t, $baseline, $exp); 105 self::release_lock($lock, $lock_key); 106 } 107 return $baseline; 108 } 109 110 private static function update_spike_score(float $load_percent): void { 111 $score = (float) get_transient(self::LOAD_SPIKE_TRANSIENT); 112 $decay = (float) apply_filters('dfehc_spike_decay', 0.5); 113 $increment = (float) apply_filters('dfehc_spike_increment', 1.0); 114 $threshold = (float) apply_filters('dfehc_spike_threshold', 3.0); 115 $hostname = self::get_hostname_key(); 116 $baseline_name = self::get_baseline_transient_name($hostname); 52 117 53 118 if ($load_percent > 90) { 54 $s pike_score += $spike_increment;55 } else { 56 $s pike_score = max(0, $spike_score - $spike_decay);57 } 58 59 if ($s pike_score >= $spike_threshold) {60 self::delete_baseline_value($baseline_ transient);119 $score += $increment; 120 } else { 121 $score = max(0.0, $score - $decay); 122 } 123 124 if ($score >= $threshold) { 125 self::delete_baseline_value($baseline_name); 61 126 delete_transient(self::LOAD_SPIKE_TRANSIENT); 62 127 } else { 63 set_transient(self::LOAD_SPIKE_TRANSIENT, $spike_score, HOUR_IN_SECONDS); 64 } 65 66 set_transient(self::LOAD_CACHE_TRANSIENT, $load_percent, $cache_ttl); 67 return $load_percent; 68 } 69 70 public static function calibrate_baseline($duration = 0.025) { 71 $start = microtime(true); 72 $end = $start + $duration; 73 $count = 0; 74 $now = $start; 75 76 while ($now < $end) { 77 $count++; 78 $now = microtime(true); 79 } 80 81 $time_taken = microtime(true) - $start; 82 return $count / max($time_taken, 0.0001); 83 } 84 85 public static function maybe_calibrate_during_cron() { 86 if (defined('DOING_CRON') && DOING_CRON) { 87 $hostname = self::get_hostname_key(); 88 $baseline_transient = self::get_baseline_transient_name($hostname); 89 if (!self::get_baseline_value($baseline_transient)) { 90 $baseline = self::calibrate_baseline(); 91 $baseline_expiration = apply_filters('dfehc_baseline_expiration', 7 * DAY_IN_SECONDS); 92 self::set_baseline_value($baseline_transient, $baseline, $baseline_expiration); 93 } 94 } 95 } 96 97 public static function maybe_calibrate_if_idle() { 98 if (!is_admin() && !is_user_logged_in()) { 99 $hostname = self::get_hostname_key(); 100 $baseline_transient = self::get_baseline_transient_name($hostname); 101 if (!self::get_baseline_value($baseline_transient)) { 102 $baseline = self::calibrate_baseline(); 103 $baseline_expiration = apply_filters('dfehc_baseline_expiration', 7 * DAY_IN_SECONDS); 104 self::set_baseline_value($baseline_transient, $baseline, $baseline_expiration); 105 } 106 } 107 } 108 109 private static function get_baseline_transient_name($hostname) { 128 set_transient(self::LOAD_SPIKE_TRANSIENT, $score, HOUR_IN_SECONDS); 129 } 130 } 131 132 private static function ensure_baseline(): void { 133 $hostname = self::get_hostname_key(); 134 $baseline_t = self::get_baseline_transient_name($hostname); 135 if (self::get_baseline_value($baseline_t)) { 136 return; 137 } 138 $lock_key = 'dfehc_calibrating_' . $hostname; 139 $lock = self::acquire_lock($lock_key, 30); 140 if (!$lock) { 141 return; 142 } 143 $baseline = self::run_loop(0.025); 144 $exp = (int) apply_filters('dfehc_baseline_expiration', 7 * DAY_IN_SECONDS); 145 self::set_baseline_value($baseline_t, $baseline, $exp); 146 self::release_lock($lock, $lock_key); 147 } 148 149 private static function acquire_lock(string $key, int $ttl) { 150 if (class_exists('WP_Lock')) { 151 $lock = new \WP_Lock($key, $ttl); 152 return $lock->acquire() ? $lock : null; 153 } 154 return wp_cache_add($key, 1, $ttl) ? (object) ['key' => $key] : null; 155 } 156 157 private static function release_lock($lock, string $key): void { 158 if ($lock instanceof \WP_Lock) { 159 $lock->release(); 160 } else { 161 wp_cache_delete($key); 162 } 163 } 164 165 private static function get_baseline_transient_name(string $hostname): string { 110 166 return self::BASELINE_TRANSIENT_PREFIX . $hostname; 111 167 } 112 168 113 private static function get_hostname_key() {114 return s anitize_key(php_uname('n'));115 } 116 117 private static function get_baseline_value( $name) {169 private static function get_hostname_key(): string { 170 return substr(md5(php_uname('n')), 0, 10); 171 } 172 173 private static function get_baseline_value(string $name) { 118 174 return is_multisite() ? get_site_transient($name) : get_transient($name); 119 175 } 120 176 121 private static function set_baseline_value( $name, $value, $expiration){177 private static function set_baseline_value(string $name, $value, int $exp): void { 122 178 if (is_multisite()) { 123 set_site_transient($name, $value, $expiration); 124 } else { 125 set_transient($name, $value, $expiration); 126 } 127 } 128 129 private static function delete_baseline_value($name) { 130 return is_multisite() ? delete_site_transient($name) : delete_transient($name); 179 set_site_transient($name, $value, $exp); 180 } else { 181 set_transient($name, $value, $exp); 182 } 183 } 184 185 private static function delete_baseline_value(string $name): void { 186 if (is_multisite()) { 187 delete_site_transient($name); 188 } else { 189 delete_transient($name); 190 } 131 191 } 132 192 } 193 133 194 add_action('init', [Dfehc_ServerLoadEstimator::class, 'maybe_calibrate_during_cron']); 134 195 add_action('template_redirect', [Dfehc_ServerLoadEstimator::class, 'maybe_calibrate_if_idle']); 135 196 136 add_filter('heartbeat_settings', function($settings) { 137 if (!class_exists(Dfehc_ServerLoadEstimator::class)) return $settings; 197 add_filter('heartbeat_settings', function ($settings) { 198 if (!class_exists(Dfehc_ServerLoadEstimator::class)) { 199 return $settings; 200 } 138 201 139 202 $load = Dfehc_ServerLoadEstimator::get_server_load(); 140 141 if ($load === false) return $settings; 142 143 $thresholds = apply_filters('dfehc_heartbeat_thresholds', [ 144 'low' => 20, 203 if ($load === false) { 204 return $settings; 205 } 206 207 $ths = apply_filters('dfehc_heartbeat_thresholds', [ 208 'low' => 20, 145 209 'medium' => 50, 146 'high' => 75,210 'high' => 75, 147 211 ]); 148 212 149 213 if (!is_admin() && !current_user_can('edit_posts')) { 150 if ($load < $thresholds['low']) { 151 $settings['interval'] = 50; 152 } elseif ($load < $thresholds['medium']) { 153 $settings['interval'] = 60; 154 } elseif ($load < $thresholds['high']) { 155 $settings['interval'] = 120; 156 } else { 157 $settings['interval'] = 180; 158 } 214 $settings['interval'] = $load < $ths['low'] ? 50 : ($load < $ths['medium'] ? 60 : ($load < $ths['high'] ? 120 : 180)); 159 215 } elseif (current_user_can('editor')) { 160 $settings['interval'] = ($load < $thresholds['high'])? 30 : 60;216 $settings['interval'] = $load < $ths['high'] ? 30 : 60; 161 217 } elseif (current_user_can('administrator')) { 162 $settings['interval'] = ($load < $thresholds['high'])? 20 : 40;218 $settings['interval'] = $load < $ths['high'] ? 20 : 40; 163 219 } 164 220 -
dynamic-front-end-heartbeat-control/trunk/engine/interval-helper.php
r3310561 r3320647 1 1 <?php 2 function dfehc_weighted_sum(array $factors, array $weights) 2 declare(strict_types=1); 3 4 define('DFEHC_OPTIONS_PREFIX', 'dfehc_'); 5 define('DFEHC_OPTION_MIN_INTERVAL', DFEHC_OPTIONS_PREFIX . 'min_interval'); 6 define('DFEHC_OPTION_MAX_INTERVAL', DFEHC_OPTIONS_PREFIX . 'max_interval'); 7 define('DFEHC_OPTION_PRIORITY_SLIDER', DFEHC_OPTIONS_PREFIX . 'priority_slider'); 8 define('DFEHC_OPTION_EMA_ALPHA', DFEHC_OPTIONS_PREFIX . 'ema_alpha'); 9 define('DFEHC_OPTION_MAX_SERVER_LOAD', DFEHC_OPTIONS_PREFIX . 'max_server_load'); 10 define('DFEHC_OPTION_MAX_RESPONSE_TIME', DFEHC_OPTIONS_PREFIX . 'max_response_time'); 11 define('DFEHC_OPTION_SMA_WINDOW', DFEHC_OPTIONS_PREFIX . 'sma_window'); 12 define('DFEHC_OPTION_MAX_DECREASE_RATE', DFEHC_OPTIONS_PREFIX . 'max_decrease_rate'); 13 14 define('DFEHC_DEFAULT_MIN_INTERVAL', 15); 15 define('DFEHC_DEFAULT_MAX_INTERVAL', 300); 16 define('DFEHC_DEFAULT_MAX_SERVER_LOAD', 85); 17 define('DFEHC_DEFAULT_MAX_RESPONSE_TIME', 5.0); 18 define('DFEHC_DEFAULT_EMA_ALPHA', 0.4); 19 define('DFEHC_DEFAULT_SMA_WINDOW', 5); 20 define('DFEHC_DEFAULT_MAX_DECREASE_RATE', 0.25); 21 define('DFEHC_DEFAULT_EMA_TTL', 600); 22 23 function dfehc_store_lockfree(string $key, $value, int $ttl): bool 3 24 { 4 $sum = 0; 25 if (function_exists('wp_cache_add') && wp_cache_add($key, $value, '', $ttl)) { 26 return true; 27 } 28 return set_transient($key, $value, $ttl); 29 } 30 31 function dfehc_set_transient(string $key, float $value, float $interval): void 32 { 33 $ttl = (int) apply_filters( 34 'dfehc_transient_ttl', 35 max(60, (int) ceil($interval) * 2), 36 $key, 37 $value, 38 $interval 39 ); 40 dfehc_store_lockfree($key, $value, $ttl); 41 } 42 43 function dfehc_weighted_sum(array $factors, array $weights): float 44 { 45 $sum = 0.0; 5 46 foreach ($factors as $k => $v) { 6 $w = $weights[$k] ?? 0; 7 $sum += $v * $w; 47 $sum += ($weights[$k] ?? 0.0) * $v; 8 48 } 9 49 return $sum; 10 50 } 11 51 12 function dfehc_ apply_exponential_moving_average(float $interval): float52 function dfehc_normalize_weights(array $weights): array 13 53 { 14 $alpha = 0.4; 15 $key = 'dfehc_previous_intervals'; 16 $previous = get_transient($key); 17 if ($previous === false) { 18 $previous = []; 54 $total = array_sum($weights); 55 if ($total <= 0) { 56 return array_fill_keys(array_keys($weights), 1 / max(1, count($weights))); 19 57 } 20 array_unshift($previous, $interval); 21 $previous = array_slice($previous, 0, 100); 22 $ema = $interval; 23 foreach ($previous as $idx => $val) { 24 if ($idx === 0) { 25 $ema = $val; 26 } else { 27 $ema = $alpha * $val + (1 - $alpha) * $ema; 28 } 58 foreach ($weights as $k => $w) { 59 $weights[$k] = $w / $total; 29 60 } 30 set_transient($key, $previous, 10 * MINUTE_IN_SECONDS); 61 return $weights; 62 } 63 64 function dfehc_apply_exponential_moving_average(float $current): float 65 { 66 $alpha = max( 67 0.01, 68 min(1.0, (float) get_option(DFEHC_OPTION_EMA_ALPHA, DFEHC_DEFAULT_EMA_ALPHA)) 69 ); 70 $key = 'dfehc_ema_' . get_current_blog_id(); 71 $prev = get_transient($key); 72 $ema = ($prev === false) ? $current : $alpha * $current + (1 - $alpha) * (float) $prev; 73 $ttl = (int) apply_filters('dfehc_ema_ttl', DFEHC_DEFAULT_EMA_TTL, $current, $ema); 74 dfehc_store_lockfree($key, $ema, $ttl); 31 75 return $ema; 32 76 } 33 77 34 function dfehc_calculate_recommended_interval(float $time_elapsed, float $load_average, float $server_response_time): float 35 { 36 $min_interval = (int) get_option('DFEHC_MIN_INTERVAL', DFEHC_MIN_INTERVAL); 37 $max_interval = (int) get_option('DFEHC_MAX_INTERVAL', DFEHC_MAX_INTERVAL); 78 function dfehc_calculate_recommended_interval( 79 float $time_elapsed, 80 float $load_average, 81 float $server_response_time 82 ): float { 83 $min_interval = max(1, (int) get_option(DFEHC_OPTION_MIN_INTERVAL, DFEHC_DEFAULT_MIN_INTERVAL)); 84 $max_interval = max($min_interval, (int) get_option(DFEHC_OPTION_MAX_INTERVAL, DFEHC_DEFAULT_MAX_INTERVAL)); 85 $max_server_load = max(0.1, (float) get_option(DFEHC_OPTION_MAX_SERVER_LOAD, DFEHC_DEFAULT_MAX_SERVER_LOAD)); 86 $max_response_time = max(0.1, (float) get_option(DFEHC_OPTION_MAX_RESPONSE_TIME, DFEHC_DEFAULT_MAX_RESPONSE_TIME)); 38 87 39 88 $factors = [ 40 'user_activity' => $time_elapsed / $max_interval, 41 'server_load' => 1 - ($load_average / DFEHC_MAX_SERVER_LOAD), 42 'response_time' => $server_response_time > 0 ? ($server_response_time / DFEHC_MAX_RESPONSE_TIME) : 0, 89 'user_activity' => max(0.0, min(1.0, $time_elapsed / $max_interval)), 90 'server_load' => max(0.0, min(1.0, $load_average / $max_server_load)), 91 'response_time' => $server_response_time > 0 92 ? max(0.0, min(1.0, $server_response_time / $max_response_time)) 93 : 0.0, 43 94 ]; 44 95 45 $slider = (float) get_option('dfehc_priority_slider', 0); 96 $slider = max(-1.0, min(1.0, (float) get_option(DFEHC_OPTION_PRIORITY_SLIDER, 0.0))); 97 $weights = [ 98 'user_activity' => 0.4 - 0.2 * $slider, 99 'server_load' => (0.6 + 0.2 * $slider) / 2, 100 'response_time' => (0.6 + 0.2 * $slider) / 2, 101 ]; 102 $weights = dfehc_normalize_weights($weights); 46 103 47 if ($slider < 0) { 48 $weights = [ 49 'user_activity' => 0.4 + (0.1 * $slider), 50 'server_load' => 0.3 - (0.1 * $slider / 2), 51 'response_time' => 0.3 - (0.1 * $slider / 2), 52 ]; 53 } else { 54 $weights = [ 55 'user_activity' => 0.4 - (0.1 * $slider), 56 'server_load' => 0.3 + (0.1 * $slider / 2), 57 'response_time' => 0.3 + (0.1 * $slider / 2), 58 ]; 104 $raw = $min_interval + dfehc_weighted_sum($factors, $weights) * ($max_interval - $min_interval); 105 $smoothed = dfehc_apply_exponential_moving_average($raw); 106 $lagged = dfehc_defensive_stance($smoothed); 107 108 return max($min_interval, min($max_interval, $lagged)); 109 } 110 111 function dfehc_calculate_interval_based_on_duration( 112 float $avg_duration, 113 float $load_average 114 ): float { 115 $min_interval = max(1, (int) get_option(DFEHC_OPTION_MIN_INTERVAL, DFEHC_DEFAULT_MIN_INTERVAL)); 116 $max_interval = max($min_interval, (int) get_option(DFEHC_OPTION_MAX_INTERVAL, DFEHC_DEFAULT_MAX_INTERVAL)); 117 if ($avg_duration <= $min_interval) { 118 return (float) $min_interval; 119 } 120 if ($avg_duration >= $max_interval) { 121 return (float) $max_interval; 122 } 123 return dfehc_calculate_recommended_interval($avg_duration, $load_average, 0.0); 124 } 125 126 function dfehc_smooth_moving(array $values): float 127 { 128 if (!$values) { 129 return 0.0; 130 } 131 $window = max(1, (int) get_option(DFEHC_OPTION_SMA_WINDOW, DFEHC_DEFAULT_SMA_WINDOW)); 132 $subset = array_slice($values, -$window); 133 if (!$subset) { 134 return 0.0; 135 } 136 return array_sum($subset) / count($subset); 137 } 138 139 function dfehc_defensive_stance(float $proposed): float 140 { 141 $key = 'dfehc_prev_int_' . get_current_blog_id(); 142 $previous = get_transient($key); 143 if ($previous === false) { 144 dfehc_store_lockfree($key, $proposed, (int) ceil($proposed)); 145 return $proposed; 59 146 } 60 147 61 $interval = $min_interval + dfehc_weighted_sum($factors, $weights) * ($max_interval - $min_interval); 148 $previous = (float) $previous; 149 $max_drop = max( 150 0.0, 151 min(1.0, (float) get_option(DFEHC_OPTION_MAX_DECREASE_RATE, DFEHC_DEFAULT_MAX_DECREASE_RATE)) 152 ); 153 $max_rise = (float) apply_filters('dfehc_max_increase_rate', 0.5); 62 154 63 return dfehc_apply_exponential_moving_average($interval); 155 $lower = $previous * (1 - $max_drop); 156 $upper = $previous * (1 + $max_rise); 157 $final = max($lower, min($upper, $proposed)); 158 159 dfehc_store_lockfree($key, $final, (int) ceil($final)); 160 return $final; 64 161 } 65 66 function dfehc_calculate_interval_based_on_duration(float $avg_duration, float $load_average): float67 {68 $min_interval = (int) get_option('DFEHC_MIN_INTERVAL', DFEHC_MIN_INTERVAL);69 $max_interval = (int) get_option('DFEHC_MAX_INTERVAL', DFEHC_MAX_INTERVAL);70 71 if ($avg_duration <= $min_interval) {72 return $min_interval;73 }74 if ($avg_duration >= $max_interval) {75 return $max_interval;76 }77 return dfehc_calculate_recommended_interval($avg_duration, $load_average, 0);78 }79 80 function dfehc_smooth_moving(array $vals): float81 {82 $window = 5;83 if (!$vals) {84 return 0;85 }86 $subset = array_slice($vals, -$window);87 return array_sum($subset) / count($subset);88 } -
dynamic-front-end-heartbeat-control/trunk/engine/server-load.php
r3310561 r3320647 2 2 declare(strict_types=1); 3 3 4 if (!defined('DFEHC_SERVER_LOAD_TTL')) { 5 define('DFEHC_SERVER_LOAD_TTL', (int) apply_filters('dfehc_server_load_ttl', 3 * MINUTE_IN_SECONDS)); 4 if (!function_exists('str_starts_with')) { 5 function str_starts_with(string $haystack, string $needle): bool 6 { 7 return $needle === '' || strpos($haystack, $needle) === 0; 8 } 9 } 10 11 define('DFEHC_SERVER_LOAD_TTL', (int) apply_filters('dfehc_server_load_ttl', 180)); 12 define('DFEHC_UNKNOWN_LOAD', 0.404); 13 define('DFEHC_SERVER_LOAD_CACHE_KEY', 'dfehc:server_load'); 14 define('DFEHC_SERVER_LOAD_PAYLOAD_KEY', 'dfehc_server_load_payload'); 15 define('DFEHC_LOAD_LOCK', 'dfehc_load_lock'); 16 17 function _dfehc_get_cache_client(): array 18 { 19 static $cached = null; 20 static $ts = 0; 21 $retryAfter = (int) apply_filters('dfehc_cache_retry_after', 60); 22 if ($cached !== null && ($cached['type'] !== 'none' || $ts > time() - $retryAfter)) { 23 return $cached; 24 } 25 $ts = time(); 26 global $wp_object_cache; 27 if (is_object($wp_object_cache) && isset($wp_object_cache->redis) && $wp_object_cache->redis instanceof Redis) { 28 return $cached = ['client' => $wp_object_cache->redis, 'type' => 'redis']; 29 } 30 if (is_object($wp_object_cache) && isset($wp_object_cache->mc) && $wp_object_cache->mc instanceof Memcached) { 31 return $cached = ['client' => $wp_object_cache->mc, 'type' => 'memcached']; 32 } 33 if (class_exists('Redis')) { 34 try { 35 $redis = new Redis(); 36 if ($redis->connect(dfehc_get_redis_server(), dfehc_get_redis_port(), 1.0)) { 37 $pass = apply_filters('dfehc_redis_auth', getenv('REDIS_PASSWORD')); 38 if ($pass) { 39 $redis->auth($pass); 40 } 41 return $cached = ['client' => $redis, 'type' => 'redis']; 42 } 43 } catch (Throwable $e) { 44 trigger_error('DFEHC Redis connect error: ' . $e->getMessage(), E_USER_WARNING); 45 } 46 } 47 if (class_exists('Memcached')) { 48 try { 49 $mc = new Memcached(); 50 $mc->addServer(dfehc_get_memcached_server(), dfehc_get_memcached_port()); 51 $user = getenv('MEMCACHED_USERNAME'); 52 $pass = getenv('MEMCACHED_PASSWORD'); 53 if ($user && $pass && method_exists($mc, 'setSaslAuthData')) { 54 $mc->setOption(Memcached::OPT_BINARY_PROTOCOL, true); 55 $mc->setSaslAuthData($user, $pass); 56 } 57 $versions = $mc->getVersion(); 58 $ok = $versions && is_array($versions) && reset($versions) && reset($versions) !== '0.0.0'; 59 if ($ok) { 60 return $cached = ['client' => $mc, 'type' => 'memcached']; 61 } 62 } catch (Throwable $e) { 63 trigger_error('DFEHC Memcached connect error: ' . $e->getMessage(), E_USER_WARNING); 64 } 65 } 66 return $cached = ['client' => null, 'type' => 'none']; 67 } 68 69 function dfehc_cache_server_load(float $value): void 70 { 71 ['client' => $client, 'type' => $type] = _dfehc_get_cache_client(); 72 if (!$client) { 73 return; 74 } 75 try { 76 if ($type === 'redis') { 77 $client->setex(DFEHC_SERVER_LOAD_CACHE_KEY, DFEHC_SERVER_LOAD_TTL, $value); 78 } elseif ($type === 'memcached') { 79 $client->set(DFEHC_SERVER_LOAD_CACHE_KEY, $value, DFEHC_SERVER_LOAD_TTL); 80 } 81 } catch (Throwable $e) { 82 trigger_error('DFEHC cache write error: ' . $e->getMessage(), E_USER_WARNING); 83 } 6 84 } 7 85 8 86 function dfehc_get_server_load(): float 9 87 { 10 $key = 'dfehc_server_load_raw'; 11 $payload = get_transient($key); 12 13 if (is_array($payload) && array_key_exists('load', $payload) && array_key_exists('source', $payload)) { 14 $load = (float) $payload['load']; 15 $source = (string) $payload['source']; 16 } else { 17 $data = dfehc_detect_load_raw_with_source(); 18 $raw = $data['load']; 19 $source = $data['source']; 20 $cores = dfehc_get_cpu_cores(); 21 $load = ($source === 'cpu_load' && $cores > 0) ? $raw / $cores : $raw; 22 set_transient($key, ['load' => $load, 'source' => $source], DFEHC_SERVER_LOAD_TTL); 23 } 24 88 $payload = get_transient(DFEHC_SERVER_LOAD_PAYLOAD_KEY); 89 if (!(is_array($payload) && isset($payload['raw'], $payload['cores'], $payload['source']))) { 90 $lock = dfehc_acquire_lock(); 91 if (!$lock) { 92 return DFEHC_UNKNOWN_LOAD; 93 } 94 $data = dfehc_detect_load_raw_with_source(); 95 $payload = [ 96 'raw' => (float) $data['load'], 97 'cores' => dfehc_get_cpu_cores(), 98 'source' => (string) $data['source'], 99 ]; 100 set_transient(DFEHC_SERVER_LOAD_PAYLOAD_KEY, $payload, DFEHC_SERVER_LOAD_TTL); 101 dfehc_release_lock($lock); 102 } 103 $raw = (float) $payload['raw']; 104 $cores = (int) ($payload['cores'] ?: dfehc_get_cpu_cores()); 105 $source = (string) $payload['source']; 106 $divide = (bool) apply_filters('dfehc_divide_cpu_load', true, $raw, $cores, $source); 107 $load = ($source === 'cpu_load' && $divide && $cores > 0) ? $raw / $cores : $raw; 25 108 return (float) apply_filters('dfehc_contextual_load_value', $load, $source); 26 109 } … … 34 117 } 35 118 } 36 37 119 if (is_readable('/proc/loadavg')) { 38 120 $txt = file_get_contents('/proc/loadavg'); … … 44 126 } 45 127 } 46 47 128 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); 48 if (function_exists('shell_exec') && !in_array('shell_exec', $disabled, true) ) {49 $out = shell_exec('LANG=C uptime ');129 if (function_exists('shell_exec') && !in_array('shell_exec', $disabled, true) && !ini_get('open_basedir')) { 130 $out = shell_exec('LANG=C uptime 2>&1'); 50 131 if ($out && preg_match('/load average: ([0-9.]+)/', $out, $m)) { 51 132 return ['load' => (float) $m[1], 'source' => 'cpu_load']; 52 133 } 53 134 } 54 55 135 if (defined('DFEHC_PLUGIN_PATH')) { 56 136 $est = DFEHC_PLUGIN_PATH . 'defibrillator/load-estimator.php'; … … 58 138 require_once $est; 59 139 if (class_exists('DynamicHeartbeat\\Dfehc_ServerLoadEstimator')) { 60 return ['load' => (float) DynamicHeartbeat\Dfehc_ServerLoadEstimator::get_server_load(), 'source' => 'fallback']; 61 } 62 } 63 } 64 65 return ['load' => 0.404, 'source' => 'fallback']; 140 return [ 141 'load' => (float) DynamicHeartbeat\Dfehc_ServerLoadEstimator::get_server_load(), 142 'source' => 'fallback', 143 ]; 144 } 145 } 146 } 147 return ['load' => DFEHC_UNKNOWN_LOAD, 'source' => 'fallback']; 66 148 } 67 149 … … 69 151 { 70 152 if (is_readable('/sys/fs/cgroup/cpu.max')) { 71 [$quota, $period] = array_map('intval', explode(' ', trim((string) file_get_contents('/sys/fs/cgroup/cpu.max')))); 72 if ($quota > 0 && $period > 0) { 73 return max(1, (int) ceil($quota / $period)); 74 } 75 } 76 77 $cg = '/proc/self/cgroup'; 78 if (is_readable($cg) && preg_match('/^[0-9]+:[^:]*cpu[^:]*:(.+)$/m', file_get_contents($cg) ?: '', $m)) { 79 $base = '/sys/fs/cgroup' . rtrim($m[1]); 80 if (is_readable("$base/cpu.cfs_quota_us") && is_readable("$base/cpu.cfs_period_us")) { 81 $quota = (int) file_get_contents("$base/cpu.cfs_quota_us"); 82 $period = (int) file_get_contents("$base/cpu.cfs_period_us"); 153 [$quota, $period] = explode(' ', trim(file_get_contents('/sys/fs/cgroup/cpu.max'))); 154 if ($quota !== 'max') { 155 $quota = (int) $quota; 156 $period = (int) $period; 83 157 if ($quota > 0 && $period > 0) { 84 158 return max(1, (int) ceil($quota / $period)); … … 86 160 } 87 161 } 88 162 if (is_readable('/proc/self/cgroup')) { 163 $content = file_get_contents('/proc/self/cgroup'); 164 if ($content !== false && preg_match('/^[0-9]+:[^:]*cpu[^:]*:(.+)$/m', $content, $m)) { 165 $path = '/' . ltrim(trim($m[1]), '/'); 166 $base = '/sys/fs/cgroup' . $path; 167 $quotaFile = "$base/cpu.cfs_quota_us"; 168 $periodFile = "$base/cpu.cfs_period_us"; 169 if (is_readable($quotaFile) && is_readable($periodFile)) { 170 $quota = (int) file_get_contents($quotaFile); 171 $period = (int) file_get_contents($periodFile); 172 if ($quota > 0 && $period > 0) { 173 return max(1, (int) ceil($quota / $period)); 174 } 175 } 176 } 177 } 89 178 if (is_readable('/sys/fs/cgroup/cpu/cpu.cfs_quota_us') && is_readable('/sys/fs/cgroup/cpu/cpu.cfs_period_us')) { 90 $quota = (int) file_get_contents('/sys/fs/cgroup/cpu/cpu.cfs_quota_us');179 $quota = (int) file_get_contents('/sys/fs/cgroup/cpu/cpu.cfs_quota_us'); 91 180 $period = (int) file_get_contents('/sys/fs/cgroup/cpu/cpu.cfs_period_us'); 92 181 if ($quota > 0 && $period > 0) { … … 94 183 } 95 184 } 96 97 185 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); 98 if (function_exists('shell_exec') && !in_array('shell_exec', $disabled, true) ) {186 if (function_exists('shell_exec') && !in_array('shell_exec', $disabled, true) && !ini_get('open_basedir')) { 99 187 $n = shell_exec('nproc 2>/dev/null'); 100 188 if ($n && ctype_digit(trim($n))) { … … 102 190 } 103 191 } 104 105 192 if (is_readable('/proc/cpuinfo')) { 106 $cnt = preg_match_all('/^processor/m', file_get_contents('/proc/cpuinfo') ?: ''); 107 if ($cnt) { 108 return $cnt; 109 } 110 } 111 193 $info = file_get_contents('/proc/cpuinfo'); 194 if ($info !== false) { 195 $cnt = preg_match_all('/^processor/m', $info); 196 if ($cnt) { 197 return $cnt; 198 } 199 } 200 } 112 201 return 1; 113 202 } … … 115 204 function dfehc_log_server_load(): void 116 205 { 117 $load = dfehc_get_server_load(); 118 $logs = get_option('dfehc_server_load_logs', []); 206 $load = dfehc_get_server_load(); 207 $optKey = 'dfehc_server_load_logs'; 208 $logs = get_site_option($optKey, []); 119 209 if (!is_array($logs)) { 120 210 $logs = []; 121 211 } 122 123 $now = time(); 212 $now = time(); 124 213 $cutoff = $now - DAY_IN_SECONDS; 125 $logs = array_filter($logs, static fn(array $row): bool => (isset($row['timestamp']) && $row['timestamp'] >= $cutoff)); 214 $logs = array_filter( 215 $logs, 216 static fn(array $row): bool => isset($row['timestamp']) && $row['timestamp'] >= $cutoff 217 ); 126 218 $logs[] = ['timestamp' => $now, 'load' => $load]; 127 update_ option('dfehc_server_load_logs', array_values($logs), false);219 update_site_option($optKey, array_values($logs), false); 128 220 } 129 221 add_action('dfehc_log_server_load_hook', 'dfehc_log_server_load'); … … 131 223 function dfehc_get_server_load_ajax_handler(): void 132 224 { 133 if (!isset($_REQUEST['nonce']) || !wp_verify_nonce((string) $_REQUEST['nonce'], 'dfehc-ajax-nonce')) { 225 $nonce = $_POST['nonce'] ?? $_GET['nonce'] ?? ''; 226 if (!wp_verify_nonce((string) $nonce, 'dfehc-ajax-nonce')) { 134 227 wp_send_json_error('Invalid nonce.'); 135 228 } 136 137 229 if (!is_user_logged_in() && !apply_filters('dfehc_allow_public_server_load', false)) { 138 230 wp_send_json_error('Not authorised.'); 139 231 } 140 141 wp_send_json_success(dfehc_get_server_load()); 232 wp_send_json_success(dfehc_get_server_load_persistent()); 142 233 } 143 234 add_action('wp_ajax_get_server_load', 'dfehc_get_server_load_ajax_handler'); … … 150 241 return $cached; 151 242 } 152 153 $redis_available = class_exists('Redis'); 154 $memcached_available = class_exists('Memcached'); 155 156 try { 157 if ($redis_available) { 158 $redis = new Redis(); 159 if ($redis->connect(dfehc_get_redis_server(), dfehc_get_redis_port())) { 160 $val = $redis->get('dfehc:server_load'); 161 $redis->close(); 162 return $cached = (float) $val; 163 } 164 } 165 166 if ($memcached_available) { 167 $mc = new Memcached(); 168 if ($mc->addServer(dfehc_get_memcached_server(), dfehc_get_memcached_port())) { 169 $val = $mc->get('dfehc:server_load'); 170 $mc->quit(); 171 return $cached = (float) $val; 172 } 173 } 174 } catch (Throwable $e) { 175 if (defined('WP_DEBUG') && WP_DEBUG) { 176 error_log('[dfehc] ' . $e->getMessage()); 177 } 178 } 179 180 return $cached = 0.404; 243 ['client' => $client, 'type' => $type] = _dfehc_get_cache_client(); 244 $val = false; 245 if ($client) { 246 try { 247 $val = $client->get(DFEHC_SERVER_LOAD_CACHE_KEY); 248 } catch (Throwable $e) { 249 trigger_error('DFEHC cache read error: ' . $e->getMessage(), E_USER_WARNING); 250 } 251 } 252 if ($val !== false) { 253 return $cached = (float) $val; 254 } 255 $fresh = dfehc_get_server_load(); 256 dfehc_cache_server_load($fresh); 257 return $cached = $fresh; 181 258 } 182 259 … … 186 263 $schedules['dfehc_minute'] = [ 187 264 'interval' => 60, 188 'display' => __('Every minute(DFEHC)', 'dfehc'),265 'display' => __('Server load (DFEHC)', 'dfehc'), 189 266 ]; 190 267 } … … 193 270 add_filter('cron_schedules', 'dfehc_register_minute_schedule'); 194 271 195 if (!wp_next_scheduled('dfehc_log_server_load_hook')) { 196 wp_schedule_event(time(), 'dfehc_minute', 'dfehc_log_server_load_hook'); 197 } 272 if (apply_filters('dfehc_enable_load_logging', true)) { 273 if (!wp_next_scheduled('dfehc_log_server_load_hook')) { 274 $start = time() - (time() % 60) + 60; 275 wp_schedule_event($start, 'dfehc_minute', 'dfehc_log_server_load_hook'); 276 } 277 } -
dynamic-front-end-heartbeat-control/trunk/engine/server-response.php
r3310561 r3320647 2 2 declare(strict_types=1); 3 3 4 defined('DFEHC_DEFAULT_RESPONSE_TIME') || define('DFEHC_DEFAULT_RESPONSE_TIME', 50.0); 5 defined('DFEHC_HEAD_NEG_TTL') || define('DFEHC_HEAD_NEG_TTL', 600); 6 defined('DFEHC_HEAD_POS_TTL') || define('DFEHC_HEAD_POS_TTL', WEEK_IN_SECONDS); 7 defined('DFEHC_SPIKE_OPT_EPS') || define('DFEHC_SPIKE_OPT_EPS', 0.1); 8 defined('DFEHC_BASELINE_EXP') || define('DFEHC_BASELINE_EXP', 7 * DAY_IN_SECONDS); 9 4 10 function dfehc_get_server_response_time(): array 5 11 { 6 $default_ms = defined('DFEHC_DEFAULT_RESPONSE_TIME') ? DFEHC_DEFAULT_RESPONSE_TIME : apply_filters('dfehc_default_response_time', 50.0); 12 $default_ms = (float) apply_filters('dfehc_default_response_time', DFEHC_DEFAULT_RESPONSE_TIME); 13 14 $defaults = [ 15 'main_response_ms' => null, 16 'db_response_ms' => null, 17 'method' => '', 18 'measurements' => [], 19 'recalibrated' => false, 20 'timestamp' => current_time('mysql'), 21 'baseline_used' => null, 22 'spike_score' => 0.0, 23 ]; 7 24 8 25 $cached = get_transient('dfehc_cached_response_data'); 9 26 if ($cached !== false && is_array($cached)) { 10 return $cached;27 return array_merge($defaults, $cached); 11 28 } 12 29 13 30 if (dfehc_is_high_traffic()) { 14 return[31 $high = [ 15 32 'main_response_ms' => $default_ms, 16 33 'db_response_ms' => null, … … 19 36 'recalibrated' => false, 20 37 'timestamp' => current_time('mysql'), 38 'baseline_used' => null, 39 'spike_score' => 0.0, 21 40 ]; 41 set_transient('dfehc_cached_response_data', $high, (int) apply_filters('dfehc_high_traffic_cache_expiration', 300)); 42 return $high; 22 43 } 23 44 24 45 if (!dfehc_acquire_lock()) { 25 return $cached ?: [];46 return array_merge($defaults, is_array($cached) ? $cached : []); 26 47 } 27 48 28 49 $results = dfehc_perform_response_measurements($default_ms); 29 50 $baseline = get_transient('dfehc_baseline_response_data'); 30 $spike = (float) get_ option('dfehc_spike_score', 0.0);51 $spike = (float) get_transient('dfehc_spike_score'); 31 52 $now = time(); 32 $max_age = apply_filters('dfehc_max_baseline_age', 7 * DAY_IN_SECONDS);53 $max_age = (int) apply_filters('dfehc_max_baseline_age', DFEHC_BASELINE_EXP); 33 54 34 55 if (is_array($baseline)) { … … 41 62 42 63 if ($baseline === false && $results['method'] === 'http_loopback' && $results['main_response_ms'] !== null) { 43 $exp = apply_filters('dfehc_baseline_expiration', 7 * DAY_IN_SECONDS);44 $results['timestamp'] = current_time('mysql');64 $exp = (int) apply_filters('dfehc_baseline_expiration', DFEHC_BASELINE_EXP); 65 $results['timestamp'] = current_time('mysql'); 45 66 set_transient('dfehc_baseline_response_data', $results, $exp); 46 67 $baseline = $results; … … 51 72 52 73 if (is_array($baseline) && $results['method'] === 'http_loopback' && isset($results['main_response_ms'], $baseline['main_response_ms'])) { 53 $base_ms = (float) $baseline['main_response_ms'];74 $base_ms = max(1.0, (float) $baseline['main_response_ms']); 54 75 $curr_ms = (float) $results['main_response_ms']; 55 $factor = apply_filters('dfehc_spike_threshold_factor', 2.0);56 $increment = apply_filters('dfehc_spike_increment', 1.0);57 $decay = apply_filters('dfehc_spike_decay', 0.25);58 $threshold = apply_filters('dfehc_recalibrate_threshold', 5.0);76 $factor = (float) apply_filters('dfehc_spike_threshold_factor', 2.0); 77 $increment = (float) apply_filters('dfehc_spike_increment', max(1.0, $curr_ms / $base_ms - $factor)); 78 $decay = (float) apply_filters('dfehc_spike_decay', 0.25); 79 $threshold = (float) apply_filters('dfehc_recalibrate_threshold', 5.0); 59 80 60 81 if ($curr_ms > $base_ms * $factor) { … … 65 86 66 87 if ($spike >= $threshold) { 67 set_transient('dfehc_baseline_response_data', $results, apply_filters('dfehc_baseline_expiration', 7 * DAY_IN_SECONDS));68 $spike = 0.0;69 $results['recalibrated'] = true;88 set_transient('dfehc_baseline_response_data', $results, (int) apply_filters('dfehc_baseline_expiration', DFEHC_BASELINE_EXP)); 89 $spike = 0.0; 90 $results['recalibrated'] = true; 70 91 } 71 92 } 72 93 73 94 $results['spike_score'] = $spike; 74 update_option('dfehc_spike_score', $spike); 75 76 set_transient('dfehc_cached_response_data', $results, apply_filters('dfehc_cache_expiration', 3 * MINUTE_IN_SECONDS)); 95 $prev_spike = (float) get_transient('dfehc_spike_score'); 96 97 if (abs($spike - $prev_spike) >= DFEHC_SPIKE_OPT_EPS) { 98 set_transient('dfehc_spike_score', $spike, DFEHC_BASELINE_EXP); 99 } 100 101 set_transient('dfehc_cached_response_data', $results, (int) apply_filters('dfehc_cache_expiration', 3 * MINUTE_IN_SECONDS)); 77 102 78 103 dfehc_release_lock(); 79 104 80 return $results;105 return array_merge($defaults, $results); 81 106 } 82 107 … … 94 119 global $wpdb; 95 120 $db_start = microtime(true); 96 if ($wpdb->get_var('SELECT 1') !== null) { 97 $r['db_response_ms'] = (microtime(true) - $db_start) * 1000; 98 } 99 100 $times = []; 101 $url = get_rest_url() ?: home_url('/wp-json/'); 121 $wpdb->get_var("SELECT option_value FROM {$wpdb->options} LIMIT 1"); 122 $r['db_response_ms'] = (microtime(true) - $db_start) * 1000; 123 124 $url = get_rest_url() ?: home_url('/wp-json/'); 102 125 if (!get_option('permalink_structure')) { 103 $url = home_url('/index.php?rest_route=/');104 } 105 106 $n = max(1, min((int) apply_filters('dfehc_num_requests', 3), 10));126 $url = add_query_arg('rest_route', '/', home_url('/index.php')); 127 } 128 129 $n = max(1, min((int) apply_filters('dfehc_num_requests', 2), 5)); 107 130 $sleep_us = (int) apply_filters('dfehc_request_pause_us', 50000); 108 131 $timeout = (int) apply_filters('dfehc_request_timeout', 10); 109 $get_fallback = apply_filters('dfehc_use_get_fallback', true); 110 111 $args = [ 112 'timeout' => $timeout, 113 'sslverify' => apply_filters('dfehc_ssl_verify', true), 114 'headers' => ['Cache-Control' => 'no-cache'], 115 ]; 132 $sslverify = (bool) apply_filters('dfehc_ssl_verify', true); 133 $get_fallback = (bool) apply_filters('dfehc_use_get_fallback', true); 134 $use_head = (bool) apply_filters('dfehc_use_head_method', true); 135 136 $head_key = 'dfehc_head_supported_' . md5($url); 137 $head_supported = get_transient($head_key); 138 if ($head_supported === false) { 139 $head_supported = null; 140 } 141 142 $times = []; 143 $hard_deadline = microtime(true) + (float) apply_filters('dfehc_total_timeout', $timeout + 2); 116 144 117 145 for ($i = 0; $i < $n; $i++) { 146 $remaining = $hard_deadline - microtime(true); 147 if ($remaining <= 0) { 148 break; 149 } 150 151 $probe_url = add_query_arg('_dfehc_ts', sprintf('%.6f', microtime(true)), $url); 152 153 $args = [ 154 'timeout' => (int) ceil($remaining), 155 'sslverify' => $sslverify, 156 'headers' => ['Cache-Control' => 'no-cache'], 157 ]; 158 118 159 $start = microtime(true); 119 $resp = wp_remote_head($url, $args); 120 121 if (is_wp_error($resp) && $get_fallback) { 122 $resp = wp_remote_get($url, $args); 160 $resp = null; 161 162 if ($use_head && $head_supported !== false) { 163 $resp = wp_remote_head($probe_url, $args); 164 if (is_wp_error($resp) || wp_remote_retrieve_response_code($resp) >= 400) { 165 if ($head_supported === null) { 166 set_transient($head_key, 0, (int) apply_filters('dfehc_head_negative_ttl', DFEHC_HEAD_NEG_TTL)); 167 } 168 $resp = null; 169 } else { 170 if ($head_supported === null) { 171 set_transient($head_key, 1, DFEHC_HEAD_POS_TTL); 172 } 173 } 174 } 175 176 if ($resp === null && $get_fallback) { 177 $resp = wp_remote_get($probe_url, $args); 123 178 } 124 179 … … 128 183 } 129 184 185 if (count($times) >= $n) { 186 break; 187 } 188 130 189 if ($i < $n - 1 && $sleep_us > 0) { 131 190 usleep($sleep_us); … … 135 194 if ($times) { 136 195 sort($times); 137 $cnt = count($times);138 $r['measurements'] = $times;196 $cnt = count($times); 197 $r['measurements'] = $times; 139 198 $r['main_response_ms'] = $cnt % 2 ? $times[intdiv($cnt, 2)] : ($times[$cnt / 2 - 1] + $times[$cnt / 2]) / 2; 140 199 } else { … … 154 213 function dfehc_is_high_traffic(): bool 155 214 { 215 $flag_key = 'dfehc_high_traffic_flag'; 216 $flag = get_transient($flag_key); 217 if ($flag !== false) { 218 return (bool) $flag; 219 } 220 156 221 $threshold = (int) apply_filters('dfehc_high_traffic_threshold', 100); 157 $count = function_exists('dfehc_get_website_visitors') ? (int) dfehc_get_website_visitors() : 0; 158 return $count >= $threshold; 222 $cnt_key = 'dfehc_cached_visitor_cnt'; 223 $count = get_transient($cnt_key); 224 if ($count === false) { 225 $count = function_exists('dfehc_get_website_visitors') && is_numeric(dfehc_get_website_visitors()) ? (int) dfehc_get_website_visitors() : 0; 226 set_transient($cnt_key, $count, 60); 227 } 228 229 $high = $count >= $threshold; 230 set_transient($flag_key, $high ? 1 : 0, 60); 231 232 return $high; 159 233 } 160 234 161 235 function dfehc_acquire_lock(): bool 162 236 { 163 $ttl = (int) apply_filters('dfehc_measurement_lock_ttl', 60); 164 165 if (wp_using_ext_object_cache()) { 166 return wp_cache_add('dfehc_measurement_lock', 1, '', $ttl); 167 } 168 169 global $wpdb; 170 $added = $wpdb->query($wpdb->prepare("INSERT IGNORE INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, %d, 'no')", 'dfehc_measurement_lock', time())); 171 if ($added) { 237 $key = 'dfehc_measure_lock_' . get_current_blog_id(); 238 239 if (class_exists('WP_Lock')) { 240 $lock = new WP_Lock($key, 60); 241 if ($lock->acquire()) { 242 $GLOBALS['dfehc_rt_lock'] = $lock; 243 return true; 244 } 245 return false; 246 } 247 248 if (function_exists('wp_cache_add') && wp_cache_add($key, 1, '', 60)) { 249 $GLOBALS['dfehc_rt_lock_cache_key'] = $key; 172 250 return true; 173 251 } 174 252 175 $value = (int) $wpdb->get_var($wpdb->prepare("SELECT option_value FROM {$wpdb->options} WHERE option_name = %s", 'dfehc_measurement_lock')); 176 if ($value < time() - $ttl) { 177 $wpdb->update($wpdb->options, ['option_value' => time()], ['option_name' => 'dfehc_measurement_lock']); 253 if (false !== get_transient($key)) { 254 return false; 255 } 256 257 if (set_transient($key, 1, 60)) { 258 $GLOBALS['dfehc_rt_lock_transient_key'] = $key; 178 259 return true; 179 260 } … … 184 265 function dfehc_release_lock(): void 185 266 { 186 if (wp_using_ext_object_cache()) { 187 wp_cache_delete('dfehc_measurement_lock'); 188 } else { 189 delete_option('dfehc_measurement_lock'); 190 } 191 } 267 if (isset($GLOBALS['dfehc_rt_lock']) && $GLOBALS['dfehc_rt_lock'] instanceof WP_Lock) { 268 $GLOBALS['dfehc_rt_lock']->release(); 269 unset($GLOBALS['dfehc_rt_lock']); 270 return; 271 } 272 273 if (isset($GLOBALS['dfehc_rt_lock_cache_key'])) { 274 wp_cache_delete($GLOBALS['dfehc_rt_lock_cache_key']); 275 unset($GLOBALS['dfehc_rt_lock_cache_key']); 276 return; 277 } 278 279 if (isset($GLOBALS['dfehc_rt_lock_transient_key'])) { 280 delete_transient($GLOBALS['dfehc_rt_lock_transient_key']); 281 unset($GLOBALS['dfehc_rt_lock_transient_key']); 282 } 283 } -
dynamic-front-end-heartbeat-control/trunk/engine/system-load-fallback.php
r3310561 r3320647 1 1 <?php 2 2 declare(strict_types=1); 3 4 defined('DFEHC_SERVER_LOAD_TTL') || define('DFEHC_SERVER_LOAD_TTL', 60); 5 defined('DFEHC_SENTINEL_NO_LOAD') || define('DFEHC_SENTINEL_NO_LOAD', 0.404); 6 defined('DFEHC_SYSTEM_LOAD_KEY') || define('DFEHC_SYSTEM_LOAD_KEY', 'dfehc_system_load_avg'); 7 8 if (!function_exists('dfehc_get_cpu_cores')) { 9 function dfehc_get_cpu_cores(): int 10 { 11 static $cached = null; 12 if ($cached !== null) { 13 return $cached; 14 } 15 16 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); 17 18 if (PHP_OS_FAMILY !== 'Windows' 19 && function_exists('shell_exec') 20 && !in_array('shell_exec', $disabled, true) 21 && !ini_get('open_basedir') 22 ) { 23 $val = trim((string) shell_exec('getconf _NPROCESSORS_ONLN 2>NUL')); 24 if (ctype_digit($val) && (int) $val > 0) { 25 return $cached = (int) $val; 26 } 27 } 28 29 if (PHP_OS_FAMILY === 'Windows' 30 && function_exists('shell_exec') 31 && !in_array('shell_exec', $disabled, true) 32 ) { 33 $out = shell_exec('wmic cpu get NumberOfLogicalProcessors /value 2>NUL'); 34 if ($out && preg_match('/NumberOfLogicalProcessors=(\d+)/i', $out, $m) && (int) $m[1] > 0) { 35 return $cached = (int) $m[1]; 36 } 37 } 38 39 if (is_readable('/proc/cpuinfo')) { 40 $cnt = preg_match_all('/^processor/m', (string) file_get_contents('/proc/cpuinfo')); 41 if ($cnt > 0) { 42 return $cached = $cnt; 43 } 44 } 45 46 return $cached = 1; 47 } 48 } 3 49 4 50 if (!function_exists('dfehc_get_system_load_average')) { 5 51 function dfehc_get_system_load_average(): float 6 52 { 7 $key = 'dfehc_system_load_avg'; 53 $ttl = (int) apply_filters('dfehc_system_load_ttl', DFEHC_SERVER_LOAD_TTL); 54 $key = DFEHC_SYSTEM_LOAD_KEY; 8 55 $cache = get_transient($key); 9 56 if ($cache !== false) { … … 11 58 } 12 59 60 $raw = null; 61 13 62 if (function_exists('dfehc_get_server_load')) { 14 $ load= dfehc_get_server_load();15 if ($ load !== 0.404) {16 dfehc_set_transient_noautoload($key, $ load, DFEHC_SERVER_LOAD_TTL);17 return $load;63 $val = dfehc_get_server_load(); 64 if ($val !== DFEHC_SENTINEL_NO_LOAD) { 65 dfehc_set_transient_noautoload($key, $val, $ttl); 66 return (float) $val; 18 67 } 19 68 } 20 69 21 $raw = 0.0;22 23 70 if (function_exists('sys_getloadavg')) { 24 71 $arr = sys_getloadavg(); 25 $raw = $arr[0] ?? 0.0; 26 } elseif (is_readable('/proc/loadavg')) { 27 $raw = (float) explode(' ', file_get_contents('/proc/loadavg'))[0]; 28 } else { 72 if ($arr && isset($arr[0])) { 73 $raw = (float) $arr[0]; 74 } 75 } 76 77 if ($raw === null && PHP_OS_FAMILY !== 'Windows' && is_readable('/proc/loadavg')) { 78 $parts = explode(' ', (string) file_get_contents('/proc/loadavg')); 79 if (isset($parts[0])) { 80 $raw = (float) $parts[0]; 81 } 82 } 83 84 if ($raw === null) { 29 85 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); 30 86 if (function_exists('shell_exec') && !in_array('shell_exec', $disabled, true)) { 31 $out = shell_exec('LANG=C uptime'); 32 if ($out && preg_match('/load average: ([0-9.]+)/', $out, $m)) { 33 $raw = (float) $m[1]; 87 if (PHP_OS_FAMILY === 'Windows') { 88 $out = shell_exec('wmic cpu get loadpercentage /value 2>NUL'); 89 if ($out && preg_match('/loadpercentage=(\d+)/i', $out, $m)) { 90 $raw = ((float) $m[1]) / 100.0 * dfehc_get_cpu_cores(); 91 } 92 } else { 93 $out = shell_exec('LANG=C uptime 2>/dev/null'); 94 if ($out && preg_match('/load average[s]?:\s*([0-9.]+)/', $out, $m)) { 95 $raw = (float) $m[1]; 96 } 34 97 } 35 98 } 36 99 } 37 100 38 if ($raw === 0.0&& class_exists('\\DynamicHeartbeat\\Dfehc_ServerLoadEstimator')) {101 if ($raw === null && class_exists('\\DynamicHeartbeat\\Dfehc_ServerLoadEstimator')) { 39 102 $raw = (float) \DynamicHeartbeat\Dfehc_ServerLoadEstimator::get_server_load(); 40 103 } 41 if ($raw === 0.0) { 42 $raw = 0.404; 104 105 if ($raw === null) { 106 $sentinel_ttl = (int) apply_filters('dfehc_sentinel_ttl', 5); 107 dfehc_set_transient_noautoload($key, DFEHC_SENTINEL_NO_LOAD, $sentinel_ttl); 108 return DFEHC_SENTINEL_NO_LOAD; 43 109 } 44 110 45 if ($raw <= 100) { 111 $divide = (bool) apply_filters('dfehc_divide_load_by_cores', true, $raw); 112 if ($divide) { 46 113 $cores = dfehc_get_cpu_cores(); 47 $raw = $cores > 0 ? $raw / $cores : $raw; 114 if ($cores > 0) { 115 $raw = $raw / $cores; 116 } 48 117 } 49 118 50 dfehc_set_transient_noautoload($key, $raw, DFEHC_SERVER_LOAD_TTL);119 dfehc_set_transient_noautoload($key, $raw, $ttl); 51 120 return (float) $raw; 52 121 } -
dynamic-front-end-heartbeat-control/trunk/heartbeat-async.php
r3310561 r3320647 5 5 define('DFEHC_MIN_INTERVAL', 15); 6 6 define('DFEHC_MAX_INTERVAL', 300); 7 define('DFEHC_FALLBACK_INTERVAL', 60); 7 8 define('DFEHC_LOAD_AVERAGES', 'dfehc_load_averages'); 8 9 define('DFEHC_SERVER_LOAD', 'dfehc_server_load'); 9 10 define('DFEHC_RECOMMENDED_INTERVAL', 'dfehc_recommended_interval'); 10 11 define('DFEHC_CAPABILITY', 'read'); 12 define('DFEHC_LOAD_LOCK_BASE', 'dfehc_compute_load_lock'); 13 14 function dfehc_store_lockfree(string $key, $value, int $ttl): bool 15 { 16 if (function_exists('wp_cache_add') && wp_cache_add($key, $value, '', $ttl)) { 17 return true; 18 } 19 return set_transient($key, $value, $ttl); 20 } 11 21 12 22 function dfehc_register_ajax(string $action, callable $callback): void 13 23 { 14 24 add_action("wp_ajax_$action", $callback); 15 add_action("wp_ajax_nopriv_$action", $callback); 25 if (apply_filters('dfehc_allow_public_server_load', false)) { 26 add_action("wp_ajax_nopriv_$action", $callback); 27 } 16 28 } 17 29 … … 22 34 return; 23 35 } 24 set_transient($key, $value, $expiration);36 dfehc_store_lockfree($key, $value, $expiration); 25 37 global $wpdb; 26 $wpdb->update( 27 $wpdb->options, 28 ['autoload' => 'no'], 29 ['option_name' => "_transient_$key"] 38 $wpdb->query( 39 $wpdb->prepare( 40 "UPDATE {$wpdb->options} SET autoload='no' WHERE option_name=%s AND autoload='yes' LIMIT 1", 41 "_transient_$key" 42 ) 30 43 ); 31 44 } 32 45 33 function dfehc_get_cpu_cores(): int 34 { 35 if (function_exists('nproc')) { 36 $cores = trim((string) shell_exec('nproc 2>/dev/null')); 37 return is_numeric($cores) ? (int) $cores : 1; 38 } 39 if (is_readable('/proc/cpuinfo')) { 40 return (int) preg_match_all('/^processor/m', (string) file_get_contents('/proc/cpuinfo')); 41 } 42 return 1; 46 if (!function_exists('dfehc_get_cpu_cores')) { 47 function dfehc_get_cpu_cores(): int 48 { 49 static $cached = null; 50 if ($cached !== null) { 51 return $cached; 52 } 53 if (is_readable('/proc/cpuinfo')) { 54 $cnt = preg_match_all('/^processor/m', (string) file_get_contents('/proc/cpuinfo')); 55 if ($cnt > 0) { 56 return $cached = $cnt; 57 } 58 } 59 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); 60 if (function_exists('shell_exec') && !in_array('shell_exec', $disabled, true)) { 61 $out = (string) (shell_exec('nproc 2>/dev/null') ?? ''); 62 if (ctype_digit(trim($out)) && (int) $out > 0) { 63 return $cached = (int) trim($out); 64 } 65 } 66 return $cached = 1; 67 } 68 } 69 70 function dfehc_acquire_lock(string $base, int $ttl) 71 { 72 $key = $base . '_' . get_current_blog_id(); 73 if (class_exists('WP_Lock')) { 74 $lock = new WP_Lock($key, $ttl); 75 if ($lock->acquire()) { 76 return $lock; 77 } 78 return null; 79 } 80 if (function_exists('wp_cache_add') && wp_cache_add($key, 1, '', $ttl)) { 81 return (object) ['cache_key' => $key]; 82 } 83 if (get_transient($key) !== false) { 84 return null; 85 } 86 if (set_transient($key, 1, $ttl)) { 87 return (object) ['transient_key' => $key]; 88 } 89 return null; 90 } 91 92 function dfehc_release_lock($lock): void 93 { 94 if ($lock instanceof WP_Lock) { 95 $lock->release(); 96 return; 97 } 98 if (is_object($lock) && isset($lock->cache_key)) { 99 wp_cache_delete($lock->cache_key); 100 return; 101 } 102 if (is_object($lock) && isset($lock->transient_key)) { 103 delete_transient($lock->transient_key); 104 } 105 } 106 107 function dfehc_get_or_calculate_server_load(): float|false 108 { 109 $load = get_transient(DFEHC_SERVER_LOAD); 110 if ($load !== false) { 111 return (float) $load; 112 } 113 $ttl = (int) apply_filters('dfehc_server_load_ttl', 180); 114 $lock = dfehc_acquire_lock(DFEHC_LOAD_LOCK_BASE, $ttl + 5); 115 if (!$lock) { 116 return false; 117 } 118 $raw = dfehc_calculate_server_load(); 119 dfehc_release_lock($lock); 120 if ($raw === false) { 121 return false; 122 } 123 $cores = max(1, dfehc_get_cpu_cores()); 124 $load_pct = min(100.0, round($raw / $cores * 100, 2)); 125 dfehc_set_transient_noautoload(DFEHC_SERVER_LOAD, $load_pct, $ttl); 126 return $load_pct; 43 127 } 44 128 45 129 function dfehc_get_server_load_ajax_handler(): void 46 130 { 47 $nonce = sanitize_text_field( 48 filter_input(INPUT_POST, 'nonce', FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH) ?? '' 49 ); 50 51 if (!wp_verify_nonce($nonce, 'dfehc-ajax-nonce')) { 52 wp_send_json_error('Heartbeat: Invalid nonce provided.'); 53 } 54 131 $nonce = $_REQUEST['nonce'] ?? ''; 132 if (!wp_verify_nonce((string) $nonce, 'dfehc-ajax-nonce')) { 133 wp_send_json_error('Heartbeat: Invalid nonce.'); 134 } 135 $cap = apply_filters('dfehc_required_capability', DFEHC_CAPABILITY); 55 136 $allow_public = apply_filters('dfehc_allow_public_server_load', false); 56 if (!current_user_can( DFEHC_CAPABILITY) && !$allow_public) {137 if (!current_user_can($cap) && !$allow_public) { 57 138 wp_send_json_error('Heartbeat: Not authorised.'); 58 139 } 59 60 $load = get_transient(DFEHC_SERVER_LOAD); 61 140 $load = dfehc_get_or_calculate_server_load(); 62 141 if ($load === false) { 63 $load_raw = dfehc_calculate_server_load(); 64 if ($load_raw === false) { 65 wp_send_json_error('Heartbeat: Failed to determine server load.'); 66 } 67 $cores = max(1, dfehc_get_cpu_cores()); 68 $load = round($load_raw / $cores * 100, 2); 69 dfehc_set_transient_noautoload(DFEHC_SERVER_LOAD, $load, 180); 70 } 71 142 wp_send_json_success(DFEHC_FALLBACK_INTERVAL); 143 } 72 144 $interval = dfehc_calculate_recommended_interval_user_activity($load); 73 74 if ($interval > 0) { 75 wp_send_json_success($interval); 76 } 77 78 wp_send_json_error('Heartbeat: Interval calculation failed.'); 145 if ($interval <= 0) { 146 $interval = DFEHC_FALLBACK_INTERVAL; 147 } 148 wp_send_json_success($interval); 79 149 } 80 150 dfehc_register_ajax('get_server_load', 'dfehc_get_server_load_ajax_handler'); 81 151 82 function dfehc_calculate_server_load() 152 function dfehc_calculate_server_load(): float|false 83 153 { 84 154 if (function_exists('sys_getloadavg')) { 85 155 $load = sys_getloadavg(); 86 return $load[0]; 156 if (isset($load[0])) { 157 return (float) $load[0]; 158 } 87 159 } 88 160 if (is_readable('/proc/loadavg')) { 89 $data = explode(' ', (string) file_get_contents('/proc/loadavg')); 90 return (float) $data[0]; 91 } 92 if (function_exists('shell_exec')) { 93 $output = trim((string) shell_exec('uptime 2>/dev/null')); 94 if ($output && preg_match('/load average[s]?:\s*([0-9.]+)/', $output, $m)) { 161 $parts = explode(' ', (string) file_get_contents('/proc/loadavg')); 162 if (isset($parts[0])) { 163 return (float) $parts[0]; 164 } 165 } 166 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); 167 if (function_exists('shell_exec') && !in_array('shell_exec', $disabled, true)) { 168 $out = (string) (shell_exec('LANG=C uptime 2>/dev/null') ?? ''); 169 if ($out && preg_match('/load average[s]?:\s*([0-9.]+)/', $out, $m)) { 95 170 return (float) $m[1]; 96 171 } … … 108 183 { 109 184 protected string $action = 'dfehc_async_heartbeat'; 110 185 protected bool $scheduled = false; 111 186 public function __construct() 112 187 { 188 add_action('init', [$this, 'maybe_schedule']); 113 189 dfehc_register_ajax($this->action, [$this, 'handle_async_request']); 190 } 191 public function maybe_schedule(): void 192 { 193 if ($this->scheduled) { 194 return; 195 } 196 $this->scheduled = true; 197 $interval = 300; 198 $aligned = time() - (time() % $interval) + $interval; 114 199 if (!wp_next_scheduled($this->action)) { 115 wp_schedule_event(time(), 'dfehc_5_minutes', $this->action); 116 } 117 register_deactivation_hook(__FILE__, [$this, 'deactivate']); 118 } 119 120 public function deactivate(): void 121 { 122 wp_clear_scheduled_hook($this->action); 123 } 124 200 wp_schedule_event($aligned, 'dfehc_5_minutes', $this->action); 201 } else { 202 $next = wp_next_scheduled($this->action); 203 if ($next === false || $next - time() > $interval * 2) { 204 wp_schedule_single_event(time() + $interval, $this->action); 205 } 206 } 207 } 125 208 public function handle_async_request(): void 126 209 { 127 $max_retry = (int) apply_filters('dfehc_async_retry', 3); 128 $retry = 0; 129 while ($retry < $max_retry) { 130 try { 131 $this->run_action(); 132 break; 133 } catch (\Throwable $e) { 134 $retry++; 135 if ($retry >= $max_retry) { 136 error_log('[DFEHC Async] ' . $e->getMessage()); 137 } 138 usleep(1000000); 210 try { 211 $this->run_action(); 212 } catch (\Throwable $e) { 213 if (defined('WP_DEBUG') && WP_DEBUG) { 214 error_log('[DFEHC Async] ' . $e->getMessage()); 139 215 } 140 216 } 141 217 wp_die(); 142 218 } 143 144 219 protected function run_action(): void 145 220 { 146 221 $last_activity = get_transient('dfehc_last_user_activity'); 147 222 if ($last_activity === false) { 148 throw new \Exception('Missing last activity time.'); 149 } 150 151 $now = time(); 152 $elapsed = $now - (int) $last_activity; 223 return; 224 } 225 $elapsed = time() - (int) $last_activity; 153 226 $load_raw = dfehc_calculate_server_load(); 154 227 if ($load_raw === false) { 155 throw new \Exception('Failed to get server load.');228 return; 156 229 } 157 230 $cores = max(1, dfehc_get_cpu_cores()); 158 $load_pct = round($load_raw / $cores * 100, 2); 159 160 dfehc_set_transient_noautoload(DFEHC_SERVER_LOAD, $load_pct, 300); 161 231 $load_pct = min(100.0, round($load_raw / $cores * 100, 2)); 232 dfehc_set_transient_noautoload(DFEHC_SERVER_LOAD, $load_pct, (int) apply_filters('dfehc_server_load_ttl', 300)); 162 233 $samples = get_transient(DFEHC_LOAD_AVERAGES) ?: []; 163 234 $samples[] = $load_pct; … … 166 237 } 167 238 dfehc_set_transient_noautoload(DFEHC_LOAD_AVERAGES, $samples, 1800); 168 169 $weights = apply_filters('dfehc_load_weights', [5, 4, 3, 2, 1]); 170 $weights = array_slice($weights + array_fill(0, count($samples), 1), 0, count($samples)); 239 $weights_raw = apply_filters('dfehc_load_weights', [5, 4, 3, 2, 1]); 240 $weights = array_slice(array_pad(array_values((array) $weights_raw), count($samples), 1), 0, count($samples)); 171 241 $avg_load = dfehc_weighted_average($samples, $weights); 172 242 $interval = $this->calculate_interval($elapsed, $avg_load); 173 174 243 dfehc_set_transient_noautoload(DFEHC_RECOMMENDED_INTERVAL, $interval, 300); 175 244 } 176 177 245 protected function calculate_interval(int $elapsed, float $load_pct): int 178 246 { … … 183 251 return DFEHC_MAX_INTERVAL; 184 252 } 185 186 253 $load_factor = min(1.0, $load_pct / DFEHC_MAX_SERVER_LOAD); 187 $activity_factor = min(1.0, max(DFEHC_MIN_INTERVAL, $elapsed)- DFEHC_MIN_INTERVAL) / (DFEHC_MAX_INTERVAL - DFEHC_MIN_INTERVAL);188 $ dominant_factor = max($load_factor, $activity_factor);189 190 return (int) round(DFEHC_MIN_INTERVAL + $dominant _factor* (DFEHC_MAX_INTERVAL - DFEHC_MIN_INTERVAL));254 $activity_factor = ($elapsed - DFEHC_MIN_INTERVAL) / (DFEHC_MAX_INTERVAL - DFEHC_MIN_INTERVAL); 255 $activity_factor = max(0.0, min(1.0, $activity_factor)); 256 $dominant = max($load_factor, $activity_factor); 257 return (int) round(DFEHC_MIN_INTERVAL + $dominant * (DFEHC_MAX_INTERVAL - DFEHC_MIN_INTERVAL)); 191 258 } 192 259 } … … 194 261 function dfehc_weighted_average(array $values, array $weights): float 195 262 { 196 $t otal_value= 0.0;197 $t otal_weight= 0.0;198 foreach ($values as $i ndex => $value) {199 $w eight = $weights[$index] ?? 1;200 $t otal_value += $value * $weight;201 $t otal_weight += $weight;202 } 203 return $t otal_weight > 0 ? round($total_value / $total_weight, 2) : 0.0;263 $tv = 0.0; 264 $tw = 0.0; 265 foreach ($values as $i => $v) { 266 $w = $weights[$i] ?? 1; 267 $tv += $v * $w; 268 $tw += $w; 269 } 270 return $tw > 0 ? round($tv / $tw, 2) : 0.0; 204 271 } 205 272 … … 213 280 } 214 281 215 function dfehc_custom_cron_interval_addition(array $schedules): array 216 { 217 if (!isset($schedules['dfehc_5_minutes'])) { 218 $schedules['dfehc_5_minutes'] = ['interval' => 300, 'display' => __('Every 5 Minutes', 'dfehc')]; 219 } 220 return $schedules; 221 } 222 add_filter('cron_schedules', 'dfehc_custom_cron_interval_addition'); 282 function dfehc_register_schedules(array $s): array 283 { 284 if (!isset($s['dfehc_5_minutes'])) { 285 $s['dfehc_5_minutes'] = ['interval' => 300, 'display' => __('Every 5 Minutes', 'dfehc')]; 286 } 287 if (!isset($s['dfehc_daily'])) { 288 $s['dfehc_daily'] = ['interval' => DAY_IN_SECONDS, 'display' => __('Once a day (DFEHC)', 'dfehc')]; 289 } 290 return $s; 291 } 292 add_filter('cron_schedules', 'dfehc_register_schedules'); 293 294 function dfehc_prune_server_load_logs(): void 295 { 296 $max_age = (int) apply_filters('dfehc_log_retention_seconds', DAY_IN_SECONDS); 297 $max_cnt = (int) apply_filters('dfehc_log_retention_max', 1440); 298 $now = time(); 299 $all_ids = function_exists('get_sites') 300 ? array_map(static fn($s) => (int) $s->blog_id, get_sites(['fields' => 'ids'])) 301 : [get_current_blog_id()]; 302 $chunk_size = (int) apply_filters('dfehc_prune_chunk_size', 50); 303 $offset_key = 'dfehc_prune_offset'; 304 $offset = (int) get_site_option($offset_key, 0); 305 $chunk = array_slice($all_ids, $offset, $chunk_size); 306 if ($chunk === []) { 307 $offset = 0; 308 $chunk = array_slice($all_ids, 0, $chunk_size); 309 } 310 foreach ($chunk as $id) { 311 if (is_multisite()) { 312 switch_to_blog($id); 313 } 314 $option = 'dfehc_server_load_logs_' . get_current_blog_id(); 315 $logs = get_option($option, []); 316 if ($logs) { 317 $cutoff = $now - $max_age; 318 $logs = array_filter( 319 $logs, 320 static fn($row) => isset($row['timestamp']) && $row['timestamp'] >= $cutoff 321 ); 322 if (count($logs) > $max_cnt) { 323 $logs = array_slice($logs, -$max_cnt); 324 } 325 update_option($option, array_values($logs), false); 326 } 327 if (is_multisite()) { 328 restore_current_blog(); 329 } 330 } 331 $offset += $chunk_size; 332 update_site_option($offset_key, $offset); 333 } 334 335 add_action('dfehc_prune_logs_hook', 'dfehc_prune_server_load_logs'); 336 337 if (!wp_next_scheduled('dfehc_prune_logs_hook')) { 338 $t = strtotime('today 03:00'); 339 if ($t < time()) { 340 $t += DAY_IN_SECONDS; 341 } 342 wp_schedule_event($t, 'dfehc_daily', 'dfehc_prune_logs_hook'); 343 } 344 345 add_filter('dfehc_required_capability', fn() => 'manage_options'); 346 add_filter('dfehc_server_load_ttl', fn() => 120); 347 add_filter('dfehc_load_weights', fn() => [3, 2, 1]); 348 add_filter('dfehc_async_retry', fn() => 1); 349 add_filter('dfehc_log_retention_seconds', fn() => 2 * DAY_IN_SECONDS); 350 add_filter('dfehc_log_retention_max', fn() => 3000); 223 351 224 352 new Heartbeat_Async(); -
dynamic-front-end-heartbeat-control/trunk/heartbeat-controller.php
r3310561 r3320647 4 4 Plugin URI: https://heartbeat.support 5 5 Description: An enhanced solution to optimize the performance of your WordPress website. Stabilize your website's load averages and enhance the browsing experience for visitors during high-traffic fluctuations. 6 Version: 1.2.9 86 Version: 1.2.99 7 7 Author: Codeloghin 8 8 Author URI: https://codeloghin.com … … 46 46 define('DFEHC_NONCE_ACTION', 'dfehc_get_recommended_intervals'); 47 47 } 48 if (!defined('DFEHC_LOCK_TTL')) { 49 define('DFEHC_LOCK_TTL', 60); 50 } 51 if (!defined('DFEHC_USER_ACTIVITY_TTL')) { 52 define('DFEHC_USER_ACTIVITY_TTL', HOUR_IN_SECONDS); 53 } 48 54 49 55 function dfehc_set_transient_noautoload(string $key, $value, int $expiration): void … … 51 57 if (wp_using_ext_object_cache()) { 52 58 wp_cache_set($key, $value, '', $expiration); 53 } else { 54 set_transient($key, $value, $expiration); 55 global $wpdb; 56 $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => '_transient_' . $key]); 57 } 59 return; 60 } 61 set_transient($key, $value, $expiration); 62 global $wpdb; 63 $wpdb->query( 64 $wpdb->prepare( 65 "UPDATE {$wpdb->options} SET autoload='no' WHERE option_name=%s AND autoload='yes' LIMIT 1", 66 '_transient_' . $key 67 ) 68 ); 58 69 } 59 70 60 71 function dfehc_enqueue_scripts(): void 61 72 { 62 wp_enqueue_script('heartbeat', plugin_dir_url(__FILE__) . 'js/heartbeat.min.js', ['jquery'], '1.5. 0', true);73 wp_enqueue_script('heartbeat', plugin_dir_url(__FILE__) . 'js/heartbeat.min.js', ['jquery'], '1.5.2', true); 63 74 if (function_exists('wp_script_add_data')) { 64 75 wp_script_add_data('heartbeat', 'async', true); 65 76 } 66 $load_average = dfehc_get_system_load_average(); 67 $recommendedInterval = dfehc_calculate_recommended_interval_user_activity($load_average, DFEHC_BATCH_SIZE); 68 77 $load_average = dfehc_get_system_load_average(); 78 $recommended = dfehc_calculate_recommended_interval_user_activity($load_average, DFEHC_BATCH_SIZE); 69 79 wp_localize_script('heartbeat', 'dfehc_heartbeat_vars', [ 70 'recommendedInterval' => $recommendedInterval,80 'recommendedInterval' => $recommended, 71 81 'heartbeat_control_enabled' => get_option('dfehc_heartbeat_control_enabled', '1'), 72 'cache_duration' => 5 * 60 * 1000,73 'nonce' => wp_create_nonce(DFEHC_NONCE_ACTION),82 'cache_duration' => 5 * 60 * 1000, 83 'nonce' => wp_create_nonce(DFEHC_NONCE_ACTION), 74 84 ]); 75 85 } 76 86 add_action('wp_enqueue_scripts', 'dfehc_enqueue_scripts'); 77 87 78 function dfehc_calculate_recommended_interval_user_activity($load_average, int $batch_size = DFEHC_BATCH_SIZE): float 79 { 80 if (PHP_OS_FAMILY !== 'Unix' || !function_exists('sys_getloadavg')) { 81 return 60; 82 } 83 $load_average = dfehc_get_system_load_average(); 84 $user_data = dfehc_gather_user_activity_data($batch_size); 88 function dfehc_get_user_activity_summary(int $batch_size = DFEHC_BATCH_SIZE): array 89 { 90 $cached = get_transient('dfehc_user_activity_summary'); 91 if ($cached !== false) { 92 return $cached; 93 } 94 $summary = dfehc_gather_user_activity_data($batch_size); 95 dfehc_set_transient_noautoload('dfehc_user_activity_summary', $summary, DFEHC_USER_ACTIVITY_TTL); 96 return $summary; 97 } 98 99 function dfehc_calculate_recommended_interval_user_activity(?float $load_average = null, int $batch_size = DFEHC_BATCH_SIZE): float 100 { 101 if (!function_exists('sys_getloadavg')) { 102 return 60.0; 103 } 104 if ($load_average === null) { 105 $load_average = dfehc_get_system_load_average(); 106 } 107 $user_data = dfehc_get_user_activity_summary($batch_size); 85 108 if ($user_data['total_weight'] === 0) { 86 return DFEHC_MIN_INTERVAL;109 return (float) DFEHC_MIN_INTERVAL; 87 110 } 88 111 $avg_duration = $user_data['total_duration'] / max(1, $user_data['total_weight']); … … 95 118 $total_weight = 0; 96 119 $offset = 0; 97 do { 98 $transient_key = 'dfehc_user_batch_' . $offset; 99 $userBatch = get_transient($transient_key); 100 if ($userBatch === false) { 101 $userBatch = dfehc_get_users_in_batches($batch_size, $offset); 102 dfehc_set_transient_noautoload($transient_key, $userBatch, HOUR_IN_SECONDS); 120 while (true) { 121 $userBatch = dfehc_get_users_in_batches($batch_size, $offset); 122 if (!$userBatch) { 123 break; 103 124 } 104 125 foreach ($userBatch as $user) { … … 107 128 continue; 108 129 } 109 $weight = count($activity['durations']);110 $avg = array_sum($activity['durations']) / $weight;130 $weight = count($activity['durations']); 131 $avg = array_sum($activity['durations']) / $weight; 111 132 $total_weighted_duration += $weight * $avg; 112 133 $total_weight += $weight; 113 134 } 114 135 $offset += $batch_size; 115 } while ($userBatch);136 } 116 137 return ['total_duration' => $total_weighted_duration, 'total_weight' => $total_weight]; 117 138 } … … 123 144 } 124 145 if (!class_exists('Heartbeat_Async')) { 125 return DFEHC_MIN_INTERVAL; 126 } 127 class Dfehc_Get_Recommended_Heartbeat_Interval_Async extends Heartbeat_Async 128 { 129 protected string $action = 'dfehc_get_recommended_interval_async'; 130 protected function run_action(): void 146 return get_transient('dfehc_recommended_interval'); 147 } 148 if (!class_exists('Dfehc_Get_Recommended_Heartbeat_Interval_Async')) { 149 class Dfehc_Get_Recommended_Heartbeat_Interval_Async extends Heartbeat_Async 131 150 { 132 $last_activity = (int) get_transient('dfehc_last_user_activity'); 133 $elapsed = time() - $last_activity; 134 $load = dfehc_get_system_load_average(); 135 $interval = dfehc_calculate_recommended_interval($elapsed, $load, 0); 136 dfehc_set_transient_noautoload('dfehc_recommended_interval', $interval, 5 * MINUTE_IN_SECONDS); 151 protected string $action = 'dfehc_get_recommended_interval_async'; 152 protected function run_action(): void 153 { 154 $lock = dfehc_acquire_lock('dfehc_interval_calculation_lock', DFEHC_LOCK_TTL); 155 if (!$lock) { 156 return; 157 } 158 $last_activity = (int) get_transient('dfehc_last_user_activity'); 159 $elapsed = time() - $last_activity; 160 $load = dfehc_get_system_load_average(); 161 $interval = dfehc_calculate_recommended_interval($elapsed, $load, 0); 162 dfehc_set_transient_noautoload('dfehc_recommended_interval', $interval, 5 * MINUTE_IN_SECONDS); 163 dfehc_release_lock($lock); 164 } 137 165 } 138 166 } 139 167 $current = dfehc_get_website_visitors(); 140 168 $prev = get_transient('dfehc_previous_visitor_count'); 141 if ($prev === false || abs($current - $prev) > $current * 0.2) { 169 $ratio = (float) apply_filters('dfehc_visitors_delta_ratio', 0.2); 170 if ($prev === false || abs($current - $prev) > $current * $ratio) { 142 171 delete_transient('dfehc_recommended_interval'); 143 172 dfehc_set_transient_noautoload('dfehc_previous_visitor_count', $current, 5 * MINUTE_IN_SECONDS); 144 173 } 145 if (get_transient('dfehc_recommended_interval') === false) { 174 $cached = get_transient('dfehc_recommended_interval'); 175 if ($cached !== false) { 176 return (float) $cached; 177 } 178 $lock = dfehc_acquire_lock('dfehc_interval_calculation_lock', DFEHC_LOCK_TTL); 179 if ($lock) { 146 180 (new Dfehc_Get_Recommended_Heartbeat_Interval_Async())->dispatch(); 147 } 148 return (float) get_transient('dfehc_recommended_interval'); 181 dfehc_release_lock($lock); 182 } 183 return get_transient('dfehc_recommended_interval'); 149 184 } 150 185 … … 160 195 function dfehc_override_heartbeat_interval(array $settings): array 161 196 { 162 $interval = (int) ($settings['interval'] ?? DFEHC_MIN_INTERVAL);163 $interval = min(max($interval, DFEHC_MIN_INTERVAL), DFEHC_MAX_INTERVAL);197 $interval = (int) ($settings['interval'] ?? DFEHC_MIN_INTERVAL); 198 $interval = min(max($interval, DFEHC_MIN_INTERVAL), DFEHC_MAX_INTERVAL); 164 199 $settings['interval'] = $interval; 165 200 return $settings; … … 167 202 add_filter('heartbeat_settings', 'dfehc_override_heartbeat_interval'); 168 203 169 add_filter('dfehc_safe_transient_get', function ($value, $key) {204 add_filter('dfehc_safe_transient_get', static function ($value, $key) { 170 205 if ($value === false) { 171 dfehc_set_transient_noautoload("dfehc_retry_{$key}", true, 60);206 dfehc_set_transient_noautoload("dfehc_retry_{$key}", true, DFEHC_LOCK_TTL); 172 207 } 173 208 return $value; -
dynamic-front-end-heartbeat-control/trunk/js/heartbeat.js
r3310561 r3320647 1 1 ((wp) => { 2 2 const intervals = { 3 low: [15, 30, 60, 120, 180, 240, 300],3 low: [15, 30, 60, 120, 180, 240, 300], 4 4 medium: [30, 60, 120, 180, 240, 300], 5 high: [60, 120, 180, 240, 300],5 high: [60, 120, 180, 240, 300], 6 6 }; 7 7 8 const cacheTimeout = dfehc_heartbeat_vars.cache_duration || 5 * 60 * 1000; 9 const cache = {}; 8 const cacheTimeout = dfehc_heartbeat_vars.cache_duration || 5 * 60 * 1000; 9 const memoryCache = Object.create(null); 10 const localCacheKey = 'dfehc_heartbeat_server_load'; 10 11 11 const get Cache = (key) => {12 const getLocalCache = (key) => { 12 13 try { 13 const cachedData = localStorage.getItem(key); 14 if (cachedData) { 15 const { timestamp, data } = JSON.parse(cachedData); 16 if (Date.now() - timestamp < cacheTimeout) { 17 return data; 18 } 19 } 20 } catch (e) { 21 console.warn('DFEHC: localStorage cache corrupted, clearing.'); 14 const raw = localStorage.getItem(key); 15 if (!raw) return null; 16 const { timestamp, data } = JSON.parse(raw); 17 return Date.now() - timestamp < cacheTimeout ? data : null; 18 } catch { 22 19 localStorage.removeItem(key); 23 } 24 return null; 25 }; 26 27 const setCache = (key, data) => { 28 try { 29 localStorage.setItem(key, JSON.stringify({ timestamp: Date.now(), data })); 30 } catch (e) { 31 console.warn('DFEHC: Failed to cache data to localStorage.'); 20 return null; 32 21 } 33 22 }; 34 23 35 const getRecommendedIntervals = async (nonce) => { 36 const cacheKey = 'dfehc_heartbeat_server_load'; 37 const cachedData = getCache(cacheKey); 38 if (cachedData) return cachedData; 24 const setLocalCache = (key, data) => { 25 try { 26 localStorage.setItem(key, JSON.stringify({ timestamp: Date.now(), data })); 27 } catch { 28 } 29 }; 30 31 const fetchServerLoad = async (nonce) => { 32 const cached = getLocalCache(localCacheKey); 33 if (cached !== null) return cached; 39 34 40 35 try { 41 const response = await fetch(ajaxurl, { 42 method: 'POST', 43 body: JSON.stringify({ 44 action: 'get_server_load', 45 nonce, 46 }), 47 headers: { 48 'Content-Type': 'application/json', 49 }, 36 const body = new URLSearchParams({ action: 'get_server_load', nonce }); 37 const res = await fetch(ajaxurl, { 38 method: 'POST', 39 body, 40 credentials: 'same-origin', 50 41 }); 51 42 52 if (!response.ok) throw new Error(`Server responded ${response.status}`); 43 if (!res.ok) throw new Error(`HTTP ${res.status}`); 44 const json = await res.json(); 45 if (!json.success || typeof json.data !== 'number') throw new Error('Bad payload'); 53 46 54 const result = await response.json(); 55 if (!result.success || typeof result.data !== 'number') { 56 throw new Error('Invalid data received'); 57 } 58 59 setCache(cacheKey, result.data); 60 return result.data; 47 setLocalCache(localCacheKey, json.data); 48 return json.data; 61 49 } catch (err) { 62 50 console.error('DFEHC fetch error:', err.message); … … 65 53 }; 66 54 67 const smoothMoving = (x) => { 68 let sum = 0; 69 const y = []; 70 x.forEach((val) => { 71 if (y.length >= 5) sum -= y.shift(); 72 y.push(val); 73 sum += val; 74 }); 75 return sum / y.length; 55 const smoothMoving = (arr, windowSize = 5) => { 56 const slice = arr.slice(-windowSize); 57 return slice.reduce((s, v) => s + v, 0) / slice.length; 76 58 }; 77 59 78 const getWeightedAverage = (recentIntervals) => 79 recentIntervals.reduce((total, value, index) => total + value * (5 - index), 0) / 15; 80 81 const getTrafficLevel = (serverLoad) => { 82 if (serverLoad <= 50) return 'low'; 83 if (serverLoad <= 75) return 'medium'; 84 return 'high'; 60 const weightedAverage = (arr) => { 61 const len = arr.length; 62 const denom = (len * (len + 1)) / 2; 63 return arr.reduce((sum, v, i) => sum + v * (i + 1), 0) / denom; 85 64 }; 86 65 87 const calculateRecommendedInterval = (serverLoad) => {88 if (cache[serverLoad]) return cache[serverLoad];66 const trafficLevel = (load) => 67 load <= 50 ? 'low' : load <= 75 ? 'medium' : 'high'; 89 68 90 const trafficLevel = getTrafficLevel(serverLoad); 91 const recentIntervals = intervals[trafficLevel].slice(-5); 92 const weightedAverage = getWeightedAverage(recentIntervals); 93 const smoothedInterval = smoothMoving(recentIntervals); 94 const maxServerLoad = 85; 95 const loadFactor = 1 - serverLoad / maxServerLoad; 96 const recommendedInterval = Math.round(weightedAverage + smoothedInterval * loadFactor); 69 const calcRecommendedInterval = (load) => { 70 const bucket = Math.round(load / 5) * 5; 71 if (bucket in memoryCache) return memoryCache[bucket]; 97 72 98 cache[serverLoad] = recommendedInterval; 99 return Math.min(Math.max(recommendedInterval, 30), 300); 73 const level = trafficLevel(load); 74 const recent = intervals[level]; 75 const avg = weightedAverage(recent); 76 const smooth = smoothMoving(recent); 77 const loadFactor = 1 - Math.min(load / 85, 1); 78 const rawInterval = Math.round(avg + smooth * loadFactor); 79 const clampedInterval = Math.min(Math.max(rawInterval, 30), 300); 80 81 memoryCache[bucket] = clampedInterval; 82 return clampedInterval; 100 83 }; 101 84 102 85 const heartbeat = { 103 updateHeartbeatInterval: (interval) => wp.heartbeat.interval(interval), 104 105 updateUI: function (recommendedInterval) { 106 const intervalSelect = document.querySelector('#dfehc-heartbeat-interval'); 107 if (intervalSelect) { 108 intervalSelect.value = recommendedInterval; 109 this.updateHeartbeatInterval(recommendedInterval); 86 update: (interval) => { 87 if (wp && wp.heartbeat && typeof wp.heartbeat.interval === 'function') { 88 wp.heartbeat.interval(interval); 110 89 } 111 90 }, 112 91 updateUI: (interval) => { 92 const sel = document.querySelector('#dfehc-heartbeat-interval'); 93 if (sel) sel.value = interval; 94 this.update(interval); 95 }, 113 96 init: async function (nonce) { 114 97 if ('deviceMemory' in navigator && navigator.deviceMemory < 2) return; 115 98 if (navigator.connection && navigator.connection.effectiveType === '2g') return; 116 99 117 try { 118 const serverLoad = await getRecommendedIntervals(nonce); 119 const recommendedInterval = calculateRecommendedInterval(serverLoad); 120 this.updateUI(recommendedInterval); 121 } catch (error) { 122 console.error(`DFEHC Error: ${error.message}`); 123 } 100 const load = await fetchServerLoad(nonce); 101 const recommended = calcRecommendedInterval(load); 102 this.updateUI(recommended); 124 103 }, 125 104 }; 126 105 127 106 document.addEventListener('DOMContentLoaded', () => { 128 if (dfehc_heartbeat_vars.heartbeat_control_enabled === '1') { 129 const { nonce } = dfehc_heartbeat_vars; 107 if (dfehc_heartbeat_vars.heartbeat_control_enabled !== '1') return; 130 108 131 if ('requestIdleCallback' in window) { 132 requestIdleCallback(() => heartbeat.init(nonce)); 133 } else { 134 setTimeout(() => heartbeat.init(nonce), 100); 135 } 109 const { nonce } = dfehc_heartbeat_vars; 136 110 137 const intervalSelect = document.querySelector('#dfehc-heartbeat-interval'); 138 if (intervalSelect) { 139 intervalSelect.addEventListener('change', function () { 140 const newInterval = parseInt(this.value, 10); 141 if (!isNaN(newInterval)) { 142 heartbeat.updateHeartbeatInterval(newInterval); 143 } 144 }); 145 } 111 if ('requestIdleCallback' in window) { 112 requestIdleCallback(() => heartbeat.init(nonce)); 113 } else { 114 setTimeout(() => heartbeat.init(nonce), 100); 115 } 116 117 const sel = document.querySelector('#dfehc-heartbeat-interval'); 118 if (sel) { 119 sel.addEventListener('change', function () { 120 const val = parseInt(this.value, 10); 121 if (!isNaN(val)) heartbeat.update(val); 122 }); 146 123 } 147 124 }); 148 })(w p);125 })(window.wp || {}); -
dynamic-front-end-heartbeat-control/trunk/js/heartbeat.min.js
r3310561 r3320647 1 ((wp)=>{const i ntervals={low:[15,30,60,120,180,240,300],medium:[30,60,120,180,240,300],high:[60,120,180,240,300]},cacheTimeout=dfehc_heartbeat_vars.cache_duration||3e5,cache={},getCache=e=>{try{const t=localStorage.getItem(e);if(t){const{timestamp:r,data:a}=JSON.parse(t);if(Date.now()-r<cacheTimeout)return a}}catch(t){console.warn(\"DFEHC: localStorage cache corrupted, clearing.\"),localStorage.removeItem(e)}return null},setCache=(e,t)=>{try{localStorage.setItem(e,JSON.stringify({timestamp:Date.now(),data:t}))}catch(e){console.warn(\"DFEHC: Failed to cache data to localStorage.\")}},getRecommendedIntervals=async e=>{const t=\"dfehc_heartbeat_server_load\",r=getCache(t);if(r)return r;try{const r=await fetch(ajaxurl,{method:\"POST\",body:JSON.stringify({action:\"get_server_load\",nonce:e}),headers:{\"Content-Type\":\"application/json\"}});if(!r.ok)throw new Error(\"Server responded \"+r.status);const a=await r.json();if(!a.success||\"number\"!=typeof a.data)throw new Error(\"Invalid data received\");return setCache(t,a.data),a.data}catch(e){return console.error(\"DFEHC fetch error:\",e.message),60}},smoothMoving=e=>{let t=0;const r=[];return e.forEach(e=>{r.length>=5&&(t-=r.shift()),r.push(e),t+=e}),t/r.length},getWeightedAverage=e=>e.reduce((e,t,r)=>e+t*(5-r),0)/15,getTrafficLevel=e=>e<=50?\"low\":e<=75?\"medium\":\"high\",calculateRecommendedInterval=e=>{if(cache[e])return cache[e];const t=getTrafficLevel(e),r=intervals[t].slice(-5),a=getWeightedAverage(r),n=smoothMoving(r),o=1-e/85,s=Math.round(a+n*o);return cache[e]=s,Math.min(Math.max(s,30),300)};const heartbeat={updateHeartbeatInterval:e=>wp.heartbeat.interval(e),updateUI:function(e){const t=document.querySelector(\"#dfehc-heartbeat-interval\");t&&(t.value=e,this.updateHeartbeatInterval(e))},init:async function(e){if(\"deviceMemory\"in navigator&&navigator.deviceMemory<2)return;if(navigator.connection&&\"2g\"===navigator.connection.effectiveType)return;try{const t=await getRecommendedIntervals(e),r=calculateRecommendedInterval(t);this.updateUI(r)}catch(e){console.error(`DFEHC Error: ${e.message}`)}}};document.addEventListener(\"DOMContentLoaded\",()=>{if(\"1\"===dfehc_heartbeat_vars.heartbeat_control_enabled){const{nonce:e}=dfehc_heartbeat_vars;\"requestIdleCallback\"in window?requestIdleCallback(()=>heartbeat.init(e)):setTimeout(()=>heartbeat.init(e),100);const t=document.querySelector(\"#dfehc-heartbeat-interval\");t&&t.addEventListener(\"change\",function(){const e=parseInt(this.value,10);isNaN(e)||heartbeat.updateHeartbeatInterval(e)})}})})(wp);1 ((wp)=>{const i={low:[15,30,60,120,180,240,300],medium:[30,60,120,180,240,300],high:[60,120,180,240,300]},c=dfehc_heartbeat_vars.cache_duration||3e5,l=Object.create(null),d="dfehc_heartbeat_server_load",a=e=>{try{const{timestamp:t,data:n}=JSON.parse(localStorage.getItem(e));return Date.now()-t<c?n:null}catch{return localStorage.removeItem(e),null}},s=(e,t)=>{try{localStorage.setItem(e,JSON.stringify({timestamp:Date.now(),data:t}))}catch{}},u=async e=>{const t=a(d);if(t!==null)return t;try{const t=new URLSearchParams({action:"get_server_load",nonce:e}),n=await fetch(ajaxurl,{method:"POST",body:t,credentials:"same-origin"});if(!n.ok)throw new Error(`HTTP ${n.status}`);const r=await n.json();if(!r.success||typeof r.data!=="number")throw new Error("Bad payload");return s(d,r.data),r.data}catch(e){return console.error("DFEHC fetch error:",e.message),60}},f=(e,t=5)=>{const n=e.slice(-t);return n.reduce((e,t)=>e+t,0)/n.length},p=e=>{const t=e.length;return e.reduce((e,n,r)=>e+n*(r+1),0)/(t*(t+1)/2)},m=e=>e<=50?"low":e<=75?"medium":"high",h=e=>{const t=Math.round(e/5)*5;if(t in l)return l[t];const n=m(e),r=i[n],o=p(r),g=f(r),a=1-Math.min(e/85,1),c_=Math.round(o+g*a),s_=Math.min(Math.max(c_,30),300);return l[t]=s_,s_},g={update:e=>{wp&&wp.heartbeat&&typeof wp.heartbeat.interval=="function"&&wp.heartbeat.interval(e)},updateUI(e){const t=document.querySelector("#dfehc-heartbeat-interval");t&&(t.value=e),this.update(e)},async init(e){if("deviceMemory"in navigator&&navigator.deviceMemory<2)return;if(navigator.connection&&navigator.connection.effectiveType==="2g")return;const t=await u(e),n=h(t);this.updateUI(n)}};document.addEventListener("DOMContentLoaded",()=>{if(dfehc_heartbeat_vars.heartbeat_control_enabled!=="1")return;const{nonce:e}=dfehc_heartbeat_vars;"requestIdleCallback"in window?requestIdleCallback(()=>g.init(e)):setTimeout(()=>g.init(e),100);const t=document.querySelector("#dfehc-heartbeat-interval");t&&t.addEventListener("change",function(){const e=parseInt(this.value,10);isNaN(e)||g.update(e)})})})(window.wp||{}); -
dynamic-front-end-heartbeat-control/trunk/readme.txt
r3310561 r3320647 1 1 Dynamic Front-End Heartbeat Control 2 2 Requires at least: 5.5 3 Tested up to: 6.8 .13 Tested up to: 6.8 4 4 Requires PHP: 7.2 5 Stable tag: 1.2.9 85 Stable tag: 1.2.99 6 6 License: GPLv2 or later 7 7 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 41 41 42 42 == Changelog == 43 44 = 1.2.99 = 45 46 * Applied final tune-ups for seamless continuity in the upcoming branch; the current plugin version is performing at its best to date. The plugin was subjected to rigorous stress tests—far beyond the traffic levels most websites will ever face. 47 48 * Plugin update recommended to benefit from these improvements. 43 49 44 50 = 1.2.98 = -
dynamic-front-end-heartbeat-control/trunk/visitor/cookie-helper.php
r3310564 r3320647 2 2 declare(strict_types=1); 3 3 4 function dfehc_get_bot_pattern(): string 5 { 6 static $pattern = null; 7 if ($pattern !== null) { 8 return $pattern; 9 } 10 $sigs = (array) apply_filters('dfehc_bot_signatures', [ 11 'bot', 'crawl', 'slurp', 'spider', 'mediapartners', 'bingpreview', 12 'yandex', 'duckduckbot', 'baiduspider', 'sogou', 'exabot', 13 'facebot', 'facebookexternalhit', 'ia_archiver', 14 ]); 15 $tokens = array_map( 16 static fn(string $s): string => '(?:^|\\b)' . preg_quote($s, '/'), 17 $sigs 18 ); 19 return $pattern = '/(' . implode('|', $tokens) . ')/i'; 20 } 21 22 function dfehc_is_request_bot(): bool 23 { 24 static $cached = null; 25 if ($cached !== null) { 26 return $cached; 27 } 28 29 $ua = $_SERVER['HTTP_USER_AGENT'] ?? ''; 30 if ($ua === '' || preg_match(dfehc_get_bot_pattern(), $ua)) { 31 return $cached = true; 32 } 33 34 $accept = $_SERVER['HTTP_ACCEPT'] ?? ''; 35 $sec_ch = $_SERVER['HTTP_SEC_CH_UA'] ?? ''; 36 if (($accept === '' || stripos($accept, 'text/html') === false) && $sec_ch === '') { 37 return $cached = true; 38 } 39 40 $ip = $_SERVER['REMOTE_ADDR'] ?? ''; 41 $ipKey = $ip ? 'dfehc_bad_ip_' . md5($ip) : ''; 42 $group = apply_filters('dfehc_cache_group', 'dfehc'); 43 44 if ($ipKey && wp_using_ext_object_cache() && function_exists('wp_cache_get')) { 45 if (wp_cache_get($ipKey, $group)) { 46 return $cached = true; 47 } 48 } 49 return $cached = false; 50 } 51 4 52 function dfehc_set_user_cookie(): void 5 53 { 6 static $bot_pattern = null; 7 if ($bot_pattern === null) { 8 $signatures = apply_filters('dfehc_bot_signatures', [ 9 'bot', 'crawl', 'slurp', 'spider', 'mediapartners', 'bingpreview', 10 'yandex', 'duckduckbot', 'baiduspider', 'sogou', 'exabot', 11 'facebot', 'facebookexternalhit', 'ia_archiver', 12 ]); 13 $bot_pattern = '/' . implode('|', array_map('preg_quote', $signatures)) . '/i'; 14 } 15 16 if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match($bot_pattern, strtolower($_SERVER['HTTP_USER_AGENT']))) { 54 if (dfehc_is_request_bot()) { 17 55 return; 18 56 } 19 57 20 $name = 'dfehc_user'; 21 $lifetime = 400; 22 $path = apply_filters('dfehc_cookie_path', '/'); 23 $secure = is_ssl(); 24 $httponly = true; 25 $same_site = apply_filters('dfehc_cookie_samesite', 'Strict'); 58 $ip = $_SERVER['REMOTE_ADDR'] ?? ''; 59 $group = apply_filters('dfehc_cache_group', 'dfehc'); 60 $maxRPM = (int) apply_filters('dfehc_max_rpm', 120); 26 61 27 $visitor_id = empty($_COOKIE[$name]) 28 ? (function_exists('random_bytes') ? bin2hex(random_bytes(16)) : uniqid('visitor_', true) . mt_rand()) 29 : sanitize_text_field($_COOKIE[$name]); 30 31 if (PHP_VERSION_ID >= 70300) { 32 setcookie($name, $visitor_id, [ 33 'expires' => time() + $lifetime, 34 'path' => $path, 35 'secure' => $secure, 36 'httponly' => $httponly, 37 'samesite' => $same_site, 38 ]); 39 } else { 40 setcookie($name, $visitor_id, time() + $lifetime, $path . '; samesite=' . strtolower($same_site), '', $secure, $httponly); 41 } 42 43 $fallback = true; 44 45 if (extension_loaded('redis') && class_exists('Redis')) { 46 try { 47 $redis = new Redis(); 48 $socket = get_option('dfehc_redis_socket', ''); 49 $connected = $socket 50 ? @$redis->connect($socket) 51 : @$redis->connect( 52 function_exists('dfehc_get_redis_server') ? dfehc_get_redis_server() : '127.0.0.1', 53 function_exists('dfehc_get_redis_port') ? dfehc_get_redis_port() : 6379, 54 1 55 ); 56 if ($connected && $redis->ping()) { 57 $redis->incr('dfehc_total_visitors'); 58 $redis->close(); 59 $fallback = false; 60 } 61 } catch (RedisException $e) { 62 if ($ip && wp_using_ext_object_cache() && function_exists('wp_cache_incr')) { 63 $rpmKey = 'dfehc_iprpm_' . md5($ip); 64 $rpm = wp_cache_incr($rpmKey, 1, $group); 65 if ($rpm === false) { 66 wp_cache_set($rpmKey, 1, $group, 60); 67 $rpm = 1; 68 } 69 if ($rpm > $maxRPM) { 70 wp_cache_set('dfehc_bad_ip_' . md5($ip), 1, $group, HOUR_IN_SECONDS); 71 return; 62 72 } 63 73 } 64 74 65 if ($fallback && extension_loaded('memcached') && class_exists('Memcached')) { 66 try { 67 $memcached = new Memcached(); 68 $connected = @$memcached->addServer( 69 function_exists('dfehc_get_memcached_server') ? dfehc_get_memcached_server() : '127.0.0.1', 70 function_exists('dfehc_get_memcached_port') ? dfehc_get_memcached_port() : 11211 71 ); 72 if ($connected) { 73 $result = $memcached->increment('dfehc_total_visitors', 1); 74 if ($result === false && $memcached->getResultCode() === Memcached::RES_NOTFOUND) { 75 $memcached->set('dfehc_total_visitors', 1); 76 } 77 $memcached->quit(); 78 $fallback = false; 79 } 80 } catch (Exception $e) { 81 } 75 $name = 'dfehc_user'; 76 $lifetime = (int) apply_filters('dfehc_cookie_lifetime', 400); 77 $path = (string) apply_filters('dfehc_cookie_path', '/'); 78 $domain = (string) apply_filters('dfehc_cookie_domain', parse_url(home_url(), PHP_URL_HOST)); 79 $sameSite = (string) apply_filters('dfehc_cookie_samesite', 'Strict'); 80 $secure = is_ssl() || $sameSite === 'None'; 81 $httpOnly = true; 82 83 $val = $_COOKIE[$name] ?? ''; 84 if (!preg_match('/^[A-Za-z0-9]{32,64}$/', $val)) { 85 $val = function_exists('random_bytes') 86 ? bin2hex(random_bytes(16)) 87 : uniqid('v_', true) . mt_rand(); 88 } 89 $expires = time() + $lifetime; 90 91 if (PHP_VERSION_ID >= 70300) { 92 setcookie($name, $val, [ 93 'expires' => $expires, 94 'path' => $path, 95 'domain' => $domain, 96 'secure' => $secure, 97 'httponly' => $httpOnly, 98 'samesite' => $sameSite, 99 ]); 100 } else { 101 header(sprintf( 102 'Set-Cookie: %s=%s; Expires=%s; Path=%s; Domain=%s%s; HttpOnly; SameSite=%s', 103 rawurlencode($name), 104 rawurlencode($val), 105 gmdate('D, d-M-Y H:i:s T', $expires), 106 $path, 107 $domain, 108 $secure ? '; Secure' : '', 109 $sameSite 110 )); 82 111 } 83 112 84 if ($fallback) { 85 $count = get_transient('dfehc_total_visitors'); 86 set_transient('dfehc_total_visitors', ($count !== false ? (int) $count : 0) + 1, 4 * MINUTE_IN_SECONDS); 113 if (isset($_COOKIE[$name])) { 114 return; 115 } 116 117 $visitorKey = 'dfehc_total_visitors'; 118 if (wp_using_ext_object_cache() && function_exists('wp_cache_incr')) { 119 if (false === wp_cache_incr($visitorKey, 1, $group)) { 120 wp_cache_set($visitorKey, 1, $group, $lifetime); 121 } 122 return; 123 } 124 125 static $client = null; 126 127 if (!$client && extension_loaded('redis') && class_exists('Redis')) { 128 try { 129 $client = new \Redis(); 130 131 $sock = get_option('dfehc_redis_socket', ''); 132 $ok = $sock 133 ? $client->pconnect($sock) 134 : $client->pconnect( 135 function_exists('dfehc_get_redis_server') ? dfehc_get_redis_server() : '127.0.0.1', 136 function_exists('dfehc_get_redis_port') ? dfehc_get_redis_port() : 6379, 137 1.0 138 ); 139 140 if (!$ok || $client->ping() !== '+PONG') { 141 $client = null; 142 } 143 } catch (\Throwable $e) { 144 $client = null; 87 145 } 88 146 } 89 147 148 if ($client) { 149 $client->incr($visitorKey); 150 $client->expire($visitorKey, $lifetime); 151 return; 152 } 153 154 static $mem = null; 155 if (!$mem && extension_loaded('memcached') && class_exists('Memcached')) { 156 $mem = new \Memcached(); 157 $mem->addServer( 158 function_exists('dfehc_get_memcached_server') ? dfehc_get_memcached_server() : '127.0.0.1', 159 function_exists('dfehc_get_memcached_port') ? dfehc_get_memcached_port() : 11211 160 ); 161 if (empty($mem->getStats())) { 162 $mem = null; 163 } 164 } 165 if ($mem) { 166 $val = $mem->increment($visitorKey, 1); 167 if ($val === false) { 168 $mem->set($visitorKey, 1, $lifetime); 169 } else { 170 $mem->touch($visitorKey, $lifetime); 171 } 172 return; 173 } 174 175 $cnt = (int) get_transient($visitorKey); 176 set_transient($visitorKey, $cnt + 1, $lifetime); 177 } 178 90 179 add_action('send_headers', 'dfehc_set_user_cookie'); -
dynamic-front-end-heartbeat-control/trunk/visitor/manager.php
r3310561 r3320647 2 2 declare(strict_types=1); 3 3 4 function dfehc_set_default_last_activity_time(int $user_id): void 5 { 6 update_user_meta($user_id, 'last_activity_time', current_time('timestamp')); 4 if (!function_exists('dfehc_acquire_lock')) { 5 function dfehc_acquire_lock(string $key, int $ttl = 60) { 6 if (class_exists('WP_Lock')) { 7 $lock = new WP_Lock($key, $ttl); 8 return $lock->acquire() ? $lock : null; 9 } 10 return wp_cache_add($key, 1, '', $ttl) ? (object) ['cache_key' => $key] : null; 11 } 12 function dfehc_release_lock($lock): void { 13 if ($lock instanceof WP_Lock) { 14 $lock->release(); 15 } elseif (is_object($lock) && isset($lock->cache_key)) { 16 wp_cache_delete($lock->cache_key, ''); 17 } 18 } 19 } 20 21 function dfehc_set_default_last_activity_time(int $user_id): void { 22 update_user_meta($user_id, 'last_activity_time', time()); 7 23 } 8 24 add_action('user_register', 'dfehc_set_default_last_activity_time'); 9 25 10 function dfehc_custom_cron_interval_addition(array $schedules): array 11 { 12 $schedules['dfehc_5_minutes'] = [ 13 'interval' => 300, 14 'display' => __('Every 5 Minutes', 'dfehc'), 15 ]; 16 return $schedules; 17 } 18 add_filter('cron_schedules', 'dfehc_custom_cron_interval_addition'); 19 20 function dfehc_schedule_user_activity_processing(): void 21 { 22 if (!wp_next_scheduled('dfehc_process_user_activity')) { 23 wp_schedule_event(time(), 'dfehc_5_minutes', 'dfehc_process_user_activity'); 26 function dfehc_add_intervals(array $s): array { 27 $s['dfehc_5_minutes'] ??= ['interval' => 300, 'display' => __('Every 5 minutes (DFEHC)', 'dfehc')]; 28 return $s; 29 } 30 add_filter('cron_schedules', 'dfehc_add_intervals'); 31 32 function dfehc_schedule_user_activity_processing(): void { 33 if (!get_option('dfehc_activity_cron_scheduled') && !wp_next_scheduled('dfehc_process_user_activity')) { 34 wp_schedule_event(time() - time() % 300 + 300, 'dfehc_5_minutes', 'dfehc_process_user_activity'); 35 update_option('dfehc_activity_cron_scheduled', 1, false); 24 36 } 25 37 } 26 38 add_action('init', 'dfehc_schedule_user_activity_processing'); 27 39 28 function dfehc_throttled_user_activity_handler(): void 29 { 30 if ( false === get_transient('dfehc_recent_user_processing')) {31 set_transient('dfehc_recent_user_processing', true, 300);32 try {33 dfehc_process_user_activity();34 } finally {35 delete_transient('dfehc_recent_user_processing');36 }40 function dfehc_throttled_user_activity_handler(): void { 41 $lock = dfehc_acquire_lock('dfehc_recent_user_processing', 300); 42 if (!$lock) { 43 return; 44 } 45 try { 46 dfehc_process_user_activity(); 47 } finally { 48 dfehc_release_lock($lock); 37 49 } 38 50 } 39 51 add_action('dfehc_process_user_activity', 'dfehc_throttled_user_activity_handler'); 40 52 41 function dfehc_process_user_activity(): void 42 { 43 $batch_size = 75; 44 $offset = 0; 45 while ($users = dfehc_get_users_in_batches($batch_size, $offset)) { 46 foreach ($users as $user) { 47 $last = (int) get_user_meta($user->ID, 'last_activity_time', true); 48 if (!$last) { 49 update_user_meta($user->ID, 'last_activity_time', current_time('timestamp')); 50 } 51 } 52 $offset += $batch_size; 53 } 54 } 55 53 function dfehc_process_user_activity(): void { 54 if (get_transient('dfehc_activity_backfill_complete')) { 55 return; 56 } 57 global $wpdb; 58 $batch = (int) apply_filters('dfehc_activity_processing_batch_size', 75); 59 $last_id = (int) get_option('dfehc_activity_last_id', 0); 60 $ids = $wpdb->get_col($wpdb->prepare( 61 "SELECT ID FROM $wpdb->users WHERE ID > %d ORDER BY ID ASC LIMIT %d", 62 $last_id, $batch 63 )); 64 if (!$ids) { 65 set_transient('dfehc_activity_backfill_complete', true, DAY_IN_SECONDS); 66 delete_option('dfehc_activity_last_id'); 67 return; 68 } 69 $now = time(); 70 foreach ($ids as $id) { 71 if (!get_user_meta($id, 'last_activity_time', true)) { 72 update_user_meta($id, 'last_activity_time', $now); 73 } 74 } 75 update_option('dfehc_activity_last_id', end($ids), false); 76 } 77 78 function dfehc_record_user_activity(): void { 79 if (!is_user_logged_in()) { 80 return; 81 } 82 static $cache = []; 83 $uid = get_current_user_id(); 84 $now = time(); 85 $interval = (int) apply_filters('dfehc_activity_update_interval', 900); 86 $last = $cache[$uid] ?? (int) get_user_meta($uid, 'last_activity_time', true); 87 if ($now - $last >= $interval) { 88 update_user_meta($uid, 'last_activity_time', $now); 89 $cache[$uid] = $now; 90 } 91 } 92 add_action('wp', 'dfehc_record_user_activity'); 93 94 function dfehc_cleanup_user_activity(int $last_id = 0, int $batch_size = 75): void { 95 $lock = dfehc_acquire_lock('dfehc_cleanup_lock', 600); 96 if (!$lock) { 97 return; 98 } 99 try { 100 global $wpdb; 101 $batch_size = (int) apply_filters('dfehc_cleanup_batch_size', $batch_size); 102 $ids = $wpdb->get_col($wpdb->prepare( 103 "SELECT ID FROM $wpdb->users WHERE ID > %d ORDER BY ID ASC LIMIT %d", 104 $last_id, $batch_size 105 )); 106 if (!$ids) { 107 return; 108 } 109 foreach ($ids as $id) { 110 delete_user_meta($id, 'last_activity_time'); 111 } 112 if (count($ids) === $batch_size) { 113 wp_schedule_single_event(time() + 15, 'dfehc_cleanup_user_activity', [end($ids), $batch_size]); 114 } 115 } finally { 116 dfehc_release_lock($lock); 117 } 118 } 119 add_action('dfehc_cleanup_user_activity', 'dfehc_cleanup_user_activity', 10, 2); 120 121 function dfehc_increment_total_visitors(): void { 122 $key = 'dfehc_total_visitors'; 123 $grp = apply_filters('dfehc_cache_group', 'dfehc'); 124 $ttl = HOUR_IN_SECONDS; 125 126 if (wp_using_ext_object_cache() && function_exists('wp_cache_incr')) { 127 if (false === wp_cache_incr($key, 1, $grp)) { 128 wp_cache_set($key, 1, $grp, $ttl); 129 } 130 return; 131 } 132 133 static $conn; 134 135 if (!$conn && extension_loaded('redis') && class_exists('Redis')) { 136 try { 137 $conn = new \Redis(); 138 if ( 139 !$conn->pconnect('127.0.0.1', 6379) || 140 $conn->ping() !== '+PONG' 141 ) { 142 $conn = null; 143 } 144 } catch (\Throwable $e) { 145 $conn = null; 146 } 147 } 148 149 if ($conn) { 150 $conn->incr($key); 151 $conn->expire($key, $ttl); 152 return; 153 } 154 155 156 $cnt = (int) get_transient($key); 157 set_transient($key, $cnt + 1, $ttl); 158 } 159 function dfehc_increment_total_visitors_fallback(): void { 160 dfehc_increment_total_visitors(); 161 } 162 163 function dfehc_safe_cache_get(string $key): int { 164 $grp = apply_filters('dfehc_cache_group', 'dfehc'); 165 if (wp_using_ext_object_cache() && function_exists('wp_cache_get')) { 166 $v = wp_cache_get($key, $grp); 167 return $v !== false ? (int) $v : 0; 168 } 169 $v = get_transient($key); 170 return $v !== false ? (int) $v : 0; 171 } 172 173 function dfehc_safe_cache_delete(string $key): void { 174 $grp = apply_filters('dfehc_cache_group', 'dfehc'); 175 if (wp_using_ext_object_cache() && function_exists('wp_cache_delete')) { 176 wp_cache_delete($key, $grp); 177 } 178 delete_transient($key); 179 } 180 181 function dfehc_get_website_visitors(): int { 182 $cache = get_transient('dfehc_total_visitors'); 183 if ($cache !== false) { 184 return (int) $cache; 185 } 186 if (get_transient('dfehc_regenerating_cache')) { 187 return (int) get_option('dfehc_stale_total_visitors', 0); 188 } 189 190 set_transient('dfehc_regenerating_cache', true, MINUTE_IN_SECONDS); 191 $total = dfehc_safe_cache_get('dfehc_total_visitors'); 192 set_transient('dfehc_total_visitors', $total, 4 * MINUTE_IN_SECONDS); 193 update_option('dfehc_stale_total_visitors', $total, false); 194 195 delete_transient('dfehc_regenerating_cache'); 196 return (int) apply_filters('dfehc_get_website_visitors_result', $total); 197 } 56 198 function dfehc_get_users_in_batches(int $batch_size, int $offset): array 57 199 { … … 63 205 return $query->get_results(); 64 206 } 65 66 function dfehc_record_user_activity(): void 67 { 68 if (is_user_logged_in()) { 69 $user = wp_get_current_user(); 70 $time = current_time('timestamp'); 71 $interval = (int) apply_filters('dfehc_activity_update_interval', 900); 72 $last_activity = (int) get_user_meta($user->ID, 'last_activity_time', true); 73 if ($time - $last_activity >= $interval) { 74 update_user_meta($user->ID, 'last_activity_time', $time); 75 } 76 } 77 } 78 add_action('wp_footer', 'dfehc_record_user_activity'); 79 add_action('wp', 'dfehc_record_user_activity'); 80 81 function dfehc_cleanup_user_activity(int $offset = 0, int $batch_size = 75): void 82 { 83 if (get_transient('dfehc_cleanup_lock')) { 84 return; 85 } 86 set_transient('dfehc_cleanup_lock', true, 600); 87 try { 88 $users = get_users(['number' => $batch_size, 'offset' => $offset, 'fields' => ['ID']]); 89 if (!$users) { 207 function dfehc_reset_total_visitors(): void { 208 $lock = dfehc_acquire_lock('dfehc_resetting_visitors', 60); 209 if (!$lock) { 210 return; 211 } 212 try { 213 $threshold = (float) apply_filters('dfehc_reset_load_threshold', 15.0); 214 $load = function_exists('dfehc_get_server_load') ? dfehc_get_server_load() : 0; 215 if ($load === \DFEHC_SENTINEL_NO_LOAD || $load >= $threshold) { 90 216 return; 91 217 } 92 foreach ($users as $user) { 93 delete_user_meta($user->ID, 'last_activity_time'); 94 } 95 if (count($users) === $batch_size) { 96 wp_schedule_single_event(time() + 15, 'dfehc_cleanup_user_activity', [$offset + $batch_size, $batch_size]); 97 } 218 dfehc_safe_cache_delete('dfehc_total_visitors'); 219 delete_option('dfehc_stale_total_visitors'); 220 delete_transient('dfehc_total_visitors'); 98 221 } finally { 99 delete_transient('dfehc_cleanup_lock'); 100 } 101 } 102 103 function dfehc_increment_total_visitors_fallback(): void 104 { 105 $count = (int) get_transient('dfehc_total_visitors'); 106 set_transient('dfehc_total_visitors', $count + 1, 0); 107 } 108 109 function dfehc_safe_cache_get(string $key, int $timeout = 1): int 110 { 111 if (extension_loaded('redis') && class_exists('Redis')) { 112 try { 113 $redis = new Redis(); 114 if (@$redis->connect(function_exists('dfehc_get_redis_server') ? dfehc_get_redis_server() : '127.0.0.1', function_exists('dfehc_get_redis_port') ? dfehc_get_redis_port() : 6379, $timeout) && $redis->ping()) { 115 $val = $redis->get($key); 116 $redis->close(); 117 if ($val !== false) { 118 return (int) $val; 119 } 120 } 121 } catch (RedisException $e) { 122 } 123 } 124 if (extension_loaded('memcached') && class_exists('Memcached')) { 125 try { 126 $memcached = new Memcached(); 127 if ($memcached->addServer(function_exists('dfehc_get_memcached_server') ? dfehc_get_memcached_server() : '127.0.0.1', function_exists('dfehc_get_memcached_port') ? dfehc_get_memcached_port() : 11211)) { 128 $val = $memcached->get($key); 129 $memcached->quit(); 130 if ($val !== false) { 131 return (int) $val; 132 } 133 } 134 } catch (Exception $e) { 135 } 136 } 137 $cached = get_transient($key); 138 return $cached !== false ? (int) $cached : 0; 139 } 140 141 function dfehc_safe_cache_delete(string $key, int $timeout = 1): void 142 { 143 if (extension_loaded('redis') && class_exists('Redis')) { 144 try { 145 $redis = new Redis(); 146 if (@$redis->connect(function_exists('dfehc_get_redis_server') ? dfehc_get_redis_server() : '127.0.0.1', function_exists('dfehc_get_redis_port') ? dfehc_get_redis_port() : 6379, $timeout) && $redis->ping()) { 147 $redis->del($key); 148 $redis->close(); 149 } 150 } catch (RedisException $e) { 151 } 152 } 153 if (extension_loaded('memcached') && class_exists('Memcached')) { 154 try { 155 $memcached = new Memcached(); 156 if ($memcached->addServer(function_exists('dfehc_get_memcached_server') ? dfehc_get_memcached_server() : '127.0.0.1', function_exists('dfehc_get_memcached_port') ? dfehc_get_memcached_port() : 11211)) { 157 $memcached->delete($key); 158 $memcached->quit(); 159 } 160 } catch (Exception $e) { 161 } 162 } 163 delete_transient($key); 164 } 165 166 function dfehc_get_website_visitors(): int 167 { 168 $cached = apply_filters('dfehc_safe_transient_get', get_transient('dfehc_total_visitors'), 'dfehc_total_visitors'); 169 if ($cached !== false) { 170 return (int) $cached; 171 } 172 if (get_transient('dfehc_regenerating_cache')) { 173 return (int) get_option('dfehc_stale_total_visitors', 0); 174 } 175 set_transient('dfehc_regenerating_cache', true, 60); 176 try { 177 $result = dfehc_safe_cache_get('dfehc_total_visitors'); 178 set_transient('dfehc_total_visitors', $result, 4 * MINUTE_IN_SECONDS); 179 update_option('dfehc_stale_total_visitors', $result); 180 } finally { 181 delete_transient('dfehc_regenerating_cache'); 182 } 183 return $result; 184 } 185 186 function dfehc_reset_total_visitors(): void 187 { 188 if (get_transient('dfehc_resetting_visitors')) { 189 return; 190 } 191 set_transient('dfehc_resetting_visitors', true, 60); 192 try { 193 $load = function_exists('dfehc_get_server_load') ? dfehc_get_server_load() : 0; 194 if ($load < 15) { 195 delete_transient('dfehc_total_visitors'); 196 delete_option('dfehc_stale_total_visitors'); 197 dfehc_safe_cache_delete('dfehc_total_visitors'); 198 } 199 } finally { 200 delete_transient('dfehc_resetting_visitors'); 222 dfehc_release_lock($lock); 201 223 } 202 224 } 203 225 add_action('dfehc_reset_total_visitors_event', 'dfehc_reset_total_visitors'); 204 226 205 if (!wp_next_scheduled('dfehc_reset_total_visitors_event')) { 206 wp_schedule_event(time(), 'hourly', 'dfehc_reset_total_visitors_event'); 207 } 227 function dfehc_on_activate(): void { 228 if (!wp_next_scheduled('dfehc_reset_total_visitors_event')) { 229 wp_schedule_event(time() + HOUR_IN_SECONDS, 'hourly', 'dfehc_reset_total_visitors_event'); 230 } 231 dfehc_process_user_activity(); 232 } 233 register_activation_hook(__FILE__, 'dfehc_on_activate'); 234 235 function dfehc_on_deactivate(): void { 236 wp_clear_scheduled_hook('dfehc_process_user_activity'); 237 wp_clear_scheduled_hook('dfehc_reset_total_visitors_event'); 238 delete_option('dfehc_activity_cron_scheduled'); 239 } 240 register_deactivation_hook(__FILE__, 'dfehc_on_deactivate'); 208 241 209 242 if (defined('WP_CLI') && WP_CLI) { 210 WP_CLI::add_command('dfehc reset_visitors',function () {243 \WP_CLI::add_command('dfehc:reset_visitors', static function () { 211 244 dfehc_reset_total_visitors(); 212 WP_CLI::success('Visitor count reset triggered manually.');245 \WP_CLI::success('Visitor count reset triggered.'); 213 246 }); 214 247 }
Note: See TracChangeset
for help on using the changeset viewer.