Merge "Do not record fetch replication metrics when all refs are excluded from fetch task" into stable-3.6
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
index 42bea3e..39db764 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
@@ -41,6 +41,7 @@
 import com.googlesource.gerrit.plugins.replication.pull.fetch.RefUpdateState;
 import java.io.IOException;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -328,22 +329,32 @@
     try {
       long startedAt = context.getStartTime();
       long delay = NANOSECONDS.toMillis(startedAt - createdAt);
-      metrics.record(config.getName(), delay, retryCount);
       git = gitManager.openRepository(projectName);
-      runImpl();
-      long elapsed = NANOSECONDS.toMillis(context.stop());
-      Optional<Long> elapsedEnd2End =
-          apiRequestMetrics
-              .flatMap(metrics -> metrics.stop(config.getName()))
-              .map(NANOSECONDS::toMillis);
-      repLog.info(
-          "[{}] Replication from {} completed in {}ms, {}ms delay, {} retries{}",
-          taskIdHex,
-          uri,
-          elapsed,
-          delay,
-          retryCount,
-          elapsedEnd2End.map(el -> String.format(", E2E %dms", el)).orElse(""));
+      List<RefSpec> fetchRefSpecs = runImpl();
+
+      if (fetchRefSpecs.isEmpty()) {
+        repLog.info(
+            "[{}] Replication from {} finished but no refs were replicated, {}ms delay, {} retries",
+            taskIdHex,
+            uri,
+            delay,
+            retryCount);
+      } else {
+        metrics.record(config.getName(), delay, retryCount);
+        long elapsed = NANOSECONDS.toMillis(context.stop());
+        Optional<Long> elapsedEnd2End =
+            apiRequestMetrics
+                .flatMap(metrics -> metrics.stop(config.getName()))
+                .map(NANOSECONDS::toMillis);
+        repLog.info(
+            "[{}] Replication from {} completed in {}ms, {}ms delay, {} retries{}",
+            taskIdHex,
+            uri,
+            elapsed,
+            delay,
+            retryCount,
+            elapsedEnd2End.map(el -> String.format(", E2E %dms", el)).orElse(""));
+      }
     } catch (RepositoryNotFoundException e) {
       stateLog.error(
           "["
@@ -417,7 +428,7 @@
     repLog.info("[{}] Cannot replicate from {}. It was canceled while running", taskIdHex, uri, e);
   }
 
-  private void runImpl() throws IOException {
+  private List<RefSpec> runImpl() throws IOException {
     Fetch fetch = fetchFactory.create(taskIdHex, uri, git);
     List<RefSpec> fetchRefSpecs = getFetchRefSpecs();
 
@@ -434,11 +445,12 @@
       delta.remove(inexistentRef);
       if (delta.isEmpty()) {
         repLog.warn("[{}] Empty replication task, skipping.", taskIdHex);
-        return;
+        return Collections.emptyList();
       }
 
       runImpl();
     }
+    return fetchRefSpecs;
   }
 
   private List<RefSpec> getFetchRefSpecs() {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java
index 3980f93..0756c5c 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/FetchOneTest.java
@@ -18,9 +18,9 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.*;
 
+import com.google.gerrit.acceptance.TestMetricMaker;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.metrics.DisabledMetricMaker;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.PerThreadRequestScope;
 import com.google.gerrit.server.util.IdGenerator;
@@ -55,6 +55,7 @@
   private final Project.NameKey PROJECT_NAME = Project.NameKey.parse(TEST_PROJECT_NAME);
   private final String TEST_REF = "refs/heads/refForReplicationTask";
   private final String URI_PATTERN = "http://test.com/" + TEST_PROJECT_NAME + ".git";
+  private final TestMetricMaker testMetricMaker = new TestMetricMaker();
 
   @Mock private GitRepositoryManager grm;
   @Mock private Repository repository;
@@ -73,8 +74,9 @@
 
   @Before
   public void setup() throws Exception {
+    testMetricMaker.reset();
     FetchReplicationMetrics fetchReplicationMetrics =
-        new FetchReplicationMetrics("pull-replication", new DisabledMetricMaker());
+        new FetchReplicationMetrics("pull-replication", testMetricMaker);
     urIish = new URIish(URI_PATTERN);
 
     grm = mock(GitRepositoryManager.class);
@@ -693,6 +695,51 @@
             TEST_PROJECT_NAME, TEST_REF, urIish, ReplicationState.RefFetchResult.FAILED, null);
   }
 
+  @Test
+  public void shouldNotRecordReplicationLatencyMetricIfAllRefsAreExcluded() throws Exception {
+    setupMocks(true);
+    String filteredRef = "refs/heads/filteredRef";
+    Set<String> refSpecs = Set.of(TEST_REF, filteredRef);
+    createTestStates(TEST_REF, 1);
+    createTestStates(filteredRef, 1);
+    setupFetchFactoryMock(
+        List.of(new FetchFactoryEntry.Builder().refSpecNameWithDefaults(TEST_REF).build()),
+        Optional.of(List.of(TEST_REF)));
+    objectUnderTest.addRefs(refSpecs);
+    objectUnderTest.setReplicationFetchFilter(replicationFilter);
+    ReplicationFetchFilter mockFilter = mock(ReplicationFetchFilter.class);
+    when(replicationFilter.get()).thenReturn(mockFilter);
+    when(mockFilter.filter(TEST_PROJECT_NAME, refSpecs)).thenReturn(Collections.emptySet());
+
+    objectUnderTest.run();
+
+    verify(pullReplicationApiRequestMetrics, never()).stop(any());
+    assertThat(testMetricMaker.getTimer("replication_latency")).isEqualTo(0);
+  }
+
+  @Test
+  public void shouldRecordReplicationLatencyMetricWhenAtLeastOneRefWasReplicated()
+      throws Exception {
+    setupMocks(true);
+    String filteredRef = "refs/heads/filteredRef";
+    Set<String> refSpecs = Set.of(TEST_REF, filteredRef);
+    createTestStates(TEST_REF, 1);
+    createTestStates(filteredRef, 1);
+    setupFetchFactoryMock(
+        List.of(new FetchFactoryEntry.Builder().refSpecNameWithDefaults(TEST_REF).build()),
+        Optional.of(List.of(TEST_REF)));
+    objectUnderTest.addRefs(refSpecs);
+    objectUnderTest.setReplicationFetchFilter(replicationFilter);
+    ReplicationFetchFilter mockFilter = mock(ReplicationFetchFilter.class);
+    when(replicationFilter.get()).thenReturn(mockFilter);
+    when(mockFilter.filter(TEST_PROJECT_NAME, refSpecs)).thenReturn(Set.of(TEST_REF));
+
+    objectUnderTest.run();
+
+    verify(pullReplicationApiRequestMetrics).stop(any());
+    assertThat(testMetricMaker.getTimer("replication_latency")).isGreaterThan(0);
+  }
+
   private void setupRequestScopeMock() {
     when(scoper.scope(any()))
         .thenAnswer(