diff --git a/moto/cloudwatch/models.py b/moto/cloudwatch/models.py index 94668f32f..4546eccce 100644 --- a/moto/cloudwatch/models.py +++ b/moto/cloudwatch/models.py @@ -388,39 +388,62 @@ class CloudWatchBackend(BaseBackend): ) ) - def get_metric_data(self, queries, start_time, end_time): + def get_metric_data( + self, queries, start_time, end_time, scan_by="TimestampAscending" + ): + period_data = [ md for md in self.metric_data if start_time <= md.timestamp <= end_time ] + results = [] for query in queries: + period_start_time = start_time query_ns = query["metric_stat._metric._namespace"] query_name = query["metric_stat._metric._metric_name"] - query_data = [ - md - for md in period_data - if md.namespace == query_ns and md.name == query_name - ] - metric_values = [m.value for m in query_data] + delta = timedelta(seconds=int(query["metric_stat._period"])) result_vals = [] + timestamps = [] stat = query["metric_stat._stat"] - if len(metric_values) > 0: - if stat == "Average": - result_vals.append(sum(metric_values) / len(metric_values)) - elif stat == "Minimum": - result_vals.append(min(metric_values)) - elif stat == "Maximum": - result_vals.append(max(metric_values)) - elif stat == "Sum": - result_vals.append(sum(metric_values)) + while period_start_time <= end_time: + period_end_time = period_start_time + delta + period_md = [ + period_md + for period_md in period_data + if period_start_time <= period_md.timestamp < period_end_time + ] + query_period_data = [ + md + for md in period_md + if md.namespace == query_ns and md.name == query_name + ] + + metric_values = [m.value for m in query_period_data] + + if len(metric_values) > 0: + if stat == "Average": + result_vals.append(sum(metric_values) / len(metric_values)) + elif stat == "Minimum": + result_vals.append(min(metric_values)) + elif stat == "Maximum": + result_vals.append(max(metric_values)) + elif stat == "Sum": + result_vals.append(sum(metric_values)) + timestamps.append( + iso_8601_datetime_without_milliseconds(period_start_time) + ) + period_start_time += delta + if scan_by == "TimestampDescending" and len(timestamps) > 0: + timestamps.reverse() + result_vals.reverse() label = query["metric_stat._metric._metric_name"] + " " + stat results.append( { "id": query["id"], "label": label, "vals": result_vals, - "timestamps": [datetime.now() for _ in result_vals], + "timestamps": timestamps, } ) return results diff --git a/moto/cloudwatch/responses.py b/moto/cloudwatch/responses.py index 159e24425..f3430052a 100644 --- a/moto/cloudwatch/responses.py +++ b/moto/cloudwatch/responses.py @@ -128,9 +128,11 @@ class CloudWatchResponse(BaseResponse): def get_metric_data(self): start = dtparse(self._get_param("StartTime")) end = dtparse(self._get_param("EndTime")) + scan_by = self._get_param("ScanBy") + queries = self._get_list_prefix("MetricDataQueries.member") results = self.cloudwatch_backend.get_metric_data( - start_time=start, end_time=end, queries=queries + start_time=start, end_time=end, queries=queries, scan_by=scan_by ) template = self.response_template(GET_METRIC_DATA_TEMPLATE) diff --git a/tests/test_cloudwatch/test_cloudwatch_boto3.py b/tests/test_cloudwatch/test_cloudwatch_boto3.py index 55f0878d4..c15805964 100644 --- a/tests/test_cloudwatch/test_cloudwatch_boto3.py +++ b/tests/test_cloudwatch/test_cloudwatch_boto3.py @@ -678,6 +678,7 @@ def test_get_metric_data_partially_within_timeframe(): } ], ) + cloudwatch.put_metric_data( Namespace=namespace1, MetricData=[ @@ -686,31 +687,89 @@ def test_get_metric_data_partially_within_timeframe(): "Value": 50, "Unit": "Seconds", "Timestamp": last_week, - } - ], - ) - # get_metric_data - response = cloudwatch.get_metric_data( - MetricDataQueries=[ + }, { - "Id": "result", - "MetricStat": { - "Metric": {"Namespace": namespace1, "MetricName": "metric1"}, - "Period": 60, - "Stat": "Sum", - }, - } + "MetricName": "metric1", + "Value": 10, + "Unit": "Seconds", + "Timestamp": last_week + timedelta(seconds=10), + }, + { + "MetricName": "metric1", + "Value": 20, + "Unit": "Seconds", + "Timestamp": last_week + timedelta(seconds=15), + }, + { + "MetricName": "metric1", + "Value": 40, + "Unit": "Seconds", + "Timestamp": last_week + timedelta(seconds=30), + }, ], - StartTime=yesterday - timedelta(seconds=60), - EndTime=utc_now + timedelta(seconds=60), ) - # + + # data for average, min, max + + def get_data(start, end, stat="Sum", scanBy="TimestampAscending"): + # get_metric_data + response = cloudwatch.get_metric_data( + MetricDataQueries=[ + { + "Id": "result", + "MetricStat": { + "Metric": {"Namespace": namespace1, "MetricName": "metric1"}, + "Period": 60, + "Stat": stat, + }, + } + ], + StartTime=start, + EndTime=end, + ScanBy=scanBy, + ) + return response + + response = get_data( + start=yesterday - timedelta(seconds=60), end=utc_now + timedelta(seconds=60), + ) + # Assert Last week's data is not returned len(response["MetricDataResults"]).should.equal(1) sum_ = response["MetricDataResults"][0] sum_["Label"].should.equal("metric1 Sum") sum_["StatusCode"].should.equal("Complete") - sum_["Values"].should.equal([30.0]) + sum_["Values"].should.equal([20.0, 10.0]) + response = get_data( + start=yesterday - timedelta(seconds=60), + end=utc_now + timedelta(seconds=60), + scanBy="TimestampDescending", + ) + response["MetricDataResults"][0]["Values"].should.equal([10.0, 20.0]) + + response = get_data( + start=last_week - timedelta(seconds=1), + end=utc_now + timedelta(seconds=60), + stat="Average", + ) + # assert average + response["MetricDataResults"][0]["Values"].should.equal([30.0, 20.0, 10.0]) + + response = get_data( + start=last_week - timedelta(seconds=1), + end=utc_now + timedelta(seconds=60), + stat="Maximum", + ) + # assert maximum + response["MetricDataResults"][0]["Values"].should.equal([50.0, 20.0, 10.0]) + + response = get_data( + start=last_week - timedelta(seconds=1), + end=utc_now + timedelta(seconds=60), + stat="Minimum", + ) + # assert minimum + response["MetricDataResults"][0]["Values"].should.equal([10.0, 20.0, 10.0]) @mock_cloudwatch