Merge changes I3bbae9f6,I23cc7581,I90ab6bb3,I4164766a into main

* changes:
  Start gradual rollout of disabling bazel mode
  Validate feature param values
  Cache the controlled feature enablement result
  Send metric for partially rolled out features
diff --git a/atest/arg_parser.py b/atest/arg_parser.py
index 3250525..05d7720 100644
--- a/atest/arg_parser.py
+++ b/atest/arg_parser.py
@@ -20,6 +20,7 @@
 
 from atest import bazel_mode
 from atest import constants
+from atest import rollout_control
 from atest.atest_utils import BuildOutputMode
 
 
@@ -119,7 +120,7 @@
   )
   parser.add_argument(
       '--bazel-mode',
-      default=True,
+      default=not rollout_control.disable_bazel_mode_by_default.is_enabled(),
       action='store_true',
       help='Run tests using Bazel (default: True).',
   )
diff --git a/atest/atest_enum.py b/atest/atest_enum.py
index 33b9861..d44fdf2 100644
--- a/atest/atest_enum.py
+++ b/atest/atest_enum.py
@@ -117,6 +117,10 @@
   IS_PLOCATEDB_LOCKED = 63
   # Device update duration
   DEVICE_UPDATE_MS = 64
+  # The ID of the feature that is controlled by rollout control. Positive value
+  # means the feature is enabled, negative value means disabled.
+  ROLLOUT_CONTROLLED_FEATURE_ID = 65
+  ROLLOUT_CONTROLLED_FEATURE_ID_OVERRIDE = 66
 
 
 @unique
diff --git a/atest/rollout_control.py b/atest/rollout_control.py
index be5c1da..df146f5 100644
--- a/atest/rollout_control.py
+++ b/atest/rollout_control.py
@@ -15,17 +15,24 @@
 
 """Rollout control for Atest features."""
 
+import functools
 import getpass
 import hashlib
 import logging
 import os
+from atest import atest_enum
+from atest.metrics import metrics
 
 
 class RolloutControlledFeature:
   """Base class for Atest features under rollout control."""
 
   def __init__(
-      self, name: str, rollout_percentage: float, env_control_flag: str
+      self,
+      name: str,
+      rollout_percentage: float,
+      env_control_flag: str,
+      feature_id: int = None,
   ):
     """Initializes the object.
 
@@ -36,10 +43,22 @@
         env_control_flag: The environment variable name to override the feature
           enablement. When set, 'true' or '1' means enable, other values means
           disable.
+        feature_id: The ID of the feature that is controlled by rollout control
+          for metric collection purpose. Must be a positive integer.
     """
+    if rollout_percentage < 0 or rollout_percentage > 100:
+      raise ValueError(
+          'Rollout percentage must be in [0, 100]. Got %s instead.'
+          % rollout_percentage
+      )
+    if feature_id is not None and feature_id <= 0:
+      raise ValueError(
+          'Feature ID must be a positive integer. Got %s instead.' % feature_id
+      )
     self._name = name
     self._rollout_percentage = rollout_percentage
     self._env_control_flag = env_control_flag
+    self._feature_id = feature_id
 
   def _check_env_control_flag(self) -> bool | None:
     """Checks the environment variable to override the feature enablement.
@@ -51,6 +70,7 @@
       return None
     return os.environ[self._env_control_flag] in ('TRUE', 'True', 'true', '1')
 
+  @functools.cache
   def is_enabled(self, username: str | None = None) -> bool:
     """Checks whether the current feature is enabled for the user.
 
@@ -69,6 +89,13 @@
           'enabled' if override_flag_value else 'disabled',
           self._env_control_flag,
       )
+      if self._feature_id:
+        metrics.LocalDetectEvent(
+            detect_type=atest_enum.DetectType.ROLLOUT_CONTROLLED_FEATURE_ID_OVERRIDE,
+            result=self._feature_id
+            if override_flag_value
+            else -self._feature_id,
+        )
       return override_flag_value
 
     if username is None:
@@ -95,4 +122,18 @@
         username,
     )
 
+    if self._feature_id and 0 < self._rollout_percentage < 100:
+      metrics.LocalDetectEvent(
+          detect_type=atest_enum.DetectType.ROLLOUT_CONTROLLED_FEATURE_ID,
+          result=self._feature_id if is_enabled else -self._feature_id,
+      )
+
     return is_enabled
+
+
+disable_bazel_mode_by_default = RolloutControlledFeature(
+    name='disable_bazel_mode_by_default',
+    rollout_percentage=0,
+    env_control_flag='DISABLE_BAZEL_MODE_BY_DEFAULT',
+    feature_id=1,
+)