55
66use std:: {
77 collections:: HashMap ,
8+ num:: NonZero ,
89 path:: { Path , PathBuf } ,
910 sync:: OnceLock ,
1011} ;
@@ -32,6 +33,28 @@ pub(crate) const DEFAULT_MAX_CONNECTIONS: u32 = 5;
3233/// Default database connection acquisition timeout in seconds.
3334pub ( crate ) const DEFAULT_CONNECT_TIMEOUT_SECS : u64 = 30 ;
3435
36+ /// Default sandbox metrics sampling interval in milliseconds.
37+ pub const DEFAULT_METRICS_SAMPLE_INTERVAL_MS : u64 = 1000 ;
38+
39+ /// Default value for `metrics_sample_interval_ms` fields.
40+ pub fn default_metrics_sample_interval ( ) -> Option < NonZero < u64 > > {
41+ NonZero :: new ( DEFAULT_METRICS_SAMPLE_INTERVAL_MS )
42+ }
43+
44+ /// Serde adapter mapping the `metrics_sample_interval_ms` wire format `u64` to `Option<NonZero<u64>>` (`0` ↔ `None`).
45+ pub ( crate ) mod metrics_interval_serde {
46+ use serde:: { Deserialize , Deserializer , Serialize , Serializer } ;
47+ use std:: num:: NonZero ;
48+
49+ pub fn serialize < S : Serializer > ( v : & Option < NonZero < u64 > > , s : S ) -> Result < S :: Ok , S :: Error > {
50+ v. map ( |n| n. get ( ) ) . unwrap_or ( 0 ) . serialize ( s)
51+ }
52+
53+ pub fn deserialize < ' de , D : Deserializer < ' de > > ( d : D ) -> Result < Option < NonZero < u64 > > , D :: Error > {
54+ Ok ( NonZero :: new ( u64:: deserialize ( d) ?) )
55+ }
56+ }
57+
3558/// Service name for microsandbox-managed registry credentials in the OS keyring.
3659#[ cfg( all(
3760 feature = "keyring" ,
@@ -131,6 +154,17 @@ pub struct SandboxDefaults {
131154
132155 /// Default working directory inside the sandbox.
133156 pub workdir : Option < String > ,
157+
158+ /// Default metrics sampling interval in milliseconds; `0` disables sampling globally.
159+ #[ serde(
160+ default = "default_metrics_sample_interval" ,
161+ with = "metrics_interval_serde"
162+ ) ]
163+ pub metrics_sample_interval_ms : Option < NonZero < u64 > > ,
164+
165+ /// Force-disable metrics sampling regardless of `metrics_sample_interval_ms`.
166+ #[ serde( default ) ]
167+ pub disable_metrics_sample : bool ,
134168}
135169
136170/// Registry configuration.
@@ -423,6 +457,8 @@ impl Default for SandboxDefaults {
423457 memory_mib : DEFAULT_MEMORY_MIB ,
424458 shell : "/bin/sh" . into ( ) ,
425459 workdir : None ,
460+ metrics_sample_interval_ms : default_metrics_sample_interval ( ) ,
461+ disable_metrics_sample : false ,
426462 }
427463 }
428464}
@@ -895,6 +931,10 @@ mod tests {
895931 assert_eq ! ( cfg. sandbox_defaults. cpus, 1 ) ;
896932 assert_eq ! ( cfg. sandbox_defaults. memory_mib, 512 ) ;
897933 assert_eq ! ( cfg. sandbox_defaults. shell, "/bin/sh" ) ;
934+ assert_eq ! (
935+ cfg. sandbox_defaults. metrics_sample_interval_ms,
936+ NonZero :: new( DEFAULT_METRICS_SAMPLE_INTERVAL_MS )
937+ ) ;
898938 assert_eq ! ( cfg. log_level, None ) ;
899939 assert_eq ! ( cfg. database. max_connections, 5 ) ;
900940 assert_eq ! ( cfg. database. connect_timeout_secs, 30 ) ;
@@ -915,6 +955,59 @@ mod tests {
915955 assert_eq ! ( cfg. sandbox_defaults. memory_mib, 512 ) ;
916956 }
917957
958+ #[ test]
959+ fn test_deserialize_metrics_interval_missing_uses_default ( ) {
960+ let json = r#"{"sandbox_defaults": {}}"# ;
961+ let cfg: GlobalConfig = serde_json:: from_str ( json) . unwrap ( ) ;
962+ assert_eq ! (
963+ cfg. sandbox_defaults. metrics_sample_interval_ms,
964+ NonZero :: new( DEFAULT_METRICS_SAMPLE_INTERVAL_MS )
965+ ) ;
966+ }
967+
968+ #[ test]
969+ fn test_deserialize_metrics_interval_zero_disables ( ) {
970+ let json = r#"{"sandbox_defaults": {"metrics_sample_interval_ms": 0}}"# ;
971+ let cfg: GlobalConfig = serde_json:: from_str ( json) . unwrap ( ) ;
972+ assert ! ( cfg. sandbox_defaults. metrics_sample_interval_ms. is_none( ) ) ;
973+ }
974+
975+ #[ test]
976+ fn test_deserialize_metrics_interval_positive ( ) {
977+ let json = r#"{"sandbox_defaults": {"metrics_sample_interval_ms": 2500}}"# ;
978+ let cfg: GlobalConfig = serde_json:: from_str ( json) . unwrap ( ) ;
979+ assert_eq ! (
980+ cfg. sandbox_defaults. metrics_sample_interval_ms,
981+ NonZero :: new( 2500 )
982+ ) ;
983+ }
984+
985+ #[ test]
986+ fn test_serialize_metrics_interval_disabled_round_trips ( ) {
987+ let mut cfg = GlobalConfig :: default ( ) ;
988+ cfg. sandbox_defaults . metrics_sample_interval_ms = None ;
989+ let json = serde_json:: to_string ( & cfg) . unwrap ( ) ;
990+ assert ! (
991+ json. contains( "\" metrics_sample_interval_ms\" :0" ) ,
992+ "expected `0` serialization, got: {json}"
993+ ) ;
994+ let round: GlobalConfig = serde_json:: from_str ( & json) . unwrap ( ) ;
995+ assert ! ( round. sandbox_defaults. metrics_sample_interval_ms. is_none( ) ) ;
996+ }
997+
998+ #[ test]
999+ fn test_deserialize_disable_metrics_sample_default_false ( ) {
1000+ let cfg: GlobalConfig = serde_json:: from_str ( "{}" ) . unwrap ( ) ;
1001+ assert ! ( !cfg. sandbox_defaults. disable_metrics_sample) ;
1002+ }
1003+
1004+ #[ test]
1005+ fn test_deserialize_disable_metrics_sample_true ( ) {
1006+ let json = r#"{"sandbox_defaults": {"disable_metrics_sample": true}}"# ;
1007+ let cfg: GlobalConfig = serde_json:: from_str ( json) . unwrap ( ) ;
1008+ assert ! ( cfg. sandbox_defaults. disable_metrics_sample) ;
1009+ }
1010+
9181011 #[ test]
9191012 fn test_deserialize_log_level ( ) {
9201013 let json = r#"{"log_level":"debug"}"# ;
0 commit comments