Skip to content

Commit c8792e8

Browse files
committed
feat: add support for transparent hugepages
Signed-off-by: Piyush Jena <jepiyush@amazon.com>
1 parent e82c9fb commit c8792e8

2 files changed

Lines changed: 121 additions & 4 deletions

File tree

  • bottlerocket-settings-models

bottlerocket-settings-models/modeled-types/src/hugepages.rs

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::{error, NonNegativeInteger};
2+
use bottlerocket_scalar_derive::Scalar;
23
use bottlerocket_string_impls_for::string_impls_for;
34
use lazy_static::lazy_static;
45
use regex::Regex;
@@ -8,6 +9,8 @@ use std::collections::HashMap;
89

910
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
1011
pub struct HugepagesSettings {
12+
#[serde(skip_serializing_if = "Option::is_none")]
13+
pub transparent: Option<HugepagesTransparent>,
1114
#[serde(flatten)]
1215
pub hugepages_config: HashMap<HugepageSize, HugepagePool>,
1316
}
@@ -126,6 +129,75 @@ impl TryFrom<&str> for HugepageSize {
126129

127130
string_impls_for!(HugepageSize, "HugepageSize");
128131

132+
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
133+
pub struct HugepagesTransparent {
134+
#[serde(skip_serializing_if = "Option::is_none")]
135+
pub enabled: Option<TransparentHugepagePolicy>,
136+
#[serde(skip_serializing_if = "Option::is_none")]
137+
pub defrag: Option<TransparentHugepageDefragPolicy>,
138+
}
139+
140+
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Scalar, Default)]
141+
#[serde(rename_all = "lowercase")]
142+
pub enum TransparentHugepagePolicy {
143+
#[default]
144+
Never,
145+
Madvise,
146+
Always,
147+
}
148+
149+
#[cfg(test)]
150+
mod test_thp_policy {
151+
use super::TransparentHugepagePolicy;
152+
use std::convert::TryFrom;
153+
154+
#[test]
155+
fn good_policy() {
156+
for ok in &["never", "madvise", "always"] {
157+
TransparentHugepagePolicy::try_from(*ok).unwrap();
158+
}
159+
}
160+
161+
#[test]
162+
fn bad_policy() {
163+
for err in &["disable", "enable", "", "sometimes", &"a".repeat(64)] {
164+
TransparentHugepagePolicy::try_from(*err).unwrap_err();
165+
}
166+
}
167+
}
168+
169+
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Scalar, Default)]
170+
#[serde(rename_all = "lowercase")]
171+
pub enum TransparentHugepageDefragPolicy {
172+
#[default]
173+
Never,
174+
Madvise,
175+
Defer,
176+
#[serde(rename = "defer+madvise")]
177+
DeferMadvise,
178+
Always,
179+
}
180+
181+
#[cfg(test)]
182+
mod test_thp_defrag_policy {
183+
use super::TransparentHugepageDefragPolicy;
184+
use std::convert::TryFrom;
185+
186+
#[test]
187+
fn good_policy() {
188+
for ok in &["never", "madvise", "defer", "defer+madvise", "always"] {
189+
TransparentHugepageDefragPolicy::try_from(*ok).unwrap();
190+
}
191+
}
192+
193+
#[test]
194+
fn bad_policy() {
195+
for err in &["disable", "enable", "", "sometimes", &"a".repeat(64)] {
196+
TransparentHugepageDefragPolicy::try_from(*err).unwrap_err();
197+
}
198+
}
199+
}
200+
129201
#[cfg(test)]
130202
mod test_hugepage_size {
131203
use super::HugepageSize;
@@ -189,7 +261,8 @@ mod test_hugepages_settings {
189261
#[test]
190262
fn pool_count_table_deserializes_and_round_trips() {
191263
// Nested `count` table form is the accepted shape.
192-
let settings: HugepagesSettings = serde_json::from_str(r#"{"1Gi": {"count": 2}}"#).unwrap();
264+
let settings: HugepagesSettings =
265+
serde_json::from_str(r#"{"1Gi": {"count": 2}}"#).unwrap();
193266
let key = HugepageSize::try_from("1Gi").unwrap();
194267
let pool = settings.hugepages_config.get(&key).expect("1Gi pool");
195268
assert_eq!(*pool.count, 2);
@@ -217,6 +290,32 @@ mod test_hugepages_settings {
217290
assert!(result.is_err(), "unknown pool field should be rejected");
218291
}
219292

293+
#[test]
294+
fn transparent_round_trips_alongside_pools() {
295+
let json = r#"{
296+
"transparent": {"enabled": "always", "defrag": "madvise"},
297+
"2Mi": {"count": 512},
298+
"1Gi": {"count": 4}
299+
}"#;
300+
let settings: HugepagesSettings = serde_json::from_str(json).unwrap();
301+
302+
let two_mi = HugepageSize::try_from("2Mi").unwrap();
303+
let one_gi = HugepageSize::try_from("1Gi").unwrap();
304+
assert_eq!(*settings.hugepages_config[&two_mi].count, 512);
305+
assert_eq!(*settings.hugepages_config[&one_gi].count, 4);
306+
307+
let transparent = settings.transparent.as_ref().expect("transparent");
308+
assert!(transparent.enabled.is_some());
309+
assert!(transparent.defrag.is_some());
310+
311+
// Round-trips: the pools serialize back as nested count tables and `transparent` stays.
312+
let out = serde_json::to_value(&settings).unwrap();
313+
assert_eq!(out["2Mi"]["count"], 512);
314+
assert_eq!(out["1Gi"]["count"], 4);
315+
assert_eq!(out["transparent"]["enabled"], "always");
316+
assert_eq!(out["transparent"]["defrag"], "madvise");
317+
}
318+
220319
#[test]
221320
fn construct_pool_directly() {
222321
let pool = HugepagePool {

bottlerocket-settings-models/settings-extensions/kernel/src/lib.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ impl SettingsModel for KernelSettingsV1 {
5353
#[cfg(test)]
5454
mod test {
5555
use super::*;
56-
use bottlerocket_modeled_types::{HugepagePool, HugepageSize, NonNegativeInteger};
56+
use bottlerocket_modeled_types::{
57+
HugepagePool, HugepageSize, HugepagesTransparent, NonNegativeInteger,
58+
TransparentHugepageDefragPolicy, TransparentHugepagePolicy,
59+
};
5760

5861
#[test]
5962
fn test_generate_kernel() {
@@ -78,7 +81,8 @@ mod test {
7881
"sysctl": {"key": "value"},
7982
"hugepages": {
8083
"2Mi": {"count": 512},
81-
"1Gi": {"count": 4}
84+
"1Gi": {"count": 4},
85+
"transparent": {"enabled": "always", "defrag": "defer+madvise"}
8286
}
8387
}"#;
8488

@@ -112,7 +116,13 @@ mod test {
112116
},
113117
);
114118

115-
let hugepages = Some(HugepagesSettings { hugepages_config });
119+
let hugepages = Some(HugepagesSettings {
120+
transparent: Some(HugepagesTransparent {
121+
enabled: Some(TransparentHugepagePolicy::try_from("always").unwrap()),
122+
defrag: Some(TransparentHugepageDefragPolicy::try_from("defer+madvise").unwrap()),
123+
}),
124+
hugepages_config,
125+
});
116126

117127
assert_eq!(
118128
kernel,
@@ -128,5 +138,13 @@ mod test {
128138
let hugepages_out = &roundtrip["hugepages"];
129139
assert_eq!(hugepages_out["2Mi"]["count"], serde_json::json!(512));
130140
assert_eq!(hugepages_out["1Gi"]["count"], serde_json::json!(4));
141+
assert_eq!(
142+
hugepages_out["transparent"]["enabled"],
143+
serde_json::json!("always")
144+
);
145+
assert_eq!(
146+
hugepages_out["transparent"]["defrag"],
147+
serde_json::json!("defer+madvise")
148+
);
131149
}
132150
}

0 commit comments

Comments
 (0)