1use std::io::{self, Cursor};
10use std::time::{Duration, Instant};
11
12use memchr::memchr;
13
14pub trait DurationExt {
15 fn write_human<W: io::Write>(&self, out: &mut W) -> io::Result<()>;
17
18 fn write_sortable<W: io::Write>(&self, out: &mut W) -> io::Result<()>;
21
22 fn human_string(&self) -> String {
24 let mut v = Cursor::new(Vec::with_capacity(16));
25 self.write_human(&mut v).unwrap();
26 String::from_utf8(v.into_inner()).unwrap()
27 }
28
29 fn sortable_string(&self) -> String {
31 let mut v = Cursor::new(Vec::with_capacity(16));
32 self.write_sortable(&mut v).unwrap();
33 String::from_utf8(v.into_inner()).unwrap()
34 }
35}
36
37impl DurationExt for Duration {
38 fn write_human<W: io::Write>(&self, out: &mut W) -> io::Result<()> {
39 let mut ts = self.as_secs();
40 let ns = self.subsec_nanos();
41
42 if ts > 0 {
43 let mut cs = (f64::from(ns) / 10_000_000_f64).round() as u8;
44 if cs == 100 {
45 ts += 1;
47 cs = 0;
48 }
49
50 let mut s = ts;
51
52 if ts >= 86400 {
53 write!(out, "{}d", s / 86400)?;
54 s %= 86400;
55 }
56
57 if ts >= 3600 {
58 write!(out, "{}h", s / 3600)?;
59 s %= 3600;
60 }
61
62 if ts >= 60 {
63 write!(out, "{}m", s / 60)?;
64 s %= 60;
65 }
66
67 write!(out, "{}.{:02}s", s, cs)?;
68 } else if ns > 100_000 {
69 write!(out, "{:.1}ms", f64::from(ns) / 1_000_000_f64)?;
70 } else if ns > 100 {
71 write!(out, "{:.1}μs", f64::from(ns) / 1_000_f64)?;
72 }
73 Ok(())
74 }
75
76 fn write_sortable<W: io::Write>(&self, out: &mut W) -> io::Result<()> {
77 let ts = self.as_secs();
78 let us = self.subsec_micros();
79
80 write!(
81 out,
82 "{:02}:{:02}:{:02}.{:06}",
83 ts / 3600,
84 (ts % 3600) / 60,
85 ts % 60,
86 us
87 )
88 }
89}
90
91pub type DurationFormatter = fn(&Duration) -> String;
92
93pub struct RtssWriter<W> {
95 inner: W,
96 formatter: DurationFormatter,
97 separator: char,
98 start: Instant,
99 last: Instant,
100 at_eol: bool,
101}
102
103impl<W: io::Write> RtssWriter<W> {
104 pub fn new(inner: W, formatter: DurationFormatter, separator: char, now: Instant) -> Self {
126 Self {
127 inner,
128 formatter,
129 separator,
130 start: now,
131 last: now,
132 at_eol: true,
133 }
134 }
135}
136
137impl<W: io::Write> io::Write for RtssWriter<W> {
138 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
141 let now = Instant::now();
142 let start_duration = (self.formatter)(&now.duration_since(self.start));
143 let line_duration = (self.formatter)(&now.duration_since(self.last));
144
145 let pfx_start = format!(
146 "{:>8} {:>8} {} ",
147 start_duration, line_duration, self.separator
148 );
149 let pfx_rest = format!(
150 "{:>8} {:>8} {} ",
151 start_duration,
152 (self.formatter)(&Duration::new(0, 0)),
153 self.separator
154 );
155
156 let mut pos: usize = 0;
157 let mut saw_eol = false;
158 let mut first = true;
159
160 let n = buf.len();
161
162 while pos < n {
163 if self.at_eol {
164 if first {
165 self.inner.write_all(pfx_start.as_bytes())?;
166 first = false;
167 } else {
168 self.inner.write_all(pfx_rest.as_bytes())?;
169 }
170 }
171
172 if let Some(newline) = memchr(b'\n', &buf[pos..n]) {
173 saw_eol = true;
174 self.at_eol = true;
175 self.inner.write_all(&buf[pos..=pos + newline])?;
176 pos += newline + 1;
177 } else {
178 self.at_eol = false;
179 self.inner.write_all(&buf[pos..n])?;
180 break;
181 }
182 }
183
184 self.inner.flush()?;
185
186 if saw_eol {
187 self.last = now;
188 }
189
190 Ok(n)
191 }
192
193 fn flush(&mut self) -> io::Result<()> {
194 self.inner.flush()
195 }
196}
197
198pub fn line_timing_copy<R: io::Read, W: io::Write>(
204 mut input: &mut R,
205 output: &mut W,
206 formatter: DurationFormatter,
207 separator: char,
208 start: Instant,
209) -> io::Result<u64> {
210 let output = io::BufWriter::new(output);
211 let mut output = RtssWriter::new(output, formatter, separator, start);
212
213 io::copy(&mut input, &mut output)
214}