reth_cli_commands/db/
stats.rs
1use crate::{common::CliNodeTypes, db::checksum::ChecksumViewer};
2use clap::Parser;
3use comfy_table::{Cell, Row, Table as ComfyTable};
4use eyre::WrapErr;
5use human_bytes::human_bytes;
6use itertools::Itertools;
7use reth_chainspec::EthereumHardforks;
8use reth_db::{mdbx, static_file::iter_static_files, DatabaseEnv};
9use reth_db_api::{database::Database, TableViewer, Tables};
10use reth_db_common::DbTool;
11use reth_fs_util as fs;
12use reth_node_builder::{NodePrimitives, NodeTypesWithDB, NodeTypesWithDBAdapter};
13use reth_node_core::dirs::{ChainPath, DataDirPath};
14use reth_provider::providers::{ProviderNodeTypes, StaticFileProvider};
15use reth_static_file_types::SegmentRangeInclusive;
16use std::{sync::Arc, time::Duration};
17
18#[derive(Parser, Debug)]
19pub struct Command {
21 #[arg(long, default_value_t = false)]
23 detailed_sizes: bool,
24
25 #[arg(long, default_value_t = false)]
27 detailed_segments: bool,
28
29 #[arg(long, default_value_t = false)]
36 checksum: bool,
37}
38
39impl Command {
40 pub fn execute<N: CliNodeTypes<ChainSpec: EthereumHardforks>>(
42 self,
43 data_dir: ChainPath<DataDirPath>,
44 tool: &DbTool<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>,
45 ) -> eyre::Result<()> {
46 if self.checksum {
47 let checksum_report = self.checksum_report(tool)?;
48 println!("{checksum_report}");
49 println!("\n");
50 }
51
52 let static_files_stats_table = self.static_files_stats_table::<N::Primitives>(data_dir)?;
53 println!("{static_files_stats_table}");
54
55 println!("\n");
56
57 let db_stats_table = self.db_stats_table(tool)?;
58 println!("{db_stats_table}");
59
60 Ok(())
61 }
62
63 fn db_stats_table<N: NodeTypesWithDB<DB = Arc<DatabaseEnv>>>(
64 &self,
65 tool: &DbTool<N>,
66 ) -> eyre::Result<ComfyTable> {
67 let mut table = ComfyTable::new();
68 table.load_preset(comfy_table::presets::ASCII_MARKDOWN);
69 table.set_header([
70 "Table Name",
71 "# Entries",
72 "Branch Pages",
73 "Leaf Pages",
74 "Overflow Pages",
75 "Total Size",
76 ]);
77
78 tool.provider_factory.db_ref().view(|tx| {
79 let mut db_tables = Tables::ALL.iter().map(|table| table.name()).collect::<Vec<_>>();
80 db_tables.sort();
81 let mut total_size = 0;
82 for db_table in db_tables {
83 let table_db = tx.inner.open_db(Some(db_table)).wrap_err("Could not open db.")?;
84
85 let stats = tx
86 .inner
87 .db_stat(&table_db)
88 .wrap_err(format!("Could not find table: {db_table}"))?;
89
90 let page_size = stats.page_size() as usize;
94 let leaf_pages = stats.leaf_pages();
95 let branch_pages = stats.branch_pages();
96 let overflow_pages = stats.overflow_pages();
97 let num_pages = leaf_pages + branch_pages + overflow_pages;
98 let table_size = page_size * num_pages;
99
100 total_size += table_size;
101 let mut row = Row::new();
102 row.add_cell(Cell::new(db_table))
103 .add_cell(Cell::new(stats.entries()))
104 .add_cell(Cell::new(branch_pages))
105 .add_cell(Cell::new(leaf_pages))
106 .add_cell(Cell::new(overflow_pages))
107 .add_cell(Cell::new(human_bytes(table_size as f64)));
108 table.add_row(row);
109 }
110
111 let max_widths = table.column_max_content_widths();
112 let mut separator = Row::new();
113 for width in max_widths {
114 separator.add_cell(Cell::new("-".repeat(width as usize)));
115 }
116 table.add_row(separator);
117
118 let mut row = Row::new();
119 row.add_cell(Cell::new("Tables"))
120 .add_cell(Cell::new(""))
121 .add_cell(Cell::new(""))
122 .add_cell(Cell::new(""))
123 .add_cell(Cell::new(""))
124 .add_cell(Cell::new(human_bytes(total_size as f64)));
125 table.add_row(row);
126
127 let freelist = tx.inner.env().freelist()?;
128 let pagesize = tx.inner.db_stat(&mdbx::Database::freelist_db())?.page_size() as usize;
129 let freelist_size = freelist * pagesize;
130
131 let mut row = Row::new();
132 row.add_cell(Cell::new("Freelist"))
133 .add_cell(Cell::new(freelist))
134 .add_cell(Cell::new(""))
135 .add_cell(Cell::new(""))
136 .add_cell(Cell::new(""))
137 .add_cell(Cell::new(human_bytes(freelist_size as f64)));
138 table.add_row(row);
139
140 Ok::<(), eyre::Report>(())
141 })??;
142
143 Ok(table)
144 }
145
146 fn static_files_stats_table<N: NodePrimitives>(
147 &self,
148 data_dir: ChainPath<DataDirPath>,
149 ) -> eyre::Result<ComfyTable> {
150 let mut table = ComfyTable::new();
151 table.load_preset(comfy_table::presets::ASCII_MARKDOWN);
152
153 if self.detailed_sizes {
154 table.set_header([
155 "Segment",
156 "Block Range",
157 "Transaction Range",
158 "Shape (columns x rows)",
159 "Data Size",
160 "Index Size",
161 "Offsets Size",
162 "Config Size",
163 "Total Size",
164 ]);
165 } else {
166 table.set_header([
167 "Segment",
168 "Block Range",
169 "Transaction Range",
170 "Shape (columns x rows)",
171 "Size",
172 ]);
173 }
174
175 let static_files = iter_static_files(&data_dir.static_files())?;
176 let static_file_provider =
177 StaticFileProvider::<N>::read_only(data_dir.static_files(), false)?;
178
179 let mut total_data_size = 0;
180 let mut total_index_size = 0;
181 let mut total_offsets_size = 0;
182 let mut total_config_size = 0;
183
184 for (segment, ranges) in static_files.into_iter().sorted_by_key(|(segment, _)| *segment) {
185 let (
186 mut segment_columns,
187 mut segment_rows,
188 mut segment_data_size,
189 mut segment_index_size,
190 mut segment_offsets_size,
191 mut segment_config_size,
192 ) = (0, 0, 0, 0, 0, 0);
193
194 for (block_range, tx_range) in &ranges {
195 let fixed_block_range = static_file_provider.find_fixed_range(block_range.start());
196 let jar_provider = static_file_provider
197 .get_segment_provider(segment, || Some(fixed_block_range), None)?
198 .ok_or_else(|| {
199 eyre::eyre!("Failed to get segment provider for segment: {}", segment)
200 })?;
201
202 let columns = jar_provider.columns();
203 let rows = jar_provider.rows();
204
205 let data_size = fs::metadata(jar_provider.data_path())
206 .map(|metadata| metadata.len())
207 .unwrap_or_default();
208 let index_size = fs::metadata(jar_provider.index_path())
209 .map(|metadata| metadata.len())
210 .unwrap_or_default();
211 let offsets_size = fs::metadata(jar_provider.offsets_path())
212 .map(|metadata| metadata.len())
213 .unwrap_or_default();
214 let config_size = fs::metadata(jar_provider.config_path())
215 .map(|metadata| metadata.len())
216 .unwrap_or_default();
217
218 if self.detailed_segments {
219 let mut row = Row::new();
220 row.add_cell(Cell::new(segment))
221 .add_cell(Cell::new(format!("{block_range}")))
222 .add_cell(Cell::new(
223 tx_range.map_or("N/A".to_string(), |tx_range| format!("{tx_range}")),
224 ))
225 .add_cell(Cell::new(format!("{columns} x {rows}")));
226 if self.detailed_sizes {
227 row.add_cell(Cell::new(human_bytes(data_size as f64)))
228 .add_cell(Cell::new(human_bytes(index_size as f64)))
229 .add_cell(Cell::new(human_bytes(offsets_size as f64)))
230 .add_cell(Cell::new(human_bytes(config_size as f64)));
231 }
232 row.add_cell(Cell::new(human_bytes(
233 (data_size + index_size + offsets_size + config_size) as f64,
234 )));
235 table.add_row(row);
236 } else {
237 if segment_columns > 0 {
238 assert_eq!(segment_columns, columns);
239 } else {
240 segment_columns = columns;
241 }
242 segment_rows += rows;
243 segment_data_size += data_size;
244 segment_index_size += index_size;
245 segment_offsets_size += offsets_size;
246 segment_config_size += config_size;
247 }
248
249 total_data_size += data_size;
250 total_index_size += index_size;
251 total_offsets_size += offsets_size;
252 total_config_size += config_size;
253
254 drop(jar_provider);
256
257 static_file_provider.remove_cached_provider(segment, fixed_block_range.end());
259 }
260
261 if !self.detailed_segments {
262 let first_ranges = ranges.first().expect("not empty list of ranges");
263 let last_ranges = ranges.last().expect("not empty list of ranges");
264
265 let block_range =
266 SegmentRangeInclusive::new(first_ranges.0.start(), last_ranges.0.end());
267
268 let tx_range = {
271 let start = ranges
272 .iter()
273 .find_map(|(_, tx_range)| tx_range.map(|r| r.start()))
274 .unwrap_or_default();
275 let end =
276 ranges.iter().rev().find_map(|(_, tx_range)| tx_range.map(|r| r.end()));
277 end.map(|end| SegmentRangeInclusive::new(start, end))
278 };
279
280 let mut row = Row::new();
281 row.add_cell(Cell::new(segment))
282 .add_cell(Cell::new(format!("{block_range}")))
283 .add_cell(Cell::new(
284 tx_range.map_or("N/A".to_string(), |tx_range| format!("{tx_range}")),
285 ))
286 .add_cell(Cell::new(format!("{segment_columns} x {segment_rows}")));
287 if self.detailed_sizes {
288 row.add_cell(Cell::new(human_bytes(segment_data_size as f64)))
289 .add_cell(Cell::new(human_bytes(segment_index_size as f64)))
290 .add_cell(Cell::new(human_bytes(segment_offsets_size as f64)))
291 .add_cell(Cell::new(human_bytes(segment_config_size as f64)));
292 }
293 row.add_cell(Cell::new(human_bytes(
294 (segment_data_size +
295 segment_index_size +
296 segment_offsets_size +
297 segment_config_size) as f64,
298 )));
299 table.add_row(row);
300 }
301 }
302
303 let max_widths = table.column_max_content_widths();
304 let mut separator = Row::new();
305 for width in max_widths {
306 separator.add_cell(Cell::new("-".repeat(width as usize)));
307 }
308 table.add_row(separator);
309
310 let mut row = Row::new();
311 row.add_cell(Cell::new("Total"))
312 .add_cell(Cell::new(""))
313 .add_cell(Cell::new(""))
314 .add_cell(Cell::new(""));
315 if self.detailed_sizes {
316 row.add_cell(Cell::new(human_bytes(total_data_size as f64)))
317 .add_cell(Cell::new(human_bytes(total_index_size as f64)))
318 .add_cell(Cell::new(human_bytes(total_offsets_size as f64)))
319 .add_cell(Cell::new(human_bytes(total_config_size as f64)));
320 }
321 row.add_cell(Cell::new(human_bytes(
322 (total_data_size + total_index_size + total_offsets_size + total_config_size) as f64,
323 )));
324 table.add_row(row);
325
326 Ok(table)
327 }
328
329 fn checksum_report<N: ProviderNodeTypes>(&self, tool: &DbTool<N>) -> eyre::Result<ComfyTable> {
330 let mut table = ComfyTable::new();
331 table.load_preset(comfy_table::presets::ASCII_MARKDOWN);
332 table.set_header(vec![Cell::new("Table"), Cell::new("Checksum"), Cell::new("Elapsed")]);
333
334 let db_tables = Tables::ALL;
335 let mut total_elapsed = Duration::default();
336
337 for &db_table in db_tables {
338 let (checksum, elapsed) = ChecksumViewer::new(tool).view_rt(db_table).unwrap();
339
340 total_elapsed += elapsed;
342
343 let mut row = Row::new();
345 row.add_cell(Cell::new(db_table));
346 row.add_cell(Cell::new(format!("{checksum:x}")));
347 row.add_cell(Cell::new(format!("{elapsed:?}")));
348 table.add_row(row);
349 }
350
351 let max_widths = table.column_max_content_widths();
353 let mut separator = Row::new();
354 for width in max_widths {
355 separator.add_cell(Cell::new("-".repeat(width as usize)));
356 }
357 table.add_row(separator);
358
359 let mut row = Row::new();
361 row.add_cell(Cell::new("Total elapsed"));
362 row.add_cell(Cell::new(""));
363 row.add_cell(Cell::new(format!("{total_elapsed:?}")));
364 table.add_row(row);
365
366 Ok(table)
367 }
368}