LCOV - code coverage report
Current view: top level - libs/tenant_size_model/src - svg.rs (source / functions) Coverage Total Hit
Test: 1b0a6a0c05cee5a7de360813c8034804e105ce1c.info Lines: 0.0 % 165 0
Test Date: 2025-03-12 00:01:28 Functions: 0.0 % 8 0

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

Generated by: LCOV version 2.1-beta