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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// Copyright (c) Facebook, Inc. and its affiliates.
use anyhow::{anyhow, Result};
use log::error;
use serde::{Deserialize, Serialize};
use std::fmt::Write as FmtWrite;
use std::fs;
use std::io::Write;

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(default)]
pub struct IoCostModelParams {
    pub rbps: u64,
    pub rseqiops: u64,
    pub rrandiops: u64,
    pub wbps: u64,
    pub wseqiops: u64,
    pub wrandiops: u64,
}

impl std::fmt::Display for IoCostModelParams {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "rbps={} rseqiops={} rrandiops={} wbps={} wseqiops={} wrandiops={}",
            self.rbps, self.rseqiops, self.rrandiops, self.wbps, self.wseqiops, self.wrandiops
        )
    }
}

impl std::ops::Mul<f64> for IoCostModelParams {
    type Output = Self;

    fn mul(self, rhs: f64) -> Self {
        let mul = |u: u64| (u as f64 * rhs).round() as u64;
        Self {
            rbps: mul(self.rbps),
            rseqiops: mul(self.rseqiops),
            rrandiops: mul(self.rrandiops),
            wbps: mul(self.wbps),
            wseqiops: mul(self.wseqiops),
            wrandiops: mul(self.wrandiops),
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(default)]
pub struct IoCostQoSParams {
    pub rpct: f64,
    pub rlat: u64,
    pub wpct: f64,
    pub wlat: u64,
    pub min: f64,
    pub max: f64,
}

impl std::fmt::Display for IoCostQoSParams {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "rpct={:.2} rlat={} wpct={:.2} wlat={} min={:.2} max={:.2}",
            self.rpct, self.rlat, self.wpct, self.wlat, self.min, self.max
        )
    }
}

impl IoCostQoSParams {
    /// The kernel reads only two digits after the decimal point. Let's
    /// rinse the floats through formatting and parsing so that they can be
    /// tested for equality with values read from kernel.
    pub fn sanitize(&mut self) {
        self.rpct = format!("{:.2}", self.rpct).parse::<f64>().unwrap();
        self.wpct = format!("{:.2}", self.wpct).parse::<f64>().unwrap();
        self.min = format!("{:.2}", self.min).parse::<f64>().unwrap();
        self.max = format!("{:.2}", self.max).parse::<f64>().unwrap();
    }
}

/// Save /sys/fs/cgroup/io.cost.model,qos and restore them on drop.
#[derive(Default)]
pub struct IoCostSysSave {
    pub devnr: (u32, u32),
    pub enable: bool,
    pub model_ctrl_user: bool,
    pub qos_ctrl_user: bool,
    pub model: IoCostModelParams,
    pub qos: IoCostQoSParams,
}

impl IoCostSysSave {
    pub fn read_from_sys(devnr: (u32, u32)) -> Result<Self> {
        let model = super::read_cgroup_nested_keyed_file("/sys/fs/cgroup/io.cost.model")
            .map_err(|e| anyhow!("failed to read io.cost.model ({})", &e))?;
        let qos = super::read_cgroup_nested_keyed_file("/sys/fs/cgroup/io.cost.qos")
            .map_err(|e| anyhow!("failed to read io.cost.model ({})", &e))?;
        let devnr_str = format!("{}:{}", devnr.0, devnr.1);

        let mut params = IoCostSysSave::default();
        params.devnr = devnr;

        let model = match model.get(&devnr_str) {
            Some(v) => v,
            None => return Ok(params),
        };
        let qos = qos.get(&devnr_str).ok_or(anyhow!(
            "io.cost.qos doesn't contain entry for {}",
            &devnr_str
        ))?;

        params.enable = qos["enable"].parse::<u32>()? > 0;
        params.model_ctrl_user = model["ctrl"] == "user";
        params.qos_ctrl_user = qos["ctrl"] == "user";

        params.model.rbps = model["rbps"].parse::<u64>()?;
        params.model.rseqiops = model["rseqiops"].parse::<u64>()?;
        params.model.rrandiops = model["rrandiops"].parse::<u64>()?;
        params.model.wbps = model["wbps"].parse::<u64>()?;
        params.model.wseqiops = model["wseqiops"].parse::<u64>()?;
        params.model.wrandiops = model["wrandiops"].parse::<u64>()?;

        params.qos.rpct = qos["rpct"].parse::<f64>()?;
        params.qos.rlat = qos["rlat"].parse::<u64>()?;
        params.qos.wpct = qos["wpct"].parse::<f64>()?;
        params.qos.wlat = qos["wlat"].parse::<u64>()?;
        params.qos.min = qos["min"].parse::<f64>()?;
        params.qos.max = qos["max"].parse::<f64>()?;

        Ok(params)
    }

    pub fn write_to_sys(&self) -> Result<()> {
        let devnr_str = format!("{}:{}", self.devnr.0, self.devnr.1);
        let model = match self.model_ctrl_user {
            false => format!("{} ctrl=auto", &devnr_str),
            true => format!(
                "{} ctrl=user rbps={} rseqiops={} rrandiops={} wbps={} wseqiops={} wrandiops={}",
                &devnr_str,
                self.model.rbps,
                self.model.rseqiops,
                self.model.rrandiops,
                self.model.wbps,
                self.model.wseqiops,
                self.model.wrandiops
            ),
        };
        let mut qos = format!("{} enable={} ", &devnr_str, if self.enable { 1 } else { 0 });
        match self.qos_ctrl_user {
            false => write!(qos, "ctrl=auto").unwrap(),
            true => write!(
                qos,
                "ctrl=user rpct={} rlat={} wpct={} wlat={} min={} max={}",
                self.qos.rpct,
                self.qos.rlat,
                self.qos.wpct,
                self.qos.wlat,
                self.qos.min,
                self.qos.max
            )
            .unwrap(),
        }

        fs::OpenOptions::new()
            .write(true)
            .open("/sys/fs/cgroup/io.cost.model")?
            .write_all(model.as_bytes())?;
        fs::OpenOptions::new()
            .write(true)
            .open("/sys/fs/cgroup/io.cost.qos")?
            .write_all(qos.as_bytes())?;
        Ok(())
    }
}

impl Drop for IoCostSysSave {
    fn drop(&mut self) {
        if let Err(e) = self.write_to_sys() {
            error!("Failed to restore io.cost.model,qos ({})", &e);
        }
    }
}