Changeset 3461136
- Timestamp:
- 02/14/2026 12:51:26 AM (6 weeks ago)
- Location:
- dynamic-front-end-heartbeat-control
- Files:
-
- 35 added
- 15 edited
-
tags/1.2.998.1 (added)
-
tags/1.2.998.1/LICENSE (added)
-
tags/1.2.998.1/admin (added)
-
tags/1.2.998.1/admin/affix.php (added)
-
tags/1.2.998.1/admin/ajax-handler.php (added)
-
tags/1.2.998.1/admin/asset-manager.php (added)
-
tags/1.2.998.1/admin/heartbeat-config.php (added)
-
tags/1.2.998.1/admin/unclogger-menu.php (added)
-
tags/1.2.998.1/css (added)
-
tags/1.2.998.1/css/dfhcsl-admin.css (added)
-
tags/1.2.998.1/defibrillator (added)
-
tags/1.2.998.1/defibrillator/cli-helper.php (added)
-
tags/1.2.998.1/defibrillator/db-health.php (added)
-
tags/1.2.998.1/defibrillator/load-estimator.php (added)
-
tags/1.2.998.1/defibrillator/rest-api.php (added)
-
tags/1.2.998.1/defibrillator/unclogger-db.php (added)
-
tags/1.2.998.1/defibrillator/unclogger.php (added)
-
tags/1.2.998.1/engine (added)
-
tags/1.2.998.1/engine/interval-helper.php (added)
-
tags/1.2.998.1/engine/server-load.php (added)
-
tags/1.2.998.1/engine/server-response.php (added)
-
tags/1.2.998.1/engine/system-load-fallback.php (added)
-
tags/1.2.998.1/heartbeat-async.php (added)
-
tags/1.2.998.1/heartbeat-controller.php (added)
-
tags/1.2.998.1/js (added)
-
tags/1.2.998.1/js/chart.js (added)
-
tags/1.2.998.1/js/dfhcsl-admin.js (added)
-
tags/1.2.998.1/js/heartbeat.js (added)
-
tags/1.2.998.1/js/heartbeat.min.js (added)
-
tags/1.2.998.1/readme.txt (added)
-
tags/1.2.998.1/settings.php (added)
-
tags/1.2.998.1/visitor (added)
-
tags/1.2.998.1/visitor/cookie-helper.php (added)
-
tags/1.2.998.1/visitor/manager.php (added)
-
tags/1.2.998.1/widget.php (added)
-
trunk/admin/affix.php (modified) (1 diff)
-
trunk/admin/asset-manager.php (modified) (1 diff)
-
trunk/defibrillator/load-estimator.php (modified) (23 diffs)
-
trunk/engine/interval-helper.php (modified) (4 diffs)
-
trunk/engine/server-load.php (modified) (23 diffs)
-
trunk/engine/server-response.php (modified) (27 diffs)
-
trunk/engine/system-load-fallback.php (modified) (9 diffs)
-
trunk/heartbeat-async.php (modified) (11 diffs)
-
trunk/heartbeat-controller.php (modified) (9 diffs)
-
trunk/js/heartbeat.js (modified) (22 diffs)
-
trunk/js/heartbeat.min.js (modified) (1 diff)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/visitor/cookie-helper.php (modified) (14 diffs)
-
trunk/visitor/manager.php (modified) (2 diffs)
-
trunk/widget.php (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
-
dynamic-front-end-heartbeat-control/trunk/admin/affix.php
r3427256 r3461136 682 682 <span style="padding-right:10px">'.esc_html__('Server','dfehc').'</span> 683 683 <input type="range" id="dfehc_priority_slider" name="dfehc_priority_slider" min="-3" max="3" step="1" value="'.esc_attr($slider_value).'" style="flex-grow:1" /> 684 <span style="padding-left:10px">'.esc_html__(' Visitor','dfehc').'</span>684 <span style="padding-left:10px">'.esc_html__('User','dfehc').'</span> 685 685 </div>'; 686 686 -
dynamic-front-end-heartbeat-control/trunk/admin/asset-manager.php
r3427163 r3461136 251 251 } 252 252 253 $js = ' 254 jQuery(function($){ 255 const overlay=$("<div>",{id:"dfehc-loader-overlay"}).addClass("dfehc-loader-overlay") 256 .append($("<div>",{class:"dfehc-loader-content"}) 257 .append($("<div>",{class:"heartbeat-loader"})) 258 .append($("<p>").css({marginTop:"20px",fontSize:"1.2em"}).text("'.esc_js(__('Processing, please wait…','dfehc')).'"))); 259 $("body").append(overlay); 260 261 $("#dfehc-optimizer-form").on("submit",function(e){ 262 e.preventDefault(); 263 let task=$(document.activeElement).val(); 264 if(!task){alert("'.esc_js(__('Could not determine task. Please click a button to optimize.','dfehc')).'");return;} 265 overlay.show(); 266 $.post(ajaxurl,{ 267 action:"dfehc_optimize", 268 optimize_function:task, 269 _ajax_nonce:"'.wp_create_nonce('dfehc_optimize_action').'" 270 }) 271 .done(()=>location.reload()) 272 .fail(xhr=>{ 273 overlay.hide(); 274 alert(xhr.responseText||"'.esc_js(__('Unexpected error – check the PHP error log.','dfehc')).'"); 275 }); 276 }); 277 }); 278 '; 279 wp_add_inline_script( 'dfhcsl-admin-js', $js ); 253 $js = ' 254 jQuery(function($){ 255 const overlay = $("<div>", { id: "dfehc-loader-overlay" }).addClass("dfehc-loader-overlay") 256 .append( 257 $("<div>", { class: "dfehc-loader-content" }) 258 .append($("<div>", { class: "heartbeat-loader" })) 259 .append($("<p>").css({ marginTop: "20px", fontSize: "1.2em" }).text("'.esc_js(__('Processing, please wait…','dfehc')).'")) 260 .append($("<p>", { id: "dfehc-loader-slow-note" }).css({ marginTop: "12px", fontSize: "1em", opacity: 0.9, display: "none" }).text("'.esc_js(__('This is taking longer than usual. The server might be busy or the task is large. To avoid heavy server load, the process runs gently, which can increase the time needed. Thanks for your patience.','dfehc')).'")) 261 ); 262 263 $("body").append(overlay); 264 265 let slowTimer = null; 266 const startSlowTimer = () => { 267 if (slowTimer) clearTimeout(slowTimer); 268 $("#dfehc-loader-slow-note").hide(); 269 slowTimer = setTimeout(() => { 270 $("#dfehc-loader-slow-note").fadeIn(200); 271 }, 50000); 272 }; 273 const stopSlowTimer = () => { 274 if (slowTimer) clearTimeout(slowTimer); 275 slowTimer = null; 276 $("#dfehc-loader-slow-note").hide(); 277 }; 278 279 $("#dfehc-optimizer-form").on("submit", function(e){ 280 e.preventDefault(); 281 let task = $(document.activeElement).val(); 282 if(!task){ 283 alert("'.esc_js(__('Could not determine task. Please click a button to optimize.','dfehc')).'"); 284 return; 285 } 286 overlay.show(); 287 startSlowTimer(); 288 289 $.post(ajaxurl, { 290 action: "dfehc_optimize", 291 optimize_function: task, 292 _ajax_nonce: "'.wp_create_nonce('dfehc_optimize_action').'" 293 }) 294 .done(() => location.reload()) 295 .fail(xhr => { 296 stopSlowTimer(); 297 overlay.hide(); 298 alert(xhr.responseText || "'.esc_js(__('Unexpected error – check the PHP error log.','dfehc')).'"); 299 }); 300 }); 301 302 $(window).on("beforeunload", function(){ 303 stopSlowTimer(); 304 }); 305 }); 306 '; 307 wp_add_inline_script('dfhcsl-admin-js', $js); 308 280 309 } 281 310 } -
dynamic-front-end-heartbeat-control/trunk/defibrillator/load-estimator.php
r3427163 r3461136 8 8 } 9 9 10 class Dfehc_ServerLoadEstimator { 10 class Dfehc_ServerLoadEstimator 11 { 11 12 const BASELINE_TRANSIENT_PREFIX = 'dfehc_baseline_'; 12 13 const LOAD_CACHE_TRANSIENT = 'dfehc_last_known_load'; … … 14 15 const BASELINE_RESET_CD_PREFIX = 'dfehc_baseline_reset_cd_'; 15 16 16 public static function get_server_load(float $duration = 0.025) { 17 private static $requestMemo = null; 18 private static $scopeSuffixMemo = null; 19 private static $hostnameKeyMemo = null; 20 private static $blogIdMemo = null; 21 private static $sapiTagMemo = null; 22 23 public static function get_server_load(float $duration = 0.025) 24 { 25 if (self::$requestMemo !== null) { 26 return self::$requestMemo; 27 } 28 17 29 if (\apply_filters('dfehc_disable_loop_estimator', false)) { 18 return false; 19 } 30 return self::$requestMemo = false; 31 } 32 20 33 if (!\function_exists('microtime') || (\defined('DFEHC_DISABLE_LOAD_ESTIMATION') && DFEHC_DISABLE_LOAD_ESTIMATION)) { 21 return false; 22 } 34 return self::$requestMemo = false; 35 } 36 37 if (!\apply_filters('dfehc_allow_estimator_on_request', true)) { 38 return self::$requestMemo = false; 39 } 40 41 $isCron = (\function_exists('wp_doing_cron') && \wp_doing_cron()); 42 $isAjax = (\function_exists('wp_doing_ajax') && \wp_doing_ajax()); 43 $isJson = (\function_exists('wp_is_json_request') && \wp_is_json_request()); 44 $isCli = (\defined('WP_CLI') && WP_CLI); 45 46 if (!$isCron && ($isAjax || $isJson || $isCli)) { 47 return self::$requestMemo = false; 48 } 49 23 50 $duration = (float) \apply_filters('dfehc_loop_duration', $duration); 24 if (!\is_finite($duration) || $duration <= 0.0) { 25 $duration = 0.025; 26 } 27 if ($duration < 0.01) { 28 $duration = 0.01; 29 } 30 if ($duration > 0.5) { 31 $duration = 0.5; 32 } 51 $duration = self::normalize_duration($duration, 0.025); 33 52 34 53 $suffix = self::scope_suffix(); 35 54 $baselineT = self::get_baseline_transient_name($suffix); 36 $cacheTtl = (int) \apply_filters('dfehc_load_cache_ttl', 90); 55 56 $cacheTtl = (int) \apply_filters('dfehc_load_cache_ttl', 90); 37 57 if ($cacheTtl < 1) { 38 58 $cacheTtl = 1; 39 59 } 40 $cacheKey = self::get_cache_key($suffix); 41 42 $cached = self::get_scoped_transient($cacheKey); 43 if ($cached !== false && $cached !== null && \is_numeric($cached)) { 44 return (float) $cached; 45 } 46 47 $sysAvg = self::try_sys_getloadavg(); 48 if ($sysAvg !== null) { 49 self::set_scoped_transient_noautoload($cacheKey, $sysAvg, $cacheTtl); 50 return $sysAvg; 51 } 52 53 $baseline = self::get_baseline_value($baselineT); 54 if ($baseline === false || $baseline === null || !\is_numeric($baseline) || (float) $baseline <= 0.0) { 55 $baseline = self::maybe_calibrate($baselineT, $duration); 56 } 57 $baseline = (float) $baseline; 58 if ($baseline <= 0.0) { 59 $baseline = 1.0; 60 } 61 62 $loopsPerSec = self::run_loop_avg($duration); 63 if ($loopsPerSec <= 0) { 64 return false; 65 } 66 67 $loadRatio = ($baseline * 0.125) / \max($loopsPerSec, 1); 68 $loadPercent = \round(\min(100.0, \max(0.0, $loadRatio * 100.0)), 2); 69 $loadPercent = (float) \apply_filters('dfehc_computed_load_percent', $loadPercent, $baseline, $loopsPerSec); 70 71 self::update_spike_score($loadPercent, $suffix); 72 self::set_scoped_transient_noautoload($cacheKey, $loadPercent, $cacheTtl); 73 74 return $loadPercent; 75 } 76 77 public static function calibrate_baseline(float $duration = 0.025): float { 60 61 $staleTtl = (int) \apply_filters('dfehc_load_cache_stale_ttl', 1800); 62 if ($staleTtl < $cacheTtl) { 63 $staleTtl = $cacheTtl; 64 } 65 66 $cacheKey = self::get_cache_key($suffix); 67 $cached = self::get_cached_load_value($cacheKey, $cacheTtl, $staleTtl); 68 69 if ($cached['fresh'] && $cached['value'] !== null) { 70 return self::$requestMemo = (float) $cached['value']; 71 } 72 73 $allowSys = (bool) \apply_filters('dfehc_allow_sys_getloadavg', true); 74 if ($allowSys) { 75 $sysAvg = self::try_sys_getloadavg(); 76 if ($sysAvg !== null) { 77 self::set_cached_load_value($cacheKey, $sysAvg, $staleTtl); 78 return self::$requestMemo = (float) $sysAvg; 79 } 80 } 81 82 $loopAllowed = $isCron || self::is_idle_context(); 83 $loopAllowed = (bool) \apply_filters('dfehc_allow_loop_estimation', $loopAllowed, $suffix); 84 85 if (!$loopAllowed) { 86 if ($cached['value'] !== null) { 87 return self::$requestMemo = (float) $cached['value']; 88 } 89 return self::$requestMemo = false; 90 } 91 92 $sampleRate = (float) \apply_filters('dfehc_loop_estimation_sample_rate', $isCron ? 1.0 : 0.05, $suffix); 93 if (!\is_finite($sampleRate) || $sampleRate < 0.0) { 94 $sampleRate = 0.0; 95 } elseif ($sampleRate > 1.0) { 96 $sampleRate = 1.0; 97 } 98 99 if (!$isCron && $sampleRate < 1.0) { 100 $r = self::rand_unit(); 101 if ($r > $sampleRate) { 102 if ($cached['value'] !== null) { 103 return self::$requestMemo = (float) $cached['value']; 104 } 105 return self::$requestMemo = false; 106 } 107 } 108 109 $cooldownTtl = (int) \apply_filters('dfehc_loop_estimation_cooldown_ttl', $isCron ? 0 : 30, $suffix); 110 if ($cooldownTtl < 0) { 111 $cooldownTtl = 0; 112 } 113 114 $cooldownKey = 'dfehc_loop_est_cd_' . $suffix; 115 if ($cooldownTtl > 0 && self::get_scoped_transient($cooldownKey) !== false) { 116 if ($cached['value'] !== null) { 117 return self::$requestMemo = (float) $cached['value']; 118 } 119 return self::$requestMemo = false; 120 } 121 122 $lockKey = 'dfehc_estimating_' . $suffix; 123 $estLockTtl = (int) \apply_filters('dfehc_estimation_lock_ttl', 15); 124 if ($estLockTtl < 1) { 125 $estLockTtl = 1; 126 } 127 128 $lock = self::acquire_lock($lockKey, $estLockTtl); 129 if (!$lock) { 130 if ($cached['value'] !== null) { 131 return self::$requestMemo = (float) $cached['value']; 132 } 133 return self::$requestMemo = false; 134 } 135 136 if ($cooldownTtl > 0) { 137 self::set_scoped_transient_noautoload($cooldownKey, 1, $cooldownTtl); 138 } 139 140 try { 141 $baseline = self::get_baseline_value($baselineT); 142 if ($baseline === false || $baseline === null || !\is_numeric($baseline) || (float) $baseline <= 0.0) { 143 $baseline = self::maybe_calibrate($baselineT, $duration); 144 } 145 $baseline = (float) $baseline; 146 if ($baseline <= 0.0) { 147 $baseline = 1.0; 148 } 149 150 $loopsPerSec = self::run_loop_avg($duration); 151 if ($loopsPerSec <= 0) { 152 if ($cached['value'] !== null) { 153 return self::$requestMemo = (float) $cached['value']; 154 } 155 return self::$requestMemo = false; 156 } 157 158 $scale = (float) \apply_filters('dfehc_loop_to_percent_scale', 0.125); 159 if (!\is_finite($scale) || $scale <= 0.0) { 160 $scale = 0.125; 161 } 162 163 $loadRatio = ($baseline * $scale) / \max($loopsPerSec, 1); 164 $loadPercent = \round(\min(100.0, \max(0.0, $loadRatio * 100.0)), 2); 165 $loadPercent = (float) \apply_filters('dfehc_computed_load_percent', $loadPercent, $baseline, $loopsPerSec); 166 167 self::update_spike_score($loadPercent, $suffix); 168 self::set_cached_load_value($cacheKey, $loadPercent, $staleTtl); 169 170 return self::$requestMemo = (float) $loadPercent; 171 } finally { 172 self::release_lock($lock); 173 } 174 } 175 176 public static function calibrate_baseline(float $duration = 0.025): float 177 { 78 178 $duration = (float) \apply_filters('dfehc_loop_duration', $duration); 79 if (!\is_finite($duration) || $duration <= 0.0) { 80 $duration = 0.025; 81 } 82 if ($duration < 0.01) { 83 $duration = 0.01; 84 } 85 if ($duration > 0.5) { 86 $duration = 0.5; 87 } 179 $duration = self::normalize_duration($duration, 0.025); 88 180 return self::run_loop_avg($duration); 89 181 } 90 182 91 public static function maybe_calibrate_if_idle(): void { 92 if (\is_admin() || \is_user_logged_in()) { 93 return; 94 } 95 if ((\function_exists('wp_doing_cron') && \wp_doing_cron()) || 96 (\function_exists('wp_doing_ajax') && \wp_doing_ajax()) || 97 (\function_exists('wp_is_json_request') && \wp_is_json_request()) || 98 (\defined('WP_CLI') && WP_CLI)) { 99 return; 100 } 101 183 public static function maybe_calibrate_if_idle(): void 184 { 185 if (!self::is_idle_context()) { 186 return; 187 } 102 188 $suffix = self::scope_suffix(); 103 189 $seenKey = 'dfehc_seen_recently_' . $suffix; … … 113 199 } 114 200 115 public static function maybe_calibrate_during_cron(): void { 201 public static function maybe_calibrate_during_cron(): void 202 { 116 203 if (!\function_exists('wp_doing_cron') || !\wp_doing_cron()) { 117 204 return; … … 130 217 } 131 218 132 private static function try_sys_getloadavg(): ?float { 219 public static function scheduled_recalibrate(string $suffix): void 220 { 221 $suffix = (string) $suffix; 222 if ($suffix === '') { 223 return; 224 } 225 $lock = self::acquire_lock('dfehc_sched_cal_' . $suffix, 30); 226 if (!$lock) { 227 return; 228 } 229 try { 230 $baselineT = self::get_baseline_transient_name($suffix); 231 self::delete_baseline_value($baselineT); 232 self::ensure_baseline_for_suffix($suffix); 233 } finally { 234 self::release_lock($lock); 235 } 236 } 237 238 private static function normalize_duration(float $duration, float $fallback): float 239 { 240 if (!\is_finite($duration) || $duration <= 0.0) { 241 $duration = $fallback; 242 } 243 if ($duration < 0.01) { 244 $duration = 0.01; 245 } elseif ($duration > 0.5) { 246 $duration = 0.5; 247 } 248 return $duration; 249 } 250 251 private static function rand_unit(): float 252 { 253 if (\function_exists('wp_rand')) { 254 return (float) \wp_rand(0, 1000000) / 1000000.0; 255 } 256 return (float) \mt_rand(0, 1000000) / 1000000.0; 257 } 258 259 private static function is_idle_context(): bool 260 { 261 if (\is_admin() || \is_user_logged_in()) { 262 return false; 263 } 264 if ((\function_exists('wp_doing_cron') && \wp_doing_cron()) || 265 (\function_exists('wp_doing_ajax') && \wp_doing_ajax()) || 266 (\function_exists('wp_is_json_request') && \wp_is_json_request()) || 267 (\defined('WP_CLI') && WP_CLI)) { 268 return false; 269 } 270 return true; 271 } 272 273 private static function try_sys_getloadavg(): ?float 274 { 133 275 if (!\function_exists('sys_getloadavg')) { 134 276 return null; … … 152 294 } 153 295 154 return \min(100.0, \round(($raw / $cores) * 100.0, 2)); 155 } 156 157 private static function now(): float { 158 return \function_exists('hrtime') ? (hrtime(true) / 1e9) : \microtime(true); 159 } 160 161 private static function run_loop(float $duration): float { 162 if ($duration <= 0.0) { 163 return 0.0; 164 } 165 if ($duration < 0.01) { 166 $duration = 0.01; 167 } 168 if ($duration > 0.5) { 169 $duration = 0.5; 170 } 296 $pct = ($raw / $cores) * 100.0; 297 if (!\is_finite($pct)) { 298 return null; 299 } 300 301 return \min(100.0, \round(\max(0.0, $pct), 2)); 302 } 303 304 private static function now(): float 305 { 306 if (\function_exists('hrtime')) { 307 return (float) (\hrtime(true) / 1e9); 308 } 309 return (float) \microtime(true); 310 } 311 312 private static function run_loop(float $duration): float 313 { 314 $duration = self::normalize_duration($duration, 0.025); 171 315 $duration += (float) \wp_rand(0, 2) * 0.001; 172 173 $warm = self::now();174 for ($i = 0; $i < 1000; $i++) { $warm += 0; }175 316 176 317 $start = self::now(); 177 318 $end = $start + $duration; 178 $cnt = 0; 179 $cap = (int) \apply_filters('dfehc_loop_iteration_cap', 10000000);319 320 $cap = (int) \apply_filters('dfehc_loop_iteration_cap', 10000000); 180 321 if ($cap < 1000) { 181 322 $cap = 1000; 182 323 } 183 $now = $start; 324 325 $batch = (int) \apply_filters('dfehc_loop_timecheck_batch', 64); 326 if ($batch < 8) { 327 $batch = 8; 328 } elseif ($batch > 2048) { 329 $batch = 2048; 330 } 331 332 $cnt = 0; 333 $now = $start; 334 $lastNow = $start; 335 $x = 0; 184 336 185 337 while ($now < $end && $cnt < $cap) { 186 ++$cnt; 338 for ($i = 0; $i < $batch && $cnt < $cap; $i++) { 339 $x = ($x + 1) & 0xFFFFFFFF; 340 $cnt++; 341 } 187 342 $now = self::now(); 343 if ($now < $lastNow) { 344 $now = $lastNow; 345 } 346 $lastNow = $now; 188 347 } 189 348 … … 192 351 } 193 352 194 private static function run_loop_avg(float $duration): float { 353 private static function run_loop_avg(float $duration): float 354 { 195 355 $a = self::run_loop($duration); 196 356 $b = self::run_loop(\min(0.5, $duration * 1.5)); … … 198 358 return 0.0; 199 359 } 200 if ($a <= 0.0) return $b; 201 if ($b <= 0.0) return $a; 360 if ($a <= 0.0) { 361 return $b; 362 } 363 if ($b <= 0.0) { 364 365 return $a; 366 } 202 367 return ($a + $b) / 2.0; 203 368 } 204 369 205 private static function maybe_calibrate(string $baselineT, float $duration): float { 370 private static function maybe_calibrate(string $baselineT, float $duration): float 371 { 206 372 $suffix = self::scope_suffix(); 207 373 $lockKey = 'dfehc_calibrating_' . $suffix; … … 231 397 } 232 398 233 private static function update_spike_score(float $loadPercent, string $suffix): void { 399 private static function update_spike_score(float $loadPercent, string $suffix): void 400 { 234 401 $spikeKey = self::get_spike_key($suffix); 235 402 $scoreRaw = self::get_scoped_transient($spikeKey); … … 256 423 257 424 if ($score >= $threshold) { 258 if (\get_transient($resetCdKey) === false) { 425 $isCron = (\function_exists('wp_doing_cron') && \wp_doing_cron()); 426 $isIdle = self::is_idle_context(); 427 $canReset = $isCron || $isIdle; 428 $canReset = (bool) \apply_filters('dfehc_allow_baseline_reset', $canReset, $loadPercent, $suffix); 429 430 if ($canReset && \get_transient($resetCdKey) === false) { 259 431 $baselineName = self::get_baseline_transient_name($suffix); 260 432 self::delete_baseline_value($baselineName); … … 263 435 return; 264 436 } 437 438 if (\function_exists('wp_schedule_single_event') && \get_transient($resetCdKey) === false) { 439 $scheduledKey = 'dfehc_sched_cal_cd_' . $suffix; 440 $schedTtl = (int) \apply_filters('dfehc_scheduled_calibration_cooldown', 600, $suffix); 441 if ($schedTtl < 60) { 442 $schedTtl = 60; 443 } 444 if (self::get_scoped_transient($scheduledKey) === false) { 445 self::set_scoped_transient_noautoload($scheduledKey, 1, $schedTtl); 446 \wp_schedule_single_event(\time() + 30, 'dfehc_calibrate_baseline_event', [$suffix]); 447 } 448 } 265 449 } 266 450 … … 268 452 } 269 453 270 private static function ensure_baseline(): void { 271 $suffix = self::scope_suffix(); 454 private static function ensure_baseline(): void 455 { 456 $suffix = self::scope_suffix(); 457 self::ensure_baseline_for_suffix($suffix); 458 } 459 460 private static function ensure_baseline_for_suffix(string $suffix): void 461 { 272 462 $baselineT = self::get_baseline_transient_name($suffix); 273 463 $existing = self::get_baseline_value($baselineT); … … 284 474 try { 285 475 $duration = (float) \apply_filters('dfehc_loop_duration', 0.025); 286 if (!\is_finite($duration) || $duration <= 0.0) { 287 $duration = 0.025; 288 } 289 if ($duration < 0.01) { 290 $duration = 0.01; 291 } 292 if ($duration > 0.5) { 293 $duration = 0.5; 294 } 476 $duration = self::normalize_duration($duration, 0.025); 295 477 296 478 $baseline = self::run_loop_avg($duration); … … 309 491 } 310 492 311 private static function acquire_lock(string $key, int $ttl) { 493 private static function acquire_lock(string $key, int $ttl) 494 { 312 495 $ttl = $ttl < 1 ? 1 : $ttl; 313 496 $scopedKey = $key; … … 317 500 return $lock->acquire() ? $lock : null; 318 501 } 502 319 503 if (\function_exists('wp_cache_add') && \wp_cache_add($scopedKey, 1, DFEHC_CACHE_GROUP, $ttl)) { 320 504 return (object) ['type' => 'cache', 'key' => $scopedKey]; 321 505 } 506 322 507 if (\get_transient($scopedKey) !== false) { 323 508 return null; 324 509 } 510 325 511 if (\set_transient($scopedKey, 1, $ttl)) { 326 512 return (object) ['type' => 'transient', 'key' => $scopedKey]; 327 513 } 514 328 515 return null; 329 516 } 330 517 331 private static function release_lock($lock): void { 518 private static function release_lock($lock): void 519 { 332 520 if ($lock instanceof \WP_Lock) { 333 521 $lock->release(); … … 348 536 } 349 537 350 private static function get_baseline_transient_name(string $suffix): string { 538 private static function get_baseline_transient_name(string $suffix): string 539 { 351 540 return self::BASELINE_TRANSIENT_PREFIX . $suffix; 352 541 } 353 542 354 private static function get_hostname_key(): string { 355 $host = @\php_uname('n'); 356 if (!$host) { 357 $url = \defined('WP_HOME') && WP_HOME ? WP_HOME : (\function_exists('home_url') ? \home_url() : ''); 358 $parts = \wp_parse_url((string) $url); 359 $host = \is_array($parts) ? ($parts['host'] ?? '') : ''; 360 if (!$host) { 361 $host = $url ?: 'unknown'; 362 } 363 } 543 private static function get_hostname_key(): string 544 { 545 if (self::$hostnameKeyMemo !== null) { 546 return self::$hostnameKeyMemo; 547 } 548 549 $url = \defined('WP_HOME') && WP_HOME ? WP_HOME : (\function_exists('home_url') ? \home_url() : ''); 550 $parts = \wp_parse_url((string) $url); 551 $host = \is_array($parts) ? (string) ($parts['host'] ?? '') : ''; 552 553 if ($host === '') { 554 $host = (string) @\php_uname('n'); 555 } 556 if ($host === '') { 557 $host = $url !== '' ? (string) $url : 'unknown'; 558 } 559 364 560 $salt = \defined('DB_NAME') ? (string) DB_NAME : ''; 365 return \substr(\md5((string) $host . $salt), 0, 10); 366 } 367 368 private static function get_blog_id(): int { 369 return \function_exists('get_current_blog_id') ? (int) \get_current_blog_id() : 0; 370 } 371 372 private static function scope_suffix(): string { 373 $sapi = \php_sapi_name(); 374 $sapiTag = $sapi ? \substr(\preg_replace('/[^a-z0-9]/i', '', strtolower($sapi)), 0, 6) : 'web'; 375 $suffix = self::get_hostname_key() . '_' . self::get_blog_id() . '_' . $sapiTag; 561 self::$hostnameKeyMemo = \substr(\md5($host . $salt), 0, 10); 562 return self::$hostnameKeyMemo; 563 } 564 565 private static function get_blog_id(): int 566 { 567 if (self::$blogIdMemo !== null) { 568 return (int) self::$blogIdMemo; 569 } 570 self::$blogIdMemo = \function_exists('get_current_blog_id') ? (int) \get_current_blog_id() : 0; 571 return (int) self::$blogIdMemo; 572 } 573 574 private static function scope_suffix(): string 575 { 576 if (self::$scopeSuffixMemo !== null) { 577 return self::$scopeSuffixMemo; 578 } 579 580 if (self::$sapiTagMemo === null) { 581 $sapi = \php_sapi_name(); 582 $sapiTag = $sapi ? (string) \substr((string) \preg_replace('/[^a-z0-9]/i', '', \strtolower((string) $sapi)), 0, 6) : 'web'; 583 self::$sapiTagMemo = $sapiTag !== '' ? $sapiTag : 'web'; 584 } 585 586 $suffix = self::get_hostname_key() . '_' . self::get_blog_id() . '_' . self::$sapiTagMemo; 376 587 $override = \apply_filters('dfehc_baseline_scope_suffix', null, $suffix); 377 588 if (\is_string($override) && $override !== '') { 378 return $override; 379 } 380 return $suffix; 381 } 382 383 private static function get_cache_key(string $suffix): string { 589 self::$scopeSuffixMemo = $override; 590 return self::$scopeSuffixMemo; 591 } 592 593 self::$scopeSuffixMemo = $suffix; 594 return self::$scopeSuffixMemo; 595 } 596 597 private static function get_cache_key(string $suffix): string 598 { 384 599 return self::LOAD_CACHE_TRANSIENT . '_' . $suffix; 385 600 } 386 601 387 private static function get_spike_key(string $suffix): string { 602 private static function get_spike_key(string $suffix): string 603 { 388 604 return self::LOAD_SPIKE_TRANSIENT . '_' . $suffix; 389 605 } 390 606 391 private static function get_baseline_value(string $name) { 607 private static function get_baseline_value(string $name) 608 { 392 609 return \is_multisite() ? \get_site_transient($name) : \get_transient($name); 393 610 } 394 611 395 private static function set_baseline_value(string $name, $value, int $exp): void { 612 private static function set_baseline_value(string $name, $value, int $exp): void 613 { 396 614 if ($exp < 1) { 397 615 $exp = 1; … … 404 622 } 405 623 406 private static function delete_baseline_value(string $name): void { 624 private static function delete_baseline_value(string $name): void 625 { 407 626 if (\is_multisite()) { 408 627 \delete_site_transient($name); … … 412 631 } 413 632 414 private static function get_scoped_transient(string $key) { 633 private static function get_scoped_transient(string $key) 634 { 415 635 return \is_multisite() ? \get_site_transient($key) : \get_transient($key); 416 636 } 417 637 418 private static function set_scoped_transient_noautoload(string $key, $value, int $ttl): void { 638 private static function set_scoped_transient_noautoload(string $key, $value, int $ttl): void 639 { 419 640 if ($ttl < 1) { 420 641 $ttl = 1; … … 427 648 } 428 649 429 private static function delete_scoped_transient(string $key): void { 650 private static function delete_scoped_transient(string $key): void 651 { 430 652 if (\is_multisite()) { 431 653 \delete_site_transient($key); … … 435 657 } 436 658 437 private static function set_transient_noautoload(string $key, $value, int $ttl): void { 659 private static function get_cached_load_value(string $key, int $freshTtl, int $staleTtl): array 660 { 661 $raw = self::get_scoped_transient($key); 662 $now = \time(); 663 664 $value = null; 665 $age = null; 666 667 if (\is_array($raw) && isset($raw['v'], $raw['t'])) { 668 $t = (int) $raw['t']; 669 if ($t > 0) { 670 $age = $now - $t; 671 if ($age < 0) { 672 $age = 0; 673 } 674 if ($age <= $staleTtl && \is_numeric($raw['v'])) { 675 $value = (float) $raw['v']; 676 } 677 } 678 } elseif ($raw !== false && $raw !== null && \is_numeric($raw)) { 679 $value = (float) $raw; 680 $age = null; 681 } 682 683 $fresh = ($value !== null) && ($age === null || $age <= $freshTtl); 684 685 return [ 686 'value' => $value, 687 'fresh' => $fresh, 688 ]; 689 } 690 691 private static function set_cached_load_value(string $key, float $value, int $ttl): void 692 { 693 self::set_scoped_transient_noautoload($key, ['v' => (float) $value, 't' => \time()], $ttl); 694 } 695 696 private static function normalize_transient_key(string $key): string 697 { 698 $key = \trim($key); 699 if ($key === '') { 700 return ''; 701 } 702 $key = \preg_replace('/[^a-zA-Z0-9_\-:]/', '_', $key) ?? $key; 703 $key = \trim($key, "_ \t\n\r\0\x0B"); 704 if ($key === '') { 705 return ''; 706 } 707 if (\strlen($key) > 172) { 708 $key = \substr($key, 0, 120) . '_' . \substr(\md5($key), 0, 16); 709 } 710 return $key; 711 } 712 713 private static function set_transient_noautoload(string $key, $value, int $ttl): void 714 { 715 $key = self::normalize_transient_key($key); 716 if ($key === '') { 717 return; 718 } 719 438 720 $jitter = 0; 439 721 if (\function_exists('random_int')) { … … 444 726 } 445 727 } 728 $ttl = (int) $ttl; 446 729 $ttl = \max(1, $ttl + $jitter); 447 730 448 731 if (\function_exists('wp_using_ext_object_cache') && \wp_using_ext_object_cache()) { 449 \wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $ttl); 732 if (\function_exists('wp_cache_set')) { 733 \wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $ttl); 734 } 450 735 return; 451 736 } … … 453 738 \set_transient($key, $value, $ttl); 454 739 740 $minTtl = (int) \apply_filters('dfehc_noautoload_db_fix_min_ttl', 600); 741 $doFix = (bool) \apply_filters('dfehc_fix_transient_autoload', false, $key, $ttl); 742 743 if (!$doFix || $ttl < $minTtl) { 744 return; 745 } 746 455 747 global $wpdb; 456 if (!isset($wpdb) || !\is_object($wpdb) || !isset($wpdb->options)) { 457 return; 458 } 459 460 $opt_key = "_transient_$key"; 461 $opt_key_to = "_transient_timeout_$key"; 462 $wpdb->suppress_errors(true); 748 if (!($wpdb instanceof \wpdb) || empty($wpdb->options)) { 749 return; 750 } 751 752 $opt_key = '_transient_' . $key; 753 $opt_key_to = '_transient_timeout_' . $key; 754 755 $prev_suppress = $wpdb->suppress_errors(true); 463 756 try { 464 $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key)); 465 if ($autoload === 'yes') { 466 $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key, 'autoload' => 'yes'], ['%s'], ['%s','%s']); 467 } 468 $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to)); 469 if ($autoload_to === 'yes') { 470 $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key_to, 'autoload' => 'yes'], ['%s'], ['%s','%s']); 471 } 757 $wpdb->query($wpdb->prepare( 758 "UPDATE {$wpdb->options} SET autoload = 'no' WHERE option_name = %s AND autoload <> 'no' LIMIT 1", 759 $opt_key 760 )); 761 $wpdb->query($wpdb->prepare( 762 "UPDATE {$wpdb->options} SET autoload = 'no' WHERE option_name = %s AND autoload <> 'no' LIMIT 1", 763 $opt_key_to 764 )); 765 } catch (\Throwable $e) { 472 766 } finally { 473 $wpdb->suppress_errors(false); 474 } 475 } 476 477 private static function set_site_transient_noautoload(string $key, $value, int $ttl): void { 767 $wpdb->suppress_errors((bool) $prev_suppress); 768 } 769 } 770 771 private static function set_site_transient_noautoload(string $key, $value, int $ttl): void 772 { 773 $key = self::normalize_transient_key($key); 774 if ($key === '') { 775 return; 776 } 777 478 778 $jitter = 0; 479 779 if (\function_exists('random_int')) { … … 484 784 } 485 785 } 786 $ttl = (int) $ttl; 486 787 $ttl = \max(1, $ttl + $jitter); 487 788 488 789 if (\function_exists('wp_using_ext_object_cache') && \wp_using_ext_object_cache()) { 489 \wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $ttl); 790 if (\function_exists('wp_cache_set')) { 791 \wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $ttl); 792 } 490 793 return; 491 794 } … … 497 800 \add_action('init', [Dfehc_ServerLoadEstimator::class, 'maybe_calibrate_during_cron']); 498 801 \add_action('template_redirect', [Dfehc_ServerLoadEstimator::class, 'maybe_calibrate_if_idle']); 802 \add_action('dfehc_calibrate_baseline_event', [Dfehc_ServerLoadEstimator::class, 'scheduled_recalibrate'], 10, 1); 499 803 500 804 \add_filter('heartbeat_settings', function ($settings) { -
dynamic-front-end-heartbeat-control/trunk/engine/interval-helper.php
r3412283 r3461136 19 19 if (!defined('DFEHC_DEFAULT_SMA_WINDOW')) define('DFEHC_DEFAULT_SMA_WINDOW', 5); 20 20 if (!defined('DFEHC_DEFAULT_MAX_DECREASE_RATE')) define('DFEHC_DEFAULT_MAX_DECREASE_RATE', 0.25); 21 if (!defined('DFEHC_DEFAULT_EMA_TTL')) define('DFEHC_DEFAULT_EMA_TTL', 600);21 if (!defined('DFEHC_DEFAULT_EMA_TTL')) define('DFEHC_DEFAULT_EMA_TTL', 900); 22 22 23 23 if (!function_exists('dfehc_host_token')) { 24 function dfehc_host_token(): string { 24 function dfehc_host_token(): string 25 { 25 26 static $t = ''; 26 27 if ($t !== '') return $t; 27 $host = @php_uname('n') ?: (defined('WP_HOME') ? WP_HOME : (function_exists('home_url') ? home_url() : 'unknown')); 28 29 $url = ''; 30 if (defined('WP_HOME') && is_string(WP_HOME) && WP_HOME !== '') { 31 $url = WP_HOME; 32 } elseif (function_exists('home_url')) { 33 $url = (string) home_url(); 34 } 35 36 $host = ''; 37 if ($url !== '' && function_exists('wp_parse_url')) { 38 $parts = wp_parse_url($url); 39 if (is_array($parts) && isset($parts['host']) && is_string($parts['host'])) { 40 $host = $parts['host']; 41 } 42 } 43 44 if ($host === '') { 45 $host = @php_uname('n') ?: 'unknown'; 46 } 47 28 48 $salt = defined('DB_NAME') ? (string) DB_NAME : ''; 29 49 return $t = substr(md5((string) $host . $salt), 0, 10); … … 32 52 33 53 if (!function_exists('dfehc_blog_id')) { 34 function dfehc_blog_id(): int { 54 function dfehc_blog_id(): int 55 { 35 56 return function_exists('get_current_blog_id') ? (int) get_current_blog_id() : 0; 36 57 } … … 38 59 39 60 if (!function_exists('dfehc_scoped_key')) { 40 function dfehc_scoped_key(string $base): string { 61 function dfehc_scoped_key(string $base): string 62 { 41 63 return "{$base}_" . dfehc_blog_id() . '_' . dfehc_host_token(); 42 64 } 43 65 } 44 66 67 if (!function_exists('dfehc_apply_filters')) { 68 function dfehc_apply_filters(string $tag, $value, ...$args) 69 { 70 if (function_exists('apply_filters')) { 71 return apply_filters($tag, $value, ...$args); 72 } 73 return $value; 74 } 75 } 76 77 if (!function_exists('dfehc_get_option')) { 78 function dfehc_get_option(string $key, $default) 79 { 80 if (function_exists('get_option')) { 81 return get_option($key, $default); 82 } 83 return $default; 84 } 85 } 86 87 if (!function_exists('dfehc_cache_get')) { 88 function dfehc_cache_get(string $key) 89 { 90 $group = defined('DFEHC_CACHE_GROUP') ? (string) DFEHC_CACHE_GROUP : 'dfehc'; 91 92 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_get')) { 93 $v = wp_cache_get($key, $group); 94 return $v === false ? null : $v; 95 } 96 97 if (function_exists('get_transient')) { 98 $v = get_transient($key); 99 return $v === false ? null : $v; 100 } 101 102 return null; 103 } 104 } 105 45 106 if (!function_exists('dfehc_store_lockfree')) { 46 function dfehc_store_lockfree(string $key, $value, int $ttl): bool { 47 $group = defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc'; 48 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) { 107 function dfehc_store_lockfree(string $key, $value, int $ttl): bool 108 { 109 $group = defined('DFEHC_CACHE_GROUP') ? (string) DFEHC_CACHE_GROUP : 'dfehc'; 110 $ttl = max(1, $ttl); 111 112 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_set')) { 49 113 wp_cache_set($key, $value, $group, $ttl); 50 114 return true; 51 115 } 52 return set_transient($key, $value, $ttl); 53 } 54 } 55 56 if (!function_exists('dfehc_set_transient')) { 57 function dfehc_set_transient(string $key, float $value, float $interval): void { 58 $ttl = (int) apply_filters('dfehc_transient_ttl', max(60, (int) ceil($interval) * 2), $key, $value, $interval); 59 $jitter = 0; 116 117 return function_exists('set_transient') ? (bool) set_transient($key, $value, $ttl) : false; 118 } 119 } 120 121 if (!function_exists('dfehc_clamp')) { 122 function dfehc_clamp(float $v, float $lo, float $hi): float 123 { 124 if (!is_finite($v)) return $lo; 125 return max($lo, min($hi, $v)); 126 } 127 } 128 129 if (!function_exists('dfehc_abs')) { 130 function dfehc_abs(float $v): float 131 { 132 return $v < 0 ? -$v : $v; 133 } 134 } 135 136 if (!function_exists('dfehc_rand_jitter')) { 137 function dfehc_rand_jitter(int $min, int $max): int 138 { 139 $min = (int) $min; 140 $max = (int) $max; 141 if ($max < $min) { $t = $min; $min = $max; $max = $t; } 142 if ($min === $max) return $min; 143 60 144 if (function_exists('random_int')) { 61 try { 62 $jitter = random_int(0, 5); 63 } catch (\Throwable $e) { 64 $jitter = 0; 65 } 66 } 67 $ttl += $jitter; 68 dfehc_set_transient_noautoload($key, $value, $ttl); 69 } 70 } 71 72 if (!function_exists('dfehc_clamp')) { 73 function dfehc_clamp(float $v, float $lo, float $hi): float { 74 return max($lo, min($hi, $v)); 145 try { return (int) random_int($min, $max); } catch (\Throwable $e) {} 146 } 147 if (function_exists('wp_rand')) return (int) wp_rand($min, $max); 148 return (int) mt_rand($min, $max); 75 149 } 76 150 } 77 151 78 152 if (!function_exists('dfehc_weighted_sum')) { 79 function dfehc_weighted_sum(array $factors, array $weights): float { 153 function dfehc_weighted_sum(array $factors, array $weights): float 154 { 80 155 $sum = 0.0; 81 156 foreach ($factors as $k => $v) { 82 $sum += ((float) ($weights[$k] ?? 0.0)) * (float) $v; 83 } 84 return $sum; 157 $w = isset($weights[$k]) ? (float) $weights[$k] : 0.0; 158 $sum += $w * (float) $v; 159 } 160 return (float) $sum; 85 161 } 86 162 } 87 163 88 164 if (!function_exists('dfehc_normalize_weights')) { 89 function dfehc_normalize_weights(array $weights): array { 90 $total = array_sum($weights); 91 if ($total <= 0) { 165 function dfehc_normalize_weights(array $weights): array 166 { 167 $total = 0.0; 168 foreach ($weights as $k => $w) { 169 $weights[$k] = (float) $w; 170 $total += $weights[$k]; 171 } 172 173 if ($total <= 0.0) { 92 174 $n = max(1, count($weights)); 93 $equal = 1 / $n;175 $equal = 1.0 / $n; 94 176 foreach ($weights as $k => $_) { 95 177 $weights[$k] = $equal; … … 97 179 return $weights; 98 180 } 181 99 182 foreach ($weights as $k => $w) { 100 $weights[$k] = $w / $total; 101 } 183 $weights[$k] = (float) ($w / $total); 184 } 185 102 186 return $weights; 103 187 } 104 188 } 105 189 190 if (!function_exists('dfehc_should_write_value')) { 191 function dfehc_should_write_value(?float $prev, float $next, float $eps): bool 192 { 193 if (!is_finite($next)) return false; 194 if ($prev === null || !is_finite($prev)) return true; 195 return dfehc_abs($next - $prev) >= max(0.0, $eps); 196 } 197 } 198 106 199 if (!function_exists('dfehc_apply_exponential_moving_average')) { 107 function dfehc_apply_exponential_moving_average(float $current): float { 108 $alpha = dfehc_clamp((float) get_option(DFEHC_OPTION_EMA_ALPHA, DFEHC_DEFAULT_EMA_ALPHA), 0.01, 1.0); 109 $key = dfehc_scoped_key('dfehc_ema'); 110 $prev = get_transient($key); 111 $ema = ($prev === false) ? $current : $alpha * $current + (1 - $alpha) * (float) $prev; 112 $ttl_default = max(DFEHC_DEFAULT_EMA_TTL, (int) get_option(DFEHC_OPTION_MAX_INTERVAL, DFEHC_DEFAULT_MAX_INTERVAL) * 2); 113 $ttl_default = (int) dfehc_clamp($ttl_default, 60, 86400); 114 $ttl = (int) apply_filters('dfehc_ema_ttl', $ttl_default, $current, $ema); 115 $jitter = 0; 116 if (function_exists('random_int')) { 117 try { 118 $jitter = random_int(0, 5); 119 } catch (\Throwable $e) { 120 $jitter = 0; 121 } 122 } 123 $ttl += $jitter; 124 dfehc_set_transient_noautoload($key, $ema, $ttl); 125 return $ema; 200 function dfehc_apply_exponential_moving_average(float $current): float 201 { 202 static $memo = null; 203 204 $current = is_finite($current) ? $current : 0.0; 205 206 $alphaOpt = dfehc_get_option(DFEHC_OPTION_EMA_ALPHA, DFEHC_DEFAULT_EMA_ALPHA); 207 $alpha = dfehc_clamp((float) $alphaOpt, 0.01, 1.0); 208 209 $key = dfehc_scoped_key('dfehc_ema'); 210 211 $prev = null; 212 if (is_array($memo) && array_key_exists($key, $memo)) { 213 $prev = $memo[$key]; 214 } else { 215 $pv = dfehc_cache_get($key); 216 $prev = (is_numeric($pv) ? (float) $pv : null); 217 } 218 219 $ema = ($prev === null) ? $current : ($alpha * $current + (1.0 - $alpha) * (float) $prev); 220 221 if (!is_finite($ema)) $ema = $current; 222 223 $ttlDefault = max((int) DFEHC_DEFAULT_EMA_TTL, (int) dfehc_get_option(DFEHC_OPTION_MAX_INTERVAL, DFEHC_DEFAULT_MAX_INTERVAL) * 2); 224 $ttlDefault = (int) dfehc_clamp((float) $ttlDefault, 60.0, 86400.0); 225 $ttl = (int) dfehc_apply_filters('dfehc_ema_ttl', $ttlDefault, $current, $ema); 226 $ttl = max(30, $ttl + dfehc_rand_jitter(0, 5)); 227 228 $eps = (float) dfehc_apply_filters('dfehc_ema_write_epsilon', 0.05, $current, $ema, $prev); 229 $eps = max(0.0, $eps); 230 231 if (function_exists('dfehc_set_transient_noautoload') && dfehc_should_write_value($prev, $ema, $eps)) { 232 dfehc_set_transient_noautoload($key, $ema, $ttl); 233 } 234 235 if (!is_array($memo)) $memo = []; 236 $memo[$key] = $ema; 237 238 return (float) $ema; 239 } 240 } 241 242 if (!function_exists('dfehc_defensive_stance')) { 243 function dfehc_defensive_stance(float $proposed): float 244 { 245 static $memo = null; 246 247 $proposed = is_finite($proposed) ? $proposed : 0.0; 248 249 $key = dfehc_scoped_key('dfehc_prev_int'); 250 251 $previous = null; 252 if (is_array($memo) && array_key_exists($key, $memo)) { 253 $previous = $memo[$key]; 254 } else { 255 $pv = dfehc_cache_get($key); 256 $previous = (is_numeric($pv) ? (float) $pv : null); 257 } 258 259 if ($previous === null) { 260 $ttl = (int) dfehc_apply_filters('dfehc_prev_interval_ttl', 1800); 261 $ttl = max(60, $ttl + dfehc_rand_jitter(0, 5)); 262 263 if (function_exists('dfehc_set_transient_noautoload')) { 264 dfehc_set_transient_noautoload($key, $proposed, $ttl); 265 } 266 267 if (!is_array($memo)) $memo = []; 268 $memo[$key] = $proposed; 269 270 return (float) $proposed; 271 } 272 273 $previous = (float) $previous; 274 275 $max_drop = dfehc_clamp((float) dfehc_get_option(DFEHC_OPTION_MAX_DECREASE_RATE, DFEHC_DEFAULT_MAX_DECREASE_RATE), 0.0, 0.95); 276 $max_rise = dfehc_clamp((float) dfehc_apply_filters('dfehc_max_increase_rate', 0.5), 0.0, 5.0); 277 278 $lower = $previous * (1.0 - $max_drop); 279 $upper = $previous * (1.0 + $max_rise); 280 281 $final = dfehc_clamp($proposed, $lower, $upper); 282 283 $ttl = (int) dfehc_apply_filters('dfehc_prev_interval_ttl', 1800); 284 $ttl = max(60, $ttl + dfehc_rand_jitter(0, 5)); 285 286 $eps = (float) dfehc_apply_filters('dfehc_prev_interval_write_epsilon', 0.5, $proposed, $final, $previous); 287 $eps = max(0.0, $eps); 288 289 if (function_exists('dfehc_set_transient_noautoload') && dfehc_should_write_value($previous, $final, $eps)) { 290 dfehc_set_transient_noautoload($key, $final, $ttl); 291 } 292 293 if (!is_array($memo)) $memo = []; 294 $memo[$key] = $final; 295 296 return (float) $final; 297 } 298 } 299 300 if (!function_exists('dfehc_set_transient')) { 301 function dfehc_set_transient(string $key, float $value, float $interval): void 302 { 303 $ttlBase = max(60, (int) ceil(max(0.0, $interval)) * 2); 304 $ttl = (int) dfehc_apply_filters('dfehc_transient_ttl', $ttlBase, $key, $value, $interval); 305 $ttl = max(30, $ttl + dfehc_rand_jitter(0, 5)); 306 307 if (function_exists('dfehc_set_transient_noautoload')) { 308 dfehc_set_transient_noautoload($key, $value, $ttl); 309 } else { 310 dfehc_store_lockfree($key, $value, $ttl); 311 } 312 } 313 } 314 315 if (!function_exists('dfehc_apply_factor_overrides')) { 316 function dfehc_apply_factor_overrides(array $factors, float $time_elapsed, float $load_average, float $server_response_time): array 317 { 318 $factors = (array) dfehc_apply_filters('dfehc_interval_factors', $factors, $time_elapsed, $load_average, $server_response_time); 319 foreach ($factors as $k => $v) { 320 $factors[$k] = is_numeric($v) ? (float) $v : 0.0; 321 } 322 return $factors; 126 323 } 127 324 } 128 325 129 326 if (!function_exists('dfehc_calculate_recommended_interval')) { 130 function dfehc_calculate_recommended_interval(float $time_elapsed, float $load_average, float $server_response_time): float { 131 $min_interval = max(1, (int) get_option(DFEHC_OPTION_MIN_INTERVAL, DFEHC_DEFAULT_MIN_INTERVAL)); 132 $max_interval = max($min_interval, (int) get_option(DFEHC_OPTION_MAX_INTERVAL, DFEHC_DEFAULT_MAX_INTERVAL)); 133 $max_server_load = max(0.1, (float) get_option(DFEHC_OPTION_MAX_SERVER_LOAD, DFEHC_DEFAULT_MAX_SERVER_LOAD)); 134 $max_response_time = max(0.1, (float) get_option(DFEHC_OPTION_MAX_RESPONSE_TIME, DFEHC_DEFAULT_MAX_RESPONSE_TIME)); 135 136 $custom_norm = apply_filters('dfehc_normalize_load', null, $load_average); 327 function dfehc_calculate_recommended_interval(float $time_elapsed, float $load_average, float $server_response_time): float 328 { 329 $min_interval = max(1, (int) dfehc_get_option(DFEHC_OPTION_MIN_INTERVAL, DFEHC_DEFAULT_MIN_INTERVAL)); 330 $max_interval = max($min_interval, (int) dfehc_get_option(DFEHC_OPTION_MAX_INTERVAL, DFEHC_DEFAULT_MAX_INTERVAL)); 331 332 $max_server_load = (float) dfehc_get_option(DFEHC_OPTION_MAX_SERVER_LOAD, DFEHC_DEFAULT_MAX_SERVER_LOAD); 333 $max_server_load = max(0.1, $max_server_load); 334 335 $max_response_time = (float) dfehc_get_option(DFEHC_OPTION_MAX_RESPONSE_TIME, DFEHC_DEFAULT_MAX_RESPONSE_TIME); 336 $max_response_time = max(0.1, $max_response_time); 337 338 $time_elapsed = max(0.0, $time_elapsed); 339 $load_average = is_finite($load_average) ? $load_average : 0.0; 340 $server_response_time = is_finite($server_response_time) ? $server_response_time : 0.0; 341 342 $custom_norm = dfehc_apply_filters('dfehc_normalize_load', null, $load_average); 137 343 if (is_numeric($custom_norm)) { 138 344 $la = dfehc_clamp((float) $custom_norm, 0.0, 1.0); 139 345 } else { 140 346 if ($load_average <= 1.0) { 141 $la = dfehc_clamp( $load_average, 0.0, 1.0);347 $la = dfehc_clamp((float) $load_average, 0.0, 1.0); 142 348 } elseif ($load_average <= 100.0) { 143 $la = dfehc_clamp( $load_average / 100.0, 0.0, 1.0);349 $la = dfehc_clamp((float) $load_average / 100.0, 0.0, 1.0); 144 350 } else { 145 $assumed_cores = (float) apply_filters('dfehc_assumed_cores_for_normalization', 8.0);146 $la = dfehc_clamp( $load_average / max(1.0, $assumed_cores), 0.0, 1.0);351 $assumed_cores = (float) dfehc_apply_filters('dfehc_assumed_cores_for_normalization', 8.0); 352 $la = dfehc_clamp((float) $load_average / max(1.0, $assumed_cores), 0.0, 1.0); 147 353 } 148 354 } 149 355 150 356 $msl_ratio = $max_server_load > 1.0 ? ($max_server_load / 100.0) : $max_server_load; 151 if ($msl_ratio <= 0) $msl_ratio = 1.0; 357 if (!is_finite($msl_ratio) || $msl_ratio <= 0.0) $msl_ratio = 1.0; 358 152 359 $server_load_factor = dfehc_clamp($la / $msl_ratio, 0.0, 1.0); 153 360 154 $rt_units = apply_filters('dfehc_response_time_is_ms', null, $server_response_time);361 $rt_units = dfehc_apply_filters('dfehc_response_time_is_ms', null, $server_response_time); 155 362 $rt = (float) $server_response_time; 156 363 if ($rt_units === true) { 157 364 $rt = $rt / 1000.0; 158 365 } elseif ($rt_units === null) { 159 if ($rt > ($max_response_time * 3 ) && $rt <= 60000.0) {366 if ($rt > ($max_response_time * 3.0) && $rt <= 60000.0) { 160 367 $rt = $rt / 1000.0; 161 368 } 162 369 } 163 $rt = max(0.0, $rt); 164 $response_time_factor = $rt > 0 ? dfehc_clamp($rt / $max_response_time, 0.0, 1.0) : 0.0; 370 $rt = max(0.0, (float) $rt); 371 372 $response_time_factor = $rt > 0.0 ? dfehc_clamp($rt / $max_response_time, 0.0, 1.0) : 0.0; 165 373 166 374 $factors = [ 167 'user_activity' => dfehc_clamp( $time_elapsed / $max_interval, 0.0, 1.0),375 'user_activity' => dfehc_clamp(($max_interval > 0 ? $time_elapsed / $max_interval : 0.0), 0.0, 1.0), 168 376 'server_load' => $server_load_factor, 169 377 'response_time' => $response_time_factor, 170 378 ]; 171 $factors = (array) apply_filters('dfehc_interval_factors', $factors, $time_elapsed, $load_average, $server_response_time); 172 173 $slider = dfehc_clamp((float) get_option(DFEHC_OPTION_PRIORITY_SLIDER, 0.0), -1.0, 1.0); 379 $factors = dfehc_apply_factor_overrides($factors, $time_elapsed, $load_average, $server_response_time); 380 381 $slider = dfehc_clamp((float) dfehc_get_option(DFEHC_OPTION_PRIORITY_SLIDER, 0.0), -1.0, 1.0); 382 174 383 $weights = [ 175 384 'user_activity' => 0.4 - 0.2 * $slider, 176 'server_load' => (0.6 + 0.2 * $slider) / 2 ,177 'response_time' => (0.6 + 0.2 * $slider) / 2 ,385 'server_load' => (0.6 + 0.2 * $slider) / 2.0, 386 'response_time' => (0.6 + 0.2 * $slider) / 2.0, 178 387 ]; 179 $weights = (array) apply_filters('dfehc_interval_weights', $weights, $slider);388 $weights = (array) dfehc_apply_filters('dfehc_interval_weights', $weights, $slider); 180 389 $weights = dfehc_normalize_weights($weights); 181 390 182 $raw = $min_interval + dfehc_weighted_sum($factors, $weights) * ($max_interval - $min_interval); 391 $raw = (float) ($min_interval + dfehc_weighted_sum($factors, $weights) * ($max_interval - $min_interval)); 392 $raw = dfehc_clamp($raw, (float) $min_interval, (float) $max_interval); 393 183 394 $smoothed = dfehc_apply_exponential_moving_average($raw); 184 $lagged = dfehc_defensive_stance($smoothed);185 186 $final = (float) apply_filters('dfehc_interval_snap', $lagged, $min_interval, $max_interval);187 $final = dfehc_clamp( $final, (float) $min_interval, (float) $max_interval);188 189 return $final;395 $lagged = dfehc_defensive_stance($smoothed); 396 397 $final = (float) dfehc_apply_filters('dfehc_interval_snap', $lagged, $min_interval, $max_interval); 398 $final = dfehc_clamp((float) $final, (float) $min_interval, (float) $max_interval); 399 400 return (float) $final; 190 401 } 191 402 } 192 403 193 404 if (!function_exists('dfehc_calculate_interval_based_on_duration')) { 194 function dfehc_calculate_interval_based_on_duration(float $avg_duration, float $load_average): float { 195 $min_interval = max(1, (int) get_option(DFEHC_OPTION_MIN_INTERVAL, DFEHC_DEFAULT_MIN_INTERVAL)); 196 $max_interval = max($min_interval, (int) get_option(DFEHC_OPTION_MAX_INTERVAL, DFEHC_DEFAULT_MAX_INTERVAL)); 197 if ($avg_duration <= $min_interval) return (float) $min_interval; 198 if ($avg_duration >= $max_interval) return (float) $max_interval; 199 $proposed = dfehc_calculate_recommended_interval($avg_duration, $load_average, 0.0); 200 return dfehc_defensive_stance($proposed); 405 function dfehc_calculate_interval_based_on_duration(float $avg_duration, float $load_average): float 406 { 407 $min_interval = max(1, (int) dfehc_get_option(DFEHC_OPTION_MIN_INTERVAL, DFEHC_DEFAULT_MIN_INTERVAL)); 408 $max_interval = max($min_interval, (int) dfehc_get_option(DFEHC_OPTION_MAX_INTERVAL, DFEHC_DEFAULT_MAX_INTERVAL)); 409 410 if (!is_finite($avg_duration)) $avg_duration = 0.0; 411 if (!is_finite($load_average)) $load_average = 0.0; 412 413 if ($avg_duration <= $min_interval) return (float) $min_interval; 414 if ($avg_duration >= $max_interval) return (float) $max_interval; 415 416 return (float) dfehc_calculate_recommended_interval($avg_duration, $load_average, 0.0); 201 417 } 202 418 } 203 419 204 420 if (!function_exists('dfehc_smooth_moving')) { 205 function dfehc_smooth_moving(array $values): float { 421 function dfehc_smooth_moving(array $values): float 422 { 206 423 if (!$values) return 0.0; 207 $window = max(1, (int) get_option(DFEHC_OPTION_SMA_WINDOW, DFEHC_DEFAULT_SMA_WINDOW));424 $window = max(1, (int) dfehc_get_option(DFEHC_OPTION_SMA_WINDOW, DFEHC_DEFAULT_SMA_WINDOW)); 208 425 $subset = array_slice($values, -$window); 209 426 if (!$subset) return 0.0; 210 return array_sum($subset) / count($subset); 211 } 212 } 213 214 if (!function_exists('dfehc_defensive_stance')) { 215 function dfehc_defensive_stance(float $proposed): float { 216 $key = dfehc_scoped_key('dfehc_prev_int'); 217 $previous = get_transient($key); 218 if ($previous === false) { 219 $ttl = (int) apply_filters('dfehc_prev_interval_ttl', 1800); 220 $jitter = 0; 221 if (function_exists('random_int')) { 222 try { 223 $jitter = random_int(0, 5); 224 } catch (\Throwable $e) { 225 $jitter = 0; 226 } 227 } 228 $ttl += $jitter; 229 dfehc_set_transient_noautoload($key, $proposed, $ttl); 230 return $proposed; 231 } 232 $previous = (float) $previous; 233 $max_drop = dfehc_clamp((float) get_option(DFEHC_OPTION_MAX_DECREASE_RATE, DFEHC_DEFAULT_MAX_DECREASE_RATE), 0.0, 0.95); 234 $max_rise = dfehc_clamp((float) apply_filters('dfehc_max_increase_rate', 0.5), 0.0, 5.0); 235 $lower = $previous * (1 - $max_drop); 236 $upper = $previous * (1 + $max_rise); 237 $final = dfehc_clamp($proposed, $lower, $upper); 238 $ttl = (int) apply_filters('dfehc_prev_interval_ttl', 1800); 239 $jitter = 0; 240 if (function_exists('random_int')) { 241 try { 242 $jitter = random_int(0, 5); 243 } catch (\Throwable $e) { 244 $jitter = 0; 245 } 246 } 247 $ttl += $jitter; 248 dfehc_set_transient_noautoload($key, $final, $ttl); 249 return $final; 250 } 251 } 427 428 $sum = 0.0; 429 $cnt = 0; 430 foreach ($subset as $v) { 431 if (is_numeric($v) && is_finite((float) $v)) { 432 $sum += (float) $v; 433 $cnt++; 434 } 435 } 436 return $cnt > 0 ? (float) ($sum / $cnt) : 0.0; 437 } 438 } -
dynamic-front-end-heartbeat-control/trunk/engine/server-load.php
r3427163 r3461136 9 9 } 10 10 11 if (!defined('DFEHC_CACHE_GROUP')) { 12 define('DFEHC_CACHE_GROUP', 'dfehc'); 13 } 14 if (!defined('DFEHC_SERVER_LOAD_TTL')) { 15 define('DFEHC_SERVER_LOAD_TTL', 180); 16 } 17 if (!defined('DFEHC_SERVER_LOAD_CACHE_KEY')) { 18 define('DFEHC_SERVER_LOAD_CACHE_KEY', 'dfehc:server_load'); 19 } 20 if (!defined('DFEHC_SERVER_LOAD_PAYLOAD_KEY')) { 21 define('DFEHC_SERVER_LOAD_PAYLOAD_KEY', 'dfehc_server_load_payload'); 22 } 11 if (!defined('DFEHC_CACHE_GROUP')) define('DFEHC_CACHE_GROUP', 'dfehc'); 12 if (!defined('DFEHC_SERVER_LOAD_TTL')) define('DFEHC_SERVER_LOAD_TTL', 180); 13 if (!defined('DFEHC_SERVER_LOAD_CACHE_KEY')) define('DFEHC_SERVER_LOAD_CACHE_KEY', 'dfehc:server_load'); 14 if (!defined('DFEHC_SERVER_LOAD_PAYLOAD_KEY')) define('DFEHC_SERVER_LOAD_PAYLOAD_KEY', 'dfehc_server_load_payload'); 15 if (!defined('DFEHC_SERVER_LOAD_SCALAR_KEY')) define('DFEHC_SERVER_LOAD_SCALAR_KEY', 'dfehc:server_load_scalar'); 23 16 24 17 if (!function_exists('dfehc_server_load_ttl')) { 25 18 function dfehc_server_load_ttl(): int 26 19 { 27 return (int) apply_filters('dfehc_server_load_ttl', (int) DFEHC_SERVER_LOAD_TTL); 20 $ttl = (int) apply_filters('dfehc_server_load_ttl', (int) DFEHC_SERVER_LOAD_TTL); 21 return max(30, $ttl); 28 22 } 29 23 } … … 58 52 { 59 53 static $t = ''; 60 if ($t !== '') return $t; 61 $host = @php_uname('n') ?: (defined('WP_HOME') ? WP_HOME : (function_exists('home_url') ? home_url() : 'unknown')); 54 if ($t !== '') { 55 return $t; 56 } 57 $host = @php_uname('n') ?: (defined('WP_HOME') ? (string) WP_HOME : (function_exists('home_url') ? (string) home_url() : 'unknown')); 62 58 $salt = defined('DB_NAME') ? (string) DB_NAME : ''; 63 59 return $t = substr(md5((string) $host . $salt), 0, 10); … … 90 86 $is_valid_ip = static function (string $ip, bool $publicOnly): bool { 91 87 $ip = trim($ip); 92 if ($ip === '') return false; 93 88 if ($ip === '') { 89 return false; 90 } 94 91 if ($publicOnly) { 95 92 return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false; … … 148 145 } 149 146 150 if (!function_exists('dfehc_get_redis_server')) {151 function dfehc_get_redis_server(): string152 {153 $h = getenv('REDIS_HOST');154 return $h ? (string) $h : '127.0.0.1';155 }156 }157 if (!function_exists('dfehc_get_redis_port')) {158 function dfehc_get_redis_port(): int159 {160 $p = getenv('REDIS_PORT');161 return $p && ctype_digit((string) $p) ? (int) $p : 6379;162 }163 }164 if (!function_exists('dfehc_get_memcached_server')) {165 function dfehc_get_memcached_server(): string166 {167 $h = getenv('MEMCACHED_HOST');168 return $h ? (string) $h : '127.0.0.1';169 }170 }171 if (!function_exists('dfehc_get_memcached_port')) {172 function dfehc_get_memcached_port(): int173 {174 $p = getenv('MEMCACHED_PORT');175 return $p && ctype_digit((string) $p) ? (int) $p : 11211;176 }177 }178 179 147 function dfehc_get_cache_client(): array 180 148 { … … 182 150 static $last_probe_ts = 0; 183 151 184 $retryAfter = (int) apply_filters('dfehc_cache_retry_after', 60);152 $retryAfter = (int) apply_filters('dfehc_cache_retry_after', 90); 185 153 $retryAfter = max(0, $retryAfter); 186 154 … … 204 172 } 205 173 206 if (class_exists('Redis')) {207 try {208 $redis = new Redis();209 if ($redis->connect(dfehc_get_redis_server(), dfehc_get_redis_port(), 1.0)) {210 $pass = apply_filters('dfehc_redis_auth', getenv('REDIS_PASSWORD') ?: null);211 $user = apply_filters('dfehc_redis_user', getenv('REDIS_USERNAME') ?: null);212 213 if (method_exists($redis, 'auth')) {214 if ($user && $pass) {215 $ok = $redis->auth([$user, $pass]);216 if ($ok === false) {217 throw new RuntimeException('Redis auth failed');218 }219 } elseif ($pass) {220 $ok = $redis->auth($pass);221 if ($ok === false) {222 throw new RuntimeException('Redis auth failed');223 }224 }225 }226 227 return $cached = ['client' => $redis, 'type' => 'redis'];228 }229 } catch (Throwable $e) {230 dfehc_debug_log('DFEHC Redis connect error: ' . $e->getMessage());231 }232 }233 234 if (class_exists('Memcached')) {235 try {236 $mc = new Memcached('dfehc');237 if (!$mc->getServerList()) {238 $mc->addServer(dfehc_get_memcached_server(), dfehc_get_memcached_port());239 }240 241 $user = getenv('MEMCACHED_USERNAME');242 $pass = getenv('MEMCACHED_PASSWORD');243 if ($user && $pass && method_exists($mc, 'setSaslAuthData')) {244 $mc->setOption(Memcached::OPT_BINARY_PROTOCOL, true);245 $mc->setSaslAuthData($user, $pass);246 }247 248 $versions = $mc->getVersion();249 $first = is_array($versions) ? reset($versions) : false;250 $ok = $first && $first !== '0.0.0' && $first !== '0.0.0.0';251 if ($ok) {252 return $cached = ['client' => $mc, 'type' => 'memcached'];253 }254 } catch (Throwable $e) {255 dfehc_debug_log('DFEHC Memcached connect error: ' . $e->getMessage());256 }257 }258 259 174 return $cached = ['client' => null, 'type' => 'none']; 260 175 } … … 262 177 function dfehc_cache_server_load(float $value): void 263 178 { 264 ['client' => $client, 'type' => $type] = dfehc_get_cache_client(); 265 if (!$client) { 266 return; 267 } 268 $key = dfehc_key(DFEHC_SERVER_LOAD_CACHE_KEY); 179 $value = max(0.0, (float) $value); 180 269 181 $ttl = dfehc_server_load_ttl(); 270 182 $jitter = 0; 271 183 if (function_exists('random_int')) { 272 184 try { 273 $jitter = random_int(0, 5);185 $jitter = random_int(0, 8); 274 186 } catch (Throwable $e) { 275 187 $jitter = 0; 276 188 } 277 189 } 278 $ttl += $jitter; 190 $ttl = max(30, $ttl + $jitter); 191 192 $scalarKey = dfehc_key(DFEHC_SERVER_LOAD_SCALAR_KEY); 193 194 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_set')) { 195 wp_cache_set($scalarKey, $value, DFEHC_CACHE_GROUP, $ttl); 196 } 197 198 $key = dfehc_key(DFEHC_SERVER_LOAD_CACHE_KEY); 199 200 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_set')) { 201 wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $ttl); 202 } 203 204 $cc = dfehc_get_cache_client(); 205 $client = isset($cc['client']) ? $cc['client'] : null; 206 $type = isset($cc['type']) ? (string) $cc['type'] : 'none'; 207 208 if (!$client) { 209 return; 210 } 211 279 212 try { 280 213 if ($type === 'redis') { … … 291 224 { 292 225 $payloadKey = dfehc_key(DFEHC_SERVER_LOAD_PAYLOAD_KEY); 226 293 227 $payload = null; 294 if ( wp_using_ext_object_cache()) {228 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) { 295 229 $payload = wp_cache_get($payloadKey, DFEHC_CACHE_GROUP); 296 230 if ($payload === false) { … … 300 234 $payload = get_transient($payloadKey); 301 235 } 236 302 237 if (!(is_array($payload) && isset($payload['raw'], $payload['cores'], $payload['source']))) { 303 238 if (!dfehc_load_acquire_lock()) { 239 $fallback = dfehc_get_server_load_persistent(); 240 if ($fallback > 0.0) { 241 return (float) $fallback; 242 } 304 243 return dfehc_unknown_load(); 305 244 } 245 306 246 try { 307 247 $data = dfehc_detect_load_raw_with_source(); 308 248 $payload = [ 309 'raw' => (float) $data['load'],310 'cores' => dfehc_get_cpu_cores(),249 'raw' => (float) $data['load'], 250 'cores' => dfehc_get_cpu_cores(), 311 251 'source' => (string) $data['source'], 312 252 ]; 253 313 254 $ttl = dfehc_server_load_ttl(); 314 255 $jitter = 0; 315 256 if (function_exists('random_int')) { 316 257 try { 317 $jitter = random_int(0, 5);258 $jitter = random_int(0, 8); 318 259 } catch (Throwable $e) { 319 260 $jitter = 0; 320 261 } 321 262 } 322 $ttl += $jitter; 323 if (wp_using_ext_object_cache()) { 263 $ttl = max(30, $ttl + $jitter); 264 265 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) { 324 266 wp_cache_set($payloadKey, $payload, DFEHC_CACHE_GROUP, $ttl); 325 267 } else { … … 330 272 } 331 273 } 332 $raw = (float) $payload['raw']; 333 $cores = (int) ($payload['cores'] ?: dfehc_get_cpu_cores()); 274 275 $raw = (float) $payload['raw']; 276 $cores = (int) ($payload['cores'] ?: dfehc_get_cpu_cores()); 334 277 $source = (string) $payload['source']; 278 335 279 $divide = (bool) apply_filters('dfehc_divide_cpu_load', true, $raw, $cores, $source); 336 $load = ($source === 'cpu_load' && $divide && $cores > 0) ? $raw / $cores : $raw; 280 $load = ($source === 'cpu_load' && $divide && $cores > 0) ? ($raw / $cores) : $raw; 281 337 282 $load = max(0.0, (float) $load); 338 return (float) apply_filters('dfehc_contextual_load_value', $load, $source); 283 284 $max = apply_filters('dfehc_server_load_max', null, $source); 285 if (is_numeric($max)) { 286 $load = min((float) $max, $load); 287 } 288 289 $load = (float) apply_filters('dfehc_contextual_load_value', $load, $source); 290 291 if ($load > 0.0) { 292 dfehc_cache_server_load($load); 293 } 294 295 return (float) $load; 339 296 } 340 297 … … 343 300 if (function_exists('sys_getloadavg')) { 344 301 $arr = sys_getloadavg(); 345 if ( $arr && isset($arr[0]) && $arr[0] >=0) {302 if (is_array($arr) && isset($arr[0]) && is_numeric($arr[0]) && (float) $arr[0] >= 0.0) { 346 303 return ['load' => (float) $arr[0], 'source' => 'cpu_load']; 347 304 } 348 305 } 306 349 307 if (is_readable('/proc/loadavg')) { 350 $txt = file_get_contents('/proc/loadavg');308 $txt = @file_get_contents('/proc/loadavg'); 351 309 if ($txt !== false) { 352 310 $parts = explode(' ', trim($txt)); 353 if (isset($parts[0]) ) {311 if (isset($parts[0]) && is_numeric($parts[0]) && (float) $parts[0] >= 0.0) { 354 312 return ['load' => (float) $parts[0], 'source' => 'cpu_load']; 355 313 } 356 314 } 357 315 } 358 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); 359 if (function_exists('shell_exec') && !in_array('shell_exec', $disabled, true) && !ini_get('open_basedir')) { 360 $out = shell_exec('LANG=C uptime 2>&1'); 361 if ($out && preg_match('/load average[s]?:\s*([0-9.]+)/', $out, $m)) { 362 return ['load' => (float) $m[1], 'source' => 'cpu_load']; 363 } 364 } 316 317 $allow_shell = (bool) apply_filters('dfehc_allow_shell_uptime', false); 318 if ($allow_shell) { 319 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); 320 $can_shell = function_exists('shell_exec') && !in_array('shell_exec', $disabled, true) && !ini_get('open_basedir'); 321 if ($can_shell) { 322 $out = @shell_exec('LANG=C uptime 2>&1'); 323 if (is_string($out) && $out !== '' && preg_match('/load average[s]?:\s*([0-9.]+)/', $out, $m)) { 324 $v = (float) $m[1]; 325 if ($v >= 0.0) { 326 return ['load' => $v, 'source' => 'cpu_load']; 327 } 328 } 329 } 330 } 331 332 $estimator_loaded = false; 333 365 334 if (defined('DFEHC_PLUGIN_PATH')) { 366 335 $est = rtrim((string) DFEHC_PLUGIN_PATH, "/\\") . '/defibrillator/load-estimator.php'; 367 if ( file_exists($est)) {336 if (is_string($est) && $est !== '' && file_exists($est)) { 368 337 require_once $est; 369 if (class_exists('DynamicHeartbeat\\Dfehc_ServerLoadEstimator')) { 370 $pct = DynamicHeartbeat\Dfehc_ServerLoadEstimator::get_server_load(); 371 if (is_numeric($pct)) { 372 $cores = dfehc_get_cpu_cores(); 373 $cpuLoad = ((float) $pct / 100.0) * max(1, $cores); 374 return ['load' => $cpuLoad, 'source' => 'cpu_load']; 375 } 376 } 377 } 378 } 338 $estimator_loaded = true; 339 } 340 } 341 342 if (!$estimator_loaded && class_exists('DynamicHeartbeat\\Dfehc_ServerLoadEstimator')) { 343 $estimator_loaded = true; 344 } 345 346 if ($estimator_loaded && class_exists('DynamicHeartbeat\\Dfehc_ServerLoadEstimator')) { 347 $pct = DynamicHeartbeat\Dfehc_ServerLoadEstimator::get_server_load(); 348 if (is_numeric($pct)) { 349 $cores = dfehc_get_cpu_cores(); 350 $raw = ((float) $pct / 100.0) * max(1, $cores); 351 $raw = max(0.0, $raw); 352 return ['load' => $raw, 'source' => 'cpu_load']; 353 } 354 } 355 379 356 if (function_exists('do_action')) { 380 357 do_action('dfehc_load_detection_fell_back'); 381 358 } 359 382 360 return ['load' => dfehc_unknown_load(), 'source' => 'fallback']; 383 361 } … … 390 368 return (int) $cores; 391 369 } 392 $override = getenv('DFEHC_CPU_CORES'); 393 if ($override && ctype_digit((string) $override) && (int) $override > 0) { 370 371 $override = getenv('DFEHC_CPU_CORES') ?: (defined('DFEHC_CPU_CORES') ? DFEHC_CPU_CORES : null); 372 if ($override !== null && is_numeric($override) && (int) $override > 0) { 394 373 $cores = (int) $override; 395 374 return (int) $cores; 396 375 } 376 377 $cores = 1; 378 397 379 if (is_readable('/sys/fs/cgroup/cpu.max')) { 398 $line = file_get_contents('/sys/fs/cgroup/cpu.max'); 399 if ($line !== false) { 400 [$quota, $period] = explode(' ', trim($line)); 401 if ($quota !== 'max') { 402 $q = (int) $quota; 403 $p = (int) $period; 404 if ($q > 0 && $p > 0) { 405 $cores = max(1, (int) ceil($q / $p)); 406 $cores = (int) apply_filters('dfehc_cpu_cores', (int) $cores); 407 return (int) $cores; 380 $line = trim((string) @file_get_contents('/sys/fs/cgroup/cpu.max')); 381 if ($line !== '') { 382 $parts = preg_split('/\s+/', $line); 383 if (is_array($parts) && count($parts) >= 2) { 384 $quota = is_numeric($parts[0]) ? (float) $parts[0] : 0.0; 385 $period = is_numeric($parts[1]) ? (float) $parts[1] : 0.0; 386 if ($period > 0.0 && $quota > 0.0) { 387 $cores = max(1, (int) ceil($quota / $period)); 408 388 } 409 389 } 410 390 } 411 391 } 412 if (is_readable('/proc/self/cgroup')) { 413 $content = file_get_contents('/proc/self/cgroup'); 414 if ($content !== false && preg_match('/^[0-9]+:[^:]*cpu[^:]*:(.+)$/m', $content, $m)) { 415 $path = '/' . ltrim(trim($m[1]), '/'); 416 $base = '/sys/fs/cgroup' . $path; 417 $quotaFile = "$base/cpu.cfs_quota_us"; 418 $periodFile = "$base/cpu.cfs_period_us"; 419 if (is_readable($quotaFile) && is_readable($periodFile)) { 420 $quota = (int) file_get_contents($quotaFile); 421 $period = (int) file_get_contents($periodFile); 422 if ($quota > 0 && $period > 0) { 423 $cores = max(1, (int) ceil($quota / $period)); 424 $cores = (int) apply_filters('dfehc_cpu_cores', (int) $cores); 425 return (int) $cores; 392 393 if ($cores === 1 && is_readable('/proc/self/cgroup')) { 394 $content = (string) @file_get_contents('/proc/self/cgroup'); 395 if ($content !== '') { 396 if (preg_match('/0::\/(.+)$/m', $content, $m) || preg_match('/cpu[^:]*:(.+)$/m', $content, $m)) { 397 $rel_path = '/' . ltrim(trim($m[1]), '/'); 398 $base = '/sys/fs/cgroup' . $rel_path; 399 $quotaFile = $base . '/cpu.max'; 400 if (is_readable($quotaFile)) { 401 $line = trim((string) @file_get_contents($quotaFile)); 402 if ($line !== '') { 403 $parts = preg_split('/\s+/', $line); 404 if (is_array($parts) && count($parts) >= 2) { 405 $quota = is_numeric($parts[0]) ? (float) $parts[0] : 0.0; 406 $period = is_numeric($parts[1]) ? (float) $parts[1] : 0.0; 407 if ($period > 0.0 && $quota > 0.0) { 408 $cores = max(1, (int) ceil($quota / $period)); 409 } 410 } 411 } 426 412 } 427 413 } 428 414 } 429 415 } 430 if (is_readable('/sys/fs/cgroup/cpu/cpu.cfs_quota_us') && is_readable('/sys/fs/cgroup/cpu/cpu.cfs_period_us')) { 431 $quota = (int) file_get_contents('/sys/fs/cgroup/cpu/cpu.cfs_quota_us'); 432 $period = (int) file_get_contents('/sys/fs/cgroup/cpu/cpu.cfs_period_us'); 433 if ($quota > 0 && $period > 0) { 434 $cores = max(1, (int) ceil($quota / $period)); 435 $cores = (int) apply_filters('dfehc_cpu_cores', (int) $cores); 436 return (int) $cores; 437 } 438 } 439 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); 440 if (function_exists('shell_exec') && !in_array('shell_exec', $disabled, true) && !ini_get('open_basedir')) { 441 $n = shell_exec('nproc 2>/dev/null'); 442 if ($n && ctype_digit(trim((string) $n))) { 443 $cores = max(1, (int) trim((string) $n)); 444 $cores = (int) apply_filters('dfehc_cpu_cores', (int) $cores); 445 return (int) $cores; 446 } 447 } 448 if (is_readable('/proc/cpuinfo')) { 449 $info = file_get_contents('/proc/cpuinfo'); 450 if ($info !== false) { 451 $cnt = preg_match_all('/^processor/m', $info); 452 if ($cnt) { 453 $cores = (int) $cnt; 454 $cores = (int) apply_filters('dfehc_cpu_cores', (int) $cores); 455 return (int) $cores; 416 417 if ($cores === 1 && is_readable('/proc/cpuinfo')) { 418 $info = (string) @file_get_contents('/proc/cpuinfo'); 419 if ($info !== '') { 420 $cnt = preg_match_all('/^processor\s*:/m', $info); 421 if ($cnt > 0) { 422 $cores = $cnt; 456 423 } 457 424 } 458 425 } 459 $cores = 1; 460 $cores = (int) apply_filters('dfehc_cpu_cores', (int) $cores);426 427 $cores = (int) apply_filters('dfehc_cpu_cores', max(1, $cores)); 461 428 return (int) $cores; 462 429 } … … 465 432 function dfehc_log_server_load(): void 466 433 { 434 if (!apply_filters('dfehc_enable_load_logging', false)) { 435 return; 436 } 437 467 438 $load = dfehc_get_server_load(); 468 439 $optKey = 'dfehc_server_load_logs_' . dfehc_blog_id() . '_' . dfehc_host_token(); 440 441 $max_entries = (int) apply_filters('dfehc_load_log_max_entries', 720); 442 $max_entries = max(50, min(5000, $max_entries)); 443 444 $retention = (int) apply_filters('dfehc_load_log_retention_seconds', DAY_IN_SECONDS); 445 $retention = max(300, $retention); 446 447 $max_bytes = (int) apply_filters('dfehc_load_log_option_max_bytes', 1048576); 448 $max_bytes = max(65536, $max_bytes); 449 450 global $wpdb; 451 if ($wpdb instanceof wpdb && !empty($wpdb->options)) { 452 $len = $wpdb->get_var($wpdb->prepare( 453 "SELECT LENGTH(option_value) FROM {$wpdb->options} WHERE option_name = %s LIMIT 1", 454 $optKey 455 )); 456 if (is_numeric($len) && (int) $len > $max_bytes) { 457 update_option($optKey, [['timestamp' => time(), 'load' => (float) $load]], false); 458 $wpdb->query($wpdb->prepare( 459 "UPDATE {$wpdb->options} SET autoload='no' WHERE option_name=%s AND autoload<>'no' LIMIT 1", 460 $optKey 461 )); 462 return; 463 } 464 } 465 469 466 $logs = get_option($optKey, []); 470 467 if (!is_array($logs)) { 471 468 $logs = []; 472 469 } 470 473 471 $now = time(); 474 $cutoff = $now - DAY_IN_SECONDS; 475 $logs = array_filter( 476 $logs, 477 static function (array $row) use ($cutoff): bool { 478 return isset($row['timestamp']) && $row['timestamp'] >= $cutoff; 479 } 480 ); 481 if (count($logs) > 2000) { 482 $logs = array_slice($logs, -2000); 483 } 484 $logs[] = ['timestamp' => $now, 'load' => $load]; 472 $cutoff = $now - $retention; 473 474 $logs = array_filter($logs, static function ($row) use ($cutoff): bool { 475 return is_array($row) && isset($row['timestamp']) && is_numeric($row['timestamp']) && (int) $row['timestamp'] >= $cutoff; 476 }); 477 478 if (count($logs) > $max_entries) { 479 $logs = array_slice($logs, -$max_entries); 480 } 481 482 $logs[] = ['timestamp' => $now, 'load' => (float) $load]; 483 484 if (count($logs) > $max_entries) { 485 $logs = array_slice($logs, -$max_entries); 486 } 487 485 488 update_option($optKey, array_values($logs), false); 489 490 if ($wpdb instanceof wpdb && !empty($wpdb->options)) { 491 $wpdb->query($wpdb->prepare( 492 "UPDATE {$wpdb->options} SET autoload='no' WHERE option_name=%s AND autoload<>'no' LIMIT 1", 493 $optKey 494 )); 495 } 486 496 } 487 497 add_action('dfehc_log_server_load_hook', 'dfehc_log_server_load'); … … 491 501 { 492 502 $allow_public = apply_filters('dfehc_allow_public_server_load', false); 503 493 504 if (!$allow_public) { 494 505 $action = 'get_server_load'; … … 520 531 $ip = dfehc_client_ip(); 521 532 $rk = dfehc_key('dfehc_rl_' . md5($ip)); 533 534 $limit = (int) apply_filters('dfehc_public_rate_limit', 30); 535 $win = (int) apply_filters('dfehc_public_rate_window', 60); 536 $limit = max(1, $limit); 537 $win = max(1, $win); 538 522 539 $cnt = (int) get_transient($rk); 523 $limit = (int) apply_filters('dfehc_public_rate_limit', 60);524 $win = (int) apply_filters('dfehc_public_rate_window', 60);525 540 if ($cnt >= $limit) { 526 541 wp_send_json_error(['message' => 'rate_limited'], 429); … … 528 543 set_transient($rk, $cnt + 1, $win); 529 544 } 545 530 546 nocache_headers(); 531 547 wp_send_json_success(dfehc_get_server_load_persistent()); … … 534 550 add_action('wp_ajax_get_server_load', 'dfehc_get_server_load_ajax_handler'); 535 551 536 add_action('init', staticfunction (): void {552 add_action('init', function (): void { 537 553 if (apply_filters('dfehc_allow_public_server_load', false)) { 538 554 add_action('wp_ajax_nopriv_get_server_load', 'dfehc_get_server_load_ajax_handler'); … … 546 562 return (float) $cached; 547 563 } 548 ['client' => $client] = dfehc_get_cache_client(); 564 565 $scalarKey = dfehc_key(DFEHC_SERVER_LOAD_SCALAR_KEY); 566 567 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) { 568 $val = wp_cache_get($scalarKey, DFEHC_CACHE_GROUP); 569 if ($val !== false && $val !== '' && is_numeric($val)) { 570 $cached = max(0.0, (float) $val); 571 return (float) $cached; 572 } 573 } 574 575 $cc = dfehc_get_cache_client(); 576 $client = isset($cc['client']) ? $cc['client'] : null; 577 549 578 $val = false; 550 $key = dfehc_key(DFEHC_SERVER_LOAD_CACHE_KEY);551 579 if ($client) { 552 580 try { 553 $val = $client->get( $key);581 $val = $client->get(dfehc_key(DFEHC_SERVER_LOAD_CACHE_KEY)); 554 582 } catch (Throwable $e) { 555 583 dfehc_debug_log('DFEHC cache read error: ' . $e->getMessage()); 556 584 } 557 585 } 558 if ($val !== false && $val !== '') { 586 587 if ($val !== false && $val !== '' && is_numeric($val)) { 559 588 $cached = max(0.0, (float) $val); 560 589 return (float) $cached; 561 590 } 591 562 592 $fresh = dfehc_get_server_load(); 563 593 $fresh = max(0.0, (float) $fresh); 594 564 595 dfehc_cache_server_load($fresh); 565 if (wp_using_ext_object_cache()) { 566 $ttl = dfehc_server_load_ttl(); 567 $jitter = 0; 568 if (function_exists('random_int')) { 569 try { 570 $jitter = random_int(0, 5); 571 } catch (Throwable $e) { 572 $jitter = 0; 573 } 574 } 575 $ttl += $jitter; 576 wp_cache_set($key, $fresh, DFEHC_CACHE_GROUP, $ttl); 577 } 596 578 597 $cached = $fresh; 579 598 return (float) $cached; … … 604 623 function dfehc_schedule_log_server_load(): void 605 624 { 606 if (!apply_filters('dfehc_enable_load_logging', true)) {625 if (!apply_filters('dfehc_enable_load_logging', false)) { 607 626 dfehc_clear_log_server_load_cron(); 608 627 return; 609 628 } 610 if ( 611 !function_exists('wp_get_schedules') 612 || !function_exists('wp_next_scheduled') 613 || !function_exists('wp_schedule_event') 614 ) { 629 if (!function_exists('wp_get_schedules') || !function_exists('wp_next_scheduled') || !function_exists('wp_schedule_event')) { 615 630 return; 616 631 } … … 641 656 if (function_exists('register_activation_hook')) { 642 657 $plugin_file = defined('DFEHC_PLUGIN_FILE') ? DFEHC_PLUGIN_FILE : __FILE__; 643 register_activation_hook( $plugin_file, 'dfehc_schedule_log_server_load');658 register_activation_hook((string) $plugin_file, 'dfehc_schedule_log_server_load'); 644 659 } 645 660 if (function_exists('register_deactivation_hook')) { 646 661 $plugin_file = defined('DFEHC_PLUGIN_FILE') ? DFEHC_PLUGIN_FILE : __FILE__; 647 register_deactivation_hook( $plugin_file, 'dfehc_deactivate_log_server_load');662 register_deactivation_hook((string) $plugin_file, 'dfehc_deactivate_log_server_load'); 648 663 } 649 664 … … 653 668 { 654 669 $key = dfehc_key('dfehc_load_lock'); 670 655 671 if (class_exists('WP_Lock')) { 656 672 $lock = new WP_Lock($key, 30); … … 661 677 return false; 662 678 } 679 663 680 if (function_exists('wp_cache_add') && wp_cache_add($key, 1, DFEHC_CACHE_GROUP, 30)) { 664 681 $GLOBALS['dfehc_load_lock_cache_key'] = $key; 665 682 return true; 666 683 } 684 667 685 if (false !== get_transient($key)) { 668 686 return false; 669 687 } 688 670 689 if (set_transient($key, 1, 30)) { 671 690 $GLOBALS['dfehc_load_lock_transient_key'] = $key; 672 691 return true; 673 692 } 693 674 694 return false; 675 695 } … … 682 702 return; 683 703 } 704 684 705 if (isset($GLOBALS['dfehc_load_lock_cache_key'])) { 685 wp_cache_delete( $GLOBALS['dfehc_load_lock_cache_key'], DFEHC_CACHE_GROUP);706 wp_cache_delete((string) $GLOBALS['dfehc_load_lock_cache_key'], DFEHC_CACHE_GROUP); 686 707 unset($GLOBALS['dfehc_load_lock_cache_key']); 687 708 return; 688 709 } 710 689 711 if (isset($GLOBALS['dfehc_load_lock_transient_key'])) { 690 delete_transient( $GLOBALS['dfehc_load_lock_transient_key']);712 delete_transient((string) $GLOBALS['dfehc_load_lock_transient_key']); 691 713 unset($GLOBALS['dfehc_load_lock_transient_key']); 692 714 } -
dynamic-front-end-heartbeat-control/trunk/engine/server-response.php
r3427163 r3461136 9 9 defined('DFEHC_CACHE_GROUP') || define('DFEHC_CACHE_GROUP', 'dfehc'); 10 10 11 if (!function_exists('dfehc_ttl_jitter')) { 12 function dfehc_ttl_jitter(int $max = 5): int 13 { 14 if ($max <= 0 || !function_exists('random_int')) { 15 return 0; 16 } 17 try { 18 return random_int(0, $max); 19 } catch (\Throwable $e) { 20 return 0; 21 } 22 } 23 } 24 25 if (!function_exists('dfehc_transient_cache_get')) { 26 function dfehc_transient_cache_get(string $key, bool &$found = null) 27 { 28 if (!isset($GLOBALS['dfehc_transient_cache']) || !is_array($GLOBALS['dfehc_transient_cache'])) { 29 $GLOBALS['dfehc_transient_cache'] = []; 30 } 31 if (array_key_exists($key, $GLOBALS['dfehc_transient_cache'])) { 32 $found = true; 33 return $GLOBALS['dfehc_transient_cache'][$key]; 34 } 35 $found = false; 36 return null; 37 } 38 } 39 40 if (!function_exists('dfehc_transient_cache_set')) { 41 function dfehc_transient_cache_set(string $key, $value): void 42 { 43 if (!isset($GLOBALS['dfehc_transient_cache']) || !is_array($GLOBALS['dfehc_transient_cache'])) { 44 $GLOBALS['dfehc_transient_cache'] = []; 45 } 46 $GLOBALS['dfehc_transient_cache'][$key] = $value; 47 } 48 } 49 50 if (!function_exists('dfehc_transient_cache_del')) { 51 function dfehc_transient_cache_del(string $key): void 52 { 53 if (isset($GLOBALS['dfehc_transient_cache']) && is_array($GLOBALS['dfehc_transient_cache'])) { 54 unset($GLOBALS['dfehc_transient_cache'][$key]); 55 } 56 } 57 } 58 59 if (!function_exists('dfehc_get_transient_cached')) { 60 function dfehc_get_transient_cached(string $key) 61 { 62 $found = false; 63 $v = dfehc_transient_cache_get($key, $found); 64 if ($found) { 65 return $v; 66 } 67 $v = get_transient($key); 68 dfehc_transient_cache_set($key, $v); 69 return $v; 70 } 71 } 72 73 if (!function_exists('dfehc_delete_transient_cached')) { 74 function dfehc_delete_transient_cached(string $key): bool 75 { 76 dfehc_transient_cache_del($key); 77 return delete_transient($key); 78 } 79 } 80 11 81 if (!function_exists('dfehc_host_token')) { 12 function dfehc_host_token(): string { 82 function dfehc_host_token(): string 83 { 13 84 static $t = ''; 14 if ($t !== '') return $t; 85 if ($t !== '') { 86 return $t; 87 } 15 88 $host = @php_uname('n') ?: (defined('WP_HOME') ? WP_HOME : (function_exists('home_url') ? home_url() : 'unknown')); 16 89 $salt = defined('DB_NAME') ? (string) DB_NAME : ''; … … 20 93 21 94 if (!function_exists('dfehc_blog_id')) { 22 function dfehc_blog_id(): int { 95 function dfehc_blog_id(): int 96 { 23 97 return function_exists('get_current_blog_id') ? (int) get_current_blog_id() : 0; 24 98 } … … 26 100 27 101 if (!function_exists('dfehc_key')) { 28 function dfehc_key(string $base): string { 102 function dfehc_key(string $base): string 103 { 29 104 return $base . '_' . dfehc_blog_id() . '_' . dfehc_host_token(); 30 105 } … … 32 107 33 108 if (!function_exists('dfehc_store_lockfree')) { 34 function dfehc_store_lockfree(string $key, $value, int $ttl): bool { 109 function dfehc_store_lockfree(string $key, $value, int $ttl): bool 110 { 35 111 if (function_exists('wp_cache_add') && wp_cache_add($key, $value, DFEHC_CACHE_GROUP, $ttl)) { 36 112 return true; 37 113 } 38 return set_transient($key, $value, $ttl); 39 } 40 } 41 42 if (!function_exists('dfehc_set_transient_noautoload')) { 43 function dfehc_set_transient_noautoload(string $key, $value, int $expiration): void { 44 if (wp_using_ext_object_cache()) { 45 if (function_exists('wp_cache_add')) { 46 if (!wp_cache_add($key, $value, DFEHC_CACHE_GROUP, $expiration)) { 47 wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $expiration); 48 } 49 } else { 50 wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $expiration); 51 } 52 return; 53 } 54 set_transient($key, $value, $expiration); 55 global $wpdb; 56 $opt_key = "_transient_$key"; 57 $opt_key_to = "_transient_timeout_$key"; 58 $wpdb->suppress_errors(true); 59 $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key)); 60 if ($autoload === 'yes') { 61 $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key, 'autoload' => 'yes'], ['%s'], ['%s','%s']); 62 } 63 $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to)); 64 if ($autoload_to === 'yes') { 65 $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key_to, 'autoload' => 'yes'], ['%s'], ['%s','%s']); 66 } 67 $wpdb->suppress_errors(false); 114 $ok = set_transient($key, $value, $ttl); 115 if ($ok) { 116 dfehc_transient_cache_set($key, $value); 117 } 118 return (bool) $ok; 119 } 120 } 121 122 if (!function_exists('dfehc_home_host')) { 123 function dfehc_home_host(): string 124 { 125 static $host = null; 126 if ($host !== null) { 127 return $host; 128 } 129 $host = ''; 130 131 if (function_exists('home_url')) { 132 $parsed = wp_parse_url((string) home_url()); 133 if (is_array($parsed) && isset($parsed['host'])) { 134 $host = (string) $parsed['host']; 135 } 136 } 137 return $host; 68 138 } 69 139 } … … 78 148 $is_valid_ip = static function (string $ip, bool $publicOnly): bool { 79 149 $ip = trim($ip); 80 if ($ip === '') return false; 150 if ($ip === '') { 151 return false; 152 } 81 153 if ($publicOnly) { 82 154 return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false; … … 107 179 108 180 if ($h === 'HTTP_X_FORWARDED_FOR') { 109 $parts = array_map('trim', explode(',', $raw)); 181 $parts_raw = array_map('trim', explode(',', $raw)); 182 $parts = []; 183 foreach ($parts_raw as $p) { 184 $p = trim(sanitize_text_field($p)); 185 if ($p !== '') { 186 $parts[] = $p; 187 } 188 } 110 189 111 190 foreach ($parts as $cand) { 112 $cand = sanitize_text_field($cand);113 191 if ($is_valid_ip($cand, $public_only)) { 114 192 return (string) apply_filters('dfehc_client_ip', $cand); … … 116 194 } 117 195 foreach ($parts as $cand) { 118 $cand = sanitize_text_field($cand);119 196 if ($is_valid_ip($cand, false)) { 120 197 return (string) apply_filters('dfehc_client_ip', $cand); … … 122 199 } 123 200 } else { 124 $cand = sanitize_text_field(trim($raw));201 $cand = trim(sanitize_text_field($raw)); 125 202 if ($is_valid_ip($cand, $public_only) || $is_valid_ip($cand, false)) { 126 203 return (string) apply_filters('dfehc_client_ip', $cand); … … 156 233 157 234 $cacheKey = dfehc_key('dfehc_cached_response_data'); 158 $cached = get_transient($cacheKey);235 $cached = dfehc_get_transient_cached($cacheKey); 159 236 if ($cached !== false && is_array($cached)) { 160 237 return array_merge($defaults, $cached); … … 174 251 ]; 175 252 $ttl = (int) apply_filters('dfehc_high_traffic_cache_expiration', 300); 176 $jitter = 0; 177 if (function_exists('random_int')) { 178 try { 179 $jitter = random_int(0, 5); 180 } catch (\Throwable $e) { 181 $jitter = 0; 182 } 183 } 184 $ttl += $jitter; 253 $ttl += dfehc_ttl_jitter(5); 185 254 dfehc_set_transient_noautoload($cacheKey, $high, $ttl); 186 255 return $high; … … 193 262 try { 194 263 $results = dfehc_perform_response_measurements($default_ms); 264 195 265 $baselineKey = dfehc_key('dfehc_baseline_response_data'); 196 266 $spikeKey = dfehc_key('dfehc_spike_score'); 197 $baseline = get_transient($baselineKey); 198 $spike = (float) get_transient($spikeKey); 267 268 $baseline = dfehc_get_transient_cached($baselineKey); 269 $prev_spike = (float) dfehc_get_transient_cached($spikeKey); 270 $spike = $prev_spike; 271 199 272 $max_age = (int) apply_filters('dfehc_max_baseline_age', DFEHC_BASELINE_EXP); 200 273 … … 202 275 $ts = isset($baseline['ts_unix']) && is_numeric($baseline['ts_unix']) ? (int) $baseline['ts_unix'] : strtotime($baseline['timestamp'] ?? 'now'); 203 276 if ($now - (int) $ts > $max_age) { 204 d elete_transient($baselineKey);277 dfehc_delete_transient_cached($baselineKey); 205 278 $baseline = false; 206 279 } … … 211 284 $results['timestamp'] = current_time('mysql'); 212 285 $results['ts_unix'] = $now; 213 $jitter = 0; 214 if (function_exists('random_int')) { 215 try { 216 $jitter = random_int(0, 5); 217 } catch (\Throwable $e) { 218 $jitter = 0; 219 } 220 } 221 $exp += $jitter; 286 $exp += dfehc_ttl_jitter(5); 222 287 dfehc_set_transient_noautoload($baselineKey, $results, $exp); 223 288 $baseline = $results; … … 248 313 $results['ts_unix'] = $now; 249 314 $exp = (int) apply_filters('dfehc_baseline_expiration', DFEHC_BASELINE_EXP); 250 $jitter = 0; 251 if (function_exists('random_int')) { 252 try { 253 $jitter = random_int(0, 5); 254 } catch (\Throwable $e) { 255 $jitter = 0; 256 } 257 } 258 $exp += $jitter; 315 $exp += dfehc_ttl_jitter(5); 259 316 dfehc_set_transient_noautoload($baselineKey, $results, $exp); 260 317 $spike = 0.0; … … 264 321 265 322 $results['spike_score'] = $spike; 266 $prev_spike = (float) get_transient($spikeKey);267 323 268 324 if (abs($spike - $prev_spike) >= DFEHC_SPIKE_OPT_EPS) { 269 $ttl = DFEHC_BASELINE_EXP; 270 $jitter = 0; 271 if (function_exists('random_int')) { 272 try { 273 $jitter = random_int(0, 5); 274 } catch (\Throwable $e) { 275 $jitter = 0; 276 } 277 } 278 $ttl += $jitter; 325 $ttl = (int) DFEHC_BASELINE_EXP; 326 $ttl += dfehc_ttl_jitter(5); 279 327 dfehc_set_transient_noautoload($spikeKey, $spike, $ttl); 280 328 } 281 329 282 330 $exp = (int) apply_filters('dfehc_cache_expiration', 3 * MINUTE_IN_SECONDS); 283 $jitter = 0; 284 if (function_exists('random_int')) { 285 try { 286 $jitter = random_int(0, 5); 287 } catch (\Throwable $e) { 288 $jitter = 0; 289 } 290 } 291 $exp += $jitter; 331 $exp += dfehc_ttl_jitter(5); 292 332 dfehc_set_transient_noautoload($cacheKey, $results, $exp); 293 333 … … 336 376 $use_ajax_fallback = false; 337 377 338 $home_host = ''; 339 if (function_exists('home_url')) { 340 $parsed = wp_parse_url((string) home_url()); 341 if (is_array($parsed) && isset($parsed['host'])) { 342 $home_host = (string) $parsed['host']; 343 } 344 } 378 $home_host = dfehc_home_host(); 345 379 346 380 $headers = (array) apply_filters('dfehc_probe_headers', [ … … 388 422 389 423 $head_key = dfehc_key('dfehc_head_supported_' . md5($scheme . '|' . $url)); 390 $head_supported = get_transient($head_key);424 $head_supported = dfehc_get_transient_cached($head_key); 391 425 if ($head_supported === false) { 392 426 $head_supported = null; … … 394 428 395 429 $negKey = dfehc_key('dfehc_probe_fail'); 396 if (get_transient($negKey)) { 430 431 static $probe_blocked = null; 432 if ($probe_blocked === null) { 433 $probe_blocked = (bool) dfehc_get_transient_cached($negKey); 434 } 435 if ($probe_blocked) { 397 436 $r['method'] = 'failed'; 398 437 $r['main_response_ms'] = $default_ms; … … 432 471 if ($head_supported === null) { 433 472 $ttl = (int) apply_filters('dfehc_head_negative_ttl', DFEHC_HEAD_NEG_TTL); 434 $jitter = 0; 435 if (function_exists('random_int')) { 436 try { 437 $jitter = random_int(0, 5); 438 } catch (\Throwable $e) { 439 $jitter = 0; 440 } 441 } 442 $ttl += $jitter; 473 $ttl += dfehc_ttl_jitter(5); 443 474 dfehc_set_transient_noautoload($head_key, 0, $ttl); 444 475 } … … 447 478 if ($head_supported === null) { 448 479 $ttl = (int) apply_filters('dfehc_head_positive_ttl', DFEHC_HEAD_POS_TTL); 449 $jitter = 0; 450 if (function_exists('random_int')) { 451 try { 452 $jitter = random_int(0, 5); 453 } catch (\Throwable $e) { 454 $jitter = 0; 455 } 456 } 457 $ttl += $jitter; 480 $ttl += dfehc_ttl_jitter(5); 458 481 dfehc_set_transient_noautoload($head_key, 1, $ttl); 459 482 } … … 490 513 } else { 491 514 $ttl = (int) apply_filters('dfehc_probe_fail_ttl', 60); 492 $jitter = 0; 493 if (function_exists('random_int')) { 494 try { 495 $jitter = random_int(0, 5); 496 } catch (\Throwable $e) { 497 $jitter = 0; 498 } 499 } 500 $ttl += $jitter; 515 $ttl += dfehc_ttl_jitter(5); 501 516 dfehc_set_transient_noautoload($negKey, 1, $ttl); 517 $probe_blocked = true; 502 518 $r['method'] = 'failed'; 503 519 } … … 513 529 } 514 530 515 function dfehc_ping_handler(): void { 531 if (!function_exists('dfehc_rl_increment')) { 532 function dfehc_rl_increment(string $key, int $ttl): int 533 { 534 if (wp_using_ext_object_cache() && function_exists('wp_cache_incr')) { 535 wp_cache_add($key, 0, DFEHC_CACHE_GROUP, $ttl); 536 $v = wp_cache_incr($key, 1, DFEHC_CACHE_GROUP); 537 if ($v === false) { 538 $curr = (int) wp_cache_get($key, DFEHC_CACHE_GROUP); 539 $curr++; 540 wp_cache_set($key, $curr, DFEHC_CACHE_GROUP, $ttl); 541 return $curr; 542 } 543 if ((int) $v === 1) { 544 wp_cache_set($key, (int) $v, DFEHC_CACHE_GROUP, $ttl); 545 } 546 return (int) $v; 547 } 548 549 $cnt = (int) dfehc_get_transient_cached($key); 550 dfehc_set_transient_noautoload($key, $cnt + 1, $ttl); 551 return $cnt + 1; 552 } 553 } 554 555 556 function dfehc_ping_handler(): void 557 { 516 558 $ip = dfehc_client_ip(); 517 559 $k = dfehc_key('dfehc_ping_rl_' . md5($ip)); 518 560 $window = (int) apply_filters('dfehc_ping_rl_ttl', 2); 519 561 $limit = (int) apply_filters('dfehc_ping_rl_limit', 2); 520 $cnt = (int) get_transient($k); 521 if ($cnt >= $limit) { 562 563 $cnt = dfehc_rl_increment($k, $window); 564 if ($cnt > $limit) { 522 565 status_header(429); 523 566 nocache_headers(); 524 567 wp_send_json_error('rate_limited', 429); 525 568 } 526 dfehc_set_transient_noautoload($k, $cnt + 1, $window); 569 527 570 nocache_headers(); 528 571 wp_send_json_success('ok'); … … 553 596 } 554 597 555 if (false !== get_transient($key)) { 598 $existing = get_transient($key); 599 dfehc_transient_cache_set($key, $existing); 600 601 if (false !== $existing) { 556 602 return false; 557 603 } 558 604 559 605 if (set_transient($key, 1, 60)) { 606 dfehc_transient_cache_set($key, 1); 560 607 $GLOBALS['dfehc_rt_lock_transient_key'] = $key; 561 608 return true; … … 582 629 583 630 if (isset($GLOBALS['dfehc_rt_lock_transient_key'])) { 584 d elete_transient($GLOBALS['dfehc_rt_lock_transient_key']);631 dfehc_delete_transient_cached($GLOBALS['dfehc_rt_lock_transient_key']); 585 632 unset($GLOBALS['dfehc_rt_lock_transient_key']); 586 633 } … … 592 639 { 593 640 $flag_key = dfehc_key('dfehc_high_traffic_flag'); 594 $flag = get_transient($flag_key);641 $flag = dfehc_get_transient_cached($flag_key); 595 642 if ($flag !== false) { 596 643 return (bool) $flag; … … 599 646 $threshold = (int) apply_filters('dfehc_high_traffic_threshold', 100); 600 647 $cnt_key = dfehc_key('dfehc_cached_visitor_cnt'); 601 $count = get_transient($cnt_key);648 $count = dfehc_get_transient_cached($cnt_key); 602 649 if ($count === false) { 603 650 $count = (int) apply_filters('dfehc_website_visitors', 0); … … 614 661 } 615 662 616 $high = $count>= $threshold;663 $high = ((int) $count) >= $threshold; 617 664 dfehc_set_transient_noautoload($flag_key, $high ? 1 : 0, 60); 618 665 -
dynamic-front-end-heartbeat-control/trunk/engine/system-load-fallback.php
r3427163 r3461136 3 3 4 4 defined('DFEHC_SERVER_LOAD_TTL') || define('DFEHC_SERVER_LOAD_TTL', 60); 5 defined('DFEHC_SENTINEL_NO_LOAD') || define('DFEHC_SENTINEL_NO_LOAD', -1.0);5 defined('DFEHC_SENTINEL_NO_LOAD') || define('DFEHC_SENTINEL_NO_LOAD', 0.404); 6 6 defined('DFEHC_SYSTEM_LOAD_KEY') || define('DFEHC_SYSTEM_LOAD_KEY', 'dfehc_system_load_avg'); 7 7 defined('DFEHC_CACHE_GROUP') || define('DFEHC_CACHE_GROUP', 'dfehc'); 8 8 9 9 if (!function_exists('dfehc_host_token')) { 10 function dfehc_host_token(): string { 10 function dfehc_host_token(): string 11 { 11 12 static $t = ''; 12 13 if ($t !== '') return $t; 13 $host = @php_uname('n') ?: (defined('WP_HOME') ? WP_HOME : (function_exists('home_url') ? home_url() : 'unknown')); 14 15 $url = ''; 16 if (defined('WP_HOME') && is_string(WP_HOME) && WP_HOME !== '') { 17 $url = WP_HOME; 18 } elseif (function_exists('home_url')) { 19 $url = (string) home_url(); 20 } 21 22 $host = ''; 23 if ($url !== '' && function_exists('wp_parse_url')) { 24 $parts = wp_parse_url($url); 25 if (is_array($parts) && isset($parts['host']) && is_string($parts['host'])) { 26 $host = $parts['host']; 27 } 28 } 29 30 if ($host === '') { 31 $host = @php_uname('n') ?: 'unknown'; 32 } 33 14 34 $salt = defined('DB_NAME') ? (string) DB_NAME : ''; 15 return $t = substr(md5( (string)$host . $salt), 0, 10);35 return $t = substr(md5($host . $salt), 0, 10); 16 36 } 17 37 } 18 38 19 39 if (!function_exists('dfehc_blog_id')) { 20 function dfehc_blog_id(): int { 40 function dfehc_blog_id(): int 41 { 21 42 return function_exists('get_current_blog_id') ? (int) get_current_blog_id() : 0; 22 43 } … … 24 45 25 46 if (!function_exists('dfehc_scoped_key')) { 26 function dfehc_scoped_key(string $base): string { 47 function dfehc_scoped_key(string $base): string 48 { 27 49 return $base . '_' . dfehc_blog_id() . '_' . dfehc_host_token(); 28 50 } 29 51 } 30 52 31 if (!function_exists('dfehc_set_transient_noautoload')) { 32 function dfehc_set_transient_noautoload(string $key, $value, int $expiration): void { 33 $jitter = 0; 53 if (!function_exists('dfehc_rand_jitter')) { 54 function dfehc_rand_jitter(int $min, int $max): int 55 { 56 $min = (int) $min; 57 $max = (int) $max; 58 if ($max < $min) { $t = $min; $min = $max; $max = $t; } 59 if ($min === $max) return $min; 34 60 if (function_exists('random_int')) { 35 try { 36 $jitter = random_int(0, 5); 37 } catch (\Throwable $e) { 38 $jitter = 0; 39 } 40 } 41 $expiration = max(1, $expiration + $jitter); 42 43 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) { 44 wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $expiration); 45 return; 46 } 47 48 set_transient($key, $value, $expiration); 49 50 if (!isset($GLOBALS['wpdb'])) { 51 return; 52 } 53 global $wpdb; 54 if (!isset($wpdb->options)) { 55 return; 56 } 57 58 $opt_key = "_transient_$key"; 59 $opt_key_to = "_transient_timeout_$key"; 60 61 $wpdb->suppress_errors(true); 62 63 $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key)); 64 if ($autoload === 'yes') { 65 $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key, 'autoload' => 'yes'], ['%s'], ['%s', '%s']); 66 } 67 68 $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to)); 69 if ($autoload_to === 'yes') { 70 $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key_to, 'autoload' => 'yes'], ['%s'], ['%s', '%s']); 71 } 72 73 $wpdb->suppress_errors(false); 61 try { return (int) random_int($min, $max); } catch (Throwable $e) {} 62 } 63 return (int) (function_exists('wp_rand') ? wp_rand($min, $max) : mt_rand($min, $max)); 64 } 65 } 66 67 if (!function_exists('dfehc_cache_get_scalar')) { 68 function dfehc_cache_get_scalar(string $key) 69 { 70 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_get')) { 71 $v = wp_cache_get($key, DFEHC_CACHE_GROUP); 72 if ($v !== false && $v !== null) return $v; 73 } 74 if (function_exists('get_transient')) { 75 $v = get_transient($key); 76 if ($v !== false) return $v; 77 } 78 return null; 79 } 80 } 81 82 if (!function_exists('dfehc_allow_shell_fallback')) { 83 function dfehc_allow_shell_fallback(): bool 84 { 85 $allow = (bool) apply_filters('dfehc_allow_shell_fallback', false); 86 if (!$allow) return false; 87 if (ini_get('open_basedir')) return false; 88 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); 89 if (!function_exists('shell_exec') || in_array('shell_exec', $disabled, true)) return false; 90 return true; 74 91 } 75 92 } 76 93 77 94 if (!function_exists('dfehc_exec_with_timeout')) { 78 function dfehc_exec_with_timeout(string $cmd, float $timeoutSec = 1.0): string { 79 $timeoutSec = max(0.1, min(5.0, $timeoutSec)); 80 81 if (ini_get('open_basedir')) { 82 return ''; 83 } 84 85 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); 86 $can_shell = function_exists('shell_exec') && !in_array('shell_exec', $disabled, true); 87 88 if (!$can_shell) { 89 return ''; 90 } 91 95 function dfehc_exec_with_timeout(string $cmd, float $timeoutSec = 1.0): string 96 { 97 if (!dfehc_allow_shell_fallback()) return ''; 98 99 $timeoutSec = max(0.15, min(3.0, $timeoutSec)); 92 100 $cmd = trim($cmd); 93 if ($cmd === '') { 94 return ''; 95 } 101 if ($cmd === '') return ''; 96 102 97 103 if (PHP_OS_FAMILY !== 'Windows') { 98 $has_timeout = trim((string) @shell_exec('command -v timeout 2>/dev/null')); 99 if ($has_timeout !== '') { 100 $sec = max(1, (int) ceil($timeoutSec)); 101 $cmd = 'timeout ' . $sec . ' ' . $cmd; 102 } 103 } 104 105 return trim((string) @shell_exec($cmd)); 104 $sec = max(1, (int) ceil($timeoutSec)); 105 $pref = 'timeout ' . $sec . ' '; 106 $cmd = $pref . $cmd; 107 } 108 109 $out = @shell_exec($cmd); 110 return is_string($out) ? trim($out) : ''; 106 111 } 107 112 } 108 113 109 114 if (!function_exists('dfehc_get_cpu_cores')) { 110 function dfehc_get_cpu_cores(): int { 115 function dfehc_get_cpu_cores(): int 116 { 111 117 static $cached = null; 112 118 if ($cached !== null) return (int) $cached; … … 114 120 $tkey = dfehc_scoped_key('dfehc_cpu_cores'); 115 121 116 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) { 117 $oc = wp_cache_get($tkey, DFEHC_CACHE_GROUP); 118 if ($oc !== false && (int) $oc > 0) { 119 return $cached = (int) $oc; 120 } 121 } 122 123 $tc = get_transient($tkey); 124 if ($tc !== false && (int) $tc > 0) { 125 return $cached = (int) $tc; 126 } 127 128 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions'))); 129 130 if (PHP_OS_FAMILY !== 'Windows' && function_exists('shell_exec') && !in_array('shell_exec', $disabled, true) && !ini_get('open_basedir')) { 131 $val = trim((string) @shell_exec('getconf _NPROCESSORS_ONLN 2>/dev/null')); 132 if (ctype_digit($val) && (int) $val > 0) { 133 $cores = (int) $val; 134 dfehc_set_transient_noautoload($tkey, $cores, (int) apply_filters('dfehc_cpu_cores_ttl', DAY_IN_SECONDS)); 135 return $cached = $cores; 136 } 137 } 138 139 if (PHP_OS_FAMILY === 'Windows' && !ini_get('open_basedir') && function_exists('shell_exec') && !in_array('shell_exec', $disabled, true)) { 140 $wout = trim((string) @shell_exec('wmic cpu get NumberOfLogicalProcessors /value 2>NUL')); 141 if ($wout && preg_match('/NumberOfLogicalProcessors=(\d+)/i', $wout, $m) && (int) $m[1] > 0) { 142 $cores = (int) $m[1]; 143 dfehc_set_transient_noautoload($tkey, $cores, (int) apply_filters('dfehc_cpu_cores_ttl', DAY_IN_SECONDS)); 144 return $cached = $cores; 145 } 146 } 147 148 if (is_readable('/proc/cpuinfo')) { 149 $cnt = preg_match_all('/^processor/m', (string) @file_get_contents('/proc/cpuinfo')); 150 if ($cnt > 0) { 151 $cores = (int) $cnt; 152 dfehc_set_transient_noautoload($tkey, $cores, (int) apply_filters('dfehc_cpu_cores_ttl', DAY_IN_SECONDS)); 153 return $cached = $cores; 154 } 155 } 156 157 dfehc_set_transient_noautoload($tkey, 1, (int) apply_filters('dfehc_cpu_cores_ttl', DAY_IN_SECONDS)); 158 return $cached = 1; 122 $v = dfehc_cache_get_scalar($tkey); 123 if ($v !== null && is_numeric($v) && (int) $v > 0) { 124 return $cached = (int) $v; 125 } 126 127 $override = getenv('DFEHC_CPU_CORES'); 128 if ($override !== false && is_numeric($override) && (int) $override > 0) { 129 $cores = (int) $override; 130 dfehc_set_transient_noautoload($tkey, $cores, (int) apply_filters('dfehc_cpu_cores_ttl', DAY_IN_SECONDS)); 131 return $cached = $cores; 132 } 133 if (defined('DFEHC_CPU_CORES') && is_numeric(DFEHC_CPU_CORES) && (int) DFEHC_CPU_CORES > 0) { 134 $cores = (int) DFEHC_CPU_CORES; 135 dfehc_set_transient_noautoload($tkey, $cores, (int) apply_filters('dfehc_cpu_cores_ttl', DAY_IN_SECONDS)); 136 return $cached = $cores; 137 } 138 139 $cores = 1; 140 141 if (is_readable('/sys/fs/cgroup/cpu.max')) { 142 $line = trim((string) @file_get_contents('/sys/fs/cgroup/cpu.max')); 143 if ($line !== '') { 144 $parts = preg_split('/\s+/', $line); 145 if (is_array($parts) && count($parts) >= 2) { 146 $quota = is_numeric($parts[0]) ? (float) $parts[0] : 0.0; 147 $period = is_numeric($parts[1]) ? (float) $parts[1] : 0.0; 148 if ($period > 0.0 && $quota > 0.0) { 149 $cores = max(1, (int) ceil($quota / $period)); 150 } 151 } 152 } 153 } 154 155 if ($cores === 1 && is_readable('/proc/cpuinfo')) { 156 $cnt = preg_match_all('/^processor\s*:/m', (string) @file_get_contents('/proc/cpuinfo')); 157 if ($cnt > 0) $cores = (int) $cnt; 158 } 159 160 $cores = (int) apply_filters('dfehc_cpu_cores', max(1, $cores)); 161 dfehc_set_transient_noautoload($tkey, $cores, (int) apply_filters('dfehc_cpu_cores_ttl', DAY_IN_SECONDS)); 162 return $cached = $cores; 159 163 } 160 164 } 161 165 162 166 if (!function_exists('dfehc_get_system_load_average')) { 163 function dfehc_get_system_load_average(): float { 164 $ttl = (int) apply_filters('dfehc_system_load_ttl', DFEHC_SERVER_LOAD_TTL); 167 function dfehc_get_system_load_average(): float 168 { 169 static $memo = null; 170 if ($memo !== null) return (float) $memo; 171 172 $ttl = (int) apply_filters('dfehc_system_load_ttl', (int) DFEHC_SERVER_LOAD_TTL); 173 $ttl = max(5, min(600, $ttl)); 174 $ttl = $ttl + dfehc_rand_jitter(0, 6); 175 165 176 $key = dfehc_scoped_key(DFEHC_SYSTEM_LOAD_KEY); 166 177 167 178 $as_percent = false; 168 179 169 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) { 170 $vc = wp_cache_get($key, DFEHC_CACHE_GROUP); 171 if ($vc !== false && $vc !== null) { 172 $ratio = (float) $vc; 173 $as_percent = (bool) apply_filters('dfehc_system_load_return_percent', false, $ratio); 174 return $as_percent ? (float) round($ratio * 100, 2) : $ratio; 175 } 176 } 177 178 $cache = get_transient($key); 179 if ($cache !== false) { 180 $ratio = (float) $cache; 180 $cached = dfehc_cache_get_scalar($key); 181 if ($cached !== null && is_numeric($cached)) { 182 $ratio = (float) $cached; 181 183 $as_percent = (bool) apply_filters('dfehc_system_load_return_percent', false, $ratio); 182 return $as_percent ? (float) round($ratio * 100, 2) : $ratio; 184 $memo = $as_percent ? (float) round($ratio * 100.0, 2) : (float) $ratio; 185 return (float) $memo; 183 186 } 184 187 … … 186 189 $source = ''; 187 190 $normalized_ratio = false; 191 192 $strict = (bool) apply_filters('dfehc_system_load_strict_fallback', true); 188 193 189 194 if ($raw === null && function_exists('dfehc_get_server_load')) { … … 207 212 if ($raw === null && function_exists('sys_getloadavg')) { 208 213 $arr = @sys_getloadavg(); 209 if ( $arr && isset($arr[0])) {214 if (is_array($arr) && isset($arr[0]) && is_numeric($arr[0]) && (float) $arr[0] >= 0.0) { 210 215 $raw = (float) $arr[0]; 211 216 $source = 'sys_getloadavg'; … … 214 219 215 220 if ($raw === null && PHP_OS_FAMILY !== 'Windows' && is_readable('/proc/loadavg')) { 216 $parts = explode(' ', (string) @file_get_contents('/proc/loadavg')); 217 if (isset($parts[0])) { 218 $raw = (float) $parts[0]; 219 $source = 'proc_loadavg'; 220 } 221 } 222 223 if ($raw === null && PHP_OS_FAMILY !== 'Windows') { 224 $out = dfehc_exec_with_timeout('LANG=C uptime 2>/dev/null', 1.0); 225 if ($out && preg_match('/load average[s]?:\s*([0-9.]+)/', $out, $m)) { 226 $raw = (float) $m[1]; 227 $source = 'uptime'; 228 } 229 } 230 231 if ($raw === null && PHP_OS_FAMILY === 'Windows' && !ini_get('open_basedir')) { 232 $csv = dfehc_exec_with_timeout('typeperf -sc 1 "\Processor(_Total)\% Processor Time" 2>NUL', 2.0); 233 if ($csv) { 234 $lines = array_values(array_filter(array_map('trim', explode("\n", $csv)))); 235 $last = end($lines); 236 if ($last && preg_match('/,"?([0-9.]+)"?$/', (string) $last, $m)) { 221 $txt = (string) @file_get_contents('/proc/loadavg'); 222 if ($txt !== '') { 223 $parts = preg_split('/\s+/', trim($txt)); 224 if (is_array($parts) && isset($parts[0]) && is_numeric($parts[0]) && (float) $parts[0] >= 0.0) { 225 $raw = (float) $parts[0]; 226 $source = 'proc_loadavg'; 227 } 228 } 229 } 230 231 if (!$strict) { 232 if ($raw === null && PHP_OS_FAMILY !== 'Windows') { 233 $out = dfehc_exec_with_timeout('LANG=C uptime 2>/dev/null', 1.0); 234 if ($out !== '' && preg_match('/load average[s]?:\s*([0-9.]+)/', $out, $m)) { 235 $v = (float) $m[1]; 236 if ($v >= 0.0) { 237 $raw = $v; 238 $source = 'uptime'; 239 } 240 } 241 } 242 243 if ($raw === null && PHP_OS_FAMILY === 'Windows') { 244 $out = dfehc_exec_with_timeout('wmic cpu get loadpercentage /value 2>NUL', 1.5); 245 if ($out !== '' && preg_match('/loadpercentage=(\d+)/i', $out, $m)) { 237 246 $pct = (float) $m[1]; 238 $raw = ($pct / 100.0) * dfehc_get_cpu_cores(); 239 $source = 'typeperf'; 240 } 241 } 242 } 243 244 if ($raw === null && PHP_OS_FAMILY === 'Windows' && !ini_get('open_basedir')) { 245 $psCmd = "powershell -NoProfile -NonInteractive -Command \"\\\$v=(Get-Counter '\\Processor(_Total)\\% Processor Time').CounterSamples[0].CookedValue; [Console]::Out.WriteLine([Math]::Round(\\\$v,2))\" 2>NUL"; 246 $ps = dfehc_exec_with_timeout($psCmd, 2.0); 247 if ($ps !== '' && is_numeric(trim($ps))) { 248 $pct = (float) trim($ps); 249 $raw = ($pct / 100.0) * dfehc_get_cpu_cores(); 250 $source = 'powershell'; 251 } 252 } 253 254 if ($raw === null && PHP_OS_FAMILY === 'Windows' && !ini_get('open_basedir')) { 255 $out = dfehc_exec_with_timeout('wmic cpu get loadpercentage /value 2>NUL', 1.5); 256 if ($out && preg_match('/loadpercentage=(\d+)/i', $out, $m)) { 257 $pct = (float) $m[1]; 258 $raw = ($pct / 100.0) * dfehc_get_cpu_cores(); 259 $source = 'wmic'; 247 if ($pct >= 0.0 && $pct <= 100.0) { 248 $raw = ($pct / 100.0) * dfehc_get_cpu_cores(); 249 $source = 'wmic'; 250 } 251 } 260 252 } 261 253 } … … 269 261 $normalized_ratio = true; 270 262 $source = 'estimator_percent'; 271 } else {272 $raw = (float) $pct;273 $source = 'estimator_raw';274 263 } 275 264 } … … 277 266 278 267 if ($raw === null) { 279 $sentinel_ttl = (int) apply_filters('dfehc_sentinel_ttl', 5); 280 dfehc_set_transient_noautoload($key, (float) DFEHC_SENTINEL_NO_LOAD, $sentinel_ttl); 281 $ratio = (float) DFEHC_SENTINEL_NO_LOAD; 282 $as_percent = (bool) apply_filters('dfehc_system_load_return_percent', false, $ratio); 283 return $as_percent ? (float) round($ratio * 100, 2) : $ratio; 268 $memo = (float) DFEHC_SENTINEL_NO_LOAD; 269 $as_percent = (bool) apply_filters('dfehc_system_load_return_percent', false, $memo); 270 return $as_percent ? (float) round($memo * 100.0, 2) : (float) $memo; 284 271 } 285 272 … … 300 287 } 301 288 302 dfehc_set_transient_noautoload($key, $ratio, $ttl); 289 $ratio = max((float) DFEHC_SENTINEL_NO_LOAD, (float) $ratio); 290 291 if ($ratio !== (float) DFEHC_SENTINEL_NO_LOAD) { 292 dfehc_set_transient_noautoload($key, $ratio, $ttl); 293 } 303 294 304 295 $as_percent = (bool) apply_filters('dfehc_system_load_return_percent', false, $ratio); 305 return $as_percent ? (float) round($ratio * 100, 2) : (float) $ratio; 306 } 307 } 296 $memo = $as_percent ? (float) round($ratio * 100.0, 2) : (float) $ratio; 297 return (float) $memo; 298 } 299 } -
dynamic-front-end-heartbeat-control/trunk/heartbeat-async.php
r3427163 r3461136 10 10 11 11 if (!function_exists('dfehc_max_server_load')) { 12 function dfehc_max_server_load(): int { static $v; if ($v === null) { $v = (int) apply_filters('dfehc_max_server_load', 85); } return $v; } 13 } 12 function dfehc_max_server_load(): int 13 { 14 static $v; 15 if ($v === null) { 16 $v = (int) apply_filters('dfehc_max_server_load', 82); 17 $v = max(1, min(100, $v)); 18 } 19 return $v; 20 } 21 } 22 14 23 if (!function_exists('dfehc_min_interval')) { 15 function dfehc_min_interval(): int { static $v; if ($v === null) { $v = (int) apply_filters('dfehc_min_interval', 15); } return $v; } 16 } 24 function dfehc_min_interval(): int 25 { 26 static $v; 27 if ($v === null) { 28 $v = (int) apply_filters('dfehc_min_interval', 20); 29 $v = max(5, $v); 30 } 31 return $v; 32 } 33 } 34 17 35 if (!function_exists('dfehc_max_interval')) { 18 function dfehc_max_interval(): int { static $v; if ($v === null) { $v = (int) apply_filters('dfehc_max_interval', 300); } return $v; } 19 } 36 function dfehc_max_interval(): int 37 { 38 static $v; 39 if ($v === null) { 40 $v = (int) apply_filters('dfehc_max_interval', 300); 41 $v = max(dfehc_min_interval(), $v); 42 } 43 return $v; 44 } 45 } 46 20 47 if (!function_exists('dfehc_fallback_interval')) { 21 function dfehc_fallback_interval(): int { static $v; if ($v === null) { $v = (int) apply_filters('dfehc_fallback_interval', 60); } return $v; } 48 function dfehc_fallback_interval(): int 49 { 50 static $v; 51 if ($v === null) { 52 $v = (int) apply_filters('dfehc_fallback_interval', 90); 53 $v = max(dfehc_min_interval(), min(dfehc_max_interval(), $v)); 54 } 55 return $v; 56 } 22 57 } 23 58 … … 26 61 { 27 62 static $t = ''; 28 if ($t !== '') return $t; 29 $host = @php_uname('n') ?: (defined('WP_HOME') ? WP_HOME : (function_exists('home_url') ? home_url() : 'unknown')); 63 if ($t !== '') { 64 return $t; 65 } 66 67 $url = ''; 68 if (defined('WP_HOME') && is_string(WP_HOME) && WP_HOME !== '') { 69 $url = WP_HOME; 70 } elseif (function_exists('home_url')) { 71 $url = (string) home_url(); 72 } 73 74 $host = ''; 75 if ($url !== '' && function_exists('wp_parse_url')) { 76 $parts = wp_parse_url($url); 77 if (is_array($parts) && isset($parts['host']) && is_string($parts['host'])) { 78 $host = $parts['host']; 79 } 80 } 81 82 if ($host === '') { 83 $host = @php_uname('n') ?: 'unknown'; 84 } 85 30 86 $salt = defined('DB_NAME') ? (string) DB_NAME : ''; 31 87 return $t = substr(md5((string) $host . $salt), 0, 10); … … 41 97 } 42 98 99 if (!function_exists('dfehc_rand_jitter')) { 100 function dfehc_rand_jitter(int $min, int $max): int 101 { 102 $min = (int) $min; 103 $max = (int) $max; 104 if ($max < $min) { 105 $t = $min; 106 $min = $max; 107 $max = $t; 108 } 109 if ($min === $max) { 110 return $min; 111 } 112 if (function_exists('random_int')) { 113 try { 114 return (int) random_int($min, $max); 115 } catch (Throwable $e) { 116 return (int) wp_rand($min, $max); 117 } 118 } 119 return (int) wp_rand($min, $max); 120 } 121 } 122 43 123 if (!function_exists('dfehc_store_lockfree')) { 44 124 function dfehc_store_lockfree(string $key, $value, int $ttl): bool 45 125 { 46 if (function_exists('wp_cache_add') && wp_cache_add($key, $value, DFEHC_CACHE_GROUP, $ttl)) { 47 return true; 48 } 49 return set_transient($key, $value, $ttl); 50 } 51 } 52 53 function dfehc_register_ajax(string $action, callable $callback): void 54 { 55 add_action("wp_ajax_$action", $callback); 56 $allow_public = false; 57 if ($action === 'get_server_load') { 58 $allow_public = (bool) apply_filters('dfehc_allow_public_server_load', false); 59 } elseif ($action === 'dfehc_async_heartbeat') { 60 $allow_public = (bool) apply_filters('dfehc_allow_public_async', false); 61 } else { 62 $allow_public = (bool) apply_filters("dfehc_{$action}_allow_public", false); 63 } 64 if ($allow_public) { 65 add_action("wp_ajax_nopriv_$action", $callback); 66 } 67 } 68 69 if (!function_exists('dfehc_set_transient_noautoload')) { 70 function dfehc_set_transient_noautoload(string $key, $value, int $expiration): void 71 { 72 if (wp_using_ext_object_cache()) { 73 if (function_exists('wp_cache_add')) { 74 if (!wp_cache_add($key, $value, DFEHC_CACHE_GROUP, $expiration)) { 75 wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $expiration); 76 } 77 } else { 78 wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $expiration); 126 $ttl = max(10, $ttl); 127 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_add')) { 128 if (wp_cache_add($key, $value, DFEHC_CACHE_GROUP, $ttl)) { 129 return true; 130 } 131 } 132 return (bool) set_transient($key, $value, $ttl); 133 } 134 } 135 136 if (!function_exists('dfehc_cache_get')) { 137 function dfehc_cache_get(string $key) 138 { 139 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) { 140 $v = wp_cache_get($key, DFEHC_CACHE_GROUP); 141 return $v === false ? null : $v; 142 } 143 $v = get_transient($key); 144 return $v === false ? null : $v; 145 } 146 } 147 148 if (!function_exists('dfehc_register_ajax')) { 149 function dfehc_register_ajax(string $action, callable $callback): void 150 { 151 add_action("wp_ajax_$action", $callback); 152 153 if ($action === 'get_server_load') { 154 if ((bool) apply_filters('dfehc_allow_public_server_load', false)) { 155 add_action("wp_ajax_nopriv_$action", $callback); 79 156 } 80 157 return; 81 158 } 82 dfehc_store_lockfree($key, $value, $expiration); 83 global $wpdb; 84 if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->options)) { 159 160 if ($action === 'dfehc_async_heartbeat') { 161 if ((bool) apply_filters('dfehc_allow_public_async', false)) { 162 add_action("wp_ajax_nopriv_$action", $callback); 163 } 85 164 return; 86 165 } 87 $opt_key = "_transient_$key"; 88 $opt_key_to = "_transient_timeout_$key"; 89 $wpdb->suppress_errors(true); 90 $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key)); 91 if ($autoload === 'yes') { 92 $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key, 'autoload' => 'yes'], ['%s'], ['%s','%s']); 93 } 94 $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to)); 95 if ($autoload_to === 'yes') { 96 $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key_to, 'autoload' => 'yes'], ['%s'], ['%s','%s']); 97 } 98 $wpdb->suppress_errors(false); 166 167 if ((bool) apply_filters("dfehc_{$action}_allow_public", false)) { 168 add_action("wp_ajax_nopriv_$action", $callback); 169 } 99 170 } 100 171 } … … 107 178 return (int) $cached; 108 179 } 180 181 $override = getenv('DFEHC_CPU_CORES'); 182 if ($override !== false && is_numeric($override) && (int) $override > 0) { 183 $cached = (int) $override; 184 return (int) $cached; 185 } 186 if (defined('DFEHC_CPU_CORES') && is_numeric(DFEHC_CPU_CORES) && (int) DFEHC_CPU_CORES > 0) { 187 $cached = (int) DFEHC_CPU_CORES; 188 return (int) $cached; 189 } 190 109 191 $detected = 1; 110 if (is_readable('/proc/cpuinfo')) { 111 $cnt = preg_match_all('/^processor/m', (string) file_get_contents('/proc/cpuinfo'));112 if ($cnt > 0) {113 $detected = $cnt;114 }115 } else{116 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions')));117 if (function_exists('shell_exec') && !in_array('shell_exec', $disabled, true)) {118 $out = (string) (shell_exec('nproc 2>/dev/null') ?? '');119 if (ctype_digit(trim($out)) && (int) $out > 0) {120 $detected = (int) trim($out);192 193 if (is_readable('/sys/fs/cgroup/cpu.max')) { 194 $line = trim((string) @file_get_contents('/sys/fs/cgroup/cpu.max')); 195 if ($line !== '') { 196 $parts = preg_split('/\s+/', $line); 197 if (is_array($parts) && count($parts) >= 2) { 198 $quota = is_numeric($parts[0]) ? (float) $parts[0] : 0.0; 199 $period = is_numeric($parts[1]) ? (float) $parts[1] : 0.0; 200 if ($period > 0.0 && $quota > 0.0) { 201 $detected = max(1, (int) ceil($quota / $period)); 202 } 121 203 } 122 204 } 123 205 } 124 $env_override = getenv('DFEHC_CPU_CORES'); 125 if ($env_override !== false && ctype_digit((string) $env_override) && (int) $env_override > 0) { 126 $detected = (int) $env_override; 127 } 128 $detected = (int) apply_filters('dfehc_cpu_cores', $detected); 129 if ($detected <= 0) { 130 $detected = 1; 131 } 206 207 if ($detected === 1 && is_readable('/proc/cpuinfo')) { 208 $content = (string) @file_get_contents('/proc/cpuinfo'); 209 if ($content !== '') { 210 $cnt = preg_match_all('/^processor\s*:/m', $content); 211 if ($cnt > 0) { 212 $detected = $cnt; 213 } 214 } 215 } 216 217 $detected = (int) apply_filters('dfehc_cpu_cores', max(1, $detected)); 132 218 $cached = $detected; 133 219 return (int) $cached; … … 135 221 } 136 222 137 function dfehc_get_or_calculate_server_load() 138 { 139 $key = dfehc_scoped_key(DFEHC_SERVER_LOAD); 140 $load = get_transient($key); 141 if ($load !== false) { 142 return (float) $load; 143 } 144 $ttl = dfehc_server_load_ttl(); 145 $lock = dfehc_acquire_lock(DFEHC_LOAD_LOCK_BASE, $ttl + 5); 146 if (!$lock) { 223 if (!function_exists('dfehc_should_attempt_load_calculation')) { 224 function dfehc_should_attempt_load_calculation(): bool 225 { 226 $is_cron = function_exists('wp_doing_cron') && wp_doing_cron(); 227 $is_ajax = function_exists('wp_doing_ajax') && wp_doing_ajax(); 228 $is_json = function_exists('wp_is_json_request') && wp_is_json_request(); 229 $is_cli = defined('WP_CLI') && WP_CLI; 230 231 if ($is_cron) { 232 return (bool) apply_filters('dfehc_allow_load_calc_in_cron', true); 233 } 234 235 if ($is_ajax || $is_json || $is_cli) { 236 return (bool) apply_filters('dfehc_allow_load_calc_in_non_html', false); 237 } 238 239 if (is_admin()) { 240 return (bool) apply_filters('dfehc_allow_load_calc_in_admin', false); 241 } 242 243 $rate = (float) apply_filters('dfehc_load_calc_sample_rate', 0.15); 244 if (!is_finite($rate) || $rate < 0.0) $rate = 0.0; 245 if ($rate > 1.0) $rate = 1.0; 246 247 if ($rate < 1.0) { 248 $r = mt_rand(0, 1000000) / 1000000; 249 if ($r > $rate) { 250 return false; 251 } 252 } 253 254 $cd = (int) apply_filters('dfehc_load_calc_cooldown', 30); 255 $cd = max(0, $cd); 256 if ($cd > 0) { 257 $cdKey = dfehc_scoped_key('dfehc_load_calc_cd'); 258 if (dfehc_cache_get($cdKey) !== null) { 259 return false; 260 } 261 dfehc_set_transient_noautoload($cdKey, 1, $cd); 262 } 263 264 return true; 265 } 266 } 267 268 if (!function_exists('dfehc_get_or_calculate_server_load')) { 269 function dfehc_get_or_calculate_server_load() 270 { 271 static $requestMemo = null; 272 if ($requestMemo !== null) { 273 return $requestMemo; 274 } 275 276 $key = dfehc_scoped_key(DFEHC_SERVER_LOAD); 277 $cached = dfehc_cache_get($key); 278 279 $ttl = (int) apply_filters('dfehc_server_load_ttl', 120); 280 $ttl = max(30, $ttl); 281 282 $staleTtl = (int) apply_filters('dfehc_server_load_stale_ttl', max(300, $ttl * 10)); 283 $staleTtl = max($ttl, $staleTtl); 284 285 $now = time(); 286 $cachedPayloadKey = dfehc_scoped_key('dfehc_server_load_payload'); 287 $payload = dfehc_cache_get($cachedPayloadKey); 288 289 if (is_array($payload) && isset($payload['v'], $payload['t']) && is_numeric($payload['v']) && is_numeric($payload['t'])) { 290 $age = $now - (int) $payload['t']; 291 if ($age < 0) $age = 0; 292 if ($age <= $ttl) { 293 $requestMemo = (float) $payload['v']; 294 return $requestMemo; 295 } 296 if ($age <= $staleTtl && !$cached) { 297 $cached = (float) $payload['v']; 298 } 299 } 300 301 if ($cached !== null && is_numeric($cached)) { 302 $requestMemo = (float) $cached; 303 if (!dfehc_should_attempt_load_calculation()) { 304 return $requestMemo; 305 } 306 } else { 307 if (!dfehc_should_attempt_load_calculation()) { 308 $requestMemo = false; 309 return $requestMemo; 310 } 311 } 312 313 $lock = dfehc_acquire_lock(DFEHC_LOAD_LOCK_BASE, $ttl + 10); 314 if (!$lock) { 315 if ($cached !== null && is_numeric($cached)) { 316 $requestMemo = (float) $cached; 317 return $requestMemo; 318 } 319 if (is_array($payload) && isset($payload['v'], $payload['t']) && is_numeric($payload['v']) && is_numeric($payload['t'])) { 320 $age = $now - (int) $payload['t']; 321 if ($age < 0) $age = 0; 322 if ($age <= $staleTtl) { 323 $requestMemo = (float) $payload['v']; 324 return $requestMemo; 325 } 326 } 327 $requestMemo = false; 328 return $requestMemo; 329 } 330 331 try { 332 $raw = dfehc_calculate_server_load(); 333 if ($raw === false) { 334 $requestMemo = false; 335 return $requestMemo; 336 } 337 338 $cores = max(1, dfehc_get_cpu_cores()); 339 $load_pct = min(100.0, round(((float) $raw / $cores) * 100.0, 2)); 340 341 $payloadStore = ['v' => (float) $load_pct, 't' => (int) $now]; 342 dfehc_set_transient_noautoload($cachedPayloadKey, $payloadStore, $ttl + dfehc_rand_jitter(0, 10)); 343 dfehc_set_transient_noautoload($key, (float) $load_pct, $ttl); 344 345 $requestMemo = (float) $load_pct; 346 return $requestMemo; 347 } finally { 348 dfehc_release_lock($lock); 349 } 350 } 351 } 352 353 if (!function_exists('dfehc_calculate_server_load')) { 354 function dfehc_calculate_server_load() 355 { 356 if (function_exists('sys_getloadavg')) { 357 $load = sys_getloadavg(); 358 if (is_array($load) && isset($load[0]) && is_numeric($load[0]) && (float) $load[0] >= 0.0) { 359 return (float) $load[0]; 360 } 361 } 362 363 if (is_readable('/proc/loadavg')) { 364 $content = (string) @file_get_contents('/proc/loadavg'); 365 $parts = explode(' ', trim($content)); 366 if (isset($parts[0]) && is_numeric($parts[0]) && (float) $parts[0] >= 0.0) { 367 return (float) $parts[0]; 368 } 369 } 370 371 if (class_exists(\DynamicHeartbeat\Dfehc_ServerLoadEstimator::class)) { 372 $pct = \DynamicHeartbeat\Dfehc_ServerLoadEstimator::get_server_load(); 373 if ($pct !== false && is_numeric($pct) && (float) $pct >= 0.0) { 374 $cores = max(1, dfehc_get_cpu_cores()); 375 return (float) ($cores * (((float) $pct) / 100.0)); 376 } 377 } 378 147 379 return false; 148 380 } 149 $raw = dfehc_calculate_server_load();150 dfehc_release_lock($lock);151 if ($raw === false) {152 return false;153 }154 $cores = max(1, dfehc_get_cpu_cores());155 $load_pct = min(100.0, round($raw / $cores * 100, 2));156 dfehc_set_transient_noautoload($key, $load_pct, $ttl);157 return $load_pct;158 }159 160 function dfehc_calculate_server_load()161 {162 if (function_exists('sys_getloadavg')) {163 $load = sys_getloadavg();164 if (isset($load[0])) {165 return (float) $load[0];166 }167 }168 if (is_readable('/proc/loadavg')) {169 $parts = explode(' ', (string) file_get_contents('/proc/loadavg'));170 if (isset($parts[0])) {171 return (float) $parts[0];172 }173 }174 $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions')));175 if (function_exists('shell_exec') && !in_array('shell_exec', $disabled, true)) {176 $out = (string) (shell_exec('LANG=C uptime 2>/dev/null') ?? '');177 if ($out && preg_match('/load average[s]?:\s*([0-9.]+)/', $out, $m)) {178 return (float) $m[1];179 }180 }181 if (defined('DFEHC_PLUGIN_PATH')) {182 $estimator = rtrim((string) DFEHC_PLUGIN_PATH, "/\\") . '/defibrillator/load-estimator.php';183 if (file_exists($estimator)) {184 require_once $estimator;185 if (class_exists(\DynamicHeartbeat\Dfehc_ServerLoadEstimator::class)) {186 if (method_exists(\DynamicHeartbeat\Dfehc_ServerLoadEstimator::class, 'get_server_load')) {187 $pct = \DynamicHeartbeat\Dfehc_ServerLoadEstimator::get_server_load();188 if ($pct !== false && is_numeric($pct)) {189 $cores = max(1, dfehc_get_cpu_cores());190 return (float) ($cores * ((float) $pct) / 100.0);191 }192 } elseif (method_exists(\DynamicHeartbeat\Dfehc_ServerLoadEstimator::class, 'estimate')) {193 $raw = \DynamicHeartbeat\Dfehc_ServerLoadEstimator::estimate();194 if (is_numeric($raw)) {195 return (float) $raw;196 }197 }198 }199 }200 }201 return false;202 381 } 203 382 … … 216 395 public function maybe_schedule(): void 217 396 { 218 if ($this->scheduled === true) {397 if ($this->scheduled) { 219 398 return; 220 399 } 221 400 $this->scheduled = true; 222 $interval = absint(300); 401 402 if (!function_exists('wp_next_scheduled') || !function_exists('wp_schedule_event')) { 403 return; 404 } 405 406 $recurrence = (string) apply_filters('dfehc_async_cron_recurrence', 'dfehc_5_minutes'); 407 $schedules = function_exists('wp_get_schedules') ? (array) wp_get_schedules() : []; 408 409 if (!isset($schedules[$recurrence])) { 410 $recurrence = 'dfehc_5_minutes'; 411 } 412 if (!isset($schedules[$recurrence])) { 413 $recurrence = 'hourly'; 414 } 415 416 $interval = isset($schedules[$recurrence]['interval']) ? (int) $schedules[$recurrence]['interval'] : 3600; 417 $interval = max(60, $interval); 418 223 419 $now = time(); 224 420 $aligned = $now - ($now % $interval) + $interval; 225 $schedules = function_exists('wp_get_schedules') ? wp_get_schedules() : []; 226 if (!isset($schedules['dfehc_5_minutes'])) { 227 return; 228 } 421 229 422 if (!wp_next_scheduled($this->action)) { 230 wp_schedule_event($aligned, 'dfehc_5_minutes', $this->action); 231 } else { 232 $next = wp_next_scheduled($this->action); 233 if ($next === false || ($next - $now) > ($interval * 2)) { 234 wp_schedule_single_event($now + $interval, $this->action); 235 } 423 wp_schedule_event($aligned, $recurrence, $this->action); 236 424 } 237 425 } … … 239 427 public function handle_async_request(): void 240 428 { 241 $cap = apply_filters('dfehc_required_capability', DFEHC_CAPABILITY);242 429 $allow_public = (bool) apply_filters('dfehc_allow_public_async', false); 243 244 430 $nonce_action = 'dfehc-' . $this->action; 245 $nonce = ''; 246 if (isset($_REQUEST['nonce'])) { 247 $nonce = sanitize_text_field(wp_unslash((string) $_REQUEST['nonce'])); 248 } 431 $nonce = isset($_REQUEST['nonce']) ? sanitize_text_field(wp_unslash((string) $_REQUEST['nonce'])) : ''; 249 432 250 433 if (!$allow_public) { 251 434 $valid = function_exists('check_ajax_referer') 252 ? check_ajax_referer($nonce_action, 'nonce', false)253 : wp_verify_nonce($nonce, $nonce_action);435 ? (bool) check_ajax_referer($nonce_action, 'nonce', false) 436 : (bool) wp_verify_nonce($nonce, $nonce_action); 254 437 255 438 if (!$valid) { 256 wp_send_json_error(['message' => 'dfehc/dfehc_async_heartbeat: invalid nonce'], 403); 257 } 258 if (!current_user_can($cap)) { 259 wp_send_json_error(['message' => 'dfehc/dfehc_async_heartbeat: not authorized'], 403); 439 wp_send_json_error(['message' => 'invalid nonce'], 403); 440 } 441 442 $cap = apply_filters('dfehc_required_capability', DFEHC_CAPABILITY); 443 if (!current_user_can((string) $cap)) { 444 wp_send_json_error(['message' => 'not authorized'], 403); 260 445 } 261 446 } else { 262 $limit = absint(apply_filters('dfehc_public_rate_limit', 30)); 263 $window = absint(apply_filters('dfehc_public_rate_window', 60)); 264 $ip = dfehc_client_ip(); 447 $limit = (int) apply_filters('dfehc_public_rate_limit', 20); 448 $window = (int) apply_filters('dfehc_public_rate_window', 60); 449 $limit = max(1, $limit); 450 $window = max(1, $window); 451 452 $ip = function_exists('dfehc_client_ip') ? (string) dfehc_client_ip() : '0.0.0.0'; 265 453 $rl_key = dfehc_scoped_key('dfehc_rl_' . md5($ip)); 266 454 $cnt = (int) get_transient($rl_key); 455 267 456 if ($cnt >= $limit) { 268 wp_send_json_error(['message' => ' dfehc/dfehc_async_heartbeat:rate limited'], 429);457 wp_send_json_error(['message' => 'rate limited'], 429); 269 458 } 270 459 dfehc_set_transient_noautoload($rl_key, $cnt + 1, $window); 271 460 } 272 461 273 try { 274 $this->run_action(); 275 wp_send_json_success(true); 276 } catch (\Throwable $e) { 277 wp_send_json_error(['message' => 'dfehc/dfehc_async_heartbeat: internal error'], 500); 278 } 279 280 wp_die(); 462 $this->run_action(); 463 wp_send_json_success(true); 281 464 } 282 465 283 466 public function run_action(): void 284 467 { 285 $lock = dfehc_acquire_lock('dfehc_async_run', 30); 468 $lock_key = dfehc_scoped_key('dfehc_async_run'); 469 $lock_ttl = (int) apply_filters('dfehc_async_lock_ttl', 45); 470 $lock_ttl = max(10, min(120, $lock_ttl)); 471 472 $lock = dfehc_acquire_lock($lock_key, $lock_ttl); 286 473 if (!$lock) { 287 474 return; 288 475 } 476 289 477 try { 290 $last_activity = get_transient(dfehc_scoped_key('dfehc_last_user_activity')); 291 if ($last_activity === false) { 292 dfehc_set_transient_noautoload(dfehc_scoped_key(DFEHC_RECOMMENDED_INTERVAL), dfehc_fallback_interval(), 300); 478 $activity_key = dfehc_scoped_key('dfehc_last_user_activity'); 479 $last_activity = dfehc_cache_get($activity_key); 480 if ($last_activity === null || !is_numeric($last_activity)) { 481 dfehc_set_transient_noautoload(dfehc_scoped_key(DFEHC_RECOMMENDED_INTERVAL), dfehc_fallback_interval(), 600); 293 482 return; 294 483 } 484 295 485 $now = time(); 296 $elapsed = $now - (int) $last_activity; 297 if ($elapsed < 0) { 298 $elapsed = 0; 299 } 300 $load_raw = dfehc_calculate_server_load(); 301 if ($load_raw === false) { 302 dfehc_set_transient_noautoload(dfehc_scoped_key(DFEHC_RECOMMENDED_INTERVAL), dfehc_fallback_interval(), 300); 486 $elapsed = max(0, $now - (int) $last_activity); 487 488 $load_pct = dfehc_get_or_calculate_server_load(); 489 if ($load_pct === false || !is_numeric($load_pct)) { 490 dfehc_set_transient_noautoload(dfehc_scoped_key(DFEHC_RECOMMENDED_INTERVAL), dfehc_fallback_interval(), 600); 303 491 return; 304 492 } 305 $cores = max(1, dfehc_get_cpu_cores()); 306 $load_pct = min(100.0, round($load_raw / $cores * 100, 2)); 307 dfehc_set_transient_noautoload(dfehc_scoped_key(DFEHC_SERVER_LOAD), $load_pct, dfehc_server_load_ttl()); 308 $samples = get_transient(dfehc_scoped_key(DFEHC_LOAD_AVERAGES)); 493 $load_pct = max(0.0, min(100.0, (float) $load_pct)); 494 495 $ttl = (int) apply_filters('dfehc_server_load_ttl', 120); 496 $ttl = max(30, $ttl); 497 dfehc_set_transient_noautoload(dfehc_scoped_key(DFEHC_SERVER_LOAD), $load_pct, $ttl); 498 499 $samples_key = dfehc_scoped_key(DFEHC_LOAD_AVERAGES); 500 $samples = dfehc_cache_get($samples_key); 309 501 if (!is_array($samples)) { 310 502 $samples = []; 311 503 } 504 312 505 $samples[] = $load_pct; 313 if (count($samples) > 5) { 314 array_shift($samples); 315 } 316 dfehc_set_transient_noautoload(dfehc_scoped_key(DFEHC_LOAD_AVERAGES), $samples, 1800); 317 $weights_raw = apply_filters('dfehc_load_weights', [5, 4, 3, 2, 1]); 318 $wr = array_values((array) $weights_raw); 319 if (!$wr) { 320 $wr = [1]; 321 } 322 $wr_count = count($wr); 323 $weights = []; 506 507 $max_samples = (int) apply_filters('dfehc_load_avg_samples', 6); 508 $max_samples = max(2, min(30, $max_samples)); 509 510 if (count($samples) > $max_samples) { 511 $samples = array_slice($samples, -$max_samples); 512 } 513 514 dfehc_set_transient_noautoload($samples_key, array_values($samples), 3600); 515 516 $weights = (array) apply_filters('dfehc_load_weights', [4, 3, 2, 1]); 517 $weights = array_values(array_filter($weights, static function ($v): bool { 518 return is_numeric($v) && (float) $v > 0.0; 519 })); 520 if (!$weights) { 521 $weights = [1]; 522 } 523 324 524 $n = count($samples); 325 for ($i = 0; $i < $n; $i++) { 326 $j = min($n - 1 - $i, $wr_count - 1); 327 $w = isset($wr[$j]) ? $wr[$j] : 1; 328 if (!is_numeric($w) || $w <= 0) { 329 $w = 1; 330 } 331 $weights[] = (float) $w; 332 } 333 $avg_load = dfehc_weighted_average($samples, $weights); 525 $wCount = count($weights); 526 527 $weighted_sum = 0.0; 528 $weight_sum = 0.0; 529 530 foreach ($samples as $i => $v) { 531 $age = $n - 1 - (int) $i; 532 $w = (float) ($weights[min($age, $wCount - 1)] ?? 1.0); 533 $w = max(0.0001, $w); 534 $weighted_sum += ((float) $v) * $w; 535 $weight_sum += $w; 536 } 537 538 $avg_load = $weight_sum > 0 ? round($weighted_sum / $weight_sum, 2) : 0.0; 539 $avg_load = max(0.0, min(100.0, (float) $avg_load)); 540 334 541 $interval = $this->calculate_interval($elapsed, $avg_load); 335 dfehc_set_transient_noautoload(dfehc_scoped_key(DFEHC_RECOMMENDED_INTERVAL), $interval, 300); 542 543 $rec_ttl = (int) apply_filters('dfehc_recommended_interval_ttl', 600); 544 $rec_ttl = max(30, $rec_ttl + dfehc_rand_jitter(0, 10)); 545 546 dfehc_set_transient_noautoload(dfehc_scoped_key(DFEHC_RECOMMENDED_INTERVAL), $interval, $rec_ttl); 336 547 } finally { 337 548 dfehc_release_lock($lock); … … 341 552 protected function calculate_interval(int $elapsed, float $load_pct): int 342 553 { 343 if ($elapsed <= dfehc_min_interval() && $load_pct < dfehc_max_server_load()) { 344 return dfehc_min_interval(); 345 } 346 if ($elapsed >= dfehc_max_interval()) { 347 return dfehc_max_interval(); 348 } 349 $load_factor = min(1.0, $load_pct / dfehc_max_server_load()); 350 $activity_factor = ($elapsed - dfehc_min_interval()) / (dfehc_max_interval() - dfehc_min_interval()); 554 $min_i = dfehc_min_interval(); 555 $max_i = dfehc_max_interval(); 556 $max_load = (float) dfehc_max_server_load(); 557 558 if ($max_i <= $min_i) { 559 return $min_i; 560 } 561 562 $load_pct = max(0.0, min(100.0, $load_pct)); 563 $elapsed = max(0, $elapsed); 564 565 $hard_load = (float) apply_filters('dfehc_hard_max_load_factor', 1.10); 566 $hard_load = max(1.0, min(2.0, $hard_load)); 567 568 if ($elapsed <= $min_i && $load_pct < $max_load) { 569 return $min_i; 570 } 571 if ($elapsed >= $max_i || $load_pct >= ($max_load * $hard_load)) { 572 return $max_i; 573 } 574 575 $load_factor = $max_load > 0 ? min(1.0, $load_pct / $max_load) : 0.0; 576 577 $activity_factor = ($elapsed - $min_i) / ($max_i - $min_i); 351 578 $activity_factor = max(0.0, min(1.0, $activity_factor)); 352 $dominant = max($load_factor, $activity_factor); 353 return (int) round(dfehc_min_interval() + $dominant * (dfehc_max_interval() - dfehc_min_interval())); 579 580 $act_w = (float) apply_filters('dfehc_activity_weight', 0.7); 581 $load_w = (float) apply_filters('dfehc_load_weight', 0.3); 582 583 $act_w = max(0.0, min(1.0, $act_w)); 584 $load_w = max(0.0, min(1.0, $load_w)); 585 586 $sum = $act_w + $load_w; 587 if ($sum <= 0.0) { 588 $act_w = 0.7; 589 $load_w = 0.3; 590 $sum = 1.0; 591 } 592 $act_w /= $sum; 593 $load_w /= $sum; 594 595 $combined = max($load_factor, ($activity_factor * $act_w) + ($load_factor * $load_w)); 596 $combined = max(0.0, min(1.0, (float) $combined)); 597 598 $interval = (int) round($min_i + $combined * ($max_i - $min_i)); 599 $interval = max($min_i, min($max_i, $interval)); 600 601 $step = (int) apply_filters('dfehc_interval_step', 5); 602 $step = max(0, $step); 603 if ($step > 1) { 604 $interval = (int) (round($interval / $step) * $step); 605 $interval = max($min_i, min($max_i, $interval)); 606 } 607 608 return $interval; 354 609 } 355 610 } … … 363 618 $tw = 0.0; 364 619 foreach ($values as $i => $v) { 365 $w = isset($weights[$i]) ? $weights[$i] : 1; 366 if (!is_numeric($w) || $w <= 0) { 367 $w = 1; 368 } 369 $tv += (float) $v * (float) $w; 370 $tw += (float) $w; 620 $w = isset($weights[$i]) && is_numeric($weights[$i]) && (float) $weights[$i] > 0.0 ? (float) $weights[$i] : 1.0; 621 $tv += (float) $v * $w; 622 $tw += $w; 371 623 } 372 624 return $tw > 0 ? round($tv / $tw, 2) : 0.0; … … 377 629 { 378 630 $key = dfehc_scoped_key(DFEHC_RECOMMENDED_INTERVAL); 379 $interval = get_transient($key);380 if ($interval !== false) {631 $interval = dfehc_cache_get($key); 632 if ($interval !== null && is_numeric($interval)) { 381 633 return (float) $interval; 382 634 } 383 return $current_load >= dfehc_max_server_load() 635 $current_load = max(0.0, min(100.0, $current_load)); 636 return $current_load >= (float) dfehc_max_server_load() 384 637 ? (float) dfehc_max_interval() 385 638 : (float) dfehc_min_interval(); … … 390 643 { 391 644 if (!isset($s['dfehc_5_minutes'])) { 392 $s['dfehc_5_minutes'] = ['interval' => 300, 'display' => __('Every 5 Minutes', 'dfehc')]; 393 } 394 if (!isset($s['dfehc_daily'])) { 395 $s['dfehc_daily'] = ['interval' => DAY_IN_SECONDS, 'display' => __('Once a day (DFEHC)', 'dfehc')]; 645 $s['dfehc_5_minutes'] = ['interval' => 300, 'display' => __('Every 5 Minutes (DFEHC)', 'dfehc')]; 396 646 } 397 647 return $s; 398 648 } 399 add_filter('cron_schedules', 'dfehc_register_schedules', 1); 400 401 function dfehc_prune_server_load_logs(): void 402 { 403 $max_age = absint(apply_filters('dfehc_log_retention_seconds', DAY_IN_SECONDS)); 404 $max_cnt = absint(apply_filters('dfehc_log_retention_max', 1440)); 405 $now = time(); 406 $all_ids = function_exists('get_sites') 407 ? array_map('intval', get_sites(['fields' => 'ids'])) 408 : [get_current_blog_id()]; 409 $chunk_size = absint(apply_filters('dfehc_prune_chunk_size', 50)); 410 $offset_key = 'dfehc_prune_offset'; 411 $offset = (int) get_site_option($offset_key, 0); 412 $chunk = array_slice($all_ids, $offset, $chunk_size); 413 if ($chunk === []) { 414 $offset = 0; 415 $chunk = array_slice($all_ids, 0, $chunk_size); 416 } 417 foreach ($chunk as $id) { 418 $did_switch = false; 419 if (is_multisite()) { 420 switch_to_blog($id); 421 $did_switch = true; 422 } 423 try { 424 $option = 'dfehc_server_load_logs_' . get_current_blog_id(); 425 $logs = get_option($option, []); 426 if ($logs) { 427 $cutoff = $now - $max_age; 428 $logs = array_filter( 429 $logs, 430 static function ($row) use ($cutoff) { 431 return isset($row['timestamp']) && (int) $row['timestamp'] >= $cutoff; 432 } 433 ); 434 if (count($logs) > $max_cnt) { 435 $logs = array_slice($logs, -$max_cnt); 436 } 437 update_option($option, array_values($logs), false); 438 } 439 } finally { 440 if ($did_switch) { 441 restore_current_blog(); 442 } 443 } 444 } 445 $offset += $chunk_size; 446 update_site_option($offset_key, $offset); 447 } 448 449 add_action('dfehc_prune_logs_hook', 'dfehc_prune_server_load_logs'); 450 451 function dfehc_schedule_prune_logs(): void 452 { 453 if (!function_exists('wp_next_scheduled') || !function_exists('wp_schedule_event')) { 454 return; 455 } 456 if (wp_next_scheduled('dfehc_prune_logs_hook')) { 457 return; 458 } 459 $t = strtotime('today 03:00'); 460 if ($t < time()) { 461 $t += DAY_IN_SECONDS; 462 } 463 wp_schedule_event($t, 'dfehc_daily', 'dfehc_prune_logs_hook'); 464 } 465 466 add_action('init', 'dfehc_schedule_prune_logs', 1); 467 468 add_filter('dfehc_required_capability', function () { return 'manage_options'; }); 469 add_filter('dfehc_server_load_ttl', function () { return 120; }); 470 add_filter('dfehc_load_weights', function () { return [3, 2, 1]; }); 471 add_filter('dfehc_async_retry', function () { return 1; }); 472 add_filter('dfehc_log_retention_seconds', function () { return 2 * DAY_IN_SECONDS; }); 473 add_filter('dfehc_log_retention_max', function () { return 3000; }); 474 add_filter('dfehc_allow_public_server_load', '__return_false'); 475 add_filter('dfehc_allow_public_async', '__return_false'); 649 add_filter('cron_schedules', 'dfehc_register_schedules', 10); 476 650 477 651 new Dfehc_Heartbeat_Async(); -
dynamic-front-end-heartbeat-control/trunk/heartbeat-controller.php
r3427163 r3461136 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.998 6 Version: 1.2.998.1 7 7 Author: Codeloghin 8 8 Author URI: https://codeloghin.com … … 73 73 } 74 74 75 if (!function_exists('dfehc_set_transient_noautoload')) {76 function dfehc_set_transient_noautoload(string $key, $value, int $expiration): void77 {78 $jitter = 0;79 if (function_exists('random_int')) {80 try {81 $jitter = random_int(0, 5);82 } catch (\Throwable $e) {83 $jitter = 0;84 }85 }86 $expiration = max(1, $expiration + $jitter);87 88 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) {89 wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $expiration);90 return;91 }92 93 set_transient($key, $value, $expiration);94 95 if (isset($GLOBALS['wpdb'])) {96 global $wpdb;97 if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->options)) {98 return;99 }100 101 $autoload_cache_key = 'dfehc_autoload_flag_' . md5((string) $key);102 $autoload_cache = wp_cache_get($autoload_cache_key, DFEHC_CACHE_GROUP);103 if ($autoload_cache === 'no') {104 return;105 }106 107 $opt_key_val = '_transient_' . $key;108 $opt_key_to = '_transient_timeout_' . $key;109 110 $wpdb->suppress_errors(true);111 try {112 $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_val));113 if ($autoload === 'yes') {114 $wpdb->query($wpdb->prepare("UPDATE {$wpdb->options} SET autoload='no' WHERE option_name=%s AND autoload='yes' LIMIT 1", $opt_key_val));115 }116 $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to));117 if ($autoload_to === 'yes') {118 $wpdb->query($wpdb->prepare("UPDATE {$wpdb->options} SET autoload='no' WHERE option_name=%s AND autoload='yes' LIMIT 1", $opt_key_to));119 }120 } finally {121 $wpdb->suppress_errors(false);122 wp_cache_set($autoload_cache_key, 'no', DFEHC_CACHE_GROUP, DAY_IN_SECONDS);123 }124 }125 }126 }127 128 75 add_action('wp_default_scripts', function (WP_Scripts $scripts) { 129 76 if (is_admin() || wp_doing_ajax()) { … … 133 80 if (isset($scripts->registered['heartbeat'])) { 134 81 $scripts->registered['heartbeat']->src = plugin_dir_url(__FILE__) . 'js/heartbeat.min.js'; 135 $scripts->registered['heartbeat']->ver = '1.6. 1';82 $scripts->registered['heartbeat']->ver = '1.6.5'; 136 83 $scripts->registered['heartbeat']->deps = array('jquery'); 137 84 } … … 146 93 wp_enqueue_script('heartbeat'); 147 94 95 $site_key = function_exists('dfehc_scoped_key') ? (string) dfehc_scoped_key('site') : (string) wp_parse_url(home_url(), PHP_URL_HOST); 96 $site_key = $site_key ?: (string) wp_parse_url(home_url(), PHP_URL_HOST); 97 $site_key = $site_key ?: 'site'; 98 99 $ver = defined('DFEHC_VERSION') ? (string) DFEHC_VERSION : (string) filemtime(__FILE__); 100 $cache_duration_ms = (int) apply_filters('dfehc_js_cache_duration_ms', 10 * 60 * 1000); 101 $cache_duration_ms = max(15000, min(60 * 60 * 1000, $cache_duration_ms)); 102 103 $cache_bypass_rate = (float) apply_filters('dfehc_js_cache_bypass_rate', 0.05); 104 $cache_bypass_rate = max(0.0, min(1.0, $cache_bypass_rate)); 105 106 $leader_ttl_ms = (int) apply_filters('dfehc_js_leader_ttl_ms', 8000); 107 $leader_beat_ms = (int) apply_filters('dfehc_js_leader_beat_ms', 3000); 108 $leader_ttl_ms = max(2000, min(20000, $leader_ttl_ms)); 109 $leader_beat_ms = max(800, min(10000, $leader_beat_ms)); 148 110 $load = function_exists('dfehc_get_server_load') ? dfehc_get_server_load() : null; 149 111 if ($load === false || $load === null) { 150 $load = (float) DFEHC_MAX_SERVER_LOAD; 151 } 112 $load = (float) DFEHC_MAX_SERVER_LOAD; 113 } 114 $load = (float) $load; 152 115 153 116 $recommended = function_exists('dfehc_calculate_recommended_interval_user_activity') 154 ? dfehc_calculate_recommended_interval_user_activity((float) $load, DFEHC_BATCH_SIZE) 155 : 60.0; 156 157 $host = wp_parse_url(home_url(), PHP_URL_HOST) ?: 'site'; 158 $ver = defined('DFEHC_VERSION') ? (string) DFEHC_VERSION : (string) filemtime(__FILE__); 159 160 $min_interval_option = defined('DFEHC_OPTION_MIN_INTERVAL') ? (int) get_option(DFEHC_OPTION_MIN_INTERVAL, defined('DFEHC_DEFAULT_MIN_INTERVAL') ? DFEHC_DEFAULT_MIN_INTERVAL : DFEHC_MIN_INTERVAL) : (int) DFEHC_MIN_INTERVAL; 161 $max_interval_option = defined('DFEHC_OPTION_MAX_INTERVAL') ? (int) get_option(DFEHC_OPTION_MAX_INTERVAL, defined('DFEHC_DEFAULT_MAX_INTERVAL') ? DFEHC_DEFAULT_MAX_INTERVAL : DFEHC_MAX_INTERVAL) : (int) DFEHC_MAX_INTERVAL; 162 $max_server_load_option = defined('DFEHC_OPTION_MAX_SERVER_LOAD') ? (float) get_option(DFEHC_OPTION_MAX_SERVER_LOAD, defined('DFEHC_DEFAULT_MAX_SERVER_LOAD') ? DFEHC_DEFAULT_MAX_SERVER_LOAD : DFEHC_MAX_SERVER_LOAD) : (float) DFEHC_MAX_SERVER_LOAD; 163 117 ? dfehc_calculate_recommended_interval_user_activity($load, DFEHC_BATCH_SIZE) 118 : 60.0; 164 119 wp_localize_script( 165 120 'heartbeat', … … 207 162 { 208 163 $total_weighted_duration = 0.0; 209 $total_weight = 0; 210 $offset = 0; 211 $start_time = microtime(true); 212 $time_limit = 1.5; 164 $total_weight = 0; 165 166 $batch_size = max(1, min(75, $batch_size)); 167 $offset = 0; 168 169 if (is_admin() && !(function_exists('wp_doing_ajax') && wp_doing_ajax())) { 170 $allow_admin = (bool) apply_filters('dfehc_allow_user_activity_scan_in_admin', false); 171 if (!$allow_admin) { 172 return ['total_duration' => 0.0, 'total_weight' => 0]; 173 } 174 } 175 176 $start_time = function_exists('microtime') ? (float) microtime(true) : (float) time(); 177 178 $time_limit = (float) apply_filters('dfehc_activity_summary_time_limit', 0.35); 179 if (!is_finite($time_limit) || $time_limit <= 0.0) { 180 $time_limit = 0.35; 181 } 182 $time_limit = max(0.10, min(1.50, $time_limit)); 183 $deadline = $start_time + $time_limit; 184 185 $max_users = (int) apply_filters('dfehc_activity_summary_max_users', 75); 186 $max_users = max(1, min(75, $max_users)); 187 188 $max_points_per_user = (int) apply_filters('dfehc_activity_summary_max_points_per_user', 40); 189 $max_points_per_user = max(1, min(75, $max_points_per_user)); 190 191 $processed = 0; 192 213 193 while (true) { 214 if (microtime(true) - $start_time > $time_limit) { 194 $now = function_exists('microtime') ? (float) microtime(true) : (float) time(); 195 if ($now > $deadline) { 215 196 break; 216 197 } 198 if ($processed >= $max_users) { 199 break; 200 } 201 217 202 $userBatch = dfehc_get_users_in_batches($batch_size, $offset); 218 if (!$userBatch ) {203 if (!$userBatch || !is_array($userBatch)) { 219 204 break; 220 205 } 206 221 207 foreach ($userBatch as $user) { 222 $activity = get_user_meta($user->ID, 'dfehc_user_activity', true); 223 if (empty($activity['durations']) || !is_array($activity['durations'])) { 208 $processed++; 209 if ($processed > $max_users) { 210 break 2; 211 } 212 213 $uid = 0; 214 if (is_object($user) && isset($user->ID)) { 215 $uid = (int) $user->ID; 216 } elseif (is_numeric($user)) { 217 $uid = (int) $user; 218 } 219 220 if ($uid <= 0) { 224 221 continue; 225 222 } 226 $weight = count($activity['durations']); 227 if ($weight <= 0) { 223 224 $activity = get_user_meta($uid, 'dfehc_user_activity', true); 225 if (!is_array($activity) || empty($activity['durations']) || !is_array($activity['durations'])) { 228 226 continue; 229 227 } 230 $avg = array_sum($activity['durations']) / $weight; 231 $total_weighted_duration += $weight * (float) $avg; 232 $total_weight += $weight; 233 } 228 229 $durations = $activity['durations']; 230 $cnt_all = count($durations); 231 if ($cnt_all <= 0) { 232 continue; 233 } 234 235 if ($cnt_all > $max_points_per_user) { 236 $durations = array_slice($durations, -$max_points_per_user); 237 } 238 239 $sum = 0.0; 240 $cnt = 0; 241 242 foreach ($durations as $d) { 243 if (!is_numeric($d)) { 244 continue; 245 } 246 $v = (float) $d; 247 if (!is_finite($v) || $v < 0.0) { 248 continue; 249 } 250 $sum += $v; 251 $cnt++; 252 } 253 254 if ($cnt <= 0) { 255 continue; 256 } 257 258 $avg = $sum / $cnt; 259 $total_weighted_duration += $cnt * $avg; 260 $total_weight += $cnt; 261 262 $now = function_exists('microtime') ? (float) microtime(true) : (float) time(); 263 if ($now > $deadline) { 264 break 2; 265 } 266 } 267 234 268 $offset += $batch_size; 235 269 } 270 236 271 return ['total_duration' => $total_weighted_duration, 'total_weight' => $total_weight]; 237 272 } … … 347 382 $raw = isset($_SERVER['REMOTE_ADDR']) ? (string) wp_unslash($_SERVER['REMOTE_ADDR']) : '0.0.0.0'; 348 383 $ip = sanitize_text_field($raw); 349 if ($ip === '') { 350 $ip = '0.0.0.0'; 351 } 384 $ip = $ip !== '' ? $ip : '0.0.0.0'; 352 385 } 353 386 … … 356 389 $limit = (int) apply_filters('dfehc_recommend_rl_limit', 30); 357 390 $window = (int) apply_filters('dfehc_recommend_rl_window', 60); 391 $limit = max(1, $limit); 392 $window = max(1, $window); 393 358 394 if ($cnt >= $limit) { 359 395 wp_send_json_error(['message' => 'rate_limited'], 429); … … 361 397 dfehc_set_transient_noautoload($rlk, $cnt + 1, $window); 362 398 399 $now = time(); 400 401 $snap_key = dfehc_scoped_tkey('dfehc_ri_snapshot'); 402 $snap_ttl = (int) apply_filters('dfehc_ri_snapshot_ttl', 15); 403 $snap_ttl = max(5, min(120, $snap_ttl)); 404 405 $stale_ttl = (int) apply_filters('dfehc_ri_snapshot_stale_ttl', 90); 406 $stale_ttl = max($snap_ttl, min(600, $stale_ttl)); 407 408 $snapshot = get_transient($snap_key); 409 $fresh = false; 410 411 if (is_array($snapshot) && isset($snapshot['t'], $snapshot['interval'])) { 412 $age = $now - (int) $snapshot['t']; 413 if ($age < 0) { 414 $age = 0; 415 } 416 if ($age <= $snap_ttl) { 417 $fresh = true; 418 $interval = (float) $snapshot['interval']; 419 $load = isset($snapshot['load']) && is_numeric($snapshot['load']) ? (float) $snapshot['load'] : null; 420 421 wp_send_json_success([ 422 'interval' => $interval > 0 ? $interval : (float) DFEHC_MIN_INTERVAL, 423 'load' => $load, 424 'fresh' => 1, 425 'next_poll_ms' => (int) apply_filters('dfehc_ajax_next_poll_ms_fresh', 45000), 426 ]); 427 } 428 if ($age <= $stale_ttl) { 429 $interval = (float) $snapshot['interval']; 430 $load = isset($snapshot['load']) && is_numeric($snapshot['load']) ? (float) $snapshot['load'] : null; 431 432 $lock_key = dfehc_scoped_tkey('dfehc_ri_snapshot_lock'); 433 $lock_ttl = (int) apply_filters('dfehc_ri_snapshot_lock_ttl', 10); 434 $lock_ttl = max(3, min(30, $lock_ttl)); 435 436 $do_recompute = false; 437 438 $global_rl_key = dfehc_scoped_tkey('dfehc_ri_global_recompute_rl'); 439 $global_rl_win = (int) apply_filters('dfehc_ri_global_recompute_window', 10); 440 $global_rl_lim = (int) apply_filters('dfehc_ri_global_recompute_limit', 4); 441 $global_rl_win = max(2, min(60, $global_rl_win)); 442 $global_rl_lim = max(1, min(50, $global_rl_lim)); 443 444 $global_cnt = (int) get_transient($global_rl_key); 445 if ($global_cnt < $global_rl_lim) { 446 $do_recompute = true; 447 } 448 449 if ($do_recompute) { 450 $got_lock = false; 451 452 if (function_exists('wp_cache_add') && function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) { 453 $got_lock = wp_cache_add($lock_key, 1, DFEHC_CACHE_GROUP, $lock_ttl); 454 } else { 455 $got_lock = (get_transient($lock_key) === false) && set_transient($lock_key, 1, $lock_ttl); 456 } 457 458 if ($got_lock) { 459 dfehc_set_transient_noautoload($global_rl_key, $global_cnt + 1, $global_rl_win); 460 461 $interval_new = dfehc_get_recommended_heartbeat_interval_async(); 462 if (!is_numeric($interval_new) || (float) $interval_new <= 0.0) { 463 $interval_new = $interval; 464 } 465 466 $load_new = function_exists('dfehc_get_server_load') ? dfehc_get_server_load() : null; 467 $load_new = (is_numeric($load_new) && (float) $load_new >= 0.0) ? (float) $load_new : $load; 468 469 $snapshot_new = [ 470 't' => $now, 471 'interval' => (float) $interval_new, 472 'load' => $load_new, 473 ]; 474 475 dfehc_set_transient_noautoload($snap_key, $snapshot_new, $stale_ttl); 476 477 if (function_exists('wp_cache_delete') && function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) { 478 wp_cache_delete($lock_key, DFEHC_CACHE_GROUP); 479 } else { 480 delete_transient($lock_key); 481 } 482 483 wp_send_json_success([ 484 'interval' => (float) $interval_new, 485 'load' => $load_new, 486 'fresh' => 1, 487 'next_poll_ms' => (int) apply_filters('dfehc_ajax_next_poll_ms_fresh', 45000), 488 ]); 489 } 490 } 491 492 wp_send_json_success([ 493 'interval' => $interval > 0 ? $interval : (float) DFEHC_MIN_INTERVAL, 494 'load' => $load, 495 'fresh' => 0, 496 'next_poll_ms' => (int) apply_filters('dfehc_ajax_next_poll_ms_stale', 90000), 497 ]); 498 } 499 } 500 363 501 $interval = dfehc_get_recommended_heartbeat_interval_async(); 364 if (!is_numeric($interval) || $interval <= 0) { 365 $interval = (float) dfehc_calculate_recommended_interval_user_activity(); 366 } 367 wp_send_json_success(['interval' => (float) $interval]); 368 } 502 if (!is_numeric($interval) || (float) $interval <= 0.0) { 503 $interval = (float) DFEHC_MIN_INTERVAL; 504 } 505 506 $load = function_exists('dfehc_get_server_load') ? dfehc_get_server_load() : null; 507 $load = (is_numeric($load) && (float) $load >= 0.0) ? (float) $load : null; 508 509 dfehc_set_transient_noautoload($snap_key, [ 510 't' => $now, 511 'interval' => (float) $interval, 512 'load' => $load, 513 ], $stale_ttl); 514 515 wp_send_json_success([ 516 'interval' => (float) $interval, 517 'load' => $load, 518 'fresh' => 1, 519 'next_poll_ms' => (int) apply_filters('dfehc_ajax_next_poll_ms_fresh', 45000), 520 ]); 521 } 522 369 523 add_action('wp_ajax_dfehc_update_heartbeat_interval', 'dfehc_get_recommended_intervals'); 370 524 add_action('wp_ajax_nopriv_dfehc_update_heartbeat_interval', 'dfehc_get_recommended_intervals'); … … 372 526 function dfehc_override_heartbeat_interval(array $settings): array 373 527 { 374 $interval = (int) ($settings['interval'] ?? DFEHC_MIN_INTERVAL); 375 $interval = min(max($interval, (int) DFEHC_MIN_INTERVAL), (int) DFEHC_MAX_INTERVAL); 528 if (is_admin() || (function_exists('wp_doing_ajax') && wp_doing_ajax())) { 529 return $settings; 530 } 531 532 if (get_option('dfehc_disable_heartbeat')) { 533 $settings['interval'] = (int) DFEHC_MAX_INTERVAL; 534 return $settings; 535 } 536 537 $enabled = (string) get_option('dfehc_heartbeat_control_enabled', '1'); 538 if ($enabled !== '1') { 539 return $settings; 540 } 541 542 $interval = 0.0; 543 544 $snap_key = dfehc_scoped_tkey('dfehc_ri_snapshot'); 545 $snapshot = get_transient($snap_key); 546 if (is_array($snapshot) && isset($snapshot['interval']) && is_numeric($snapshot['interval'])) { 547 $interval = (float) $snapshot['interval']; 548 } 549 550 if ($interval <= 0.0) { 551 $interval = function_exists('dfehc_get_recommended_heartbeat_interval_async') 552 ? (float) dfehc_get_recommended_heartbeat_interval_async() 553 : (float) dfehc_calculate_recommended_interval_user_activity(); 554 } 555 556 $interval = (int) round($interval); 557 $interval = min(max($interval, (int) DFEHC_MIN_INTERVAL), (int) DFEHC_MAX_INTERVAL); 558 376 559 $settings['interval'] = $interval; 377 560 return $settings; 378 561 } 379 add_filter('heartbeat_settings', 'dfehc_override_heartbeat_interval'); 562 add_filter('heartbeat_settings', 'dfehc_override_heartbeat_interval', 99); 563 380 564 381 565 function dfehc_get_server_health_status(float $load): string -
dynamic-front-end-heartbeat-control/trunk/js/heartbeat.js
r3412283 r3461136 1 1 ((wp) => { 2 2 const intervals = { 3 low: [15, 30, 60, 120, 180, 240, 300],4 medium: [ 30, 60, 120, 180, 240, 300],5 high: [60, 120, 180, 240, 300]6 }; 7 8 const vars = typeof window.dfehc_heartbeat_vars === 'object' ? window.dfehc_heartbeat_vars : {};3 low: [20, 40, 60, 90, 120, 180, 240], 4 medium: [40, 60, 90, 120, 180, 240], 5 high: [60, 90, 120, 180, 240] 6 }; 7 8 const vars = typeof window.dfehc_heartbeat_vars === 'object' && window.dfehc_heartbeat_vars ? window.dfehc_heartbeat_vars : {}; 9 9 10 10 const clampNumber = (v, lo, hi, d) => { … … 14 14 }; 15 15 16 const DEFAULT_MIN = 15;17 const DEFAULT_MAX = 300;18 const ABS_MIN = 1 ;19 const ABS_MAX = 3 600;16 const DEFAULT_MIN = 20; 17 const DEFAULT_MAX = 240; 18 const ABS_MIN = 15; 19 const ABS_MAX = 300; 20 20 21 21 let MIN = clampNumber(vars.min_interval, ABS_MIN, ABS_MAX, DEFAULT_MIN); 22 22 let MAX = clampNumber(vars.max_interval, MIN, ABS_MAX, DEFAULT_MAX); 23 if (MIN > MAX) { const t = MIN; MIN = MAX; MAX = t; } 24 25 const LOAD_CAP = Number.isFinite(vars.max_server_load) ? Number(vars.max_server_load) : 85; 23 if (MIN > MAX) { 24 const t = MIN; 25 MIN = MAX; 26 MAX = t; 27 } 28 29 const LOAD_CAP = clampNumber(vars.max_server_load, 10, 100, 85); 26 30 27 31 const msOrAuto = (v, d) => { … … 30 34 return n <= 600 ? n * 1000 : n; 31 35 }; 32 const cacheTimeout = msOrAuto(vars.cache_duration, 5 * 60 * 1000); 33 const cacheBypassRate = Math.min(1, Math.max(0, Number(vars.cache_bypass_rate) || 0.05)); 36 37 const cacheTimeout = Math.max(15000, msOrAuto(vars.cache_duration, 10 * 60 * 1000)); 38 const cacheBypassRate = Math.min(1, Math.max(0, Number(vars.cache_bypass_rate) || 0.03)); 34 39 35 40 const sanitizeKey = (s) => String(s || '').replace(/[^a-z0-9_.-]/gi, '_'); 36 41 const siteKey = sanitizeKey(vars.site_key || location.host); 37 const localCacheKey = `dfehc_heartbeat_server_load:${siteKey}:${vars.ver || '1'}`; 42 const versionKey = sanitizeKey(vars.ver || '1'); 43 const localCacheKey = `dfehc_heartbeat_server_load:${siteKey}:${versionKey}`; 44 const leaderKey = `${localCacheKey}:leader`; 45 38 46 const memoryCache = Object.create(null); 39 47 40 48 const SUPPORTS_BC = typeof window.BroadcastChannel === 'function'; 41 49 const bc = SUPPORTS_BC ? new BroadcastChannel('dfehc-heartbeat') : null; 42 const TAB_ID = Math.random().toString(36).slice(2) + Date.now().toString(36); 50 51 const TAB_ID = (Math.random().toString(36).slice(2) + Date.now().toString(36)); 43 52 let isLeader = false; 44 53 let lastLeaderBeat = 0; 45 const LEADER_TTL = 5000; 46 const leaderKey = `${localCacheKey}:leader`; 47 48 function readLeader() { 54 55 const LEADER_TTL = Math.max(4000, clampNumber(vars.leader_ttl_ms, 2000, 20000, 8000)); 56 const LEADER_BEAT = Math.max(1200, clampNumber(vars.leader_beat_ms, 800, 10000, 3000)); 57 58 const jitter = (ms) => Math.floor(Math.random() * ms); 59 const safeJsonParse = (s) => { 49 60 try { 50 const raw = localStorage.getItem(leaderKey); 51 if (!raw) return null; 52 const obj = JSON.parse(raw); 53 if (!obj || typeof obj !== 'object') return null; 54 const ts = Number(obj.ts); 55 const tab = String(obj.tab || ''); 56 if (!Number.isFinite(ts) || !tab) return null; 57 return { ts, tab }; 61 return JSON.parse(s); 58 62 } catch { 59 63 return null; 60 64 } 65 }; 66 67 function readLeader() { 68 const raw = (() => { try { return localStorage.getItem(leaderKey); } catch { return null; } })(); 69 if (!raw) return null; 70 const obj = safeJsonParse(raw); 71 if (!obj || typeof obj !== 'object') return null; 72 const ts = Number(obj.ts); 73 const tab = String(obj.tab || ''); 74 if (!Number.isFinite(ts) || !tab) return null; 75 return { ts, tab }; 61 76 } 62 77 … … 88 103 return true; 89 104 } 105 isLeader = false; 90 106 return false; 91 107 } … … 94 110 if (!isLeader) return; 95 111 const now = Date.now(); 96 if (now - lastLeaderBeat > 2000) {112 if (now - lastLeaderBeat > LEADER_BEAT) { 97 113 writeLeader(now, TAB_ID); 98 114 lastLeaderBeat = now; … … 108 124 const getLocalCache = (key) => { 109 125 try { 110 const raw = window.localStorage.getItem(key);126 const raw = localStorage.getItem(key); 111 127 if (!raw) return null; 112 const parsed = JSON.parse(raw);128 const parsed = safeJsonParse(raw); 113 129 if (!parsed || typeof parsed !== 'object') return null; 114 130 const timestamp = Number(parsed.timestamp); 115 const data = parsed.data;116 131 if (!Number.isFinite(timestamp)) return null; 117 return Date.now() - timestamp < cacheTimeout ? data : null; 132 const age = Date.now() - timestamp; 133 if (age < 0 || age > cacheTimeout) return null; 134 return parsed.data; 118 135 } catch { 119 try { window.localStorage.removeItem(key); } catch {}136 try { localStorage.removeItem(key); } catch {} 120 137 return null; 121 138 } … … 124 141 const setLocalCache = (key, data) => { 125 142 try { 126 const jitterMs = Math.floor(Math.random() * 5000); 127 window.localStorage.setItem(key, JSON.stringify({ timestamp: Date.now() - jitterMs, data })); 143 localStorage.setItem(key, JSON.stringify({ timestamp: Date.now() - jitter(8000), data })); 128 144 } catch {} 129 145 }; 130 146 131 const nearestFrom = (value, list) => 132 list.reduce((best, v) => (Math.abs(v - value) < Math.abs(best - value) ? v : best), list[0]); 147 const nearestFrom = (value, list) => { 148 if (!Array.isArray(list) || list.length === 0) return value; 149 let best = list[0]; 150 let bestD = Math.abs(best - value); 151 for (let i = 1; i < list.length; i++) { 152 const v = list[i]; 153 const d = Math.abs(v - value); 154 if (d < bestD) { 155 bestD = d; 156 best = v; 157 } 158 } 159 return best; 160 }; 133 161 134 162 const trafficLevel = (load) => (load <= 50 ? 'low' : load <= 75 ? 'medium' : 'high'); … … 137 165 const bucket = Math.max(0, Math.min(100, Math.round(load))); 138 166 if (Object.prototype.hasOwnProperty.call(memoryCache, bucket)) return memoryCache[bucket]; 167 139 168 const level = trafficLevel(load); 140 const opts = intervals[level]; 169 const opts = intervals[level] || intervals.low; 170 141 171 const min = Math.max(MIN, opts[0]); 142 172 const max = Math.min(MAX, opts[opts.length - 1]); 173 143 174 const ratio = Math.max(0, Math.min(1, load / LOAD_CAP)); 144 175 const raw = min + (max - min) * ratio; 176 145 177 const clamped = Math.min(Math.max(raw, MIN), MAX); 146 178 const snapped = nearestFrom(clamped, opts); 179 147 180 memoryCache[bucket] = snapped; 148 181 return snapped; … … 162 195 if (Number.isFinite(Number(val))) { 163 196 const n = Number(val); 164 const mode = (vars.server_payload || 'auto').toLowerCase();197 const mode = String(vars.server_payload || 'auto').toLowerCase(); 165 198 if (mode === 'interval') return { interval: n, load: null }; 166 199 if (mode === 'load') return { interval: null, load: n }; … … 184 217 if (!hb || typeof hb.interval !== 'function') return; 185 218 if (typeof secs !== 'number' || !Number.isFinite(secs)) return; 186 if (lastInterval !== null && Math.round(lastInterval) === Math.round(secs)) return; 187 const preset = secs <= 20 ? 'fast' : secs <= 60 ? 'standard' : 'slow'; 219 const rounded = Math.round(secs); 220 if (lastInterval !== null && Math.round(lastInterval) === rounded) return; 221 const preset = secs <= 30 ? 'fast' : secs <= 90 ? 'standard' : 'slow'; 188 222 try { hb.interval(preset); hb.interval(secs); } catch {} 189 223 lastInterval = secs; … … 192 226 const setHeartbeatInterval = (secs) => { 193 227 let s = secs; 228 if (!Number.isFinite(s)) return; 229 s = Math.max(MIN, Math.min(MAX, s)); 194 230 if (s >= 60) { 195 const j itter = Math.floor(Math.random() * 3) - 1;196 s = Math.max(MIN, Math.min(MAX, s + j itter));231 const j = (Math.floor(Math.random() * 5) - 2); 232 s = Math.max(MIN, Math.min(MAX, s + j)); 197 233 } 198 234 applyPresetThenExact(s); 199 235 }; 200 236 201 const debouncedSetInterval = debounce(setHeartbeatInterval, 150);237 const debouncedSetInterval = debounce(setHeartbeatInterval, 200); 202 238 203 239 let failureCount = 0; 204 240 let nextRetryAt = 0; 205 const backoffMs = () => Math.min(1 20000, 2000 * Math.pow(2, Math.min(failureCount, 5)));241 const backoffMs = () => Math.min(180000, 3000 * Math.pow(2, Math.min(failureCount, 6))); 206 242 207 243 const fetchWithTimeout = (url, opts, ms) => { … … 219 255 let reqSeq = 0; 220 256 257 const fallbackPayload = (rid) => { 258 const base = Number(vars.fallback_interval) || 90; 259 const j = Math.floor(Math.random() * 10) - 5; 260 return { interval: Math.max(MIN, Math.min(MAX, base + j)), load: null, rid }; 261 }; 262 221 263 const fetchServerData = async (nonce, rid) => { 222 if (Date.now() < nextRetryAt) { 223 const base = Number(vars.fallback_interval) || 60; 224 const jitter = Math.floor(Math.random() * 7) - 3; 225 return { interval: Math.max(MIN, Math.min(MAX, base + jitter)), load: null, rid }; 226 } 264 if (Date.now() < nextRetryAt) return fallbackPayload(rid); 227 265 228 266 if (Math.random() >= cacheBypassRate) { … … 233 271 const I_AM_LEADER = tryBecomeLeader(); 234 272 if (!I_AM_LEADER) { 235 const wait = Math.min( 1500, Math.max(150, Math.floor(Math.random() *600)));236 await new Promise( r=> setTimeout(r, wait));273 const wait = Math.min(2000, Math.max(200, 200 + jitter(600))); 274 await new Promise((r) => setTimeout(r, wait)); 237 275 const cached = getLocalCache(localCacheKey); 238 276 if (cached !== null) return { ...coerceServerPayload(cached), rid }; 239 277 } 240 278 241 if (navigator.onLine === false) { 242 const base = Number(vars.fallback_interval) || 60; 243 const jitter = Math.floor(Math.random() * 7) - 3; 244 return { interval: Math.max(MIN, Math.min(MAX, base + jitter)), load: null, rid }; 245 } 279 if (navigator.onLine === false) return fallbackPayload(rid); 246 280 247 281 const url = … … 252 286 const body = new URLSearchParams({ action: 'dfehc_update_heartbeat_interval' }); 253 287 if (nonce) body.append('nonce', nonce); 288 254 289 const res = await fetchWithTimeout(url, { 255 290 method: 'POST', … … 260 295 'Cache-Control': 'no-store' 261 296 } 262 }, 6000); 263 if (!res.ok) throw new Error(String(res.status)); 297 }, 8000); 298 299 if (!res || !res.ok) throw new Error(res ? String(res.status) : 'no_response'); 300 264 301 const json = await res.json(); 265 if (!json || json.success !== true) throw new Error('bad payload'); 302 if (!json || json.success !== true) throw new Error('bad_payload'); 303 266 304 setLocalCache(localCacheKey, json.data); 267 305 if (bc) bc.postMessage({ t: 'payload', data: json.data }); 306 268 307 failureCount = 0; 269 308 nextRetryAt = 0; 309 270 310 renewLeadership(); 271 311 return { ...coerceServerPayload(json.data), rid }; … … 273 313 failureCount += 1; 274 314 nextRetryAt = Date.now() + backoffMs(); 275 const base = Number(vars.fallback_interval) || 60;276 const jitter = Math.floor(Math.random() * 7) - 3;277 315 relinquishLeadership(); 278 return { interval: Math.max(MIN, Math.min(MAX, base + jitter)), load: null, rid }; 279 } 316 return fallbackPayload(rid); 317 } 318 }; 319 320 const resolveIntervalFromPayload = (payload) => { 321 const interval = payload && typeof payload.interval === 'number' ? payload.interval : null; 322 const load = payload && typeof payload.load === 'number' ? payload.load : null; 323 324 const snapList = intervals[trafficLevel(typeof load === 'number' ? load : LOAD_CAP)] || intervals.low; 325 326 if (typeof interval === 'number' && Number.isFinite(interval)) { 327 const clamped = Math.min(Math.max(interval, MIN), MAX); 328 return nearestFrom(clamped, snapList); 329 } 330 331 return calcRecommendedIntervalFromLoad(typeof load === 'number' ? load : 60); 280 332 }; 281 333 282 334 const heartbeat = { 283 update(interval) { debouncedSetInterval(interval); }, 335 update(interval) { 336 debouncedSetInterval(interval); 337 }, 284 338 updateUI(interval) { 285 339 const sel = document.querySelector('#dfehc-heartbeat-interval'); … … 290 344 const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection; 291 345 const et = conn && conn.effectiveType ? String(conn.effectiveType) : ''; 346 292 347 if ('deviceMemory' in navigator && navigator.deviceMemory < 2) return; 293 348 if ((conn && conn.saveData) || et.startsWith('2g') || et.startsWith('slow-2g')) return; 349 294 350 const myRid = ++reqSeq; 295 351 const payload = await fetchServerData(nonce, myRid); 296 if (payload.rid !== reqSeq) return; 297 const interval = payload.interval; 298 const load = payload.load; 299 const snapList = 300 intervals[trafficLevel(typeof load === 'number' ? load : LOAD_CAP)] || intervals.low; 301 const finalInterval = 302 typeof interval === 'number' 303 ? nearestFrom(Math.min(Math.max(interval, MIN), MAX), snapList) 304 : calcRecommendedIntervalFromLoad(typeof load === 'number' ? load : 60); 352 if (!payload || payload.rid !== reqSeq) return; 353 354 const finalInterval = resolveIntervalFromPayload(payload); 305 355 this.updateUI(finalInterval); 306 356 } … … 309 359 if (bc) { 310 360 bc.onmessage = (e) => { 311 const m = e.data ||{};361 const m = (e && e.data) ? e.data : {}; 312 362 if (m.t === 'leader') { 313 363 const tab = String(m.tab || ''); … … 315 365 } 316 366 if (m.t === 'payload') { 317 try { setLocalCache(localCacheKey, m.data); } catch {} 318 Object.keys(memoryCache).forEach(k => delete memoryCache[k]); 319 const coerced = coerceServerPayload(m.data); 320 const interval = coerced.interval; 321 const load = coerced.load; 322 const snapList = 323 intervals[trafficLevel(typeof load === 'number' ? load : LOAD_CAP)] || intervals.low; 324 const finalInterval = 325 typeof interval === 'number' 326 ? nearestFrom(Math.min(Math.max(interval, MIN), MAX), snapList) 327 : calcRecommendedIntervalFromLoad(typeof load === 'number' ? load : 60); 328 debouncedSetInterval(finalInterval); 367 setLocalCache(localCacheKey, m.data); 368 Object.keys(memoryCache).forEach((k) => delete memoryCache[k]); 369 debouncedSetInterval(resolveIntervalFromPayload(coerceServerPayload(m.data))); 329 370 } 330 371 }; … … 332 373 333 374 window.addEventListener('storage', (e) => { 334 if (e.key !== localCacheKey || !e.newValue) return; 335 try { 336 const v = JSON.parse(e.newValue); 337 if (!v || typeof v !== 'object' || !('data' in v)) return; 338 Object.keys(memoryCache).forEach(k => delete memoryCache[k]); 339 const coerced = coerceServerPayload(v.data); 340 const interval = coerced.interval; 341 const load = coerced.load; 342 const snapList = 343 intervals[trafficLevel(typeof load === 'number' ? load : LOAD_CAP)] || intervals.low; 344 const finalInterval = 345 typeof interval === 'number' 346 ? nearestFrom(Math.min(Math.max(interval, MIN), MAX), snapList) 347 : calcRecommendedIntervalFromLoad(typeof load === 'number' ? load : 60); 348 debouncedSetInterval(finalInterval); 349 } catch {} 375 if (!e || e.key !== localCacheKey || !e.newValue) return; 376 const v = safeJsonParse(e.newValue); 377 if (!v || typeof v !== 'object' || !('data' in v)) return; 378 Object.keys(memoryCache).forEach((k) => delete memoryCache[k]); 379 debouncedSetInterval(resolveIntervalFromPayload(coerceServerPayload(v.data))); 350 380 }); 351 381 352 let memoVersion = String(v ars.ver || '1');382 let memoVersion = String(versionKey); 353 383 function maybeResetMemo() { 354 const v = String( vars.ver || '1');384 const v = String(sanitizeKey(vars.ver || '1')); 355 385 if (v !== memoVersion) { 356 386 memoVersion = v; 357 Object.keys(memoryCache).forEach( k=> delete memoryCache[k]);387 Object.keys(memoryCache).forEach((k) => delete memoryCache[k]); 358 388 } 359 389 } … … 370 400 }); 371 401 window.addEventListener('online', () => { nextRetryAt = 0; }); 372 window.addEventListener('pageshow', (e) => { if (e .persisted) nextRetryAt = 0; });402 window.addEventListener('pageshow', (e) => { if (e && e.persisted) nextRetryAt = 0; }); 373 403 window.addEventListener('pagehide', cleanup, { once: true }); 374 404 window.addEventListener('beforeunload', cleanup, { once: true }); … … 376 406 document.addEventListener('DOMContentLoaded', () => { 377 407 maybeResetMemo(); 378 if ((vars.heartbeat_control_enabled || '') !== '1') return; 408 if (String(vars.heartbeat_control_enabled || '') !== '1') return; 409 379 410 const nonce = vars.nonce; 411 380 412 if ('requestIdleCallback' in window) { 381 window.requestIdleCallback(() => heartbeat.init(nonce) );413 window.requestIdleCallback(() => heartbeat.init(nonce), { timeout: 500 }); 382 414 } else { 383 setTimeout(() => heartbeat.init(nonce), 100); 384 } 415 setTimeout(() => heartbeat.init(nonce), 150); 416 } 417 385 418 const sel = document.querySelector('#dfehc-heartbeat-interval'); 386 419 if (sel) { 387 420 sel.addEventListener('change', function () { 388 const val = parseInt( this.value, 10);421 const val = parseInt(String(this.value), 10); 389 422 if (!Number.isNaN(val)) heartbeat.update(Math.min(Math.max(val, MIN), MAX)); 390 423 }); -
dynamic-front-end-heartbeat-control/trunk/js/heartbeat.min.js
r3412283 r3461136 1 ((wp)=>{const intervals={low:[ 15,30,60,120,180,240,300],medium:[30,60,120,180,240,300],high:[60,120,180,240,300]};const vars=typeof window.dfehc_heartbeat_vars==="object"?window.dfehc_heartbeat_vars:{};const clampNumber=(v,lo,hi,d)=>{const n=Number(v);if(!Number.isFinite(n))return d;return Math.min(hi,Math.max(lo,n))};const DEFAULT_MIN=15;const DEFAULT_MAX=300;const ABS_MIN=1;const ABS_MAX=3600;let MIN=clampNumber(vars.min_interval,ABS_MIN,ABS_MAX,DEFAULT_MIN);let MAX=clampNumber(vars.max_interval,MIN,ABS_MAX,DEFAULT_MAX);if(MIN>MAX){const t=MIN;MIN=MAX;MAX=t}const LOAD_CAP=Number.isFinite(vars.max_server_load)?Number(vars.max_server_load):85;const msOrAuto=(v,d)=>{const n=Number(v);if(!Number.isFinite(n))return d;return n<=600?n*1e3:n};const cacheTimeout=msOrAuto(vars.cache_duration,5*60*1e3);const cacheBypassRate=Math.min(1,Math.max(0,Number(vars.cache_bypass_rate)||.05));const sanitizeKey=s=>String(s||"").replace(/[^a-z0-9_.-]/gi,"_");const siteKey=sanitizeKey(vars.site_key||location.host);const localCacheKey=`dfehc_heartbeat_server_load:${siteKey}:${vars.ver||"1"}`;const memoryCache=Object.create(null);const SUPPORTS_BC=typeof window.BroadcastChannel==="function";const bc=SUPPORTS_BC?new BroadcastChannel("dfehc-heartbeat"):null;const TAB_ID=Math.random().toString(36).slice(2)+Date.now().toString(36);let isLeader=false;let lastLeaderBeat=0;const LEADER_TTL=5e3;const leaderKey=`${localCacheKey}:leader`;function readLeader(){try{const raw=localStorage.getItem(leaderKey);if(!raw)return null;const obj=JSON.parse(raw);if(!obj||typeof obj!=="object")return null;const ts=Number(obj.ts);const tab=String(obj.tab||"");if(!Number.isFinite(ts)||!tab)return null;return{ts,tab}}catch{return null}}function writeLeader(ts,tab){try{localStorage.setItem(leaderKey,JSON.stringify({ts,tab}))}catch{}}function clearLeader(tab){try{const cur=readLeader();if(!cur||cur.tab===tab)localStorage.removeItem(leaderKey)}catch{}}function tryBecomeLeader(){if(document.hidden)return false;const now=Date.now();const cur=readLeader();if(!cur||now-cur.ts>LEADER_TTL){writeLeader(now,TAB_ID);isLeader=true;lastLeaderBeat=now;if(bc)bc.postMessage({t:"leader",ts:now,tab:TAB_ID});return true}if(cur.tab===TAB_ID){isLeader=true;lastLeaderBeat=cur.ts;return true}return false}function renewLeadership(){if(!isLeader)return;const now=Date.now();if(now-lastLeaderBeat>2e3){writeLeader(now,TAB_ID);lastLeaderBeat=now}}function relinquishLeadership(){if(!isLeader)return;clearLeader(TAB_ID);isLeader=false}const getLocalCache=key=>{try{const raw=window.localStorage.getItem(key);if(!raw)return null;const parsed=JSON.parse(raw);if(!parsed||typeof parsed!=="object")return null;const timestamp=Number(parsed.timestamp);const data=parsed.data;if(!Number.isFinite(timestamp))return null;return Date.now()-timestamp<cacheTimeout?data:null}catch{try{window.localStorage.removeItem(key)}catch{}return null}};const setLocalCache=(key,data)=>{try{const jitterMs=Math.floor(Math.random()*5e3);window.localStorage.setItem(key,JSON.stringify({timestamp:Date.now()-jitterMs,data}))}catch{}};const nearestFrom=(value,list)=>list.reduce((best,v)=>Math.abs(v-value)<Math.abs(best-value)?v:best,list[0]);const trafficLevel=load=>load<=50?"low":load<=75?"medium":"high";const calcRecommendedIntervalFromLoad=load=>{const bucket=Math.max(0,Math.min(100,Math.round(load)));if(Object.prototype.hasOwnProperty.call(memoryCache,bucket))return memoryCache[bucket];const level=trafficLevel(load);const opts=intervals[level];const min=Math.max(MIN,opts[0]);const max=Math.min(MAX,opts[opts.length-1]);const ratio=Math.max(0,Math.min(1,load/LOAD_CAP));const raw=min+(max-min)*ratio;const clamped=Math.min(Math.max(raw,MIN),MAX);const snapped=nearestFrom(clamped,opts);memoryCache[bucket]=snapped;return snapped};const toNum=v=>{const n=Number(v);return Number.isFinite(n)?n:null};const coerceServerPayload=val=>{if(val&&typeof val==="object"){const interval=toNum(val.interval);const load=toNum(val.load);return{interval,load}}if(Number.isFinite(Number(val))){const n=Number(val);const mode=(vars.server_payload||"auto").toLowerCase();if(mode==="interval")return{interval:n,load:null};if(mode==="load")return{interval:null,load:n};if(n>=0&&n<=100)return{interval:null,load:n};if(n>100)return{interval:n,load:null}}return{interval:null,load:null}};const debounce=(fn,wait)=>{let t;return(...args)=>{clearTimeout(t);t=setTimeout(()=>fn(...args),wait)}};let lastInterval=null;const applyPresetThenExact=secs=>{const hb=wp&&wp.heartbeat;if(!hb||typeof hb.interval!=="function")return;if(typeof secs!=="number"||!Number.isFinite(secs))return;if(lastInterval!==null&&Math.round(lastInterval)===Math.round(secs))return;const preset=secs<=20?"fast":secs<=60?"standard":"slow";try{hb.interval(preset);hb.interval(secs)}catch{}lastInterval=secs};const setHeartbeatInterval=secs=>{let s=secs;if(s>=60){const jitter=Math.floor(Math.random()*3)-1;s=Math.max(MIN,Math.min(MAX,s+jitter))}applyPresetThenExact(s)};const debouncedSetInterval=debounce(setHeartbeatInterval,150);let failureCount=0;let nextRetryAt=0;const backoffMs=()=>Math.min(12e4,2e3*Math.pow(2,Math.min(failureCount,5)));const fetchWithTimeout=(url,opts,ms)=>{if("AbortController"in window){const ctrl=new AbortController;const t=setTimeout(()=>ctrl.abort(),ms);return fetch(url,{...opts,signal:ctrl.signal}).finally(()=>clearTimeout(t))}return Promise.race([fetch(url,opts),new Promise((_,rej)=>setTimeout(()=>rej(new Error("timeout")),ms))])};let reqSeq=0;const fetchServerData=async(nonce,rid)=>{if(Date.now()<nextRetryAt){const base=Number(vars.fallback_interval)||60;const jitter=Math.floor(Math.random()*7)-3;return{interval:Math.max(MIN,Math.min(MAX,base+jitter)),load:null,rid}}if(Math.random()>=cacheBypassRate){const cached=getLocalCache(localCacheKey);if(cached!==null)return{...coerceServerPayload(cached),rid}}const I_AM_LEADER=tryBecomeLeader();if(!I_AM_LEADER){const wait=Math.min(1500,Math.max(150,Math.floor(Math.random()*600)));await new Promise(r=>setTimeout(r,wait));const cached=getLocalCache(localCacheKey);if(cached!==null)return{...coerceServerPayload(cached),rid}}if(navigator.onLine===false){const base=Number(vars.fallback_interval)||60;const jitter=Math.floor(Math.random()*7)-3;return{interval:Math.max(MIN,Math.min(MAX,base+jitter)),load:null,rid}}const url=vars.ajax_url||typeof window.ajaxurl!=="undefined"?window.ajaxurl:`${location.origin.replace(/\/$/,"")}/wp-admin/admin-ajax.php`;try{const body=new URLSearchParams({action:"dfehc_update_heartbeat_interval"});if(nonce)body.append("nonce",nonce);const res=await fetchWithTimeout(url,{method:"POST",body,credentials:"same-origin",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8","Cache-Control":"no-store"}},6e3);if(!res.ok)throw new Error(String(res.status));const json=await res.json();if(!json||json.success!==true)throw new Error("bad payload");setLocalCache(localCacheKey,json.data);if(bc)bc.postMessage({t:"payload",data:json.data});failureCount=0;nextRetryAt=0;renewLeadership();return{...coerceServerPayload(json.data),rid}}catch{failureCount+=1;nextRetryAt=Date.now()+backoffMs();const base=Number(vars.fallback_interval)||60;const jitter=Math.floor(Math.random()*7)-3;relinquishLeadership();return{interval:Math.max(MIN,Math.min(MAX,base+jitter)),load:null,rid}}};const heartbeat={update(interval){debouncedSetInterval(interval)},updateUI(interval){const sel=document.querySelector("#dfehc-heartbeat-interval");if(sel)sel.value=String(interval);debouncedSetInterval(interval)},async init(nonce){const conn=navigator.connection||navigator.mozConnection||navigator.webkitConnection;const et=conn&&conn.effectiveType?String(conn.effectiveType):"";if("deviceMemory"in navigator&&navigator.deviceMemory<2)return;if(conn&&conn.saveData||et.startsWith("2g")||et.startsWith("slow-2g"))return;const myRid=++reqSeq;const payload=await fetchServerData(nonce,myRid);if(payload.rid!==reqSeq)return;const interval=payload.interval;const load=payload.load;const snapList=intervals[trafficLevel(typeof load==="number"?load:LOAD_CAP)]||intervals.low;const finalInterval=typeof interval==="number"?nearestFrom(Math.min(Math.max(interval,MIN),MAX),snapList):calcRecommendedIntervalFromLoad(typeof load==="number"?load:60);this.updateUI(finalInterval)}};if(bc){bc.onmessage=e=>{const m=e.data||{};if(m.t==="leader"){const tab=String(m.tab||"");if(tab&&tab!==TAB_ID)isLeader=false}if(m.t==="payload"){try{setLocalCache(localCacheKey,m.data)}catch{}Object.keys(memoryCache).forEach(k=>delete memoryCache[k]);const coerced=coerceServerPayload(m.data);const interval=coerced.interval;const load=coerced.load;const snapList=intervals[trafficLevel(typeof load==="number"?load:LOAD_CAP)]||intervals.low;const finalInterval=typeof interval==="number"?nearestFrom(Math.min(Math.max(interval,MIN),MAX),snapList):calcRecommendedIntervalFromLoad(typeof load==="number"?load:60);debouncedSetInterval(finalInterval)}}}window.addEventListener("storage",e=>{if(e.key!==localCacheKey||!e.newValue)return;try{const v=JSON.parse(e.newValue);if(!v||typeof v!=="object"||!("data"in v))return;Object.keys(memoryCache).forEach(k=>delete memoryCache[k]);const coerced=coerceServerPayload(v.data);const interval=coerced.interval;const load=coerced.load;const snapList=intervals[trafficLevel(typeof load==="number"?load:LOAD_CAP)]||intervals.low;const finalInterval=typeof interval==="number"?nearestFrom(Math.min(Math.max(interval,MIN),MAX),snapList):calcRecommendedIntervalFromLoad(typeof load==="number"?load:60);debouncedSetInterval(finalInterval)}catch{}});let memoVersion=String(vars.ver||"1");function maybeResetMemo(){const v=String(vars.ver||"1");if(v!==memoVersion){memoVersion=v;Object.keys(memoryCache).forEach(k=>delete memoryCache[k])}}function cleanup(){relinquishLeadership();if(bc&&typeof bc.close==="function"){try{bc.close()}catch{}}}document.addEventListener("visibilitychange",()=>{if(!document.hidden)nextRetryAt=0});window.addEventListener("online",()=>{nextRetryAt=0});window.addEventListener("pageshow",e=>{if(e.persisted)nextRetryAt=0});window.addEventListener("pagehide",cleanup,{once:true});window.addEventListener("beforeunload",cleanup,{once:true});document.addEventListener("DOMContentLoaded",()=>{maybeResetMemo();if((vars.heartbeat_control_enabled||"")!=="1")return;const nonce=vars.nonce;if("requestIdleCallback"in window){window.requestIdleCallback(()=>heartbeat.init(nonce))}else{setTimeout(()=>heartbeat.init(nonce),100)}const sel=document.querySelector("#dfehc-heartbeat-interval");if(sel){sel.addEventListener("change",function(){const val=parseInt(this.value,10);if(!Number.isNaN(val))heartbeat.update(Math.min(Math.max(val,MIN),MAX))})}})})(window.wp||{});1 ((wp)=>{const intervals={low:[20,40,60,90,120,180,240],medium:[40,60,90,120,180,240],high:[60,90,120,180,240]};const vars=typeof window.dfehc_heartbeat_vars==="object"&&window.dfehc_heartbeat_vars?window.dfehc_heartbeat_vars:{};const clampNumber=(v,lo,hi,d)=>{const n=Number(v);if(!Number.isFinite(n))return d;return Math.min(hi,Math.max(lo,n))};const DEFAULT_MIN=20,DEFAULT_MAX=240,ABS_MIN=15,ABS_MAX=300;let MIN=clampNumber(vars.min_interval,ABS_MIN,ABS_MAX,DEFAULT_MIN);let MAX=clampNumber(vars.max_interval,MIN,ABS_MAX,DEFAULT_MAX);if(MIN>MAX){const t=MIN;MIN=MAX;MAX=t}const LOAD_CAP=clampNumber(vars.max_server_load,10,100,85);const msOrAuto=(v,d)=>{const n=Number(v);if(!Number.isFinite(n))return d;return n<=600?n*1000:n};const cacheTimeout=Math.max(15000,msOrAuto(vars.cache_duration,600000));const cacheBypassRate=Math.min(1,Math.max(0,Number(vars.cache_bypass_rate)||0.03));const sanitizeKey=s=>String(s||"").replace(/[^a-z0-9_.-]/gi,"_");const siteKey=sanitizeKey(vars.site_key||location.host);const versionKey=sanitizeKey(vars.ver||"1");const localCacheKey=`dfehc_heartbeat_server_load:${siteKey}:${versionKey}`;const leaderKey=`${localCacheKey}:leader`;const memoryCache=Object.create(null);const SUPPORTS_BC=typeof window.BroadcastChannel==="function";const bc=SUPPORTS_BC?new BroadcastChannel("dfehc-heartbeat"):null;const TAB_ID=Math.random().toString(36).slice(2)+Date.now().toString(36);let isLeader=false,lastLeaderBeat=0;const LEADER_TTL=Math.max(4000,clampNumber(vars.leader_ttl_ms,2000,20000,8000));const LEADER_BEAT=Math.max(1200,clampNumber(vars.leader_beat_ms,800,10000,3000));const jitter=ms=>Math.floor(Math.random()*ms);const safeJsonParse=s=>{try{return JSON.parse(s)}catch{return null}};function readLeader(){let raw=null;try{raw=localStorage.getItem(leaderKey)}catch{}if(!raw)return null;const obj=safeJsonParse(raw);if(!obj||typeof obj!=="object")return null;const ts=Number(obj.ts);const tab=String(obj.tab||"");if(!Number.isFinite(ts)||!tab)return null;return{ts,tab}}function writeLeader(ts,tab){try{localStorage.setItem(leaderKey,JSON.stringify({ts,tab}))}catch{}}function clearLeader(tab){try{const cur=readLeader();if(!cur||cur.tab===tab)localStorage.removeItem(leaderKey)}catch{}}function tryBecomeLeader(){if(document.hidden)return false;const now=Date.now();const cur=readLeader();if(!cur||now-cur.ts>LEADER_TTL){writeLeader(now,TAB_ID);isLeader=true;lastLeaderBeat=now;if(bc)bc.postMessage({t:"leader",ts:now,tab:TAB_ID});return true}if(cur.tab===TAB_ID){isLeader=true;lastLeaderBeat=cur.ts;return true}isLeader=false;return false}function renewLeadership(){if(!isLeader)return;const now=Date.now();if(now-lastLeaderBeat>LEADER_BEAT){writeLeader(now,TAB_ID);lastLeaderBeat=now}}function relinquishLeadership(){if(!isLeader)return;clearLeader(TAB_ID);isLeader=false}const getLocalCache=key=>{try{const raw=localStorage.getItem(key);if(!raw)return null;const parsed=safeJsonParse(raw);if(!parsed||typeof parsed!=="object")return null;const ts=Number(parsed.timestamp);if(!Number.isFinite(ts))return null;const age=Date.now()-ts;if(age<0||age>cacheTimeout)return null;return parsed.data}catch{try{localStorage.removeItem(key)}catch{}return null}};const setLocalCache=(key,data)=>{try{localStorage.setItem(key,JSON.stringify({timestamp:Date.now()-jitter(8000),data}))}catch{}};const nearestFrom=(value,list)=>{if(!Array.isArray(list)||!list.length)return value;let best=list[0],bestD=Math.abs(best-value);for(let i=1;i<list.length;i++){const v=list[i],d=Math.abs(v-value);if(d<bestD){bestD=d;best=v}}return best};const trafficLevel=load=>load<=50?"low":load<=75?"medium":"high";const calcRecommendedIntervalFromLoad=load=>{const bucket=Math.max(0,Math.min(100,Math.round(load)));if(Object.prototype.hasOwnProperty.call(memoryCache,bucket))return memoryCache[bucket];const opts=intervals[trafficLevel(load)]||intervals.low;const min=Math.max(MIN,opts[0]);const max=Math.min(MAX,opts[opts.length-1]);const ratio=Math.max(0,Math.min(1,load/LOAD_CAP));const raw=min+(max-min)*ratio;const clamped=Math.min(Math.max(raw,MIN),MAX);const snapped=nearestFrom(clamped,opts);memoryCache[bucket]=snapped;return snapped};const toNum=v=>{const n=Number(v);return Number.isFinite(n)?n:null};const coerceServerPayload=val=>{if(val&&typeof val==="object"){return{interval:toNum(val.interval),load:toNum(val.load)}}if(Number.isFinite(Number(val))){const n=Number(val);const mode=String(vars.server_payload||"auto").toLowerCase();if(mode==="interval")return{interval:n,load:null};if(mode==="load")return{interval:null,load:n};if(n>=0&&n<=100)return{interval:null,load:n};if(n>100)return{interval:n,load:null}}return{interval:null,load:null}};const debounce=(fn,wait)=>{let t;return(...args)=>{clearTimeout(t);t=setTimeout(()=>fn(...args),wait)}};let lastInterval=null;const applyPresetThenExact=secs=>{const hb=wp&&wp.heartbeat;if(!hb||typeof hb.interval!=="function")return;if(typeof secs!=="number"||!Number.isFinite(secs))return;const rounded=Math.round(secs);if(lastInterval!==null&&Math.round(lastInterval)===rounded)return;const preset=secs<=30?"fast":secs<=90?"standard":"slow";try{hb.interval(preset);hb.interval(secs)}catch{}lastInterval=secs};const setHeartbeatInterval=secs=>{if(!Number.isFinite(secs))return;let s=Math.max(MIN,Math.min(MAX,secs));if(s>=60){s=Math.max(MIN,Math.min(MAX,s+(Math.floor(Math.random()*5)-2)))}applyPresetThenExact(s)};const debouncedSetInterval=debounce(setHeartbeatInterval,200);let failureCount=0,nextRetryAt=0;const backoffMs=()=>Math.min(180000,3000*Math.pow(2,Math.min(failureCount,6)));const fetchWithTimeout=(url,opts,ms)=>{"AbortController"in window?(()=>{const ctrl=new AbortController(),t=setTimeout(()=>ctrl.abort(),ms);return fetch(url,{...opts,signal:ctrl.signal}).finally(()=>clearTimeout(t))})():Promise.race([fetch(url,opts),new Promise((_,rej)=>setTimeout(()=>rej(new Error("timeout")),ms))])};let reqSeq=0;const fallbackPayload=rid=>{const base=Number(vars.fallback_interval)||90;const j=Math.floor(Math.random()*10)-5;return{interval:Math.max(MIN,Math.min(MAX,base+j)),load:null,rid}};const fetchServerData=async(nonce,rid)=>{if(Date.now()<nextRetryAt)return fallbackPayload(rid);if(Math.random()>=cacheBypassRate){const cached=getLocalCache(localCacheKey);if(cached!==null)return{...coerceServerPayload(cached),rid}}const I_AM_LEADER=tryBecomeLeader();if(!I_AM_LEADER){await new Promise(r=>setTimeout(r,Math.min(2000,Math.max(200,200+jitter(600)))));const cached=getLocalCache(localCacheKey);if(cached!==null)return{...coerceServerPayload(cached),rid}}if(navigator.onLine===false)return fallbackPayload(rid);const url=vars.ajax_url||(typeof window.ajaxurl!=="undefined"?window.ajaxurl:`${location.origin.replace(/\/$/,"")}/wp-admin/admin-ajax.php`);try{const body=new URLSearchParams({action:"dfehc_update_heartbeat_interval"});if(nonce)body.append("nonce",nonce);const res=await fetchWithTimeout(url,{method:"POST",body,credentials:"same-origin",headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8","Cache-Control":"no-store"}},8000);if(!res||!res.ok)throw 0;const json=await res.json();if(!json||json.success!==true)throw 0;setLocalCache(localCacheKey,json.data);if(bc)bc.postMessage({t:"payload",data:json.data});failureCount=0;nextRetryAt=0;renewLeadership();return{...coerceServerPayload(json.data),rid}}catch{failureCount++;nextRetryAt=Date.now()+backoffMs();relinquishLeadership();return fallbackPayload(rid)}};const resolveIntervalFromPayload=payload=>{const interval=payload&&typeof payload.interval==="number"?payload.interval:null;const load=payload&&typeof payload.load==="number"?payload.load:null;const snapList=intervals[trafficLevel(typeof load==="number"?load:LOAD_CAP)]||intervals.low;if(typeof interval==="number"&&Number.isFinite(interval))return nearestFrom(Math.min(Math.max(interval,MIN),MAX),snapList);return calcRecommendedIntervalFromLoad(typeof load==="number"?load:60)};const heartbeat={update:i=>debouncedSetInterval(i),updateUI:i=>{const sel=document.querySelector("#dfehc-heartbeat-interval");if(sel)sel.value=String(i);debouncedSetInterval(i)},init:async nonce=>{const conn=navigator.connection||navigator.mozConnection||navigator.webkitConnection;const et=conn&&conn.effectiveType?String(conn.effectiveType):"";if("deviceMemory"in navigator&&navigator.deviceMemory<2)return;if((conn&&conn.saveData)||et.startsWith("2g")||et.startsWith("slow-2g"))return;const myRid=++reqSeq;const payload=await fetchServerData(nonce,myRid);if(!payload||payload.rid!==reqSeq)return;heartbeat.updateUI(resolveIntervalFromPayload(payload))}};if(bc){bc.onmessage=e=>{const m=e&&e.data?e.data:{};if(m.t==="leader"){const tab=String(m.tab||"");if(tab&&tab!==TAB_ID)isLeader=false}if(m.t==="payload"){setLocalCache(localCacheKey,m.data);Object.keys(memoryCache).forEach(k=>delete memoryCache[k]);debouncedSetInterval(resolveIntervalFromPayload(coerceServerPayload(m.data)))}}}window.addEventListener("storage",e=>{if(!e||e.key!==localCacheKey||!e.newValue)return;const v=safeJsonParse(e.newValue);if(!v||typeof v!=="object"||!("data"in v))return;Object.keys(memoryCache).forEach(k=>delete memoryCache[k]);debouncedSetInterval(resolveIntervalFromPayload(coerceServerPayload(v.data)))});let memoVersion=String(versionKey);function maybeResetMemo(){const v=String(sanitizeKey(vars.ver||"1"));if(v!==memoVersion){memoVersion=v;Object.keys(memoryCache).forEach(k=>delete memoryCache[k])}}function cleanup(){relinquishLeadership();if(bc&&typeof bc.close==="function"){try{bc.close()}catch{}}}document.addEventListener("visibilitychange",()=>{if(!document.hidden)nextRetryAt=0});window.addEventListener("online",()=>{nextRetryAt=0});window.addEventListener("pageshow",e=>{if(e&&e.persisted)nextRetryAt=0});window.addEventListener("pagehide",cleanup,{once:true});window.addEventListener("beforeunload",cleanup,{once:true});document.addEventListener("DOMContentLoaded",()=>{maybeResetMemo();if(String(vars.heartbeat_control_enabled||"")!=="1")return;const nonce=vars.nonce;if("requestIdleCallback"in window){window.requestIdleCallback(()=>heartbeat.init(nonce),{timeout:500})}else{setTimeout(()=>heartbeat.init(nonce),150)}const sel=document.querySelector("#dfehc-heartbeat-interval");if(sel){sel.addEventListener("change",function(){const val=parseInt(String(this.value),10);if(!Number.isNaN(val))heartbeat.update(Math.min(Math.max(val,MIN),MAX))})}})})(window.wp||{}); -
dynamic-front-end-heartbeat-control/trunk/readme.txt
r3441926 r3461136 3 3 Tested up to: 6.9 4 4 Requires PHP: 7.2 5 Stable tag: 1.2.998 5 Stable tag: 1.2.998.1 6 6 License: GPLv2 or later 7 7 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 20 20 ✅ Full control when you want it — adjust priority settings, set custom intervals for the Backend and Editor, or disable Heartbeat entirely with a single click. 21 21 ✅ Highly advanced real-time decision making done on your behalf. Works with minimal server resources and completely prioritises user experience and website speed. 22 ✅ Cleans up database clutter with 1 click. 22 23 ✅ Install, activate, done — performance improvements begin immediately with no configuration required in most cases. 23 24 … … 61 62 62 63 == Changelog == 64 65 = 1.2.998.1 = 66 67 * Performance upgrade. 63 68 64 69 = 1.2.998 = -
dynamic-front-end-heartbeat-control/trunk/visitor/cookie-helper.php
r3427163 r3461136 2 2 declare(strict_types=1); 3 3 4 function dfehc_get_bot_pattern(): string 5 { 6 static $pattern = null; 7 if ($pattern !== null) { 4 if (!function_exists('dfehc_get_bot_pattern')) { 5 function dfehc_get_bot_pattern(): string 6 { 7 static $pattern = null; 8 if (is_string($pattern) && $pattern !== '') { 9 return $pattern; 10 } 11 12 $sigs = (array) apply_filters('dfehc_bot_signatures', [ 13 'bot','crawl','crawler','slurp','spider','mediapartners','bingpreview', 14 'yandex','duckduckbot','baiduspider','sogou','exabot', 15 'facebot','facebookexternalhit','ia_archiver', 16 'GPTBot','ChatGPT-User','OAI-SearchBot', 17 'ClaudeBot','Claude-Web','anthropic-ai', 18 'PerplexityBot','Perplexity-User', 19 'Meta-ExternalAgent','DuckAssistBot', 20 'Bytespider','Google-Extended','GoogleOther', 21 'Applebot-Extended','Amazonbot','CCBot', 22 'cohere-ai','ImagesiftBot','Diffbot','YouBot','GeminiBot', 23 'MistralAI-User','xAI-GrokBot','AI2Bot', 24 ]); 25 26 $clean = []; 27 foreach ($sigs as $s) { 28 if (!is_string($s)) { 29 continue; 30 } 31 $s = trim($s); 32 if ($s === '') { 33 continue; 34 } 35 $clean[$s] = true; 36 } 37 38 if (!$clean) { 39 $pattern = '/$^/'; 40 return $pattern; 41 } 42 43 $tokens = []; 44 foreach (array_keys($clean) as $s) { 45 $tokens[] = preg_quote($s, '/'); 46 } 47 48 $pattern = '/(' . implode('|', $tokens) . ')/i'; 8 49 return $pattern; 9 50 } 10 $sigs = (array) apply_filters('dfehc_bot_signatures', [11 'bot','crawl','crawler','slurp','spider','mediapartners','bingpreview',12 'yandex','duckduckbot','baiduspider','sogou','exabot',13 'facebot','facebookexternalhit','ia_archiver',14 ]);15 $tokens = array_map(16 static function (string $s): string {17 return '(?<![A-Za-z0-9])' . preg_quote($s, '/') . '(?![A-Za-z0-9])';18 },19 $sigs20 );21 return $pattern = '/(' . implode('|', $tokens) . ')/i';22 51 } 23 52 … … 33 62 { 34 63 static $t = ''; 35 if ($t !== '') return $t; 36 $host = @php_uname('n') ?: (defined('WP_HOME') ? WP_HOME : (function_exists('home_url') ? home_url() : 'unknown')); 64 if ($t !== '') { 65 return $t; 66 } 67 68 $url = ''; 69 if (defined('WP_HOME') && is_string(WP_HOME) && WP_HOME !== '') { 70 $url = WP_HOME; 71 } elseif (function_exists('home_url')) { 72 $url = (string) home_url(); 73 } 74 75 $host = ''; 76 if ($url !== '' && function_exists('wp_parse_url')) { 77 $parts = wp_parse_url($url); 78 if (is_array($parts) && isset($parts['host']) && is_string($parts['host'])) { 79 $host = $parts['host']; 80 } 81 } 82 83 if ($host === '') { 84 $host = @php_uname('n') ?: 'unknown'; 85 } 86 37 87 $salt = defined('DB_NAME') ? (string) DB_NAME : ''; 38 return $t = substr(md5((string) $host . $salt), 0, 10); 88 $t = substr(md5($host . $salt), 0, 10); 89 return $t; 39 90 } 40 91 } … … 53 104 return (bool) filter_var($ip, FILTER_VALIDATE_IP) && $ip === $cidr; 54 105 } 55 [$subnet, $mask] = explode('/', $cidr, 2); 56 $mask = (int) $mask; 57 58 $ip_bin = @inet_pton($ip); 106 107 $parts = explode('/', $cidr, 2); 108 if (count($parts) !== 2) { 109 return false; 110 } 111 112 $subnet = (string) $parts[0]; 113 $mask = (int) $parts[1]; 114 115 $ip_bin = @inet_pton($ip); 59 116 $sub_bin = @inet_pton($subnet); 60 117 if ($ip_bin === false || $sub_bin === false) { … … 65 122 } 66 123 67 $len = strlen($ip_bin);124 $len = strlen($ip_bin); 68 125 $max_bits = $len * 8; 69 126 if ($mask < 0 || $mask > $max_bits) { … … 72 129 73 130 $bytes = intdiv($mask, 8); 74 $bits = $mask % 8;131 $bits = $mask % 8; 75 132 76 133 if ($bytes && substr($ip_bin, 0, $bytes) !== substr($sub_bin, 0, $bytes)) { … … 80 137 return true; 81 138 } 82 $ip_byte = ord($ip_bin[$bytes]); 83 $sub_byte = ord($sub_bin[$bytes]); 139 140 $ip_byte = ord($ip_bin[$bytes]); 141 $sub_byte = ord($sub_bin[$bytes]); 84 142 $mask_byte = (0xFF << (8 - $bits)) & 0xFF; 143 85 144 return ($ip_byte & $mask_byte) === ($sub_byte & $mask_byte); 86 145 } … … 90 149 function dfehc_select_client_ip_from_xff(string $xff, array $trustedCidrs): ?string 91 150 { 92 $candidates = array_filter(array_map('trim', explode(',', $xff)), 'strlen'); 151 $parts = explode(',', $xff); 152 $candidates = []; 153 foreach ($parts as $p) { 154 $p = trim($p); 155 if ($p !== '') { 156 $candidates[] = $p; 157 } 158 } 159 93 160 $ipNonTrusted = null; 94 161 95 162 foreach ($candidates as $ip) { 96 $ip = preg_replace('/%[0-9A-Za-z.\-]+$/', '', $ip); 97 $ip = trim((string) $ip); 163 $ip = (string) preg_replace('/%[0-9A-Za-z.\-]+$/', '', $ip); 164 $ip = trim($ip); 165 98 166 if ($ip === '' || !filter_var($ip, FILTER_VALIDATE_IP)) { 99 167 continue; … … 124 192 } 125 193 126 $last = trim((string) end($candidates)); 127 return ($last !== '' && filter_var($last, FILTER_VALIDATE_IP)) ? $last : null; 194 if ($candidates) { 195 $last = trim((string) end($candidates)); 196 if ($last !== '' && filter_var($last, FILTER_VALIDATE_IP)) { 197 return $last; 198 } 199 } 200 201 return null; 128 202 } 129 203 } … … 132 206 function dfehc_client_ip(): string 133 207 { 208 static $memo = null; 209 if (is_string($memo) && $memo !== '') { 210 return $memo; 211 } 212 134 213 $remote_raw = isset($_SERVER['REMOTE_ADDR']) ? (string) wp_unslash($_SERVER['REMOTE_ADDR']) : ''; 135 214 $remote_raw = trim($remote_raw); 136 $remote = ($remote_raw !== '' && filter_var($remote_raw, FILTER_VALIDATE_IP)) ? $remote_raw : '';215 $remote = ($remote_raw !== '' && filter_var($remote_raw, FILTER_VALIDATE_IP)) ? $remote_raw : ''; 137 216 138 217 $trustedCidrs = (array) apply_filters('dfehc_trusted_proxies', []); 139 $trustedCidrs = array_values(array_filter(array_map( 140 static function ($v): string { return is_string($v) ? trim($v) : ''; }, 141 $trustedCidrs 142 ), 'strlen')); 218 $trusted = []; 219 foreach ($trustedCidrs as $v) { 220 if (!is_string($v)) { 221 continue; 222 } 223 $v = trim($v); 224 if ($v !== '') { 225 $trusted[] = $v; 226 } 227 } 143 228 144 229 $isTrustedRemote = false; 145 if ($remote !== '' && $trusted Cidrs) {146 foreach ($trusted Cidrsas $cidr) {230 if ($remote !== '' && $trusted) { 231 foreach ($trusted as $cidr) { 147 232 if (dfehc_ip_in_cidr($remote, $cidr)) { 148 233 $isTrustedRemote = true; … … 155 240 156 241 if ($isTrustedRemote) { 157 $headers = (array) apply_filters('dfehc_proxy_ip_headers', ['HTTP_X_FORWARDED_FOR', 'HTTP_CF_CONNECTING_IP', 'HTTP_X_REAL_IP']); 158 $headers = array_values(array_filter(array_map( 159 static function ($h): string { return is_string($h) ? trim($h) : ''; }, 160 $headers 161 ), 'strlen')); 162 242 $headers = (array) apply_filters('dfehc_proxy_ip_headers', [ 243 'HTTP_CF_CONNECTING_IP', 244 'HTTP_X_FORWARDED_FOR', 245 'HTTP_X_REAL_IP', 246 ]); 247 248 $hdrs = []; 163 249 foreach ($headers as $h) { 250 if (!is_string($h)) { 251 continue; 252 } 253 $h = trim($h); 254 if ($h !== '') { 255 $hdrs[] = $h; 256 } 257 } 258 259 foreach ($hdrs as $h) { 164 260 if (empty($_SERVER[$h])) { 165 261 continue; 166 262 } 263 167 264 $raw = trim((string) wp_unslash($_SERVER[$h])); 168 265 if ($raw === '') { … … 171 268 172 269 if ($h === 'HTTP_X_FORWARDED_FOR') { 173 $picked = dfehc_select_client_ip_from_xff($raw, $trusted Cidrs);270 $picked = dfehc_select_client_ip_from_xff($raw, $trusted); 174 271 if ($picked !== null) { 175 272 $cand = $picked; … … 179 276 } 180 277 181 $raw = preg_replace('/%[0-9A-Za-z.\-]+$/', '', $raw); 278 $raw = (string) preg_replace('/%[0-9A-Za-z.\-]+$/', '', $raw); 279 $raw = trim($raw); 280 182 281 if ($raw !== '' && filter_var($raw, FILTER_VALIDATE_IP)) { 183 282 $cand = $raw; … … 188 287 189 288 if ($cand === null) { 190 $cand = $remote !== '' ? $remote : '0.0.0.0'; 191 } 192 193 return (string) apply_filters('dfehc_client_ip', $cand); 194 } 195 } 196 197 if (!function_exists('dfehc_set_transient_noautoload')) { 198 function dfehc_set_transient_noautoload(string $key, $value, int $expiration): void 199 { 200 $group = defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc'; 201 202 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) { 289 $cand = ($remote !== '' ? $remote : '0.0.0.0'); 290 } 291 292 $memo = (string) apply_filters('dfehc_client_ip', $cand); 293 if ($memo === '') { 294 $memo = '0.0.0.0'; 295 } 296 return $memo; 297 } 298 } 299 300 if (!function_exists('dfehc_is_request_bot')) { 301 function dfehc_is_request_bot(): bool 302 { 303 static $cached = null; 304 static $server_ctx = null; 305 306 if ($server_ctx === null) { 307 $server_ctx = [ 308 'REMOTE_ADDR' => isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field((string) wp_unslash($_SERVER['REMOTE_ADDR'])) : '', 309 'HTTP_USER_AGENT' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field((string) wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '', 310 'HTTP_ACCEPT' => isset($_SERVER['HTTP_ACCEPT']) ? sanitize_text_field((string) wp_unslash($_SERVER['HTTP_ACCEPT'])) : '', 311 'HTTP_SEC_CH_UA' => isset($_SERVER['HTTP_SEC_CH_UA']) ? sanitize_text_field((string) wp_unslash($_SERVER['HTTP_SEC_CH_UA'])) : '', 312 'REQUEST_URI' => isset($_SERVER['REQUEST_URI']) ? sanitize_text_field((string) wp_unslash($_SERVER['REQUEST_URI'])) : '', 313 'REQUEST_METHOD' => isset($_SERVER['REQUEST_METHOD']) ? sanitize_text_field((string) wp_unslash($_SERVER['REQUEST_METHOD'])) : '', 314 ]; 315 } 316 317 if ($cached !== null) { 318 return (bool) apply_filters('dfehc_is_request_bot', (bool) $cached, $server_ctx); 319 } 320 321 $ua = (string) ($server_ctx['HTTP_USER_AGENT'] ?? ''); 322 323 $treatEmptyUaAsBot = (bool) apply_filters('dfehc_empty_ua_is_bot', true, $server_ctx); 324 if ($ua === '') { 325 $cached = $treatEmptyUaAsBot; 326 return (bool) apply_filters('dfehc_is_request_bot', (bool) $cached, $server_ctx); 327 } 328 329 if (preg_match(dfehc_get_bot_pattern(), $ua)) { 330 $cached = true; 331 return (bool) apply_filters('dfehc_is_request_bot', true, $server_ctx); 332 } 333 334 $accept = (string) ($server_ctx['HTTP_ACCEPT'] ?? ''); 335 $sec_ch = (string) ($server_ctx['HTTP_SEC_CH_UA'] ?? ''); 336 337 $strictHeuristic = (bool) apply_filters('dfehc_bot_accept_heuristic_enabled', true, $server_ctx); 338 if ($strictHeuristic && ($accept === '' || stripos($accept, 'text/html') === false) && $sec_ch === '') { 339 $cached = true; 340 return (bool) apply_filters('dfehc_is_request_bot', true, $server_ctx); 341 } 342 343 $ip = dfehc_client_ip(); 344 $group = (string) apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc'); 345 346 $badBase = (string) apply_filters('dfehc_bad_ip_key_base', 'dfehc_bad_ip_'); 347 $badKey = dfehc_scoped_key($badBase) . hash('sha256', $ip ?: 'none'); 348 $badKeyLocal = $badKey . '_t'; 349 350 if ($ip && function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_get')) { 351 if (wp_cache_get($badKey, $group)) { 352 $cached = true; 353 return (bool) apply_filters('dfehc_is_request_bot', true, $server_ctx); 354 } 355 } 356 357 if ($ip && get_transient($badKeyLocal)) { 358 $cached = true; 359 return (bool) apply_filters('dfehc_is_request_bot', true, $server_ctx); 360 } 361 362 $cached = false; 363 return (bool) apply_filters('dfehc_is_request_bot', false, $server_ctx); 364 } 365 } 366 367 if (!function_exists('dfehc_should_set_cookie')) { 368 function dfehc_should_set_cookie(): bool 369 { 370 if (defined('DOING_CRON') && DOING_CRON) { 371 return false; 372 } 373 if (defined('WP_CLI') && WP_CLI) { 374 return false; 375 } 376 if (function_exists('is_admin') && is_admin()) { 377 return false; 378 } 379 if (function_exists('wp_doing_ajax') && wp_doing_ajax()) { 380 return false; 381 } 382 if (function_exists('wp_is_json_request') && wp_is_json_request()) { 383 return false; 384 } 385 386 if (function_exists('rest_get_url_prefix')) { 387 $p = rest_get_url_prefix(); 388 $uri = isset($_SERVER['REQUEST_URI']) ? (string) wp_unslash($_SERVER['REQUEST_URI']) : ''; 389 $uri = (string) preg_replace('#\?.*$#', '', $uri); 390 $uri = ltrim($uri, '/'); 391 392 $p = is_string($p) ? trim($p, " \t\n\r\0\x0B/") : ''; 393 if ($p && strpos($uri, $p . '/') === 0) { 394 return false; 395 } 396 } 397 398 if (function_exists('is_feed') && is_feed()) { 399 return false; 400 } 401 if (function_exists('is_robots') && is_robots()) { 402 return false; 403 } 404 405 $method = isset($_SERVER['REQUEST_METHOD']) ? (string) wp_unslash($_SERVER['REQUEST_METHOD']) : ''; 406 $method = strtoupper(trim($method)); 407 if ($method !== '' && $method !== 'GET') { 408 return false; 409 } 410 411 return true; 412 } 413 } 414 415 if (!function_exists('dfehc_cache_increment')) { 416 function dfehc_cache_increment(string $key, string $group, int $ttl): int 417 { 418 static $memo = []; 419 420 $ttl = max(10, (int) $ttl); 421 422 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_incr')) { 203 423 if (function_exists('wp_cache_add')) { 204 if (!wp_cache_add($key, $value, $group, $expiration)) { 205 wp_cache_set($key, $value, $group, $expiration); 206 } 424 wp_cache_add($key, 0, $group, $ttl); 425 } 426 $value = wp_cache_incr($key, 1, $group); 427 if ($value === false) { 428 if (function_exists('wp_cache_set')) { 429 wp_cache_set($key, 1, $group, $ttl); 430 } 431 return 1; 432 } 433 return (int) $value; 434 } 435 436 if (isset($memo[$key]) && is_numeric($memo[$key])) { 437 $memo[$key] = (int) $memo[$key] + 1; 438 if (function_exists('dfehc_set_transient_noautoload')) { 439 dfehc_set_transient_noautoload($key, (int) $memo[$key], $ttl); 207 440 } else { 208 wp_cache_set($key, $value, $group, $expiration); 209 } 441 set_transient($key, (int) $memo[$key], $ttl); 442 } 443 return (int) $memo[$key]; 444 } 445 446 $cur = get_transient($key); 447 $cur = is_numeric($cur) ? (int) $cur : 0; 448 $cur++; 449 $memo[$key] = $cur; 450 451 if (function_exists('dfehc_set_transient_noautoload')) { 452 dfehc_set_transient_noautoload($key, $cur, $ttl); 453 } else { 454 set_transient($key, $cur, $ttl); 455 } 456 457 return (int) $cur; 458 } 459 } 460 461 if (!function_exists('dfehc_should_count_visitor_now')) { 462 function dfehc_should_count_visitor_now(bool $shouldRefresh, bool $hadCookie, array $ctx): bool 463 { 464 $mode = (string) apply_filters('dfehc_visitor_count_mode', 'refresh_only', $ctx); 465 $mode = strtolower(trim($mode)); 466 467 if ($mode === 'always') { 468 return true; 469 } 470 471 if ($mode === 'refresh_only') { 472 return $shouldRefresh || !$hadCookie; 473 } 474 475 if ($mode === 'sample') { 476 $rate = (float) apply_filters('dfehc_visitor_count_sample_rate', 0.05, $ctx); 477 if (!is_finite($rate) || $rate <= 0.0) { 478 return false; 479 } 480 if ($rate >= 1.0) { 481 return true; 482 } 483 $roll = function_exists('wp_rand') ? (int) wp_rand(0, 1000000) : (int) (time() % 1000001); 484 return ((float) $roll / 1000000.0) < $rate; 485 } 486 487 return $shouldRefresh || !$hadCookie; 488 } 489 } 490 491 if (!function_exists('dfehc_set_user_cookie')) { 492 function dfehc_set_user_cookie(): void 493 { 494 if (!dfehc_should_set_cookie()) { 210 495 return; 211 496 } 212 213 set_transient($key, $value, $expiration); 214 215 global $wpdb; 216 if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->options)) { 497 if (dfehc_is_request_bot()) { 217 498 return; 218 499 } 219 $opt_key = "_transient_$key"; 220 $opt_key_to = "_transient_timeout_$key"; 221 $wpdb->suppress_errors(true); 222 try { 223 $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key)); 224 if ($autoload === 'yes') { 225 $wpdb->update( 226 $wpdb->options, 227 ['autoload' => 'no'], 228 ['option_name' => $opt_key, 'autoload' => 'yes'], 229 ['%s'], 230 ['%s', '%s'] 231 ); 232 } 233 $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to)); 234 if ($autoload_to === 'yes') { 235 $wpdb->update( 236 $wpdb->options, 237 ['autoload' => 'no'], 238 ['option_name' => $opt_key_to, 'autoload' => 'yes'], 239 ['%s'], 240 ['%s', '%s'] 241 ); 242 } 243 } finally { 244 $wpdb->suppress_errors(false); 245 } 246 } 247 } 248 249 function dfehc_trusted_proxy_request(): bool 250 { 251 $remote_raw = isset($_SERVER['REMOTE_ADDR']) ? (string) wp_unslash($_SERVER['REMOTE_ADDR']) : ''; 252 $remote_raw = trim($remote_raw); 253 if ($remote_raw === '' || !filter_var($remote_raw, FILTER_VALIDATE_IP)) return false; 254 255 $trusted = (array) apply_filters('dfehc_trusted_proxies', []); 256 foreach ($trusted as $cidr) { 257 $cidr = is_string($cidr) ? trim($cidr) : ''; 258 if ($cidr !== '' && dfehc_ip_in_cidr($remote_raw, $cidr)) { 259 return true; 260 } 261 } 262 return false; 263 } 264 265 function dfehc_is_request_bot(): bool 266 { 267 static $cached = null; 268 269 $server_ctx = [ 270 'REMOTE_ADDR' => isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field((string) wp_unslash($_SERVER['REMOTE_ADDR'])) : '', 271 'HTTP_USER_AGENT' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field((string) wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '', 272 'HTTP_ACCEPT' => isset($_SERVER['HTTP_ACCEPT']) ? sanitize_text_field((string) wp_unslash($_SERVER['HTTP_ACCEPT'])) : '', 273 'HTTP_SEC_CH_UA' => isset($_SERVER['HTTP_SEC_CH_UA']) ? sanitize_text_field((string) wp_unslash($_SERVER['HTTP_SEC_CH_UA'])) : '', 274 'REQUEST_URI' => isset($_SERVER['REQUEST_URI']) ? sanitize_text_field((string) wp_unslash($_SERVER['REQUEST_URI'])) : '', 275 'REQUEST_METHOD' => isset($_SERVER['REQUEST_METHOD']) ? sanitize_text_field((string) wp_unslash($_SERVER['REQUEST_METHOD'])) : '', 276 ]; 277 278 if ($cached !== null) { 279 return (bool) apply_filters('dfehc_is_request_bot', $cached, $server_ctx); 280 } 281 282 $ua = $server_ctx['HTTP_USER_AGENT']; 283 if ($ua === '' || preg_match(dfehc_get_bot_pattern(), $ua)) { 284 $cached = true; 285 return (bool) apply_filters('dfehc_is_request_bot', $cached, $server_ctx); 286 } 287 288 $accept = $server_ctx['HTTP_ACCEPT']; 289 $sec_ch = $server_ctx['HTTP_SEC_CH_UA']; 290 291 if (($accept === '' || stripos($accept, 'text/html') === false) && $sec_ch === '') { 292 $cached = true; 293 return (bool) apply_filters('dfehc_is_request_bot', $cached, $server_ctx); 294 } 295 296 $ip = dfehc_client_ip(); 297 $ipKeyScoped = dfehc_scoped_key('dfehc_bad_ip_') . md5($ip ?: 'none'); 298 $group = apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc'); 299 300 if ($ip && function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_get')) { 301 if (wp_cache_get($ipKeyScoped, $group)) { 302 $cached = true; 303 return (bool) apply_filters('dfehc_is_request_bot', $cached, $server_ctx); 304 } 305 } else { 306 $tkey = $ipKeyScoped . '_t'; 307 if ($ip && get_transient($tkey)) { 308 $cached = true; 309 return (bool) apply_filters('dfehc_is_request_bot', $cached, $server_ctx); 310 } 311 } 312 313 $cached = false; 314 return (bool) apply_filters('dfehc_is_request_bot', $cached, $server_ctx); 315 } 316 317 function dfehc_should_set_cookie(): bool 318 { 319 if (defined('DOING_CRON') && DOING_CRON) return false; 320 if (defined('WP_CLI') && WP_CLI) return false; 321 if (function_exists('is_admin') && is_admin()) return false; 322 if (function_exists('wp_doing_ajax') && wp_doing_ajax()) return false; 323 if (function_exists('wp_is_json_request') && wp_is_json_request()) return false; 324 325 if (function_exists('rest_get_url_prefix')) { 326 $p = rest_get_url_prefix(); 327 $uri = isset($_SERVER['REQUEST_URI']) ? (string) wp_unslash($_SERVER['REQUEST_URI']) : ''; 328 $uri = (string) preg_replace('#\?.*$#', '', $uri); 329 $uri = ltrim($uri, '/'); 330 $p = is_string($p) ? trim($p, " \t\n\r\0\x0B/") : ''; 331 if ($p && strpos($uri, $p . '/') === 0) return false; 332 } 333 334 if (function_exists('is_feed') && is_feed()) return false; 335 if (function_exists('is_robots') && is_robots()) return false; 336 337 $method = isset($_SERVER['REQUEST_METHOD']) ? (string) wp_unslash($_SERVER['REQUEST_METHOD']) : ''; 338 $method = strtoupper(trim($method)); 339 if ($method !== '' && $method !== 'GET') return false; 340 341 return true; 342 } 343 344 function dfehc_set_user_cookie(): void 345 { 346 if (!dfehc_should_set_cookie()) { 347 return; 348 } 349 if (dfehc_is_request_bot()) { 350 return; 351 } 352 353 $ip = dfehc_client_ip(); 354 $group = apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc'); 355 $maxRPM = (int) apply_filters('dfehc_max_rpm', 120); 356 $badIpTtl = (int) apply_filters('dfehc_bad_ip_ttl', HOUR_IN_SECONDS); 357 $scopedVisitorKey = dfehc_scoped_key('dfehc_total_visitors'); 358 359 if ($ip && function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_incr')) { 360 $rpmKey = dfehc_scoped_key('dfehc_iprpm_') . md5($ip); 361 $rpmTtl = 60; 362 363 if (function_exists('wp_rand')) { 364 $rpmTtl += (int) wp_rand(0, 5); 365 } 366 367 if (false === wp_cache_add($rpmKey, 0, $group, $rpmTtl)) { 368 wp_cache_set($rpmKey, (int) (wp_cache_get($rpmKey, $group) ?: 0), $group, $rpmTtl); 369 } 370 $rpm = wp_cache_incr($rpmKey, 1, $group); 371 if ($rpm === false) { 372 wp_cache_set($rpmKey, 1, $group, $rpmTtl); 373 $rpm = 1; 374 } else { 375 wp_cache_set($rpmKey, (int) $rpm, $group, $rpmTtl); 376 } 377 378 if ($rpm > $maxRPM) { 379 $badKey = dfehc_scoped_key('dfehc_bad_ip_') . md5($ip); 380 wp_cache_set($badKey, 1, $group, $badIpTtl); 381 $tkey = $badKey . '_t'; 382 dfehc_set_transient_noautoload($tkey, 1, $badIpTtl); 383 wp_cache_delete($rpmKey, $group); 384 return; 385 } 386 } 387 388 $name = (string) apply_filters('dfehc_cookie_name', 'dfehc_user'); 389 $lifetime = (int) apply_filters('dfehc_cookie_lifetime', 400); 390 $path = (string) apply_filters('dfehc_cookie_path', defined('COOKIEPATH') ? COOKIEPATH : '/'); 391 392 $home = function_exists('home_url') ? (string) home_url('/') : ''; 393 $host = ''; 394 if ($home !== '') { 395 $parsed = function_exists('wp_parse_url') ? (array) wp_parse_url($home) : []; 396 $host = !empty($parsed['host']) ? (string) $parsed['host'] : ''; 397 } 398 399 $isIpHost = $host && (filter_var($host, FILTER_VALIDATE_IP) !== false); 400 $domainDefault = $isIpHost ? '' : ($host ?: (defined('COOKIE_DOMAIN') ? COOKIE_DOMAIN : '')); 401 $domain = (string) apply_filters('dfehc_cookie_domain', $domainDefault); 402 403 $sameSite = (string) apply_filters('dfehc_cookie_samesite', 'Lax'); 404 $map = ['lax' => 'Lax', 'strict' => 'Strict', 'none' => 'None']; 405 $sameSiteUpper = $map[strtolower($sameSite)] ?? 'Lax'; 406 407 $secure = (function_exists('is_ssl') && is_ssl()) || $sameSiteUpper === 'None'; 408 if ($sameSiteUpper === 'None' && !$secure) { 409 $sameSiteUpper = 'Lax'; 410 } 411 $httpOnly = true; 412 413 $nowTs = isset($_SERVER['REQUEST_TIME']) ? (int) wp_unslash($_SERVER['REQUEST_TIME']) : time(); 414 415 $existing = isset($_COOKIE[$name]) ? (string) wp_unslash($_COOKIE[$name]) : ''; 416 $existing = sanitize_text_field($existing); 417 418 $val = $existing; 419 420 if (!preg_match('/^[A-Fa-f0-9]{32,64}$/', (string) $val)) { 421 if (function_exists('random_bytes')) { 422 try { 423 $val = bin2hex(random_bytes(16)); 424 } catch (\Throwable $e) { 425 $val = bin2hex(wp_hash((string) wp_rand() . '|' . $nowTs . '|' . dfehc_host_token(), 'nonce')); 426 $val = substr(preg_replace('/[^A-Fa-f0-9]/', '', (string) $val), 0, 32); 427 } 428 } else { 429 $val = bin2hex(wp_hash((string) wp_rand() . '|' . $nowTs . '|' . dfehc_host_token(), 'nonce')); 430 $val = substr(preg_replace('/[^A-Fa-f0-9]/', '', (string) $val), 0, 32); 431 } 432 } 433 434 $refreshPct = (int) apply_filters('dfehc_cookie_refresh_percent', 5); 435 $refreshPct = max(0, min(99, $refreshPct)); 436 $shouldRefresh = !isset($_COOKIE[$name]) || ((int) wp_rand(0, 99) < $refreshPct); 437 438 if ($shouldRefresh) { 439 if (!headers_sent()) { 500 501 $ip = dfehc_client_ip(); 502 $group = (string) apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc'); 503 504 $maxRPM = (int) apply_filters('dfehc_max_rpm', 300); 505 $maxRPM = max(1, $maxRPM); 506 507 $badIpTtl = (int) apply_filters('dfehc_bad_ip_ttl', defined('HOUR_IN_SECONDS') ? HOUR_IN_SECONDS : 3600); 508 $badIpTtl = max(60, $badIpTtl); 509 510 $name = (string) apply_filters('dfehc_cookie_name', 'dfehc_user'); 511 $lifetime = (int) apply_filters('dfehc_cookie_lifetime', 400); 512 $lifetime = max(60, min(86400, $lifetime)); 513 514 $path = (string) apply_filters('dfehc_cookie_path', defined('COOKIEPATH') ? COOKIEPATH : '/'); 515 516 $home = function_exists('home_url') ? (string) home_url('/') : ''; 517 $host = ''; 518 if ($home !== '' && function_exists('wp_parse_url')) { 519 $parsed = (array) wp_parse_url($home); 520 $host = !empty($parsed['host']) ? (string) $parsed['host'] : ''; 521 } 522 523 $isIpHost = $host && (filter_var($host, FILTER_VALIDATE_IP) !== false); 524 $domainDefault = $isIpHost ? '' : ($host ?: (defined('COOKIE_DOMAIN') ? (string) COOKIE_DOMAIN : '')); 525 $domain = (string) apply_filters('dfehc_cookie_domain', $domainDefault); 526 527 $sameSite = (string) apply_filters('dfehc_cookie_samesite', 'Lax'); 528 $map = ['lax' => 'Lax', 'strict' => 'Strict', 'none' => 'None']; 529 $sameSiteUpper = $map[strtolower($sameSite)] ?? 'Lax'; 530 531 $secure = (function_exists('is_ssl') && is_ssl()) || $sameSiteUpper === 'None'; 532 if ($sameSiteUpper === 'None' && !$secure) { 533 $sameSiteUpper = 'Lax'; 534 } 535 536 $httpOnly = true; 537 538 $nowTs = isset($_SERVER['REQUEST_TIME']) ? (int) wp_unslash($_SERVER['REQUEST_TIME']) : time(); 539 540 $existing = isset($_COOKIE[$name]) ? (string) wp_unslash($_COOKIE[$name]) : ''; 541 $existing = sanitize_text_field($existing); 542 543 $val = $existing; 544 if (!preg_match('/^[A-Fa-f0-9]{32}$/', (string) $val)) { 545 $val = ''; 546 if (function_exists('random_bytes')) { 547 try { 548 $val = bin2hex(random_bytes(16)); 549 } catch (\Throwable $e) { 550 $val = ''; 551 } 552 } 553 if ($val === '' || !preg_match('/^[A-Fa-f0-9]{32}$/', $val)) { 554 $seed = (string) (function_exists('wp_rand') ? wp_rand() : mt_rand()) . '|' . $nowTs . '|' . dfehc_host_token() . '|' . uniqid('', true); 555 $val = md5($seed); 556 } 557 } 558 559 $refreshPct = (int) apply_filters('dfehc_cookie_refresh_percent', 5); 560 $refreshPct = max(0, min(99, $refreshPct)); 561 562 $roll = function_exists('wp_rand') ? (int) wp_rand(0, 99) : (int) (time() % 100); 563 $hadCookie = isset($_COOKIE[$name]); 564 $shouldRefresh = !$hadCookie || ($roll < $refreshPct); 565 566 $ctx = [ 567 'ip' => $ip, 568 'cookie_name' => $name, 569 'had_cookie' => $hadCookie ? 1 : 0, 570 'should_refresh' => $shouldRefresh ? 1 : 0, 571 'lifetime' => $lifetime, 572 ]; 573 574 if ($ip) { 575 $rpmGateMode = (string) apply_filters('dfehc_rpm_mode', 'refresh_only', $ctx); 576 $rpmGateMode = strtolower(trim($rpmGateMode)); 577 $doRpm = ($rpmGateMode === 'always') ? true : ($shouldRefresh || !$hadCookie); 578 579 if ($doRpm) { 580 $rpmKeyBase = (string) apply_filters('dfehc_ip_rpm_key_base', 'dfehc_iprpm_', $ctx); 581 $rpmKey = dfehc_scoped_key($rpmKeyBase) . hash('sha256', $ip); 582 $rpmTtl = 60 + (function_exists('wp_rand') ? (int) wp_rand(0, 29) : (int) (time() % 30)); 583 $rpm = dfehc_cache_increment($rpmKey, $group, $rpmTtl); 584 585 if ($rpm > $maxRPM) { 586 $badBase = (string) apply_filters('dfehc_bad_ip_key_base', 'dfehc_bad_ip_', $ctx); 587 $badKey = dfehc_scoped_key($badBase) . hash('sha256', $ip); 588 if (function_exists('wp_cache_set')) { 589 wp_cache_set($badKey, 1, $group, $badIpTtl); 590 } 591 if (function_exists('dfehc_set_transient_noautoload')) { 592 dfehc_set_transient_noautoload($badKey . '_t', 1, $badIpTtl); 593 } else { 594 set_transient($badKey . '_t', 1, $badIpTtl); 595 } 596 if (function_exists('wp_cache_delete')) { 597 wp_cache_delete($rpmKey, $group); 598 } 599 return; 600 } 601 } 602 } 603 604 if ($shouldRefresh && !headers_sent()) { 440 605 $expires = $nowTs + $lifetime; 606 441 607 if (PHP_VERSION_ID >= 70300) { 442 608 setcookie($name, $val, [ … … 461 627 } 462 628 } 463 } 464 465 if (isset($_COOKIE[$name])) { 466 return; 467 } 468 469 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_incr')) { 470 $vTtl = $lifetime; 471 $vTtl += (int) wp_rand(0, 5); 472 473 if (false === wp_cache_add($scopedVisitorKey, 0, $group, $vTtl)) { 474 wp_cache_set($scopedVisitorKey, (int) (wp_cache_get($scopedVisitorKey, $group) ?: 0), $group, $vTtl); 475 } 476 $valInc = wp_cache_incr($scopedVisitorKey, 1, $group); 477 if ($valInc === false) { 478 wp_cache_set($scopedVisitorKey, 1, $group, $vTtl); 479 } else { 480 wp_cache_set($scopedVisitorKey, (int) $valInc, $group, $vTtl); 481 } 482 return; 483 } 484 485 $allowDirectClients = (bool) apply_filters('dfehc_enable_direct_cache_clients', false); 486 487 static $client = null; 488 if ($allowDirectClients && !$client && extension_loaded('redis') && class_exists('Redis')) { 489 try { 490 $client = new \Redis(); 491 $sock = function_exists('get_option') ? (string) get_option('dfehc_redis_socket', '') : ''; 492 $sock = is_string($sock) ? trim($sock) : ''; 493 494 $ok = $sock 495 ? $client->pconnect($sock) 496 : $client->pconnect( 497 function_exists('dfehc_get_redis_server') ? dfehc_get_redis_server() : '127.0.0.1', 498 function_exists('dfehc_get_redis_port') ? dfehc_get_redis_port() : 6379, 499 1.0 500 ); 501 502 if ($ok) { 503 $pass = apply_filters('dfehc_redis_auth', getenv('REDIS_PASSWORD') ?: null); 504 $user = apply_filters('dfehc_redis_user', getenv('REDIS_USERNAME') ?: null); 505 if ($user && $pass && method_exists($client, 'auth')) { 506 $client->auth([$user, $pass]); 507 } elseif ($pass && method_exists($client, 'auth')) { 508 $client->auth($pass); 509 } 510 $pong = $client->ping(); 511 if (!in_array($pong, ['+PONG','PONG',true], true)) { 512 $client = null; 513 } 514 } else { 515 $client = null; 516 } 517 } catch (\Throwable $e) { 518 $client = null; 519 } 520 } 521 522 if ($client) { 523 try { 524 $client->incr($scopedVisitorKey); 525 $client->expire($scopedVisitorKey, $lifetime); 526 return; 527 } catch (\Throwable $e) { 528 } 529 } 530 531 static $mem = null; 532 if ($allowDirectClients && !$mem && extension_loaded('memcached') && class_exists('Memcached')) { 533 $mem = new \Memcached('dfehc-cookie'); 534 if (!$mem->getServerList()) { 535 $mem->addServer( 536 function_exists('dfehc_get_memcached_server') ? dfehc_get_memcached_server() : '127.0.0.1', 537 function_exists('dfehc_get_memcached_port') ? dfehc_get_memcached_port() : 11211 538 ); 539 } 540 if (empty($mem->getStats())) { 541 $mem = null; 542 } 543 } 544 545 if ($mem) { 546 try { 547 $valInc = $mem->increment($scopedVisitorKey, 1); 548 if ($valInc === false) { 549 $mem->set($scopedVisitorKey, 1, $lifetime); 550 } else { 551 if (method_exists($mem, 'touch')) { 552 $mem->touch($scopedVisitorKey, $lifetime); 553 } else { 554 $mem->set($scopedVisitorKey, (int) $valInc, $lifetime); 555 } 556 } 557 return; 558 } catch (\Throwable $e) { 559 } 560 } 561 562 $cnt = (int) get_transient($scopedVisitorKey); 563 $vTtl = $lifetime + (int) wp_rand(0, 5); 564 dfehc_set_transient_noautoload($scopedVisitorKey, $cnt + 1, $vTtl); 629 630 $jitterMax = (int) apply_filters('dfehc_visitor_ttl_jitter', 30); 631 $jitterMax = max(0, min(300, $jitterMax)); 632 $jitter = $jitterMax > 0 ? (function_exists('wp_rand') ? (int) wp_rand(0, $jitterMax) : (int) (time() % ($jitterMax + 1))) : 0; 633 634 $visitorKeyBase = (string) apply_filters('dfehc_total_visitors_key_base', 'dfehc_total_visitors', $ctx); 635 $visitorKey = dfehc_scoped_key($visitorKeyBase); 636 637 $shouldCount = dfehc_should_count_visitor_now($shouldRefresh, $hadCookie, $ctx); 638 $shouldCount = (bool) apply_filters('dfehc_should_count_visitor', $shouldCount, $ctx); 639 640 if ($shouldCount) { 641 dfehc_cache_increment($visitorKey, $group, $lifetime + $jitter); 642 } 643 } 565 644 } 566 645 -
dynamic-front-end-heartbeat-control/trunk/visitor/manager.php
r3427163 r3461136 2 2 declare(strict_types=1); 3 3 4 defined('ABSPATH') || exit; 5 4 6 if (!function_exists('dfehc_blog_id')) { 5 function dfehc_blog_id(): int { 7 function dfehc_blog_id(): int 8 { 6 9 return function_exists('get_current_blog_id') ? (int) get_current_blog_id() : 0; 7 10 } 8 11 } 12 9 13 if (!function_exists('dfehc_host_token')) { 10 function dfehc_host_token(): string { 14 function dfehc_host_token(): string 15 { 11 16 static $t = ''; 12 if ($t !== '') return $t; 13 $host = @php_uname('n') ?: (defined('WP_HOME') ? WP_HOME : (function_exists('home_url') ? home_url() : 'unknown')); 17 if ($t !== '') { 18 return $t; 19 } 20 21 $url = ''; 22 if (defined('WP_HOME') && is_string(WP_HOME) && WP_HOME !== '') { 23 $url = WP_HOME; 24 } elseif (function_exists('home_url')) { 25 $url = (string) home_url('/'); 26 } 27 28 $host = ''; 29 if ($url !== '' && function_exists('wp_parse_url')) { 30 $p = wp_parse_url($url); 31 if (is_array($p) && isset($p['host']) && is_string($p['host'])) { 32 $host = $p['host']; 33 } 34 } 35 36 if ($host === '') { 37 $host = @php_uname('n') ?: 'unknown'; 38 } 39 14 40 $salt = defined('DB_NAME') ? (string) DB_NAME : ''; 15 return $t = substr(md5((string) $host . $salt), 0, 10); 16 } 17 } 41 $t = substr(md5($host . $salt), 0, 10); 42 return $t; 43 } 44 } 45 18 46 if (!function_exists('dfehc_scoped_key')) { 19 function dfehc_scoped_key(string $base): string { 47 function dfehc_scoped_key(string $base): string 48 { 20 49 return $base . '_' . dfehc_blog_id() . '_' . dfehc_host_token(); 21 }22 }23 if (!function_exists('dfehc_set_transient_noautoload')) {24 function dfehc_set_transient_noautoload(string $key, $value, int $expiration): void {25 $group = defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc';26 27 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) {28 if (function_exists('wp_cache_add')) {29 if (!wp_cache_add($key, $value, $group, $expiration)) {30 wp_cache_set($key, $value, $group, $expiration);31 }32 } else {33 wp_cache_set($key, $value, $group, $expiration);34 }35 return;36 }37 38 set_transient($key, $value, $expiration);39 40 global $wpdb;41 if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->options)) return;42 $opt_key = "_transient_$key";43 $opt_key_to = "_transient_timeout_$key";44 $wpdb->suppress_errors(true);45 try {46 $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key));47 if ($autoload === 'yes') {48 $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key, 'autoload' => 'yes'], ['%s'], ['%s','%s']);49 }50 $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to));51 if ($autoload_to === 'yes') {52 $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key_to, 'autoload' => 'yes'], ['%s'], ['%s','%s']);53 }54 } finally {55 $wpdb->suppress_errors(false);56 }57 50 } 58 51 } … … 66 59 $max = $t; 67 60 } 68 69 if (function_exists('wp_rand')) { 70 return (int) wp_rand($min, $max); 71 } 72 73 try { 74 return (int) random_int($min, $max); 75 } catch (\Throwable $e) { 61 if ($min === $max) { 76 62 return $min; 77 63 } 78 } 79 } 80 81 if (!function_exists('dfehc_get_redis_server')) { 82 function dfehc_get_redis_server(): string { 83 $h = getenv('REDIS_HOST'); 84 return $h ? (string) $h : '127.0.0.1'; 85 } 86 } 87 if (!function_exists('dfehc_get_redis_port')) { 88 function dfehc_get_redis_port(): int { 89 $p = getenv('REDIS_PORT'); 90 return $p && ctype_digit((string) $p) ? (int) $p : 6379; 91 } 92 } 93 if (!function_exists('dfehc_get_memcached_server')) { 94 function dfehc_get_memcached_server(): string { 95 $h = getenv('MEMCACHED_HOST'); 96 return $h ? (string) $h : '127.0.0.1'; 97 } 98 } 99 if (!function_exists('dfehc_get_memcached_port')) { 100 function dfehc_get_memcached_port(): int { 101 $p = getenv('MEMCACHED_PORT'); 102 return $p && ctype_digit((string) $p) ? (int) $p : 11211; 103 } 104 } 64 if (function_exists('random_int')) { 65 try { 66 return (int) random_int($min, $max); 67 } catch (\Throwable $e) { 68 } 69 } 70 return function_exists('wp_rand') ? (int) wp_rand($min, $max) : $min; 71 } 72 } 73 105 74 if (!defined('DFEHC_SENTINEL_NO_LOAD')) { 106 define('DFEHC_SENTINEL_NO_LOAD', -1); 75 define('DFEHC_SENTINEL_NO_LOAD', 0.404); 76 } 77 78 if (!function_exists('dfehc_set_transient_noautoload')) { 79 function dfehc_set_transient_noautoload(string $key, $value, int $expiration): void 80 { 81 $expiration = (int) $expiration; 82 if ($expiration < 1) { 83 $expiration = 1; 84 } 85 if (!function_exists('set_transient')) { 86 return; 87 } 88 set_transient($key, $value, $expiration); 89 } 107 90 } 108 91 109 92 if (!function_exists('dfehc_acquire_lock')) { 110 function dfehc_acquire_lock(string $key, int $ttl = 60) { 111 $group = apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc'); 93 function dfehc_acquire_lock(string $key, int $ttl = 60) 94 { 95 $ttl = max(5, (int) $ttl); 96 $group = (string) apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? (string) DFEHC_CACHE_GROUP : 'dfehc'); 112 97 $scoped = dfehc_scoped_key($key); 98 113 99 if (class_exists('WP_Lock')) { 114 100 $lock = new WP_Lock($scoped, $ttl); 115 101 return $lock->acquire() ? $lock : null; 116 102 } 117 if (function_exists('wp_cache_add') && wp_cache_add($scoped, 1, $group, $ttl)) { 118 return (object) ['cache_key' => $scoped, 'cache_group' => $group]; 119 } 120 if (false !== get_transient($scoped)) { 103 104 if (function_exists('wp_cache_add') && function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) { 105 if (wp_cache_add($scoped, 1, $group, $ttl)) { 106 return (object) ['cache_key' => $scoped, 'cache_group' => $group]; 107 } 121 108 return null; 122 109 } 123 if (set_transient($scoped, 1, $ttl)) { 110 111 if (function_exists('get_transient') && false !== get_transient($scoped)) { 112 return null; 113 } 114 115 if (function_exists('set_transient') && set_transient($scoped, 1, $ttl)) { 124 116 return (object) ['transient_key' => $scoped]; 125 117 } 118 126 119 return null; 127 120 } 128 121 } 122 129 123 if (!function_exists('dfehc_release_lock')) { 130 function dfehc_release_lock($lock): void { 124 function dfehc_release_lock($lock): void 125 { 131 126 if ($lock instanceof WP_Lock) { 132 127 $lock->release(); 133 128 return; 134 129 } 130 135 131 if (is_object($lock) && isset($lock->cache_key)) { 136 $group = $lock->cache_group ?? apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc'); 132 $group = isset($lock->cache_group) 133 ? (string) $lock->cache_group 134 : (string) apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? (string) DFEHC_CACHE_GROUP : 'dfehc'); 135 137 136 if (function_exists('wp_cache_delete')) { 138 wp_cache_delete($lock->cache_key, $group); 139 } 140 return; 141 } 142 if (is_object($lock) && isset($lock->transient_key)) { 143 delete_transient($lock->transient_key); 144 } 145 } 146 } 147 148 function dfehc_set_default_last_activity_time(int $user_id): void { 149 $meta_key = (string) apply_filters('dfehc_last_activity_meta_key', 'last_activity_time'); 150 update_user_meta($user_id, $meta_key, time()); 137 wp_cache_delete((string) $lock->cache_key, $group); 138 } 139 return; 140 } 141 142 if (is_object($lock) && isset($lock->transient_key) && function_exists('delete_transient')) { 143 delete_transient((string) $lock->transient_key); 144 } 145 } 146 } 147 148 if (!function_exists('dfehc_set_default_last_activity_time')) { 149 function dfehc_set_default_last_activity_time(int $user_id): void 150 { 151 if (!function_exists('update_user_meta')) { 152 return; 153 } 154 $meta_key = (string) apply_filters('dfehc_last_activity_meta_key', 'last_activity_time'); 155 update_user_meta($user_id, $meta_key, time()); 156 } 151 157 } 152 158 add_action('user_register', 'dfehc_set_default_last_activity_time'); 153 159 154 function dfehc_add_intervals(array $s): array { 155 if (!isset($s['dfehc_5_minutes'])) { 156 $s['dfehc_5_minutes'] = ['interval' => 300, 'display' => __('Every 5 minutes (DFEHC)', 'dfehc')]; 157 } 158 if (!isset($s['dfehc_daily'])) { 159 $s['dfehc_daily'] = ['interval' => DAY_IN_SECONDS, 'display' => __('Daily (DFEHC)', 'dfehc')]; 160 } 161 return $s; 160 if (!function_exists('dfehc_add_intervals')) { 161 function dfehc_add_intervals(array $s): array 162 { 163 if (!isset($s['dfehc_5_minutes'])) { 164 $s['dfehc_5_minutes'] = [ 165 'interval' => 300, 166 'display' => __('Every 5 minutes (DFEHC)', 'dfehc'), 167 ]; 168 } 169 if (!isset($s['dfehc_daily'])) { 170 $s['dfehc_daily'] = [ 171 'interval' => defined('DAY_IN_SECONDS') ? (int) DAY_IN_SECONDS : 86400, 172 'display' => __('Daily (DFEHC)', 'dfehc'), 173 ]; 174 } 175 return $s; 176 } 162 177 } 163 178 add_filter('cron_schedules', 'dfehc_add_intervals'); 164 179 165 function dfehc_schedule_user_activity_processing(): void { 166 $lock = dfehc_acquire_lock('dfehc_cron_sched_lock', 15); 167 if (!$lock) { 168 return; 169 } 170 171 $aligned = time() - time() % 300 + 300; 172 173 try { 174 if (!get_option('dfehc_activity_cron_scheduled') && !wp_next_scheduled('dfehc_process_user_activity')) { 175 $ok = wp_schedule_event($aligned, 'dfehc_5_minutes', 'dfehc_process_user_activity'); 176 if ($ok && !is_wp_error($ok)) { 177 update_option('dfehc_activity_cron_scheduled', 1, false); 178 } else { 179 wp_schedule_single_event($aligned, 'dfehc_process_user_activity'); 180 } 181 } 182 183 if (!wp_next_scheduled('dfehc_cleanup_user_activity')) { 184 $args = [0, (int) apply_filters('dfehc_cleanup_batch_size', 75)]; 185 $ok2 = wp_schedule_event($aligned + 300, 'dfehc_daily', 'dfehc_cleanup_user_activity', $args); 186 if (!$ok2 || is_wp_error($ok2)) { 187 wp_schedule_single_event($aligned + 300, 'dfehc_cleanup_user_activity', $args); 188 } 189 } 190 } finally { 191 dfehc_release_lock($lock); 192 } 193 } 194 add_action('init', 'dfehc_schedule_user_activity_processing'); 195 196 function dfehc_throttled_user_activity_handler(): void { 197 $lock = dfehc_acquire_lock('dfehc_recent_user_processing', 300); 198 if (!$lock) { 199 return; 200 } 201 202 $prev = (bool) ignore_user_abort(true); 203 204 try { 205 dfehc_process_user_activity(); 206 } finally { 207 ignore_user_abort((bool) $prev); 208 dfehc_release_lock($lock); 180 if (!function_exists('dfehc_activity_scheduled_option_key')) { 181 function dfehc_activity_scheduled_option_key(): string 182 { 183 return dfehc_scoped_key('dfehc_activity_cron_scheduled'); 184 } 185 } 186 187 if (!function_exists('dfehc_schedule_user_activity_processing')) { 188 function dfehc_schedule_user_activity_processing(): void 189 { 190 static $ran = false; 191 if ($ran) { 192 return; 193 } 194 $ran = true; 195 196 $fast_key = dfehc_scoped_key('dfehc_sched_fast_guard'); 197 if (function_exists('get_transient') && get_transient($fast_key) !== false) { 198 return; 199 } 200 dfehc_set_transient_noautoload($fast_key, 1, (int) apply_filters('dfehc_schedule_fast_guard_ttl', 300)); 201 202 $lock = dfehc_acquire_lock('dfehc_cron_sched_lock', 15); 203 if (!$lock) { 204 return; 205 } 206 207 $aligned = time() - (time() % 300) + 300; 208 209 try { 210 $schedOpt = dfehc_activity_scheduled_option_key(); 211 212 $needs_primary = true; 213 if (function_exists('get_option')) { 214 $needs_primary = !get_option($schedOpt); 215 } 216 217 if ($needs_primary && function_exists('wp_next_scheduled') && !wp_next_scheduled('dfehc_process_user_activity')) { 218 $ok = function_exists('wp_schedule_event') ? wp_schedule_event($aligned, 'dfehc_5_minutes', 'dfehc_process_user_activity') : false; 219 if ($ok && !is_wp_error($ok)) { 220 if (function_exists('update_option')) { 221 update_option($schedOpt, 1, false); 222 } 223 } else { 224 if (function_exists('wp_schedule_single_event')) { 225 wp_schedule_single_event($aligned, 'dfehc_process_user_activity'); 226 } 227 } 228 } 229 230 $cleanupArgs = [0, (int) apply_filters('dfehc_cleanup_batch_size', 75)]; 231 if (function_exists('wp_next_scheduled') && !wp_next_scheduled('dfehc_cleanup_user_activity', $cleanupArgs)) { 232 $ok2 = function_exists('wp_schedule_event') ? wp_schedule_event($aligned + 300, 'dfehc_daily', 'dfehc_cleanup_user_activity', $cleanupArgs) : false; 233 if (!$ok2 || is_wp_error($ok2)) { 234 if (function_exists('wp_schedule_single_event')) { 235 wp_schedule_single_event($aligned + 300, 'dfehc_cleanup_user_activity', $cleanupArgs); 236 } 237 } 238 } 239 } finally { 240 dfehc_release_lock($lock); 241 } 242 } 243 } 244 add_action('init', 'dfehc_schedule_user_activity_processing', 10); 245 246 if (!function_exists('dfehc_get_users_in_batches')) { 247 function dfehc_get_users_in_batches(int $batch_size = 75, int $offset = 0): array 248 { 249 $batch_size = max(1, min(1000, $batch_size)); 250 $offset = max(0, $offset); 251 252 if (!class_exists('WP_User_Query')) { 253 return []; 254 } 255 256 $q = new WP_User_Query([ 257 'number' => $batch_size, 258 'offset' => $offset, 259 'fields' => 'ID', 260 ]); 261 262 $res = $q->get_results(); 263 return is_array($res) ? $res : []; 264 } 265 } 266 267 if (!function_exists('dfehc_throttled_user_activity_handler')) { 268 function dfehc_throttled_user_activity_handler(): void 269 { 270 $lock = dfehc_acquire_lock('dfehc_recent_user_processing', 300); 271 if (!$lock) { 272 return; 273 } 274 275 $prev = function_exists('ignore_user_abort') ? (bool) ignore_user_abort(true) : false; 276 try { 277 dfehc_process_user_activity(); 278 } finally { 279 if (function_exists('ignore_user_abort')) { 280 ignore_user_abort((bool) $prev); 281 } 282 dfehc_release_lock($lock); 283 } 209 284 } 210 285 } 211 286 add_action('dfehc_process_user_activity', 'dfehc_throttled_user_activity_handler'); 212 287 213 function dfehc_process_user_activity(): void { 214 $flag_opt = dfehc_scoped_key('dfehc_activity_backfill_done'); 215 if (get_option($flag_opt)) { 216 return; 217 } 218 global $wpdb; 219 if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->users)) { 220 return; 221 } 222 $meta_key = (string) apply_filters('dfehc_last_activity_meta_key', 'last_activity_time'); 223 $batch = (int) apply_filters('dfehc_activity_processing_batch_size', 75); 224 $batch = max(1, min(500, $batch)); 225 $last_id_opt = dfehc_scoped_key('dfehc_activity_last_id'); 226 $last_id = (int) get_option($last_id_opt, 0); 227 $ids = $wpdb->get_col($wpdb->prepare( 228 "SELECT ID FROM $wpdb->users WHERE ID > %d ORDER BY ID ASC LIMIT %d", 229 $last_id, $batch 230 )); 231 if (!$ids) { 232 update_option($flag_opt, 1, false); 233 delete_option($last_id_opt); 234 update_option(dfehc_scoped_key('dfehc_last_activity_cron'), time(), false); 235 return; 236 } 237 $now = time(); 238 $written = 0; 239 $max_writes = (int) apply_filters('dfehc_activity_max_writes_per_run', 500); 240 foreach ($ids as $id) { 241 if (!get_user_meta((int) $id, $meta_key, true)) { 242 update_user_meta((int) $id, $meta_key, $now); 243 $written++; 244 if ($written >= $max_writes) { 245 break; 246 } 247 } 248 } 249 $last = end($ids); 250 update_option($last_id_opt, $last ? (int) $last : (int) $last_id, false); 251 update_option(dfehc_scoped_key('dfehc_last_activity_cron'), time(), false); 252 } 253 254 function dfehc_record_user_activity(): void { 255 if (!function_exists('is_user_logged_in') || !is_user_logged_in()) { 256 return; 257 } 258 static $cache = []; 259 $meta_key = (string) apply_filters('dfehc_last_activity_meta_key', 'last_activity_time'); 260 $uid = (int) get_current_user_id(); 261 if ($uid <= 0) { 262 return; 263 } 264 $now = time(); 265 $interval = (int) apply_filters('dfehc_activity_update_interval', 900); 266 $interval = max(60, $interval); 267 $last = $cache[$uid] ?? (int) get_user_meta($uid, $meta_key, true); 268 if ($now - $last >= $interval) { 269 update_user_meta($uid, $meta_key, $now); 270 $cache[$uid] = $now; 271 } 272 } 273 add_action('wp', 'dfehc_record_user_activity'); 274 275 function dfehc_cleanup_user_activity(int $last_id = 0, int $batch_size = 75): void { 276 $lock = dfehc_acquire_lock('dfehc_cleanup_lock', 600); 277 if (!$lock) { 278 return; 279 } 280 281 $prev = (bool) ignore_user_abort(true); 282 283 try { 288 if (!function_exists('dfehc_process_user_activity')) { 289 function dfehc_process_user_activity(): void 290 { 291 static $memo_done = null; 292 static $memo_flag_opt = null; 293 static $memo_last_id_opt = null; 294 295 $flag_opt = $memo_flag_opt ?? ($memo_flag_opt = dfehc_scoped_key('dfehc_activity_backfill_done')); 296 if (function_exists('get_option')) { 297 if ($memo_done === true) { 298 return; 299 } 300 $done = (bool) get_option($flag_opt); 301 if ($done) { 302 $memo_done = true; 303 return; 304 } 305 } 306 284 307 global $wpdb; 285 308 if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->users)) { 286 309 return; 287 310 } 311 288 312 $meta_key = (string) apply_filters('dfehc_last_activity_meta_key', 'last_activity_time'); 289 $batch_size = (int) apply_filters('dfehc_cleanup_batch_size', $batch_size); 290 $batch_size = max(1, min(500, $batch_size)); 291 292 $ids = $wpdb->get_col( 293 $wpdb->prepare( 294 "SELECT ID FROM $wpdb->users WHERE ID > %d ORDER BY ID ASC LIMIT %d", 313 $batch = (int) apply_filters('dfehc_activity_processing_batch_size', 75); 314 $batch = max(1, min(500, $batch)); 315 316 $last_id_opt = $memo_last_id_opt ?? ($memo_last_id_opt = dfehc_scoped_key('dfehc_activity_last_id')); 317 $last_id = function_exists('get_option') ? (int) get_option($last_id_opt, 0) : 0; 318 319 $ids = $wpdb->get_col($wpdb->prepare( 320 "SELECT ID FROM {$wpdb->users} WHERE ID > %d ORDER BY ID ASC LIMIT %d", 321 $last_id, 322 $batch 323 )); 324 325 if (!$ids) { 326 if (function_exists('update_option')) { 327 update_option($flag_opt, 1, false); 328 $memo_done = true; 329 delete_option($last_id_opt); 330 update_option(dfehc_scoped_key('dfehc_last_activity_cron'), time(), false); 331 } 332 return; 333 } 334 335 $now = time(); 336 $written = 0; 337 $max_writes = (int) apply_filters('dfehc_activity_max_writes_per_run', 500); 338 $max_writes = max(1, min(2000, $max_writes)); 339 340 foreach ($ids as $id) { 341 $id = (int) $id; 342 $has = function_exists('get_user_meta') ? get_user_meta($id, $meta_key, true) : null; 343 if (!$has) { 344 if (function_exists('update_user_meta')) { 345 update_user_meta($id, $meta_key, $now); 346 $written++; 347 if ($written >= $max_writes) { 348 break; 349 } 350 } 351 } 352 } 353 354 $last = end($ids); 355 if (function_exists('update_option')) { 356 update_option($last_id_opt, $last ? (int) $last : (int) $last_id, false); 357 update_option(dfehc_scoped_key('dfehc_last_activity_cron'), time(), false); 358 } 359 } 360 } 361 362 if (!function_exists('dfehc_record_user_activity')) { 363 function dfehc_record_user_activity(): void 364 { 365 if (!function_exists('is_user_logged_in') || !is_user_logged_in()) { 366 return; 367 } 368 369 $uid = function_exists('get_current_user_id') ? (int) get_current_user_id() : 0; 370 if ($uid <= 0) { 371 return; 372 } 373 374 static $cache = []; 375 $meta_key = (string) apply_filters('dfehc_last_activity_meta_key', 'last_activity_time'); 376 377 $now = time(); 378 $interval = (int) apply_filters('dfehc_activity_update_interval', 900); 379 $interval = max(60, $interval); 380 381 $last = isset($cache[$uid]) ? (int) $cache[$uid] : (int) (function_exists('get_user_meta') ? get_user_meta($uid, $meta_key, true) : 0); 382 383 if ($last <= 0) { 384 if (function_exists('update_user_meta')) { 385 update_user_meta($uid, $meta_key, $now); 386 } 387 $cache[$uid] = $now; 388 return; 389 } 390 391 $delta = $now - $last; 392 if ($delta < 0) { 393 $delta = 0; 394 } 395 396 if ($delta >= $interval) { 397 if (function_exists('update_user_meta')) { 398 update_user_meta($uid, $meta_key, $now); 399 } 400 $cache[$uid] = $now; 401 402 $k = 'dfehc_user_activity'; 403 $activity = function_exists('get_user_meta') ? get_user_meta($uid, $k, true) : null; 404 if (!is_array($activity)) { 405 $activity = []; 406 } 407 $dur = isset($activity['durations']) && is_array($activity['durations']) ? $activity['durations'] : []; 408 409 $delta_f = (float) $delta; 410 if (is_finite($delta_f) && $delta_f >= 0.0) { 411 $dur[] = $delta_f; 412 } 413 414 $max_points = (int) apply_filters('dfehc_activity_max_points_per_user', 40); 415 $max_points = max(5, min(150, $max_points)); 416 if (count($dur) > $max_points) { 417 $dur = array_slice($dur, -$max_points); 418 } 419 420 $activity['durations'] = $dur; 421 $activity['t'] = $now; 422 423 if (function_exists('update_user_meta')) { 424 update_user_meta($uid, $k, $activity); 425 } 426 } 427 } 428 } 429 add_action('wp', 'dfehc_record_user_activity', 10); 430 431 if (!function_exists('dfehc_cleanup_user_activity')) { 432 function dfehc_cleanup_user_activity(int $last_id = 0, int $batch_size = 75): void 433 { 434 $lock = dfehc_acquire_lock('dfehc_cleanup_lock', 600); 435 if (!$lock) { 436 return; 437 } 438 439 $prev = function_exists('ignore_user_abort') ? (bool) ignore_user_abort(true) : false; 440 try { 441 global $wpdb; 442 if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->users)) { 443 return; 444 } 445 446 $meta_key = (string) apply_filters('dfehc_last_activity_meta_key', 'last_activity_time'); 447 $batch_size = (int) apply_filters('dfehc_cleanup_batch_size', $batch_size); 448 $batch_size = max(1, min(500, $batch_size)); 449 450 $ids = $wpdb->get_col($wpdb->prepare( 451 "SELECT ID FROM {$wpdb->users} WHERE ID > %d ORDER BY ID ASC LIMIT %d", 295 452 (int) $last_id, 296 453 (int) $batch_size 297 ) 298 ); 299 300 if (!$ids) { 301 update_option(dfehc_scoped_key('dfehc_last_cleanup_cron'), time(), false); 302 return; 303 } 304 305 $cutoff = time() - (int) apply_filters('dfehc_activity_expiration', WEEK_IN_SECONDS); 306 307 foreach ($ids as $id) { 308 $id = (int) $id; 309 $ts = (int) get_user_meta($id, $meta_key, true); 310 if ($ts && $ts < $cutoff) { 311 delete_user_meta($id, $meta_key); 312 } 313 } 314 315 if (count($ids) === $batch_size) { 316 $delay = 15 + dfehc_rand_int(0, 5); 317 wp_schedule_single_event( 318 time() + $delay, 319 'dfehc_cleanup_user_activity', 320 [(int) end($ids), (int) $batch_size] 321 ); 322 } 323 324 update_option(dfehc_scoped_key('dfehc_last_cleanup_cron'), time(), false); 325 326 } finally { 327 ignore_user_abort((bool) $prev); 328 dfehc_release_lock($lock); 454 )); 455 456 if (!$ids) { 457 if (function_exists('update_option')) { 458 update_option(dfehc_scoped_key('dfehc_last_cleanup_cron'), time(), false); 459 } 460 return; 461 } 462 463 $cutoff = time() - (int) apply_filters('dfehc_activity_expiration', defined('WEEK_IN_SECONDS') ? (int) WEEK_IN_SECONDS : 604800); 464 465 foreach ($ids as $id) { 466 $id = (int) $id; 467 $ts = function_exists('get_user_meta') ? (int) get_user_meta($id, $meta_key, true) : 0; 468 if ($ts && $ts < $cutoff) { 469 if (function_exists('delete_user_meta')) { 470 delete_user_meta($id, $meta_key); 471 } 472 } 473 } 474 475 if (count($ids) === $batch_size && function_exists('wp_schedule_single_event')) { 476 $delay = 15 + dfehc_rand_int(0, 5); 477 wp_schedule_single_event(time() + $delay, 'dfehc_cleanup_user_activity', [(int) end($ids), (int) $batch_size]); 478 } 479 480 if (function_exists('update_option')) { 481 update_option(dfehc_scoped_key('dfehc_last_cleanup_cron'), time(), false); 482 } 483 } finally { 484 if (function_exists('ignore_user_abort')) { 485 ignore_user_abort((bool) $prev); 486 } 487 dfehc_release_lock($lock); 488 } 329 489 } 330 490 } 331 491 add_action('dfehc_cleanup_user_activity', 'dfehc_cleanup_user_activity', 10, 2); 332 492 333 function dfehc_increment_total_visitors(): void { 334 $key = dfehc_scoped_key('dfehc_total_visitors'); 335 $grp = apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc'); 336 $ttl = (int) apply_filters('dfehc_total_visitors_ttl', HOUR_IN_SECONDS); 337 if (function_exists('random_int')) { 493 if (!function_exists('dfehc_total_visitors_storage_key')) { 494 function dfehc_total_visitors_storage_key(): string 495 { 496 return dfehc_scoped_key('dfehc_total_visitors'); 497 } 498 } 499 500 if (!function_exists('dfehc_increment_total_visitors')) { 501 function dfehc_increment_total_visitors(): void 502 { 503 $key = dfehc_total_visitors_storage_key(); 504 $grp = (string) apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? (string) DFEHC_CACHE_GROUP : 'dfehc'); 505 $ttl = (int) apply_filters('dfehc_total_visitors_ttl', defined('HOUR_IN_SECONDS') ? (int) HOUR_IN_SECONDS : 3600); 506 $ttl = max(60, $ttl + dfehc_rand_int(0, 5)); 507 508 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_incr')) { 509 if (function_exists('wp_cache_add')) { 510 wp_cache_add($key, 0, $grp, $ttl); 511 } 512 $val = wp_cache_incr($key, 1, $grp); 513 if ($val === false && function_exists('wp_cache_set')) { 514 wp_cache_set($key, 1, $grp, $ttl); 515 } 516 return; 517 } 518 519 $cur = function_exists('get_transient') ? get_transient($key) : false; 520 $cur = is_numeric($cur) ? (int) $cur : 0; 521 $cur++; 522 dfehc_set_transient_noautoload($key, $cur, $ttl); 523 } 524 } 525 526 if (!function_exists('dfehc_safe_cache_get_raw')) { 527 function dfehc_safe_cache_get_raw(string $rawKey): int 528 { 529 $grp = (string) apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? (string) DFEHC_CACHE_GROUP : 'dfehc'); 530 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_get')) { 531 $v = wp_cache_get($rawKey, $grp); 532 return is_numeric($v) ? (int) $v : 0; 533 } 534 $v = function_exists('get_transient') ? get_transient($rawKey) : false; 535 return is_numeric($v) ? (int) $v : 0; 536 } 537 } 538 539 if (!function_exists('dfehc_safe_cache_delete_raw')) { 540 function dfehc_safe_cache_delete_raw(string $rawKey): void 541 { 542 $grp = (string) apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? (string) DFEHC_CACHE_GROUP : 'dfehc'); 543 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_delete')) { 544 wp_cache_delete($rawKey, $grp); 545 } 546 if (function_exists('delete_transient')) { 547 delete_transient($rawKey); 548 } 549 } 550 } 551 552 if (!function_exists('dfehc_get_website_visitors')) { 553 function dfehc_get_website_visitors(): int 554 { 555 static $request_memo = null; 556 if ($request_memo !== null) { 557 return (int) $request_memo; 558 } 559 560 $cache_key = dfehc_scoped_key('dfehc_total_visitors_cache'); 561 $regen_key = dfehc_scoped_key('dfehc_regenerating_cache'); 562 $stale_opt = dfehc_scoped_key('dfehc_stale_total_visitors'); 563 564 $cache = function_exists('get_transient') ? get_transient($cache_key) : false; 565 if ($cache !== false && is_numeric($cache)) { 566 $request_memo = (int) $cache; 567 return (int) $request_memo; 568 } 569 570 if (function_exists('get_transient') && get_transient($regen_key)) { 571 $stale = function_exists('get_option') ? get_option($stale_opt, 0) : 0; 572 $request_memo = is_numeric($stale) ? (int) $stale : 0; 573 return (int) $request_memo; 574 } 575 576 $regen_ttl = (int) (defined('MINUTE_IN_SECONDS') ? (int) MINUTE_IN_SECONDS : 60) + dfehc_rand_int(0, 5); 577 dfehc_set_transient_noautoload($regen_key, 1, $regen_ttl); 578 579 $rawKey = dfehc_total_visitors_storage_key(); 580 $total = dfehc_safe_cache_get_raw($rawKey); 581 582 $ttl = (int) apply_filters('dfehc_visitors_cache_ttl', 10 * (defined('MINUTE_IN_SECONDS') ? (int) MINUTE_IN_SECONDS : 60)); 583 $ttl = max(30, $ttl + dfehc_rand_int(0, 5)); 584 585 dfehc_set_transient_noautoload($cache_key, (int) $total, $ttl); 586 if (function_exists('update_option')) { 587 update_option($stale_opt, (int) $total, false); 588 } 589 if (function_exists('delete_transient')) { 590 delete_transient($regen_key); 591 } 592 593 $request_memo = (int) apply_filters('dfehc_get_website_visitors_result', (int) $total); 594 return (int) $request_memo; 595 } 596 } 597 598 if (!function_exists('dfehc_reset_total_visitors')) { 599 function dfehc_reset_total_visitors(): void 600 { 601 $start = microtime(true); 602 $lock = dfehc_acquire_lock('dfehc_resetting_visitors', 60); 603 if (!$lock) { 604 return; 605 } 606 338 607 try { 339 $ttl += random_int(0, 5); 340 } catch (\Throwable $e) { 341 } 342 } 343 344 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_incr')) { 345 if (false === wp_cache_add($key, 0, $grp, $ttl)) { 346 $existing = wp_cache_get($key, $grp); 347 wp_cache_set($key, (int) ($existing ?: 0), $grp, $ttl); 348 } 349 $val = wp_cache_incr($key, 1, $grp); 350 if ($val === false) { 351 wp_cache_set($key, 1, $grp, $ttl); 352 } 353 return; 354 } 355 356 $allowDirectClients = (bool) apply_filters('dfehc_enable_direct_cache_clients', false); 357 358 static $redis = null; 359 if ($allowDirectClients && !$redis && extension_loaded('redis') && class_exists('Redis')) { 360 try { 361 $redis = new \Redis(); 362 $ok = $redis->pconnect(dfehc_get_redis_server(), dfehc_get_redis_port(), 1.0); 363 if ($ok) { 364 $pass = apply_filters('dfehc_redis_auth', getenv('REDIS_PASSWORD') ?: null); 365 $user = apply_filters('dfehc_redis_user', getenv('REDIS_USERNAME') ?: null); 366 if ($user && $pass && method_exists($redis, 'auth')) { 367 $redis->auth([$user, $pass]); 368 } elseif ($pass && method_exists($redis, 'auth')) { 369 $redis->auth($pass); 608 $threshold = (float) apply_filters('dfehc_reset_load_threshold', 15.0); 609 $load = function_exists('dfehc_get_server_load') ? dfehc_get_server_load() : (defined('DFEHC_SENTINEL_NO_LOAD') ? (float) DFEHC_SENTINEL_NO_LOAD : 0.404); 610 if (!is_numeric($load)) { 611 return; 612 } 613 614 $load = (float) $load; 615 if ($load === (defined('DFEHC_SENTINEL_NO_LOAD') ? (float) DFEHC_SENTINEL_NO_LOAD : 0.404) || $load >= $threshold) { 616 return; 617 } 618 619 $rawKey = dfehc_total_visitors_storage_key(); 620 dfehc_safe_cache_delete_raw($rawKey); 621 622 if (function_exists('delete_transient')) { 623 delete_transient(dfehc_scoped_key('dfehc_total_visitors_cache')); 624 delete_transient(dfehc_scoped_key('dfehc_regenerating_cache')); 625 } 626 if (function_exists('delete_option')) { 627 delete_option(dfehc_scoped_key('dfehc_stale_total_visitors')); 628 } 629 630 if ((microtime(true) - $start) > 5.0) { 631 return; 632 } 633 } finally { 634 dfehc_release_lock($lock); 635 } 636 } 637 } 638 add_action('dfehc_reset_total_visitors_event', 'dfehc_reset_total_visitors'); 639 640 if (!function_exists('dfehc_on_activate')) { 641 function dfehc_on_activate(): void 642 { 643 $aligned = time() - (time() % 300) + 300; 644 645 if (function_exists('wp_next_scheduled') && function_exists('wp_schedule_event') && !wp_next_scheduled('dfehc_reset_total_visitors_event')) { 646 $ts = $aligned + (defined('HOUR_IN_SECONDS') ? (int) HOUR_IN_SECONDS : 3600); 647 $ok = wp_schedule_event($ts, 'hourly', 'dfehc_reset_total_visitors_event'); 648 if (!$ok || is_wp_error($ok)) { 649 if (function_exists('wp_schedule_single_event')) { 650 wp_schedule_single_event($ts, 'dfehc_reset_total_visitors_event'); 370 651 } 371 $pong = $redis->ping(); 372 if (!in_array($pong, ['+PONG','PONG', true], true)) { 373 $redis = null; 374 } 375 } else { 376 $redis = null; 377 } 378 } catch (\Throwable $e) { 379 $redis = null; 380 } 381 } 382 if ($redis) { 383 try { 384 $redis->incr($key); 385 $redis->expire($key, $ttl); 386 return; 387 } catch (\Throwable $e) { 388 } 389 } 390 391 static $mem = null; 392 if ($allowDirectClients && !$mem && extension_loaded('memcached') && class_exists('Memcached')) { 393 $mem = new \Memcached('dfehc-visitors'); 394 if (!$mem->getServerList()) { 395 $mem->addServer(dfehc_get_memcached_server(), dfehc_get_memcached_port()); 396 } 397 if (empty($mem->getStats())) { 398 $mem = null; 399 } 400 } 401 if ($mem) { 402 try { 403 $inc = $mem->increment($key, 1); 404 if ($inc === false) { 405 $mem->set($key, 1, $ttl); 406 } else { 407 if (method_exists($mem, 'touch')) { 408 $mem->touch($key, $ttl); 409 } else { 410 $mem->set($key, (int) $inc, $ttl); 411 } 412 } 413 return; 414 } catch (\Throwable $e) { 415 } 416 } 417 418 $cnt = (int) get_transient($key); 419 dfehc_set_transient_noautoload($key, $cnt + 1, $ttl); 420 } 421 function dfehc_increment_total_visitors_fallback(): void { 422 dfehc_increment_total_visitors(); 423 } 424 425 function dfehc_safe_cache_get(string $key): int { 426 $scoped = dfehc_scoped_key($key); 427 $grp = apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc'); 428 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_get')) { 429 $v = wp_cache_get($scoped, $grp); 430 return is_numeric($v) ? (int) $v : 0; 431 } 432 $v = get_transient($scoped); 433 return is_numeric($v) ? (int) $v : 0; 434 } 435 436 function dfehc_safe_cache_delete(string $key): void { 437 $scoped = dfehc_scoped_key($key); 438 $grp = apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc'); 439 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_delete')) { 440 wp_cache_delete($scoped, $grp); 441 } 442 delete_transient($scoped); 443 } 444 445 function dfehc_get_website_visitors(): int { 446 $cache_key = dfehc_scoped_key('dfehc_total_visitors_cache'); 447 $regen_key = dfehc_scoped_key('dfehc_regenerating_cache'); 448 $stale_opt = dfehc_scoped_key('dfehc_stale_total_visitors'); 449 $cache = get_transient($cache_key); 450 if ($cache !== false) { 451 return (int) $cache; 452 } 453 if (get_transient($regen_key)) { 454 $stale = get_option($stale_opt, 0); 455 return is_numeric($stale) ? (int) $stale : 0; 456 } 457 458 $regen_ttl = (int) MINUTE_IN_SECONDS; 459 if (function_exists('random_int')) { 460 try { 461 $regen_ttl += random_int(0, 5); 462 } catch (\Throwable $e) { 463 } 464 } 465 dfehc_set_transient_noautoload($regen_key, true, $regen_ttl); 466 467 $total = dfehc_safe_cache_get('dfehc_total_visitors'); 468 469 $ttl = (int) apply_filters('dfehc_visitors_cache_ttl', 10 * MINUTE_IN_SECONDS); 470 if (function_exists('random_int')) { 471 try { 472 $ttl += random_int(0, 5); 473 } catch (\Throwable $e) { 474 } 475 } 476 dfehc_set_transient_noautoload($cache_key, (int) $total, $ttl); 477 update_option($stale_opt, (int) $total, false); 478 delete_transient($regen_key); 479 480 return (int) apply_filters('dfehc_get_website_visitors_result', (int) $total); 481 } 482 483 function dfehc_get_users_in_batches(int $batch_size, int $offset): array { 484 $batch_size = max(1, min(1000, $batch_size)); 485 $offset = max(0, $offset); 486 $query = new WP_User_Query([ 487 'number' => $batch_size, 488 'offset' => $offset, 489 'fields' => ['ID'], 490 ]); 491 $res = $query->get_results(); 492 return is_array($res) ? $res : []; 493 } 494 495 function dfehc_reset_total_visitors(): void { 496 $start = microtime(true); 497 $lock = dfehc_acquire_lock('dfehc_resetting_visitors', 60); 498 if (!$lock) { 499 return; 500 } 501 try { 502 $threshold = (float) apply_filters('dfehc_reset_load_threshold', 15.0); 503 $load = function_exists('dfehc_get_server_load') ? dfehc_get_server_load() : DFEHC_SENTINEL_NO_LOAD; 504 if (!is_numeric($load)) { 505 return; 506 } 507 $load = (float) $load; 508 if ($load === (float) DFEHC_SENTINEL_NO_LOAD || $load >= $threshold) { 509 return; 510 } 511 dfehc_safe_cache_delete('dfehc_total_visitors'); 512 delete_option(dfehc_scoped_key('dfehc_stale_total_visitors')); 513 delete_transient(dfehc_scoped_key('dfehc_total_visitors')); 514 delete_transient(dfehc_scoped_key('dfehc_regenerating_cache')); 515 if (microtime(true) - $start > 5) { 516 return; 517 } 518 } finally { 519 dfehc_release_lock($lock); 520 } 521 } 522 add_action('dfehc_reset_total_visitors_event', 'dfehc_reset_total_visitors'); 523 524 function dfehc_on_activate(): void { 525 $aligned = time() - time() % 300 + 300; 526 if (!wp_next_scheduled('dfehc_reset_total_visitors_event')) { 527 $ok = wp_schedule_event($aligned + HOUR_IN_SECONDS, 'hourly', 'dfehc_reset_total_visitors_event'); 652 } 653 } 654 655 dfehc_process_user_activity(); 656 dfehc_schedule_user_activity_processing(); 657 } 658 } 659 660 if (!function_exists('dfehc_ensure_reset_visitors_schedule')) { 661 function dfehc_ensure_reset_visitors_schedule(): void 662 { 663 if (!function_exists('wp_next_scheduled') || !function_exists('wp_schedule_event')) { 664 return; 665 } 666 if (wp_next_scheduled('dfehc_reset_total_visitors_event')) { 667 return; 668 } 669 670 $aligned = time() - (time() % 300) + 300; 671 $ts = $aligned + (defined('HOUR_IN_SECONDS') ? (int) HOUR_IN_SECONDS : 3600); 672 673 $ok = wp_schedule_event($ts, 'hourly', 'dfehc_reset_total_visitors_event'); 528 674 if (!$ok || is_wp_error($ok)) { 529 wp_schedule_single_event($aligned + HOUR_IN_SECONDS, 'dfehc_reset_total_visitors_event'); 530 } 531 } 532 dfehc_process_user_activity(); 533 } 675 if (function_exists('wp_schedule_single_event')) { 676 wp_schedule_single_event($ts, 'dfehc_reset_total_visitors_event'); 677 } 678 } 679 } 680 } 681 add_action('init', 'dfehc_ensure_reset_visitors_schedule', 10); 682 683 if (!function_exists('dfehc_unschedule_all')) { 684 function dfehc_unschedule_all(string $hook, array $args = []): void 685 { 686 if (!function_exists('wp_next_scheduled') || !function_exists('wp_unschedule_event')) { 687 return; 688 } 689 while ($ts = wp_next_scheduled($hook, $args)) { 690 wp_unschedule_event($ts, $hook, $args); 691 } 692 } 693 } 694 695 if (!function_exists('dfehc_on_deactivate')) { 696 function dfehc_on_deactivate(): void 697 { 698 dfehc_unschedule_all('dfehc_process_user_activity'); 699 dfehc_unschedule_all('dfehc_reset_total_visitors_event'); 700 $cleanupArgs = [0, (int) apply_filters('dfehc_cleanup_batch_size', 75)]; 701 dfehc_unschedule_all('dfehc_cleanup_user_activity', $cleanupArgs); 702 703 if (function_exists('delete_option')) { 704 delete_option(dfehc_activity_scheduled_option_key()); 705 delete_option(dfehc_scoped_key('dfehc_activity_backfill_done')); 706 delete_option(dfehc_scoped_key('dfehc_activity_last_id')); 707 } 708 } 709 } 710 534 711 if (function_exists('register_activation_hook')) { 535 register_activation_hook(__FILE__, 'dfehc_on_activate'); 536 } 537 538 function dfehc_ensure_reset_visitors_schedule(): void { 539 if (!function_exists('wp_next_scheduled') || !function_exists('wp_schedule_event')) { 540 return; 541 } 542 if (wp_next_scheduled('dfehc_reset_total_visitors_event')) { 543 return; 544 } 545 $aligned = time() - time() % 300 + 300; 546 $ts = $aligned + HOUR_IN_SECONDS; 547 $ok = wp_schedule_event($ts, 'hourly', 'dfehc_reset_total_visitors_event'); 548 if (!$ok || is_wp_error($ok)) { 549 wp_schedule_single_event($ts, 'dfehc_reset_total_visitors_event'); 550 } 551 } 552 add_action('init', 'dfehc_ensure_reset_visitors_schedule', 10); 553 554 function dfehc_on_deactivate(): void { 555 wp_clear_scheduled_hook('dfehc_process_user_activity'); 556 wp_clear_scheduled_hook('dfehc_reset_total_visitors_event'); 557 wp_clear_scheduled_hook('dfehc_cleanup_user_activity'); 558 delete_option('dfehc_activity_cron_scheduled'); 559 delete_option(dfehc_scoped_key('dfehc_activity_backfill_done')); 560 } 712 $plugin_file = defined('DFEHC_PLUGIN_FILE') ? (string) DFEHC_PLUGIN_FILE : __FILE__; 713 register_activation_hook($plugin_file, 'dfehc_on_activate'); 714 } 715 561 716 if (function_exists('register_deactivation_hook')) { 562 register_deactivation_hook(__FILE__, 'dfehc_on_deactivate'); 563 } 564 565 if (defined('WP_CLI') && WP_CLI) { 717 $plugin_file = defined('DFEHC_PLUGIN_FILE') ? (string) DFEHC_PLUGIN_FILE : __FILE__; 718 register_deactivation_hook($plugin_file, 'dfehc_on_deactivate'); 719 } 720 721 if (defined('WP_CLI') && WP_CLI && class_exists('\WP_CLI')) { 566 722 \WP_CLI::add_command('dfehc:reset_visitors', static function () { 567 723 dfehc_reset_total_visitors(); -
dynamic-front-end-heartbeat-control/trunk/widget.php
r3427163 r3461136 1 1 <?php 2 2 function dfehc_enqueue_chart_js() { 3 if (!is_admin()) { 4 return; 5 } 6 3 7 $load_logs = get_option('dfehc_server_load_logs', []); 4 8 if (!is_array($load_logs)) { … … 29 33 add_action('admin_enqueue_scripts', 'dfehc_enqueue_chart_js'); 30 34 35 function dfehc_logs_max_entries(): int { 36 $n = (int) apply_filters('dfehc_server_load_logs_max_entries', 1500); 37 return max(50, min(20000, $n)); 38 } 39 40 function dfehc_trim_load_logs(array $logs): array { 41 $max = dfehc_logs_max_entries(); 42 $count = count($logs); 43 if ($count <= $max) return $logs; 44 return array_slice($logs, $count - $max); 45 } 46 47 function dfehc_update_load_logs_option(array $logs): void { 48 $logs = array_values($logs); 49 $logs = dfehc_trim_load_logs($logs); 50 update_option('dfehc_server_load_logs', $logs, false); 51 } 52 31 53 function dfehc_heartbeat_health_dashboard_widget_function() { 54 static $memo = null; 55 if (is_array($memo)) { 56 echo $memo['html']; 57 return; 58 } 59 60 $now = time(); 61 32 62 $heartbeat_status = get_transient('dfehc_heartbeat_health_status'); 63 33 64 $server_load = dfehc_get_server_load(); 34 65 $server_response_time = dfehc_get_server_response_time(); … … 45 76 if (!is_array($load_logs)) $load_logs = []; 46 77 47 $load_logs[] = ['timestamp' => time(), 'load' => $server_load]; 48 49 $now = time(); 78 $log_enabled = (bool) apply_filters('dfehc_widget_log_enabled', true); 79 $log_sample_rate = (float) apply_filters('dfehc_widget_log_sample_rate', 1.0); 80 if (!is_finite($log_sample_rate) || $log_sample_rate < 0.0) $log_sample_rate = 0.0; 81 if ($log_sample_rate > 1.0) $log_sample_rate = 1.0; 82 83 $should_log = $log_enabled; 84 if ($should_log && $log_sample_rate < 1.0) { 85 $r = function_exists('wp_rand') ? (int) wp_rand(0, 1000000) : (int) (mt_rand(0, 1000000)); 86 $should_log = ((float) $r / 1000000.0) <= $log_sample_rate; 87 } 88 89 if ($should_log) { 90 $load_logs[] = ['timestamp' => $now, 'load' => $server_load]; 91 } 92 93 $snap_key = 'dfehc_server_load_logs_snapshot'; 94 $snap = get_transient($snap_key); 95 $snap_ttl = (int) apply_filters('dfehc_widget_logs_snapshot_ttl', 60); 96 if ($snap_ttl < 5) $snap_ttl = 5; 97 if ($snap_ttl > 600) $snap_ttl = 600; 98 99 $save_interval = (int) apply_filters('dfehc_widget_logs_save_interval', 120); 100 if ($save_interval < 10) $save_interval = 10; 101 if ($save_interval > 1800) $save_interval = 1800; 102 103 $save_lock_key = 'dfehc_server_load_logs_save_lock'; 104 $save_lock_ttl = (int) apply_filters('dfehc_widget_logs_save_lock_ttl', 15); 105 if ($save_lock_ttl < 5) $save_lock_ttl = 5; 106 if ($save_lock_ttl > 120) $save_lock_ttl = 120; 107 108 $has_snapshot = is_array($snap) && isset($snap['t'], $snap['logs']) && is_array($snap['logs']); 109 $snap_time = $has_snapshot ? (int) $snap['t'] : 0; 110 $use_snapshot = $has_snapshot && ($now - $snap_time) >= 0 && ($now - $snap_time) <= $snap_ttl; 111 112 $logs_for_chart = $use_snapshot ? $snap['logs'] : $load_logs; 113 50 114 $clean_logs = []; 51 foreach ($load_logs as $log) { 115 $cutoff = $now - 86400; 116 $upper = $now + 60; 117 118 foreach ($logs_for_chart as $log) { 52 119 if (!is_array($log)) continue; 53 120 $ts = isset($log['timestamp']) ? (int) $log['timestamp'] : 0; 54 if ($ts < ($now - 86400) || $ts > ($now + 60)|| $ts <= 0) continue;121 if ($ts < $cutoff || $ts > $upper || $ts <= 0) continue; 55 122 $ld = $log['load'] ?? null; 56 123 $ld = is_numeric($ld) ? (float) $ld : 0.0; 57 124 $clean_logs[] = ['timestamp' => $ts, 'load' => $ld]; 58 125 } 59 $load_logs = array_values($clean_logs); 60 update_option('dfehc_server_load_logs', $load_logs); 126 127 $logs_for_chart = array_values($clean_logs); 128 129 if (!$use_snapshot) { 130 set_transient($snap_key, ['t' => $now, 'logs' => $logs_for_chart], $snap_ttl); 131 } 132 133 $should_save = false; 134 $last_saved = (int) get_option('dfehc_server_load_logs_last_saved', 0); 135 if ($last_saved < 0) $last_saved = 0; 136 137 if ($should_log) { 138 if ($last_saved === 0 || ($now - $last_saved) >= $save_interval) { 139 $should_save = true; 140 } 141 } 142 143 if ($should_save) { 144 $got_lock = false; 145 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_add')) { 146 $got_lock = wp_cache_add($save_lock_key, 1, 'dfehc', $save_lock_ttl); 147 } else { 148 $got_lock = (get_transient($save_lock_key) === false) && set_transient($save_lock_key, 1, $save_lock_ttl); 149 } 150 151 if ($got_lock) { 152 update_option('dfehc_update_load_logs_option', $logs_for_chart, false); 153 update_option('dfehc_server_load_logs_last_saved', $now, false); 154 155 if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_delete')) { 156 wp_cache_delete($save_lock_key, 'dfehc'); 157 } else { 158 delete_transient($save_lock_key); 159 } 160 } 161 } 61 162 62 163 $status = is_string($heartbeat_status) ? $heartbeat_status : 'Stopped'; … … 127 228 $ajax_url = function_exists('admin_url') ? (string) admin_url('admin-ajax.php') : ''; 128 229 $ajax_nonce = function_exists('wp_create_nonce') ? (string) wp_create_nonce('dfehc_widget_stats') : ''; 230 231 ob_start(); 129 232 130 233 echo "<style> … … 539 642 }); 540 643 </script>'; 644 645 $memo = ['html' => (string) ob_get_clean()]; 646 echo $memo['html']; 541 647 } 542 648 … … 546 652 } 547 653 check_ajax_referer('dfehc_widget_stats'); 654 655 $cache_ttl = (int) apply_filters('dfehc_widget_refresh_cache_ttl', 3); 656 if ($cache_ttl < 0) $cache_ttl = 0; 657 if ($cache_ttl > 30) $cache_ttl = 30; 658 659 $cache_key = 'dfehc_widget_refresh_cache'; 660 if ($cache_ttl > 0) { 661 $cached = get_transient($cache_key); 662 if (is_array($cached) && isset($cached['server_load'], $cached['server_response_time'], $cached['recommended_interval'])) { 663 wp_send_json_success($cached); 664 } 665 } 548 666 549 667 $server_load = dfehc_get_server_load(); … … 577 695 if (!is_numeric($safe_load) && !is_string($safe_load)) $safe_load = ''; 578 696 579 wp_send_json_success([697 $payload = [ 580 698 'server_load' => $safe_load, 581 699 'server_response_time' => (float) $response_seconds, 582 700 'recommended_interval' => (float) $recommended_interval, 583 ]); 701 ]; 702 703 if ($cache_ttl > 0) { 704 set_transient($cache_key, $payload, $cache_ttl); 705 } 706 707 wp_send_json_success($payload); 584 708 }); 585 709 586 710 function dfehc_add_heartbeat_health_dashboard_widget() { 587 711 wp_add_dashboard_widget('heartbeat_health_dashboard_widget', 'Dynamic Heartbeat Health Check', 'dfehc_heartbeat_health_dashboard_widget_function'); 588 589 712 } 590 713 add_action('wp_dashboard_setup', 'dfehc_add_heartbeat_health_dashboard_widget');
Note: See TracChangeset
for help on using the changeset viewer.