a73x

benches/core_ops.rs

Ref:   Size: 8.2 KiB

use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use ed25519_dalek::SigningKey;
use git2::Repository;
use rand_core::OsRng;
use tempfile::TempDir;

use git_collab::dag;
use git_collab::event::{Action, Author, Event};
use git_collab::state::{IssueState, PatchState};

fn test_author() -> Author {
    Author {
        name: "Bench User".to_string(),
        email: "bench@example.com".to_string(),
    }
}

fn test_key() -> SigningKey {
    SigningKey::generate(&mut OsRng)
}

fn now() -> String {
    chrono::Utc::now().to_rfc3339()
}

fn init_repo(dir: &std::path::Path) -> Repository {
    let repo = Repository::init(dir).expect("init repo");
    {
        let mut config = repo.config().unwrap();
        config.set_str("user.name", "Bench User").unwrap();
        config.set_str("user.email", "bench@example.com").unwrap();
    }
    repo
}

/// Create N issues in a repo, each with no comments. Returns the repo and temp dir.
fn setup_issues(n: usize) -> (Repository, TempDir) {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path());
    let sk = test_key();
    let author = test_author();

    for i in 0..n {
        let event = Event {
            timestamp: now(),
            author: author.clone(),
            action: Action::IssueOpen {
                title: format!("Issue {}", i),
                body: format!("Body for issue {}", i),
                relates_to: None,
            },
            clock: 0,
        };
        let oid = dag::create_root_event(&repo, &event, &sk).unwrap();
        let ref_name = format!("refs/collab/issues/{}", oid);
        repo.reference(&ref_name, oid, false, "bench").unwrap();
    }

    (repo, dir)
}

/// Create N patches in a repo. Returns the repo and temp dir.
fn setup_patches(n: usize) -> (Repository, TempDir) {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path());
    let sk = test_key();
    let author = test_author();

    for i in 0..n {
        let event = Event {
            timestamp: now(),
            author: author.clone(),
            action: Action::PatchCreate {
                title: format!("Patch {}", i),
                body: format!("Body for patch {}", i),
                base_ref: "main".to_string(),
                branch: format!("feature-{}", i),
                fixes: None,
                commit: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
                tree: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
                base_commit: None,
            },
            clock: 0,
        };
        let oid = dag::create_root_event(&repo, &event, &sk).unwrap();
        let ref_name = format!("refs/collab/patches/{}", oid);
        repo.reference(&ref_name, oid, false, "bench").unwrap();
    }

    (repo, dir)
}

/// Create a single issue with N comment events appended. Returns (repo, ref_name, id, tempdir).
fn setup_issue_with_comments(n: usize) -> (Repository, String, String, TempDir) {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path());
    let sk = test_key();
    let author = test_author();

    let open_event = Event {
        timestamp: now(),
        author: author.clone(),
        action: Action::IssueOpen {
            title: "Big issue".to_string(),
            body: "An issue with many comments".to_string(),
            relates_to: None,
        },
        clock: 0,
    };
    let oid = dag::create_root_event(&repo, &open_event, &sk).unwrap();
    let id = oid.to_string();
    let ref_name = format!("refs/collab/issues/{}", id);
    repo.reference(&ref_name, oid, false, "bench").unwrap();

    for i in 0..n {
        let event = Event {
            timestamp: now(),
            author: author.clone(),
            action: Action::IssueComment {
                body: format!("Comment number {}", i),
            },
            clock: 0,
        };
        dag::append_event(&repo, &ref_name, &event, &sk).unwrap();
    }

    (repo, ref_name, id, dir)
}

fn bench_list_issues(c: &mut Criterion) {
    let mut group = c.benchmark_group("list_issues");
    for count in [10, 100, 1000] {
        let (repo, _dir) = setup_issues(count);
        group.bench_with_input(
            BenchmarkId::from_parameter(count),
            &count,
            |b, _| {
                b.iter(|| {
                    let issues = git_collab::state::list_issues(&repo).unwrap();
                    assert_eq!(issues.len(), count);
                });
            },
        );
    }
    group.finish();
}

fn bench_list_patches(c: &mut Criterion) {
    let mut group = c.benchmark_group("list_patches");
    for count in [10, 100, 1000] {
        let (repo, _dir) = setup_patches(count);
        group.bench_with_input(
            BenchmarkId::from_parameter(count),
            &count,
            |b, _| {
                b.iter(|| {
                    let patches = git_collab::state::list_patches(&repo).unwrap();
                    assert_eq!(patches.len(), count);
                });
            },
        );
    }
    group.finish();
}

fn bench_walk_events(c: &mut Criterion) {
    let mut group = c.benchmark_group("walk_events");
    for count in [10, 100, 500] {
        let (repo, ref_name, _id, _dir) = setup_issue_with_comments(count);
        group.bench_with_input(
            BenchmarkId::from_parameter(count),
            &count,
            |b, _| {
                b.iter(|| {
                    let events = dag::walk_events(&repo, &ref_name).unwrap();
                    // 1 open + N comments
                    assert_eq!(events.len(), count + 1);
                });
            },
        );
    }
    group.finish();
}

fn bench_issue_from_ref(c: &mut Criterion) {
    let mut group = c.benchmark_group("issue_from_ref");
    for count in [10, 100, 500] {
        let (repo, ref_name, id, _dir) = setup_issue_with_comments(count);
        group.bench_with_input(
            BenchmarkId::from_parameter(count),
            &count,
            |b, _| {
                b.iter(|| {
                    let state = IssueState::from_ref(&repo, &ref_name, &id).unwrap();
                    assert_eq!(state.comments.len(), count);
                });
            },
        );
    }
    group.finish();
}

fn bench_patch_from_ref(c: &mut Criterion) {
    let mut group = c.benchmark_group("patch_from_ref");
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path());
    let sk = test_key();
    let author = test_author();

    // Create a patch with many comments
    for count in [10, 100, 500] {
        let create_event = Event {
            timestamp: now(),
            author: author.clone(),
            action: Action::PatchCreate {
                title: format!("Patch with {} comments", count),
                body: "A patch".to_string(),
                base_ref: "main".to_string(),
                branch: format!("feature-bench-{}", count),
                fixes: None,
                commit: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
                tree: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
                base_commit: None,
            },
            clock: 0,
        };
        let oid = dag::create_root_event(&repo, &create_event, &sk).unwrap();
        let id = oid.to_string();
        let ref_name = format!("refs/collab/patches/{}", id);
        repo.reference(&ref_name, oid, false, "bench").unwrap();

        for i in 0..count {
            let event = Event {
                timestamp: now(),
                author: author.clone(),
                action: Action::PatchComment {
                    body: format!("Patch comment {}", i),
                },
                clock: 0,
            };
            dag::append_event(&repo, &ref_name, &event, &sk).unwrap();
        }

        group.bench_with_input(
            BenchmarkId::from_parameter(count),
            &count,
            |b, _| {
                b.iter(|| {
                    let state = PatchState::from_ref(&repo, &ref_name, &id).unwrap();
                    assert_eq!(state.comments.len(), count);
                });
            },
        );
    }
    group.finish();
}

criterion_group!(
    benches,
    bench_list_issues,
    bench_list_patches,
    bench_walk_events,
    bench_issue_from_ref,
    bench_patch_from_ref,
);
criterion_main!(benches);