1use anyhow::Result;
3use enum_iterator::IntoEnumIterator;
4use serde::{Deserialize, Serialize};
5use std::collections::BTreeMap;
6use std::ops::{Index, IndexMut};
7
8use rd_util::*;
9
10pub const ROOT_SLICE: &'static str = "-.slice";
11
12const SLICE_DOC: &str = "\
13//
14// rd-agent top-level systemd slice resource configurations
15//
16// Memory configuration can be either None or Bytes.
17//
18// disable_seqs.cpu: Disable CPU control if >= report::seq
19// disable_seqs.mem: Disable memory control if >= report::seq
20// disable_seqs.io: Disable IO control if >= report::seq
21// slices.SLICE_ID.cpu_weight: CPU weight [1..10000]
22// slices.SLICE_ID.io_weight: IO weight [1..10000]
23// slices.SLICE_ID.mem_min: memory.min
24// slices.SLICE_ID.mem_low: memory.low
25// slices.SLICE_ID.mem_high: memory.high
26//
27";
28
29#[derive(Debug, Copy, Clone, IntoEnumIterator, PartialEq, Eq)]
30pub enum Slice {
31 Init = 0,
32 Host = 1,
33 User = 2,
34 Sys = 3,
35 Work = 4,
36 Side = 5,
37}
38
39impl Slice {
40 pub fn name(&self) -> &'static str {
41 match self {
42 Slice::Init => "init.scope",
43 Slice::Host => "hostcritical.slice",
44 Slice::User => "user.slice",
45 Slice::Sys => "system.slice",
46 Slice::Work => "workload.slice",
47 Slice::Side => "sideload.slice",
48 }
49 }
50
51 pub fn cgrp(&self) -> &'static str {
52 match self {
53 Slice::Init => "/sys/fs/cgroup/init.scope",
54 Slice::Host => "/sys/fs/cgroup/hostcritical.slice",
55 Slice::User => "/sys/fs/cgroup/user.slice",
56 Slice::Sys => "/sys/fs/cgroup/system.slice",
57 Slice::Work => "/sys/fs/cgroup/workload.slice",
58 Slice::Side => "/sys/fs/cgroup/sideload.slice",
59 }
60 }
61}
62
63#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
64pub enum MemoryKnob {
65 None,
66 Bytes(u64),
67}
68
69impl Default for MemoryKnob {
70 fn default() -> Self {
71 Self::None
72 }
73}
74
75impl MemoryKnob {
76 pub fn nr_bytes(&self, is_limit: bool) -> u64 {
77 let nocfg = match is_limit {
78 true => std::u64::MAX,
79 false => 0,
80 };
81 match self {
82 Self::None => nocfg,
83 Self::Bytes(s) => *s,
84 }
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89pub struct SliceConfig {
90 pub cpu_weight: u32,
91 pub io_weight: u32,
92 pub mem_min: MemoryKnob,
93 pub mem_low: MemoryKnob,
94 pub mem_high: MemoryKnob,
95}
96
97impl Default for SliceConfig {
98 fn default() -> Self {
99 Self {
100 cpu_weight: 100,
101 io_weight: 100,
102 mem_min: Default::default(),
103 mem_low: Default::default(),
104 mem_high: Default::default(),
105 }
106 }
107}
108
109impl SliceConfig {
110 pub const DFL_SYS_CPU_RATIO: f64 = 0.1;
111 pub const DFL_SYS_IO_RATIO: f64 = 0.1;
112 pub const DFL_MEM_MARGIN: f64 = 0.25;
113
114 pub fn dfl_mem_margin(total: usize, fb_prod: bool) -> u64 {
115 let margin = (total as f64 * Self::DFL_MEM_MARGIN) as u64;
116 if fb_prod {
117 (margin + 2 << 30).min(total as u64 / 2)
118 } else {
119 margin
120 }
121 }
122
123 fn default(slice: Slice) -> Self {
124 let mut hostcrit_min = 768 << 20;
125 if *IS_FB_PROD {
126 hostcrit_min += 512 << 20;
127 }
128
129 match slice {
130 Slice::Init => Self {
131 cpu_weight: 10,
132 mem_min: MemoryKnob::Bytes(16 << 20),
133 ..Default::default()
134 },
135 Slice::Host => Self {
136 cpu_weight: 10,
137 mem_min: MemoryKnob::Bytes(hostcrit_min),
138 ..Default::default()
139 },
140 Slice::User => Self {
141 mem_low: MemoryKnob::Bytes(512 << 20),
142 ..Default::default()
143 },
144 Slice::Sys => Self {
145 cpu_weight: 10,
146 io_weight: 50,
147 ..Default::default()
148 },
149 Slice::Work => Self {
150 io_weight: 500,
151 mem_low: MemoryKnob::Bytes(
152 total_memory() as u64 - Self::dfl_mem_margin(total_memory(), *IS_FB_PROD),
153 ),
154 ..Default::default()
155 },
156 Slice::Side => Self {
157 cpu_weight: 1,
158 io_weight: 1,
159 ..Default::default()
160 },
161 }
162 }
163}
164
165#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
166pub struct DisableSeqKnobs {
167 pub cpu: u64,
168 pub mem: u64,
169 pub io: u64,
170}
171
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173#[serde(default)]
174pub struct SliceKnobs {
175 pub disable_seqs: DisableSeqKnobs,
176 pub slices: BTreeMap<String, SliceConfig>,
177 #[serde(skip)]
178 pub work_mem_low_none: bool,
179}
180
181impl Default for SliceKnobs {
182 fn default() -> Self {
183 let mut slices = BTreeMap::new();
184 for slc in Slice::into_enum_iter() {
185 slices.insert(slc.name().into(), SliceConfig::default(slc));
186 }
187 Self {
188 disable_seqs: Default::default(),
189 slices,
190 work_mem_low_none: false,
191 }
192 }
193}
194
195impl JsonLoad for SliceKnobs {
196 fn loaded(&mut self, _prev: Option<&mut Self>) -> Result<()> {
197 let sk = self.slices.get(Slice::Work.name()).unwrap();
198 self.work_mem_low_none = if let MemoryKnob::None = sk.mem_low {
199 true
200 } else {
201 false
202 };
203 Ok(())
204 }
205}
206
207impl JsonSave for SliceKnobs {
208 fn preamble() -> Option<String> {
209 Some(SLICE_DOC.to_string())
210 }
211}
212
213impl SliceKnobs {
214 pub fn controlls_disabled(&self, seq: u64) -> bool {
215 let dseqs = &self.disable_seqs;
216 dseqs.cpu >= seq || dseqs.mem >= seq || dseqs.io >= seq
217 }
218}
219
220impl Index<Slice> for SliceKnobs {
221 type Output = SliceConfig;
222
223 fn index(&self, slc: Slice) -> &Self::Output {
224 self.slices.get(slc.name()).unwrap()
225 }
226}
227
228impl IndexMut<Slice> for SliceKnobs {
229 fn index_mut(&mut self, slc: Slice) -> &mut Self::Output {
230 self.slices.get_mut(slc.name()).unwrap()
231 }
232}