This is an automated email from the ASF dual-hosted git repository.

devmadhuu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new ca3c9798d2b HDDS-15413. Recon and SCM Container Sync Metrics addition. 
(#10384)
ca3c9798d2b is described below

commit ca3c9798d2b6ef2048f1986902502e16f147b6f1
Author: Devesh Kumar Singh <[email protected]>
AuthorDate: Wed Jun 10 15:34:10 2026 +0530

    HDDS-15413. Recon and SCM Container Sync Metrics addition. (#10384)
---
 .../ozone/recon/TestNSSummaryMemoryLeak.java       |   5 +-
 .../TestReconContainerHealthSummaryEndToEnd.java   |   2 +-
 .../apache/hadoop/ozone/recon/TestReconTasks.java  |   2 +-
 .../metrics/ReconScmContainerSyncMetrics.java      | 182 +++++++++++++++++----
 .../scm/ReconStorageContainerManagerFacade.java    |  33 ++--
 .../recon/scm/ReconStorageContainerSyncHelper.java |  53 +++++-
 .../metrics/TestReconScmContainerSyncMetrics.java  |  99 +++++++++++
 .../scm/TestReconSCMContainerSyncIntegration.java  |  19 ++-
 .../TestReconStorageContainerManagerFacade.java    | 117 +++++++++++++
 .../scm/TestReconStorageContainerSyncHelper.java   |  50 +++++-
 10 files changed, 503 insertions(+), 59 deletions(-)

diff --git 
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestNSSummaryMemoryLeak.java
 
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestNSSummaryMemoryLeak.java
index fdb0a3c1204..34ba542b1de 100644
--- 
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestNSSummaryMemoryLeak.java
+++ 
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestNSSummaryMemoryLeak.java
@@ -240,7 +240,7 @@ public void testNSSummaryCleanupOnHardDelete() throws 
Exception {
     // This simulates the background process that hard deletes entries
     simulateHardDelete(omMetadataManager);
     
-    // Verify cleanup after hard delete simulation.
+    // Verify memory leak fix - NSSummary entries should be cleaned up
     verifyNSSummaryCleanup(omMetadataManager, "memoryLeakTest");
     
     LOG.info("NSSummary memory leak fix test completed successfully");
@@ -309,8 +309,9 @@ public void testMemoryLeakWithLargeStructure() throws 
Exception {
     
     // Simulate hard delete
     simulateHardDelete(omMetadataManager);
+    syncDataFromOM();
 
-    // Verify cleanup after hard delete simulation.
+    // Verify NSSummary cleanup
     verifyNSSummaryCleanup(omMetadataManager, "largeMemoryLeakTest");
     
     LOG.info("Large structure memory leak test completed successfully");
diff --git 
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconContainerHealthSummaryEndToEnd.java
 
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconContainerHealthSummaryEndToEnd.java
index f1976f5a878..af60f46a60b 100644
--- 
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconContainerHealthSummaryEndToEnd.java
+++ 
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconContainerHealthSummaryEndToEnd.java
@@ -1182,7 +1182,7 @@ private void syncAndWaitForReconContainers(
       ReconStorageContainerManagerFacade reconScm,
       ReconContainerManager reconCm,
       List<ContainerID> containerIDs) throws Exception {
-    reconScm.triggerTargetedSCMContainerSync();
+    reconScm.triggerSCMContainerSync();
     drainScmAndReconEventQueues();
     backfillMissingContainersFromScm(reconCm, containerIDs);
     LambdaTestUtils.await(REPLICA_SYNC_TIMEOUT_MS, POLL_INTERVAL_MS,
diff --git 
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconTasks.java
 
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconTasks.java
index c3e213c07ea..f847812edcd 100644
--- 
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconTasks.java
+++ 
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/TestReconTasks.java
@@ -185,7 +185,7 @@ public void testSyncSCMContainerInfo() throws Exception {
     int scmContainersCount = scmContainerManager.getContainers().size();
     int reconContainersCount = reconCm.getContainers().size();
     assertNotEquals(scmContainersCount, reconContainersCount);
-    reconScm.triggerTargetedSCMContainerSync();
+    reconScm.triggerSCMContainerSync();
     reconContainersCount = reconCm.getContainers().size();
     assertEquals(scmContainersCount, reconContainersCount);
   }
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/metrics/ReconScmContainerSyncMetrics.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/metrics/ReconScmContainerSyncMetrics.java
index d652a63b4ef..67d4520fd91 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/metrics/ReconScmContainerSyncMetrics.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/metrics/ReconScmContainerSyncMetrics.java
@@ -17,50 +17,78 @@
 
 package org.apache.hadoop.ozone.recon.metrics;
 
+import com.google.common.base.CaseFormat;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 import org.apache.hadoop.hdds.annotation.InterfaceAudience;
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.metrics2.MetricsCollector;
+import org.apache.hadoop.metrics2.MetricsInfo;
+import org.apache.hadoop.metrics2.MetricsRecordBuilder;
+import org.apache.hadoop.metrics2.MetricsSource;
 import org.apache.hadoop.metrics2.MetricsSystem;
-import org.apache.hadoop.metrics2.annotation.Metric;
 import org.apache.hadoop.metrics2.annotation.Metrics;
 import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
-import org.apache.hadoop.metrics2.lib.MutableGaugeInt;
-import org.apache.hadoop.metrics2.lib.MutableGaugeLong;
+import org.apache.hadoop.metrics2.lib.Interns;
 import org.apache.hadoop.ozone.OzoneConsts;
 
 /**
- * Metrics for Recon SCM targeted sync execution.
+ * Metrics for Recon SCM container sync execution.
  */
 @InterfaceAudience.Private
 @Metrics(about = "Recon SCM Container Sync Metrics", context = 
OzoneConsts.OZONE)
-public final class ReconScmContainerSyncMetrics {
+public final class ReconScmContainerSyncMetrics implements MetricsSource {
 
   private static final String SOURCE_NAME =
       ReconScmContainerSyncMetrics.class.getSimpleName();
 
+  private static final HddsProtos.LifeCycleState[] SYNC_STATES = {
+      HddsProtos.LifeCycleState.OPEN,
+      HddsProtos.LifeCycleState.QUASI_CLOSED,
+      HddsProtos.LifeCycleState.CLOSED,
+      HddsProtos.LifeCycleState.DELETED
+  };
+
+  private static final MetricsInfo SCM_CONTAINER_SYNC_STATUS = Interns.info(
+      "scmContainerSyncStatus",
+      "SCM container sync status: 0=idle, 1=in progress, 2=success, 
3=failure");
+
+  private static final MetricsInfo SCM_CONTAINER_SYNC_DURATION_MS = 
Interns.info(
+      "scmContainerSyncDurationMs",
+      "Time taken by the SCM container sync in milliseconds");
+
   /**
-   * No targeted sync has run yet, or the latest scheduler cycle did not run 
one.
-   */
-  public static final int TARGETED_SYNC_STATUS_IDLE = 0;
-  /**
-   * Targeted sync is currently running.
+   * SCM container sync is currently running.
    */
-  public static final int TARGETED_SYNC_STATUS_IN_PROGRESS = 1;
+  public static final int SCM_CONTAINER_SYNC_STATUS_IN_PROGRESS = 1;
   /**
-   * The last targeted sync completed successfully.
+   * SCM container sync completed successfully.
    */
-  public static final int TARGETED_SYNC_STATUS_SUCCESS = 2;
+  public static final int SCM_CONTAINER_SYNC_STATUS_SUCCESS = 2;
   /**
-   * The last targeted sync completed with one or more failed passes.
+   * SCM container sync completed with one or more failed passes.
    */
-  public static final int TARGETED_SYNC_STATUS_FAILURE = 3;
-
-  @Metric(about = "Targeted sync status: 0=idle, 1=in progress, "
-      + "2=success, 3=failure")
-  private MutableGaugeInt targetedSyncStatus;
+  public static final int SCM_CONTAINER_SYNC_STATUS_FAILURE = 3;
 
-  @Metric(about = "Time taken by the last targeted sync in milliseconds")
-  private MutableGaugeLong lastTargetedSyncDurationMs;
+  private final AtomicInteger scmContainerSyncStatus = new AtomicInteger();
+  private final AtomicLong scmContainerSyncDurationMs = new AtomicLong();
+  private final Map<HddsProtos.LifeCycleState, AtomicLong>
+      containerSyncDurationMs;
+  private final Map<HddsProtos.LifeCycleState, AtomicLong>
+      containerCountDrift;
+  private final Map<HddsProtos.LifeCycleState, MetricsInfo>
+      containerSyncDurationMetricInfo;
+  private final Map<HddsProtos.LifeCycleState, MetricsInfo>
+      containerCountDriftMetricInfo;
 
   private ReconScmContainerSyncMetrics() {
+    containerSyncDurationMs = initStateGaugeValues();
+    containerCountDrift = initStateGaugeValues();
+    containerSyncDurationMetricInfo = initSyncDurationMetricInfo();
+    containerCountDriftMetricInfo = initCountDriftMetricInfo();
   }
 
   public static ReconScmContainerSyncMetrics create() {
@@ -75,19 +103,115 @@ public void unRegister() {
     ms.unregisterSource(SOURCE_NAME);
   }
 
-  public void setTargetedSyncStatus(int status) {
-    targetedSyncStatus.set(status);
+  public void setScmContainerSyncStatus(int status) {
+    scmContainerSyncStatus.set(status);
+  }
+
+  public void setScmContainerSyncDurationMs(long durationMs) {
+    scmContainerSyncDurationMs.set(durationMs);
+  }
+
+  public void setContainerSyncDurationMs(
+      HddsProtos.LifeCycleState state, long durationMs) {
+    setStateGauge(containerSyncDurationMs, state, durationMs);
+  }
+
+  public void setContainerCountDrift(
+      HddsProtos.LifeCycleState state, long drift) {
+    setStateGauge(containerCountDrift, state, drift);
+  }
+
+  public int getScmContainerSyncStatus() {
+    return scmContainerSyncStatus.get();
+  }
+
+  public long getScmContainerSyncDurationMs() {
+    return scmContainerSyncDurationMs.get();
+  }
+
+  public long getContainerSyncDurationMs(
+      HddsProtos.LifeCycleState state) {
+    return getStateGauge(containerSyncDurationMs, state);
+  }
+
+  public long getContainerCountDrift(
+      HddsProtos.LifeCycleState state) {
+    return getStateGauge(containerCountDrift, state);
+  }
+
+  @Override
+  public void getMetrics(MetricsCollector collector, boolean all) {
+    MetricsRecordBuilder builder = collector.addRecord(SOURCE_NAME);
+    builder.addGauge(SCM_CONTAINER_SYNC_STATUS, getScmContainerSyncStatus());
+    builder.addGauge(SCM_CONTAINER_SYNC_DURATION_MS,
+        getScmContainerSyncDurationMs());
+    for (HddsProtos.LifeCycleState state : SYNC_STATES) {
+      builder.addGauge(containerSyncDurationMetricInfo.get(state),
+          getContainerSyncDurationMs(state));
+      builder.addGauge(containerCountDriftMetricInfo.get(state),
+          getContainerCountDrift(state));
+    }
+  }
+
+  private static Map<HddsProtos.LifeCycleState, AtomicLong>
+      initStateGaugeValues() {
+    Map<HddsProtos.LifeCycleState, AtomicLong> gauges =
+        new EnumMap<>(HddsProtos.LifeCycleState.class);
+    for (HddsProtos.LifeCycleState state : SYNC_STATES) {
+      gauges.put(state, new AtomicLong());
+    }
+    return Collections.unmodifiableMap(gauges);
+  }
+
+  private static Map<HddsProtos.LifeCycleState, MetricsInfo>
+      initSyncDurationMetricInfo() {
+    Map<HddsProtos.LifeCycleState, MetricsInfo> metrics =
+        new EnumMap<>(HddsProtos.LifeCycleState.class);
+    for (HddsProtos.LifeCycleState state : SYNC_STATES) {
+      String stateName = metricStateName(state);
+      metrics.put(state, Interns.info(
+          CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, stateName)
+              + "ContainerSyncDurationMs",
+          "Time taken by the " + stateName
+              + " container sync pass in milliseconds"));
+    }
+    return Collections.unmodifiableMap(metrics);
+  }
+
+  private static Map<HddsProtos.LifeCycleState, MetricsInfo>
+      initCountDriftMetricInfo() {
+    Map<HddsProtos.LifeCycleState, MetricsInfo> metrics =
+        new EnumMap<>(HddsProtos.LifeCycleState.class);
+    for (HddsProtos.LifeCycleState state : SYNC_STATES) {
+      String stateName = metricStateName(state);
+      metrics.put(state, Interns.info(
+          CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, stateName)
+              + "ContainerCountDrift",
+          "Last successfully observed container count drift at start of sync 
pass "
+              + "(SCM count minus Recon count for " + stateName + " state)."));
+    }
+    return Collections.unmodifiableMap(metrics);
   }
 
-  public void setLastTargetedSyncDurationMs(long durationMs) {
-    lastTargetedSyncDurationMs.set(durationMs);
+  private static String metricStateName(HddsProtos.LifeCycleState state) {
+    return CaseFormat.UPPER_UNDERSCORE.to(
+        CaseFormat.UPPER_CAMEL, state.name());
   }
 
-  public int getTargetedSyncStatus() {
-    return targetedSyncStatus.value();
+  private static void setStateGauge(
+      Map<HddsProtos.LifeCycleState, AtomicLong> gauges,
+      HddsProtos.LifeCycleState state,
+      long value) {
+    AtomicLong gauge = gauges.get(state);
+    if (gauge != null) {
+      gauge.set(value);
+    }
   }
 
-  public long getLastTargetedSyncDurationMs() {
-    return lastTargetedSyncDurationMs.value();
+  private static long getStateGauge(
+      Map<HddsProtos.LifeCycleState, AtomicLong> gauges,
+      HddsProtos.LifeCycleState state) {
+    AtomicLong gauge = gauges.get(state);
+    return gauge != null ? gauge.get() : 0L;
   }
 }
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java
index 679e3c3c71e..73f76e4731a 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerManagerFacade.java
@@ -548,7 +548,8 @@ public 
ReconStorageContainerManagerFacade(OzoneConfiguration conf,
     containerSyncHelper = new ReconStorageContainerSyncHelper(
         scmServiceProvider,
         ozoneConfiguration,
-        containerManager
+        containerManager,
+        containerSyncMetrics
     );
   }
 
@@ -597,8 +598,8 @@ public void start() {
       initializePipelinesFromScm();
     }
     // -----------------------------------------------------------------------
-    // Scheduler (incremental/targeted sync): runs on the configured interval.
-    // Each cycle directly runs targeted reconciliation. The sync itself 
already
+    // Scheduler (SCM container sync): runs on the configured interval.
+    // Each cycle directly runs SCM container reconciliation. The sync itself 
already
     // fetches the SCM state counts needed for pagination, so a separate drift
     // preflight would duplicate SCM calls before doing the same work.
     // -----------------------------------------------------------------------
@@ -617,9 +618,9 @@ public void start() {
         return;
       }
       try {
-        boolean success = runTargetedSyncWithMetrics();
+        boolean success = runScmContainerSyncWithMetrics();
         if (!success) {
-          LOG.warn("Targeted sync completed with one or more phase failures. "
+          LOG.warn("SCM container sync completed with one or more phase 
failures. "
               + "Check logs above for details.");
         }
       } catch (Throwable t) {
@@ -883,10 +884,10 @@ private void cleanupFailedOrCancelledCheckpoint(File 
checkpointLocation,
    * Runs targeted reconciliation immediately rather than waiting for the next
    * scheduled cycle.
    */
-  public boolean triggerTargetedSCMContainerSync() {
+  public boolean triggerSCMContainerSync() {
     if (isSyncDataFromSCMRunning.compareAndSet(false, true)) {
       try {
-        return runTargetedSyncWithMetrics();
+        return runScmContainerSyncWithMetrics();
       } finally {
         isSyncDataFromSCMRunning.compareAndSet(true, false);
       }
@@ -896,22 +897,22 @@ public boolean triggerTargetedSCMContainerSync() {
     }
   }
 
-  private boolean runTargetedSyncWithMetrics() {
+  private boolean runScmContainerSyncWithMetrics() {
     long startTime = Time.monotonicNow();
-    containerSyncMetrics.setTargetedSyncStatus(
-        ReconScmContainerSyncMetrics.TARGETED_SYNC_STATUS_IN_PROGRESS);
+    containerSyncMetrics.setScmContainerSyncStatus(
+        ReconScmContainerSyncMetrics.SCM_CONTAINER_SYNC_STATUS_IN_PROGRESS);
     try {
       boolean success = containerSyncHelper.syncWithSCMContainerInfo();
-      containerSyncMetrics.setTargetedSyncStatus(success
-          ? ReconScmContainerSyncMetrics.TARGETED_SYNC_STATUS_SUCCESS
-          : ReconScmContainerSyncMetrics.TARGETED_SYNC_STATUS_FAILURE);
+      containerSyncMetrics.setScmContainerSyncStatus(success
+          ? ReconScmContainerSyncMetrics.SCM_CONTAINER_SYNC_STATUS_SUCCESS
+          : ReconScmContainerSyncMetrics.SCM_CONTAINER_SYNC_STATUS_FAILURE);
       return success;
     } catch (RuntimeException | Error e) {
-      containerSyncMetrics.setTargetedSyncStatus(
-          ReconScmContainerSyncMetrics.TARGETED_SYNC_STATUS_FAILURE);
+      containerSyncMetrics.setScmContainerSyncStatus(
+          ReconScmContainerSyncMetrics.SCM_CONTAINER_SYNC_STATUS_FAILURE);
       throw e;
     } finally {
-      containerSyncMetrics.setLastTargetedSyncDurationMs(
+      containerSyncMetrics.setScmContainerSyncDurationMs(
           Time.monotonicNow() - startTime);
     }
   }
diff --git 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerSyncHelper.java
 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerSyncHelper.java
index bbd03aab0d5..1a7d4e28d00 100644
--- 
a/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerSyncHelper.java
+++ 
b/hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/scm/ReconStorageContainerSyncHelper.java
@@ -33,6 +33,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
@@ -42,7 +43,9 @@
 import org.apache.hadoop.hdds.scm.container.ContainerNotFoundException;
 import 
org.apache.hadoop.hdds.scm.container.common.helpers.ContainerWithPipeline;
 import 
org.apache.hadoop.ozone.common.statemachine.InvalidStateTransitionException;
+import org.apache.hadoop.ozone.recon.metrics.ReconScmContainerSyncMetrics;
 import org.apache.hadoop.ozone.recon.spi.StorageContainerServiceProvider;
+import org.apache.hadoop.util.Time;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -121,6 +124,8 @@ class ReconStorageContainerSyncHelper {
    */
   private static final long CONTAINER_WITH_PIPELINE_PROTO_SIZE_BYTES = 1024;
 
+  private static final int LIVE_STATE_SYNC_PROGRESS_LOG_INTERVAL = 50;
+
   /**
    * Monotonic cursor for OPEN add-only sync. OPEN containers are
    * created with increasing container IDs, so each cycle only needs to scan
@@ -141,13 +146,17 @@ class ReconStorageContainerSyncHelper {
   private final StorageContainerServiceProvider scmServiceProvider;
   private final OzoneConfiguration ozoneConfiguration;
   private final ReconContainerManager containerManager;
+  private final ReconScmContainerSyncMetrics containerSyncMetrics;
 
   ReconStorageContainerSyncHelper(StorageContainerServiceProvider 
scmServiceProvider,
                                   OzoneConfiguration ozoneConfiguration,
-                                  ReconContainerManager containerManager) {
+                                  ReconContainerManager containerManager,
+                                  ReconScmContainerSyncMetrics 
containerSyncMetrics) {
     this.scmServiceProvider = scmServiceProvider;
     this.ozoneConfiguration = ozoneConfiguration;
     this.containerManager = containerManager;
+    this.containerSyncMetrics =
+        Objects.requireNonNull(containerSyncMetrics, "containerSyncMetrics");
   }
 
   /**
@@ -168,8 +177,10 @@ public boolean syncWithSCMContainerInfo() {
    */
   private boolean syncContainersForState(HddsProtos.LifeCycleState scmState,
                                          boolean incrementalOpen) {
+    long startTime = Time.monotonicNow();
     try {
       long total = scmServiceProvider.getContainerCount(scmState);
+      updateContainerCountDrift(scmState, total);
       if (total == 0) {
         LOG.debug("{} sync: no containers found in SCM.", scmState);
         return true;
@@ -181,7 +192,10 @@ private boolean 
syncContainersForState(HddsProtos.LifeCycleState scmState,
       long retrieved = 0;
       int addedCount = 0;
       int reconciledCount = 0;
+      int batchCount = 0;
 
+      LOG.info("{} sync starting: total={}, batchSize={}, startId={}.",
+          scmState, total, batchSize, initialStart);
       while (true) {
         List<ContainerID> batch = scmServiceProvider.getListOfContainerIDs(
             startContainerId, batchSize, scmState);
@@ -215,6 +229,14 @@ private boolean 
syncContainersForState(HddsProtos.LifeCycleState scmState,
         }
         startContainerId = ContainerID.valueOf(nextID);
         retrieved += batch.size();
+        batchCount++;
+
+        if (batchCount % LIVE_STATE_SYNC_PROGRESS_LOG_INTERVAL == 0) {
+          LOG.info("{} sync progress: batch={}, totalRetrieved={}, added={}, "
+                  + "reconciled={}, nextId={}.",
+              scmState, batchCount, retrieved, addedCount, reconciledCount,
+              nextID);
+        }
       }
 
       LOG.info("{} sync complete from start {}, checked {}, added {}, 
reconciled {}.",
@@ -223,6 +245,8 @@ private boolean 
syncContainersForState(HddsProtos.LifeCycleState scmState,
     } catch (Exception e) {
       LOG.error("{} sync: unexpected error.", scmState, e);
       return false;
+    } finally {
+      updateContainerSyncDuration(scmState, Time.monotonicNow() - startTime);
     }
   }
 
@@ -360,7 +384,9 @@ private int rebuildContainerFromScm(ContainerID containerID,
    * @return {@code true} if all RPC calls completed without error
    */
   private boolean syncDeletedContainers() {
+    long startTime = Time.monotonicNow();
     try {
+      updateDeletedContainerCountDrift();
       int configuredBatch = ozoneConfiguration.getInt(
           OZONE_RECON_SCM_DELETED_CONTAINER_CHECK_BATCH_SIZE,
           OZONE_RECON_SCM_DELETED_CONTAINER_CHECK_BATCH_SIZE_DEFAULT);
@@ -393,9 +419,34 @@ private boolean syncDeletedContainers() {
     } catch (Exception e) {
       LOG.error("DELETED sync: unexpected error.", e);
       return false;
+    } finally {
+      updateContainerSyncDuration(HddsProtos.LifeCycleState.DELETED,
+          Time.monotonicNow() - startTime);
+    }
+  }
+
+  private void updateDeletedContainerCountDrift() {
+    try {
+      long total = scmServiceProvider.getContainerCount(
+          HddsProtos.LifeCycleState.DELETED);
+      updateContainerCountDrift(HddsProtos.LifeCycleState.DELETED, total);
+    } catch (Exception e) {
+      LOG.warn("DELETED sync: unable to update pre-sync count drift metric.", 
e);
     }
   }
 
+  private void updateContainerCountDrift(HddsProtos.LifeCycleState state,
+                                         long scmCount) {
+    long reconCount = containerManager.getContainerStateCount(state);
+    containerSyncMetrics.setContainerCountDrift(state,
+        scmCount - reconCount);
+  }
+
+  private void updateContainerSyncDuration(HddsProtos.LifeCycleState state,
+                                           long durationMs) {
+    containerSyncMetrics.setContainerSyncDurationMs(state, durationMs);
+  }
+
   /**
    * Processes one page of DELETED container IDs from SCM.
    * For each container:
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/metrics/TestReconScmContainerSyncMetrics.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/metrics/TestReconScmContainerSyncMetrics.java
new file mode 100644
index 00000000000..6208a7cd96a
--- /dev/null
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/metrics/TestReconScmContainerSyncMetrics.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.hadoop.ozone.recon.metrics;
+
+import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.CLOSED;
+import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.CLOSING;
+import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.DELETED;
+import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.DELETING;
+import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.OPEN;
+import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.QUASI_CLOSED;
+import static org.apache.hadoop.metrics2.lib.Interns.info;
+import static org.apache.ozone.test.MetricsAsserts.eqName;
+import static org.apache.ozone.test.MetricsAsserts.getIntGauge;
+import static org.apache.ozone.test.MetricsAsserts.getLongGauge;
+import static org.apache.ozone.test.MetricsAsserts.getMetrics;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import org.apache.hadoop.metrics2.MetricsRecordBuilder;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for Recon SCM container sync metrics.
+ */
+class TestReconScmContainerSyncMetrics {
+
+  private ReconScmContainerSyncMetrics metrics;
+
+  @BeforeEach
+  void setUp() {
+    metrics = ReconScmContainerSyncMetrics.create();
+  }
+
+  @AfterEach
+  void tearDown() {
+    metrics.unRegister();
+  }
+
+  @Test
+  void testStateMetricsAreEmittedForReconciledStatesOnly() {
+    metrics.setContainerSyncDurationMs(OPEN, 10L);
+    metrics.setContainerSyncDurationMs(QUASI_CLOSED, 20L);
+    metrics.setContainerSyncDurationMs(CLOSED, 30L);
+    metrics.setContainerSyncDurationMs(DELETED, 40L);
+    metrics.setContainerCountDrift(OPEN, 2L);
+    metrics.setContainerCountDrift(QUASI_CLOSED, 0L);
+    metrics.setContainerCountDrift(CLOSED, -3L);
+    metrics.setContainerCountDrift(DELETED, 4L);
+    metrics.setContainerCountDrift(CLOSING, 100L);
+    metrics.setContainerSyncDurationMs(DELETING, 200L);
+    metrics.setScmContainerSyncStatus(
+        ReconScmContainerSyncMetrics.SCM_CONTAINER_SYNC_STATUS_SUCCESS);
+    metrics.setScmContainerSyncDurationMs(500L);
+
+    MetricsRecordBuilder builder = getMetrics(metrics);
+
+    
assertEquals(ReconScmContainerSyncMetrics.SCM_CONTAINER_SYNC_STATUS_SUCCESS,
+        getIntGauge("scmContainerSyncStatus", builder));
+    assertEquals(500L, getLongGauge("scmContainerSyncDurationMs", builder));
+    assertEquals(10L, getLongGauge("openContainerSyncDurationMs", builder));
+    assertEquals(20L,
+        getLongGauge("quasiClosedContainerSyncDurationMs", builder));
+    assertEquals(30L, getLongGauge("closedContainerSyncDurationMs", builder));
+    assertEquals(40L, getLongGauge("deletedContainerSyncDurationMs", builder));
+    assertEquals(2L, getLongGauge("openContainerCountDrift", builder));
+    assertEquals(0L,
+        getLongGauge("quasiClosedContainerCountDrift", builder));
+    assertEquals(-3L, getLongGauge("closedContainerCountDrift", builder));
+    assertEquals(4L, getLongGauge("deletedContainerCountDrift", builder));
+
+    verify(builder, never()).addGauge(
+        eqName(info("closingContainerSyncDurationMs", "")), eq(100L));
+    verify(builder, never()).addGauge(
+        eqName(info("deletingContainerSyncDurationMs", "")), eq(200L));
+    verify(builder, never()).addGauge(
+        eqName(info("closingContainerCountDrift", "")), eq(100L));
+    verify(builder, never()).addGauge(
+        eqName(info("deletingContainerCountDrift", "")), eq(200L));
+  }
+}
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconSCMContainerSyncIntegration.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconSCMContainerSyncIntegration.java
index acb751197f3..0d7147c7e8a 100644
--- 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconSCMContainerSyncIntegration.java
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconSCMContainerSyncIntegration.java
@@ -51,7 +51,9 @@
 import org.apache.hadoop.hdds.scm.container.ContainerInfo;
 import 
org.apache.hadoop.hdds.scm.container.common.helpers.ContainerWithPipeline;
 import org.apache.hadoop.ozone.recon.ReconServerConfigKeys;
+import org.apache.hadoop.ozone.recon.metrics.ReconScmContainerSyncMetrics;
 import org.apache.hadoop.ozone.recon.spi.StorageContainerServiceProvider;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -59,7 +61,7 @@
 
 /**
  * Integration tests for {@link ReconStorageContainerSyncHelper} and
- * {@link 
ReconStorageContainerManagerFacade#triggerTargetedSCMContainerSync()}.
+ * {@link ReconStorageContainerManagerFacade#triggerSCMContainerSync()}.
  *
  * <p>Uses a <em>real</em> {@link ReconContainerManager} backed by RocksDB
  * (from {@link AbstractReconContainerManagerTest}) and a mocked
@@ -86,13 +88,20 @@ public class TestReconSCMContainerSyncIntegration
     extends AbstractReconContainerManagerTest {
 
   private StorageContainerServiceProvider mockScm;
+  private ReconScmContainerSyncMetrics metrics;
   private ReconStorageContainerSyncHelper syncHelper;
 
   @BeforeEach
   void setupSyncHelper() {
     mockScm = mock(StorageContainerServiceProvider.class);
+    metrics = ReconScmContainerSyncMetrics.create();
     syncHelper = new ReconStorageContainerSyncHelper(
-        mockScm, getConf(), getContainerManager());
+        mockScm, getConf(), getContainerManager(), metrics);
+  }
+
+  @AfterEach
+  void tearDownSyncMetrics() {
+    metrics.unRegister();
   }
 
   // 
---------------------------------------------------------------------------
@@ -278,7 +287,7 @@ void multiplePagesAllBatchesProcessed() throws Exception {
       getConf().setLong(
           ReconServerConfigKeys.OZONE_RECON_SCM_CONTAINER_ID_BATCH_SIZE, 3L);
       ReconStorageContainerSyncHelper pagedHelper = new 
ReconStorageContainerSyncHelper(
-          mockScm, getConf(), getContainerManager());
+          mockScm, getConf(), getContainerManager(), metrics);
 
       when(mockScm.getContainerCount(CLOSED)).thenReturn(7L);
       // Page 1: IDs 1-3
@@ -411,7 +420,7 @@ void openSyncUsesCursorAndOnlyFetchesNewOpenContainers() 
throws Exception {
       getConf().setLong(
           ReconServerConfigKeys.OZONE_RECON_SCM_CONTAINER_ID_BATCH_SIZE, 2L);
       ReconStorageContainerSyncHelper pagedHelper = new 
ReconStorageContainerSyncHelper(
-          mockScm, getConf(), getContainerManager());
+          mockScm, getConf(), getContainerManager(), metrics);
 
       when(mockScm.getContainerCount(OPEN)).thenReturn(2L, 1L, 0L);
       when(mockScm.getListOfContainerIDs(
@@ -681,7 +690,7 @@ void batchSizeLimitsDeletedListPagePerCycle() throws 
Exception {
       seedRecon(400, 10, CLOSED);
       getConf().setInt(OZONE_RECON_SCM_DELETED_CONTAINER_CHECK_BATCH_SIZE, 3);
       ReconStorageContainerSyncHelper batchHelper = new 
ReconStorageContainerSyncHelper(
-          mockScm, getConf(), getContainerManager());
+          mockScm, getConf(), getContainerManager(), metrics);
 
       // SCM's DELETED list page 1 (IDs 400-402), then empty.
       List<ContainerID> firstPage = idRange(400, 403);
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconStorageContainerManagerFacade.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconStorageContainerManagerFacade.java
index 54b01d3f5fb..42fd25b36c7 100644
--- 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconStorageContainerManagerFacade.java
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconStorageContainerManagerFacade.java
@@ -22,13 +22,17 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.io.File;
+import java.lang.reflect.Field;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager;
 import org.apache.hadoop.hdds.utils.db.DBCheckpoint;
@@ -36,10 +40,12 @@
 import org.apache.hadoop.hdds.utils.db.DBStoreBuilder;
 import org.apache.hadoop.ozone.om.OMMetadataManager;
 import org.apache.hadoop.ozone.recon.ReconTestInjector;
+import org.apache.hadoop.ozone.recon.metrics.ReconScmContainerSyncMetrics;
 import org.apache.hadoop.ozone.recon.recovery.ReconOMMetadataManager;
 import org.apache.hadoop.ozone.recon.spi.StorageContainerServiceProvider;
 import org.apache.hadoop.ozone.recon.spi.impl.OzoneManagerServiceProviderImpl;
 import 
org.apache.hadoop.ozone.recon.spi.impl.StorageContainerServiceProviderImpl;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
@@ -51,6 +57,15 @@ class TestReconStorageContainerManagerFacade {
   @TempDir
   private Path temporaryFolder;
 
+  private final List<ReconScmContainerSyncMetrics> metricsToUnregister =
+      new ArrayList<>();
+
+  @AfterEach
+  void tearDown() {
+    metricsToUnregister.forEach(ReconScmContainerSyncMetrics::unRegister);
+    metricsToUnregister.clear();
+  }
+
   @Test
   void testScmSnapshotDbIsOpenedAtCanonicalReconPath() throws Exception {
     StorageContainerServiceProvider scmServiceProvider =
@@ -75,6 +90,7 @@ void testScmSnapshotDbIsOpenedAtCanonicalReconPath() throws 
Exception {
 
     ReconStorageContainerManagerFacade reconScm =
         injector.getInstance(ReconStorageContainerManagerFacade.class);
+    metricsToUnregister.add(getContainerSyncMetrics(reconScm));
     OzoneConfiguration conf = injector.getInstance(OzoneConfiguration.class);
     File checkpointDir =
         temporaryFolder.resolve("scm.snapshot.db_test").toFile();
@@ -98,4 +114,105 @@ void testScmSnapshotDbIsOpenedAtCanonicalReconPath() 
throws Exception {
     assertTrue(canonicalReconScmDb.exists());
     assertFalse(checkpointDir.exists());
   }
+
+  @Test
+  void testTriggerSCMContainerSyncSetsSuccessStatusAndDuration()
+      throws Exception {
+    ReconStorageContainerManagerFacade reconScm = createReconScmFacade();
+    ReconStorageContainerSyncHelper syncHelper =
+        mock(ReconStorageContainerSyncHelper.class);
+    ReconScmContainerSyncMetrics metrics = getContainerSyncMetrics(reconScm);
+    metrics.setScmContainerSyncDurationMs(-1L);
+    setContainerSyncHelper(reconScm, syncHelper);
+    when(syncHelper.syncWithSCMContainerInfo()).thenReturn(true);
+
+    assertTrue(reconScm.triggerSCMContainerSync());
+
+    
assertEquals(ReconScmContainerSyncMetrics.SCM_CONTAINER_SYNC_STATUS_SUCCESS,
+        metrics.getScmContainerSyncStatus());
+    assertNotEquals(-1L, metrics.getScmContainerSyncDurationMs());
+  }
+
+  @Test
+  void testTriggerSCMContainerSyncSetsFailureStatusAndDuration()
+      throws Exception {
+    ReconStorageContainerManagerFacade reconScm = createReconScmFacade();
+    ReconStorageContainerSyncHelper syncHelper =
+        mock(ReconStorageContainerSyncHelper.class);
+    ReconScmContainerSyncMetrics metrics = getContainerSyncMetrics(reconScm);
+    metrics.setScmContainerSyncDurationMs(-1L);
+    setContainerSyncHelper(reconScm, syncHelper);
+    when(syncHelper.syncWithSCMContainerInfo()).thenReturn(false);
+
+    assertFalse(reconScm.triggerSCMContainerSync());
+
+    
assertEquals(ReconScmContainerSyncMetrics.SCM_CONTAINER_SYNC_STATUS_FAILURE,
+        metrics.getScmContainerSyncStatus());
+    assertNotEquals(-1L, metrics.getScmContainerSyncDurationMs());
+  }
+
+  @Test
+  void testTriggerSCMContainerSyncSetsFailureStatusAndDurationOnException()
+      throws Exception {
+    ReconStorageContainerManagerFacade reconScm = createReconScmFacade();
+    ReconStorageContainerSyncHelper syncHelper =
+        mock(ReconStorageContainerSyncHelper.class);
+    ReconScmContainerSyncMetrics metrics = getContainerSyncMetrics(reconScm);
+    RuntimeException failure = new RuntimeException("sync failed");
+    metrics.setScmContainerSyncDurationMs(-1L);
+    setContainerSyncHelper(reconScm, syncHelper);
+    when(syncHelper.syncWithSCMContainerInfo()).thenThrow(failure);
+
+    RuntimeException thrown = assertThrows(RuntimeException.class,
+        reconScm::triggerSCMContainerSync);
+
+    assertEquals(failure, thrown);
+    
assertEquals(ReconScmContainerSyncMetrics.SCM_CONTAINER_SYNC_STATUS_FAILURE,
+        metrics.getScmContainerSyncStatus());
+    assertNotEquals(-1L, metrics.getScmContainerSyncDurationMs());
+  }
+
+  private ReconStorageContainerManagerFacade createReconScmFacade()
+      throws Exception {
+    StorageContainerServiceProvider scmServiceProvider =
+        mock(StorageContainerServiceProviderImpl.class);
+    OMMetadataManager omMetadataManager = initializeNewOmMetadataManager(
+        Files.createDirectory(temporaryFolder.resolve("OmMetadata")).toFile());
+    ReconOMMetadataManager reconOMMetadataManager =
+        getTestReconOmMetadataManager(omMetadataManager,
+            Files.createDirectory(temporaryFolder.resolve("ReconOmMetadata"))
+                .toFile());
+    ReconTestInjector injector =
+        new ReconTestInjector.Builder(temporaryFolder.toFile())
+            .withReconSqlDb()
+            .withReconOm(reconOMMetadataManager)
+            .withOmServiceProvider(mock(OzoneManagerServiceProviderImpl.class))
+            .addBinding(OzoneStorageContainerManager.class,
+                ReconStorageContainerManagerFacade.class)
+            .withContainerDB()
+            .addBinding(StorageContainerServiceProvider.class,
+                scmServiceProvider)
+            .build();
+    ReconStorageContainerManagerFacade reconScm =
+        injector.getInstance(ReconStorageContainerManagerFacade.class);
+    metricsToUnregister.add(getContainerSyncMetrics(reconScm));
+    return reconScm;
+  }
+
+  private static void setContainerSyncHelper(
+      ReconStorageContainerManagerFacade reconScm,
+      ReconStorageContainerSyncHelper syncHelper) throws Exception {
+    Field helperField = ReconStorageContainerManagerFacade.class
+        .getDeclaredField("containerSyncHelper");
+    helperField.setAccessible(true);
+    helperField.set(reconScm, syncHelper);
+  }
+
+  private static ReconScmContainerSyncMetrics getContainerSyncMetrics(
+      ReconStorageContainerManagerFacade reconScm) throws Exception {
+    Field metricsField = ReconStorageContainerManagerFacade.class
+        .getDeclaredField("containerSyncMetrics");
+    metricsField.setAccessible(true);
+    return (ReconScmContainerSyncMetrics) metricsField.get(reconScm);
+  }
 }
diff --git 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconStorageContainerSyncHelper.java
 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconStorageContainerSyncHelper.java
index 03b771e7ed2..d858c554c73 100644
--- 
a/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconStorageContainerSyncHelper.java
+++ 
b/hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/scm/TestReconStorageContainerSyncHelper.java
@@ -18,8 +18,12 @@
 package org.apache.hadoop.ozone.recon.scm;
 
 import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.CLOSED;
+import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.DELETED;
+import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.OPEN;
+import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.LifeCycleState.QUASI_CLOSED;
 import static 
org.apache.hadoop.hdds.protocol.proto.HddsProtos.ReplicationFactor.ONE;
 import static 
org.apache.hadoop.ozone.recon.ReconServerConfigKeys.OZONE_RECON_SCM_CONTAINER_ID_BATCH_SIZE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -36,7 +40,10 @@
 import org.apache.hadoop.hdds.scm.container.ContainerID;
 import org.apache.hadoop.hdds.scm.container.ContainerInfo;
 import 
org.apache.hadoop.hdds.scm.container.common.helpers.ContainerWithPipeline;
+import org.apache.hadoop.ozone.recon.metrics.ReconScmContainerSyncMetrics;
 import org.apache.hadoop.ozone.recon.spi.StorageContainerServiceProvider;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 class TestReconStorageContainerSyncHelper {
@@ -47,16 +54,51 @@ class TestReconStorageContainerSyncHelper {
   private final ReconContainerManager mockContainerManager =
       mock(ReconContainerManager.class);
 
-  private final ReconStorageContainerSyncHelper syncHelper;
+  private ReconScmContainerSyncMetrics metrics;
+  private ReconStorageContainerSyncHelper syncHelper;
 
-  TestReconStorageContainerSyncHelper() {
+  @BeforeEach
+  void setUp() {
+    metrics = ReconScmContainerSyncMetrics.create();
     syncHelper = new ReconStorageContainerSyncHelper(
         mockScmServiceProvider,
         new OzoneConfiguration(),
-        mockContainerManager
+        mockContainerManager,
+        metrics
     );
   }
 
+  @AfterEach
+  void tearDown() {
+    metrics.unRegister();
+  }
+
+  @Test
+  void testContainerSyncMetricsTrackPreSyncDriftAndDuration() throws Exception 
{
+    when(mockScmServiceProvider.getContainerCount(OPEN)).thenReturn(5L);
+    
when(mockScmServiceProvider.getContainerCount(QUASI_CLOSED)).thenReturn(1L);
+    when(mockScmServiceProvider.getContainerCount(CLOSED)).thenReturn(8L);
+    when(mockScmServiceProvider.getContainerCount(DELETED)).thenReturn(9L);
+    when(mockContainerManager.getContainerStateCount(OPEN)).thenReturn(3);
+    
when(mockContainerManager.getContainerStateCount(QUASI_CLOSED)).thenReturn(1);
+    when(mockContainerManager.getContainerStateCount(CLOSED)).thenReturn(10);
+    when(mockContainerManager.getContainerStateCount(DELETED)).thenReturn(7);
+    when(mockScmServiceProvider.getListOfContainerIDs(
+        any(), any(Integer.class), any())).thenReturn(Collections.emptyList());
+
+    boolean result = syncHelper.syncWithSCMContainerInfo();
+
+    assertTrue(result);
+    assertEquals(2L, metrics.getContainerCountDrift(OPEN));
+    assertEquals(0L, metrics.getContainerCountDrift(QUASI_CLOSED));
+    assertEquals(-2L, metrics.getContainerCountDrift(CLOSED));
+    assertEquals(2L, metrics.getContainerCountDrift(DELETED));
+    assertTrue(metrics.getContainerSyncDurationMs(OPEN) >= 0);
+    assertTrue(metrics.getContainerSyncDurationMs(QUASI_CLOSED) >= 0);
+    assertTrue(metrics.getContainerSyncDurationMs(CLOSED) >= 0);
+    assertTrue(metrics.getContainerSyncDurationMs(DELETED) >= 0);
+  }
+
   @Test
   void testContainerMissingFromReconIsAdded() throws Exception {
     ContainerID cid = ContainerID.valueOf(42L);
@@ -92,7 +134,7 @@ void testContainerMissingFromReconIsAddedWhenMultiplePages() 
throws Exception {
     OzoneConfiguration pagedConf = new OzoneConfiguration();
     pagedConf.setLong(OZONE_RECON_SCM_CONTAINER_ID_BATCH_SIZE, 2L);
     ReconStorageContainerSyncHelper pagedHelper = new 
ReconStorageContainerSyncHelper(
-        mockScmServiceProvider, pagedConf, mockContainerManager);
+        mockScmServiceProvider, pagedConf, mockContainerManager, metrics);
 
     // Page 1: containers 1 and 2 (both missing from Recon)
     ContainerID cid1 = ContainerID.valueOf(1L);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to