11use crate :: { error, NonNegativeInteger } ;
2+ use bottlerocket_scalar_derive:: Scalar ;
23use bottlerocket_string_impls_for:: string_impls_for;
34use lazy_static:: lazy_static;
45use regex:: Regex ;
@@ -8,6 +9,8 @@ use std::collections::HashMap;
89
910#[ derive( Debug , Clone , PartialEq , Default , Serialize , Deserialize ) ]
1011pub 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
127130string_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) ]
130202mod 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 {
0 commit comments