chadtmiller.com/knotless
Git hosted in your atproto account, on any stock PDS, fast enough for agents.
git clone knotless://chadtmiller.com/knotlessneeds the knotless git helper. set it up
//! 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()
);
}