chadtmiller.com/knotless

Git hosted in your atproto account, on any stock PDS, fast enough for agents.

git clone knotless://chadtmiller.com/knotless

needs the knotless git helper. set it up

171 lines ยท 5.6 KB Raw History
//! What social cards cost: each stage of drawing one and the PNG it makes,
//! a cached card against a new one, and how many a second the cores draw
//! together.
//!
//!   cargo run --release -p knotless-serve --example cards -- [--out <dir>]
//!
//! With `--out`, the cards are written there as PNGs, to look at.

use knotless_serve::card::{self, Card};
use std::time::{Duration, Instant};

/// Runs `f` until a second has passed, at least ten times; the median and
/// the slowest one in a hundred.
fn time<T>(mut f: impl FnMut() -> T) -> (Duration, Duration) {
    let mut runs = Vec::new();
    let start = Instant::now();
    while runs.len() < 10 || start.elapsed() < Duration::from_secs(1) {
        let t = Instant::now();
        std::hint::black_box(f());
        runs.push(t.elapsed());
    }
    runs.sort();
    (runs[runs.len() / 2], runs[runs.len() * 99 / 100])
}

fn ms(d: Duration) -> String {
    format!("{:.3}", d.as_secs_f64() * 1000.0)
}

fn cards() -> Vec<(&'static str, Card)> {
    vec![
        ("home", Card::Home),
        (
            "repo",
            Card::Repo {
                owner: "alice.bsky.social".into(),
                name: "knotless".into(),
                description: Some(
                    "Git repositories stored natively on atproto: no git server, nothing to install on the PDS."
                        .into(),
                ),
                pulls: 3,
                issues: 12,
            },
        ),
        (
            "issue",
            Card::Item {
                owner: "alice.bsky.social".into(),
                name: "knotless".into(),
                pull: false,
                state: "open".into(),
                title: "Cloning a repository with ten thousand refs takes a minute on a slow PDS"
                    .into(),
                author: "bob.example.com".into(),
                comments: 4,
            },
        ),
        (
            "pull",
            Card::Item {
                owner: "alice.bsky.social".into(),
                name: "knotless".into(),
                pull: true,
                state: "merged".into(),
                title: "Draw social cards in process".into(),
                author: "carol.test".into(),
                comments: 1,
            },
        ),
        (
            "emoji",
            Card::Item {
                owner: "alice.bsky.social".into(),
                name: "knotless".into(),
                pull: false,
                state: "closed".into(),
                title: "Titles with emoji ๐ŸŽ‰ draw in color: ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ป ๐Ÿ‡ฏ๐Ÿ‡ต โค๏ธ ๐Ÿฆ€ not as boxes".into(),
                author: "dana.test".into(),
                comments: 2,
            },
        ),
    ]
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let out = args
        .iter()
        .position(|a| a == "--out")
        .map(|i| std::path::PathBuf::from(&args[i + 1]));
    if let Some(dir) = &out {
        std::fs::create_dir_all(dir).unwrap();
    }
    // The fonts load on first use; that is not what is measured.
    card::render(&Card::Home.svg());

    println!(
        "{:<6} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8}",
        "card", "svg ms", "hash ms", "draw ms", "png ms", "all ms", "KB"
    );
    for (name, card) in cards() {
        let svg = card.svg();
        let (svg_ms, _) = time(|| card.svg());
        let (hash_ms, _) = time(|| card::version(&svg));
        let (draw_ms, _) = time(|| card::raster(&svg));
        let pixmap = card::raster(&svg);
        let (png_ms, _) = time(|| card::encode(&pixmap));
        let (all_ms, _) = time(|| card::render(&svg));
        let png = card::render(&svg);
        println!(
            "{name:<6} {:>8} {:>8} {:>8} {:>8} {:>8} {:>8.1}",
            ms(svg_ms),
            ms(hash_ms),
            ms(draw_ms),
            ms(png_ms),
            ms(all_ms),
            png.len() as f64 / 1024.0
        );
        if let Some(dir) = &out {
            std::fs::write(dir.join(format!("{name}.png")), png).unwrap();
        }
    }

    // A whole card as a request for it pays, new and cached; and the cores
    // drawing new ones together.
    let rt = tokio::runtime::Runtime::new().unwrap();
    let (_, issue) = &cards()[2];
    let svg = issue.svg();
    let (new, new99) = time(|| card::render(&svg));
    let cache = card::Cache::new(1 << 16);
    let version = card::version(&svg);
    rt.block_on(cache.png(&version, svg.clone()));
    let (hit, hit99) = time(|| {
        rt.block_on(async {
            let svg = issue.svg();
            let version = card::version(&svg);
            cache.png(&version, svg).await
        })
    });
    println!(
        "\nan issue card, new: {} ms (p99 {}); cached, with its SVG and hash: {} ms (p99 {})",
        ms(new),
        ms(new99),
        ms(hit),
        ms(hit99)
    );
    let n = 256;
    let start = Instant::now();
    rt.block_on(futures::future::join_all((0..n).map(|i| {
        let card = Card::Item {
            owner: "alice.bsky.social".into(),
            name: "knotless".into(),
            pull: i % 2 == 0,
            state: "open".into(),
            title: format!("Issue number {i}, one of many drawn at once"),
            author: "bob.example.com".into(),
            comments: i,
        };
        let svg = card.svg();
        let cache = &cache;
        async move { cache.png(&card::version(&svg), svg).await }
    })));
    let took = start.elapsed();
    let cores = std::thread::available_parallelism().map_or(1, |n| n.get());
    println!(
        "{n} new cards at once on {cores} cores: {} ms, {:.0} cards a second",
        took.as_millis(),
        n as f64 / took.as_secs_f64()
    );
}