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
93 self.indexes.usage.read().unwrap_or_else(PoisonError::into_inner).iter()
94 {
95 let margin = c.min_margin.load(std::sync::atomic::Ordering::Relaxed);
96 if let Some(dot) = name.iter().position(|&b| b == b'.')
97 && let Some(spec) = tables.get(&name[..dot])
98 && let Some(advice) = kevy_index::narrow_advice(spec, margin)
99 {
100 narrow.push(IdxAdvice { count: 0, name: name.clone(), advice });
101 }
102 let (hits, _, declared) = c.read();
103 if hits == 0 {
104 let n = String::from_utf8_lossy(name).into_owned();
105 let age = (now_s - declared).max(0);
106 unused.push(IdxAdvice {
107 count: 0,
108 name: name.clone(),
109 advice: format!("IDX.DROP {n} (never hit in the {age}s since declare)"),
110 });
111 }
112 }
113 narrow.sort_by(|a, b| a.name.cmp(&b.name));
114 unused.sort_by(|a, b| a.name.cmp(&b.name));
115 narrow.extend(unused);
116 narrow
117 }
118
119 pub(crate) fn observe_refused(&self, name: &[u8], shape: AdviseShape) {
124 let count = self
125 .tables
126 .advise
127 .lock()
128 .unwrap_or_else(PoisonError::into_inner)
129 .observe(name, shape.clone(), &[]);
130 if count >= kevy_index::AUTODECLARE_AFTER {
131 self.auto_declare(name, shape, count);
132 }
133 }
134
135 fn auto_declare(&self, name: &[u8], shape: AdviseShape, count: u64) {
140 let Some(dot) = name.iter().position(|&b| b == b'.') else { return };
141 let mut spec = {
142 let g = self.tables.catalog.read().unwrap_or_else(PoisonError::into_inner);
143 match g.get(&name[..dot]) {
144 Some(s) if s.autodeclare != 0 => s.clone(),
145 _ => return,
146 }
147 };
148 let entry =
149 kevy_index::AdviseEntry { name: name.to_vec(), shape, count, sample: Vec::new() };
150 let Some(ledger) = kevy_index::apply_auto(&mut spec, &entry) else { return };
151 let Ok(compiled) = kevy_index::compile_table(&spec) else { return };
152 let path = match ledger.iter().position(|&b| b == b'#') {
153 Some(p) => ledger[..p].to_vec(),
154 None => ledger,
155 };
156 let Some(ispec) = compiled.into_iter().find(|s| s.name == path) else { return };
157 #[cfg(all(feature = "tier", not(target_arch = "wasm32")))]
161 if crate::ops_index_sync::tier_floor_check(&self.shards).is_err() {
162 return;
163 }
164 self.idx_drop(&path);
165 if self.register_spec(ispec).is_err() {
166 return;
167 }
168 {
169 let mut g = self.tables.catalog.write().unwrap_or_else(PoisonError::into_inner);
170 g.drop_table(&spec.name);
171 if g.create(spec).is_err() {
172 return;
173 }
174 }
175 self.persist_table_sidecar();
176 }
177
178 pub(crate) fn observe_noindex<T>(&self, name: &[u8], shape: AdviseShape, r: &KevyResult<T>) {
182 if let Err(KevyError::NotFound(m)) = r
183 && (m == "no such index" || m == "no such text index")
184 {
185 self.observe_refused(name, shape);
186 }
187 }
188
189 pub(crate) fn advise_clear(&self) {
191 self.tables.advise.lock().unwrap_or_else(PoisonError::into_inner).clear();
192 }
193
194 #[cfg(feature = "text")]
199 pub fn idx_match_faceted(
200 &self,
201 name: &[u8],
202 query: &[u8],
203 limit: usize,
204 opts: crate::MatchOpts<'_>,
205 ) -> KevyResult<crate::MatchPage> {
206 let r = self.match_faceted_run(name, query, limit, opts);
207 self.observe_noindex(name, AdviseShape::Match, &r);
208 if r.is_ok() {
209 self.observe_hit(name);
210 }
211 r
212 }
213
214 pub(crate) fn observe_hit(&self, name: &[u8]) {
217 let cell =
218 self.indexes.usage.read().unwrap_or_else(PoisonError::into_inner).get(name).cloned();
219 if let Some(c) = cell {
220 c.hit((kevy_store::now_unix_ms() / 1000) as i64);
221 }
222 }
223
224 #[cfg(not(target_arch = "wasm32"))]
228 pub(crate) fn usage_cell(&self, name: &[u8]) -> Option<std::sync::Arc<kevy_index::UsageCell>> {
229 self.indexes.usage.read().unwrap_or_else(PoisonError::into_inner).get(name).cloned()
230 }
231
232 pub(crate) fn is_auto_path(&self, name: &[u8]) -> bool {
234 let g = self.tables.catalog.read().unwrap_or_else(PoisonError::into_inner);
235 g.iter().any(|s| s.auto_added.iter().any(|e| e == name))
236 }
237
238 #[must_use]
240 pub fn idx_usage(&self, name: &[u8]) -> Option<(u64, i64, i64)> {
241 self.indexes
242 .usage
243 .read()
244 .unwrap_or_else(PoisonError::into_inner)
245 .get(name)
246 .map(|c| c.read())
247 }
248
249 pub(crate) fn usage_rekey(&self) {
253 let names: Vec<Vec<u8>> = {
254 let g = self.indexes.catalog.read().unwrap_or_else(PoisonError::into_inner);
255 g.1.iter().map(|(s, _)| s.name.clone()).collect()
256 };
257 let now_s = (kevy_store::now_unix_ms() / 1000) as i64;
258 let mut g = self.indexes.usage.write().unwrap_or_else(PoisonError::into_inner);
259 let old = std::mem::take(&mut *g);
260 for n in names {
261 let cell = old
262 .get(&n)
263 .cloned()
264 .unwrap_or_else(|| std::sync::Arc::new(kevy_index::UsageCell::declared_at(now_s)));
265 g.insert(n, cell);
266 }
267 }
268}