LCOV - differential code coverage report
Current view: top level - pageserver/ctl/src - draw_timeline_dir.rs (source / functions) Coverage Total Hit UBC
Current: f6946e90941b557c917ac98cd5a7e9506d180f3e.info Lines: 0.0 % 108 0 108
Current Date: 2023-10-19 02:04:12 Functions: 0.0 % 4 0 4
Baseline: c8637f37369098875162f194f92736355783b050.info
Baseline Date: 2023-10-18 20:25:20

           TLA  Line data    Source code
       1                 : //! A tool for visualizing the arrangement of layerfiles within a timeline.
       2                 : //!
       3                 : //! It reads filenames from stdin and prints a svg on stdout. The image is a plot in
       4                 : //! page-lsn space, where every delta layer is a rectangle and every image layer is a
       5                 : //! thick line. Legend:
       6                 : //! - The x axis (left to right) represents page index.
       7                 : //! - The y axis represents LSN, growing upwards.
       8                 : //!
       9                 : //! Coordinates in both axis are compressed for better readability.
      10                 : //! (see <https://medium.com/algorithms-digest/coordinate-compression-2fff95326fb>)
      11                 : //!
      12                 : //! Example use:
      13                 : //! ```bash
      14                 : //! $ ls test_output/test_pgbench\[neon-45-684\]/repo/tenants/$TENANT/timelines/$TIMELINE | \
      15                 : //! $   grep "__" | cargo run --release --bin pagectl draw-timeline-dir > out.svg
      16                 : //! $ firefox out.svg
      17                 : //! ```
      18                 : //!
      19                 : //! This API was chosen so that we can easily work with filenames extracted from ssh,
      20                 : //! or from pageserver log files.
      21                 : //!
      22                 : //! TODO Consider shipping this as a grafana panel plugin:
      23                 : //!      <https://grafana.com/tutorials/build-a-panel-plugin/>
      24                 : use anyhow::Result;
      25                 : use pageserver::repository::Key;
      26                 : use pageserver::METADATA_FILE_NAME;
      27                 : use std::cmp::Ordering;
      28                 : use std::io::{self, BufRead};
      29                 : use std::path::PathBuf;
      30                 : use std::str::FromStr;
      31                 : use std::{
      32                 :     collections::{BTreeMap, BTreeSet},
      33                 :     ops::Range,
      34                 : };
      35                 : use svg_fmt::{rectangle, rgb, BeginSvg, EndSvg, Fill, Stroke};
      36                 : use utils::{lsn::Lsn, project_git_version};
      37                 : 
      38                 : project_git_version!(GIT_VERSION);
      39                 : 
      40                 : // Map values to their compressed coordinate - the index the value
      41                 : // would have in a sorted and deduplicated list of all values.
      42 UBC           0 : fn build_coordinate_compression_map<T: Ord + Copy>(coords: Vec<T>) -> BTreeMap<T, usize> {
      43               0 :     let set: BTreeSet<T> = coords.into_iter().collect();
      44               0 : 
      45               0 :     let mut map: BTreeMap<T, usize> = BTreeMap::new();
      46               0 :     for (i, e) in set.iter().enumerate() {
      47               0 :         map.insert(*e, i);
      48               0 :     }
      49                 : 
      50               0 :     map
      51               0 : }
      52                 : 
      53               0 : fn parse_filename(name: &str) -> (Range<Key>, Range<Lsn>) {
      54               0 :     let split: Vec<&str> = name.split("__").collect();
      55               0 :     let keys: Vec<&str> = split[0].split('-').collect();
      56               0 :     let mut lsns: Vec<&str> = split[1].split('-').collect();
      57               0 :     if lsns.len() == 1 {
      58               0 :         lsns.push(lsns[0]);
      59               0 :     }
      60                 : 
      61               0 :     let keys = Key::from_hex(keys[0]).unwrap()..Key::from_hex(keys[1]).unwrap();
      62               0 :     let lsns = Lsn::from_hex(lsns[0]).unwrap()..Lsn::from_hex(lsns[1]).unwrap();
      63               0 :     (keys, lsns)
      64               0 : }
      65                 : 
      66               0 : pub fn main() -> Result<()> {
      67               0 :     // Parse layer filenames from stdin
      68               0 :     let mut ranges: Vec<(Range<Key>, Range<Lsn>)> = vec![];
      69               0 :     let stdin = io::stdin();
      70               0 :     for line in stdin.lock().lines() {
      71               0 :         let line = line.unwrap();
      72               0 :         let line = PathBuf::from_str(&line).unwrap();
      73               0 :         let filename = line.file_name().unwrap();
      74               0 :         let filename = filename.to_str().unwrap();
      75               0 :         if filename == METADATA_FILE_NAME {
      76                 :             // Don't try and parse "metadata" like a key-lsn range
      77               0 :             continue;
      78               0 :         }
      79               0 :         let range = parse_filename(filename);
      80               0 :         ranges.push(range);
      81                 :     }
      82                 : 
      83                 :     // Collect all coordinates
      84               0 :     let mut keys: Vec<Key> = vec![];
      85               0 :     let mut lsns: Vec<Lsn> = vec![];
      86               0 :     for (keyr, lsnr) in &ranges {
      87               0 :         keys.push(keyr.start);
      88               0 :         keys.push(keyr.end);
      89               0 :         lsns.push(lsnr.start);
      90               0 :         lsns.push(lsnr.end);
      91               0 :     }
      92                 : 
      93                 :     // Analyze
      94               0 :     let key_map = build_coordinate_compression_map(keys);
      95               0 :     let lsn_map = build_coordinate_compression_map(lsns);
      96               0 : 
      97               0 :     // Initialize stats
      98               0 :     let mut num_deltas = 0;
      99               0 :     let mut num_images = 0;
     100               0 : 
     101               0 :     // Draw
     102               0 :     let stretch = 3.0; // Stretch out vertically for better visibility
     103               0 :     println!(
     104               0 :         "{}",
     105               0 :         BeginSvg {
     106               0 :             w: key_map.len() as f32,
     107               0 :             h: stretch * lsn_map.len() as f32
     108               0 :         }
     109               0 :     );
     110               0 :     for (keyr, lsnr) in &ranges {
     111               0 :         let key_start = *key_map.get(&keyr.start).unwrap();
     112               0 :         let key_end = *key_map.get(&keyr.end).unwrap();
     113               0 :         let key_diff = key_end - key_start;
     114               0 :         let lsn_max = lsn_map.len();
     115               0 : 
     116               0 :         if key_start >= key_end {
     117               0 :             panic!("Invalid key range {}-{}", key_start, key_end);
     118               0 :         }
     119               0 : 
     120               0 :         let lsn_start = *lsn_map.get(&lsnr.start).unwrap();
     121               0 :         let lsn_end = *lsn_map.get(&lsnr.end).unwrap();
     122               0 : 
     123               0 :         let mut lsn_diff = (lsn_end - lsn_start) as f32;
     124               0 :         let mut fill = Fill::None;
     125               0 :         let mut ymargin = 0.05 * lsn_diff; // Height-dependent margin to disambiguate overlapping deltas
     126               0 :         let xmargin = 0.05; // Height-dependent margin to disambiguate overlapping deltas
     127               0 :         let mut lsn_offset = 0.0;
     128               0 : 
     129               0 :         // Fill in and thicken rectangle if it's an
     130               0 :         // image layer so that we can see it.
     131               0 :         match lsn_start.cmp(&lsn_end) {
     132               0 :             Ordering::Less => num_deltas += 1,
     133               0 :             Ordering::Equal => {
     134               0 :                 num_images += 1;
     135               0 :                 lsn_diff = 0.3;
     136               0 :                 lsn_offset = -lsn_diff / 2.0;
     137               0 :                 ymargin = 0.05;
     138               0 :                 fill = Fill::Color(rgb(0, 0, 0));
     139               0 :             }
     140               0 :             Ordering::Greater => panic!("Invalid lsn range {}-{}", lsn_start, lsn_end),
     141                 :         }
     142                 : 
     143               0 :         println!(
     144               0 :             "    {}",
     145               0 :             rectangle(
     146               0 :                 key_start as f32 + stretch * xmargin,
     147               0 :                 stretch * (lsn_max as f32 - (lsn_end as f32 - ymargin - lsn_offset)),
     148               0 :                 key_diff as f32 - stretch * 2.0 * xmargin,
     149               0 :                 stretch * (lsn_diff - 2.0 * ymargin)
     150               0 :             )
     151               0 :             .fill(fill)
     152               0 :             .stroke(Stroke::Color(rgb(0, 0, 0), 0.1))
     153               0 :             .border_radius(0.4)
     154               0 :         );
     155                 :     }
     156               0 :     println!("{}", EndSvg);
     157               0 : 
     158               0 :     eprintln!("num_images: {}", num_images);
     159               0 :     eprintln!("num_deltas: {}", num_deltas);
     160               0 : 
     161               0 :     Ok(())
     162               0 : }
        

Generated by: LCOV version 2.1-beta