Skip to main content

loro_internal/state/
analyzer.rs

1use crate::{change::Timestamp, LoroDoc};
2use loro_common::ContainerID;
3use rle::HasLength;
4use rustc_hash::FxHashMap;
5
6#[derive(Debug, Clone)]
7pub struct DocAnalysis {
8    pub containers: FxHashMap<ContainerID, ContainerAnalysisInfo>,
9}
10
11#[derive(Debug, Clone)]
12pub struct ContainerAnalysisInfo {
13    pub size: u32,
14    pub dropped: bool,
15    pub depth: u32,
16    pub ops_num: u32,
17    pub last_edit_time: Timestamp,
18}
19
20impl DocAnalysis {
21    pub fn analyze(doc: &LoroDoc) -> Self {
22        let mut ops_nums = FxHashMap::default();
23        let mut last_edit_time = FxHashMap::default();
24        {
25            let oplog = doc.oplog().lock();
26            oplog.change_store().visit_all_changes(&mut |c| {
27                for op in c.ops().iter() {
28                    let idx = op.container;
29                    let info = ops_nums.entry(idx).or_insert(0);
30                    *info += op.atom_len();
31
32                    let time = last_edit_time.entry(idx).or_insert(c.timestamp());
33                    if *time < c.timestamp() {
34                        *time = c.timestamp();
35                    }
36                }
37            });
38        }
39
40        let mut containers = FxHashMap::default();
41        let mut state = doc.app_state().lock();
42        let alive_containers = state
43            .get_all_alive_containers()
44            .expect("analyzing a valid document should resolve its container hierarchy");
45        for (idx, c) in state.iter_all_containers_mut() {
46            let ops_num = ops_nums.get(&idx).unwrap_or(&0);
47            let id = doc.arena().get_container_id(idx).unwrap();
48            let dropped = !alive_containers.contains(&id);
49            containers.insert(
50                id,
51                ContainerAnalysisInfo {
52                    depth: c.depth() as u32,
53                    dropped,
54                    size: c.encode().len() as u32,
55                    ops_num: *ops_num as u32,
56                    last_edit_time: *last_edit_time.get(&idx).unwrap_or(&0),
57                },
58            );
59        }
60
61        Self { containers }
62    }
63
64    #[allow(unused)]
65    pub fn len(&self) -> usize {
66        self.containers.len()
67    }
68
69    #[allow(unused)]
70    #[must_use]
71    pub fn is_empty(&self) -> bool {
72        self.len() == 0
73    }
74
75    pub fn dropped_len(&self) -> usize {
76        self.containers
77            .iter()
78            .filter(|(_, info)| info.dropped)
79            .count()
80    }
81
82    pub fn tiny_container_len(&self) -> usize {
83        self.containers
84            .iter()
85            .filter(|(_, info)| info.size < 128)
86            .count()
87    }
88
89    pub fn large_container_len(&self) -> usize {
90        self.containers
91            .iter()
92            .filter(|(_, info)| info.size >= 1024)
93            .count()
94    }
95}