lix 0.18.1

Embeddable version control for apps and AI agents.
Documentation
use std::time::Instant;

use lix::changelog::bench::{append_ordered_linear_commits, stage_append_once};
use lix::storage_adapter::{Memory, StorageAdapter};
use lix::storage_bench::{
    CommitGraphBenchMode, read_commit_graph_for_bench, seed_commit_graph_members_for_bench,
};

fn main() {
    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .expect("create commit-graph benchmark runtime")
        .block_on(run());
}

async fn run() {
    let args = std::env::args().collect::<Vec<_>>();
    let commits = parse_positive(args.get(1), "commits", 10_000);
    let samples = parse_positive(args.get(2), "samples", 11);
    let warmups = parse_nonnegative(args.get(3), "warmups", 3);
    let mode_filter = args.get(4).map(String::as_str);
    let append = append_ordered_linear_commits(commits).expect("build linear commit history");
    let commit_ids = append.commit_ids();
    let head_commit_id = commit_ids
        .last()
        .cloned()
        .expect("linear history has a head");
    let memory = Memory::new();
    stage_append_once(memory.clone(), &append)
        .await
        .expect("seed linear commit history");
    let storage = StorageAdapter::new(memory);
    seed_commit_graph_members_for_bench(&storage, &commit_ids)
        .await
        .expect("seed representative member payloads");

    for (name, mode) in [
        ("all_nodes", CommitGraphBenchMode::AllNodes),
        ("legacy_all_nodes", CommitGraphBenchMode::LegacyAllNodes),
        ("reachable_nodes", CommitGraphBenchMode::ReachableNodes),
        (
            "legacy_reachable_nodes",
            CommitGraphBenchMode::LegacyReachableNodes,
        ),
        ("history_full", CommitGraphBenchMode::HistoryFull),
        ("history_depth0", CommitGraphBenchMode::HistoryDepth0),
        ("history_limit10", CommitGraphBenchMode::HistoryLimit10),
    ] {
        if mode_filter.is_some_and(|filter| filter != name) {
            continue;
        }
        for _ in 0..warmups {
            std::hint::black_box(
                read_commit_graph_for_bench(&storage, &head_commit_id, mode)
                    .await
                    .expect("warm commit graph read"),
            );
        }
        let mut elapsed_us = Vec::with_capacity(samples);
        let mut result = None;
        for _ in 0..samples {
            let started = Instant::now();
            result = Some(
                read_commit_graph_for_bench(&storage, &head_commit_id, mode)
                    .await
                    .expect("measured commit graph read"),
            );
            elapsed_us.push(started.elapsed().as_secs_f64() * 1_000_000.0);
        }
        elapsed_us.sort_by(f64::total_cmp);
        let result = result.expect("positive sample count");
        let median_us = elapsed_us[elapsed_us.len() / 2];
        let p95_index = ((elapsed_us.len() - 1) * 95).div_ceil(100);
        println!(
            "commit_graph_scale,mode={name},commits={commits},samples={samples},median_us={median_us:.3},p95_us={:.3},nodes={},edges={},member_changes={}",
            elapsed_us[p95_index], result.nodes, result.edges, result.member_changes,
        );
    }
}

fn parse_positive(value: Option<&String>, name: &str, default: usize) -> usize {
    let parsed = parse_nonnegative(value, name, default);
    assert!(parsed > 0, "{name} must be positive");
    parsed
}

fn parse_nonnegative(value: Option<&String>, name: &str, default: usize) -> usize {
    value.map_or(default, |value| {
        value
            .parse::<usize>()
            .unwrap_or_else(|error| panic!("invalid {name} '{value}': {error}"))
    })
}