summaryrefslogtreecommitdiffstats
path: root/src/app/data_harvester/memory/general.rs
blob: 2afe119f07e7ba49f72c6abd87a124bd9e78f88a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! Data collection for memory via heim.

#[derive(Debug, Clone, Default)]
pub struct MemHarvest {
    pub mem_total_in_kib: u64,
    pub mem_used_in_kib: u64,
    pub use_percent: Option<f64>,
}

pub async fn get_mem_data(
    actually_get: bool,
) -> (
    crate::utils::error::Result<Option<MemHarvest>>,
    crate::utils::error::Result<Option<MemHarvest>>,
) {
    use futures::join;

    if !actually_get {
        (Ok(None), Ok(None))
    } else {
        join!(get_ram_data(), get_swap_data())
    }
}

pub async fn get_ram_data() -> crate::utils::error::Result<Option<MemHarvest>> {
    let (mem_total_in_kib, mem_used_in_kib) = {
        #[cfg(target_os = "linux")]
        {
            use smol::fs::read_to_string;
            let meminfo = read_to_string("/proc/meminfo").await?;

            // All values are in KiB by default.
            let mut mem_total = 0;
            let mut cached = 0;
            let mut s_reclaimable = 0;
            let mut shmem = 0;
            let mut buffers = 0;
            let mut mem_free = 0;

            let mut keys_read: u8 = 0;
            const TOTAL_KEYS_NEEDED: u8 = 6;

            for line in meminfo.lines() {
                if let Some((label, value)) = line.split_once(':') {
                    let to_write = match label {
                        "MemTotal" => &mut mem_total,
                        "MemFree" => &mut mem_free,
                        "Buffers" => &mut buffers,
                        "Cached" => &mut cached,
                        "Shmem" => &mut shmem,
                        "SReclaimable" => &mut s_reclaimable,
                        _ => {
                            continue;
                        }
                    };

                    if let Some((number, _unit)) = value.trim_start().split_once(' ') {
                        // Parse the value, remember it's in KiB!
                        if let Ok(number) = number.parse::<u64>() {
                            *to_write = number;

                            // We only need a few keys, so we can bail early.
                            keys_read += 1;
                            if keys_read == TOTAL_KEYS_NEEDED {
                                break;
                            }
                        }
                    }
                }
            }

            // Let's preface this by saying that memory usage calculations are... not straightforward.
            // There are conflicting implementations everywhere.
            //
            // Now that we've added this preface (mainly for future reference), the current implementation below for usage
            // is based on htop's calculation formula. See
            // https://github.com/htop-dev/htop/blob/976c6123f41492aaf613b9d172eef1842fb7b0a3/linux/LinuxProcessList.c#L1584
            // for implementation details as of writing.
            //
            // Another implementation, commonly used in other things, is to skip the shmem part of the calculation,
            // which matches gopsutil and stuff like free.

            let total = mem_total;
            let cached_mem = cached + s_reclaimable - shmem;
            let used_diff = mem_free + cached_mem + buffers;
            let used = if total >= used_diff {
                total - used_diff
            } else {
                total - mem_free
            };

            (total, used)
        }
        #[cfg(target_os = "macos")]
        {
            let memory = heim::memory::memory().await?;

            use heim::memory::os::macos::MemoryExt;
            use heim::units::information::kibibyte;
            (
                memory.total().get::<kibibyte>(),
                memory.active().get::<kibibyte>() + memory.wire().get::<kibibyte>(),
            )
        }
        #[cfg(target_os = "windows")]
        {
            let memory = heim::memory::memory().await?;

            use heim::units::information::kibibyte;
            let mem_total_in_kib = memory.total().get::<kibibyte>();
            (
                mem_total_in_kib,
                mem_total_in_kib - memory.available().get::<kibibyte>(),
            )
        }
    };

    Ok(Some(MemHarvest {
        mem_total_in_kib,
        mem_used_in_kib,
        use_percent: if mem_total_in_kib == 0 {
            None
        } else {
            Some(mem_used_in_kib as f64 / mem_total_in_kib as f64 * 100.0)
        },
    }))
}

pub async fn get_swap_data() -> crate::utils::error::Result<Option<MemHarvest>> {
    let memory = heim::memory::swap().await?;

    let (mem_total_in_kib, mem_used_in_kib) = {
        #[cfg(target_os = "linux")]
        {
            // Similar story to above - heim parses this information incorrectly as far as I can tell, so kilobytes = kibibytes here.
            use heim::units::information::kilobyte;
            (
                memory.total().get::<kilobyte>(),
                memory.used().get::<kilobyte>(),
            )
        }
        #[cfg(any(target_os = "windows", target_os = "macos"))]
        {
            use heim::units::information::kibibyte;
            (
                memory.total().get::<kibibyte>(),
                memory.used().get::<kibibyte>(),
            )
        }
    };

    Ok(Some(MemHarvest {
        mem_total_in_kib,
        mem_used_in_kib,
        use_percent: if mem_total_in_kib == 0 {
            None
        } else {
            Some(mem_used_in_kib as f64 / mem_total_in_kib as f64 * 100.0)
        },
    }))
}