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