Merge branch 'stable-3.2' into stable-3.3

* stable-3.2:
  Missing global metric

Change-Id: Ib05c3f40b41eee490b748b8c929f7192d1b5adfb
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckMetrics.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckMetrics.java
index edc3223..fb298fd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckMetrics.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckMetrics.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.healthcheck.check.GlobalHealthCheck;
 import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck;
 import java.util.Collections;
 import java.util.HashSet;
@@ -36,13 +37,18 @@
   private final MetricMaker metricMaker;
   private final Set<RegistrationHandle> registeredMetrics;
   private final Set<Runnable> triggers;
+  private final GlobalHealthCheck globalHealthCheck;
 
   @Inject
-  public HealthCheckMetrics(DynamicSet<HealthCheck> healthChecks, MetricMaker metricMaker) {
+  public HealthCheckMetrics(
+      DynamicSet<HealthCheck> healthChecks,
+      GlobalHealthCheck globalHealthCheck,
+      MetricMaker metricMaker) {
     this.healthChecks = healthChecks;
     this.metricMaker = metricMaker;
     this.registeredMetrics = Collections.synchronizedSet(new HashSet<>());
     this.triggers = Collections.synchronizedSet(new HashSet<>());
+    this.globalHealthCheck = globalHealthCheck;
   }
 
   @Override
@@ -51,35 +57,58 @@
     for (HealthCheck healthCheck : healthChecks) {
       String name = healthCheck.name();
 
-      Counter0 failureMetric =
-          metricMaker.newCounter(
-              String.format("%s/failure", name),
-              new Description(String.format("%s healthcheck failures count", name))
-                  .setCumulative()
-                  .setRate()
-                  .setUnit("failures"));
-
-      CallbackMetric0<Long> latencyMetric =
-          metricMaker.newCallbackMetric(
-              String.format("%s/latest_latency", name),
-              Long.class,
-              new Description(String.format("%s health check latency execution (ms)", name))
-                  .setGauge()
-                  .setUnit(Description.Units.MILLISECONDS));
-
-      Runnable metricCallBack =
-          () -> {
-            HealthCheck.StatusSummary status = healthCheck.getLatestStatus();
-            latencyMetric.set(healthCheck.getLatestStatus().elapsed);
-            if (status.isFailure()) {
-              failureMetric.increment();
-            }
-          };
+      Counter0 failureMetric = getFailureMetric(name);
+      CallbackMetric0<Long> latencyMetric = getLatencyMetric(name);
+      Runnable metricCallBack = getCallbackMetric(healthCheck, failureMetric, latencyMetric);
 
       registeredMetrics.add(failureMetric);
       registeredMetrics.add(metricMaker.newTrigger(latencyMetric, metricCallBack));
       triggers.add(metricCallBack);
     }
+
+    Counter0 globalFailureMetric = getFailureMetric("global");
+    CallbackMetric0<Long> globalLatencyMetric = getLatencyMetric("global");
+    Runnable globalMetricCallBack =
+        () -> {
+          HealthCheck.StatusSummary status = globalHealthCheck.getGlobalStatusSummary();
+          globalLatencyMetric.set(status.elapsed);
+          if (status.isFailure()) {
+            globalFailureMetric.increment();
+          }
+        };
+
+    registeredMetrics.add(globalFailureMetric);
+    registeredMetrics.add(metricMaker.newTrigger(globalLatencyMetric, globalMetricCallBack));
+    triggers.add(globalMetricCallBack);
+  }
+
+  private Runnable getCallbackMetric(
+      HealthCheck healthCheck, Counter0 failureMetric, CallbackMetric0<Long> latencyMetric) {
+    return () -> {
+      HealthCheck.StatusSummary status = healthCheck.getLatestStatus();
+      latencyMetric.set(healthCheck.getLatestStatus().elapsed);
+      if (status.isFailure()) {
+        failureMetric.increment();
+      }
+    };
+  }
+
+  private CallbackMetric0<Long> getLatencyMetric(String name) {
+    return metricMaker.newCallbackMetric(
+        String.format("%s/latest_latency", name),
+        Long.class,
+        new Description(String.format("%s health check latency execution (ms)", name))
+            .setGauge()
+            .setUnit(Description.Units.MILLISECONDS));
+  }
+
+  private Counter0 getFailureMetric(String name) {
+    return metricMaker.newCounter(
+        String.format("%s/failure", name),
+        new Description(String.format("%s healthcheck failures count", name))
+            .setCumulative()
+            .setRate()
+            .setUnit("failures"));
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/api/HealthCheckStatusEndpoint.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/api/HealthCheckStatusEndpoint.java
index ecae202..9e0a607 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/api/HealthCheckStatusEndpoint.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/api/HealthCheckStatusEndpoint.java
@@ -36,24 +36,16 @@
   @Override
   public Response<Map<String, Object>> apply(ConfigResource resource)
       throws AuthException, BadRequestException, ResourceConflictException, Exception {
-    long ts = System.currentTimeMillis();
     Map<String, Object> result = healthChecks.run();
-    long elapsed = System.currentTimeMillis() - ts;
-    result.put("ts", new Long(ts));
-    result.put("elapsed", new Long(elapsed));
-    return Response.withStatusCode(getResultStatus(result), result);
+
+    result.put("ts", healthChecks.getGlobalStatusSummary().ts);
+    result.put("elapsed", healthChecks.getGlobalStatusSummary().elapsed);
+    return Response.withStatusCode(getHTTPResultCode(result), result);
   }
 
-  private int getResultStatus(Map<String, Object> result) {
-    if (result.values().stream()
-        .filter(
-            res ->
-                res instanceof HealthCheck.StatusSummary
-                    && ((HealthCheck.StatusSummary) res).isFailure())
-        .findFirst()
-        .isPresent()) {
-      return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
-    }
-    return HttpServletResponse.SC_OK;
+  private int getHTTPResultCode(Map<String, Object> result) {
+    return healthChecks.getResultStatus(result) == HealthCheck.Result.FAILED
+        ? HttpServletResponse.SC_INTERNAL_SERVER_ERROR
+        : HttpServletResponse.SC_OK;
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/GlobalHealthCheck.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/GlobalHealthCheck.java
index 76e8706..c9066c5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/GlobalHealthCheck.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/GlobalHealthCheck.java
@@ -16,24 +16,51 @@
 
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import java.util.Arrays;
 import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
+@Singleton
 public class GlobalHealthCheck {
 
   private final DynamicSet<HealthCheck> healthChecks;
+  private HealthCheck.StatusSummary globalStatusSummary;
 
   @Inject
   public GlobalHealthCheck(DynamicSet<HealthCheck> healthChecks) {
     this.healthChecks = healthChecks;
+    this.globalStatusSummary = HealthCheck.StatusSummary.INITIAL_STATUS;
   }
 
   public Map<String, Object> run() {
     Iterable<HealthCheck> iterable = () -> healthChecks.iterator();
-    return StreamSupport.stream(iterable.spliterator(), false)
-        .map(check -> Arrays.asList(check.name(), check.run()))
-        .collect(Collectors.toMap(k -> (String) k.get(0), v -> v.get(1)));
+    long ts = System.currentTimeMillis();
+    Map<String, Object> checkToResults =
+        StreamSupport.stream(iterable.spliterator(), false)
+            .map(check -> Arrays.asList(check.name(), check.run()))
+            .collect(Collectors.toMap(k -> (String) k.get(0), v -> v.get(1)));
+    long elapsed = System.currentTimeMillis() - ts;
+    globalStatusSummary =
+        new HealthCheck.StatusSummary(getResultStatus(checkToResults), ts, elapsed);
+    return checkToResults;
+  }
+
+  public HealthCheck.StatusSummary getGlobalStatusSummary() {
+    return this.globalStatusSummary;
+  }
+
+  public HealthCheck.Result getResultStatus(Map<String, Object> result) {
+    if (result.values().stream()
+        .filter(
+            res ->
+                res instanceof HealthCheck.StatusSummary
+                    && ((HealthCheck.StatusSummary) res).isFailure())
+        .findFirst()
+        .isPresent()) {
+      return HealthCheck.Result.FAILED;
+    }
+    return HealthCheck.Result.PASSED;
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/GlobalHealthCheckTest.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/GlobalHealthCheckTest.java
new file mode 100644
index 0000000..a1c792e
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/GlobalHealthCheckTest.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed 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 com.googlesource.gerrit.plugins.healthcheck;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.healthcheck.check.GlobalHealthCheck;
+import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck;
+import java.util.HashMap;
+import org.junit.Test;
+
+public class GlobalHealthCheckTest {
+
+  @Inject private DynamicSet<HealthCheck> healthChecks;
+
+  GlobalHealthCheck globalHealthCheck = new GlobalHealthCheck(healthChecks);
+
+  @Test
+  public void shouldFailIfOneCheckFails() {
+
+    HashMap<String, Object> checks =
+        new HashMap<String, Object>() {
+          {
+            put("failingCheck", new HealthCheck.StatusSummary(HealthCheck.Result.FAILED, 0L, 0L));
+            put("passingCheck", new HealthCheck.StatusSummary(HealthCheck.Result.PASSED, 0L, 0L));
+          }
+        };
+    assertThat(globalHealthCheck.getResultStatus(checks)).isEqualTo(HealthCheck.Result.FAILED);
+  }
+
+  @Test
+  public void shouldPassIfAllChecksPass() {
+    HashMap<String, Object> checks =
+        new HashMap<String, Object>() {
+          {
+            put("passingCheck", new HealthCheck.StatusSummary(HealthCheck.Result.PASSED, 0L, 0L));
+          }
+        };
+    assertThat(globalHealthCheck.getResultStatus(checks)).isEqualTo(HealthCheck.Result.PASSED);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckMetricsTest.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckMetricsTest.java
index 9955a33..fb64048 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckMetricsTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckMetricsTest.java
@@ -120,7 +120,9 @@
       return new Counter0() {
         @Override
         public void incrementBy(long value) {
-          failures += value;
+          if (!name.startsWith("global/")) {
+            failures += value;
+          }
         }
 
         @Override
@@ -134,7 +136,9 @@
       return new CallbackMetric0<V>() {
         @Override
         public void set(V value) {
-          latency = (Long) value;
+          if (!name.startsWith("global/")) {
+            latency = (Long) value;
+          }
         }
 
         @Override