Skip to content

Commit 94d2d8c

Browse files
authored
feat: add api to get information about diskspace usage of database. (jsonrpc method: get_storage_usage_report_string) (#7486)
new jsonrpc api: `get_storage_usage_report_string(accountId)` new rust API: `get_storage_usage(&context)`
1 parent ba3cad6 commit 94d2d8c

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed

deltachat-jsonrpc/src/api.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use deltachat::qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg};
3434
use deltachat::reaction::{get_msg_reactions, send_reaction};
3535
use deltachat::securejoin;
3636
use deltachat::stock_str::StockMessage;
37+
use deltachat::storage_usage::get_storage_usage;
3738
use deltachat::webxdc::StatusUpdateSerial;
3839
use deltachat::EventEmitter;
3940
use sanitize_filename::is_sanitized;
@@ -366,6 +367,13 @@ impl CommandApi {
366367
ctx.get_info().await
367368
}
368369

370+
/// Get storage usage report as formatted string
371+
async fn get_storage_usage_report_string(&self, account_id: u32) -> Result<String> {
372+
let ctx = self.get_context(account_id).await?;
373+
let storage_usage = get_storage_usage(&ctx).await?;
374+
Ok(storage_usage.to_string())
375+
}
376+
369377
/// Get the blob dir.
370378
async fn get_blob_dir(&self, account_id: u32) -> Result<Option<String>> {
371379
let ctx = self.get_context(account_id).await?;

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ pub mod securejoin;
8989
mod simplify;
9090
mod smtp;
9191
pub mod stock_str;
92+
pub mod storage_usage;
9293
mod sync;
9394
mod timesmearing;
9495
mod token;

src/storage_usage.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//! Module to collect and display Disk Space Usage of a Profile.
2+
use crate::{context::Context, message::MsgId};
3+
use anyhow::Result;
4+
use humansize::{BINARY, format_size};
5+
6+
/// Storage Usage Report
7+
/// Useful for debugging space usage problems in the deltachat database.
8+
#[derive(Debug)]
9+
pub struct StorageUsage {
10+
/// Total database size, subtract this from the backup size to estimate size of all blobs
11+
pub db_size: usize,
12+
/// size and row count of the 10 biggest tables
13+
pub largest_tables: Vec<(String, usize, Option<usize>)>,
14+
/// count and total size of status updates
15+
/// for the 10 webxdc apps with the most size usage in status updates
16+
pub largest_webxdc_data: Vec<(MsgId, usize, usize)>,
17+
}
18+
19+
impl std::fmt::Display for StorageUsage {
20+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21+
writeln!(f, "Storage Usage:")?;
22+
let human_db_size = format_size(self.db_size, BINARY);
23+
writeln!(f, "[Database Size]: {human_db_size}")?;
24+
writeln!(f, "[Largest Tables]:")?;
25+
for (name, size, row_count) in &self.largest_tables {
26+
let human_table_size = format_size(*size, BINARY);
27+
writeln!(
28+
f,
29+
" {name:<20} {human_table_size:>10}, {row_count:>6} rows",
30+
name = format!("{name}:"),
31+
row_count = row_count.map(|c| c.to_string()).unwrap_or("?".to_owned())
32+
)?;
33+
}
34+
writeln!(f, "[Webxdc With Biggest Status Update Space Usage]:")?;
35+
for (msg_id, size, update_count) in &self.largest_webxdc_data {
36+
let human_size = format_size(*size, BINARY);
37+
writeln!(
38+
f,
39+
" {msg_id:<8} {human_size:>10} across {update_count:>5} updates",
40+
msg_id = format!("{msg_id}:")
41+
)?;
42+
}
43+
Ok(())
44+
}
45+
}
46+
47+
/// Get storage usage information for the Context's database
48+
pub async fn get_storage_usage(ctx: &Context) -> Result<StorageUsage> {
49+
let page_size: usize = ctx
50+
.sql
51+
.query_get_value("PRAGMA page_size", ())
52+
.await?
53+
.unwrap_or_default();
54+
let page_count: usize = ctx
55+
.sql
56+
.query_get_value("PRAGMA page_count", ())
57+
.await?
58+
.unwrap_or_default();
59+
60+
let mut largest_tables = ctx
61+
.sql
62+
.query_map_vec(
63+
"SELECT name,
64+
SUM(pgsize) AS size
65+
FROM dbstat
66+
WHERE name IN (SELECT name FROM sqlite_master WHERE type='table')
67+
GROUP BY name ORDER BY size DESC LIMIT 10",
68+
(),
69+
|row| {
70+
let name: String = row.get(0)?;
71+
let size: usize = row.get(1)?;
72+
Ok((name, size, None))
73+
},
74+
)
75+
.await?;
76+
77+
for row in &mut largest_tables {
78+
let name = &row.0;
79+
let row_count: Result<Option<usize>> = ctx
80+
.sql
81+
// SECURITY: the table name comes from the db, not from the user
82+
.query_get_value(&format!("SELECT COUNT(*) FROM {name}"), ())
83+
.await;
84+
row.2 = row_count.unwrap_or_default();
85+
}
86+
87+
let largest_webxdc_data = ctx
88+
.sql
89+
.query_map_vec(
90+
"SELECT msg_id, SUM(length(update_item)) as size, COUNT(*) as update_count
91+
FROM msgs_status_updates
92+
GROUP BY msg_id ORDER BY size DESC LIMIT 10",
93+
(),
94+
|row| {
95+
let msg_id: MsgId = row.get(0)?;
96+
let size: usize = row.get(1)?;
97+
let count: usize = row.get(2)?;
98+
99+
Ok((msg_id, size, count))
100+
},
101+
)
102+
.await?;
103+
104+
Ok(StorageUsage {
105+
db_size: page_size * page_count,
106+
largest_tables,
107+
largest_webxdc_data,
108+
})
109+
}

0 commit comments

Comments
 (0)