use super::Key; use anyhow::Result; use std::cmp::Ordering; use std::{ collections::{BTreeMap, BTreeSet, HashSet}, fmt::Write, ops::Range, }; use svg_fmt::{rgb, BeginSvg, EndSvg, Fill, Stroke, Style}; use utils::lsn::Lsn; // Map values to their compressed coordinate - the index the value // would have in a sorted and deduplicated list of all values. struct CoordinateMap { map: BTreeMap, stretch: f32, } impl CoordinateMap { fn new(coords: Vec, stretch: f32) -> Self { let set: BTreeSet = coords.into_iter().collect(); let mut map: BTreeMap = BTreeMap::new(); for (i, e) in set.iter().enumerate() { map.insert(*e, i); } Self { map, stretch } } // This assumes that the map contains an exact point for this. // Use map_inexact for values inbetween fn map(&self, val: T) -> f32 { *self.map.get(&val).unwrap() as f32 * self.stretch } // the value is still assumed to be within the min/max bounds // (this is currently unused) fn _map_inexact(&self, val: T) -> f32 { let prev = *self.map.range(..=val).next().unwrap().1; let next = *self.map.range(val..).next().unwrap().1; // interpolate (prev as f32 + (next - prev) as f32) * self.stretch } fn max(&self) -> f32 { self.map.len() as f32 * self.stretch } } #[derive(PartialEq, Hash, Eq)] pub enum LayerTraceOp { Flush, CreateDelta, CreateImage, Delete, } impl std::fmt::Display for LayerTraceOp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { let op_str = match self { LayerTraceOp::Flush => "flush", LayerTraceOp::CreateDelta => "create_delta", LayerTraceOp::CreateImage => "create_image", LayerTraceOp::Delete => "delete", }; f.write_str(op_str) } } #[derive(PartialEq, Hash, Eq, Clone)] pub struct LayerTraceFile { pub filename: String, pub key_range: Range, pub lsn_range: Range, } impl LayerTraceFile { fn is_image(&self) -> bool { self.lsn_range.end == self.lsn_range.start } } pub struct LayerTraceEvent { pub time_rel: u64, pub op: LayerTraceOp, pub file: LayerTraceFile, } pub fn draw_history(history: &[LayerTraceEvent], mut output: W) -> Result<()> { let mut files: Vec = Vec::new(); for event in history { files.push(event.file.clone()); } let last_time_rel = history.last().unwrap().time_rel; // Collect all coordinates let mut keys: Vec = vec![]; let mut lsns: Vec = vec![]; for f in files.iter() { keys.push(f.key_range.start); keys.push(f.key_range.end); lsns.push(f.lsn_range.start); lsns.push(f.lsn_range.end); } // Analyze let key_map = CoordinateMap::new(keys, 2.0); // Stretch out vertically for better visibility let lsn_map = CoordinateMap::new(lsns, 3.0); let mut svg = String::new(); // Draw writeln!( svg, "{}", BeginSvg { w: key_map.max(), h: lsn_map.max(), } )?; let lsn_max = lsn_map.max(); // Sort the files by LSN, but so that image layers go after all delta layers // The SVG is painted in the order the elements appear, and we want to draw // image layers on top of the delta layers if they overlap // // (This could also be implemented via z coordinates: image layers get one z // coord, delta layers get another z coord.) let mut files_sorted: Vec = files.into_iter().collect(); files_sorted.sort_by(|a, b| { if a.is_image() && !b.is_image() { Ordering::Greater } else if !a.is_image() && b.is_image() { Ordering::Less } else { a.lsn_range.end.cmp(&b.lsn_range.end) } }); writeln!(svg, "")?; let mut files_seen = HashSet::new(); for f in files_sorted { if files_seen.contains(&f) { continue; } let key_start = key_map.map(f.key_range.start); let key_end = key_map.map(f.key_range.end); let key_diff = key_end - key_start; if key_start >= key_end { panic!("Invalid key range {}-{}", key_start, key_end); } let lsn_start = lsn_map.map(f.lsn_range.start); let lsn_end = lsn_map.map(f.lsn_range.end); // Fill in and thicken rectangle if it's an // image layer so that we can see it. let mut style = Style::default(); style.fill = Fill::Color(rgb(0x80, 0x80, 0x80)); style.stroke = Stroke::Color(rgb(0, 0, 0), 0.5); let y_start = lsn_max - lsn_start; let y_end = lsn_max - lsn_end; let x_margin = 0.25; let y_margin = 0.5; match f.lsn_range.start.cmp(&f.lsn_range.end) { Ordering::Less => { write!( svg, r#" "#, f.filename, key_start + x_margin, y_end + y_margin, key_diff - x_margin * 2.0, y_start - y_end - y_margin * 2.0, 1.0, // border_radius, style, )?; write!(svg, "{}", f.filename)?; writeln!(svg, "")?; } Ordering::Equal => { //lsn_diff = 0.3; //lsn_offset = -lsn_diff / 2.0; //margin = 0.05; style.fill = Fill::Color(rgb(0x80, 0, 0x80)); style.stroke = Stroke::Color(rgb(0x80, 0, 0x80), 3.0); write!( svg, r#" "#, f.filename, key_start + x_margin, y_end, key_end - x_margin, y_end, style, )?; write!( svg, "{}<br>{} - {}", f.filename, lsn_end, y_end )?; writeln!(svg, "")?; } Ordering::Greater => panic!("Invalid lsn range {}-{}", lsn_start, lsn_end), } files_seen.insert(f); } let mut record_style = Style::default(); record_style.fill = Fill::Color(rgb(0x80, 0x80, 0x80)); record_style.stroke = Stroke::None; writeln!(svg, "{}", EndSvg)?; let mut layer_events_str = String::new(); let mut first = true; for e in history { if !first { writeln!(layer_events_str, ",")?; } write!( layer_events_str, r#" {{"time_rel": {}, "filename": "{}", "op": "{}"}}"#, e.time_rel, e.file.filename, e.op )?; first = false; } writeln!(layer_events_str)?; writeln!( output, r#"
:
pos:
event:
gc:
{svg}
"# )?; Ok(()) }