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); + } + } +}