LCOV - code coverage report
Current view: top level - pageserver/compaction/src/simulator - draw.rs (source / functions) Coverage Total Hit
Test: 3eba1babe267649f8cebefc91c236589db030548.info Lines: 0.0 % 335 0
Test Date: 2024-11-22 12:36:12 Functions: 0.0 % 16 0

            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 : }
        

Generated by: LCOV version 2.1-beta