dockworker/
stats.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// response type of containers/stats api
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub struct Stats {
7    pub id: String,
8    pub name: String,
9    pub read: String,
10    pub networks: Option<HashMap<String, Network>>,
11    #[serde(with = "format::memory_stats")]
12    pub memory_stats: Option<MemoryStats>,
13    pub cpu_stats: CpuStats,
14    /// The precpu_stats is the CPU statistic of the previous read, and is used to calculate the CPU usage percentage.
15    /// It is not an exact copy of the cpu_stats field.
16    pub precpu_stats: CpuStats,
17    pub blkio_stats: BlkioStats,
18    /// The number of pids in the cgroup
19    pub pids_stats: PidsStats,
20}
21
22impl Stats {
23    pub fn used_memory(&self) -> Option<u64> {
24        self.memory_stats
25            .as_ref()
26            .map(|mem| mem.usage - mem.stats.cache)
27    }
28    pub fn available_memory(&self) -> Option<u64> {
29        self.memory_stats.as_ref().map(|mem| mem.limit)
30    }
31    /// memory usage %
32    pub fn memory_usage(&self) -> Option<f64> {
33        if let (Some(used_memory), Some(available_memory)) =
34            (self.used_memory(), self.available_memory())
35        {
36            Some((used_memory as f64 / available_memory as f64) * 100.0)
37        } else {
38            None
39        }
40    }
41    pub fn cpu_delta(&self) -> u64 {
42        self.cpu_stats.cpu_usage.total_usage - self.precpu_stats.cpu_usage.total_usage
43    }
44    pub fn system_cpu_delta(&self) -> Option<u64> {
45        if let (Some(cpu), Some(pre)) = (
46            self.cpu_stats.system_cpu_usage,
47            self.precpu_stats.system_cpu_usage,
48        ) {
49            Some(cpu - pre)
50        } else {
51            None
52        }
53    }
54    /// If either `precpu_stats.online_cpus` or `cpu_stats.online_cpus` is nil then for
55    /// compatibility with older daemons the length of the corresponding `cpu_usage.percpu_usage` array should be used.
56    pub fn number_cpus(&self) -> u64 {
57        if let Some(cpus) = self.cpu_stats.online_cpus {
58            cpus
59        } else {
60            let empty = &[];
61            self.cpu_stats
62                .cpu_usage
63                .percpu_usage
64                .as_deref()
65                .unwrap_or(empty)
66                .len() as u64
67        }
68    }
69    /// cpu usage %
70    pub fn cpu_usage(&self) -> Option<f64> {
71        self.system_cpu_delta().map(|system_cpu_delta| {
72            (self.cpu_delta() as f64 / system_cpu_delta as f64) * self.number_cpus() as f64 * 100.0
73        })
74    }
75}
76
77#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)]
78pub struct Network {
79    pub rx_dropped: u64,
80    pub rx_bytes: u64,
81    pub rx_errors: u64,
82    pub tx_packets: u64,
83    pub tx_dropped: u64,
84    pub rx_packets: u64,
85    pub tx_errors: u64,
86    pub tx_bytes: u64,
87}
88
89#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)]
90pub struct MemoryStats {
91    pub max_usage: u64,
92    pub usage: u64,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub failcnt: Option<u64>,
95    pub limit: u64,
96    pub stats: MemoryStat,
97}
98
99#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)]
100pub struct MemoryStat {
101    pub total_pgmajfault: u64,
102    pub cache: u64,
103    pub mapped_file: u64,
104    pub total_inactive_file: u64,
105    pub pgpgout: u64,
106    pub rss: u64,
107    pub total_mapped_file: u64,
108    pub writeback: u64,
109    pub unevictable: u64,
110    pub pgpgin: u64,
111    pub total_unevictable: u64,
112    pub pgmajfault: u64,
113    pub total_rss: u64,
114    pub total_rss_huge: u64,
115    pub total_writeback: u64,
116    pub total_inactive_anon: u64,
117    pub rss_huge: u64,
118    pub hierarchical_memory_limit: u64,
119    pub total_pgfault: u64,
120    pub total_active_file: u64,
121    pub active_anon: u64,
122    pub total_active_anon: u64,
123    pub total_pgpgout: u64,
124    pub total_cache: u64,
125    pub inactive_anon: u64,
126    pub active_file: u64,
127    pub pgfault: u64,
128    pub inactive_file: u64,
129    pub total_pgpgin: u64,
130}
131
132#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)]
133pub struct CpuStats {
134    pub cpu_usage: CpuUsage,
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub system_cpu_usage: Option<u64>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub online_cpus: Option<u64>,
139    pub throttling_data: ThrottlingData,
140}
141
142#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)]
143pub struct CpuUsage {
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub percpu_usage: Option<Vec<u64>>,
146    pub usage_in_usermode: u64,
147    pub total_usage: u64,
148    pub usage_in_kernelmode: u64,
149}
150
151#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)]
152pub struct ThrottlingData {
153    pub periods: u64,
154    pub throttled_periods: u64,
155    pub throttled_time: u64,
156}
157
158#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)]
159pub struct BlkioStats {
160    pub io_service_bytes_recursive: Option<Vec<BlkioStat>>,
161    pub io_serviced_recursive: Option<Vec<BlkioStat>>,
162    pub io_queue_recursive: Option<Vec<BlkioStat>>,
163    pub io_service_time_recursive: Option<Vec<BlkioStat>>,
164    pub io_wait_time_recursive: Option<Vec<BlkioStat>>,
165    pub io_merged_recursive: Option<Vec<BlkioStat>>,
166    pub io_time_recursive: Option<Vec<BlkioStat>>,
167    pub sectors_recursive: Option<Vec<BlkioStat>>,
168}
169
170#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)]
171pub struct BlkioStat {
172    pub major: u64,
173    pub minor: u64,
174    pub op: String,
175    pub value: u64,
176}
177
178#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)]
179pub struct PidsStats {
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub current: Option<u64>,
182}
183
184mod format {
185    use super::*;
186    use serde::de::{DeserializeOwned, Deserializer};
187    use serde::ser::{Serialize, Serializer};
188
189    pub mod memory_stats {
190        use super::*;
191
192        #[derive(Debug, Serialize, Deserialize)]
193        #[serde(untagged)]
194        enum Plus1<T> {
195            Item(T),
196            Empty {},
197        }
198
199        impl<T> From<Plus1<T>> for Option<T> {
200            fn from(value: Plus1<T>) -> Option<T> {
201                match value {
202                    Plus1::Item(t) => Some(t),
203                    Plus1::Empty {} => None,
204                }
205            }
206        }
207
208        impl<T> From<Option<T>> for Plus1<T> {
209            fn from(value: Option<T>) -> Plus1<T> {
210                match value {
211                    Option::Some(t) => Plus1::Item(t),
212                    Option::None => Plus1::Empty {},
213                }
214            }
215        }
216
217        pub fn deserialize<'de, D, T>(de: D) -> std::result::Result<Option<T>, D::Error>
218        where
219            D: Deserializer<'de>,
220            T: DeserializeOwned,
221        {
222            Plus1::<T>::deserialize(de).map(Into::into)
223        }
224
225        pub fn serialize<T, S>(t: &Option<T>, se: S) -> std::result::Result<S::Ok, S::Error>
226        where
227            S: Serializer,
228            T: Serialize,
229        {
230            Into::<Plus1<&T>>::into(t.as_ref()).serialize(se)
231        }
232    }
233}