This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-bench / src / fixtures.rs
18 kB 657 lines
1use std::path::{Path, PathBuf}; 2 3use knot_cob::{CobError, CobHome, CobId, CobStore}; 4use knot_cobs::{ 5 CollaboratorsChange, Grant, MembersChange, MembersCob, Registration, RegistryChange, 6 RepoRegistryCob, add_member, register_repo, 7}; 8use knot_git::{ 9 EntryKind, Identity, Layout, NewCommit, RefUpdate, Repo, StagedAction, StagedChange, 10}; 11use knot_index::Index; 12use knot_runtime::{K256Signer, SeededEntropy}; 13use knot_types::{ 14 AccountDid, AuthorName, Email, KnotId, Oid, OwnerDid, RefName, RepoDid, RepoName, RepoRkey, 15 UnixSeconds, 16}; 17use tempfile::TempDir; 18 19#[derive(Debug, Clone, Copy, PartialEq, Eq)] 20pub struct CommitCount(u32); 21 22#[derive(Debug, Clone, Copy, PartialEq, Eq)] 23pub struct PathCount(u32); 24 25#[derive(Debug, Clone, Copy, PartialEq, Eq)] 26pub struct ChurnCount(u32); 27 28#[derive(Debug, Clone, Copy, PartialEq, Eq)] 29pub struct ChangeCount(u32); 30 31#[derive(Debug, Clone, Copy, PartialEq, Eq)] 32pub struct RepoCount(u64); 33 34#[derive(Debug, Clone, Copy, PartialEq, Eq)] 35pub struct RefCount(u32); 36 37#[derive(Debug, Clone, Copy, PartialEq, Eq)] 38pub struct RosterCount(u32); 39 40impl CommitCount { 41 pub fn new(value: u32) -> Self { 42 Self(value.max(1)) 43 } 44 fn get(self) -> u32 { 45 self.0 46 } 47} 48 49impl PathCount { 50 pub fn new(value: u32) -> Self { 51 Self(value.max(1)) 52 } 53 fn get(self) -> u32 { 54 self.0 55 } 56} 57 58impl ChurnCount { 59 pub fn new(value: u32) -> Self { 60 Self(value.max(1)) 61 } 62 fn get(self) -> u32 { 63 self.0 64 } 65} 66 67impl ChangeCount { 68 pub fn new(value: u32) -> Self { 69 Self(value.max(1)) 70 } 71 fn get(self) -> u32 { 72 self.0 73 } 74} 75 76impl RepoCount { 77 pub fn new(value: u64) -> Self { 78 Self(value.max(1)) 79 } 80 pub fn get(self) -> u64 { 81 self.0 82 } 83} 84 85impl RefCount { 86 pub fn new(value: u32) -> Self { 87 Self(value.max(1)) 88 } 89 fn get(self) -> u32 { 90 self.0 91 } 92} 93 94impl RosterCount { 95 pub fn new(value: u32) -> Self { 96 Self(value.max(1)) 97 } 98 fn get(self) -> u32 { 99 self.0 100 } 101} 102 103#[derive(Debug, Clone, Copy)] 104pub struct HistorySpec { 105 pub commits: CommitCount, 106 pub paths: PathCount, 107 pub churn: ChurnCount, 108} 109 110const CONTENT_BYTES: usize = 128; 111const GENESIS_SECONDS: i64 = 1_700_000_000; 112 113fn splitmix(seed: u64) -> u64 { 114 let z = seed.wrapping_add(0x9e37_79b9_7f4a_7c15); 115 let z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); 116 let z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); 117 z ^ (z >> 31) 118} 119 120knot_types::scalar_newtype! { 121 struct PathIndex(u32); 122 struct Revision(u32); 123} 124 125fn blob_content(path_index: PathIndex, revision: Revision) -> Vec<u8> { 126 let seed = splitmix(u64::from(path_index.get()) ^ u64::from(revision.get()).rotate_left(32)); 127 (0..CONTENT_BYTES) 128 .scan(seed, |state, _| { 129 *state = splitmix(*state); 130 Some((*state & 0xff) as u8) 131 }) 132 .collect() 133} 134 135fn path_at(path_index: PathIndex) -> knot_types::RepoPath { 136 let path_index = path_index.get(); 137 knot_types::RepoPath::new(format!( 138 "src/m{:04}/f{:06}.dat", 139 path_index / 256, 140 path_index 141 )) 142 .expect("generated fixture path is well-formed") 143} 144 145fn identity(revision: Revision) -> Identity { 146 Identity { 147 name: AuthorName::new("nel"), 148 email: Email::new("nel@oyster.cafe"), 149 time: UnixSeconds::new(GENESIS_SECONDS + i64::from(revision.get())), 150 offset_seconds: 0, 151 } 152} 153 154fn put(path_index: PathIndex, revision: Revision) -> StagedChange { 155 StagedChange { 156 path: path_at(path_index), 157 action: StagedAction::Put { 158 content: blob_content(path_index, revision), 159 kind: EntryKind::Blob, 160 }, 161 } 162} 163 164fn churn_indices(revision: Revision, spec: HistorySpec) -> impl Iterator<Item = PathIndex> { 165 let span = u64::from(spec.paths.get()); 166 let base = u64::from(revision.get()).wrapping_mul(u64::from(spec.churn.get())); 167 (0..spec.churn.get()) 168 .map(move |offset| PathIndex::new(((base + u64::from(offset)) % span) as u32)) 169} 170 171pub struct BuiltHistory { 172 _dir: TempDir, 173 repo: Repo, 174 tip: Oid, 175} 176 177impl BuiltHistory { 178 pub fn repo(&self) -> &Repo { 179 &self.repo 180 } 181 pub fn tip(&self) -> Oid { 182 self.tip 183 } 184 pub fn tips(&self) -> Vec<Oid> { 185 vec![self.tip] 186 } 187} 188 189pub fn build_history(spec: HistorySpec) -> BuiltHistory { 190 let dir = tempfile::tempdir().expect("tempdir"); 191 let repo = Repo::create(dir.path().join("repo.git")).expect("create repo"); 192 let tip = write_history(&repo, spec); 193 BuiltHistory { 194 _dir: dir, 195 repo, 196 tip, 197 } 198} 199 200pub fn write_history(repo: &Repo, spec: HistorySpec) -> Oid { 201 let empty_tree = Oid::from(repo.git().empty_tree().id().detach()); 202 203 let genesis_changes: Vec<StagedChange> = (0..spec.paths.get()) 204 .map(|index| put(PathIndex::new(index), Revision::new(0))) 205 .collect(); 206 let genesis_tree = repo 207 .write_staged_tree(empty_tree, &genesis_changes) 208 .expect("genesis tree"); 209 let genesis_commit = repo 210 .write_commit(&NewCommit { 211 tree: genesis_tree, 212 parents: Vec::new(), 213 author: identity(Revision::new(0)), 214 committer: identity(Revision::new(0)), 215 message: "genesis".to_string(), 216 extra_headers: Vec::new(), 217 }) 218 .expect("genesis commit"); 219 220 let (_, tip) = (1..spec.commits.get()) 221 .try_fold( 222 (genesis_tree, genesis_commit), 223 |(prev_tree, prev_commit), revision| -> Result<(Oid, Oid), knot_git::GitError> { 224 let revision = Revision::new(revision); 225 let changes: Vec<StagedChange> = churn_indices(revision, spec) 226 .map(|index| put(index, revision)) 227 .collect(); 228 let tree = repo.write_staged_tree(prev_tree, &changes)?; 229 let commit = repo.write_commit(&NewCommit { 230 tree, 231 parents: vec![prev_commit], 232 author: identity(revision), 233 committer: identity(revision), 234 message: format!("revision {}", revision.get()), 235 extra_headers: Vec::new(), 236 })?; 237 Ok((tree, commit)) 238 }, 239 ) 240 .expect("commit chain"); 241 242 let main = RefName::new("refs/heads/main").expect("main ref name"); 243 repo.update_ref(&RefUpdate::Create { 244 name: main.clone(), 245 new: tip, 246 }) 247 .expect("create main"); 248 repo.set_head(&main).expect("set head"); 249 250 tip 251} 252 253pub struct BuiltRefs { 254 _dir: TempDir, 255 repo: Repo, 256} 257 258impl BuiltRefs { 259 pub fn repo(&self) -> &Repo { 260 &self.repo 261 } 262} 263 264pub fn build_many_refs(count: RefCount) -> BuiltRefs { 265 let dir = tempfile::tempdir().expect("tempdir"); 266 let repo = Repo::create(dir.path().join("repo.git")).expect("create repo"); 267 let tip = write_history( 268 &repo, 269 HistorySpec { 270 commits: CommitCount::new(1), 271 paths: PathCount::new(1), 272 churn: ChurnCount::new(1), 273 }, 274 ); 275 (0..count.get()).for_each(|index| { 276 let name = RefName::new(format!("refs/heads/branch{index:06}")).expect("ref name"); 277 repo.update_ref(&RefUpdate::Create { name, new: tip }) 278 .expect("create ref"); 279 }); 280 BuiltRefs { _dir: dir, repo } 281} 282 283fn synthetic_account(seed: u64) -> AccountDid { 284 AccountDid::new(format!("did:plc:acct{seed:012}")).expect("account did") 285} 286 287fn synthetic_repo_did(index: u64) -> RepoDid { 288 RepoDid::new(format!("did:plc:repo{index:012}")).expect("repo did") 289} 290 291fn registry_owner() -> OwnerDid { 292 OwnerDid::new("did:plc:nel").expect("owner did") 293} 294 295fn knot_home() -> CobHome { 296 CobHome::from(&KnotId::new("did:web:knot.nel.pet").expect("knot did")) 297} 298 299fn registration(index: u64) -> Registration { 300 let rkey = format!("repo{index:012}"); 301 Registration { 302 owner: registry_owner(), 303 rkey: RepoRkey::new(&rkey).expect("rkey"), 304 name: RepoName::new(&rkey).expect("repo name"), 305 repo: synthetic_repo_did(index), 306 created_at: UnixSeconds::new(GENESIS_SECONDS + index as i64), 307 } 308} 309 310fn grant(seed: u64) -> Grant { 311 Grant { 312 subject: synthetic_account(seed), 313 added_by: synthetic_account(0), 314 created_at: UnixSeconds::new(GENESIS_SECONDS), 315 } 316} 317 318pub struct BuiltRegistry { 319 _dir: TempDir, 320 meta_path: PathBuf, 321 layout: Layout, 322 dids: Vec<RepoDid>, 323} 324 325impl BuiltRegistry { 326 pub fn index(&self) -> Index { 327 Index::new(&self.meta_path, self.layout.clone()) 328 } 329 pub fn dids(&self) -> &[RepoDid] { 330 &self.dids 331 } 332 pub fn alias(&self, index: u64) -> (OwnerDid, RepoRkey) { 333 let reg = registration(index); 334 (reg.owner, reg.rkey) 335 } 336} 337 338fn seed_members(meta_path: &Path, signer: &dyn knot_runtime::Signer) { 339 let meta = Repo::open(meta_path).expect("open meta"); 340 let store = CobStore::new(&meta); 341 store 342 .create( 343 &knot_home(), 344 &knot_cobs::MembersChange::Add(grant(1)), 345 signer, 346 UnixSeconds::new(GENESIS_SECONDS), 347 ) 348 .expect("seed members"); 349} 350 351pub fn build_registry(repos: RepoCount) -> BuiltRegistry { 352 let dir = tempfile::tempdir().expect("tempdir"); 353 let meta_path = dir.path().join("meta.git"); 354 Repo::create(&meta_path).expect("create meta"); 355 let layout = Layout::new(dir.path().join("repos")); 356 let signer = K256Signer::generate(&SeededEntropy::new(7)); 357 358 seed_members(&meta_path, &signer); 359 360 let meta = Repo::open(&meta_path).expect("open meta"); 361 let store = CobStore::new(&meta); 362 let registry_object = store 363 .create( 364 &knot_home(), 365 &RegistryChange::Register(registration(0)), 366 &signer, 367 UnixSeconds::new(GENESIS_SECONDS), 368 ) 369 .expect("create registry") 370 .object; 371 (1..repos.get()).for_each(|index| { 372 store 373 .update( 374 &knot_home(), 375 registry_object, 376 &RegistryChange::Register(registration(index)), 377 &signer, 378 UnixSeconds::new(GENESIS_SECONDS + index as i64), 379 ) 380 .expect("register repo"); 381 }); 382 383 let dids: Vec<RepoDid> = (0..repos.get()).map(synthetic_repo_did).collect(); 384 385 dids.iter().enumerate().for_each(|(index, did)| { 386 let git = layout.create(did).expect("create repo dir"); 387 let collab = CobStore::new(&git); 388 collab 389 .create( 390 &CobHome::from(did), 391 &CollaboratorsChange::Add(grant(index as u64 + 2)), 392 &signer, 393 UnixSeconds::new(GENESIS_SECONDS), 394 ) 395 .expect("seed collaborator"); 396 }); 397 398 BuiltRegistry { 399 _dir: dir, 400 meta_path, 401 layout, 402 dids, 403 } 404} 405 406pub struct BuiltRoster { 407 _dir: TempDir, 408 meta_path: PathBuf, 409 layout: Layout, 410 repo: RepoDid, 411} 412 413impl BuiltRoster { 414 pub fn index(&self) -> Index { 415 Index::new(&self.meta_path, self.layout.clone()) 416 } 417 pub fn repo(&self) -> &RepoDid { 418 &self.repo 419 } 420} 421 422pub fn build_collaborator_roster(collaborators: RosterCount) -> BuiltRoster { 423 let dir = tempfile::tempdir().expect("tempdir"); 424 let meta_path = dir.path().join("meta.git"); 425 Repo::create(&meta_path).expect("create meta"); 426 let layout = Layout::new(dir.path().join("repos")); 427 let signer = K256Signer::generate(&SeededEntropy::new(9)); 428 429 seed_members(&meta_path, &signer); 430 431 let repo = synthetic_repo_did(0); 432 let git = layout.create(&repo).expect("create repo dir"); 433 let store = CobStore::new(&git); 434 let home = CobHome::from(&repo); 435 let object = store 436 .create( 437 &home, 438 &CollaboratorsChange::Add(grant(2)), 439 &signer, 440 UnixSeconds::new(GENESIS_SECONDS), 441 ) 442 .expect("seed collaborator") 443 .object; 444 (1..collaborators.get()).for_each(|index| { 445 store 446 .update( 447 &home, 448 object, 449 &CollaboratorsChange::Add(grant(u64::from(index) + 2)), 450 &signer, 451 UnixSeconds::new(GENESIS_SECONDS + i64::from(index)), 452 ) 453 .expect("add collaborator"); 454 }); 455 456 BuiltRoster { 457 _dir: dir, 458 meta_path, 459 layout, 460 repo, 461 } 462} 463 464pub struct BuiltRegistryWriter { 465 _dir: TempDir, 466 meta_path: PathBuf, 467 object: CobId, 468 signer: K256Signer, 469} 470 471impl BuiltRegistryWriter { 472 pub fn probe(&self) { 473 let meta = Repo::open(&self.meta_path).expect("reopen meta"); 474 let store = CobStore::new(&meta); 475 register_repo( 476 &store, 477 &knot_home(), 478 self.object, 479 registration(0), 480 &self.signer, 481 UnixSeconds::new(GENESIS_SECONDS), 482 ) 483 .expect("idempotent re-register folds registry"); 484 } 485 486 pub fn full_fold(&self) { 487 let meta = Repo::open(&self.meta_path).expect("reopen meta"); 488 let store = CobStore::new(&meta); 489 store 490 .get::<RepoRegistryCob>(self.object) 491 .expect("full fold of registry"); 492 } 493} 494 495pub fn build_registry_checkpointed(repos: RepoCount) -> BuiltRegistryWriter { 496 let dir = tempfile::tempdir().expect("tempdir"); 497 let meta_path = dir.path().join("meta.git"); 498 Repo::create(&meta_path).expect("create meta"); 499 let signer = K256Signer::generate(&SeededEntropy::new(13)); 500 501 let meta = Repo::open(&meta_path).expect("open meta"); 502 let store = CobStore::new(&meta); 503 let object = store 504 .create( 505 &knot_home(), 506 &RegistryChange::Register(registration(0)), 507 &signer, 508 UnixSeconds::new(GENESIS_SECONDS), 509 ) 510 .expect("create registry") 511 .object; 512 (1..repos.get()).for_each(|index| { 513 register_repo( 514 &store, 515 &knot_home(), 516 object, 517 registration(index), 518 &signer, 519 UnixSeconds::new(GENESIS_SECONDS + index as i64), 520 ) 521 .expect("register repo"); 522 }); 523 524 BuiltRegistryWriter { 525 _dir: dir, 526 meta_path, 527 object, 528 signer, 529 } 530} 531 532pub struct BuiltMembersWriter { 533 _dir: TempDir, 534 meta_path: PathBuf, 535 object: CobId, 536 signer: K256Signer, 537} 538 539impl BuiltMembersWriter { 540 pub fn probe(&self) { 541 let meta = Repo::open(&self.meta_path).expect("reopen meta"); 542 let store = CobStore::new(&meta); 543 store 544 .update_maybe_checkpointed::<MembersCob, CobError>( 545 &knot_home(), 546 self.object, 547 &self.signer, 548 UnixSeconds::new(GENESIS_SECONDS), 549 |roster| { 550 Ok(if roster.contains(&grant(0).subject) { 551 None 552 } else { 553 Some(MembersChange::Add(grant(0))) 554 }) 555 }, 556 ) 557 .expect("idempotent re-add folds the bounded suffix"); 558 } 559 560 pub fn full_fold(&self) { 561 let meta = Repo::open(&self.meta_path).expect("reopen meta"); 562 let store = CobStore::new(&meta); 563 store 564 .get::<MembersCob>(self.object) 565 .expect("full fold of members"); 566 } 567} 568 569pub fn build_members_checkpointed(members: RosterCount) -> BuiltMembersWriter { 570 let dir = tempfile::tempdir().expect("tempdir"); 571 let meta_path = dir.path().join("meta.git"); 572 Repo::create(&meta_path).expect("create meta"); 573 let signer = K256Signer::generate(&SeededEntropy::new(17)); 574 575 let meta = Repo::open(&meta_path).expect("open meta"); 576 let store = CobStore::new(&meta); 577 let object = store 578 .create( 579 &knot_home(), 580 &MembersChange::Add(grant(0)), 581 &signer, 582 UnixSeconds::new(GENESIS_SECONDS), 583 ) 584 .expect("create members") 585 .object; 586 (1..members.get()).for_each(|index| { 587 add_member( 588 &store, 589 &knot_home(), 590 object, 591 grant(u64::from(index) + 1), 592 &signer, 593 UnixSeconds::new(GENESIS_SECONDS + i64::from(index)), 594 ) 595 .expect("add member"); 596 }); 597 598 BuiltMembersWriter { 599 _dir: dir, 600 meta_path, 601 object, 602 signer, 603 } 604} 605 606pub struct BuiltLinearCob { 607 _dir: TempDir, 608 repo: Repo, 609 object: CobId, 610} 611 612impl BuiltLinearCob { 613 pub fn fold(&self) -> usize { 614 let store = CobStore::new(&self.repo); 615 store 616 .get::<RepoRegistryCob>(self.object) 617 .expect("fold registry") 618 .state() 619 .len() 620 } 621} 622 623pub fn build_linear_cob(changes: ChangeCount) -> BuiltLinearCob { 624 let dir = tempfile::tempdir().expect("tempdir"); 625 let meta_path = dir.path().join("meta.git"); 626 Repo::create(&meta_path).expect("create meta"); 627 let signer = K256Signer::generate(&SeededEntropy::new(11)); 628 629 let meta = Repo::open(&meta_path).expect("open meta"); 630 let store = CobStore::new(&meta); 631 let object = store 632 .create( 633 &knot_home(), 634 &RegistryChange::Register(registration(0)), 635 &signer, 636 UnixSeconds::new(GENESIS_SECONDS), 637 ) 638 .expect("create registry") 639 .object; 640 (1..changes.get()).for_each(|index| { 641 store 642 .update( 643 &knot_home(), 644 object, 645 &RegistryChange::Register(registration(u64::from(index))), 646 &signer, 647 UnixSeconds::new(GENESIS_SECONDS + i64::from(index)), 648 ) 649 .expect("append change"); 650 }); 651 652 BuiltLinearCob { 653 _dir: dir, 654 repo: Repo::open(&meta_path).expect("reopen meta"), 655 object, 656 } 657}