blob: aa7623baa3bfe18690f65b0220bbe3bf140cfc49 [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Balloon related control APIs.
use std::collections::VecDeque;
use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
pub use balloon_control::BalloonStats;
use balloon_control::BalloonTubeCommand;
pub use balloon_control::BalloonTubeResult;
pub use balloon_control::BalloonWS;
pub use balloon_control::WSBucket;
pub use balloon_control::VIRTIO_BALLOON_WS_MAX_NUM_BINS;
pub use balloon_control::VIRTIO_BALLOON_WS_MIN_NUM_BINS;
use base::Error as SysError;
use base::Tube;
use serde::Deserialize;
use serde::Serialize;
use crate::VmResponse;
// Balloon commands that are sent on the crosvm control socket.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum BalloonControlCommand {
/// Set the size of the VM's balloon.
Adjust {
num_bytes: u64,
},
Stats,
WorkingSet,
WorkingSetConfig {
bins: Vec<u64>,
refresh_threshold: u64,
report_threshold: u64,
},
}
fn do_send(tube: &Tube, cmd: &BalloonControlCommand) -> Option<VmResponse> {
match *cmd {
BalloonControlCommand::Adjust { num_bytes } => {
match tube.send(&BalloonTubeCommand::Adjust {
num_bytes,
allow_failure: false,
}) {
Ok(_) => Some(VmResponse::Ok),
Err(_) => Some(VmResponse::Err(SysError::last())),
}
}
BalloonControlCommand::WorkingSetConfig {
ref bins,
refresh_threshold,
report_threshold,
} => {
match tube.send(&BalloonTubeCommand::WorkingSetConfig {
bins: bins.clone(),
refresh_threshold,
report_threshold,
}) {
Ok(_) => Some(VmResponse::Ok),
Err(_) => Some(VmResponse::Err(SysError::last())),
}
}
BalloonControlCommand::Stats => match tube.send(&BalloonTubeCommand::Stats) {
Ok(_) => None,
Err(_) => Some(VmResponse::Err(SysError::last())),
},
BalloonControlCommand::WorkingSet => match tube.send(&BalloonTubeCommand::WorkingSet) {
Ok(_) => None,
Err(_) => Some(VmResponse::Err(SysError::last())),
},
}
}
/// Utility for multiplexing a balloon tube between multiple control tubes. Commands
/// are sent and processed serially.
pub struct BalloonTube {
tube: Tube,
pending_queue: VecDeque<(BalloonControlCommand, Option<usize>)>,
}
#[cfg(feature = "balloon")]
impl BalloonTube {
pub fn new(tube: Tube) -> Self {
BalloonTube {
tube,
pending_queue: VecDeque::new(),
}
}
/// Sends or queues the given command to this tube. Associates the
/// response with the given key.
pub fn send_cmd(
&mut self,
cmd: BalloonControlCommand,
key: Option<usize>,
) -> Option<VmResponse> {
if !self.pending_queue.is_empty() {
self.pending_queue.push_back((cmd, key));
return None;
}
let resp = do_send(&self.tube, &cmd);
if resp.is_none() {
self.pending_queue.push_back((cmd, key));
}
resp
}
/// Receives responses from the balloon tube, and returns them with
/// their assoicated keys.
pub fn recv(&mut self) -> Result<Vec<(VmResponse, usize)>> {
let mut responses = vec![];
let res = self
.tube
.recv::<BalloonTubeResult>()
.context("failed to read balloon tube")?;
if self.pending_queue.is_empty() {
bail!("Unexpected balloon tube result {:?}", res)
}
let resp = match (
&self.pending_queue.front().expect("entry disappeared").0,
res,
) {
(
BalloonControlCommand::Stats,
BalloonTubeResult::Stats {
stats,
balloon_actual,
},
) => VmResponse::BalloonStats {
stats,
balloon_actual,
},
(
BalloonControlCommand::WorkingSet,
BalloonTubeResult::WorkingSet { ws, balloon_actual },
) => VmResponse::BalloonWS { ws, balloon_actual },
(_, resp) => {
bail!("Unexpected balloon tube result {:?}", resp);
}
};
let key = self.pending_queue.pop_front().expect("entry disappeared").1;
if let Some(key) = key {
responses.push((resp, key))
}
while let Some((cmd, key)) = self.pending_queue.front() {
match do_send(&self.tube, cmd) {
Some(resp) => {
if let Some(key) = key {
responses.push((resp, *key));
}
self.pending_queue.pop_front();
}
None => break,
}
}
Ok(responses)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn balloon_device_respond_stats(device: &Tube) {
let BalloonTubeCommand::Stats = device.recv::<BalloonTubeCommand>().unwrap() else {
panic!("unexpected command");
};
device
.send(&BalloonTubeResult::Stats {
stats: BalloonStats::default(),
balloon_actual: 0,
})
.unwrap();
}
#[test]
fn test_stat_command() {
let (host, device) = Tube::pair().unwrap();
let mut balloon_tube = BalloonTube::new(host);
let resp = balloon_tube.send_cmd(BalloonControlCommand::Stats, Some(0xc0ffee));
assert!(resp.is_none());
balloon_device_respond_stats(&device);
let resp = balloon_tube.recv().unwrap();
assert_eq!(resp.len(), 1);
assert_eq!(resp[0].1, 0xc0ffee);
assert!(matches!(resp[0].0, VmResponse::BalloonStats { .. }));
}
#[test]
fn test_multiple_stat_command() {
let (host, device) = Tube::pair().unwrap();
let mut balloon_tube = BalloonTube::new(host);
let resp = balloon_tube.send_cmd(BalloonControlCommand::Stats, Some(0xc0ffee));
assert!(resp.is_none());
let resp = balloon_tube.send_cmd(BalloonControlCommand::Stats, Some(0xbadcafe));
assert!(resp.is_none());
balloon_device_respond_stats(&device);
let resp = balloon_tube.recv().unwrap();
assert_eq!(resp.len(), 1);
assert_eq!(resp[0].1, 0xc0ffee);
assert!(matches!(resp[0].0, VmResponse::BalloonStats { .. }));
balloon_device_respond_stats(&device);
let resp = balloon_tube.recv().unwrap();
assert_eq!(resp.len(), 1);
assert_eq!(resp[0].1, 0xbadcafe);
assert!(matches!(resp[0].0, VmResponse::BalloonStats { .. }));
}
#[test]
fn test_queued_adjust() {
let (host, device) = Tube::pair().unwrap();
let mut balloon_tube = BalloonTube::new(host);
let resp = balloon_tube.send_cmd(BalloonControlCommand::Stats, Some(0xc0ffee));
assert!(resp.is_none());
let resp = balloon_tube.send_cmd(
BalloonControlCommand::Adjust { num_bytes: 0 },
Some(0xbadcafe),
);
assert!(resp.is_none());
balloon_device_respond_stats(&device);
let resp = balloon_tube.recv().unwrap();
assert_eq!(resp.len(), 2);
assert_eq!(resp[0].1, 0xc0ffee);
assert!(matches!(resp[0].0, VmResponse::BalloonStats { .. }));
assert_eq!(resp[1].1, 0xbadcafe);
assert!(matches!(resp[1].0, VmResponse::Ok));
let cmd = device.recv::<BalloonTubeCommand>().unwrap();
assert!(matches!(cmd, BalloonTubeCommand::Adjust { .. }));
}
}