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