risingwave_object_store/benchmark/
trace.rs1use std::fs::OpenOptions;
16use std::io::{BufWriter, Write};
17use std::path::Path;
18use std::sync::Arc;
19use std::sync::atomic::{AtomicU64, Ordering};
20use std::sync::mpsc::{self, SyncSender, TrySendError};
21use std::thread::JoinHandle;
22use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
23
24use anyhow::{Context, Result};
25use serde::Serialize;
26
27use super::ReadMode;
28
29#[derive(Clone, Debug)]
31pub struct ReadTrace {
32 sender: SyncSender<Message>,
33 ids: Arc<AtomicU64>,
34 dropped: Arc<AtomicU64>,
35}
36
37#[derive(Debug)]
38enum Message {
39 Record(Record),
40 Finish,
41}
42
43#[derive(Debug, Serialize)]
44pub(super) struct Record {
45 pub request_id: u64,
46 pub event: &'static str,
47 pub started_unix_us: u128,
48 pub elapsed_us: u128,
49 pub offset: usize,
50 pub bytes: usize,
51 pub status: &'static str,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub request: Option<RequestInfo>,
54}
55
56#[derive(Debug, Serialize)]
57pub(super) struct RequestInfo {
58 pub phase: String,
59 pub key: String,
60 pub reader_id: usize,
61 pub mode: ReadMode,
62 pub range_start: usize,
63 pub range_length: usize,
64}
65
66#[derive(Debug, Serialize)]
67pub struct TraceSummary {
68 pub records_written: u64,
69 pub records_dropped: u64,
70}
71
72pub struct TraceWriter {
75 trace: ReadTrace,
76 worker: Option<JoinHandle<Result<u64>>>,
77}
78
79impl TraceWriter {
80 pub fn create(path: &Path) -> Result<Self> {
82 let file = OpenOptions::new()
83 .write(true)
84 .create_new(true)
85 .open(path)
86 .context("create benchmark read trace (destination must not exist)")?;
87 let (sender, receiver) = mpsc::sync_channel(8192);
88 let worker = std::thread::Builder::new()
89 .name("bench-read-trace".into())
90 .spawn(move || {
91 let mut file = BufWriter::new(file);
92 let mut count = 0;
93 let mut flushed = Instant::now();
94 loop {
95 match receiver.recv_timeout(Duration::from_secs(1)) {
96 Ok(Message::Record(record)) => {
97 serde_json::to_writer(&mut file, &record)?;
98 file.write_all(b"\n")?;
99 count += 1;
100 }
101 Ok(Message::Finish) | Err(mpsc::RecvTimeoutError::Disconnected) => break,
102 Err(mpsc::RecvTimeoutError::Timeout) => {}
103 }
104 if flushed.elapsed() >= Duration::from_secs(1) {
105 file.flush()?;
106 flushed = Instant::now();
107 }
108 }
109 file.flush()?;
110 Ok(count)
111 })
112 .context("start benchmark trace writer")?;
113 Ok(Self {
114 trace: ReadTrace {
115 sender,
116 ids: Arc::new(AtomicU64::new(0)),
117 dropped: Arc::new(AtomicU64::new(0)),
118 },
119 worker: Some(worker),
120 })
121 }
122
123 pub fn trace(&self) -> ReadTrace {
124 self.trace.clone()
125 }
126
127 pub fn finish(mut self) -> Result<TraceSummary> {
128 self.join()
129 }
130
131 fn join(&mut self) -> Result<TraceSummary> {
132 let _ = self.trace.sender.send(Message::Finish);
134 let records_written = self
135 .worker
136 .take()
137 .expect("writer joined once")
138 .join()
139 .map_err(|_| anyhow::anyhow!("benchmark trace writer panicked"))?
140 .context("write benchmark read trace")?;
141 Ok(TraceSummary {
142 records_written,
143 records_dropped: self.trace.dropped.load(Ordering::Relaxed),
144 })
145 }
146}
147
148impl Drop for TraceWriter {
149 fn drop(&mut self) {
150 if self.worker.is_some() {
151 let _ = self.join();
152 }
153 }
154}
155
156impl ReadTrace {
157 pub(super) fn request_id(&self) -> u64 {
158 self.ids.fetch_add(1, Ordering::Relaxed)
159 }
160
161 pub(super) fn record(&self, record: Record) {
162 if let Err(TrySendError::Full(_) | TrySendError::Disconnected(_)) =
163 self.sender.try_send(Message::Record(record))
164 {
165 self.dropped.fetch_add(1, Ordering::Relaxed);
166 }
167 }
168}
169
170pub(super) fn unix_us() -> u128 {
171 SystemTime::now()
172 .duration_since(UNIX_EPOCH)
173 .unwrap_or_default()
174 .as_micros()
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 fn record() -> Record {
182 Record {
183 request_id: 0,
184 event: "next",
185 started_unix_us: 0,
186 elapsed_us: 1,
187 offset: 0,
188 bytes: 1,
189 status: "data",
190 request: None,
191 }
192 }
193
194 #[test]
195 fn bounded_writer_counts_loss_and_preserves_existing_files() {
196 let (sender, receiver) = mpsc::sync_channel(1);
197 let trace = ReadTrace {
198 sender,
199 ids: Arc::new(AtomicU64::new(0)),
200 dropped: Arc::new(AtomicU64::new(0)),
201 };
202 trace.record(record());
203 trace.record(record());
204 assert_eq!(trace.dropped.load(Ordering::Relaxed), 1);
205 drop(receiver);
206 trace.record(record());
207 assert_eq!(trace.dropped.load(Ordering::Relaxed), 2);
208
209 let directory = tempfile::tempdir().unwrap();
210 let path = directory.path().join("trace.jsonl");
211 {
212 let writer = TraceWriter::create(&path).unwrap();
213 writer.trace().record(record());
214 assert!(TraceWriter::create(&path).is_err());
215 }
217 let rows = std::fs::read_to_string(&path).unwrap();
218 assert_eq!(rows.lines().count(), 1);
219 assert!(TraceWriter::create(&path).is_err());
220 assert_eq!(std::fs::read_to_string(path).unwrap(), rows);
221 }
222}