Load system properties for mmd

mmd uses system properties in "mmd." namespace. mmd also uses server
configurations under "persist.device_config.mmd_native." namespace.

Bug: 375431994
Test: manual

Change-Id: Ieb9ddf77ee68a71a2cfad6fde9212ee14a258ae7
diff --git a/Android.bp b/Android.bp
index 7c67c9b..369fbe4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -10,6 +10,7 @@
     rustlibs: [
         "libanyhow",
         "libbinder_rs",
+        "libflags_rust",
         "liblogger",
         "liblog_rust",
         "libmmd",
diff --git a/src/main.rs b/src/main.rs
index 06f5113..374beb6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -16,6 +16,7 @@
 //!
 //! * zram
 
+mod properties;
 mod service;
 
 use std::time::Duration;
@@ -34,6 +35,8 @@
 use mmd_aidl_interface::aidl::android::os::IMmd::BnMmd;
 use rustutils::system_properties;
 
+use crate::properties::BoolProp;
+
 // In Android zram writeback file is always "/data/per_boot/zram_swap".
 const ZRAM_WRITEBACK_FILE_PATH: &str = "/data/per_boot/zram_swap";
 
@@ -71,35 +74,45 @@
             std::process::exit(1);
         }
     };
-    let zram_writeback = match load_zram_writeback_disk_size() {
-        Ok(Some(zram_writeback_disk_size)) => {
-            info!("zram writeback is enabled");
-            Some(ZramWriteback::new(total_zram_size, zram_writeback_disk_size))
-        }
-        Ok(None) => {
-            info!("zram writeback is disabled");
-            None
-        }
-        Err(e) => {
-            error!("failed to load zram writeback file size: {e:?}");
-            None
-        }
-    };
-
-    let zram_recompression = match is_zram_recompression_activated::<SysfsZramApiImpl>() {
-        Ok(is_activated) => {
-            if is_activated {
-                info!("zram recompression is enabled");
-                Some(ZramRecompression::new())
-            } else {
-                info!("zram recompression is disabled");
+    let zram_writeback = if BoolProp::ZramWritebackEnabled.get(true) {
+        match load_zram_writeback_disk_size() {
+            Ok(Some(zram_writeback_disk_size)) => {
+                info!("zram writeback is activated");
+                Some(ZramWriteback::new(total_zram_size, zram_writeback_disk_size))
+            }
+            Ok(None) => {
+                info!("zram writeback is not activated");
+                None
+            }
+            Err(e) => {
+                error!("failed to load zram writeback file size: {e:?}");
                 None
             }
         }
-        Err(e) => {
-            error!("failed to check zram recompression is activated: {e:?}");
-            None
+    } else {
+        info!("zram writeback is disabled");
+        None
+    };
+
+    let zram_recompression = if BoolProp::ZramRecompressionEnabled.get(true) {
+        match is_zram_recompression_activated::<SysfsZramApiImpl>() {
+            Ok(is_activated) => {
+                if is_activated {
+                    info!("zram recompression is activated");
+                    Some(ZramRecompression::new())
+                } else {
+                    info!("zram recompression is not activated");
+                    None
+                }
+            }
+            Err(e) => {
+                error!("failed to check zram recompression is activated: {e:?}");
+                None
+            }
         }
+    } else {
+        info!("zram recompression is disabled");
+        None
     };
 
     let mmd_service = service::MmdService::new(zram_writeback, zram_recompression);
@@ -121,11 +134,3 @@
         Ok(None)
     }
 }
-
-#[cfg(test)]
-mod tests {
-    #[test]
-    fn it_works() {
-        assert_eq!(2 + 2, 4);
-    }
-}
diff --git a/src/properties.rs b/src/properties.rs
new file mode 100644
index 0000000..435b1b2
--- /dev/null
+++ b/src/properties.rs
@@ -0,0 +1,193 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This module defines system properties used for mmd.
+//!
+//! System properties "mmd.<flag_name>" are defined per OEM.
+//!
+//! Server configrations "persist.device_config.mmd_native.<flag_name>" overrides the corresponding
+//! system properties for in-field experiment on a small population.
+
+use std::str::FromStr;
+use std::time::Duration;
+
+use flags_rust::GetServerConfigurableFlag;
+use log::error;
+use rustutils::system_properties;
+
+const SERVER_CONFIG_NAMESPACE: &str = "mmd_native";
+
+fn generate_property_name(flag_name: &str) -> String {
+    format!("mmd.{flag_name}")
+}
+
+/// bool system properties for mmd.
+///
+/// clippy::enum_variant_names is allowed because we may add more properties.
+#[allow(clippy::enum_variant_names)]
+pub enum BoolProp {
+    ZramWritebackEnabled,
+    ZramWritebackHugeIdleEnabled,
+    ZramWritebackIdleEnabled,
+    ZramWritebackHugeEnabled,
+    ZramRecompressionEnabled,
+    ZramRecompressionHugeIdleEnabled,
+    ZramRecompressionIdleEnabled,
+    ZramRecompressionHugeEnabled,
+}
+
+impl BoolProp {
+    fn flag_name(&self) -> &'static str {
+        match self {
+            Self::ZramWritebackEnabled => "zram.writeback.enabled",
+            Self::ZramWritebackHugeIdleEnabled => "zram.writeback.huge_idle.enabled",
+            Self::ZramWritebackIdleEnabled => "zram.writeback.idle.enabled",
+            Self::ZramWritebackHugeEnabled => "zram.writeback.huge.enabled",
+            Self::ZramRecompressionEnabled => "zram.recompression.enabled",
+            Self::ZramRecompressionHugeIdleEnabled => "zram.recompression.huge_idle.enabled",
+            Self::ZramRecompressionIdleEnabled => "zram.recompression.idle.enabled",
+            Self::ZramRecompressionHugeEnabled => "zram.recompression.huge.enabled",
+        }
+    }
+
+    pub fn get(&self, default: bool) -> bool {
+        if let Some(v) = read(self.flag_name()) {
+            v
+        } else {
+            default
+        }
+    }
+}
+
+/// u64 system properties for mmd.
+///
+/// clippy::enum_variant_names is allowed because we may add more properties.
+#[allow(clippy::enum_variant_names)]
+pub enum U64Prop {
+    ZramWritebackMinBytes,
+    ZramWritebackMaxBytes,
+    ZramWritebackMaxBytesPerDay,
+    ZramRecompressionThresholdMib,
+}
+
+impl U64Prop {
+    fn flag_name(&self) -> &'static str {
+        match self {
+            Self::ZramWritebackMinBytes => "zram.writeback.min_bytes",
+            Self::ZramWritebackMaxBytes => "zram.writeback.max_bytes",
+            Self::ZramWritebackMaxBytesPerDay => "zram.writeback.max_bytes_per_day",
+            Self::ZramRecompressionThresholdMib => "zram.recompression.threshold_mib",
+        }
+    }
+
+    pub fn get(&self, default: u64) -> u64 {
+        if let Some(v) = read(self.flag_name()) {
+            v
+        } else {
+            default
+        }
+    }
+}
+
+/// Duration system properties for mmd in seconds.
+///
+/// clippy::enum_variant_names is allowed because we may add more properties.
+#[allow(clippy::enum_variant_names)]
+pub enum SecondsProp {
+    ZramWritebackBackoff,
+    ZramWritebackMinIdle,
+    ZramWritebackMaxIdle,
+    ZramRecompressionBackoff,
+    ZramRecompressionMinIdle,
+    ZramRecompressionMaxIdle,
+}
+
+impl SecondsProp {
+    fn flag_name(&self) -> &'static str {
+        match self {
+            Self::ZramWritebackBackoff => "zram.writeback.backoff_seconds",
+            Self::ZramWritebackMinIdle => "zram.writeback.min_idle_seconds",
+            Self::ZramWritebackMaxIdle => "zram.writeback.max_idle_seconds",
+            Self::ZramRecompressionBackoff => "zram.recompression.backoff_seconds",
+            Self::ZramRecompressionMinIdle => "zram.recompression.min_idle_seconds",
+            Self::ZramRecompressionMaxIdle => "zram.recompression.max_idle_seconds",
+        }
+    }
+
+    pub fn get(&self, default: Duration) -> Duration {
+        if let Some(v) = read::<u64>(self.flag_name()) {
+            Duration::from_secs(v)
+        } else {
+            default
+        }
+    }
+}
+
+fn read<T: FromStr>(flag_name: &str) -> Option<T> {
+    let value = GetServerConfigurableFlag(SERVER_CONFIG_NAMESPACE, flag_name, "");
+    if !value.is_empty() {
+        if let Ok(v) = value.parse() {
+            return Some(v);
+        }
+        error!("failed to parse server config flag: {flag_name}={value}");
+    }
+
+    // fallback if server flag is not set or broken.
+    let property_name = generate_property_name(flag_name);
+    match system_properties::read(&property_name) {
+        Ok(Some(v)) => {
+            if let Ok(v) = v.parse() {
+                return Some(v);
+            } else {
+                error!("failed to parse system property: {property_name}={v}");
+            }
+        }
+        Ok(None) => {}
+        Err(e) => {
+            error!("failed to read system property: {property_name} {e:?}");
+        }
+    }
+
+    None
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn bool_prop_from_default() {
+        // We can't test system properties directly. Just a unit test for
+        // default value.
+        assert!(BoolProp::ZramWritebackEnabled.get(true));
+        assert!(!BoolProp::ZramWritebackEnabled.get(false));
+    }
+
+    #[test]
+    fn u64_prop_from_default() {
+        // We can't test system properties directly. Just a unit test for
+        // default value.
+        assert_eq!(U64Prop::ZramWritebackMinBytes.get(12345), 12345);
+    }
+
+    #[test]
+    fn seconds_prop_from_default() {
+        // We can't test system properties directly. Just a unit test for
+        // default value.
+        assert_eq!(
+            SecondsProp::ZramWritebackBackoff.get(Duration::from_secs(12345)),
+            Duration::from_secs(12345)
+        );
+    }
+}
diff --git a/src/service.rs b/src/service.rs
index be3913f..c7d67fc 100644
--- a/src/service.rs
+++ b/src/service.rs
@@ -28,6 +28,10 @@
 use mmd::zram::writeback::ZramWriteback;
 use mmd::zram::SysfsZramApiImpl;
 
+use crate::properties::BoolProp;
+use crate::properties::SecondsProp;
+use crate::properties::U64Prop;
+
 struct ZramContext {
     zram_writeback: Option<ZramWriteback>,
     zram_recompression: Option<ZramRecompression>,
@@ -87,8 +91,17 @@
 }
 
 fn load_zram_writeback_params() -> mmd::zram::writeback::Params {
-    // TODO: load params from system properties.
-    mmd::zram::writeback::Params::default()
+    let mut params = mmd::zram::writeback::Params::default();
+    params.backoff_duration = SecondsProp::ZramWritebackBackoff.get(params.backoff_duration);
+    params.min_idle = SecondsProp::ZramWritebackMinIdle.get(params.min_idle);
+    params.max_idle = SecondsProp::ZramWritebackMaxIdle.get(params.max_idle);
+    params.huge_idle = BoolProp::ZramWritebackHugeIdleEnabled.get(params.huge_idle);
+    params.idle = BoolProp::ZramWritebackIdleEnabled.get(params.idle);
+    params.huge = BoolProp::ZramWritebackHugeEnabled.get(params.huge);
+    params.min_bytes = U64Prop::ZramWritebackMinBytes.get(params.min_bytes);
+    params.max_bytes = U64Prop::ZramWritebackMaxBytes.get(params.max_bytes);
+    params.max_bytes_per_day = U64Prop::ZramWritebackMaxBytesPerDay.get(params.max_bytes_per_day);
+    params
 }
 
 fn load_zram_writeback_stats() -> anyhow::Result<mmd::zram::writeback::Stats> {
@@ -103,6 +116,13 @@
 }
 
 fn load_zram_recompression_params() -> mmd::zram::recompression::Params {
-    // TODO: load params from system properties.
-    mmd::zram::recompression::Params::default()
+    let mut params = mmd::zram::recompression::Params::default();
+    params.backoff_duration = SecondsProp::ZramRecompressionBackoff.get(params.backoff_duration);
+    params.min_idle = SecondsProp::ZramRecompressionMinIdle.get(params.min_idle);
+    params.max_idle = SecondsProp::ZramRecompressionMaxIdle.get(params.max_idle);
+    params.huge_idle = BoolProp::ZramRecompressionHugeIdleEnabled.get(params.huge_idle);
+    params.idle = BoolProp::ZramRecompressionIdleEnabled.get(params.idle);
+    params.huge = BoolProp::ZramRecompressionHugeEnabled.get(params.huge);
+    params.max_mib = U64Prop::ZramRecompressionThresholdMib.get(params.max_mib);
+    params
 }