deltachat/
storage_usage.rs

1//! Module to collect and display Disk Space Usage of a Profile.
2use crate::{context::Context, message::MsgId};
3use anyhow::Result;
4use humansize::{BINARY, format_size};
5
6/// Storage Usage Report
7/// Useful for debugging space usage problems in the deltachat database.
8#[derive(Debug)]
9pub 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
19impl 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
48pub 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}