flatten: move poc-memory contents to workspace root

No more subcrate nesting — src/, agents/, schema/, defaults/, build.rs
all live at the workspace root. poc-daemon remains as the only workspace
member. Crate name (poc-memory) and all imports unchanged.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-03-25 00:54:12 -04:00
parent 891cca57f8
commit 998b71e52c
113 changed files with 79 additions and 78 deletions

116
src/counters.rs Normal file
View file

@ -0,0 +1,116 @@
// counters.rs — persistent counters backed by redb
//
// Tracks search hits, visit counts, and other per-key metrics that
// need fast increment/read without loading the full capnp store.
//
// Tables:
// search_hits: key → u64 (how often memory-search found this node)
// last_hit_ts: key → i64 (unix timestamp of last search hit)
use redb::{Database, ReadableTable, TableDefinition};
use std::path::PathBuf;
const SEARCH_HITS: TableDefinition<&str, u64> = TableDefinition::new("search_hits");
const LAST_HIT_TS: TableDefinition<&str, i64> = TableDefinition::new("last_hit_ts");
fn db_path() -> PathBuf {
crate::config::get().data_dir.join("counters.redb")
}
/// Open (or create) the counters database.
pub fn open() -> Result<Database, String> {
Database::create(db_path()).map_err(|e| format!("open counters db: {}", e))
}
/// Increment search hit count for a set of keys.
pub fn record_search_hits(keys: &[&str]) -> Result<(), String> {
let db = open()?;
let ts = chrono::Utc::now().timestamp();
let txn = db.begin_write().map_err(|e| format!("begin write: {}", e))?;
{
let mut hits = txn.open_table(SEARCH_HITS).map_err(|e| format!("open table: {}", e))?;
let mut ts_table = txn.open_table(LAST_HIT_TS).map_err(|e| format!("open table: {}", e))?;
for key in keys {
let count = hits.get(*key).map_err(|e| format!("get: {}", e))?
.map(|v| v.value())
.unwrap_or(0);
hits.insert(*key, count + 1).map_err(|e| format!("insert: {}", e))?;
ts_table.insert(*key, ts).map_err(|e| format!("insert ts: {}", e))?;
}
}
txn.commit().map_err(|e| format!("commit: {}", e))?;
Ok(())
}
/// Get search hit count for a key.
pub fn search_hit_count(key: &str) -> u64 {
let db = match open() {
Ok(db) => db,
Err(_) => return 0,
};
let txn = match db.begin_read() {
Ok(t) => t,
Err(_) => return 0,
};
let table = match txn.open_table(SEARCH_HITS) {
Ok(t) => t,
Err(_) => return 0,
};
table.get(key).ok().flatten().map(|v| v.value()).unwrap_or(0)
}
/// Get all search hit counts (for rename agent).
/// Returns keys sorted by count descending.
pub fn all_search_hits() -> Vec<(String, u64)> {
let db = match open() {
Ok(db) => db,
Err(_) => return Vec::new(),
};
let txn = match db.begin_read() {
Ok(t) => t,
Err(_) => return Vec::new(),
};
let table = match txn.open_table(SEARCH_HITS) {
Ok(t) => t,
Err(_) => return Vec::new(),
};
let mut results: Vec<(String, u64)> = match table.iter() {
Ok(iter) => iter
.flatten()
.map(|(k, v)| (k.value().to_string(), v.value()))
.collect(),
Err(_) => return Vec::new(),
};
results.sort_by(|a, b| b.1.cmp(&a.1));
results
}
/// Decay all counters by a factor (e.g. 0.9 = 10% decay).
/// Removes entries that drop to zero.
pub fn decay_all(factor: f64) -> Result<usize, String> {
let db = open()?;
let txn = db.begin_write().map_err(|e| format!("begin write: {}", e))?;
let mut removed = 0;
{
let mut table = txn.open_table(SEARCH_HITS).map_err(|e| format!("open table: {}", e))?;
// Collect keys first to avoid borrow conflict
let entries: Vec<(String, u64)> = table.iter()
.map_err(|e| format!("iter: {}", e))?
.flatten()
.map(|(k, v)| (k.value().to_string(), v.value()))
.collect();
for (key, count) in entries {
let new_count = (count as f64 * factor) as u64;
if new_count == 0 {
table.remove(key.as_str()).ok();
removed += 1;
} else {
table.insert(key.as_str(), new_count).ok();
}
}
}
txn.commit().map_err(|e| format!("commit: {}", e))?;
Ok(removed)
}