This repository has no description
1use std::time::Duration;
2
3use divan::Bencher;
4use divan::counter::{BytesCount, ItemsCount};
5use knot_bench::{ChurnCount, CommitCount, HistorySpec, PathCount, build_history};
6use knot_git::{Filter, Haves, PackBudget, Repo, Wants};
7use knot_pack::{
8 HaveOids, PackLimits, ReceiveCommand, ReceiveGuard, RefDecision, WantOids, count_expanded,
9 local_pack, receive_pack_guarded, upload_archive, write_expanded, write_pack,
10};
11use knot_types::{ObjectCount, Oid};
12
13#[global_allocator]
14static ALLOC: divan::AllocProfiler = divan::AllocProfiler::system();
15
16const GRADES: &[u32] = &[64, 256, 1024];
17const CAP: u64 = 512 * 1024 * 1024;
18
19fn spec_for(commits: u32) -> HistorySpec {
20 HistorySpec {
21 commits: CommitCount::new(commits),
22 paths: PathCount::new(commits.max(64)),
23 churn: ChurnCount::new(8),
24 }
25}
26
27fn pkt(payload: &[u8]) -> Vec<u8> {
28 let mut out = format!("{:04x}", payload.len() + 4).into_bytes();
29 out.extend_from_slice(payload);
30 out
31}
32
33struct AllowAll;
34
35impl ReceiveGuard for AllowAll {
36 fn authorize(&self, _staged: &Repo, commands: &[ReceiveCommand]) -> Vec<RefDecision> {
37 commands.iter().map(|_| RefDecision::Allow).collect()
38 }
39}
40
41fn main() {
42 divan::main();
43}
44
45#[divan::bench(args = GRADES)]
46fn select(bencher: Bencher, commits: u32) {
47 let history = build_history(spec_for(commits));
48 let tips = history.tips();
49 let count = history
50 .repo()
51 .select_pack_objects_filtered(
52 Wants::new(&tips),
53 Haves::new(&[]),
54 Filter::None,
55 PackBudget::unbounded(),
56 )
57 .unwrap()
58 .send
59 .len();
60 bencher.counter(ItemsCount::new(count)).bench_local(|| {
61 history
62 .repo()
63 .select_pack_objects_filtered(
64 Wants::new(&tips),
65 Haves::new(&[]),
66 Filter::None,
67 PackBudget::unbounded(),
68 )
69 .unwrap()
70 });
71}
72
73#[divan::bench(args = GRADES)]
74fn clone(bencher: Bencher, commits: u32) {
75 let history = build_history(spec_for(commits));
76 let wants = WantOids::new(history.tips());
77 let no_haves = HaveOids::default();
78 let bytes = local_pack(history.repo(), &wants, &no_haves, CAP)
79 .unwrap()
80 .len();
81 bencher
82 .counter(BytesCount::new(bytes))
83 .bench_local(|| local_pack(history.repo(), &wants, &no_haves, CAP).unwrap());
84}
85
86#[divan::bench(args = GRADES)]
87fn full_clone_manual(bencher: Bencher, commits: u32) {
88 let history = build_history(spec_for(commits));
89 let tips = history.tips();
90 let dir = history.repo().objects_dir();
91 let count = history
92 .repo()
93 .select_pack_objects_filtered(
94 Wants::new(&tips),
95 Haves::new(&[]),
96 Filter::None,
97 PackBudget::unbounded(),
98 )
99 .unwrap()
100 .send
101 .len();
102 bencher.counter(ItemsCount::new(count)).bench_local(|| {
103 let send = history
104 .repo()
105 .select_pack_objects_filtered(
106 Wants::new(&tips),
107 Haves::new(&[]),
108 Filter::None,
109 PackBudget::unbounded(),
110 )
111 .unwrap()
112 .send;
113 write_pack(
114 &dir,
115 send,
116 None,
117 &mut std::io::sink(),
118 history.repo().object_format().kind(),
119 )
120 .unwrap();
121 });
122}
123
124#[divan::bench(args = GRADES)]
125fn full_clone_expanding(bencher: Bencher, commits: u32) {
126 let history = build_history(spec_for(commits));
127 let tips = history.tips();
128 let dir = history.repo().objects_dir();
129 let far = Duration::from_secs(3600);
130 let roots = history
131 .repo()
132 .clone_roots(&tips, PackBudget::unbounded())
133 .unwrap();
134 let kind = history.repo().object_format().kind();
135 let count = count_expanded(&dir, roots, ObjectCount::new(usize::MAX), far, kind)
136 .unwrap()
137 .len();
138 bencher.counter(ItemsCount::new(count)).bench_local(|| {
139 let roots = history
140 .repo()
141 .clone_roots(&tips, PackBudget::unbounded())
142 .unwrap();
143 let pack = count_expanded(&dir, roots, ObjectCount::new(usize::MAX), far, kind).unwrap();
144 write_expanded(pack, &mut std::io::sink()).unwrap();
145 });
146}
147
148#[divan::bench(args = GRADES)]
149fn fetch(bencher: Bencher, commits: u32) {
150 let history = build_history(spec_for(commits));
151 let tips = history.tips();
152 let walk = history
153 .repo()
154 .rev_walk(Wants::new(&tips), Haves::new(&[]))
155 .unwrap();
156 let haves = HaveOids::new(vec![walk[walk.len() / 2]]);
157 let wants = WantOids::new(tips);
158 let bytes = local_pack(history.repo(), &wants, &haves, CAP)
159 .unwrap()
160 .len();
161 bencher
162 .counter(BytesCount::new(bytes))
163 .bench_local(|| local_pack(history.repo(), &wants, &haves, CAP).unwrap());
164}
165
166#[divan::bench(args = GRADES)]
167fn push(bencher: Bencher, commits: u32) {
168 let history = build_history(spec_for(commits));
169 let tips = history.tips();
170 let pack = local_pack(
171 history.repo(),
172 &WantOids::new(tips.clone()),
173 &HaveOids::default(),
174 CAP,
175 )
176 .unwrap();
177 let walk = history
178 .repo()
179 .rev_walk(Wants::new(&tips), Haves::new(&[]))
180 .unwrap();
181 let stride = walk.len().max(1) / 8 + 1;
182 let branch_tips: Vec<Oid> = walk.iter().step_by(stride).copied().collect();
183 let request = build_receive_request(&branch_tips, &pack);
184 let limits = PackLimits::default();
185 bencher
186 .counter(ItemsCount::new(branch_tips.len()))
187 .with_inputs(fresh_target)
188 .bench_local_values(|target| {
189 receive_pack_guarded(
190 target.repo(),
191 &request,
192 &limits,
193 &AllowAll,
194 &|_| {},
195 &knot_pack::default_catalog().reject,
196 )
197 .unwrap()
198 });
199}
200
201#[divan::bench(args = GRADES)]
202fn archive(bencher: Bencher, commits: u32) {
203 let history = build_history(spec_for(commits));
204 let request = build_archive_request(history.tip());
205 let bytes = upload_archive(history.repo(), &request, knot_git::ArchiveLimit::default())
206 .unwrap()
207 .len();
208 bencher.counter(BytesCount::new(bytes)).bench_local(|| {
209 upload_archive(history.repo(), &request, knot_git::ArchiveLimit::default()).unwrap()
210 });
211}
212
213struct FreshTarget {
214 _dir: tempfile::TempDir,
215 repo: Repo,
216}
217
218impl FreshTarget {
219 fn repo(&self) -> &Repo {
220 &self.repo
221 }
222}
223
224fn fresh_target() -> FreshTarget {
225 let dir = tempfile::tempdir().expect("tempdir");
226 let repo = Repo::create(dir.path().join("target.git")).expect("create target");
227 FreshTarget { _dir: dir, repo }
228}
229
230fn build_receive_request(branch_tips: &[Oid], pack: &[u8]) -> Vec<u8> {
231 let null = Oid::null().to_hex();
232 let commands: Vec<Vec<u8>> = branch_tips
233 .iter()
234 .enumerate()
235 .map(|(index, tip)| {
236 let line = format!("{null} {} refs/heads/b{index}", tip.to_hex());
237 match index {
238 0 => {
239 let mut payload = line.into_bytes();
240 payload.push(0);
241 payload.extend_from_slice(b"report-status\n");
242 pkt(&payload)
243 }
244 _ => pkt(format!("{line}\n").as_bytes()),
245 }
246 })
247 .collect();
248 let mut request: Vec<u8> = commands.concat();
249 request.extend_from_slice(b"0000");
250 request.extend_from_slice(pack);
251 request
252}
253
254fn build_archive_request(tip: Oid) -> Vec<u8> {
255 let mut request = pkt(b"argument --format=tar");
256 request.extend_from_slice(&pkt(format!("argument {}", tip.to_hex()).as_bytes()));
257 request.extend_from_slice(b"0000");
258 request
259}