Line data Source code
1 : use std::fs::{self, File};
2 : use std::path::{Path, PathBuf};
3 :
4 : use anyhow::Result;
5 : use camino::{Utf8Path, Utf8PathBuf};
6 : use clap::Subcommand;
7 : use pageserver::context::{DownloadBehavior, RequestContext};
8 : use pageserver::task_mgr::TaskKind;
9 : use pageserver::tenant::storage_layer::{DeltaLayer, ImageLayer, delta_layer, image_layer};
10 : use pageserver::tenant::{TENANTS_SEGMENT_NAME, TIMELINES_SEGMENT_NAME};
11 : use pageserver::virtual_file::api::IoMode;
12 : use pageserver::{page_cache, virtual_file};
13 : use utils::id::{TenantId, TimelineId};
14 :
15 : use crate::layer_map_analyzer::parse_filename;
16 :
17 : #[derive(Subcommand)]
18 : pub(crate) enum LayerCmd {
19 : /// List all tenants and timelines under the pageserver path
20 : ///
21 : /// Example: `cargo run --bin pagectl layer list .neon/`
22 0 : List { path: PathBuf },
23 : /// List all layers of a given tenant and timeline
24 : ///
25 : /// Example: `cargo run --bin pagectl layer list .neon/`
26 : ListLayer {
27 0 : path: PathBuf,
28 0 : tenant: String,
29 0 : timeline: String,
30 : },
31 : /// Dump all information of a layer file
32 : DumpLayer {
33 0 : path: PathBuf,
34 0 : tenant: String,
35 0 : timeline: String,
36 : /// The id from list-layer command
37 0 : id: usize,
38 : },
39 : RewriteSummary {
40 0 : layer_file_path: Utf8PathBuf,
41 : #[clap(long)]
42 : new_tenant_id: Option<TenantId>,
43 : #[clap(long)]
44 : new_timeline_id: Option<TimelineId>,
45 : },
46 : }
47 :
48 0 : async fn read_delta_file(path: impl AsRef<Path>, ctx: &RequestContext) -> Result<()> {
49 0 : virtual_file::init(
50 0 : 10,
51 0 : virtual_file::api::IoEngineKind::StdFs,
52 0 : IoMode::preferred(),
53 0 : virtual_file::SyncMode::Sync,
54 0 : );
55 0 : page_cache::init(100);
56 0 : let path = Utf8Path::from_path(path.as_ref()).expect("non-Unicode path");
57 0 : let file = File::open(path)?;
58 0 : let delta_layer = DeltaLayer::new_for_path(path, file)?;
59 0 : delta_layer.dump(true, ctx).await?;
60 0 : Ok(())
61 0 : }
62 :
63 0 : async fn read_image_file(path: impl AsRef<Path>, ctx: &RequestContext) -> Result<()> {
64 0 : virtual_file::init(
65 0 : 10,
66 0 : virtual_file::api::IoEngineKind::StdFs,
67 0 : IoMode::preferred(),
68 0 : virtual_file::SyncMode::Sync,
69 0 : );
70 0 : page_cache::init(100);
71 0 : let path = Utf8Path::from_path(path.as_ref()).expect("non-Unicode path");
72 0 : let file = File::open(path)?;
73 0 : let image_layer = ImageLayer::new_for_path(path, file)?;
74 0 : image_layer.dump(true, ctx).await?;
75 0 : Ok(())
76 0 : }
77 :
78 0 : pub(crate) async fn main(cmd: &LayerCmd) -> Result<()> {
79 0 : let ctx =
80 0 : RequestContext::new(TaskKind::DebugTool, DownloadBehavior::Error).with_scope_debug_tools();
81 0 : match cmd {
82 0 : LayerCmd::List { path } => {
83 0 : for tenant in fs::read_dir(path.join(TENANTS_SEGMENT_NAME))? {
84 0 : let tenant = tenant?;
85 0 : if !tenant.file_type()?.is_dir() {
86 0 : continue;
87 0 : }
88 0 : println!("tenant {}", tenant.file_name().to_string_lossy());
89 0 : for timeline in fs::read_dir(tenant.path().join(TIMELINES_SEGMENT_NAME))? {
90 0 : let timeline = timeline?;
91 0 : if !timeline.file_type()?.is_dir() {
92 0 : continue;
93 0 : }
94 0 : println!("- timeline {}", timeline.file_name().to_string_lossy());
95 : }
96 : }
97 0 : Ok(())
98 : }
99 : LayerCmd::ListLayer {
100 0 : path,
101 0 : tenant,
102 0 : timeline,
103 0 : } => {
104 0 : let timeline_path = path
105 0 : .join(TENANTS_SEGMENT_NAME)
106 0 : .join(tenant)
107 0 : .join(TIMELINES_SEGMENT_NAME)
108 0 : .join(timeline);
109 0 : let mut idx = 0;
110 0 : for layer in fs::read_dir(timeline_path)? {
111 0 : let layer = layer?;
112 0 : if let Ok(layer_file) = parse_filename(&layer.file_name().into_string().unwrap()) {
113 0 : println!(
114 0 : "[{:3}] key:{}-{}\n lsn:{}-{}\n delta:{}",
115 0 : idx,
116 0 : layer_file.key_range.start,
117 0 : layer_file.key_range.end,
118 0 : layer_file.lsn_range.start,
119 0 : layer_file.lsn_range.end,
120 0 : layer_file.is_delta,
121 0 : );
122 0 : idx += 1;
123 0 : }
124 : }
125 0 : Ok(())
126 : }
127 : LayerCmd::DumpLayer {
128 0 : path,
129 0 : tenant,
130 0 : timeline,
131 0 : id,
132 0 : } => {
133 0 : let timeline_path = path
134 0 : .join("tenants")
135 0 : .join(tenant)
136 0 : .join("timelines")
137 0 : .join(timeline);
138 0 : let mut idx = 0;
139 0 : for layer in fs::read_dir(timeline_path)? {
140 0 : let layer = layer?;
141 0 : if let Ok(layer_file) = parse_filename(&layer.file_name().into_string().unwrap()) {
142 0 : if *id == idx {
143 : // TODO(chi): dedup code
144 0 : println!(
145 0 : "[{:3}] key:{}-{}\n lsn:{}-{}\n delta:{}",
146 0 : idx,
147 0 : layer_file.key_range.start,
148 0 : layer_file.key_range.end,
149 0 : layer_file.lsn_range.start,
150 0 : layer_file.lsn_range.end,
151 0 : layer_file.is_delta,
152 0 : );
153 0 :
154 0 : if layer_file.is_delta {
155 0 : read_delta_file(layer.path(), &ctx).await?;
156 : } else {
157 0 : read_image_file(layer.path(), &ctx).await?;
158 : }
159 :
160 0 : break;
161 0 : }
162 0 : idx += 1;
163 0 : }
164 : }
165 0 : Ok(())
166 : }
167 : LayerCmd::RewriteSummary {
168 0 : layer_file_path,
169 0 : new_tenant_id,
170 0 : new_timeline_id,
171 0 : } => {
172 0 : pageserver::virtual_file::init(
173 0 : 10,
174 0 : virtual_file::api::IoEngineKind::StdFs,
175 0 : IoMode::preferred(),
176 0 : virtual_file::SyncMode::Sync,
177 0 : );
178 0 : pageserver::page_cache::init(100);
179 0 :
180 0 : let ctx = RequestContext::new(TaskKind::DebugTool, DownloadBehavior::Error)
181 0 : .with_scope_debug_tools();
182 :
183 : macro_rules! rewrite_closure {
184 : ($($summary_ty:tt)*) => {{
185 : |summary| $($summary_ty)* {
186 : tenant_id: new_tenant_id.unwrap_or(summary.tenant_id),
187 : timeline_id: new_timeline_id.unwrap_or(summary.timeline_id),
188 : ..summary
189 0 : }
190 : }};
191 : }
192 :
193 0 : let res = ImageLayer::rewrite_summary(
194 0 : layer_file_path,
195 0 : rewrite_closure!(image_layer::Summary),
196 0 : &ctx,
197 0 : )
198 0 : .await;
199 0 : match res {
200 : Ok(()) => {
201 0 : println!("Successfully rewrote summary of image layer {layer_file_path}");
202 0 : return Ok(());
203 : }
204 0 : Err(image_layer::RewriteSummaryError::MagicMismatch) => (), // fallthrough
205 0 : Err(image_layer::RewriteSummaryError::Other(e)) => {
206 0 : return Err(e);
207 : }
208 : }
209 :
210 0 : let res = DeltaLayer::rewrite_summary(
211 0 : layer_file_path,
212 0 : rewrite_closure!(delta_layer::Summary),
213 0 : &ctx,
214 0 : )
215 0 : .await;
216 0 : match res {
217 : Ok(()) => {
218 0 : println!("Successfully rewrote summary of delta layer {layer_file_path}");
219 0 : return Ok(());
220 : }
221 0 : Err(delta_layer::RewriteSummaryError::MagicMismatch) => (), // fallthrough
222 0 : Err(delta_layer::RewriteSummaryError::Other(e)) => {
223 0 : return Err(e);
224 : }
225 : }
226 :
227 0 : anyhow::bail!("not an image or delta layer: {layer_file_path}");
228 : }
229 : }
230 0 : }
|