Implement deadlock check

Check if JVM has detected a deadlock, using metric introduced in [1].

[1] https://gerrit-review.googlesource.com/c/gerrit/+/268196

Change-Id: Iba54e55610a97ab937ce13d54cd530bc50c9cb92
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 078010a..7a64c57 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckSubsystemsModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckSubsystemsModule.java
@@ -19,6 +19,7 @@
 import com.google.inject.AbstractModule;
 import com.googlesource.gerrit.plugins.healthcheck.check.ActiveWorkersCheck;
 import com.googlesource.gerrit.plugins.healthcheck.check.AuthHealthCheck;
+import com.googlesource.gerrit.plugins.healthcheck.check.DeadlockCheck;
 import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck;
 import com.googlesource.gerrit.plugins.healthcheck.check.JGitHealthCheck;
 import com.googlesource.gerrit.plugins.healthcheck.check.ProjectsListHealthCheck;
@@ -35,6 +36,7 @@
     bindChecker(QueryChangesHealthCheck.class);
     bindChecker(AuthHealthCheck.class);
     bindChecker(ActiveWorkersCheck.class);
+    bindChecker(DeadlockCheck.class);
     bind(LifecycleListener.class).to(HealthCheckMetrics.class);
   }
 
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
new file mode 100644
index 0000000..db5e83a
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/check/DeadlockCheck.java
@@ -0,0 +1,50 @@
+// 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.DEADLOCK;
+
+import com.codahale.metrics.MetricRegistry;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.healthcheck.HealthCheckConfig;
+import java.util.Optional;
+
+public class DeadlockCheck extends AbstractHealthCheck {
+
+  public static final String DEADLOCKED_THREADS_METRIC_NAME =
+      "proc/jvm/thread/num_deadlocked_threads";
+
+  private final MetricRegistry metricRegistry;
+
+  @Inject
+  public DeadlockCheck(
+      ListeningExecutorService executor,
+      HealthCheckConfig healthCheckConfig,
+      MetricRegistry metricRegistry) {
+    super(executor, healthCheckConfig, DEADLOCK);
+    this.metricRegistry = metricRegistry;
+  }
+
+  @Override
+  protected Result doCheck() throws Exception {
+    return Optional.ofNullable(metricRegistry.getGauges().get(DEADLOCKED_THREADS_METRIC_NAME))
+        .map(
+            metric -> {
+              return (int) metric.getValue() == 0 ? Result.PASSED : Result.FAILED;
+            })
+        .orElse(Result.PASSED);
+  }
+}
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 9614484..ce97a1b 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
@@ -21,4 +21,5 @@
   String QUERYCHANGES = "querychanges";
   String AUTH = "auth";
   String ACTIVEWORKERS = "activeworkers";
+  String DEADLOCK = "deadlock";
 }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 739a43a..3738b9b 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -49,6 +49,7 @@
 - `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
+- `deadlock` : check if Java deadlocks are reported by the JVM
 
 Each check name can be disabled by setting the `enabled` parameter to **false**,
 by default this parameter is set to **true**
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/DeadlockCheckTest.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/DeadlockCheckTest.java
new file mode 100644
index 0000000..01a1147
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/DeadlockCheckTest.java
@@ -0,0 +1,115 @@
+// 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.DEADLOCK;
+
+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.DeadlockCheck;
+import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck.Result;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+public class DeadlockCheckTest {
+
+  @Test
+  public void shouldPassCheckWhenNoMetric() {
+
+    Injector injector = testInjector(new TestModule(new MetricRegistry()));
+
+    DeadlockCheck check = createCheck(injector);
+    assertThat(check.run().result).isEqualTo(Result.PASSED);
+  }
+
+  @Test
+  public void shouldPassCheckWhenNoDeadlock() {
+
+    MetricRegistry metricRegistry = createMetricRegistry(0);
+
+    Injector injector = testInjector(new TestModule(createMetricRegistry(0)));
+
+    DeadlockCheck check = createCheck(injector);
+    assertThat(check.run().result).isEqualTo(Result.PASSED);
+  }
+
+  @Test
+  public void shouldFailCheckWhenSingleDeadlock() {
+
+    Injector injector = testInjector(new TestModule(createMetricRegistry(1)));
+
+    DeadlockCheck check = createCheck(injector);
+    assertThat(check.run().result).isEqualTo(Result.FAILED);
+  }
+
+  @Test
+  public void shouldFailCheckWhenMultipleDeadlocks() {
+
+    Injector injector = testInjector(new TestModule(createMetricRegistry(5)));
+
+    DeadlockCheck check = createCheck(injector);
+    assertThat(check.run().result).isEqualTo(Result.FAILED);
+  }
+
+
+  private Injector testInjector(AbstractModule testModule) {
+    return Guice.createInjector(new HealthCheckModule(), testModule);
+  }
+
+  private MetricRegistry createMetricRegistry(Integer value) {
+    MetricRegistry metricRegistry = new MetricRegistry();
+    metricRegistry.register(
+        DeadlockCheck.DEADLOCKED_THREADS_METRIC_NAME,
+        new Gauge<Integer>() {
+          @Override
+          public Integer getValue() {
+            return value;
+          }
+        });
+    return metricRegistry;
+  }
+
+  private DeadlockCheck createCheck(Injector injector) {
+    return new DeadlockCheck(
+        injector.getInstance(ListeningExecutorService.class),
+        DEFAULT_CONFIG,
+        injector.getInstance(MetricRegistry.class));
+  }
+
+  private class TestModule extends AbstractModule {
+    Config gerritConfig;
+    MetricRegistry metricRegistry;
+
+    public TestModule(MetricRegistry metricRegistry) {
+      this.gerritConfig = new Config();
+      this.metricRegistry = metricRegistry;
+    }
+
+    @Override
+    protected void configure() {
+      bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(gerritConfig);
+      bind(ThreadSettingsConfig.class);
+      bind(MetricRegistry.class).toInstance(metricRegistry);
+    }
+  }
+}