vmm/devices/virtio/balloon/
persist.rs1use std::sync::Arc;
7use std::time::Duration;
8
9use serde::{Deserialize, Serialize};
10use timerfd::{SetTimeFlags, TimerState};
11
12use super::*;
13use crate::devices::virtio::balloon::device::{BalloonStats, ConfigSpace, HintingState};
14use crate::devices::virtio::device::{ActiveState, DeviceState};
15use crate::devices::virtio::generated::virtio_ids::VIRTIO_ID_BALLOON;
16use crate::devices::virtio::persist::VirtioDeviceState;
17use crate::devices::virtio::queue::FIRECRACKER_MAX_QUEUE_SIZE;
18use crate::devices::virtio::transport::VirtioInterrupt;
19use crate::snapshot::Persist;
20use crate::vstate::memory::GuestMemoryMmap;
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct BalloonConfigSpaceState {
26 num_pages: u32,
27 actual_pages: u32,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct BalloonStatsState {
34 swap_in: Option<u64>,
35 swap_out: Option<u64>,
36 major_faults: Option<u64>,
37 minor_faults: Option<u64>,
38 free_memory: Option<u64>,
39 total_memory: Option<u64>,
40 available_memory: Option<u64>,
41 disk_caches: Option<u64>,
42 hugetlb_allocations: Option<u64>,
43 hugetlb_failures: Option<u64>,
44 oom_kill: Option<u64>,
45 alloc_stall: Option<u64>,
46 async_scan: Option<u64>,
47 direct_scan: Option<u64>,
48 async_reclaim: Option<u64>,
49 direct_reclaim: Option<u64>,
50}
51
52impl BalloonStatsState {
53 fn from_stats(stats: &BalloonStats) -> Self {
54 Self {
55 swap_in: stats.swap_in,
56 swap_out: stats.swap_out,
57 major_faults: stats.major_faults,
58 minor_faults: stats.minor_faults,
59 free_memory: stats.free_memory,
60 total_memory: stats.total_memory,
61 available_memory: stats.available_memory,
62 disk_caches: stats.disk_caches,
63 hugetlb_allocations: stats.hugetlb_allocations,
64 hugetlb_failures: stats.hugetlb_failures,
65 oom_kill: stats.oom_kill,
66 alloc_stall: stats.alloc_stall,
67 async_scan: stats.async_scan,
68 direct_scan: stats.direct_scan,
69 async_reclaim: stats.async_reclaim,
70 direct_reclaim: stats.direct_reclaim,
71 }
72 }
73
74 fn create_stats(&self) -> BalloonStats {
75 BalloonStats {
76 target_pages: 0,
77 actual_pages: 0,
78 target_mib: 0,
79 actual_mib: 0,
80 swap_in: self.swap_in,
81 swap_out: self.swap_out,
82 major_faults: self.major_faults,
83 minor_faults: self.minor_faults,
84 free_memory: self.free_memory,
85 total_memory: self.total_memory,
86 available_memory: self.available_memory,
87 disk_caches: self.disk_caches,
88 hugetlb_allocations: self.hugetlb_allocations,
89 hugetlb_failures: self.hugetlb_failures,
90 oom_kill: self.oom_kill,
91 alloc_stall: self.alloc_stall,
92 async_scan: self.async_scan,
93 direct_scan: self.direct_scan,
94 async_reclaim: self.async_reclaim,
95 direct_reclaim: self.direct_reclaim,
96 }
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct BalloonState {
104 stats_polling_interval_s: u16,
105 stats_desc_index: Option<u16>,
106 latest_stats: BalloonStatsState,
107 config_space: BalloonConfigSpaceState,
108 hinting_state: HintingState,
109 pub virtio_state: VirtioDeviceState,
110}
111
112#[derive(Debug)]
114pub struct BalloonConstructorArgs {
115 pub mem: GuestMemoryMmap,
117}
118
119impl Persist<'_> for Balloon {
120 type State = BalloonState;
121 type ConstructorArgs = BalloonConstructorArgs;
122 type Error = super::BalloonError;
123
124 fn save(&self) -> Self::State {
125 BalloonState {
126 stats_polling_interval_s: self.stats_polling_interval_s,
127 stats_desc_index: self.stats_desc_index,
128 latest_stats: BalloonStatsState::from_stats(&self.latest_stats),
129 hinting_state: self.hinting_state,
130 config_space: BalloonConfigSpaceState {
131 num_pages: self.config_space.num_pages,
132 actual_pages: self.config_space.actual_pages,
133 },
134 virtio_state: VirtioDeviceState::from_device(self),
135 }
136 }
137
138 fn restore(
139 constructor_args: Self::ConstructorArgs,
140 state: &Self::State,
141 ) -> Result<Self, Self::Error> {
142 let free_page_hinting =
143 state.virtio_state.avail_features & (1u64 << VIRTIO_BALLOON_F_FREE_PAGE_HINTING) != 0;
144
145 let free_page_reporting =
146 state.virtio_state.avail_features & (1u64 << VIRTIO_BALLOON_F_FREE_PAGE_REPORTING) != 0;
147
148 let mut balloon = Balloon::new(
151 0,
152 false,
153 state.stats_polling_interval_s,
154 free_page_hinting,
155 free_page_reporting,
156 )?;
157
158 let mut num_queues = BALLOON_MIN_NUM_QUEUES;
159 if state.stats_polling_interval_s > 0 {
162 num_queues += 1;
163 }
164
165 if free_page_hinting {
166 num_queues += 1;
167 }
168
169 if free_page_reporting {
170 num_queues += 1;
171 }
172
173 balloon.queues = state
174 .virtio_state
175 .build_queues_checked(
176 &constructor_args.mem,
177 VIRTIO_ID_BALLOON,
178 num_queues,
179 FIRECRACKER_MAX_QUEUE_SIZE,
180 )
181 .map_err(|_| Self::Error::QueueRestoreError)?;
182 balloon.avail_features = state.virtio_state.avail_features;
183 balloon.acked_features = state.virtio_state.acked_features;
184 balloon.latest_stats = state.latest_stats.create_stats();
185 balloon.config_space = ConfigSpace {
186 num_pages: state.config_space.num_pages,
187 actual_pages: state.config_space.actual_pages,
188 free_page_hint_cmd_id: FREE_PAGE_HINT_DONE,
190 };
191 balloon.hinting_state = state.hinting_state;
192
193 if state.virtio_state.activated && balloon.stats_enabled() {
194 balloon.set_stats_desc_index(state.stats_desc_index);
196
197 let timer_state = TimerState::Periodic {
199 current: Duration::from_secs(u64::from(state.stats_polling_interval_s)),
200 interval: Duration::from_secs(u64::from(state.stats_polling_interval_s)),
201 };
202 balloon
203 .stats_timer
204 .set_state(timer_state, SetTimeFlags::Default);
205 }
206
207 Ok(balloon)
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use crate::devices::virtio::device::VirtioDevice;
215 use crate::devices::virtio::test_utils::{default_interrupt, default_mem};
216 use crate::snapshot::Snapshot;
217
218 #[test]
219 fn test_persistence() {
220 let guest_mem = default_mem();
221 let mut mem = vec![0; 4096];
222
223 let balloon = Balloon::new(0x42, false, 2, false, false).unwrap();
225
226 Snapshot::new(balloon.save())
227 .save(&mut mem.as_mut_slice())
228 .unwrap();
229
230 let restored_balloon = Balloon::restore(
232 BalloonConstructorArgs { mem: guest_mem },
233 &Snapshot::load_without_crc_check(mem.as_slice())
234 .unwrap()
235 .data,
236 )
237 .unwrap();
238
239 assert_eq!(restored_balloon.device_type(), VIRTIO_ID_BALLOON);
240
241 assert_eq!(restored_balloon.acked_features, balloon.acked_features);
242 assert_eq!(restored_balloon.avail_features, balloon.avail_features);
243 assert_eq!(
244 restored_balloon.config_space.num_pages,
245 balloon.config_space.num_pages
246 );
247 assert_eq!(
248 restored_balloon.config_space.actual_pages,
249 balloon.config_space.actual_pages
250 );
251 assert_eq!(
252 restored_balloon.config_space.free_page_hint_cmd_id,
253 FREE_PAGE_HINT_DONE
254 );
255 assert_eq!(restored_balloon.queues(), balloon.queues());
256 assert!(!restored_balloon.is_activated());
257 assert!(!balloon.is_activated());
258
259 assert_eq!(
260 restored_balloon.stats_polling_interval_s,
261 balloon.stats_polling_interval_s
262 );
263 assert_eq!(restored_balloon.stats_desc_index, balloon.stats_desc_index);
264 assert_eq!(restored_balloon.latest_stats, balloon.latest_stats);
265 }
266}