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!(result, "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" height=\"300\" width=\"500\">")?;
80 :
81 0 : draw.calculate_svg_layout();
82 :
83 : // Draw the tree
84 0 : for (seg_id, _seg) in storage.segments.iter().enumerate() {
85 0 : draw.draw_seg_phase1(seg_id, &mut result)?;
86 : }
87 :
88 : // Draw snapshots
89 0 : for (seg_id, _seg) in storage.segments.iter().enumerate() {
90 0 : draw.draw_seg_phase2(seg_id, &mut result)?;
91 : }
92 :
93 0 : draw_legend(&mut result)?;
94 :
95 0 : write!(result, "</svg>")?;
96 :
97 0 : Ok(result)
98 0 : }
99 :
100 : impl SvgDraw<'_> {
101 0 : fn calculate_svg_layout(&mut self) {
102 0 : // Find x scale
103 0 : let segments = &self.storage.segments;
104 0 : let min_lsn = segments.iter().map(|s| s.lsn).fold(u64::MAX, std::cmp::min);
105 0 : let max_lsn = segments.iter().map(|s| s.lsn).fold(0, std::cmp::max);
106 0 :
107 0 : // Start with 1 pixel = 1 byte. Double the scale until it fits into the image
108 0 : let mut xscale = 1.0;
109 0 : while (max_lsn - min_lsn) as f32 / xscale > SVG_WIDTH {
110 0 : xscale *= 2.0;
111 0 : }
112 :
113 : // Layout the timelines on Y dimension.
114 : // TODO
115 0 : let mut y = 120.0;
116 0 : let mut branch_y_coordinates = Vec::new();
117 0 : for _branch in self.branches {
118 0 : branch_y_coordinates.push(y);
119 0 : y += 40.0;
120 0 : }
121 :
122 : // Calculate coordinates for each point
123 0 : let seg_coordinates = std::iter::zip(segments, self.seg_to_branch)
124 0 : .map(|(seg, (branch_id, _))| {
125 0 : let x = (seg.lsn - min_lsn) as f32 / xscale;
126 0 : let y = branch_y_coordinates[*branch_id];
127 0 : (x, y)
128 0 : })
129 0 : .collect();
130 0 :
131 0 : self.xscale = xscale;
132 0 : self.min_lsn = min_lsn;
133 0 : self.seg_coordinates = seg_coordinates;
134 0 : }
135 :
136 : /// Draws lines between points
137 0 : fn draw_seg_phase1(&self, seg_id: usize, result: &mut String) -> anyhow::Result<()> {
138 0 : let seg = &self.storage.segments[seg_id];
139 :
140 0 : let wal_bytes = if let Some(parent_id) = seg.parent {
141 0 : seg.lsn - self.storage.segments[parent_id].lsn
142 : } else {
143 0 : 0
144 : };
145 :
146 0 : let style = match self.sizes[seg_id].method {
147 0 : SegmentMethod::SnapshotHere => "stroke-width=\"1\" stroke=\"gray\"",
148 0 : SegmentMethod::Wal if seg.needed && wal_bytes > 0 => {
149 0 : "stroke-width=\"6\" stroke=\"black\""
150 : }
151 0 : SegmentMethod::Wal => "stroke-width=\"3\" stroke=\"black\"",
152 0 : SegmentMethod::Skipped => "stroke-width=\"1\" stroke=\"gray\"",
153 : };
154 0 : if let Some(parent_id) = seg.parent {
155 0 : let (x1, y1) = self.seg_coordinates[parent_id];
156 0 : let (x2, y2) = self.seg_coordinates[seg_id];
157 0 :
158 0 : writeln!(
159 0 : result,
160 0 : "<line x1=\"{x1}\" y1=\"{y1}\" x2=\"{x2}\" y2=\"{y2}\" {style}>",
161 0 : )?;
162 0 : writeln!(
163 0 : result,
164 0 : " <title>{wal_bytes} bytes of WAL (seg {seg_id})</title>"
165 0 : )?;
166 0 : writeln!(result, "</line>")?;
167 : } else {
168 : // draw a little dash to mark the starting point of this branch
169 0 : let (x, y) = self.seg_coordinates[seg_id];
170 0 : let (x1, y1) = (x, y - 5.0);
171 0 : let (x2, y2) = (x, y + 5.0);
172 0 :
173 0 : writeln!(
174 0 : result,
175 0 : "<line x1=\"{x1}\" y1=\"{y1}\" x2=\"{x2}\" y2=\"{y2}\" {style}>",
176 0 : )?;
177 0 : writeln!(result, " <title>(seg {seg_id})</title>")?;
178 0 : writeln!(result, "</line>")?;
179 : }
180 :
181 0 : Ok(())
182 0 : }
183 :
184 : /// Draw circles where snapshots are taken
185 0 : fn draw_seg_phase2(&self, seg_id: usize, result: &mut String) -> anyhow::Result<()> {
186 0 : let seg = &self.storage.segments[seg_id];
187 0 :
188 0 : // draw a snapshot point if it's needed
189 0 : let (coord_x, coord_y) = self.seg_coordinates[seg_id];
190 0 :
191 0 : let (_, kind) = &self.seg_to_branch[seg_id];
192 0 : if kind == &SvgBranchKind::Lease {
193 0 : let (x1, y1) = (coord_x, coord_y - 10.0);
194 0 : let (x2, y2) = (coord_x, coord_y + 10.0);
195 0 :
196 0 : let style = "stroke-width=\"3\" stroke=\"blue\"";
197 0 :
198 0 : writeln!(
199 0 : result,
200 0 : "<line x1=\"{x1}\" y1=\"{y1}\" x2=\"{x2}\" y2=\"{y2}\" {style}>",
201 0 : )?;
202 0 : writeln!(result, " <title>leased lsn at {}</title>", seg.lsn)?;
203 0 : writeln!(result, "</line>")?;
204 0 : }
205 :
206 0 : if self.sizes[seg_id].method == SegmentMethod::SnapshotHere {
207 0 : writeln!(
208 0 : result,
209 0 : "<circle cx=\"{coord_x}\" cy=\"{coord_y}\" r=\"5\" stroke=\"red\">",
210 0 : )?;
211 0 : writeln!(
212 0 : result,
213 0 : " <title>logical size {}</title>",
214 0 : seg.size.unwrap()
215 0 : )?;
216 0 : write!(result, "</circle>")?;
217 0 : }
218 :
219 0 : Ok(())
220 0 : }
221 : }
|