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 / ngram / NGramUsageTelemetry.php

NGramUsageTelemetry.php in 404 Solution trunk, at includes/ngram/NGramUsageTelemetry.php

111 lines 5.1 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 * N-gram filter usage telemetry.
9 *
10 * Records per-query counters (total queries, entries examined, candidates
11 * returned, total duration in ms, rolling avg reduction) into a single
12 * WordPress option. Self-resets monthly to bound option size. Read path
13 * derives avg-per-query stats from the same option.
14 *
15 * Used by NGramFilter::findSimilarPages() (write) and the admin diagnostics
16 * UI (read via getUsageStats).
17 */
18 class ABJ_404_Solution_NGramUsageTelemetry {
19
20 const OPTION_KEY = 'abj404_ngram_usage_stats';
21
22 /** Reset stats after 30 days to avoid unbounded growth */
23 const RESET_INTERVAL_SECONDS = 2592000; // 30 * 24 * 60 * 60
24
25 /**
26 * Track a single findSimilarPages invocation.
27 *
28 * @param int $totalInCache Total entries in cache
29 * @param int $examined Number of entries examined
30 * @param int $candidates Number of candidates returned
31 * @param float $duration Time in milliseconds
32 * @return void
33 */
34 public function trackNGramUsage($totalInCache, $examined, $candidates, $duration) {
35 $defaultStats = [
36 'total_queries' => 0,
37 'total_entries_examined' => 0,
38 'total_candidates_returned' => 0,
39 'total_duration_ms' => 0,
40 'avg_reduction_percent' => 0,
41 'last_reset' => abj_clock()->now(),
42 ];
43 $statsRaw = get_option(self::OPTION_KEY, $defaultStats);
44 /** @var array<string, mixed> $stats */
45 $stats = is_array($statsRaw) ? $statsRaw : $defaultStats;
46
47 $stats['total_queries'] = (isset($stats['total_queries']) && is_numeric($stats['total_queries'])) ? (int)$stats['total_queries'] + 1 : 1;
48 $stats['total_entries_examined'] = (isset($stats['total_entries_examined']) && is_numeric($stats['total_entries_examined'])) ? (int)$stats['total_entries_examined'] + $examined : $examined;
49 $stats['total_candidates_returned'] = (isset($stats['total_candidates_returned']) && is_numeric($stats['total_candidates_returned'])) ? (int)$stats['total_candidates_returned'] + $candidates : $candidates;
50 $stats['total_duration_ms'] = (isset($stats['total_duration_ms']) && is_numeric($stats['total_duration_ms'])) ? (float)$stats['total_duration_ms'] + $duration : $duration;
51
52 if ($totalInCache > 0) {
53 $reductionPercent = (($totalInCache - $examined) / $totalInCache) * 100;
54 $prevAvgReduction = (isset($stats['avg_reduction_percent']) && is_numeric($stats['avg_reduction_percent'])) ? (float)$stats['avg_reduction_percent'] : 0;
55 $totalQueries = (int)$stats['total_queries'];
56 $stats['avg_reduction_percent'] = (($prevAvgReduction * ($totalQueries - 1)) + $reductionPercent) / $totalQueries;
57 }
58
59 $monthAgo = abj_clock()->now() - self::RESET_INTERVAL_SECONDS;
60 $lastReset = (isset($stats['last_reset']) && is_numeric($stats['last_reset'])) ? (int)$stats['last_reset'] : 0;
61 if ($lastReset < $monthAgo) {
62 $stats = [
63 'total_queries' => 1,
64 'total_entries_examined' => $examined,
65 'total_candidates_returned' => $candidates,
66 'total_duration_ms' => $duration,
67 'avg_reduction_percent' => ($totalInCache > 0) ? (($totalInCache - $examined) / $totalInCache) * 100 : 0,
68 'last_reset' => abj_clock()->now(),
69 ];
70 }
71
72 update_option(self::OPTION_KEY, $stats);
73 }
74
75 /**
76 * Get usage statistics with derived per-query averages.
77 *
78 * @return array<string, mixed>
79 */
80 public function getUsageStats() {
81 $defaultStats = [
82 'total_queries' => 0,
83 'total_entries_examined' => 0,
84 'total_candidates_returned' => 0,
85 'total_duration_ms' => 0,
86 'avg_reduction_percent' => 0,
87 'last_reset' => abj_clock()->now(),
88 ];
89 $statsRaw = get_option(self::OPTION_KEY, $defaultStats);
90 /** @var array<string, mixed> $stats */
91 $stats = is_array($statsRaw) ? $statsRaw : $defaultStats;
92
93 $totalQueries = (isset($stats['total_queries']) && is_numeric($stats['total_queries'])) ? (int)$stats['total_queries'] : 0;
94 $totalExamined = (isset($stats['total_entries_examined']) && is_numeric($stats['total_entries_examined'])) ? (float)$stats['total_entries_examined'] : 0;
95 $totalCandidates = (isset($stats['total_candidates_returned']) && is_numeric($stats['total_candidates_returned'])) ? (float)$stats['total_candidates_returned'] : 0;
96 $totalDuration = (isset($stats['total_duration_ms']) && is_numeric($stats['total_duration_ms'])) ? (float)$stats['total_duration_ms'] : 0;
97
98 if ($totalQueries > 0) {
99 $stats['avg_examined_per_query'] = round($totalExamined / $totalQueries, 1);
100 $stats['avg_candidates_per_query'] = round($totalCandidates / $totalQueries, 1);
101 $stats['avg_duration_ms'] = round($totalDuration / $totalQueries, 2);
102 } else {
103 $stats['avg_examined_per_query'] = 0;
104 $stats['avg_candidates_per_query'] = 0;
105 $stats['avg_duration_ms'] = 0;
106 }
107
108 return $stats;
109 }
110 }
111