vmm/devices/virtio/balloon/
persist.rs

1// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Defines the structures needed for saving/restoring balloon devices.
5
6use 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/// Information about the balloon config's that are saved
23/// at snapshot.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct BalloonConfigSpaceState {
26    num_pages: u32,
27    actual_pages: u32,
28}
29
30/// Information about the balloon stats that are saved
31/// at snapshot.
32#[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/// Information about the balloon that are saved
101/// at snapshot.
102#[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/// Auxiliary structure for creating a device when resuming from a snapshot.
113#[derive(Debug)]
114pub struct BalloonConstructorArgs {
115    /// Pointer to guest memory.
116    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        // We can safely create the balloon with arbitrary flags and
149        // num_pages because we will overwrite them after.
150        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        // As per the virtio 1.1 specification, the statistics queue
160        // should not exist if the statistics are not enabled.
161        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            // On restore allow the guest to reclaim pages
189            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            // Restore the stats descriptor.
195            balloon.set_stats_desc_index(state.stats_desc_index);
196
197            // Restart timer if needed.
198            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        // Create and save the balloon device.
224        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        // Deserialize and restore the balloon device.
231        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}