TLA Line data Source code
1 : use std::path::{Path, PathBuf};
2 :
3 : use anyhow::Result;
4 : use camino::Utf8Path;
5 : use clap::Subcommand;
6 : use pageserver::context::{DownloadBehavior, RequestContext};
7 : use pageserver::task_mgr::TaskKind;
8 : use pageserver::tenant::block_io::BlockCursor;
9 : use pageserver::tenant::disk_btree::DiskBtreeReader;
10 : use pageserver::tenant::storage_layer::delta_layer::{BlobRef, Summary};
11 : use pageserver::tenant::{TENANTS_SEGMENT_NAME, TIMELINES_SEGMENT_NAME};
12 : use pageserver::{page_cache, virtual_file};
13 : use pageserver::{
14 : repository::{Key, KEY_SIZE},
15 : tenant::{
16 : block_io::FileBlockReader, disk_btree::VisitDirection,
17 : storage_layer::delta_layer::DELTA_KEY_SIZE,
18 : },
19 : virtual_file::VirtualFile,
20 : };
21 : use std::fs;
22 : use utils::bin_ser::BeSer;
23 :
24 : use crate::layer_map_analyzer::parse_filename;
25 :
26 UBC 0 : #[derive(Subcommand)]
27 : pub(crate) enum LayerCmd {
28 : /// List all tenants and timelines under the pageserver path
29 : ///
30 : /// Example: `cargo run --bin pagectl layer list .neon/`
31 0 : List { path: PathBuf },
32 : /// List all layers of a given tenant and timeline
33 : ///
34 : /// Example: `cargo run --bin pagectl layer list .neon/`
35 : ListLayer {
36 0 : path: PathBuf,
37 0 : tenant: String,
38 0 : timeline: String,
39 : },
40 : /// Dump all information of a layer file
41 : DumpLayer {
42 0 : path: PathBuf,
43 0 : tenant: String,
44 0 : timeline: String,
45 : /// The id from list-layer command
46 0 : id: usize,
47 0 : },
48 : }
49 :
50 0 : async fn read_delta_file(path: impl AsRef<Path>, ctx: &RequestContext) -> Result<()> {
51 0 : let path = Utf8Path::from_path(path.as_ref()).expect("non-Unicode path");
52 0 : virtual_file::init(10);
53 0 : page_cache::init(100);
54 0 : let file = FileBlockReader::new(VirtualFile::open(path).await?);
55 0 : let summary_blk = file.read_blk(0, ctx).await?;
56 0 : let actual_summary = Summary::des_prefix(summary_blk.as_ref())?;
57 0 : let tree_reader = DiskBtreeReader::<_, DELTA_KEY_SIZE>::new(
58 0 : actual_summary.index_start_blk,
59 0 : actual_summary.index_root_blk,
60 0 : &file,
61 0 : );
62 0 : // TODO(chi): dedup w/ `delta_layer.rs` by exposing the API.
63 0 : let mut all = vec![];
64 0 : tree_reader
65 0 : .visit(
66 0 : &[0u8; DELTA_KEY_SIZE],
67 0 : VisitDirection::Forwards,
68 0 : |key, value_offset| {
69 0 : let curr = Key::from_slice(&key[..KEY_SIZE]);
70 0 : all.push((curr, BlobRef(value_offset)));
71 0 : true
72 0 : },
73 0 : ctx,
74 0 : )
75 0 : .await?;
76 0 : let cursor = BlockCursor::new_fileblockreader(&file);
77 0 : for (k, v) in all {
78 0 : let value = cursor.read_blob(v.pos(), ctx).await?;
79 0 : println!("key:{} value_len:{}", k, value.len());
80 : }
81 : // TODO(chi): special handling for last key?
82 0 : Ok(())
83 0 : }
84 :
85 0 : pub(crate) async fn main(cmd: &LayerCmd) -> Result<()> {
86 0 : let ctx = RequestContext::new(TaskKind::DebugTool, DownloadBehavior::Error);
87 0 : match cmd {
88 0 : LayerCmd::List { path } => {
89 0 : for tenant in fs::read_dir(path.join(TENANTS_SEGMENT_NAME))? {
90 0 : let tenant = tenant?;
91 0 : if !tenant.file_type()?.is_dir() {
92 0 : continue;
93 0 : }
94 0 : println!("tenant {}", tenant.file_name().to_string_lossy());
95 0 : for timeline in fs::read_dir(tenant.path().join(TIMELINES_SEGMENT_NAME))? {
96 0 : let timeline = timeline?;
97 0 : if !timeline.file_type()?.is_dir() {
98 0 : continue;
99 0 : }
100 0 : println!("- timeline {}", timeline.file_name().to_string_lossy());
101 : }
102 : }
103 : }
104 : LayerCmd::ListLayer {
105 0 : path,
106 0 : tenant,
107 0 : timeline,
108 0 : } => {
109 0 : let timeline_path = path
110 0 : .join(TENANTS_SEGMENT_NAME)
111 0 : .join(tenant)
112 0 : .join(TIMELINES_SEGMENT_NAME)
113 0 : .join(timeline);
114 0 : let mut idx = 0;
115 0 : for layer in fs::read_dir(timeline_path)? {
116 0 : let layer = layer?;
117 0 : if let Some(layer_file) = parse_filename(&layer.file_name().into_string().unwrap())
118 0 : {
119 0 : println!(
120 0 : "[{:3}] key:{}-{}\n lsn:{}-{}\n delta:{}",
121 0 : idx,
122 0 : layer_file.key_range.start,
123 0 : layer_file.key_range.end,
124 0 : layer_file.lsn_range.start,
125 0 : layer_file.lsn_range.end,
126 0 : layer_file.is_delta,
127 0 : );
128 0 : idx += 1;
129 0 : }
130 : }
131 : }
132 : LayerCmd::DumpLayer {
133 0 : path,
134 0 : tenant,
135 0 : timeline,
136 0 : id,
137 0 : } => {
138 0 : let timeline_path = path
139 0 : .join("tenants")
140 0 : .join(tenant)
141 0 : .join("timelines")
142 0 : .join(timeline);
143 0 : let mut idx = 0;
144 0 : for layer in fs::read_dir(timeline_path)? {
145 0 : let layer = layer?;
146 0 : if let Some(layer_file) = parse_filename(&layer.file_name().into_string().unwrap())
147 : {
148 0 : if *id == idx {
149 : // TODO(chi): dedup code
150 0 : println!(
151 0 : "[{:3}] key:{}-{}\n lsn:{}-{}\n delta:{}",
152 0 : idx,
153 0 : layer_file.key_range.start,
154 0 : layer_file.key_range.end,
155 0 : layer_file.lsn_range.start,
156 0 : layer_file.lsn_range.end,
157 0 : layer_file.is_delta,
158 0 : );
159 0 :
160 0 : if layer_file.is_delta {
161 0 : read_delta_file(layer.path(), &ctx).await?;
162 : } else {
163 0 : anyhow::bail!("not supported yet :(");
164 : }
165 :
166 0 : break;
167 0 : }
168 0 : idx += 1;
169 0 : }
170 : }
171 : }
172 : }
173 0 : Ok(())
174 0 : }
|