PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / logs / LogsMetricsReader.php

LogsMetricsReader.php in 404 Solution trunk, at includes/logs/LogsMetricsReader.php

169 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Logs-table row count and on-disk size reads, with a multisite-aware
9 * transient cache around the unfiltered count path.
10 *
11 * Extracted from ViewReadService in the i805 decomposition. The unfiltered
12 * `getLogsCount(0)` path is cached keyed on (blog_id, max_log_id) because the
13 * old logsv2 table can be very large and dashboards hit it on every page
14 * load; the per-id variant joins by logID and is left uncached (callers ask
15 * for a single id, the cache hit ratio is near zero).
16 */
17 class ABJ_404_Solution_LogsMetricsReader {
18
19 /** Cache TTL for the unfiltered getLogsCount(0) total row count. */
20 const LOGS_COUNT_CACHE_TTL_SECONDS = 60;
21
22 /** @var ABJ_404_Solution_DatabaseCore */
23 private $dbCore;
24
25 /** @var ABJ_404_Solution_LogsRepository */
26 private $logsRepo;
27
28 /** @var ABJ_404_Solution_Functions */
29 private $f;
30
31 /** @var ABJ_404_Solution_Logging */
32 private $logger;
33
34 /**
35 * @param ABJ_404_Solution_DatabaseCore $dbCore
36 * @param ABJ_404_Solution_LogsRepository $logsRepo
37 * @param ABJ_404_Solution_Functions $f
38 * @param ABJ_404_Solution_Logging $logger
39 */
40 public function __construct(
41 ABJ_404_Solution_DatabaseCore $dbCore,
42 ABJ_404_Solution_LogsRepository $logsRepo,
43 $f,
44 $logger
45 ) {
46 $this->dbCore = $dbCore;
47 $this->logsRepo = $logsRepo;
48 $this->f = $f;
49 $this->logger = $logger;
50 }
51
52 /**
53 * @param int $logID 0 for the unfiltered total (cached); nonzero for a per-id count.
54 * @return int
55 */
56 public function getLogsCount($logID): int {
57 $logID = absint($logID);
58
59 $cacheKey = $this->logsCountCacheKey($logID);
60 if ($cacheKey !== null) {
61 $cached = get_transient($cacheKey);
62 if (is_numeric($cached)) {
63 return (int)$cached;
64 }
65 }
66
67 $query = ABJ_404_Solution_FileSystemService::readFileContents(__DIR__ . "/../sql/getLogsCount.sql");
68
69 if ($logID != 0) {
70 $query = $this->f->str_replace('/* {SPECIFIC_ID}', '', $query);
71 $query = $this->f->str_replace('{logID}', (string)$logID, $query);
72 }
73
74 $result = $this->dbCore->queryAndGetResults($query);
75 $hadError = !empty($result['timed_out'])
76 || (isset($result['last_error']) && $result['last_error'] != '');
77
78 $rows = is_array($result['rows'] ?? null) ? $result['rows'] : array();
79 $count = 0;
80 if (!empty($rows)) {
81 $first = $rows[0];
82 $value = is_array($first) ? reset($first) : $first;
83 $count = is_scalar($value) ? intval($value) : 0;
84 }
85
86 if (!$hadError && $cacheKey !== null && function_exists('set_transient')
87 && empty($GLOBALS['abj404_feedback_preview_readonly'])) {
88 set_transient($cacheKey, $count, self::LOGS_COUNT_CACHE_TTL_SECONDS);
89 }
90
91 return function_exists('apply_filters')
92 ? (int) apply_filters('abj404_logs_count', $count, $logID)
93 : $count;
94 }
95
96 /**
97 * Returns the on-disk size in bytes of the logsv2 table, or -1 on query
98 * failure (so callers can distinguish "table is empty" from "we don't
99 * know"). The `abj404_log_disk_usage` filter lets tests stub the value.
100 *
101 * @return int
102 */
103 public function getLogDiskUsage(): int {
104 $query = 'SELECT (data_length+index_length) tablesize FROM information_schema.tables '
105 . 'WHERE table_schema=DATABASE() AND table_name=\'{wp_abj404_logsv2}\'';
106
107 $result = $this->dbCore->queryAndGetResults($query);
108
109 if (!empty($result['timed_out']) || (isset($result['last_error']) && $result['last_error'] != '')) {
110 $err = isset($result['last_error']) && is_string($result['last_error']) ? $result['last_error'] : '';
111 if ($err !== '') {
112 $this->logger->errorMessage("Error: " . esc_html($err));
113 }
114 return -1;
115 }
116
117 $rows = is_array($result['rows'] ?? null) ? $result['rows'] : array();
118 if (empty($rows)) {
119 return 0;
120 }
121
122 $row = is_array($rows[0] ?? null) ? $rows[0] : array();
123 $size = $row['tablesize'] ?? null;
124 if ($size === null || !is_scalar($size)) {
125 return 0;
126 }
127 $bytes = intval($size);
128 return function_exists('apply_filters')
129 ? (int) apply_filters('abj404_log_disk_usage', $bytes)
130 : $bytes;
131 }
132
133 /**
134 * Cache key for the unfiltered getLogsCount(0) total. Returns null when
135 * caching is not applicable (per-id path, or transient API unavailable).
136 *
137 * @param int $logID
138 * @return string|null
139 */
140 private function logsCountCacheKey(int $logID): ?string {
141 if ($logID !== 0 || !function_exists('get_transient')) {
142 return null;
143 }
144 return 'abj404_logs_count_v1_' . $this->currentBlogIdForCache() . '_' . $this->maxLogIdForCache();
145 }
146
147 /** @return int */
148 private function currentBlogIdForCache(): int {
149 if (!function_exists('get_current_blog_id')) {
150 return 1;
151 }
152 $rawBlogId = function_exists('absint')
153 ? absint(get_current_blog_id())
154 : abs(intval(get_current_blog_id()));
155 return $rawBlogId > 0 ? $rawBlogId : 1;
156 }
157
158 /** @return int */
159 private function maxLogIdForCache(): int {
160 try {
161 return max(0, intval($this->logsRepo->getMaxLogId()));
162 } catch (Throwable $e) {
163 $this->logger->debugMessage(__FUNCTION__ . ' getMaxLogId() failed: '
164 . $e->getMessage() . '. Falling back to maxLogId=0.');
165 return 0;
166 }
167 }
168 }
169