Line data Source code
1 : use super::Key;
2 : use anyhow::Result;
3 : use std::cmp::Ordering;
4 : use std::{
5 : collections::{BTreeMap, BTreeSet, HashSet},
6 : fmt::Write,
7 : ops::Range,
8 : };
9 : use svg_fmt::{rgb, BeginSvg, EndSvg, Fill, Stroke, Style};
10 : use utils::lsn::Lsn;
11 :
12 : // Map values to their compressed coordinate - the index the value
13 : // would have in a sorted and deduplicated list of all values.
14 : struct CoordinateMap<T: Ord + Copy> {
15 : map: BTreeMap<T, usize>,
16 : stretch: f32,
17 : }
18 :
19 : impl<T: Ord + Copy> CoordinateMap<T> {
20 0 : fn new(coords: Vec<T>, stretch: f32) -> Self {
21 0 : let set: BTreeSet<T> = coords.into_iter().collect();
22 0 :
23 0 : let mut map: BTreeMap<T, usize> = BTreeMap::new();
24 0 : for (i, e) in set.iter().enumerate() {
25 0 : map.insert(*e, i);
26 0 : }
27 :
28 0 : Self { map, stretch }
29 0 : }
30 :
31 : // This assumes that the map contains an exact point for this.
32 : // Use map_inexact for values inbetween
33 0 : fn map(&self, val: T) -> f32 {
34 0 : *self.map.get(&val).unwrap() as f32 * self.stretch
35 0 : }
36 :
37 : // the value is still assumed to be within the min/max bounds
38 : // (this is currently unused)
39 0 : fn _map_inexact(&self, val: T) -> f32 {
40 0 : let prev = *self.map.range(..=val).next().unwrap().1;
41 0 : let next = *self.map.range(val..).next().unwrap().1;
42 0 :
43 0 : // interpolate
44 0 : (prev as f32 + (next - prev) as f32) * self.stretch
45 0 : }
46 :
47 0 : fn max(&self) -> f32 {
48 0 : self.map.len() as f32 * self.stretch
49 0 : }
50 : }
51 :
52 : #[derive(PartialEq, Hash, Eq)]
53 : pub enum LayerTraceOp {
54 : Flush,
55 : CreateDelta,
56 : CreateImage,
57 : Delete,
58 : }
59 :
60 : impl std::fmt::Display for LayerTraceOp {
61 0 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
62 0 : let op_str = match self {
63 0 : LayerTraceOp::Flush => "flush",
64 0 : LayerTraceOp::CreateDelta => "create_delta",
65 0 : LayerTraceOp::CreateImage => "create_image",
66 0 : LayerTraceOp::Delete => "delete",
67 : };
68 0 : f.write_str(op_str)
69 0 : }
70 : }
71 :
72 : #[derive(PartialEq, Hash, Eq, Clone)]
73 : pub struct LayerTraceFile {
74 : pub filename: String,
75 : pub key_range: Range<Key>,
76 : pub lsn_range: Range<Lsn>,
77 : }
78 :
79 : impl LayerTraceFile {
80 0 : fn is_image(&self) -> bool {
81 0 : self.lsn_range.end == self.lsn_range.start
82 0 : }
83 : }
84 :
85 : pub struct LayerTraceEvent {
86 : pub time_rel: u64,
87 : pub op: LayerTraceOp,
88 : pub file: LayerTraceFile,
89 : }
90 :
91 0 : pub fn draw_history<W: std::io::Write>(history: &[LayerTraceEvent], mut output: W) -> Result<()> {
92 0 : let mut files: Vec<LayerTraceFile> = Vec::new();
93 :
94 0 : for event in history {
95 0 : files.push(event.file.clone());
96 0 : }
97 0 : let last_time_rel = history.last().unwrap().time_rel;
98 0 :
99 0 : // Collect all coordinates
100 0 : let mut keys: Vec<Key> = vec![];
101 0 : let mut lsns: Vec<Lsn> = vec![];
102 0 : for f in files.iter() {
103 0 : keys.push(f.key_range.start);
104 0 : keys.push(f.key_range.end);
105 0 : lsns.push(f.lsn_range.start);
106 0 : lsns.push(f.lsn_range.end);
107 0 : }
108 :
109 : // Analyze
110 0 : let key_map = CoordinateMap::new(keys, 2.0);
111 0 : // Stretch out vertically for better visibility
112 0 : let lsn_map = CoordinateMap::new(lsns, 3.0);
113 0 :
114 0 : let mut svg = String::new();
115 0 :
116 0 : // Draw
117 0 : writeln!(
118 0 : svg,
119 0 : "{}",
120 0 : BeginSvg {
121 0 : w: key_map.max(),
122 0 : h: lsn_map.max(),
123 0 : }
124 0 : )?;
125 0 : let lsn_max = lsn_map.max();
126 0 :
127 0 : // Sort the files by LSN, but so that image layers go after all delta layers
128 0 : // The SVG is painted in the order the elements appear, and we want to draw
129 0 : // image layers on top of the delta layers if they overlap
130 0 : //
131 0 : // (This could also be implemented via z coordinates: image layers get one z
132 0 : // coord, delta layers get another z coord.)
133 0 : let mut files_sorted: Vec<LayerTraceFile> = files.into_iter().collect();
134 0 : files_sorted.sort_by(|a, b| {
135 0 : if a.is_image() && !b.is_image() {
136 0 : Ordering::Greater
137 0 : } else if !a.is_image() && b.is_image() {
138 0 : Ordering::Less
139 : } else {
140 0 : a.lsn_range.end.cmp(&b.lsn_range.end)
141 : }
142 0 : });
143 0 :
144 0 : writeln!(svg, "<!-- layers -->")?;
145 0 : let mut files_seen = HashSet::new();
146 0 : for f in files_sorted {
147 0 : if files_seen.contains(&f) {
148 0 : continue;
149 0 : }
150 0 : let key_start = key_map.map(f.key_range.start);
151 0 : let key_end = key_map.map(f.key_range.end);
152 0 : let key_diff = key_end - key_start;
153 0 :
154 0 : if key_start >= key_end {
155 0 : panic!("Invalid key range {}-{}", key_start, key_end);
156 0 : }
157 0 :
158 0 : let lsn_start = lsn_map.map(f.lsn_range.start);
159 0 : let lsn_end = lsn_map.map(f.lsn_range.end);
160 0 :
161 0 : // Fill in and thicken rectangle if it's an
162 0 : // image layer so that we can see it.
163 0 : let mut style = Style::default();
164 0 : style.fill = Fill::Color(rgb(0x80, 0x80, 0x80));
165 0 : style.stroke = Stroke::Color(rgb(0, 0, 0), 0.5);
166 0 :
167 0 : let y_start = lsn_max - lsn_start;
168 0 : let y_end = lsn_max - lsn_end;
169 0 :
170 0 : let x_margin = 0.25;
171 0 : let y_margin = 0.5;
172 0 :
173 0 : match f.lsn_range.start.cmp(&f.lsn_range.end) {
174 : Ordering::Less => {
175 0 : write!(
176 0 : svg,
177 0 : r#" <rect id="layer_{}" x="{}" y="{}" width="{}" height="{}" ry="{}" style="{}">"#,
178 0 : f.filename,
179 0 : key_start + x_margin,
180 0 : y_end + y_margin,
181 0 : key_diff - x_margin * 2.0,
182 0 : y_start - y_end - y_margin * 2.0,
183 0 : 1.0, // border_radius,
184 0 : style,
185 0 : )?;
186 0 : write!(svg, "<title>{}</title>", f.filename)?;
187 0 : writeln!(svg, "</rect>")?;
188 : }
189 : Ordering::Equal => {
190 : //lsn_diff = 0.3;
191 : //lsn_offset = -lsn_diff / 2.0;
192 : //margin = 0.05;
193 0 : style.fill = Fill::Color(rgb(0x80, 0, 0x80));
194 0 : style.stroke = Stroke::Color(rgb(0x80, 0, 0x80), 3.0);
195 0 : write!(
196 0 : svg,
197 0 : r#" <line id="layer_{}" x1="{}" y1="{}" x2="{}" y2="{}" style="{}">"#,
198 0 : f.filename,
199 0 : key_start + x_margin,
200 0 : y_end,
201 0 : key_end - x_margin,
202 0 : y_end,
203 0 : style,
204 0 : )?;
205 0 : write!(
206 0 : svg,
207 0 : "<title>{}<br>{} - {}</title>",
208 0 : f.filename, lsn_end, y_end
209 0 : )?;
210 0 : writeln!(svg, "</line>")?;
211 : }
212 0 : Ordering::Greater => panic!("Invalid lsn range {}-{}", lsn_start, lsn_end),
213 : }
214 0 : files_seen.insert(f);
215 : }
216 :
217 0 : let mut record_style = Style::default();
218 0 : record_style.fill = Fill::Color(rgb(0x80, 0x80, 0x80));
219 0 : record_style.stroke = Stroke::None;
220 0 :
221 0 : writeln!(svg, "{}", EndSvg)?;
222 :
223 0 : let mut layer_events_str = String::new();
224 0 : let mut first = true;
225 0 : for e in history {
226 0 : if !first {
227 0 : writeln!(layer_events_str, ",")?;
228 0 : }
229 0 : write!(
230 0 : layer_events_str,
231 0 : r#" {{"time_rel": {}, "filename": "{}", "op": "{}"}}"#,
232 0 : e.time_rel, e.file.filename, e.op
233 0 : )?;
234 0 : first = false;
235 : }
236 0 : writeln!(layer_events_str)?;
237 :
238 0 : writeln!(
239 0 : output,
240 0 : r#"<!DOCTYPE html>
241 0 : <html>
242 0 : <head>
243 0 : <style>
244 0 : /* Keep the slider pinned at top */
245 0 : .topbar {{
246 0 : display: block;
247 0 : overflow: hidden;
248 0 : background-color: lightgrey;
249 0 : position: fixed;
250 0 : top: 0;
251 0 : width: 100%;
252 0 : /* width: 500px; */
253 0 : }}
254 0 : .slidercontainer {{
255 0 : float: left;
256 0 : width: 50%;
257 0 : margin-right: 200px;
258 0 : }}
259 0 : .slider {{
260 0 : float: left;
261 0 : width: 100%;
262 0 : }}
263 0 : .legend {{
264 0 : width: 200px;
265 0 : float: right;
266 0 : }}
267 0 :
268 0 : /* Main content */
269 0 : .main {{
270 0 : margin-top: 50px; /* Add a top margin to avoid content overlay */
271 0 : }}
272 0 : </style>
273 0 : </head>
274 0 :
275 0 : <body onload="init()">
276 0 : <script type="text/javascript">
277 0 :
278 0 : var layer_events = [{layer_events_str}]
279 0 :
280 0 : let ticker;
281 0 :
282 0 : function init() {{
283 0 : for (let i = 0; i < layer_events.length; i++) {{
284 0 : var layer = document.getElementById("layer_" + layer_events[i].filename);
285 0 : layer.style.visibility = "hidden";
286 0 : }}
287 0 : last_layer_event = -1;
288 0 : moveSlider(last_slider_pos)
289 0 : }}
290 0 :
291 0 : function startAnimation() {{
292 0 : ticker = setInterval(animateStep, 100);
293 0 : }}
294 0 : function stopAnimation() {{
295 0 : clearInterval(ticker);
296 0 : }}
297 0 :
298 0 : function animateStep() {{
299 0 : if (last_layer_event < layer_events.length - 1) {{
300 0 : var slider = document.getElementById("time-slider");
301 0 : let prevPos = slider.value
302 0 : let nextEvent = last_layer_event + 1
303 0 : while (nextEvent <= layer_events.length - 1) {{
304 0 : if (layer_events[nextEvent].time_rel > prevPos) {{
305 0 : break;
306 0 : }}
307 0 : nextEvent += 1;
308 0 : }}
309 0 : let nextPos = layer_events[nextEvent].time_rel
310 0 : slider.value = nextPos
311 0 : moveSlider(nextPos)
312 0 : }}
313 0 : }}
314 0 :
315 0 : function redoLayerEvent(n, dir) {{
316 0 : var layer = document.getElementById("layer_" + layer_events[n].filename);
317 0 : switch (layer_events[n].op) {{
318 0 : case "flush":
319 0 : layer.style.visibility = "visible";
320 0 : break;
321 0 : case "create_delta":
322 0 : layer.style.visibility = "visible";
323 0 : break;
324 0 : case "create_image":
325 0 : layer.style.visibility = "visible";
326 0 : break;
327 0 : case "delete":
328 0 : layer.style.visibility = "hidden";
329 0 : break;
330 0 : }}
331 0 : }}
332 0 : function undoLayerEvent(n) {{
333 0 : var layer = document.getElementById("layer_" + layer_events[n].filename);
334 0 : switch (layer_events[n].op) {{
335 0 : case "flush":
336 0 : layer.style.visibility = "hidden";
337 0 : break;
338 0 : case "create_delta":
339 0 : layer.style.visibility = "hidden";
340 0 : break;
341 0 : case "create_image":
342 0 : layer.style.visibility = "hidden";
343 0 : break;
344 0 : case "delete":
345 0 : layer.style.visibility = "visible";
346 0 : break;
347 0 : }}
348 0 : }}
349 0 :
350 0 : var last_slider_pos = 0
351 0 : var last_layer_event = 0
352 0 :
353 0 : var moveSlider = function(new_pos) {{
354 0 : if (new_pos > last_slider_pos) {{
355 0 : while (last_layer_event < layer_events.length - 1) {{
356 0 : if (layer_events[last_layer_event + 1].time_rel > new_pos) {{
357 0 : break;
358 0 : }}
359 0 : last_layer_event += 1;
360 0 : redoLayerEvent(last_layer_event)
361 0 : }}
362 0 : }}
363 0 : if (new_pos < last_slider_pos) {{
364 0 : while (last_layer_event >= 0) {{
365 0 : if (layer_events[last_layer_event].time_rel <= new_pos) {{
366 0 : break;
367 0 : }}
368 0 : undoLayerEvent(last_layer_event)
369 0 : last_layer_event -= 1;
370 0 : }}
371 0 : }}
372 0 : last_slider_pos = new_pos;
373 0 : document.getElementById("debug_pos").textContent=new_pos;
374 0 : if (last_layer_event >= 0) {{
375 0 : document.getElementById("debug_layer_event").textContent=last_layer_event + " " + layer_events[last_layer_event].time_rel + " " + layer_events[last_layer_event].op;
376 0 : }} else {{
377 0 : document.getElementById("debug_layer_event").textContent="begin";
378 0 : }}
379 0 : }}
380 0 : </script>
381 0 :
382 0 : <div class="topbar">
383 0 : <div class="slidercontainer">
384 0 : <label for="time-slider">TIME</label>:
385 0 : <input id="time-slider" class="slider" type="range" min="0" max="{last_time_rel}" value="0" oninput="moveSlider(this.value)"><br>
386 0 :
387 0 : pos: <span id="debug_pos"></span><br>
388 0 : event: <span id="debug_layer_event"></span><br>
389 0 : gc: <span id="debug_gc_event"></span><br>
390 0 : </div>
391 0 :
392 0 : <button onclick="startAnimation()">Play</button>
393 0 : <button onclick="stopAnimation()">Stop</button>
394 0 :
395 0 : <svg class="legend">
396 0 : <rect x=5 y=0 width=20 height=20 style="fill:rgb(128,128,128);stroke:rgb(0,0,0);stroke-width:0.5;fill-opacity:1;stroke-opacity:1;"/>
397 0 : <line x1=5 y1=30 x2=25 y2=30 style="fill:rgb(128,0,128);stroke:rgb(128,0,128);stroke-width:3;fill-opacity:1;stroke-opacity:1;"/>
398 0 : <line x1=0 y1=40 x2=30 y2=40 style="fill:none;stroke:rgb(255,0,0);stroke-width:0.5;fill-opacity:1;stroke-opacity:1;"/>
399 0 : </svg>
400 0 : </div>
401 0 :
402 0 : <div class="main">
403 0 : {svg}
404 0 : </div>
405 0 : </body>
406 0 : </html>
407 0 : "#
408 0 : )?;
409 :
410 0 : Ok(())
411 0 : }
|