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