LCOV - differential code coverage report
Current view: top level - libs/tenant_size_model/src - svg.rs (source / functions) Coverage Total Hit CBC
Current: cd44433dd675caa99df17a61b18949c8387e2242.info Lines: 100.0 % 142 142 142
Current Date: 2024-01-09 02:06:09 Functions: 100.0 % 8 8 8
Baseline: 66c52a629a0f4a503e193045e0df4c77139e344b.info
Baseline Date: 2024-01-08 15:34:46

           TLA  Line data    Source code
       1                 : use crate::{SegmentMethod, SegmentSizeResult, SizeResult, StorageModel};
       2                 : use std::fmt::Write;
       3                 : 
       4                 : const SVG_WIDTH: f32 = 500.0;
       5                 : 
       6                 : struct SvgDraw<'a> {
       7                 :     storage: &'a StorageModel,
       8                 :     branches: &'a [String],
       9                 :     seg_to_branch: &'a [usize],
      10                 :     sizes: &'a [SegmentSizeResult],
      11                 : 
      12                 :     // layout
      13                 :     xscale: f32,
      14                 :     min_lsn: u64,
      15                 :     seg_coordinates: Vec<(f32, f32)>,
      16                 : }
      17                 : 
      18 CBC          19 : fn draw_legend(result: &mut String) -> anyhow::Result<()> {
      19              19 :     writeln!(
      20              19 :         result,
      21              19 :         "<circle cx=\"10\" cy=\"10\" r=\"5\" stroke=\"red\"/>"
      22              19 :     )?;
      23              19 :     writeln!(result, "<text x=\"20\" y=\"15\">logical snapshot</text>")?;
      24              19 :     writeln!(
      25              19 :         result,
      26              19 :         "<line x1=\"5\" y1=\"30\" x2=\"15\" y2=\"30\" stroke-width=\"6\" stroke=\"black\" />"
      27              19 :     )?;
      28              19 :     writeln!(
      29              19 :         result,
      30              19 :         "<text x=\"20\" y=\"35\">WAL within retention period</text>"
      31              19 :     )?;
      32              19 :     writeln!(
      33              19 :         result,
      34              19 :         "<line x1=\"5\" y1=\"50\" x2=\"15\" y2=\"50\" stroke-width=\"3\" stroke=\"black\" />"
      35              19 :     )?;
      36              19 :     writeln!(
      37              19 :         result,
      38              19 :         "<text x=\"20\" y=\"55\">WAL retained to avoid copy</text>"
      39              19 :     )?;
      40              19 :     writeln!(
      41              19 :         result,
      42              19 :         "<line x1=\"5\" y1=\"70\" x2=\"15\" y2=\"70\" stroke-width=\"1\" stroke=\"gray\" />"
      43              19 :     )?;
      44              19 :     writeln!(result, "<text x=\"20\" y=\"75\">WAL not retained</text>")?;
      45              19 :     Ok(())
      46              19 : }
      47                 : 
      48              19 : pub fn draw_svg(
      49              19 :     storage: &StorageModel,
      50              19 :     branches: &[String],
      51              19 :     seg_to_branch: &[usize],
      52              19 :     sizes: &SizeResult,
      53              19 : ) -> anyhow::Result<String> {
      54              19 :     let mut draw = SvgDraw {
      55              19 :         storage,
      56              19 :         branches,
      57              19 :         seg_to_branch,
      58              19 :         sizes: &sizes.segments,
      59              19 : 
      60              19 :         xscale: 0.0,
      61              19 :         min_lsn: 0,
      62              19 :         seg_coordinates: Vec::new(),
      63              19 :     };
      64              19 : 
      65              19 :     let mut result = String::new();
      66              19 : 
      67              19 :     writeln!(result, "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" height=\"300\" width=\"500\">")?;
      68                 : 
      69              19 :     draw.calculate_svg_layout();
      70                 : 
      71                 :     // Draw the tree
      72              68 :     for (seg_id, _seg) in storage.segments.iter().enumerate() {
      73              68 :         draw.draw_seg_phase1(seg_id, &mut result)?;
      74                 :     }
      75                 : 
      76                 :     // Draw snapshots
      77              68 :     for (seg_id, _seg) in storage.segments.iter().enumerate() {
      78              68 :         draw.draw_seg_phase2(seg_id, &mut result)?;
      79                 :     }
      80                 : 
      81              19 :     draw_legend(&mut result)?;
      82                 : 
      83              19 :     write!(result, "</svg>")?;
      84                 : 
      85              19 :     Ok(result)
      86              19 : }
      87                 : 
      88                 : impl<'a> SvgDraw<'a> {
      89              19 :     fn calculate_svg_layout(&mut self) {
      90              19 :         // Find x scale
      91              19 :         let segments = &self.storage.segments;
      92              68 :         let min_lsn = segments.iter().map(|s| s.lsn).fold(u64::MAX, std::cmp::min);
      93              68 :         let max_lsn = segments.iter().map(|s| s.lsn).fold(0, std::cmp::max);
      94              19 : 
      95              19 :         // Start with 1 pixel = 1 byte. Double the scale until it fits into the image
      96              19 :         let mut xscale = 1.0;
      97             207 :         while (max_lsn - min_lsn) as f32 / xscale > SVG_WIDTH {
      98             188 :             xscale *= 2.0;
      99             188 :         }
     100                 : 
     101                 :         // Layout the timelines on Y dimension.
     102                 :         // TODO
     103              19 :         let mut y = 100.0;
     104              19 :         let mut branch_y_coordinates = Vec::new();
     105              46 :         for _branch in self.branches {
     106              27 :             branch_y_coordinates.push(y);
     107              27 :             y += 40.0;
     108              27 :         }
     109                 : 
     110                 :         // Calculate coordinates for each point
     111              19 :         let seg_coordinates = std::iter::zip(segments, self.seg_to_branch)
     112              68 :             .map(|(seg, branch_id)| {
     113              68 :                 let x = (seg.lsn - min_lsn) as f32 / xscale;
     114              68 :                 let y = branch_y_coordinates[*branch_id];
     115              68 :                 (x, y)
     116              68 :             })
     117              19 :             .collect();
     118              19 : 
     119              19 :         self.xscale = xscale;
     120              19 :         self.min_lsn = min_lsn;
     121              19 :         self.seg_coordinates = seg_coordinates;
     122              19 :     }
     123                 : 
     124                 :     /// Draws lines between points
     125              68 :     fn draw_seg_phase1(&self, seg_id: usize, result: &mut String) -> anyhow::Result<()> {
     126              68 :         let seg = &self.storage.segments[seg_id];
     127                 : 
     128              68 :         let wal_bytes = if let Some(parent_id) = seg.parent {
     129              49 :             seg.lsn - self.storage.segments[parent_id].lsn
     130                 :         } else {
     131              19 :             0
     132                 :         };
     133                 : 
     134              68 :         let style = match self.sizes[seg_id].method {
     135              19 :             SegmentMethod::SnapshotHere => "stroke-width=\"1\" stroke=\"gray\"",
     136              45 :             SegmentMethod::Wal if seg.needed && wal_bytes > 0 => {
     137              23 :                 "stroke-width=\"6\" stroke=\"black\""
     138                 :             }
     139              22 :             SegmentMethod::Wal => "stroke-width=\"3\" stroke=\"black\"",
     140               4 :             SegmentMethod::Skipped => "stroke-width=\"1\" stroke=\"gray\"",
     141                 :         };
     142              68 :         if let Some(parent_id) = seg.parent {
     143              49 :             let (x1, y1) = self.seg_coordinates[parent_id];
     144              49 :             let (x2, y2) = self.seg_coordinates[seg_id];
     145              49 : 
     146              49 :             writeln!(
     147              49 :                 result,
     148              49 :                 "<line x1=\"{x1}\" y1=\"{y1}\" x2=\"{x2}\" y2=\"{y2}\" {style}>",
     149              49 :             )?;
     150              49 :             writeln!(
     151              49 :                 result,
     152              49 :                 "  <title>{wal_bytes} bytes of WAL (seg {seg_id})</title>"
     153              49 :             )?;
     154              49 :             writeln!(result, "</line>")?;
     155                 :         } else {
     156                 :             // draw a little dash to mark the starting point of this branch
     157              19 :             let (x, y) = self.seg_coordinates[seg_id];
     158              19 :             let (x1, y1) = (x, y - 5.0);
     159              19 :             let (x2, y2) = (x, y + 5.0);
     160              19 : 
     161              19 :             writeln!(
     162              19 :                 result,
     163              19 :                 "<line x1=\"{x1}\" y1=\"{y1}\" x2=\"{x2}\" y2=\"{y2}\" {style}>",
     164              19 :             )?;
     165              19 :             writeln!(result, "  <title>(seg {seg_id})</title>")?;
     166              19 :             writeln!(result, "</line>")?;
     167                 :         }
     168                 : 
     169              68 :         Ok(())
     170              68 :     }
     171                 : 
     172                 :     /// Draw circles where snapshots are taken
     173              68 :     fn draw_seg_phase2(&self, seg_id: usize, result: &mut String) -> anyhow::Result<()> {
     174              68 :         let seg = &self.storage.segments[seg_id];
     175              68 : 
     176              68 :         // draw a snapshot point if it's needed
     177              68 :         let (coord_x, coord_y) = self.seg_coordinates[seg_id];
     178              68 :         if self.sizes[seg_id].method == SegmentMethod::SnapshotHere {
     179              19 :             writeln!(
     180              19 :                 result,
     181              19 :                 "<circle cx=\"{coord_x}\" cy=\"{coord_y}\" r=\"5\" stroke=\"red\">",
     182              19 :             )?;
     183              19 :             writeln!(
     184              19 :                 result,
     185              19 :                 "  <title>logical size {}</title>",
     186              19 :                 seg.size.unwrap()
     187              19 :             )?;
     188              19 :             write!(result, "</circle>")?;
     189              49 :         }
     190                 : 
     191              68 :         Ok(())
     192              68 :     }
     193                 : }
        

Generated by: LCOV version 2.1-beta