pinakes-core: update remaining modules and tests

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I9e0ff5ea33a5cf697473423e88f167ce6a6a6964
This commit is contained in:
raf 2026-03-08 00:42:29 +03:00
commit 3d9f8933d2
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
44 changed files with 1207 additions and 578 deletions

View file

@ -30,13 +30,18 @@ pub struct CacheStats {
}
impl CacheStats {
#[must_use]
pub fn hit_rate(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
self.hits as f64 / total as f64
}
// Compute ratio using integer arithmetic: hits * 10000 / total gives basis
// points (0..=10000), then scale back to [0.0, 1.0]. Returns 0.0 if total
// is zero.
let basis_points = self
.hits
.saturating_mul(10_000)
.checked_div(total)
.unwrap_or(0);
f64::from(u32::try_from(basis_points).unwrap_or(u32::MAX)) / 10_000.0
}
}
@ -88,6 +93,7 @@ where
V: Clone + Send + Sync + 'static,
{
/// Create a new cache with the specified TTL and maximum capacity.
#[must_use]
pub fn new(ttl: Duration, max_capacity: u64) -> Self {
let inner = MokaCache::builder()
.time_to_live(ttl)
@ -101,6 +107,7 @@ where
}
/// Create a new cache with TTL, max capacity, and time-to-idle.
#[must_use]
pub fn new_with_idle(
ttl: Duration,
tti: Duration,
@ -120,16 +127,16 @@ where
/// Get a value from the cache.
pub async fn get(&self, key: &K) -> Option<V> {
match self.inner.get(key).await {
Some(value) => {
self.metrics.record_hit();
Some(value)
},
None => {
self.inner.get(key).await.map_or_else(
|| {
self.metrics.record_miss();
None
},
}
|value| {
self.metrics.record_hit();
Some(value)
},
)
}
/// Insert a value into the cache.
@ -150,11 +157,13 @@ where
}
/// Get the current number of entries in the cache.
#[must_use]
pub fn entry_count(&self) -> u64 {
self.inner.entry_count()
}
/// Get cache statistics.
#[must_use]
pub fn stats(&self) -> CacheStats {
let (hits, misses) = self.metrics.stats();
CacheStats {
@ -168,11 +177,12 @@ where
/// Specialized cache for search query results.
pub struct QueryCache {
/// Cache keyed by (query_hash, offset, limit)
/// Cache keyed by (`query_hash`, offset, limit)
inner: Cache<String, String>,
}
impl QueryCache {
#[must_use]
pub fn new(ttl: Duration, max_capacity: u64) -> Self {
Self {
inner: Cache::new(ttl, max_capacity),
@ -224,6 +234,7 @@ impl QueryCache {
self.inner.invalidate_all().await;
}
#[must_use]
pub fn stats(&self) -> CacheStats {
self.inner.stats()
}
@ -236,6 +247,7 @@ pub struct MetadataCache {
}
impl MetadataCache {
#[must_use]
pub fn new(ttl: Duration, max_capacity: u64) -> Self {
Self {
inner: Cache::new(ttl, max_capacity),
@ -257,6 +269,7 @@ impl MetadataCache {
self.inner.invalidate(&content_hash.to_string()).await;
}
#[must_use]
pub fn stats(&self) -> CacheStats {
self.inner.stats()
}
@ -268,6 +281,7 @@ pub struct MediaCache {
}
impl MediaCache {
#[must_use]
pub fn new(ttl: Duration, max_capacity: u64) -> Self {
Self {
inner: Cache::new(ttl, max_capacity),
@ -290,6 +304,7 @@ impl MediaCache {
self.inner.invalidate_all().await;
}
#[must_use]
pub fn stats(&self) -> CacheStats {
self.inner.stats()
}
@ -348,6 +363,7 @@ pub struct CacheLayer {
impl CacheLayer {
/// Create a new cache layer with the specified TTL (using defaults for other
/// settings).
#[must_use]
pub fn new(ttl_secs: u64) -> Self {
let config = CacheConfig {
response_ttl_secs: ttl_secs,
@ -357,6 +373,7 @@ impl CacheLayer {
}
/// Create a new cache layer with full configuration.
#[must_use]
pub fn with_config(config: CacheConfig) -> Self {
Self {
responses: Cache::new(
@ -401,6 +418,7 @@ impl CacheLayer {
}
/// Get aggregated statistics for all caches.
#[must_use]
pub fn stats(&self) -> CacheLayerStats {
CacheLayerStats {
responses: self.responses.stats(),
@ -411,7 +429,8 @@ impl CacheLayer {
}
/// Get the current configuration.
pub fn config(&self) -> &CacheConfig {
#[must_use]
pub const fn config(&self) -> &CacheConfig {
&self.config
}
}
@ -427,6 +446,7 @@ pub struct CacheLayerStats {
impl CacheLayerStats {
/// Get the overall hit rate across all caches.
#[must_use]
pub fn overall_hit_rate(&self) -> f64 {
let total_hits = self.responses.hits
+ self.queries.hits
@ -438,15 +458,16 @@ impl CacheLayerStats {
+ self.metadata.misses
+ self.media.misses;
if total_requests == 0 {
0.0
} else {
total_hits as f64 / total_requests as f64
}
let basis_points = total_hits
.saturating_mul(10_000)
.checked_div(total_requests)
.unwrap_or(0);
f64::from(u32::try_from(basis_points).unwrap_or(u32::MAX)) / 10_000.0
}
/// Get the total number of entries across all caches.
pub fn total_entries(&self) -> u64 {
#[must_use]
pub const fn total_entries(&self) -> u64 {
self.responses.size
+ self.queries.size
+ self.metadata.size
@ -460,7 +481,7 @@ mod tests {
#[tokio::test]
async fn test_cache_basic_operations() {
let cache: Cache<String, String> = Cache::new(Duration::from_secs(60), 100);
let cache: Cache<String, String> = Cache::new(Duration::from_mins(1), 100);
// Insert and get
cache.insert("key1".to_string(), "value1".to_string()).await;
@ -479,7 +500,7 @@ mod tests {
#[tokio::test]
async fn test_cache_stats() {
let cache: Cache<String, String> = Cache::new(Duration::from_secs(60), 100);
let cache: Cache<String, String> = Cache::new(Duration::from_mins(1), 100);
cache.insert("key1".to_string(), "value1".to_string()).await;
let _ = cache.get(&"key1".to_string()).await; // hit
@ -493,7 +514,7 @@ mod tests {
#[tokio::test]
async fn test_query_cache() {
let cache = QueryCache::new(Duration::from_secs(60), 100);
let cache = QueryCache::new(Duration::from_mins(1), 100);
cache
.insert("test query", 0, 10, Some("name"), "results".to_string())