rd_util/
iocost.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2use anyhow::{anyhow, Result};
3use log::error;
4use serde::{Deserialize, Serialize};
5use std::fmt::Write as FmtWrite;
6use std::fs;
7use std::io::Write;
8
9#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
10#[serde(default)]
11pub struct IoCostModelParams {
12    pub rbps: u64,
13    pub rseqiops: u64,
14    pub rrandiops: u64,
15    pub wbps: u64,
16    pub wseqiops: u64,
17    pub wrandiops: u64,
18}
19
20impl std::fmt::Display for IoCostModelParams {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        write!(
23            f,
24            "rbps={} rseqiops={} rrandiops={} wbps={} wseqiops={} wrandiops={}",
25            self.rbps, self.rseqiops, self.rrandiops, self.wbps, self.wseqiops, self.wrandiops
26        )
27    }
28}
29
30impl std::ops::Mul<f64> for IoCostModelParams {
31    type Output = Self;
32
33    fn mul(self, rhs: f64) -> Self {
34        let mul = |u: u64| (u as f64 * rhs).round() as u64;
35        Self {
36            rbps: mul(self.rbps),
37            rseqiops: mul(self.rseqiops),
38            rrandiops: mul(self.rrandiops),
39            wbps: mul(self.wbps),
40            wseqiops: mul(self.wseqiops),
41            wrandiops: mul(self.wrandiops),
42        }
43    }
44}
45
46#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
47#[serde(default)]
48pub struct IoCostQoSParams {
49    pub rpct: f64,
50    pub rlat: u64,
51    pub wpct: f64,
52    pub wlat: u64,
53    pub min: f64,
54    pub max: f64,
55}
56
57impl std::fmt::Display for IoCostQoSParams {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        write!(
60            f,
61            "rpct={:.2} rlat={} wpct={:.2} wlat={} min={:.2} max={:.2}",
62            self.rpct, self.rlat, self.wpct, self.wlat, self.min, self.max
63        )
64    }
65}
66
67impl IoCostQoSParams {
68    /// The kernel reads only two digits after the decimal point. Let's
69    /// rinse the floats through formatting and parsing so that they can be
70    /// tested for equality with values read from kernel.
71    pub fn sanitize(&mut self) {
72        self.rpct = format!("{:.2}", self.rpct).parse::<f64>().unwrap();
73        self.wpct = format!("{:.2}", self.wpct).parse::<f64>().unwrap();
74        self.min = format!("{:.2}", self.min).parse::<f64>().unwrap();
75        self.max = format!("{:.2}", self.max).parse::<f64>().unwrap();
76    }
77}
78
79/// Save /sys/fs/cgroup/io.cost.model,qos and restore them on drop.
80#[derive(Default)]
81pub struct IoCostSysSave {
82    pub devnr: (u32, u32),
83    pub enable: bool,
84    pub model_ctrl_user: bool,
85    pub qos_ctrl_user: bool,
86    pub model: IoCostModelParams,
87    pub qos: IoCostQoSParams,
88}
89
90impl IoCostSysSave {
91    pub fn read_from_sys(devnr: (u32, u32)) -> Result<Self> {
92        let model = super::read_cgroup_nested_keyed_file("/sys/fs/cgroup/io.cost.model")
93            .map_err(|e| anyhow!("failed to read io.cost.model ({})", &e))?;
94        let qos = super::read_cgroup_nested_keyed_file("/sys/fs/cgroup/io.cost.qos")
95            .map_err(|e| anyhow!("failed to read io.cost.model ({})", &e))?;
96        let devnr_str = format!("{}:{}", devnr.0, devnr.1);
97
98        let mut params = IoCostSysSave::default();
99        params.devnr = devnr;
100
101        let model = match model.get(&devnr_str) {
102            Some(v) => v,
103            None => return Ok(params),
104        };
105        let qos = qos.get(&devnr_str).ok_or(anyhow!(
106            "io.cost.qos doesn't contain entry for {}",
107            &devnr_str
108        ))?;
109
110        params.enable = qos["enable"].parse::<u32>()? > 0;
111        params.model_ctrl_user = model["ctrl"] == "user";
112        params.qos_ctrl_user = qos["ctrl"] == "user";
113
114        params.model.rbps = model["rbps"].parse::<u64>()?;
115        params.model.rseqiops = model["rseqiops"].parse::<u64>()?;
116        params.model.rrandiops = model["rrandiops"].parse::<u64>()?;
117        params.model.wbps = model["wbps"].parse::<u64>()?;
118        params.model.wseqiops = model["wseqiops"].parse::<u64>()?;
119        params.model.wrandiops = model["wrandiops"].parse::<u64>()?;
120
121        params.qos.rpct = qos["rpct"].parse::<f64>()?;
122        params.qos.rlat = qos["rlat"].parse::<u64>()?;
123        params.qos.wpct = qos["wpct"].parse::<f64>()?;
124        params.qos.wlat = qos["wlat"].parse::<u64>()?;
125        params.qos.min = qos["min"].parse::<f64>()?;
126        params.qos.max = qos["max"].parse::<f64>()?;
127
128        Ok(params)
129    }
130
131    pub fn write_to_sys(&self) -> Result<()> {
132        let devnr_str = format!("{}:{}", self.devnr.0, self.devnr.1);
133        let model = match self.model_ctrl_user {
134            false => format!("{} ctrl=auto", &devnr_str),
135            true => format!(
136                "{} ctrl=user rbps={} rseqiops={} rrandiops={} wbps={} wseqiops={} wrandiops={}",
137                &devnr_str,
138                self.model.rbps,
139                self.model.rseqiops,
140                self.model.rrandiops,
141                self.model.wbps,
142                self.model.wseqiops,
143                self.model.wrandiops
144            ),
145        };
146        let mut qos = format!("{} enable={} ", &devnr_str, if self.enable { 1 } else { 0 });
147        match self.qos_ctrl_user {
148            false => write!(qos, "ctrl=auto").unwrap(),
149            true => write!(
150                qos,
151                "ctrl=user rpct={} rlat={} wpct={} wlat={} min={} max={}",
152                self.qos.rpct,
153                self.qos.rlat,
154                self.qos.wpct,
155                self.qos.wlat,
156                self.qos.min,
157                self.qos.max
158            )
159            .unwrap(),
160        }
161
162        fs::OpenOptions::new()
163            .write(true)
164            .open("/sys/fs/cgroup/io.cost.model")?
165            .write_all(model.as_bytes())?;
166        fs::OpenOptions::new()
167            .write(true)
168            .open("/sys/fs/cgroup/io.cost.qos")?
169            .write_all(qos.as_bytes())?;
170        Ok(())
171    }
172}
173
174impl Drop for IoCostSysSave {
175    fn drop(&mut self) {
176        if let Err(e) = self.write_to_sys() {
177            error!("Failed to restore io.cost.model,qos ({})", &e);
178        }
179    }
180}