ekv 1.0.0

Key-value database for embedded systems, for raw NOR flash, using an LSM-Tree
Documentation
use ekv::flash::MemFlash;
use ekv::{Config, Database};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use plotters::prelude::*;
use rand::Rng;

struct Params {
    key_count: usize,
    key_len: usize,
    val_len: usize,
}

fn rand_data(len: usize) -> Vec<u8> {
    let mut res = vec![0; len];
    rand::thread_rng().fill(&mut res[..]);
    res
}

#[allow(unused)]
fn print_counters(f: &mut MemFlash, baseline: usize) {
    println!(
        "    read:  {}, {} bytes ({:.1}%)",
        f.read_count,
        f.read_bytes,
        100.0 * f.read_bytes as f64 / baseline as f64
    );
    println!(
        "    write: {}, {} bytes ({:.1}%)",
        f.write_count,
        f.write_bytes,
        100.0 * f.write_bytes as f64 / baseline as f64
    );
    println!(
        "    erase: {}, {} bytes ({:.1}%)",
        f.erase_count,
        f.erase_bytes,
        100.0 * f.erase_bytes as f64 / baseline as f64
    );
}

async fn run(p: Params) -> f64 {
    // Generate keys
    let mut keys = Vec::new();
    keys.push(b"foo".to_vec());
    while keys.len() < p.key_count {
        let key = rand_data(p.key_len);
        if !keys.contains(&key) {
            keys.push(key)
        }
    }

    let mut f = MemFlash::new();
    let config = Config::default();
    let db = Database::<_, NoopRawMutex>::new(&mut f, config);
    db.format().await.unwrap();

    for key in &keys {
        let mut wtx = db.write_transaction().await;
        wtx.write(key, &rand_data(p.val_len)).await.unwrap();
        wtx.commit().await.unwrap();
    }

    db.lock_flash().await.reset_counters();

    let mut buf = [0; 1024];

    for key in &keys {
        let rtx = db.read_transaction().await;
        rtx.read(key, &mut buf).await.unwrap();
    }

    let baseline = p.key_count * (p.key_len + p.val_len);
    let read_bytes = db.lock_flash().await.read_bytes;
    read_bytes as f64 / baseline as f64
}

const OUT_FILE_NAME: &str = "area-chart.png";

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    env_logger::init();

    let max = 500;

    let mut data = Vec::new();
    for count in (10..max).step_by(1) {
        let amplification = run(Params {
            key_count: count,
            key_len: 4,
            val_len: 10,
        })
        .await;
        data.push((count as f64, amplification));
    }

    let max_y = data
        .iter()
        .map(|(_, y)| *y)
        .max_by(|a, b| a.partial_cmp(b).unwrap())
        .unwrap();

    let root = BitMapBackend::new(OUT_FILE_NAME, (1920, 768)).into_drawing_area();

    root.fill(&WHITE)?;

    let mut chart = ChartBuilder::on(&root)
        .set_label_area_size(LabelAreaPosition::Left, 60)
        .set_label_area_size(LabelAreaPosition::Bottom, 60)
        .build_cartesian_2d(0.0..(max as f64), 0.0..max_y)?;

    chart.configure_mesh().disable_x_mesh().disable_y_mesh().draw()?;

    chart.draw_series(AreaSeries::new(data, 0.0, RED.mix(0.2)).border_style(RED))?;

    root.present()?;
    Ok(())
}