kevy_embedded/
ops_index_advise.rs1use std::sync::PoisonError;
17
18use kevy_index::{AdviseShape, advice_of};
19
20use crate::store::Store;
21use crate::{KevyError, KevyResult};
22
23#[cfg(not(target_arch = "wasm32"))]
28pub(crate) fn probe_window(
29 cell: &Option<std::sync::Arc<kevy_index::UsageCell>>,
30 win: &kevy_window::WindowRt,
31 lower: &kevy_index::IndexValue,
32) {
33 let Some(c) = cell else { return };
34 if win.boundary() == i64::MIN {
35 return;
36 }
37 if let Some(v) = kevy_index::window_value_of(lower, win.shape) {
38 c.probe(v.saturating_sub(win.boundary()));
39 }
40}
41
42#[derive(Debug, Clone)]
45pub struct IdxAdvice {
46 pub count: u64,
48 pub name: Vec<u8>,
50 pub advice: String,
52}
53
54impl Store {
55 pub fn idx_advise(&self) -> Vec<IdxAdvice> {
61 let entries: Vec<kevy_index::AdviseEntry> = {
62 let g = self.tables.advise.lock().unwrap_or_else(PoisonError::into_inner);
63 g.entries().into_iter().cloned().collect()
64 };
65 let mut rows: Vec<IdxAdvice> = {
66 let cat = self.tables.catalog.read().unwrap_or_else(PoisonError::into_inner);
67 entries
68 .iter()
69 .filter_map(|e| {
70 advice_of(e, &cat).map(|advice| IdxAdvice {
71 count: e.count,
72 name: e.name.clone(),
73 advice,
74 })
75 })
76 .collect()
77 };
78 rows.extend(self.reclaim_rows());
79 rows
80 }
81
82 fn reclaim_rows(&self) -> Vec<IdxAdvice> {
88 let now_s = (kevy_store::now_unix_ms() / 1000) as i64;
89 let mut narrow: Vec<IdxAdvice> = Vec::new();
90 let mut unused: Vec<IdxAdvice> = Vec::new();
91 let tables = self.tables.catalog.read().unwrap_or_else(PoisonError::into_inner);
92 for (name, c) in self.indexes.usage.read().unwrap_or_else(PoisonError::into_inner).iter() {
93 let margin = c.min_margin.load(std::sync::atomic::Ordering::Relaxed);
94 if let Some(dot) = name.iter().position(|&b| b == b'.')
95 && let Some(spec) = tables.get(&name[..dot])
96 && let Some(advice) = kevy_index::narrow_advice(spec, margin)
97 {
98 narrow.push(IdxAdvice { count: 0, name: name.clone(), advice });
99 }
100 let (hits, _, declared) = c.read();
101 if hits == 0 {
102 let n = String::from_utf8_lossy(name).into_owned();
103 let age = (now_s - declared).max(0);
104 unused.push(IdxAdvice {
105 count: 0,
106 name: name.clone(),
107 advice: format!("IDX.DROP {n} (never hit in the {age}s since declare)"),
108 });
109 }
110 }
111 narrow.sort_by(|a, b| a.name.cmp(&b.name));
112 unused.sort_by(|a, b| a.name.cmp(&b.name));
113 narrow.extend(unused);
114 narrow
115 }
116
117 pub(crate) fn observe_refused(&self, name: &[u8], shape: AdviseShape) {
122 let count = self.tables.advise.lock().unwrap_or_else(PoisonError::into_inner).observe(
123 name,
124 shape.clone(),
125 &[],
126 );
127 if count >= kevy_index::AUTODECLARE_AFTER {
128 self.auto_declare(name, shape, count);
129 }
130 }
131
132 fn auto_declare(&self, name: &[u8], shape: AdviseShape, count: u64) {
137 let Some(dot) = name.iter().position(|&b| b == b'.') else { return };
138 let mut spec = {
139 let g = self.tables.catalog.read().unwrap_or_else(PoisonError::into_inner);
140 match g.get(&name[..dot]) {
141 Some(s) if s.autodeclare != 0 => s.clone(),
142 _ => return,
143 }
144 };
145 let entry =
146 kevy_index::AdviseEntry { name: name.to_vec(), shape, count, sample: Vec::new() };
147 let Some(ledger) = kevy_index::apply_auto(&mut spec, &entry) else { return };
148 let Ok(compiled) = kevy_index::compile_table(&spec) else { return };
149 let path = match ledger.iter().position(|&b| b == b'#') {
150 Some(p) => ledger[..p].to_vec(),
151 None => ledger,
152 };
153 let Some(ispec) = compiled.into_iter().find(|s| s.name == path) else { return };
154 #[cfg(all(feature = "tier", not(target_arch = "wasm32")))]
158 if crate::ops_index_sync::tier_floor_check(&self.shards).is_err() {
159 return;
160 }
161 self.idx_drop(&path);
162 if self.register_spec(ispec).is_err() {
163 return;
164 }
165 {
166 let mut g = self.tables.catalog.write().unwrap_or_else(PoisonError::into_inner);
167 g.drop_table(&spec.name);
168 if g.create(spec).is_err() {
169 return;
170 }
171 }
172 self.persist_table_sidecar();
173 }
174
175 pub(crate) fn observe_noindex<T>(&self, name: &[u8], shape: AdviseShape, r: &KevyResult<T>) {
179 if let Err(KevyError::NotFound(m)) = r
180 && (m == "no such index" || m == "no such text index")
181 {
182 self.observe_refused(name, shape);
183 }
184 }
185
186 pub(crate) fn advise_clear(&self) {
188 self.tables.advise.lock().unwrap_or_else(PoisonError::into_inner).clear();
189 }
190
191 #[cfg(feature = "text")]
196 pub fn idx_match_faceted(
197 &self,
198 name: &[u8],
199 query: &[u8],
200 limit: usize,
201 opts: crate::MatchOpts<'_>,
202 ) -> KevyResult<crate::MatchPage> {
203 let r = self.match_faceted_run(name, query, limit, opts);
204 self.observe_noindex(name, AdviseShape::Match, &r);
205 if r.is_ok() {
206 self.observe_hit(name);
207 }
208 r
209 }
210
211 pub(crate) fn observe_hit(&self, name: &[u8]) {
214 let cell =
215 self.indexes.usage.read().unwrap_or_else(PoisonError::into_inner).get(name).cloned();
216 if let Some(c) = cell {
217 c.hit((kevy_store::now_unix_ms() / 1000) as i64);
218 }
219 }
220
221 #[cfg(not(target_arch = "wasm32"))]
225 pub(crate) fn usage_cell(&self, name: &[u8]) -> Option<std::sync::Arc<kevy_index::UsageCell>> {
226 self.indexes.usage.read().unwrap_or_else(PoisonError::into_inner).get(name).cloned()
227 }
228
229 pub(crate) fn is_auto_path(&self, name: &[u8]) -> bool {
231 let g = self.tables.catalog.read().unwrap_or_else(PoisonError::into_inner);
232 g.iter().any(|s| s.auto_added.iter().any(|e| e == name))
233 }
234
235 #[must_use]
237 pub fn idx_usage(&self, name: &[u8]) -> Option<(u64, i64, i64)> {
238 self.indexes
239 .usage
240 .read()
241 .unwrap_or_else(PoisonError::into_inner)
242 .get(name)
243 .map(|c| c.read())
244 }
245
246 pub(crate) fn usage_rekey(&self) {
250 let names: Vec<Vec<u8>> = {
251 let g = self.indexes.catalog.read().unwrap_or_else(PoisonError::into_inner);
252 g.1.iter().map(|(s, _)| s.name.clone()).collect()
253 };
254 let now_s = (kevy_store::now_unix_ms() / 1000) as i64;
255 let mut g = self.indexes.usage.write().unwrap_or_else(PoisonError::into_inner);
256 let old = std::mem::take(&mut *g);
257 for n in names {
258 let cell = old
259 .get(&n)
260 .cloned()
261 .unwrap_or_else(|| std::sync::Arc::new(kevy_index::UsageCell::declared_at(now_s)));
262 g.insert(n, cell);
263 }
264 }
265}