Initial implementation of cloudwatch reporter Report gerrit metrics to AWS cloudwatch Feature: Issue 13125 Change-Id: I62a96fff0999e301e4e3dc7e0d9fb8af33efbcae
diff --git a/BUILD b/BUILD new file mode 100644 index 0000000..1be89ee --- /dev/null +++ b/BUILD
@@ -0,0 +1,58 @@ +load( + "//tools/bzl:plugin.bzl", + "PLUGIN_DEPS", + "PLUGIN_TEST_DEPS", + "gerrit_plugin", +) + +load("//tools/bzl:junit.bzl", "junit_tests") + +gerrit_plugin( + name = "metrics-reporter-cloudwatch", + srcs = glob(["src/main/java/**/*.java"]), + manifest_entries = [ + "Gerrit-PluginName: metrics-reporter-cloudwatch", + "Gerrit-Module: com.googlesource.gerrit.plugins.metricsreportercloudwatch.GerritCloudwatchModule", + ], + resources = glob(["src/main/resources/**/*"]), + deps = [ + "@dropwizard_metrics_cloudwatch//jar", + "@dropwizard_metrics_jvm//jar", + "@awssdk_cloudwatch//jar", + "@awssdk_aws_core//jar", + "@awssdk_regions//jar", + "@aws_java_sdk_core//jar", + "@awssdk_auth//jar", + "@awssdk_sdk_core//jar", + "@awssdk_utils//jar", + "@awssdk_http_client_spi//jar", + "@awssdk_profiles//jar", + "@awssdk_query_protocol//jar", + "@awssdk_protocol_core//jar", + "@io_netty_all//jar", + "@awssdk_netty_nio_client//jar", + "@awssdk_metrics_spi//jar", + "@reactivestreams//jar", + "@jackson_core//jar", + "@jackson_annotations//jar", + "@jackson_databind//jar", + ], +) + +junit_tests( + name = "metrics-reporter-cloudwatch_tests", + srcs = glob(["src/test/java/**/*.java"]), + resources = glob(["src/test/resources/**/*"]), + deps = [ + ":metrics-reporter-cloudwatch__plugin_test_deps", + ], +) + +java_library( + name = "metrics-reporter-cloudwatch__plugin_test_deps", + testonly = 1, + visibility = ["//visibility:public"], + exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [ + ":metrics-reporter-cloudwatch__plugin", + ], +)
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl new file mode 100644 index 0000000..b20ee27 --- /dev/null +++ b/external_plugin_deps.bzl
@@ -0,0 +1,123 @@ +load("//tools/bzl:maven_jar.bzl", "maven_jar") + +def external_plugin_deps(): + maven_jar( + name = 'dropwizard_metrics_cloudwatch', + artifact = 'io.github.azagniotov:dropwizard-metrics-cloudwatch:2.0.5', + sha1 = '7fed805d8fb31e54d75597a2b4f5b958eecad0ab', + ) + + maven_jar( + name = 'dropwizard_metrics_jvm', + artifact = 'io.dropwizard.metrics:metrics-jvm:4.1.10.1', + sha1 = '88d9e476c2944a9f0158474dc2b7064c96e26317', + ) + + maven_jar( + name = 'reactivestreams', + artifact = 'org.reactivestreams:reactive-streams:1.0.3', + sha1 = 'd9fb7a7926ffa635b3dcaa5049fb2bfa25b3e7d0', + ) + + maven_jar( + name = 'awssdk_cloudwatch', + artifact = 'software.amazon.awssdk:cloudwatch:2.13.54', + sha1 = '46e4f3dd21f1b6a61f08ae7195f0f025e207af5f', + ) + + maven_jar( + name = 'awssdk_auth', + artifact = 'software.amazon.awssdk:auth:2.13.54', + sha1 = '68b522302874b580ecd5563fe58e492136a24c81', + ) + + maven_jar( + name = 'awssdk_sdk_core', + artifact = 'software.amazon.awssdk:sdk-core:2.13.54', + sha1 = '10163b0cbe76600891e74516b958ee7628e70e2a', + ) + + maven_jar( + name = 'awssdk_aws_core', + artifact = 'software.amazon.awssdk:aws-core:2.13.54', + sha1 = '356c0c26afa7fb2a1edb921fc16e9de6a533f559', + ) + + maven_jar( + name = 'awssdk_profiles', + artifact = 'software.amazon.awssdk:profiles:2.13.54', + sha1 = '705146ffaa1aab5442791d57238b4ba304787e16', + ) + + maven_jar( + name = 'awssdk_regions', + artifact = 'software.amazon.awssdk:regions:2.13.54', + sha1 = '0de5b12c51d5332720fcb32581af3bd3ff88b21c', + ) + + maven_jar( + name = 'awssdk_metrics_spi', + artifact = 'software.amazon.awssdk:metrics-spi:2.13.54', + sha1 = '0610a43ba773be1a05fdf05416bb671d0eaa9916', + ) + + maven_jar( + name = 'awssdk_utils', + artifact = 'software.amazon.awssdk:utils:2.13.54', + sha1 = 'dae698acd98027117a13550f106b1ea6539d076a', + ) + + maven_jar( + name = 'awssdk_http_client_spi', + artifact = 'software.amazon.awssdk:http-client-spi:2.13.54', + sha1 = 'bdecd06aa2793f1366b1eeaa385314f22336fddb', + ) + + maven_jar( + name = 'awssdk_query_protocol', + artifact = 'software.amazon.awssdk:aws-query-protocol:2.13.54', + sha1 = '2145252d0352b89ee59d3a4fab6b6ea03b032eb1', + ) + + maven_jar( + name = 'awssdk_protocol_core', + artifact = 'software.amazon.awssdk:protocol-core:2.13.54', + sha1 = '28bc71e1450dd1515a251d74511652ccf0f2af01', + ) + + + maven_jar( + name = 'awssdk_netty_nio_client', + artifact = 'software.amazon.awssdk:netty-nio-client:2.13.54', + sha1 = '03de0e583e6b244743915640fa9d00d68c5fd077', + ) + + maven_jar( + name = 'io_netty_all', + artifact = 'io.netty:netty-all:4.1.51.Final', + sha1 = '5e5f741acc4c211ac4572c31c7e5277ec465e4e4', + ) + + maven_jar( + name = 'aws_java_sdk_core', + artifact = 'com.amazonaws:aws-java-sdk-core:1.11.820', + sha1 = '8902eefbbcd087a89e57a3e88c8e383ed0d7bab9', + ) + + maven_jar( + name = 'jackson_core', + artifact = 'com.fasterxml.jackson.core:jackson-core:2.11.1', + sha1 = '8b02908d53183fdf9758e7e20f2fdee87613a962', + ) + + maven_jar( + name = 'jackson_annotations', + artifact = 'com.fasterxml.jackson.core:jackson-annotations:2.11.1', + sha1 = 'f083c4ac0fb8b3c6b8d5b62cd54122228ef62cee', + ) + + maven_jar( + name = 'jackson_databind', + artifact = 'com.fasterxml.jackson.core:jackson-databind:2.11.1', + sha1 = 'f5d24a1dcf46000316d40c8c61196c48dd5677c5', + )
diff --git a/src/main/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/GerritCloudwatchModule.java b/src/main/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/GerritCloudwatchModule.java new file mode 100644 index 0000000..50ae062 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/GerritCloudwatchModule.java
@@ -0,0 +1,27 @@ +// 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.metricsreportercloudwatch; + +import com.google.gerrit.lifecycle.LifecycleModule; +import com.google.inject.Scopes; + +public class GerritCloudwatchModule extends LifecycleModule { + + @Override + protected void configure() { + bind(GerritCloudwatchReporterConfig.class).in(Scopes.SINGLETON); + bind(GerritCloudwatchReporter.class).in(Scopes.SINGLETON); + listener().to(GerritCloudwatchReporter.class); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/GerritCloudwatchReporter.java b/src/main/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/GerritCloudwatchReporter.java new file mode 100644 index 0000000..1e218e4 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/GerritCloudwatchReporter.java
@@ -0,0 +1,69 @@ +// 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.metricsreportercloudwatch; + +import com.codahale.metrics.MetricFilter; +import com.codahale.metrics.MetricRegistry; +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.extensions.events.LifecycleListener; +import com.google.inject.Inject; +import io.github.azagniotov.metrics.reporter.cloudwatch.CloudWatchReporter; +import java.util.concurrent.TimeUnit; +import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient; +import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClientBuilder; + +public class GerritCloudwatchReporter implements LifecycleListener { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + private final CloudWatchReporter cloudWatchReporter; + private final GerritCloudwatchReporterConfig config; + + @Inject + public GerritCloudwatchReporter(GerritCloudwatchReporterConfig config, MetricRegistry registry) + throws IllegalStateException { + this.config = config; + + CloudWatchAsyncClientBuilder cloudWatchAsyncClientBuilder = CloudWatchAsyncClient.builder(); + + CloudWatchReporter.Builder cloudWatchReporterBuilder = + CloudWatchReporter.forRegistry( + registry, cloudWatchAsyncClientBuilder.build(), config.getNamespace()) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .filter(MetricFilter.ALL) + .withZeroValuesSubmission() + .withReportRawCountValue() + .withHighResolution(); + + if (config.getDryRun()) { + cloudWatchReporterBuilder = cloudWatchReporterBuilder.withDryRun(); + } + + cloudWatchReporter = cloudWatchReporterBuilder.build(); + } + + @Override + public void start() { + logger.atInfo().log( + String.format( + "Reporting to CloudWatch [namespace:'%s'] at rate of '%d' seconds, after initial delay of %d seconds", + config.getNamespace(), config.getRate(), config.getInitialDelay())); + cloudWatchReporter.start(config.getInitialDelay(), config.getRate(), TimeUnit.SECONDS); + } + + @Override + public void stop() { + cloudWatchReporter.stop(); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/GerritCloudwatchReporterConfig.java b/src/main/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/GerritCloudwatchReporterConfig.java new file mode 100644 index 0000000..858d5d2 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/GerritCloudwatchReporterConfig.java
@@ -0,0 +1,79 @@ +// 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.metricsreportercloudwatch; + +import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.server.config.ConfigUtil; +import com.google.gerrit.server.config.PluginConfig; +import com.google.gerrit.server.config.PluginConfigFactory; +import com.google.inject.Inject; +import java.util.concurrent.TimeUnit; + +class GerritCloudwatchReporterConfig { + protected static final String KEY_NAMESPACE = "namespace"; + protected static final String KEY_RATE = "rate"; + protected static final String KEY_DRYRUN = "dryRun"; + protected static final String KEY_INITIAL_DELAY = "initialDelay"; + + protected static final String DEFAULT_NAMESPACE = "gerrit"; + protected static final String DEFAULT_EMPTY_STRING = ""; + protected static final Boolean DEFAULT_DRY_RUN = false; + protected static final Long DEFAULT_RATE_SECS = 60L; + protected static final Integer DEFAULT_INITIAL_DELAY_SECS = 0; + + private final int rate; + private final String namespace; + private final int initialDelay; + private final Boolean dryRun; + + @Inject + public GerritCloudwatchReporterConfig( + PluginConfigFactory configFactory, @PluginName String pluginName) { + PluginConfig pluginConfig = configFactory.getFromGerritConfig(pluginName); + + this.namespace = pluginConfig.getString(KEY_NAMESPACE, DEFAULT_NAMESPACE); + + this.dryRun = pluginConfig.getBoolean(KEY_DRYRUN, DEFAULT_DRY_RUN); + + this.rate = + (int) + ConfigUtil.getTimeUnit( + pluginConfig.getString(KEY_RATE, DEFAULT_EMPTY_STRING), + DEFAULT_RATE_SECS, + TimeUnit.SECONDS); + + this.initialDelay = + (int) + ConfigUtil.getTimeUnit( + pluginConfig.getString(KEY_INITIAL_DELAY, DEFAULT_EMPTY_STRING), + DEFAULT_INITIAL_DELAY_SECS, + TimeUnit.SECONDS); + } + + public int getRate() { + return rate; + } + + public String getNamespace() { + return namespace; + } + + public int getInitialDelay() { + return initialDelay; + } + + public Boolean getDryRun() { + return dryRun; + } +}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md new file mode 100644 index 0000000..f40b8bb --- /dev/null +++ b/src/main/resources/Documentation/about.md
@@ -0,0 +1,4 @@ +# About + +This plugin sends Gerrit metrics to Cloud Watch in AWS. +You can find more information on CloudWatch [here](https://aws.amazon.com/cloudwatch/)
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md new file mode 100644 index 0000000..a634655 --- /dev/null +++ b/src/main/resources/Documentation/build.md
@@ -0,0 +1,35 @@ +# Build + +This plugin can be built with Bazel. + +Clone (or link) this plugin to the `plugins` directory of Gerrit's source tree. +Put the external dependency Bazel build file into the Gerrit /plugins directory, +replacing the existing empty one. + +``` + cd gerrit/plugins + ln -fs metrics-reporter-cloudwatch/external_plugin_deps.bzl . +``` + +Then run: + +```bash + bazelisk build plugins/metrics-reporter-cloudwatch +``` + +in the root of Gerrit's source tree to build +The output is created in: + +``` + bazel-bin/plugins/metrics-reporter-cloudwatch/metrics-reporter-cloudwatch.jar +``` + +# Test + + +You can run tests with bazelisk, as such: + +```bash +bazelisk plugins/metrics-reporter-cloudwatch:metrics-reporter-cloudwatch_tests +``` +
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md new file mode 100644 index 0000000..36d1271 --- /dev/null +++ b/src/main/resources/Documentation/config.md
@@ -0,0 +1,53 @@ +# Configuration + +The configuration of the @PLUGIN@ plugin is done in the `[plugin "@PLUGIN@"]` +section of the `gerrit.config` file. + +## Authentication + +To make requests to AWS, this plugin uses the default AWS credential provider +chain. This means that the java SDK will try to find the relevant AWS +credentials (and region) by looking, in order to environment variables +(`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`), then system properties +`aws.accessKeyId` and `aws.secretKey`, then `Web Identity Token`, your +`~/.aws/credentials`, and so on. + +Find the all the details about the default provider chain +[here](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) and +[here](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/setup-credentials.html) + +## Metrics Reporter + +* `plugin.@PLUGIN@.dryRun` (Optional): the reporter will log.DEBUG the metrics, +instead of doing a real POST to CloudWatch. + * Type: Boolean + * Default: false + * Example: true + +There will also be a log entry at WARN level to inform the plugin is running in +dry-run mode: + +```** Reporter is running in 'DRY RUN' mode **``` + +To observe the metrics increase the log level, as such: + +```bash +ssh -p <port> admin@<server> gerrit logging set-level debug io.github.azagniotov.metrics.reporter.cloudwatch.CloudWatchReporter +``` + +* `plugin.@PLUGIN@.namespace` (Optional): The CloudWatch namespace for Gerrit metrics. + * Type: String + * Default: "gerrit" + * AWS Docs: [Namespaces](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Namespace) + * Example: "my-gerrit-metrics" + +* `plugin.@PLUGIN@.rate` (Optional): The rate at which metrics should be fired to AWS. + * Type: Time + * Default: "60s" + * Example: 5m + +* `plugin.@PLUGIN@.initialDelay` (Optional): The time to delay the first reporting +execution. + * Type: Time + * Default: "0" + * Example: 60 seconds \ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/GerritCloudwatchReporterConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/GerritCloudwatchReporterConfigTest.java new file mode 100644 index 0000000..0a6be81 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/GerritCloudwatchReporterConfigTest.java
@@ -0,0 +1,67 @@ +// 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.metricsreportercloudwatch; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.gerrit.server.config.PluginConfig; +import com.google.gerrit.server.config.PluginConfigFactory; +import org.eclipse.jgit.lib.Config; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class GerritCloudwatchReporterConfigTest { + private static final String PLUGIN_NAME = "foo"; + private final PluginConfig emptyGlobalPluginConfig = new PluginConfig(PLUGIN_NAME, new Config()); + + GerritCloudwatchReporterConfig reporterConfig; + + @Mock PluginConfigFactory configFactory; + + @Test + public void shouldGetAllDefaultsWhenConfigurationIsEmpty() { + when(configFactory.getFromGerritConfig(PLUGIN_NAME)).thenReturn(emptyGlobalPluginConfig); + reporterConfig = new GerritCloudwatchReporterConfig(configFactory, PLUGIN_NAME); + + assertThat(reporterConfig.getInitialDelay()) + .isEqualTo(GerritCloudwatchReporterConfig.DEFAULT_INITIAL_DELAY_SECS); + assertThat(reporterConfig.getNamespace()) + .isEqualTo(GerritCloudwatchReporterConfig.DEFAULT_NAMESPACE); + assertThat(reporterConfig.getRate()) + .isEqualTo(GerritCloudwatchReporterConfig.DEFAULT_RATE_SECS); + assertThat(reporterConfig.getDryRun()) + .isEqualTo(GerritCloudwatchReporterConfig.DEFAULT_DRY_RUN); + } + + @Test + public void shouldReadMetricValuesFromConfiguration() { + PluginConfig globalPluginConfig = emptyGlobalPluginConfig; + globalPluginConfig.setString(GerritCloudwatchReporterConfig.KEY_NAMESPACE, "foobar"); + globalPluginConfig.setString(GerritCloudwatchReporterConfig.KEY_RATE, "3m"); + globalPluginConfig.setString(GerritCloudwatchReporterConfig.KEY_INITIAL_DELAY, "20s"); + globalPluginConfig.setBoolean(GerritCloudwatchReporterConfig.KEY_DRYRUN, true); + + when(configFactory.getFromGerritConfig(PLUGIN_NAME)).thenReturn(globalPluginConfig); + reporterConfig = new GerritCloudwatchReporterConfig(configFactory, PLUGIN_NAME); + + assertThat(reporterConfig.getInitialDelay()).isEqualTo(20); + assertThat(reporterConfig.getNamespace()).isEqualTo("foobar"); + assertThat(reporterConfig.getRate()).isEqualTo(180); + assertThat(reporterConfig.getDryRun()).isTrue(); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/MetricsReporterCloudwatchIT.java b/src/test/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/MetricsReporterCloudwatchIT.java new file mode 100644 index 0000000..9e79351 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/metricsreportercloudwatch/MetricsReporterCloudwatchIT.java
@@ -0,0 +1,133 @@ +// 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.metricsreportercloudwatch; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import com.google.common.base.Splitter; +import com.google.common.base.Stopwatch; +import com.google.gerrit.acceptance.LightweightPluginDaemonTest; +import com.google.gerrit.acceptance.TestPlugin; +import com.google.gerrit.acceptance.UseLocalDisk; +import com.google.gerrit.acceptance.config.GerritConfig; +import com.google.gerrit.metrics.Counter0; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.MetricMaker; +import com.google.inject.Inject; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.function.Supplier; +import java.util.stream.Stream; +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; +import org.junit.Test; + +@UseLocalDisk +@TestPlugin( + name = "metrics-reporter-cloudwatch", + sysModule = "com.googlesource.gerrit.plugins.metricsreportercloudwatch.GerritCloudwatchModule") +public class MetricsReporterCloudwatchIT extends LightweightPluginDaemonTest { + private static final String TEST_METRIC_NAME = "test/metric/name"; + private static final long TEST_METRIC_INCREMENT = 1234567L; + private static final String TEST_TIMEOUT = "10"; + private static final Duration TEST_TIMEOUT_DURATION = + Duration.ofSeconds(Integer.valueOf(TEST_TIMEOUT)); + + @Inject private MetricMaker metricMaker; + private Counter0 testCounterMetric; + + @Override + public void setUpTestPlugin() throws Exception { + System.setProperty("aws.region", "us-west-1"); + + testCounterMetric = metricMaker.newCounter(TEST_METRIC_NAME, new Description("test metric")); + + super.setUpTestPlugin(); + } + + @Test + @GerritConfig(name = "plugin.metrics-reporter-cloudwatch.dryrun", value = "true") + public void shouldCloudwatchReporterBeStartedInDryRun() throws Exception { + InMemoryLoggerAppender dryRunMetricsOutput = newInMemoryLogger(); + + waitUntil(() -> dryRunMetricsOutput.metricsStream().anyMatch(l -> l.contains("DRY RUN"))); + } + + @Test + @GerritConfig(name = "plugin.metrics-reporter-cloudwatch.dryrun", value = "true") + @GerritConfig(name = "plugin.metrics-reporter-cloudwatch.rate", value = TEST_TIMEOUT) + public void shouldReportMetricValueToCloudwatch() throws Exception { + InMemoryLoggerAppender dryRunMetricsOutput = newInMemoryLogger(); + + testCounterMetric.incrementBy(TEST_METRIC_INCREMENT); + + waitUntil( + () -> + dryRunMetricsOutput + .metricsStream() + .filter(l -> l.contains("MetricName=" + TEST_METRIC_NAME)) + .anyMatch(l -> l.contains("Value=" + TEST_METRIC_INCREMENT))); + } + + private static InMemoryLoggerAppender newInMemoryLogger() { + InMemoryLoggerAppender dryRunMetricsOutput = new InMemoryLoggerAppender(); + for (Enumeration<?> logger = LogManager.getCurrentLoggers(); logger.hasMoreElements(); ) { + Logger log = (Logger) logger.nextElement(); + if (log.getName().contains("CloudWatchReporter")) { + log.addAppender(dryRunMetricsOutput); + log.setLevel(Level.DEBUG); + } + } + return dryRunMetricsOutput; + } + + private static void waitUntil(Supplier<Boolean> waitCondition) throws InterruptedException { + Stopwatch stopwatch = Stopwatch.createStarted(); + while (!waitCondition.get()) { + if (stopwatch.elapsed().compareTo(TEST_TIMEOUT_DURATION) > 0) { + throw new InterruptedException(); + } + MILLISECONDS.sleep(50); + } + } + + static class InMemoryLoggerAppender extends AppenderSkeleton { + private final Splitter metricsDatumSplitter = Splitter.on("MetricDatum"); + + private ArrayList<String> logLines = new ArrayList<>(); + + @Override + public void close() {} + + @Override + public boolean requiresLayout() { + return false; + } + + @Override + protected void append(LoggingEvent event) { + String logMessage = event.getMessage().toString(); + logLines.add(logMessage); + } + + public Stream<String> metricsStream() { + return logLines.stream().flatMap(metricsDatumSplitter::splitToStream); + } + } +}