diff --git a/README.md b/README.md index 336236f8..e2817a0f 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ set_timestamp | Optional. Boolean for whether to set the Prometheus metric times use_get_metric_data | Optional. Boolean (experimental) Use GetMetricData API to get metrics instead of GetMetricStatistics. Can be set globally and per metric. list_metrics_cache_ttl | Optional. Number of seconds to cache the result of calling the ListMetrics API. Defaults to 0 (no cache). Can be set globally and per metric. warn_on_empty_list_dimensions | Optional. Boolean Emit warning if the exporter cannot determine what metrics to request +global_cache_ttl | Optional. Number of seconds to cache the result from /metrics. Any value greater than 0 means that the last result will be returned. Defaults to 0 (no cache). Can be set globally. The above config will export time series such as diff --git a/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java b/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java index cb34407f..06896b9d 100644 --- a/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java +++ b/src/main/java/io/prometheus/cloudwatch/CloudWatchCollector.java @@ -10,15 +10,9 @@ import java.io.IOException; import java.io.Reader; import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; +import java.time.Instant; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.yaml.snakeyaml.LoaderOptions; @@ -32,11 +26,7 @@ import software.amazon.awssdk.services.cloudwatch.model.Statistic; import software.amazon.awssdk.services.resourcegroupstaggingapi.ResourceGroupsTaggingApiClient; import software.amazon.awssdk.services.resourcegroupstaggingapi.ResourceGroupsTaggingApiClientBuilder; -import software.amazon.awssdk.services.resourcegroupstaggingapi.model.GetResourcesRequest; -import software.amazon.awssdk.services.resourcegroupstaggingapi.model.GetResourcesResponse; -import software.amazon.awssdk.services.resourcegroupstaggingapi.model.ResourceTagMapping; -import software.amazon.awssdk.services.resourcegroupstaggingapi.model.Tag; -import software.amazon.awssdk.services.resourcegroupstaggingapi.model.TagFilter; +import software.amazon.awssdk.services.resourcegroupstaggingapi.model.*; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider; import software.amazon.awssdk.services.sts.model.AssumeRoleRequest; @@ -50,11 +40,14 @@ static class ActiveConfig { ResourceGroupsTaggingApiClient taggingClient; DimensionSource dimensionSource; + Map globalConfig; + public ActiveConfig(ActiveConfig cfg) { this.rules = new ArrayList<>(cfg.rules); this.cloudWatchClient = cfg.cloudWatchClient; this.taggingClient = cfg.taggingClient; this.dimensionSource = cfg.dimensionSource; + this.globalConfig = cfg.globalConfig; } public ActiveConfig() {} @@ -96,29 +89,32 @@ static class AWSTagSelect { "ReadThrottleEvents", "WriteThrottleEvents"); public CloudWatchCollector(Reader in) { - loadConfig(in, null, null); + loadConfig(in, null, null, null); } public CloudWatchCollector(String yamlConfig) { - this(yamlConfig, null, null); + this(yamlConfig, null, null, null); } /* For unittests. */ protected CloudWatchCollector( String jsonConfig, CloudWatchClient cloudWatchClient, - ResourceGroupsTaggingApiClient taggingClient) { + ResourceGroupsTaggingApiClient taggingClient, + Map globalConfig) { this( (Map) new Yaml(new SafeConstructor(new LoaderOptions())).load(jsonConfig), cloudWatchClient, - taggingClient); + taggingClient, + globalConfig); } private CloudWatchCollector( Map config, CloudWatchClient cloudWatchClient, - ResourceGroupsTaggingApiClient taggingClient) { - loadConfig(config, cloudWatchClient, taggingClient); + ResourceGroupsTaggingApiClient taggingClient, + Map globalConfig) { + loadConfig(config, cloudWatchClient, taggingClient, globalConfig); } @Override @@ -129,26 +125,39 @@ public List describe() { protected void reloadConfig() throws IOException { LOGGER.log(Level.INFO, "Reloading configuration"); try (FileReader reader = new FileReader(WebServer.configFilePath); ) { - loadConfig(reader, activeConfig.cloudWatchClient, activeConfig.taggingClient); + loadConfig( + reader, + activeConfig.cloudWatchClient, + activeConfig.taggingClient, + activeConfig.globalConfig); } } protected void loadConfig( - Reader in, CloudWatchClient cloudWatchClient, ResourceGroupsTaggingApiClient taggingClient) { + Reader in, + CloudWatchClient cloudWatchClient, + ResourceGroupsTaggingApiClient taggingClient, + Map globalConfig) { loadConfig( (Map) new Yaml(new SafeConstructor(new LoaderOptions())).load(in), cloudWatchClient, - taggingClient); + taggingClient, + globalConfig); } private void loadConfig( Map config, CloudWatchClient cloudWatchClient, - ResourceGroupsTaggingApiClient taggingClient) { + ResourceGroupsTaggingApiClient taggingClient, + Map globalConfig) { if (config == null) { // Yaml config empty, set config to empty map. config = new HashMap<>(); } + if (globalConfig == null) { // Yaml config empty, set config to empty map. + globalConfig = new HashMap<>(); + } + int defaultPeriod = 60; if (config.containsKey("period_seconds")) { defaultPeriod = ((Number) config.get("period_seconds")).intValue(); @@ -178,6 +187,12 @@ private void loadConfig( Duration.ofSeconds(((Number) config.get("list_metrics_cache_ttl")).intValue()); } + int defaultGlobalCacheSeconds = 0; + if (config.containsKey("global_cache_ttl")) { + defaultGlobalCacheSeconds = ((Number) config.get("global_cache_ttl")).intValue(); + } + globalConfig.put("globalCacheSeconds", defaultGlobalCacheSeconds); + boolean defaultWarnOnMissingDimensions = false; if (config.containsKey("warn_on_empty_list_dimensions")) { defaultWarnOnMissingDimensions = (Boolean) config.get("warn_on_empty_list_dimensions"); @@ -331,19 +346,21 @@ private void loadConfig( dimensionSource = new CachingDimensionSource(dimensionSource, metricCacheConfig); } - loadConfig(rules, cloudWatchClient, taggingClient, dimensionSource); + loadConfig(rules, cloudWatchClient, taggingClient, dimensionSource, globalConfig); } private void loadConfig( ArrayList rules, CloudWatchClient cloudWatchClient, ResourceGroupsTaggingApiClient taggingClient, - DimensionSource dimensionSource) { + DimensionSource dimensionSource, + Map globalConfig) { synchronized (activeConfig) { activeConfig.cloudWatchClient = cloudWatchClient; activeConfig.taggingClient = taggingClient; activeConfig.rules = rules; activeConfig.dimensionSource = dimensionSource; + activeConfig.globalConfig = globalConfig; } } @@ -634,10 +651,46 @@ private void scrape(List mfs) { infoSamples)); } + private void updateCacheMetric(List mfs, double value) { + List samples = new ArrayList<>(); + MetricFamilySamples cacheMetric = null; + for (MetricFamilySamples metric : mfs) { + if (metric.name.equals("cloudwatch_exporter_cached_answer")) { + cacheMetric = metric; + break; + } + } + + if (cacheMetric == null) { + cacheMetric = + new MetricFamilySamples( + "cloudwatch_exporter_cached_answer", + Type.GAUGE, + "Non-zero means this scrape was from cache", + samples); + mfs.add(cacheMetric); + } else { + cacheMetric.samples.clear(); + } + + cacheMetric.samples.add( + new MetricFamilySamples.Sample( + "cloudwatch_exporter_cached_answer", new ArrayList<>(), new ArrayList<>(), value)); + } + + List cachedMfs = new ArrayList<>(); + public List collect() { long start = System.nanoTime(); double error = 0; List mfs = new ArrayList<>(); + + if (shouldCache() && shouldReturnFromCache()) { + LOGGER.log(Level.INFO, "Returning from cache"); + this.updateCacheMetric(this.cachedMfs, 1.0); + return this.cachedMfs; + } + this.updateCacheMetric(mfs, 0.0); try { scrape(mfs); } catch (Exception e) { @@ -668,9 +721,28 @@ public List collect() { Type.GAUGE, "Non-zero if this scrape failed.", samples)); + if (shouldCache()) { + this.cachedMfs = mfs; + } + this.lastCall = Instant.now(); return mfs; } + public Instant lastCall; + + private boolean shouldCache() { + return (int) this.activeConfig.globalConfig.get("globalCacheSeconds") > 0; + } + + private boolean shouldReturnFromCache() { + if (this.lastCall == null) { + return false; + } + Duration elapsedTime = Duration.between(lastCall, Instant.now()); + return elapsedTime.toSeconds() + <= (int) this.activeConfig.globalConfig.get("globalCacheSeconds"); + } + private String extractResourceIdFromArn(String arn) { // ARN parsing is based on // https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html diff --git a/src/test/java/io/prometheus/cloudwatch/CloudWatchCollectorTest.java b/src/test/java/io/prometheus/cloudwatch/CloudWatchCollectorTest.java index 1ae8f286..269e76ac 100644 --- a/src/test/java/io/prometheus/cloudwatch/CloudWatchCollectorTest.java +++ b/src/test/java/io/prometheus/cloudwatch/CloudWatchCollectorTest.java @@ -13,14 +13,8 @@ import io.prometheus.client.CollectorRegistry; import io.prometheus.cloudwatch.RequestsMatchers.*; import java.time.Instant; -import java.util.Arrays; -import java.util.Date; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Properties; -import java.util.Set; +import java.time.temporal.ChronoUnit; +import java.util.*; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @@ -49,7 +43,8 @@ public void testMetricPeriod() { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n period_seconds: 100\n range_seconds: 200\n delay_seconds: 300", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when(cloudWatchClient.getMetricStatistics((GetMetricStatisticsRequest) any())) @@ -72,7 +67,8 @@ public void testMetricPeriodUsingGetMetricData() { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n period_seconds: 100\n range_seconds: 200\n delay_seconds: 300\n use_get_metric_data: true\n", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when(cloudWatchClient.getMetricStatistics((GetMetricStatisticsRequest) any())) @@ -107,7 +103,8 @@ public void testDefaultPeriod() { new CloudWatchCollector( "---\nregion: reg\nperiod_seconds: 100\nrange_seconds: 200\ndelay_seconds: 300\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when(cloudWatchClient.getMetricStatistics((GetMetricStatisticsRequest) any())) @@ -131,7 +128,8 @@ public void testAllStatistics() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -195,7 +193,8 @@ public void testAllStatisticsUsingGetMetricData() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n use_get_metric_data: true\n", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); List timestamps = List.of(new Date().toInstant()); MetricMatcher metricMatcher = @@ -304,7 +303,8 @@ public void testCloudwatchTimestamps() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n set_timestamp: true\n- aws_namespace: AWS/ELB\n aws_metric_name: HTTPCode_Backend_2XX\n set_timestamp: false", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Date timestamp = new Date(); @@ -359,7 +359,8 @@ public void testUsesNewestDatapoint() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -390,7 +391,8 @@ public void testDimensions() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n aws_dimensions:\n - AvailabilityZone\n - LoadBalancerName", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -486,7 +488,8 @@ public void testDimensionSelect() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n aws_dimensions:\n - AvailabilityZone\n - LoadBalancerName\n aws_dimension_select:\n LoadBalancerName:\n - myLB", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( cloudWatchClient.listMetrics( @@ -572,7 +575,8 @@ public void testAllSelectDimensionsKnown() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n aws_dimensions:\n - AvailabilityZone\n - LoadBalancerName\n aws_dimension_select:\n LoadBalancerName:\n - myLB\n AvailabilityZone:\n - a\n - b", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( cloudWatchClient.getMetricStatistics( @@ -630,7 +634,8 @@ public void testAllSelectDimensionsKnownUsingGetMetricData() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n aws_dimensions:\n - AvailabilityZone\n - LoadBalancerName\n aws_dimension_select:\n LoadBalancerName:\n - myLB\n AvailabilityZone:\n - a\n - b\n use_get_metric_data: true\n", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); List timestamps = List.of(new Date().toInstant()); MetricMatcher firstMetric = @@ -704,7 +709,8 @@ public void testDimensionSelectRegex() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n aws_dimensions:\n - AvailabilityZone\n - LoadBalancerName\n aws_dimension_select_regex:\n LoadBalancerName:\n - myLB(.*)", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -791,7 +797,8 @@ public void testGetDimensionsUsesNextToken() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n aws_dimensions:\n - AvailabilityZone\n - LoadBalancerName\n aws_dimension_select:\n LoadBalancerName:\n - myLB", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -851,7 +858,8 @@ public void testExtendedStatistics() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: Latency\n aws_extended_statistics:\n - p95\n - p99.99", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); HashMap extendedStatistics = new HashMap(); @@ -892,7 +900,8 @@ public void testDynamoIndexDimensions() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/DynamoDB\n aws_metric_name: ConsumedReadCapacityUnits\n aws_dimensions:\n - TableName\n - GlobalSecondaryIndexName\n- aws_namespace: AWS/DynamoDB\n aws_metric_name: OnlineIndexConsumedWriteCapacity\n aws_dimensions:\n - TableName\n - GlobalSecondaryIndexName\n- aws_namespace: AWS/DynamoDB\n aws_metric_name: ConsumedReadCapacityUnits\n aws_dimensions:\n - TableName", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( cloudWatchClient.listMetrics( @@ -1017,7 +1026,8 @@ public void testDynamoNoDimensions() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/DynamoDB\n aws_metric_name: AccountProvisionedReadCapacityUtilization\n", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -1047,7 +1057,8 @@ public void testTagSelectEC2() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/EC2\n aws_metric_name: CPUUtilization\n aws_dimensions:\n - InstanceId\n aws_tag_select:\n resource_type_selection: \"ec2:instance\"\n resource_id_dimension: InstanceId\n tag_selections:\n Monitoring: [enabled]\n", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -1142,7 +1153,8 @@ public void testTagSelectALB() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ApplicationELB\n aws_metric_name: RequestCount\n aws_dimensions:\n - AvailabilityZone\n - LoadBalancer\n aws_tag_select:\n resource_type_selection: \"elasticloadbalancing:loadbalancer/app\"\n resource_id_dimension: LoadBalancer\n tag_selections:\n Monitoring: [enabled]\n", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -1278,7 +1290,8 @@ public void testTagSelectUsesPaginationToken() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/EC2\n aws_metric_name: CPUUtilization\n aws_dimensions:\n - InstanceId\n aws_tag_select:\n resource_type_selection: \"ec2:instance\"\n resource_id_dimension: InstanceId\n tag_selections:\n Monitoring: [enabled]\n", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -1402,7 +1415,8 @@ public void testNoSelection() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/EC2\n aws_metric_name: CPUUtilization\n aws_dimensions:\n - InstanceId\n", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -1474,7 +1488,8 @@ public void testMultipleSelection() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/EC2\n aws_metric_name: CPUUtilization\n aws_dimensions:\n - InstanceId\n aws_tag_select:\n resource_type_selection: \"ec2:instance\"\n resource_id_dimension: InstanceId\n tag_selections:\n Monitoring: [enabled]\n aws_dimension_select:\n InstanceId: [\"i-1\"]", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -1583,7 +1598,8 @@ public void testOptionalTagSelection() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/EC2\n aws_metric_name: CPUUtilization\n aws_dimensions:\n - InstanceId\n aws_tag_select:\n resource_type_selection: \"ec2:instance\"\n resource_id_dimension: InstanceId\n aws_dimension_select:\n InstanceId: [\"i-1\", \"i-no-tag\"]", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -1723,7 +1739,8 @@ public void testNotRecentlyActive() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n aws_dimensions:\n - AvailabilityZone\n - LoadBalancerName\n range_seconds: 12000", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -1829,7 +1846,8 @@ public void testDimensionsWithDefaultCache() throws Exception { new CloudWatchCollector( "---\nregion: reg\nlist_metrics_cache_ttl: 500\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n aws_dimensions:\n - AvailabilityZone\n - LoadBalancerName", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -1890,7 +1908,8 @@ public void testDimensionsWithMetricLevelCache() throws Exception { new CloudWatchCollector( "---\nregion: reg\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount\n list_metrics_cache_ttl: 500\n aws_dimensions:\n - AvailabilityZone\n - LoadBalancerName", cloudWatchClient, - taggingClient) + taggingClient, + new HashMap<>()) .register(registry); Mockito.when( @@ -1945,4 +1964,89 @@ public void testDimensionsWithMetricLevelCache() throws Exception { Mockito.verify(cloudWatchClient, times(2)) .getMetricStatistics(any(GetMetricStatisticsRequest.class)); } + + @Test + public void testGlobalCacheCanCache() { + CloudWatchCollector cwc = + new CloudWatchCollector( + "---\nregion: reg\nglobal_cache_ttl: 10\nmetrics:\n- aws_namespace: AWS/ELB\n aws_metric_name: RequestCount", + cloudWatchClient, + taggingClient, + new HashMap<>()) + .register(registry); + + Mockito.when( + cloudWatchClient.getMetricStatistics( + (GetMetricStatisticsRequest) + argThat( + new GetMetricStatisticsRequestMatcher() + .Namespace("AWS/ELB").MetricName("RequestCount")))) + .thenReturn( + GetMetricStatisticsResponse.builder() // First + .datapoints( + Datapoint.builder() + .timestamp(new Date().toInstant()) + .average(1.0) + .maximum(2.0) + .build()) + .build(), + GetMetricStatisticsResponse.builder() // Second + .datapoints( + Datapoint.builder() + .timestamp(new Date().toInstant()) + .average(2.0) + .maximum(4.0) + .build()) + .build(), + GetMetricStatisticsResponse.builder() // Third + .datapoints( + Datapoint.builder() + .timestamp(new Date().toInstant()) + .average(4.0) + .maximum(8.0) + .build()) + .build()); + + for (Collector.MetricFamilySamples it : Collections.list(registry.metricFamilySamples())) { + if (it.name.equals("cloudwatch_exporter_cached_answer")) + assertEquals(0.0, it.samples.get(0).value, .01); + if (it.name.equals("aws_elb_request_count_average")) + assertEquals(1.0, it.samples.get(0).value, .01); + if (it.name.equals("aws_elb_request_count_maximum")) + assertEquals(2.0, it.samples.get(0).value, .01); + } + + cwc.lastCall = Instant.now().minus(1, ChronoUnit.SECONDS); + + for (Collector.MetricFamilySamples it : Collections.list(registry.metricFamilySamples())) { + if (it.name.equals("cloudwatch_exporter_cached_answer")) + assertEquals(1.0, it.samples.get(0).value, .01); + if (it.name.equals("aws_elb_request_count_average")) + assertEquals(1.0, it.samples.get(0).value, .01); + if (it.name.equals("aws_elb_request_count_maximum")) + assertEquals(2.0, it.samples.get(0).value, .01); + } + + cwc.lastCall = Instant.now().minus(11, ChronoUnit.SECONDS); + + for (Collector.MetricFamilySamples it : Collections.list(registry.metricFamilySamples())) { + if (it.name.equals("cloudwatch_exporter_cached_answer")) + assertEquals(0.0, it.samples.get(0).value, .01); + if (it.name.equals("aws_elb_request_count_average")) + assertEquals(2.0, it.samples.get(0).value, .01); + if (it.name.equals("aws_elb_request_count_maximum")) + assertEquals(4.0, it.samples.get(0).value, .01); + } + + cwc.lastCall = Instant.now().minus(11, ChronoUnit.SECONDS); + + for (Collector.MetricFamilySamples it : Collections.list(registry.metricFamilySamples())) { + if (it.name.equals("cloudwatch_exporter_cached_answer")) + assertEquals(0.0, it.samples.get(0).value, .01); + if (it.name.equals("aws_elb_request_count_average")) + assertEquals(4.0, it.samples.get(0).value, .01); + if (it.name.equals("aws_elb_request_count_maximum")) + assertEquals(8.0, it.samples.get(0).value, .01); + } + } }