Merge branch 'stable-3.7' into stable-3.8

* stable-3.7:
  Add missing Singleton to ActiveWokersCheck and DeadlockCheck
  Add active http workers check

Change-Id: I2262f7071c55e739b52d151f3f7d5a258f2f08df
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckSubsystemsModule.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckSubsystemsModule.java
index 3cda7b6..bb70839 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckSubsystemsModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckSubsystemsModule.java
@@ -21,6 +21,7 @@
 import com.googlesource.gerrit.plugins.healthcheck.check.BlockedThreadsCheck;
 import com.googlesource.gerrit.plugins.healthcheck.check.DeadlockCheck;
 import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck;
+import com.googlesource.gerrit.plugins.healthcheck.check.HttpActiveWorkersCheck;
 import com.googlesource.gerrit.plugins.healthcheck.check.JGitHealthCheck;
 import com.googlesource.gerrit.plugins.healthcheck.check.ProjectsListHealthCheck;
 import com.googlesource.gerrit.plugins.healthcheck.check.QueryChangesHealthCheck;
@@ -34,6 +35,7 @@
     bindChecker(QueryChangesHealthCheck.class);
     bindChecker(AuthHealthCheck.class);
     bindChecker(ActiveWorkersCheck.class);
+    bindChecker(HttpActiveWorkersCheck.class);
     bindChecker(DeadlockCheck.class);
     bindChecker(BlockedThreadsCheck.class);
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/AbstractWorkersHealthCheck.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/AbstractWorkersHealthCheck.java
new file mode 100644
index 0000000..a01eba1
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/AbstractWorkersHealthCheck.java
@@ -0,0 +1,72 @@
+// Copyright (C) 2020 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.check;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.MetricRegistry;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.googlesource.gerrit.plugins.healthcheck.HealthCheckConfig;
+import com.googlesource.gerrit.plugins.healthcheck.HealthCheckMetrics;
+import java.util.Optional;
+
+public abstract class AbstractWorkersHealthCheck extends AbstractHealthCheck {
+
+  private final String metricName;
+  private final Integer maxPoolSize;
+  private final Integer threshold;
+  private final MetricRegistry metricRegistry;
+
+  protected AbstractWorkersHealthCheck(
+      ListeningExecutorService executor,
+      HealthCheckConfig config,
+      MetricRegistry metricRegistry,
+      String name,
+      String metricName,
+      Integer maxPoolSize,
+      HealthCheckMetrics.Factory healthCheckMetricsFactory) {
+    super(executor, config, name, healthCheckMetricsFactory);
+    this.metricRegistry = metricRegistry;
+    this.metricName = metricName;
+    this.maxPoolSize = maxPoolSize;
+    this.threshold = config.getActiveWorkersThreshold(name);
+  }
+
+  @Override
+  protected Result doCheck() throws Exception {
+    return Optional.ofNullable(metricRegistry.getGauges().get(metricName))
+        .map(
+            metric -> {
+              float currentThreadsPercentage = (getMetricValue(metric) * 100) / maxPoolSize;
+              return (currentThreadsPercentage <= threshold) ? Result.PASSED : Result.FAILED;
+            })
+        .orElse(Result.PASSED);
+  }
+
+  private Long getMetricValue(Gauge<?> metric) {
+    Object value = metric.getValue();
+    if (value instanceof Long) {
+      return (Long) value;
+    }
+
+    if (value instanceof Integer) {
+      return ((Integer) value).longValue();
+    }
+
+    throw new IllegalArgumentException(
+        String.format(
+            "Workers metric value must be of type java.lang.Long or java.lang.Integer but was %s ",
+            value.getClass().getName()));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/ActiveWorkersCheck.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/ActiveWorkersCheck.java
index 8e64593..a23567c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/ActiveWorkersCheck.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/ActiveWorkersCheck.java
@@ -21,20 +21,17 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.ThreadSettingsConfig;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.healthcheck.HealthCheckConfig;
 import com.googlesource.gerrit.plugins.healthcheck.HealthCheckMetrics;
-import java.util.Optional;
 import org.eclipse.jgit.lib.Config;
 
-public class ActiveWorkersCheck extends AbstractHealthCheck {
+@Singleton
+public class ActiveWorkersCheck extends AbstractWorkersHealthCheck {
 
   public static final String ACTIVE_WORKERS_METRIC_NAME =
       "queue/ssh_interactive_worker/active_threads";
 
-  private Integer threshold;
-  private Integer interactiveThreadsMaxPoolSize;
-  private MetricRegistry metricRegistry;
-
   @Inject
   public ActiveWorkersCheck(
       @GerritServerConfig Config gerritConfig,
@@ -43,25 +40,15 @@
       ThreadSettingsConfig threadSettingsConfig,
       MetricRegistry metricRegistry,
       HealthCheckMetrics.Factory healthCheckMetricsFactory) {
-    super(executor, healthCheckConfig, ACTIVEWORKERS, healthCheckMetricsFactory);
-    this.threshold = healthCheckConfig.getActiveWorkersThreshold(ACTIVEWORKERS);
-    this.metricRegistry = metricRegistry;
-    this.interactiveThreadsMaxPoolSize =
-        getInteractiveThreadsMaxPoolSize(threadSettingsConfig, gerritConfig);
-  }
 
-  @Override
-  protected Result doCheck() throws Exception {
-    return Optional.ofNullable(metricRegistry.getGauges().get(ACTIVE_WORKERS_METRIC_NAME))
-        .map(
-            metric -> {
-              float currentInteractiveThreadsPercentage =
-                  ((long) metric.getValue() * 100) / interactiveThreadsMaxPoolSize;
-              return (currentInteractiveThreadsPercentage <= threshold)
-                  ? Result.PASSED
-                  : Result.FAILED;
-            })
-        .orElse(Result.PASSED);
+    super(
+        executor,
+        healthCheckConfig,
+        metricRegistry,
+        ACTIVEWORKERS,
+        ACTIVE_WORKERS_METRIC_NAME,
+        getInteractiveThreadsMaxPoolSize(threadSettingsConfig, gerritConfig),
+        healthCheckMetricsFactory);
   }
 
   /**
@@ -72,7 +59,7 @@
    *
    * @return max number of allowed threads in interactive work queue
    */
-  private Integer getInteractiveThreadsMaxPoolSize(
+  private static Integer getInteractiveThreadsMaxPoolSize(
       ThreadSettingsConfig threadSettingsConfig, Config gerritConfig) {
     int poolSize = threadSettingsConfig.getSshdThreads();
     int batchThreads =
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/DeadlockCheck.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/DeadlockCheck.java
index 6a6ade6..85e721d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/DeadlockCheck.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/DeadlockCheck.java
@@ -19,10 +19,12 @@
 import com.codahale.metrics.MetricRegistry;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.healthcheck.HealthCheckConfig;
 import com.googlesource.gerrit.plugins.healthcheck.HealthCheckMetrics;
 import java.util.Optional;
 
+@Singleton
 public class DeadlockCheck extends AbstractHealthCheck {
 
   public static final String DEADLOCKED_THREADS_METRIC_NAME =
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/HealthCheckNames.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/HealthCheckNames.java
index 4771942..4604af0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/HealthCheckNames.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/HealthCheckNames.java
@@ -20,6 +20,7 @@
   String QUERYCHANGES = "querychanges";
   String AUTH = "auth";
   String ACTIVEWORKERS = "activeworkers";
+  String HTTPACTIVEWORKERS = "httpactiveworkers";
   String DEADLOCK = "deadlock";
   String BLOCKEDTHREADS = "blockedthreads";
   String GLOBAL = "global";
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/HttpActiveWorkersCheck.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/HttpActiveWorkersCheck.java
new file mode 100644
index 0000000..19c2815
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/HttpActiveWorkersCheck.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2020 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.check;
+
+import static com.googlesource.gerrit.plugins.healthcheck.check.HealthCheckNames.HTTPACTIVEWORKERS;
+
+import com.codahale.metrics.MetricRegistry;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.server.config.ThreadSettingsConfig;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.healthcheck.HealthCheckConfig;
+import com.googlesource.gerrit.plugins.healthcheck.HealthCheckMetrics;
+
+public class HttpActiveWorkersCheck extends AbstractWorkersHealthCheck {
+  public static final String HTTP_WORKERS_METRIC_NAME =
+      "http/server/jetty/threadpool/active_threads";
+
+  @Inject
+  public HttpActiveWorkersCheck(
+      ListeningExecutorService executor,
+      HealthCheckConfig healthCheckConfig,
+      ThreadSettingsConfig threadSettingsConfig,
+      MetricRegistry metricRegistry,
+      HealthCheckMetrics.Factory healthCheckMetricsFactory) {
+    super(
+        executor,
+        healthCheckConfig,
+        metricRegistry,
+        HTTPACTIVEWORKERS,
+        HTTP_WORKERS_METRIC_NAME,
+        threadSettingsConfig.getHttpdMaxThreads(),
+        healthCheckMetricsFactory);
+  }
+}
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 0b4f05f..be418b8 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -54,6 +54,8 @@
 - `projectslist` : check the ability to list projects with their descriptions
 - `auth`: check the ability to authenticate with username and password
 - `activeworkers`: check the number of active worker threads and the ability to create a new one
+- `httpactiveworkers`: check the number of active HTTP worker threads and the ability
+   to create a new one
 - `deadlock` : check if Java deadlocks are reported by the JVM
 - `blockedthreads` : check the number of blocked threads
 
@@ -91,8 +93,8 @@
  
    Default: no password
 
- - `healthcheck.activeworkers.threshold` : Percent of queue occupancy above which queue is consider 
-    as full.
+ - `healthcheck.activeworkers.threshold` : Percent of queue occupancy above which queue is
+    considered as full.
 
    Default: 80
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/ActiveWorkersCheckTest.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/ActiveWorkersCheckTest.java
index df88c48..2d2ebd4 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/ActiveWorkersCheckTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/ActiveWorkersCheckTest.java
@@ -104,6 +104,27 @@
     assertThat(check.run().result).isEqualTo(Result.PASSED);
   }
 
+  @Test
+  public void shouldFailCheckWhenMetricValueIsNotLongOrInteger() {
+
+    MetricRegistry metricRegistry = new MetricRegistry();
+    metricRegistry.register(
+        ActiveWorkersCheck.ACTIVE_WORKERS_METRIC_NAME,
+        new Gauge<String>() {
+          @Override
+          public String getValue() {
+            return "55";
+          }
+        });
+    Config gerritConfig = new Config();
+    gerritConfig.setInt("sshd", null, "threads", 12);
+
+    Injector injector = testInjector(new TestModule(gerritConfig, metricRegistry));
+
+    ActiveWorkersCheck check = createCheck(injector);
+    assertThat(check.run().result).isEqualTo(Result.FAILED);
+  }
+
   private Injector testInjector(AbstractModule testModule) {
     return Guice.createInjector(new HealthCheckModule(), testModule);
   }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HttpActiveWorkersCheckTest.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HttpActiveWorkersCheckTest.java
new file mode 100644
index 0000000..515a8e6
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HttpActiveWorkersCheckTest.java
@@ -0,0 +1,143 @@
+// Copyright (C) 2020 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 static com.googlesource.gerrit.plugins.healthcheck.HealthCheckConfig.DEFAULT_CONFIG;
+import static com.googlesource.gerrit.plugins.healthcheck.check.HealthCheckNames.HTTPACTIVEWORKERS;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.MetricRegistry;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.ThreadSettingsConfig;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck.Result;
+import com.googlesource.gerrit.plugins.healthcheck.check.HttpActiveWorkersCheck;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+public class HttpActiveWorkersCheckTest {
+
+  @Test
+  public void shouldPassCheckWhenActiveHttpWorkersLessThanThreshold() {
+
+    int thresholdPerc = 50;
+    int maxThreads = 12;
+    int usedThreads = maxThreads * thresholdPerc / 100 - 1;
+
+    MetricRegistry metricRegistry = createHttpMetricRegistry(usedThreads);
+
+    Config gerritConfig = new Config();
+    gerritConfig.setInt("httpd", null, "maxThreads", maxThreads);
+
+    Injector injector = testInjector(new TestModule(gerritConfig, metricRegistry));
+
+    HealthCheckConfig healthCheckConfig =
+        new HealthCheckConfig(
+            "[healthcheck \"" + HTTPACTIVEWORKERS + "\"]\n" + "  threshold = " + thresholdPerc);
+    HttpActiveWorkersCheck check = createCheck(injector, healthCheckConfig);
+    assertThat(check.run().result).isEqualTo(Result.PASSED);
+  }
+
+  @Test
+  public void shouldFailCheckWhenActiveHttpWorkersMoreThanThreshold() {
+    int thresholdPerc = 50;
+    int maxThreads = 12;
+    int usedThreads = maxThreads * thresholdPerc / 100 + 1;
+
+    MetricRegistry metricRegistry = createHttpMetricRegistry(usedThreads);
+
+    Config gerritConfig = new Config();
+    gerritConfig.setInt("httpd", null, "maxThreads", maxThreads);
+
+    Injector injector = testInjector(new TestModule(gerritConfig, metricRegistry));
+
+    HealthCheckConfig healthCheckConfig =
+        new HealthCheckConfig(
+            "[healthcheck \"" + HTTPACTIVEWORKERS + "\"]\n" + "  threshold = " + thresholdPerc);
+    HttpActiveWorkersCheck check = createCheck(injector, healthCheckConfig);
+    assertThat(check.run().result).isEqualTo(Result.FAILED);
+  }
+
+  @Test
+  public void shouldUseHttpWorkersDefaultThreshold() {
+    HealthCheckConfig healthCheckConfig =
+        new HealthCheckConfig("[healthcheck \"" + HTTPACTIVEWORKERS + "\"]\n");
+    assertThat(healthCheckConfig.getActiveWorkersThreshold(HTTPACTIVEWORKERS)).isEqualTo(80);
+  }
+
+  @Test
+  public void shouldPassCheckWhenNoActiveHttpWorkers() {
+
+    MetricRegistry metricRegistry = createHttpMetricRegistry(0);
+
+    Injector injector = testInjector(new TestModule(new Config(), metricRegistry));
+
+    HttpActiveWorkersCheck check = createCheck(injector);
+    assertThat(check.run().result).isEqualTo(Result.PASSED);
+  }
+
+  private MetricRegistry createHttpMetricRegistry(Integer value) {
+    MetricRegistry metricRegistry = new MetricRegistry();
+
+    metricRegistry.register(
+        HttpActiveWorkersCheck.HTTP_WORKERS_METRIC_NAME,
+        new Gauge<Integer>() {
+          @Override
+          public Integer getValue() {
+            return value;
+          }
+        });
+    return metricRegistry;
+  }
+
+  private HttpActiveWorkersCheck createCheck(Injector injector) {
+    return createCheck(injector, DEFAULT_CONFIG);
+  }
+
+  private HttpActiveWorkersCheck createCheck(
+      Injector injector, HealthCheckConfig healtchCheckConfig) {
+    return new HttpActiveWorkersCheck(
+        injector.getInstance(ListeningExecutorService.class),
+        healtchCheckConfig,
+        injector.getInstance(ThreadSettingsConfig.class),
+        injector.getInstance(MetricRegistry.class),
+        new DummyHealthCheckMetricsFactory());
+  }
+
+  private Injector testInjector(AbstractModule testModule) {
+    return Guice.createInjector(new HealthCheckModule(), testModule);
+  }
+
+  private class TestModule extends AbstractModule {
+    Config gerritConfig;
+    MetricRegistry metricRegistry;
+
+    public TestModule(Config gerritConfig, MetricRegistry metricRegistry) {
+      this.gerritConfig = gerritConfig;
+      this.metricRegistry = metricRegistry;
+    }
+
+    @Override
+    protected void configure() {
+      bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(gerritConfig);
+      bind(ThreadSettingsConfig.class);
+      bind(MetricRegistry.class).toInstance(metricRegistry);
+    }
+  }
+}