1use choir_merge::{MergeOutcome, Pipeline};
18use choir_oplog::MemLog;
19use choir_sequencer::Sequencer;
20use std::path::Path;
21use std::time::{Duration, Instant};
22
23const WORKSPACES: usize = 16;
24const FILES_IN_BASE: usize = 500;
25
26#[cfg(target_os = "macos")]
27fn cow_clone(src: &Path, dst: &Path) -> std::io::Result<()> {
28 use std::os::raw::{c_char, c_int, c_uint};
29 extern "C" {
30 fn clonefile(src: *const c_char, dst: *const c_char, flags: c_uint) -> c_int;
31 }
32 let s = std::ffi::CString::new(src.as_os_str().as_encoded_bytes()).unwrap();
33 let d = std::ffi::CString::new(dst.as_os_str().as_encoded_bytes()).unwrap();
34 if unsafe { clonefile(s.as_ptr(), d.as_ptr(), 0) } == 0 {
35 Ok(())
36 } else {
37 Err(std::io::Error::last_os_error())
38 }
39}
40
41#[cfg(not(target_os = "macos"))]
42fn cow_clone(src: &Path, dst: &Path) -> std::io::Result<()> {
43 fn copy_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
45 std::fs::create_dir_all(dst)?;
46 for e in std::fs::read_dir(src)? {
47 let e = e?;
48 let to = dst.join(e.file_name());
49 if e.file_type()?.is_dir() {
50 copy_dir(&e.path(), &to)?;
51 } else {
52 std::fs::copy(e.path(), to)?;
53 }
54 }
55 Ok(())
56 }
57 copy_dir(src, dst)
58}
59
60fn percentile(sorted: &[Duration], q: f64) -> Duration {
61 sorted[((sorted.len() - 1) as f64 * q) as usize]
62}
63
64fn main() {
65 let work = std::env::temp_dir().join(format!("choir-spike-{}", std::process::id()));
66 let base = work.join("base");
67 std::fs::create_dir_all(&base).unwrap();
68
69 let mut shared = Vec::new();
73 for i in 0..(WORKSPACES * 4) {
74 shared.push(format!("region {i}"));
75 }
76 let shared_base = shared.join("\n") + "\n";
77 std::fs::write(base.join("shared.txt"), &shared_base).unwrap();
78 let hot_base = "hot line\n".to_string();
79 std::fs::write(base.join("hot.txt"), &hot_base).unwrap();
80 for i in 0..FILES_IN_BASE {
81 let d = base.join(format!("dir{}", i % 50));
82 std::fs::create_dir_all(&d).unwrap();
83 std::fs::write(d.join(format!("f{i}.txt")), format!("file {i}\n")).unwrap();
84 }
85
86 let mut provision_times = Vec::new();
88 let mut ws_paths = Vec::new();
89 for w in 0..WORKSPACES {
90 let dst = work.join(format!("ws{w}"));
91 let t = Instant::now();
92 cow_clone(&base, &dst).expect("workspace provisioning");
93 provision_times.push(t.elapsed());
94 ws_paths.push(dst);
95 }
96 provision_times.sort();
97
98 let sequencer = Sequencer::spawn(Box::new(MemLog::new()));
102 let mut threads = Vec::new();
103 for (w, path) in ws_paths.iter().enumerate() {
104 let handle = sequencer.handle();
105 let path = path.clone();
106 let base_text = shared_base.clone();
107 threads.push(std::thread::spawn(move || {
108 let mut lines: Vec<String> = base_text.lines().map(String::from).collect();
109 lines[w * 4 + 2] = format!("region {} edited by ws{w}", w * 4 + 2);
110 let shared_edit = lines.join("\n") + "\n";
111 let hot_edit = format!("hot line edited by ws{w}\n");
112 std::fs::write(path.join("shared.txt"), &shared_edit).unwrap();
113 std::fs::write(path.join("hot.txt"), &hot_edit).unwrap();
114 let accepted = handle.submit(&format!("ws{w}"), shared_edit.clone().into_bytes());
115 (shared_edit, hot_edit, accepted.decision_latency)
116 }));
117 }
118 let mut shared_edits = Vec::new();
119 let mut hot_edits = Vec::new();
120 let mut latencies = Vec::new();
121 for t in threads {
122 let (shared_edit, hot_edit, lat) = t.join().unwrap();
123 shared_edits.push(shared_edit);
124 hot_edits.push(hot_edit);
125 latencies.push(lat);
126 }
127 latencies.sort();
128 let log = sequencer.shutdown();
129 assert_eq!(log.len(), WORKSPACES as u64, "zero op loss");
130
131 let pipeline = Pipeline::default_v1();
133 let mut acc = shared_edits[0].clone();
134 let mut clean = 0usize;
135 let mut wrong_conflicts = 0usize;
136 for edit in &shared_edits[1..] {
137 match pipeline.merge(&shared_base, &acc, edit).outcome {
138 MergeOutcome::Resolved(merged) => {
139 clean += 1;
140 acc = merged;
141 }
142 MergeOutcome::Conflict { .. } => wrong_conflicts += 1,
143 MergeOutcome::Unavailable(_) => unreachable!(),
144 }
145 }
146 let regions_merged = (0..WORKSPACES)
147 .filter(|w| acc.contains(&format!("edited by ws{w}")))
148 .count();
149
150 let mut hot_conflicts = 0usize;
153 let mut silent_hot_merges = 0usize;
154 for pair in hot_edits.windows(2) {
155 match pipeline.merge(&hot_base, &pair[0], &pair[1]).outcome {
156 MergeOutcome::Conflict { .. } => hot_conflicts += 1,
157 MergeOutcome::Resolved(_) => silent_hot_merges += 1,
158 MergeOutcome::Unavailable(_) => unreachable!(),
159 }
160 }
161
162 std::fs::remove_dir_all(&work).ok();
163
164 println!("== Phase-0 integrated gate report ==");
165 println!(
166 "workspaces: {WORKSPACES} (gate: >10) | base tree: {FILES_IN_BASE} files + shared.txt"
167 );
168 println!(
169 "provisioning (CoW clone): p50={:?} p90={:?} max={:?} (target p50 < 50 ms warm)",
170 percentile(&provision_times, 0.5),
171 percentile(&provision_times, 0.9),
172 provision_times.last().unwrap()
173 );
174 println!(
175 "sequencer decision latency: p50={:?} p99={:?} (gate: < 100 ms, CI excluded)",
176 percentile(&latencies, 0.5),
177 percentile(&latencies, 0.99)
178 );
179 println!(
180 "disjoint-region fold: {clean}/{} clean merges, {regions_merged}/{WORKSPACES} regions present, {wrong_conflicts} spurious conflicts",
181 WORKSPACES - 1
182 );
183 println!(
184 "same-line (hot) merges: {hot_conflicts}/{} first-class conflicts, {silent_hot_merges} silent picks (must be 0)",
185 WORKSPACES - 1
186 );
187
188 let pass = WORKSPACES > 10
189 && percentile(&provision_times, 0.5) < Duration::from_millis(50)
190 && percentile(&latencies, 0.99) < Duration::from_millis(100)
191 && wrong_conflicts == 0
192 && regions_merged == WORKSPACES
193 && silent_hot_merges == 0;
194 println!(
195 "GATE ({} dev-machine): {}",
196 std::env::consts::OS,
197 if pass { "PASS" } else { "FAIL" }
198 );
199 std::process::exit(if pass { 0 } else { 1 });
200}