Introduce E2E apply object REST-API metrics

The E2E replication through the apply object REST-API
needs to be calculated from the client originating the call
where the initial ref-update happens.

Introduce a new end_2_end metric to track this latency.

Change-Id: Ib6b585e076ffcea55b53e603ad1c7e5f5b7b69fc
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ApplyObjectMetrics.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ApplyObjectMetrics.java
index 78b6ddb..78745bb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ApplyObjectMetrics.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ApplyObjectMetrics.java
@@ -26,25 +26,33 @@
 @Singleton
 public class ApplyObjectMetrics {
   private final Timer1<String> executionTime;
+  private final Timer1<String> end2EndTime;
 
   @Inject
   ApplyObjectMetrics(@PluginName String pluginName, MetricMaker metricMaker) {
-    Field<String> SOURCE_FIELD =
+    Field<String> field =
         Field.ofString(
-                "source",
+                "pull_replication",
                 (metadataBuilder, fieldValue) ->
                     metadataBuilder
                         .pluginName(pluginName)
-                        .addPluginMetadata(PluginMetadata.create("source", fieldValue)))
+                        .addPluginMetadata(PluginMetadata.create("pull_replication", fieldValue)))
             .build();
-
     executionTime =
         metricMaker.newTimer(
             "apply_object_latency",
             new Description("Time spent applying object from remote source.")
                 .setCumulative()
                 .setUnit(Description.Units.MILLISECONDS),
-            SOURCE_FIELD);
+            field);
+
+    end2EndTime =
+        metricMaker.newTimer(
+            "apply_object_end_2_end_latency",
+            new Description("Time spent for e2e replication with the apply object REST API")
+                .setCumulative()
+                .setUnit(Description.Units.MILLISECONDS),
+            field);
   }
 
   /**
@@ -56,4 +64,14 @@
   public Timer1.Context<String> start(String name) {
     return executionTime.start(name);
   }
+
+  /**
+   * Start the replication latency timer from a source.
+   *
+   * @param name the source name.
+   * @return the timer context.
+   */
+  public Timer1.Context<String> startEnd2End(String name) {
+    return end2EndTime.start(name);
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
index 0ce125d..6393c08 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
@@ -15,7 +15,6 @@
 package com.googlesource.gerrit.plugins.replication.pull;
 
 import com.google.auto.value.AutoValue;
-import com.google.common.base.Stopwatch;
 import com.google.common.collect.Queues;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.Project.NameKey;
@@ -24,6 +23,7 @@
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.metrics.Timer1.Context;
 import com.google.gerrit.server.events.EventDispatcher;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
@@ -76,6 +76,7 @@
   private Integer fetchCallsTimeout;
   private ExcludedRefsFilter refsFilter;
   private RevisionReader revisionReader;
+  private final ApplyObjectMetrics applyObjectMetrics;
 
   @Inject
   ReplicationQueue(
@@ -85,7 +86,8 @@
       ReplicationStateListeners sl,
       FetchApiClient.Factory fetchClientFactory,
       ExcludedRefsFilter refsFilter,
-      RevisionReader revReader) {
+      RevisionReader revReader,
+      ApplyObjectMetrics applyObjectMetrics) {
     workQueue = wq;
     dispatcher = dis;
     sources = rd;
@@ -94,6 +96,7 @@
     this.fetchClientFactory = fetchClientFactory;
     this.refsFilter = refsFilter;
     this.revisionReader = revReader;
+    this.applyObjectMetrics = applyObjectMetrics;
   }
 
   @Override
@@ -287,7 +290,7 @@
               project,
               refName,
               revision);
-          Stopwatch apiTimer = Stopwatch.createStarted();
+          Context<String> apiTimer = applyObjectMetrics.startEnd2End(source.getRemoteConfigName());
           HttpResult result = fetchClient.callSendObject(project, refName, isDelete, revision, uri);
           repLog.info(
               "Pull replication REST API apply object to {} COMPLETED for {}:{} - {}, HTTP Result:"
@@ -297,7 +300,7 @@
               refName,
               revision,
               result,
-              apiTimer.elapsed(TimeUnit.MILLISECONDS));
+              apiTimer.stop() / 1000000.0);
 
           if (isProjectMissing(result, project) && source.isCreateMissingRepositories()) {
             result = initProject(project, uri, fetchClient, result);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
index 4e831bc..08d7e5a 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener.Event;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.metrics.DisabledMetricMaker;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.events.EventDispatcher;
 import com.google.gerrit.server.git.WorkQueue;
@@ -79,6 +80,7 @@
   @Mock RevisionReader revReader;
   @Mock RevisionData revisionData;
   @Mock HttpResult httpResult;
+  ApplyObjectMetrics applyObjectMetrics;
 
   @Captor ArgumentCaptor<String> stringCaptor;
   @Captor ArgumentCaptor<Project.NameKey> projectNameKeyCaptor;
@@ -110,8 +112,11 @@
     when(httpResult.isSuccessful()).thenReturn(true);
     when(httpResult.isProjectMissing(any())).thenReturn(false);
 
+    applyObjectMetrics = new ApplyObjectMetrics("pull-replication", new DisabledMetricMaker());
+
     objectUnderTest =
-        new ReplicationQueue(wq, rd, dis, sl, fetchClientFactory, refsFilter, revReader);
+        new ReplicationQueue(
+            wq, rd, dis, sl, fetchClientFactory, refsFilter, revReader, applyObjectMetrics);
   }
 
   @Test
@@ -242,7 +247,8 @@
     refsFilter = new ExcludedRefsFilter(replicationConfig);
 
     objectUnderTest =
-        new ReplicationQueue(wq, rd, dis, sl, fetchClientFactory, refsFilter, revReader);
+        new ReplicationQueue(
+            wq, rd, dis, sl, fetchClientFactory, refsFilter, revReader, applyObjectMetrics);
     Event event = new TestEvent("refs/multi-site/version");
     objectUnderTest.onGitReferenceUpdated(event);