diff --git a/.github/workflows/maven.yml b/.github/workflows/build.yml
similarity index 97%
rename from .github/workflows/maven.yml
rename to .github/workflows/build.yml
index 7960bae8..1b7391df 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/build.yml
@@ -30,9 +30,9 @@ jobs:
distribution: temurin
cache: maven
- name: Check formatting
- run: ./mvnw --batch-mode fmt:check
- - name: Build
- run: ./mvnw --batch-mode package
+ run: ./mvnw -B fmt:check
+ - name: Build and verify
+ run: ./mvnw -B verify
- name: Update dependency graph
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: advanced-security/maven-dependency-submission-action@b275d12641ac2d2108b2cbb7598b154ad2f2cee8 # v5.0.0
diff --git a/RELEASING.md b/RELEASING.md
index dc01527c..d8e2ddc1 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -91,7 +91,7 @@ It usually appears immediately after the release process is done, but can take a
## Docker images
As part of the release process, `./mvnw` will create the git tag.
-This tag is picked up by [GitHub Actions](https://github.com/prometheus/cloudwatch_exporter/actions/workflows/maven.yml), which builds and pushes the [Docker images](README.md#docker-images).
+This tag is picked up by [GitHub Actions](https://github.com/prometheus/cloudwatch_exporter/actions/workflows/build.yml), which builds and pushes the [Docker images](README.md#docker-images).
## GitHub Release
diff --git a/pom.xml b/pom.xml
index 60d611c5..faf34a4b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
The Apache Software License, Version 2.0
- http://www.apache.org/licenses/LICENSE-2.0.txt
+ https://www.apache.org/licenses/LICENSE-2.0.txt
repo
@@ -50,11 +50,13 @@
${maven.build.timestamp}
17
+ 3.27.7
3.2.4
1.22.0
3.0
0.16.0
- 4.13.2
+ 12.1.10
+ 6.1.0
5.23.0
2.0.18
2.6
@@ -72,6 +74,8 @@
3.4.0
3.5.6
2.21.0
+ 0.8.14
+
@@ -123,12 +127,12 @@
org.eclipse.jetty
jetty-server
- 12.0.36
+ ${jetty.version}
org.eclipse.jetty.ee10
jetty-ee10-servlet
- 12.0.36
+ ${jetty.version}
com.github.ben-manes.caffeine
@@ -137,9 +141,15 @@
- junit
- junit
- ${junit.version}
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.jupiter.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
test
@@ -246,9 +256,31 @@
maven-surefire-plugin
${maven-surefire-plugin.version}
- -javaagent:${settings.localRepository}/org/mockito/mockito-core/5.23.0/mockito-core-5.23.0.jar -XX:+EnableDynamicAgentLoading -Djdk.net.URLClassPath.disableClassPathURLCheck=true
+ false
+ -javaagent:${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar @{argLine} -XX:+EnableDynamicAgentLoading -Djdk.net.URLClassPath.disableClassPathURLCheck=true
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco-maven-plugin.version}
+
+
+ jacoco-prepare-agent
+ process-test-classes
+
+ prepare-agent
+
+
+
+ jacoco-report
+ verify
+
+ report
+
+
+
+
org.apache.maven.plugins
maven-javadoc-plugin
@@ -367,4 +399,4 @@
-
+
\ No newline at end of file
diff --git a/scripts/stress-test.sh b/scripts/stress-test.sh
new file mode 100755
index 00000000..8c32719e
--- /dev/null
+++ b/scripts/stress-test.sh
@@ -0,0 +1,106 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) The Prometheus Authors
+#
+# 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.
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+readonly SCRIPT_DIR
+readonly PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
+readonly MVNW="${PROJECT_DIR}/mvnw"
+
+readonly LOG_FILE="stress-test.log"
+
+usage() {
+ cat <<'EOF'
+Usage: ./scripts/stress-test.sh
+
+Run CloudWatch Exporter tests multiple times to check for flaky tests.
+
+Arguments:
+ Number of times to run the test suite.
+
+Examples:
+ ./scripts/stress-test.sh 5
+ ./scripts/stress-test.sh 10
+EOF
+}
+
+log() {
+ echo "[INFO] $*" | tee -a "${LOG_FILE}"
+}
+
+fail() {
+ echo "[ERROR] $*" | tee -a "${LOG_FILE}" >&2
+ exit 1
+}
+
+main() {
+ local iterations iteration start_time end_time elapsed_time total_elapsed
+
+ if [[ $# -ne 1 ]]; then
+ usage
+ exit 1
+ fi
+
+ iterations="$1"
+
+ [[ -x "${MVNW}" ]] || fail "mvnw not found or not executable: ${MVNW}"
+
+ if [[ ! "${iterations}" =~ ^[1-9][0-9]*$ ]]; then
+ fail "Invalid iterations '${iterations}'. Must be a positive integer."
+ fi
+
+ rm -f "${LOG_FILE}"
+
+ log "Starting stress test with ${iterations} iteration(s)"
+ log ""
+
+ log "Building project (initial build)..."
+ local build_start
+ build_start="${SECONDS}"
+ if ! (cd "${PROJECT_DIR}" && ./mvnw -B compile -DskipTests) |& tee -a "${LOG_FILE}"; then
+ fail "Initial build failed. See ${LOG_FILE} for details."
+ fi
+ local build_elapsed
+ build_elapsed=$((SECONDS - build_start))
+ log "Initial build completed in ${build_elapsed}s"
+ log ""
+
+ log "Running ${iterations} test iteration(s)..."
+ log ""
+
+ for ((iteration = 1; iteration <= iterations; iteration++)); do
+ start_time="${SECONDS}"
+ log "Iteration ${iteration}/${iterations} started"
+
+ if ! (cd "${PROJECT_DIR}" && ./mvnw -B test) |& tee -a "${LOG_FILE}"; then
+ end_time="${SECONDS}"
+ elapsed_time=$((end_time - start_time))
+ fail "Iteration ${iteration}/${iterations} failed after ${elapsed_time}s. See ${LOG_FILE} for details."
+ fi
+
+ end_time="${SECONDS}"
+ elapsed_time=$((end_time - start_time))
+ log "Iteration ${iteration}/${iterations} passed (${elapsed_time}s)"
+ log ""
+ done
+
+ total_elapsed="${SECONDS}"
+
+ log "All ${iterations} iteration(s) completed successfully in ${total_elapsed}s"
+}
+
+main "$@"
diff --git a/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java b/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java
index 71ff755c..d5c960ca 100644
--- a/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java
+++ b/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java
@@ -112,6 +112,7 @@ public CloudWatchCollector(String yamlConfig) {
}
/* For unittests. */
+ @SuppressWarnings("unchecked")
protected CloudWatchCollector(
String jsonConfig,
CloudWatchClient cloudWatchClient,
@@ -141,6 +142,7 @@ protected void reloadConfig() throws IOException {
}
}
+ @SuppressWarnings("unchecked")
protected void loadConfig(
Reader in, CloudWatchClient cloudWatchClient, ResourceGroupsTaggingApiClient taggingClient) {
loadConfig(
@@ -149,6 +151,7 @@ protected void loadConfig(
taggingClient);
}
+ @SuppressWarnings("unchecked")
private void loadConfig(
Map config,
CloudWatchClient cloudWatchClient,
diff --git a/src/test/java/io/prometheus/cloudwatch/CachingDimensionExpiryTest.java b/src/test/java/io/prometheus/cloudwatch/CachingDimensionExpiryTest.java
index 4b77db8a..41efe0c9 100644
--- a/src/test/java/io/prometheus/cloudwatch/CachingDimensionExpiryTest.java
+++ b/src/test/java/io/prometheus/cloudwatch/CachingDimensionExpiryTest.java
@@ -1,6 +1,6 @@
package io.prometheus.cloudwatch;
-import static org.junit.Assert.assertEquals;
+import static org.assertj.core.api.Assertions.assertThat;
import io.prometheus.cloudwatch.CachingDimensionSource.DimensionCacheKey;
import io.prometheus.cloudwatch.CachingDimensionSource.DimensionExpiry;
@@ -8,7 +8,7 @@
import java.time.Instant;
import java.util.Collections;
import java.util.List;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
public class CachingDimensionExpiryTest {
@@ -27,7 +27,7 @@ public void expireAfterCreateUsesDefaultWithEmptyOverrides() {
emptyData,
Instant.now().toEpochMilli());
- assertEquals(35, Duration.ofNanos(afterCreate).toSeconds());
+ assertThat(Duration.ofNanos(afterCreate).toSeconds()).isEqualTo(35);
}
@Test
@@ -42,7 +42,7 @@ public void expireAfterCreateUsesMetricLevelOverride() {
emptyData,
Instant.now().toEpochMilli());
- assertEquals(100, Duration.ofNanos(afterCreate).toSeconds());
+ assertThat(Duration.ofNanos(afterCreate).toSeconds()).isEqualTo(100);
}
@Test
@@ -57,7 +57,7 @@ public void expireAfterCreateUsesDefaultIfNoMatchedOverride() {
emptyData,
Instant.now().toEpochMilli());
- assertEquals(35, Duration.ofNanos(afterCreate).toSeconds());
+ assertThat(Duration.ofNanos(afterCreate).toSeconds()).isEqualTo(35);
}
@Test
@@ -73,7 +73,7 @@ public void expireAfterUpdateUsesCurrentDuration() {
Instant.now().toEpochMilli(),
10_000_000);
- assertEquals(10_000_000, afterUpdate);
+ assertThat(afterUpdate).isEqualTo(10_000_000);
}
@Test
@@ -88,7 +88,30 @@ public void expireAfterReadUsesCurrentDuration() {
emptyData,
Instant.now().toEpochMilli(),
20_000_000);
- assertEquals(20_000_000, afterRead);
+ assertThat(afterRead).isEqualTo(20_000_000);
+ }
+
+ @Test
+ public void dimensionCacheKeyEqualsHandlesIdentityNullDifferentTypesAndFields() {
+ DimensionCacheKey key = createDimensionCacheKey("AWS/S3", "BucketSizeBytes", 100);
+ DimensionCacheKey same = createDimensionCacheKey("AWS/S3", "BucketSizeBytes", 100);
+ DimensionCacheKey differentRule = createDimensionCacheKey("AWS/EC2", "CPUUtilization", 100);
+ DimensionCacheKey differentTags =
+ new DimensionCacheKey(
+ createMetricRule("AWS/S3", "BucketSizeBytes", 100), List.of("bucket-a"));
+
+ assertThat(key).isEqualTo(key);
+ assertThat(key).isEqualTo(same);
+ assertThat(key).isNotEqualTo(null);
+ assertThat(key).isNotEqualTo("not a key");
+ assertThat(key).isNotEqualTo(differentRule);
+ assertThat(key).isNotEqualTo(differentTags);
+ assertThat(key.hashCode()).isEqualTo(same.hashCode());
+ }
+
+ @Test
+ public void dimensionCacheKeyHashCodeHandlesNullFields() {
+ assertThat(new DimensionCacheKey(null, null).hashCode()).isZero();
}
private DimensionCacheKey createDimensionCacheKey(
diff --git a/src/test/java/io/prometheus/cloudwatch/CachingDimensionSourceTest.java b/src/test/java/io/prometheus/cloudwatch/CachingDimensionSourceTest.java
index ea24bd99..734d1736 100644
--- a/src/test/java/io/prometheus/cloudwatch/CachingDimensionSourceTest.java
+++ b/src/test/java/io/prometheus/cloudwatch/CachingDimensionSourceTest.java
@@ -1,13 +1,13 @@
package io.prometheus.cloudwatch;
import static io.prometheus.cloudwatch.DimensionSource.DimensionData;
-import static org.junit.Assert.assertEquals;
+import static org.assertj.core.api.Assertions.assertThat;
import io.prometheus.cloudwatch.CachingDimensionSource.DimensionCacheConfig;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import software.amazon.awssdk.services.cloudwatch.model.Dimension;
public class CachingDimensionSourceTest {
@@ -24,8 +24,8 @@ public void cachedFromDelegate() {
sut.getDimensions(createMetricRule("AWS/Redshift", "WriteIOPS"), Collections.emptyList());
Dimension dimension = Dimension.builder().name("AWS/Redshift").value("WriteIOPS").build();
- assertEquals(1, source.called);
- assertEquals(dimension, expected.getDimensions().get(0).get(0));
+ assertThat(source.called).isEqualTo(1);
+ assertThat(expected.getDimensions().get(0).get(0)).isEqualTo(dimension);
}
private MetricRule createMetricRule(String namespace, String name) {
diff --git a/src/test/java/io/prometheus/cloudwatch/CloudWatchCollectorTest.java b/src/test/java/io/prometheus/cloudwatch/CloudWatchCollectorTest.java
index 796e32e6..7433af9b 100644
--- a/src/test/java/io/prometheus/cloudwatch/CloudWatchCollectorTest.java
+++ b/src/test/java/io/prometheus/cloudwatch/CloudWatchCollectorTest.java
@@ -1,8 +1,9 @@
package io.prometheus.cloudwatch;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.fail;
+import static org.assertj.core.api.Assertions.within;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.never;
@@ -12,6 +13,10 @@
import io.prometheus.client.Collector;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.cloudwatch.RequestsMatchers.*;
+import java.io.StringReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
@@ -21,8 +26,8 @@
import java.util.List;
import java.util.Properties;
import java.util.Set;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.*;
@@ -37,7 +42,7 @@ public class CloudWatchCollectorTest {
ResourceGroupsTaggingApiClient taggingClient;
CollectorRegistry registry;
- @Before
+ @BeforeEach
public void setUp() {
cloudWatchClient = Mockito.mock(CloudWatchClient.class);
taggingClient = Mockito.mock(ResourceGroupsTaggingApiClient.class);
@@ -153,41 +158,36 @@ public void testAllStatistics() throws Exception {
.build())
.build());
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance"},
- new String[] {"aws_elb", ""}),
- .01);
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_maximum",
- new String[] {"job", "instance"},
- new String[] {"aws_elb", ""}),
- .01);
- assertEquals(
- 3.0,
- registry.getSampleValue(
- "aws_elb_request_count_minimum",
- new String[] {"job", "instance"},
- new String[] {"aws_elb", ""}),
- .01);
- assertEquals(
- 4.0,
- registry.getSampleValue(
- "aws_elb_request_count_sample_count",
- new String[] {"job", "instance"},
- new String[] {"aws_elb", ""}),
- .01);
- assertEquals(
- 5.0,
- registry.getSampleValue(
- "aws_elb_request_count_sum",
- new String[] {"job", "instance"},
- new String[] {"aws_elb", ""}),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(1.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_maximum",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_minimum",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(3.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_sample_count",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(4.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_sum",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(5.0, within(.01));
}
@Test
@@ -262,41 +262,36 @@ public void testAllStatisticsUsingGetMetricData() throws Exception {
.build()))
.build());
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance"},
- new String[] {"aws_elb", ""}),
- .01);
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_maximum",
- new String[] {"job", "instance"},
- new String[] {"aws_elb", ""}),
- .01);
- assertEquals(
- 3.0,
- registry.getSampleValue(
- "aws_elb_request_count_minimum",
- new String[] {"job", "instance"},
- new String[] {"aws_elb", ""}),
- .01);
- assertEquals(
- 4.0,
- registry.getSampleValue(
- "aws_elb_request_count_sample_count",
- new String[] {"job", "instance"},
- new String[] {"aws_elb", ""}),
- .01);
- assertEquals(
- 5.0,
- registry.getSampleValue(
- "aws_elb_request_count_sum",
- new String[] {"job", "instance"},
- new String[] {"aws_elb", ""}),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(1.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_maximum",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_minimum",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(3.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_sample_count",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(4.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_sum",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(5.0, within(.01));
}
@Test
@@ -346,7 +341,7 @@ void assertMetricTimestampEquals(
for (Collector.MetricFamilySamples.Sample s : samples.samples) {
metricNames.add(s.name);
if (s.name.equals(name)) {
- assertEquals(expectedTimestamp, (Long) s.timestampMs);
+ assertThat((Long) s.timestampMs).isEqualTo(expectedTimestamp);
return;
}
}
@@ -376,13 +371,12 @@ public void testUsesNewestDatapoint() throws Exception {
Datapoint.builder().timestamp(new Date(2).toInstant()).average(2.0).build())
.build());
- assertEquals(
- 3.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance"},
- new String[] {"aws_elb", ""}),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(3.0, within(.01));
}
@Test
@@ -454,31 +448,30 @@ public void testDimensions() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(3.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myLB"}),
- .01);
- assertEquals(
- 3.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "b", "myOtherLB"}),
- .01);
- assertNull(
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {
- "job",
- "instance",
- "availability_zone",
- "load_balancer_name",
- "this_extra_dimension_is_ignored"
- },
- new String[] {"aws_elb", "", "a", "myLB", "dummy"}));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myLB"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "b", "myOtherLB"}))
+ .isCloseTo(3.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {
+ "job",
+ "instance",
+ "availability_zone",
+ "load_balancer_name",
+ "this_extra_dimension_is_ignored"
+ },
+ new String[] {"aws_elb", "", "a", "myLB", "dummy"}))
+ .isNull();
}
@Test
@@ -546,25 +539,24 @@ public void testDimensionSelect() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(2.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myLB"}),
- .01);
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "b", "myLB"}),
- .01);
- assertNull(
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myOtherLB"}));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myLB"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "b", "myLB"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myOtherLB"}))
+ .isNull();
}
@Test
@@ -604,25 +596,24 @@ public void testAllSelectDimensionsKnown() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(2.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myLB"}),
- .01);
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "b", "myLB"}),
- .01);
- assertNull(
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myOtherLB"}));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myLB"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "b", "myLB"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myOtherLB"}))
+ .isNull();
}
@Test
@@ -678,25 +669,24 @@ public void testAllSelectDimensionsKnownUsingGetMetricData() throws Exception {
.build()))
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myLB"}),
- .01);
- assertEquals(
- 3.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "b", "myLB"}),
- .01);
- assertNull(
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myOtherLB"}));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myLB"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "b", "myLB"}))
+ .isCloseTo(3.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myOtherLB"}))
+ .isNull();
}
@Test
@@ -765,25 +755,24 @@ public void testDimensionSelectRegex() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(2.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myLB1"}),
- .01);
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "b", "myLB2"}),
- .01);
- assertNull(
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myOtherLB"}));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myLB1"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "b", "myLB2"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myOtherLB"}))
+ .isNull();
}
@Test
@@ -837,13 +826,12 @@ public void testGetDimensionsUsesNextToken() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(2.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myLB"}),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myLB"}))
+ .isCloseTo(2.0, within(.01));
}
@Test
@@ -873,18 +861,18 @@ public void testExtendedStatistics() throws Exception {
.build())
.build());
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_elb_latency_p95", new String[] {"job", "instance"}, new String[] {"aws_elb", ""}),
- .01);
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_latency_p99_99",
- new String[] {"job", "instance"},
- new String[] {"aws_elb", ""}),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_latency_p95",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(1.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_latency_p99_99",
+ new String[] {"job", "instance"},
+ new String[] {"aws_elb", ""}))
+ .isCloseTo(2.0, within(.01));
}
@Test
@@ -989,27 +977,24 @@ public void testDynamoIndexDimensions() throws Exception {
.datapoints(Datapoint.builder().timestamp(new Date().toInstant()).sum(3.0).build())
.build());
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_dynamodb_consumed_read_capacity_units_index_sum",
- new String[] {"job", "instance", "table_name", "global_secondary_index_name"},
- new String[] {"aws_dynamodb", "", "myTable", "myIndex"}),
- .01);
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_dynamodb_online_index_consumed_write_capacity_sum",
- new String[] {"job", "instance", "table_name", "global_secondary_index_name"},
- new String[] {"aws_dynamodb", "", "myTable", "myIndex"}),
- .01);
- assertEquals(
- 3.0,
- registry.getSampleValue(
- "aws_dynamodb_consumed_read_capacity_units_sum",
- new String[] {"job", "instance", "table_name"},
- new String[] {"aws_dynamodb", "", "myTable"}),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_dynamodb_consumed_read_capacity_units_index_sum",
+ new String[] {"job", "instance", "table_name", "global_secondary_index_name"},
+ new String[] {"aws_dynamodb", "", "myTable", "myIndex"}))
+ .isCloseTo(1.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_dynamodb_online_index_consumed_write_capacity_sum",
+ new String[] {"job", "instance", "table_name", "global_secondary_index_name"},
+ new String[] {"aws_dynamodb", "", "myTable", "myIndex"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_dynamodb_consumed_read_capacity_units_sum",
+ new String[] {"job", "instance", "table_name"},
+ new String[] {"aws_dynamodb", "", "myTable"}))
+ .isCloseTo(3.0, within(.01));
}
@Test
@@ -1032,13 +1017,12 @@ public void testDynamoNoDimensions() throws Exception {
.datapoints(Datapoint.builder().timestamp(new Date().toInstant()).sum(1.0).build())
.build());
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_dynamodb_account_provisioned_read_capacity_utilization_sum",
- new String[] {"job", "instance"},
- new String[] {"aws_dynamodb", ""}),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_dynamodb_account_provisioned_read_capacity_utilization_sum",
+ new String[] {"job", "instance"},
+ new String[] {"aws_dynamodb", ""}))
+ .isCloseTo(1.0, within(.01));
}
@Test
@@ -1113,27 +1097,26 @@ public void testTagSelectEC2() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(2.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_ec2_cpuutilization_average",
- new String[] {"job", "instance", "instance_id"},
- new String[] {"aws_ec2", "", "i-1"}),
- .01);
- assertNull(
- registry.getSampleValue(
- "aws_ec2_cpuutilization_average",
- new String[] {"job", "instance", "instance_id"},
- new String[] {"aws_ec2", "", "i-2"}));
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_resource_info",
- new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
- new String[] {
- "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-1", "i-1", "enabled"
- }),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_ec2_cpuutilization_average",
+ new String[] {"job", "instance", "instance_id"},
+ new String[] {"aws_ec2", "", "i-1"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_ec2_cpuutilization_average",
+ new String[] {"job", "instance", "instance_id"},
+ new String[] {"aws_ec2", "", "i-2"}))
+ .isNull();
+ assertThat(
+ registry.getSampleValue(
+ "aws_resource_info",
+ new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
+ new String[] {
+ "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-1", "i-1", "enabled"
+ }))
+ .isCloseTo(1.0, within(.01));
}
@Test
@@ -1197,26 +1180,24 @@ public void testTagSelectWebACL() {
Datapoint.builder().timestamp(new Date().toInstant()).sum(200.0).build())
.build());
- assertEquals(
- 200.0,
- registry.getSampleValue(
- "aws_wafv2_counted_requests_sum",
- new String[] {"job", "instance", "region", "rule", "web_acl"},
- new String[] {"aws_wafv2", "", "eu-west-1", "WebAclLog", "svc-integration-xxxx"}),
- .01);
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_resource_info",
- new String[] {"job", "instance", "arn", "web_acl", "tag_Monitoring"},
- new String[] {
- "aws_wafv2",
- "",
- "arn:aws:wafv2:eu-west-1:123456789:regional/webacl/svc-integration-xxxx/d177aaf1-b18f-4f84-aa8e-f1c5c40fc426",
- "svc-integration-xxxx",
- "enabled"
- }),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_wafv2_counted_requests_sum",
+ new String[] {"job", "instance", "region", "rule", "web_acl"},
+ new String[] {"aws_wafv2", "", "eu-west-1", "WebAclLog", "svc-integration-xxxx"}))
+ .isCloseTo(200.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_resource_info",
+ new String[] {"job", "instance", "arn", "web_acl", "tag_Monitoring"},
+ new String[] {
+ "aws_wafv2",
+ "",
+ "arn:aws:wafv2:eu-west-1:123456789:regional/webacl/svc-integration-xxxx/d177aaf1-b18f-4f84-aa8e-f1c5c40fc426",
+ "svc-integration-xxxx",
+ "enabled"
+ }))
+ .isCloseTo(1.0, within(.01));
}
@Test
@@ -1307,33 +1288,30 @@ public void testTagSelectTargetGroup() {
Datapoint.builder().timestamp(new Date().toInstant()).average(3.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_applicationelb_un_healthy_host_count_average",
- new String[] {"job", "instance", "target_group", "load_balancer"},
- new String[] {"aws_applicationelb", "", "targetgroup/abc-123", "app/myLB/123"}),
- .01);
- assertEquals(
- 3.0,
- registry.getSampleValue(
- "aws_applicationelb_un_healthy_host_count_average",
- new String[] {"job", "instance", "target_group", "load_balancer"},
- new String[] {"aws_applicationelb", "", "targetgroup/abc-234", "app/myLB/123"}),
- .01);
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_resource_info",
- new String[] {"job", "instance", "arn", "target_group", "tag_Monitoring"},
- new String[] {
- "aws_applicationelb",
- "",
- "arn:aws:elasticloadbalancing:us-east-1:121212121212:targetgroup/abc-123",
- "targetgroup/abc-123",
- "enabled"
- }),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_applicationelb_un_healthy_host_count_average",
+ new String[] {"job", "instance", "target_group", "load_balancer"},
+ new String[] {"aws_applicationelb", "", "targetgroup/abc-123", "app/myLB/123"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_applicationelb_un_healthy_host_count_average",
+ new String[] {"job", "instance", "target_group", "load_balancer"},
+ new String[] {"aws_applicationelb", "", "targetgroup/abc-234", "app/myLB/123"}))
+ .isCloseTo(3.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_resource_info",
+ new String[] {"job", "instance", "arn", "target_group", "tag_Monitoring"},
+ new String[] {
+ "aws_applicationelb",
+ "",
+ "arn:aws:elasticloadbalancing:us-east-1:121212121212:targetgroup/abc-123",
+ "targetgroup/abc-123",
+ "enabled"
+ }))
+ .isCloseTo(1.0, within(.01));
}
@Test
@@ -1438,38 +1416,36 @@ public void testTagSelectALB() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(4.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_applicationelb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer"},
- new String[] {"aws_applicationelb", "", "a", "app/myLB/123"}),
- .01);
- assertEquals(
- 3.0,
- registry.getSampleValue(
- "aws_applicationelb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer"},
- new String[] {"aws_applicationelb", "", "b", "app/myLB/123"}),
- .01);
- assertNull(
- registry.getSampleValue(
- "aws_applicationelb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer"},
- new String[] {"aws_applicationelb", "", "a", "app/myOtherLB/456"}));
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_resource_info",
- new String[] {"job", "instance", "arn", "load_balancer", "tag_Monitoring"},
- new String[] {
- "aws_applicationelb",
- "",
- "arn:aws:elasticloadbalancing:us-east-1:121212121212:loadbalancer/app/myLB/123",
- "app/myLB/123",
- "enabled"
- }),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_applicationelb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer"},
+ new String[] {"aws_applicationelb", "", "a", "app/myLB/123"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_applicationelb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer"},
+ new String[] {"aws_applicationelb", "", "b", "app/myLB/123"}))
+ .isCloseTo(3.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_applicationelb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer"},
+ new String[] {"aws_applicationelb", "", "a", "app/myOtherLB/456"}))
+ .isNull();
+ assertThat(
+ registry.getSampleValue(
+ "aws_resource_info",
+ new String[] {"job", "instance", "arn", "load_balancer", "tag_Monitoring"},
+ new String[] {
+ "aws_applicationelb",
+ "",
+ "arn:aws:elasticloadbalancing:us-east-1:121212121212:loadbalancer/app/myLB/123",
+ "app/myLB/123",
+ "enabled"
+ }))
+ .isCloseTo(1.0, within(.01));
}
@Test
@@ -1562,38 +1538,34 @@ public void testTagSelectUsesPaginationToken() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(3.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_ec2_cpuutilization_average",
- new String[] {"job", "instance", "instance_id"},
- new String[] {"aws_ec2", "", "i-1"}),
- .01);
- assertEquals(
- 3.0,
- registry.getSampleValue(
- "aws_ec2_cpuutilization_average",
- new String[] {"job", "instance", "instance_id"},
- new String[] {"aws_ec2", "", "i-2"}),
- .01);
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_resource_info",
- new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
- new String[] {
- "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-1", "i-1", "enabled"
- }),
- .01);
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_resource_info",
- new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
- new String[] {
- "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-2", "i-2", "enabled"
- }),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_ec2_cpuutilization_average",
+ new String[] {"job", "instance", "instance_id"},
+ new String[] {"aws_ec2", "", "i-1"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_ec2_cpuutilization_average",
+ new String[] {"job", "instance", "instance_id"},
+ new String[] {"aws_ec2", "", "i-2"}))
+ .isCloseTo(3.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_resource_info",
+ new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
+ new String[] {
+ "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-1", "i-1", "enabled"
+ }))
+ .isCloseTo(1.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_resource_info",
+ new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
+ new String[] {
+ "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-2", "i-2", "enabled"
+ }))
+ .isCloseTo(1.0, within(.01));
}
@Test
@@ -1652,20 +1624,18 @@ public void testNoSelection() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(3.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_ec2_cpuutilization_average",
- new String[] {"job", "instance", "instance_id"},
- new String[] {"aws_ec2", "", "i-1"}),
- .01);
- assertEquals(
- 3.0,
- registry.getSampleValue(
- "aws_ec2_cpuutilization_average",
- new String[] {"job", "instance", "instance_id"},
- new String[] {"aws_ec2", "", "i-2"}),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_ec2_cpuutilization_average",
+ new String[] {"job", "instance", "instance_id"},
+ new String[] {"aws_ec2", "", "i-1"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_ec2_cpuutilization_average",
+ new String[] {"job", "instance", "instance_id"},
+ new String[] {"aws_ec2", "", "i-2"}))
+ .isCloseTo(3.0, within(.01));
}
@Test
@@ -1744,36 +1714,34 @@ public void testMultipleSelection() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(3.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_ec2_cpuutilization_average",
- new String[] {"job", "instance", "instance_id"},
- new String[] {"aws_ec2", "", "i-1"}),
- .01);
- assertNull(
- registry.getSampleValue(
- "aws_ec2_cpuutilization_average",
- new String[] {"job", "instance", "instance_id"},
- new String[] {"aws_ec2", "", "i-2"}));
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_resource_info",
- new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
- new String[] {
- "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-1", "i-1", "enabled"
- }),
- .01);
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_resource_info",
- new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
- new String[] {
- "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-2", "i-2", "enabled"
- }),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_ec2_cpuutilization_average",
+ new String[] {"job", "instance", "instance_id"},
+ new String[] {"aws_ec2", "", "i-1"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_ec2_cpuutilization_average",
+ new String[] {"job", "instance", "instance_id"},
+ new String[] {"aws_ec2", "", "i-2"}))
+ .isNull();
+ assertThat(
+ registry.getSampleValue(
+ "aws_resource_info",
+ new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
+ new String[] {
+ "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-1", "i-1", "enabled"
+ }))
+ .isCloseTo(1.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_resource_info",
+ new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
+ new String[] {
+ "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-2", "i-2", "enabled"
+ }))
+ .isCloseTo(1.0, within(.01));
}
@Test
@@ -1868,54 +1836,52 @@ public void testOptionalTagSelection() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(4.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_ec2_cpuutilization_average",
- new String[] {"job", "instance", "instance_id"},
- new String[] {"aws_ec2", "", "i-1"}),
- .01);
- assertNull(
- registry.getSampleValue(
- "aws_ec2_cpuutilization_average",
- new String[] {"job", "instance", "instance_id"},
- new String[] {"aws_ec2", "", "i-2"}));
- assertEquals(
- 4.0,
- registry.getSampleValue(
- "aws_ec2_cpuutilization_average",
- new String[] {"job", "instance", "instance_id"},
- new String[] {"aws_ec2", "", "i-no-tag"}),
- .01);
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_resource_info",
- new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
- new String[] {
- "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-1", "i-1", "enabled"
- }),
- .01);
- assertEquals(
- 1.0,
- registry.getSampleValue(
- "aws_resource_info",
- new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
- new String[] {
- "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-2", "i-2", "enabled"
- }),
- .01);
- assertNull(
- registry.getSampleValue(
- "aws_resource_info",
- new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
- new String[] {
- "aws_ec2",
- "",
- "arn:aws:ec2:us-east-1:121212121212:instance/i-no-tag",
- "i-no-tag",
- "enabled"
- }));
+ assertThat(
+ registry.getSampleValue(
+ "aws_ec2_cpuutilization_average",
+ new String[] {"job", "instance", "instance_id"},
+ new String[] {"aws_ec2", "", "i-1"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_ec2_cpuutilization_average",
+ new String[] {"job", "instance", "instance_id"},
+ new String[] {"aws_ec2", "", "i-2"}))
+ .isNull();
+ assertThat(
+ registry.getSampleValue(
+ "aws_ec2_cpuutilization_average",
+ new String[] {"job", "instance", "instance_id"},
+ new String[] {"aws_ec2", "", "i-no-tag"}))
+ .isCloseTo(4.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_resource_info",
+ new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
+ new String[] {
+ "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-1", "i-1", "enabled"
+ }))
+ .isCloseTo(1.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_resource_info",
+ new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
+ new String[] {
+ "aws_ec2", "", "arn:aws:ec2:us-east-1:121212121212:instance/i-2", "i-2", "enabled"
+ }))
+ .isCloseTo(1.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_resource_info",
+ new String[] {"job", "instance", "arn", "instance_id", "tag_Monitoring"},
+ new String[] {
+ "aws_ec2",
+ "",
+ "arn:aws:ec2:us-east-1:121212121212:instance/i-no-tag",
+ "i-no-tag",
+ "enabled"
+ }))
+ .isNull();
}
@Test
@@ -2012,16 +1978,15 @@ public void testBuildInfo() throws Exception {
String buildVersion = properties.getProperty("BuildVersion");
String releaseDate = properties.getProperty("ReleaseDate");
- assertEquals(
- 1L,
- registry.getSampleValue(
- "cloudwatch_exporter_build_info",
- new String[] {"build_version", "release_date"},
- new String[] {
- buildVersion != null ? buildVersion : "unknown",
- releaseDate != null ? releaseDate : "unknown"
- }),
- .00001);
+ assertThat(
+ registry.getSampleValue(
+ "cloudwatch_exporter_build_info",
+ new String[] {"build_version", "release_date"},
+ new String[] {
+ buildVersion != null ? buildVersion : "unknown",
+ releaseDate != null ? releaseDate : "unknown"
+ }))
+ .isCloseTo(1L, within(.00001));
}
@Test
@@ -2065,20 +2030,18 @@ public void testDimensionsWithDefaultCache() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(2.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myLB"}),
- .01);
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myLB"}),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myLB"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myLB"}))
+ .isCloseTo(2.0, within(.01));
Mockito.verify(cloudWatchClient).listMetrics(any(ListMetricsRequest.class));
Mockito.verify(cloudWatchClient, times(2))
@@ -2126,23 +2089,306 @@ public void testDimensionsWithMetricLevelCache() throws Exception {
Datapoint.builder().timestamp(new Date().toInstant()).average(2.0).build())
.build());
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myLB"}),
- .01);
- assertEquals(
- 2.0,
- registry.getSampleValue(
- "aws_elb_request_count_average",
- new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
- new String[] {"aws_elb", "", "a", "myLB"}),
- .01);
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myLB"}))
+ .isCloseTo(2.0, within(.01));
+ assertThat(
+ registry.getSampleValue(
+ "aws_elb_request_count_average",
+ new String[] {"job", "instance", "availability_zone", "load_balancer_name"},
+ new String[] {"aws_elb", "", "a", "myLB"}))
+ .isCloseTo(2.0, within(.01));
Mockito.verify(cloudWatchClient).listMetrics(any(ListMetricsRequest.class));
Mockito.verify(cloudWatchClient, times(2))
.getMetricStatistics(any(GetMetricStatisticsRequest.class));
}
+
+ @Test
+ public void loadConfigFromReaderRejectsEmptyYaml() {
+ CloudWatchCollector collector =
+ new CloudWatchCollector(
+ "---\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n",
+ cloudWatchClient,
+ taggingClient);
+
+ assertThatThrownBy(
+ () -> collector.loadConfig(new StringReader(""), cloudWatchClient, taggingClient))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Must provide metrics");
+ }
+
+ @Test
+ public void rejectsConfigWithoutMetrics() {
+ assertThatThrownBy(
+ () -> new CloudWatchCollector("---\nregion: reg\n", cloudWatchClient, taggingClient))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Must provide metrics");
+ }
+
+ @Test
+ public void rejectsMetricWithoutRequiredCloudWatchNames() {
+ assertThatThrownBy(
+ () ->
+ new CloudWatchCollector(
+ "---\nmetrics:\n- aws_namespace: AWS/ELB\n", cloudWatchClient, taggingClient))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Must provide aws_namespace and aws_metric_name");
+ }
+
+ @Test
+ public void parsesGlobalDefaultsAndMetricHelp() {
+ CloudWatchCollector collector =
+ new CloudWatchCollector(
+ "---\n"
+ + "period_seconds: 15\n"
+ + "range_seconds: 30\n"
+ + "delay_seconds: 45\n"
+ + "set_timestamp: false\n"
+ + "use_get_metric_data: true\n"
+ + "warn_on_empty_list_dimensions: true\n"
+ + "list_metrics_cache_ttl: 120\n"
+ + "metrics:\n"
+ + "- aws_namespace: AWS/ELB\n"
+ + " aws_metric_name: RequestCount\n"
+ + " help: Custom help\n"
+ + " aws_dimensions: [LoadBalancerName]\n"
+ + " aws_extended_statistics: [p99]\n",
+ cloudWatchClient,
+ taggingClient);
+
+ MetricRule rule = collector.activeConfig.rules.get(0);
+
+ assertThat(rule.periodSeconds).isEqualTo(15);
+ assertThat(rule.rangeSeconds).isEqualTo(30);
+ assertThat(rule.delaySeconds).isEqualTo(45);
+ assertThat(rule.cloudwatchTimestamp).isFalse();
+ assertThat(rule.useGetMetricData).isTrue();
+ assertThat(rule.warnOnEmptyListDimensions).isTrue();
+ assertThat(rule.listMetricsCacheTtl).isEqualTo(Duration.ofSeconds(120));
+ assertThat(rule.help).isEqualTo("Custom help");
+ assertThat(rule.awsDimensions).containsExactly("LoadBalancerName");
+ assertThat(rule.awsExtendedStatistics).containsExactly("p99");
+ assertThat(rule.awsStatistics).isNull();
+ }
+
+ @Test
+ public void stringConstructorParsesConfigWithProvidedClients() {
+ CloudWatchCollector collector =
+ new CloudWatchCollector(
+ "---\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n",
+ cloudWatchClient,
+ taggingClient);
+
+ assertThat(collector.activeConfig.rules).hasSize(1);
+ }
+
+ @Test
+ public void readerConstructorParsesConfig() {
+ CloudWatchCollector collector =
+ new CloudWatchCollector(
+ new StringReader(
+ "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n"));
+
+ assertThat(collector.activeConfig.rules).hasSize(1);
+ assertThat(collector.activeConfig.cloudWatchClient).isNotNull();
+ assertThat(collector.activeConfig.taggingClient).isNotNull();
+ }
+
+ @Test
+ public void rejectsCombinedDimensionSelectAndRegex() {
+ assertThatThrownBy(
+ () ->
+ new CloudWatchCollector(
+ "---\n"
+ + "metrics:\n"
+ + "- aws_namespace: AWS/ELB\n"
+ + " aws_metric_name: RequestCount\n"
+ + " aws_dimension_select:\n"
+ + " LoadBalancerName: [lb]\n"
+ + " aws_dimension_select_regex:\n"
+ + " LoadBalancerName: [lb.*]\n",
+ cloudWatchClient,
+ taggingClient))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage(
+ "Must not provide aws_dimension_select and aws_dimension_select_regex at the same time");
+ }
+
+ @Test
+ public void rejectsIncompleteTagSelect() {
+ assertThatThrownBy(
+ () ->
+ new CloudWatchCollector(
+ "---\n"
+ + "metrics:\n"
+ + "- aws_namespace: AWS/EC2\n"
+ + " aws_metric_name: CPUUtilization\n"
+ + " aws_tag_select:\n"
+ + " resource_type_selection: ec2:instance\n",
+ cloudWatchClient,
+ taggingClient))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Must provide resource_type_selection and resource_id_dimension");
+ }
+
+ @Test
+ public void collectReportsScrapeErrorWhenCloudWatchRequestFails() {
+ CloudWatchCollector collector =
+ new CloudWatchCollector(
+ "---\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n",
+ cloudWatchClient,
+ taggingClient);
+ Mockito.when(cloudWatchClient.getMetricStatistics(any(GetMetricStatisticsRequest.class)))
+ .thenThrow(new RuntimeException("boom"));
+
+ List samples = collector.collect();
+
+ assertThat(errorSample(samples)).isEqualTo(1.0);
+ }
+
+ @Test
+ public void stringConstructorBuildsClientsWithoutAwsCalls() {
+ CloudWatchCollector collector =
+ new CloudWatchCollector(
+ "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n");
+
+ assertThat(collector.activeConfig.rules).hasSize(1);
+ assertThat(collector.activeConfig.cloudWatchClient).isNotNull();
+ assertThat(collector.activeConfig.taggingClient).isNotNull();
+ }
+
+ @Test
+ public void parsesExplicitStatisticsAndMetricWarnOverride() {
+ CloudWatchCollector collector =
+ new CloudWatchCollector(
+ "---\n"
+ + "warn_on_empty_list_dimensions: false\n"
+ + "metrics:\n"
+ + "- aws_namespace: AWS/ELB\n"
+ + " aws_metric_name: RequestCount\n"
+ + " aws_statistics: [Sum, Average]\n"
+ + " warn_on_empty_list_dimensions: true\n",
+ cloudWatchClient,
+ taggingClient);
+
+ MetricRule rule = collector.activeConfig.rules.get(0);
+
+ assertThat(rule.awsStatistics).containsExactly(Statistic.SUM, Statistic.AVERAGE);
+ assertThat(rule.warnOnEmptyListDimensions).isTrue();
+ }
+
+ @Test
+ public void reloadConfigUsesExistingClientsAndUpdatedFile() throws Exception {
+ Path config =
+ Files.writeString(
+ Files.createTempFile("cloudwatch-exporter-reload", ".yml"),
+ "---\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n");
+ String previousConfigFilePath = WebServer.configFilePath;
+ WebServer.configFilePath = config.toString();
+ try {
+ CloudWatchCollector collector =
+ new CloudWatchCollector(
+ "---\nmetrics:\n- aws_namespace: AWS/EC2\n aws_metric_name: CPUUtilization\n",
+ cloudWatchClient,
+ taggingClient);
+
+ collector.reloadConfig();
+
+ assertThat(collector.activeConfig.rules).hasSize(1);
+ assertThat(collector.activeConfig.rules.get(0).awsNamespace).isEqualTo("AWS/ELB");
+ assertThat(collector.activeConfig.cloudWatchClient).isSameAs(cloudWatchClient);
+ assertThat(collector.activeConfig.taggingClient).isSameAs(taggingClient);
+ } finally {
+ WebServer.configFilePath = previousConfigFilePath;
+ Files.deleteIfExists(config);
+ }
+ }
+
+ @Test
+ public void customHelpIsUsedForMetricFamily() {
+ CloudWatchCollector collector =
+ new CloudWatchCollector(
+ "---\n"
+ + "metrics:\n"
+ + "- aws_namespace: AWS/ELB\n"
+ + " aws_metric_name: RequestCount\n"
+ + " help: Custom metric help\n",
+ cloudWatchClient,
+ taggingClient);
+ Mockito.when(cloudWatchClient.getMetricStatistics(any(GetMetricStatisticsRequest.class)))
+ .thenReturn(
+ GetMetricStatisticsResponse.builder()
+ .datapoints(
+ Datapoint.builder().timestamp(new Date().toInstant()).average(2.0).build())
+ .build());
+
+ Collector.MetricFamilySamples average =
+ metricFamily(collector.collect(), "aws_elb_request_count_average");
+
+ assertThat(average.help).isEqualTo("Custom metric help");
+ }
+
+ @Test
+ public void resourceInfoIsEmittedOnceForDuplicateTagMappings() {
+ CloudWatchCollector collector =
+ new CloudWatchCollector(
+ "---\n"
+ + "region: reg\n"
+ + "metrics:\n"
+ + "- aws_namespace: AWS/EC2\n"
+ + " aws_metric_name: CPUUtilization\n"
+ + " aws_dimensions: [InstanceId]\n"
+ + " aws_tag_select:\n"
+ + " resource_type_selection: ec2:instance\n"
+ + " resource_id_dimension: InstanceId\n",
+ cloudWatchClient,
+ taggingClient);
+ ResourceTagMapping mapping =
+ ResourceTagMapping.builder()
+ .resourceARN("arn:aws:ec2:reg:123456789012:instance/i-1")
+ .tags(Tag.builder().key("Name").value("example").build())
+ .build();
+ Mockito.when(taggingClient.getResources(any(GetResourcesRequest.class)))
+ .thenReturn(
+ GetResourcesResponse.builder().resourceTagMappingList(mapping, mapping).build());
+ Mockito.when(cloudWatchClient.listMetrics(any(ListMetricsRequest.class)))
+ .thenReturn(
+ ListMetricsResponse.builder()
+ .metrics(
+ Metric.builder()
+ .dimensions(Dimension.builder().name("InstanceId").value("i-1").build())
+ .build())
+ .build());
+ Mockito.when(cloudWatchClient.getMetricStatistics(any(GetMetricStatisticsRequest.class)))
+ .thenReturn(
+ GetMetricStatisticsResponse.builder()
+ .datapoints(
+ Datapoint.builder().timestamp(new Date().toInstant()).average(2.0).build())
+ .build());
+
+ Collector.MetricFamilySamples info = metricFamily(collector.collect(), "aws_resource_info");
+
+ assertThat(info.samples).hasSize(1);
+ assertThat(info.samples.get(0).labelNames).contains("instance_id", "tag_Name");
+ assertThat(info.samples.get(0).labelValues).contains("i-1", "example");
+ }
+
+ private Collector.MetricFamilySamples metricFamily(
+ List samples, String name) {
+ return samples.stream().filter(sample -> sample.name.equals(name)).findFirst().orElseThrow();
+ }
+
+ private double errorSample(List samples) {
+ return samples.stream()
+ .filter(sample -> sample.name.equals("cloudwatch_exporter_scrape_error"))
+ .findFirst()
+ .orElseThrow()
+ .samples
+ .get(0)
+ .value;
+ }
}
diff --git a/src/test/java/io/prometheus/cloudwatch/DefaultDimensionSourceTest.java b/src/test/java/io/prometheus/cloudwatch/DefaultDimensionSourceTest.java
new file mode 100644
index 00000000..d047a674
--- /dev/null
+++ b/src/test/java/io/prometheus/cloudwatch/DefaultDimensionSourceTest.java
@@ -0,0 +1,74 @@
+package io.prometheus.cloudwatch;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.prometheus.client.Counter;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
+import software.amazon.awssdk.services.cloudwatch.model.Dimension;
+import software.amazon.awssdk.services.cloudwatch.model.ListMetricsRequest;
+import software.amazon.awssdk.services.cloudwatch.model.ListMetricsResponse;
+
+class DefaultDimensionSourceTest {
+
+ @Test
+ void usesSelectedDimensionValuesWithoutCallingCloudWatchWhenAllDimensionsAreKnown() {
+ CloudWatchClient client = mock(CloudWatchClient.class);
+ MetricRule rule = metricRule();
+ rule.awsDimensions = List.of("LoadBalancerName", "AvailabilityZone");
+ rule.awsDimensionSelect =
+ Map.of("LoadBalancerName", List.of("lb-a", "lb-b"), "AvailabilityZone", List.of("us-a"));
+
+ DimensionSource.DimensionData data = source(client).getDimensions(rule, List.of());
+
+ assertThat(data.getDimensions())
+ .containsExactlyInAnyOrder(
+ List.of(dimension("LoadBalancerName", "lb-a"), dimension("AvailabilityZone", "us-a")),
+ List.of(dimension("LoadBalancerName", "lb-b"), dimension("AvailabilityZone", "us-a")));
+ verify(client, never()).listMetrics(any(ListMetricsRequest.class));
+ }
+
+ @Test
+ void returnsEmptyDimensionsWhenListMetricsReturnsNoMatchesAndWarningEnabled() {
+ CloudWatchClient client = mock(CloudWatchClient.class);
+ when(client.listMetrics(any(ListMetricsRequest.class)))
+ .thenReturn(ListMetricsResponse.builder().metrics(List.of()).build());
+ MetricRule rule = metricRule();
+ rule.awsDimensions = List.of("LoadBalancerName");
+ rule.warnOnEmptyListDimensions = true;
+
+ DimensionSource.DimensionData data = source(client).getDimensions(rule, List.of());
+
+ assertThat(data.getDimensions()).isEmpty();
+ verify(client).listMetrics(any(ListMetricsRequest.class));
+ }
+
+ private DefaultDimensionSource source(CloudWatchClient client) {
+ return new DefaultDimensionSource(
+ client,
+ Counter.build()
+ .name("default_dimension_source_test_cloudwatch_requests")
+ .help("requests")
+ .labelNames("action", "namespace")
+ .create());
+ }
+
+ private MetricRule metricRule() {
+ MetricRule rule = new MetricRule();
+ rule.awsNamespace = "AWS/ELB";
+ rule.awsMetricName = "RequestCount";
+ rule.rangeSeconds = 60;
+ return rule;
+ }
+
+ private Dimension dimension(String name, String value) {
+ return Dimension.builder().name(name).value(value).build();
+ }
+}
diff --git a/src/test/java/io/prometheus/cloudwatch/DisallowHttpMethodsTest.java b/src/test/java/io/prometheus/cloudwatch/DisallowHttpMethodsTest.java
new file mode 100644
index 00000000..fb7c0ab5
--- /dev/null
+++ b/src/test/java/io/prometheus/cloudwatch/DisallowHttpMethodsTest.java
@@ -0,0 +1,51 @@
+package io.prometheus.cloudwatch;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.EnumSet;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.Callback;
+import org.junit.jupiter.api.Test;
+
+class DisallowHttpMethodsTest {
+
+ private final Request request = mock(Request.class);
+ private final Response response = mock(Response.class);
+ private final Callback callback = mock(Callback.class);
+
+ @Test
+ void returnsMethodNotAllowedForDisallowedMethod() throws Exception {
+ when(request.getMethod()).thenReturn("TRACE");
+ DisallowHttpMethods handler = new DisallowHttpMethods(EnumSet.of(HttpMethod.TRACE));
+
+ boolean handled = handler.handle(request, response, callback);
+
+ assertThat(handled).isTrue();
+ verify(response).setStatus(HttpStatus.METHOD_NOT_ALLOWED_405);
+ verify(callback).succeeded();
+ }
+
+ @Test
+ void delegatesAllowedMethodToWrappedHandler() throws Exception {
+ when(request.getMethod()).thenReturn("GET");
+ Handler wrappedHandler = mock(Handler.class);
+ when(wrappedHandler.handle(request, response, callback)).thenReturn(true);
+ DisallowHttpMethods handler = new DisallowHttpMethods(EnumSet.of(HttpMethod.TRACE));
+ handler.setHandler(wrappedHandler);
+
+ boolean handled = handler.handle(request, response, callback);
+
+ assertThat(handled).isTrue();
+ verify(wrappedHandler).handle(request, response, callback);
+ verify(response, never()).setStatus(HttpStatus.METHOD_NOT_ALLOWED_405);
+ verify(callback, never()).succeeded();
+ }
+}
diff --git a/src/test/java/io/prometheus/cloudwatch/GetMetricDataGetterTest.java b/src/test/java/io/prometheus/cloudwatch/GetMetricDataGetterTest.java
index 2a94821a..dd73d9ae 100644
--- a/src/test/java/io/prometheus/cloudwatch/GetMetricDataGetterTest.java
+++ b/src/test/java/io/prometheus/cloudwatch/GetMetricDataGetterTest.java
@@ -1,10 +1,22 @@
package io.prometheus.cloudwatch;
-import static org.junit.Assert.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import io.prometheus.client.Counter;
+import java.time.Instant;
import java.util.Collections;
import java.util.List;
-import org.junit.Test;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
+import software.amazon.awssdk.services.cloudwatch.model.Dimension;
+import software.amazon.awssdk.services.cloudwatch.model.GetMetricDataRequest;
+import software.amazon.awssdk.services.cloudwatch.model.GetMetricDataResponse;
+import software.amazon.awssdk.services.cloudwatch.model.MetricDataResult;
public class GetMetricDataGetterTest {
@Test
@@ -12,8 +24,93 @@ public void testPartition() {
List originalList = List.copyOf(Collections.nCopies(28, 0));
List> partitions = GetMetricDataDataGetter.partitionByMaxSize(originalList, 40);
for (List p : partitions) {
- assertTrue("partition must be smaller than 40", p.size() <= 40);
- assertTrue("partition should not be empty", !p.isEmpty());
+ assertThat(p.size()).isLessThanOrEqualTo(40);
+ assertThat(p.size()).isPositive();
}
}
+
+ @Test
+ public void partitionReturnsEmptyListForEmptyInput() {
+ assertThat(GetMetricDataDataGetter.partitionByMaxSize(List.of(), 40)).isEmpty();
+ }
+
+ @Test
+ public void partitionSplitsInputIntoMaxSizedPartitions() {
+ List originalList = List.copyOf(Collections.nCopies(85, 0));
+
+ List> partitions = GetMetricDataDataGetter.partitionByMaxSize(originalList, 40);
+
+ assertThat(partitions).hasSize(3);
+ assertThat(partitions).extracting(List::size).containsExactly(40, 40, 5);
+ }
+
+ @Test
+ public void metricLabelsEncodeStatAndSortedDimensions() {
+ String label =
+ GetMetricDataDataGetter.MetricLabels.labelFor(
+ "Average",
+ List.of(
+ Dimension.builder().name("InstanceId").value("i-123").build(),
+ Dimension.builder().name("AutoScalingGroupName").value("asg").build()));
+
+ assertThat(label).isEqualTo("Average/AutoScalingGroupName=asg,InstanceId=i-123");
+ }
+
+ @Test
+ public void metricLabelsRejectLabelsWithoutStatSeparator() {
+ assertThatThrownBy(() -> GetMetricDataDataGetter.MetricLabels.decode("Average"))
+ .isInstanceOf(GetMetricDataDataGetter.MetricLabels.UnexpectedLabel.class)
+ .hasMessage("Cannot decode label Average");
+ }
+
+ @Test
+ public void metricRuleDataForMapsExtendedStatisticsAndSkipsEmptyResults() {
+ CloudWatchClient client = mock(CloudWatchClient.class);
+ when(client.getMetricData(any(GetMetricDataRequest.class)))
+ .thenReturn(
+ GetMetricDataResponse.builder()
+ .metricDataResults(
+ MetricDataResult.builder()
+ .label("p99/InstanceId=i-123")
+ .timestamps(List.of(Instant.parse("2024-01-01T00:00:00Z")))
+ .values(List.of(99.0))
+ .build(),
+ MetricDataResult.builder()
+ .label("p95/InstanceId=i-123")
+ .timestamps(List.of())
+ .values(List.of(95.0))
+ .build(),
+ MetricDataResult.builder()
+ .label("p90/InstanceId=i-123")
+ .timestamps(List.of(Instant.parse("2024-01-01T00:00:00Z")))
+ .values(List.of())
+ .build())
+ .build());
+ MetricRule rule = new MetricRule();
+ rule.awsNamespace = "AWS/EC2";
+ rule.awsMetricName = "CPUUtilization";
+ rule.awsExtendedStatistics = List.of("p99");
+ rule.periodSeconds = 60;
+ rule.rangeSeconds = 120;
+ rule.delaySeconds = 30;
+ Dimension dimension = Dimension.builder().name("InstanceId").value("i-123").build();
+
+ DataGetter.MetricRuleData data =
+ new GetMetricDataDataGetter(
+ client,
+ 1_704_067_200_000L,
+ rule,
+ counter("get_metric_data_api_requests"),
+ counter("get_metric_data_metrics_requested"),
+ List.of(List.of(dimension)))
+ .metricRuleDataFor(List.of(dimension));
+
+ assertThat(data.timestamp).isEqualTo(Instant.parse("2024-01-01T00:00:00Z"));
+ assertThat(data.extendedValues).containsOnly(Map.entry("p99", 99.0));
+ assertThat(data.statisticValues).isEmpty();
+ }
+
+ private Counter counter(String name) {
+ return Counter.build().name(name).help(name).labelNames("a", "b").create();
+ }
}
diff --git a/src/test/java/io/prometheus/cloudwatch/GetMetricStatisticsDataGetterTest.java b/src/test/java/io/prometheus/cloudwatch/GetMetricStatisticsDataGetterTest.java
new file mode 100644
index 00000000..585ba643
--- /dev/null
+++ b/src/test/java/io/prometheus/cloudwatch/GetMetricStatisticsDataGetterTest.java
@@ -0,0 +1,113 @@
+package io.prometheus.cloudwatch;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import io.prometheus.client.Counter;
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
+import software.amazon.awssdk.services.cloudwatch.model.Datapoint;
+import software.amazon.awssdk.services.cloudwatch.model.Dimension;
+import software.amazon.awssdk.services.cloudwatch.model.GetMetricStatisticsRequest;
+import software.amazon.awssdk.services.cloudwatch.model.GetMetricStatisticsResponse;
+import software.amazon.awssdk.services.cloudwatch.model.StandardUnit;
+import software.amazon.awssdk.services.cloudwatch.model.Statistic;
+
+class GetMetricStatisticsDataGetterTest {
+
+ @Test
+ void returnsNewestDatapointWithStatisticsAndExtendedStatistics() {
+ CloudWatchClient client = mock(CloudWatchClient.class);
+ when(client.getMetricStatistics(any(GetMetricStatisticsRequest.class)))
+ .thenReturn(
+ GetMetricStatisticsResponse.builder()
+ .datapoints(
+ Datapoint.builder()
+ .timestamp(Instant.parse("2024-01-02T00:00:00Z"))
+ .unit(StandardUnit.COUNT)
+ .sum(1.0)
+ .sampleCount(2.0)
+ .minimum(3.0)
+ .maximum(4.0)
+ .average(5.0)
+ .extendedStatistics(Map.of("p99", 6.0))
+ .build(),
+ Datapoint.builder()
+ .timestamp(Instant.parse("2024-01-01T00:00:00Z"))
+ .unit(StandardUnit.COUNT)
+ .sum(100.0)
+ .build())
+ .build());
+
+ DataGetter.MetricRuleData data = getter(client).metricRuleDataFor(List.of(dimension()));
+
+ assertThat(data.timestamp).isEqualTo(Instant.parse("2024-01-02T00:00:00Z"));
+ assertThat(data.unit).isEqualTo("Count");
+ assertThat(data.statisticValues)
+ .containsEntry(Statistic.SUM, 1.0)
+ .containsEntry(Statistic.SAMPLE_COUNT, 2.0)
+ .containsEntry(Statistic.MINIMUM, 3.0)
+ .containsEntry(Statistic.MAXIMUM, 4.0)
+ .containsEntry(Statistic.AVERAGE, 5.0);
+ assertThat(data.extendedValues).containsEntry("p99", 6.0);
+ }
+
+ @Test
+ void returnsNullWhenCloudWatchReturnsNoDatapoints() {
+ CloudWatchClient client = mock(CloudWatchClient.class);
+ when(client.getMetricStatistics(any(GetMetricStatisticsRequest.class)))
+ .thenReturn(GetMetricStatisticsResponse.builder().datapoints(List.of()).build());
+
+ assertThat(getter(client).metricRuleDataFor(List.of(dimension()))).isNull();
+ }
+
+ @Test
+ void handlesDatapointWithoutExtendedStatistics() {
+ CloudWatchClient client = mock(CloudWatchClient.class);
+ when(client.getMetricStatistics(any(GetMetricStatisticsRequest.class)))
+ .thenReturn(
+ GetMetricStatisticsResponse.builder()
+ .datapoints(
+ Datapoint.builder()
+ .timestamp(Instant.parse("2024-01-01T00:00:00Z"))
+ .unit(StandardUnit.COUNT)
+ .average(7.0)
+ .build())
+ .build());
+
+ DataGetter.MetricRuleData data = getter(client).metricRuleDataFor(List.of(dimension()));
+
+ assertThat(data.statisticValues).containsEntry(Statistic.AVERAGE, 7.0);
+ assertThat(data.extendedValues).isEmpty();
+ }
+
+ private GetMetricStatisticsDataGetter getter(CloudWatchClient client) {
+ MetricRule rule = new MetricRule();
+ rule.awsNamespace = "AWS/EC2";
+ rule.awsMetricName = "CPUUtilization";
+ rule.awsStatistics = List.of(Statistic.SUM, Statistic.AVERAGE);
+ rule.awsExtendedStatistics = List.of("p99");
+ rule.periodSeconds = 60;
+ rule.rangeSeconds = 120;
+ rule.delaySeconds = 30;
+ return new GetMetricStatisticsDataGetter(
+ client, 1_704_156_600_000L, rule, counter("api_requests"), counter("metrics_requested"));
+ }
+
+ private Dimension dimension() {
+ return Dimension.builder().name("InstanceId").value("i-123").build();
+ }
+
+ private Counter counter(String name) {
+ return Counter.build()
+ .name("get_metric_statistics_test_" + name)
+ .help(name)
+ .labelNames("a", "b")
+ .create();
+ }
+}
diff --git a/src/test/java/io/prometheus/cloudwatch/MetricRuleTest.java b/src/test/java/io/prometheus/cloudwatch/MetricRuleTest.java
new file mode 100644
index 00000000..e797dcda
--- /dev/null
+++ b/src/test/java/io/prometheus/cloudwatch/MetricRuleTest.java
@@ -0,0 +1,119 @@
+package io.prometheus.cloudwatch;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.services.cloudwatch.model.Statistic;
+
+public class MetricRuleTest {
+
+ @Test
+ public void equalRulesHaveSameHashCode() {
+ MetricRule left = populatedRule();
+ MetricRule right = populatedRule();
+ right.awsTagSelect = left.awsTagSelect;
+
+ assertThat(left).isEqualTo(right);
+ assertThat(left.hashCode()).isEqualTo(right.hashCode());
+ }
+
+ @Test
+ public void equalsHandlesIdentityNullAndDifferentTypes() {
+ MetricRule rule = populatedRule();
+
+ assertThat(rule).isEqualTo(rule);
+ assertThat(rule).isNotEqualTo(null);
+ assertThat(rule).isNotEqualTo("not a metric rule");
+ }
+
+ @Test
+ public void equalsDetectsDifferentFields() {
+ assertThat(populatedRule()).isNotEqualTo(changedRule(rule -> rule.periodSeconds = 61));
+ assertThat(populatedRule()).isNotEqualTo(changedRule(rule -> rule.rangeSeconds = 121));
+ assertThat(populatedRule()).isNotEqualTo(changedRule(rule -> rule.delaySeconds = 31));
+ assertThat(populatedRule()).isNotEqualTo(changedRule(rule -> rule.cloudwatchTimestamp = false));
+ assertThat(populatedRule()).isNotEqualTo(changedRule(rule -> rule.useGetMetricData = false));
+ assertThat(populatedRule()).isNotEqualTo(changedRule(rule -> rule.awsNamespace = "AWS/S3"));
+ assertThat(populatedRule()).isNotEqualTo(changedRule(rule -> rule.awsMetricName = "Latency"));
+ assertThat(populatedRule())
+ .isNotEqualTo(changedRule(rule -> rule.awsStatistics = List.of(Statistic.SUM)));
+ assertThat(populatedRule())
+ .isNotEqualTo(changedRule(rule -> rule.awsExtendedStatistics = List.of("p99")));
+ assertThat(populatedRule())
+ .isNotEqualTo(changedRule(rule -> rule.awsDimensions = List.of("BucketName")));
+ assertThat(populatedRule())
+ .isNotEqualTo(
+ changedRule(
+ rule -> rule.awsDimensionSelect = Map.of("LoadBalancerName", List.of("b"))));
+ assertThat(populatedRule())
+ .isNotEqualTo(
+ changedRule(
+ rule -> rule.awsDimensionSelectRegex = Map.of("LoadBalancerName", List.of("b.*"))));
+ assertThat(populatedRule())
+ .isNotEqualTo(
+ changedRule(rule -> rule.awsTagSelect = new CloudWatchCollector.AWSTagSelect()));
+ assertThat(populatedRule()).isNotEqualTo(changedRule(rule -> rule.help = "other help"));
+ assertThat(populatedRule())
+ .isNotEqualTo(changedRule(rule -> rule.listMetricsCacheTtl = Duration.ofMinutes(2)));
+ }
+
+ @Test
+ public void equalsDetectsDifferentFieldsAfterEqualTagSelect() {
+ MetricRule left = populatedRule();
+ MetricRule right = populatedRule();
+ right.awsTagSelect = left.awsTagSelect;
+ right.help = "other help";
+
+ assertThat(left).isNotEqualTo(right);
+ }
+
+ @Test
+ public void warnOnEmptyListDimensionsDoesNotAffectEquality() {
+ MetricRule left = populatedRule();
+ MetricRule right = populatedRule();
+ right.awsTagSelect = left.awsTagSelect;
+ right.warnOnEmptyListDimensions = !left.warnOnEmptyListDimensions;
+
+ assertThat(left).isEqualTo(right);
+ assertThat(left.hashCode()).isEqualTo(right.hashCode());
+ }
+
+ @Test
+ public void hashCodeHandlesNullFields() {
+ assertThat(new MetricRule().hashCode()).isZero();
+ }
+
+ private MetricRule changedRule(RuleChange change) {
+ MetricRule rule = populatedRule();
+ change.apply(rule);
+ return rule;
+ }
+
+ private MetricRule populatedRule() {
+ MetricRule rule = new MetricRule();
+ rule.awsNamespace = "AWS/ELB";
+ rule.awsMetricName = "RequestCount";
+ rule.periodSeconds = 60;
+ rule.rangeSeconds = 120;
+ rule.delaySeconds = 30;
+ rule.awsStatistics = List.of(Statistic.AVERAGE, Statistic.MAXIMUM);
+ rule.awsExtendedStatistics = List.of("p95");
+ rule.awsDimensions = List.of("LoadBalancerName");
+ rule.awsDimensionSelect = Map.of("LoadBalancerName", List.of("a"));
+ rule.awsDimensionSelectRegex = Map.of("LoadBalancerName", List.of("a.*"));
+ rule.awsTagSelect = new CloudWatchCollector.AWSTagSelect();
+ rule.help = "help text";
+ rule.cloudwatchTimestamp = true;
+ rule.useGetMetricData = true;
+ rule.listMetricsCacheTtl = Duration.ofMinutes(1);
+ rule.warnOnEmptyListDimensions = true;
+ return rule;
+ }
+
+ private interface RuleChange {
+ void apply(MetricRule rule);
+ }
+}
diff --git a/src/test/java/io/prometheus/cloudwatch/ServletTest.java b/src/test/java/io/prometheus/cloudwatch/ServletTest.java
new file mode 100644
index 00000000..aee7ffae
--- /dev/null
+++ b/src/test/java/io/prometheus/cloudwatch/ServletTest.java
@@ -0,0 +1,135 @@
+package io.prometheus.cloudwatch;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import org.junit.jupiter.api.Test;
+
+public class ServletTest {
+ private final HttpServletRequest request = mock(HttpServletRequest.class);
+
+ @Test
+ public void healthServletReturnsPlainTextOk() throws Exception {
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ StringWriter responseBody = responseBody(response);
+
+ new HealthServlet().doGet(request, response);
+
+ verify(response).setContentType("text/plain");
+ assertThat(responseBody.toString()).isEqualTo("ok");
+ }
+
+ @Test
+ public void homePageServletReturnsHtmlLinks() throws Exception {
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ StringWriter responseBody = responseBody(response);
+
+ new HomePageServlet().doGet(request, response);
+
+ verify(response).setContentType("text/html");
+ assertThat(responseBody.toString())
+ .contains("CloudWatch Exporter
")
+ .contains("Metrics");
+ }
+
+ @Test
+ public void dynamicReloadServletRejectsGetRequests() throws Exception {
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ StringWriter responseBody = responseBody(response);
+
+ new DynamicReloadServlet(mock(CloudWatchCollector.class)).doGet(request, response);
+
+ verify(response).setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ verify(response).setContentType(DynamicReloadServlet.CONTENT_TYPE);
+ assertThat(responseBody.toString()).isEqualTo("Only POST requests allowed");
+ }
+
+ @Test
+ public void dynamicReloadServletReloadsConfigForPostRequests() throws Exception {
+ CloudWatchCollector collector = mock(CloudWatchCollector.class);
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ StringWriter responseBody = responseBody(response);
+
+ new DynamicReloadServlet(collector).doPost(request, response);
+
+ verify(collector).reloadConfig();
+ verify(response, never()).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ verify(response).setContentType(DynamicReloadServlet.CONTENT_TYPE);
+ assertThat(responseBody.toString()).isEqualTo("OK");
+ }
+
+ @Test
+ public void dynamicReloadServletReturnsErrorWhenReloadFails() throws Exception {
+ CloudWatchCollector collector = mock(CloudWatchCollector.class);
+ doThrow(new IOException("boom")).when(collector).reloadConfig();
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ StringWriter responseBody = responseBody(response);
+
+ new DynamicReloadServlet(collector).doPost(request, response);
+
+ verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ verify(response).setContentType(DynamicReloadServlet.CONTENT_TYPE);
+ assertThat(responseBody.toString()).isEqualTo("Reloading config failed");
+ }
+
+ @Test
+ public void homePageServletIgnoresWriterFailures() throws Exception {
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ when(response.getWriter()).thenThrow(new IOException("boom"));
+
+ new HomePageServlet().doGet(request, response);
+
+ verify(response).setContentType("text/html");
+ }
+
+ @Test
+ public void dynamicReloadServletIgnoresGetWriterFailures() throws Exception {
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ when(response.getWriter()).thenThrow(new IOException("boom"));
+
+ new DynamicReloadServlet(mock(CloudWatchCollector.class)).doGet(request, response);
+
+ verify(response).setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+ verify(response).setContentType(DynamicReloadServlet.CONTENT_TYPE);
+ }
+
+ @Test
+ public void dynamicReloadServletIgnoresSuccessWriterFailures() throws Exception {
+ CloudWatchCollector collector = mock(CloudWatchCollector.class);
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ when(response.getWriter()).thenThrow(new IOException("boom"));
+
+ new DynamicReloadServlet(collector).doPost(request, response);
+
+ verify(collector).reloadConfig();
+ verify(response).setContentType(DynamicReloadServlet.CONTENT_TYPE);
+ }
+
+ @Test
+ public void dynamicReloadServletIgnoresErrorWriterFailures() throws Exception {
+ CloudWatchCollector collector = mock(CloudWatchCollector.class);
+ doThrow(new IOException("reload boom")).when(collector).reloadConfig();
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ when(response.getWriter()).thenThrow(new IOException("writer boom"));
+
+ new DynamicReloadServlet(collector).doPost(request, response);
+
+ verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ verify(response).setContentType(DynamicReloadServlet.CONTENT_TYPE);
+ }
+
+ private StringWriter responseBody(HttpServletResponse response) throws IOException {
+ StringWriter responseBody = new StringWriter();
+ when(response.getWriter()).thenReturn(new PrintWriter(responseBody));
+ return responseBody;
+ }
+}
diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MemberAccessor b/src/test/resources/mockito-extensions/org.mockito.plugins.MemberAccessor
new file mode 100644
index 00000000..71111e33
--- /dev/null
+++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MemberAccessor
@@ -0,0 +1 @@
+member-accessor-reflection
diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 00000000..fdbd0b15
--- /dev/null
+++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-subclass