pub fn open(
host: &str,
) -> Result<(SshChild, BufWriter<ChildStdin>, BufReader<ChildStdout>)>Examples found in repository?
examples/measure-roundtrips.rs (line 43)
36async fn main() -> Result<()> {
37 let mut args = std::env::args().skip(1);
38 let host = args
39 .next()
40 .context("usage: measure-roundtrips <ssh-host> [remote-dir]")?;
41 let dir = args.next().unwrap_or_else(|| "/usr/include".to_string());
42
43 let (_child, w, r) = transport::open(&host)?;
44 let mut s = Sftp::handshake(w, r).await?;
45 println!("host {host} sftp v{}", s.version());
46
47 let tau = measure_tau(&mut s).await?;
48 println!("tau (one round trip) = {:.1} ms", ms(tau));
49
50 let entries = list(&mut s, &dir).await?;
51 let files: Vec<String> = entries
52 .iter()
53 .filter(|(name, a)| {
54 !a.is_dir() && !a.is_symlink() && a.size.unwrap_or(0) > 0 && name != "." && name != ".."
55 })
56 .map(|(name, _)| format!("{}/{}", dir.trim_end_matches('/'), name))
57 .collect();
58 ensure!(
59 !files.is_empty(),
60 "no regular non-empty files in {dir}; pass a different remote-dir"
61 );
62 println!(
63 "{} entries in {dir}, {} usable files",
64 entries.len(),
65 files.len()
66 );
67 println!();
68
69 let mut largest = 0usize;
70 let mut largest_open = 0.0f64;
71 let mut largest_read = 0.0f64;
72 let mut prev_read = 0.0f64;
73 let mut last_read = 0.0f64;
74 for n in BATCH_SIZES {
75 if n > files.len() {
76 continue;
77 }
78 let batch = &files[..n];
79 let (t_open, handles) = batch_open(&mut s, batch).await?;
80 let (t_read, bytes) = batch_read(&mut s, &handles).await?;
81 batch_close(&mut s, &handles).await?;
82
83 let open_tau = t_open.as_secs_f64() / tau.as_secs_f64();
84 let read_tau = t_read.as_secs_f64() / tau.as_secs_f64();
85 println!(
86 "n={n:<3} open {:>7.1} ms ({open_tau:>5.2} tau) read {:>7.1} ms ({read_tau:>5.2} tau) {bytes} B",
87 ms(t_open),
88 ms(t_read)
89 );
90 largest = n;
91 largest_open = open_tau;
92 largest_read = read_tau;
93 if n > 1 {
94 prev_read = last_read;
95 }
96 last_read = read_tau;
97 }
98
99 println!();
100 ensure!(
101 largest > 1,
102 "only one usable file; cannot distinguish pipelined from serial"
103 );
104 // The verdict rests on opens alone. An open carries a path and nothing else, so
105 // its cost is round trips and only round trips. A read also carries the file,
106 // and under an injected per-packet delay the transfer dominates: 400 KB reads
107 // as 15 tau however few round trips fetched it. Judging on reads made this
108 // check fail on how much data the chosen directory happens to hold, which is
109 // a property of the machine rather than of the code -- exactly the kind of
110 // flaky gate that teaches people to ignore a red check.
111 //
112 // Reads still say something, just not in absolute terms: if doubling the batch
113 // does not double the time, the round trips did not scale either.
114 if prev_read > 0.0 {
115 let growth = largest_read / prev_read;
116 println!(
117 "reads {largest_read:.2} tau at n={largest}, {growth:.2}x the previous batch (serial would be about 2x)"
118 );
119 }
120
121 let serial = largest as f64;
122 if largest_open < serial / PIPELINE_MARGIN {
123 println!(
124 "VERDICT pipelined: opens cost {largest_open:.2} tau at n={largest} (serial would cost about {serial:.0})"
125 );
126 Ok(())
127 } else {
128 bail!(
129 "VERDICT serial: opens cost {largest_open:.2} tau at n={largest}, near the serial cost {serial:.0}. Invariant 1 (O(1) round trips per page) is not reachable over this transport."
130 )
131 }
132}