1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
//! Hierarchical Binary-Indexed Cache System Demo
//!
//! This example demonstrates the multi-level cache hierarchy:
//! - L1: Fast in-memory LRU cache (configurable size)
//! - L2: Binary-indexed disk cache with 256 SHA1-based buckets
//! - L3: Remote package registry fallback (simulated)
use std::time::Instant;
use spandx::cache::Cache;
use camino::Utf8PathBuf;
use tempfile::TempDir;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("š Hierarchical Binary-Indexed Cache System Demo");
println!("================================================");
// Initialize cache with small L1 cache for demonstration
let temp_dir = TempDir::new()?;
let cache_dir = Utf8PathBuf::from_path_buf(temp_dir.path().to_path_buf())
.map_err(|e| format!("Failed to convert path: {:?}", e))?;
let mut cache = Cache::with_memory_cache_size(cache_dir, 3); // Only 3 entries in L1
println!("š Initial cache state:");
print_cache_stats(&cache);
// Simulate populating cache with package license data
let packages = [
("rails", "7.0.0", "rubygems", vec!["MIT".to_string()]),
("express", "4.18.0", "npm", vec!["MIT".to_string()]),
("django", "4.2.0", "pypi", vec!["BSD-3-Clause".to_string()]),
("spring-boot", "3.0.0", "maven", vec!["Apache-2.0".to_string()]),
("react", "18.0.0", "npm", vec!["MIT".to_string()]),
("numpy", "1.24.0", "pypi", vec!["BSD-3-Clause".to_string()]),
];
println!("\nš Populating cache with {} packages...", packages.len());
for (name, version, pm, licenses) in &packages {
cache.set_licenses(name, version, pm, licenses.clone()).await?;
println!(" ā
Cached {}@{} ({}): {:?}", name, version, pm, licenses);
}
println!("\nš Cache state after population:");
print_cache_stats(&cache);
// Demonstrate L1 cache hits (fastest)
println!("\nā” Testing L1 cache hits (should be very fast):");
for (name, version, pm, expected) in packages.iter().take(3) {
let start = Instant::now();
let result = cache.get_licenses(name, version, pm).await?;
let duration = start.elapsed();
match result {
Some(licenses) => {
println!(" šÆ L1 HIT: {}@{} -> {:?} ({:.2}μs)",
name, version, licenses, duration.as_micros());
assert_eq!(licenses, *expected);
}
None => println!(" ā MISS: {}@{}", name, version),
}
}
// Clear L1 cache to test L2 fallback
println!("\nš§¹ Clearing L1 cache to demonstrate L2 fallback...");
cache.clear_memory_cache();
print_cache_stats(&cache);
// Demonstrate L2 cache hits (slower but still fast)
println!("\nš¾ Testing L2 cache hits (binary-indexed disk):");
for (name, version, pm, expected) in &packages {
let start = Instant::now();
let result = cache.get_licenses(name, version, pm).await?;
let duration = start.elapsed();
match result {
Some(licenses) => {
println!(" šÆ L2 HIT: {}@{} -> {:?} ({:.2}μs)",
name, version, licenses, duration.as_micros());
assert_eq!(licenses, *expected);
}
None => println!(" ā MISS: {}@{}", name, version),
}
}
println!("\nš Final cache state (L2 entries promoted to L1):");
print_cache_stats(&cache);
// Demonstrate cache miss (would trigger L3 fallback in real system)
println!("\nš Testing cache miss (would trigger remote registry lookup):");
let start = Instant::now();
let result = cache.get_licenses("nonexistent", "1.0.0", "npm").await?;
let duration = start.elapsed();
match result {
Some(licenses) => println!(" šÆ Unexpected hit: {:?}", licenses),
None => println!(" ā MISS: nonexistent@1.0.0 -> would fetch from registry ({:.2}μs)",
duration.as_micros()),
}
// Demonstrate bucket distribution
println!("\nšļø Cache bucket distribution:");
print_bucket_analysis(&packages);
println!("\n⨠Demo complete! The hierarchical cache provides:");
println!(" ⢠L1: Ultra-fast memory access (μs latency)");
println!(" ⢠L2: Fast binary-indexed disk access (ms latency)");
println!(" ⢠L3: Remote registry fallback (s latency, not shown)");
println!(" ⢠Automatic promotion between levels");
println!(" ⢠LRU eviction in L1 memory cache");
println!(" ⢠SHA1-based bucketing for optimal distribution");
Ok(())
}
fn print_cache_stats(cache: &Cache) {
let stats = cache.memory_cache_stats();
println!(" L1 Memory Cache: {}/{} entries ({:.1}% utilization, {} remaining)",
stats.entries,
stats.max_entries,
stats.utilization() * 100.0,
stats.remaining_capacity());
}
fn print_bucket_analysis(packages: &[(&str, &str, &str, Vec<String>)]) {
use sha1::{Digest, Sha1};
for (name, version, pm, _) in packages {
let mut hasher = Sha1::new();
hasher.update(name.as_bytes());
let hash = hasher.finalize();
let bucket = format!("{:02x}", hash[0]);
println!(" š {}@{} ({}) -> bucket {} (hash: {:02x}{}...)",
name, version, pm, bucket, hash[0],
hash.iter().skip(1).take(2).map(|b| format!("{:02x}", b)).collect::<String>());
}
}
|