1use 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 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#[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}