use crate::events::EventData;
use crate::events::EventImportance;
use crate::events::EventType;
use crate::events::Eventable;
use crate::events::ExData;
pub enum EventTimePrecision {
MilliSeconds,
MicroSeconds,
NanoSeconds,
}
fn duration_to_millis(
dur: std::time::Duration, precision: &EventTimePrecision,
) -> f64 {
match precision {
EventTimePrecision::MilliSeconds => dur.as_millis() as f64,
EventTimePrecision::MicroSeconds => dur.as_micros() as f64 / 1_000.0,
EventTimePrecision::NanoSeconds => dur.as_nanos() as f64 / 1_000_000.0,
}
}
fn elapsed_millis(
start: std::time::Instant, now: std::time::Instant,
precision: &EventTimePrecision,
) -> f64 {
if cfg!(test) {
return 0.0;
}
let dur = now.saturating_duration_since(start);
duration_to_millis(dur, precision)
}
use super::*;
#[derive(PartialEq, Eq, Debug)]
pub enum StreamerState {
Initial,
Ready,
Finished,
}
pub struct QlogStreamer {
start_time: std::time::Instant,
writer: Box<dyn std::io::Write + Send + Sync>,
qlog: QlogSeq,
state: StreamerState,
log_level: EventImportance,
time_precision: EventTimePrecision,
}
impl QlogStreamer {
#[allow(clippy::too_many_arguments)]
pub fn new(
title: Option<String>, description: Option<String>,
start_time: std::time::Instant, trace: TraceSeq,
log_level: EventImportance, time_precision: EventTimePrecision,
writer: Box<dyn std::io::Write + Send + Sync>,
) -> Self {
let qlog = QlogSeq {
file_schema: QLOGFILESEQ_URI.to_string(),
serialization_format: "JSON-SEQ".to_string(),
title,
description,
trace,
};
QlogStreamer {
start_time,
writer,
qlog,
state: StreamerState::Initial,
log_level,
time_precision,
}
}
pub fn start_log(&mut self) -> Result<()> {
if self.state != StreamerState::Initial {
return Err(Error::Done);
}
self.writer.as_mut().write_all(b"")?;
serde_json::to_writer(self.writer.as_mut(), &self.qlog)
.map_err(|_| Error::Done)?;
self.writer.as_mut().write_all(b"\n")?;
self.state = StreamerState::Ready;
Ok(())
}
pub fn finish_log(&mut self) -> Result<()> {
if self.state == StreamerState::Initial ||
self.state == StreamerState::Finished
{
return Err(Error::InvalidState);
}
self.state = StreamerState::Finished;
self.writer.as_mut().flush()?;
Ok(())
}
pub fn add_event_now<E: Serialize + Eventable>(
&mut self, event: E,
) -> Result<()> {
let now = std::time::Instant::now();
self.add_event_with_instant(event, now)
}
pub fn add_event_now_pretty<E: Serialize + Eventable>(
&mut self, event: E,
) -> Result<()> {
let now = std::time::Instant::now();
self.add_event_with_instant_pretty(event, now)
}
pub fn add_event_with_instant<E: Serialize + Eventable>(
&mut self, event: E, now: std::time::Instant,
) -> Result<()> {
self.event_with_instant(event, now, false)
}
pub fn add_event_with_instant_pretty<E: Serialize + Eventable>(
&mut self, event: E, now: std::time::Instant,
) -> Result<()> {
self.event_with_instant(event, now, true)
}
fn event_with_instant<E: Serialize + Eventable>(
&mut self, mut event: E, now: std::time::Instant, pretty: bool,
) -> Result<()> {
if self.state != StreamerState::Ready {
return Err(Error::InvalidState);
}
if !event.importance().is_contained_in(&self.log_level) {
return Err(Error::Done);
}
event.set_time(elapsed_millis(
self.start_time,
now,
&self.time_precision,
));
if pretty {
self.add_event_pretty(event)
} else {
self.add_event(event)
}
}
pub fn add_event_data_now(&mut self, event_data: EventData) -> Result<()> {
self.add_event_data_ex_now(event_data, Default::default())
}
pub fn add_event_data_now_pretty(
&mut self, event_data: EventData,
) -> Result<()> {
self.add_event_data_ex_now_pretty(event_data, Default::default())
}
pub fn add_event_data_ex_now(
&mut self, event_data: EventData, ex_data: ExData,
) -> Result<()> {
let now = std::time::Instant::now();
self.add_event_data_ex_with_instant(event_data, ex_data, now)
}
pub fn add_event_data_ex_now_pretty(
&mut self, event_data: EventData, ex_data: ExData,
) -> Result<()> {
let now = std::time::Instant::now();
self.add_event_data_ex_with_instant_pretty(event_data, ex_data, now)
}
pub fn add_event_data_with_instant(
&mut self, event_data: EventData, now: std::time::Instant,
) -> Result<()> {
self.add_event_data_ex_with_instant(event_data, Default::default(), now)
}
pub fn add_event_data_with_instant_pretty(
&mut self, event_data: EventData, now: std::time::Instant,
) -> Result<()> {
self.add_event_data_ex_with_instant_pretty(
event_data,
Default::default(),
now,
)
}
pub fn add_event_data_ex_with_instant(
&mut self, event_data: EventData, ex_data: ExData,
now: std::time::Instant,
) -> Result<()> {
self.event_data_ex_with_instant(event_data, ex_data, now, false)
}
pub fn add_event_data_ex_with_instant_pretty(
&mut self, event_data: EventData, ex_data: ExData,
now: std::time::Instant,
) -> Result<()> {
self.event_data_ex_with_instant(event_data, ex_data, now, true)
}
fn event_data_ex_with_instant(
&mut self, event_data: EventData, ex_data: ExData,
now: std::time::Instant, pretty: bool,
) -> Result<()> {
if self.state != StreamerState::Ready {
return Err(Error::InvalidState);
}
let ty = EventType::from(&event_data);
if !EventImportance::from(ty).is_contained_in(&self.log_level) {
return Err(Error::Done);
}
let event = Event::with_time_ex(
elapsed_millis(self.start_time, now, &self.time_precision),
event_data,
ex_data,
);
if pretty {
self.add_event_pretty(event)
} else {
self.add_event(event)
}
}
pub fn add_event<E: Serialize + Eventable>(
&mut self, event: E,
) -> Result<()> {
self.write_event(event, false)
}
pub fn add_event_pretty<E: Serialize + Eventable>(
&mut self, event: E,
) -> Result<()> {
self.write_event(event, true)
}
fn write_event<E: Serialize + Eventable>(
&mut self, event: E, pretty: bool,
) -> Result<()> {
if self.state != StreamerState::Ready {
return Err(Error::InvalidState);
}
if !event.importance().is_contained_in(&self.log_level) {
return Err(Error::Done);
}
self.writer.as_mut().write_all(b"")?;
if pretty {
serde_json::to_writer_pretty(self.writer.as_mut(), &event)
.map_err(|_| Error::Done)?;
} else {
serde_json::to_writer(self.writer.as_mut(), &event)
.map_err(|_| Error::Done)?;
}
self.writer.as_mut().write_all(b"\n")?;
Ok(())
}
#[allow(clippy::borrowed_box)]
pub fn writer(&self) -> &Box<dyn std::io::Write + Send + Sync> {
&self.writer
}
pub fn start_time(&self) -> std::time::Instant {
self.start_time
}
}
impl Drop for QlogStreamer {
fn drop(&mut self) {
let _ = self.finish_log();
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::*;
use crate::events::quic;
use crate::events::quic::QuicFrame;
use crate::events::RawInfo;
use testing::*;
use serde_json::json;
#[test]
fn serialization_states() {
let v: Vec<u8> = Vec::new();
let buff = std::io::Cursor::new(v);
let writer = Box::new(buff);
let trace = make_trace_seq();
let pkt_hdr = make_pkt_hdr(quic::PacketType::Handshake);
let raw = Some(RawInfo {
length: Some(1251),
payload_length: Some(1224),
data: None,
});
let frame1 = QuicFrame::Stream {
stream_id: 40,
offset: Some(40),
raw: Some(Box::new(RawInfo {
length: None,
payload_length: Some(400),
data: None,
})),
fin: Some(true),
};
let event_data1 = EventData::QuicPacketSent(quic::PacketSent {
header: pkt_hdr.clone(),
frames: Some(vec![frame1]),
raw: raw.clone(),
..Default::default()
});
let ev1 = Event::with_time(0.0, event_data1);
let frame2 = QuicFrame::Stream {
stream_id: 0,
offset: Some(0),
raw: Some(Box::new(RawInfo {
length: None,
payload_length: Some(100),
data: None,
})),
fin: Some(true),
};
let frame3 = QuicFrame::Stream {
stream_id: 0,
offset: Some(0),
raw: Some(Box::new(RawInfo {
length: None,
payload_length: Some(100),
data: None,
})),
fin: Some(true),
};
let event_data2 = EventData::QuicPacketSent(quic::PacketSent {
header: pkt_hdr.clone(),
frames: Some(vec![frame2]),
raw: raw.clone(),
..Default::default()
});
let ev2 = Event::with_time(0.0, event_data2);
let event_data3 = EventData::QuicPacketSent(quic::PacketSent {
header: pkt_hdr,
frames: Some(vec![frame3]),
stateless_reset_token: Some(Box::new("reset_token".to_string())),
raw,
..Default::default()
});
let ev3 = Event::with_time(0.0, event_data3);
let mut s = streamer::QlogStreamer::new(
Some("title".to_string()),
Some("description".to_string()),
std::time::Instant::now(),
trace,
EventImportance::Base,
EventTimePrecision::NanoSeconds,
writer,
);
assert!(matches!(s.add_event(ev2.clone()), Err(Error::InvalidState)));
assert!(matches!(s.finish_log(), Err(Error::InvalidState)));
assert!(matches!(s.start_log(), Ok(())));
assert!(matches!(s.add_event(ev1), Ok(())));
assert!(matches!(s.add_event(ev2), Ok(())));
assert!(matches!(s.add_event(ev3.clone()), Ok(())));
let now = std::time::Instant::now();
assert!(matches!(s.add_event_with_instant(ev3, now), Ok(())));
assert!(matches!(s.finish_log(), Ok(())));
let r = s.writer();
#[allow(clippy::borrowed_box)]
let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };
let log_string = r#"{"file_schema":"urn:ietf:params:qlog:file:sequential","serialization_format":"JSON-SEQ","title":"title","description":"description","trace":{"title":"Quiche qlog trace","description":"Quiche qlog trace description","vantage_point":{"type":"server"},"event_schemas":[]}}
{"time":0.0,"name":"quic:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":40,"offset":40,"fin":true,"raw":{"payload_length":400}}]}}
{"time":0.0,"name":"quic:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":0,"offset":0,"fin":true,"raw":{"payload_length":100}}]}}
{"time":0.0,"name":"quic:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"stateless_reset_token":"reset_token","raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":0,"offset":0,"fin":true,"raw":{"payload_length":100}}]}}
{"time":0.0,"name":"quic:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"stateless_reset_token":"reset_token","raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":0,"offset":0,"fin":true,"raw":{"payload_length":100}}]}}
"#;
let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap();
pretty_assertions::assert_eq!(log_string, written_string);
}
#[test]
fn stream_json_event() {
let data = json!({"foo": "Bar", "hello": 123});
let ev = events::JsonEvent {
time: 0.0,
importance: events::EventImportance::Core,
name: "jsonevent:sample".into(),
data,
};
let v: Vec<u8> = Vec::new();
let buff = std::io::Cursor::new(v);
let writer = Box::new(buff);
let trace = make_trace_seq();
let mut s = streamer::QlogStreamer::new(
Some("title".to_string()),
Some("description".to_string()),
std::time::Instant::now(),
trace,
EventImportance::Base,
EventTimePrecision::NanoSeconds,
writer,
);
assert!(matches!(s.start_log(), Ok(())));
assert!(matches!(s.add_event(ev), Ok(())));
assert!(matches!(s.finish_log(), Ok(())));
let r = s.writer();
#[allow(clippy::borrowed_box)]
let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };
let log_string = r#"{"file_schema":"urn:ietf:params:qlog:file:sequential","serialization_format":"JSON-SEQ","title":"title","description":"description","trace":{"title":"Quiche qlog trace","description":"Quiche qlog trace description","vantage_point":{"type":"server"},"event_schemas":[]}}
{"time":0.0,"name":"jsonevent:sample","data":{"foo":"Bar","hello":123}}
"#;
let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap();
pretty_assertions::assert_eq!(log_string, written_string);
}
#[test]
fn stream_data_ex() {
let v: Vec<u8> = Vec::new();
let buff = std::io::Cursor::new(v);
let writer = Box::new(buff);
let trace = make_trace_seq();
let pkt_hdr = make_pkt_hdr(quic::PacketType::Handshake);
let raw = Some(RawInfo {
length: Some(1251),
payload_length: Some(1224),
data: None,
});
let frame1 = QuicFrame::Stream {
stream_id: 40,
offset: Some(40),
raw: Some(Box::new(RawInfo {
length: None,
payload_length: Some(400),
data: None,
})),
fin: Some(true),
};
let event_data1 = EventData::QuicPacketSent(quic::PacketSent {
header: pkt_hdr.clone(),
frames: Some(vec![frame1]),
raw: raw.clone(),
..Default::default()
});
let j1 = json!({"foo": "Bar", "hello": 123});
let j2 = json!({"baz": [1,2,3,4]});
let mut ex_data = BTreeMap::new();
ex_data.insert("first".to_string(), j1);
ex_data.insert("second".to_string(), j2);
let ev1 = Event::with_time_ex(0.0, event_data1, ex_data);
let frame2 = QuicFrame::Stream {
stream_id: 1,
offset: Some(0),
raw: Some(Box::new(RawInfo {
length: None,
payload_length: Some(100),
data: None,
})),
fin: Some(true),
};
let event_data2 = EventData::QuicPacketSent(quic::PacketSent {
header: pkt_hdr.clone(),
frames: Some(vec![frame2]),
raw: raw.clone(),
..Default::default()
});
let ev2 = Event::with_time(0.0, event_data2);
let mut s = streamer::QlogStreamer::new(
Some("title".to_string()),
Some("description".to_string()),
std::time::Instant::now(),
trace,
EventImportance::Base,
EventTimePrecision::NanoSeconds,
writer,
);
assert!(matches!(s.start_log(), Ok(())));
assert!(matches!(s.add_event(ev1), Ok(())));
assert!(matches!(s.add_event(ev2), Ok(())));
assert!(matches!(s.finish_log(), Ok(())));
let r = s.writer();
#[allow(clippy::borrowed_box)]
let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };
let log_string = r#"{"file_schema":"urn:ietf:params:qlog:file:sequential","serialization_format":"JSON-SEQ","title":"title","description":"description","trace":{"title":"Quiche qlog trace","description":"Quiche qlog trace description","vantage_point":{"type":"server"},"event_schemas":[]}}
{"time":0.0,"name":"quic:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":40,"offset":40,"fin":true,"raw":{"payload_length":400}}]},"first":{"foo":"Bar","hello":123},"second":{"baz":[1,2,3,4]}}
{"time":0.0,"name":"quic:packet_sent","data":{"header":{"packet_type":"handshake","packet_number":0,"version":"1","scil":8,"dcil":8,"scid":"7e37e4dcc6682da8","dcid":"36ce104eee50101c"},"raw":{"length":1251,"payload_length":1224},"frames":[{"frame_type":"stream","stream_id":1,"offset":0,"fin":true,"raw":{"payload_length":100}}]}}
"#;
let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap();
pretty_assertions::assert_eq!(log_string, written_string);
}
#[test]
fn elapsed_millis_precision() {
let dur = std::time::Duration::from_nanos(1_234_567);
assert_eq!(
duration_to_millis(dur, &EventTimePrecision::MilliSeconds),
1.0
);
assert_eq!(
duration_to_millis(dur, &EventTimePrecision::MicroSeconds),
1.234000
);
assert_eq!(
duration_to_millis(dur, &EventTimePrecision::NanoSeconds),
1.234567
);
}
#[test]
fn elapsed_millis_zero_duration_all_precisions() {
let dur = std::time::Duration::from_secs(0);
assert_eq!(
duration_to_millis(dur, &EventTimePrecision::MilliSeconds),
0.0
);
assert_eq!(
duration_to_millis(dur, &EventTimePrecision::MicroSeconds),
0.0
);
assert_eq!(
duration_to_millis(dur, &EventTimePrecision::NanoSeconds),
0.0
);
}
}