Merge "PerformanceMetrics: Log per request latency for configured operations"
diff --git a/java/com/google/gerrit/server/PerformanceMetrics.java b/java/com/google/gerrit/server/PerformanceMetrics.java
index 3d21fed..b894d49 100644
--- a/java/com/google/gerrit/server/PerformanceMetrics.java
+++ b/java/com/google/gerrit/server/PerformanceMetrics.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.metrics.Counter3;
@@ -26,20 +27,23 @@
 import com.google.gerrit.server.logging.PerformanceLogger;
 import com.google.gerrit.server.logging.TraceContext;
 import com.google.inject.Inject;
-import com.google.inject.Singleton;
 import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import org.eclipse.jgit.lib.Config;
 
 /** Performance logger that records the execution times as a metric. */
-@Singleton
 public class PerformanceMetrics implements PerformanceLogger {
   private static final String OPERATION_LATENCY_METRIC_NAME = "performance/operations";
   private static final String OPERATION_COUNT_METRIC_NAME = "performance/operations_count";
 
   private final ImmutableList<String> tracedOperations;
-  public final Timer3<String, String, String> operationsLatency;
-  public final Counter3<String, String, String> operationsCounter;
+  private final Timer3<String, String, String> operationsLatency;
+  private final Counter3<String, String, String> operationsCounter;
+  private final Timer3<String, String, String> operationsRequestLatency;
+
+  private final Map<MetricKey, Long> perRequestLatencyNanos = new HashMap<>();
 
   @Inject
   PerformanceMetrics(@GerritServerConfig Config cfg, MetricMaker metricMaker) {
@@ -82,6 +86,17 @@
             operationNameField,
             requestField,
             pluginField);
+    this.operationsRequestLatency =
+        metricMaker
+            .newTimer(
+                OPERATION_LATENCY_METRIC_NAME,
+                new Description("Per request latency of performing operations")
+                    .setCumulative()
+                    .setUnit(Description.Units.MILLISECONDS),
+                operationNameField,
+                requestField,
+                pluginField)
+            .suppressLogging();
   }
 
   @Override
@@ -100,5 +115,36 @@
     String pluginTag = TraceContext.getPluginTag().orElse("");
     operationsLatency.record(operation, requestTag, pluginTag, durationNanos, TimeUnit.NANOSECONDS);
     operationsCounter.increment(operation, requestTag, pluginTag);
+
+    perRequestLatencyNanos.compute(
+        MetricKey.create(operation, requestTag, pluginTag),
+        (metricKey, latencyNanos) ->
+            (latencyNanos == null) ? durationNanos : latencyNanos + durationNanos);
+  }
+
+  @Override
+  public void done() {
+    perRequestLatencyNanos.forEach(
+        (metricKey, latencyNanos) ->
+            operationsRequestLatency.record(
+                metricKey.operation(),
+                metricKey.requestTag(),
+                metricKey.pluginTag(),
+                latencyNanos,
+                TimeUnit.NANOSECONDS));
+    perRequestLatencyNanos.clear();
+  }
+
+  @AutoValue
+  abstract static class MetricKey {
+    abstract String operation();
+
+    abstract String requestTag();
+
+    abstract String pluginTag();
+
+    public static MetricKey create(String operation, String requestTag, String pluginTag) {
+      return new AutoValue_PerformanceMetrics_MetricKey(operation, requestTag, pluginTag);
+    }
   }
 }
diff --git a/java/com/google/gerrit/server/logging/PerformanceLogContext.java b/java/com/google/gerrit/server/logging/PerformanceLogContext.java
index 90e716f..8cf5b84 100644
--- a/java/com/google/gerrit/server/logging/PerformanceLogContext.java
+++ b/java/com/google/gerrit/server/logging/PerformanceLogContext.java
@@ -92,6 +92,7 @@
             p -> {
               try (TraceContext traceContext = newPluginTrace(p)) {
                 performanceLogRecords.forEach(r -> r.writeTo(p.get()));
+                p.get().done();
               } catch (RuntimeException e) {
                 logger.atWarning().withCause(e).log(
                     "Failure in %s of plugin %s", p.get().getClass(), p.getPluginName());
diff --git a/java/com/google/gerrit/server/logging/PerformanceLogger.java b/java/com/google/gerrit/server/logging/PerformanceLogger.java
index fa61eb4..02fb37c 100644
--- a/java/com/google/gerrit/server/logging/PerformanceLogger.java
+++ b/java/com/google/gerrit/server/logging/PerformanceLogger.java
@@ -48,4 +48,10 @@
    * @param metadata metadata
    */
   void logNanos(String operation, long durationNanos, Instant endTime, Metadata metadata);
+
+  /**
+   * Called after all performance events of a request have been logged via {@link #logNanos(String,
+   * long, Instant)} or {@link #logNanos(String, long, Instant, Metadata)}.
+   */
+  default void done() {}
 }