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

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


The following commit(s) were added to refs/heads/master by this push:
     new dd52940c3493 feat(flink): add Hive sync metrics to the write 
coordinator (#20148)
dd52940c3493 is described below

commit dd52940c349327fa51da6ffc8973a334a44b5c7f
Author: Shihuan Liu <[email protected]>
AuthorDate: Wed Sep 30 21:51:34 2026 -0700

    feat(flink): add Hive sync metrics to the write coordinator (#20148)
    
    * feat(flink): add Hive sync metrics to the write coordinator
    
    The Flink write coordinator runs Hive sync on its own executor, which
    only logs a failure. A table that has stopped syncing therefore leaves
    no trace outside the JobManager log.
    
    Register FlinkHiveSyncMetrics on the coordinator's metric group when
    Hive sync is enabled, and record every sync in doSyncHive():
    
    - hiveSyncSuccessCount / hiveSyncFailureCount (counters)
    - hiveSyncDurationMs (histogram over the last 100 syncs, failures included)
    - hiveSyncLastSuccessTimeMs (gauge, epoch millis of the last completed sync)
    
    * Add tests for the Hive sync metrics
    
    * Count a sync skipped by hive_sync.ignore_exceptions as a failure
    
    * refactor(flink): simplify Hive sync exception handling
    
    ---------
    
    Co-authored-by: danny0405 <[email protected]>
---
 .../apache/hudi/metrics/FlinkHiveSyncMetrics.java  |  88 +++++++++
 .../hudi/sink/StreamWriteOperatorCoordinator.java  |  18 ++
 .../hudi/metrics/TestFlinkHiveSyncMetrics.java     | 201 +++++++++++++++++++++
 .../sink/TestStreamWriteOperatorCoordinator.java   | 103 +++++++++++
 .../java/org/apache/hudi/hive/HiveSyncTool.java    |   8 +
 5 files changed, 418 insertions(+)

diff --git 
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/metrics/FlinkHiveSyncMetrics.java
 
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/metrics/FlinkHiveSyncMetrics.java
new file mode 100644
index 000000000000..cc25e6080dce
--- /dev/null
+++ 
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/metrics/FlinkHiveSyncMetrics.java
@@ -0,0 +1,88 @@
+/*
+ * 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.hudi.metrics;
+
+import com.codahale.metrics.SlidingWindowReservoir;
+import org.apache.flink.dropwizard.metrics.DropwizardHistogramWrapper;
+import org.apache.flink.metrics.Counter;
+import org.apache.flink.metrics.Histogram;
+import org.apache.flink.metrics.MetricGroup;
+import org.apache.flink.metrics.ThreadSafeSimpleCounter;
+
+/**
+ * Metrics for the Hive sync that {@link 
org.apache.hudi.sink.StreamWriteOperatorCoordinator} runs
+ * on the JobManager after each commit.
+ *
+ * <p>A sync runs on the coordinator's Hive sync executor after a checkpoint 
commit, and on its event
+ * executor after the commit that ends a bounded input, so each metric may be 
updated from either
+ * thread.
+ */
+public class FlinkHiveSyncMetrics extends HoodieFlinkMetrics {
+  private static final int HISTOGRAM_WINDOW_SIZE = 100;
+
+  static final String HIVE_SYNC_SUCCESS_COUNT = "hiveSyncSuccessCount";
+  static final String HIVE_SYNC_FAILURE_COUNT = "hiveSyncFailureCount";
+  static final String HIVE_SYNC_DURATION_MS = "hiveSyncDurationMs";
+  static final String HIVE_SYNC_LAST_SUCCESS_TIME_MS = 
"hiveSyncLastSuccessTimeMs";
+
+  /** Number of syncs that completed. */
+  private final Counter syncSuccessCount = new ThreadSafeSimpleCounter();
+
+  /**
+   * Number of syncs that threw, or that did nothing because the metastore 
client could not be created
+   * and {@code hive_sync.ignore_exceptions} swallowed the error. The sync 
executor only logs a
+   * failure, so without this counter a table that has stopped syncing leaves 
no trace outside the
+   * JobManager log.
+   */
+  private final Counter syncFailureCount = new ThreadSafeSimpleCounter();
+
+  /**
+   * Duration of each sync in milliseconds, failed ones included: a sync that 
fails after retrying
+   * an unreachable metastore is the slow case this is meant to show.
+   */
+  private final Histogram syncDurationMs;
+
+  /** Wall-clock time of the last completed sync in epoch milliseconds, or 0 
until the first one. */
+  private volatile long lastSyncSuccessTimeMs = 0L;
+
+  public FlinkHiveSyncMetrics(MetricGroup metricGroup) {
+    super(metricGroup);
+    this.syncDurationMs = new DropwizardHistogramWrapper(
+        new com.codahale.metrics.Histogram(new 
SlidingWindowReservoir(HISTOGRAM_WINDOW_SIZE)));
+  }
+
+  @Override
+  public void registerMetrics() {
+    metricGroup.counter(HIVE_SYNC_SUCCESS_COUNT, syncSuccessCount);
+    metricGroup.counter(HIVE_SYNC_FAILURE_COUNT, syncFailureCount);
+    metricGroup.histogram(HIVE_SYNC_DURATION_MS, syncDurationMs);
+    metricGroup.gauge(HIVE_SYNC_LAST_SUCCESS_TIME_MS, () -> 
lastSyncSuccessTimeMs);
+  }
+
+  public void markSyncSucceeded(long durationMs) {
+    syncDurationMs.update(durationMs);
+    syncSuccessCount.inc();
+    lastSyncSuccessTimeMs = System.currentTimeMillis();
+  }
+
+  public void markSyncFailed(long durationMs) {
+    syncDurationMs.update(durationMs);
+    syncFailureCount.inc();
+  }
+}
diff --git 
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/StreamWriteOperatorCoordinator.java
 
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/StreamWriteOperatorCoordinator.java
index 1cdc59a8286a..8a0e2a16ae99 100644
--- 
a/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/StreamWriteOperatorCoordinator.java
+++ 
b/hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/StreamWriteOperatorCoordinator.java
@@ -25,6 +25,7 @@ import org.apache.hudi.common.model.WriteOperationType;
 import org.apache.hudi.common.table.HoodieTableMetaClient;
 import org.apache.hudi.common.table.timeline.HoodieTimeline;
 import org.apache.hudi.common.util.CommitUtils;
+import org.apache.hudi.common.util.HoodieTimer;
 import org.apache.hudi.common.util.Option;
 import org.apache.hudi.common.util.SerializationUtils;
 import org.apache.hudi.common.util.ValidationUtils;
@@ -35,6 +36,7 @@ import org.apache.hudi.configuration.OptionsResolver;
 import org.apache.hudi.exception.HoodieException;
 import org.apache.hudi.hadoop.fs.HadoopFSUtils;
 import org.apache.hudi.hive.HiveSyncTool;
+import org.apache.hudi.metrics.FlinkHiveSyncMetrics;
 import org.apache.hudi.sink.common.AbstractStreamWriteFunction;
 import org.apache.hudi.sink.event.Correspondent;
 import org.apache.hudi.sink.event.WriteMetadataEvent;
@@ -190,6 +192,11 @@ public class StreamWriteOperatorCoordinator
    */
   private HiveSyncContext hiveSyncContext;
 
+  /**
+   * Metrics for hive sync, registered on the coordinator's metric group when 
hive sync is enabled.
+   */
+  private FlinkHiveSyncMetrics hiveSyncMetrics;
+
   /**
    * The table state.
    */
@@ -457,6 +464,8 @@ public class StreamWriteOperatorCoordinator
         .threadFactory(getThreadFactory("hive-sync"))
         .waitForTasksFinish(true).build();
     this.hiveSyncContext = HiveSyncContext.create(conf, this.storageConf);
+    this.hiveSyncMetrics = new FlinkHiveSyncMetrics(context.metricGroup());
+    this.hiveSyncMetrics.registerMetrics();
   }
 
   private void syncHiveAsync() {
@@ -476,9 +485,18 @@ public class StreamWriteOperatorCoordinator
    * Sync hoodie table metadata to Hive metastore.
    */
   public void doSyncHive() {
+    HoodieTimer timer = HoodieTimer.start();
     try (HiveSyncTool syncTool = hiveSyncContext.hiveSyncTool()) {
+      if (!syncTool.isSyncClientInitialized()) {
+        hiveSyncMetrics.markSyncFailed(timer.endTimer());
+        return;
+      }
       syncTool.syncHoodieTable();
+    } catch (Throwable t) {
+      hiveSyncMetrics.markSyncFailed(timer.endTimer());
+      throw t;
     }
+    hiveSyncMetrics.markSyncSucceeded(timer.endTimer());
   }
 
   private void scheduleTableServices(Boolean committed) {
diff --git 
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/metrics/TestFlinkHiveSyncMetrics.java
 
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/metrics/TestFlinkHiveSyncMetrics.java
new file mode 100644
index 000000000000..536999adb79e
--- /dev/null
+++ 
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/metrics/TestFlinkHiveSyncMetrics.java
@@ -0,0 +1,201 @@
+/*
+ * 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.hudi.metrics;
+
+import org.apache.flink.metrics.Counter;
+import org.apache.flink.metrics.Gauge;
+import org.apache.flink.metrics.Histogram;
+import org.apache.flink.metrics.groups.UnregisteredMetricsGroup;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static 
org.apache.hudi.metrics.FlinkHiveSyncMetrics.HIVE_SYNC_DURATION_MS;
+import static 
org.apache.hudi.metrics.FlinkHiveSyncMetrics.HIVE_SYNC_FAILURE_COUNT;
+import static 
org.apache.hudi.metrics.FlinkHiveSyncMetrics.HIVE_SYNC_LAST_SUCCESS_TIME_MS;
+import static 
org.apache.hudi.metrics.FlinkHiveSyncMetrics.HIVE_SYNC_SUCCESS_COUNT;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link FlinkHiveSyncMetrics}.
+ */
+class TestFlinkHiveSyncMetrics {
+
+  private CapturingMetricGroup metricGroup;
+  private FlinkHiveSyncMetrics metrics;
+
+  @BeforeEach
+  void setUp() {
+    metricGroup = new CapturingMetricGroup();
+    metrics = new FlinkHiveSyncMetrics(metricGroup);
+    metrics.registerMetrics();
+  }
+
+  @Test
+  void testRegisterMetrics() {
+    assertNotNull(metricGroup.counters.get(HIVE_SYNC_SUCCESS_COUNT));
+    assertNotNull(metricGroup.counters.get(HIVE_SYNC_FAILURE_COUNT));
+    assertNotNull(metricGroup.histograms.get(HIVE_SYNC_DURATION_MS));
+    assertNotNull(metricGroup.gauges.get(HIVE_SYNC_LAST_SUCCESS_TIME_MS));
+  }
+
+  @Test
+  void testInitialValues() {
+    assertEquals(0, successCount());
+    assertEquals(0, failureCount());
+    assertEquals(0, duration().getCount());
+    assertEquals(0L, lastSuccessTime());
+  }
+
+  @Test
+  void testMarkSyncSucceeded() {
+    long before = System.currentTimeMillis();
+    metrics.markSyncSucceeded(42L);
+    long after = System.currentTimeMillis();
+
+    assertEquals(1, successCount());
+    assertEquals(0, failureCount());
+    assertEquals(1, duration().getCount());
+    assertEquals(42L, duration().getStatistics().getMax());
+    long lastSuccessTime = lastSuccessTime();
+    assertTrue(lastSuccessTime >= before && lastSuccessTime <= after,
+        "Last success time " + lastSuccessTime + " should be within [" + 
before + ", " + after + "]");
+  }
+
+  @Test
+  void testMarkSyncFailed() {
+    metrics.markSyncFailed(1000L);
+
+    assertEquals(0, successCount());
+    assertEquals(1, failureCount());
+    assertEquals(1, duration().getCount());
+    assertEquals(1000L, duration().getStatistics().getMax());
+    assertEquals(0L, lastSuccessTime(), "A failed sync must not set the last 
success time");
+  }
+
+  @Test
+  void testFailureKeepsLastSuccessTime() {
+    metrics.markSyncSucceeded(10L);
+    long lastSuccessTime = lastSuccessTime();
+
+    metrics.markSyncFailed(20L);
+
+    assertEquals(1, successCount());
+    assertEquals(1, failureCount());
+    assertEquals(2, duration().getCount());
+    assertEquals(10L, duration().getStatistics().getMin());
+    assertEquals(20L, duration().getStatistics().getMax());
+    assertEquals(lastSuccessTime, lastSuccessTime());
+  }
+
+  @Test
+  void testDurationWindowKeepsLatestSamples() {
+    for (int i = 1; i <= 150; i++) {
+      metrics.markSyncSucceeded(i);
+    }
+
+    assertEquals(150, successCount());
+    assertEquals(150, duration().getCount(), "The count covers every sync, not 
just the window");
+    assertEquals(100, duration().getStatistics().size());
+    assertEquals(51L, duration().getStatistics().getMin());
+    assertEquals(150L, duration().getStatistics().getMax());
+  }
+
+  @Test
+  void testConcurrentUpdates() throws Exception {
+    int threads = 2;
+    int syncsPerThread = 10_000;
+    ExecutorService executor = Executors.newFixedThreadPool(threads);
+    try {
+      CountDownLatch start = new CountDownLatch(1);
+      Future<?> succeeding = executor.submit(() -> {
+        start.await();
+        for (int i = 0; i < syncsPerThread; i++) {
+          metrics.markSyncSucceeded(1L);
+        }
+        return null;
+      });
+      Future<?> failing = executor.submit(() -> {
+        start.await();
+        for (int i = 0; i < syncsPerThread; i++) {
+          metrics.markSyncFailed(1L);
+        }
+        return null;
+      });
+      start.countDown();
+      succeeding.get(30, TimeUnit.SECONDS);
+      failing.get(30, TimeUnit.SECONDS);
+    } finally {
+      executor.shutdownNow();
+    }
+
+    assertEquals(syncsPerThread, successCount());
+    assertEquals(syncsPerThread, failureCount());
+    assertEquals(2L * syncsPerThread, duration().getCount());
+  }
+
+  private long successCount() {
+    return metricGroup.counters.get(HIVE_SYNC_SUCCESS_COUNT).getCount();
+  }
+
+  private long failureCount() {
+    return metricGroup.counters.get(HIVE_SYNC_FAILURE_COUNT).getCount();
+  }
+
+  private Histogram duration() {
+    return metricGroup.histograms.get(HIVE_SYNC_DURATION_MS);
+  }
+
+  private long lastSuccessTime() {
+    return (Long) 
metricGroup.gauges.get(HIVE_SYNC_LAST_SUCCESS_TIME_MS).getValue();
+  }
+
+  private static class CapturingMetricGroup extends UnregisteredMetricsGroup {
+    private final Map<String, Counter> counters = new HashMap<>();
+    private final Map<String, Histogram> histograms = new HashMap<>();
+    private final Map<String, Gauge<?>> gauges = new HashMap<>();
+
+    @Override
+    public <C extends Counter> C counter(String name, C counter) {
+      counters.put(name, counter);
+      return counter;
+    }
+
+    @Override
+    public <H extends Histogram> H histogram(String name, H histogram) {
+      histograms.put(name, histogram);
+      return histogram;
+    }
+
+    @Override
+    public <T, G extends Gauge<T>> G gauge(String name, G gauge) {
+      gauges.put(name, gauge);
+      return gauge;
+    }
+  }
+}
diff --git 
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/TestStreamWriteOperatorCoordinator.java
 
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/TestStreamWriteOperatorCoordinator.java
index 7dd73af9dfaa..5a06be17b40a 100644
--- 
a/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/TestStreamWriteOperatorCoordinator.java
+++ 
b/hudi-flink-datasource/hudi-flink/src/test/java/org/apache/hudi/sink/TestStreamWriteOperatorCoordinator.java
@@ -47,6 +47,7 @@ import org.apache.hudi.configuration.HadoopConfigurations;
 import org.apache.hudi.exception.HoodieException;
 import org.apache.hudi.exception.MissingSchemaFieldException;
 import org.apache.hudi.hadoop.fs.HadoopFSUtils;
+import org.apache.hudi.hive.HoodieHiveSyncException;
 import org.apache.hudi.metadata.HoodieTableMetadata;
 import org.apache.hudi.metadata.MetadataPartitionType;
 import org.apache.hudi.sink.event.Correspondent;
@@ -66,6 +67,11 @@ import org.apache.hudi.utils.TestConfigurations;
 import org.apache.hudi.utils.TestUtils;
 
 import org.apache.flink.configuration.Configuration;
+import org.apache.flink.metrics.Counter;
+import org.apache.flink.metrics.Gauge;
+import org.apache.flink.metrics.Histogram;
+import org.apache.flink.metrics.groups.OperatorCoordinatorMetricGroup;
+import org.apache.flink.metrics.groups.UnregisteredMetricsGroup;
 import org.apache.flink.runtime.jobgraph.OperatorID;
 import org.apache.flink.runtime.operators.coordination.CoordinationResponse;
 import 
org.apache.flink.runtime.operators.coordination.MockOperatorCoordinatorContext;
@@ -112,6 +118,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -604,6 +611,60 @@ public class TestStreamWriteOperatorCoordinator {
     assertDoesNotThrow(() -> coordinator.notifyCheckpointComplete(1));
   }
 
+  @Test
+  void testHiveSyncMetricsNotRegisteredWhenHiveSyncDisabled() throws Exception 
{
+    coordinator.close();
+    CapturingCoordinatorMetricGroup metricGroup = new 
CapturingCoordinatorMetricGroup();
+    coordinator = 
startCoordinator(TestConfigurations.getDefaultConf(tempFile.getAbsolutePath()), 
metricGroup);
+
+    assertTrue(metricGroup.counters.isEmpty());
+    assertTrue(metricGroup.histograms.isEmpty());
+    assertTrue(metricGroup.gauges.isEmpty());
+  }
+
+  @Test
+  void testHiveSyncFailureUpdatesMetrics() throws Exception {
+    coordinator.close();
+    Configuration conf = getUnreachableMetastoreConf();
+    CapturingCoordinatorMetricGroup metricGroup = new 
CapturingCoordinatorMetricGroup();
+    coordinator = startCoordinator(conf, metricGroup);
+
+    assertEquals(0L, 
metricGroup.gauges.get("hiveSyncLastSuccessTimeMs").getValue());
+
+    assertThrows(HoodieHiveSyncException.class, () -> 
coordinator.doSyncHive());
+    assertThrows(HoodieHiveSyncException.class, () -> 
coordinator.doSyncHive());
+
+    assertEquals(0, 
metricGroup.counters.get("hiveSyncSuccessCount").getCount());
+    assertEquals(2, 
metricGroup.counters.get("hiveSyncFailureCount").getCount());
+    assertEquals(2, 
metricGroup.histograms.get("hiveSyncDurationMs").getCount());
+    assertEquals(0L, 
metricGroup.gauges.get("hiveSyncLastSuccessTimeMs").getValue());
+  }
+
+  @Test
+  void testHiveSyncWithIgnoredClientFailureCountsAsFailure() throws Exception {
+    coordinator.close();
+    Configuration conf = getUnreachableMetastoreConf();
+    conf.set(FlinkOptions.HIVE_SYNC_IGNORE_EXCEPTIONS, true);
+    CapturingCoordinatorMetricGroup metricGroup = new 
CapturingCoordinatorMetricGroup();
+    coordinator = startCoordinator(conf, metricGroup);
+
+    assertDoesNotThrow(() -> coordinator.doSyncHive());
+
+    assertEquals(0, 
metricGroup.counters.get("hiveSyncSuccessCount").getCount());
+    assertEquals(1, 
metricGroup.counters.get("hiveSyncFailureCount").getCount());
+    assertEquals(1, 
metricGroup.histograms.get("hiveSyncDurationMs").getCount());
+    assertEquals(0L, 
metricGroup.gauges.get("hiveSyncLastSuccessTimeMs").getValue());
+  }
+
+  private Configuration getUnreachableMetastoreConf() {
+    Configuration conf = 
TestConfigurations.getDefaultConf(tempFile.getAbsolutePath());
+    conf.set(FlinkOptions.HIVE_SYNC_ENABLED, true);
+    // nothing listens on port 1, so creating the metastore client fails on 
its only attempt
+    conf.set(FlinkOptions.HIVE_SYNC_METASTORE_URIS, "thrift://localhost:1");
+    conf.setString(HadoopConfigurations.HADOOP_PREFIX + 
"hive.metastore.connect.retries", "0");
+    return conf;
+  }
+
   @Test
   void testSyncMetadataTable() throws Exception {
     // reset
@@ -998,6 +1059,20 @@ public class TestStreamWriteOperatorCoordinator {
     return coordinator;
   }
 
+  private static StreamWriteOperatorCoordinator startCoordinator(
+      Configuration conf, OperatorCoordinatorMetricGroup metricGroup) throws 
Exception {
+    MockOperatorCoordinatorContext coordinatorContext = new 
MockOperatorCoordinatorContext(new OperatorID(), 1) {
+      @Override
+      public OperatorCoordinatorMetricGroup metricGroup() {
+        return metricGroup;
+      }
+    };
+    StreamWriteOperatorCoordinator coordinator = new 
StreamWriteOperatorCoordinator(conf, coordinatorContext);
+    coordinator.start();
+    setSynchronousExecutors(coordinator);
+    return coordinator;
+  }
+
   private static void setSynchronousExecutors(StreamWriteOperatorCoordinator 
coordinator) throws Exception {
     MockOperatorCoordinatorContext coordinatorContext = 
(MockOperatorCoordinatorContext) coordinator.getContext();
     coordinator.setExecutor(new MockCoordinatorExecutor(coordinatorContext));
@@ -1009,6 +1084,34 @@ public class TestStreamWriteOperatorCoordinator {
     return new StreamWriteOperatorCoordinator(conf, coordinatorContext);
   }
 
+  /**
+   * A coordinator metric group that keeps what is registered on it.
+   */
+  private static final class CapturingCoordinatorMetricGroup extends 
UnregisteredMetricsGroup
+      implements OperatorCoordinatorMetricGroup {
+    private final Map<String, Counter> counters = new HashMap<>();
+    private final Map<String, Histogram> histograms = new HashMap<>();
+    private final Map<String, Gauge<?>> gauges = new HashMap<>();
+
+    @Override
+    public <C extends Counter> C counter(String name, C counter) {
+      counters.put(name, counter);
+      return counter;
+    }
+
+    @Override
+    public <H extends Histogram> H histogram(String name, H histogram) {
+      histograms.put(name, histogram);
+      return histogram;
+    }
+
+    @Override
+    public <T, G extends Gauge<T>> G gauge(String name, G gauge) {
+      gauges.put(name, gauge);
+      return gauge;
+    }
+  }
+
   /**
    * A real single-thread instant-request worker whose submitted creation task 
blocks on a latch before
    * running, to deterministically delay instant creation (simulating a held 
table lock).
diff --git 
a/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/HiveSyncTool.java 
b/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/HiveSyncTool.java
index 4c8aca377089..f0e3ee7da2a9 100644
--- 
a/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/HiveSyncTool.java
+++ 
b/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/HiveSyncTool.java
@@ -190,6 +190,14 @@ public class HiveSyncTool extends HoodieSyncTool 
implements AutoCloseable {
     }
   }
 
+  /**
+   * Returns whether the metastore client was created. It is not when creating 
it failed and
+   * {@code hoodie.datasource.hive_sync.ignore_exceptions} is set, in which 
case a sync does nothing.
+   */
+  public boolean isSyncClientInitialized() {
+    return syncClient != null;
+  }
+
   protected void doSync() {
     // create database if needed
     checkAndCreateDatabase();

Reply via email to